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 字符
总线冲突 多主机 改为单主机
相关推荐
✎ ﹏梦醒͜ღ҉繁华落℘2 天前
单片机基础知识---stm32单片机的优先级
stm32·单片机·mongodb
u152109648493 天前
S.S.Audio PRO A2音频隔离器
嵌入式硬件·音视频·实时音视频·视频编解码·视频
zd8451015003 天前
RS485 总线详解
单片机·嵌入式硬件
半条-咸鱼3 天前
【STM32】I2C协议原理、HAL读写与OLED显示操作
嵌入式硬件·c·信息与通信
牛根生同志3 天前
SPI数据收发的时候 TXE与RXNE标志位置位的时机
stm32·spi·transfer
wohoo_wangzi3 天前
苏州晟雅泰电子:关于W25Q128JVSIQ这个芯片物料的参数,规格及应用领域
嵌入式硬件
goldenrolan3 天前
学习型红外控制系统稳定性挂测工装专项总结
软件测试·python·stm32·嵌入式·红外
✎ ﹏梦醒͜ღ҉繁华落℘3 天前
编程基础 --高内聚,低耦合
c语言·单片机
科芯创展3 天前
1A,1MHz,30VIN,XZ4115,降压恒流LED驱动芯片
单片机·嵌入式硬件
集芯微电科技有限公司3 天前
四通道2A输出集成功率电感降压模块专为紧凑型方案设计
人工智能·单片机·嵌入式硬件·生成对抗网络·计算机外设