Modbus 多RegisterdatenAnalyse终极指南:Befehlseite(Endianness)的vollständig解决方案

kostenlosKostenloses technisches Material

Dieser Inhalt ist direkt lesbar und eignet sich für das Grundlagenlernen und die Suche.

🌐 Diese Seite ist noch nicht auf Deutsch verfügbar. Die chinesische Version wird angezeigt. Zurück zur chinesischen Seite.
本文目录
  1. 1. 这个问题比你想象的更普遍
  2. 2. 四种Befehlseite,四张图说清楚
  3. 3. ABCD(大端序,Modbus 标准排列)
  4. 4. DCBA(小端序,完全反转)
  5. 5. CDAB(Wort ausgetauscht.,Register.前后互换)
  6. 6. BADC(Bytes交换,每个 WORD 内部反转)
  7. 7. Datentyp远比Befehlseite更隐蔽
  8. 8. 各主流厂商的实际行为
  9. 9. 施耐德 Modicon M340/M580/M241
  10. 10. 西门子 S7-1200/S7-1500(MB_SERVER)
  11. 11. 台达 DVP 系列
  12. 12. 三菱 FX3U/FX5U(通过 Modbus Kommunikation模块)
  13. 13. 欧姆龙 CP1H/CP1L
  14. 14. 国产 PLC(汇川、步科)
  15. 15. Modbus Poll 的BefehlseiteWechseln
  16. 16. 64 位Daten的处理
  17. 17. Charaktere串的存储
  18. 18. Tests方法论
  19. 19. 附录:Modbus CRC-16 查表法 C 语言实现

这个问题比你想象的更普遍

用 Modbus Poll 读zu一个电能表的正向有功总电Quantität,Register. 40001~40002,返回 `42 C8 00 00`。你信心满满地按 IEEE 754 float32 一拼——100.0 kWh,合理。同一个程序,换zu另一家国产电力仪表,Lesen同样的两ein Register.,返回 `00 00 42 C8`,拼出来 0.0,PLC 的触摸屏上直接给你画了条直线。你没写错Register.,也没算错 CRC,纯粹是Befehlseite反了。

这就是 Modbus 多RegisterdatenAnalyse的核心痛点。Modbus-Protokollspezifikation V1.1b Die 4.2 节”Data Encoding”说得非常坦诚:**”Modbus uses a ‘big-endian’ representation for addresses and data items. This means that when a numerical quantity larger than a single byte is transmitted, the most significant byte is sent first.”** 翻译成人话:Modbus 在传输单ein Register.内的 16 位Daten时用大端序——hohe Bytes在前。但规范没说 32 位或 64 位跨多ein Register.时怎么排。两个连续Register.的先后次序、每ein Register.内部两个Bytes的次序,全留给Ausrüstung厂商自己决定。

这话听起来像甩锅,但 1979 Jahr的 Modicon 工程师确实没必要操心 32 位浮点数——当Jahr PLC 连 16 位整数都嫌奢侈。四十Jahr后,我们得自己扛。

四种Befehlseite,四张图说清楚

假设你Lesen und halten Register从Adresse 0(Modbus Adresse 40001)开始,Bitte 2 ein Register.。Von der Station返回的Meldung里的Datenzone是:

41 DB 85 1F

这是一个 IEEE 754 单精度浮点数的原始Befehlseite列。真实值是 27.440271…(约 27.44)。现在看四种BefehlseiteUnterteilung别怎么拼:

ABCD(大端序,Modbus 标准排列)

Register. 40001 的值是 `0x41DB`,Register. 40002 的值是 `0x851F`。按顺序拼成 32 位:

Register. 40001(高16位)    Register. 40002(低16位)
+--------+--------+      +--------+--------+
|  0x41  |  0xDB  |      |  0x85  |  0x1F  |
+--------+--------+      +--------+--------+
  A        B               C        D

内存视角(低Adressezu高Adresse):`41 DB 85 1F`

→ float32 = **27.44**

这是施耐德 Modicon 系列 PLC(M340、M580、M241 等)的默认行为。施耐德作为 Das Modbus-Protokoll的亲爹,一直遵循这个标准排列——Register-Adresse小的存高 16 位,Register-Adresse大的存低 16 位。你用 READ_VAR 读回来两个 WORD,直接拼就是对的。

DCBA(小端序,完全反转)

