网络知识 娱乐 值得收藏 Modbus RTU 协议详解

值得收藏 Modbus RTU 协议详解

值得收藏!Modbus RTU 协议详解~

目录

  • 值得收藏!Modbus RTU 协议详解~
      • Modbus是什么?
      • Modbus分类
      • Modbus通讯过程
      • Modbus-RTU协议数据帧结构
      • 功能码01:读线圈状态
      • 功能码02:读离散量输入
      • 功能码03:读保持寄存器
      • 功能码04:读输入寄存器
      • 功能码05:写单个线圈
      • 功能码06:写单个寄存器
      • 功能码15:写多个线圈
      • 功能码16:写多个寄存器
      • 附录:Modbus CRC校验函数C语言实现


Modbus是什么?

         ~~~~~~~~         Modbus是一个总线协议,属于应用层的一层协议。应用层面的协议还有TCP、UDP。因modbus其协议流程简单明了,易于组网被广泛使用,目前应该是在工业上使用的最多的,像是与PLC通信。
         ~~~~~~~~         嵌入式领域最常见的用法就是硬件电路采用RS485,在此硬件基础上使用modbus。


Modbus分类

Modbus协议分为三种,包括:

  • Modbus-RTU
  • Modbus-ASCII
  • Modbus-TCP

最常见使用的就是RTU了,所以本篇的重点放在讲解RTU上。


Modbus通讯过程

         ~~~~~~~~         Modbus是主从方式通信,通信由主机发起,一问一答式,从机无法主动向主机发送数据。通信方式类似于IIC、SPI协议。

         ~~~~~~~~         modbus数据帧在传输过程中,两个字节之间的相邻时间不得大于3.5个字符的时间,否则视为一帧数据传输结束。

         ~~~~~~~~         以:波特率9600、1bit起始位、8bit数据位、1bit停止位,1bit校验位、无流控为例,那么1s内就可以传输(1+8+1+1)/9600*3.5*1000≈4ms,所以,如果从机在接收过程中,超过了4ms没有收到数据,则认为本帧数据接收结束;同样的,在发送完数据后也要延时等待4ms的延时时间。

《GB/T 19582.2》中规定:
         ~~~~~~~~         
         ~~~~~~~~         RTU模式中每个字节为11位,格式为:8bit数据位(先发低位)、1bit起始位、1bit奇偶校验、1bit停止位
         ~~~~~~~~         
         ~~~~~~~~         要求使用偶校验。也可以使用其他模式(奇校验、无校验)。为了保证与其他产品的最大兼容性,建议还支持无校验模式。默认校验模式必须是偶校验。
         ~~~~~~~~         
         ~~~~~~~~         注:使用无校验时要求2个停止位,以此来满足11bit的数据。
         ~~~~~~~~         
串行的传输字符的方法为:
发送每个字符或字节的顺序是从左到右,如下图:


Modbus-RTU协议数据帧结构

地址码功能码数据区CRC校验
1 Bytes1ByteN Bytes2Byte
  • 地址码:1个字节的从机地址码,=0:广播地址,=1-247:从机地址,=248-255:保留
  • 功能码:常用的就是01、02、03、04、05、06、15、16,具体描述见下图
  • 数据区:数据区包含这么几部分:起始地址、数量、数据,这三项是大端模式
  • CRC校验:两个字节,小端模式,校验的数据范围为:地址码+功能码+数据区

下面将实际将常用的6个功能码进行实际的演示示例。


功能码01:读线圈状态

示例1:读1个线圈状态,线圈地址为0:

主机发送:01 01 00 00 00 01 FD CA
从机返回:01 01 01 00 51 88

解析主机发送的数据:

010100 0000 01FD CA
从机地址功能码要读的线圈起始地址(大端模式)要读取的线圈数量(大端模式)CRC校验码(小端模式)

解析从机返回的数据,只说数据区:

  • 01:后跟的字节数
  • 00:线圈状态

示例2:读从线圈0开始的10个线圈状态:

主机发送:01 01 00 00 00 0A BC 0D
从机返回:01 01 02 00 00 b9 fc

解析从机返回的数据,只说数据区:

  • 02:后跟的字节数
  • 00 00:一个比特代表一个线圈的状态

功能码02:读离散量输入

协议格式同功能码01。


功能码03:读保持寄存器

示例1:读1个保持寄存器,保持寄存器地址为0:

主机发送:01 03 00 00 00 01 84 0A
从机接收:01 03 02 00 00 b8 44

解析主机发送的数据:

010300 0000 0184 0A
从机地址功能码要读取的保持寄存器起始地址(大端模式)要读取的保持寄存器数量(大端模式)CRC校验码(小端模式)

