最近将一些老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);
}
}
0条评论