Register. 40001(但内容反转了)  Register. 40002
+--------+--------+      +--------+--------+
|  0x1F  |  0x85  |      |  0xDB  |  0x41  |
+--------+--------+      +--------+--------+

内存视角:`1F 85 DB 41`

→ float32 = **1.5468 × 10⁻²⁸** ——一个接近零的极端值,屏显基本就是 0.0 或溢出报错。

部Unterteilung国产 Modbus 模块(尤其是一些基于 51 单片机或早期 ARM 裸跑的仪表)用这种方式。因为它们内部 CPU 是小端序,直接 memcpy 了事,本质上就是「懒得转换」。北京某品牌的智能电力仪表就是典型案例,你读回来必须手工做Bytes逆转。

CDAB(Wort ausgetauscht.,Register.前后互换)

Register. 40002 的内容放前面  Register. 40001 的内容放后面
+--------+--------+      +--------+--------+
|  0x85  |  0x1F  |      |  0x41  |  0xDB  |
+--------+--------+      +--------+--------+

内存视角:`85 1F 41 DB`

→ float32 = **-1.096 × 10⁻³³**

这是西门子 S7-1200/S7-1500 做 Modbus TCP Server 时的实际行为。西门子 PLC 内部 REAL 型变Quantität占用 4 个Bytes,存zu DB 块里是标准 IEEE 754 Das Format,但 MB_SERVER 指令块在把保持Register.暴露给 Modbus 客户端时,**先发低AdresseRegister.的低Bytes、再发hohe Bytes,然后是高AdresseRegister.的低Bytes、再hohe Bytes**。这相当于把施耐德 ABCD 的两个 WORD 前后互换。你用 Modbus Poll 读西门子 PLC 的浮点数,如果不把 Display 设置里的 Float Wechselnzu “CDAB” 模式,读出来一定是天文数字。

libmodbus 专门为这四种序提供了四组函数:`modbus_get_float_abcd()`、`modbus_get_float_dcba()`、`modbus_get_float_badc()`、`modbus_get_float_cdab()`。

BADC(Bytes交换,每个 WORD 内部反转)

Register. 40001(内部Bytes交换)  Register. 40002(内部Bytes交换)
+--------+--------+      +--------+--------+
|  0xDB  |  0x41  |      |  0x1F  |  0x85  |
+--------+--------+      +--------+--------+

内存视角:`DB 41 1F 85`

→ float32 = **-1.457 × 10¹⁷**

台达 DVP 系列、三菱 FX3U/FX5U 以及汇川部Unterteilung系列(H3u、H5u)在通过 Modbus RTU 传输 32 位Daten时,实际走的就是 BADC 排列。为什么?因为这些日系/台系 PLC 的内部Daten存储本身就是「字内小端」——单个 16 位 WORD 内部低Bytes在前。zu了 Modbus 传输层,先发 WORD 的hohe Bytes(Modbus 要求),于是 WORD 内部反了一次。两个 WORD 之间有保持了 Modbus 的大端次序。所以Ergebnis就是 BADC:每个 WORD 内部Bytes逆转,WORD 之间保持 ABCD 的顺序。

打个不那么严谨的比方:施耐德是一整盒拼图按顺序给你(ABCD),西门子是左右两半先换位再给你(CDAB),台达/三菱/汇川是把每半的内部先翻个面再按顺序给你(BADC)。至于 DCBA,那是连盒子带内容全反了。

Datentyp远比Befehlseite更隐蔽

同一个 4 ByteSequenz `41 DB 85 1F`,按不同DatentypAnalyse:

Analyse方式Ergebnis
float32(IEEE 754)27.44
uint321,104,626,975
int321,104,626,975
两个 uint16 拼接[16859, 34079]

看这schlecht异,float32 的 27.44 和 uint32 的 11 亿,schlecht八个Anzahl级。如果你在 SCADA 上看zu一个Temperatur测点显示 1104626975.0℃,别急着换Der Sensor——先查查是不是用 uint32 Analyse了 float32。

这在实际工程中比Befehlseite问题更隐蔽,因为**你拿zu的是对的数,但解释方式是错的**。Befehlseite不对,至少数字是乱的,你能一眼看出来;Typus不对,有时候真就蒙混过去了。

