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

STM32 + MODBUS RTU + RS485 实现方案

一、系统架构与硬件连接

1.1 硬件连接表

STM32F103C8T6 RS485模块 说明
PA9 (TX1) DI 发送数据
PA10 (RX1) RO 接收数据
PA8 DE/RE 收发控制(高电平发送,低电平接收)
3.3V VCC 电源
GND GND 共地

1.2 MODBUS RTU协议帧格式

[从机地址][功能码][数据起始地址][数据数量/数据值][CRC16校验]
  • 地址:1字节(1-247)
  • 功能码:1字节(03=读保持寄存器,06=写单个寄存器,16=写多个寄存器)
  • 数据:N字节
  • CRC16:2字节(低位在前)

二、代码实现

2.1 头文件(modbus_rtu.h)

#ifndef __MODBUS_RTU_H
#define __MODBUS_RTU_H#include "stm32f10x.h"// MODBUS配置
#define MODBUS_SLAVE_ADDR   0x01    // 从机地址
#define MODBUS_BAUDRATE    9600     // 波特率
#define MODBUS_REG_NUM      10      // 寄存器数量// 功能码定义
#define FUNC_READ_REG      0x03    // 读保持寄存器
#define FUNC_WRITE_SINGLE  0x06    // 写单个寄存器
#define FUNC_WRITE_MULTI  0x10    // 写多个寄存器// 错误码定义
#define ERR_NONE           0x00     // 无错误
#define ERR_FUNC_CODE     0x01     // 非法功能码
#define ERR_REG_ADDR      0x02     // 非法数据地址
#define ERR_REG_VALUE     0x03     // 非法数据值// 全局变量
extern uint16_t modbus_regs[MODBUS_REG_NUM];  // MODBUS寄存器数组
extern uint8_t modbus_rx_buf[256];            // 接收缓冲区
extern uint8_t modbus_tx_buf[256];            // 发送缓冲区
extern uint8_t modbus_rx_len;                 // 接收长度
extern uint8_t modbus_frame_flag;              // 帧接收完成标志// 函数声明
void MODBUS_Init(void);
void MODBUS_ProcessFrame(void);
void MODBUS_SendResponse(uint8_t *data, uint8_t len);
uint16_t CRC16_Calculate(uint8_t *data, uint16_t len);
void MODBUS_UpdateDisplay(void);#endif /* __MODBUS_RTU_H */

2.2 主程序(main.c)

#include "stm32f10x.h"
#include "modbus_rtu.h"
#include "usart.h"
#include "delay.h"
#include "lcd.h"// MODBUS寄存器数据
uint16_t modbus_regs[MODBUS_REG_NUM] = {0x1234, 0x5678, 0x9ABC, 0xDEF0, 0x1111,0x2222, 0x3333, 0x4444, 0x5555, 0x6666
};// 通信缓冲区
uint8_t modbus_rx_buf[256];
uint8_t modbus_tx_buf[256];
uint8_t modbus_rx_len = 0;
uint8_t modbus_frame_flag = 0;// 系统状态显示
typedef struct {uint32_t rx_count;       // 接收帧计数uint32_t tx_count;       // 发送帧计数uint16_t last_reg_addr;   // 最后操作的寄存器地址uint16_t last_reg_value;  // 最后操作的寄存器值uint8_t last_func_code;   // 最后功能码
} SystemStatus;SystemStatus sys_status = {0};int main(void)
{// 系统初始化SystemInit();Delay_Init();USART1_Init(9600);  // MODBUS通信串口USART2_Init(115200); // 调试输出串口LCD_Init();          // LCD显示屏printf("STM32 MODBUS RTU Slave Starting...\r\n");LCD_ShowString(0, 0, "MODBUS RTU Slave");LCD_ShowString(0, 20, "Addr: 0x01 Baud: 9600");// MODBUS初始化MODBUS_Init();uint32_t last_display_update = 0;while(1){// 处理MODBUS帧if(modbus_frame_flag){MODBUS_ProcessFrame();modbus_frame_flag = 0;modbus_rx_len = 0;}// 每秒更新显示if(millis() - last_display_update > 1000){MODBUS_UpdateDisplay();last_display_update = millis();}Delay_ms(10);}
}

