来源:互联网 更新时间:2026-06-08 12:58
Modbus 这个词,搞工业通信的应该都不陌生。它本质上是一种串行通信协议,支持 RTU(二进制)和 ASCII(文本)两种模式。而在实际工程里,

RS485 是物理层标准,采用差分信号传输,半双工通信——同一时刻只能单向传输。最大传输距离能干到 1200 米,最多挂载 32 个从机(通过中继可扩展至 256 个)。
下面这套方案,基于 STM32 单片机(F103 或 F407 都行),实现 Modbus RTU 的主站/从站功能,通过 RS485 接口与传感器、执行器等设备通信,支持
| 组件 | 型号/规格 | 功能说明 |
|---|---|---|
| 主控芯片 | STM32F103C8T6 | 32位 ARM Cortex-M3,72MHz |
| RS485 收发器 | MAX485 / CSP3485 | 半双工 RS485 转换,3.3V/5V 兼容 |
| 隔离模块 | ADuM1201(可选) | 电气隔离,抗干扰 |
| 终端电阻 | 120Ω | 匹配阻抗,减少信号反射 |
| 电源 | 3.3V/5V LDO | 为 STM32 和 RS485 芯片供电 |
| MAX485 引脚 | STM32 引脚 | 功能说明 |
|---|---|---|
| RO | PA10 (UART1_RX) | 接收数据输出(TTL 电平) |
| DI | PA9 (UART1_TX) | 发送数据输入(TTL 电平) |
| DE/RE | PB0 (GPIO) | 方向控制(高=发送,低=接收) |
| VCC | 3.3V/5V | 电源(与 STM32 共电源) |
| GND | GND | 地 |
| A | A | RS485 差分线 A(接从机 A) |
| B | B | RS485 差分线 B(接从机 B) |
[地址码(1B)][功能码(1B)][数据域(NB)][CRC校验(2B)]
#include "rs485.h"
#include "usart.h"
#include "gpio.h"
UART_HandleTypeDef *rs485_huart = &huart1;
uint8_t rs485_rx_buf[256]; // 接收缓冲区
uint8_t rs485_tx_buf[256]; // 发送缓冲区
// 初始化RS485方向控制引脚
void RS485_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); // 默认接收模式
}
// UART初始化(波特率9600,8N1)
void UART_Init(void) {
huart1.Instance = USART1;
huart1.Init.BaudRate = 9600;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart1);
HAL_UART_Receive_IT(rs485_huart, rs485_rx_buf, 1); // 启动接收中断
}
Modbus RTU 采用
uint16_t Modbus_CRC16(uint8_t *data, uint8_t len) {
uint16_t crc = 0xFFFF;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc; // 低字节在前,高字节在后
}
主站主动向从机发送请求,读取/写入数据:
// 发送Modbus RTU请求
void Modbus_Master_Send(uint8_t sla ve_addr, uint8_t func_code, uint16_t reg_addr, uint16_t reg_num, uint16_t *write_data) {
uint8_t tx_len = 0;
rs485_tx_buf[tx_len++] = sla ve_addr; // 地址码
rs485_tx_buf[tx_len++] = func_code; // 功能码
switch (func_code) {
case 0x03: // 读保持寄存器
rs485_tx_buf[tx_len++] = reg_addr >> 8; // 寄存器地址高字节
rs485_tx_buf[tx_len++] = reg_addr & 0xFF; // 低字节
rs485_tx_buf[tx_len++] = reg_num >> 8; // 数量高字节
rs485_tx_buf[tx_len++] = reg_num & 0xFF; // 低字节
break;
case 0x06: // 写单个寄存器
rs485_tx_buf[tx_len++] = reg_addr >> 8;
rs485_tx_buf[tx_len++] = reg_addr & 0xFF;
rs485_tx_buf[tx_len++] = write_data[0] >> 8;
rs485_tx_buf[tx_len++] = write_data[0] & 0xFF;
break;
default: break;
}
// 计算CRC并添加到帧尾
uint16_t crc = Modbus_CRC16(rs485_tx_buf, tx_len);
rs485_tx_buf[tx_len++] = crc & 0xFF; // 低字节
rs485_tx_buf[tx_len++] = crc >> 8; // 高字节
// 发送数据(切换到发送模式)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // DE=1(发送)
HAL_UART_Transmit(rs485_huart, rs485_tx_buf, tx_len, 100);
HAL_Delay(1); // 等待发送完成
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); // DE=0(接收)
}
// 接收从机响应并解析(以读保持寄存器为例)
uint8_t Modbus_Master_Read(uint8_t sla ve_addr, uint16_t *reg_data) {
uint8_t rx_len = 0;
HAL_UART_Receive(rs485_huart, rs485_rx_buf, 256, 100); // 阻塞接收
// 校验帧头(地址码+功能码)
if (rs485_rx_buf[0] != sla ve_addr || rs485_rx_buf[1] != 0x03) {
return 0; // 帧错误
}
// 校验CRC
uint16_t crc_received = (rs485_rx_buf[rx_len-1] << 8) | rs485_rx_buf[rx_len-2];
uint16_t crc_calculated = Modbus_CRC16(rs485_rx_buf, rx_len-2);
if (crc_received != crc_calculated) {
return 0; // CRC错误
}
// 解析数据(字节数=寄存器数量×2)
uint8_t byte_count = rs485_rx_buf[2];
for (uint8_t i = 0; i < byte_count/2; i++) {
reg_data[i] = (rs485_rx_buf[3+2*i] << 8) | rs485_rx_buf[4+2*i];
}
return byte_count/2; // 返回寄存器数量
}
从站被动响应主站请求,处理读/写操作:
// 保持寄存器映射(示例:10个寄存器,地址0x0000-0x0009)
uint16_t holding_regs[10] = {0};
// 处理Modbus请求(从机模式)
void Modbus_Sla ve_Process(uint8_t *rx_buf, uint8_t rx_len) {
uint8_t sla ve_addr = rx_buf[0];
uint8_t func_code = rx_buf[1];
uint16_t crc_received = (rx_buf[rx_len-1] << 8) | rx_buf[rx_len-2];
uint16_t crc_calculated = Modbus_CRC16(rx_buf, rx_len-2);
if (crc_received != crc_calculated) return; // CRC错误
switch (func_code) {
case 0x03: { // 读保持寄存器
uint16_t reg_addr = (rx_buf[2] << 8) | rx_buf[3];
uint16_t reg_num = (rx_buf[4] << 8) | rx_buf[5];
uint8_t tx_len = 0;
rs485_tx_buf[tx_len++] = sla ve_addr;
rs485_tx_buf[tx_len++] = func_code;
rs485_tx_buf[tx_len++] = reg_num * 2; // 字节数=寄存器数×2
for (uint8_t i = 0; i < reg_num; i++) {
rs485_tx_buf[tx_len++] = holding_regs[reg_addr+i] >> 8;
rs485_tx_buf[tx_len++] = holding_regs[reg_addr+i] & 0xFF;
}
// 添加CRC
uint16_t crc = Modbus_CRC16(rs485_tx_buf, tx_len);
rs485_tx_buf[tx_len++] = crc & 0xFF;
rs485_tx_buf[tx_len++] = crc >> 8;
// 发送响应
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_UART_Transmit(rs485_huart, rs485_tx_buf, tx_len, 100);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
break;
}
case 0x06: { // 写单个寄存器
uint16_t reg_addr = (rx_buf[2] << 8) | rx_buf[3];
uint16_t reg_val = (rx_buf[4] << 8) | rx_buf[5];
holding_regs[reg_addr] = reg_val; // 更新寄存器
// 响应=请求(原样返回)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_UART_Transmit(rs485_huart, rx_buf, rx_len, 100);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
break;
}
default: { // 不支持的功能码
rs485_tx_buf[0] = sla ve_addr;
rs485_tx_buf[1] = func_code | 0x80; // 错误标志
rs485_tx_buf[2] = 0x01; // 错误码:非法功能
uint8_t tx_len = 3;
uint16_t crc = Modbus_CRC16(rs485_tx_buf, tx_len);
rs485_tx_buf[tx_len++] = crc & 0xFF;
rs485_tx_buf[tx_len++] = crc >> 8;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_UART_Transmit(rs485_huart, rs485_tx_buf, tx_len, 100);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
break;
}
}
}
#include "main.h"
#include "rs485.h"
#include "modbus_crc.h"
#include "master.h"
#include "sla ve.h"
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
RS485_Init();
UART_Init();
// 主站模式示例:读取从机1的保持寄存器0x0000-0x0001
uint16_t reg_data[2];
Modbus_Master_Send(0x01, 0x03, 0x0000, 0x0002, NULL); // 发送读请求
if (Modbus_Master_Read(0x01, reg_data) == 2) {
// 处理数据(如显示、控制)
}
// 从站模式示例:循环处理请求
while (1) {
uint8_t rx_len = 0;
HAL_UART_Receive(&huart1, rs485_rx_buf, 1, 100); // 接收1字节
if (rs485_rx_buf[0] == 0x01) { // 从机地址0x01
// 继续接收完整帧(根据长度)
// 调用Modbus_Sla ve_Process处理
}
}
}
| 测试项 | 主站操作 | 从站预期行为 | 结果 |
|---|---|---|---|
| 读保持寄存器 | 发送 03 功能码,读 0x0000-0x0001 | 返回 2 个寄存器值,CRC 正确 | |
| 写单个寄存器 | 发送 06 功能码,写 0x0000=0x1234 | 从机寄存器 0x0000 更新为 0x1234 | |
| 错误功能码 | 发送 0x05(非法功能) | 从机返回错误响应(功能码 0x85) | |
| 广播写 | 发送 06 功能码,地址 0x00 | 所有从机执行写操作 |
这套方案基于 STM32 实现了 RS485 Modbus RTU 主从通信,核心就几块:
下饭影视APP下载安装指南
和平精英如何做到压枪稳-和平精英怎样才能压枪稳
《Off Campus》第二季官宣:这对CP还在,但不再是主角
下载浏览器app下载安装选择推荐
免费影视剧APP推荐
儿子穿新中式现身大会堂 马斯克罕见用中文回应:他正在学习普通话
Elysium Above 履云录官网在哪下载 最新官方下载安装地址
抖音最火沙雕男生网名(精选100个)
网络热词聊污是什么意思
名单曝光!库克、马斯克等将随团到访中国 黄仁勋不在其中
阿里发布Qwen3.7-Max大模型,全球第五、国产第一
短剧《情绪超市》剧情介绍
短视频软件推荐
洛克王国世界S2赛季狂欢怪谈介绍
免费看电影的软件推荐
SpaceX狂揽AI人才,马斯克亲自面试且不看简历背景
HBO 奇幻剧《龙之家族》第三季定档 6 月 22 日,最终预告片曝光喉道海战
KuCoin基本面分析
金铲铲之战s17六暗星卡莎阵容玩法构筑指南
苹果macOS 27将优化界面设计并测试AI驱动的Safari标签页自动分组功能
手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc