STM32实战:基于STM32F103的Modbus RTU通信(从机实现)

文章目录

一、前言

Modbus 是工业控制中最常用的串口通信协议 ,分为 RTU、ASCII、TCP 三种模式,其中 Modbus RTU 凭借传输效率高、抗干扰能力强,成为工业串口设备的首选。

STM32F103 是入门级嵌入式开发最经典的芯片,本教程从零开始,手把手带你实现 STM32F103 作为 Modbus RTU 从机,支持主机读写线圈、离散输入、保持寄存器、输入寄存器,代码可直接移植、直接落地运行。

本教程适用人群

  1. 零基础 STM32 开发者
  2. 想学习工业 Modbus 协议的电子/自动化工程师
  3. 需要实现串口设备通信的毕业设计/项目开发人员

本教程实现功能

  1. STM32F103C8T6 作为 Modbus RTU 从机
  2. 支持功能码:0x01(读线圈)、0x02(读离散输入)、0x03(读保持寄存器)、0x04(读输入寄存器)、0x05(写单个线圈)、0x06(写单个保持寄存器)
  3. 串口波特率:9600,数据位8,停止位1,无校验
  4. 完整的 CRC 校验、超时处理、异常码返回

二、硬件准备

  1. 核心板:STM32F103C8T6 最小系统板
  2. 调试下载器:ST-Link/V2
  3. USB-TTL 模块(用于主机调试,如 CH340)
  4. 杜邦线若干
  5. 电脑(安装 Keil5、STM32CubeMX)

硬件接线

STM32F103 USB-TTL
PA9 (TX) RX
PA10(RX) TX
GND GND

注意:串口通信必须共地,否则无法正常通信!

三、软件环境准备

  1. STM32CubeMX:用于初始化工程配置
  2. Keil MDK-ARM V5:用于代码编写、编译、下载
  3. Modbus 调试工具:如 Modbus Poll(主机调试)
  4. CH340 驱动:电脑识别 USB-TTL 模块

四、STM32CubeMX 工程创建与配置

步骤1:新建工程

  1. 打开 STM32CubeMX,点击 ACCESS TO MCU SELECTOR
  2. 芯片选择:STM32F103C8T6,点击 Start Project

步骤2:系统时钟配置

  1. 点击 RCCHigh Speed Clock 选择 Crystal/Ceramic Resonator(外部晶振)
  2. 点击 Clock Configuration,配置系统时钟为 72MHz
    • HSE 输入:8MHz
    • PLL 倍频:9
    • 系统时钟:72MHz

步骤3:串口配置(USART1)

Modbus RTU 基于串口通信,本教程使用 USART1

  1. 点击 USART1,模式选择 Asynchronous(异步串口)
  2. 参数配置:
    • 波特率:9600
    • 数据位:8
    • 校验位:None
    • 停止位:1
  3. 开启串口接收中断:
    • 点击 NVIC Settings
    • 勾选 USART1 global interrupt 使能中断
    • 优先级默认即可

步骤4:工程生成配置

  1. 点击 Project Manager
  2. 填写工程名称、选择工程路径
  3. Toolchain/IDE 选择 MDK-ARM V5
  4. 点击 Code Generator,勾选 Generate peripheral initialization as a pair of .c/.h files per peripheral
  5. 点击 GENERATE CODE 生成工程

五、Modbus RTU 从机代码实现

生成工程后,用 Keil5 打开,我们开始添加 Modbus 相关代码。

本教程所有代码分文件编写,结构清晰,零基础可直接复制。

1. 创建文件:modbus_rtu.h

该文件为 Modbus RTU 从机头文件,定义函数、宏、数据结构。

c 复制代码
#ifndef __MODBUS_RTU_H
#define __MODBUS_RTU_H

#include "main.h"
#include "usart.h"
#include <string.h>
#include <stdint.h>

