打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
SHT30使用的学习过程2 SHT30驱动程序

SHT30使用的学习过程2代码篇
给各位道个歉,代码拖得有点久了,最近事情颇多,抱歉抱歉!

综述

嗯,代码篇我想把我写的所有的代码给各位需要使用sht30的朋友们介绍一遍,由于我这版是测试版,所以很多函数没有封装的很好,不过代码可以用了,我测试的代码已经通过,测量温度和湿度精确到小数点后1位,在这里想仔细给各位介绍一下我代码的写作过程,因为网上的代码仅仅是代码,很多开发sht30的小白(像我这样的)没办法移植,或者根本不知道怎么移植,在这里我想详细叙述我的代码,包括最基本的I2C通信,所以可能本次内容很啰嗦,希望各位见谅哈[by zwx lvmm]

I2C代码部分

这部分是SHT30和单片机通信的基础协议,I2C有四根线组成,除去vcc和gnd之外还有SCL (时钟线)与 SDA (数据线),STM32F407参考手册上写,I2C最大的通信周期是4MHz,普通模式下是2MHz。这部分涉及I2C通信的时序,可以参考原子哥(正点原子)的相关视频资源,我的基础也是和原子哥的视频学习的,这里简单介绍一下,有什么不清楚的可以参考原子哥的视频。

  1. I2C时序:

    这个是I2C时序图。整个I2C通信分为这样几个过程,I2C起始信号,I2C数据写入,I2C数据读取,I2C应答信号,I2C结束信号等,接下来分别介绍。(代码参考的原子哥代码,我看懂了直接用的)

1.1 IO初始化:

上代码,我的开发板是STM32F407,这部分属于初始化配置,没啥说的。

//IO方向设置#define SDA_IN()  {GPIOB->MODER&=~(3<<(11*2));GPIOB->MODER|=0<<11*2;}	//PB11输入模式#define SDA_OUT() {GPIOB->MODER&=~(3<<(11*2));GPIOB->MODER|=1<<11*2;} //PB11输出模式//IO操作函数	 #define IIC_SCL    PBout(10) //SCL#define IIC_SDA    PBout(11) //SDA	 #define READ_SDA   PBin(11)  //输入SDA void IIC_Init(void){			  GPIO_InitTypeDef  GPIO_InitStructure;  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟  //GPIOB10,B11初始化设置  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化	IIC_SCL=1;	IIC_SDA=1;}

此部分代码看的原子哥的,IO配置的方法也是学习原子哥的设置的。

1.2 I2C起始信号:

如最开始的图所示当SCL是高电平的时候,把SDA从高电平拉至低电平就可以了,先上这部分的代码

