STM32 开发板上用 USART 实现 Modbus 协议控制设备的方案

一、总体方案(工业标准做法)

复制代码
┌──────────────────────────────────────┐
│          STM32 开发板                │
│                                      │
│  USART1  ──────────▶ RS485/USB-TTL   │
│        Modbus RTU (9600,8,N,1)       │
│                                      │
│  ┌────────────┐  ┌────────────┐      │
│  │ Modbus 协议│  │ 设备控制   │      │
│  │ 解析层     │──│ 任务/驱动  │      │
│  └────────────┘  └────────────┘      │
└──────────────────────────────────────┘

最常用Modbus RTU + USART + RS485

裸机 / FreeRTOS 都可


二、硬件连接

1、STM32 ↔ 设备(RS485)

STM32 RS485 模块 说明
USART_TX DI 发送
USART_RX RO 接收
GPIO DE/RE 方向控制
GND GND 共地
--- A/B 接设备

DE/RE 接同一个 GPIO

  • 高电平:发送
  • 低电平:接收

2、串口参数

复制代码
波特率:9600 / 19200 / 38400
数据位:8
校验位:None / Even(常用 None)
停止位:1

三、Modbus RTU 协议速查

1、帧格式

复制代码
| 地址 | 功能码 | 数据 | CRC低 | CRC高 |
| 1B   | 1B    | N B  | 1B   | 1B   |

2、常用功能码

功能码 说明
0x03 读保持寄存器
0x06 写单个寄存器
0x10 写多个寄存器

3、示例(读寄存器)

请求:

复制代码
01 03 00 00 00 02 C4 0B
字段 含义
01 从机地址
03 读保持寄存器
00 00 起始地址
00 02 读 2 个寄存器
C4 0B CRC

响应:

复制代码
01 03 04 00 64 00 C8 FA XX

→ 返回 2 个寄存器:

  • 0x0064 = 100
  • 0x00C8 = 200

四、STM32 工程结构

复制代码
Modbus_RTU/
├── Core/
│   ├── Inc/
│   │   ├── modbus.h
│   │   ├── usart.h
│   │   └── device.h
│   └── Src/
│       ├── main.c
│       ├── modbus.c
│       ├── usart.c
│       └── device.c

五、核心代码实现

1、CRC16 校验(Modbus 必用)

c 复制代码
uint16_t Modbus_CRC16(uint8_t *buf, uint16_t len)
{
    uint16_t crc = 0xFFFF;
    for (uint16_t pos = 0; pos < len; pos++) {
        crc ^= (uint16_t)buf[pos];
        for (int i = 8; i != 0; i--) {
            if ((crc & 0x0001) != 0) {
                crc >>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

2、USART + DMA 接收

usart.c

c 复制代码
UART_HandleTypeDef huart1;
uint8_t rx_buf[256];

void USART1_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;
    HAL_UART_Init(&huart1);

    HAL_UART_Receive_DMA(&huart1, rx_buf, 256);
}

3、Modbus 状态机

modbus.h

c 复制代码
typedef enum {
    MODBUS_IDLE = 0,
    MODBUS_RX,
    MODBUS_PROCESS,
    MODBUS_TX
} Modbus_State_t;

typedef struct {
    uint8_t addr;
    uint8_t func;
    uint16_t reg_addr;
    uint16_t reg_value;
    uint16_t crc;
} Modbus_Frame_t;

modbus.c

c 复制代码
Modbus_State_t mb_state = MODBUS_IDLE;
Modbus_Frame_t mb_rx;

void Modbus_Process(uint8_t *data, uint16_t len)
{
    if (len < 4) return;

    uint16_t crc = Modbus_CRC16(data, len - 2);
    if (crc != (data[len - 2] | (data[len - 1] << 8)))
        return;

    mb_rx.addr = data[0];
    mb_rx.func = data[1];
    mb_rx.reg_addr = (data[2] << 8) | data[3];
    mb_rx.reg_value = (data[4] << 8) | data[5];

    switch (mb_rx.func) {
        case 0x03:  // 读寄存器
            Device_Read_Register(mb_rx.reg_addr);
            break;
        case 0x06:  // 写单个寄存器
            Device_Write_Register(mb_rx.reg_addr, mb_rx.reg_value);
            break;
        default:
            break;
    }
}

4、设备控制示例

device.c

c 复制代码
uint16_t device_regs[10] = {0};

void Device_Write_Register(uint16_t addr, uint16_t value)
{
    if (addr < 10) {
        device_regs[addr] = value;

        // 实际控制
        if (addr == 0x00) {
            PWM_SetDuty(value);   // 控制设备
        }
    }
}

void Device_Read_Register(uint16_t addr)
{
    uint8_t tx_buf[8];
    tx_buf[0] = 0x01;            // 从机地址
    tx_buf[1] = 0x03;            // 功能码
    tx_buf[2] = 0x02;            // 字节数
    tx_buf[3] = device_regs[addr] >> 8;
    tx_buf[4] = device_regs[addr] & 0xFF;

    uint16_t crc = Modbus_CRC16(tx_buf, 5);
    tx_buf[5] = crc & 0xFF;
    tx_buf[6] = crc >> 8;

    HAL_UART_Transmit(&huart1, tx_buf, 7, 100);
}

5、主循环 / FreeRTOS 任务

c 复制代码
void Modbus_Task(void)
{
    while (1) {
        if (rx_complete_flag) {
            Modbus_Process(rx_buf, rx_len);
            rx_complete_flag = 0;
            HAL_UART_Receive_DMA(&huart1, rx_buf, 256);
        }
        osDelay(10);
    }
}

参考代码 stm32开发板下,使用modbus协议,usart通讯控制设备 www.youwenfan.com/contentcsv/72776.html

六、Modbus 调试神器

工具 用途
Modbus Poll 主机测试
Modbus Slave 从机模拟
USB-RS485 硬件连接
串口助手 原始帧查看

七、常见坑

问题 原因 解决
无响应 地址不对 确认从机地址
CRC 错 校验算法 用标准 CRC16
乱码 波特率不一致 统一 9600
丢包 未延时 帧间 ≥ 3.5 字符
总线冲突 多主机 改为单主机
相关推荐
Lucky_ldy15 小时前
51单片机的学习上(结合中科协的个人自用笔记)
嵌入式硬件·学习·51单片机
全球通史15 小时前
Jetson Nano 双摄像头芯片检测视觉系统:小尺度难定位问题解决,从零开始实现教程说明
嵌入式硬件·算法·ubuntu·性能优化
崇山峻岭之间15 小时前
单片机RTC实验
单片机·嵌入式硬件·实时音视频
踏着七彩祥云的小丑15 小时前
嵌入式测试学习第 21 天:常见硬件故障现象:不开机、死机、串口无输出
单片机·嵌入式硬件
chuwengeileyan11 天前
过零比较器 proteus
嵌入式硬件
foundbug9991 天前
51单片机 PT100 温度测量程序
单片机·嵌入式硬件·51单片机
星夜夏空991 天前
STM32单片机学习(21) —— I2C通信
stm32·单片机·学习
qq_333120971 天前
深入探讨8051单片机C351语言及编译器应用
单片机·嵌入式硬件·51单片机
时光の尘1 天前
【STM32实战】ESP8266 通过 MQTT 协议对接 OneNET 云平台实现数据上传与下发(源码可直接移植)
stm32·mqtt·嵌入式·iot·esp8266