分享

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

 goodwangLib 2020-05-13

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上,太小了,照片不是很清楚,就没图了~
技术小白自己摸爬滚打写的代码,希望大佬指正~~感谢感谢,也希望帮助有需要的人。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多