最近将一些老STM32工程所使用的标准库迁移成HAL库,其中对原来的I2C和SHT20的工程发现的问题作了一些修改。

其中,对于SHT20,这篇百度文库的关于SHT2x的芯片中文说明书中,有以下错误:

1.关于非主机模式的测量时序图不完整

2.非主机模式的测量时序图中的Checksum是错的,不是01100011,而是01100100

可以参考官网最新的英文文档

对于SHT20的调试,参照文档的时序图用模拟I2C就可以顺利写出来,在这个过程中遇到了以下坑:

1.STM32通过I2C发送ACK信号给SHT20时,需要先拉高SCL再拉高SDA,否则之后去读I2C上的数据时,会发现都是0xFF

2.一开始看文档时没有仔细看。对于分辨率的设置,比如需要将相对湿度和温度的分辨率设成10bit和13bit,那么按照文档,需要将寄存器的第7位置1,第0位置0,而不是将第7、6位置1和0。在下面的sht20的h文件中,我将这个分辨率设成了0x80,也就是0b10000000,也就是说当拿到寄存器的值的后,按位与0b01111110,将第7和第0位置零,然后再按位或0x80就设置好了寄存器了

3.使用非主机模式测量时,在等待ACK,也就是I2C_WaitAck函数中,如果在规定时间内没有读到ACK,不能发出STOP信号

下面是代码粘贴时间,注释蛮细的。

首先是I2C的c文件

#include "main.h"
#include "i2c.h"
#include "stm32f1xx_hal.h"

#define I2C_Delay_Times 5//延时的个数,5即为延时5Us

/**
* @name		void I2C_Start(void)
* @brief	发送I2C起始信号
* @details	当SCL为高时,SDA由高拉低,就会形成起始信号
* @param	None
* @retval	None
*/
void I2C_Start(void) {
	SDA_H;										//SDA拉高
	//I2C_Delay_Us(I2C_Delay_Times);				//延时
	SCL_H;										//SCL拉高
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SDA_L;										//SDA拉低
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SCL_L;										//SCL拉低,钳住
}
/**
* @brief	发送I2C结束信号
* @details	当SCL为高时,SDA由低拉高,就会形成结束信号
* @param	None
* @retval	None
*/
void I2C_Stop(void) {
	SDA_L;										//SDA拉低
	SCL_L;										//SCL拉低
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SCL_H;										//SCL拉高
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SDA_H;										//SDA拉低
	I2C_Delay_Us((I2C_Delay_Times * 2));		//延时
}
/**
* @brief	通过I2C发送(写)数据
* @details	当SCL为高时,保持SDA(高为1,低位0)稳定,即可传输一位数据
* @param	byte:想要发送的数据(1字节,8位)
* @retval	None
*/
void I2C_SendByte(uint8_t sendByte) {
	SCL_L;										//先拉低时钟线
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	for (int i = 0; i < 8; i++)
	{
		if (sendByte & 0x80) {					//如果高位为1则拉高,否则拉低
			SDA_H;								//SDA拉高
		}
		else {
			SDA_L;								//SDA拉低
		}
		sendByte <<= 1;							//数据左移1位
		I2C_Delay_Us(I2C_Delay_Times);			//延时
		SCL_H;									//SCL拉高
		I2C_Delay_Us(I2C_Delay_Times);			//延时
		SCL_L;									//SCL拉低
	}
}
/**
* @brief	等待从机应答信号
* @details	主机将和SCL和SDA都拉高之后,释放SDA,然后读取SDA,为低则表示接收到从机的应答信号
* @param	timeout: 在规定时间内等待应答信号
* @retval	I2C_WaitAck_Succeed:	成功等到应答信号
* @retval	I2C_WaitAck_Fail:		在规定时间内未等到应答信号
*/
uint8_t I2C_WaitAck(uint16_t timeout) {
	SDA_H;										//SDA拉高
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SCL_H;										//SCL拉高
	I2C_Delay_Us(I2C_Delay_Times);				//延时	

	//开始循环查询SDA线上是否有应答信号
	while (SDA_R)
	{
		//如果为1,则说明未产生应答
		if (!(--timeout)) {
			//如果时间超时
			//如果为非主机测量,则此处不能Stop
			//I2C_Stop();							//发出终止信号
			return I2C_Fail;					//返回失败信息
		}
		//如果时间未超时
		I2C_Delay_Us(1);						//延时一微秒
	}
	//如果收到了应答信号,将SCL拉低
	SCL_L;										//SCL拉低
	return I2C_Succeed;							//返回成功信息
}
/**
* @brief	向从机发送应答信号
* @details	在SDA持续为低时,SCL产生一个正脉冲即表示产生一个应答信号
* @param	None
* @retval	None
*/
void I2C_SendAck(void) {
	//重大bug 一定要先拉低scl再拉低sda,否则会读到0xFF
	SCL_L;										//SCL拉低
	SDA_L;										//SDA拉低
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SCL_H;										//SCL拉高
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SCL_L;										//SCL拉低
}
/**
* @brief	向从机发送非应答信号
* @details	在SDA持续为高时,SCL产生一个正脉冲即表示产生一个非应答信号
* @param	None
* @retval	None
*/
void I2C_SendNAck(void) {
	SDA_H;										//SDA拉低
	SCL_L;										//SCL拉低
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SCL_H;										//SCL拉高
	I2C_Delay_Us(I2C_Delay_Times);				//延时
	SCL_L;										//SCL拉低
}
/**
* @brief	读取SDA线上的数据
* @details	将SDA拉高后,不断产生正脉冲后读取SDA的高低,即为从机发送的数据
* @param	None
* @retval	readByte:读到的一字节数据
*/
uint8_t I2C_ReadByte(void) {
	uint8_t readByte = 0;
	SDA_H;
	for (uint8_t i = 0; i < 8; i++) {
		SCL_L;										//SCL拉低
		I2C_Delay_Us(I2C_Delay_Times);				//延时
		SCL_H;										//SCL拉高
		readByte <<= 1;
		if (SDA_R) {
			readByte++;
		}
		I2C_Delay_Us(I2C_Delay_Times);				//延时
	}
	return readByte;
}

