STM32+HTU21D 获取温湿度数据

2024-06-18 21:08

本文主要是介绍STM32+HTU21D 获取温湿度数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

                                                              创作不易,转载请注明出处!

                              如果该文章对你有所帮助,请留下你的足迹在评论区,以资鼓励,谢谢!

/************************************************************************************************************/

平台:Keil MDK 5 +STM32F103c8t6

软件: C语言+ST标准固件库 3.5版

接口:软件模拟IIC, 开漏输出模式

/************************************************************************************************************/

一、引脚

    HTU21D引脚如下表所示,采用DFN封装,所以在绘制PCB时尽量将引脚焊盘向外多预留出1~2mm,有利于焊接。

  典型线路连接图

  1、采用两线制传输数据,SCK-时钟信号,SDA-数据,类似于IIC总线,手册中提到SCK、SDA线长最长不超过10cm,否则可能造成数据干扰或者丢失。

  2、HTU21D(F)传感器的电源电压必须在1.5VDC - 3.6VDC范围内。推荐的电源电压为3VDC(稳压),为了方便3.3V即可。

  3、单片机引脚设置为开漏模式下,需要在数据线SCK和SDA上加10K上拉电阻;引脚配置上拉模式可以不加。为了安全起见,建议外部加上拉电阻。

  4、VDD和GND之间的放置100nF去耦电容,并且尽可能靠近传感器。

  5、SCK时钟信号最大400KHz,一般使用软件模式,基本不会超。

  6、引脚初始化,将stm32引脚初始化为开漏模式,至于为什么设置为开漏模式,自行百度,“IIC为什么配置开漏模式  ”

以下是引脚初始化

#define HTU_I2C_SCL_OUT   PB_OUT(5)
#define HTU_I2C_SDA_OUT   PB_OUT(6)
#define HTU_I2C_SDA_IN    PB_IN(6)/*I2C引脚初始化函数*/
void HTU_I2CInit(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟       GPIO_SetBits(GPIOB, GPIO_Pin_5|GPIO_Pin_6);           //SCL和SDA初始输出高电平//先设置引脚电平可以避免IO初始化过程中可能产生的毛刺GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;  //选择SCL和SDA引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;      //选择开漏输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;     //输出速率10MHzGPIO_Init(GPIOB, &GPIO_InitStructure);
} 

注意:有些童鞋在使用软件模式IIC时,经常需要将SDA引脚频换切换引入与输出模式。由于STM32引脚结构,再此不需要设置为输入状态也能读取引脚数据,具体原因,有兴趣的同学可以查看STM32的引脚结构,一看便知,不再赘述。

二、HTU21D IIC 时序

HTU21D可以采用IIC总线进行操作,也就是可以使用标准IIC去读写。关于IIC总线,再此不在多讲,不会的自行百度即可,下面直接贴代码。

HTU21D的器件地址长度为7位(地址0x40,0b1000000), 通讯时,先发送起始位+7位地址+W/R位+ACK。
其中W/R位,0-表示写数据,1-表示读数据。

1、起始信号,启动传输时,必须发出一个起始位。在SCK为高时降低数据线,然后降低SCK。

/* 产生总线起始信号 */
void HTU_I2CStart(void)
{HTU_I2C_SDA_OUT = 1; //首先确保SDA、SCL都是高电平HTU_I2C_SCL_OUT = 1;delay_us(5);HTU_I2C_SDA_OUT = 0; //先拉低SDAdelay_us(5);HTU_I2C_SCL_OUT = 0; //再拉低SCL
}

2、停止信号,停止传输时,必须发出一个停止位。当SCK为高时,在SCK升高之前的SDA线拉高。

/* 产生总线停止信号 */
void HTU_I2CStop(void)
{HTU_I2C_SCL_OUT = 0; //首先确保SDA、SCL都是低电平HTU_I2C_SDA_OUT = 0;delay_us(5);HTU_I2C_SCL_OUT = 1; //先拉高SCLdelay_us(5);HTU_I2C_SDA_OUT = 1; //再拉高SDAdelay_us(5);
}

