嗨,相信在上一篇经过我的兄弟 RTU 的介绍之后,已经对 Modbus 有了一定的了解了吧;那么本篇就跟紧我的脚步一起学习新的知识吧。

# 描述

Modbus 在串行设备中通过实现主从模型结构,解决了电子设备之间的数据通讯问题;在采用 Modbus 协议时,它有两种主要的原始传输方式 ---- Modbus RTU 和 Modbus ASCII。而 Modbus RTU 已经在上一篇介绍了,那么就在本篇中瞅瞅 ASCII 吧。

# 通讯方式

# 帧格式

NameLength (bytes)Description
Start1Starts with colon : (ASCII hex value is 3A)<br/>(以冒号 : 开头,ASCII 十六进制值为 3A)
Address2Node address in hex<br/>(十六进制节点地址,字符表示)
Function2Function code in hex<br/>(十六进制功能码,字符表示)
Datan x 2n is the number of data bytes, it depends on function<br/>(n 是数据字节数,它取决于功能码)
LRC2Longitudinal redundancy check<br/>(LRC 校验码)
End2CR / LF

注:地址、功能、数据和 LRC 都是表示 8 位值 (0-255) 的大写十六进制可读字符对;即:在 Modbus ASCII 中,每个数据字节被分割成表示十六进制值中的两个 ASCII 字符的两个字节。

在 ASCII 模式下,消息以冒号 : 字符开头(ASCII 表示为 0x3A),以回车换行对 \r\n (ASCII 表示为 0x0D 和 0x0A)结尾;所有其他字段传输的数据所允许的十六进制表示字符为的 0-9A-F

STARTADDRESSFUNCTIONDATALRC CHECKEND
1 CHAR<br/>:2 CHARS2 CHARSn CHARS2 CHARS2 CHARS<br/>CRLF

image-20220326235527004

# 功能码

ASCII 最常用的功能代码跟 RTU 的功能代码定义是一样的,这里就不多说了,可以去查看 《Modbus 家族之 RTU》篇章的功能码部分,这里只是格式上有所不同而已,下一篇会对这两个原始传输方式进行对比的。 嘛,还是直接合并到本篇,对 RTU 和 ASCII 进行对比分析吧,顺便回顾一下 RTU 协议。

访问地址:address映射地址描述功能R/W
1 ~ 10000address-1Coils01/05/15R/W
10001 ~ 20000address-10001Discrete Inputs02R
30001 ~ 40000address-30001Input Registers04R
40001 ~ 50000address-40001Holding Registers03/06/16R/W

在这里,简单的举个 ASCII 传输例子:

例如,要读取 VAR1,你需要从地址 0x20C1 读取 2 个寄存器,所以你需要发送以下 ASCII 消息:

:010420C1000218<CRLF>

  • 请求:

    NameDescription
    ‘:’Start of message - 0x3A
    ‘0’ ‘1’Node address – 0x01
    ‘0’ ‘4’Function code (Read Input Registers) – 0x04
    ‘2’ ‘0’ ‘C’ ‘1’Register address for reading VAR1 – 0x20C1
    ‘0’ ‘0’ ‘0’ ‘2’Length of registers to be read (must be 2) – 0x0002
    ‘1’ ‘8’LRC
    <CRLF>End of message, carriage return and line feed – 0x0D0A

此消息的响应如下:

:01040400001234B1<CRLF>

  • 响应:

    NameDescription
    ‘:’Start of message - 0x3A
    ‘0’ ‘1’Node address – 0x01
    ‘0’ ‘4’Function code (Read Input Registers) – 0x04
    ‘0’ ‘4’Read data length (4 bytes) – 0x04
    ‘0’ ‘0’ ‘0’ ‘0’ ‘1’ ‘2’ ‘3’ ‘4’Value read from VAR1 – 0x00001234
    ‘B’ ‘1’LRC
    <CRLF>End of message, carriage return and line feed – 0x0D0A

好了,那么就直入主题吧,常用功能码部分依然是如下几个:

# 功能 01(01H)读线圈

  • 请求

    读取从机中线圈的 ON/OFF 状态。不支持广播。请求消息指定了开始线圈和要读取的线圈数量。

    下面是一个请求读取线圈的例子:19 - 55(Coil 20 to 56),37 个线圈,从设备节点 3(注意起始地址是 19 或 0x13,比线圈 20 小 1):

    image-20220327151916968

  • 响应

    线圈状态响应消息被打包为数据字段的每比特表示一个线圈。状态表示为:1 = ON,0 = OFF。第一个数据字节的 LSB 包含请求中寻址的线圈。其他线圈跟随这个字节的高阶末端,并在随后的字节中从低阶到高阶。

    例如,当线圈 20 - 27 的状态显示 ON - ON - OFF - OFF - ON - OFF - ON - OFF - ON - OFF 时,以字节值二进制 0101 0011 (0x53) 表示。一个字节包含八个线圈的状态。如果返回的线圈数量不是 8 的倍数,则最终数据字节中的剩余位将用 0 填充 (朝向字节的高阶末端);字节计数字段指定数据的完整字节数。

    Figure 6 shows an example of a response to the query shown in Figure 5:

    image-20220327152253123