I2C的h文件

#ifndef __I2C_H
#define __I2C_H

#include "stdint.h"
//可以将I2C的微秒延时函数替换成其他自定义的延时函数
#define I2C_Delay_Us   My_UsDelay
#define I2C_Delay_Ms   My_MsDelay
#define I2C_Delay_S   My_SDelay

#define I2C_Fail 0
#define I2C_Succeed 1

//定义SCL输出的宏定义
#define SCL_H HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port,I2C1_SCL_Pin,GPIO_PIN_SET)
#define SCL_L HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port,I2C1_SCL_Pin,GPIO_PIN_RESET)
//定义SDA输出、读取的宏定义
#define SDA_H HAL_GPIO_WritePin(I2C1_SDA_GPIO_Port,I2C1_SDA_Pin,GPIO_PIN_SET)
#define SDA_L HAL_GPIO_WritePin(I2C1_SDA_GPIO_Port,I2C1_SDA_Pin,GPIO_PIN_RESET)
#define SDA_R HAL_GPIO_ReadPin(I2C1_SDA_GPIO_Port,I2C1_SDA_Pin)



//定义I2C相关函数
void I2C_Start(void);
void I2C_Stop(void); 
void I2C_SendByte(uint8_t sendByte);
uint8_t I2C_WaitAck(uint16_t timeout);
void I2C_SendAck(void);
void I2C_SendNAck(void);
uint8_t I2C_ReadByte(void);

#endif /* __I2C_H */

SHT20的c文件

#include "main.h"
#include "i2c.h"
#include "sht20.h"
#include "usart.h"
#include "string.h"

//开启调试打印
#define PRINTF_SHT20_INFO 