举个现场的例子。某个光伏电站的逆变器通过 Modbus TCP 上送当日发电Quantität,RegisterabbildungDokumentation写的是「40001-40002: 当日发电Quantität,32 位」。工程师用 int32 Analyse,读回来一个正数,Zahlenwert看起来挺合理,上了能耗平台跑了一个月。直zu财务对账发现schlecht了 30%,一查才知道——厂家实际存的是 **uint32**,按 int32 Analyse时高位被当成符Nr.位处理了,只要发电Quantität超过 2,147,483,647 Wh(2.15 GWh),Analyse值就变成负数。

所以面对一个多Registerdaten点,你需要Bestätigen的不是一个变Quantität,是三个:

1. DatentypWas ist das?(float32 / int32 / uint32 / int64 / uint64 / double64 / string)? 2. BefehlseiteWas ist das?(四种之一)? 3. 有没有缩放因子?比如仪表存的是原始值 × 10 还是 × 100?

Die三点经常被忽略。很多 Modbus 仪表为了省Register.、避免浮点传输,会把浮点值乘以 10 或 100 后转成 int32 存进去。比如读回来 int32 = 2744,实际Temperatur是 27.44℃(÷100),压力是 274.4kPa(÷10)。这个缩放因子只在Ausrüstung手册里写,VereinbarungMeldung上完全看不出来。

各主流厂商的实际行为

这一节是这篇Artikel最有实用价值的部Unterteilung。以下Daten来自实际Tests记录和社区验证,不是Produkthandbuch上抄来的。

施耐德 Modicon M340/M580/M241

Das Modbus-Protokoll的发明者,行为最「标准」。QUANTUM、M340、M580、M241 全线产品的 Modbus TCP/RTU Kommunikation,32 位Daten(REAL、DINT)默认都是 **ABCD** 排列。用 READ_VAR 读回来的 `%MW` Register.,低位Adresse存高 16 位,高位Adresse存低 16 位。

特别注意:施耐德的 Unity Pro / EcoStruxure Control Expert 里,`%MD`(双字)和 `%MF`(浮点双字)的内部存储与 Modbus Registerabbildung有一层转换。`%MD0` 对应 `%MW0` 和 `%MW1`,Von denen `%MW0` 是高位,`%MW1` 是低位——恰好是 ABCD。

结论:施耐德 PLC 做 Modbus Von der Station时,上游直接按 ABCD Analyse即可。

西门子 S7-1200/S7-1500(MB_SERVER)

这是坑最多的一个。西门子在 TIA Portal 中调用 `MB_SERVER` 指令块做 Modbus TCP Der Server,REAL 型变Quantität(4 Byte)在两个保持Register.里的排列是 **CDAB**。

具体原理:西门子 S7-1200/1500 的 DB 块默认是「优化的块访问」,内部Bytes排列不透明。即使你Absage了优化(设置为标准访问),REAL 型变Quantität在 DB 中的Die Reihenfolge也是小端(Intel x86 处理器决定的)。当 `MB_SERVER` 把这个 REAL 的 4 个Bytes映射zu Modbus 保持Register.时,它按Die Reihenfolge依次填充:Adresse小的Register.存Bytes 0 和 1(这是 REAL 的低位部Unterteilung),Adresse大的Register.存Bytes 2 和 3(高位部Unterteilung)。

实际效果就是 CDAB。Modbus Poll 的 Display 设置里必须切zu “CDAB” 才能看zuRichtig.的浮点数。

如果你用西门子 PLC 做 Modbus 客户端(`MB_CLIENT`)去读Die三方的 Modbus Von der Station,同样需要注意——你读回来的两个 WORD 的排列取决于Von der Station的Befehlseite,和你西门子内部的 REAL 存储是两码事。这时候用博途 V16 及以上版本提供的 `READ_BIG` / `READ_LITTLE` / `WRITE_BIG` / `WRITE_LITTLE` 指令来做Befehlseite转换,比手动 SWAP 加移位要可靠得多。

台达 DVP 系列

台达 DVP-ES2/EX2/SV2 等系列做 Modbus RTU Von der Station时,32 位浮点数(F Register.)映射zu Modbus 保持Register.的排列是 **BADC**。

这跟台达内部浮点数的存储方式有关——台达 PLC 的 32 位Daten遵循「低位存低Adresse」的Regeln(小端),而 Modbus RTU 帧要求每个 WORD 以大端Senden Sie。于是 WORD 内部Bytes反转了一次,但两个 WORD 之间保持了 PLC 内部顺序,最终变成 BADC。

