英文:
Bit-packing Problem in a Union (Register Mapping)
问题
以下是翻译好的部分:
我正在尝试创建一个联合体来映射寄存器映射中的一些位字段。我拥有的代码如下:
typedef union __attribute__((packed)) {
struct {
uint8_t MODE:3;
uint8_t VSHCT:3;
uint8_t VBUSCT:3;
uint8_t AVG:3;
uint8_t RSVD:3;
uint8_t RST:1;
};
struct {
uint8_t lsbyte:8;
uint8_t msbyte:8;
};
uint16_t w;
} CON_MAP_t;
我正在使用以下方式初始化字段:
CON_MAP_t map = {
.RST = 0,
.RSVD = 4,
.AVG = 0,
.VBUSCT = 4,
.VSHCT = 4,
.MODE = 7
}
到目前为止一切正常,没有编译器问题或警告。
我期望二进制/十六进制表示应为 01000001_00100111 / 0x4127。
然而,在调试器中,'w' 的值是:00000100_00100111。最不显著字节是正确的,但最显著字节不正确。
我不确定是否在这里漏掉了一些基本的东西,或者我已经盯着它看得太久了,但任何见解都将不胜感激!
我正在使用的工具和设备是:
MPLABX v6.05
最新的XC32编译器
设备是一款PIC32MX130F064D,使用PICKIT4进行调试。
英文:
I am trying to get a union together to map out some bit fields in a register map. The code I have is the following:
typedef union __attribute__((packed)) {
struct {
uint8_t MODE:3;
uint8_t VSHCT:3;
uint8_t VBUSCT:3;
uint8_t AVG:3;
uint8_t RSVD:3;
uint8_t RST:1;
};
struct {
uint8_t lsbyte:8;
uint8_t msbyte:8;
};
uint16_t w;
} CON_MAP_t;
I am initializing the fields with:
CON_MAP_t map = {
.RST = 0,
.RSVD = 4,
.AVG = 0,
.VBUSCT = 4,
.VSHCT = 4,
.MODE = 7
}
So far this is all fine, no compiler issues or warnings.
I expect the binary/hex representation to be 01000001_00100111 / 0x4127.
However, in the debugger I end up with a value for 'w' of: 00000100_00100111
The least significant byte is correct, but the msb(yte) is not.
I am not sure if I'm missing something fundamental here and I've just been staring at it too long, but any insight would be highly appreciated!
I am using:
MPLABX v6.05
Latest XC32 Compiler
Device is a PIC32MX130F064D debugging with a PICKIT4.
答案1
得分: 2
如在注释和其他帖子中所指出,例如 https://stackoverflow.com/questions/6043483/why-bit-endianness-is-an-issue-in-bitfields,C 标准对位域的定义非常不明确,甚至令人发笑。将像 MPLAB 这样声名狼藉的编译器套在这个混乱中,你将会得到一场灾难。
我的建议是忘记你曾经听说过位域,使用 100% 可移植、标准化的整数常量通过宏来编写代码。在这种特定情况下,没有明显的需要在字和字节之间进行类型转换 - 你为什么需要访问字节,当位域到处都是呢?
假设你的硬件寄存器命名为 `CON_MAP`,并且 CPU 是小端序的(大小端序对于你的位域代码很重要,但对于我下面的解决方案不重要),那么:
```c
#define CON_MAP (*(volatile uint16_t*)0x12345678u) // 物理地址
#define CON_MAP_MODE_MASK 0x3u
#define CON_MAP_MODE_BIT 0u
#define CON_MAP_MODE(val) ( ((val) & CON_MAP_MODE_MASK) << CON_MAP_MODE_BIT )
#define CON_MAP_VSHCT_MASK 0x3u
#define CON_MAP_VSHCT_BIT 2u
#define CON_MAP_VSHCT(val) ( ((val) & CON_MAP_VSHCT_MASK) << CON_MAP_VSHCT_BIT )
...
用法:
CON_MAP = CON_MAP_MODE(2u) |
CON_MAP_VSHCT(3u) |
CON_MAP_VBUSCT(0u) |
... ;
值如 2u
、3u
等最好由具有一些有意义的名称替代,以防你对它们有一些有意义的名称,比如:CON_MAP_MODE(FANCY_MODE | SPECIAL_MODE)
。
上述是在嵌入式系统中实现硬件寄存器的几种常见行业标准之一。更多信息请参见:如何从固件中访问硬件寄存器?
<details>
<summary>英文:</summary>
As noted in comments and in other posts such as https://stackoverflow.com/questions/6043483/why-bit-endianness-is-an-issue-in-bitfields, bit-fields are so poorly defined by the C standard that it's not even funny. Put a nasty reputation compiler like MPLAB on top of that mess and you'll have a recipe for disaster.
My advise is to forget that you ever heard of bit-fields and write the code using 100% portable, standardized integer constants through macros. In this specific case there's no obvious need to type pun between word and byte - why would you ever need to byte access this since the bit-fields are all over the place?
Assuming your hardware register is named `CON_MAP` and the CPU is little endian (endianess matters for your bit-field code but not for my solution below) then:
```c
#define CON_MAP (*(volatile uint16_t*)0x12345678u) // physical address
#define CON_MAP_MODE_MASK 0x3u
#define CON_MAP_MODE_BIT 0u
#define CON_MAP_MODE(val) ( ((val) & CON_MAP_MODE_MASK) << CON_MAP_MODE_BIT )
#define CON_MAP_VSHCT_MASK 0x3u
#define CON_MAP_VSHCT_BIT 2u
#define CON_MAP_VSHCT(val) ( ((val) & CON_MAP_VSHCT_MASK) << CON_MAP_VSHCT_BIT )
...
Usage:
CON_MAP = CON_MAP_MODE(2u) |
CON_MAP_VSHCT(3u) |
CON_MAP_VBUSCT(0u) |
... ;
The values 2u
, 3u
etc should ideally be replaced by named constants in case you have some meaningful names for them. Like: CON_MAP_MODE(FANCY_MODE | SPECIAL_MODE)
.
The above is one of several common industry standard ways to implement hardware registers in embedded systems. More info here: How to access a hardware register from firmware?
答案2
得分: 1
以下是翻译好的部分:
"Whether bit-fields can span across the boundary of the datatype on which they're defined":位域是否可以跨越其所定义的数据类型的边界是实现定义的。
"Whether types other than int
, signed int
, unsigned int
, and _Bool
(since C99) are permitted":是否允许除 int
、signed int
、unsigned int
和 _Bool
(自C99以来)之外的类型是实现定义的。
"Whether a bit-field can straddle an allocation unit boundary":位域是否可以跨越分配单元边界是实现定义的。
"The order of bit-fields within an allocation unit (on some platforms, bit-fields are packed left-to-right, on others right-to-left)":分配单元内部位域的顺序(在某些平台上,位域从左到右打包,而在其他平台上从右到左)是实现定义的。
"In this case, you have an 8-bit datatype into which you're trying to pack 3 groups of 3 bits":在这种情况下,您有一个8位数据类型,您试图将3组3位打包到其中。
"The third group straddles a boundary and it seems your compiler implementation does not support that":第三组跨越边界,似乎您的编译器实现不支持这种情况。
"It looks like the value has been moved to the next byte, which probably means sizeof(CON_MAP_t)
is greater than 2":看起来值已经移动到了下一个字节,这可能意味着sizeof(CON_MAP_t)
大于2。
"So at the very least, change your types used in the first struct to uint16_t
, but be aware that support for this is also implementation-defined (as per excerpts shown earlier)":所以至少,将第一个结构中使用的类型更改为uint16_t
,但请注意,对此的支持也是实现定义的(如前面的摘录所示)。
"As a side-note, there's no need to define bit-fields in that second struct (where you've specified 8 bits for each uint8_t
)":作为一种附注,没有必要在第二个结构中定义位域(您已为每个uint8_t
指定了8位)。
"It should be noted that using bit-fields for this kind of thing is heavily platform- and compiler-dependent. And using them for type-punning violates strict aliasing":应该注意,对于这种情况使用位域非常依赖于平台和编译器。并且将它们用于类型转换会违反严格别名规则。
"For any kind of portability, you should look to other solutions. And even targeting one specific platform, it's wise to at least create plenty of unit tests for this structure to ensure sanity":为了确保可移植性,您应该寻找其他解决方案。即使针对一个特定平台,至少创建大量的单元测试来确保该结构的合理性是明智的。
英文:
It's implementation-defined whether bit-fields can span across the boundary of the datatype on which they're defined. There are various other details too. See following excerpts from the reference:
> The following properties of bit-fields are implementation-defined:
> * Whether types other than int
, signed int
, unsigned int
, and _Bool
(since C99) are permitted
> * Whether a bit-field can straddle an allocation unit boundary
> * The order of bit-fields within an allocation unit (on some platforms, bit-fields are packed left-to-right, on others right-to-left)
In this case, you have an 8-bit datatype into which you're trying to pack 3 groups of 3 bits. The third group straddles a boundary and it seems your compiler implementation does not support that. It looks like the value has been moved to the next byte, which probably means sizeof(CON_MAP_t)
is greater than 2.
So at the very least, change your types used in the first struct to uint16_t
, but be aware that support for this is also implementation-defined (as per excerpts shown earlier).
As a side-note, there's no need to define bit-fields in that second struct (where you've specified 8 bits for each uint8_t
).
typedef union __attribute__((packed)) {
struct {
uint16_t MODE : 3;
uint16_t VSHCT : 3;
uint16_t VBUSCT : 3;
uint16_t AVG : 3;
uint16_t RSVD : 3;
uint16_t RST : 1;
};
struct {
uint8_t lsbyte;
uint8_t msbyte;
};
uint16_t w;
} CON_MAP_t;
It should be noted that using bit-fields for this kind of thing is heavily platform- and compiler-dependent. And using them for type-punning violates strict aliasing.
For any kind of portability, you should look to other solutions. And even targeting one specific platform, it's wise to at least create plenty of unit tests for this structure to ensure sanity.
答案3
得分: 1
When it comes to using compilers that support some additional features over standard C compilers, like XC32 Compiler, it's always better to refer to its guides and manuals for special cases like this one.
The XC32 compiler fully supports bit fields in structures and guarantees that the first defined bit will be the Least Significant bit. This is described in section 8.6.2 Bit Fields in Structures of the XC32 C Compiler User's Guide:
MPLAB XC32 C/C++ Compiler fully supports bit fields in structures.
Bit fields are always allocated within 8-bit storage units, even though it is usual to use the typeunsigned int
in the definition. Storage units are aligned on a 32-bit boundary, although this can be changed using thepacked
attribute.
The first bit defined will be the Least Significant bit of the word in which it will be stored. When a bit field is declared, it is allocated within the current 8-bit unit if it will fit; otherwise, a new byte is allocated within the structure. Bit fields can never cross the boundary between 8-bit allocation units.
For example, the declaration:
struct {
unsigned lo : 1;
unsigned dummy : 6;
unsigned hi : 1;
} foo;
will produce a structure occupying 1 byte.
So according to the guide's description, you should see the same order as per your bit definition.
Also note that the packed
attribute is meant to be used only if you want to alter the 32-bit boundary. But it is not necessary since yours is only 16-bits.
Here is a demo showing the expected result following with the code:
Demo code shown in the screenshot:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef union {
struct {
unsigned MODE:3;
unsigned VSHCT:3;
unsigned VBUSCT:3;
unsigned AVG:3;
unsigned RSVD:3;
unsigned RST:1;
};
struct {
uint8_t lsbyte:8;
uint8_t msbyte:8;
};
uint16_t w;
} CON_MAP_t;
int main(int argc, char** argv) {
CON_MAP_t map = {
.RST = 0,
.RSVD = 4,
.AVG = 0,
.VBUSCT = 4,
.VSHCT = 4,
.MODE = 7
};
if(map.lsbyte == 0x27 && map.msbyte == 0x41)
return (EXIT_SUCCESS);
else
return (EXIT_FAILURE);
}
英文:
When it comes to using compilers that supports some additional features over standard C compilers, like XC32 Compiler, it always is better to refer to its guides and manuals for special cases like this one.
The XC32 does fully support bit fields in structures and also guarantee the order as the first defined bit as to be the Least Significant bit. Here is how it described in section 8.6.2 Bit Fields in Structures of XC32 C Compiler User's Guide:
> MPLAB XC32 C/C++ Compiler fully supports bit fields in structures.
Bit fields are always allocated within 8-bit storage units, even though it is usual to use the type unsigned int
in the definition. Storage units are aligned on a 32-bit boundary, although this can be changed using the packed
attribute.
The first bit defined will be the Least Significant bit of the word in which it will be stored. When a bit field is declared, it is allocated within the current 8-bit unit if it will fit; otherwise, a new byte is allocated within the structure. Bit fields can never cross the boundary between 8-bit allocation units.
For example, the declaration:
struct {
unsigned lo : 1;
unsigned dummy : 6;
unsigned hi : 1;
} foo;
will produce a structure occupying 1 byte.
So according to the guide description you should see the same order as per your bit definition.
Also note that packed
attribute is meant to use only if you want to alter the 32-bit boundary. But it is not necessary since yours is only 16-bits.
Here is a demo showing the expected result following with the code:
Demo code shown in the screenshot:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef union {
struct {
unsigned MODE:3;
unsigned VSHCT:3;
unsigned VBUSCT:3;
unsigned AVG:3;
unsigned RSVD:3;
unsigned RST:1;
};
struct {
uint8_t lsbyte:8;
uint8_t msbyte:8;
};
uint16_t w;
} CON_MAP_t;
int main(int argc, char** argv) {
CON_MAP_t map = {
.RST = 0,
.RSVD = 4,
.AVG = 0,
.VBUSCT = 4,
.VSHCT = 4,
.MODE = 7
};
if(map.lsbyte == 0x27 && map.msbyte == 0x41)
return (EXIT_SUCCESS);
else
return (EXIT_FAILURE);
}
答案4
得分: 0
我认为我可能已经通过尝试的最后一种方法解决了这个问题。我将宏类型定义更改为标准的 "int",并且还在 "w" 字段中添加了位计数:
typedef union __attribute__((packed)) {
struct {
int MODE:3;
int VSHCT:3;
int VBUSCT:3;
int AVG:3;
int RSVD:3;
int RST:1;
};
struct {
int lsbyte:8;
int msbyte:8;
};
int w:16;
} CON_MAP_t;
这似乎已经解决了硬件实现中的问题。
如果还有其他基本理解方面的问题我可能忽略了的话,请告诉我。
谢谢!
英文:
I think I may have figured this out by doing the last thing I could think of.
I changed from the macro type definitions to standard "int" and also added a bit count to the "w" field:
typedef union __attribute__((packed)) {
struct {
int MODE:3;
int VSHCT:3;
int VBUSCT:3;
int AVG:3;
int RSVD:3;
int RST:1;
};
struct {
int lsbyte:8;
int msbyte:8;
};
int w:16;
} CON_MAP_t;
This seems to have fixed the problem in the hardware implementation.
Let me know if there's any other fundamental understanding here that I may have missed.
Thanks!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论