资讯详情

小熊派GD32开发(7)— 使用软件模拟I2C读取SHT30温湿度传感器

小熊派GD32开发(7)— 使用软件模拟I2C读取SHT30温湿度传感器

一、编写软件模拟I2C驱动程序

在【Devices】下新建soft_i2c.csoft_i2c.h首先,查看硬件原理图,可以看到文件,IIC_SCL使用PB6引脚,IIC_SDA使用PB7引脚可以初始化这两个引脚:

/* 软件模拟IIC引脚初始化 * IIC_SCL --> PB6 * IIC_SDA --> PB7 */ void IIC_Init(void) { 
             rcu_periph_clock_enable(RCU_GPIOB); /* 使能GPIOB时钟 */   /* 配置IIC_SCL引脚是推挽输出 */      gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);  /* 配置IIC_SDA引脚是推挽输出 */      gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);      gpio_bit_set(GPIOB, GPIO_PIN_6);  gpio_bit_set(GPIOB, GPIO_PIN_7); }  

因为模拟IIC需要在通信中使用us所以,写一个延迟,us延时函数

/* 描述:us级延时函数 * 参数nus:需要延时的us数 * 返回值:无*/                  void delay_us(uint32_t nus) { 
            uint32_t ticks;     uint32_t told, tnow, tcnt = 0;     uint32_t reload = SysTick->LOAD;  /* 滴答定时器的重装载值 */     ticks = nus * 120;       /* 需要的节拍数 */     told = SysTick->VAL;           /* 刚进入时的计数器值 */      while(1)     { 
                 tnow = SysTick->VAL;          if(tnow != told)         { 
                     if(tnow < told)tcnt  = told - tnow;             else tcnt  = reload - tnow 
       
        + told
        ; 
        if
        (tcnt 
        >= ticks
        )
        break
        ; 
        /* 时间超过/等于要延迟的时间,则退出. */ told 
        = tnow
        ; 
        } 
        } 
        } 
       

模拟IIC通信的函数如下

/* 描述:启动I2C总线,即发送I2C起始条件. * 参数: 无 * 返回值:无 */
void IIC_Start(void)
{ 
        
	SDA_OUT();
	IIC_SDA(1);
	IIC_SCL(1);
	delay_us(4);	
	IIC_SDA(0);
	delay_us(4); 
	IIC_SCL(0);
}
 
/* 描述:结束I2C总线,即发送I2C结束条件. * 参数: 无 * 返回值:无 */
void IIC_Stop(void)
{ 
        
	SDA_OUT();
	IIC_SCL(0);
	IIC_SDA(0);  
	delay_us(4);	
	IIC_SCL(1);
	delay_us(4);
	IIC_SDA(1);
	delay_us(4);
}
 
/* 描述:发送应答 ACK * 参数: 无 * 返回值:无 */
void IIC_ACK(void)
{ 
        
	SDA_OUT();
	IIC_SCL(0);
	delay_us(2); 
	IIC_SDA(0);
	delay_us(2);     
	IIC_SCL(1);
	delay_us(2);                  
	IIC_SCL(0);                     
	delay_us(1);    
}

/* 描述:发送非应答 NACK * 参数: 无 * 返回值:无 */
void IIC_NACK(void)
{ 
        
	SDA_OUT();
	IIC_SCL(0);
	delay_us(2); 
	IIC_SDA(1);
	delay_us(2);      
	IIC_SCL(1);
	delay_us(2);                   
	IIC_SCL(0);                     
	delay_us(1);    
}

/* 描述:等待ACK * 参数: 无 * 返回值:等待应答返回0,没有等待到应答返回1 */
uint8_t IIC_wait_ACK(void)
{ 
        
    uint8_t t = 200;
    SDA_OUT();
    IIC_SDA(1);		
    delay_us(1);
    IIC_SCL(0);
    delay_us(1); 
    SDA_IN();		/* 数据发送完后释放数据线,准备接收应答位 */
    delay_us(1); 
    while(READ_SDA)	/* 等待IIC应答*/
    { 
        
		t--;
		delay_us(1); 
		if(t==0)
		{ 
        
			IIC_SCL(0);
			return 1;
		}
		delay_us(1); 
    }
    delay_us(1);      
    IIC_SCL(1);
    delay_us(1);
    IIC_SCL(0);             
    delay_us(1);    
    return 0;	
}

