ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于STM8S003F3和SHT30的MODBUS RTU温湿度节点实现

基于STM8S003F3和SHT30的MODBUS RTU温湿度节点实现 简介一套基于STM8S单片机与SHT30温湿度传感器、采用MODBUS RTU协议与外部设备通信的完整IAR工程源码适用于工业环境监测、物联网终端及嵌入式温湿度采集场景。资源共82个文件以33个.h头文件和32个.c源文件为主体涵盖I²C驱动、SHT30读取、UART1通信、MODBUS寄存器映射及CRC校验等模块另含IAR工程配置.ewp/.ewd/.eww、编译烧录脚本.bat、调试日志.log和STM8S标准外设库手册.chm整体3.81MB目录结构清晰从底层驱动到协议应答均有对应文件可查。目前已有407人学习。包内不仅提供可编译的STM8S固件工程还体现了V2.01版本对通信效率和稳定性的优化思路开发者可直接导入IAR Embedded Workbench for STM8进行二次开发或参照源码理解SHT30时序控制、MODBUS从站实现与异常处理适合作为学习8位MCU与工业总线通信的参考范例。1. 从“能读温湿度”到“能进工业总线”——这个基础工程踩过的坎一个环境监测节点要接入现有SCADA或PLC系统最省事的做法是给上位机暴露MODBUS RTU寄存器而不是让用户去解析自定义帧。这个WSD_04_V2工程正是这么做的STM8S003F3做主控SHT30通过I2C采集温湿度UART1接RS485收发器对外走MODBUS协议。很多人以为8位机上跑MODBUS就是调个串口库的事实际动手才会碰到几个典型问题SHT30的CRC校验位处理不当会读回跳变的湿度值MODBUS的寄存器高低字节顺序和上位机预期不一致会导致数据差一大截I2C时钟在STM8S上用硬件外设还容易受中断干扰。这篇直接把这个工程从启动文件到协议栈拆开讲适合正在做传感器节点接入总线、或者想把现有裸机程序加上标准工业协议的开发者参考。2. 工程骨架拆解IAR目录结构、启动流程与软I2C引脚分配2.1 拿到源码先看什么压缩包解压后第一眼是ReCloseProject.ewp和ReCloseProject.ewd这是IAR for STM8的工程文件分别对应编译配置和调试配置。ReCloseProject.cspy.bat是命令行调试脚本如果IAR版本对不上打不开GUI可以直接用这个批处理启动cspy。真正的业务代码集中在Main.c、Define.c、bsp_sht3x.c、bsp_i2c_gpio.c、UART1.c和stm8s_it.c其余是ST官方标准外设库和芯片头文件。这个工程最有参考价值的不是功能多复杂而是它的外设分工非常干净。bsp_i2c_gpio.c负责用GPIO模拟I2C时序bsp_sht3x.c负责SHT30的命令交互UART1.c负责MODBUS帧的物理收发Main.c只做调度。实际项目里如果要把整个协议栈换成自定义格式只需要改UART1.c的解析部分传感器驱动可以原封不动。2.1.1 文件职责速查文件职责依赖Main.c主循环调度、全局变量声明Define.hbsp_i2c_gpio.cGPIO模拟I2C起始/停止/读写时序stm8s_conf.hbsp_sht3x.cSHT30命令封装、温湿度数据解析bsp_i2c_gpio.hUART1.c串口收发、MODBUS帧组装与解析stm8s_it.cstm8s_it.cUART1接收中断入口、定时器中断stm8s_it.hDefine.c寄存器地址定义、系统参数初始化stm8s003f3.h2.2 上电初始化顺序stm8s_it.c里的main入口做完时钟配置后进入外设初始化。STM8S003F3默认时钟源是HSI高速内部RC频率16MHz工程里一般直接拿这个做系统时钟。注意这只芯片没有外部晶振引脚别按STM32的习惯去找HSE相关代码。以下是一般工程实际使用的初始化序列各外设之间有关联顺序void System_Init(void) { CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 16MHz GPIO_ConfigPorts(); // 配置所有GPIO方向 I2C_SoftInit(); // 拉高SDA/SCL UART1_Init(9600, UART1_WORDLENGTH_08D, UART1_STOPBITS_1); EnableInterrupts(); // 开全局中断 SHT30_Init(); // 发送软复位命令0x30 0xA2 }这里UART1_Init的波特率是9600MODBUS RTU默认常用的是9600和19200。如果现场总线速率要求更高改这一处参数就行但要注意后续CRC的超时时间窗也要跟着波特率缩放。SHT30_Init里发软复位命令是为了清掉传感器上电后的不确定状态这一步不做第一次读取可能拿到全0xFF。2.2.1 软I2C引脚分配以普通GPIO为例常见做法是把PB4和PB5分别映射为SDA和SCL这两个引脚在STM8S003F3上支持开漏输出配合外接4.7kΩ上拉电阻到3.3V就能稳定跑100kHz标准I2C时序。工程里bsp_i2c_gpio.h的宏定义允许直接改引脚#define I2C_SDA_PORT GPIOB #define I2C_SDA_PIN GPIO_PIN_4 #define I2C_SCL_PORT GPIOB #define I2C_SCL_PIN GPIO_PIN_5用GPIO模拟I2C而不是用片上I2C外设原因很简单STM8S003F3的I2C外设在从机模式下的时序要求苛刻而且在主频不高时容易和中断产生竞争。软I2C把时序控制权完全握在手里虽然多占CPU但在这个低采样率的温湿度场景里完全够用。引脚还可以随意改到任意GPIO布线方便很多。如果后续要接多个I2C设备或者采样频率要求超过10Hz再考虑切回硬件I2C也不迟。3. SHT30采集链路I2C时序、CRC校验与温湿度换算3.1 SHT30的I2C协议细节SHT30从机地址是0x44ADDR引脚接低电平或0x45接高电平。工程里如果SHT30_ADDR宏定义的是0x44说明硬件上将ADDR直接接地。测量命令用得最多的是0x2C 0x06对应高重复性、时钟拉伸使能每秒最多可以触发10次测量完全满足环境监测的刷新需求。有个关键点SHT30返回的数据是6字节格式是温度MSB、温度LSB、温度CRC、湿度MSB、湿度LSB、湿度CRC。CRC多项式是x^8 x^5 x^4 1初始值为0xFF。不校验CRC直接算温湿度大概率会在湿度读数上看到偶发跳变——别省这一步。3.2 测量命令发送与数据读取bsp_sht3x.c中测量过程分两次I2C操作先发命令然后以重复起始条件读取数据。部分工程师习惯发送命令后切换为读模式这在实际工程里容易因为Tsetup时间不够而丢第一字节数据。稳妥的流程是uint8_t SHT30_ReadTempHum(int16_t *temp, uint16_t *humi) { uint8_t buf[6]; uint8_t cmd[2] {0x2C, 0x06}; I2C_Start(); I2C_WriteByte((SHT30_ADDR 1) | 0); // 写方向 if (I2C_CheckACK() ! 0) { I2C_Stop(); return 1; } I2C_WriteByte(cmd[0]); I2C_WriteByte(cmd[1]); I2C_Stop(); // 等待测量完成高重复性典型耗时4.5ms DelayMs(10); I2C_Start(); I2C_WriteByte((SHT30_ADDR 1) | 1); // 读方向 for (int i 0; i 6; i) { buf[i] I2C_ReadByte(i 5); // 最后一个字节发NACK } I2C_Stop(); if (CRC8(buf, 2) ! buf[2] || CRC8(buf[3], 2) ! buf[5]) { return 2; // CRC错误 } *temp (int16_t)(((buf[0] 8) | buf[1]) 0xFFFC); *humi (uint16_t)(((buf[3] 8) | buf[4]) 0xFFFC); return 0; }这段代码里I2C_ReadByte(1)表示最后一字节要回复非应答I2C协议规定读方在最后一个字节必须发NACK否则传感器会认为主机还要继续读而拉低SDA导致总线释放不了。另外 0xFFFC的作用是屏蔽最低2位SHT30数据手册明确说明这2位恒为0保留它们会引入噪声。CRC8的查表实现不多展开了网上标准表直接抄就行。注意CRC8(buf, 2)只对前两字节做校验参数不能写6否则会把湿度数据也卷进来校验必挂。3.3 温湿度换算公式拿到原始16位数据后要转换成物理量。SHT30的线性公式是温度 -45 175 * (原始值 / 65535) 湿度 100 * (原始值 / 65535)浮点运算在STM8S上比较贵提升运行效能的常见做法是先把原始值除以10再转成定点数放到MODBUS寄存器里。例如温度原始值raw_t为0x6A8A即27306算出实际温度27.06℃工程里存储的寄存器值是2706上位机拿到后除以100就是最终温度。这样整型运算快而且避免在8位机上频繁触发软件浮点库。4. MODBUS RTU从站寄存器映射、CRC16与串口状态机4.1 寄存器地址规划MODBUS保持寄存器按惯例从40001开始编号但在协议帧里地址是从0x0000开始的。工程里Define.c给出的映射关系决定了上位机看到的数据布局一般会这样分配寄存器地址协议帧对应MODBUS地址内容数据类型0x000040001温度值放大100倍uint160x000140002湿度值放大100倍uint160x000240003设备状态0正常1为CRC错误uint160x000340004软件版本号0x0201表示V2.01uint164.1.1 数据结构体设计typedef struct { uint16_t temperature; // 单位0.01℃ uint16_t humidity; // 单位0.01% uint16_t status; uint16_t version; } ModbusRegs_t;需要特别提醒的是MODBUS协议规定多字节数据大端在前高字节先发。温度值2706在帧里要发0x0A 0x92而不是0x92 0x0A。如果总是出现上位机读到的数据乘256再加上另一个字节才对得上就是字节序反了。4.2 CRC16-MODBUS计算MODBUS RTU的CRC是多项式0xA001的查表实现。收到帧后对从地址开始的整帧做CRC计算算出的16位CRC与帧尾两字节比对。帧尾是低字节在前这是新人最容易搞反的地方。下面这个函数是完整验证帧的核心uint16_t MODBUS_CRC16(uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; while (len--) { crc ^ *data; for (int i 0; i 8; i) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }整个计算过程里最重要的参数是多项式0xA001这是MODBUS专用反向多项式其他协议如CRC16-CCITT用的多项式不一样不能通用。调用时传入的len必须是从站地址到数据末尾的完整长度不包括CRC本身等两个字节。4.3 主轮询状态机协议栈核心在UART1.c主循环不断检查串口有没有收到完整帧。MODBUS RTU用帧间隔来定界标准是3.5个字符时间9600波特率下大约是4ms。工程里用定时器中断累加判断超时每收到一个字节就清零定时器超过4ms没新字节就当作帧结束。MODBUS_State_t MODBUS_Poll(void) { if (frameComplete 0) return MODBUS_IDLE; // 校验地址是否匹配 if (rxBuffer[0] ! SLAVE_ADDR) { frameComplete 0; return MODBUS_ADDR_ERR; } // 校验CRC uint16_t crcCalc MODBUS_CRC16(rxBuffer, rxLen - 2); uint16_t crcRecv (rxBuffer[rxLen - 1] 8) | rxBuffer[rxLen - 2]; if (crcCalc ! crcRecv) { frameComplete 0; return MODBUS_CRC_ERR; } switch (rxBuffer[1]) { case 0x03: // 读保持寄存器 MODBUS_HandleReadRegisters(); break; case 0x06: // 写单寄存器 MODBUS_HandleWriteSingleRegister(); break; default: MODBUS_SendException(0x01); // 非法功能码 } frameComplete 0; return MODBUS_OK; }注意crcRecv的拼接顺序是rxBuffer[rxLen-1] 8 | rxBuffer[rxLen-2]因为帧尾CRC低字节在前。如果写反了所有帧都会报CRC错。这个状态机只用了一个全局frameComplete标志中断只负责填缓冲区主循环做解析结构简单在裸机上不容易死锁。4.3.1 读寄存器响应帧构造MODBUS_HandleReadRegisters里先判断寄存器数量和起始地址合法性响应帧格式是地址、功能码、字节数、数据、CRC。比如上位机读地址0x0000往后的2个寄存器响应帧就是01 03 04 0A 92 07 6E 34 D6拆开看01从站地址03功能码04表示后面有4字节数据0A 92是温度270607 6E是湿度190219.02%最后34 D6是CRC。如果看到上位机能请求但一直报超时检查响应帧字节数是不是比实际少MODBUS调试工具对长度非常敏感。5. 下载烧录常见错误与MODBUS Poll实测验证5.1 IAR编译与烧录配置用IAR for STM8打开ReCloseProject.ewp后首先要确认工程选项里芯片型号是STM8S003F3。如果打开工程直接提示fatal error[lms001]: license check failed这是许可证未激活或版本不匹配需要打开IAR License Manager重新配置许可证确认Host ID和许可证绑定的机器一致。烧录时在Project Options Debugger里选择ST-LINKDownload标签页勾选Use flash loader。STM8S003F3的Flash是8KB这个工程编译出来大约4KB空间充裕。下载前先执行一次Project Clean有时候烧录后程序不跑是因为旧的调试信息和新代码错位。5.2 用MODBUS Poll端到端验证MODBUS Poll是调试从站最顺手的工具。工程下载后USB转485接上STM8S板的A/B线打开MODBUS Poll做如下设置Setup Connection Serial Port: COM3实际串口号 Baud Rate: 9600 Data Bits: 8 Parity: None Stop Bits: 1 Mode: RTU连接后Setup Read/Write Definition里设置从站地址为1功能码选03起始地址填0读取数量填4。轮询周期设1000ms对应每秒读一次温湿度。如果读到额定值用手握住传感器温度寄存器数值应该缓慢上升对着传感器哈气湿度寄存器的变化会非常灵敏。5.3 常见异常定位现象可能原因检查方向能请求响应超时串口参数不一致波特率/校验位核对8N1配置读回的数值都是0SHT30 I2C通信失败查CRC8校验是否通过读回的数值翻倍字节序错位交换寄存器高/低字节偶发一个寄存器跳变SHT30读取时机与测量完成时间重合延时至10ms以上接上485后程序复位隔离电源不足增加TVS管和共地处理5.4 顺手提高一下通信健壮性如果在实测中发现偶发CRC错误可以在stm8s_it.c接收中断里加一个简单的过滤条件只把地址字段等于本机地址的帧放进缓冲区其他直接丢弃。不推荐在裸机上上完整状态机队列8位机SRAM太小效果有限。另外如果MODBUS Poll评估版到期不能保存配置可以改用命令行方式启动modpoll.exe -m rtu -a 1 -r 0 -c 4 -t 4:hex COM3 9600一句就能发起同样的读取自动化测试脚本里更好用。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进