I2C通信实例 - SHTx温湿度传感器 03

前文回顾:


现在我们来编写驱动代码。

SHT35的生产厂商提供了一个驱动程序样本代码。可以到sensirion官网,搜索SHT35,然后在芯片说明页面的Technical Dowloads部分,可以找到该Sample code。遗憾的是这个样本代码是基于操作寄存器来实现的,非常复杂,甚至指令都是根据I2C的通讯协议,通过改变SDA引脚的电平,逐位发送的。读一下这些代码对深入理解I2C通讯确实很有帮助。用HAL库开发时,集成这种代码进去可能会导致以后维护起来非常麻烦。这里我们根据前述工作原理,基于HAL库来重新实现。不用担心网上传言的HAL库效率低之类的评价,任何API都是对寄存器操作的封装,真遇到了效率瓶颈,可以照着库函数中的寄存器操作实现一遍,删繁就简,程序没问题后去掉那些无用的验证就可以啦。

1. 定义指令别名

为了提高程序的可读性,需要为控制指令定义别名。大部分可以从官方例程包中的shtx.h中复制,缺少的指令模仿着添加上即可。在前面创建的sht35.h中定义如下枚举类:

// Sensor Commandstypedef enum {CMD_READ_SERIALNBR  = 0x3780, // read serial numberCMD_READ_STATUS     = 0xF32D, // read status registerCMD_CLEAR_STATUS    = 0x3041, // clear status registerCMD_HEATER_ENABLE   = 0x306D, // enabled heaterCMD_HEATER_DISABLE  = 0x3066, // disable heaterCMD_SOFT_RESET      = 0x30A2, // soft reset// Single shot modeCMD_MEAS_CLOCKSTR_H = 0x2C06, // measurement: clock stretching, high repeatabilityCMD_MEAS_CLOCKSTR_M = 0x2C0D, // measurement: clock stretching, medium repeatabilityCMD_MEAS_CLOCKSTR_L = 0x2C10, // measurement: clock stretching, low repeatabilityCMD_MEAS_POLLING_H  = 0x2400, // measurement: polling, high repeatabilityCMD_MEAS_POLLING_M  = 0x240B, // measurement: polling, medium repeatabilityCMD_MEAS_POLLING_L  = 0x2416, // measurement: polling, low repeatability// Periodic Data Acquisition modeCMD_MEAS_PERI_05_H  = 0x2032, // measurement: periodic 0.5 mps, high repeatabilityCMD_MEAS_PERI_05_M  = 0x2024, // measurement: periodic 0.5 mps, medium repeatabilityCMD_MEAS_PERI_05_L  = 0x202F, // measurement: periodic 0.5 mps, low repeatabilityCMD_MEAS_PERI_1_H   = 0x2130, // measurement: periodic 1 mps, high repeatabilityCMD_MEAS_PERI_1_M   = 0x2126, // measurement: periodic 1 mps, medium repeatabilityCMD_MEAS_PERI_1_L   = 0x212D, // measurement: periodic 1 mps, low repeatabilityCMD_MEAS_PERI_2_H   = 0x2236, // measurement: periodic 2 mps, high repeatabilityCMD_MEAS_PERI_2_M   = 0x2220, // measurement: periodic 2 mps, medium repeatabilityCMD_MEAS_PERI_2_L   = 0x222B, // measurement: periodic 2 mps, low repeatabilityCMD_MEAS_PERI_4_H   = 0x2334, // measurement: periodic 4 mps, high repeatabilityCMD_MEAS_PERI_4_M   = 0x2322, // measurement: periodic 4 mps, medium repeatabilityCMD_MEAS_PERI_4_L   = 0x2329, // measurement: periodic 4 mps, low repeatabilityCMD_MEAS_PERI_10_H  = 0x2737, // measurement: periodic 10 mps, high repeatabilityCMD_MEAS_PERI_10_M  = 0x2721, // measurement: periodic 10 mps, medium repeatabilityCMD_MEAS_PERI_10_L  = 0x272A, // measurement: periodic 10 mps, low repeatabilityCMD_FETCH_DATA      = 0xE000, // readout measurements for periodic modeCMD_BREAK_PERI      = 0x3093, // stop the periodic data acquisitionCMD_MEAS_ART        = 0x2B32, // accelerated response time - boost the acquisition to 4HzCMD_R_AL_LIM_LS     = 0xE102, // read alert limits, low setCMD_R_AL_LIM_LC     = 0xE109, // read alert limits, low clearCMD_R_AL_LIM_HS     = 0xE11F, // read alert limits, high setCMD_R_AL_LIM_HC     = 0xE114, // read alert limits, high clearCMD_W_AL_LIM_HS     = 0x611D, // write alert limits, high setCMD_W_AL_LIM_HC     = 0x6116, // write alert limits, high clearCMD_W_AL_LIM_LC     = 0x610B, // write alert limits, low clearCMD_W_AL_LIM_LS     = 0x6100, // write alert limits, low setCMD_NO_SLEEP        = 0x303E,} SHT35_CMD;// Measurement Modetypedef enum {MODE_CLKSTRETCH, // clock stretchingMODE_POLLING,    // polling} etMode;// Frequency Choicetypedef enum {FREQUENCY_HZ5,  //  0.5 measurements per secondsFREQUENCY_1HZ,  //  1.0 measurements per secondsFREQUENCY_2HZ,  //  2.0 measurements per secondsFREQUENCY_4HZ,  //  4.0 measurements per secondsFREQUENCY_10HZ, // 10.0 measurements per seconds} etFrequency;