/* 描述:一个字节数据发送函数 * 参数: 无 * 返回值:无 */
void IIC_SendByte(uint8_t byte)
{ 
        
	uint8_t BitCnt;
	SDA_OUT();
	IIC_SCL(0);
	for(BitCnt=0;BitCnt<8;BitCnt++) /* 要传送的数据长度为8位 */
	{ 
        
		if(byte&0x80) IIC_SDA(1);	/* 判断发送位 */
		else IIC_SDA(0); 
		byte<<=1;
		delay_us(2); 
		IIC_SCL(1);
		delay_us(2);
		IIC_SCL(0);
		delay_us(2);
	}
}

/* 描述:一个字节数据接收函数 * 参数: 无 * 返回值:接收到的字节数据 */   
uint8_t IIC_RcvByte(void)
{ 
        
	uint8_t retc;
	uint8_t BitCnt;
	retc=0; 
	SDA_IN();			/* 设置数据线为输入方式 */
	delay_us(1);                    
	for(BitCnt=0;BitCnt<8;BitCnt++)
	{ 
          
		IIC_SCL(0);		/* 设置时钟线为低,准备接收数据位 */
		delay_us(2);               
		IIC_SCL(1);		/* 设置时钟线为高使数据线上数据有效 */              
		retc=retc<<1;
		if(READ_SDA) retc |=1;	/* 读数据位,接收的数据位放入retc中 */
		delay_us(1);
	}
	IIC_SCL(0);    
	return(retc);
}

soft_i2c.h文件如下,

#ifndef __SOFT_I2C_H_
#define __SOFT_I2C_H_

#include "gd32f30x.h"

/* 设置IIC_SDA的方向,输入还是输出 */
#define SDA_IN() {GPIO_CTL0(GPIOB)&=~GPIO_MODE_MASK(7);GPIO_CTL0(GPIOB)|=GPIO_MODE_SET(7,0x8);}//PB9输入模式
#define SDA_OUT() {GPIO_CTL0(GPIOB)&=~GPIO_MODE_MASK(7);GPIO_CTL0(GPIOB)|=GPIO_MODE_SET(7,0x3);}//PB9输出模式

/* 设置SCL和SDA输出电平,以及读取SDA电平 */
#define IIC_SCL(n) (n?gpio_bit_set(GPIOB, GPIO_PIN_6):gpio_bit_reset(GPIOB, GPIO_PIN_6))
#define IIC_SDA(n) (n?gpio_bit_set(GPIOB, GPIO_PIN_7):gpio_bit_reset(GPIOB, GPIO_PIN_7))
#define READ_SDA gpio_input_bit_get(GPIOB,GPIO_PIN_7)

void IIC_Init(void); 			/* 软件模拟IIC引脚初始化 */
void IIC_Start(void);			/* 启动I2C总线,即发送I2C起始条件 */
void IIC_Stop(void);			/* 结束I2C总线,即发送I2C结束条件 */
void IIC_ACK(void);				/* 发送应答 ACK */
void IIC_NACK(void);			/* 发送非应答 NACK */
uint8_t IIC_wait_ACK(void);		/* 等待ACK */
void IIC_SendByte(uint8_t byte);/* 一个字节数据发送函数 */ 
uint8_t IIC_RcvByte(void); 		/* 一个字节数据接收函数 */

#endif

二、编写SHT30应用程序

在【Devices】下新建sht3x.csht3x.h文件,头文件如下

#ifndef __SHT3X_H_
#define __SHT3X_H_

#include "gd32f30x.h"
#include "systick.h"

uint8_t SHT3x_Init(void);	/* 描述:SHT3x初始化函数 */
uint8_t SHT3x_ReadSerialNumber(uint32_t* serialNumber);
uint8_t SHT3x_Get_Humiture_single(double *Tem_val,double *Hum_val);	/* 单次获取 */
uint8_t SHT3x_Get_Humiture_periodic(double *Tem_val,double *Hum_val); /* 周期获取 */

