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 告诉编译器该值可能会意外更改。
只读寄存器被声明为volatile const。这将防止您意外写入内存的只读部分(或者如果这样做至少会引发编译器错误)。
对于保留字段,请使用uint32_t : 32;,这是一个未命名的 32 位位字段。位域很少有用,因为实际上对它们的保证很少,但在这里它们工作得很好。