sht11温湿度传感器图(I2C通信实例-SHTx温湿度传感器)
sht11温湿度传感器图(I2C通信实例-SHTx温湿度传感器)可以看到,SHT35_ReadData函数中并无写入指令。这是因为不同测量模式对应不同的指令,为了提高代码的通用性,发指令的任务被提到了上一层的函数中。以带时钟延长单次测量为例,上层函数如下: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]; crcByt
前文回顾:- I2C通信实例 - SHTx温湿度传感器 01
 - I2C通信实例 - SHTx温湿度传感器 02
 
现在我们来编写驱动代码。
SHT35的生产厂商提供了一个驱动程序样本代码。可以到sensirion官网,搜索SHT35,然后在芯片说明页面的Technical Dowloads部分,可以找到该Sample code。这个例程代码是基于操作寄存器来实现的,而且没有利用MCU的I2C接口,通信指令都是根据I2C的通讯协议,通过操控SDA和SCL引脚的电平,逐位发送的。好像是为没有I2C接口的MCU开发的。读一下这些代码对深入理解I2C通讯很有帮助。用HAL库开发时,集成这种代码进去可能会导致以后维护起来非常麻烦。这里我们根据前述工作原理,利用MCU自带的I2C接口,基于HAL库来重新实现。不用担心网上传言的HAL库效率低之类的评价,任何API都是对寄存器操作的封装,真遇到了效率瓶颈,可以照着库函数中的寄存器操作实现一遍,删繁就简,程序没问题后去掉那些无用的验证,把通用版整成专用版就可以啦。
1. 定义指令别名为了提高程序的可读性,需要为控制指令定义别名。大部分可以从官方例程包中的shtx.h中复制,缺少的指令模仿着添加上即可。在前面创建的sht35.h中定义如下枚举类:
// Sensor Commands
typedef enum {
	CMD_READ_SERIALNBR  = 0x3780  // read serial number
	CMD_READ_STATUS     = 0xF32D  // read status register
	CMD_CLEAR_STATUS    = 0x3041  // clear status register
	CMD_HEATER_ENABLE   = 0x306D  // enabled heater
	CMD_HEATER_DISABLE  = 0x3066  // disable heater
	CMD_SOFT_RESET      = 0x30A2  // soft reset
	// Single shot mode
	CMD_MEAS_CLOCKSTR_H = 0x2C06  // measurement: clock stretching  high repeatability
	CMD_MEAS_CLOCKSTR_M = 0x2C0D  // measurement: clock stretching  medium repeatability
	CMD_MEAS_CLOCKSTR_L = 0x2C10  // measurement: clock stretching  low repeatability
	CMD_MEAS_POLLING_H  = 0x2400  // measurement: polling  high repeatability
	CMD_MEAS_POLLING_M  = 0x240B  // measurement: polling  medium repeatability
	CMD_MEAS_POLLING_L  = 0x2416  // measurement: polling  low repeatability
	// Periodic Data Acquisition mode
	CMD_MEAS_PERI_05_H  = 0x2032  // measurement: periodic 0.5 mps  high repeatability
	CMD_MEAS_PERI_05_M  = 0x2024  // measurement: periodic 0.5 mps  medium repeatability
	CMD_MEAS_PERI_05_L  = 0x202F  // measurement: periodic 0.5 mps  low repeatability
	CMD_MEAS_PERI_1_H   = 0x2130  // measurement: periodic 1 mps  high repeatability
	CMD_MEAS_PERI_1_M   = 0x2126  // measurement: periodic 1 mps  medium repeatability
	CMD_MEAS_PERI_1_L   = 0x212D  // measurement: periodic 1 mps  low repeatability
	CMD_MEAS_PERI_2_H   = 0x2236  // measurement: periodic 2 mps  high repeatability
	CMD_MEAS_PERI_2_M   = 0x2220  // measurement: periodic 2 mps  medium repeatability
	CMD_MEAS_PERI_2_L   = 0x222B  // measurement: periodic 2 mps  low repeatability
	CMD_MEAS_PERI_4_H   = 0x2334  // measurement: periodic 4 mps  high repeatability
	CMD_MEAS_PERI_4_M   = 0x2322  // measurement: periodic 4 mps  medium repeatability
	CMD_MEAS_PERI_4_L   = 0x2329  // measurement: periodic 4 mps  low repeatability
	CMD_MEAS_PERI_10_H  = 0x2737  // measurement: periodic 10 mps  high repeatability
	CMD_MEAS_PERI_10_M  = 0x2721  // measurement: periodic 10 mps  medium repeatability
	CMD_MEAS_PERI_10_L  = 0x272A  // measurement: periodic 10 mps  low repeatability
	CMD_FETCH_DATA      = 0xE000  // readout measurements for periodic mode
	CMD_BREAK_PERI      = 0x3093  // stop the periodic data acquisition
	CMD_MEAS_ART        = 0x2B32  // accelerated response time - boost the acquisition to 4Hz
	CMD_R_AL_LIM_LS     = 0xE102  // read alert limits  low set
	CMD_R_AL_LIM_LC     = 0xE109  // read alert limits  low clear
	CMD_R_AL_LIM_HS     = 0xE11F  // read alert limits  high set
	CMD_R_AL_LIM_HC     = 0xE114  // read alert limits  high clear
	CMD_W_AL_LIM_HS     = 0x611D  // write alert limits  high set
	CMD_W_AL_LIM_HC     = 0x6116  // write alert limits  high clear
	CMD_W_AL_LIM_LC     = 0x610B  // write alert limits  low clear
	CMD_W_AL_LIM_LS     = 0x6100  // write alert limits  low set
	CMD_NO_SLEEP        = 0x303E 
} SHT35_CMD;
// Measurement Mode
typedef enum {
	MODE_CLKSTRETCH  // clock stretching
	MODE_POLLING     // polling
} etMode;
// Frequency Choice
typedef enum {
	FREQUENCY_HZ5   //  0.5 measurements per seconds
	FREQUENCY_1HZ   //  1.0 measurements per seconds
	FREQUENCY_2HZ   //  2.0 measurements per seconds
	FREQUENCY_4HZ   //  4.0 measurements per seconds
	FREQUENCY_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; //MSB
	cmd_buffer[1] = cmd;    //LSB
	if (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 float
	rawData = 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\n");
		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 checksum
	crc = SHT35_CalcCrc(data  nbrOfBytes);
	// verify checksum
	if (crc!=checksum){
    Error_Handler();
  }		
}
//-----------------------------------------------------------------------------
static uint8_t SHT35_CalcCrc(uint8_t data[]  uint8_t nbrOfBytes) {
	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<nbrOfBytes; byteCtr  ) {
		crc ^= (data[byteCtr]);
		for (bit = 8; bit>0; --bit) {
			if (crc&0x80)
				crc = (crc<<1)^POLYNOMIAL;
			else
				crc = (crc<<1);
		}
	}
	return crc;
}
    
