C语言 联合中的位打包问题(寄存器Map)

bis0qfac  于 2023-05-16  发布在  其他
关注(0)|答案(4)|浏览(125)

我正试图得到一个工会在一起Map出一些位字段在寄存器Map。我的代码如下:

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 /0x 4127。
然而,在调试器中,我最终得到了'w'的值:00000100_00100111最低有效字节是正确的,但msb(yte)不是。
我不确定我是否错过了一些基本的东西,我只是盯着它太久了,但任何见解都将受到高度赞赏!
我正在使用:MPLABX v6.05最新XC 32编译器
设备是一个PIC 32 MX 130 F064 D调试用的PICKIT 4.

vd2z7a6w

vd2z7a6w1#

位字段是否可以跨越定义它们的数据类型的边界是由实现定义的。还有其他各种细节。请参阅reference的以下摘录:
位字段的以下属性是 * 实现定义的 *:

  • 是否允许intsigned intunsigned int_Bool(自C99起)以外的类型
  • 位字段是否可以跨越分配单元边界
  • 一个分配单元中位字段的顺序(在某些平台上,位字段是从左到右打包的,在其他平台上是从右到左打包的)

在本例中,您有一个8位数据类型,您试图将3组3位数据打包到其中。第三组跨越了边界,您的编译器实现似乎不支持这一点。看起来该值已移动到下一个字节,这可能意味着sizeof(CON_MAP_t)大于2。
因此,至少将第一个结构体中使用的类型更改为uint16_t,但要注意,对它的支持也是实现定义的(根据前面显示的摘录)。
顺便说一句,不需要在第二个结构体中定义位字段(在这里,您为每个uint8_t指定了8位)。

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;

需要注意的是,使用位字段进行这种操作是严重依赖于平台和编译器的。使用它们进行类型双关语违反了严格的别名。
对于任何类型的可移植性,您都应该考虑其他解决方案。即使是针对一个特定的平台,至少为这个结构创建大量的单元测试以确保合理性也是明智的。

alen0pnh

alen0pnh2#

当涉及到使用支持标准C编译器(如XC32 Compiler)的一些附加功能的编译器时,最好参考它的指南和手册,以了解像这样的特殊情况。
XC 32完全支持结构中的位字段,并保证第一个定义的位的顺序为最低有效位。以下是XC32 C Compiler User's Guide8.6.2结构中的位字段部分的描述:
MPLAB XC 32 C/C++编译器完全支持结构中的位字段。位字段总是在8位存储单元中分配,即使在定义中通常使用类型unsigned int。存储单元在32位边界上对齐,但可以使用packed属性进行更改。
定义的第一位将是存储它的字的最低有效位。当声明位字段时,如果它适合,则在当前8位单元内分配它;否则,在结构内分配新的字节。位字段永远不能跨越8位分配单元之间的边界。例如,声明:

struct {
    unsigned lo    : 1;
    unsigned dummy : 6;
    unsigned hi    : 1;
} foo;

将产生占用1字节的结构。
因此,根据指南描述,您应该看到与您的位定义相同的顺序。
另外请注意,packed属性仅在您希望更改32位边界时使用。但这是没有必要的,因为你是只有16位。
下面是一个演示,显示了代码后面的预期结果:

演示代码如图所示

#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);
}
fwzugrvs

fwzugrvs3#

正如在评论和其他帖子(如Why bit endianness is an issue in bitfields?)中所指出的那样,C标准对位字段的定义非常糟糕,甚至不好笑。在这个混乱的环境中再加上一个令人讨厌的声誉编译器(如MPLAB),您将面临灾难。
我的建议是忘记你曾经听说过位字段,并通过宏使用100%可移植的、标准化的整数常量来编写代码。在这个特定的例子中,没有明显的必要在字和字节之间输入双关语--既然位字段到处都是,为什么你还需要字节访问呢?
假设你的硬件寄存器名为CON_MAP,CPU是小字节序(字节序对你的位域代码很重要,但对我下面的解决方案不重要),那么:

#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 )
...

使用方法:

CON_MAP = CON_MAP_MODE(2u)   | 
          CON_MAP_VSHCT(3u)  |
          CON_MAP_VBUSCT(0u) |
          ...                ;

2u3u等理想情况下应该用命名常量替换,以防您对它们有一些有意义的名称。喜欢:CON_MAP_MODE(FANCY_MODE | SPECIAL_MODE).
以上是在嵌入式系统中实现硬件寄存器的几种常见工业标准方式之一。更多信息:How to access a hardware register from firmware?

ujv3wf0j

ujv3wf0j4#

我想我做了我能想到的最后一件事才弄明白了这一点。我将宏类型定义改为标准的“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;

这似乎解决了硬件实现中的问题。
如果还有其他我可能遗漏的基本理解,请告诉我。
谢谢!

相关问题