实现51单片机和485设备通信的Modbus协议源码

付费 ¥4.99付费 ¥4.99

该内容已接入现有会员/付费系统,解锁后可查看完整资料或下载附件。

查看解锁方式
实现51单片机和485设备通信的Modbus协议源码缩略图
本文目录
  1. 1. 一、系统架构
  2. 2. 二、Modbus RTU协议帧格式
  3. 3. 三、完整C语言源码
  4. 4. 四、使用说明
  5. 5. 五、常见问题
  6. 6. 实现51单片机和485设备通信的Modbus协议源码¥4.99

51单片机是一种经典的8位嵌入式微控制器,因其价格低廉、开发简单而被广泛应用于工业控制、智能家居、仪器仪表等领域。RS485总线是工业现场最常用的串行通讯标准之一,而Modbus RTU协议则是RS485总线上最主流的应用层协议。本文提供一套完整的51单片机实现Modbus RTU从站通讯的C语言源码,支持线圈读写、寄存器读写等标准功能码。

一、系统架构

本方案以STC89C52RC(或兼容的AT89S52)为主控芯片,通过MAX485芯片实现RS485电平转换。51单片机作为Modbus从站,响应主站(PLC、触摸屏、上位机等)的读写请求。

1.1 硬件连接

51单片机引脚MAX485引脚说明
P3.0 (RXD)RO (1脚)接收数据
P3.1 (TXD)DI (4脚)发送数据
P2.0 (或其他IO)DE/RE (2,3脚)收发控制:高=发送,低=接收
VCC (5V)VCC (8脚)电源
GNDGND (5脚)地
–A (6脚)RS485 A线
–B (7脚)RS485 B线

1.2 资源分配

  • 串口:使用UART,模式1(8位数据,可变波特率)
  • 定时器:T1作为波特率发生器,9600bps @ 11.0592MHz晶振
  • 中断:串口中断接收数据,定时器中断处理超时
  • 内存:线圈状态使用位寻址区,寄存器使用内部RAM

二、Modbus RTU协议帧格式

Modbus RTU帧由以下字段组成:

字段长度说明
从站地址1字节1-247,0为广播地址
功能码1字节01/02/03/05/06/15/16等
数据N字节地址、数量、数据值等
CRC校验2字节低字节在前,高字节在后

三、完整C语言源码

3.1 头文件定义(modbus.h)

#ifndef __MODBUS_H
#define __MODBUS_H

#include <reg52.h>

// 基本类型定义
typedef unsigned char  uint8;
typedef unsigned int   uint16;
typedef unsigned long  uint32;

// IO定义
sbit RS485_DIR = P2^0;   // RS485收发控制

// Modbus参数配置
#define MODBUS_ADDR     1       // 从站地址
#define MODBUS_BAUD     9600    // 波特率
#define RX_BUF_SIZE     64      // 接收缓冲区大小
#define TX_BUF_SIZE     64      // 发送缓冲区大小
#define TIMEOUT_MS      50      // 帧超时时间(ms)

// 功能码定义
#define FUNC_READ_COIL      0x01    // 读线圈
#define FUNC_READ_INPUT     0x02    // 读离散输入
#define FUNC_READ_REG       0x03    // 读保持寄存器
#define FUNC_READ_INPUT_REG 0x04    // 读输入寄存器
#define FUNC_WRITE_COIL     0x05    // 写单线圈
#define FUNC_WRITE_REG      0x06    // 写单寄存器
#define FUNC_WRITE_COILS    0x0F    // 写多线圈
#define FUNC_WRITE_REGS     0x10    // 写多寄存器

// 异常码
#define EXCEPT_ILLEGAL_FUNC  0x01
#define EXCEPT_ILLEGAL_ADDR  0x02
#define EXCEPT_ILLEGAL_VAL   0x03
#define EXCEPT_SLAVE_FAIL    0x04

// 全局变量
extern uint8 rx_buf[RX_BUF_SIZE];
extern uint8 tx_buf[TX_BUF_SIZE];
extern uint8 rx_len;
extern uint8 tx_len;
extern bit   rx_done;
extern bit   tx_done;

// 函数声明
void modbus_init(void);
void modbus_task(void);
uint16 modbus_crc16(uint8 *buf, uint8 len);

#endif

3.2 主程序(main.c)

#include "modbus.h"

// 全局变量定义
uint8 rx_buf[RX_BUF_SIZE];
uint8 tx_buf[TX_BUF_SIZE];
uint8 rx_len = 0;
uint8 tx_len = 0;
bit   rx_done = 0;
bit   tx_done = 1;

// 线圈状态(位寻址区,最多16路)
// 20H-2FH为位寻址区,共128位
// 线圈0-7对应20H.0-20H.7
// 线圈8-15对应21H.0-21H.7
uint8 coil_buf[2] = {0x00, 0x00};

