基于STC89C52的Modbus RTU从机完整工程实现

基于STC89C52的Modbus RTU从机完整工程实现

一、工程概述

本工程基于STC89C52单片机,实现Modbus RTU从机功能,支持01(读线圈)、02(读离散输入)、03(读保持寄存器)、04(读输入寄存器)、05(写单个线圈)、06(写单个寄存器)等常用功能码。工程采用Keil C51开发,包含串口通信、Modbus协议解析、CRC校验、定时器超时处理等核心模块,可直接用于工业自动化、智能设备等场景的Modbus从机开发。

二、工程文件结构

Modbus_RTU_Slave_STC89C52/
├── Core/                // 核心代码
│   ├── inc/             // 头文件
│   │   ├── modbus.h     // Modbus协议定义
│   │   ├── uart.h       // 串口驱动头文件
│   │   └── timer.h      // 定时器驱动头文件
│   └── src/             // 源文件
│       ├── main.c       // 主函数
│       ├── modbus.c     // Modbus协议解析
│       ├── uart.c       // 串口通信实现
│       └── timer.c      // 定时器超时处理
├── Drivers/             // 驱动代码(如MAX485)
│   └── max485.c        // MAX485收发控制
├── Project/             // 工程文件(Keil)
│   └── Modbus_Slave.uvproj
└── Docs/                // 文档
    └── 使用说明.txt     // 工程使用指南

三、核心代码实现

1. 串口驱动(uart.c)

实现STC89C52的UART初始化(波特率9600,8位数据位,1位停止位,无校验),以及串口中断接收/发送。

#include "uart.h"
#include "modbus.h"

#define BAUD_RATE 9600
#define CRYSTAL_FREQ 11059200

void UART_Init(void) {
    SCON = 0x50;          // 8位数据位,1位停止位,允许接收
    TMOD |= 0x20;         // 定时器1工作在模式2(自动重载)
    TH1 = 256 - (CRYSTAL_FREQ / (12 * 32 * BAUD_RATE));  // 计算波特率重装值
    TL1 = TH1;
    TR1 = 1;              // 启动定时器1
    ES = 1;               // 使能串口中断
    EA = 1;               // 使能总中断
}

void UART_SendByte(uint8_t dat) {
    SBUF = dat;
    while (!TI);          // 等待发送完成
    TI = 0;
}

void UART_SendString(uint8_t *str) {
    while (*str) {
        UART_SendByte(*str++);
    }
}

// 串口中断服务函数
void UART_ISR(void) interrupt 4 {
    static uint8_t rx_buf[MODBUS_FRAME_MAX_LEN];
    static uint8_t rx_len = 0;
    static uint32_t last_rx_time = 0;

    if (RI) {
        RI = 0;
        rx_buf[rx_len++] = SBUF;
        last_rx_time = Timer_GetTick();  // 记录最后接收时间
    }

    // 超时处理(3.5字符时间,约4ms@9600bps)
    if (rx_len > 0 && (Timer_GetTick() - last_rx_time) > 4) {
        if (Modbus_VerifyFrame(rx_buf, rx_len)) {
            Modbus_ProcessFrame(rx_buf, rx_len);  // 处理Modbus帧
        }
        rx_len = 0;  // 清空接收缓冲区
    }
}
2. Modbus协议解析(modbus.c)

实现Modbus RTU帧的CRC校验功能码处理(如读保持寄存器、写单个线圈)。

#include "modbus.h"
#include "uart.h"
#include "timer.h"

// Modbus寄存器定义(示例:保持寄存器、线圈)
uint16_t holding_registers[MODBUS_HOLDING_REG_NUM] = {0};
uint8_t coil_registers[MODBUS_COIL_REG_NUM] = {0};