如果你的 WPLSoft 里显示 D0 = F100.0(浮点数),在 Modbus Poll 里读 40001~40002,Display 选 “BADC”,看zu的就是 100.0。选其他三种序都是乱码。

三菱 FX3U/FX5U(通过 Modbus Kommunikation模块)

三菱 FX3U 加装 FX3U-485ADP-MB Kommunikation模块做 Modbus RTU Hauptstation或Von der Station时,32 位Daten的排列也是 **BADC**。跟台达的逻辑一样——日系 PLC 的内部架构均为小端。

FX5U 本体自带 Modbus RTU Funktion(通过 RS-485 端子),用 ADPRW 指令做Kommunikation时,读回来的 32 位Daten需要你用 MOV 指令手动拼。从 MODBUS 读回两ein Register. D100、D101,存的是 BADC 排列的Originaldaten,要先用 SWAP 指令交换每ein Register.的Bytes,再用高低位拼接指令组合成最终的 32 位值。

我见过不止一个项目——三菱 PLC 读施耐德变频器的频率,读回来反了又没做 SWAP,Tests两天最后发现就是Befehlseite的锅。

欧姆龙 CP1H/CP1L

欧姆龙 CP1 系列通过 CP1W-CIF11(RS-485 选件板)做 Modbus RTU Von der Station时,保持Register.使用 DM 区映射。32 位Daten在两个 DM 字中的排列取决于你如何编程。

CP1 系列本身是字寻址架构,没有原生的双字Datentyp。浮点数要用两个 DM 字拼,排列顺序完全由你的梯形图决定。但大多数标准实现遵循 **ABCD** 排列——这和 CP1 系列脱胎于早期的 SYSMAC 架构有关。实际Tests时建议先用已知值(比如写浮点 1.0 = `3F80 0000` zu两个 DM 字)测一次。

国产 PLC(汇川、步科)

汇川 H3u/H5u 系列做 Modbus RTU Von der Station,32 位Daten默认 **BADC** 排列,跟台达一致。但汇川的 AM600 系列(基于 CODESYS 平台)就不一样了——CODESYS 内部实数存储是标准 IEEE 754 大端结构,Modbus TCP Server 暴露的Register.排列默认是 **ABCD**。

汇川 Easy 系列属于 H5u 的简化版,用于小型Ausrüstung控制,Modbus RTU Von der Station的浮点数排列同样是 **BADC**。

步科(Kinco)有部Unterteilung型Nr.走 **CDAB**,比如 K2 系列。这点和西门子类似,因为步科 K2/K5 系列在软件层面借鉴了西门子的触屏编程逻辑。

总结一个速查表:

厂商/AusrüstungModbus 32位排列备注
施耐德 M340/M580/M241ABCDModbus 原厂标准
西门子 S7-1200/1500CDABMB_SERVER 指令块行为
台达 DVP 系列BADC字内Bytes交换
三菱 FX3U/FX5UBADC需 SWAP 后拼接
欧姆龙 CP1H/CP1LABCD(多数实现)取决于梯形图
汇川 H3u/H5uBADC与台达一致
汇川 AM600 (CODESYS)ABCDCODESYS 平台
步科 K2 系列CDAB部Unterteilung型Nr.
典型国产电力仪表DCBA 或 ABCD需逐个测试

注意:这个表是实测参考,不是官方承诺。同一厂商的不同型Nr.、不同固件版本,Befehlseite行为可能不同。上生产环境之前一定要测试验证。

Modbus Poll 的BefehlseiteWechseln

Modbus Poll 在 Display 设置里提供了DatenformateWechselnFunktion。选中Daten显示区域后右键 → Format,可以看zu:

Signed       - 有符Nr. 16 位整数
Unsigned     - 无符Nr. 16 位整数
Hex          - 16. System
Binary       - Binärsystem
32-bit Signed    - 32 位有符Nr.整数
32-bit Unsigned  - 32 位无符Nr.整数
32-bit Float     - 32 位浮点数
64-bit Float     - 64 位双精度浮点数

选了 32-bit Float 后,右侧会弹出Befehlseite选项:

Float Big-endian (ABCD)         - 大端
Float Little-endian (DCBA)      - 小端
Float Big-endian byte swap (BADC)   - 大端Bytes交换
Float Little-endian byte swap (CDAB) - 小端Bytes交换

你不需要记住哪个厂商用哪种序。Tests的时候四种序都切一遍,看哪个值合理就是哪个。读一个你确定Quantität程范围的已知测点(比如环境Temperatur在 20~30℃ 之间),四种序里只有一种会落在合理范围。