/************************* Modbus 常量定义 *************************/
#define MODBUS_SLAVE_ADDR       0x01    // 从机地址
#define MODBUS_BUFFER_SIZE      64      // 数据缓冲区大小
#define MODBUS_TIMEOUT          100     // 超时时间(ms)

/************************* 功能码定义 *************************/
#define READ_COILS              0x01    // 读线圈
#define READ_DISCRETE_INPUTS    0x02    // 读离散输入
#define READ_HOLDING_REGS       0x03    // 读保持寄存器
#define READ_INPUT_REGS         0x04    // 读输入寄存器
#define WRITE_SINGLE_COIL       0x05    // 写单个线圈
#define WRITE_SINGLE_REG        0x06    // 写单个保持寄存器

/************************* 异常码定义 *************************/
#define EXC_ILLEGAL_FUNC        0x01    // 非法功能码
#define EXC_ILLEGAL_ADDR        0x02    // 非法数据地址
#define EXC_ILLEGAL_VALUE       0x03    // 非法数据值
#define EXC_SLAVE_FAILURE       0x04    // 从机故障

/************************* 寄存器数量定义 *************************/
#define COILS_NUM               16      // 线圈数量(0-15)
#define DISCRETE_INPUT_NUM      16      // 离散输入数量(0-15)
#define HOLDING_REG_NUM         16      // 保持寄存器数量(0-15)
#define INPUT_REG_NUM           16      // 输入寄存器数量(0-15)

/************************* 全局变量声明 *************************/
extern uint8_t Coils[COILS_NUM];                // 线圈(可读写)
extern uint8_t Discrete_Input[DISCRETE_INPUT_NUM]; // 离散输入(只读)
extern uint16_t Holding_Reg[HOLDING_REG_NUM];  // 保持寄存器(可读写)
extern uint16_t Input_Reg[INPUT_REG_NUM];      // 输入寄存器(只读)

extern uint8_t Modbus_Rx_Buf[MODBUS_BUFFER_SIZE];  // 接收缓冲区
extern uint8_t Modbus_Tx_Buf[MODBUS_BUFFER_SIZE];  // 发送缓冲区
extern uint16_t Modbus_Rx_Len;                    // 接收数据长度

/************************* 函数声明 *************************/
void Modbus_Slave_Init(void);                // Modbus从机初始化
void Modbus_Slave_Process(void);             // Modbus数据处理
uint16_t CRC16_Modbus(uint8_t *pucFrame, uint16_t usLen); // CRC校验
void Modbus_Send_Data(uint8_t *buf, uint16_t len);       // 发送数据
void Modbus_Reset_Rx(void);                  // 重置接收

#endif

2. 创建文件:modbus_rtu.c

该文件为 Modbus RTU 核心实现文件,包含协议解析、数据处理、异常返回。

c 复制代码
#include "modbus_rtu.h"

/************************* 全局变量定义 *************************/
uint8_t Coils[COILS_NUM] = {0};              // 线圈初始化全0
uint8_t Discrete_Input[DISCRETE_INPUT_NUM] = {0}; // 离散输入初始化全0
uint16_t Holding_Reg[HOLDING_REG_NUM] = {0};  // 保持寄存器初始化全0
uint16_t Input_Reg[INPUT_REG_NUM] = {0};      // 输入寄存器初始化全0

uint8_t Modbus_Rx_Buf[MODBUS_BUFFER_SIZE] = {0};
uint8_t Modbus_Tx_Buf[MODBUS_BUFFER_SIZE] = {0};
uint16_t Modbus_Rx_Len = 0;

/************************* 函数实现 *************************/

/**
 * @brief  Modbus从机初始化
 * @param  无
 * @retval 无
 */
