我正在摆弄Raspberry Pi上的GPIO访问,所以当然会涉及内存。
我的想法是我有一个基地址:(gpios + GPIO_INPUT_READ_OFFSET)
。根据我想要做的事情,我会在某个地址上找到更远的地址并更改一些位。
(gpios + GPIO_INPUT_READ_OFFSET + 4)
在这个例子中,我想读取引脚的逻辑值。为此,有两个寄存器,第一个在地址volatile uint32_t* input = (uint32_t*)
((uint32_t)gpios + GPIO_INPUT_READ_OFFSET + 4*(gpio/32));
用于32个第一个gpios,第二个在地址gpio
用于剩余的gpios。
我总是写这样的操作:
volatile uint32_t* input = gpios + GPIO_INPUT_READ_OFFSET/4 + (gpio/32);
Application.CommandBars("Records").Controls(4).Enabled = False
Application.CommandBars("Menu Bar").Controls("Data").Enabled = False
Application.CommandBars("Menu Bar").Controls("Filter").Enabled = False
只是在当前函数中操作的gpio数字。
但是我一直在想,如果我没弄错的话,很可能就是这样写的:
DoCmd.ShowToolbar "Ribbon", acToolbarNo
因为指针shenanigans。
我喜欢这两种,但我无法确定哪种优先于另一种优先。
是否有最佳实践"关于这种类型的地址摆弄?
答案 0 :(得分:3)
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引脚号选择要读取的寄存器,那么我可能会做的是将相应的struct成员更改为数组并以这种方式访问寄存器:
// 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位位域。 Bitfields很少有用,因为实际上对它们的保证很少,但在这里它们工作正常。