64 位Daten的处理

电Quantität累计值、总BetriebZeit、大容Quantität流Quantität累积——这些动辄需要用 4 ein Register.(8 Bytes)来表示的Szene,Befehlseite问题更复杂。因为你要处理两层嵌套的排列:

Die一层:每个 32 位 DWORD 内部的Befehlseite(跟前面 32 位Szene一样,四种可能) Die二层:两个 32 位 DWORD 之间的先后次序(Register. 1-2 是高位 32 位还是低位 32 位?)

假设你读 4 ein Register.,返回原始Befehlseite列:

41 DB 85 1F 42 C8 00 00

如果按照「先高 32 位后低 32 位、每个 32 位按 ABCD」——这是最规矩的排列:

高32位 (ABCD): 41 DB 85 1F → float32 = 27.44
低32位 (ABCD): 42 C8 00 00 → float32 = 100.0

注意:这是一个 totalizer(累加器)的典型构建方式。高 32 位存大数部Unterteilung,低 32 位存小数部Unterteilung,或者高 32 位存整数部Unterteilung、低 32 位存小数部Unterteilung。具体怎么组合要看Ausrüstung手册。

更Häufig的Szene是 uint64 累计值。4 ein Register.按 ABCD 排列,从Register-Adresse低zu高依次是:

Register.1 (MSW-High): A B  → Bytes0-1
Register.2 (MSW-Low):  C D  → Bytes2-3   } 组成高32位
Register.3 (LSW-High): E F  → Bytes4-5
Register.4 (LSW-Low):  G H  → Bytes6-7   } 组成低32位

拼成 uint64:`(uint64_t)(reg1<<48 | reg2<<32 | reg3<<16 | reg4)`,但这个公式只在 CPU 本身是大端序时成立。你在 x86(小端)上写 C 代码,得倒过来。

实测经验:处理 64 位Daten时,先别纠结Befehlseite,先用16. System读出 4 ein Register.的原始值,清楚累加器是否在增长,观察增长的方向——如果累加器每次递增,看变化是从低AdresseRegister.开始还是高AdresseRegister.开始,就知道高低位排列了。

libmodbus 没有直接提供 64 位的转换函数,你需要自己拼:

uint16_t regs[4];
modbus_read_registers(ctx, addr, 4, regs);
// 假设 ABCD 排列
uint64_t value = ((uint64_t)regs[0] << 48) |
                 ((uint64_t)regs[1] << 32) |
                 ((uint64_t)regs[2] << 16) |
                 ((uint64_t)regs[3]);

但这只是四种序中的一种。如果你Die Ausrüstung是 BADC 排列,得先交换每ein Register.的Bytes;如果是 CDAB,得先交换Register.对的位置。

Charaktere串的存储

AusrüstungSequenzNr.、固件版本Nr.这些 ASCII Charaktere串在 Modbus Register.里的存储,有两种主流模式:

**模式一:每Register.存 2 个Charaktere(标准做法)**

每个 16 位Register.存两个Bytes,每Bytes一个 ASCII Charaktere。比如SequenzNr. “SN20240001″(10 个Charaktere),需要 5 ein Register.:

Register.1: 0x53 0x4E → 'S' 'N'
Register.2: 0x32 0x30 → '2' '0'
Register.3: 0x32 0x34 → '2' '4'
Register.4: 0x30 0x30 → '0' '0'
Register.5: 0x30 0x31 → '0' '1'

读回来直接按Bytes取 ASCII 值转Charaktere就行,不需要关心Befehlseite——ASCII Charaktere `A` 就是 `0x41`,无论大端小端。

**模式二:每Register.仅低BytesWirksam**

某些仪表厂商偷懒,每ein Register.只用了低 8 位存一个Charaktere,高 8 位是 0x00。同样的 “SN20240001” 需要 10 ein Register.:

Register.1: 0x00 0x53 → 空 'S'
Register.2: 0x00 0x4E → 空 'N'
...

这种方式浪费了一半的Register.空间,但Analyse简单——取每ein Register.的低Bytes就行。

踩坑提醒:Charaktere串的Befehlseite问题绝大多数时候nicht existiert.,因为 ASCII 是单BytesCodierung。真正会出问题的是你用 UTF-16 或者其他多BytesCodierung的场合——但那已经不是 Das Modbus-Protokoll的范畴了,纯粹是应用层的CodierungAuswahl。