void Modbus_Slave_Init(void)
{
    // 初始化输入寄存器测试数据
    for(uint8_t i=0; i<INPUT_REG_NUM; i++)
    {
        Input_Reg[i] = 0x1111 + i;
    }
    // 初始化离散输入测试数据
    for(uint8_t i=0; i<DISCRETE_INPUT_NUM; i++)
    {
        Discrete_Input[i] = i % 2;
    }
    // 开启串口接收
    HAL_UART_Receive_IT(&huart1, &Modbus_Rx_Buf[Modbus_Rx_Len], 1);
}

/**
 * @brief  Modbus CRC16校验
 * @param  pucFrame: 数据指针
 * @param  usLen: 数据长度
 * @retval 校验值
 */
uint16_t CRC16_Modbus(uint8_t *pucFrame, uint16_t usLen)
{
    uint16_t usCRC = 0xFFFF;
    uint16_t i, j;

    for(i=0; i<usLen; i++)
    {
        usCRC ^= pucFrame[i];
        for(j=0; j<8; j++)
        {
            if(usCRC & 0x0001)
            {
                usCRC >>= 1;
                usCRC ^= 0xA001;
            }
            else
            {
                usCRC >>= 1;
            }
        }
    }
    return usCRC;
}

/**
 * @brief  Modbus发送数据
 * @param  buf: 发送数据指针
 * @param  len: 发送长度
 * @retval 无
 */
void Modbus_Send_Data(uint8_t *buf, uint16_t len)
{
    HAL_UART_Transmit(&huart1, buf, len, 100);
    Modbus_Reset_Rx();
}

/**
 * @brief  重置接收状态
 * @param  无
 * @retval 无
 */
void Modbus_Reset_Rx(void)
{
    memset(Modbus_Rx_Buf, 0, MODBUS_BUFFER_SIZE);
    Modbus_Rx_Len = 0;
    HAL_UART_Receive_IT(&huart1, &Modbus_Rx_Buf[Modbus_Rx_Len], 1);
}

/**
 * @brief  发送异常响应
 * @param  func: 功能码
 * @param  exc: 异常码
 * @retval 无
 */
static void Modbus_Send_Exception(uint8_t func, uint8_t exc)
{
    uint8_t len = 3;
    Modbus_Tx_Buf[0] = MODBUS_SLAVE_ADDR;
    Modbus_Tx_Buf[1] = func | 0x80;
    Modbus_Tx_Buf[2] = exc;

    uint16_t crc = CRC16_Modbus(Modbus_Tx_Buf, len-2);
    Modbus_Tx_Buf[len-2] = crc & 0xFF;
    Modbus_Tx_Buf[len-1] = (crc >> 8) & 0xFF;

    Modbus_Send_Data(Modbus_Tx_Buf, len);
}

/**
 * @brief  读线圈(0x01)
 * @param  addr: 起始地址
 * @param  num: 数量
 * @retval 无
 */
static void Read_Coils(uint16_t addr, uint16_t num)
{
    if(addr + num > COILS_NUM)
    {
        Modbus_Send_Exception(READ_COILS, EXC_ILLEGAL_ADDR);
        return;
    }

    uint8_t byte_num = (num + 7) / 8;
    uint8_t len = 3 + byte_num + 2;
    Modbus_Tx_Buf[0] = MODBUS_SLAVE_ADDR;
    Modbus_Tx_Buf[1] = READ_COILS;
    Modbus_Tx_Buf[2] = byte_num;

    memset(&Modbus_Tx_Buf[3], 0, byte_num);
    for(uint16_t i=0; i<num; i++)
    {
        if(Coils[addr + i])
        {
            Modbus_Tx_Buf[3 + i/8] |= (1 << (i%8));
        }
    }

    uint16_t crc = CRC16_Modbus(Modbus_Tx_Buf, len-2);
    Modbus_Tx_Buf[len-2] = crc & 0xFF;
    Modbus_Tx_Buf[len-1] = (crc >> 8) & 0xFF;

    Modbus_Send_Data(Modbus_Tx_Buf, len);
}

