1、关于SHT30
由于用gpio直接驱动ds18b20的失败,我只好将目光放到了I2c上,恰好在网络上也看到一篇关于esp23驱动sht30的详细文章可以参chao考xi,就买来准备解决温湿度监测的问题了。
sht30有两种通信方式,这次一方面也想学学I2c所以选择I2c来通信了,但是没想到是个这么大的坑。用I2c感觉很像计网里学的什么东西,实现的技术原理看起来非常典型。一个I2c总线可以连接多个设备,并且能对他们的操作也是区分开的。其实esp32是直接支持I2c的,还有配套的API可用,不过看了他的API,发现里面充满了freertos的身影,那我要想在alios上使用岂不是还要移植,这个坑让我打消了使用官方API的念头,还是使用alios写得API好了。
学习中主要参考了几个材料:
SHT30的驱动简易流程:SHT30使用的学习过程1SHT30工作模式介绍
DATASHEET:SHT30中文datasheet
2、alios things的I2C API
首先需要说的是alios things的API适配还不够完全,至少对于esp32来说是这样的,不只是I2C的部分还有之前的PWM等。不过整体来说API的易用程度还是非常高的,是在官方API基础上更加深度的功能封装。
1.初始化
在初始化上,alios提供了十分统一化的结构体来实现——i2c_dev_t,其中主要是两个成员,设备号port(主机指定从机所需),配置结构体config(包括了设备地址长度,频率,主从模式,设备地址),对其相应赋值即可:
//I2C初始化
SHT30_dev.port = 1;
i2c_config_t sht30_cfg = {
8 ,
100000,
I2C_MODE_MASTER,
SHT30_WRITE_ADDR
};
SHT30_dev.config = sht30_cfg;
hal_i2c_init(&SHT30_dev);
不过在这里需要注意API里有个小问题,配置里没有指定scl和sda的gpio口,他们被默认配置在i2c.c中,由于我的esp32 devkitC的scl和sda口分别在22和21,所以在这里改一下(这里预设了两个设备,我选的是第二个)
static i2c_resource_t g_dev[I2C_NUM_MAX] = {
{(volatile i2c_dev_t *)0x60013000,23,22},
{(volatile i2c_dev_t *)0x60027000,22,21}
};
接着是关于SHT30的初始化,主要是向其发送控制命令,这个主要包括了发送地址——>MSB——>LSB,在这里就需要说一下alios的API逻辑。他所给的I2C API对于发送只需要一个函数hal_i2c_master_send,这其中包括了发送准备,发送地址,接受ack,结束发送等一系列操作,读取也是类似,只需先给予发送buf和字节数就可以实现一站式发送。
但是我估计alios开发人员在写esp32的api时只写拉11位地址的操作,漏掉了7位地址的。。在试过直接使用API出不了结果后,之好去看i2c.c的详细函数实现,又发现了几个问题。
问题一,从hal_i2c_master_send 向着i2c_write_bytes 传递设备地址参数dev_addr时将其往右移了一位,虽然在后面的代码中又移了回来,但是这不是相当于没移吗,只是把末尾置0了,而sht30地址末尾本就是0了,应该的是不要移位的,所以在这里我去掉了移位操作,仅留下赋值代码。
问题二,在i2c_write_bytes 函数中运用了一个发送地址的函数i2c_send_addr 但是函数里只包括了处理11位地址设备的代码,而没有根据读写添加末位的代码,所以在这里应该加上读写位的操作
static void i2c_send_addr(i2c_dev_t * handle,uint8_t is_write,uint16_t addr_value,int8_t addr_width)
{
uint8_t low_mask = (is_write)?(0xF0):(0xF1);
if(addr_width) {
handle->fifo_data.data = (((addr_value >> 8) & 0x6) | low_mask);
handle->fifo_data.data = ((addr_value >> 1) & 0xFF);
return;
}
//对读写标识符进行判断加入尾部操作位
if(is_write){
handle->fifo_data.data = addr_value & 0xFE;
}
else{
handle->fifo_data.data = addr_value & 0xFF;
}
}
还有一点是上面函数中的地址宽度参数,是失去了其本来意思的,在i2c_dev_t结构体中其如果是7位就填0,11位就填1,后面几个地址宽参数变成了单纯的flag位
解决好上面的问题后基本就可以正常使用API了。
2.获取数据
由于可以直接使用API,获取数据也变得十分便捷,直接调用函数一箩筐地接数据。然后就是CRC校验,最后按照sht30的数据格式对接受到的6bytes数据进行解读。
int sht30_get_value(){
int ret = 0;
hal_i2c_master_recv(&SHT30_dev, SHT30_WRITE_ADDR, sht30_buf, 6, 100);
if( (!SHT3X_CheckCrc(sht30_buf,2,sht30_buf[2])) && (!SHT3X_CheckCrc(sht30_buf+3,2,sht30_buf[5])) ){
ret = 0;//成功
}
else{
ret = 1;
}
return ret;
}
3、主函数代码
int application_start(int argc, char *argv[]){
//I2C初始化
SHT30_dev.port = 1;
i2c_config_t sht30_cfg = {
8 ,
100000,
I2C_MODE_MASTER,
SHT30_WRITE_ADDR
};
SHT30_dev.config = sht30_cfg;
hal_i2c_init(&SHT30_dev);
//sht30初始化
uint8_t data_hl[2] = {0x22, 0x36};
hal_i2c_master_send(&SHT30_dev, SHT30_WRITE_ADDR, data_hl, 2, 100);
aos_msleep(1000);
while(1){
if(!sht30_get_value()){
for(int i = 0; i < 6; i++)printf("%d", sht30_buf[i]);
printf("\n");
sht30_temp =((((sht30_buf[0]*256) + sht30_buf[1]) *175)/65535.0 - 45);
sht30_rh = (((sht30_buf[3]*256) + (sht30_buf[4]))*100/65535.0);
ESP_LOGI("SHT30", "temp:%4.2f C \r\n", sht30_temp);
ESP_LOGI("SHT30", "hum:%4.2f %%RH \r\n", sht30_rh);
}
aos_msleep(3000);
}
return 0;
}
结果