void IIC_Start(void){	SDA_OUT();     //sda线输出模式	IIC_SDA=1;	  	  	IIC_SCL=1;	delay_us(4); 	IIC_SDA=0;//开始信号,scl=1,sda=1->sda=0。	delay_us(4);	IIC_SCL=0;//scl=0,拉低时钟线,准备数据的发送}	 

这里为什么是延时4us,我猜测是按照标准频率2MHz,最高4MHz计算一下,一个数据周期大概5us-2.5us之间,还要考虑信号的建立时间,所以选择了4us作为一个周期。其余的不懂得可以看I2C的介绍。

1.3 I2C停止信号:

void IIC_Stop(void){	SDA_OUT();//sda线输出模式	IIC_SCL=0;	IIC_SDA=0;// 	delay_us(4);	IIC_SCL=1; //结束信号,scl=1,sda=0->sda=1。	IIC_SDA=1;	delay_us(4);							   	}

1.4 I2C应答信号和等待应答信号:

这个东西就是说,机器之间的通信嘛,肯定是像我们使用对讲机一样,说完了一句话,就要加一句over,对方听到了你的over,才能说他想说的,要不然就会两个人一起说,肯定乱套了,所以才会有这两个信号,而有的时候我们不用说over,比如我们对话结束了,和对方说再见,说完就意味着对话结束了,所以也可以不产生应答信号。代码如下,具体的时序逻辑同样参考原子哥,各位可以自己学习一下,如果你没学,就理解一下这个代码是干啥的就行,也一样可以写代码。

void IIC_Ack(void){	IIC_SCL=0;	SDA_OUT();	IIC_SDA=0;	delay_us(2);	IIC_SCL=1;	delay_us(2);	IIC_SCL=0;}void IIC_NAck(void){	IIC_SCL=0;	SDA_OUT();	IIC_SDA=1;	delay_us(2);	IIC_SCL=1;	delay_us(2);	IIC_SCL=0;}		u8 IIC_Wait_Ack(void){	u8 ucErrTime=0;	SDA_IN();      //SDA设置为输入  	IIC_SDA=1;delay_us(1);	   	IIC_SCL=1;delay_us(1);	 	while(READ_SDA)	{		ucErrTime++;		if(ucErrTime>250)		{			IIC_Stop();			return 1;		}	}	IIC_SCL=0;//时钟输出0 	   	return 0;  } 

1.5 I2C发送数据:

这部分是比较关键的,I2C发送数据这里是8位的模式,我看到的I2C一般都是数据8位8位发的,发送的时候一个周期是4us,SCL是高电平的时候要保证数据有效,所以数据必须要在SCL变高之前建立完毕,这里可能有些人不太明白数据的建立是什么意思,其实就是点平从0变为1不是一瞬间变化完成的,而实经过一段时间涨上去的,其实就是需要一段时间才能点平0->1或者1->0,虽然这段时间很短,但是对于一些速度比较快的协议可能就不能忽略这个影响了。>>这个是移位操作符,不懂得可以去查一查。

void IIC_Send_Byte(u8 txd){                            u8 t;   	SDA_OUT(); 	        IIC_SCL=0;//拉低时钟开始数据传输    for(t=0;t<8;t++)    {                      IIC_SDA=(txd&0x80)>>7;        txd<<=1; 	  		delay_us(1);   		IIC_SCL=1;		delay_us(2); 		IIC_SCL=0;			delay_us(1);    }	 }

1.6 I2C接收数据:

这部分接收,和发送差不多,每个周期都分成了两个2us

//一个参数 ack  当ack=1,发送应答信号,ack=0不发送应答信号u8 IIC_Read_Byte(unsigned char ack){	unsigned char i,receive=0;	SDA_IN();//SDA设置为输入    for(i=0;i<8;i++ )	{        IIC_SCL=0;         delay_us(2);		IIC_SCL=1;        receive<<=1;        if(READ_SDA)receive++;   		delay_us(2);     }					     if (!ack)        IIC_NAck();//发送nACK    else        IIC_Ack(); //发送ACK       return receive;}

2. SHT30代码部分

这部分代码是关于SHT30与单片机之间的通信的流程和对读到的数据进行处理,包括初始化,读取数据与数据处理,这部分代码是阅读datasheet之后结合datasheet相关内容写的。

2.1 SHT30的初始化:

这个我是采用的SHT30的周期模式,这部分的内容大家可以参考我上一篇博客所写的,里面给出了相关内容的介绍。
关于SHT30 datasheet的相关内容
流程:
2C开始信号->7位I2C地址+0(写操作标志位)(前面介绍了,如果ADDR接低电平,那么这里就是0x88,如果接高电平就是0x8a)->命令MSB->命令LSB(eg 0x2130 高可重复性,1秒测量一次)-> I2C停止信号。

void SHT_Init(void){   delay_ms(250);    //0x2130  表示周期模式 周期1ms   	IIC_Start();   	IIC_Send_Byte(0x88);   	IIC_Wait_Ack();   	IIC_Send_Byte(0x21);   	IIC_Wait_Ack();   	IIC_Send_Byte(0x30);   	IIC_Wait_Ack();     IIC_Stop();      delay_ms(150); }

这样设置完毕之后,SHT30每1秒就会自动测量一次温度,相关寄存器就更新一次值。

2.2 SHT30的读取数据:

我加了注释,各位看代码的注释:

void sht30_read_temp_humi(u8 *p){//这里和前面的一样,也是写一个命令给SHT30,这个命令是访问SHT30转换结果的寄存器的   IIC_Start();   IIC_Send_Byte(0x88);   IIC_Wait_Ack();   IIC_Send_Byte(0xe0);   IIC_Wait_Ack();   IIC_Send_Byte(0x00);   IIC_Wait_Ack();//下面是开始读取数据,其中的数组p存放结果,前三个存放温度值,后三个是湿度值,在前三个温度值里面,//p[0]是温度的高八位,p[1]是低八位,p[2]是CRC校验,有关CRC校验的知识我是百度上面看的,//要是各位不懂得话,可以不用crc校验,直接用p[0]、p[1]就可以转换出来温度的值。   IIC_Start();   IIC_Send_Byte(0x89);   IIC_Wait_Ack();      //前五次读取都要发送ack信号,最后一次就不用发了。   p[0] = IIC_Read_Byte(1);   p[1] = IIC_Read_Byte(1);   p[2] = IIC_Read_Byte(1);   p[3] = IIC_Read_Byte(1);   p[4] = IIC_Read_Byte(1);   p[5] = IIC_Read_Byte(0);   IIC_Stop();}

2.3 数据处理函数

这里面有两个全局变量用来存放转换的温度和湿度数据,下面我也写了数据,各位可以参考

int sht30_data_process(void){  u8 temporary[3];  u16 data;  u8 crc_result;  //data_process.sht30_data_buffer这个变量是我程序里面使用的存放SHT30寄存器读到的值的数组,  //定义类型为 unsigned short int   sht30_data_buffer[6]  sht30_read_temp_humi(data_process.sht30_data_buffer);  //先处理温度的相关数据,位于数组的前三个  temporary[0]=data_process.sht30_data_buffer[0];  temporary[1]=data_process.sht30_data_buffer[1];  temporary[2]=data_process.sht30_data_buffer[2];  //crc校验  crc_result=sht30_crc8_check(temporary,2,temporary[2]);  //crc校验要是不成功就返回1,同时不会更新温度值  if(crc_result==0)  {  //把2个8位数据拼接为一个16位的数据  data=((uint16)temporary[0] << 8) | temporary[1];  //温度转换,将16位温度数据转化为10进制的温度数据,这里保留了一位小数,data_process.SHT30_temperature这是一个全局变量,至于为什么变量名字里面有个.不懂得各位可以百度一下c语言结构体的相关说明。  data_process.SHT30_temperature = (int)((175.0 * ((float)data) / 65535.0 - 45.0) *10.0);  }  else  {  return 1;  }  temporary[0]=data_process.sht30_data_buffer[3];  temporary[1]=data_process.sht30_data_buffer[4];  temporary[2]=data_process.sht30_data_buffer[5];  //crc校验  crc_result=sht30_crc8_check(temporary,2,temporary[2]);  	if(crc_result==0)  {  //参考上面温度的代码  data=((uint16)temporary[0] << 8) | temporary[1];  data_process.SHT30_humidity = (int)((100.0 * (float)data / 65535.0) *10.0);   return 0;  }  else  {  return 2;  }}

2.4 CRC校验函数

关于CRC校验的相关知识可以百度查一下,我对这个了解不是很深,我只知道这就是一个校验码,是确保通信过程中数据传输没问题的,没有缺少任何数据,当我们接收到数据后,按照一定的方式计算一个crc校验码,经过和提供的crc校验码进行比较,如果两个码一样,那么数据就是没问题的。以前做别的比赛的时候听大佬说过计算过程,就写在下面了,下面有一个数字0x31那个是crc校验所用的函数,数据手册上写了使用0x31,这里就直接用了

 int  crc8_compute(u8 *check_data, u8 num_of_data){ 	uint8_t bit;        // bit mask uint8_t crc = 0xFF; // calculated checksum uint8_t byteCtr;    // byte counter // calculates 8-Bit checksum with given polynomial for(byteCtr = 0; byteCtr < num_of_data; byteCtr++) {     crc ^= (check_data[byteCtr]); 	//crc校验,最高位是1就^0x31     for(bit = 8; bit > 0; --bit) {         if(crc & 0x80) {             crc = (crc << 1) ^ 0x31;         }  else {             crc = (crc << 1);         }     } } return crc;}int sht30_crc8_check(u8 *p,u8 num_of_data,u8 CrcData){  uint8_t crc;  crc = crc8_compute(p, num_of_data);// calculates 8-Bit checksum if(crc != CrcData)  {        return 1;            } return 0;}

最后说一下怎么使用我的这些代码,大家可以把所有的代码都放到一个c文件里面,然后主函数里面先初始化一下IO口,然后初始化一下SHT30,之后就可以调用一次sht30_data_process();这个函数就可以得到温度值了(别忘记那个函数里面提到的全局变量)(我设置的模式是1s一次,所以SHT30内部寄存器只会1s改变一次数据,如果程序设置的读取周期太快,也不会一直变化哦,也是1s一次,如果想要刷新频率快一些,可以尝试其他模式,不过太快了个人感觉没什么必要)。
亲自测试过了,这个代码可以用,测得的室温是26.1度左右,数据不是很稳定,有0.2度左右的波动。由于数据显示在0.96的OLED上,太小了,照片不是很清楚,就没图了~
技术小白自己摸爬滚打写的代码,希望大佬指正~~感谢感谢,也希望帮助有需要的人。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
IIC通信协议代码分享及调试过程中的一些小误区分享
linux e2prom 驱动代码
超详细陀螺仪MPU6050模块输出姿态角(有完整版源码)
STM32的I2C的原理与使用(24C02附代码)
C语言使用面向对象实现IIC驱动
IIC详解,包括原理、过程,最后一步步教你实现IIC
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服