/*===========================================================
* @name		void SHT20_Call(void)
* @brief	测试通讯正常与否
* @details	发送IIC起始信号和SHT20的地址,SHT20会做出相应
* @param	None
* @retval	None
===========================================================*/
void SHT20_Call(void) {
	I2C_Start();
	uint8_t addr = SHT20_ADDRESS << 1;
	I2C_SendByte(addr);//写地址
	if (I2C_WaitAck(2000)) {//等待2毫秒,从机发送应答信号
		I2C_Stop();
#ifdef PRINTF_SHT20_INFO
		uint8_t info[] = "sht20应答成功\r\n";
		HAL_UART_Transmit(&huart1, info, sizeof(info) - 1, 0xffff);
#endif//PRINTF_SHT20_INFO
	}
	else {
#ifdef PRINTF_SHT20_INFO
		uint8_t info[] = "sht20应答失败\r\n";
		HAL_UART_Transmit(&huart1, info, sizeof(info) - 1, 0xffff);
#endif//PRINTF_SHT20_INFO
	}
}
/*===========================================================
* @name		float SHT20_Measure_Humidity(void)
* @brief	测量相对湿度
* @details	调用测量函数,向测量函数传入参数'H'表示测量相对湿度
* @param	None
* @retval	float类型的测量结果
===========================================================*/
float SHT20_Measure_Humidity(void) {
	return SHT20_Measure('H');
}
/*===========================================================
* @name		float SHT20_Measure_Temperature(void)
* @brief	测量温度
* @details	调用测量函数,向测量函数传入参数'T'表示测量温度
* @param	None
* @retval	float类型的测量结果
===========================================================*/
float SHT20_Measure_Temperature(void) {
	return SHT20_Measure('T');
}
/*===========================================================
* @name		float SHT20_Measure(char MeasureTorH)
* @brief	测量函数
* @details	通过接受参数测量相对湿度或温度
* @param	MeasureTorH:'H'表示测量相对湿度,'T'表示温度
* @retval	float类型的测量结果
===========================================================*/
float SHT20_Measure(char MeasureTorH) {
	//SHT20_Reset();//可以注释掉,非必须
	//I2C_Delay_S(2);//可以注释掉,非必须

	I2C_Start();//开始
	I2C_SendByte(SHT20_ADDRESS_W);//写地址

	if (I2C_WaitAck(50000)) {
		if (MeasureTorH == 'H') {//如果是测量相对湿度
			I2C_SendByte(SHT20_N_RH_CMD);
		}
		else {//如果是测量温度
			I2C_SendByte(SHT20_N_T_CMD);
		}

		if (I2C_WaitAck(50000)) {
			I2C_Start();
			I2C_SendByte(SHT20_ADDRESS_R);
			while (!I2C_WaitAck(50000))
			{
				I2C_Start();//开始信号
				I2C_SendByte(SHT20_ADDRESS_R);//写地址
			}//循环等待测量结束
			I2C_Delay_Ms(70);
			char RHresult[3] = { 0 };
			RHresult[0] = I2C_ReadByte();//MSB
			I2C_SendAck();
			RHresult[1] = I2C_ReadByte();//LSB
			I2C_SendAck();
			RHresult[2] = I2C_ReadByte();//校验和
			I2C_SendNAck();
			I2C_Stop();
			if (!SHT20_CheckSum_CRC8(RHresult)) {//进行CRC8校验
#ifdef PRINTF_SHT20_INFO
				SHT20_Print("CRC8检验失败");
#endif//PRINTF_SHT20_INFO
			}
			else if (MeasureTorH == 'H') {
				return SHT20_Calculate('H', (RHresult[0] << 8) + RHresult[1]);//进行换算
			}
			else {
				return SHT20_Calculate('T', (RHresult[0] << 8) + RHresult[1]);//进行换算
			}
		}
		else {
#ifdef PRINTF_SHT20_INFO
			SHT20_Print("I2C通讯异常");
#endif//PRINTF_SHT20_INFO
			return I2C_Fail;
		}
	}
	else {
#ifdef PRINTF_SHT20_INFO
		SHT20_Print("I2C通讯异常");
#endif//PRINTF_SHT20_INFO
		return I2C_Fail;
	}

	return I2C_Fail;

}
/*===========================================================
* @name		void SHT20_Reset(void)
* @brief	SHT20软复位函数
* @details	通过写入软复位命令进行复位
* @param	None
* @retval	None
===========================================================*/
void SHT20_Reset(void) {
	unsigned char addr = 0;
	addr = SHT20_ADDRESS << 1;
	I2C_Start();
	I2C_SendByte(addr);
	if (!I2C_WaitAck(50000)) {
#ifdef PRINTF_SHT20_INFO
		SHT20_Print("SHT20软复位失败");
#endif//PRINTF_SHT20_INFO
	}
	I2C_SendByte(0xFE);
	if (!I2C_WaitAck(50000)) {
#ifdef PRINTF_SHT20_INFO
		SHT20_Print("SHT20软复位失败");
#endif//PRINTF_SHT20_INFO
	}
	I2C_Stop();
}
/*===========================================================
* @name		uint8_t SHT20_ReadRegister(void)
* @brief	读寄存器
* @details	读取SHT20的寄存器值
* @param	None
* @retval	ReadResult:返回当前SHT20寄存器的值
===========================================================*/
uint8_t SHT20_ReadRegister(void) {
	SHT20_Reset();
	I2C_Delay_S(2);
	I2C_Start();//开始
	I2C_SendByte(SHT20_ADDRESS_W);//写地址

	if (I2C_WaitAck(50000)) {
		I2C_SendByte(SHT20_READ_REG);
		if (I2C_WaitAck(50000)) {
			I2C_Start();
			I2C_SendByte(SHT20_ADDRESS_R);
			if (I2C_WaitAck(50000)) {
				uint8_t ReadResult = I2C_ReadByte();
				I2C_SendNAck();
				I2C_Start();
				I2C_SendByte(SHT20_ADDRESS_W);
				if (I2C_WaitAck(50000)) {
					I2C_SendByte(SHT20_WRITE_REG);
					if (I2C_WaitAck(50000)) {
						I2C_SendByte(ReadResult);
						if (I2C_WaitAck(50000)) {
							I2C_Stop();
							return ReadResult;
						}
					}
				}
			}
		}
	}
	return 0;
}
/*===========================================================
* @name		uint8_t SHT20_WriteRegister(uint8_t WaitWrite)
* @brief	写寄存器
* @details	读取SHT20的寄存器值
* @param	WaitWrite:写入寄存器的值
* @retval	写入成功返回1,失败返回0
===========================================================*/
uint8_t SHT20_WriteRegister(uint8_t WaitWrite) {
	SHT20_Reset();
	I2C_Delay_S(2);
	I2C_Start();//开始
	I2C_SendByte(SHT20_ADDRESS_W);//写地址

	if (I2C_WaitAck(50000)) {
		I2C_SendByte(SHT20_READ_REG);
		if (I2C_WaitAck(50000)) {
			I2C_Start();
			I2C_SendByte(SHT20_ADDRESS_R);
			if (I2C_WaitAck(50000)) {
				uint8_t ReadResult = I2C_ReadByte();
				I2C_SendNAck();
				I2C_Start();
				I2C_SendByte(SHT20_ADDRESS_W);
				if (I2C_WaitAck(50000)) {
					I2C_SendByte(SHT20_WRITE_REG);
					if (I2C_WaitAck(50000)) {
						I2C_SendByte(WaitWrite);
						if (I2C_WaitAck(50000)) {
							I2C_Stop();
							return 1;
						}
					}
				}
			}
		}
	}
	return 0;
}
/*===========================================================
* @name		uint8_t SHT20_CheckSum_CRC8(char* Result)
* @brief	CRC8检验
* @details	对测量结果的MSB和LSB进行校验,判断是否等于接收到的检验和
* @param	Result:测量结果所在数组的头指针
* @retval	检验成功返回1,失败返回0
===========================================================*/
uint8_t SHT20_CheckSum_CRC8(char* Result) {
	char data[2];
	data[0] = Result[0];
	data[1] = Result[1];

	uint32_t POLYNOMIAL = 0x131;
	char crc = 0;
	char bit = 0;
	char byteCtr = 0;

	//calculates 8-Bit checksum with given polynomial
	for (byteCtr = 0; byteCtr < 2; ++byteCtr)
	{
		crc ^= (data[byteCtr]);
		for (bit = 8; bit > 0; --bit)
		{
			if (crc & 0x80) crc = (crc << 1) ^ POLYNOMIAL;
			else crc = (crc << 1);
		}
	}
	if (crc == Result[2]) {
		return 1;
	}
	else {
		return 0;
	}

}
/*===========================================================
* @name		float SHT20_Calculate(char TorR, uint16_t data)
* @brief	相对湿度和温度的换算
* @details	根据接受到的数据进行换算
* @param	TorR:由'H'还是'T'选择进行相对湿度的换算还是温度的换算
* @param	data:测量结果
* @retval	float类型的换算结果,亦是最终结果
===========================================================*/
float SHT20_Calculate(char TorR, uint16_t data) {
	data &= 0xfffc;
	if (TorR == 'H') {
		return (data *125.0 / 65536.0) - 6;
	}
	else {
		return (data *175.72 / 65536.0) - 46.85;
	}
}

