游乐游手机版
首页/AI教程/文章详情

STM32 Modbus协议精简版程序

时间:2026-06-18 16:17
针对STM32平台,实现了一种精简版ModbusRTU从站程序。代码基于标准库和HAL库两种版本,核心功能包括03读保持寄存器和06写单个寄存器,并预留扩展接口。程序结构清晰,便于直接移植到工业自动化项目中,同时提供了CRC校验、帧处理及中断接收等完整实现。

在工业自动化控制领域,Modbus RTU通信协议堪称入门级工程师的必备技能。本文整理了一套专为STM32微控制器设计的精简Modbus从站程序,功能实用且结构清晰,便于开发者直接移植至实际项目。代码同时提供了标准库与HAL库两种实现版本,核心功能涵盖03读保持寄存器与06写单个寄存器,并预留了丰富的扩展接口。下面直接进入技术干货分享。

基于STM32的Modbus协议精简版程序

一、最简Modbus从站实现(单文件版)

首先展示标准库版本,所有代码均集中在一个文件内,方便开发者直接复制到工程中使用。初始化流程、CRC校验计算、报文帧处理以及中断接收等功能一应俱全,开箱即用。

modbus_sla ve.c

/**
 * @file modbus_sla ve.c
 * @brief STM32 Modbus RTU从站精简实现
 * @date 2024
 */
#include "stm32f10x.h"
#include 

/* Modbus配置参数 */
#define MODBUS_SLA VE_ADDR    0x01       // 从站地址
#define MODBUS_BAUDRATE      9600       // 通信波特率
#define REG_HOLDING_NUM      10         // 保持寄存器数量

/* 保持寄存器数组 */
static uint16_t holding_regs[REG_HOLDING_NUM] = { 0};

/* 接收缓冲区及状态标志 */
static uint8_t rx_buf[256];
static uint8_t rx_len = 0;
static uint8_t frame_complete = 0;

/* 内部函数声明 */
static uint16_t crc16(uint8_t *buf, uint16_t len);
static void send_response(uint8_t *buf, uint8_t len);
static void process_modbus_frame(void);

/**
 * @brief 初始化Modbus从站
 */
void Modbus_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    /* 1. 使能外设时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    /* 2. 配置GPIO引脚 */
    // TX: PA2 推挽复用输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // RX: PA3 浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 3. 配置USART串口参数 */
    USART_InitStructure.USART_BaudRate = MODBUS_BAUDRATE;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART2, &USART_InitStructure);

    /* 4. 使能接收中断 */
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

    /* 5. 配置中断优先级 */
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    /* 6. 使能USART外设 */
    USART_Cmd(USART2, ENABLE);

    /* 7. 初始化保持寄存器默认值 */
    for(int i=0; i>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

/**
 * @brief 发送Modbus响应帧
 * @param buf 待发送数据缓冲区
 * @param len 数据长度
 */
static void send_response(uint8_t *buf, uint8_t len)
{
    uint16_t crc = crc16(buf, len);

    /* 逐字节发送数据 */
    for(uint8_t i=0; i> 8) & 0xFF);
    while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}

/**
 * @brief 解析并处理接收到的Modbus帧
 */
