现在我们来编写驱动代码。
SHT35的生产厂商提供了一个驱动程序样本代码。可以到sensirion官网,搜索SHT35,然后在芯片说明页面的Technical Dowloads部分,可以找到该Sample code。遗憾的是这个样本代码是基于操作寄存器来实现的,非常复杂,甚至指令都是根据I2C的通讯协议,通过改变SDA引脚的电平,逐位发送的。读一下这些代码对深入理解I2C通讯确实很有帮助。用HAL库开发时,集成这种代码进去可能会导致以后维护起来非常麻烦。这里我们根据前述工作原理,基于HAL库来重新实现。不用担心网上传言的HAL库效率低之类的评价,任何API都是对寄存器操作的封装,真遇到了效率瓶颈,可以照着库函数中的寄存器操作实现一遍,删繁就简,程序没问题后去掉那些无用的验证就可以啦。
为了提高程序的可读性,需要为控制指令定义别名。大部分可以从官方例程包中的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定义的是周期性测量模式下的测量频率选项名称。目前有这三个枚举类就足够了。
根据前文所述原理,我们需要通过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读取数据。对禁用时钟延长的情况,可以写一个类似的函数,再用一个上层函数包装这两种情况。以此类推。由于前面设置的是启用时钟延长,这里暂不考虑其他情况。
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过来的。可完美工作。
由于选用是单次测量模式,我们通过按键触发来调用并返回结果。在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的硬件控制练习。虽是坐井观天,但自我感觉收获良多。子曰:路漫漫其修远兮,不积跬步无以至千里,……
留言与评论(共有 0 条评论) “” |