/**
 * @brief  读离散输入(0x02)
 * @param  addr: 起始地址
 * @param  num: 数量
 * @retval 无
 */
static void Read_Discrete_Inputs(uint16_t addr, uint16_t num)
{
    if(addr + num > DISCRETE_INPUT_NUM)
    {
        Modbus_Send_Exception(READ_DISCRETE_INPUTS, EXC_ILLEGAL_ADDR);
        return;
    }

    uint8_t byte_num = (num + 7) / 8;
    uint8_t len = 3 + byte_num + 2;
    Modbus_Tx_Buf[0] = MODBUS_SLAVE_ADDR;
    Modbus_Tx_Buf[1] = READ_DISCRETE_INPUTS;
    Modbus_Tx_Buf[2] = byte_num;

    memset(&Modbus_Tx_Buf[3], 0, byte_num);
    for(uint16_t i=0; i<num; i++)
    {
        if(Discrete_Input[addr + i])
        {
            Modbus_Tx_Buf[3 + i/8] |= (1 << (i%8));
        }
    }

    uint16_t crc = CRC16_Modbus(Modbus_Tx_Buf, len-2);
    Modbus_Tx_Buf[len-2] = crc & 0xFF;
    Modbus_Tx_Buf[len-1] = (crc >> 8) & 0xFF;

    Modbus_Send_Data(Modbus_Tx_Buf, len);
}

/**
 * @brief  读保持寄存器(0x03)
 * @param  addr: 起始地址
 * @param  num: 数量
 * @retval 无
 */
static void Read_Holding_Regs(uint16_t addr, uint16_t num)
{
    if(addr + num > HOLDING_REG_NUM)
    {
        Modbus_Send_Exception(READ_HOLDING_REGS, EXC_ILLEGAL_ADDR);
        return;
    }

    uint8_t len = 3 + num*2 + 2;
    Modbus_Tx_Buf[0] = MODBUS_SLAVE_ADDR;
    Modbus_Tx_Buf[1] = READ_HOLDING_REGS;
    Modbus_Tx_Buf[2] = num * 2;

    for(uint16_t i=0; i<num; i++)
    {
        Modbus_Tx_Buf[3 + i*2] = (Holding_Reg[addr + i] >> 8) & 0xFF;
        Modbus_Tx_Buf[4 + i*2] = Holding_Reg[addr + i] & 0xFF;
    }

    uint16_t crc = CRC16_Modbus(Modbus_Tx_Buf, len-2);
    Modbus_Tx_Buf[len-2] = crc & 0xFF;
    Modbus_Tx_Buf[len-1] = (crc >> 8) & 0xFF;

    Modbus_Send_Data(Modbus_Tx_Buf, len);
}

/**
 * @brief  读输入寄存器(0x04)
 * @param  addr: 起始地址
 * @param  num: 数量
 * @retval 无
 */
static void Read_Input_Regs(uint16_t addr, uint16_t num)
{
    if(addr + num > INPUT_REG_NUM)
    {
        Modbus_Send_Exception(READ_INPUT_REGS, EXC_ILLEGAL_ADDR);
        return;
    }

    uint8_t len = 3 + num*2 + 2;
    Modbus_Tx_Buf[0] = MODBUS_SLAVE_ADDR;
    Modbus_Tx_Buf[1] = READ_INPUT_REGS;
    Modbus_Tx_Buf[2] = num * 2;

    for(uint16_t i=0; i<num; i++)
    {
        Modbus_Tx_Buf[3 + i*2] = (Input_Reg[addr + i] >> 8) & 0xFF;
        Modbus_Tx_Buf[4 + i*2] = Input_Reg[addr + i] & 0xFF;
    }

    uint16_t crc = CRC16_Modbus(Modbus_Tx_Buf, len-2);
    Modbus_Tx_Buf[len-2] = crc & 0xFF;
    Modbus_Tx_Buf[len-1] = (crc >> 8) & 0xFF;

    Modbus_Send_Data(Modbus_Tx_Buf, len);
}

