端序问题分析

大小端模式是计算机处理多字节数字时内存存储的顺序,影响通信协议和数据传输。大端模式将高字节存放在低地址,小端模式反之。转换方法包括位移操作。常见于网络数据解析和跨平台通信。Intel的x86系列是小端,PowerPC和某些ARM是大端。判断和转换是解决字节序不一致的关键。

通信协议中的数据传输、数组的存储方式、数据的强制转换等这些都会牵涉到大小端问题。

1、为什么会有大小端模式之分呢?

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。

1个byte的存储不涉及端序,端序是字节之间,而不是bit之间。

且地址不管是对于大小端工作的CPU来说都是一样的从小到大的地址增长。

所以端序是指cpu读写内存时的高低顺序。

但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的int型。另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。

对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。

而大小端只是表示存储数据的两种顺序方式,默认内存的读写方式是从低位到高位,处理数据是低位在地址低位,还是高位在地址低位,分为小端和大端两种模式。

因为最开始制定时,没有统一的标准,因而导致了大端和小端两种模式。

同时,什么时候需要注意大端和小端模式呢?

一般是涉及到按位对数据进行处理、网络数据在不同CPU上传输的时候,考虑解析数据的顺序时候;网络上传输的数据都是以大端的形式存放的,所以拿到网络的数据,需要转换为主机字节序。这也是为了屏蔽不同主机的端序问题。

二、什么是大端和小端?

大小端的差异在于存放数据的顺序不同,跟内存本身没有关系,同一段内存,不同的CPU操作同一个数,导致数据存放顺序不同而已。所以大端写入的数据,如果是小端cpu读出,就需要进行端序swap,才能进行计算。

大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。

小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

假如32位宽(uint32_t)的数据0x12345678,从地址0x08004000开始存放:

 

大小端转换:

开篇说了,实际应用中,大小端应用的地方很多通信协议、数据存储等。如果字节序不一致,就需要转换。
只要你理解其中原理(高低顺序),转换的方法很多,下面简单列列两个。

1.对于16位字数据

#define BigtoLittle16(A)   (( ((uint16)(A) & 0xff00) >> 8)    | \
                                       (( (uint16)(A) & 0x00ff) << 8))
2.对于32位字数据

#define BigtoLittle32(A)   ((( (uint32)(A) & 0xff000000) >> 24) | \
                                       (( (uint32)(A) & 0x00ff0000) >> 8)   | \
                                       (( (uint32)(A) & 0x0000ff00) << 8)   | \
                                       (( (uint32)(A) & 0x000000ff) << 24))

大小端主要由CPU 决定,但是如ARM可以选择大小端模式,准确说是由运行环境决定大小端模式。

可以通过向内存写多字节数据,然后按照内存由小到大的数据读取,判断大小端模式。

1)就CPU架构而言

小端模式:STM32 X86 
大端模式:PowerPC IBM Sun
ARM 既可以是小端也可以是大端。

Intel的80x86系列芯片是唯一还在坚持使用小端的芯片,ARM芯片默认采用小端,但可以切换为大端;而MIPS等芯片要么采用全部大端的方式储存,要么提供选项支持大端——可以在大小端之间切换。

以下为参考文章:
————————————————
版权声明:本文为优快云博主「查尔斯是码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/weixin_42121713/article/details/105043335

————————————————
版权声明:本文为优快云博主「幽幽起来写代码啦」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/weixin_44890250/article/details/109186636

 

