This problem is more common than you imagine
Read the total positive active power of an energy meter using Modbus Poll, register 40001-40002, and return '42 C8 00 00'. You are confident that according to IEEE 754 float32, it is reasonable to spell 100.0 kWh. Using the same program, switch to another domestic power meter, read the same two registers, return '00 00 42 C8', spell out 0.0, and a straight line will be drawn directly on the PLC touch screen. You didn't write to the wrong register, nor did you miscalculate the CRC, it was purely a reverse byte order.
This is the core pain point of Modbus multi register data parsing. Section 4.2 "Data Encoding" of Modbus Protocol Specification V1.1b is very frank: "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. But the specification does not specify how to arrange 32-bit or 64 bit across multiple registers. The order of two consecutive registers and the order of the two bytes inside each register are left to the device manufacturer to decide.
This may sound like throwing the blame, but Modicon engineers in 1979 really didn't need to worry about 32-bit floating-point numbers - back then PLCs were even considered extravagant with 16 bit integers. Forty years later, we will have to bear it ourselves.
Four types of byte order, clearly illustrated in four images
Assuming you start reading the hold register from address 0 (Modbus address 40001) and request 2 registers. The data area in the message returned from the station is:
41 DB 85 1FThis is the raw byte sequence of an IEEE 754 single precision floating-point number. The true value is 27.440271... (approximately 27.44). Now let's see how to spell the four byte orders:
ABCD (Big End Sequence, Modbus Standard Arrangement)
The value of register 40001 is 0x41DB, and the value of register 40002 is 0x851F. Combine them into 32 bits in order:
register 40001(16 digits high) register 40002(Low 16 bits)
+--------+--------+ +--------+--------+
| 0x41 | 0xDB | | 0x85 | 0x1F |
+--------+--------+ +--------+--------+
A B C DMemory perspective (low address to high address): ` 41 DB 85 1F`
→ float32 = **27.44**
This is the default behavior for Schneider Modicon series PLCs (M340, M580, M241, etc.). Schneider, as the father of the Modbus protocol, has always followed this standard arrangement - storing 16 bits higher for smaller register addresses and 16 bits lower for larger register addresses. You can read back two WORD characters with READ_VAR and spell them together directly.
DCBA (small end sequence, completely reversed)
register 40001(But the content has been reversed) register 40002
+--------+--------+ +--------+--------+
| 0x1F | 0x85 | | 0xDB | 0x41 |
+--------+--------+ +--------+--------+Memory perspective: 1F 85 DB 41`
Float32=* * 1.5468 × 10 ⁻² ⁸ * * - an extreme value close to zero, and the screen display is basically 0.0 or overflow error.
Some domestic Modbus modules (especially some instruments based on 51 microcontrollers or early ARM bare running) use this method. Because their internal CPU is small endian, simply memcpy it is essentially 'too lazy to convert'. The intelligent power meter of a certain brand in Beijing is a typical case, and when you read it back, you must manually do byte reversal.
CDAB (Word Swap, Register Front Back Swap)
register 40002 Put the content at the front register 40001 Put the content at the end
+--------+--------+ +--------+--------+
| 0x85 | 0x1F | | 0x41 | 0xDB |
+--------+--------+ +--------+--------+Memory perspective: 85 1F 41 DB`
→ float32 = **-1.096 × 10⁻³³**
This is the actual behavior of Siemens S7-1200/S7-1500 when using Modbus TCP Server. The REAL type variables inside Siemens PLC occupy 4 bytes and are stored in the DB block in standard IEEE 754 format. However, when exposing the hold register to the Modbus client, the MB_SRVER instruction block sends the low byte of the low address register first, followed by the high byte, and then the low byte of the high address register, followed by the high byte. This is equivalent to swapping the two WORD versions of Schneider ABCD. If you use Modbus Poll to read floating-point numbers from Siemens PLC, and do not switch the Float in the Display settings to "CDAB" mode, the reading will definitely be astronomical numbers.
Libmodbus provides four sets of functions specifically for these four orders:` modbus_get_float_abcd()`、`modbus_get_float_dcba()`、`modbus_get_float_badc()`、`modbus_get_float_cdab()`。
BADC (byte swapping, internal inversion of each WORD)
register 40001(Internal Byte Swap) register 40002(Internal Byte Swap)
+--------+--------+ +--------+--------+
| 0xDB | 0x41 | | 0x1F | 0x85 |
+--------+--------+ +--------+--------+Memory perspective: DB 41 1F 85`
→ float32 = **-1.457 × 10¹⁷**
The Delta DVP series, Mitsubishi FX3U/FX5U, and some of the Huichuan series (H3u, H5u) actually use BADC arrangement when transmitting 32-bit data through Modbus RTU. Why? Because the internal data storage of these Japanese/Taiwanese PLCs itself is a 'small end within the word' - the low byte within a single 16 bit WORD comes first. At the Modbus transport layer, the high byte of WORD is sent first (as required by Modbus), so WORD reverses internally once. There is a Modbus big end order maintained between two WORD files. So the result is BADC: each internal byte of WORD is reversed, and the order of ABCD between WORD is maintained.
Let me give you a less rigorous example: Schneider gives you a whole box of puzzle pieces in order (ABCD), Siemens puts the left and right halves in place before giving them to you (CDAB), and Delta/Mitsubishi/Huichuan flips the inside of each half first and then gives them to you in order (BADC). As for DCBA, it's completely reversed with both the box and the content.
Data types are much more covert than byte order
The same 4-byte sequence '41 DB 85 1F' can be parsed according to different data types:
| parsing method | Result |
|---|---|
| float32(IEEE 754) | 27.44 |
| uint32 | 1,104,626,975 |
| int32 | 1,104,626,975 |
| Two uint16 splices | [16859, 34079] |
Look at this difference, float32's 2.744 and uint32's 1.1 billion are eight orders of magnitude apart. If you see a temperature measurement point on SCADA displaying 1104626975.0 ℃, don't rush to replace the sensor - first check if float32 has been resolved using uint32.
This is more covert than the byte order problem in practical engineering, because * * you get the right number, but the explanation is wrong * *. The byte order is incorrect, at least the numbers are messy, you can tell at a glance; The type is wrong, sometimes I just get lost.
Give an on-site example. The inverter of a certain photovoltaic power station sends the daily power generation through Modbus TCP, and the register mapping document reads "40001-40002: Daily Power Generation, 32-bit". The engineer used int32 to parse and read back a positive number, which seemed reasonable. They ran it on the energy consumption platform for a month. It was not until the financial reconciliation revealed a 30% discrepancy that it was discovered - the manufacturer actually stored * * uint32 * *, which was treated as a sign bit when parsed according to int32. As long as the power generation exceeds 2147483647 Wh (2.15 GWh), the parsed value becomes negative.
So when facing a multi register data point, what you need to confirm is not one variable, but three:
What is the data type (float32/int32/uint32/int64/uint64/double64/string)?
The third point is often overlooked. Many Modbus instruments multiply floating-point values by 10 or 100 and convert them to int32 to save registers and avoid floating-point transmission. For example, when reading int32=2744, the actual temperature is 27.44 ℃ (÷ 100) and the pressure is 274.4kPa (÷ 10). This scaling factor is only written in the device manual and cannot be seen on the protocol message.
Actual behavior of mainstream manufacturers
This section is the most practical part of this article. The following data is from actual debugging records and community verification, not copied from the product manual.
Schneider Modicon M340/M580/M241
The inventor of Modbus protocol, with the most 'standard' behavior. The Modbus TCP/RTU communication of QUANTUM, M340, M580, and M241 products, with 32-bit data (REAL, DINT) arranged in * * ABCD * * by default. The '% MW' register read back with READ_VAR stores the high 16 bits of the low bit address and the low 16 bits of the high bit address.
Special note: In Schneider's Unity Pro/EcoStruxure Control Expert, there is a layer of conversion between the internal storage of '% MD' (doubleword) and '% MF' (floating-point doubleword) and Modbus register mapping. `%MD0 'corresponds to'% MW0 'and'% MW1 ', where'% MW0 'is high and'% MW1 'is low - exactly ABCD.
Conclusion: When Schneider PLC is used as a Modbus slave, the upstream can be directly parsed according to ABCD.
Siemens S7-1200/S7-1500 (MB_SRVER)
This is the one with the most pits. Siemens calls the 'MB_SERVER' instruction block in TIA Portal as a Modbus TCP server. The REAL type variables (4 bytes) are arranged in two holding registers as * * CDAB * *.
Specific principle: The DB block of Siemens S7-1200/1500 defaults to "optimized block access", with opaque internal byte arrangement. Even if you cancel the optimization (set as standard access), the byte order of REAL type variables in the DB is still small (determined by Intel x86 processors). When 'MB_SRVER' maps the 4 bytes of this REAL to the Modbus hold register, it fills in byte order: the register with the smaller address stores bytes 0 and 1 (which are the lower bits of the REAL), and the register with the larger address stores bytes 2 and 3 (the higher bits).
The actual effect is CDAB. The Display settings of Modbus Poll must be switched to "CDAB" to see the correct floating-point numbers.
If you use Siemens PLC as a Modbus client (MB_CIENT) to read third-party Modbus slaves, you also need to pay attention - the arrangement of the two WORD words you read back depends on the byte order of the slave station, which is different from your Siemens internal REAL storage. At this point, using the ` READ-BIG `/` READ_LITTLE `/` WRITE-BIG `/` WRITE_LITTLE ` instructions provided by Boruto V16 and above to perform byte order conversion is much more reliable than manually using SWAP with shift.
Delta DVP Series
When using Delta DVP-ES2/EX2/SV2 and other series as Modbus RTU slaves, the arrangement of mapping 32-bit floating-point numbers (F registers) to Modbus hold registers is * * BADC * *.
This is related to the internal floating point storage method of Delta - the 32-bit data of Delta PLC follows the rule of "low bit storage low address" (small end), while Modbus RTU frames require each WORD to be sent with the big end. So the internal bytes of WORD were reversed once, but the internal order of PLC was maintained between the two WORD, ultimately becoming BADC.
If D0=F100.0 (floating point number) is displayed in your WPLSoft, read 40001-40002 in Modbus Poll, select "BADC" for Display, and you will see 100.0. The other three orders are all garbled.
Mitsubishi FX3U/FX5U (via Modbus communication module)
When installing the FX3U-485ADP-MB communication module on the Mitsubishi FX3U as a Modbus RTU master or slave, the arrangement of 32-bit data is also * * BADC * *. Similar to Delta's logic, the internal architecture of Japanese PLCs is mostly small end.
The FX5U itself comes with Modbus RTU function (via RS-485 terminal). When communicating with ADPRW commands, the 32-bit data read back needs to be manually pieced together using MOV commands. Read back two registers D100 and D101 from MODBUS, which store the raw data arranged in BADC. First, swap the bytes of each register using the SWAP instruction, and then combine the high and low bit concatenation instructions to form the final 32-bit value.
I have seen more than one project - Mitsubishi PLC reads the frequency of Schneider inverter, but reads it back in reverse without doing SWAP. After two days of debugging, I finally found out that it is the fault of byte order.
Omron CP1H/CP1L
When the Omron CP1 series uses CP1W-CIF11 (RS-485 option board) as a Modbus RTU slave, it maintains register mapping using the DM area. The arrangement of 32-bit data in two DM words depends on how you program it.
The CP1 series itself is a word addressing architecture and does not have a native dual word data type. Floating point numbers need to be spelled with two DM characters, and the order of arrangement is completely determined by your ladder diagram. But most standard implementations follow the ABCD arrangement, which is related to the CP1 series being derived from the early SYSMAC architecture. During actual debugging, it is recommended to first test with a known value (such as writing floating-point 1.0=` 3F800000 ` to two DM words).
Domestic PLC (Huichuan, BuKe)
Huichuan H3u/H5u series is a Modbus RTU slave station, with 32-bit data arranged in * * BADC * * by default, consistent with Delta. But Huichuan's AM600 series (based on the CODESYS platform) is different - the internal real number storage of CODESYS is a standard IEEE 754 big end structure, and the register arrangement exposed by Modbus TCP Server defaults to * * ABCD * *.
The Huichuan Easy series is a simplified version of H5u, used for small device control. The floating point arrangement of Modbus RTU slave stations is also * * BADC * *.
Kinco has some models that use CDAB, such as the K2 series. This is similar to Siemens, as the Buchner K2/K5 series draws on Siemens' touchscreen programming logic at the software level.
Summarize a quick reference table:
| Manufacturer/Equipment | Modbus 32-bit arrangement | Remark |
|---|---|---|
| Schneider M340/M580/M241 | ABCD | Modbus original factory standard |
| Siemens S7-1200/1500 | CDAB | MB_SRVER instruction block behavior |
| Delta DVP Series | BADC | Byte swapping within words |
| Mitsubishi FX3U/FX5U | BADC | SWAP needs to be spliced afterwards |
| Omron CP1H/CP1L | ABCD (mostly implemented) | Depends on the ladder diagram |
| Huichuan H3u/H5u | BADC | Consistent with Delta |
| Huichuan AM600 (CODESYS) | ABCD | CODESYS platform |
| Step Science K2 Series | CDAB | Some models |
| Typical domestic power meters | DCBA or ABCD | Need to test one by one |
Note: This table is a reference for actual testing and not an official commitment. The byte order behavior may vary among different models and firmware versions of the same manufacturer. Before entering the production environment, it is necessary to test and verify.
Byte order switching of Modbus Poll
Modbus Poll provides data format switching functionality in the Display settings. After selecting the data display area, right-click and go to Format to see:
Signed - Signed 16 Bit integer
Unsigned - unsigned 16 Bit integer
Hex - hexadecimal
Binary - binary
32-bit Signed - 32 Signed integer
32-bit Unsigned - 32 Unsigned integer
32-bit Float - 32 Floating point number
64-bit Float - 64 Double precision floating-point numberAfter selecting 32-bit Float, a byte order option will pop up on the right side:
Float Big-endian (ABCD) - big-endian
Float Little-endian (DCBA) - little-endian
Float Big-endian byte swap (BADC) - Big end byte exchange
Float Little-endian byte swap (CDAB) - Small end byte exchangeYou don't need to remember which manufacturer uses which sequence. When debugging, switch all four sequences and see which value is reasonable. Read a known measuring point with a range you have determined (such as an ambient temperature between 20-30 ℃), and only one of the four sequences will fall within a reasonable range.
Processing of 64 bit data
The byte order problem is more complex for scenarios that often require 4 registers (8 bytes) to represent, such as cumulative power consumption, total operating time, and large capacity flow accumulation. Because you need to handle two nested arrangements:
First layer: The byte order inside each 32-bit WORD (as in the previous 32-bit scenario, there are four possibilities)
Assuming you read 4 registers and return the original byte sequence:
41 DB 85 1F 42 C8 00 00If we follow the most regular arrangement of 'first high 32 bits, then low 32 bits, and each 32 bit is sorted by ABCD':
High 32-bit (ABCD): 41 DB 85 1F → float32 = 27.44
Low 32-bit (ABCD): 42 C8 00 00 → float32 = 100.0Note: This is a typical construction method for a accumulator. The upper 32 bits store the large part, the lower 32 bits store the decimal part, or the upper 32 bits store the integer part and the lower 32 bits store the decimal part. The specific combination depends on the equipment manual.
A more common scenario is the accumulated value of uint64. The four registers are arranged in ABCD order, from register address low to high:
Register 1 (MSW-High): A B → Byte 0-1
Register 2 (MSW-Low): C D → Byte 2-3 } Composed of high 32 bits
Register 3 (LSW-High): E F → Byte 4-5
Register 4 (LSW-Low): G H → Byte 6-7 } Form the lower 32 bitsSpell it as uint64: ` (uint64_t) (reg1<<48 | reg2<<32 | reg3<<16 | reg4) `, but this formula only holds when the CPU itself is large endian. You have to write C code on x86 (small end) in reverse.
Practical experience: When processing 64 bit data, don't worry about byte order first. Use hexadecimal to read the original values of the four registers, and observe whether the accumulator is growing. Observe the direction of growth - if the accumulator is increasing each time, see whether the change starts from the low address register or the high address register, and you will know the high and low bit arrangement.
Libmodbus does not directly provide 64 bit conversion functions, you need to spell them yourself:
uint16_t regs[4];
modbus_read_registers(ctx, addr, 4, regs);
// hypothesis ABCD arrange
uint64_t value = ((uint64_t)regs[0] << 48) |
((uint64_t)regs[1] << 32) |
((uint64_t)regs[2] << 16) |
((uint64_t)regs[3]);But this is just one of the four orders. If your device is arranged in BADC, you need to swap the bytes of each register first; If it is CDAB, the positions of the register pairs need to be swapped first.
Storage of strings
There are two mainstream modes for storing ASCII strings such as device serial number and firmware version number in Modbus registers:
**Mode 1: Store 2 characters per register (standard practice)**
Each 16 bit register stores two bytes, with one ASCII character per byte. For example, the serial number "SN20240001" (10 characters) requires 5 registers:
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'Simply read it back and convert it from ASCII value to character by byte, without worrying about byte order - the ASCII character 'A' is' 0x41 ', regardless of the big end or small end.
**Mode 2: Each register is only valid for low bytes**
Some instrument manufacturers are lazy, using only the low 8 bits of each register to store one character, and the high 8 bits are 0x00. The same 'SN20240001' requires 10 registers:
Register 1: 0x00 0x53 → Empty 'S'
Register 2: 0x00 0x4E → Empty 'N'
...This method wastes half of the register space, but the parsing is simple - just take the low byte of each register.
Reminder for stepping on pitfalls: The byte order problem of strings does not exist most of the time, because ASCII is a single byte encoding. The real problem is when you use UTF-16 or other multi byte encodings - but that's no longer within the scope of the Modbus protocol, it's purely an application layer encoding choice.
Debugging methodology
Don't guess, speak with data. The following is a standard byte order troubleshooting process:
**Step 1: Read the original hexadecimal system**
Cut the Display format into Hex using Modbus Poll, and first record the original register values. Regardless of the byte order, let's first look at the raw data.
Read 40001-40002 Return: 41 DB 85 1F**Step 2: Reverse calculate using known values**
Find a measuring point where you know the true value. For example, the current temperature is 27.44 ° C. If this value is read from the device's built-in display screen, take note. Then see which of the four sequences is spelled as 27.44, and that is the correct answer.
A better approach is to proactively write a known value. Write '0x3F800000' (which is the standard IEEE 754 representation of float32=1.0) to the register pair using function code 16 (write multiple registers), and then read it back. Look at the original hexadecimal:
-If it reads back as' 3F 80 00 00 '→ ABCD
**Step 3: Capture packets with Wireshark**
The port for Modbus TCP is 502. Filter 'tcp. port==502' in Wireshark, right-click → decode As → Modbus/TCP. In the response message you see, the data area is the original byte sequence. Compare this sequence with your parsing result:
Wireshark message:
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): 0x851fThe register values displayed by Wireshark are the result of its big endian parsing. If your PLC is internal to BADC, the '0x41db' and '0x851f' displayed by Wireshark are no longer the original byte sequence - the original byte sequence should be 'DB 41 1F 85'.
That's why when debugging byte order issues, you should look more at Wireshark's hexadecimal dump instead of the register values it parses for you. Expand the "Data" field in the message details to see the original bytes.
**Step 4: Online Byte Order Calculator**
The concept is simple - if you input 4 hexadecimal bytes, it will calculate the float32, int32, and uint32 values corresponding to the four orders, and you can tell at a glance which one is the correct answer.
If there is no ready-made one, writing one yourself is not complicated:
#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;
}Appendix: Modbus CRC-16 Table lookup Method C Language Implementation
The issue of byte order is often mixed with the byte order of CRC check - not because there is an algorithm problem, but because the two bytes of CRC calculation result are sent first and then in the message, which is written differently in different documents.
The Modbus specification is clear: CRC-16's * * low byte first send * *. That is to say, if the CRC calculation result is' 0x1234 ', then the last two bytes in the message are' 34 12 ', not' 12 34 '.
The table lookup method below directly returns results that comply with the Modbus specification - low bytes come first and can be appended directly to the end of the message.
#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
};
/*
* 计算 Modbus CRC-16,返回符合 Modbus 帧格式的结果
* 低字节在前(低地址),可直接追加到报文末尾
*/
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 值
* 使用时追加到 Modbus RTU 帧末尾:
* frame[len] = crc & 0xFF; // 低字节在前
* frame[len+1] = (crc >> 8) & 0xFF; // 高字节在后
*/
return ((uint16_t)crc_lo << 8) | crc_hi;
}There is a detail worth noting here: if you look at other CRC-16 implementations, some return results with high bytes first and some low bytes first. The difference is that the 'return' statement - '(crc_lo<<8) | crc_hi' and '(crc_hi<<8) | crc_lo' are reversed. The Modbus specification states that the CRC field is' low byte first ', so the above implementation returns a uint16ut, but you know that the low 8 bits are the low bytes of the CRC, and the high 8 bits are the high bytes of the CRC. When adding to a message, write '(crc&0xFF)' first, then '(crc>>8)&0xFF)'.
If you use 'return (crc_hi<<8) | crc_lo', the order of append must be reversed - this is also one of the reasons why many debuggers find that the CRC check is always incorrect.
Let's talk if there are any issues. Next time you encounter a situation where the byte order is incorrect, simply run around ABCD/DCBA/BADC/CDAB with these four bytes, which is faster than flipping through the manufacturer's manual.
Leave a Reply