当前位置: 首页 > news >正文

STM32+FreeModbus实战:用AHT20传感器搭建低成本温湿度监测从机(附完整代码)

STM32+FreeModbus实战:用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 外设初始化

关键外设配置参数:

  1. 时钟配置

    • HSE晶振:8MHz
    • 系统时钟:72MHz
    • APB1分频:2(TIM3时钟36MHz)
  2. USART1配置(Modbus通信)

    • 模式:Asynchronous
    • 波特率:115200
    • 数据位:8
    • 停止位:1
    • 校验位:None
  3. I2C1配置(AHT20通信)

    • 模式:I2C
    • 时钟速度:100kHz
    • 地址模式:7-bit
  4. TIM3配置(Modbus定时器)

    • 时钟源:Internal Clock
    • 预分频:35(1MHz计数频率)
    • 计数模式:Up
    • 自动重装载:50-1(对应50μs时基)

3.2 中断配置

确保以下中断已使能:

  • USART1全局中断
  • TIM3全局中断
  • I2C1事件中断
  • DMA通道6/7中断(如果使用DMA)

NVIC优先级建议设置:

USART1_IRQn -> 0 TIM3_IRQn -> 1 I2C1_EV_IRQn -> 2

4. 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_ADDRESS<<1, cmd, 3, 100); HAL_Delay(50); cmd[0] = 0x71; HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS<<1, 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_ADDRESS<<1, &cmd, 1, 100); HAL_Delay(80); if(HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS<<1, 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 i=0; i<usNRegs; i++) { *pucRegBuffer++ = usRegInputBuf[usAddress+i] >> 8; *pucRegBuffer++ = usRegInputBuf[usAddress+i] & 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测试时,关键配置参数:

  1. 连接设置

    • 端口:对应COM口
    • 波特率:115200
    • 数据位:8
    • 停止位:1
    • 校验:None
  2. 读写定义

    • 功能码:04 (Read Input Registers)
    • 从站地址:1
    • 起始地址:0
    • 寄存器数量:4

6.3 常见问题排查

问题1:Modbus Poll显示"Timeout"

  • 检查接线是否正确(RX-TX交叉)
  • 确认波特率等参数一致
  • 检查FreeModbus初始化参数

问题2:读取数据全为0

  • 确认AHT20初始化成功
  • 检查I2C上拉电阻(通常需要4.7kΩ)
  • 验证传感器供电电压(3.3V)

问题3:通信不稳定

  • 降低波特率测试(如改为9600)
  • 检查线路长度(RS485建议不超过1200米)
  • 添加终端电阻(120Ω)

7. 性能优化与扩展

7.1 低功耗优化策略

对于电池供电场景,可实施以下优化:

  1. 采用间歇工作模式,每5分钟唤醒一次
  2. 关闭不必要的外设时钟
  3. 使用STOP模式降低待机功耗
  4. 优化FreeModbus响应超时(缩短至100ms)

7.2 多传感器扩展

通过I2C总线可扩展多个传感器:

  1. 硬件修改

    • 为每个传感器分配独立地址
    • 增加I2C缓冲器(如PCA9548A)扩展通道
  2. 软件修改

    • 扩展寄存器映射范围
    • 实现传感器轮询机制
#define SENSOR_NUM 3 uint8_t sensorAddr[SENSOR_NUM] = {0x38, 0x39, 0x3A}; for(int i=0; i<SENSOR_NUM; i++) { AHT20_SetAddress(sensorAddr[i]); AHT20_Read_CTdata(&ctData[i][0]); }

7.3 无线传输扩展

通过串口连接无线模块(如LoRa、NB-IoT):

  1. 硬件连接

    • 无线模块的RX/TX连接STM32的USART2
    • 共地连接,注意电平匹配
  2. 协议适配

    • 保持Modbus RTU协议不变
    • 增加无线模块的AT指令控制
void SendViaLoRa(uint8_t *data, uint16_t len) { HAL_UART_Transmit(&huart2, "AT+SEND=", 8, 100); for(int i=0; i<len; 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秒间隔,过于频繁的读取会导致传感器内部温升影响精度。

http://www.jsqmd.com/news/680045/

相关文章:

  • make = make install?
  • Campus-i茅台:自动化预约解决方案的技术探索与实践
  • 从校园卡到公交卡:拆解你钱包里那些M1卡的前世今生与安全困境
  • 从“对称”到“非对称”:手把手教你用ADDA为自定义数据集做域适配(避坑指南)
  • 2026年合肥工程纠纷律师选择指南:合肥合同纠纷律师事务所、合肥安徽律师事务所、合肥工伤律师事务所、合肥工程纠纷律师事务所选择指南 - 优质品牌商家
  • 告别迷茫!手把手教你用CANoe 15.0从零搭建第一个仿真工程(附DBC文件创建)
  • MangoPi-MQ(麻雀)开发板Tina系统编译避坑指南:从补丁到烧录的完整实战
  • 别再只用AUC了!手把手教你给XGBoost模型添加F1和准确率评估(附完整代码)
  • 别再手动配环境了!用Docker Compose一键部署ELK 7.17.2(附SpringBoot日志接入完整配置)
  • 你的第一个实例分割项目:从Labelme标注到用MMDetection训练(COCO格式实战)
  • Mini PCIe vs M.2接口全对比:看完这篇就知道你的项目该选哪种
  • 告别玄学调试:用Wireshark抓包实战解析PCIe链路训练与有序集(TS1/TS2/EIOS全解)
  • 2026年轴销螺栓供应商梯队盘点:GB31.1/GB32.1/六角头头部带孔螺栓/六角头螺杆带孔螺栓/带孔紧固件/选择指南 - 优质品牌商家
  • 别再乱用事件过滤器了!Qt中让QLineEdit智能失焦的两种正确姿势(附QCompleter处理)
  • 用Python+CAPL玩转CANoe自动化测试:从环境搭建到实战脚本(附GitHub源码)
  • MediaCreationTool.bat终极指南:Windows 10/11全版本部署与硬件限制突破实战
  • Arm Linux身份证读卡器开发实战:从交叉编译到so库生成全流程
  • 不止是参数表:手把手带你玩转飞凌OK3588-C开发板,从开箱到跑通第一个AI Demo
  • 3D地球卫星轨道可视化平台开发 Day14(彻底移除多余阴影)
  • Spring Boot 4.0:云原生 Java 开发的范式革命
  • 避坑指南:CEEMDAN参数(Nstd, NE, MaxIter)怎么调?附MATLAB代码与效果对比
  • 从Kaggle竞赛到业务报表:回归模型评估指标R²、RMSE、MAE的‘场景化生存指南’
  • ESP32 + micro-ROS实战:手把手教你用Action Server做个智能小车遥控器
  • 保姆级教程:手把手教你用Python解析GFS气象数据(附完整变量对照表)
  • 虚幻引擎串口通信插件终极指南:5分钟连接Arduino硬件
  • 用XC7K325T+XDMA实现PC与FPGA高速数据交换:手把手教你玩转驱动自带测试工具
  • Python和LabVIEW搞TCP通信,这3个坑我帮你踩过了(附完整调试流程)
  • 碧蓝航线Alas脚本:告别手动肝船的全自动游戏管家终极指南
  • 如何快速配置暗黑3自动化工具:D3KeyHelper新手完整入门指南
  • 用J-Link Commander和逻辑分析仪,手把手教你调试ARM Cortex-M4的JTAG-DAP接口