保障SPI串行数据传输中结构体的位端序一致性
背景
需要通过MSbit优先的SPI接口发送自定义结构体 message,但跨平台下结构体的内存布局(字节序)存在差异,同时需明确位序相关问题,确保传输数据的正确性。
结构体定义如下:
using data_settings = uint8_t; enum class channel_settings : uint8_t { power_down_setting = 0, DA1, DA2, DA3, DA4, DA5, DA6, DA7, DA8, power_down_release, NA1, NA2, io_da_select, io_serial_parallel, io_parallel_serial, io_status_setting, }; struct message // send this over SPI { data_settings data; channel_settings channel; };
问题解答
1. htonl/htons 是否会翻转位排序?
不会。这些函数仅负责转换多字节数据的字节顺序(处理大端/小端字节序差异),完全不涉及单个字节内部的位排列顺序。位的存储顺序属于CPU架构的内部实现细节,htonl/htons 不会对其做任何修改。
2. 现实中是否存在大端字节序但LSbit优先的情况?字节序能否指示位序?
字节序(大端/小端)和位序(MSbit/LSbit优先)是两个完全独立的概念:
- 字节序描述多字节数据中字节的排列顺序;
- 位序描述单个字节内部位的发送/存储顺序。
现实中几乎不存在大端字节序且LSbit优先的通用计算平台,绝大多数主流架构(x86、ARM、RISC-V等)均采用MSbit优先的位序。但理论上两者没有绑定关系,特殊嵌入式硬件可能存在例外。不过对于SPI传输来说,驱动通常允许配置位序(你的场景已明确是MSbit优先),无需过度考虑极端硬件情况。
3. 如何跨平台保证结构体转换为位缓冲区的正确性?
核心原则是完全避免依赖结构体的内存布局,通过手动序列化的方式构建符合SPI要求的字节缓冲区,具体步骤如下:
步骤1:明确SPI传输顺序
根据需求,SPI要求:
- 最高有效字节(MSB)优先发送;
- 每个字节内部最高有效位(MSbit)优先发送。
首先需确定 message 中哪个字段是高字节、哪个是低字节。例如,假设硬件要求先发送 channel(高字节),再发送 data(低字节),则按此顺序构建缓冲区。
步骤2:手动序列化实现
编写跨平台的序列化函数,直接控制每个字节的位置和内容,不受系统字节序影响:
#include <cstdint> #include <array> // 序列化message到SPI发送缓冲区 // 假设SPI要求先发送channel(高字节),再发送data(低字节) std::array<uint8_t, 2> serialize_message(const message& msg) { std::array<uint8_t, 2> buffer{}; // 将channel转换为uint8_t,放入高字节位置(缓冲区第一个元素) buffer[0] = static_cast<uint8_t>(msg.channel); // 将data放入低字节位置(缓冲区第二个元素) buffer[1] = msg.data; return buffer; }
步骤3:验证位序一致性
由于SPI要求MSbit优先发送,而主流CPU的位存储顺序与发送顺序一致(即字节的第7位是MSbit,会被先发送),因此无需对单个字节内的位做翻转。如果遇到极端硬件要求LSbit优先,则需要额外编写位翻转函数处理每个字节:
// 单个字节的位翻转(LSbit转MSbit,反之亦然) uint8_t reverse_bits(uint8_t byte) { byte = ((byte >> 1) & 0x55) | ((byte << 1) & 0xAA); byte = ((byte >> 2) & 0x33) | ((byte << 2) & 0xCC); byte = ((byte >> 4) & 0x0F) | ((byte << 4) & 0xF0); return byte; }
但你的场景中SPI是MSbit优先,因此不需要此步骤。
关键注意事项
- 永远不要直接使用
memcpy将结构体复制到缓冲区,这会依赖系统的字节序和结构体对齐规则,完全不具备跨平台性; - 枚举类型转换为
uint8_t时,确保枚举的底层类型是uint8_t(你的代码中已明确指定,无需额外处理); - 如果后续结构体扩展为多字节字段(如
uint16_t),则需要使用htons转换字节序后再放入缓冲区。
内容的提问来源于stack exchange,提问作者glades

