MODBUS-RTU协议详细整理(基于RS485、Uart、stm32f1)
RS485、MODBUS
串口、UART、TTL、RS232、RS422、RS485之间的联系
串口指通过串行通信方式传输数据的接口,数据按位(bit)顺序传输。是一个物理或逻辑接口的概念。UART是串口通信的协议,RS232、RS422、RS485是定义物理层电气特性和接口的标准。
UART(通用异步收发器):一种串行通信协议,负责将并行数据转换为串行数据(或反向转换),定义数据帧格式(起始位、数据位、停止位等),不涉及电气特性。
![]()
TTL是一种逻辑电平标准,通常用于数字电路。‘1’ 2.4v~3.3v ‘0’ 0~0.4v。适合短距离通信(通常几厘米到几米),不适合长距离传输。应用:微控制器、传感器等短距离 UART 通信(TTL 串口)。
CMOS是一种基于场效应晶体管(MOSFET)的**逻辑电平标准,广泛用于现代集成电路。‘1’ 0.7VDD ~ VDD ‘0’ 0V ~ 0.3VDD**,VDD取3.3V、5V等。功耗极低,抗干扰能力较强,输入阻抗高,输出驱动能力稍弱。应用:现代低功耗设备,如 3.3V 微控制器(CMOS 串口)。
RS232是一种串行通信物理层标准,定义了物理层接口和电平标准。“1” -3V ~ -15V,“0” +3V ~ +15V。单端点对点全双工通信,传输距离通常不超过15米。
RS422是一种差分信号(两条线表示一个信号,电压差判定逻辑)的串行通信物理层标准,抗干扰强,支持一对多点全双工通信(一个发送端连多个接收端,最多10个)。支持更远距离和更高速度的通信。传输距离可达1200米(速率降低时)。**
RS485是一种差分信号的串行通信物理层标准。**差分信号抗干扰,多点半双工通信,可组网(标准支持32个单元负载,部分设备支持128+个)。距离和速率与RS422类似。“1”:两线电压差 +(2~6)V “0”:两线电压差 -(2~6)VTTL、CMOS 与 RS232、RS422、RS485 的区别
![]()
TTL和CMOS是数字逻辑电路技术,可以说是逻辑电平标准,决定UART信号的电压范围,适合短距离通信。如芯片间或板级通信;
RS232、RS422、RS485是物理层标准,解决UART的长距离传输问题,不仅包括电平标准,还定义了信号传输方式(单端或差分)、连接方式(点对点或多点)等,适合不同场景。如设备间长距离数据传输。TTL/CMOS电平的UART信号可通过转换芯片(如 MAX232、MAX490、MAX485)适配 RS232、RS422或 RS485标准
4.1 关于S485
RS485是隶属于OSI模型**物理层的电气特性标准,采用两线差分信号,支持半双工、多点通信。**
-
电平标准:逻辑“1”:两线间电压差 +(2~6)V。 逻辑“0”:两线间电压差 -(2~6)V。接口电平低,不易损坏芯片。
-
传输速率高、距离远:10米最高35Mbps,1200米可达100kbps;最长1200米;
-
抗干扰强:差分传输+平衡驱动器,抗共模干扰,适合工业环境。
-
支持节点多:标准支持32个,特制芯片可达128~400 个。
-
连接方式:推荐线型或总线型结构,避免星型或环型;
- 理想情况下,需在总线起止端(即主机和设备 4)各加入一个 120Ω 匹配电阻。
-
示例:MAX3485—>一款RS-485/RS-422 收发器芯片,专为高速、抗干扰、多点通信设计
-
- 硬件连接
-
-
-
-
-
-
组网功能强,支持多设备联网,可在串口协议基础上使用 MODBUS 协议。
-
==增强RS485数据传输健壮性的方式==
1、带屏蔽层的双绞线(抗干扰)
2、总线连接–线型/总线型(所有设备通过一条双绞线依次串接(A/B 线))。 差分总线协议,星形/环形拓扑会导致阻抗不匹配,引发信号反射
3、加终端电阻(抑制信号反射),标准值 120Ω。通信速率 > 500kbps 或 传输距离 > 50米 时必须添加。
4、光耦隔离器,解决工业设备间的地电位差问题,防止通信误码和芯片损坏。
当然可以,以下是对你原文的简化版总结,保留核心信息,便于快速理解:
4.2 MODBUS协议
RS-485、串口基础协议(UART)、MODBUS RTU三者的联系 MODBUS参考文章
RS-485(物理层)是一种串行通信标准,支持多点(32+)、长距离通信(<1200米),抗干扰能力强。
- 例:差分信号(A/B线)抗共模干扰、多设备总线(可挂接多个从机)、收发切换(半双工通信,需控制方向)
串口基础协议(如UART,物理层、数据链路层):定义了串行通信的基本数据格式(波特率、数据位、停止位、校验位等),提供点对点短距(如RS-232)或多点长距(如RS-485)**的异步串行通信。**是一种协议。
**MODBUS协议(应用层、数据链路层):基于串口基础协议构建数据帧格式和主从通信逻辑,用于设备间的数据交互。**是一种应用层协议,运行在串口标准(如RS-232/485)、TCP/IP等物理层之上。
-
数据帧格式:
从机地址(1字节)、功能码(1字节)、数据(可变长度)、CRC16校验(2字节) -
主从通信:主站发送请求帧,从站响应,主站解析响应帧。
-
#1. Modbus RTU:串口通信,二进制格式,效率高。 #2. Modbus ASCII:串口通信,ASCII码格式,效率低,适用于低通信量场景。 #3. Modbus TCP:以太网通信,基于 TCP/IP 协议OSI模型将网络通信划分为七层,每层负责不同的功能。从底层到高层依次为:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
OSI层级 UART RS-485 Modbus RTU 物理层 TTL/CMOS电平;如需RS-232/RS-485需外接电平转换芯片 差分双线电气标准 依赖RS-485/RS-232等物理介质 数据链路层 UART帧格式(起始位/停止位/校验等) 无定义(需使用UART等帧协议) 串行帧格式(地址、功能码、数据、CRC) **网络层 ** 不适用 不适用(多点地址由上层协议定义) 不适用(点对点,无路由) **传输层 ** 不适用 不适用 无独立实现(字节序等编码规则属于应用层的一部分) **会话层 ** 不适用 不适用 无独立实现(请求-响应逻辑由应用层处理) **表示层 ** 不适用 不适用 不适用 **应用层 ** 不适用 不适用(由上层协议实现) Modbus协议通信逻辑(读写寄存器/线圈等)
ModBus是一种应用层主从式协议,通常采用串口通信(如RS-232C/RS-485),并使用主从结构:一个主站发起数据访问,多个从站响应。每个从站有唯一地址(1~247),其中地址0为广播地址(用于广播模式,无需从站响应)
-
异步通讯、字节为单位,11位串口数据帧格式(如uart,含校验码)、n字节ModBus数据帧结构
-
数据帧格式:
从机地址(1字节)、功能码(1字节)、数据(可变长度)、CRC16校验(2字节) -
通信过程:主站发送请求帧,符合地址的从站接收命令,验证CRC。验证正确,返回响应帧(正常/异常)由主站接收解析处理;CRC错误,不返回任何数据
- 异常响应帧仅在 CRC 正确、但请求内容无效(如非法功能码、地址)时返回。
-
数据区:存放需要读取/写入的寄存器地址、数量、以及数据
MODBUS RTU数据帧格式:地址码、功能码、数据区和CRC校验。 Modbus协议规定为 大端序
-
地址码 1 字节 指定目标从站的地址(1-247),0 为广播地址。 功能码 1 字节 定义主机请求/从机响应的操作(读/写等) 数据区 N 字节 数据内容或参数(高字节在前) CRC校验 2 字节 错误检测,16位冗余循环码,低字节在前 -
帧间隔:帧之间有至少3.5 个字符时间的静默间隔(由波特率决定),用于帧同步-
1字符 = 11位(1起始位 + 8数据位 + 1停止位 + 1校验位) 9600波特率下,字符时间(秒) = 11/波特率 ≈ 1ms,则帧间隔=3.5*1≈3.5ms
-
-
常用功能码
| 功能码 | 功能 | 功能描述 | 请求帧 | 正常响应帧 | 异常响应帧 |
|---|---|---|---|---|---|
| 0x01 | 读线圈状态 | 读位(读Nbit),读取一组线圈的当前状态 | [Addr][01][起始bit位/线圈地址][线圈数量][CRC] | [Addr][01][字节数][线圈状态字节流][CRC] | [从机地址Addr][功能码81][异常码Ex][CRC] |
| 0x02 | 读离散输入 | 读位(读Nbit)读取一组离散输入的当前状态 | 同0x01 | 同0x01 | [Addr][82][Ex][CRC] |
| 0x03 | 读保持寄存器 | 读取一个或多个保持寄存器的值 | [Addr][03][起始寄存器地址][寄存器数量][CRC] | [Addr][03][字节数][寄存器数据流][CRC] | [Addr][83][Ex][CRC] |
| 0x04 | 读输入寄存器 | 读取一个或多个只读输入寄存器的值 | 同0x03 | 同0x03 | [Addr][84][Ex][CRC] |
| 0x05 | 写单线圈 | 写位(写1bit),写单个线圈状态(开或关) | [Addr][05][线圈地址][写入值][CRC] (FF00=ON, 0000=OFF) | 同请求帧 | [Addr][85][Ex][CRC] |
| 0x06 | 写单寄存器 | (写半字),写入一个保持寄存器的值 | [Addr][06][寄存器地址][值][CRC] | 同请求帧 | [Addr][86][Ex][CRC] |
| 0x0F | 写多线圈 | 写入多个线圈状态 | [Addr][0F][起始线圈地址][数量][字节数][数据][CRC] | [Addr][0F][起始线圈地址][线圈数量][CRC] | [Addr][8F][Ex][CRC] |
| 0x10 | 写多寄存器 | 多个保持寄存器值 | [Addr][10][起始寄存器地址][数量][字节数][数据][CRC] | [Addr][10][起始寄存器地址][寄存器数量][CRC] | [Addr][90][Ex][CRC] |
- 常见异常码:01=非法功能,02=非法地址,03=非法值,04=设备故障。
- 线圈:1位布尔值0/1,表示开关状态(如开/关)
- 输入保持寄存器:16位无符号整数,存储设备参数或数据。
- 输入寄存器:16位无符号整数,只读,存储实时采集的数据(如温度、电压等实时数据)
5.示例
5.1 modbus主机
实现一个Modbus RTU主站,能够周期性发送读寄存器请求(功能码 03),接收并解析从机响应,存储寄存器数据
硬件初始化:初始化UART4(PC10-TX, PC11-RX,PC8作为RS485方向控制引脚–0=接收,1=发送)用于RS485通信
- 初始化 STM32F10x 系统时钟、LED、UART(其他串口)、定时器(TIM2,周期 1ms)以及 UART4(波特率 9600,8 位数据,无校验,1 位停止位)。
- 支持与 PC 上运行的 Modbus Slave 软件交互,通过 USB 转串口转换器实现电平转换和协议适配
Modbus主站功能:生成Modbus RTU请求帧(以03功能码读保持寄存器为例){从机地址, 功能码, 起始地址高字节, 起始地址低字节, 寄存器数量高字节, 寄存器数量低字节, CRC高字节, CRC低字节} CRC在线计算
- 发送请求帧到从站设备(使用上位机软件Modbus Slave),接收并解析从站响应帧数据
数据处理:解析从站返回的寄存器数据(16位值),将解析结果存储在master_data数组中
现象:每500ms翻转LED1,表示程序运行状态。打印从机响应帧解析来的数据至串口助手
Modbus Slave配置
main.c
-
#include <stm32f10x.h> #include "led.h" #include "delay.h" #include "uart.h"//printf函数支持 #include "timer.h" #include "modbus_master.h" int main(void) { Systick_Init(); Led_GPIO_Init(); Uart_Init(); Timer_Init(100, 720); Uart4_Init();//初始化UART4 //uint8_t modbus_read_request[8] = {0x02, 0x03, 0x00, 0x00, 0x00, 0x04, 0x44, 0x3A}; //示例:生成Modbus_RTU读多路寄存器输入寄存器请求帧,返回长度 uint8_t modbus_read_request[8]; u8 request_frame_length = modbus_generate_request_frame(0x02, 0x03, 0x0000, 0x0004, modbus_read_request); while (1) { //每500ms执行一次发送任务 if (sendTask[0] > sendTask[1]) { sendTask[0] = 0; // 重置计数器 LED1 = !LED1; // 翻转LED1 //向从机发送Modbus请求帧 Uart4_SendStr(modbus_read_request, request_frame_length); //接收从机响应帧,进行数据帧解析 if (uart4_rx_status & 0x8000) { //modbus_frame_parse();//modbus协议帧解析 } Flash_Test(); } if (receiveTask[0] > receiveTask[1]) receiveTask[0] = 0; // 重置计数器 } return 0; }
modbus_master.c
-
/*PC10 UART4_Tx 复用推挽输出 PC11 UART4_Rx 浮空/上拉输入 PC8 RS485EN 通用推挽输出 0输入 1输出 软件环境:RS485接口 modbus-RTU协议 PC安装上位机软件Modbus Slave 硬件环境:单片机stm32RCT6(含RS485模块)、外接另一个RSRS485模块、通过USB转串口转换器 与PC上的Modbus Slave软件进行交互 USB转串口转换器:实现电脑与STM32之间的电平转换、协议适配和电气隔离*/ #include <stdio.h> // 也包含NULL定义 #include "modbus.h" uint8_t uart4_rx_buffer[UART4_RX_BUFFER_SIZE];// 接收缓冲区 volatile uint16_t uart4_rx_status;//接收状态:设Bit15=完成,Bit14:0=计数 void Uart4_Gpio_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef gpio_init = {0}; // PC10: UART4_Tx 复用推挽输出 gpio_init.GPIO_Mode = GPIO_Mode_AF_PP; gpio_init.GPIO_Pin = GPIO_Pin_10; gpio_init.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &gpio_init); // PC11: UART4_Rx 浮空输入 gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING; gpio_init.GPIO_Pin = GPIO_Pin_11; GPIO_Init(GPIOC, &gpio_init); // PC8: RS485EN 通用推挽输出 gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; gpio_init.GPIO_Pin = GPIO_Pin_8; GPIO_Init(GPIOC, &gpio_init); // 默认接收模式 RS485_MODE = 0; } // 初始化 UART4 void Uart4_Init(void) { //1.初始化GPIO,开启USART4时钟 Uart4_Gpio_Init(); RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE); //2.配置UART4,设置波特率、流控制、启用发送和接收模式、奇偶校验位、停止位、数据位数等 USART_InitTypeDef usart_init = {0}; usart_init.USART_BaudRate = 9600; usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; usart_init.USART_Parity = USART_Parity_No; usart_init.USART_StopBits = USART_StopBits_1; usart_init.USART_WordLength = USART_WordLength_8b; USART_Init(UART4, &usart_init); USART_Cmd(UART4, ENABLE);// 使能 UART4 //3.中断配置 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef nvic_init = {0}; nvic_init.NVIC_IRQChannel = UART4_IRQn; nvic_init.NVIC_IRQChannelPreemptionPriority = 1; nvic_init.NVIC_IRQChannelSubPriority = 1; nvic_init.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init);//misc.c USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);// 使能 RXNE 中断 USART_ITConfig(UART4, USART_IT_IDLE, ENABLE);// 使能 IDLE 中断 } //UART4字符串发送函数 void Uart4_SendStr(uint8_t *data, uint8_t length) { RS485_MODE = 1; // 发送模式 while (length--) { while (!(UART4->SR & 0x40)); // 等待发送完成 UART4->DR = *data++; } while (!(UART4->SR & 0x40)); // 确保最后一个字节发送完成 RS485_MODE = 0; // 接收模式 } //生成ModbusRTU请求帧 //参数:从机地址、功能码、读取寄存器的起始地址、请求数据的长度、指向输出请求帧缓冲区的指针(至少8字节) uint8_t modbus_generate_request_frame(uint8_t slave_address, uint8_t function_code, uint16_t start_register,uint16_t register_count, uint8_t *request) { // 检查参数有效性 if (request == NULL || slave_address == 0 || register_count == 0 || register_count > 125) { return 0; // 参数无效,返回0 } // 填充请求帧 request[0] = slave_address; // 从机地址 request[1] = function_code; // 功能码 request[2] = (start_register >> 8); // 起始寄存器地址高字节 request[3] = (start_register & 0xFF); // 起始寄存器地址低字节 request[4] = (register_count >> 8); // 寄存器数量高字节 request[5] = (register_count & 0xFF); // 寄存器数量低字节 // 计算CRC16校验码 uint16_t crc = modbus_crc16(request, 6); request[6] = (crc >> 8); // CRC高字节 request[7] = (crc & 0xFF); // CRC低字节 return 8; // 返回帧长度 } uint16_t master_data[10];//Modbus接收数据缓冲区,存储从机发来的寄存器数据 //解析Modbus数据帧中的寄存器数据 void modbus_data_parse(void) { uint8_t frame_pos = 3;//从机响应帧0/1/2为从机地址/功能码/读取字节数 uint16_t data_length = uart4_rx_buffer[2];//数据长度 if (data_length > 20 || data_length % 2 != 0) return;//获取数据长度是否合法 // 解析字节对至接收数据缓冲区 for (uint16_t i = 0; i < data_length / 2; i++) { master_data[i] = (uart4_rx_buffer[frame_pos++] << 8); master_data[i] |= uart4_rx_buffer[frame_pos++]; } // 输出解析结果(便于观察,调试用) printf("%x %x %x %x\n", master_data[0], master_data[1], master_data[2], master_data[3]); } //解析Modbus RTU数据帧 void modbus_frame_parse(void) { uint16_t frame_length = uart4_rx_status & 0x7FFF;//数据帧长度 //验证CRC校验 uint16_t crcRx = (uart4_rx_buffer[frame_length - 2] << 8) | uart4_rx_buffer[frame_length - 1]; uint16_t crcTx = modbus_crc16(uart4_rx_buffer, frame_length - 2); //检查设备ID和功能码 if (crcRx == crcTx) { uint8_t idx = 0; if (MODBUS_DEVICE_ID == uart4_rx_buffer[idx++]) { if (MODBUS_FUNCTION_CODE == uart4_rx_buffer[idx]) { switch (uart4_rx_buffer[idx]) { case 0: break; case 1: break; case 2: break; case 3: modbus_data_parse(); break; default: break; } } } } //清空缓冲区并重置状态 memset(uart4_rx_buffer, 0, frame_length); uart4_rx_status = 0; } //UART4中断服务函数 void UART4_IRQHandler(void) { //处理RXNE中断:接收数据并存入缓冲区 if(USART_GetITStatus(UART4, USART_IT_RXNE)){ //读取接收到的数据,自动清除RXNE标志 uint8_t data = (uint8_t)USART_ReceiveData(UART4); uart4_rx_buffer[uart4_rx_status++] = data; //检查缓冲区是否溢出 if ((uart4_rx_status & 0x3FFF) > UART4_RX_BUFFER_SIZE - 1) uart4_rx_status = 0; // 缓冲区溢出重置 USART_ClearITPendingBit(UART4, USART_IT_RXNE); } //处理总线空闲中断 if(USART_GetITStatus(UART4, USART_IT_IDLE)){ uart4_rx_status |= 0x8000;//设置接收完成标志(Bit15) UART4->SR; UART4->DR; //清除IDLE中断 } } //查表法计算CRC16 高效 //CRC高位字节值表 const u8 auchCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 } ; //CRC低位字节值表 const u8 auchCRCLo[] = { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 } ; //查表法计算计算Modbus RTU的CRC16校验码 u16 modbus_crc16(u8 *puchMsg, u16 usDataLen) { u8 uchCRCHi = 0xFF ; // CRC高位字节初始值 u8 uchCRCLo = 0xFF ; u32 uIndex ; while (usDataLen--) // 逐字节处理数据 { uIndex = uchCRCHi ^ *puchMsg++ ; uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; uchCRCLo = auchCRCLo[uIndex] ; } return (uchCRCHi << 8 | uchCRCLo) ; // 返回16位CRC值(高位字节在前) }-
#ifndef _MODBUS_RTU_MASTER_H #define _MODBUS_RTU_MASTER_H #include "stm32f10x.h" #include "sys.h" #define UART4_RX_BUFFER_SIZE 200//UART4接收缓冲区大小 #define RS485_MODE PCout(8) //RS485模式控制引脚(PC8),0=接收,1=发送,默认接收 #define MODBUS_DEVICE_ID 0x02 #define MODBUS_FUNCTION_CODE 0x03 extern uint8_t uart4_rx_buffer[UART4_RX_BUFFER_SIZE];// 接收缓冲区 extern volatile uint16_t uart4_rx_status;//接收状态:设Bit15=完成,Bit14:0=计数 //typedef struct { // uint8_t slave_address; // 从机地址 // uint8_t function_code; // 功能码 // uint16_t start_register; // 起始寄存器地址 // uint16_t register_count; // 寄存器数量 // uint16_t crc; // CRC校验 //} ModbusRTU_Frame; void Uart4_Init(void);//初始化UART4 void Uart4_SendStr(uint8_t *data, uint8_t length);//通过UART4发送数据 uint16_t modbus_crc16(uint8_t *data, uint16_t length);//查表法计算CRC16 uint8_t modbus_generate_request_frame(uint8_t slave_address, uint8_t function_code, uint16_t start_register,uint16_t register_count, uint8_t *request);//生成Modbus RTU请求帧 void modbus_data_parse(void);//解析响应数据帧中的寄存器数据,并存入主/本机的寄存器 void modbus_frame_parse(void);////解析Modbus RTU从机响应数据帧,验证CRC并处理功能码 #endif
-
5.2 modbus从机
实现一个Modbus RTU从站,能够接收主机的读保持寄存器请求(功能码 03),从预定义的slave_data数组中返回寄存器数据,并生成符合Modbus RTU协议的响应帧发送回主机(Modbus Poll)。
硬件初始化:略
Modbus RTU从站功能:收到主站请求后,自动校验帧格式并生成响应帧{从机地址, 功能码, 字节数, 数据..., CRC高字节, CRC低字节}。
数据处理:从slave_data数组(包含预设寄存器数据,如 0x11, 0x22, 0x33, 0x44 等)读取指定起始地址和数量的寄存器数据,用于生成响应帧。
现象:每500ms翻转LED1,表示程序运行状态。打印从机响应帧的内容至串口助手。
Modbus Pull配置
main.c
-
#include <stm32f10x.h> #include "led.h" #include "delay.h" #include "uart.h"//printf函数支持 #include "timer.h" #include "modbus_slave.h" int main(void) { Systick_Init(); Led_GPIO_Init(); Uart_Init(); Timer_Init(100, 720); Uart4_Init();//初始化UART4 while (1) { //每500ms执行一次发送任务 if (sendTask[0] > sendTask[1]) { sendTask[0] = 0; // 重置计数器 LED1 = !LED1; // 翻转LED1 //接收主机请求帧,检查是否接收到完整数据帧,并进行数据处理,生成响应数据帧 if (uart4_rx_status & 0x8000) { modbus_frame_parse();//modbus协议帧解析 } Flash_Test(); } if (receiveTask[0] > receiveTask[1]) receiveTask[0] = 0; // 重置计数器 } return 0; }
modbus_slave.c
-
/*PC10 UART4_Tx 复用推挽输出 PC11 UART4_Rx 浮空/上拉输入 PC8 RS485EN 通用推挽输出 0输入 1输出 软件环境:RS485接口 modbus-RTU协议 PC安装上位机软件Modbus Poll 硬件环境:单片机stm32RCT6(含RS485模块)、外接另一个RSRS485模块、通过USB转串口转换器 与PC上的Modbus Poll软件进行交互*/ #include "modbus_slave.h" #include "uart.h" uint8_t uart4_rx_buffer[UART4_RX_BUFFER_SIZE];// 接收缓冲区 volatile uint16_t uart4_rx_status;//接收状态:设Bit15=完成,Bit14:0=计数 void Uart4_Gpio_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef gpio_init = {0}; // PC10: UART4_Tx 复用推挽输出 gpio_init.GPIO_Mode = GPIO_Mode_AF_PP; gpio_init.GPIO_Pin = GPIO_Pin_10; gpio_init.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &gpio_init); // PC11: UART4_Rx 浮空输入 gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING; gpio_init.GPIO_Pin = GPIO_Pin_11; GPIO_Init(GPIOC, &gpio_init); // PC8: RS485EN 通用推挽输出 gpio_init.GPIO_Mode = GPIO_Mode_Out_PP; gpio_init.GPIO_Pin = GPIO_Pin_8; GPIO_Init(GPIOC, &gpio_init); // 默认接收模式 RS485_MODE = 0; } // 初始化 UART4 void Uart4_Init(void) { //1.初始化GPIO,开启USART4时钟 Uart4_Gpio_Init(); RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE); //2.配置UART4,设置波特率、流控制、启用发送和接收模式、奇偶校验位、停止位、数据位数等 USART_InitTypeDef usart_init = {0}; usart_init.USART_BaudRate = 9600; usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; usart_init.USART_Parity = USART_Parity_No; usart_init.USART_StopBits = USART_StopBits_1; usart_init.USART_WordLength = USART_WordLength_8b; USART_Init(UART4, &usart_init); USART_Cmd(UART4, ENABLE);// 使能 UART4 //3.中断配置 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef nvic_init = {0}; nvic_init.NVIC_IRQChannel = UART4_IRQn; nvic_init.NVIC_IRQChannelPreemptionPriority = 1; nvic_init.NVIC_IRQChannelSubPriority = 1; nvic_init.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init);//misc.c USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);// 使能 RXNE 中断 USART_ITConfig(UART4, USART_IT_IDLE, ENABLE);// 使能 IDLE 中断 } //UART4字符串发送函数 void Uart4_SendStr(uint8_t *data, uint8_t length) { RS485_MODE = 1; // 发送模式 while (length--) { while (!(UART4->SR & 0x40)); // 等待发送完成 UART4->DR = *data++; } while (!(UART4->SR & 0x40)); // 确保最后一个字节发送完成 RS485_MODE = 0; // 接收模式 } u8 modbus_response_fram[150] = {0x02,0x03,0x08};//从机响应数据帧,已包含地址、功能码、字节数 u16 slave_data[10]={0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99};//从机寄存器数据,可被主机读写 //生成响应数据帧,若主机发送的是03功能码,发送的是从机寄存器数据 void modbus_generate_response_frame(void) { u8 frame_pos=3; u16 start_addr = (uart4_rx_buffer[2] << 8) | uart4_rx_buffer[3]; // 起始寄存器地址; u16 dataLength = 2*(((u16)uart4_rx_buffer[4]<<8) | (u16)uart4_rx_buffer[5]);//2*主机要读取的寄存器数(主机发送帧的第5、6个字节)==字节数 for(u16 i=0;i<dataLength/2;i++) { modbus_response_fram[frame_pos++]=(slave_data[start_addr]>>8)& 0xff; modbus_response_fram[frame_pos++]=slave_data[start_addr] & 0xff; start_addr++; } u16 crc = modbus_crc16(modbus_response_fram,frame_pos);//计算校验码 modbus_response_fram[frame_pos++]=crc>>8;//前8位校验码 modbus_response_fram[frame_pos++]=crc & 0xff;//后8位 Uart4_SendStr(modbus_response_fram,frame_pos);//发送响应数据帧 for (int i = 0; i < frame_pos; i++) //02 03 08 00 33 00 44 00 55 00 66 F9 A5 { printf("%02X ", modbus_response_fram[i]); // 打印16进制,空格分隔 } printf("\n"); // 换行 //printf("%X",modbus_crc16(modbus_response_fram,sdpos-2));检验码无问题,F9A5 } #define slave_id 0x02 #define function_id 0x03 //主机Modbus协议(数据帧)解析 void modbus_frame_parse(void) { u16 frame_length = uart4_rx_status & 0x7fff;//数据帧长度 u16 crcRx = (u16)uart4_rx_buffer[frame_length-2]<<8 | (u16)uart4_rx_buffer[frame_length-1];//接收主机发送来的校验码 u16 crcTx = modbus_crc16(uart4_rx_buffer,frame_length-2);//从机接收后再次计算校验码验证 u8 idx = 0; if(crcRx == crcTx)//CRC验证 { if(slave_id == uart4_rx_buffer[idx++])//验证地址 { if(function_id == uart4_rx_buffer[idx])//验证功能号 { switch(function_id) { case 0: break; case 1: break; case 2: break; case 3: modbus_generate_response_frame(); break; default:break; } } } } memset(uart4_rx_buffer, 0, frame_length); uart4_rx_status = 0; } //UART4中断服务函数 void UART4_IRQHandler(void) { //处理RXNE中断:接收数据并存入缓冲区 if(USART_GetITStatus(UART4, USART_IT_RXNE)){ //读取接收到的数据,自动清除RXNE标志 uint8_t data = (uint8_t)USART_ReceiveData(UART4); uart4_rx_buffer[uart4_rx_status++] = data; //检查缓冲区是否溢出 if ((uart4_rx_status & 0x7FFF) > UART4_RX_BUFFER_SIZE - 1) uart4_rx_status = 0; // 缓冲区溢出重置 USART_ClearITPendingBit(UART4, USART_IT_RXNE); } //处理总线空闲中断 if(USART_GetITStatus(UART4, USART_IT_IDLE)){ uart4_rx_status |= 0x8000;//设置接收完成标志(Bit15) UART4->SR; UART4->DR; //清除IDLE中断 } } //查表法计算CRC16 高效--同上,略-
#ifndef _MODBUS_SLAVE_H #define _MODBUS_SLAVE_H #include "stm32f10x.h" #include "sys.h" #define UART4_RX_BUFFER_SIZE 200//UART4接收缓冲区大小 #define RS485_MODE PCout(8) //RS485模式控制引脚(PC8),0=接收,1=发送,默认接收 #define MODBUS_DEVICE_ID 0x02 #define MODBUS_FUNCTION_CODE 0x03 extern uint8_t uart4_rx_buffer[UART4_RX_BUFFER_SIZE]; // 接收缓冲区 extern volatile uint16_t uart4_rx_status; // 接收状态:Bit15=完成,Bit14=收到0x0D, extern u8 modbus_response_fram[150];// 从机响应数据缓冲区(作为响应数据帧) void Uart4_SendStr(u8 arr[], u8 len);//通过UART4发送数据 void Uart4_Init();//初始化UART4 u16 modbus_crc16(u8 *puchMsg, u16 usDataLen);//查表法计算CRC16 void modbus_generate_response_frame(void);//生成响应数据帧 void modbus_frame_parse(void);//解析Modbus RTU主机请求数据帧,验证CRC并处理功能码 #endif
-
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)