基于STM32F103的RS485 MODBUS从站例程
STM32F103 RS485 MODBUS从站实现,包含RTU协议解析、寄存器映射、CRC校验、中断处理等功能,可直接用于工业现场设备通信。
一、系统架构
STM32F103 RS485 MODBUS从站架构:
├── 硬件层
│ ├── STM32F103C8T6/CBT6 微控制器
│ ├── SP3485/MAX485 RS485收发器
│ ├── 120Ω终端电阻
│ └── TVS防静电保护
├── 驱动层
│ ├── USART DMA传输
│ ├── GPIO控制DE/RE
│ ├── 定时器3.5字符超时检测
│ └、 中断优先级管理
├── 协议层
│ ├── MODBUS RTU帧解析
│ ├── CRC16校验计算
│ ├── 功能码处理(01/02/03/04/05/06/15/16)
│ └、 异常响应生成
├── 应用层
│ ├── 保持寄存器映射
│ ├── 输入寄存器映射
│ ├── 线圈状态映射
│ └、 离散输入映射
└── 工具层
├── 参数配置存储
├── 设备ID管理
├── 通信诊断
└、 看门狗保护
二、硬件连接
2.1 RS485接口连接
STM32F103 SP3485 RS485总线
PA2(TX1) --> DI --
PA3(RX1) <-- RO --
PA1 --> DE/RE --
PB0 --> LED -- 通信指示灯
GND --> GND --
3.3V --> VCC --
A --> A+ --> 设备1 A+
B --> B- --> 设备1 B-
2.2 寄存器映射表
| 寄存器地址 | 名称 | 读写权限 | 说明 |
|---|---|---|---|
| 40001 | 设备状态 | 只读 | 运行状态字 |
| 40002 | 设备温度 | 只读 | 温度值×10 |
| 40003 | 运行时间 | 只读 | 小时数 |
| 40004 | 错误代码 | 只读 | 错误状态 |
| 40005 | 控制命令 | 读写 | 控制指令 |
| 40006 | 设定速度 | 读写 | 速度设定值 |
| 40007 | 实际速度 | 只读 | 实际速度值 |
| 40008 | 报警阈值 | 读写 | 报警温度阈值 |
三、核心代码实现
3.1 头文件定义 (modbus_slave.h)
#ifndef MODBUS_SLAVE_H
#define MODBUS_SLAVE_H
#include "stm32f10x.h"
#include <stdint.h>
#include <string.h>
// MODBUS配置参数
#define MODBUS_SLAVE_ID 0x01 // 从站地址
#define MODBUS_BAUDRATE 9600 // 波特率
#define MODBUS_PARITY USART_Parity_Even // 偶校验
#define MODBUS_DATA_BITS 8 // 数据位
#define MODBUS_STOP_BITS 1 // 停止位
// 功能码定义
#define MB_FUNC_READ_COILS 0x01
#define MB_FUNC_READ_DISCRETE_INPUTS 0x02
#define MB_FUNC_READ_HOLDING_REG 0x03
#define MB_FUNC_READ_INPUT_REG 0x04
#define MB_FUNC_WRITE_SINGLE_COIL 0x05
#define MB_FUNC_WRITE_SINGLE_REG 0x06
#define MB_FUNC_WRITE_MULTIPLE_COILS 0x0F
#define MB_FUNC_WRITE_MULTIPLE_REG 0x10
// 异常码定义
#define MB_EX_NONE 0x00
#define MB_EX_ILLEGAL_FUNCTION 0x01
#define MB_EX_ILLEGAL_DATA_ADDRESS 0x02
#define MB_EX_ILLEGAL_DATA_VALUE 0x03
#define MB_EX_SLAVE_DEVICE_FAILURE 0x04
#define MB_EX_ACKNOWLEDGE 0x05
#define MB_EX_SLAVE_DEVICE_BUSY 0x06
// 寄存器地址定义
#define REG_START_ADDR 0x0000
#define REG_HOLDING_START 0x0000
#define REG_INPUT_START 0x0000
#define REG_COIL_START 0x0000
#define REG_DISCRETE_START 0x0000
// 寄存器数量定义
#define REG_HOLDING_NREGS 100
#define REG_INPUT_NREGS 50
#define REG_COIL_NREGS 32
#define REG_DISCRETE_NREGS 32
// 帧超时定义(3.5个字符时间)
#define MODBUS_FRAME_TIMEOUT 4 // 4ms @ 9600bps
// MODBUS数据结构
typedef struct {
uint8_t slave_id; // 从站地址
uint8_t function_code; // 功能码
uint16_t start_addr; // 起始地址
uint16_t reg_count; // 寄存器数量
uint8_t *data; // 数据指针
uint16_t data_len; // 数据长度
uint8_t exception_code; // 异常码
} ModbusFrame;
// 寄存器映射结构
typedef struct {
uint16_t holding_regs[REG_HOLDING_NREGS]; // 保持寄存器
uint16_t input_regs[REG_INPUT_NREGS]; // 输入寄存器
uint8_t coil_status[REG_COIL_NREGS]; // 线圈状态
uint8_t discrete_inputs[REG_DISCRETE_NREGS]; // 离散输入
} RegisterMap;
// 通信状态结构
typedef struct {
uint32_t total_frames; // 总帧数
uint32_t error_frames; // 错误帧数
uint32_t timeout_frames; // 超时帧数
uint32_t crc_errors; // CRC错误数
uint32_t last_comm_time; // 最后通信时间
uint8_t comm_status; // 通信状态
} CommStatus;
// 函数声明
void Modbus_Init(void);
void Modbus_ProcessFrame(uint8_t *rx_buf, uint16_t rx_len, uint8_t *tx_buf, uint16_t *tx_len);
uint16_t Modbus_CRC16(uint8_t *buffer, uint16_t length);
void Modbus_SendResponse(uint8_t *tx_buf, uint16_t tx_len);
void Modbus_HandleException(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len);
void Modbus_ReadHoldingRegisters(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len);
void Modbus_ReadInputRegisters(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len);
void Modbus_WriteSingleRegister(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len);
void Modbus_WriteMultipleRegisters(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len);
void Modbus_ReadCoils(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len);
void Modbus_WriteSingleCoil(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len);
void Modbus_UpdateRegisters(void);
// 外部变量声明
extern RegisterMap g_reg_map;
extern CommStatus g_comm_status;
extern volatile uint8_t g_frame_received;
#endif // MODBUS_SLAVE_H
3.2 主程序实现 (main.c)
#include "stm32f10x.h"
#include "modbus_slave.h"
#include "usart.h"
#include "timer.h"
#include "gpio.h"
#include "delay.h"
// 全局变量
RegisterMap g_reg_map;
CommStatus g_comm_status;
volatile uint8_t g_frame_received = 0;
volatile uint8_t g_frame_timeout = 0;
uint8_t rx_buffer[256];
uint8_t tx_buffer[256];
uint16_t rx_length = 0;
// 系统初始化
void System_Init(void)
{
// 初始化系统时钟
SystemClock_Init();
// 初始化延时
Delay_Init();
// 初始化GPIO
GPIO_Init_Config();
// 初始化USART1 (RS485)
USART1_Init(MODBUS_BAUDRATE);
// 初始化定时器(用于3.5字符超时检测)
TIM3_Init(MODBUS_FRAME_TIMEOUT);
// 初始化MODBUS
Modbus_Init();
// 初始化寄存器默认值
g_reg_map.holding_regs[0] = 0x1234; // 设备ID
g_reg_map.holding_regs[1] = 250; // 温度值 25.0℃
g_reg_map.holding_regs[2] = 1000; // 运行时间
g_reg_map.holding_regs[3] = 0x0000; // 错误代码
g_reg_map.holding_regs[4] = 0x0001; // 控制命令
g_reg_map.holding_regs[5] = 50; // 设定速度 50%
g_reg_map.holding_regs[6] = 48; // 实际速度 48%
g_reg_map.holding_regs[7] = 300; // 报警阈值 30.0℃
// 初始化通信状态
memset(&g_comm_status, 0, sizeof(CommStatus));
g_comm_status.comm_status = 1; // 通信正常
}
int main(void)
{
uint16_t tx_len;
// 系统初始化
System_Init();
// 主循环
while(1)
{
// 检查是否接收到完整帧
if(g_frame_received)
{
g_frame_received = 0;
// 处理MODBUS帧
Modbus_ProcessFrame(rx_buffer, rx_length, tx_buffer, &tx_len);
// 发送响应
if(tx_len > 0)
{
Modbus_SendResponse(tx_buffer, tx_len);
g_comm_status.total_frames++;
g_comm_status.last_comm_time = HAL_GetTick();
}
}
// 更新寄存器数据(模拟传感器数据变化)
Modbus_UpdateRegisters();
// 检查通信超时
if(HAL_GetTick() - g_comm_status.last_comm_time > 5000) // 5秒无通信
{
g_comm_status.comm_status = 0; // 通信超时
}
else
{
g_comm_status.comm_status = 1; // 通信正常
}
// 通信指示灯闪烁
if(g_comm_status.comm_status)
{
GPIO_SetBits(GPIOB, GPIO_Pin_0); // LED亮
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_0); // LED灭
}
Delay_ms(10);
}
}
3.3 MODBUS协议处理 (modbus_slave.c)
#include "modbus_slave.h"
#include "crc16.h"
RegisterMap g_reg_map;
CommStatus g_comm_status;
volatile uint8_t g_frame_received = 0;
// MODBUS初始化
void Modbus_Init(void)
{
// 设置从站ID
g_reg_map.holding_regs[0] = MODBUS_SLAVE_ID;
// 初始化其他寄存器
for(int i = 1; i < REG_HOLDING_NREGS; i++)
{
g_reg_map.holding_regs[i] = 0x0000;
}
for(int i = 0; i < REG_INPUT_NREGS; i++)
{
g_reg_map.input_regs[i] = 0x0000;
}
for(int i = 0; i < REG_COIL_NREGS; i++)
{
g_reg_map.coil_status[i] = 0x00;
}
for(int i = 0; i < REG_DISCRETE_NREGS; i++)
{
g_reg_map.discrete_inputs[i] = 0x00;
}
}
// 处理MODBUS帧
void Modbus_ProcessFrame(uint8_t *rx_buf, uint16_t rx_len, uint8_t *tx_buf, uint16_t *tx_len)
{
ModbusFrame frame;
uint16_t crc_calc, crc_recv;
*tx_len = 0;
// 检查帧长度
if(rx_len < 4)
{
g_comm_status.error_frames++;
return;
}
// 解析帧头
frame.slave_id = rx_buf[0];
frame.function_code = rx_buf[1];
// 检查从站地址
if(frame.slave_id != MODBUS_SLAVE_ID && frame.slave_id != 0x00) // 0x00为广播地址
{
return; // 不是发给本设备的帧
}
// 检查CRC
crc_calc = Modbus_CRC16(rx_buf, rx_len - 2);
crc_recv = (rx_buf[rx_len-2] << 8) | rx_buf[rx_len-1];
if(crc_calc != crc_recv)
{
g_comm_status.crc_errors++;
return;
}
// 解析地址和数据
frame.start_addr = (rx_buf[2] << 8) | rx_buf[3];
frame.reg_count = (rx_buf[4] << 8) | rx_buf[5];
// 根据功能码处理
switch(frame.function_code)
{
case MB_FUNC_READ_HOLDING_REG:
Modbus_ReadHoldingRegisters(&frame, tx_buf, tx_len);
break;
case MB_FUNC_READ_INPUT_REG:
Modbus_ReadInputRegisters(&frame, tx_buf, tx_len);
break;
case MB_FUNC_WRITE_SINGLE_REG:
Modbus_WriteSingleRegister(&frame, tx_buf, tx_len);
break;
case MB_FUNC_WRITE_MULTIPLE_REG:
Modbus_WriteMultipleRegisters(&frame, rx_buf, tx_buf, tx_len);
break;
case MB_FUNC_READ_COILS:
Modbus_ReadCoils(&frame, tx_buf, tx_len);
break;
case MB_FUNC_WRITE_SINGLE_COIL:
Modbus_WriteSingleCoil(&frame, tx_buf, tx_len);
break;
default:
Modbus_HandleException(&frame, tx_buf, tx_len);
break;
}
}
// 计算CRC16校验
uint16_t Modbus_CRC16(uint8_t *buffer, uint16_t length)
{
uint16_t crc = 0xFFFF;
uint8_t i, j;
for(i = 0; i < length; i++)
{
crc ^= buffer[i];
for(j = 0; j < 8; j++)
{
if(crc & 0x0001)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
// 发送MODBUS响应
void Modbus_SendResponse(uint8_t *tx_buf, uint16_t tx_len)
{
uint16_t crc;
// 计算CRC
crc = Modbus_CRC16(tx_buf, tx_len);
tx_buf[tx_len++] = crc & 0xFF;
tx_buf[tx_len++] = (crc >> 8) & 0xFF;
// 发送数据
USART1_SendData(tx_buf, tx_len);
}
// 处理异常响应
void Modbus_HandleException(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len)
{
tx_buf[0] = frame->slave_id;
tx_buf[1] = frame->function_code | 0x80; // 设置异常标志
tx_buf[2] = frame->exception_code;
*tx_len = 3;
}
// 读取保持寄存器
void Modbus_ReadHoldingRegisters(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len)
{
uint16_t start_addr = frame->start_addr;
uint16_t reg_count = frame->reg_count;
uint16_t i;
// 检查地址范围
if(start_addr >= REG_HOLDING_NREGS ||
start_addr + reg_count > REG_HOLDING_NREGS ||
reg_count == 0 || reg_count > 125)
{
frame->exception_code = MB_EX_ILLEGAL_DATA_ADDRESS;
Modbus_HandleException(frame, tx_buf, tx_len);
return;
}
// 构建响应帧
tx_buf[0] = frame->slave_id;
tx_buf[1] = frame->function_code;
tx_buf[2] = reg_count * 2; // 字节数
// 填充寄存器数据
for(i = 0; i < reg_count; i++)
{
tx_buf[3 + i*2] = (g_reg_map.holding_regs[start_addr + i] >> 8) & 0xFF;
tx_buf[4 + i*2] = g_reg_map.holding_regs[start_addr + i] & 0xFF;
}
*tx_len = 3 + reg_count * 2;
}
// 读取输入寄存器
void Modbus_ReadInputRegisters(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len)
{
uint16_t start_addr = frame->start_addr;
uint16_t reg_count = frame->reg_count;
uint16_t i;
// 检查地址范围
if(start_addr >= REG_INPUT_NREGS ||
start_addr + reg_count > REG_INPUT_NREGS ||
reg_count == 0 || reg_count > 125)
{
frame->exception_code = MB_EX_ILLEGAL_DATA_ADDRESS;
Modbus_HandleException(frame, tx_buf, tx_len);
return;
}
// 构建响应帧
tx_buf[0] = frame->slave_id;
tx_buf[1] = frame->function_code;
tx_buf[2] = reg_count * 2; // 字节数
// 填充寄存器数据
for(i = 0; i < reg_count; i++)
{
tx_buf[3 + i*2] = (g_reg_map.input_regs[start_addr + i] >> 8) & 0xFF;
tx_buf[4 + i*2] = g_reg_map.input_regs[start_addr + i] & 0xFF;
}
*tx_len = 3 + reg_count * 2;
}
// 写单个寄存器
void Modbus_WriteSingleRegister(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len)
{
uint16_t start_addr = frame->start_addr;
uint16_t reg_value;
// 检查地址范围
if(start_addr >= REG_HOLDING_NREGS)
{
frame->exception_code = MB_EX_ILLEGAL_DATA_ADDRESS;
Modbus_HandleException(frame, tx_buf, tx_len);
return;
}
// 获取写入值
reg_value = (rx_buffer[4] << 8) | rx_buffer[5];
// 检查写入值范围
if(reg_value > 65535)
{
frame->exception_code = MB_EX_ILLEGAL_DATA_VALUE;
Modbus_HandleException(frame, tx_buf, tx_len);
return;
}
// 写入寄存器
g_reg_map.holding_regs[start_addr] = reg_value;
// 构建响应帧(回显请求)
memcpy(tx_buf, rx_buffer, 6);
*tx_len = 6;
}
// 写多个寄存器
void Modbus_WriteMultipleRegisters(ModbusFrame *frame, uint8_t *rx_buf, uint8_t *tx_buf, uint16_t *tx_len)
{
uint16_t start_addr = frame->start_addr;
uint16_t reg_count = frame->reg_count;
uint8_t byte_count = rx_buf[6];
uint16_t i;
// 检查地址范围
if(start_addr >= REG_HOLDING_NREGS ||
start_addr + reg_count > REG_HOLDING_NREGS ||
reg_count == 0 || reg_count > 123 ||
byte_count != reg_count * 2)
{
frame->exception_code = MB_EX_ILLEGAL_DATA_ADDRESS;
Modbus_HandleException(frame, tx_buf, tx_len);
return;
}
// 写入寄存器
for(i = 0; i < reg_count; i++)
{
g_reg_map.holding_regs[start_addr + i] = (rx_buf[7 + i*2] << 8) | rx_buf[8 + i*2];
}
// 构建响应帧
tx_buf[0] = frame->slave_id;
tx_buf[1] = frame->function_code;
tx_buf[2] = (start_addr >> 8) & 0xFF;
tx_buf[3] = start_addr & 0xFF;
tx_buf[4] = (reg_count >> 8) & 0xFF;
tx_buf[5] = reg_count & 0xFF;
*tx_len = 6;
}
// 读取线圈状态
void Modbus_ReadCoils(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len)
{
uint16_t start_addr = frame->start_addr;
uint16_t coil_count = frame->reg_count;
uint16_t byte_count = (coil_count + 7) / 8;
uint16_t i, j;
// 检查地址范围
if(start_addr >= REG_COIL_NREGS ||
start_addr + coil_count > REG_COIL_NREGS ||
coil_count == 0 || coil_count > 2000)
{
frame->exception_code = MB_EX_ILLEGAL_DATA_ADDRESS;
Modbus_HandleException(frame, tx_buf, tx_len);
return;
}
// 构建响应帧
tx_buf[0] = frame->slave_id;
tx_buf[1] = frame->function_code;
tx_buf[2] = byte_count; // 字节数
// 填充线圈数据
memset(&tx_buf[3], 0, byte_count);
for(i = 0; i < coil_count; i++)
{
if(g_reg_map.coil_status[start_addr + i])
{
tx_buf[3 + i/8] |= (1 << (i % 8));
}
}
*tx_len = 3 + byte_count;
}
// 写单个线圈
void Modbus_WriteSingleCoil(ModbusFrame *frame, uint8_t *tx_buf, uint16_t *tx_len)
{
uint16_t start_addr = frame->start_addr;
uint16_t coil_value = (rx_buffer[4] << 8) | rx_buffer[5];
// 检查地址范围
if(start_addr >= REG_COIL_NREGS)
{
frame->exception_code = MB_EX_ILLEGAL_DATA_ADDRESS;
Modbus_HandleException(frame, tx_buf, tx_len);
return;
}
// 检查线圈值(只能是0x0000或0xFF00)
if(coil_value != 0x0000 && coil_value != 0xFF00)
{
frame->exception_code = MB_EX_ILLEGAL_DATA_VALUE;
Modbus_HandleException(frame, tx_buf, tx_len);
return;
}
// 写入线圈
g_reg_map.coil_status[start_addr] = (coil_value == 0xFF00) ? 1 : 0;
// 构建响应帧(回显请求)
memcpy(tx_buf, rx_buffer, 6);
*tx_len = 6;
}
// 更新寄存器数据(模拟传感器数据)
void Modbus_UpdateRegisters(void)
{
static uint32_t last_update = 0;
uint32_t current_time = HAL_GetTick();
// 每秒更新一次
if(current_time - last_update >= 1000)
{
last_update = current_time;
// 更新温度值(模拟变化)
g_reg_map.holding_regs[1] += 1;
if(g_reg_map.holding_regs[1] > 350) g_reg_map.holding_regs[1] = 250;
// 更新运行时间
g_reg_map.holding_regs[2] += 1;
// 更新输入寄存器(模拟传感器数据)
g_reg_map.input_regs[0] = g_reg_map.holding_regs[1]; // 温度
g_reg_map.input_regs[1] = g_reg_map.holding_regs[6]; // 实际速度
g_reg_map.input_regs[2] = g_reg_map.holding_regs[7]; // 报警阈值
}
}
3.4 USART驱动 (usart.c)
#include "usart.h"
#include "modbus_slave.h"
volatile uint8_t g_frame_received = 0;
volatile uint8_t g_frame_timeout = 0;
uint8_t rx_buffer[256];
uint16_t rx_index = 0;
// USART1初始化
void USART1_Init(uint32_t baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置USART1 TX (PA9) 和 RX (PA10)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART参数
USART_InitStructure.USART_BaudRate = baudrate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_Even; // MODBUS使用偶校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 配置中断
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 使能USART
USART_Cmd(USART1, ENABLE);
}
// USART1发送数据
void USART1_SendData(uint8_t *data, uint16_t length)
{
uint16_t i;
// 使能RS485发送
GPIO_SetBits(GPIOA, GPIO_Pin_1); // DE/RE高电平,发送模式
// 发送数据
for(i = 0; i < length; i++)
{
USART_SendData(USART1, data[i]);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
// 等待发送完成
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
// 禁用RS485发送
GPIO_ResetBits(GPIOA, GPIO_Pin_1); // DE/RE低电平,接收模式
}
// USART1中断服务函数
void USART1_IRQHandler(void)
{
uint8_t data;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
// 读取接收到的数据
data = USART_ReceiveData(USART1);
// 存储到接收缓冲区
if(rx_index < 256)
{
rx_buffer[rx_index++] = data;
}
// 重置超时计数器
g_frame_timeout = MODBUS_FRAME_TIMEOUT;
}
}
3.5 定时器驱动 (timer.c)
#include "timer.h"
#include "modbus_slave.h"
volatile uint8_t g_frame_timeout = 0;
extern volatile uint8_t g_frame_received;
extern uint16_t rx_index;
// TIM3初始化(用于3.5字符超时检测)
void TIM3_Init(uint16_t period_ms)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能TIM3时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 配置TIM3
TIM_TimeBaseStructure.TIM_Period = period_ms * 10 - 1; // 10kHz计数,period_ms毫秒
TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1; // 72MHz/7200 = 10kHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// 配置中断
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能更新中断
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
// 启动TIM3
TIM_Cmd(TIM3, ENABLE);
}
// TIM3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
// 检查是否超时
if(g_frame_timeout > 0)
{
g_frame_timeout--;
// 超时检测
if(g_frame_timeout == 0 && rx_index > 0)
{
g_frame_received = 1; // 设置帧接收完成标志
}
}
}
}
四、测试与验证
4.1 Modbus Poll测试配置
连接设置:
- 连接: Serial Port
- 串口: COM1
- 波特率: 9600
- 数据位: 8
- 奇偶校验: Even
- 停止位: 1
- Mode: RTU
从站设置:
- Slave ID: 1
- Function: 03 Read Holding Registers
- Address: 0000
- Quantity: 10
4.2 预期测试结果
寄存器数据:
40001: 0001 (从站ID)
40002: 0250 (温度25.0℃)
40003: 1000 (运行时间1000小时)
40004: 0000 (无错误)
40005: 0001 (控制命令)
40006: 0050 (设定速度50%)
40007: 0048 (实际速度48%)
40008: 0300 (报警阈值30.0℃)
4.3 串口调试助手测试
# Python测试脚本
import serial
import struct
import time
def modbus_read_holding_registers(port, slave_id, start_addr, reg_count):
"""读取保持寄存器"""
ser = serial.Serial(port, 9600, parity='E', timeout=1)
# 构建请求帧
request = struct.pack('>BBHH', slave_id, 0x03, start_addr, reg_count)
crc = calculate_crc(request)
request += struct.pack('<H', crc)
# 发送请求
ser.write(request)
time.sleep(0.1)
# 读取响应
response = ser.read(5 + reg_count * 2)
ser.close()
return response
# 测试
response = modbus_read_holding_registers('COM1', 0x01, 0x0000, 0x0008)
print(f"Response: {response.hex()}")
参考代码 基于STM32F103的RS485 MODBUS从站例程 www.youwenfan.com/contentcnv/72249.html
五、编译与部署
5.1 Keil工程配置
Target: STM32F103C8
Device: STM32F103C8T6
C/C++:
Include Paths: .\inc; .\User; .\Hardware
Define: STM32F10X_MD, USE_STDPERIPH_DRIVER
Linker:
Scatter File: STM32_FLASH.sct
Debug:
Debugger: ST-Link Debugger
5.2 烧录步骤
# 使用ST-Link Utility
1. 连接ST-Link到JTAG接口
2. 打开STM32 ST-Link Utility
3. Target -> Connect
4. Target -> Program & Verify
5. 选择编译生成的.hex文件
6. Start
六、故障排除
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 无法通信 | 波特率不匹配 | 检查双方波特率设置 |
| 数据错误 | 校验位错误 | 确认使用偶校验 |
| 帧不完整 | 超时时间太短 | 增加MODBUS_FRAME_TIMEOUT值 |
| 无响应 | 从站地址错误 | 确认从站ID为0x01 |
| 乱码 | 接线错误 | 检查A/B线是否接反 |
浙公网安备 33010602011771号