-1

我正在尝试将 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 端口。即我可以通过更改 GPIO00portbits.GPIO00x 来控制 GPIO00 的单个引脚。它对 GPIO01 的工作原理相同。

实际上,GPIO00和GPIO01实际上是一个叫GPIO0的端口,其中GPIO00是pin 0-17,GPIO01是pin 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;

我怎样才能做到这一点?

先感谢您。

4

2 回答 2

0

数据类型一般

您已经定义了两种不同的类型__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)

请注意,顺便说一句,extern如果要在多个 .c 文件中使用标头,则需要 s ,并且在这种情况下,这些 .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) {
    // ...
}

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

这意味着 my__GPIOhalfportbits_t和 your __GPIO00portbits_tand的大小__GPIO01portbits_t至少为 24 位,而不是 18 位。因此,如果您将其中两个一个接一个地布置,则位域不能布置为从对象开头开始的连续 36 位范围。第一个对象的至少 6 个(填充)位需要在第二个半端口对象的位之前到达某处。

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

另一方面,如果这两个部分一开始就不连续,那么你可以使用这样的方法:

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

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


重要的其他注意事项

总体而言,位域是一项棘手的业务,C 对它们的行为做出的保证很少——比很多人想象或期望的要少得多。使用位域写入硬件端口是一个特别糟糕的主意,因为您一次不能写入少于CHAR_BIT位,并且如果您通过一个大小不是 2 的倍数的位域进行写入,CHAR_BIT那么您将还要写入额外的位,其值未指定。

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


备择方案

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

于 2021-01-30T16:10:23.157 回答
0
  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

于 2021-01-30T17:04:40.833 回答