解析主机返回的数据,只说数据区:

  • 02:后跟的字节数
  • 00 00:读取到的保持寄存器的值,大端模式

示例2:读10个保持寄存器,保持寄存器起始地址为0:

主机发送:01 03 00 00 00 0A C5 CD
从机返回:01 03 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a3 67

解析主机返回的数据(只说数据区):

  • 14:后跟的字节数
  • 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00:读取到的保持寄存器的值,大端模式,一个寄存器值用2字节表示。

功能码04:读输入寄存器

协议格式同功能码03。


功能码05:写单个线圈

示例1:写线圈0为0:

主机发送:01 05 00 00 00 00 CD CA
从机返回:01 05 00 00 00 00 cd ca

解析主机发送的数据:

010500 0000 00CD CA
从机地址功能码要写的线圈地址(大端模式)要写的线圈状态(大端模式)CRC校验码(小端模式)

主机发送什么,从机原样返回。

示例2:写线圈0为1:

主机发送:01 05 00 00 FF 00 8C 3A
从机返回:01 05 00 00 ff 00 8c 3a

解析主机发送的数据,只说数据区:

  • 00 00:要写的线圈地址,大端模式
  • FF 00:要写的线圈状态,FF 00表示将线圈置1

功能码06:写单个寄存器

示例1:写寄存器0为0:

主机发送:01 06 00 00 00 00 89 CA
从机返回;01 06 00 00 00 00 89 CA

解析主机发送的数据:

010600 0000 0089 CA
从机地址功能码要写的寄存器地址(大端模式)要写的寄存器(大端模式)CRC校验码(小端模式)

主机发送什么,从机原样返回。

示例2:写寄存器0为1:

主机发送:01 06 00 00 00 01 48 0A
从机返回:01 06 00 00 00 01 48 0a


功能码15:写多个线圈

写从线圈编号0开始的10个线圈:0-3线圈写1,4-7线圈写0,8-9线圈写1(0F 03):
主机发送:01 0F 00 00 00 0A 02 0F 03 A0 C9
从机返回:01 0f 00 00 00 0a d5 cc

解析主机发送的数据:

010F00 0000 0A020F 03A0 C9
从机地址功能码要写的线圈起始地址(大端模式)要写的线圈数量(大端模式)后跟的字节数待写入的线圈状态,小端模式,换成比特由低位到高位就是:1111 0000 11XX XXXX,X表示未用到,一个比特代表一个线圈的状态CRC校验码(小端模式)

解析从机返回的数据,只说数据区:

  • 00 00:已写的线圈起始地址,大端模式
  • 00 0A:已写的线圈数量,大端模式

功能码16:写多个寄存器

写寄存器编号0开始的10个寄存器:0-3寄存器写1,4-7寄存器写0,8-9寄存器写1:
主机发送:01 10 00 00 00 0A 14 00 01 00 01 00 01 00 01 00 00 00 00 00 00 00 00 00 01 00 01 4F 13
从机返回:01 10 00 00 00 0a 40 0e

解析主机发送的数据:

011000 0000 0A1400 01 00 01 00 01 00 01 00 00 00 00 00 00 00 00 00 01 00 014F 13
从机地址功能码要写的寄存器起始地址(大端模式)要写的寄存器数量(大端模式)后跟的字节数待写入的寄存器值,大端模式,两个字节代表一个寄存器的值CRC校验码(小端模式)

解析从机返回的数据,只说数据区:

  • 00 00:已写的寄存器起始地址,大端模式
  • 00 0A:已写的寄存器数量,大端模式

附录:Modbus CRC校验函数C语言实现

USHORT usMBCRC16( UCHAR * pucFrame, USHORT usLen )
{
    static const UCHAR aucCRCHi[] =
    {
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40
    };

    static const UCHAR aucCRCLo[] =
    {
        0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
        0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
        0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
        0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
        0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
        0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
        0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
        0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
        0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
        0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
        0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
        0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
        0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
        0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
        0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
        0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
        0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
        0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
        0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
        0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
        0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
        0x41, 0x81, 0x80, 0x40
    };
    UCHAR           ucCRCHi = 0xFF;
    UCHAR           ucCRCLo = 0xFF;
    int             iIndex;

    while( usLen-- )
    {
        iIndex = ucCRCLo ^ *( pucFrame++ );
        ucCRCLo = ( UCHAR )( ucCRCHi ^ aucCRCHi[iIndex] );
        ucCRCHi = aucCRCLo[iIndex];
    }
    return ( USHORT )( ucCRCHi << 8 | ucCRCLo );
}


modbus相关文章推荐:

  • STMC2CubeMX | STM32 HAL库移植FreeModbus详细步骤

ends…完!