其中SHT35_CMD中为指令定义了别名,etMode定义的是测量模式名称,etFrequency定义的是周期性测量模式下的测量频率选项名称。目前有这三个枚举类就足够了。

2. 编写读写函数

根据前文所述原理,我们需要通过I2C接口向传感器发送指令、读取数据。在sht35.c中定义如下宏和函数:

#define   SHT35_ADDR_WRITE     0x44<<1        //1000 1000#define   SHT35_ADDR_READ     (0x44<<1)|0x01  //1000 1001,根据用户手册的指示,这个就是SHT30的读取地址// Generator polynomial for CRC#define POLYNOMIAL  0x131 // P(x) = x^8 + x^5 + x^4 + 1 = 100110001//-----------------------------------------------------------------------------static void SHT35_WriteCommand(SHT35_CMD cmd) {uint8_t cmd_buffer[2];cmd_buffer[0] = cmd>>8; //MSBcmd_buffer[1] = cmd;    //LSBif (HAL_I2C_Master_Transmit( &hi2c2, SHT35_ADDR_WRITE, (uint8_t*) cmd_buffer,2, 0xFFFF)!=HAL_OK) {Error_Handler();}}

sht35.c中首先需要定义三个宏。分别是写入地址,读取地址和CRC多项式,这些产品手册上都有说明。因为ADDR接了地,所以默认地址是0x44。函数代码很简单,先把指令的高位和低位分别存入一个缓存数组中,然后调用HAL库的发送函数,通过句柄&hi2c2指向的I2C主机将两个字节发送到指定地址。之所以这么处理,是因为指令是16-bit的,而I2C发送时是按字节发送的。注意,写入时地址要用SHT35_ADDR_WRITE。SHT35_WriteCommand是一个通用函数,可以写入传感器支持的所有指令。但这个函数不需要被外部调用,因此声明为了static。

再来看读取数据的函数:

