AHT10温湿度传感器I2C驱动移植与数据采集实战(基于立创开发板)
AHT10温湿度传感器I2C驱动移植与数据采集实战基于立创开发板最近在做一个环境监测的小项目需要用到温湿度传感器。选来选去最终敲定了AHT10这款传感器。它体积小、精度高关键是采用I2C接口接线简单非常适合咱们嵌入式开发。今天我就以立创开发板主控为瑞萨R7FA6E2BB3CNE为例手把手带你从零开始把AHT10的驱动移植到你的工程里并成功读取到温湿度数据。整个过程我会拆解成几个清晰的步骤先了解传感器再分析通信协议接着配置硬件引脚然后编写底层I2C时序模拟代码最后完成数据读取和换算。即使你是第一次接触I2C或者AHT10跟着做下来也一定能搞定。1. 认识AHT10你的数字环境感知器在动手写代码之前咱们先花几分钟了解一下AHT10到底是个啥。这能帮你更好地理解后续的通信流程。AHT10是一款集成了温度和湿度测量功能的数字传感器。它内部有一个专门处理信号的芯片ASIC、一个电容式的湿度传感元件和一个温度传感元件。它最大的好处是直接输出校准好的数字信号咱们的MCU通过I2C总线读取就行省去了模拟信号放大、AD转换的麻烦。它的几个关键参数在选型和设计电路时很重要工作电压1.8V ~ 3.6V。这意味着它可以直接用咱们开发板的3.3V供电非常方便。测量范围与精度湿度测量范围0-100%RH典型误差±2%RH。温度测量范围-40℃ ~ 85℃典型误差±0.3℃。通信接口标准I2C。这是我们今天重点要搞定的部分。封装非常小巧只有4mm x 5mm适合集成到各种产品中。简单来说AHT10就是一个通过I2C“报数”的传感器我们MCU的任务就是按照正确的“暗号”协议去问它然后听懂它回复的“数字密码”原始数据并翻译成我们能理解的温度和湿度值。2. 硬件连接与引脚配置硬件连接是第一步也是最简单的一步。AHT10模块通常只有三个引脚VCC, GND, SDA, SCL我们需要把它正确地接到开发板上。根据原始资料连接关系如下AHT10模块引脚立创开发板对应引脚VCC3V3GNDGNDSCLP408SDAP409注意这里使用的是软件模拟I2C即用两个普通的GPIO口来模拟I2C的时序所以SCL和SDA可以接到任意两个空闲的GPIO上不一定是硬件I2C外设专用的引脚。我们例子中用的是P408和P409。接下来我们需要在开发环境里配置这两个引脚。如果你使用的是立创开发板配套的图形化配置工具如RASC操作步骤如下打开引脚配置界面。找到P408和P409这两个引脚。将它们都配置为**通用输出Output**模式。因为模拟I2C需要MCU主动控制引脚输出高低电平。记得按下Ctrl S保存配置。点击生成代码按钮将配置更新到你的工程中。这样硬件和底层引脚初始化就准备好了。3. 编写驱动从I2C时序到数据解析这是最核心的部分。我们将创建两个文件bsp_aht10.c和bsp_aht10.h把所有的驱动代码都放在里面。3.1 头文件定义与宏首先来看头文件bsp_aht10.h。这里主要定义了一些类型别名、延时宏、引脚宏以及最重要的——引脚操作宏。#ifndef BSP_CODE_BSP_AHT10_H_ #define BSP_CODE_BSP_AHT10_H_ #include hal_data.h #include stdio.h // 一些常用的类型定义让代码更简洁 #ifndef u8 #define u8 uint8_t #endif #ifndef u16 #define u16 uint16_t #endif #ifndef u32 #define u32 uint32_t #endif // 延时函数宏直接调用开发板提供的软件延时函数 #ifndef delay_ms #define delay_ms(x) R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MILLISECONDS) #endif #ifndef delay_us #define delay_us(x) R_BSP_SoftwareDelay(x, BSP_DELAY_UNITS_MICROSECONDS) #endif // 定义我们使用的SCL和SDA引脚 #define Module_SCL_PIN BSP_IO_PORT_04_PIN_08 // SCL 对应 P408 #define Module_SDA_PIN BSP_IO_PORT_04_PIN_09 // SDA 对应 P409 // 关键SDA引脚方向控制宏 // SDA在通信过程中需要在输出主机发送和输入主机接收模式间切换 #define SDA_IN() { \ fsp_err_t err R_IOPORT_PinCfg(g_ioport_ctrl, Module_SDA_PIN, \ (uint32_t) IOPORT_CFG_PORT_DIRECTION_INPUT); \ if(err ! FSP_SUCCESS) { \ printf(SDA设置为输入模式失败!\r\n); \ } \ } #define SDA_OUT() { \ fsp_err_t err R_IOPORT_PinCfg(g_ioport_ctrl, Module_SDA_PIN, \ ((uint32_t) IOPORT_CFG_DRIVE_HIGH \ | (uint32_t) IOPORT_CFG_NMOS_ENABLE \ | (uint32_t) IOPORT_CFG_PORT_DIRECTION_OUTPUT \ | (uint32_t) IOPORT_CFG_PORT_OUTPUT_HIGH)); \ if(err ! FSP_SUCCESS) { \ printf(SDA设置为输出模式失败!\r\n); \ } \ } // SCL和SDA引脚输出电平控制 #define SCL(BIT) R_IOPORT_PinWrite(g_ioport_ctrl, Module_SCL_PIN, BIT) #define SDA(BIT) R_IOPORT_PinWrite(g_ioport_ctrl, Module_SDA_PIN, BIT) // 读取SDA引脚输入电平 static inline bsp_io_level_t SDA_GET(void) { bsp_io_level_t p_pin_value; fsp_err_t err R_IOPORT_PinRead(g_ioport_ctrl, Module_SDA_PIN, p_pin_value); if(err ! FSP_SUCCESS) { printf(读取SDA引脚电平失败!\r\n); } return p_pin_value; } // 对外提供的函数声明 void AHT10_Init(void); uint8_t AHT10_Get_TempHumi(float *Temperature, float *Humidity); #endif /* BSP_CODE_BSP_AHT10_H_ */这里需要特别关注SDA_IN()和SDA_OUT()这两个宏。因为I2C协议中SDA线是双向的主机在发送数据时要控制它输出在接收数据时要把它设置为输入以读取从机电平。很多初学者调试不通问题就出在忘了切换SDA的方向。3.2 模拟I2C底层时序I2C通信有一套严格的时序规则就像两个人对话要遵守的礼节。我们用GPIO模拟就要用代码精确地再现这些时序。这些函数是驱动的基础写在bsp_aht10.c里。起始信号StartSCL为高电平时SDA产生一个下降沿。这告诉总线上的所有设备“主机要开始通信了”。static void IIC_Start(void) { SDA_OUT(); // 先确保SDA是输出模式 SDA(1); SCL(1); delay_us(4); // 保持一段时间确保信号稳定 SDA(0); // 在SCL高电平期间拉低SDA产生起始条件 delay_us(4); SCL(0); // 拉低SCL为后续发送数据位做准备 }停止信号StopSCL为高电平时SDA产生一个上升沿。表示“本次通信结束”。static void IIC_Stop(void) { SDA_OUT(); SCL(0); SDA(0); delay_us(4); SCL(1); // 先将SCL拉高 SDA(1); // 然后在SCL高电平期间拉高SDA产生停止条件 delay_us(4); }发送一个字节数据在SCL低电平期间变化在SCL高电平期间保持稳定从最高位MSB开始发送。static void Send_Byte(uint8_t dat) { int i 0; SDA_OUT(); SCL(0); // 拉低时钟开始数据传输 for( i 0; i 8; i ) { // 取出最高位判断是1还是0然后设置SDA电平 SDA( (dat 0x80) 7 ); delay_us(1); SCL(1); // 拉高SCL从机在此时刻采样SDA数据 delay_us(2); SCL(0); // 拉低SCL为发送下一位做准备 delay_us(2); dat1; // 数据左移准备发送下一位 } }接收一个字节主机控制SCL产生时钟在SCL高电平期间读取SDA线上的数据。static uint8_t Read_Byte(void) { unsigned char i, receive0; SDA_IN(); // 非常重要接收前要把SDA设置为输入模式 for(i0;i8;i ) { SCL(0); delay_us(2); SCL(1); // 拉高SCL此时从机将数据放到SDA线上 delay_us(2); receive1; // 左移为接收新数据位腾出空间 if( SDA_GET() ) // 读取SDA电平 { receive|1; // 如果为高电平则该位为1 } delay_us(1); } SCL(0); return receive; }应答ACK与等待应答I2C协议规定接收方在收到一个字节后需要发送一个应答信号ACK拉低SDA。I2C_WaitAck函数就是主机发送完地址或命令后等待从机AHT10回应的过程。static unsigned char I2C_WaitAck(void) { char ack 0; unsigned char ack_flag 10; // 超时计数 SDA(1); delay_us(1); SCL(1); delay_us(1); SDA_IN(); // 切换为输入准备读取从机的应答信号 delay_us(2); // 循环等待SDA被从机拉低应答 while( (SDA_GET()1) ( ack_flag ) ) { ack_flag--; delay_us(3); } if( ack_flag 0 ) // 超时未收到应答 { IIC_Stop(); return 1; // 返回错误 } else { SCL(0); SDA_OUT(); // 等待完毕切回输出模式 } SDA(0); return ack; // 返回0表示成功收到应答 }把这些底层时序函数理解透彻I2C通信就掌握了一大半。它们就像乐高积木后面复杂的读写操作都是由它们组合而成的。3.3 AHT10的初始化与数据读取有了基础的I2C通信能力我们就可以和AHT10“对话”了。首先需要初始化传感器。器件地址AHT10的固定I2C地址是0x38。但在I2C协议中实际发送的地址字节是7位地址加上1位读写方向位。所以我们需要将0x38左移一位得到0x70。当最后一位为0时表示写为1时表示读。初始化函数主要是发送一个校准命令让传感器做好测量准备。void AHT10_Init(void) { // 先拉高SCL和SDA让总线处于空闲状态 SCL(1); SDA(1); delay_ms(100); // 上电后等待传感器稳定 // 发送初始化校准命令序列0xE1, 0x08, 0x00 IIC_Start(); Send_Byte(0x70); // 器件地址 写 I2C_WaitAck(); Send_Byte(0xe1); // 初始化命令 I2C_WaitAck(); Send_Byte(0x08); I2C_WaitAck(); Send_Byte(0x00); I2C_WaitAck(); IIC_Stop(); delay_ms(50); // 等待校准完成 }核心数据读取函数这个过程稍复杂我结合代码和注释一步步说。static uint8_t AHT10_Read(float *Temperature, float *Humidity) { char timeout 0; unsigned char buff[6] {0}; // 用于存放读取到的6个字节原始数据 // 1. 启动测量 IIC_Start(); Send_Byte(0X381 | 0); // 发送器件地址写命令 (0x70) I2C_WaitAck(); Send_Byte(0XAC); // 发送触发测量命令 I2C_WaitAck(); Send_Byte(0X33); // 命令参数1 I2C_WaitAck(); Send_Byte(0X00); // 命令参数2 I2C_WaitAck(); IIC_Stop(); // 发送完启动命令先停止 // 2. 等待测量完成AHT10需要时间进行测量 do{ delay_ms(1); timeout; // 重新发起通信尝试读取状态字 IIC_Start(); Send_Byte(0X381 | 1); // 器件地址读命令 }while( I2C_WaitAck() 1 timeout 5 ); // 如果设备忙无应答则重试 if(timeout 5) // 超时 return 1; // 3. 连续读取6个字节数据 buff[0] Read_Byte(); // 第1字节状态字 IIC_Send_Ack(0); // 主机发送应答(ACK) buff[1] Read_Byte(); // 第2字节湿度高8位 IIC_Send_Ack(0); buff[2] Read_Byte(); // 第3字节湿度低8位 IIC_Send_Ack(0); buff[3] Read_Byte(); // 第4字节湿度低4位 温度高4位 IIC_Send_Ack(0); buff[4] Read_Byte(); // 第5字节温度中8位 IIC_Send_Ack(0); buff[5] Read_Byte(); // 第6字节温度低8位 IIC_Send_Ack(1); // 最后一个字节主机发送非应答(NACK)通知从机停止发送 IIC_Stop(); delay_ms(20); // 4. 数据换算这是关键 // AHT10的湿度数据是20位的存储在buff[1], buff[2], buff[3]的高4位 uint32_t humidity_raw ( (uint32_t)buff[1] 12 ) | ( (uint32_t)buff[2] 4 ) | ( buff[3] 4 ); // 换算公式湿度(%) (原始值 / 2^20) * 100 *Humidity humidity_raw / 1048576.0f * 100.0f; // 2^20 1048576 // AHT10的温度数据也是20位的存储在buff[3]的低4位, buff[4], buff[5] uint32_t temp_raw ( (uint32_t)(buff[3] 0x0F) 16 ) | ( (uint32_t)buff[4] 8 ) | buff[5]; // 换算公式温度(℃) (原始值 / 2^20) * 200 - 50 *Temperature (temp_raw / 1048576.0f) * 200.0f - 50.0f; // 5. 可选进行一次软复位为下次测量做准备 AHT10Reset(); AHT10_Init(); return 0; }数据换算部分是理解传感器输出的核心。AHT10将温湿度模拟量转换成了一个20位的数字量。公式虽然看起来有点复杂但记住1048576就是2的20次方代表满量程。湿度的范围是0-100%所以乘以100温度的量程是-50~150℃跨度200℃所以先按200倍缩放再减去50的偏移。最后我们封装一个更友好的函数供主程序调用uint8_t AHT10_Get_TempHumi(float *Temperature, float *Humidity) { float Temp_Temperature 0; float Temp_Humidity 0; if(AHT10_Read(Temp_Temperature, Temp_Humidity) ! 0) { printf(AHT10 读取错误!!\r\n); return 1; } if(Temperature ! NULL) { *Temperature Temp_Temperature; } if(Humidity ! NULL) { *Humidity Temp_Humidity; } return 0; }4. 实战验证在应用中读取数据驱动写好了最后一步就是把它用起来。在你的主应用程序文件例如app.c中包含头文件初始化传感器然后在循环中读取即可。#include bsp_aht10.h #include bsp_uart.h // 用于printf打印 #include stdio.h void Run(void) { // 初始化调试串口方便打印数据 UART0_Debug_Init(); printf(系统启动...\r\n); // 初始化AHT10传感器 AHT10_Init(); printf(AHT10 初始化成功\r\n); while(1) { float Temperature 0; float Humidity 0; // 调用函数获取温湿度 if(!AHT10_Get_TempHumi(Temperature, Humidity)) { printf(温度 %.2f ℃\r\n, Temperature); printf(湿度 %.2f %%\r\n, Humidity); } else { printf(读取温湿度失败\r\n); } delay_ms(1000); // 每秒读取一次 } }编译、下载程序到开发板打开串口调试助手波特率根据你的bsp_uart初始化配置而定你应该能看到每秒打印一次的温湿度数据了。5. 常见问题与调试心得第一次移植很可能不会一帆风顺这里分享几个我踩过的坑没有应答ACK这是最常见的问题。首先检查硬件连接VCC、GND是否接对SCL、SDA是否接反上拉电阻是否加上模块一般自带如果没有需要在SDA和SCL线上各接一个4.7kΩ上拉到3.3V。其次检查代码SDA_IN()和SDA_OUT()切换是否正确延时时间是否太短AHT10的地址0x70是否发送正确读取的数据全是0或255检查数据读取阶段的ACK/NACK发送顺序是否正确。读取最后一个字节后必须发送NACK然后发送停止信号。另外检查Read_Byte函数中SDA_IN()是否在循环前正确调用。数据值明显不对大概率是数据换算公式用错了。务必确认你读取的6个字节buff[0]到buff[5]分别对应什么数据并严格按照AHT10数据手册的公式计算。本文使用的公式是经过验证的。时序问题I2C对时序有一定要求但AHT10不算特别严格。如果通信不稳定可以适当增加delay_us中的延时参数尤其是SCL高电平期间的延时。整个移植过程其实就是理解协议、搭建底层、组装逻辑的过程。当你看到串口里稳定输出房间的温湿度时那种成就感就是嵌入式开发的乐趣所在。希望这篇教程能帮你顺利搞定AHT10如果你在移植中遇到其他问题不妨多看看时序波形用逻辑分析仪抓一下SDA和SCL的线和数据手册那才是解决问题的终极法宝。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2419375.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!