结合ARM SoC的两个union结构体

问题描述

我正在尝试将 ARM SoC 的 GPIO 端口的两个 typedef 联合合并为一个,并将地址指针合并为一个。目前,我有一些看起来像这样的东西:

.h 文件

//GPIO00 port
typedef union {
  struct {
    uint32_t GPIO000:1;
    uint32_t GPIO001:1;
    ...
    uint32_t GPIO0017:1;
  };
  struct {
    uint32_t w:18;
  };
} __GPIO00portbits_t;

volatile __GPIO00portbits_t * PTR_GPIO00portbits;
#define GPIO00portbits (*PTR_GPIO00portbits)


//GPIO01 port
typedef union {
  struct {
    uint32_t GPIO010:1;
    uint32_t GPIO011:1;
    ...
    uint32_t GPIO0117:1;
  };
  struct {
    uint32_t w:18;
  };
} __GPIO01portbits_t;

volatile __GPIO01portbits_t * PTR_GPIO01portbits;
#define GPIO01portbits (*PTR_GPIO01portbits)

.c 文件

//GPIO 00 port
volatile __GPIO00portbits_t * PTR_GPIO00portbits = (__GPIO00portbits_t *) (AXIBRIDGE_BASE_ADDR + GPIO_00_BASE);


//GPIO 01 port
volatile __GPIO01portbits_t * PTR_GPIO01portbits = (__GPIO01portbits_t *) (AXIBRIDGE_BASE_ADDR + GPIO_01_BASE);
}

我可以用它来控制 ARM SoC 的 GPIO 端口。 IE。我可以通过更改 GPIO00portbits.GPIO00x 来控制 GPIO00 的单个引脚。 GPIO01 也一样。

实际上,GPIO00和GPIO01实际上是一个叫做GPIO0的端口,其中GPIO00是引脚0-17,GPIO01是引脚18-35,所以我也想把GPIO00和GPIO01合二为一,可以通过改变来控制GPIO0portbits.GPIO0x.

所以我想要这样的东西:

typedef union {
  struct {
    uint64_t GPIO00:1 = GPIO00portbits.GPIO000;
    uint64_t GPIO01:1 = GPIO00portbits.GPIO001;
    ...
    uint64_t GPIO035:1 = GPIO01portbits.GPIO0117;
  };
  struct {
    uint32_t w:36;
  };
} __GPIO0portbits_t;

我该怎么做?

提前致谢。

解决方法

一般数据类型

您定义了两种不同的类型,__GPIO00portbits_t__GPIO01portbits_t,它们具有相同的结构和密切相关的用法。这是毫无意义的,它甚至可能会妨碍您。我可能会这样做:

typedef union {
  struct {
    uint32_t GPIO0:1;
    uint32_t GPIO1:1;
    ...
    uint32_t GPIO17:1;
  };
  uint32_t w:18;
} __GPIOhalfportbits_t;

extern volatile __GPIOhalfportbits_t *PTR_GPIO00portbits;
#define GPIO00portbits (*PTR_GPIO00portbits)

extern volatile __GPIOhalfportbits_t * PTR_GPIO01portbits;
#define GPIO01portbits (*PTR_GPIO01portbits)

请注意,顺便说一句,如果标题将在多个 .c 文件中使用,则您需要 extern ,并且在这种情况下,这些 .c 文件中的一个应该包含定义你展示。


您的具体要求

我还想将GPIO00和GPIO01合二为一,可以通过改变GPIO0portbits.GPIO0x来控制

您似乎没有在对象及其数据类型之间保持适当的心理区别。这将解释您奇怪的数据类型重复,以及您描述您正在寻找的内容的方式。如果您希望能够选择将数据视为完整的 36 位或两个 18 位的一半,那么您可以想象继续上述内容:

// XXX: see below
typedef union {
  struct {
      __GPIOhalfportbits_t group0;
      __GPIOhalfportbits_t group1;
  };
  struct {
    uint32_t GPIO0:1;
    uint32_t GPIO1:1;
    ...
    uint32_t GPIO35:1;
  };
  uint64_t w:36;  // uint32_t isn't wide enough
} __GPIOportbits_t;

原则上,您可以通过直接访问位来访问该类型的对象......

__GPIOportbits_t portbits;

// ...

if (portbits.GPIO23) {
    // ...
}

... 或通过半端口件...

if (portbits.group1.GPIO5) {
    // ...
}

类似的东西在不同的情况下可能会起作用,但在您的情况下,这不起作用。问题在于半端口部分中的位数不是 char 中位数的倍数(硬件上为 8)。 char 的大小是衡量对象大小的单位,因此是地址的最细粒度。

这意味着我的 __GPIOhalfportbits_t 和你的 __GPIO00portbits_t__GPIO01portbits_t 的大小至少是 24 位,而不是 18 位。因此,如果将它们中的两个一个接一个地布置,则位域不能布置为从对象开头开始的连续 36 位范围。第一个对象的至少 6 个(填充)位需要放在第二个半端口对象的位之前的某个位置。

出于基本相同的原因,也没有指针技巧可以完成您所追求的任务。如果您有一个由 36 个连续位组成的区域,那么后半部分不会从可寻址边界开始,因此您无法形成指向它的指针。