2.3 MODBUS核心实现(modbus_rtu.c)

#include "modbus_rtu.h"
#include "string.h"// GPIO控制RS485收发
#define RS485_TX_EN()   GPIO_SetBits(GPIOA, GPIO_Pin_8)   // PA8高电平,发送模式
#define RS485_RX_EN()   GPIO_ResetBits(GPIOA, GPIO_Pin_8) // PA8低电平,接收模式/*** @brief  MODBUS初始化*/
void MODBUS_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;// 1. 使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);// 2. 配置RS485收发控制引脚(PA8)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);RS485_RX_EN();  // 默认接收模式// 3. 配置USART1引脚(PA9-TX, PA10-RX)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// 4. USART参数配置USART_InitStructure.USART_BaudRate = MODBUS_BAUDRATE;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_Init(USART1, &USART_InitStructure);// 5. 使能接收中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 6. NVIC配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 7. 使能USARTUSART_Cmd(USART1, ENABLE);printf("MODBUS RTU Initialized: Addr=0x%02X, Baud=%d\r\n", MODBUS_SLAVE_ADDR, MODBUS_BAUDRATE);
}/*** @brief  CRC16计算* @param  data: 数据指针* @param  len: 数据长度* @retval CRC16校验值*/
uint16_t CRC16_Calculate(uint8_t *data, uint16_t len)
{uint16_t crc = 0xFFFF;uint16_t i, j;for(i = 0; i < len; i++){crc ^= data[i];for(j = 0; j < 8; j++){if(crc & 0x0001){crc >>= 1;crc ^= 0xA001;  // MODBUS CRC多项式}else{crc >>= 1;}}}return crc;
}/*** @brief  发送MODBUS响应* @param  data: 发送数据指针* @param  len: 数据长度*/
void MODBUS_SendResponse(uint8_t *data, uint8_t len)
{uint16_t crc;// 切换到发送模式RS485_TX_EN();Delay_us(10);// 发送数据for(uint8_t i = 0; i < len; i++){USART_SendData(USART1, data[i]);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);}// 等待发送完成while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);// 切换回接收模式Delay_us(10);RS485_RX_EN();sys_status.tx_count++;
}/*** @brief  处理MODBUS帧*/
void MODBUS_ProcessFrame(void)
{uint8_t slave_addr = modbus_rx_buf[0];uint8_t func_code = modbus_rx_buf[1];uint16_t crc_received, crc_calculated;// 检查从机地址if(slave_addr != MODBUS_SLAVE_ADDR && slave_addr != 0x00){return;  // 不是发给本机的帧}// 检查CRCcrc_received = (modbus_rx_buf[modbus_rx_len-1] << 8) | modbus_rx_buf[modbus_rx_len-2];crc_calculated = CRC16_Calculate(modbus_rx_buf, modbus_rx_len-2);if(crc_received != crc_calculated){printf("CRC Error: Received=0x%04X, Calculated=0x%04X\r\n", crc_received, crc_calculated);return;}sys_status.last_func_code = func_code;sys_status.rx_count++;printf("RX Frame: ");for(uint8_t i = 0; i < modbus_rx_len; i++){printf("%02X ", modbus_rx_buf[i]);}printf("\r\n");// 处理不同功能码switch(func_code){case FUNC_READ_REG:  // 读保持寄存器Handle_ReadRegisters();break;case FUNC_WRITE_SINGLE:  // 写单个寄存器Handle_WriteSingleRegister();break;case FUNC_WRITE_MULTI:  // 写多个寄存器Handle_WriteMultipleRegisters();break;default:  // 非法功能码Send_ExceptionResponse(ERR_FUNC_CODE);break;}
}/*** @brief  处理读寄存器请求*/
void Handle_ReadRegisters(void)
{uint16_t start_addr = (modbus_rx_buf[2] << 8) | modbus_rx_buf[3];uint16_t reg_count = (modbus_rx_buf[4] << 8) | modbus_rx_buf[5];uint8_t resp_len = 0;uint16_t crc;// 检查地址合法性if(start_addr >= MODBUS_REG_NUM || start_addr + reg_count > MODBUS_REG_NUM){Send_ExceptionResponse(ERR_REG_ADDR);return;}// 构建响应帧modbus_tx_buf[resp_len++] = MODBUS_SLAVE_ADDR;modbus_tx_buf[resp_len++] = FUNC_READ_REG;modbus_tx_buf[resp_len++] = reg_count * 2;  // 字节数// 添加寄存器数据for(uint16_t i = 0; i < reg_count; i++){modbus_tx_buf[resp_len++] = (modbus_regs[start_addr + i] >> 8) & 0xFF;modbus_tx_buf[resp_len++] = modbus_regs[start_addr + i] & 0xFF;}// 计算CRCcrc = CRC16_Calculate(modbus_tx_buf, resp_len);modbus_tx_buf[resp_len++] = crc & 0xFF;modbus_tx_buf[resp_len++] = (crc >> 8) & 0xFF;// 发送响应MODBUS_SendResponse(modbus_tx_buf, resp_len);sys_status.last_reg_addr = start_addr;sys_status.last_reg_value = modbus_regs[start_addr];printf("Read Registers: Addr=%d, Count=%d\r\n", start_addr, reg_count);
}/*** @brief  处理写单个寄存器请求*/
void Handle_WriteSingleRegister(void)
{uint16_t reg_addr = (modbus_rx_buf[2] << 8) | modbus_rx_buf[3];uint16_t reg_value = (modbus_rx_buf[4] << 8) | modbus_rx_buf[5];uint8_t resp_len = 0;uint16_t crc;// 检查地址合法性if(reg_addr >= MODBUS_REG_NUM){Send_ExceptionResponse(ERR_REG_ADDR);return;}// 写入寄存器modbus_regs[reg_addr] = reg_value;// 回显相同的数据作为响应memcpy(modbus_tx_buf, modbus_rx_buf, 6);resp_len = 6;// 计算CRCcrc = CRC16_Calculate(modbus_tx_buf, resp_len);modbus_tx_buf[resp_len++] = crc & 0xFF;modbus_tx_buf[resp_len++] = (crc >> 8) & 0xFF;// 发送响应MODBUS_SendResponse(modbus_tx_buf, resp_len);sys_status.last_reg_addr = reg_addr;sys_status.last_reg_value = reg_value;printf("Write Register: Addr=%d, Value=0x%04X\r\n", reg_addr, reg_value);
}/*** @brief  发送异常响应*/
void Send_ExceptionResponse(uint8_t error_code)
{uint8_t resp_len = 0;uint16_t crc;modbus_tx_buf[resp_len++] = MODBUS_SLAVE_ADDR;modbus_tx_buf[resp_len++] = 0x80 | modbus_rx_buf[1];  // 错误响应功能码modbus_tx_buf[resp_len++] = error_code;crc = CRC16_Calculate(modbus_tx_buf, resp_len);modbus_tx_buf[resp_len++] = crc & 0xFF;modbus_tx_buf[resp_len++] = (crc >> 8) & 0xFF;MODBUS_SendResponse(modbus_tx_buf, resp_len);printf("Exception Response: Error Code=%d\r\n", error_code);
}/*** @brief  更新显示*/
void MODBUS_UpdateDisplay(void)
{char display_buf[32];LCD_ShowString(0, 40, "MODBUS Status:");sprintf(display_buf, "RX: %lu TX: %lu", sys_status.rx_count, sys_status.tx_count);LCD_ShowString(0, 60, display_buf);sprintf(display_buf, "Func: 0x%02X", sys_status.last_func_code);LCD_ShowString(0, 80, display_buf);sprintf(display_buf, "Reg[%d]: 0x%04X", sys_status.last_reg_addr, sys_status.last_reg_value);LCD_ShowString(0, 100, display_buf);// 显示部分寄存器值sprintf(display_buf, "R0:%04X R1:%04X", modbus_regs[0], modbus_regs[1]);LCD_ShowString(0, 120, display_buf);
}

2.4 串口中断处理(stm32f10x_it.c)

#include "stm32f10x_it.h"
#include "modbus_rtu.h"extern uint8_t modbus_rx_buf[];
extern uint8_t modbus_rx_len;
extern uint8_t modbus_frame_flag;// 帧超时检测(3.5个字符时间)
#define FRAME_TIMEOUT   (3500000 / MODBUS_BAUDRATE)  // 微秒void USART1_IRQHandler(void)
{static uint32_t last_rx_time = 0;uint8_t data;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){data = USART_ReceiveData(USART1);// 检查缓冲区是否溢出if(modbus_rx_len < sizeof(modbus_rx_buf)){modbus_rx_buf[modbus_rx_len++] = data;}// 更新最后接收时间last_rx_time = micros();// 清除中断标志USART_ClearITPendingBit(USART1, USART_IT_RXNE);}// 在主循环中检查帧超时if(modbus_rx_len > 0 && (micros() - last_rx_time) > FRAME_TIMEOUT){modbus_frame_flag = 1;}
}

2.5 调试串口输出(usart.c)

#include "usart.h"
#include <stdarg.h>
#include <stdio.h>// 重定向printf到USART2
int fputc(int ch, FILE *f)
{USART_SendData(USART2, (uint8_t)ch);while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);return ch;
}void USART2_Init(uint32_t baudrate)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);// PA2-TX, PA3-RXGPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);USART_InitStructure.USART_BaudRate = baudrate;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_Init(USART2, &USART_InitStructure);USART_Cmd(USART2, ENABLE);
}

