STM32 Modbus RTU从站程序,包含核心功能,易于理解和移植。
一、最简Modbus从站实现(单文件版)
modbus_slave.c
/**
* @file modbus_slave.c
* @brief STM32 Modbus RTU从站精简实现
* @date 2024
*/
#include "stm32f10x.h"
#include <string.h>
/* Modbus配置 */
#define MODBUS_SLAVE_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 */
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<REG_HOLDING_NUM; i++) {
holding_regs[i] = i * 100;
}
}
/**
* @brief CRC16计算
* @param buf 数据缓冲区
* @param len 数据长度
* @return CRC16值
*/
static uint16_t crc16(uint8_t *buf, uint16_t len)
{
uint16_t crc = 0xFFFF;
uint16_t i, j;
for (i = 0; i < len; i++) {
crc ^= buf[i];
for (j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
/**
* @brief 发送响应
* @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<len; i++) {
USART_SendData(USART2, buf[i]);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
/* 发送CRC */
USART_SendData(USART2, crc & 0xFF);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
USART_SendData(USART2, (crc >> 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_SLAVE_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<reg_num; i++) {
tx_buf[tx_len++] = (holding_regs[reg_addr+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;
// 回显请求
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<reg_num; i++) {
holding_regs[reg_addr+i] = (rx_buf[idx] << 8) | rx_buf[idx+1];
idx += 2;
}
// 回显请求的前6个字节
memcpy(tx_buf, rx_buf, 6);
tx_len = 6;
break;
default:
// 不支持的功能码
tx_buf[0] = addr;
tx_buf[1] = func | 0x80;
tx_buf[2] = 0x01; // 非法功能
tx_len = 3;
break;
}
/* 发送响应 */
if(tx_len > 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_slave.h"
#include <stdio.h>
/* 系统时钟配置 */
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<ms*8000; i++);
}
int main(void)
{
/* 初始化系统 */
SystemClock_Init();
/* 初始化Modbus */
Modbus_Init();
/* 初始化寄存器示例值 */
Modbus_WriteReg(0, 1234); // 寄存器0
Modbus_WriteReg(1, 5678); // 寄存器1
Modbus_WriteReg(2, 100); // 寄存器2
while(1)
{
/* 处理Modbus请求 */
Modbus_Poll();
/* 更新寄存器值(示例:模拟传感器数据) */
static uint32_t counter = 0;
if(++counter >= 1000) {
counter = 0;
uint16_t temp = Modbus_ReadReg(2);
Modbus_WriteReg(2, temp + 1); // 温度递增
}
Delay_ms(1);
}
}
二、使用CubeMX生成的Modbus程序(HAL库版)
如果你使用STM32CubeMX生成代码,这是更现代的方式:
modbus.c (HAL库版本)
/* modbus.c - HAL库版本 */
#include "modbus.h"
#include <string.h>
#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<length; i++) {
crc ^= data[i];
for(uint8_t j=0; j<8; j++) {
if(crc & 0x0001) {
crc >>= 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<reg_count; i++) {
if(start_addr + i < REG_COUNT) {
tx_buffer[tx_length++] = (holding_regs[start_addr+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从站测试脚本
"""
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):
"""计算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, slave_addr, start_addr, count):
"""读取保持寄存器"""
# 构建请求帧
frame = bytes([
slave_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, slave_addr, reg_addr, value):
"""写入单个寄存器"""
frame = bytes([
slave_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("\n测试2:写入寄存器")
response = self.write_single_register(0x01, 0, 9999)
print(f"响应: {response.hex()}")
# 测试3:再次读取
print("\n测试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()
参考代码 stm32-modbus协议程序 www.youwenfan.com/contentali/71977.html
四、常见问题解决
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. 添加更多功能码
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_SLAVE_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}, // 只读
// ...
};
这个精简版Modbus程序可以直接在你的STM32F103上运行,支持最常用的03和06功能码。