SHT30使用的学习过程2代码篇
给各位道个歉,代码拖得有点久了,最近事情颇多,抱歉抱歉!
嗯,代码篇我想把我写的所有的代码给各位需要使用sht30的朋友们介绍一遍,由于我这版是测试版,所以很多函数没有封装的很好,不过代码可以用了,我测试的代码已经通过,测量温度和湿度精确到小数点后1位,在这里想仔细给各位介绍一下我代码的写作过程,因为网上的代码仅仅是代码,很多开发sht30的小白(像我这样的)没办法移植,或者根本不知道怎么移植,在这里我想详细叙述我的代码,包括最基本的I2C通信,所以可能本次内容很啰嗦,希望各位见谅哈[by zwx lvmm]
这部分是SHT30和单片机通信的基础协议,I2C有四根线组成,除去vcc和gnd之外还有SCL (时钟线)与 SDA (数据线),STM32F407参考手册上写,I2C最大的通信周期是4MHz,普通模式下是2MHz。这部分涉及I2C通信的时序,可以参考原子哥(正点原子)的相关视频资源,我的基础也是和原子哥的视频学习的,这里简单介绍一下,有什么不清楚的可以参考原子哥的视频。
上代码,我的开发板是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配置的方法也是学习原子哥的设置的。
如最开始的图所示当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的介绍。
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);
}
这个东西就是说,机器之间的通信嘛,肯定是像我们使用对讲机一样,说完了一句话,就要加一句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;
}
这部分是比较关键的,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);
}
}
这部分接收,和发送差不多,每个周期都分成了两个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;
}
这部分代码是关于SHT30与单片机之间的通信的流程和对读到的数据进行处理,包括初始化,读取数据与数据处理,这部分代码是阅读datasheet之后结合datasheet相关内容写的。
这个我是采用的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秒就会自动测量一次温度,相关寄存器就更新一次值。
我加了注释,各位看代码的注释:
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();
}
这里面有两个全局变量用来存放转换的温度和湿度数据,下面我也写了数据,各位可以参考
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;
}
}
关于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上,太小了,照片不是很清楚,就没图了~
技术小白自己摸爬滚打写的代码,希望大佬指正~~感谢感谢,也希望帮助有需要的人。