组合 ARM SoC 的两个联合结构
Combining two union structs of ARM SoC
我正在尝试将 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是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;
我该怎么做?
提前致谢。
一般数据类型
您定义了两个不同的类型,__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)
请注意,顺便说一下,如果 header 将在多个 .c 文件中使用,那么您需要 extern
s,并且在这种情况下恰好是其中一个.c 文件应包含您显示的定义。
您的具体要求
I would also like to combine GPIO00 and GPIO01 into one stuct which can be controlled by changing GPIO0portbits.GPIO0x
看来您可能没有在 object 及其数据类型之间保持适当的心理区分。这将解释您奇怪的数据类型重复,以及您描述您正在寻找的内容的方式。如果您希望能够选择将数据视为完整的 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;
那么,原则上,您可以通过直接访问位来访问该类型的 object ...
__GPIOportbits_t portbits;
// ...
if (portbits.GPIO23) {
// ...
}
... 或通过 half-port 件 ...
if (portbits.group1.GPIO5) {
// ...
}
类似的方法可能会在不同的情况下起作用,但在您的情况下,这不会起作用。问题是 half-port 块中的位数不是 char
中位数的倍数(硬件上为 8)。 char
的大小是衡量 object 大小的单位,因此也是地址的最细粒度。
这意味着我的 __GPIOhalfportbits_t
和你的 __GPIO00portbits_t
和 __GPIO01portbits_t
的大小至少是 24 位,而不是 18 位。因此,如果您将其中两个一个接一个地放置,则位域 不能 被布置为从 object 开头开始的连续 36 位范围。第一个 object 至少有 6 个(填充)位需要在第二个 half-port object.
的位之前到达某个地方
出于基本相同的原因,也没有可以完成您所追求的指针技巧。如果您有一个 36 位连续位的区域,那么后半部分不会从可寻址边界开始,因此您无法形成指向它的指针。
另一方面,如果两半一开始就不是连续的,那么您也许可以这样处理:
typedef struct {
__GPIOhalfportbits_t group0;
__GPIOhalfportbits_t group1;
} __GPIOportbits_t;
您必须注意两个 half-port 部分的对齐,但可能有一种 implementation-specific 方法可以做到这一点。鉴于底层数据(我们现在已经假设)首先没有呈现为 36 位的连续跨度,因此与 36 位位域形成联合没有意义。尽管如此,通过插入适当大小的显式填充,可以使用联合将单个 single-bit 位域映射到该对结构的顶部,但是您需要考虑这是否真的值得做。具体请参见下文。
其他重要注意事项
位域通常是一项棘手的工作,C 对其行为的保证很少——比很多人想象或期望的要少得多。使用位域写入硬件端口是一个特别糟糕的主意,因为您不能一次写入少于 CHAR_BIT
位,并且如果您通过大小不是 power-of-two 倍数的位域写入CHAR_BIT
那么你也将写入额外的位,其值未指定。
我通常建议完全避免使用位域,除非可能在相关硬件制造商提供的 C-language 编程接口中以与这些接口文档一致的方式使用位域。
备选方案
您可以想出一些包装宏来访问两个半端口的 GPIO 端口,甚至是这些端口中的各个位。但是这个答案已经很长了,这样的 macro-centric 方法将是另一回事。
- 你不能这样做,因为它们位于内存中的不同地址。
- 使用对象访问硬件寄存器效率很低。在这个级别的编程上,你需要尽可能优化代码。
你只能通过拥有额外的对象来“组合”它们,你将从实际寄存器中读取数据,并在更改后将其保存到寄存器中。
#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;
}
我正在尝试将 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是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;
我该怎么做?
提前致谢。
一般数据类型
您定义了两个不同的类型,__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)
请注意,顺便说一下,如果 header 将在多个 .c 文件中使用,那么您需要 extern
s,并且在这种情况下恰好是其中一个.c 文件应包含您显示的定义。
您的具体要求
I would also like to combine GPIO00 and GPIO01 into one stuct which can be controlled by changing GPIO0portbits.GPIO0x
看来您可能没有在 object 及其数据类型之间保持适当的心理区分。这将解释您奇怪的数据类型重复,以及您描述您正在寻找的内容的方式。如果您希望能够选择将数据视为完整的 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;
那么,原则上,您可以通过直接访问位来访问该类型的 object ...
__GPIOportbits_t portbits;
// ...
if (portbits.GPIO23) {
// ...
}
... 或通过 half-port 件 ...
if (portbits.group1.GPIO5) {
// ...
}
类似的方法可能会在不同的情况下起作用,但在您的情况下,这不会起作用。问题是 half-port 块中的位数不是 char
中位数的倍数(硬件上为 8)。 char
的大小是衡量 object 大小的单位,因此也是地址的最细粒度。
这意味着我的 __GPIOhalfportbits_t
和你的 __GPIO00portbits_t
和 __GPIO01portbits_t
的大小至少是 24 位,而不是 18 位。因此,如果您将其中两个一个接一个地放置,则位域 不能 被布置为从 object 开头开始的连续 36 位范围。第一个 object 至少有 6 个(填充)位需要在第二个 half-port object.
出于基本相同的原因,也没有可以完成您所追求的指针技巧。如果您有一个 36 位连续位的区域,那么后半部分不会从可寻址边界开始,因此您无法形成指向它的指针。
另一方面,如果两半一开始就不是连续的,那么您也许可以这样处理:
typedef struct {
__GPIOhalfportbits_t group0;
__GPIOhalfportbits_t group1;
} __GPIOportbits_t;
您必须注意两个 half-port 部分的对齐,但可能有一种 implementation-specific 方法可以做到这一点。鉴于底层数据(我们现在已经假设)首先没有呈现为 36 位的连续跨度,因此与 36 位位域形成联合没有意义。尽管如此,通过插入适当大小的显式填充,可以使用联合将单个 single-bit 位域映射到该对结构的顶部,但是您需要考虑这是否真的值得做。具体请参见下文。
其他重要注意事项
位域通常是一项棘手的工作,C 对其行为的保证很少——比很多人想象或期望的要少得多。使用位域写入硬件端口是一个特别糟糕的主意,因为您不能一次写入少于 CHAR_BIT
位,并且如果您通过大小不是 power-of-two 倍数的位域写入CHAR_BIT
那么你也将写入额外的位,其值未指定。
我通常建议完全避免使用位域,除非可能在相关硬件制造商提供的 C-language 编程接口中以与这些接口文档一致的方式使用位域。
备选方案
您可以想出一些包装宏来访问两个半端口的 GPIO 端口,甚至是这些端口中的各个位。但是这个答案已经很长了,这样的 macro-centric 方法将是另一回事。
- 你不能这样做,因为它们位于内存中的不同地址。
- 使用对象访问硬件寄存器效率很低。在这个级别的编程上,你需要尽可能优化代码。
你只能通过拥有额外的对象来“组合”它们,你将从实际寄存器中读取数据,并在更改后将其保存到寄存器中。
#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;
}