STM32FreeModbus实战用AHT20传感器搭建低成本温湿度监测从机附完整代码在工业物联网和智能家居领域温湿度监测是最基础也最普遍的需求之一。如何用最低的成本构建一个稳定可靠的监测节点本文将带你从零开始基于STM32F103C8T6俗称蓝莓派和AHT20温湿度传感器通过FreeModbus协议栈实现一个完整的Modbus RTU从机设备。不同于市面上常见的教程我们将重点关注实际工程中容易遇到的坑点并提供经过验证的完整解决方案。1. 硬件选型与方案设计1.1 核心硬件选择STM32F103C8T6最小系统板市场价格约10-15元作为主控具有以下优势Cortex-M3内核72MHz主频性能足够处理Modbus协议内置64KB Flash和20KB SRAM丰富的外设接口USART、I2C、TIM等广泛的社区支持和成熟的工具链AHT20温湿度传感器市场价格约5-8元的突出特点数字输出I2C接口±2%RH湿度精度±0.3℃温度精度低功耗典型待机电流0.2μA出厂校准无需额外校准电路1.2 系统架构设计整个系统的数据流如下图所示[温湿度传感器AHT20] ↓ I2C [STM32F103C8T6] ↓ USART(Modbus RTU) [上位机/网关设备]关键设计考虑采用Modbus RTU协议波特率115200可根据需求调整使用FreeModbus协议栈实现从机功能通过I2C接口每2秒读取一次传感器数据定义4个保持寄存器分别存储温度整数部分温度小数部分湿度整数部分湿度小数部分2. 开发环境搭建2.1 工具链准备推荐使用以下开发工具组合工具类型推荐选择备注IDESTM32CubeIDE 1.11.0免费且集成CubeMX调试器ST-Link V2兼容性好价格低廉串口工具Modbus PollModbus专用测试工具终端模拟Tera Term查看调试输出2.2 FreeModbus协议栈准备FreeModbus官方版本已停止维护推荐使用社区改进版git clone https://github.com/cwalter-at/freemodbus.git关键目录结构说明freemodbus ├── modbus # 协议栈核心代码 │ ├── include # 头文件 │ └── rtu # RTU模式实现 └── demo └── bare # 裸机移植示例 ├── port # 硬件相关移植层 └── demo.c # 应用示例提示建议将协议栈作为子模块加入项目便于后续更新维护。3. STM32CubeMX配置3.1 外设初始化关键外设配置参数时钟配置HSE晶振8MHz系统时钟72MHzAPB1分频2TIM3时钟36MHzUSART1配置Modbus通信模式Asynchronous波特率115200数据位8停止位1校验位NoneI2C1配置AHT20通信模式I2C时钟速度100kHz地址模式7-bitTIM3配置Modbus定时器时钟源Internal Clock预分频351MHz计数频率计数模式Up自动重装载50-1对应50μs时基3.2 中断配置确保以下中断已使能USART1全局中断TIM3全局中断I2C1事件中断DMA通道6/7中断如果使用DMANVIC优先级建议设置USART1_IRQn - 0 TIM3_IRQn - 1 I2C1_EV_IRQn - 24. FreeModbus移植关键代码4.1 串口驱动适配修改portserial.c实现硬件相关串口操作BOOL xMBPortSerialPutByte(CHAR ucByte) { if(HAL_UART_Transmit(huart1, (uint8_t*)ucByte, 1, 10) ! HAL_OK) return FALSE; return TRUE; } BOOL xMBPortSerialGetByte(CHAR *pucByte) { if(HAL_UART_Receive(huart1, (uint8_t*)pucByte, 1, 10) ! HAL_OK) return FALSE; return TRUE; } void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) { if(xRxEnable) __HAL_UART_ENABLE_IT(huart1, UART_IT_RXNE); else __HAL_UART_DISABLE_IT(huart1, UART_IT_RXNE); if(xTxEnable) __HAL_UART_ENABLE_IT(huart1, UART_IT_TXE); else __HAL_UART_DISABLE_IT(huart1, UART_IT_TXE); }4.2 定时器驱动适配修改porttimer.c实现Modbus要求的超时检测void vMBPortTimersEnable() { __HAL_TIM_SET_COUNTER(htim3, 0); __HAL_TIM_CLEAR_IT(htim3, TIM_IT_UPDATE); __HAL_TIM_ENABLE_IT(htim3, TIM_IT_UPDATE); __HAL_TIM_ENABLE(htim3); } void vMBPortTimersDisable() { __HAL_TIM_DISABLE(htim3); __HAL_TIM_DISABLE_IT(htim3, TIM_IT_UPDATE); }4.3 中断服务程序在stm32f1xx_it.c中添加Modbus相关中断处理void USART1_IRQHandler(void) { if(__HAL_UART_GET_IT_SOURCE(huart1, UART_IT_RXNE) ! RESET) prvvUARTRxISR(); if(__HAL_UART_GET_IT_SOURCE(huart1, UART_IT_TXE) ! RESET) prvvUARTTxReadyISR(); HAL_UART_IRQHandler(huart1); } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM3) prvvTIMERExpiredISR(); }5. 传感器驱动与数据采集5.1 AHT20初始化#define AHT20_ADDRESS 0x38 void AHT20_Init(void) { uint8_t cmd[3] {0xBE, 0x08, 0x00}; HAL_I2C_Master_Transmit(hi2c1, AHT20_ADDRESS1, cmd, 3, 100); HAL_Delay(50); cmd[0] 0x71; HAL_I2C_Master_Transmit(hi2c1, AHT20_ADDRESS1, cmd, 1, 100); HAL_Delay(350); }5.2 温湿度数据读取int AHT20_Read_CTdata(uint32_t *ctData) { uint8_t buf[6] {0}; uint8_t cmd 0xAC; HAL_I2C_Master_Transmit(hi2c1, AHT20_ADDRESS1, cmd, 1, 100); HAL_Delay(80); if(HAL_I2C_Master_Receive(hi2c1, AHT20_ADDRESS1, buf, 6, 100) ! HAL_OK) return -1; if(!(buf[0] 0x80)) { ctData[0] ((uint32_t)buf[1]12) | ((uint32_t)buf[2]4) | (buf[3]4); ctData[1] ((uint32_t)(buf[3]0x0F)16) | ((uint32_t)buf[4]8) | buf[5]; return 0; } return -2; }5.3 数据转换与寄存器映射在demo.c中实现Modbus寄存器回调eMBErrorCode eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs) { static uint32_t ctData[2]; static int16_t temp, humi; if(AHT20_Read_CTdata(ctData) 0) { humi ctData[0] * 1000 / 1048576; // 湿度值(放大10倍) temp ctData[1] * 2000 / 1048576 - 500; // 温度值(放大10倍) // 寄存器映射 usRegInputBuf[0] temp / 10; // 温度整数部分 usRegInputBuf[1] temp % 10; // 温度小数部分 usRegInputBuf[2] humi / 10; // 湿度整数部分 usRegInputBuf[3] humi % 10; // 湿度小数部分 } // 寄存器数据返回处理 for(int i0; iusNRegs; i) { *pucRegBuffer usRegInputBuf[usAddressi] 8; *pucRegBuffer usRegInputBuf[usAddressi] 0xFF; } return MB_ENOERR; }6. 系统集成与测试6.1 主程序流程int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); MX_TIM3_Init(); AHT20_Init(); HAL_Delay(1000); eMBInit(MB_RTU, 0x01, 1, 115200, MB_PAR_NONE); eMBEnable(); while(1) { eMBPoll(); HAL_Delay(100); } }6.2 Modbus Poll测试配置使用Modbus Poll测试时关键配置参数连接设置端口对应COM口波特率115200数据位8停止位1校验None读写定义功能码04 (Read Input Registers)从站地址1起始地址0寄存器数量46.3 常见问题排查问题1Modbus Poll显示Timeout检查接线是否正确RX-TX交叉确认波特率等参数一致检查FreeModbus初始化参数问题2读取数据全为0确认AHT20初始化成功检查I2C上拉电阻通常需要4.7kΩ验证传感器供电电压3.3V问题3通信不稳定降低波特率测试如改为9600检查线路长度RS485建议不超过1200米添加终端电阻120Ω7. 性能优化与扩展7.1 低功耗优化策略对于电池供电场景可实施以下优化采用间歇工作模式每5分钟唤醒一次关闭不必要的外设时钟使用STOP模式降低待机功耗优化FreeModbus响应超时缩短至100ms7.2 多传感器扩展通过I2C总线可扩展多个传感器硬件修改为每个传感器分配独立地址增加I2C缓冲器如PCA9548A扩展通道软件修改扩展寄存器映射范围实现传感器轮询机制#define SENSOR_NUM 3 uint8_t sensorAddr[SENSOR_NUM] {0x38, 0x39, 0x3A}; for(int i0; iSENSOR_NUM; i) { AHT20_SetAddress(sensorAddr[i]); AHT20_Read_CTdata(ctData[i][0]); }7.3 无线传输扩展通过串口连接无线模块如LoRa、NB-IoT硬件连接无线模块的RX/TX连接STM32的USART2共地连接注意电平匹配协议适配保持Modbus RTU协议不变增加无线模块的AT指令控制void SendViaLoRa(uint8_t *data, uint16_t len) { HAL_UART_Transmit(huart2, ATSEND, 8, 100); for(int i0; ilen; i) { char hex[3]; sprintf(hex, %02X, data[i]); HAL_UART_Transmit(huart2, (uint8_t*)hex, 2, 100); } HAL_UART_Transmit(huart2, \r\n, 2, 100); }在实际项目中这套方案已经成功应用于智能农业大棚监测系统连续运行6个月无故障。一个实用的经验是在AHT20数据读取之间至少保持1秒间隔过于频繁的读取会导致传感器内部温升影响精度。