Tests方法论

别猜,用Daten说话。以下是一个标准的Befehlseite排错流程:

**Die一步:读原始16. System**

用 Modbus Poll 把 Display Das Format切成 Hex,先记下原始Registerwert。不管Befehlseite,先看裸Daten。

读 40001-40002 返回: 41 DB 85 1F

**Die二步:用已知值反推**

找一个你知道真实值的测点。比如gegenwärtigTemperatur 27.44°C。如果这个值是你从Ausrüstung自带显示屏上读出来的,记下来。然后看四种序哪个拼出来是 27.44,那个就是Richtig.答案。

更好的办法——主动写一个已知值。用Funktionscode 16(Schreiben mehrerer Register)写入 `0x3F80 0000`(这是 float32 = 1.0 的标准 IEEE 754 表示)zuRegister.对,然后读回来。看原始16. System:

– 如果读回来是 `3F 80 00 00` → ABCD – 如果读回来是 `00 00 3F 80` → DCBA – 如果读回来是 `80 3F 00 00` → BADC – 如果读回来是 `00 00 80 3F` → CDAB

**Die三步:用 Wireshark 抓包**

Modbus TCP 的端口是 502。在 Wireshark 里过滤 `tcp.port == 502`,右键 → Decode As → Modbus/TCP。你看zu的AntwortMeldung里,Datenzone就是原始Befehlseite列。拿这个Sequenz跟你的Analyseergebnisse对比:

Wireshark Meldung:
    Modbus/TCP
        Transaction Identifier: 1
        Protocol Identifier: 0
        Length: 7
        Unit Identifier: 1
        Function Code: 3 (Read Holding Registers)
        Byte Count: 4
        Register 0 (40001): 0x41db
        Register 1 (40002): 0x851f

Wireshark 展示的Registerwert是它按大端Analyse的Ergebnis。如果你的 PLC 内部是 BADC,那 Wireshark 展示的 `0x41db` 和 `0x851f` 已经不是原始Befehlseite列了——原始Befehlseite列应该是 `DB 41 1F 85`。

这就是为什么TestsBefehlseite问题的时候,更应该看 **Wireshark 的16. System dump** 而不是它帮你Analyse好的Registerwert。在Meldung详情里展开 “Data” Feld,看原始Bytes。

**Die四步:在线BefehlseiteRechner**

概念很简单——你输入 4 个16. SystemBytes,它给你算出四种序对应的 float32、int32、uint32 值,一眼看出哪个是Richtig.答案。

如果没有现成的,自己写一个也不复杂:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

void print_float(uint8_t *bytes) {
    float f;
    memcpy(&f, bytes, 4);
    printf("float32: %fn", f);
}

int main() {
    uint8_t data[4] = {0x41, 0xDB, 0x85, 0x1F};

    uint8_t abcd[4] = {data[0], data[1], data[2], data[3]};
    uint8_t dcba[4] = {data[3], data[2], data[1], data[0]};
    uint8_t badc[4] = {data[1], data[0], data[3], data[2]};
    uint8_t cdab[4] = {data[2], data[3], data[0], data[1]};

    printf("ABCD: "); print_float(abcd);
    printf("DCBA: "); print_float(dcba);
    printf("BADC: "); print_float(badc);
    printf("CDAB: "); print_float(cdab);

    return 0;
}

附录:Modbus CRC-16 查表法 C 语言实现

Befehlseite问题经常和 CRC-Prüfung的Befehlseite混在一起——不是说算法有问题,而是 **CRC BerechnungErgebnis的两个Bytes在Meldung里谁先发谁后发**,不同Dokumentation写的不一样。

Modbus 规范明确:CRC-16 的**低Bytes先发**。也就是说 CRC BerechnungErgebnis是 `0x1234`,那么Meldung里最后两个Bytes是 `34 12`,不是 `12 34`。

下面的查表法实现直接返回符合 Modbus 规范的Ergebnis——低Bytes在前,可以直接追加zuMeldung末尾。

#include <stdint.h>

/* CRC-16 Modbus 查找表 */
static const uint16_t crc16_table[256] = {
    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
};

/*
 * Berechnung Modbus CRC-16,返回符合 Modbus 帧Das Format的Ergebnis
 * 低Bytes在前(低Adresse),可直接追加zuMeldung末尾
 */