三、测试工具与验证

3.1 PC端测试工具

  1. Modbus Poll(Windows)

    • 连接:选择COM口,波特率9600,RTU模式
    • 功能码03:读取寄存器
    • 功能码06:写入单个寄存器
  2. QModMaster(跨平台)

    • 开源MODBUS主站工具
    • 支持RTU/TCP

3.2 测试步骤

  1. 连接硬件:STM32通过RS485模块连接到PC

  2. 启动从机:STM32运行MODBUS从机程序

  3. 发送测试帧

    读寄存器:01 03 00 00 00 01 84 0A
    (从机地址01,功能码03,起始地址0000,读取1个寄存器)
    
  4. 观察响应

    响应帧:01 03 02 12 34 B5 33
    (从机地址01,功能码03,字节数2,数据1234,CRC校验)
    

3.3 LCD显示效果

MODBUS RTU Slave
Addr: 0x01 Baud: 9600
MODBUS Status:
RX: 125 TX: 124
Func: 0x03
Reg[0]: 0x1234
R0:1234 R1:5678

四、常见问题解决

现象 原因 解决方案
无响应 地址不匹配 检查从机地址设置
CRC错误 波特率不一致 确认双方波特率相同
数据错乱 收发切换延迟不足 增加RS485收发切换延时
丢帧 中断处理不及时 优化中断服务程序
显示乱码 LCD初始化问题 检查LCD引脚连接