/**
 * @brief  写单个线圈(0x05)
 * @param  addr: 地址
 * @param  value: 值
 * @retval 无
 */
static void Write_Single_Coil(uint16_t addr, uint16_t value)
{
    if(addr >= COILS_NUM)
    {
        Modbus_Send_Exception(WRITE_SINGLE_COIL, EXC_ILLEGAL_ADDR);
        return;
    }
    if(value != 0xFF00 && value != 0x0000)
    {
        Modbus_Send_Exception(WRITE_SINGLE_COIL, EXC_ILLEGAL_VALUE);
        return;
    }

    Coils[addr] = (value == 0xFF00) ? 1 : 0;

    uint8_t len = 6 + 2;
    Modbus_Tx_Buf[0] = MODBUS_SLAVE_ADDR;
    Modbus_Tx_Buf[1] = WRITE_SINGLE_COIL;
    Modbus_Tx_Buf[2] = (addr >> 8) & 0xFF;
    Modbus_Tx_Buf[3] = addr & 0xFF;
    Modbus_Tx_Buf[4] = (value >> 8) & 0xFF;
    Modbus_Tx_Buf[5] = value & 0xFF;

    uint16_t crc = CRC16_Modbus(Modbus_Tx_Buf, len-2);
    Modbus_Tx_Buf[len-2] = crc & 0xFF;
    Modbus_Tx_Buf[len-1] = (crc >> 8) & 0xFF;

    Modbus_Send_Data(Modbus_Tx_Buf, len);
}

/**
 * @brief  写单个保持寄存器(0x06)
 * @param  addr: 地址
 * @param  value: 值
 * @retval 无
 */
static void Write_Single_Reg(uint16_t addr, uint16_t value)
{
    if(addr >= HOLDING_REG_NUM)
    {
        Modbus_Send_Exception(WRITE_SINGLE_REG, EXC_ILLEGAL_ADDR);
        return;
    }

    Holding_Reg[addr] = value;

    uint8_t len = 6 + 2;
    Modbus_Tx_Buf[0] = MODBUS_SLAVE_ADDR;
    Modbus_Tx_Buf[1] = WRITE_SINGLE_REG;
    Modbus_Tx_Buf[2] = (addr >> 8) & 0xFF;
    Modbus_Tx_Buf[3] = addr & 0xFF;
    Modbus_Tx_Buf[4] = (value >> 8) & 0xFF;
    Modbus_Tx_Buf[5] = value & 0xFF;

    uint16_t crc = CRC16_Modbus(Modbus_Tx_Buf, len-2);
    Modbus_Tx_Buf[len-2] = crc & 0xFF;
    Modbus_Tx_Buf[len-1] = (crc >> 8) & 0xFF;

    Modbus_Send_Data(Modbus_Tx_Buf, len);
}

/**
 * @brief  Modbus从机数据处理主函数
 * @param  无
 * @retval 无
 */
