STM32 上实现 Modbus-RTU

STM32 上实现 Modbus-RTU ,最稳妥的是"裸机驱动 + 状态机"(资源小、实时性强)。


一、方案选型(STM32 推荐)

方案 适用场景 评价
裸机状态机(推荐) 从站 / 简单主站 稳定、可控
FreeMODBUS 快速成型 但裁剪麻烦
libmodbus Linux / 高端 MCU ❌ 不适合小 RAM
自己写全套 学习 ⚠️ 易踩坑

工业现场:裸机 + 定时器 + UART 中断


二、硬件与串口配置

1、硬件连接(RS485)

复制代码
STM32 USART2
TX  ->  MAX485 DI
RX  ->  MAX485 RO
PB8 ->  MAX485 DE/RE

2、CubeMX 配置

参数 设置
Baudrate 9600 / 19200 / 115200
Word Length 8 Bits
Parity None
Stop Bits 1
Mode Asynchronous
USART Interrupt Enable

三、Modbus-RTU 核心规则

帧结构

复制代码
[地址][功能码][数据...][CRC低][CRC高]

帧结束判定(关键)

RTU 必须用 3.5 个字符静默时间

波特率 3.5字符时间
9600 ~4 ms
19200 ~2 ms
115200 ~0.35 ms

四、CRC16 校验(查表法 )

c 复制代码
static const uint16_t crc_tab[] = {
    0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,
    // ...(完整表略)
};

uint16_t Modbus_CRC16(uint8_t *buf, uint16_t len)
{
    uint16_t crc = 0xFFFF;
    while (len--)
        crc = (crc >> 8) ^ crc_tab[(crc ^ *buf++) & 0xFF];
    return crc;
}

五、接收状态机

c 复制代码
typedef enum {
    RX_IDLE,
    RX_RECEIVING,
    RX_DONE
} RxState;

volatile RxState rx_state = RX_IDLE;
volatile uint8_t rx_buf[256];
volatile uint8_t rx_len = 0;
volatile uint16_t idle_cnt = 0;

UART 接收中断

c 复制代码
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART2)
    {
        rx_state = RX_RECEIVING;
        idle_cnt = 0;
        HAL_UART_Receive_IT(&huart2, (uint8_t *)&rx_buf[rx_len++], 1);
    }
}

1ms 定时器中断(帧结束判定)

c 复制代码
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        if (rx_state == RX_RECEIVING)
        {
            if (++idle_cnt >= 4)   // 3.5字符
            {
                rx_state = RX_DONE;
            }
        }
    }
}

六、Modbus 从站处理(功能码 03 / 06)

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

void Modbus_Process(void)
{
    if (rx_state != RX_DONE) return;

    uint16_t crc_rx = (rx_buf[rx_len-1]<<8) | rx_buf[rx_len-2];
    if (Modbus_CRC16((uint8_t*)rx_buf, rx_len-2) != crc_rx)
    {
        rx_len = 0;
        rx_state = RX_IDLE;
        return;
    }

    if (rx_buf[0] != 0x01) goto exit;   // 地址不符

    switch (rx_buf[1])
    {
        case 0x03:  // 读保持寄存器
            Modbus_ReadHolding();
            break;
        case 0x06:  // 写单个寄存器
            Modbus_WriteSingle();
            break;
    }

exit:
    rx_len = 0;
    rx_state = RX_IDLE;
}

七、功能码 03 示例(读寄存器)

c 复制代码
void Modbus_ReadHolding(void)
{
    uint16_t addr = (rx_buf[2]<<8) | rx_buf[3];
    uint16_t cnt  = (rx_buf[4]<<8) | rx_buf[5];

    uint8_t tx[256];
    uint8_t i = 0;

    tx[i++] = 0x01;
    tx[i++] = 0x03;
    tx[i++] = cnt * 2;

    for (uint8_t j = 0; j < cnt; j++)
    {
        tx[i++] = hold_reg[addr + j] >> 8;
        tx[i++] = hold_reg[addr + j] & 0xFF;
    }

    uint16_t crc = Modbus_CRC16(tx, i);
    tx[i++] = crc & 0xFF;
    tx[i++] = crc >> 8;

    HAL_UART_Transmit(&huart2, tx, i, 100);
}

参考代码 stm32 实现Modbus-rtu www.youwenfan.com/contentcst/181948.html

八、调试与避坑指南

问题 原因
帧不完整 3.5字符时间不准
CRC 错 字节序反了
只收到一次 未重新开启接收中断
总线冲突 多从站地址重复
数据乱码 波特率不一致

调试工具

  • Modbus Poll
  • USB-RS485 转换器
  • 示波器看 3.5T 静默
相关推荐
贤哥哥yyds4 小时前
【无标题】
stm32
崇山峻岭之间7 小时前
单片机步进电机实验
单片机·嵌入式硬件
xiangw@GZ9 小时前
802.11全系列标准调制编码与速率档对应关系
网络·单片机·嵌入式硬件·架构
希希之光9 小时前
Aurix Tc3xx Port&Dio模块总结
单片机·嵌入式硬件
三品吉他手会点灯9 小时前
STM32F103 学习笔记-24-I2C-读写EEPROM(第1节)-I2C物理层介绍
笔记·stm32·学习
日拱一卒的小田9 小时前
ZYNQ学习笔记2-ZYNQ的UART控制器1
单片机·嵌入式硬件
我想走路带风10 小时前
OPENWRT-Day01
stm32·单片机·嵌入式硬件
ACP广源盛1392462567310 小时前
GSV2221@ACP#DP 1.4 MST 多屏转换芯片,物理 AI 多模态交互的视觉中枢
大数据·人工智能·嵌入式硬件·gpt·spark
云栖梦泽11 小时前
Linux内核与驱动:pinctl子系统和GPIO子系统
linux·单片机·嵌入式硬件
电气_空空11 小时前
基于 LabVIEW 的单片机串口通信设计
单片机·嵌入式硬件·毕业设计·labview