

51单片机实现Modbus RTU从机协议开发
一、引言
在工业自动化领域,Modbus通信协议因其简单可靠的特性被广泛应用。本文基于STC15F2K60S2单片机,详细解析如何开发一个支持读保持寄存器(0x03)和写单个寄存器(0x06)功能、读单个线圈(0x01)和写单个线圈(0x05)功能的Modbus RTU从机,适合嵌入式开发工程师和物联网爱好者参考。
二、硬件与软件环境
2.1 硬件平台
- 主控芯片:STC15F2K60S2(1T模式,11.0592MHz晶振)
- 通信接口:UART串口(波特率9600,8位数据位,1位停止位,无校验)
2.2 软件工具
- 开发环境:Keil C51
- 协议标准:Modbus RTU V1.1b
三、代码架构解析
3.1 核心模块划分
// 模块划分注释
├── 类型定义与常量声明 // 数据类型重定义、Modbus功能码/错误码、通信参数
├── 全局变量 // 接收/发送缓冲区、状态机变量、寄存器数组
├── 串口通信模块 // 字节/字符串发送、串口初始化
├── 定时器模块 // 超时检测(3.5字符间隔)
├── CRC16校验模块 // Modbus RTU帧校验
├── 功能码处理模块 // 0x03读寄存器、0x06写寄存器逻辑
├── 状态机驱动模块 // 接收/执行/发送状态管理
3.2 状态机设计
通过枚举类型定义4种状态,实现帧接收超时检测和协议处理流程控制:
typedef enum {
STATE_IDLE, // 空闲状态,等待接收起始字节
STATE_RECEIVING, // 接收状态,累计接收字节并重启定时器
STATE_EXECUTING, // 执行状态,解析帧数据并生成响应
STATE_SENT // 发送状态,完成响应帧发送
} RxState;
状态转移逻辑:
- IDLE → RECEIVING:接收到第一个字节时触发
- RECEIVING → EXECUTING:接收超时(3.5字符时间)时触发
- EXECUTING → SENT:生成响应帧后进入发送状态
- SENT → IDLE:响应帧发送完成后重置状态
四、关键功能实现
4.1 串口与定时器初始化
void UART_Init(void) {
SCON = 0x50; // 8位数据,可变波特率(串口模式1)
AUXR |= 0x01 | 0x04; // 定时器2为波特率发生器,1T模式
T2L = 0xE0; T2H = 0xFE; // 9600波特率初值(11.0592MHz)
AUXR |= 0x10; // 启动定时器2
ES = 1; // 使能串口中断
}
void Timer0_Init(void) {
AUXR |= 0x80; // 定时器0 1T模式
TMOD &= 0xF0; // 定时器模式0(16位自动重装)
TL0 = 0xD7; TH0 = 0xFD; // 50μs定时初值
}
定时器作用:
- 实现3.5个字符的接收超时检测(约4ms@9600bps),通过
T3_5_TIMEOUT=80计算(50μs×80=4ms)
4.2 CRC16校验算法
uint CRC16(uchar *puchMsg, uint usDataLen) {
uint wCRCin = 0xFFFF; // 初始值
uint wCPoly = 0xA001; // 生成多项式
uchar i, j;
for (i = 0; i < usDataLen; i++) {
wCRCin ^= puchMsg[i];
for (j = 0; j < 8; j++) {
if (wCRCin & 0x0001)
wCRCin = (wCRCin >> 1) ^ wCPoly;
else
wCRCin >>= 1;
}
}
return wCRCin; // 低字节在前,高字节在后
}
校验流程:
- 对接收到的帧数据(除CRC字段)计算CRC值
- 与帧末尾的CRC字段对比,验证数据完整性
4.3 功能码处理逻辑
4.3.1 读保持寄存器(0x03)
void handle_modbus_03(uint start_addr, uchar reg_count) {
if (start_addr + reg_count <= REGISTER_COUNT) { // 地址有效性检查
mb_buffer[2] = reg_count * 2; // 数据字节数(每个寄存器2字节)
// 填充寄存器数据(高字节在前)
for (uint i=0; i<reg_count; i++) {
mb_buffer[3+2*i] = (holding_registers[start_addr+i] >> 8);
mb_buffer[4+2*i] = (holding_registers[start_addr+i] & 0xFF);
}
} else { // 异常响应(非法地址)
mb_buffer[1] = 0x83; // 异常功能码=0x03+0x80
mb_buffer[2] = 0x02; // 错误码:非法数据地址
}
}
4.3.2 写单个寄存器(0x06)
void handle_modbus_06(uint reg_addr, uint reg_value) {
if (reg_addr < REGISTER_COUNT) { // 地址有效性检查
holding_registers[reg_addr] = reg_value; // 更新寄存器值
// 构建响应帧(地址+值)
mb_buffer[1] = 0x06;
mb_buffer[2] = (reg_addr >> 8);
mb_buffer[3] = (reg_addr & 0xFF);
mb_buffer[4] = (reg_value >> 8);
mb_buffer[5] = (reg_value & 0xFF);
} else { // 异常响应
mb_buffer[1] = 0x86;
mb_buffer[2] = 0x02; // 错误码复用非法地址
}
}
4.3.3读单个线圈(0x01)
/**
* @brief 设置单个线圈状态
* @param coil_addr 线圈地址(0-99)
* @param state 线圈状态(0-关,非0-开)
*/
void set_coil(uint coil_addr, uchar state) {
if(coil_addr < COIL_COUNT) {
uchar byte_idx = coil_addr / 8;
uchar bit_idx = coil_addr % 8;
if(state)
coils[byte_idx] |= (1 << bit_idx);
else
coils[byte_idx] &= ~(1 << bit_idx);
}
}
/**
* @brief 处理Modbus 01功能码请求(读线圈)
* @param start_addr 起始地址
* @param coil_count 线圈数量
*/
void handle_modbus_01(uint start_addr, uchar coil_count) {
uint crc;
uchar i, byte_count, bit_pos, byte_val;
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR; // 从站地址
mb_buffer[tx_index++] = MB_FUNC_READ_COIL; // 功能码
// 处理正常响应
if (start_addr + coil_count <= COIL_COUNT) {
byte_count = (coil_count + 7) / 8; // 计算需要的字节数
mb_buffer[tx_index++] = byte_count; // 字节数
// 填充线圈数据
for (i = 0; i < byte_count; i++) {
byte_val = 0;
for (bit_pos = 0; bit_pos < 8 && (i*8 + bit_pos) < coil_count; bit_pos++) {
byte_val |= get_coil(start_addr + i*8 + bit_pos) << bit_pos;
}
mb_buffer[tx_index++] = byte_val;
}
}
// 处理异常响应
else {
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR;
mb_buffer[tx_index++] = MB_FUNC_READ_COIL + MB_EXCEPTION_OFFSET; // 异常功能码
mb_buffer[tx_index++] = MB_ERROR_ILLEGAL; // 非法数据地址错误码
}
// 计算并添加CRC
crc = CRC16(mb_buffer, tx_index);
mb_buffer[tx_index++] = (uchar)(crc & 0xFF);
mb_buffer[tx_index++] = (uchar)(crc >> 8);
rx_state = STATE_SENT;
}
写单个线圈(0x05)
/**
* @brief 获取单个线圈状态
* @param coil_addr 线圈地址(0-99)
* @return 线圈状态(0-关,1-开)
*/
uchar get_coil(uint coil_addr) {
if(coil_addr < COIL_COUNT) {
uchar byte_idx = coil_addr / 8;
uchar bit_idx = coil_addr % 8;
return (coils[byte_idx] >> bit_idx) & 0x01;
}
return 0;
}
/**
* @brief 处理Modbus 05功能码请求(写单个线圈)
* @param coil_addr 线圈地址
* @param coil_value 线圈值(0xFF00-开,0x0000-关)
*/
void handle_modbus_05(uint coil_addr, uint coil_value) {
uint crc;
uchar state;
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR; // 从站地址
// 处理正常响应
if (coil_addr < COIL_COUNT) {
// 检查线圈值是否合法
if(coil_value == 0xFF00 || coil_value == 0x0000) {
state = (coil_value == 0xFF00) ? 1 : 0;
set_coil(coil_addr, state); // 更新线圈状态
mb_buffer[tx_index++] = MB_FUNC_WRITE_COIL; // 功能码
mb_buffer[tx_index++] = (uchar)(coil_addr >> 8); // 线圈地址高字节
mb_buffer[tx_index++] = (uchar)(coil_addr & 0xFF); // 线圈地址低字节
mb_buffer[tx_index++] = (uchar)(coil_value >> 8); // 线圈值高字节
mb_buffer[tx_index++] = (uchar)(coil_value & 0xFF); // 线圈值低字节
} else {
mb_buffer[tx_index++] = MB_FUNC_WRITE_COIL + MB_EXCEPTION_OFFSET; // 异常功能码
mb_buffer[tx_index++] = MB_ERROR_ILLEGAL_DATA; // 非法数据值错误码
}
}
// 处理异常响应
else {
mb_buffer[tx_index++] = MB_FUNC_WRITE_COIL + MB_EXCEPTION_OFFSET; // 异常功能码
mb_buffer[tx_index++] = MB_ERROR_ILLEGAL; // 非法数据地址错误码
}
// 计算并添加CRC
crc = CRC16(mb_buffer, tx_index);
mb_buffer[tx_index++] = (uchar)(crc & 0xFF);
mb_buffer[tx_index++] = (uchar)(crc >> 8);
rx_state = STATE_SENT;
}
五、协议处理主流程
void eMBPoll() {
switch (rx_state) {
case STATE_EXECUTING:
// 解析地址和功能码
if (mb_buffer[0] == SLAVE_ADDR) { // 从站地址匹配
switch (mb_buffer[1]) {
case 0x03: handle_modbus_03(...); break;
case 0x06: handle_modbus_06(...); break;
default: // 不支持的功能码,忽略
}
}
break;
case STATE_SENT:
Uart_SendString(mb_buffer, tx_index); // 发送响应帧
break;
}
}
六、调试与测试
6.1 工具推荐
- 串口调试助手:SSCOM、Modbus Poll/ Slave(用于模拟主站)
- 逻辑分析仪:抓取串口波形验证CRC校验和帧格式
6.2 常见问题
-
通信失败
- 检查波特率、校验位是否与主站一致
- 确认从站地址(
SLAVE_ADDR=0x01)匹配 - 验证CRC校验顺序(低字节在前)
-
寄存器读写异常
- 确保地址范围在
0x0000~0x001F(32个寄存器) - 检查数据字节顺序(高字节在前)
- 确保地址范围在
七、扩展与优化方向
- 功能扩展
- 添加写多个寄存器(0x10)、读输入寄存器(0x04)等功能码
- 实现寄存器数据掉电保存(结合EEPROM或Flash)
八、完整代码
/*********************************************************************
* @file main.c
* @brief STC15F2K60S2单片机Modbus RTU从机程序
* @details 实现Modbus RTU通信协议,支持线圈和保持寄存器的读写功能,
*
* @硬件平台 STC15F2K60S2单片机(11.0592MHz晶振,1T模式)
* @通信参数 波特率9600,8位数据位,1位停止位,无校验
* @协议支持 - 0x01 读线圈(Read Coils)
* - 0x03 读保持寄存器(Read Holding Registers)
* - 0x05 写单个线圈(Write Single Coil)
* - 0x06 写单个寄存器(Write Single Register)
* @功能特性 1. 状态机驱动的帧接收处理流程
* 2. CRC16硬件校验算法
* 3. 寄存器地址越界检测与异常响应
* 4. 3.5字符时间间隔的超时检测机制
* @版本信息 V1.0.0(2025-05-20)
* - *
* - *
* @作者 小谦3251
* @联系方式 1093274722@qq.com
*********************************************************************/
#include <STC15F2K60S2.H>
// 类型定义
#define uchar unsigned char
#define uint unsigned int
// Modbus 相关常量定义
#define SLAVE_ADDR 0x01 // 从站地址
#define COIL_COUNT 100 // 线圈数量
#define REGISTER_COUNT 32 // 保持寄存器数量
#define MB_FUNC_READ_COIL 0x01 // 读线圈功能码
#define MB_FUNC_READ_HOLD 0x03 // 读保持寄存器功能码
#define MB_FUNC_WRITE_COIL 0x05 // 写单个线圈功能码
#define MB_FUNC_WRITE_SINGLE 0x06 // 写单个寄存器功能码
#define MB_ERROR_ILLEGAL 0x02 // 非法数据地址错误码
#define MB_ERROR_ILLEGAL_DATA 0x03 // 非法数据值错误码
#define MB_EXCEPTION_OFFSET 0x80 // 异常功能码偏移量
// 串口通信参数
#define MB_SER_PDU_SIZE_MIN 4 // Modbus RTU帧最小长度
#define MB_SER_PDU_SIZE_MAX 128 // Modbus RTU帧最大长度
#define MB_SER_PDU_SIZE_CRC 2 // CRC字段长度
#define MB_SER_PDU_ADDR_OFF 0 // 从站地址偏移量
#define MB_SER_PDU_PDU_OFF 1 // PDU偏移量
// 定时器参数
#define TIMER0_RELOAD_HIGH 0xFD // 定时器0重装值高字节
#define TIMER0_RELOAD_LOW 0xD7 // 定时器0重装值低字节
#define T3_5_TIMEOUT 60 // 3.5个字符时间(约4ms@9600bps)
// 全局变量
uchar idata mb_buffer[MB_SER_PDU_SIZE_MAX] = {0}; // 串口收发缓冲区
uchar rx_index = 0; // 接收缓冲区索引
uchar tx_index = 0; // 发送缓冲区索引
// 状态机枚举
typedef enum {
STATE_IDLE, // 空闲状态
STATE_RECEIVING, // 接收状态
STATE_EXECUTING, // 执行状态
STATE_SENT // 发送状态
} RxState;
typedef enum {
MB_PAR_NONE, /*!< No parity. */
MB_PAR_ODD, /*!< Odd parity. */
MB_PAR_EVEN /*!< Even parity. */
} eMBParity;
RxState rx_state = STATE_IDLE; // 当前接收状态
uint timer_count = 0; // 定时器计数变量
// 保持寄存器数组和线圈数组
uint holding_registers[REGISTER_COUNT] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
uchar coils[COIL_COUNT/8 + 1] = {0}; // 按字节存储线圈状态
/**
* @brief 设置单个线圈状态
* @param coil_addr 线圈地址(0-99)
* @param state 线圈状态(0-关,非0-开)
*/
void set_coil(uint coil_addr, uchar state) {
if(coil_addr < COIL_COUNT) {
uchar byte_idx = coil_addr / 8;
uchar bit_idx = coil_addr % 8;
if(state)
coils[byte_idx] |= (1 << bit_idx);
else
coils[byte_idx] &= ~(1 << bit_idx);
}
}
/**
* @brief 获取单个线圈状态
* @param coil_addr 线圈地址(0-99)
* @return 线圈状态(0-关,1-开)
*/
uchar get_coil(uint coil_addr) {
if(coil_addr < COIL_COUNT) {
uchar byte_idx = coil_addr / 8;
uchar bit_idx = coil_addr % 8;
return (coils[byte_idx] >> bit_idx) & 0x01;
}
return 0;
}
/**
* @brief 串口发送单个字节
* @param dat 要发送的数据
*/
void Uart_SendChar(uchar dat) {
SBUF = dat; // 数据写入发送缓冲区
while (!TI); // 等待发送完成
TI = 0; // 清除发送标志
}
/**
* @brief 串口发送字符串
* @param str 字符串指针
* @param len 发送长度
*/
void Uart_SendString(uchar *str, uint len) {
uint i;
for (i = 0; i < len; i++) {
Uart_SendChar(str[i]);
}
}
/**
* @brief 串口初始化
* @note 波特率9600@11.0592MHz,1T模式
*/
void UART_Init(void) {
SCON = 0x50; // 8位数据,可变波特率
AUXR |= 0x01; // 选择定时器2为波特率发生器
AUXR |= 0x04; // 定时器2时钟为1T模式
T2L = 0xE0; // 波特率9600
T2H = 0xFE;
AUXR |= 0x10; // 启动定时器2
ES = 1; // 使能串口中断
}
/**
* @brief 定时器0初始化
* @note 50微秒@11.0592MHz
*/
void Timer0_Init(void) {
AUXR |= 0x80; // 定时器0 1T模式
TMOD &= 0xF0; // 设置定时器模式
TL0 = TIMER0_RELOAD_LOW; // 设置定时初值
TH0 = TIMER0_RELOAD_HIGH;
TF0 = 0; // 清除标志
TR0 = 0; // 初始停止
ET0 = 0; // 初始禁止中断
}
/**
* @brief 启动定时器0
*/
void Timer0_Start(void) {
TR0 = 1; // 启动定时器
ET0 = 1; // 使能中断
}
/**
* @brief 停止定时器0
*/
void Timer0_Stop(void) {
TR0 = 0; // 停止定时器
ET0 = 0; // 禁止中断
}
/**
* @brief 重设定时器0计数
*/
void Timer0_reloadCount(void) {
timer_count = 0;
TL0 = TIMER0_RELOAD_LOW; // 重装初值
TH0 = TIMER0_RELOAD_HIGH;
}
/**
* @brief CRC16计算
* @param puchMsg 数据指针
* @param usDataLen 数据长度
* @return CRC16校验值
*/
uint CRC16(uchar *puchMsg, uint usDataLen) {
uint wCRCin = 0xFFFF;
uint wCPoly = 0xA001;
uchar i, j;
for (i = 0; i < usDataLen; i++) {
wCRCin ^= puchMsg[i];
for (j = 0; j < 8; j++) {
if (wCRCin & 0x0001)
wCRCin = (wCRCin >> 1) ^ wCPoly;
else
wCRCin >>= 1;
}
}
return wCRCin;
}
/**
* @brief 处理Modbus 01功能码请求(读线圈)
* @param start_addr 起始地址
* @param coil_count 线圈数量
*/
void handle_modbus_01(uint start_addr, uchar coil_count) {
uint crc;
uchar i, byte_count, bit_pos, byte_val;
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR; // 从站地址
mb_buffer[tx_index++] = MB_FUNC_READ_COIL; // 功能码
// 处理正常响应
if (start_addr + coil_count <= COIL_COUNT) {
byte_count = (coil_count + 7) / 8; // 计算需要的字节数
mb_buffer[tx_index++] = byte_count; // 字节数
// 填充线圈数据
for (i = 0; i < byte_count; i++) {
byte_val = 0;
for (bit_pos = 0; bit_pos < 8 && (i*8 + bit_pos) < coil_count; bit_pos++) {
byte_val |= get_coil(start_addr + i*8 + bit_pos) << bit_pos;
}
mb_buffer[tx_index++] = byte_val;
}
}
// 处理异常响应
else {
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR;
mb_buffer[tx_index++] = MB_FUNC_READ_COIL + MB_EXCEPTION_OFFSET; // 异常功能码
mb_buffer[tx_index++] = MB_ERROR_ILLEGAL; // 非法数据地址错误码
}
// 计算并添加CRC
crc = CRC16(mb_buffer, tx_index);
mb_buffer[tx_index++] = (uchar)(crc & 0xFF);
mb_buffer[tx_index++] = (uchar)(crc >> 8);
rx_state = STATE_SENT;
}
/**
* @brief 处理Modbus 03功能码请求(读保持寄存器)
* @param start_addr 起始地址
* @param reg_count 寄存器数量
*/
void handle_modbus_03(uint start_addr, uchar reg_count) {
uint crc;
uchar i;
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR; // 从站地址
mb_buffer[tx_index++] = MB_FUNC_READ_HOLD; // 功能码
// 处理正常响应
if (start_addr + reg_count <= REGISTER_COUNT) {
mb_buffer[tx_index++] = reg_count * 2; // 字节数
// 填充寄存器数据
for (i = 0; i < reg_count; i++) {
mb_buffer[tx_index++] = (uchar)(holding_registers[start_addr + i] >> 8);
mb_buffer[tx_index++] = (uchar)(holding_registers[start_addr + i] & 0xFF);
}
}
// 处理异常响应
else {
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR;
mb_buffer[tx_index++] = MB_FUNC_READ_HOLD + MB_EXCEPTION_OFFSET; // 异常功能码
mb_buffer[tx_index++] = MB_ERROR_ILLEGAL; // 非法数据地址错误码
}
// 计算并添加CRC
crc = CRC16(mb_buffer, tx_index);
mb_buffer[tx_index++] = (uchar)(crc & 0xFF);
mb_buffer[tx_index++] = (uchar)(crc >> 8);
rx_state = STATE_SENT;
}
/**
* @brief 处理Modbus 05功能码请求(写单个线圈)
* @param coil_addr 线圈地址
* @param coil_value 线圈值(0xFF00-开,0x0000-关)
*/
void handle_modbus_05(uint coil_addr, uint coil_value) {
uint crc;
uchar state;
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR; // 从站地址
// 处理正常响应
if (coil_addr < COIL_COUNT) {
// 检查线圈值是否合法
if(coil_value == 0xFF00 || coil_value == 0x0000) {
state = (coil_value == 0xFF00) ? 1 : 0;
set_coil(coil_addr, state); // 更新线圈状态
mb_buffer[tx_index++] = MB_FUNC_WRITE_COIL; // 功能码
mb_buffer[tx_index++] = (uchar)(coil_addr >> 8); // 线圈地址高字节
mb_buffer[tx_index++] = (uchar)(coil_addr & 0xFF); // 线圈地址低字节
mb_buffer[tx_index++] = (uchar)(coil_value >> 8); // 线圈值高字节
mb_buffer[tx_index++] = (uchar)(coil_value & 0xFF); // 线圈值低字节
} else {
mb_buffer[tx_index++] = MB_FUNC_WRITE_COIL + MB_EXCEPTION_OFFSET; // 异常功能码
mb_buffer[tx_index++] = MB_ERROR_ILLEGAL_DATA; // 非法数据值错误码
}
}
// 处理异常响应
else {
mb_buffer[tx_index++] = MB_FUNC_WRITE_COIL + MB_EXCEPTION_OFFSET; // 异常功能码
mb_buffer[tx_index++] = MB_ERROR_ILLEGAL; // 非法数据地址错误码
}
// 计算并添加CRC
crc = CRC16(mb_buffer, tx_index);
mb_buffer[tx_index++] = (uchar)(crc & 0xFF);
mb_buffer[tx_index++] = (uchar)(crc >> 8);
rx_state = STATE_SENT;
}
/**
* @brief 处理Modbus 06功能码请求(写单个寄存器)
* @param reg_addr 寄存器地址
* @param reg_value 寄存器值
*/
void handle_modbus_06(uint reg_addr, uint reg_value) {
uint crc;
tx_index = 0;
mb_buffer[tx_index++] = SLAVE_ADDR; // 从站地址
// 处理正常响应
if (reg_addr < REGISTER_COUNT) {
holding_registers[reg_addr] = reg_value; // 更新寄存器值
mb_buffer[tx_index++] = MB_FUNC_WRITE_SINGLE; // 功能码
mb_buffer[tx_index++] = (uchar)(reg_addr >> 8); // 寄存器地址高字节
mb_buffer[tx_index++] = (uchar)(reg_addr & 0xFF); // 寄存器地址低字节
mb_buffer[tx_index++] = (uchar)(reg_value >> 8); // 寄存器值高字节
mb_buffer[tx_index++] = (uchar)(reg_value & 0xFF); // 寄存器值低字节
}
// 处理异常响应
else {
mb_buffer[tx_index++] = MB_FUNC_WRITE_SINGLE + MB_EXCEPTION_OFFSET; // 异常功能码
mb_buffer[tx_index++] = MB_ERROR_ILLEGAL; // 非法数据地址错误码
}
// 计算并添加CRC
crc = CRC16(mb_buffer, tx_index);
mb_buffer[tx_index++] = (uchar)(crc & 0xFF);
mb_buffer[tx_index++] = (uchar)(crc >> 8);
rx_state = STATE_SENT;
}
/**
* @brief Modbus轮询处理
*/
void eMBPoll() {
uchar addr, func;
uint start_addr, reg_count,reg_value, crc_received, crc_calculated;
switch (rx_state) {
case STATE_EXECUTING:
// 检查最小帧长度
if (rx_index >= MB_SER_PDU_SIZE_MIN) {
addr = mb_buffer[MB_SER_PDU_ADDR_OFF]; // 从站地址
func = mb_buffer[MB_SER_PDU_PDU_OFF]; // 功能码
// 校验CRC
crc_received = (mb_buffer[rx_index - 1] << 8) | mb_buffer[rx_index - 2];
crc_calculated = CRC16(mb_buffer, rx_index - MB_SER_PDU_SIZE_CRC);
// 处理有效请求
if (addr == SLAVE_ADDR && crc_received == crc_calculated) {
switch (func) {
case MB_FUNC_READ_COIL: // 01功能码
start_addr = (mb_buffer[2] << 8) | mb_buffer[3];
reg_count = (mb_buffer[4] << 8) | mb_buffer[5];
handle_modbus_01(start_addr, (uchar)reg_count);
break;
case MB_FUNC_READ_HOLD: // 03功能码
start_addr = (mb_buffer[2] << 8) | mb_buffer[3];
reg_count = (mb_buffer[4] << 8) | mb_buffer[5];
handle_modbus_03(start_addr, (uchar)reg_count);
break;
case MB_FUNC_WRITE_COIL: // 05功能码
start_addr = (mb_buffer[2] << 8) | mb_buffer[3]; // 线圈地址
reg_value = (mb_buffer[4] << 8) | mb_buffer[5]; // 线圈值
handle_modbus_05(start_addr, reg_value);
break;
case MB_FUNC_WRITE_SINGLE: // 06功能码
start_addr = (mb_buffer[2] << 8) | mb_buffer[3]; // 寄存器地址
reg_value = (mb_buffer[4] << 8) | mb_buffer[5]; // 寄存器值
handle_modbus_06(start_addr, reg_value);
break;
default: // 不支持的功能码
rx_index = 0;
rx_state = STATE_IDLE;
break;
}
} else {
rx_index = 0;
rx_state = STATE_IDLE;
}
} else {
rx_index = 0;
rx_state = STATE_IDLE;
}
break;
case STATE_SENT:
// 发送响应帧
Uart_SendString(mb_buffer, tx_index);
rx_state = STATE_IDLE;
break;
case STATE_IDLE:
ES = 1; // 使能串口中断
break;
}
}
/**
* @brief 定时器0中断服务函数
*/
void Timer0_Isr(void) interrupt 1 {
TL0 = TIMER0_RELOAD_LOW; // 重装初值
TH0 = TIMER0_RELOAD_HIGH;
if (rx_state == STATE_RECEIVING) {
if (++timer_count >= T3_5_TIMEOUT) {
// 超时处理
mb_buffer[rx_index] = '\0';
rx_state = STATE_EXECUTING;
Timer0_Stop();
Timer0_reloadCount();
ES = 0; // 禁止串口中断
}
}
}
/**
* @brief 串口接收中断函数
*/
void INT_UartRcv(void) interrupt 4 {
uchar Rcv;
if (RI) {
Rcv = SBUF;
RI = 0; // 清除接收标志
switch (rx_state) {
case STATE_IDLE:
// 开始接收新帧
rx_state = STATE_RECEIVING;
rx_index = 0;
Timer0_reloadCount();
mb_buffer[rx_index++] = Rcv;
Timer0_Start();
break;
case STATE_RECEIVING:
// 继续接收数据
Timer0_reloadCount();
if (rx_index < MB_SER_PDU_SIZE_MAX - 1) {
mb_buffer[rx_index++] = Rcv;
}
break;
}
}
}
/**
* @brief 主函数
*/
void main(void) {
UART_Init(); // 初始化串口
Timer0_Init(); // 初始化定时器
EA = 1; // 允许总中断
// 初始化部分线圈状态
set_coil(0, 1); // 线圈0置为ON
set_coil(1, 1); // 线圈1置为ON
set_coil(2, 0); // 线圈2置为OFF
while (1) {
eMBPoll(); // 主循环处理Modbus协议
}
}
九、总结
本文通过STC15F2K60S2单片机实现了Modbus RTU从机的核心功能,代码结构清晰,包含完整的状态机管理、CRC校验和功能码处理逻辑。测试49万多次100ms查询频率未出错

4645

被折叠的 条评论
为什么被折叠?