void Modbus_Slave_Process(void)
{
    if(Modbus_Rx_Len < 8) return; // 最小有效帧长度

    // 校验从机地址
    if(Modbus_Rx_Buf[0] != MODBUS_SLAVE_ADDR)
    {
        Modbus_Reset_Rx();
        return;
    }

    // 校验CRC
    uint16_t recv_crc = (Modbus_Rx_Buf[Modbus_Rx_Len-1] << 8) | Modbus_Rx_Buf[Modbus_Rx_Len-2];
    uint16_t calc_crc = CRC16_Modbus(Modbus_Rx_Buf, Modbus_Rx_Len-2);
    if(recv_crc != calc_crc)
    {
        Modbus_Reset_Rx();
        return;
    }

    // 解析功能码
    uint8_t func = Modbus_Rx_Buf[1];
    uint16_t addr, num, value;

    switch(func)
    {
        case READ_COILS:
            addr = (Modbus_Rx_Buf[2] << 8) | Modbus_Rx_Buf[3];
            num = (Modbus_Rx_Buf[4] << 8) | Modbus_Rx_Buf[5];
            Read_Coils(addr, num);
            break;

        case READ_DISCRETE_INPUTS:
            addr = (Modbus_Rx_Buf[2] << 8) | Modbus_Rx_Buf[3];
            num = (Modbus_Rx_Buf[4] << 8) | Modbus_Rx_Buf[5];
            Read_Discrete_Inputs(addr, num);
            break;

        case READ_HOLDING_REGS:
            addr = (Modbus_Rx_Buf[2] << 8) | Modbus_Rx_Buf[3];
            num = (Modbus_Rx_Buf[4] << 8) | Modbus_Rx_Buf[5];
            Read_Holding_Regs(addr, num);
            break;

        case READ_INPUT_REGS:
            addr = (Modbus_Rx_Buf[2] << 8) | Modbus_Rx_Buf[3];
            num = (Modbus_Rx_Buf[4] << 8) | Modbus_Rx_Buf[5];
            Read_Input_Regs(addr, num);
            break;

        case WRITE_SINGLE_COIL:
            addr = (Modbus_Rx_Buf[2] << 8) | Modbus_Rx_Buf[3];
            value = (Modbus_Rx_Buf[4] << 8) | Modbus_Rx_Buf[5];
            Write_Single_Coil(addr, value);
            break;

        case WRITE_SINGLE_REG:
            addr = (Modbus_Rx_Buf[2] << 8) | Modbus_Rx_Buf[3];
            value = (Modbus_Rx_Buf[4] << 8) | Modbus_Rx_Buf[5];
            Write_Single_Reg(addr, value);
            break;

        default:
            Modbus_Send_Exception(func, EXC_ILLEGAL_FUNC);
            break;
    }
}

3. 修改串口中断文件:usart.c

打开 usart.c,添加串口接收中断回调函数,实现数据接收。

c 复制代码
#include "usart.h"
#include "modbus_rtu.h"  // 添加头文件

UART_HandleTypeDef huart1;

// USART1初始化函数(CubeMX生成,无需修改)
void MX_USART1_UART_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;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
}

// 串口中断服务函数(CubeMX生成,无需修改)
void USART1_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart1);
}