这两个函数是直接从官方例程copy过来的。可完美工作。
4. 功能的实现由于选用是单次测量模式,我们通过按键触发来调用并返回结果。在HAL_GPIO_EXTI_Callback函数中,根据按键动作,执行如下代码:
.............
case GPIO_PIN_4:  /* KEY0 pressed - PE4  */
		SHT35_GetTempAndHumiClkStretch(&Tm  &RH  REPEATAB_HIGH  0xFF);
		printf("SHT35  T = %2.2f degC.\r\n"  Tm);
		printf("SHT35 RH = %2.2f %%.\r\n\r\n"  RH);
		break;
    
先Get数据,然后分成整数部分和小数部分,调用printf打印到上位机。经测试可完美工作,有图为证:

图1. 串口助手收到的信息
总结:
为了实现上述功能,先花一周,系统学习了STM32 Reference manual上的I2C interface;仔细读了SHT35的Data sheet;读了芯片的官方的例程,这个是直接操作寄存器实现的,虽然最后没选用,但对初学者理解I2C协议非常有帮助。
SHT35功能还是很强大的,精度很高,芯片非常小,可选设置比较多。诸如超限报警,阈值设置,周期性测量,内置加热器控制等功能会逐步添加,并逐步实现在上位机设置这些功能。
这是自学STM32以来第一个可以work的硬件控制练习。虽是坐井观天,但自我感觉收获良多。子曰:路漫漫其修远兮,不积跬步无以至千里,……