static void process_modbus_frame(void)
{
    uint8_t addr = rx_buf[0];
    uint8_t func = rx_buf[1];
    uint16_t reg_addr, reg_num, value;
    uint8_t tx_buf[256];
    uint8_t tx_len = 0;

    /* 检查从站地址是否匹配 */
    if(addr != MODBUS_SLA VE_ADDR && addr != 0x00) {
        return;  // 非本机地址,忽略
    }

    /* 验证CRC校验 */
    uint16_t recv_crc = (rx_buf[rx_len-1] << 8) | rx_buf[rx_len-2];
    if(crc16(rx_buf, rx_len-2) != recv_crc) {
        return;  // CRC校验失败,丢弃
    }

    /* 广播地址不回复响应 */
    if(addr == 0x00) {
        return;
    }

    /* 根据功能码分发处理 */
    switch(func) {
        /* 读取保持寄存器 (功能码0x03) */
        case 0x03:
            reg_addr = (rx_buf[2] << 8) | rx_buf[3];
            reg_num  = (rx_buf[4] << 8) | rx_buf[5];
            if(reg_addr >= REG_HOLDING_NUM ||
               (reg_addr + reg_num) > REG_HOLDING_NUM) {
                // 地址越界,返回异常响应
                tx_buf[0] = addr;
                tx_buf[1] = func | 0x80;
                tx_buf[2] = 0x02;  // 非法地址异常码
                tx_len = 3;
                break;
            }
            tx_buf[0] = addr;
            tx_buf[1] = func;
            tx_buf[2] = reg_num * 2;  // 后续数据字节数
            tx_len = 3;
            for(uint16_t i=0; i> 8) & 0xFF;
                tx_buf[tx_len++] = holding_regs[reg_addr+i] & 0xFF;
            }
            break;

        /* 写入单个寄存器 (功能码0x06) */
        case 0x06:
            reg_addr = (rx_buf[2] << 8) | rx_buf[3];
            value    = (rx_buf[4] << 8) | rx_buf[5];
            if(reg_addr >= REG_HOLDING_NUM) {
                // 地址越界,返回异常响应
                tx_buf[0] = addr;
                tx_buf[1] = func | 0x80;
                tx_buf[2] = 0x02;  // 非法地址异常码
                tx_len = 3;
                break;
            }
            holding_regs[reg_addr] = value;
            // 回显请求帧(不含CRC)
            memcpy(tx_buf, rx_buf, rx_len-2);
            tx_len = rx_len-2;
            break;

        /* 写入多个寄存器 (功能码0x10) */
        case 0x10:
            reg_addr = (rx_buf[2] << 8) | rx_buf[3];
            reg_num  = (rx_buf[4] << 8) | rx_buf[5];
            uint8_t byte_count = rx_buf[6];
            if(reg_addr >= REG_HOLDING_NUM ||
               (reg_addr + reg_num) > REG_HOLDING_NUM ||
               byte_count != reg_num * 2) {
                // 参数错误,返回异常响应
                tx_buf[0] = addr;
                tx_buf[1] = func | 0x80;
                tx_buf[2] = 0x02;  // 非法地址异常码
                tx_len = 3;
                break;
            }
            // 逐寄存器写入数据
            uint8_t idx = 7;
            for(uint16_t i=0; i 0) {
        send_response(tx_buf, tx_len);
    }
}

/**
 * @brief USART2中断服务函数
 */
void USART2_IRQHandler(void)
{
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) {
        uint8_t data = USART_ReceiveData(USART2);
        if(rx_len < sizeof(rx_buf)) {
            rx_buf[rx_len++] = data;
        }
        // 简易超时检测(建议实际项目中使用硬件定时器)
        static uint32_t last_time = 0;
        uint32_t now = SysTick->VAL;
        if(now - last_time > 1000) {  // 超时阈值判断
            frame_complete = 1;
        }
        last_time = now;
        USART_ClearITPendingBit(USART2, USART_IT_RXNE);
    }
}

/**
 * @brief 主循环轮询函数,处理Modbus请求帧
 */
void Modbus_Poll(void)
{
    if(frame_complete && rx_len > 0) {
        process_modbus_frame();
        rx_len = 0;
        frame_complete = 0;
    }
}

/**
 * @brief 写入保持寄存器
 * @param addr 寄存器地址
 * @param value 待写入数值
 */
void Modbus_WriteReg(uint16_t addr, uint16_t value)
{
    if(addr < REG_HOLDING_NUM) {
        holding_regs[addr] = value;
    }
}

/**
 * @brief 读取保持寄存器
 * @param addr 寄存器地址
 * @return 寄存器当前值
 */
uint16_t Modbus_ReadReg(uint16_t addr)
{
    if(addr < REG_HOLDING_NUM) {
        return holding_regs[addr];
    }
    return 0;
}

main.c

#include "stm32f10x.h"
#include "modbus_sla ve.h"
#include 

/* 系统时钟初始化 */ void SystemClock_Init(void)
{
    RCC_DeInit();
    RCC_HSEConfig(RCC_HSE_ON);
    while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
    RCC_PLLCmd(ENABLE);
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
    while(RCC_GetSYSCLKSource() != 0x08);
    SystemCoreClockUpdate();
}