/**
 * @brief  串口接收完成回调函数
 * @param  huart: 串口句柄
 * @retval 无
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        if(Modbus_Rx_Len < MODBUS_BUFFER_SIZE-1)
        {
            Modbus_Rx_Len++;
        }
        // 继续接收下一个字节
        HAL_UART_Receive_IT(&huart1, &Modbus_Rx_Buf[Modbus_Rx_Len], 1);
    }
}

4. 修改主函数文件:main.c

添加头文件包含、初始化、循环处理。

c 复制代码
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "modbus_rtu.h"  // 添加Modbus头文件

void SystemClock_Config(void);

int main(void)
{
  // 初始化
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  // Modbus从机初始化
  Modbus_Slave_Init();

  while (1)
  {
    // Modbus数据处理
    Modbus_Slave_Process();

    // 模拟输入寄存器数据更新(可替换为实际传感器数据)
    static uint32_t tick = 0;
    if(HAL_GetTick() - tick > 1000)
    {
        tick = HAL_GetTick();
        Input_Reg[0]++;
        if(Input_Reg[0] > 1000) Input_Reg[0] = 0;
    }
  }
}

// 系统时钟配置(CubeMX生成,无需修改)
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }
}

六、Modbus RTU 从机工作流程图









系统上电初始化
Modbus从机初始化

配置寄存器/开启串口中断
串口等待接收数据
接收到完整数据帧?
校验从机地址
地址匹配?
重置接收状态
校验CRC16
CRC校验正确?
解析功能码
功能码支持?
返回非法功能异常
执行对应功能操作

读/写寄存器/线圈
构造响应数据帧
添加CRC校验
串口发送响应数据

七、代码编译与下载

步骤1:添加文件到工程

  1. modbus_rtu.h 复制到工程 Inc 文件夹
  2. modbus_rtu.c 复制到工程 Src 文件夹
  3. Keil 中右键 Source Group 1Add Existing Files,添加 modbus_rtu.c

步骤2:编译工程

  1. 点击 Keil 编译按钮,确保0错误0警告
  2. 若有报错,检查头文件包含、文件路径、函数声明

步骤3:程序下载

  1. ST-Link 连接 STM32 最小系统板
  2. 点击 Keil 下载按钮,下载程序到芯片

八、Modbus Poll 主机调试

步骤1:打开 Modbus Poll

  1. 连接串口:选择 USB-TTL 对应的串口
  2. 参数配置:波特率9600,数据位8,无校验,停止位1
  3. 从机地址:1(与代码中 MODBUS_SLAVE_ADDR 一致)

步骤2:功能测试

  1. 读保持寄存器(0x03)

    • 功能码:03
    • 起始地址:0
    • 寄存器数量:10
    • 可看到初始值全为0,写入后可读取
  2. 写单个保持寄存器(0x06)

    • 地址:0
    • 值:1234
    • 写入后,读寄存器可验证数据
  3. 读输入寄存器(0x04)

    • 功能码:04
    • 可看到数据每秒自动递增(代码模拟传感器数据)
  4. 读线圈(0x01)/写单个线圈(0x05)

    • 支持读写开关量状态

九、代码移植说明

  1. 修改串口 :将代码中 huart1 替换为目标串口句柄
  2. 修改从机地址 :修改 modbus_rtu.hMODBUS_SLAVE_ADDR
  3. 修改寄存器数量:修改对应宏定义,适配实际需求
  4. 修改波特率:在 CubeMX 中重新配置串口,代码无需修改
  5. 添加实际外设 :将 Input_Reg 替换为 ADC、传感器采集数据;将 Coils 替换为 LED、继电器控制

十、常见问题排查

  1. 无法通信

    • 检查接线:TX-RX、RX-TX、GND-GND
    • 检查串口参数:波特率、校验位、停止位一致
    • 检查从机地址是否匹配
  2. 数据错误

    • 检查 CRC 校验函数是否正确
    • 检查数据大小端模式(Modbus 为大端模式)
  3. 无响应

    • 检查串口中断是否开启
    • 检查接收回调函数是否正常调用
    • 检查数据长度是否满足最小帧要求
相关推荐
爱喝纯牛奶的柠檬1 小时前
【已验证】STM32 LoRa 环境监测与远程控制系统
arm开发·stm32·单片机·嵌入式硬件
东京老树根10 小时前
Arduino - 入门02 - Arduino编程基础 Arduino程序结构,实物与模拟器对照,Arduino常用函数
单片机·机器人
salipopl12 小时前
基于STM32平台的多旋翼无人机系统设计与实现
stm32·嵌入式硬件·无人机
iCxhust20 小时前
8086/8088单板机VSCode集成自动下载功能(完善串口接收显示版)
ide·vscode·单片机·编辑器·微机原理·8088单板机·8086单板机
振南的单片机世界21 小时前
从数码管到点阵屏:动态扫描加595,3个IO驱动256个LED
stm32·单片机·嵌入式硬件
listhi52021 小时前
STC8 16通道模拟采集 + 4路串口 + 8路PWM 程序
stm32·单片机·嵌入式硬件
星夜夏空991 天前
STM32单片机学习(4)——嵌入式概述
stm32·单片机·学习
Deitymoon1 天前
STM32——OLED显示字符串
单片机·嵌入式硬件
LCG元1 天前
STM32实战:基于STM32F407的FFT频谱分析(音频信号处理)
stm32·音视频·信号处理