# 功能 02(02H)读离散输入

  • 请求

    读取从机中离散输入的 ON/OFF 状态。不支持广播。请求消息指定起始输入和要读取的输入数量。

    下面是一个从从设备节点 3 读取离散输入 10101 - 10120,总共 20 个输入的例子(注意起始地址是 100 或 0x64,比输入 10101 小 10001):

    image-20220327155421391

  • 响应

    离散输入状态响应消息的构造与线圈状态 (01H) 操作相同。

    Figure 8 shows an example of a response to the query shown in Figure 7:

    image-20220327155851480

# 功能 03(03H)读保持寄存器

  • 请求

    读取从机中保持寄存器的二进制内容。不支持广播。请求消息指定起始寄存器和要读取的寄存器数量。

    下面是一个从从设备节点 7 读取保持寄存器 40201 - 40203,总共 3 个寄存器的请求的例子(注意起始地址是 200 或 0xC8,比寄存器 40201 小 40001):

    image-20220327160332698

  • 响应

    响应消息中的保持寄存器数据在每个寄存器中打包为两个字节,二进制内容在每个字节中右对齐;对于每个寄存器,第一个字节包含高阶位,第二个字节包含低阶位。

    Figure 10 shows an example of a response to the query shown in Figure 9:

    image-20220327160607639

# 功能 04(04H)读输入寄存器

  • 请求

    读取从机中保持寄存器的二进制内容。不支持广播。请求消息指定起始寄存器和要读取的寄存器数量。

    下面是一个从从设备节点 7 读取输入寄存器 30301 - 30303,总共 3 个寄存器的请求的例子(注意起始地址是 300 或 0x12C,比寄存器 30301 小 30001):

    image-20220327171629490

  • 响应

    读输入寄存器数据的响应消息的构造与读取保持寄存器 (03H) 操作相同。

    Figure 12 shows an example of a response to the query shown in Figure 11:

    image-20220327171942458

# 功能 05(05H)写单线圈

  • 请求

    将单个线圈写入 ON 或 OFF。当广播时,该函数强制所有附加的从机使用相同的线圈引用。请求消息指定要写入的线圈引用(启动线圈和状态)。

    FF 00 的值要求线圈打开,值为 00 00 的请求为关闭,所有其他值都是非法的,不会影响线圈。

    下面是一个在从设备节点 3 中请求打开线圈 150 的例子(注意起始地址是 149 或 0x95,比线圈 150 小 1):

    image-20220327172937327

  • 响应

    正常的响应是请求的回显,在写入线圈状态之后返回。

    Figure 14 shows an example of a response to the query shown in Figure 13:

    image-20220327173100544

# 功能 06(06H)写单个保持寄存器

  • 请求

    将一个值写入单个保持寄存器中。当广播时,该函数在所有附加的从机上设置相同的寄存器引用。请求消息指定要写入的寄存器引用(指定地址和数值)。

    下面是一个请求从从设备节点 3 中的保持寄存器 40150 写入 1000 数值的例子(注意起始地址为 149 或 0x95,比寄存器 40150 小 40001):

    image-20220327175033869

  • 响应

    正常的响应是请求的回显,在写入保持寄存器内容之后返回。

    Figure 16 shows an example of a response to the query shown in Figure 15:

    image-20220327175344005

# 功能 15(0FH)写多个线圈

  • 请求

    将一个线圈序列中的每个线圈写入 ON 或 OFF。当广播时,该函数强制所有附加的从机使用相同的线圈引用。请求消息指定要写入的线圈引用(起始线圈和状态)。

    下面的示例显示了从设备节点 5 中的线圈 20 开始写入一系列 10 个线圈状态的请求。二进制位与线圈的对应方式如下(注意起始地址是 19 或 0x13,比线圈 20 小 1):

    Bit1101000100000101
    Coil2726252423222120...............302928

    image-20220327182406708

  • 响应

    正常响应返回从地址、功能代码、起始地址和写入的线圈数量,不包括字节数和对应写入的状态。

    Figure 24 shows an example of a response to the query shown in Figure 23:

    image-20220327182618637

# 功能 16(10H)写多个保持寄存器

  • 请求

    将值写入到一个保持寄存器序列中。当广播时,该函数在所有附加的从机上设置相同的寄存器引用。请求消息指定要写入的寄存器引用(起始寄存器和数值)。

    下面是一个请求从从设备节点 5 中的保持寄存器 40020 到 40022 写入以下数据的示例(注意起始地址是 19 或 0x13,比寄存器 40020 小 40001):

    addressdata
    400200x0164
    400210x0165
    400220x0166

    image-20220327184941057

  • 响应

    正常响应返回从地址、功能代码、起始地址和写入的寄存器数量,不包括字节数和对应写入的数据。

    Figure 26 shows an example of a response to the query shown in Figure 25:

    image-20220327185223421

# LRC 校验

unsigned char
ucMBLRC( unsigned char * pucFrame, unsigned short usLen )
{
    unsigned char ucLRC = 0;  /* LRC char initialized */
    while( usLen-- )
    {
        ucLRC += *pucFrame++;   /* Add buffer byte without carry */
    }
    /* Return twos complement */
    ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) );
    return ucLRC;
}

校验原理可看 常用校验算法 - LRC 章节

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