Modbus家族之 ASCII
Posted 夏沫の浅雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Modbus家族之 ASCII相关的知识,希望对你有一定的参考价值。
写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
目录
嗨,相信在上一篇经过我的兄弟 RTU的介绍之后,已经对 Modbus有了一定的了解了吧;那么本篇就跟紧我的脚步一起学习新的知识吧。
描述
Modbus在串行设备中通过实现主从模型结构,解决了电子设备之间的数据通讯问题;在采用 Modbus协议时,它有两种主要的原始传输方式 ---- Modbus RTU 和 Modbus ASCII。而 Modbus RTU已经在上一篇介绍了,那么就在本篇中瞅瞅 ASCII吧。
通讯方式
帧格式
Name | Length (bytes) | Description |
---|---|---|
Start | 1 | Starts with colon : (ASCII hex value is 3A) (以冒号 : 开头,ASCII十六进制值为 3A) |
Address | 2 | Node address in hex (十六进制节点地址,字符表示) |
Function | 2 | Function code in hex (十六进制功能码,字符表示) |
Data | n x 2 | n is the number of data bytes, it depends on function (n是数据字节数,它取决于功能码) |
LRC | 2 | Longitudinal redundancy check (LRC校验码) |
End | 2 | CR / LF |
注:地址、功能、数据和 LRC都是表示 8位值 (0-255) 的大写十六进制可读字符对;即:在 Modbus ASCII中,每个数据字节被分割成表示十六进制值中的两个 ASCII字符的两个字节。
在 ASCII模式下,消息以冒号 :
字符开头(ASCII表示为 0x3A),以回车换行对 \\r\\n
(ASCII表示为 0x0D和 0x0A)结尾;所有其他字段传输的数据所允许的十六进制表示字符为的 0-9
、A-F
。
START | ADDRESS | FUNCTION | DATA | LRC CHECK | END |
---|---|---|---|---|---|
1 CHAR : | 2 CHARS | 2 CHARS | n CHARS | 2 CHARS | 2 CHARS CRLF |
功能码
ASCII最常用的功能代码跟 RTU的功能代码定义是一样的,这里就不多说了,可以去查看 《Modbus家族之 RTU》篇章的功能码部分,这里只是格式上有所不同而已,下一篇会对这两个原始传输方式进行对比的。 嘛,还是直接合并到本篇,对 RTU和 ASCII进行对比分析吧,顺便回顾一下 RTU协议。
访问地址:address | 映射地址 | 描述 | 功能 | R/W |
---|---|---|---|---|
1 ~ 10000 | address-1 | Coils | 01/05/15 | R/W |
10001 ~ 20000 | address-10001 | Discrete Inputs | 02 | R |
30001 ~ 40000 | address-30001 | Input Registers | 04 | R |
40001 ~ 50000 | address-40001 | Holding Registers | 03/06/16 | R/W |
在这里,简单的举个 ASCII传输例子:
例如,要读取 VAR1,你需要从地址 0x20C1读取 2个寄存器,所以你需要发送以下 ASCII消息:
:010420C1000218<CRLF>
-
请求:
Name Description ‘:’ 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>
-
响应:
Name Description ‘:’ 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):
Bit 1 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 Coil 27 26 25 24 23 22 21 20 … … … … … 30 29 28 -
响应
正常响应返回从地址、功能代码、起始地址和写入的线圈数量,不包括字节数和对应写入的状态。
Figure 24 shows an example of a response to the query shown in Figure 23:
功能 16(10H)写多个保持寄存器
-
请求
将值写入到一个保持寄存器序列中。当广播时,该函数在所有附加的从机上设置相同的寄存器引用。请求消息指定要写入的寄存器引用(起始寄存器和数值)。
下面是一个请求从从设备节点 5中的保持寄存器 40020到 40022写入以下数据的示例(注意起始地址是 19或 0x13,比寄存器 40020小 40001):
address data 40020 0x0164 40021 0x0165 40022 0x0166 -
响应
正常响应返回从地址、功能代码、起始地址和写入的寄存器数量,不包括字节数和对应写入的数据。
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的主要内容,如果未能解决你的问题,请参考以下文章