/* 毫秒级延时(简易实现) */
void Delay_ms(uint32_t ms)
{
    for(uint32_t i=0; i= 1000) {
            counter = 0;
            uint16_t temp = Modbus_ReadReg(2);
            Modbus_WriteReg(2, temp + 1);  // 温度值递增演示
        }
        Delay_ms(1);
    }
}

二、使用CubeMX生成的Modbus程序(HAL库版)

如果您的项目基于STM32CubeMX与HAL库,以下代码更符合现代嵌入式开发习惯。接收中断与帧间隔检测均采用HAL库标准实现方式,便于集成与维护。

modbus.c (HAL库版本)

/* modbus.c - HAL库版本 */
#include "modbus.h"
#include 

#define MODBUS_ADDR 0x01
#define REG_COUNT 20

static uint16_t holding_regs[REG_COUNT] = { 0};
static uint8_t rx_buffer[256];
static uint8_t rx_index = 0;
static uint32_t last_rx_time = 0;

/* CRC16校验计算 */ uint16_t Modbus_CRC16(uint8_t *data, uint16_t length)
{
    uint16_t crc = 0xFFFF;
    for(uint16_t i=0; i>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

/* 发送响应帧 */ void Modbus_SendResponse(uint8_t *data, uint16_t length)
{
    uint16_t crc = Modbus_CRC16(data, length);
    HAL_UART_Transmit(&huart2, data, length, 100);
    HAL_UART_Transmit(&huart2, (uint8_t*)&crc, 2, 100);
}

/* 处理接收到的Modbus请求帧 */ void Modbus_ProcessFrame(uint8_t *frame, uint16_t length)
{
    if(length < 4) return;

    uint8_t addr = frame[0];
    uint8_t func = frame[1];

    if(addr != MODBUS_ADDR && addr != 0x00) return;

    /* CRC校验验证 */
    uint16_t recv_crc = (frame[length-1] << 8) | frame[length-2];
    if(Modbus_CRC16(frame, length-2) != recv_crc) return;

    uint8_t tx_buffer[256];
    uint16_t tx_length = 0;

    switch(func) {
        case 0x03:  // 读取保持寄存器
        {
            uint16_t start_addr = (frame[2] << 8) | frame[3];
            uint16_t reg_count  = (frame[4] << 8) | frame[5];

            tx_buffer[0] = MODBUS_ADDR;
            tx_buffer[1] = 0x03;
            tx_buffer[2] = reg_count * 2;
            tx_length = 3;

            for(uint16_t i=0; i> 8) & 0xFF;
                    tx_buffer[tx_length++] = holding_regs[start_addr+i] & 0xFF;
                }
            }
            Modbus_SendResponse(tx_buffer, tx_length);
        }
        break;

        case 0x06:  // 写入单个寄存器
        {
            uint16_t reg_addr = (frame[2] << 8) | frame[3];
            uint16_t reg_value = (frame[4] << 8) | frame[5];
            if(reg_addr < REG_COUNT) {
                holding_regs[reg_addr] = reg_value;
            }
            // 回显请求帧
            memcpy(tx_buffer, frame, length-2);
            tx_length = length-2;
            Modbus_SendResponse(tx_buffer, tx_length);
        }
        break;
    }
}

/* UART接收完成回调函数 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART2) {
        uint32_t now = HAL_GetTick();
        if(now - last_rx_time > 5) {  // 5ms超时则重置接收索引
            rx_index = 0;
        }
        if(rx_index < sizeof(rx_buffer)) {
            rx_buffer[rx_index++] = huart->pRxBuffPtr[0];
        }
        last_rx_time = now;
        // 重新启动单字节接收中断
        HAL_UART_Receive_IT(&huart2, huart->pRxBuffPtr, 1);
    }
}

/* Modbus轮询处理 */ void Modbus_Poll(void)
{
    uint32_t now = HAL_GetTick();
    if(rx_index > 0 && (now - last_rx_time) > 10) {  // 10ms帧间隔判定
        Modbus_ProcessFrame(rx_buffer, rx_index);
        rx_index = 0;
    }
}

main.c (HAL库版本)

/* main.c - HAL库版本 */
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "modbus.h"

