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

利用STM32实现Modbus通信(RTU从机方案)

一、系统概述

Modbus是工业领域广泛应用的串行通信协议,支持RTU(二进制)和ASCII(文本)模式,本方案基于STM32F103C8T6(Cortex-M3,72MHz)实现Modbus RTU从机,通过RS485总线与主机(如PLC、PC)通信,支持保持寄存器(0x03/0x06/0x10)、线圈(0x01/0x05/0x0F) 读写,可扩展至其他功能码。核心功能包括:Modbus协议解析、寄存器管理、RS485收发控制、CRC校验。

二、硬件设计

2.1 核心组件

模块 型号/参数 功能说明
主控 STM32F103C8T6(64KB Flash,20KB RAM) Modbus协议处理、寄存器管理、串口通信
通信接口 MAX485(RS485物理层) 实现Modbus RTU差分信号传输(半双工)
串口 USART1(PA9-TX,PA10-RX,115200bps) 连接MAX485,配置8N1(8数据位、无校验、1停止位)
定时器 TIM2(16位通用定时器) 计算Modbus RTU帧超时(3.5字符时间,如9600bps时≈3.64ms)
控制引脚 PB0(DE/RE) 控制MAX485收发模式(高电平发送,低电平接收)

2.2 硬件连接

模块 引脚(STM32F103C8T6) 说明
USART1 PA9(TX)→ MAX485 DI 发送数据
PA10(RX)← MAX485 RO 接收数据
MAX485 DE/RE → PB0 收发控制(高=发送,低=接收)
RS485总线 A/B端子 连接主机(A-A,B-B)

三、软件设计(STM32标准库3.5)

3.1 系统架构

graph TDA[主机(Modbus RTU)] --RS485--> B[STM32 USART1]B --数据接收--> C[FreeModbus协议栈]C --解析请求--> D[寄存器管理(保持寄存器/线圈)]D --返回响应--> CC --数据发送--> BB --RS485--> AE[TIM2] --3.5T超时中断--> C

3.2 核心原理

  1. Modbus RTU帧格式[地址码(1)][功能码(1)][数据(n)][CRC16(2)],CRC16校验确保数据完整性。

  2. 从机工作流程

    • 接收主机请求帧,校验CRC;

    • 解析功能码,操作对应寄存器(读/写);

    • 生成响应帧(含数据+CRC),通过RS485返回主机。

3.3 开发步骤

3.3.1 FreeModbus协议栈移植

  • 获取源码:从FreeModbus官网下载V1.6稳定版,关键文件:
    • modbus.c/modbus.h:协议核心(从机模式);

    • port/portserial.c/portserial.h:串口操作(需移植);

    • port/porttimer.c/porttimer.h:定时器操作(需移植);

    • port/portother.c/portother.h:临界区保护(关中断/开中断)。

3.3.2 STM32硬件初始化

(1)时钟与GPIO配置
#include "stm32f10x.h"void RCC_Config(void) {RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART1 | RCC_APB1Periph_TIM2, ENABLE); // 串口1+定时器2时钟
}void GPIO_Config(void) {GPIO_InitTypeDef GPIO_InitStruct;// USART1_TX(PA9):复用推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);// USART1_RX(PA10):浮空输入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStruct);// MAX485_DE/RE(PB0):推挽输出(高=发送,低=接收)GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);GPIO_ResetBits(GPIOB, GPIO_Pin_0); // 初始接收模式
}
(2)USART1初始化(Modbus RTU,115200bps,8N1)
void USART1_Init(void) {USART_InitTypeDef USART_InitStruct;USART_InitStruct.USART_BaudRate = 115200;USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_InitStruct.USART_StopBits = USART_StopBits_1;USART_InitStruct.USART_Parity = USART_Parity_No;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_Init(USART1, &USART_InitStruct);USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 使能接收中断USART_Cmd(USART1, ENABLE);
}
(3)TIM2初始化(3.5T超时定时器,1ms基准)
void TIM2_Init(void) {TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;TIM_TimeBaseStruct.TIM_Period = 999;         // 1ms重装值(72MHz/72=1MHz,1ms=1000计数,重装值999)TIM_TimeBaseStruct.TIM_Prescaler = 71;        // 预分频72(72MHz/72=1MHz)TIM_TimeBaseStruct.TIM_ClockDivision = 0;TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);   // 使能更新中断TIM_Cmd(TIM2, DISABLE);                      // 初始关闭,接收数据时启动
}

3.3.3 FreeModbus接口移植

