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 静默
相关推荐
飞睿科技2 小时前
飞睿智能5.8G毫米波雷达智能猫砂盆检测方案
嵌入式硬件·物联网·雷达·智能猫砂盆·宠物用品
小叮当⇔2 小时前
TI电源管理芯片——TPS65251RHAR手册
单片机·嵌入式硬件
leo__5203 小时前
51单片机实现读写U盘
嵌入式硬件·mongodb·51单片机
项目題供诗3 小时前
STM32-GPIO输入(四)
stm32·单片机·嵌入式硬件
我在人间贩卖青春3 小时前
ADC采集
stm32·adc
西城微科方案开发4 小时前
华润微(SEMICO)高速度低功耗的8位MCU——CS98P171 SOP8
单片机·嵌入式硬件
青鱼294 小时前
SysTick_Handler在裸机和RTOS中的区别
单片机·嵌入式硬件·rtos·systick_handler
我在人间贩卖青春4 小时前
SPI总线
stm32·spi
波特率1152006 小时前
单片机启动流程以STM32举例
stm32·单片机·嵌入式硬件·嵌入式·嵌入式软件