文章目录
-
- 一、前言
- 二、硬件准备
- 三、软件环境准备
- [四、STM32CubeMX 工程创建与配置](#四、STM32CubeMX 工程创建与配置)
- [五、Modbus RTU 从机代码实现](#五、Modbus RTU 从机代码实现)
-
- [1. 创建文件:modbus_rtu.h](#1. 创建文件:modbus_rtu.h)
- [2. 创建文件:modbus_rtu.c](#2. 创建文件:modbus_rtu.c)
- [3. 修改串口中断文件:usart.c](#3. 修改串口中断文件:usart.c)
- [4. 修改主函数文件:main.c](#4. 修改主函数文件:main.c)
- [六、Modbus RTU 从机工作流程图](#六、Modbus RTU 从机工作流程图)
- 七、代码编译与下载
- [八、Modbus Poll 主机调试](#八、Modbus Poll 主机调试)
-
- [步骤1:打开 Modbus Poll](#步骤1:打开 Modbus Poll)
- 步骤2:功能测试
- 九、代码移植说明
- 十、常见问题排查
一、前言
Modbus 是工业控制中最常用的串口通信协议 ,分为 RTU、ASCII、TCP 三种模式,其中 Modbus RTU 凭借传输效率高、抗干扰能力强,成为工业串口设备的首选。
STM32F103 是入门级嵌入式开发最经典的芯片,本教程从零开始,手把手带你实现 STM32F103 作为 Modbus RTU 从机,支持主机读写线圈、离散输入、保持寄存器、输入寄存器,代码可直接移植、直接落地运行。
本教程适用人群
- 零基础 STM32 开发者
- 想学习工业 Modbus 协议的电子/自动化工程师
- 需要实现串口设备通信的毕业设计/项目开发人员
本教程实现功能
- STM32F103C8T6 作为 Modbus RTU 从机
- 支持功能码:0x01(读线圈)、0x02(读离散输入)、0x03(读保持寄存器)、0x04(读输入寄存器)、0x05(写单个线圈)、0x06(写单个保持寄存器)
- 串口波特率:9600,数据位8,停止位1,无校验
- 完整的 CRC 校验、超时处理、异常码返回
二、硬件准备
- 核心板:STM32F103C8T6 最小系统板
- 调试下载器:ST-Link/V2
- USB-TTL 模块(用于主机调试,如 CH340)
- 杜邦线若干
- 电脑(安装 Keil5、STM32CubeMX)
硬件接线
| STM32F103 | USB-TTL |
|---|---|
| PA9 (TX) | RX |
| PA10(RX) | TX |
| GND | GND |
注意:串口通信必须共地,否则无法正常通信!
三、软件环境准备
- STM32CubeMX:用于初始化工程配置
- Keil MDK-ARM V5:用于代码编写、编译、下载
- Modbus 调试工具:如 Modbus Poll(主机调试)
- CH340 驱动:电脑识别 USB-TTL 模块
四、STM32CubeMX 工程创建与配置
步骤1:新建工程
- 打开 STM32CubeMX,点击
ACCESS TO MCU SELECTOR - 芯片选择:
STM32F103C8T6,点击Start Project
步骤2:系统时钟配置
- 点击
RCC,High Speed Clock选择Crystal/Ceramic Resonator(外部晶振) - 点击
Clock Configuration,配置系统时钟为 72MHz- HSE 输入:8MHz
- PLL 倍频:9
- 系统时钟:72MHz
步骤3:串口配置(USART1)
Modbus RTU 基于串口通信,本教程使用 USART1
- 点击
USART1,模式选择Asynchronous(异步串口) - 参数配置:
- 波特率:9600
- 数据位:8
- 校验位:None
- 停止位:1
- 开启串口接收中断:
- 点击
NVIC Settings - 勾选
USART1 global interrupt使能中断 - 优先级默认即可
- 点击
步骤4:工程生成配置
- 点击
Project Manager - 填写工程名称、选择工程路径
Toolchain/IDE选择MDK-ARM V5- 点击
Code Generator,勾选Generate peripheral initialization as a pair of .c/.h files per peripheral - 点击
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:添加文件到工程
- 将
modbus_rtu.h复制到工程Inc文件夹 - 将
modbus_rtu.c复制到工程Src文件夹 - Keil 中右键
Source Group 1→Add Existing Files,添加modbus_rtu.c
步骤2:编译工程
- 点击 Keil 编译按钮,确保0错误0警告
- 若有报错,检查头文件包含、文件路径、函数声明
步骤3:程序下载
- ST-Link 连接 STM32 最小系统板
- 点击 Keil 下载按钮,下载程序到芯片
八、Modbus Poll 主机调试
步骤1:打开 Modbus Poll
- 连接串口:选择 USB-TTL 对应的串口
- 参数配置:波特率9600,数据位8,无校验,停止位1
- 从机地址:1(与代码中
MODBUS_SLAVE_ADDR一致)
步骤2:功能测试
-
读保持寄存器(0x03)
- 功能码:03
- 起始地址:0
- 寄存器数量:10
- 可看到初始值全为0,写入后可读取
-
写单个保持寄存器(0x06)
- 地址:0
- 值:1234
- 写入后,读寄存器可验证数据
-
读输入寄存器(0x04)
- 功能码:04
- 可看到数据每秒自动递增(代码模拟传感器数据)
-
读线圈(0x01)/写单个线圈(0x05)
- 支持读写开关量状态
九、代码移植说明
- 修改串口 :将代码中
huart1替换为目标串口句柄 - 修改从机地址 :修改
modbus_rtu.h中MODBUS_SLAVE_ADDR - 修改寄存器数量:修改对应宏定义,适配实际需求
- 修改波特率:在 CubeMX 中重新配置串口,代码无需修改
- 添加实际外设 :将
Input_Reg替换为 ADC、传感器采集数据;将Coils替换为 LED、继电器控制
十、常见问题排查
-
无法通信
- 检查接线:TX-RX、RX-TX、GND-GND
- 检查串口参数:波特率、校验位、停止位一致
- 检查从机地址是否匹配
-
数据错误
- 检查 CRC 校验函数是否正确
- 检查数据大小端模式(Modbus 为大端模式)
-
无响应
- 检查串口中断是否开启
- 检查接收回调函数是否正常调用
- 检查数据长度是否满足最小帧要求