参考代码 STM32+MODEBUS+485代码,串口发送,实时显示 www.youwenfan.com/contentcnu/56209.html

五、扩展功能建议

5.1 增加更多功能码

// 增加04功能码(读输入寄存器)
case 0x04:Handle_ReadInputRegisters();break;// 增加05功能码(写单个线圈)
case 0x05:Handle_WriteSingleCoil();break;

5.2 数据记录与报警

// 监控寄存器变化
if(modbus_regs[0] > 0xFF00)
{// 触发报警GPIO_SetBits(GPIOB, GPIO_Pin_0);  // 蜂鸣器报警printf("Alarm Triggered! Value=0x%04X\r\n", modbus_regs[0]);
}

5.3 多从机支持

// 支持广播地址0x00
if(slave_addr == MODBUS_SLAVE_ADDR || slave_addr == 0x00)
{// 处理广播命令Process_BroadcastCommand();
}

六、总结

这套STM32+MODBUS+RS485方案具有以下特点:

完整协议实现:支持03/06/16功能码
实时数据显示:LCD实时显示通信状态
稳定可靠:CRC校验、超时检测、错误处理
易于扩展:模块化设计,方便添加新功能

应用场景

  • 工业自动化控制系统
  • 智能仪表数据采集
  • 楼宇自动化系统
  • 远程监控系统