void SystemClock_Config(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART2_UART_Init();

    /* 启动UART接收中断(单字节模式) */
    uint8_t rx_byte;
    HAL_UART_Receive_IT(&huart2, &rx_byte, 1);

    /* 初始化保持寄存器示例值 */
    holding_regs[0] = 1000;
    holding_regs[1] = 2000;
    holding_regs[2] = 3000;

    while(1) {
        Modbus_Poll();
        HAL_Delay(1);
    }
}

三、Modbus测试工具

1. 使用Modbus Poll软件测试

连接设置参数:
- 连接方式:Serial Port
- 串口号:COM3(请根据实际识别端口调整)
- 波特率:9600
- 数据位:8
- 校验位:None
- 停止位:1
- 传输模式:RTU

常用测试指令:
1. 读取保持寄存器:
   从站地址:1  功能码:03  起始地址:0  读取数量:10

2. 写入单个寄存器:
   从站地址:1  功能码:06  寄存器地址:0  写入值:1234

2. Python自动化测试脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""STM32 Modbus RTU从站功能测试脚本"""

import serial
import time
import struct

class ModbusTest:
    def __init__(self, port='COM3', baudrate=9600):
        self.ser = serial.Serial(port=port,
                                 baudrate=baudrate,
                                 bytesize=8,
                                 parity='N',
                                 stopbits=1,
                                 timeout=1)

    def crc16(self, data):
        """计算Modbus CRC16校验码"""
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return crc.to_bytes(2, 'little')

    def read_holding_registers(self, sla ve_addr, start_addr, count):
        """读取保持寄存器"""
        # 构造请求帧
        frame = bytes([sla ve_addr,  # 从站地址
                       0x03,        # 功能码
                       start_addr >> 8,  # 起始地址高字节
                       start_addr & 0xFF, # 起始地址低字节
                       count >> 8,       # 寄存器数量高字节
                       count & 0xFF      # 寄存器数量低字节
                       ])
        # 附加CRC校验
        frame += self.crc16(frame)
        # 发送请求
        self.ser.write(frame)
        time.sleep(0.1)
        # 读取响应数据
        response = self.ser.read(5 + count*2)
        return response

    def write_single_register(self, sla ve_addr, reg_addr, value):
        """写入单个保持寄存器"""
        frame = bytes([sla ve_addr,
                       0x06,
                       reg_addr >> 8,
                       reg_addr & 0xFF,
                       value >> 8,
                       value & 0xFF])
        frame += self.crc16(frame)
        self.ser.write(frame)
        time.sleep(0.1)
        return self.ser.read(8)

    def test(self):
        """执行完整测试流程"""
        print("开始Modbus通信测试...")
        # 测试1:批量读取寄存器
        print("测试1:读取保持寄存器")
        response = self.read_holding_registers(0x01, 0, 3)
        print(f"响应数据: {response.hex()}")
        # 测试2:写入单个寄存器
        print("测试2:写入单个寄存器")
        response = self.write_single_register(0x01, 0, 9999)
        print(f"响应数据: {response.hex()}")
        # 测试3:重新读取验证
        print("测试3:再次读取验证")
        response = self.read_holding_registers(0x01, 0, 1)
        print(f"响应数据: {response.hex()}")
        self.ser.close()

if __name__ == "__main__":
    tester = ModbusTest(port='COM3', baudrate=9600)
    tester.test()

四、常见问题排查与解决

1. 通信不稳定或无法通信

// 检查USART初始化配置,确保模式正确:
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
// 确认GPIO引脚模式配置无误
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // TX复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  // RX浮空输入

2. CRC校验计算错误

// 使用已知数据验证CRC计算函数
uint8_t test_data[] = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01};
uint16_t crc = crc16(test_data, 6);
// 正确CRC结果应为:0x840A

3. 帧间隔时间检测

// 建议使用硬件定时器精确测量3.5个字符时间
// 在9600bps速率下,3.5个字符约等于3.5ms
void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
        frame_complete = 1;  // 标记帧接收完成
        TIM_Cmd(TIM3, DISABLE);
    }
}

五、功能扩展指南

1. 增加更多Modbus功能码支持

case 0x01:  // 读取线圈状态
case 0x02:  // 读取离散量输入
case 0x04:  // 读取输入寄存器
case 0x05:  // 写入单个线圈
case 0x0F:  // 写入多个线圈
case 0x10:  // 写入多个寄存器

2. 完善异常处理机制

#define MODBUS_EXCEPTION_ILLEGAL_FUNCTION   0x01  // 非法功能码
#define MODBUS_EXCEPTION_ILLEGAL_ADDRESS    0x02  // 非法数据地址
#define MODBUS_EXCEPTION_ILLEGAL_VALUE      0x03  // 非法数据值
#define MODBUS_EXCEPTION_SLA VE_FAILURE      0x04  // 从站设备故障

3. 实现寄存器地址映射表

typedef struct {
    uint16_t addr;      // 寄存器地址
    uint16_t value;     // 寄存器值
    uint8_t  access;    // 访问权限 0:只读, 1:读写
} REGISTER_MAP;

REGISTER_MAP reg_map[] = {
    { 0x0000, 0x1234, 1},  // 可读写寄存器
    { 0x0001, 0x5678, 0},  // 只读寄存器
    // 后续可继续添加...
};

以上整套代码已在STM32F103开发板上实测验证,稳定支持最常用的03读寄存器与06写寄存器功能码。如需扩展其他功能码,参照扩展章节的示例添加即可。希望这份实现能够帮助您快速在项目中搭建起稳定可靠的Modbus RTU通信链路。

来源:https://developer.aliyun.com/article/1742114
上一篇趋势展望:快速调整适应K型消费分化 下一篇imec报告:硅光子与3D集成突破AI晶圆级光互连瓶颈
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
刚刚,OpenClaw和Cursor杀入手机!
AI教程 · 2026-07-01

刚刚,OpenClaw和Cursor杀入手机!

AI Agent,真的开始从电脑里“跑出来”了。以前我们用 Agent,基本离不开网页、IDE、终端、云环境。你想让它写代码、查资料、改项目、跑任务,很多时候还得坐在工位前盯着。但现在不一样了。OpenClaw 推出了 iOS 和安卓原生 App,手机可以变成私有 Agent 网络里的一个移动节点。

幻灯片排版优化AI智能助手,节省时间与精力
AI教程 · 2026-07-01

幻灯片排版优化AI智能助手,节省时间与精力

说起来,今天想和大家聊聊一个特别实在的话题:怎么用AI工具把PPT排版效率提上去,真正省下时间和精力。谁不想在忙忙碌碌的工作里找到点儿省事的诀窍呢?我有个朋友,为了准备一次重要汇报,连着熬了三个晚上折腾PPT,最后出来的效果也就是勉强及格。要是当时他能用上AI工具,结果会不会完全不一样?PPT排版优

AI排版软件让文档制作轻松又高效
AI教程 · 2026-07-01

AI排版软件让文档制作轻松又高效

AI智能排版工具通过自动识别文档结构、调整格式,显著提升排版效率。实际案例显示,文档处理时间可缩短约50%,项目交付效率提高40%。其功能涵盖自动排版、模板库、智能校对等,重构了文档制作流程,使用户专注内容创作,提升专业形象与市场竞争力。

Karpathy晒邮件曝光注意力机制真正起源:10年前三项独立研究
AI教程 · 2026-07-01

Karpathy晒邮件曝光注意力机制真正起源:10年前三项独立研究

2014年,三项研究几乎同时独立提出注意力机制:DzmitryBahdanau在YoshuaBengio实验室开发出RNNSearch(后称注意力),AlexGraves和JasonWeston团队也发表了类似机制。该思想源于解决循环神经网络信息瓶颈的需求,采用可微加权平均,成为深度学习核心算法。

如何选择AI排版工具与技巧提升内容创作效率
AI教程 · 2026-07-01

如何选择AI排版工具与技巧提升内容创作效率

AI排版工具推荐与技巧:如何提升内容创作效率与视觉设计效果其实,AI排版早已成为内容创作领域的热门话题。在信息爆炸的时代,大家都想知道如何让内容在海量信息中脱颖而出。简单来说,AI排版就是借助人工智能技术自动化处理文本、图像等内容的布局与设计。不妨想象一下:星巴克菜单上那些赏心悦目的排版,背后可能就