Modbus家族之 ASCII

Posted 夏沫の浅雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Modbus家族之 ASCII相关的知识,希望对你有一定的参考价值。

写在前面:

本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。


目录


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


描述

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


通讯方式

帧格式

NameLength (bytes)Description
Start1Starts with colon : (ASCII hex value is 3A)
(以冒号 : 开头,ASCII十六进制值为 3A)
Address2Node address in hex
(十六进制节点地址,字符表示)
Function2Function code in hex
(十六进制功能码,字符表示)
Datan x 2n is the number of data bytes, it depends on function
(n是数据字节数,它取决于功能码)
LRC2Longitudinal redundancy check
(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
:
2 CHARS2 CHARSn CHARS2 CHARS2 CHARS
CRLF

功能码

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):

  • 响应

    线圈状态响应消息被打包为数据字段的每比特表示一个线圈。状态表示为: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:

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

  • 请求

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

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

  • 响应

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

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

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

  • 请求

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

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

  • 响应

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

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

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

  • 请求

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

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

  • 响应

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

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

功能 05(05H)写单线圈

  • 请求

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

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

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

  • 响应

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

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

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

  • 请求

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

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

  • 响应

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

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

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

  • 请求

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

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

    Bit1101000100000101
    Coil2726252423222120302928

  • 响应

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

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

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

  • 请求

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

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

    addressdata
    400200x0164
    400210x0165
    400220x0166

  • 响应

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

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

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章节

以上是关于Modbus家族之 ASCII的主要内容,如果未能解决你的问题,请参考以下文章

Modbus家族之 ASCII

Modbus家族之 RTU

Modbus家族之 RTU

Modbus家族之 RTU

Modbus家族之开篇

Modbus家族之开篇