// 离散输入状态
uint8 input_buf[2] = {0x00, 0x00};

// 保持寄存器(最多10个)
uint16 hold_reg[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

// 输入寄存器
uint16 input_reg[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

// 毫秒计数器
uint16 ms_counter = 0;
uint16 frame_timer = 0;

void main(void)
{
    modbus_init();
    
    while(1)
    {
        if(rx_done)
        {
            modbus_task();
            rx_done = 0;
            rx_len = 0;
        }
        
        // 在这里添加用户应用代码
        // 例如:读取传感器数据更新到input_reg
        // input_reg[0] = read_temperature();
    }
}

// 串口初始化
void modbus_init(void)
{
    TMOD |= 0x20;   // T1模式2,8位自动重装
    TH1 = 0xFD;     // 9600bps @ 11.0592MHz
    TL1 = 0xFD;
    TR1 = 1;        // 启动T1
    
    SCON = 0x50;    // 串口模式1,允许接收
    PCON = 0x00;    // SMOD=0
    
    ES = 1;         // 使能串口中断
    EA = 1;         // 开总中断
    
    RS485_DIR = 0;  // 默认接收模式
}

// 串口中断服务程序
void uart_isr(void) interrupt 4
{
    if(RI)  // 接收中断
    {
        RI = 0;
        if(rx_len < RX_BUF_SIZE)
        {
            rx_buf[rx_len++] = SBUF;
            frame_timer = 0;  // 重置帧超时计时器
        }
    }
    
    if(TI)  // 发送中断
    {
        static uint8 tx_idx = 0;
        TI = 0;
        tx_idx++;
        if(tx_idx < tx_len)
        {
            SBUF = tx_buf[tx_idx];
        }
        else
        {
            tx_idx = 0;
            RS485_DIR = 0;  // 切换回接收模式
            tx_done = 1;
        }
    }
}

// 定时器0中断(1ms @ 11.0592MHz)
void timer0_isr(void) interrupt 1
{
    TH0 = 0xFC;
    TL0 = 0x67;
    ms_counter++;
    
    // 帧超时检测
    if(rx_len > 0 && !rx_done)
    {
        frame_timer++;
        if(frame_timer >= TIMEOUT_MS)
        {
            rx_done = 1;  // 帧接收完成
        }
    }
}

3.3 Modbus协议处理(modbus.c)

#include "modbus.h"

// 外部变量声明
extern uint8 coil_buf[2];
extern uint8 input_buf[2];
extern uint16 hold_reg[10];
extern uint16 input_reg[10];

// CRC16校验表(Modbus多项式0xA001)
const uint16 code crc_table[256] = {
    0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,
    0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,
    0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40,
    0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841,
    0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,
    0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,
    0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,
    0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,
    0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,
    0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441,
    0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,
    0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,
    0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,
    0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40,
    0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,
    0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,
    0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,
    0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441,
    0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,
    0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,
    0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,
    0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40,
    0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,
    0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,
    0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,
    0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440,
    0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,
    0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,
    0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,
    0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,
    0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,
    0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040
};

// CRC16计算
uint16 modbus_crc16(uint8 *buf, uint8 len)
{
    uint16 crc = 0xFFFF;
    uint8 i;
    
    while(len--)
    {
        i = crc ^ *buf++;
        crc >>= 8;
        crc ^= crc_table[i];
    }
    return crc;
}

// 发送响应帧
void modbus_send(uint8 len)
{
    uint16 crc;
    
    // 添加CRC校验
    crc = modbus_crc16(tx_buf, len);
    tx_buf[len++] = crc & 0xFF;       // 低字节
    tx_buf[len++] = (crc >> 8) & 0xFF; // 高字节
    
    tx_len = len;
    tx_done = 0;
    
    RS485_DIR = 1;  // 切换到发送模式
    
    // 发送第一个字节触发中断
    SBUF = tx_buf[0];
}

// 异常响应
void modbus_exception(uint8 func, uint8 code)
{
    tx_buf[0] = MODBUS_ADDR;
    tx_buf[1] = func | 0x80;
    tx_buf[2] = code;
    modbus_send(3);
}

// Modbus主处理函数
void modbus_task(void)
{
    uint8 addr, func;
    uint16 reg_addr, reg_num;
    uint16 crc_recv, crc_calc;
    uint8 i, j, idx;
    
    // 检查帧长度
    if(rx_len < 4) return;
    
    // 检查从站地址
    addr = rx_buf[0];
    if(addr != MODBUS_ADDR && addr != 0) return;
    
    // CRC校验
    crc_recv = rx_buf[rx_len-2] | (rx_buf[rx_len-1] << 8);
    crc_calc = modbus_crc16(rx_buf, rx_len-2);
    if(crc_recv != crc_calc) return;
    
    func = rx_buf[1];
    
    switch(func)
    {
        case FUNC_READ_COIL:  // 读线圈 0x01
        {
            reg_addr = (rx_buf[2]<<8) | rx_buf[3];
            reg_num  = (rx_buf[4]<<8) | rx_buf[5];
            
            if(reg_addr >= 16 || reg_addr + reg_num > 16)
            {
                modbus_exception(func, EXCEPT_ILLEGAL_ADDR);
                return;
            }
            
            tx_buf[0] = MODBUS_ADDR;
            tx_buf[1] = func;
            tx_buf[2] = (reg_num + 7) / 8;
            
            for(i = 0; i < tx_buf[2]; i++)
            {
                tx_buf[3+i] = 0;
                for(j = 0; j < 8; j++)
                {
                    idx = reg_addr + i*8 + j;
                    if(idx < reg_addr + reg_num)
                    {
                        if(coil_buf[idx/8] & (1 << (idx%8)))
                            tx_buf[3+i] |= (1 << j);
                    }
                }
            }
            modbus_send(3 + tx_buf[2]);
            break;
        }
        
        case FUNC_READ_REG:  // 读保持寄存器 0x03
        {
            reg_addr = (rx_buf[2]<<8) | rx_buf[3];
            reg_num  = (rx_buf[4]<<8) | rx_buf[5];
            
            if(reg_addr >= 10 || reg_addr + reg_num > 10)
            {
                modbus_exception(func, EXCEPT_ILLEGAL_ADDR);
                return;
            }
            
            tx_buf[0] = MODBUS_ADDR;
            tx_buf[1] = func;
            tx_buf[2] = reg_num * 2;
            
            for(i = 0; i < reg_num; i++)
            {
                tx_buf[3 + i*2]     = (hold_reg[reg_addr+i] >> 8) & 0xFF;
                tx_buf[3 + i*2 + 1] = hold_reg[reg_addr+i] & 0xFF;
            }
            modbus_send(3 + reg_num*2);
            break;
        }
        
        case FUNC_WRITE_COIL:  // 写单线圈 0x05
        {
            reg_addr = (rx_buf[2]<<8) | rx_buf[3];
            reg_num  = (rx_buf[4]<<8) | rx_buf[5];
            
            if(reg_addr >= 16)
            {
                modbus_exception(func, EXCEPT_ILLEGAL_ADDR);
                return;
            }
            
            if(reg_num == 0xFF00)
                coil_buf[reg_addr/8] |= (1 << (reg_addr%8));
            else if(reg_num == 0x0000)
                coil_buf[reg_addr/8] &= ~(1 << (reg_addr%8));
            else
            {
                modbus_exception(func, EXCEPT_ILLEGAL_VAL);
                return;
            }
            
            // 回显请求
            for(i = 0; i < 6; i++) tx_buf[i] = rx_buf[i];
            modbus_send(6);
            break;
        }
        
        case FUNC_WRITE_REG:  // 写单寄存器 0x06
        {
            reg_addr = (rx_buf[2]<<8) | rx_buf[3];
            reg_num  = (rx_buf[4]<<8) | rx_buf[5];
            
            if(reg_addr >= 10)
            {
                modbus_exception(func, EXCEPT_ILLEGAL_ADDR);
                return;
            }
            
            hold_reg[reg_addr] = reg_num;
            
            for(i = 0; i < 6; i++) tx_buf[i] = rx_buf[i];
            modbus_send(6);
            break;
        }
        
        case FUNC_WRITE_COILS:  // 写多线圈 0x0F
        {
            reg_addr = (rx_buf[2]<<8) | rx_buf[3];
            reg_num  = (rx_buf[4]<<8) | rx_buf[5];
            
            if(reg_addr >= 16 || reg_addr + reg_num > 16)
            {
                modbus_exception(func, EXCEPT_ILLEGAL_ADDR);
                return;
            }
            
            for(i = 0; i < reg_num; i++)
            {
                idx = reg_addr + i;
                if(rx_buf[7 + i/8] & (1 << (i%8)))
                    coil_buf[idx/8] |= (1 << (idx%8));
                else
                    coil_buf[idx/8] &= ~(1 << (idx%8));
            }
            
            tx_buf[0] = MODBUS_ADDR;
            tx_buf[1] = func;
            tx_buf[2] = rx_buf[2];
            tx_buf[3] = rx_buf[3];
            tx_buf[4] = rx_buf[4];
            tx_buf[5] = rx_buf[5];
            modbus_send(6);
            break;
        }
        
        case FUNC_WRITE_REGS:  // 写多寄存器 0x10
        {
            reg_addr = (rx_buf[2]<<8) | rx_buf[3];
            reg_num  = (rx_buf[4]<<8) | rx_buf[5];
            
            if(reg_addr >= 10 || reg_addr + reg_num > 10)
            {
                modbus_exception(func, EXCEPT_ILLEGAL_ADDR);
                return;
            }
            
            for(i = 0; i < reg_num; i++)
            {
                hold_reg[reg_addr+i] = (rx_buf[7+i*2]<<8) | rx_buf[7+i*2+1];
            }
            
            tx_buf[0] = MODBUS_ADDR;
            tx_buf[1] = func;
            tx_buf[2] = rx_buf[2];
            tx_buf[3] = rx_buf[3];
            tx_buf[4] = rx_buf[4];
            tx_buf[5] = rx_buf[5];
            modbus_send(6);
            break;
        }
        
        default:
            modbus_exception(func, EXCEPT_ILLEGAL_FUNC);
            break;
    }
}

四、使用说明

4.1 编译环境

  • 推荐使用Keil uVision4或uVision5进行编译
  • 晶振频率:11.0592MHz(必须使用此频率才能保证9600bps准确)
  • 单片机型号:STC89C52RC / AT89S52 / STC12C5A60S2等兼容型号
  • 代码空间:约4KB,RAM占用约150字节

4.2 测试方法

  • 使用USB转RS485转换器连接电脑和模块
  • 打开Modbus Poll或Modbus Slave调试软件
  • 设置:从站地址1、波特率9600、数据位8、停止位1、无校验
  • 功能码03读取保持寄存器,地址0开始,读取10个
  • 功能码06写单寄存器,修改hold_reg的值
  • 功能码01读线圈,查看coil_buf的状态
  • 功能码05写单线圈,控制继电器吸合/断开

4.3 自定义修改

  • 修改从站地址:改变MODBUS_ADDR宏定义
  • 修改波特率:修改TH1/TL1初值(9600=0xFD,4800=0xFA,19200=0xFD+SMOD)
  • 增加线圈数量:修改coil_buf数组大小
  • 增加寄存器数量:修改hold_reg数组大小
  • 添加应用逻辑:在main函数的while循环中添加用户代码

五、常见问题

  • 通讯无响应:检查A/B线是否接反,从站地址是否一致,波特率是否匹配
  • CRC校验错误:确认晶振为11.0592MHz,检查串口配置
  • 数据乱码:检查波特率设置,确认SMOD位配置正确
  • 只能接收不能发送:检查RS485_DIR引脚控制是否正确,DE/RE是否接在一起
  • 多从站冲突:确保每个从站地址唯一,总线两端接120Ω终端电阻

实现51单片机和485设备通信的Modbus协议源码¥4.99

实现51单片机和485设备通信的Modbus协议源码插图星耀会员¥3.99
实现51单片机和485设备通信的Modbus协议源码插图1璀璨会员¥2.99
曜钻会员免费
已付费?登录 或 刷新
技术术语(共 11 个)—— 点击展开
Modbus RTU基于串行链路的Modbus协议,使用二进制编码和CRC校验
RS485工业常用的差分串行通信标准,支持多点通信
功能码Modbus功能码指定读/写操作类型,如01读线圈、03读保持寄存器
寄存器Modbus 寄存器存储数据单元,分线圈/离散输入/保持/输入寄存器四类
CRC校验循环冗余校验,用于检测数据传输中的错误
PLC可编程逻辑控制器,工业自动化控制的核心设备
波特率串行通信每秒传输符号数,Modbus RTU常用9600/19200
串口计算机与外部设备进行串行通信的物理接口
传感器将物理量转换为电信号的检测装置
线圈Modbus位可读写数据,地址从00001开始
保持寄存器Modbus 16位可读写数据,地址从40001开始
来源/工具信息 —— 点击展开
来源 Modbus中文网(modbus.cn) —— 国内领先的Modbus通信协议技术社区 分类 Modbus编程开发 字数 8977 字 · 阅读约 23 分钟 更新 2026-08-19 永久链接 https://www.modbus.cn/11864.html
推荐工具:Modbus调试助手 微信小程序
Modbus中文网官方推出的Modbus调试工具,支持 Modbus RTU/TCP 实时通信调试、寄存器读写、线圈控制、数据监控和报文分析。 无需安装,微信搜索「Modbus调试助手」即可使用。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
📝 作者声明
本文由 Modbus中文网技术团队 原创撰写,内容基于实际项目案例与技术文档,力求为读者提供准确、实用的参考信息。
把这篇资料用于真实项目?

进入工具中心进行报文解析、CRC 校验和设备调试,或提交需求获取选型与接入建议。

工程师会员

把这篇文章变成可执行的调试资料

开通后可使用高级报文解析、资料包下载、代码示例、工程案例和优先技术支持,适合真实项目交付。

高级工具不限次
资料包与代码包
完整工程案例库
优先技术支持入口

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注