void SHT20_Print(char* str)
{
	uint16_t len = 0;
	char* str_temp = str;
	while (*str_temp++ != '\0') {
		len++;
	}
	HAL_UART_Transmit(&huart1, (uint8_t *)str, len, 0xffff);
	HAL_UART_Transmit(&huart1, (uint8_t *)("\r\n"), 2, 0xffff);
}

SHT20的h文件

#ifndef __SHT20_H
#define __SHT20_H

//SHT20地址
#define SHT20_ADDRESS  0x40
//SHT20写指令
#define SHT20_ADDRESS_W  0x80
//SHT20读指令
#define SHT20_ADDRESS_R  0x81
//读寄存器
#define SHT20_READ_REG  0xE7
//写寄存器
#define SHT20_WRITE_REG  0xE6
//非主机模式触发T(温度)测量、触发RH(湿度)测量
#define SHT20_N_T_CMD 0xF3
#define SHT20_N_RH_CMD 0xF5
//主机模式触发T(温度)测量、触发RH(湿度)测量
#define SHT20_T_CMD 0xE3
#define SHT20_RH_CMD 0xE5
/*测量分辨率 RH-T*/
//12bit-14bit
#define RH_T_12_14_Bit	0x00
//8bit-12bit
#define RH_T_8_12_Bit	0x01
//10bit-13bit
#define RH_T_10_13_Bit	0x80
//11bit-11bit
#define RH_T_11_11_Bit	0x81
/*测量分辨率 RH-T*/
//片上加热器开启(1)或关闭(0)
#define SHT20_HOT 0