/* 枚举SHT3x命令列表 */
typedef enum
{ 
        
    /* 软件复位命令 */
    SOFT_RESET_CMD = 0x30A2,	
    /* 单次测量模式 命名格式:Repeatability_CS_CMD CS: Clock stretching */
    HIGH_ENABLED_CMD    = 0x2C06,
    MEDIUM_ENABLED_CMD  = 0x2C0D,
    LOW_ENABLED_CMD     = 0x2C10,
    HIGH_DISABLED_CMD   = 0x2400,
    MEDIUM_DISABLED_CMD = 0x240B,
    LOW_DISABLED_CMD    = 0x2416,

    /* 周期测量模式 命名格式:Repeatability_MPS_CMD MPS:measurement per second */
    HIGH_0_5_CMD   = 0x2032,
    MEDIUM_0_5_CMD = 0x2024,
    LOW_0_5_CMD    = 0x202F,
    HIGH_1_CMD     = 0x2130,
    MEDIUM_1_CMD   = 0x2126,
    LOW_1_CMD      = 0x212D,
    HIGH_2_CMD     = 0x2236,
    MEDIUM_2_CMD   = 0x2220,
    LOW_2_CMD      = 0x222B,
    HIGH_4_CMD     = 0x2334,
    MEDIUM_4_CMD   = 0x2322,
    LOW_4_CMD      = 0x2329,
    HIGH_10_CMD    = 0x2737,
    MEDIUM_10_CMD  = 0x2721,
    LOW_10_CMD     = 0x272A,
	/* 周期测量模式读取数据命令 */
	READOUT_FOR_PERIODIC_MODE = 0xE000,
	/* 读取传感器编号命令 */
	READ_SERIAL_NUMBER = 0x3780,
} SHT3X_CMD;

#endif

sht3x.c文件中,先将读写标志以及SHT30的地址设置好:

#define write 0
#define read 1

uint8_t addr = 0x44;	//设置传感器地址 

然后就是发送命令和接收数据函数

/* 描述:向SHT30发送一条16bit指令 * 参数cmd:SHT30指令(在SHT30_MODE中枚举定义) * 返回值:发送成功返回0,发送失败返回1 */
static uint8_t SHT3x_Send_Cmd(SHT3X_CMD cmd)
{ 
        
    uint8_t cmd_buffer[2];
	uint8_t ret;
    cmd_buffer[0] = cmd >> 8;
    cmd_buffer[1] = cmd;
	
	IIC_SendByte(addr<<1 | write);	/* 写7位I2C设备地址加0作为写取位 */
	ret = IIC_wait_ACK();
	IIC_SendByte(cmd_buffer[0]);
	ret |= IIC_wait_ACK();
	IIC_SendByte(cmd_buffer[1]);
	ret |= IIC_wait_ACK();
	
	return ret;
}

/* 描述:从SHT3x读取数据 * 参数data_len:读取多少个字节数据 * 参数data_arr:读取的数据存放在一个数组里 * 返回值:读取成功返回0,读取失败返回1 */
static uint8_t SHT3x_Recv_Data(uint8_t data_len, uint8_t* data_arr)
{ 
        
	uint8_t ret,i;
	IIC_SendByte(addr<<1 | read);	/* 写7位I2C设备地址加1为读取位 */
	ret = IIC_wait_ACK();
	if(ret != 0) return 1;
	for(i = 0; i < (data_len - 1); i++)
	{ 
        
		data_arr[i]=IIC_RcvByte();
		IIC_ACK();
	}
	data_arr[i]=IIC_RcvByte();
	IIC_NACK();

	return 0;
}

接下来我们测试一下读取传感器的编号

/* 描述:读取传感器编号 * 参数:存储编号数据的指针 * 返回值:0-读取成功,1-读取失败 */
uint8_t SHT3x_ReadSerialNumber(uint32_t* serialNumber)
{ 
         
	uint8_t ret = 0; 
	uint8_t Num_buf[4] = { 
        0xFF,0xFF,0xFF,0xFF};
	
	IIC_Start();
	SHT3x_Send_Cmd(READ_SERIAL_NUMBER);
	IIC_Stop();
	delay_1ms(10);	/* 有问题时需要适当延长!!!!!!*/
	IIC_Start();
	ret = SHT3x_Recv_Data(4,Num_buf);
	IIC_Stop();
	
	*serialNumber = ((Num_buf[0] << 24) | (Num_buf[1] << 16) |(Num_buf[2] << 8) |(Num_buf[3]));
	if(0xFF == *serialNumber) return 1; 
	return ret; 
}