通过LCD实时显示MODBUS通信状态,可以直观地监控系统运行情况,便于调试和维护。

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

相关文章:

  • 2026热门室内地图建模工具推荐:SLAM与矢量绘制全收录 - 品牌2025
  • 大语言模型(LLM)入门学习路线图
  • 2026最新避暑攻略/景点/景区/打卡地推荐!贵州优质避暑目的地榜单发布,高口碑值得去贵阳安顺等地避暑打卡地推荐 - 十大品牌榜
  • 2025届学术党必备的六大AI论文方案横评
  • RK3399开发环境搭建实录:在Ubuntu 22.04上配置Arm GNU Toolchain 12.2交叉编译器的完整流程
  • 退休金的本质的庖丁解牛
  • 2026年温州黄金回收六家机构实测对比 避坑指南与优选推荐 - 福正美黄金回收
  • 漫画翻译工具完全指南:5分钟快速上手,轻松翻译日漫
  • 2026年全国沥青筑路设备采购指南:德州霖垚与山东五大厂商深度横评 - 企业名录优选推荐
  • 建站公司哪家安全性最高?良心推荐以下4家平台! - FaiscoJeff
  • Android手把手编写儿童手机远程监控App之UUID
  • 2026年温州视频制作:从技术赋能到全品类定制的行业进阶路径 - GrowthUME
  • 智能制造行业海外营销代运营公司有哪些?涵盖海外营销代运营服务商+外贸AI营销平台推荐,高效拓客不踩坑(附带联系方式) - 品牌2026
  • 3步掌握飞书文档转Markdown:告别手动复制的完整指南
  • 千问3.5-9B运维知识库构建:智能故障诊断与解决方案推荐
  • 别再死记硬背了!用COCA和BNC语料库,像母语者一样地道学英语
  • UGUI源码剖析 (24):常用插件扩展介绍
  • 洛谷官方题单[Java版题解]--【入门3】循环结构
  • 如何通过 NoETL 指标平台构建企业唯一指标计算中心
  • 3个关键步骤彻底解决电脑风扇噪音!Fan Control完全指南
  • 5G应用下的网络延迟测试专业方案
  • 2025届学术党必备的十大AI辅助论文神器实际效果
  • 15分钟构建专业级流程图:Flowchart-Vue组件实战指南
  • 从房价预测到信贷评分:岭回归在真实业务场景中的落地实践与避坑指南
  • 【花雕动手做】当设备学会“思考”:ESP-Claw如何用AI重塑物联网的未来
  • 2026年3月评价好的工业厂房搭建公司口碑推荐,工业厂房搭建工程,专业电气布局,厂房用电安全便捷 - 品牌推荐师
  • **发散创新:基于Python的渗透测试自动化框架设计与实战**在网络安全日益
  • 如何快速构建微信智能助手:实用高效的自动化工具指南
  • MySQL 主从同步延迟优化
  • IPv6无状态配置的‘潜规则’:RA报文里那些M/O/A位,到底怎么设才安全又高效?