(1)串口操作(portserial.c)
#include "portserial.h"
#include "stm32f10x_usart.h"// 串口初始化(已在USART1_Init中实现)
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity) {return TRUE;
}// 使能串口收发(DE/RE控制)
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) {if (xRxEnable) {GPIO_ResetBits(GPIOB, GPIO_Pin_0); // MAX485接收模式(DE=0)USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 使能接收中断} else {USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);}if (xTxEnable) {GPIO_SetBits(GPIOB, GPIO_Pin_0); // MAX485发送模式(DE=1)} else {GPIO_ResetBits(GPIOB, GPIO_Pin_0);}
}// 发送单个字节
BOOL xMBPortSerialPutByte(CHAR ucByte) {USART_SendData(USART1, ucByte);while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成return TRUE;
}// 接收单个字节(中断中调用)
BOOL xMBPortSerialGetByte(CHAR *pucByte) {*pucByte = USART_ReceiveData(USART1);return TRUE;
}
(2)定时器操作(porttimer.c)
#include "porttimer.h"
#include "stm32f10x_tim.h"// 定时器初始化(3.5T超时,单位:50μs,115200bps时3.5T≈364μs=7.28×50μs→取整8)
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us) {return TRUE; // 已在TIM2_Init中配置1ms基准,此处无需额外初始化
}// 启动定时器(超时时间=usTim1Timerout50us×50μs)
void vMBPortTimersEnable(void) {TIM_SetCounter(TIM2, 0);       // 清零计数器TIM_Cmd(TIM2, ENABLE);          // 启动定时器
}// 关闭定时器
void vMBPortTimersDisable(void) {TIM_Cmd(TIM2, DISABLE);         // 关闭定时器TIM_SetCounter(TIM2, 0);       // 清零计数器
}// TIM2中断服务函数(1ms触发一次,累计超时)
void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {TIM_ClearITPendingBit(TIM2, TIM_IT_Update);pxMBPortCBTimerExpired(); // 通知FreeModbus帧超时}
}
(3)寄存器定义(modbus_slave.c)

定义保持寄存器(0x03功能码)和线圈(0x01功能码):

#include "modbus.h"// 保持寄存器(地址0-9,16位整数)
USHORT usRegHoldingBuf[10] = {0x1234, 0x5678, 0x9ABC, 0xDEF0, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006};// 线圈(地址0-7,1位布尔值)
UCHAR ucRegCoilsBuf[1] = {0x01}; // 线圈0=ON(bit0=1),其余OFF// 保持寄存器回调函数(读/写)
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode) {if (usAddress + usNRegs > 10) return MB_ENOREG; // 地址越界if (eMode == MB_REG_READ) {memcpy(pucRegBuffer, &usRegHoldingBuf[usAddress], usNRegs * 2); // 16位寄存器,2字节/个} else { // 写memcpy(&usRegHoldingBuf[usAddress], pucRegBuffer, usNRegs * 2);}return MB_ENOERR;
}// 线圈回调函数(读/写)
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode) {if (usAddress + usNCoils > 8) return MB_ENOREG; // 地址越界(8个线圈)if (eMode == MB_REG_READ) {for (int i=0; i<usNCoils; i++) {UCHAR bit = (ucRegCoilsBuf[usAddress/8] >> (usAddress%8)) & 0x01;pucRegBuffer[i/8] |= (bit << (i%8));usAddress++;}} else { // 写for (int i=0; i<usNCoils; i++) {if (pucRegBuffer[i/8] & (1 << (i%8))) {ucRegCoilsBuf[usAddress/8] |= (1 << (usAddress%8));} else {ucRegCoilsBuf[usAddress/8] &= ~(1 << (usAddress%8));}usAddress++;}}return MB_ENOERR;
}

3.3.4 主函数(Modbus轮询)

