Modbus 协议说明
MODBUS©协议是一种消息结构,广泛用于在智能设备之间建立主从通信。 从主站发送到从站的MODBUS消息包含从站的地址、“命令”(例如“读寄存器”或“写寄存器”)、数据和校验和(LRC或CRC)。
由于Modbus协议只是一种消息传递结构,因此它独立于底层物理层。 传统上使用 RS232、RS422 或 RS485 实现
请求 请求
中的功能代码告诉寻址的从设备要执行哪种操作。 数据字节包含从站执行功能所需的任何附加信息。 例如,功能代码 03 将请求从机读取保持寄存器并用其内容进行响应。 数据字段必须包含告诉从机从哪个寄存器开始以及要读取多少个寄存器的信息。 错误检查字段为从机提供了一种验证消息内容完整性的方法。
响应
如果从站做出正常响应,则响应中的功能代码是请求中功能代码的回显。 数据字节包含从机收集的数据,例如寄存器值或状态。 如果发生错误,则修改功能代码以指示响应是错误响应,并且数据字节包含描述错误的代码。 错误检查字段允许主机确认消息内容是否有效。
控制器可以设置为使用两种传输模式之一在标准 Modbus 网络上进行通信:ASCII 或 RTU。
ASCII 模式
当控制器设置为使用 ASCII(美国信息交换标准代码)模式在 Modbus 网络上进行通信时,消息中的每个八位字节将作为两个 ASCII 字符发送。 此模式的主要优点是它允许字符之间出现长达一秒的时间间隔,而不会导致错误。
编码系统
十六进制 ASCII 可打印字符 0 ... 9、A ... F
每字节位数
1 个起始位
7 个数据位,最低有效位先发送
1 位表示偶/奇校验 - 无位表示无奇偶校验
1 个停止位(如果奇偶校验)如果没有奇偶校验,则使用 2 位
错误检查
纵向冗余检查 (LRC)
RTU 模式
当控制器设置为使用 RTU(远程终端单元)模式在 Modbus 网络上进行通信时,消息中的每个八位字节包含两个四位十六进制字符。 该模式的主要优点是,在相同的波特率下,其更大的字符密度比 ASCII 具有更高的数据吞吐量。 每条消息必须以连续流的形式传输。
编码系统
八位二进制、十六进制 0 ... 9、A ... F
消息的每个八位字段中包含两个十六进制字符
每字节位数
1 个起始位
8 个数据位,最先发送的
1 位为最低有效位 偶/奇奇偶校验 - 无奇偶校验时无位
如果使用奇偶校验则为 1 个停止位 - 如果无奇偶校验则为 2 位
错误检查字段
循环冗余校验 (CRC)
在 ASCII 模式下,消息以冒号 (:) 字符(ASCII 3A 十六进制)开始,以回车换行 (CRLF) 对(ASCII 0D 和 0A 十六进制)结束。
所有其他字段允许传输的字符为十六进制 0 ... 9、A ... F。联网设备连续监视网络总线中的冒号字符。 当接收到一个设备时,每个设备都会解码下一个字段(地址字段)以查明它是否是寻址设备。
消息中的字符之间最多可以间隔一秒。 如果出现更大的间隔,接收设备将假定发生了错误。 典型的消息帧如下所示。
开始 | 地址 | 功能 | 数据 | LRC | 结尾 |
: | 2 个字符 | 2 个字符 | N 个字符 | 2 个字符 | 回车低频 |
RTU 成帧
在 RTU 模式下,消息以至少 3.5 个字符时间的静默间隔开始。 这最容易实现为网络上使用的波特率下的多个字符时间(如下图中的 T1-T2-T3-T4 所示)。 然后传输的第一个字段是设备地址。
所有字段允许传输的字符为十六进制 0 ... 9、A ... F。联网设备连续监视网络总线,包括在静默间隔期间。 当接收到第一个字段(地址字段)时,每个设备都会对其进行解码以查明它是否是寻址设备。
在最后传输的字符之后,至少 3.5 个字符时间的类似间隔标志着消息的结束。 在此间隔之后可以开始新消息。
整个消息帧必须作为连续流传输。 如果在帧完成之前出现超过 1.5 个字符时间的静默间隔,则接收设备将刷新不完整的消息,并假定下一个字节将是新消息的地址字段。
同样,如果新消息开始时间早于上一条消息的 3.5 个字符时间,则接收设备将认为它是上一条消息的延续。 这将设置一个错误,因为最终 CRC 字段中的值对于组合消息无效。 典型的消息帧如下所示。
开始 | 地址 | 功能 | 数据 | CRC | 结尾 |
3.5 炭化时间 | 8 位 | 8 位 | N*8位 | 16位 | 3.5 炭化时间 |
地址字段
消息帧的地址字段包含两个字符 (ASCII) 或八位 (RTU)。 为各个从站设备分配的地址范围为 1 ... 247。
功能字段
功能代码字段告诉被寻址的从机要执行什么功能。
Modbus Poll 支持以下功能。
01 (0x01) 读取线圈
02 (0x02) 读取离散输入
03 (0x03) 读取保持寄存器
04 (0x04) 读取输入寄存器
05 (0x05) 写入单个线圈
06 (0x06) 写入单个寄存器
08 (0x08) 诊断(仅限串行线路) )
11 (0x0B) 获取通讯事件计数器(仅限串行线路)
15 (0x0F) 写入多个线圈
16 (0x10) 写入多个寄存器
17 (0x11) 报告服务器 ID(仅限串行线路)
22 (0x16) 掩码写入寄存器
23 (0x17) ) 读/写多个寄存器
43 / 14 (0x2B / 0x0E) 读取设备标识
数据字段包含请求或发送的数据。
错误检查字段的内容
标准 Modbus 网络使用两种错误检查方法。 错误检查字段内容取决于所使用的方法。
ASCII
当 ASCII 模式用于字符帧时,错误检查字段包含两个 ASCII 字符。 错误检查字符是对消息内容执行纵向冗余检查 (LRC) 计算的结果,不包括开始冒号和终止 CRLF 字符。
LRC 字符作为 CRLF 字符之前的最后一个字段附加到消息中。
LRC 示例代码
RTU
当 RTU 模式用于字符帧时,错误检查字段包含一个以两个 8 位字节实现的 16 位值。 错误校验值是对消息内容执行循环冗余校验计算的结果。
CRC 字段作为消息中的最后一个字段附加到消息中。 完成此操作后,首先附加字段的低位字节,然后附加高位字节。 CRC 高位字节是消息中要发送的最后一个字节。
CRC 示例代码
功能 01 (01hex) 读取线圈
读取从站中离散线圈的开/关状态。
请求
请求消息指定起始线圈和要读取的线圈数量。
从从设备地址 4 读取 13 个线圈地址 10...22(线圈 11 至 23)的请求示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 04 | 0 4 |
功能 | 01 | 0 1 |
起始地址嗨 | 00 | 0 0 |
起始地址Lo | 0A | 0A |
线圈 数量 嗨 | 00 | 0 0 |
线圈数量 Lo | 0D | 0 日 |
错误检查洛 | DD | LRC (E 4) |
错误检查嗨 | 98 | |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
响应
线圈状态响应消息被打包为数据字段的每一位一个线圈。 状态表示为:1 为 ON 值,0 为 OFF 值。 第一个数据字节的 LSB 包含请求中寻址的线圈。 其他线圈跟随该字节的高位端,并在后续字节中从低位到高位。 如果返回的线圈数量不是八的倍数,则最终数据字节中的剩余位将用零填充(朝向字节的高位端)。 字节计数字段指定数据的完整字节的数量。
对请求的响应示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 04 | 0 4 |
功能 | 01 | 0 1 |
字节数 | 02 | 0 2 |
数据(线圈 18...11) | 0A | 0A |
数据(线圈 23...19) | 11 | 1 1 |
错误检查洛 | B3 | LRC (德国) |
错误检查嗨 | 50 | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 7 | 15 |
功能 02(02hex) 读取离散输入
读取从站中离散输入的 ON/OFF 状态。
请求
请求消息指定起始输入和要读取的输入数量。
从从设备地址 4 读取 13 个输入地址 10...22(输入 10011 至 10023)的请求示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 04 | 0 4 |
功能 | 02 | 0 2 |
起始地址嗨 | 00 | 0 0 |
起始地址Lo | 0A | 0A |
输入 数量 Hi | 00 | 0 0 |
输入 数量 Lo | 0D | 0 日 |
错误检查洛 | 99 | LRC (E 3) |
错误检查嗨 | 98 | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
响应
输入状态响应消息被打包为数据字段的每一位一个输入。 状态表示为:1 为 ON 值,0 为 OFF 值。 第一个数据字节的 LSB 包含请求中寻址的输入。 其他输入遵循该字节的高位端,并在后续字节中从低位到高位。 如果返回的输入数量不是八的倍数,则最终数据字节中的剩余位将用零填充(朝向字节的高位端)。 字节计数字段指定数据的完整字节的数量。
对请求的响应示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 04 | 0 4 |
功能 | 02 | 0 2 |
字节数 | 02 | 0 2 |
数据(输入 18...11) | 0A | 0A |
数据(输入 23...19) | 11 | 1 1 |
错误检查洛 | B3 | LRC (DD) |
错误检查嗨 | 14 | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 7 | 15 |
功能 03 (03hex) 读取保持寄存器
读取从机中保持寄存器的二进制内容。
请求
请求消息指定起始寄存器和要读取的寄存器的数量。
从从设备 1 读取 0...1(寄存器 40001 至 40002)的请求示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 01 | 0 1 |
功能 | 03 | 0 3 |
起始地址嗨 | 00 | 0 0 |
起始地址Lo | 00 | 0 0 |
寄存器 数量 嗨 | 00 | 0 0 |
寄存器 数量 Lo | 02 | 0 2 |
错误检查洛 | C4 | LRC (FA) |
错误检查嗨 | 0B | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
响应
响应消息中的寄存器数据被打包为每个寄存器两个字节,每个字节内的二进制内容右对齐。 对于每个寄存器,第一个字节包含高位,第二个字节包含低位。
对请求的响应示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 01 | 0 1 |
功能 | 03 | 0 3 |
字节数 | 04 | 0 4 |
数据嗨 | 00 | 0 0 |
数据洛 | 06 | 0 6 |
数据嗨 | 00 | 0 0 |
数据洛 | 05 | 0 5 |
错误检查洛 | DA | LRC (ED) |
错误检查嗨 | 31 | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 9 | 19 |
功能 04 (04hex) 读取输入寄存器
读取从机中输入寄存器的二进制内容。
请求
请求消息指定起始寄存器和要读取的寄存器的数量。
从从设备 1 读取 0...1(寄存器 30001 至 30002)的请求示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 01 | 0 1 |
功能 | 04 | 0 4 |
起始地址嗨 | 00 | 0 0 |
起始地址Lo | 00 | 0 0 |
寄存器 数量 嗨 | 00 | 0 0 |
寄存器 数量 Lo | 02 | 0 2 |
错误检查洛 | 71 | LRC (F 9) |
错误检查嗨 | CB | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
响应
响应消息中的寄存器数据被打包为每个寄存器两个字节,每个字节内的二进制内容右对齐。 对于每个寄存器,第一个字节包含高位,第二个字节包含低位。
对请求的响应示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 01 | 0 1 |
功能 | 04 | 0 4 |
字节数 | 04 | 0 4 |
数据嗨 | 00 | 0 0 |
数据洛 | 06 | 0 6 |
数据嗨 | 00 | 0 0 |
数据洛 | 05 | 0 5 |
错误检查洛 | D B | LRC (欧共体) |
错误检查嗨 | 86 | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 9 | 19 |
功能 05 (05hex) 写入单个线圈
将单个线圈写入 ON 或 OFF。
请求
请求消息指定要写入的线圈参考号。 线圈从零开始编址,线圈 1 的编址为 0。
请求的ON/OFF状态由请求数据字段中的常量指定。 十六进制 FF 00 值请求线圈打开。 值 00 00 请求其关闭。 所有其他值都是非法的,不会影响线圈。
以下是在从设备 17 中写入线圈 173 ON 的请求示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 11 | 1 1 |
功能 | 05 | 0 5 |
线圈地址嗨 | 00 | 0 0 |
线圈地址 Lo | 交流电 | 交流电 |
写入数据嗨 | FF | 0 0 |
写数据低 | 00 | FF |
错误检查洛 | 4E | LRC (3楼) |
错误检查嗨 | 8B | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
响应
正常响应是请求的回显,在写入线圈状态后返回。
对请求的响应示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 11 | 1 1 |
功能 | 05 | 0 5 |
线圈地址嗨 | 00 | 0 0 |
线圈地址 Lo | 交流电 | 交流电 |
写入数据嗨 | FF | 0 0 |
写数据低 | 00 | FF |
错误检查洛 | 4E | LRC (3楼) |
错误检查嗨 | 8B | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
功能 06 (06hex) 写入单个寄存器
将值写入单个保持寄存器。
请求
请求消息指定要写入的寄存器参考。 寄存器从零开始寻址,寄存器 1 的寻址方式为 0。
请求的写入值在请求数据字段中指定。 以下是将从设备 17 中的寄存器 40002 写入十六进制 00 03 的请求示例。
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 11 | 1 1 |
功能 | 06 | 0 6 |
注册地址嗨 | 00 | 0 0 |
寄存器地址Lo | 01 | 0 1 |
写入数据嗨 | 00 | 0 0 |
写数据低 | 03 | 0 3 |
错误检查洛 | 9A | LRC (E 5) |
错误检查嗨 | 9B | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
响应
正常响应是请求的回显,在写入寄存器内容后返回。
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 11 | 1 1 |
功能 | 06 | 0 6 |
线圈地址嗨 | 00 | 0 0 |
线圈地址 Lo | 01 | 0 1 |
写入数据嗨 | 00 | 0 0 |
写数据低 | 03 | 0 3 |
错误检查洛 | 9A | LRC (E 5) |
错误检查嗨 | 9B | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
功能 15 (0Fhex) 写入多个线圈
将线圈序列中的每个线圈写入 ON 或 OFF。
请求
请求消息指定要写入的线圈参考。 线圈从零开始编址,线圈 1 的编址为 0。
请求的ON/OFF状态由请求数据字段的内容指定。 字段的位位置中的逻辑 1 请求相应的线圈打开。 逻辑 0 请求其关闭。
下面是请求写入从属设备 17 中从线圈 20(寻址为 19,或十六进制 13)开始的一系列 10 个线圈的示例。
请求数据内容为两个字节:CD 01 十六进制(1100 1101 0000 0001 二进制)。 二进制位与线圈的对应关系如下:
位: 1 1 0 0 1 1 0 1 0 0 0 0 0 0 0 1
线圈: 27 26 25 24 23 22 21 20 - - - - - - 29 28
传输的第一个字节(CD 十六进制)寻址线圈 27 ... 20,最低有效位寻址该组中的最低线圈 (20)。
传输的下一个字节(十六进制 01)对线圈 29 和 28 进行寻址,最低有效位对该组中的最低线圈 (28) 进行寻址。 最后一个数据字节中未使用的位应填零。
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 11 | 1 1 |
功能 | 0F | 0F |
线圈地址嗨 | 00 | 0 0 |
线圈地址 Lo | 13 | 1 3 |
线圈数量嗨 | 00 | 0 0 |
线圈数量 Lo | 0A | 0A |
字节数 | 02 | 0 2 |
写入数据嗨 | 光盘 | 光盘 |
写数据低 | 01 | 0 1 |
错误检查洛 | BF | LRC (F 3) |
错误检查嗨 | 0B | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 11 | 23 |
响应
正常响应返回从机地址、功能码、起始地址、写入线圈数。 这是对上面显示的请求的响应示例
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 11 | 1 1 |
功能 | 0F | 0F |
线圈地址嗨 | 00 | 0 0 |
线圈地址 Lo | 13 | 1 3 |
线圈 数量 嗨 | 00 | 0 0 |
线圈数量 Lo | 0A | 0A |
错误检查洛 | 26 | LRC (C 3) |
错误检查嗨 | 99 | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
功能 16 (10hex) 写入多个寄存器
将值写入一系列保持寄存器
请求
请求消息指定要写入的寄存器引用。 寄存器从零开始寻址,寄存器 1 的寻址方式为 0。
请求的写入值在请求数据字段中指定。 每个寄存器数据打包为两个字节。
以下是在从设备 17 中将两个从 40002 开始的寄存器写入十六进制 00 0A 和 01 02 的请求示例:
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 11 | 1 1 |
功能 | 10 | 1 0 |
起始地址嗨 | 00 | 0 0 |
起始地址Lo | 01 | 0 1 |
寄存器 数量 嗨 | 00 | 0 0 |
寄存器 数量 Lo | 02 | 0 2 |
字节数 | 04 | 0 4 |
数据嗨 | 00 | 0 0 |
数据洛 | 0A | 0A |
数据嗨 | 01 | 0 1 |
数据洛 | 02 | 0 2 |
错误检查洛 | C6 | LRC (CB) |
错误检查嗨 | F0 | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 13 | 23 |
响应
正常响应返回从机地址、功能码、起始地址、写入的寄存器个数。 以下是对上述请求的响应示例。
字段名称 | RTU(十六进制) | ASCII 字符 |
标头 | 没有任何 | : (冒号) |
从机地址 | 11 | 1 1 |
功能 | 10 | 1 0 |
起始地址嗨 | 00 | 0 0 |
起始地址Lo | 01 | 0 1 |
寄存器 数量 嗨 | 00 | 0 0 |
寄存器 数量 Lo | 02 | 0 2 |
错误检查洛 | 12 | LRC(直流) |
错误检查嗨 | 98 | 没有任何 |
预告片 | 没有任何 | 回车低频 |
总字节数 | 8 | 17 号 |
LRC 示例代码
该函数是如何使用 C 语言计算 LRC BYTE 的示例。
BYTE LRC (BYTE *nData, WORD wLength)
{
BYTE nLRC = 0 ; // LRC char initialized
for (int i = 0; i < wLength; i++)
nLRC += *nData++;
return (BYTE)(-nLRC);
} // End: LRC
CRC 示例代码
该函数是如何使用 C 语言计算 CRC 字的示例。
WORD CRC16 (const BYTE *nData, WORD wLength) { static const WORD wCRCTable[] = { 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 }; BYTE nTemp; WORD wCRCWord = 0xFFFF; while (wLength--) { nTemp = *nData++ ^ wCRCWord; wCRCWord >>= 8; wCRCWord ^= wCRCTable[nTemp]; } return wCRCWord;
} // 结束:CRC16