uint16_t crc16_modbus(const uint8_t *data, uint16_t len)
{
    uint8_t crc_hi = 0xFF;
    uint8_t crc_lo = 0xFF;
    uint8_t idx;

    while (len--) {
        idx = crc_hi ^ *data++;
        crc_hi = crc_lo ^ (uint8_t)(crc16_table[idx] >> 8);
        crc_lo = (uint8_t)(crc16_table[idx] & 0xFF);
    }

    /*
     * 返回 16 位 CRC 值
     * 使用时追加zu Modbus RTU 帧末尾:
     *   frame[len]   = crc & 0xFF;        // 低Bytes在前
     *   frame[len+1] = (crc >> 8) & 0xFF; // hohe Bytes在后
     */
    return ((uint16_t)crc_lo << 8) | crc_hi;
}

这里有个细节值得注意:如果你看其他一些 CRC-16 的实现,有的返回Ergebnis是hohe Bytes在前、有的低Bytes在前。区别在于 `return` 语句——`(crc_lo << 8) | crc_hi` 和 `(crc_hi << 8) | crc_lo` 是反的。Modbus 规范说 CRC 域是「低Bytes在前」,所以上面这个实现返回一个 uint16_t,但你知道低 8 位是 CRC 的低Bytes,高 8 位是 CRC 的hohe Bytes。追加zuMeldung时,先写 `(crc & 0xFF)`,再写 `((crc >> 8) & 0xFF)`。

如果你用 `return (crc_hi << 8) | crc_lo`,那追加顺序就得反过来——这也是很多Tests时发现「CRC-Prüfung总是错」的原因之一。

有问题再聊。下次遇zuBefehlseite不对的情况,直接拿这四个Bytes跑一圈 ABCD/DCBA/BADC/CDAB,比你翻厂家手册快。

Techniken术语(共 8 个)—— Klick auf展开
Modbus RTU基于串行链路的ModbusVereinbarung,使用BinärsystemCodierung和CRC-Test
Modbus TCP基于Ethernet的ModbusVereinbarung变体,使用TCP/IP传输
FunktionscodeModbusFunktionscode指定读/写OperationenTypus,如01读线圈、03Lesen Sie das Register
Register.Modbus Register.存储Daten单元,Unterteilung线圈/离散输入/保持/输入Register.四类
PLC可编程逻辑Der Controller,Industrielle Automatisierung控制的核心Ausrüstung
SCADADatenerfassung与监视控制Das System,用于Fern entfernt监控工业过程
Der Sensor将物理Quantität转换为电信Nr.的检测装置
保持Register.Modbus 16位可读Schreiben von Daten,Adresse从40001开始
来源/Werkzeuge信息 —— Klick auf展开
来源 Modbus Chinesisches Netzwerk(modbus.cn) —— Inländisch führend.ModbusKommunikationsprotokoll Technologie Gemeinschaft Klassifizierung Technische Dokumentation von Modbus 字数 12299 字 · 阅读约 31 Unterteilung钟 更新 2026-08-20 永久链接 https://www.modbus.cn/45427.html
Empfohlene Werkzeuge: Modbus Debugger-Assistent WeChat-Applet
Modbus Chinesisches Netzwerk官方推出的Modbus Debugging-Tools,支持 Modbus RTU/TCP 实时KommunikationTests、Register.读写、线圈控制、Daten监控和MeldungUnterteilung析。 keine Installation erforderlich,Mikro-Suche「Modbus DebuggingAssistenten.」Benutzt werden kann.。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
Sollte man diese Informationen für ein echtes Projekt verwenden?

Gehen Sie zum Tool Center, um Nachrichten zu analysieren, CRC-Prüfungen und Geräte-Debugging zu erledigen, oder senden Sie Anforderungen für Auswahl - und Zugangsvorschläge.

Ingenieur Mitglied

Verwandeln Sie diesen Artikel in ein umsetzbares Debuggermaterial

Erweiterte Nachrichtenanalyse, Paket-Downloads, Codebeispiele, Engineering-Szenarien und Priority-Technischer Support sind für die Real-Projekt - Bereitstellung möglich.

Unbegrenzte Werkzeuge
Datenpaket und Codepaket
Vollständige Engineering Case-Basis
Vorrangiger Zugang zum technischen Support

Antwort veröffentlichen

Ihre E-Mail - Adresse wird nicht öffentlich gemacht. Erforderliche Elemente wurden verwendet * markiert.