<think>我们正在处理MODBUS TCP通信中的大小问题。根据引用[1][2][3],Modbus协议默认使用大(Big-Endian),但实际应用中可能会遇到小(Little-Endian)的设备。因此,我们需要能够灵活处理不同字节的转换。 在HslCommunication库中,字节顺转换通过`IByteTransform`接口实现。该接口提供了多种方法用于转换不同数据类型。我们可以通过设置`ModbusTcpNet`对象的`ByteTransform`属性来控制字节顺。 ### 处理步骤 1. **理解大小**: - **大**:高位字节在前(如Modbus默认) - **小**:低位字节在前(如x86架构CPU) 2. **配置字节转换器**: - HslCommunication默认使用大(符合Modbus标准) - 若设备使用小,需调整转换设置 3. **数据类型转换注意事项**: - 单寄存器(16位):只需关注字节顺(高/低字节交换) - 多寄存器(32/64位):还需考虑寄存器排列顺(字) ### 示例代码:配置字节顺 ```csharp using HslCommunication; using HslCommunication.ModBus; // 创建Modbus TCP客户 ModbusTcpNet modbusClient = new ModbusTcpNet("192.168.1.10", 502, 1); // 默认大(Modbus标准)无需修改 // 若设备使用小(字节内交换) modbusClient.ByteTransform.IsByteReverse = true; // 启用字节反转 // 若设备使用小且寄存器顺也反转(字内交换) modbusClient.ByteTransform.IsTransformReverseWord = true; // 启用字反转 ``` ### 字节顺处理场景 | 设备类型 | 配置方式 | 说明 | |----------------|------------------------------------------|------------------------------------------| | 标准Modbus设备 | 无需修改 | 默认大,字节和字顺均不反转 | | 字节小设备 | `IsByteReverse = true` | 每个寄存器内高低字节交换 | | 字小设备 | `IsTransformReverseWord = true` | 多寄存器数据整体反转字顺(如32位数据) | | 完全小设备 | `IsByteReverse = true` + `IsTransformReverseWord = true` | 同时应用字节反转和字反转 | ### 完整示例:带字节配置的批量写入 ```csharp using System; using HslCommunication; using HslCommunication.ModBus; public class ModbusWriterWithEndianness { public static void Main() { ModbusTcpNet modbusClient = new ModbusTcpNet("192.168.1.10", 502, 1); // 配置字节顺(根据设备调整) modbusClient.ByteTransform.IsByteReverse = true; // 示例:启用字节反转(小字节) modbusClient.ByteTransform.IsTransformReverseWord = false; // 不反转字顺 try { modbusClient.ConnectServer(); // 准备数据(混合类型) object[] data = { (short)1000, 123456, 3.14f }; // 转换并写入 ushort[] registers = ConvertToRegisters(data, modbusClient.ByteTransform); OperateResult result = modbusClient.Write("40001", registers); Console.WriteLine(result.IsSuccess ? "成功" : $"失败: {result.Message}"); } finally { modbusClient.ConnectClose(); } } private static ushort[] ConvertToRegisters(object[] values, IByteTransform transform) { // 计算总寄存器数 int totalRegisters = 0; foreach (var value in values) { totalRegisters += GetRegisterCount(value); } ushort[] registers = new ushort[totalRegisters]; int index = 0; foreach (var value in values) { byte[] bytes = GetBytes(value, transform); for (int i = 0; i < bytes.Length; i += 2) { // 将两个字节转换为一个ushort(寄存器值) registers[index++] = transform.TransUInt16(bytes, i); } } return registers; } private static int GetRegisterCount(object value) { return value switch { short or ushort => 1, int or float => 2, long or double => 4, _ => throw new NotSupportedException() }; } private static byte[] GetBytes(object value, IByteTransform transform) { return value switch { short s => transform.TransByte(s), ushort us => transform.TransByte(us), int i => transform.TransByte(i), float f => transform.TransByte(f), long l => transform.TransByte(l), double d => transform.TransByte(d), _ => throw new NotSupportedException() }; } } ``` ### 关键说明 1. **字节反转(`IsByteReverse`)**: - 控制单个寄存器内字节顺 - `true`:小模式(低位字节在前) - `false`:大模式(默认) 2. **字反转(`IsTransformReverseWord`)**: - 控制多寄存器数据中寄存器的排列顺 - `true`:反转寄存器顺(如将`[寄存器A, 寄存器B]`转为`[寄存器B, 寄存器A]`) - 适用于32位/64位数据 3. **组合应用**: - 完全小设备:同时启用字节反转和字反转 - 仅字节小:只启用字节反转 - 仅字小:只启写字反转 > 注:具体配置需根据设备手册确定[^2][^3] ### 调试建议 - 使用Wireshark捕获MODBUS报文验证字节顺 - 先测试单寄存器写入确认字节顺 - 再扩展至多寄存器数据类型 §§相关问题§§ 1. 如何通过Wireshark分析MODBUS TCP通信中的字节顺? 2. 在HslCommunication中如何实现自定义字节转换器? 3. 不同品牌的PLC设备在MODBUS实现中有哪些常见差异? 4. 如何动态切换字节顺配置以适应多设备环境?</think>### MODBUS TCP 通信中的大小处理方案 在 MODBUS TCP 通信中正确处理大小至关重要,这直接关系到数据的正确解析。以下是完整的处理方案: #### 一、核心概念解析 1. **字节类型**: - **大 (Big-Endian)**:高位字节在前(如 `0x1234` 发送为 `0x12 0x34`),MODBUS 协议默认采用[^2][^3] - **小 (Little-Endian)**:低位字节在前(如 `0x1234` 发送为 `0x34 0x12`),常见于 x86/x64 CPU[^3] 2. **多寄存器处理**: - 32/64位数据(int/float/double)需要跨多个寄存器 - 寄存器间排存在多种约定: - **ABCD**:标准大(寄存器顺:高位→低位) - **CDAB**:寄存器内大,寄存器间小 - **BADC**:寄存器内小,寄存器间大 - **DCBA**:完全小[^2] #### 二、HslCommunication 的字节配置 通过 `IByteTransform` 接口实现灵活控制: ```csharp // 创建 Modbus 客户 ModbusTcpNet modbusClient = new ModbusTcpNet("192.168.1.10", 502, 1); // ===== 字节配置方案 ===== // 方案1:标准大 (ABCD) - MODBUS 默认 modbusClient.ByteTransform = new RegularByteTransform(); // 方案2:小 (DCBA) modbusClient.ByteTransform = new ReverseByteTransform(); // 方案3:自定义混合字节 (CDAB) var customTransform = new RegularByteTransform() { IsByteReverse = false, // 寄存器内不反转字节 IsTransformReverseWord = true // 反转寄存器顺 }; // 方案4:BADC 模式 var badcTransform = new RegularByteTransform() { IsByteReverse = true, // 寄存器内反转字节 IsTransformReverseWord = false // 不反转寄存器顺 }; ``` #### 三、完整数据读写示例 ```csharp using HslCommunication; using HslCommunication.ModBus; using System; public class ModbusEndiannessDemo { public static void Main() { ModbusTcpNet modbusClient = new ModbusTcpNet("192.168.1.10", 502, 1); // 配置为小设备 modbusClient.ByteTransform = new ReverseByteTransform(); try { modbusClient.ConnectServer(); // 写入混合数据类型(小) ushort startAddress = 40001; object[] writeValues = { (short)0x1234, 0x56789ABC, 3.14159f, 2.71828 }; WriteMixedData(modbusClient, startAddress, writeValues); // 读取验证 object[] readValues = ReadMixedData(modbusClient, startAddress, writeValues); Console.WriteLine("验证结果: " + (readValues[0].Equals(writeValues[0]) && readValues[1].Equals(writeValues[1]) ? "成功" : "失败")); } finally { modbusClient.ConnectClose(); } } // 混合数据类型写入 private static void WriteMixedData(ModbusTcpNet client, ushort address, object[] values) { ushort[] registers = ConvertToRegisters(values, client.ByteTransform); OperateResult result = client.Write(address.ToString(), registers); if (!result.IsSuccess) throw new Exception($"写入失败: {result.Message}"); } // 混合数据类型读取 private static object[] ReadMixedData(ModbusTcpNet client, ushort address, object[] template) { int totalRegisters = CalculateTotalRegisters(template); ushort[] registers = client.Read(address.ToString(), (ushort)totalRegisters).Content; return ParseRegisters(registers, template, client.ByteTransform); } // 寄存器转换辅助方法 private static ushort[] ConvertToRegisters(object[] values, IByteTransform transform) { int totalRegisters = CalculateTotalRegisters(values); ushort[] registers = new ushort[totalRegisters]; int index = 0; foreach (var value in values) { byte[] bytes = GetBytes(value, transform); for (int i = 0; i < bytes.Length; i += 2) { registers[index++] = transform.TransUInt16(bytes, i); } } return registers; } private static int CalculateTotalRegisters(object[] values) { int count = 0; foreach (var value in values) { count += value switch { short or ushort => 1, int or float => 2, long or double => 4, _ => throw new NotSupportedException() }; } return count; } private static byte[] GetBytes(object value, IByteTransform transform) { return value switch { short s => transform.TransByte(s), ushort us => transform.TransByte(us), int i => transform.TransByte(i), float f => transform.TransByte(f), long l => transform.TransByte(l), double d => transform.TransByte(d), _ => throw new NotSupportedException() }; } private static object[] ParseRegisters(ushort[] registers, object[] template, IByteTransform transform) { object[] result = new object[template.Length]; int registerIndex = 0; for (int i = 0; i < template.Length; i++) { int regCount = template[i] switch { short or ushort => 1, int or float => 2, long or double => 4, _ => 0 }; byte[] buffer = new byte[regCount * 2]; for (int j = 0; j < regCount; j++) { byte[] regBytes = transform.TransByte(registers[registerIndex++]); Array.Copy(regBytes, 0, buffer, j * 2, 2); } result[i] = template[i] switch { short => transform.TransInt16(buffer, 0), ushort => transform.TransUInt16(buffer, 0), int => transform.TransInt32(buffer, 0), float => transform.TransSingle(buffer, 0), long => transform.TransInt64(buffer, 0), double => transform.TransDouble(buffer, 0), _ => null }; } return result; } } ``` ### 四、最佳实践建议 1. **设备兼容性处理**: ```csharp // 自动检测设备字节 bool isLittleEndianDevice = DetectDeviceEndianness(modbusClient); modbusClient.ByteTransform = isLittleEndianDevice ? new ReverseByteTransform() : new RegularByteTransform(); ``` 2. **调试与验证**: - 使用 Wireshark 抓包分析原始报文[^4] - 单步调试验证字节数组转换结果 - 创建字节测试寄存器(如写入 `0x1234` 验证读取值) 3. **性能优化**: - 批量读写减少请求次数 - 缓存字节配置避免重复检测 - 使用内存流优化字节数组操作 > 关键提示:不同厂商设备可能采用非标准字节,务必参考设备手册确认具体实现[^2][^5] ### 五、常见问题解决方案 1. **数据错位**: - 检查 `IsByteReverse` 和 `IsTransformReverseWord` 的组合设置 - 验证多寄存器数据的字顺(ABCD/CDAB/BADC/DCBA) 2. **浮点数异常**: - 确认 IEEE 754 格式兼容性 - 使用 `BitConverter` 进行手动转换验证: ```csharp float testValue = 3.14f; byte[] bytes = BitConverter.GetBytes(testValue); if (BitConverter.IsLittleEndian) Array.Reverse(bytes); ``` 3. **混合设备环境**: ```csharp // 为不同设备创建独立客户实例 ModbusTcpNet deviceA = new ModbusTcpNet("192.168.1.10", 502, 1) { ByteTransform = new RegularByteTransform() }; ModbusTcpNet deviceB = new ModbusTcpNet("192.168.1.11", 502, 1) { ByteTransform = new ReverseByteTransform() }; ``` 通过正确配置字节转换策略,可确保 MODBUS TCP 通信中各种数据类型的准确解析,满足不同工业设备的兼容性需求[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值