51单片机实现Modbus RTU从机协议开发

该文章已生成可运行项目,

请添加图片描述

在这里插入图片描述

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; // 低字节在前,高字节在后
}

校验流程

  1. 对接收到的帧数据(除CRC字段)计算CRC值
  2. 与帧末尾的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 常见问题

  1. 通信失败

    • 检查波特率、校验位是否与主站一致
    • 确认从站地址(SLAVE_ADDR=0x01)匹配
    • 验证CRC校验顺序(低字节在前)
  2. 寄存器读写异常

    • 确保地址范围在0x0000~0x001F(32个寄存器)
    • 检查数据字节顺序(高字节在前)

七、扩展与优化方向

  1. 功能扩展
    • 添加写多个寄存器(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查询频率未出错

在这里插入图片描述

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值