在main函数中调用,编译下载运行,可以得到其编号: 在这里插入图片描述 然后我们将其设置为周期读取模式,周期读取数据

/* 描述:数据CRC校验 * 参数message:需要校验的数据 * 参数initial_value:crc初始值 * 返回值:计算得到的CRC码 */
#define CRC8_POLYNOMIAL 0x131
uint8_t CheckCrc8(uint8_t* const message, uint8_t initial_value)
{ 
        
    uint8_t  remainder;	    //余数
    uint8_t  i = 0, j = 0;  //循环变量

    /* 初始化 */
    remainder = initial_value;
    for(j = 0; j < 2;j++)
    { 
        
        remainder ^= message[j];
        /* 从最高位开始依次计算 */
        for (i = 0; i < 8; i++)
        { 
        
            if (remainder & 0x80)
                remainder = (remainder << 1)^CRC8_POLYNOMIAL;
            else
                remainder = (remainder << 1);
        }
    }
    /* 返回计算的CRC码 */
    return remainder;
}

/* 描述:温湿度数据获取函数,周期读取,注意,需要提前设置周期模式 * 参数Tem_val:存储温度数据的指针, 温度单位为°C * 参数Hum_val:存储湿度数据的指针, 温度单位为% * 返回值:0-读取成功,1-读取失败 ********************************************************************/
uint8_t SHT3x_Get_Humiture_periodic(double *Tem_val,double *Hum_val)
{ 
        
	uint8_t ret=0;
	uint8_t buff[6];
	uint16_t tem,hum;
	double Temperature=0;
	double Humidity=0;

	IIC_Start();
	ret = SHT3x_Send_Cmd(READOUT_FOR_PERIODIC_MODE);	
	IIC_Start();
	ret = SHT3x_Recv_Data(6,buff);
	IIC_Stop();
	
	/* 校验温度数据和湿度数据是否接收正确 */
	if(CheckCrc8(buff, 0xFF) != buff[2] || CheckCrc8(&buff[3], 0xFF) != buff[5])
	{ 
        	
		printf("CRC_ERROR,ret = 0x%x\r\n",ret);
		return 1;
	}
		
	/* 转换温度数据 */
	tem = (((uint16_t)buff[0]<<8) | buff[1]);//温度数据拼接
	Temperature= (175.0*(double)tem/65535.0-45.0) ;	// T = -45 + 175 * tem / (2^16-1)
	
	/* 转换湿度数据 */
	hum = (((uint16_t)buff[3]<<8) | buff[4]);//湿度数据拼接
	Humidity= (100.0*(double)hum/65535.0);			// RH = hum*100 / (2^16-1)
	
	/* 过滤错误数据 */
	if((Temperature>=-20)&&(Temperature<=125)&&(Humidity>=0)&&(Humidity<=100))
	{ 
        
		*Tem_val = Temperature;
		*Hum_val = Humidity;
		return 0;
	}
	else
		return 1;
}

在主函数中,每隔一秒读取一次数据

	if( 0 == SHT3x_Init())
		printf("SHT3x_Init OK \r\n");
	else
		printf("SHT3x_Init ERR \r\n");
	
	while(1)
	{ 
        
		delay_1ms(1000);
		if(SHT3x_Get_Humiture_periodic(&Tem_val,&Hum_val) == 0)
			printf("Tem_val:%f C,\t&Hum_val:%f %%Rh\r\n",Tem_val,Hum_val);
		else
			printf("Get_Humiture ERR\r\n");
	}

编译下载运行,可以得到,读取数据成功 另外,也可以单次读取

/* 描述:温湿度数据获取函数,单次获取 * 参数Tem_val:存储温度数据的指针, 温度单位为°C * 参数Hum_val:存储湿度数据的指针, 温度单位为% * 返回值:0-读取成功,1-读取失败 ********************************************************************/
uint8_t SHT3x_Get_Humiture_single(double *Tem_val,double *Hum_val)
{ 
        
	uint8_t ret=0;
	uint8_t buff[6];
	uint16_t tem,hum;
	double Temperature=0;
	double Humidity=0;

	IIC_Start();
	SHT3x_Send_Cmd(HIGH_ENABLED_CMD);
	IIC_Stop();
	
	delay_1ms(50);
	
	IIC_Start() 

标签: gd温湿度传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台