博客
关于我
小熊派GD32开发(7)— 使用软件模拟I2C读取SHT30温湿度传感器
阅读量:554 次
发布时间:2019-03-09

本文共 11862 字,大约阅读时间需要 39 分钟。

小熊派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  1uint8_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 0x131uint8_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();	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;}

三、上传到云端

结合之前的代码,将其温湿度数据上传到EMQ,5秒上报一次:

#include "gd32f30x.h"#include "systick.h"#include "usart.h"#include "led.h"#include "timer.h"#include "app_nbiot.h"#include "soft_i2c.h"#include "sht3x.h"extern uint8_t update_flag;extern int udp_socket;int main(void){   //	uint32_t sn;	char res_buf[128];	char data_buf[128];	int times = 0;	double Tem_val,Hum_val;	systick_config();	/* 配置系统时钟 */	LED_init();			/* 初始化 LED */		uart_init(115200);	/* 初始化USART0 */	uart1_init(9600);	/* 初始化USART1 */	timer5_init(50000,12000);	/* 定时5000ms*/		printf("Hello world! \r\n");		while(NB_Start())	{   		u1_printf("AT+NRB\r\n");	/* 重启*/		while(USART1_RX_NUM == 0) 			;		if(USART1_RX_NUM >0)		{   			printf("NBIoT: %s\r\n",rx1_date_buf);			USART1_RX_NUM = 0;		}	}	printf("start OK\r\n");	//	IIC_Init();//	if( 0 == SHT3x_ReadSerialNumber(&sn))//		printf("ReadSerialNumber OK sn = 0x%x \r\n",sn);//	else//		printf("ReadSerialNumber ERR \r\n");		if( 0 == SHT3x_Init())		printf("SHT3x_Init OK \r\n");	else		printf("SHT3x_Init ERR \r\n");		while(1)	{   			if(update_flag  == 1)		{   			update_flag = 0;			times++;			LED(times%2);			/* 采集温湿度数据然后上传到EMQ */			if(SHT3x_Get_Humiture_periodic(&Tem_val,&Hum_val) == 0)			{   				sprintf(data_buf,"{\"temperature\":%6.2lf ,\"humidity\":%6.2lf }",Tem_val,Hum_val);				NB_Send_data("39.96.35.207", "SHT3x", data_buf);				printf("%s\r\nData send to EMQ...\r\n",data_buf);			}			else				printf("Get_Humiture ERR\r\n");		}		if(USART1_RX_NUM >0)		{   			USART1_RX_NUM = 0;			if(Find_string((char *)rx1_date_buf,"+NSONMI:","\r\n",res_buf)>0)			{   				u1_printf("AT+NSORF=%d,%d\r\n",udp_socket,res_buf[2]-'0');			}			printf("NBIOT: %s\r\n",rx1_date_buf);					}		if(USART_RX_NUM >0)		{   			USART_RX_NUM = 0;			u1_printf("%s",rx0_date_buf);					}	}}

编译下载运行,可以看到,在EMQ上接收到数据

在这里插入图片描述

四、代码

完整代码我存放在码云,可以查看:

转载地址:http://qtqsz.baihongyu.com/

你可能感兴趣的文章
MuseTalk如何生成高质量视频(使用技巧)
查看>>
mutiplemap 总结
查看>>
MySQL DELETE 表别名问题
查看>>
MySQL Error Handling in Stored Procedures---转载
查看>>
MVC 区域功能
查看>>
MySQL FEDERATED 提示
查看>>
mysql generic安装_MySQL 5.6 Generic Binary安装与配置_MySQL
查看>>
Mysql group by
查看>>
MySQL I 有福啦,窗口函数大大提高了取数的效率!
查看>>
mysql id自动增长 初始值 Mysql重置auto_increment初始值
查看>>
MySQL in 太多过慢的 3 种解决方案
查看>>
MySQL InnoDB 三大文件日志,看完秒懂
查看>>
Mysql InnoDB 数据更新导致锁表
查看>>
Mysql Innodb 锁机制
查看>>
MySQL InnoDB中意向锁的作用及原理探
查看>>
MySQL InnoDB事务隔离级别与锁机制深入解析
查看>>
Mysql InnoDB存储引擎 —— 数据页
查看>>
Mysql InnoDB存储引擎中的checkpoint技术
查看>>
Mysql InnoDB存储引擎中缓冲池Buffer Pool、Redo Log、Bin Log、Undo Log、Channge Buffer
查看>>
MySQL InnoDB引擎的锁机制详解
查看>>