十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F103+FreeRTOS实现Modbus RTU主机轮询从站实战

STM32F103+FreeRTOS实现Modbus RTU主机轮询从站实战 简介基于 STM32F103 的 Modbus 主机完整工程代码以 FreeRTOS 作为实时调度框架适合工业控制、物联网等领域中需要学习或部署串行主从通信的嵌入式开发者也可作为课程设计或产品原型的参考实现。代码实现了 Modbus RTU 主站核心功能包括串口初始化、帧编解码与 CRC 校验、请求任务和响应解析任务之间的信号量/消息队列同步、串口中断接收以及超时和 CRC 错误处理任务划分清晰可直接参照移植用于二次开发。压缩包采用 7z 格式共 303 个文件其中 61 个 C 源文件与 95 个头文件构成协议栈和任务主体62 个 o 文件及 axf、hex、map 等调试产物帮助还原编译与运行现场整体大小仅 849KB轻量易用。已有 1617 人学习浏览。工程保留 HAL 库配置文件、FreeRTOS 配置和调试信息不仅提供可运行实例还能帮助开发者理解 Modbus 主站的设计思路、任务划分与异常处理机制适合做功能扩展和代码移植。 如果你接过一个活儿要让一块STM32F103同时跑FreeRTOS还得当Modbus RTU主机去轮询一串仪表那你大概率会发现网上搜“stm32f103 modbus”出来的全是从机例程搜“freertos modbus”又都是各种半成品。真正能落地的、能直接抄的主机代码确实不多。这篇就是我在实际设备上跑通的方案总结。核心思路很直接用STM32F103标准库V3.5把FreeRTOS跑起来然后用“任务事件组串口中断”的方式实现Modbus RTU主机去轮询从站的保持寄存器。项目里要接的从站包括电能表、IO模块、温湿度传感器都是标准Modbus RTU设备。如果你也在做类似的数据采集、设备监控控制器这篇文章应该能帮你省掉不少自己踩坑的时间。1. 整体设计与方案选型1.1 为什么不是裸机状态机而是FreeRTOS很多老的Modbus主机代码是裸机写的主循环里一个状态机加一堆标志位。对单站点场景够用但从站一多、功能一复杂就麻烦轮询超时、串口收发、数据处理、显示刷新全挤在一个循环里逻辑稍多点就乱。上了FreeRTOS以后思路就清晰很多轮询从站是一个独立任务数据处理是一个任务人机交互又是一个任务。串口中断只需要把收到的字节丢进队列或者环形缓冲解析工作放在任务里做。这样各个模块之间的耦合度低后续加功能也不至于把原逻辑搞崩。从硬件成本上说STM32F103C8T6这种级别的MCU跑FreeRTOS完全没压力RAM消耗通常在1-2KBFlash也就多个5-8KB剩下的资源还是够用的。1.2 主机协议栈为什么不直接用现成的别人问我为什么不用FreeModbus我的回答是FreeModbus本身是偏向从机实现的虽然也能魔改成主机但改动量不小而且出来之后维护成本高。主机协议栈其实比从机简单得多核心就是三个功能码读寄存器03、写单个寄存器06、写多个寄存器10。正常设备轮询用03参数下发用06或10这就覆盖了90%以上的需求。自己写还有一个好处报文格式、超时策略、重试机制都能根据现场情况灵活调。工业现场调试的时候能用Modbus Poll这类工具模拟从站快速验证主机发的报文对不对比折腾什么重型协议栈舒服多了。2. 工程搭建与FreeRTOS移植要点2.1 标准库V3.5和编译器的一些坑我用的还是STM32标准库V3.5配合Keil MDK。很多人说标准库过时了但做小项目、做维护标准库的资料量和稳定性依然是巨大优势CubeMXHAL反而在DEBUG时会多一点绕路的成本。有一点要提醒如果你用的Keil MDK版本比较新编译器很可能是AC6。AC6和AC5对代码的语法检查更严格标准库里有些隐式类型转换会报警告。还有如果你从网上拷来的代码里有#pragma pack这种结构体对齐指令务必确认它在AC6下的行为是否符合预期否则结构体指针直接强制转型解析Modbus报文时会踩字节对齐的雷。SysTick在FreeRTOS里默认用来做系统节拍时钟初始化用SystemInit之后就不要再随便改SystemCoreClock的值。标准库V3.5自带的SystemCoreClock 72000000FreeRTOS的configCPU_CLOCK_HZ要保持一致否则软件定时器、延时全是乱的。2.2 内存分配heap_4和任务栈规划FreeRTOS的内存堆heap_4是默认选择碎片和分配效率平衡是最好的。我一般把configTOTAL_HEAP_SIZE设置成12KB左右对F103C8T6这种20KB RAM的芯片来说留的余量还行。如果外设多、环形缓冲多建议直接上F103RCT6或者F103ZET664KB RAM就不用抠这几百字节了。任务栈建议按这个经验值开modbus_task轮询任务256字约1KB足够跑协议栈和状态机。data_process_task数据处理任务256字。显示/按键任务128字也能跑但建议给256字。栈开太小会出现各种诡异问题函数调用深度一大局部变量把栈顶冲掉最常见的就是串口解析到一半栈直接飞了。调试阶段可以在每个任务循环里调用uxTaskGetStackHighWaterMark查看剩余栈空间把栈大小调到合理值。2.3 串口和485方向控制的细节Modbus RTU主机一般走RS485所以串口配置是USART2或USART38位数据、无校验、1位停止位波特率9600或19200。串口使用中断接收有空闲中断就用空闲中断没有就字节中断配合稍后说的队列机制。RS485的方向控制引脚DE/RE是关键很多“主机发出去从机收不到”的问题就出在这里。发送前先把DE拉高让RS485芯片进入发送模式数据全部发送完成再拉低DE让总线回到接收模式。注意拉低的时机不能太早最好在发送完最后一个字节之后再等几十微秒否则最后一个字节会被截断。调试的时候用示波器同时看TX、RX和DE三个信号能很清楚地看到方向切换的时序。我还见过有人把DE接到PA8上结果PA8默认复用成了MCO输出方向控制引脚怎么拉都不对——这类引脚冲突问题布线前最好对着原理图过一遍。3. Modbus RTU主机协议栈核心实现3.1 报文格式和CRC16校验Modbus RTU的报文格式不复杂但细节要卡准。每个报文由地址码、功能码、数据和CRC组成字段长度说明从站地址1字节1-247主机发起时填目标从站地址功能码1字节0x03读寄存器、0x06写单寄存器、0x10写多寄存器数据N字节根据功能码变化CRC162字节Modbus专用CRC低字节在前高字节在后CRC16的算法是固定套路多项式是0xA001。我一般用查表法速度比逐位快很多uint16_t modbus_crc16(uint8_t *buffer, uint16_t length) { uint16_t crc 0xFFFF; for (uint16_t i 0; i length; i) { crc ^ buffer[i]; for (uint8_t j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }发送时先发低字节再发高字节这是Modbus RTU的固定要求千万别发反了。测试的时候用Modbus Poll模拟从站能直接看出来主机发的报文对不对。3.2 三个核心功能码的报文构造03功能码是最常用的。比如要读从站地址为1的设备从寄存器地址0开始连续读4个寄存器8个字节请求报文是这样的01 03 00 00 00 04 44 09其中01是从站地址03是功能码00 00是起始寄存器地址高字节/低字节00 04是寄存器数量44 09是前面的CRC16。如果响应正常从站会返回01 03 08 xx xx xx xx xx xx xx xx CRC_L CRC_H08是返回的字节数等于寄存器数乘以2。如果从站报异常功能码最高位会置1比如83后面跟一个异常码01非法功能、02非法地址、03非法数据值。06功能码写单个寄存器报文结构是地址功能码寄存器地址数据值CRC。10功能码写多个寄存器报文要带字节长度和寄存器数据。这三种功能码覆盖了绝大多数Modbus RTU主站的业务场景。有个容易忽略的限制03功能码一次读取的寄存器数量最多125个因为响应报文有长度限制。如果你要读的数据超过125个寄存器必须分批读不然从站返回的异常的会让你排查半天。3.3 状态机设计发送、等待、校验、完成Modbus RTU主机软件核心是一个状态机最简单的实现有四态空闲、等待响应、校验、完成。typedef enum { MB_STATE_IDLE, MB_STATE_WAIT_RESP, MB_STATE_CHECK, MB_STATE_COMPLETE } mb_state_t;工作时主机先组装请求报文通过485发送出去然后进入等待响应状态。等收到完整一帧响应后切换到校验状态做长度检查和CRC校验通过后进入完成状态把解析出来的寄存器数据存到共享缓冲区里然后状态机回到空闲状态准备下一轮轮询。超时处理是这个状态机的关键。Modbus RTU从机响应时间通常很短工业设备一般50-200ms内都会回复。我用FreeRTOS的tick计数做超时基准在发送完成后记录当前tick数在等待状态下每次循环检查是否超过设定的超时时间比如200ms超了就重发或跳过该从站。4. FreeRTOS任务中怎么跑Modbus主机4.1 串口接收中断里丢数据任务里解析串口接收一定要用中断不能在主任务轮询中阻塞等待。我的做法很直接USART接收中断里每收到一个字节就调用一次xQueueSendFromISR把字节放进队列解析任务阻塞在xQueueReceive上。为什么不用信号量而用队列因为队列天然能缓存未及时处理的数据。如果某个时刻中断连续来了20个字节解析任务还没来得及处理队列可以把字节先存住不丢数据。信号量只能告诉你“来数据了”数据本身还得靠缓冲区接住多一套机制。在中断里千万别调用xQueueSend这种非中断版本API必须用带FromISR后缀的版本参数里带上pxHigherPriorityTaskWoken并在中断结束后做一次portYIELD_FROM_ISR。很多刚开始用FreeRTOS的人在这上面翻过车。4.2 事件组还是信号量怎么把收发流程串起来Modbus主机任务的伪流程是构建请求、发数据、等串口收到完整响应、解析校验、更新数据。如果只用队列任务无法区分“收到的是完整一帧”还是“收到了一半”。我常用的方案是事件组。发完请求之后等待两个事件响应事件或超时事件。串口接收中断在每收到一个字节时写入队列然后在空闲中断或按帧间隔判断里设置了EVT_RX_FRAME事件位解析任务在收到这个事件后再清空队列里的数据组装成一帧。有了事件组任务可以这样组织EventBits_t bits xEventGroupWaitBits(mb_event_group, EVT_RX_FRAME | EVT_TIMEOUT, pdTRUE, pdFALSE, pdMS_TO_TICKS(200));这样Modbus主机任务在等待期间不会无限期阻塞超时了可以重发或切换从站。整个协议栈跑在低优先级的modbus_task里不会死等串口数据CPU利用率很健康。4.3 任务优先级怎么定任务优先级设置没有绝对标准但有个基本方向串口中断优于任务处理实时性高的任务优先级高于机械轮询任务。我项目里排序是按键/显示任务低优先级modbus_task中优先级data_process_task中高优先级。如果增加了一个屏幕刷新任务建议把刷新放低优先级别让它和Modbus任务抢CPU。共享缓冲区要注意防竞争。比如data_process_task读寄存器数据modbus_task在后台更新数据两者同时操作同一片内存时用二值信号量或互斥量保护。不过如果你的数据只是简单的整数拷贝F103做32位读写本身是原子的风险不大但写结构体数据时还是加锁稳妥。5. 常见问题排查与实用调试技巧5.1 主机连不上从机先查接线再查报文“485主从分开测都正常一连在一起就不正常”这个问题我遇到不下十次。先用万用表量A、B两根线的电压静态时A相对B高2-5V是正常的。如果A、B接反从机肯定没有响应。其次是共地485是差分信号看似不需要地线但现场共模电压不一致会导致通信不稳建议把设备的地连一下。如果接线没问题就用Modbus Poll模拟从站来验证主机。Modbus Poll的界面很简单选好串口、波特率、从站地址就能看到主机发过来的请求报文还能手动模拟从站响应。这样能快速确认是主机报文有问题还是从站响应有问题。5.2 串口乱码、丢字节、CRC不过这种问题依次排查三个环节时钟配置F103的外部晶振如果焊的是8MHz你却在RCC配置里按12MHz算波特率就会差得一塌糊涂。先用示波器看PWM输出或MCO引脚确认系统时钟没问题。中断优先级FreeRTOS要求能用API的中断优先级不能高于configMAX_SYSCALL_INTERRUPT_PRIORITY如果你把串口中断设成了0最高优先级调用FromISR系列API反而会出各种系统崩溃。CRC校验失败多半是报文不完整尤其是485方向脚切回接收太晚响应帧最后一个字节丢了。在拉低DE前加一点延时或者改用TXETXC完成中断来控制方向脚。5.3 FreeRTOS任务卡死和堆栈溢出任务卡死最常见的原因是队列或信号量等待超时设置成无限阻塞而另一个任务因为某种原因永远不释放资源。Modbus从站异常时杳无音信串口又没有数据如果此时等待队列的xQueueReceive用了portMAX_DELAY任务就挂死了。所以Modbus相关等待一律要设置超时。堆栈溢出建议直接打开FreeRTOS的configCHECK_FOR_STACK_OVERFLOW2这个检查使用tick中断来检测栈指针是否越界比较可靠。同时在任务循环里周期调用uxTaskGetStackHighWaterMark打印剩余栈空间新任务刚跑起来的时候是最容易露出马脚的。我在实际项目里最后又加了一个统计任务把每个任务的栈水位、任务状态、运行次数都通过串口打印出来并且把Modbus轮询超时次数记录下来。现场设备出问题时让运维把调试日志发回来那个“超时次数”分分钟能定位是哪台从站偶尔不响应。调试阶段再习惯用Modbus Poll做模拟从站配合FreeRTOS的栈水位监控这套组合拳打下来Modbus主机FreeRTOS的项目基本不会有大坑。如果你后续要扩展Modbus TCP也是同样的状态机思路只是把串口收发换成网络收发那又是另一个话题了。本文还有配套的精品资源点击获取
返回列表