// CRC16校验(Modbus RTU标准)
uint16_t Modbus_CRC16(uint8_t *data, uint8_t len) {
    uint16_t crc = 0xFFFF;
    for (uint8_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

// 验证Modbus帧(地址、CRC)
uint8_t Modbus_VerifyFrame(uint8_t *frame, uint8_t len) {
    if (len < 4) return 0;  // 帧长不足(地址+功能码+CRC)
    if (frame[0] != MODBUS_SLAVE_ADDR) return 0;  // 地址不匹配
    uint16_t crc = Modbus_CRC16(frame, len - 2);
    if ((crc & 0xFF) != frame[len - 1] || (crc >> 8) != frame[len - 2]) {
        return 0;  // CRC校验失败
    }
    return 1;
}

// 处理Modbus功能码
void Modbus_ProcessFrame(uint8_t *frame, uint8_t len) {
    uint8_t func_code = frame[1];
    switch (func_code) {
        case MODBUS_FUNC_READ_HOLDING_REG:  // 03 读保持寄存器
            Modbus_HandleReadHoldingReg(frame, len);
            break;
        case MODBUS_FUNC_WRITE_SINGLE_COIL:  // 05 写单个线圈
            Modbus_HandleWriteSingleCoil(frame, len);
            break;
        // 其他功能码(如01、02、04、06)可扩展
        default:
            Modbus_SendException(func_code, MODBUS_EXCEPT_ILLEGAL_FUNC);
            break;
    }
}

// 处理03功能码(读保持寄存器)
void Modbus_HandleReadHoldingReg(uint8_t *frame, uint8_t len) {
    uint16_t start_addr = (frame[2] << 8) | frame[3];
    uint16_t reg_count = (frame[4] << 8) | frame[5];
    if (start_addr + reg_count > MODBUS_HOLDING_REG_NUM) {
        Modbus_SendException(MODBUS_FUNC_READ_HOLDING_REG, MODBUS_EXCEPT_ILLEGAL_ADDR);
        return;
    }
    // 构造响应帧(地址+功能码+数据长度+数据+CRC)
    uint8_t resp[256];
    resp[0] = MODBUS_SLAVE_ADDR;
    resp[1] = MODBUS_FUNC_READ_HOLDING_REG;
    resp[2] = reg_count * 2;  // 数据长度(每个寄存器2字节)
    for (uint8_t i = 0; i < reg_count; i++) {
        resp[3 + 2*i] = (holding_registers[start_addr + i] >> 8) & 0xFF;  // 高字节
        resp[4 + 2*i] = holding_registers[start_addr + i] & 0xFF;         // 低字节
    }
    uint16_t crc = Modbus_CRC16(resp, 3 + reg_count * 2);
    resp[3 + reg_count * 2] = crc & 0xFF;
    resp[4 + reg_count * 2] = (crc >> 8) & 0xFF;
    UART_SendString(resp);  // 发送响应
}

// 处理05功能码(写单个线圈)
void Modbus_HandleWriteSingleCoil(uint8_t *frame, uint8_t len) {
    uint16_t coil_addr = (frame[2] << 8) | frame[3];
    uint16_t coil_value = (frame[4] << 8) | frame[5];
    if (coil_addr >= MODBUS_COIL_REG_NUM) {
        Modbus_SendException(MODBUS_FUNC_WRITE_SINGLE_COIL, MODBUS_EXCEPT_ILLEGAL_ADDR);
        return;
    }
    // 设置线圈状态(0xFF00=ON,0x0000=OFF)
    coil_registers[coil_addr] = (coil_value == 0xFF00) ? 1 : 0;
    // 构造响应( echo 原帧)
    UART_SendString(frame);
}
3. 主函数(main.c)

初始化串口、定时器,进入主循环。

#include "uart.h"
#include "timer.h"
#include "modbus.h"

void main(void) {
    UART_Init();  // 初始化串口
    Timer_Init();  // 初始化定时器(1ms中断)
    while (1) {
        // 主循环(可添加其他任务,如数据采集)
    }
}

参考代码 modbus slave C51 STC89C52 完整工程 www.youwenfan.com/contentcnr/103031.html

四、硬件设计

五、使用说明

  1. 编译工程:使用Keil C51打开Project/Modbus_Slave.uvproj,编译生成Modbus_Slave.hex
  2. 下载程序:使用STC-ISP工具将Modbus_Slave.hex下载到STC89C52单片机。
  3. 测试:使用Modbus主站工具(如Modbus Poll)连接从机(地址0x01,波特率9600),发送读保持寄存器(03功能码)或写单个线圈(05功能码)命令,验证从机响应。

六、扩展功能

七、注意事项

 

专注于matlab/simulink,电子电路,编程