我正在摆弄 Raspberry Pi 上的 GPIO 访问,所以当然涉及到内存。
我的想法是我有一个基地址:gpios
。根据我想要执行的操作,我将进一步访问该基地址的某个地址并更改一些位。
volatile uint32_t* gpios;
在这个例子中,我想读取引脚的逻辑值。为此,有两个寄存器,第一个在地址 (gpios + GPIO_INPUT_READ_OFFSET)
用于第 32 个 gpios,第二个在地址 (gpios + GPIO_INPUT_READ_OFFSET + 4)
用于剩余的 gpios。
我总是这样写这样的操作:
volatile uint32_t* input = (uint32_t*)
((uint32_t)gpios + GPIO_INPUT_READ_OFFSET + 4*(gpio/32));
gpio
只是当前函数中操作的 gpio 编号。
但是我一直在想,如果我没记错的话,完全可以这样写:
volatile uint32_t* input = gpios + GPIO_INPUT_READ_OFFSET/4 + (gpio/32);
因为指针恶作剧。
两者我都喜欢,但我真的说不出哪个更受青睐。
是否有关于此类地址摆弄的“最佳实践”?
最佳答案
IMO 在 C 中执行此操作的最清晰方法是使用指向结构的指针。看着 RPi register documentation我可以看到 GPIO 寄存器是:
0x7E20 0000 | GPFSEL0 GPIO Function Select 0 | 32 | R/W
0x7E20 0004 | GPFSEL1 GPIO Function Select 1 | 32 | R/W
0x7E20 0008 | GPFSEL2 GPIO Function Select 2 | 32 | R/W
0x7E20 000C | GPFSEL3 GPIO Function Select 3 | 32 | R/W
0x7E20 0010 | GPFSEL4 GPIO Function Select 4 | 32 | R/W
0x7E20 0014 | GPFSEL5 GPIO Function Select 5 | 32 | R/W
0x7E20 0018 | - Reserved | - | -
0x7E20 001C | GPSET0 GPIO Pin Output Set 0 | 32 | W
0x7E20 0020 | GPSET1 GPIO Pin Output Set 1 | 32 | W
0x7E20 0024 | - Reserved | - | -
0x7E20 0028 | GPCLR0 GPIO Pin Output Clear 0 | 32 | W
0x7E20 002C | GPCLR1 GPIO Pin Output Clear 1 | 32 | W
0x7E20 0030 | - Reserved | - | -
0x7E20 0034 | GPLEV0 GPIO Pin Level 0 | 32 | R
0x7E20 0038 | GPLEV1 GPIO Pin Level 1 | 32 | R
....
我要做的是按照这个布局创建一个结构:
typedef struct {
volatile uint32_t gpfsel0;
volatile uint32_t gpfsel1;
volatile uint32_t gpfsel2;
volatile uint32_t gpfsel3;
volatile uint32_t gpfsel4;
volatile uint32_t gpfsel5;
volatile uint32_t : 32;
volatile uint32_t gpset0;
volatile uint32_t gpset1;
volatile uint32_t : 32;
volatile uint32_t gpclr0;
volatile uint32_t gpclr1;
volatile uint32_t : 32;
volatile const uint32_t gplev0;
volatile const uint32_t gplev1;
....
} RPiGpio;
然后取一个指向基地址的 RPiGpio 指针并更改/读取一些位:
RPiGpio* rPiGpio = (RPiGpio*)(0x7E200000);
rPiGpio->gpclr1 = 0x0001;
uint32_t pins = rPiGpio->gplev0;
读取 GPIO 引脚
如果您想根据 gpio 引脚号选择读取哪个寄存器,那么我可能会做的是将适当的结构成员更改为数组并以这种方式访问寄存器:
// Change this
typedef struct {
....
volatile const uint32_t gplev0;
volatile const uint32_t gplev1;
....
} RPiGpio;
// To this
typedef struct {
....
volatile const uint32_t gplev[2];
....
} RPiGpio;
// Read a pin
bool isPinHigh(unsigned pinNum)
{
const RPiGpio* rPiGpio = (const RPiGpio*)(0x7E200000);
return rPiGpio->gplev[pinNum / 32] & (1 << (pinNum % 32));
}
pinNum
是 GPIO 引脚号 0-63(我不知道您在 Raspberry Pi 上实际可以访问多少个引脚,所以我只是假设 gpio 0 到 gpio 63)。
pinNum / 32
选择适当的寄存器(0 或 1)。
1 << (pinNum % 32)
选择适当的位。
&
将屏蔽掉其他位,留下所需位的状态。
一些注意事项:
结构中的成员被声明为可变的。这在读取寄存器时非常重要,因为寄存器值可能会在读取调用之间发生变化。如果不包含 volatile,那么编译器可能会优化读取,因为在编译器看来,该值不会改变。 Volatile 告诉编译器该值可能会意外更改。
只读寄存器声明volatile const
.这将防止您不小心写入内存的只读部分(或者如果您这样做,至少会引发编译器错误)。
对于保留字段,请使用 uint32_t : 32;
这是一个未命名的 32 位位域。位域很少有用,因为实际上对它们的保证很少,但在这里它们工作得很好。
关于用于操作的 C 指针转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39288587/