#include "stm32f10x.h"
#include "modbus.h"int main(void) {RCC_Config();       // 时钟配置GPIO_Config();      // GPIO配置(USART1+PB0)USART1_Init();      // USART1初始化(115200bps)TIM2_Init();        // TIM2初始化(超时定时器)NVIC_Config();      // 中断优先级配置(USART1+TIM2)// 初始化FreeModbus从机(地址0x01,RTU模式,串口1,115200bps,无校验)eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE);eMBEnable();        // 使能Modbuswhile (1) {eMBPoll();        // 轮询Modbus事件(解析请求、发送响应)}
}// 中断优先级配置(NVIC)
void NVIC_Config(void) {NVIC_InitTypeDef NVIC_InitStruct;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 分组2(2位抢占,2位响应)// USART1中断(接收)NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStruct);// TIM2中断(超时)NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;NVIC_Init(&NVIC_InitStruct);
}// USART1中断服务函数(接收数据)
void USART1_IRQHandler(void) {if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {pxMBFrameCBByteReceived(); // 通知FreeModbus接收到一个字节USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}

参考代码 利用STM32单片机实现modbus通信 www.youwenfan.com/contentcns/56553.html

四、测试与验证

4.1 测试工具

  • Modbus Poll(PC端):发送0x03功能码读取保持寄存器(地址0-9),0x01功能码读取线圈(地址0-7);

  • 串口助手:监控USART1数据,验证帧格式(如读寄存器请求:01 03 00 00 00 0A C5 CD,响应:01 03 14 12 34 56 78 9A BC DE F0 00 01 00 02 00 03 00 04 00 05 00 06 XX XX,XX为CRC)。

4.2 关键参数

  • 波特率:115200bps时,1字符=10位(8数据+1停止+1校验,无校验时为9位),3.5T≈3.5×9/115200≈273μs,TIM2中断1ms累计3次触发超时;

  • CRC校验:FreeModbus自动处理CRC16(多项式0x8005),无需手动计算。

五、关键问题与解决方案

5.1 通信乱码

  • 原因:波特率不匹配(如STM32用115200,主机用9600)、串口参数错误(数据位/校验位);

  • 解决:用示波器测量USART_TX引脚,确认波特率(115200bps时1位≈8.68μs),检查USART_InitStruct配置。

5.2 帧超时错误

  • 原因:TIM2定时不准确(如预分频/重装值错误)、3.5T计算偏差;

  • 解决:根据波特率调整TIM2重装值(如9600bps时3.5T≈364μs,TIM2 1ms中断累计1次触发超时)。

5.3 寄存器读写异常

  • 原因:寄存器地址映射错误(如主机请求地址10,但从机仅定义0-9);

  • 解决:在eMBRegHoldingCB中添加地址越界判断(if (usAddress + usNRegs > 10) return MB_ENOREG;)。

六、总结

基于STM32F103实现了Modbus RTU从机,核心是FreeModbus协议栈移植+RS485收发控制+寄存器管理。通过简单修改寄存器定义(如扩展保持寄存器数量、添加输入寄存器),可支持更多功能码(0x04读输入寄存器、0x05写单线圈等),适用于工业传感器、执行器等设备的通信接口。

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

相关文章:

  • 3大维度解析Source Han Serif CN如何重塑中文字体应用生态
  • 大模型工具调用乱斗:MCP协议凭什么火?实战踩坑与选型建议
  • 一套完整可商运行的 德州扑克app源码
  • ExplorerPatcher系统残留深度清理与恢复指南
  • 光通信行业彻底爆了!三项世界纪录背后藏着多少财富密码
  • 2026年豆包GEO优化实战榜单:从技术到效果落地 - 博客湾
  • 2026虚拟主播动作创作工具专业选型指南,新手也能精准选对
  • 从售后服务到品牌口碑——热膨胀仪(含真空、高温)哪家更值得选? - 品牌推荐大师
  • 从 CLI 调用到 SDK 集成:GitHub Copilot 在 .NET 项目中的最佳实践
  • 从灰白到绚丽:G-Helper如何一键拯救ROG笔记本的色彩显示
  • 全自动高精度测量系统厂家实力对比与优选推荐白皮书(2026年版) - 品牌推荐大师
  • 植物大战僵尸修改器完整指南:3步快速解锁游戏新玩法
  • 2025终极网盘下载方案:八大平台直链解析助手完全指南
  • 终极B站视频下载指南:使用BBDown快速获取高清资源
  • Jimeng LoRA应用场景:游戏原画师LoRA风格预研、角色设定快速迭代方案
  • qobuz-dl:终极无损音乐下载解决方案——专业级Qobuz音乐库管理工具
  • Android Studio中文语言包:突破本地化困境的社区解决方案
  • 提升文献处理效率的完整解决方案:Zotero PDF预览插件实战指南
  • 暗黑3技能自动化释放:告别机械操作,重燃战斗激情 - 基于AutoHotkey的智能宏工具实现
  • 永辉超市购物卡怎么回收最划算? - 团团收购物卡回收
  • AO3镜像站终极指南:如何免费访问全球最大同人创作平台
  • 分析国际货运代理价格,福建领航在福州费用贵吗? - 工业推荐榜
  • Windows更新故障解决方案:Reset Windows Update Tool深度应用指南
  • OpenClaw自动化测试:Qwen3-32B镜像驱动GUI应用实战
  • 3分钟快速上手:Xournal++手写笔记软件完整指南
  • 精选旋盖机公司:2026年不可错过的选择,防伪标签贴标机/贴标机/日化贴标机/农药贴标机/灌装机,旋盖机公司推荐 - 品牌推荐师
  • 【人生底稿】07:2017-2018:从Java后端到全栈,我如何用一年时间为北漂埋下伏笔
  • 有了AI之后,我们会不会彻底丧失思考能力?
  • 全面掌握Source Sans 3:现代UI设计的开源字体解决方案
  • BilibiliCacheVideoMerge:安卓端B站缓存视频合并的终极解决方案