#define SHT20_RH_WAIT_TIME_MS 29 
void SHT20_Call(void);
float SHT20_Measure(char MeasureTorH);
float SHT20_Measure_Humidity(void);
float SHT20_Measure_Temperature(void);
void SHT20_Reset(void);
uint8_t SHT20_ReadRegister(void);
uint8_t SHT20_WriteRegister(uint8_t WaitWrite);
uint8_t SHT20_CheckSum_CRC8(char* Result);
float SHT20_Calculate(char TorRH, uint16_t data);
void SHT20_Print(char* str);
#endif /* __SHT20_H */

最后是主函数while(1)中的测试函数,以打开、关闭SHT20的片上加热器为例

void SHT20_Test(void) {
	uint8_t ReadRegResult = SHT20_ReadRegister();
	if (ReadRegResult&(1 << 2)) {
		SHT20_Print("片上加热器已开启");
		if (SHT20_HOT_Temp == 0) {//若要关闭片上加热器
			SHT20_WriteRegister(ReadRegResult & 0xFB ); 
			SHT20_Print("片上加热器已关闭");
		}
	}
	else if (SHT20_HOT_Temp) {//若要打开片上加热器
		SHT20_WriteRegister(ReadRegResult | 0x04);
		SHT20_Print("片上加热器已开启");
	}
	I2C_Delay_Ms(70);
	//ReadRegResult = SHT20_ReadRegister();
	float RH_result = SHT20_Measure('H');
	float T_result = SHT20_Measure('T');
	if (RH_result) {
		char RH_reslutArray[64] = { 0 };
		sprintf(RH_reslutArray, "湿度是%.2f%%\r\n", RH_result);
		HAL_UART_Transmit(&huart1, (uint8_t *)RH_reslutArray, strlen(RH_reslutArray), 0xffff);
	}
	if (T_result) {
		char RH_reslutArray[64] = { 0 };
		sprintf(RH_reslutArray, "温度是%.2f度摄氏度\r\n", T_result);
		HAL_UART_Transmit(&huart1, (uint8_t *)RH_reslutArray, strlen(RH_reslutArray), 0xffff);
	}
}