3、ACK应答

再此特别说明下ACK应答

0:表示从机应答,可以继续下一步操作;
1:表示从机非应答,不能进行下一步操作

由于从机接收到数据响应后会拉低数据线,也就是发送0 表示接收到数据。 因此主机在操作后读取ACK应答位,可以判断从机状态。为了符合正常逻辑

我们对读到的ACK位进行取反操作,也就是从机发送0 表示应答,但是通常 0表示 错误或者失败,所对0 进行取反 得到1,表示检测到应答位。反之,从机返回1,取反得 0,表示未检测到应答。

4、写入一个字节数据

/* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
u8 HTU_I2CWrite(u8 dat)
{int i;u8 ack;   //用于暂存应答位的值for (i=0; i<8; i++)  //循环将8bit数据输出到总线上{HTU_I2C_SDA_OUT = (dat&0x80) ? 1 : 0; //将最高位的值输出到SDA上delay_us(5);HTU_I2C_SCL_OUT = 1; //拉高SCLdelay_us(5);HTU_I2C_SCL_OUT = 0; //再拉低SCL,完成一个位周期dat <<= 1;       //左移将次高位变为最高位,实现高位在先低位在后的发送顺序}HTU_I2C_SDA_OUT = 1;  //8位数据发送完后,主机释放SDA,以检测从机应答delay_us(5);HTU_I2C_SCL_OUT = 1;  //拉高SCLack = HTU_I2C_SDA_IN; //读取此时的SDA值,即为从机的应答值delay_us(5);HTU_I2C_SCL_OUT = 0;  //再拉低SCL完成应答位,并保持住总线        delay_us(5);return (!ack); //应答值取反以符合通常的逻辑://0=不存在或忙或写入失败,1=存在且空闲或写入成功
}

5、读取一个字节数据

/* I2C总线读取8位数据,返回值-读到的字节 */
u8 HTU_I2CRead(void)
{int i;u8 dat = 0; //数据接收变量赋初值0HTU_I2C_SDA_OUT = 1;    //首先确保主机释放SDAfor (i=0; i<8; i++) //循环将总线上的8bit数据读入dat中{delay_us(5);HTU_I2C_SCL_OUT = 1;    //拉高SCLdat <<= 1;          //左移将己读到的位向高位移动,实现高位在先低位在后的接收顺序if(HTU_I2C_SDA_IN != 0) //读取SDA的值到dat最低位上{dat |= 0x01;    //SDA为1时设置dat最低位为1,SDA为0时无操作,即仍为初始值的0}delay_us(5);HTU_I2C_SCL_OUT = 0;    //再拉低SCL,以使从机发送出下一位}return dat;
}/* I2C总线读操作,并发送非应答信号,返回值-读到的字节 */
u8 HTU_I2CReadNAK(void)
{u8 dat;dat = HTU_I2CRead();  //读取8位数据HTU_I2C_SDA_OUT = 1;  //8位数据读取完后,拉高SDA,发送非应答信号delay_us(5);HTU_I2C_SCL_OUT = 1;  //拉高SCLdelay_us(5);HTU_I2C_SCL_OUT = 0;  //再拉低SCL完成非应答位,并保持住总线delay_us(5);return dat;
}/* I2C总线读操作,并发送应答信号,返回值-读到的字节 */
u8 HTU_I2CReadACK(void)
{u8 dat;dat = HTU_I2CRead();  //读取8位数据HTU_I2C_SDA_OUT = 0;  //8位数据读取完后,拉低SDA,发送应答信号delay_us(5);HTU_I2C_SCL_OUT = 1;  //拉高SCLdelay_us(5);HTU_I2C_SCL_OUT = 0;  //再拉低SCL完成应答位,并保持住总线delay_us(5);return dat;

上面为 No Hold Master模式下的发送指令读取温度数据的时序。

写数据流程:发送起始位 >>  写入0x80(器件地址0x40+写模式0, 0b10000000)  >> 检测ACK应答  >> 写入数据  >>检测ACK应答 >> 直至写完所有数据

读数据流程  发送起始位 >>  写入0x81(器件地址0x40+读模式1, 0b10000001)  >> 检测ACK应答  >> 读取数据  >>检测ACK应答 >> 最后1字节数据不需要检测ACK应答。

注意:读去温湿度数据时,如果如果写入0x81 ,没有检测到ACK应答 说明传感器内部未转换完毕。

6、写入多个字节数据

* HTU写数据 ;pbdata-数据指针,len-数据长度*/
u8 HTU_WriteBytes(u8 *pbdata, u8 len)
{u8 res = 0;u8 i = 0;u8 num = 0;do { //用寻址操作查询当前是否可进行读写HTU_I2CStart();if (HTU_I2CWrite(0x40<<1)) //寻址器件,写操作,应答则跳出循环,否则继续查询{break;}HTU_I2CStop();if(num++ > 100) //连续100次读取失败{return 0; //未查找到器件}delay_us(5);} while(1);	 	for (i=0; i<len; i++)//循环写入多个字节数据{res = HTU_I2CWrite(pbdata[i]);if (res == 0)//写入失败{HTU_I2CStop();return 0;}}HTU_I2CStop();return 1; //写入成功}

7、读取多个字节数据

/* HTU读数据 pbdata-数据指针,len-数据长度*/
u8 HTU_ReadBytes(u8 *pbdata, u16 len)
{u8 res = 0;u8 i = 0;u8 num = 0;do { //用寻址操作查询当前是否可进行读写HTU_I2CStart();if (HTU_I2CWrite(0x40<<1 | 0x01)) //寻址器件,读操作,读操作应答则跳出循环,否则继续查询{break;}HTU_I2CStop();if(num++ > 100) //连续100次读取失败{return 0; //寻址失败,未发现器件}delay_us(5);} while(1);	for (i=0; i<len-1; i++){*pbdata++ = HTU_I2CReadACK(); //写入数据}*pbdata  = HTU_I2CReadNAK(); //最后1字节数据非应答HTU_I2CStop();return 1;
}

三、HTU21D驱动函数

传感器通信有两种不同的操作模式:保持主模式和不保持主模式。

保持主模式,SCK线在测量过程中被阻塞(由HTU21D(F)传感器控制),也就是IIC总线只能挂在一个HTU21D传感器 ,不能挂载多个IIC器件

不保持主模式下,SCK线在传感器处理测量过程中保持开放进行其他通信。可以挂载多个IIC器件。通常我们选择不保持主模式

下面是HTU21D的操作指令

#define HTDU21D_ADDRESS              0x40  //Unshifted 7-bit I2C address for the sensor
#define HOLD_TEMP_MEASURE            0xE3
#define HOLD_HUMD_MEASURE            0xE5
#define NOHOLD_TEMP_MEASURE          0xF3
#define NOHOLD_HUMI_MEASURE          0xF5
#define WRITE_USER_REG               0xE6
#define READ_USER_REG                0xE7
#define SOFT_RESET                   0xFE

 

1、上电,上电后需要等待15ms,传感器从休眠状态唤醒

2、软件复位,时序如下所示:

   主机写入0xFE,进行软件复位。

/* 软件复位 */
void HTU_Reset(void)
{u8 num = 0;do { //用寻址操作查询当前是否可进行读写HTU_I2CStart();if (HTU_I2CWrite(0x40<<1)) //寻址器件,应答则跳出循环,否则继续查询{break;}HTU_I2CStop(); if(num++ > 100){break; //寻址失败,跳出}} while(1);	HTU_I2CWrite(SOFT_RESET);		//软复位HTU_I2CStop();delay_ms(15);		 //软复位时间最多需要15ms
}

2、用户寄存器

HTU21D拥有一个8位的User register 用于控制传感器状态。寄存器默认值为 0x01,

寄存器各个数据为信息如下表所示:


 

   (1)、设置User register 

   设置寄存器需要发送0xE6 指令,之后写入要设置的数据即可,写User register时序如下:

/* 设置HTU21D 用户寄存器 */
u8 HTU_Set_UserReg(u8 data)
{u8 cmd[2];cmd[0] = WRITE_USER_REG;cmd[1] = data;HTU_WriteBytes(cmd, 2);//发送写寄存器指令和待写入数据
}

 (2)、读取User register 

读取寄存器需要发送0xE5 指令,之后读取数据即可,读User register时序如下:

/* 读取HTU21D 用户寄存器 */
u8 HTU_Read_UserReg(void)
{u8 cmd = READ_USER_REG;u8 data = 0;HTU_WriteBytes(&cmd, 1);//发送读寄存器指令HTU_ReadBytes(&data, 1);//读取寄存器数据return data; //返回寄存器数据
}

3、读取温度和湿度数据

读取温湿度数据与操作User register  一样需要先发送操作指令,高速传感器要读取说明数据。由于HTU21D分为HOLD和 NO HOLD 两种模式,因此每种数据对应两种指令。

本文以No Hold Master 模式下操作。读取温度时发送 0xF3, 读取湿度时发送0xF5。

发送读取指令后,传感器输出3字节数据,数据内容为  14位数据+2位状态信息+8位CRC校验

 数据:占14位字节,数据按照MSB(高位在前,低位在后)

 状态信息:占2字节,高字节表示数据状态, 0-温度 1-湿度;低字节保留,默认为0

CRC:占8字节,按照X8 + X5 + X4 + 1模式编码,

关于CRC校验信息可以参考下面链接 https://blog.csdn.net/zjli321/article/details/52998468

读数据时序如下:

获得数据后还要根据官方文档提供的公式转换为温湿度值,温湿度转换公式如下:

 

(1)读取温度数据

/* 获取温度数据 */
u8 HTU_Get_NoHold_Temp(float *t)
{u8 cmd = 0;u8 num = 0;u8 data[3]={0};u8 crc = 0;u16 buf = 0;cmd = NOHOLD_TEMP_MEASURE;HTU_WriteBytes(&cmd, 1); //发送读取温度命令delay_ms(50);while(HTU_ReadBytes(data, 3)) //读取转换数据和校验数据{if(num++ >100){return 0; //读取失败}}crc = Get_CRC8(data, 2); //根据读取的数据生产CRC校验码if(crc != data[2]) //CRC校验错误{return 0; //数据无效}buf = data[0]<<8 | data[1]; //获取温度数据*t = (float)(175.72f * buf /65535.0f -46.85f);return 1;
}

(2)获取湿度数据

/* 获取湿度数据 */
u8 HTU_Get_NoHold_Humi(float *h)
{u8 cmd = 0;u8 num = 0;u8 data[3]={0};u8 crc = 0;u16 buf = 0;cmd = NOHOLD_HUMI_MEASURE;HTU_WriteBytes(&cmd, 1); //发送读取湿度命令delay_ms(50);while(HTU_ReadBytes(data, 3)) //读取转换数据和校验数据{if(num++ >100){return 0; //读取失败}}crc = Get_CRC8(data, 2); //根据读取的数据生产CRC校验码if(crc != data[2]) //CRC校验错误{return 0; //数据无效}buf = data[0]<<8 | data[1]; //获取温度数据*h = (float)(125.0f * buf /65535.0f - 6);return 1;
}

(3)获取温湿度数据

/* 获取温湿度数据 */
void Get_HTUData(float *temp, float *humi)
{HTU_Get_NoHold_Temp(temp);HTU_Get_NoHold_Humi(humi);
} 

 

 

4、读取数据是对数据进行了CRC校验,这里也可以不用判断CRC校验,为了安全起见,建议增加CRC校验。

一下是CRC校验代码

static const unsigned char crc_table[] =
{0x00,0x31,0x62,0x53,0xc4,0xf5,0xa6,0x97,0xb9,0x88,0xdb,0xea,0x7d,0x4c,0x1f,0x2e,0x43,0x72,0x21,0x10,0x87,0xb6,0xe5,0xd4,0xfa,0xcb,0x98,0xa9,0x3e,0x0f,0x5c,0x6d,0x86,0xb7,0xe4,0xd5,0x42,0x73,0x20,0x11,0x3f,0x0e,0x5d,0x6c,0xfb,0xca,0x99,0xa8,0xc5,0xf4,0xa7,0x96,0x01,0x30,0x63,0x52,0x7c,0x4d,0x1e,0x2f,0xb8,0x89,0xda,0xeb,0x3d,0x0c,0x5f,0x6e,0xf9,0xc8,0x9b,0xaa,0x84,0xb5,0xe6,0xd7,0x40,0x71,0x22,0x13,0x7e,0x4f,0x1c,0x2d,0xba,0x8b,0xd8,0xe9,0xc7,0xf6,0xa5,0x94,0x03,0x32,0x61,0x50,0xbb,0x8a,0xd9,0xe8,0x7f,0x4e,0x1d,0x2c,0x02,0x33,0x60,0x51,0xc6,0xf7,0xa4,0x95,0xf8,0xc9,0x9a,0xab,0x3c,0x0d,0x5e,0x6f,0x41,0x70,0x23,0x12,0x85,0xb4,0xe7,0xd6,0x7a,0x4b,0x18,0x29,0xbe,0x8f,0xdc,0xed,0xc3,0xf2,0xa1,0x90,0x07,0x36,0x65,0x54,0x39,0x08,0x5b,0x6a,0xfd,0xcc,0x9f,0xae,0x80,0xb1,0xe2,0xd3,0x44,0x75,0x26,0x17,0xfc,0xcd,0x9e,0xaf,0x38,0x09,0x5a,0x6b,0x45,0x74,0x27,0x16,0x81,0xb0,0xe3,0xd2,0xbf,0x8e,0xdd,0xec,0x7b,0x4a,0x19,0x28,0x06,0x37,0x64,0x55,0xc2,0xf3,0xa0,0x91,0x47,0x76,0x25,0x14,0x83,0xb2,0xe1,0xd0,0xfe,0xcf,0x9c,0xad,0x3a,0x0b,0x58,0x69,0x04,0x35,0x66,0x57,0xc0,0xf1,0xa2,0x93,0xbd,0x8c,0xdf,0xee,0x79,0x48,0x1b,0x2a,0xc1,0xf0,0xa3,0x92,0x05,0x34,0x67,0x56,0x78,0x49,0x1a,0x2b,0xbc,0x8d,0xde,0xef,0x82,0xb3,0xe0,0xd1,0x46,0x77,0x24,0x15,0x3b,0x0a,0x59,0x68,0xff,0xce,0x9d,0xac
};/* 获取CRC8校验 */
u8 Get_CRC8(u8 *ptr, u8 len) 
{u8  crc = 0x00;while (len--){crc = crc_table[crc ^ *ptr++];}return (crc);
}

 

至此,结束!

参考:

HTU21D官方文档

crc校验算法, 链接 https://blog.csdn.net/zjli321/article/details/52998468

 

 

如有错误,请联系更改。

 

这篇关于STM32+HTU21D 获取温湿度数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1073061

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

pandas数据过滤

Pandas 数据过滤方法 Pandas 提供了多种方法来过滤数据,可以根据不同的条件进行筛选。以下是一些常见的 Pandas 数据过滤方法,结合实例进行讲解,希望能帮你快速理解。 1. 基于条件筛选行 可以使用布尔索引来根据条件过滤行。 import pandas as pd# 创建示例数据data = {'Name': ['Alice', 'Bob', 'Charlie', 'Dav

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者