51 MCU 와 485 장치 통신을 위한 Modbus 프로토콜 소스 코드 구현

Paid ¥4.99Paid ¥4.99

该内容已接入现有会員/有料系统,解锁后可完全な情報を見る或添付ファイルのダウンロード。

表示解锁方式
51 MCU 와 485 장치 통신을 위한 Modbus 프로토콜 소스 코드 구현

51单片机是一种经典的8位エンベデッド·イン微コントローラーは,因其价格低廉、开发简单而被广泛应用于工业控制、Smart home、仪器仪表等领域。RS485バス是工业现场最もよく使われる串行通讯標準之一,而Modbus RTU协议则是RS485バス上最主流的アプリケーション層プロトコル。本記事提供一套完全的51单片机実装Modbus RTUスレーブ通讯的C语言源码,サポートコイル読み書き、レジスタ読み書き等標準機能コード。

一、系统架构

本方案以STC89C52RC(或兼容的AT89S52)为主控芯片,経由MAX485芯片実装RS485电平转换。51单片机作为Modbusslave,レスポンスマスター(PLC、タッチパネル、トップマシン等)的読み書きリクエスト。

1.1 硬件连接

51单片机引脚MAX485引脚説明
P3.0 (RXD)RO (1脚)受信データ
P3.1 (TXD)DI (4脚)送信データ
P2.0 (または他のIO)DE/RE (2,3脚)收发控制:高=send,低=receive
VCC (5V)VCC (8脚)电源
GNDGND (5脚)
-A (6脚)RS485 A线
-B (7脚)RS485 B线

1.2 リソース分配

  • serial port:使用UART,モード1(8ビット·データ,可变ボーレート)
  • タイマータイマー。:T1作为ボーレート发生器,9600bps @ 11.0592MHz晶振
  • 中断:シリアルポート中断受信データ,タイマータイマー。中断処理タイムアウト
  • 内存:コイルの状態使用位寻址区,レジスタ使用内部RAM

二、Modbus RTU协议フレーム形式

Modbus RTUフレーム由以下字段组成:

fieldLength説明
slave address1バイト1-247,0ブロードキャストアドレス
function code1バイト01/02/03/05/06/15/16等
dataNbyteaddress、Quantity、データ值等
CRC check2バイト低バイト数前に,ハイバイト在后

三、completeC语言源码

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       // slave address
#define MODBUS_BAUD     9600    // Baud rate
#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    // read discrete inputs
#define FUNC_READ_REG       0x03    // read holding registers
#define FUNC_READ_INPUT_REG 0x04    // read input registers
#define FUNC_WRITE_COIL     0x05    // シングルコイルを書く。
#define FUNC_WRITE_REG      0x06    // 単一レジスタの書き込み
#define FUNC_WRITE_COILS    0x0F    // マルチコイルを書く
#define FUNC_WRITE_REGS     0x10    // マルチレジスタの書き込み

// Exception Code
#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;

// Coil status(位寻址区,最多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 check
    crc = modbus_crc16(tx_buf, len);
    tx_buf[len++] = crc & 0xFF;       // 低バイト数
    tx_buf[len++] = (crc >> 8) & 0xFF; // high byte
    
    tx_len = len;
    tx_done = 0;
    
    RS485_DIR = 1;  // 切换到送信モード
    
    // 送信第一バイト単位。触发中断
    SBUF = tx_buf[0];
}

// Exception Response
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 check
    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:  // read holding registers 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;
    }
}

四、Instructions for Use

4.1 编译环境

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

4.2 测试方法

  • 使用USB转RS485変換器です。连接电脑和モジュール
  • openModbus Poll或Modbus Slaveデバッグ软件
  • 設定:slave address1、Baud rate9600、data bit8、stop bit1、検証なし。
  • function code03リードホールドレジスタ,address0开始,read10个
  • function code06単一レジスタの書き込み,修改hold_reg的值
  • function code01リードコイルを読む,Viewcoil_buf的ステータス
  • function code05シングルコイルを書く。,控制继电器吸合/断开

4.3 自定义修改

  • アドレスの変更:改变MODBUS_ADDR宏定义
  • 修改ボーレート:修改TH1/TL1初值(9600=0xFD,4800=0xFA,19200=0xFD+SMOD)
  • 增加コイルの数:修改coil_buf配列大小
  • 增加レジスタの数:修改hold_reg配列大小
  • 追加应用逻辑:在main函数的while循环中追加用户代码

五、common problems

  • 通讯応答なし。:チェックA/B线是否接反,駅からの住所是否一致,ボーレート一致するかどうか
  • CRC checkError:確認晶振为11.0592MHz,チェックシリアルポート設定
  • データ乱码:チェックボーレート設定,ConfirmSMOD位設定正确
  • 只能受信不能送信:チェックRS485_DIR引脚控制是否正确,DE/RE是否接在一起
  • 多スレーブ冲突:确保每个駅からの住所唯一,バス两端接120Ω終端抵抗
Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC verification and device debugging, or submit your requirements for selection and integration advice.

Engineer Membership

Turn this article into actionable debugging resources

After activation, you can use advanced message parsing, resource pack downloads, code examples, engineering cases and priority technical support, suitable for real project delivery.

Unlimited Advanced Tools
Resource & Code Packs
Complete Engineering Case Library
Priority Technical Support

Leave a Reply

Your email address will not be published. Required fields are marked *.