void SHT35_ReadData(float *temperature, float *humidity) {uint8_t dataBuff[6] = {0U};uint16_t rawData;uint8_t checksum, crcBytes[2];etError error;if (HAL_I2C_Master_Receive( &hi2c2, SHT35_ADDR_READ, dataBuff, 6, 0xFFFF)!=HAL_OK) {Error_Handler();}crcBytes[0] = dataBuff[0];crcBytes[1] = dataBuff[1];checksum = dataBuff[3];error = SHT35_CheckCrc(crcBytes, 2, checksum);crcBytes[0] = dataBuff[3];crcBytes[1] = dataBuff[4];checksum = dataBuff[5];error = SHT35_CheckCrc(crcBytes, 2, checksum);if (error!=NO_ERROR) {Error_Handler();}//Convert to floatrawData = dataBuff[0]<<8|dataBuff[1];*temperature = SHT35_CalcTemperature(rawData);rawData = dataBuff[3]<<8|dataBuff[4];*humidity = SHT35_CalcHumidity(rawData);}

代码也很简单。每次读取要返回6个字节:前2个字节为温度原始数据,第3个为温度数据的CRC,第4-5个字节为湿度原始数据,第6个字节为湿度数据的CRC。函数中首先将原始数据分开,进行CRC校验,最后转化为浮点格式的温度和湿度。

可以看到,SHT35_ReadData函数中并无写入指令。这是因为不同测量模式对应不同的指令,为了提高代码的通用性,发指令的任务被提到了上一层的函数中。以带时钟延长单次测量为例,上层函数如下:

void SHT35_GetTempAndHumiClkStretch(float *temperature, float *humidity,etRepeatability repeatability, uint8_t timeout) {switch (repeatability) {case REPEATAB_LOW:SHT35_WriteCommand(CMD_MEAS_CLOCKSTR_L);break;case REPEATAB_MEDIUM:SHT35_WriteCommand(CMD_MEAS_CLOCKSTR_M);break;case REPEATAB_HIGH:SHT35_WriteCommand(CMD_MEAS_CLOCKSTR_H);break;default:printf("Undefined repeatability!\r
");break;}SHT35_ReadData(temperature, humidity);}

这个函数根据可靠性高中低,分三种情况发出对应测量指令,最后调用SHT35_ReadData读取数据。对禁用时钟延长的情况,可以写一个类似的函数,再用一个上层函数包装这两种情况。以此类推。由于前面设置的是启用时钟延长,这里暂不考虑其他情况。

3. CRC校验函数

CRC校验是提高通信可靠性的一种方式。发送和接收端按相同的公式,对字节逐位串行计算CRC码,接收端对收到的CRC与本地计算的比对,如果不一致则说明数据传输出错。这个CRC校验不是非有不可的,可按需取舍。校验函数代码如下:

//-----------------------------------------------------------------------------static SHT35_CheckCrc(uint8_t data[], uint8_t nbrOfBytes,uint8_t checksum) {uint8_t crc;     // calculated checksum// calculates 8-Bit checksumcrc = SHT35_CalcCrc(data, nbrOfBytes);// verify checksumif (crc!=checksum){    Error_Handler();  }}//-----------------------------------------------------------------------------static uint8_t SHT35_CalcCrc(uint8_t data[], uint8_t nbrOfBytes) {uint8_t bit;        // bit maskuint8_t crc = 0xFF; // calculated checksumuint8_t byteCtr;    // byte counter// calculates 8-Bit checksum with given polynomialfor (byteCtr = 0; byteCtr0; --bit) {if (crc&0x80)crc = (crc<<1)^POLYNOMIAL;elsecrc = (crc<<1);}}return crc;}

这两个函数是直接从官方例程copy过来的。可完美工作。

4. 功能的实现

由于选用是单次测量模式,我们通过按键触发来调用并返回结果。在HAL_GPIO_EXTI_Callback函数中,根据按键动作,执行如下代码:

.............case GPIO_PIN_4:  /* KEY0 pressed - PE4  */SHT35_GetTempAndHumiClkStretch(&Tm, &RH, REPEATAB_HIGH, 0xFF);I_NT = (int16_t) Tm;F_RC = (uint32_t)((Tm - I_NT) * 100U);//with 2 digitsprintf("SHT T = %d.%lu degC.\r
", I_NT, F_RC);I_NT = (int16_t) RH;F_RC = (uint32_t)((RH - I_NT) * 100U);//with 2 digitsprintf("SHT RH = %d.%lu %%.\r
\r
", I_NT, F_RC);break;

先Get数据,然后分成整数部分和小数部分,调用printf打印到上位机。经测试可完美工作,有图为证:

图1. 串口助手收到的信息



总结:

为了实现上述功能,先花一周,系统学习了STM32 Reference manual上的I2C interface;仔细读了SHT35的Data sheet;读了芯片的官方的例程,这个是直接操作寄存器实现的,虽然最后没选用,但对初学者理解I2C协议非常有帮助。

SHT35功能还是很强大的,精度很高,芯片非常小,可选设置比较多。诸如超限报警,阈值设置,周期性测量,内置加热器控制等功能会逐步添加,并逐步实现在上位机设置这些功能。

这是自学STM32以来第一个可以work的硬件控制练习。虽是坐井观天,但自我感觉收获良多。子曰:路漫漫其修远兮,不积跬步无以至千里,……

实例   通信   I2C
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章