另一方面,如果两半一开始就不连续,那么您可能可以采用以下方法:

typedef struct {
    __GPIOhalfportbits_t group0;
    __GPIOhalfportbits_t group1;
} __GPIOportbits_t;

您必须注意两个半端口部分的对齐方式,但可能有一种特定于实现的方法可以做到这一点。考虑到底层数据(我们现在已经假设)首先不是以连续的 36 位跨度呈现,因此与 36 位位域形成联合是没有意义的。尽管如此,仍然可以通过插入适当大小的显式填充来使用联合来映射那对结构之上的单个单比特位域,但您需要考虑这些是否真的值得做。特别是,见下文。


重要的其他注意事项

位域通常是一项棘手的业务,C 对它们的行为几乎没有保证——比很多人想象或期望的要少得多。使用位域写入硬件端口是一个特别糟糕的主意,因为您不能一次写入少于 CHAR_BIT 位,并且如果您通过大小不是二次幂的位域写入的 CHAR_BIT 那么您还将写入其他位,其值未指定。

我通常建议完全避免使用位域,除非可能在相关硬件制造商提供的 C 语言编程接口中使用位域,方式与这些接口的文档一致。


替代方案

你可以想出一些封装宏来访问两个半端口的 GPIO 端口,甚至是这些端口中的单个位。但是这个答案已经很长了,这种以宏观为中心的方法将是另一回事。

,
  1. 您不能这样做,因为它们位于内存中的不同地址下。
  2. 使用对象访问硬件寄存器的效率非常低。在这个级别的编程中,您需要尽可能地优化代码。

https://godbolt.org/z/ncbr8o

您只能通过拥有额外的对象来“组合”它们,您可以在其中从实际寄存器中读取数据,并在更改后将其保存到寄存器中。

#include <stdint.h>

#define AXIBRIDGE_BASE_ADDR 0x12340000
#define GPIO_00_BASE  0x400
#define GPIO_01_BASE  0x800

//GPIO00 port
typedef union {
  struct {
    uint32_t GPIO000:1;
    uint32_t GPIO001:1;
    uint32_t GPIO002:1;
    uint32_t GPIO003:1;
    uint32_t GPIO004:1;
    uint32_t GPIO005:1;
    uint32_t GPIO006:1;
    uint32_t GPIO007:1;
    uint32_t GPIO008:1;
    uint32_t GPIO009:1;
    uint32_t GPIO010:1;
    uint32_t GPIO011:1;
    uint32_t GPIO012:1;
    uint32_t GPIO013:1;
    uint32_t GPIO014:1;
    uint32_t GPIO015:1;
    uint32_t GPIO016:1;
    uint32_t GPIO017:1;
  };
  struct {
    uint32_t w:18;
  };
} __GPIO00portbits_t;



typedef union {
  struct {
    uint32_t GPIO000:1;
    uint32_t GPIO001:1;
    uint32_t GPIO002:1;
    uint32_t GPIO003:1;
    uint32_t GPIO004:1;
    uint32_t GPIO005:1;
    uint32_t GPIO006:1;
    uint32_t GPIO007:1;
    uint32_t GPIO008:1;
    uint32_t GPIO009:1;
    uint32_t GPIO010:1;
    uint32_t GPIO011:1;
    uint32_t GPIO012:1;
    uint32_t GPIO013:1;
    uint32_t GPIO014:1;
    uint32_t GPIO015:1;
    uint32_t GPIO016:1;
    uint32_t GPIO017:1;
    uint32_t GPIO100:1;
    uint32_t GPIO101:1;
    uint32_t GPIO102:1;
    uint32_t GPIO103:1;
    uint32_t GPIO104:1;
    uint32_t GPIO105:1;
    uint32_t GPIO106:1;
    uint32_t GPIO107:1;
    uint32_t GPIO108:1;
    uint32_t GPIO109:1;
    uint32_t GPIO110:1;
    uint32_t GPIO111:1;
    uint32_t GPIO112:1;
    uint32_t GPIO113:1;
    uint32_t GPIO114:1;
    uint32_t GPIO115:1;
    uint32_t GPIO116:1;
    uint32_t GPIO117:1;
  };
  struct {
    uint64_t GPIO1w:18;
    uint64_t GPIO2w:18;
  };
} __GPIO12portbits_t;


#define GPIO1       ((volatile __GPIO00portbits_t *)(AXIBRIDGE_BASE_ADDR + GPIO_00_BASE))
#define GPIO2       ((volatile __GPIO00portbits_t *)(AXIBRIDGE_BASE_ADDR + GPIO_01_BASE))

#define COMBINE()   (&(__GPIO12portbits_t){.GPIO1w = GPIO1 -> w,.GPIO2w = GPIO2 -> w})
#define UPDATEGPIO(ptr)  do{GPIO1 -> w = ptr -> GPIO1w; GPIO2 -> w = ptr -> GPIO2w;}while(0)

void foo()
{
    __GPIO12portbits_t *ptr = COMBINE();

    ptr -> GPIO014 = 1;
    ptr -> GPIO110 = 1;

    UPDATEGPIO(ptr);
}

void bar()
{
    GPIO1 -> GPIO014 = 1;
    GPIO2 -> GPIO010 = 1;
}

但是效率很低https://godbolt.org/z/jMsc7j