英文:
Guaranteeing bit endianness of structs for serial data transmissions (SPI)
问题
I'm trying to send data over serial peripheral interface (SPI) which takes a buffer and sends it MSbit first through to data out. For this to succeed, I need my structs to be translated to a bit buffer in exactly the right way and I'm not really sure how I can guarantee cross platform that it always works.
Here's the struct that I want to send:
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;
};
As you can see the struct message is 2 bytes in size and I suppose it will be layed out differently on big endian / little endian systems. But what about the bit ordering? In theory, the bits could also be layed out either way regardless of the byte endianness, or can't they? But the SPI driver only accepts exactly one solution where most significant bytes are sent first and most significant bits are also sent first. Here's how I think about it:
Now my questions:
- Do htonl and htons also flip the bit ordering?
- Does something like big endian with LSbit first even exist in real life? Or is the byte ordering always indicative of the bit ordering?
- How can I guarantee cross platform that the datastruct in question is always layed out exactly right in the bit buffer?
英文:
I'm trying to send data over serial peripheral interface (SPI) which takes a buffer and sends it MSbit first through to data out. For this to succeed, I need my structs to be translated to a bit buffer in exactly the right way and I'm not really sure how I can guarantee cross platform that it always works.
Here's the struct that I want to send:
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;
};
As you can see the struct message is 2 bytes in size and I suppose it will be layed out differently on big endian / little endian systems. But what about the bit ordering? In theory, the bits could also be layed out either way regardless of the byte endianness, or can't they? But the SPI driver only accepts exactly one solution where most significant bytes are sent first and most significant bits are also sent first. Here's how I think about it:
Now my questions:
-
Do htonl and htons also flip the bit ordering?
-
Does something like big endian with LSbit first even exist in real life? Or is the byte ordering always indicative of the bit ordering?
-
How can I guarantee cross platform that the datastruct in question is always layed out exactly right in the bit buffer?
答案1
得分: 1
比特顺序在大多数(或者说所有?)处理器中实际上并不是一个问题,因为内存是以字节为单位寻址的。可以将比特看作相邻的,字节则叠放在一起。
例如:
uint8_t c = 13;
bool lsBit = c & 0x01;
lsBit 总是最不重要的比特。它在寄存器中的物理位置并不重要,因为我们不能像 c[n]
一样寻址它,以获取第 n 位的值。对于位移操作也是一样的:
c << 1
总是等同于 c*2
。
字节序(即字节的排列顺序)的问题出现在以下情况:
uint32_t someValue = 123;
uint8_t* smallptr = (uint8_t*) &someValue;
*smallptr == ???
现在我们取一个32位值的地址,并将其解释为一个8位值。因此,我们现在处理的是地址,因为有4个地址指向 someValue
的4个字节,smallptr 指向其中的哪一个取决于字节序。
尽管如此,传输线上是有顺序的。因此,将位放置在正确的顺序是外设(或者如果你在进行位操作,那就是驱动程序)的责任。大多数协议实际上规定了哪个比特位先传输。但是维基百科关于 SPI 协议的说法是:
通常,数据是按照最重要的位(MSB)先传输的。
这似乎并不是完全强制执行的。
结构体不强制规定字节序/打包方式。因此,不能保证 sizeof(message)==2
。或者说顺序保持不变(尽管这很可能)。处理这个问题的方法是使用 packed
属性,但这样可能会引发其他问题:https://stackoverflow.com/questions/8568432/is-gccs-attribute-packed-pragma-pack-unsafe
我认为最合适的做法是编写一个函数,类似于:
message::serialize(uint8_t* dest){
dest[0] = data;
dest[1] = channel;
}
尽管你很可能也可以使用结构体的打包特性。
英文:
Bit ordering not really a thing in most (all?) processors, since memory is addressed in bytes. Look at it as if all the bits are next to eachother, and bytes are stacked on top of eachother.
For example:
uint8_t c = 13;
bool lsBit = c & 0x01;
lsBit is always the least significant bit. It doesn't matter where it is physically stored in register. Because we cannot address it like c[n]
to get the n'th bit.
Same for bitshifts:
c << 1
is always the same as c*2
.
Where endianness comes into play is when you do this:
uint32_t someValue = 123;
uint8_t* smallptr = (uint8_t*) &someValue;
*smallptr == ???
Now we take an address of a 32 bit value, and interpret it as an 8bit value. So now we are dealing with addresses, since there are 4 addresses that point to the 4 bytes of someValue
, and which one does smallptr point to, depends on your endianness.
That being said, on wires there is an order. So then it is the responsibility of the peripheral (or the driver if you are bit-banging) to put the bits in the right order. Most protocols actually define which bit goes first. But wikipedia says this about spi:
> Data is usually shifted out with the most significant bit first.
Which seems like it not entirely enforced.
Structs don't enforce byte ordering/packing. So it is not guaranteed that sizeof(message)==2
. Or that the order stays the same (though that is very likely).
The hacky way to do it is to use the packed
attribute. But then you might have other issues: https://stackoverflow.com/questions/8568432/is-gccs-attribute-packed-pragma-pack-unsafe
I think the most proper way to do this is to write a function like:
message::serialize(uint8_t* dest){
dest[0] = data;
dest[1] = channel;
}
Although you will most likely get away with struct packing.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论