【问题标题】:C pointer casting for operations用于操作的 C 指针转换
【发布时间】:2016-09-02 09:10:34
【问题描述】:

我在 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);

因为指针的恶作剧。

我都喜欢,但我真的不知道哪个应该比另一个更受欢迎。

对于这种类型的地址摆弄是否有“最佳实践”?

【问题讨论】:

  • “恶作剧”是什么意思?
  • 将指针转换为uint32_t 绝对不是一件好事(在这里转换为char * 会更有意义)。
  • 但是您基本上是在问“我应该以字节为单位还是以指向类型为单位执行指针运算?”。我认为答案最终是“视情况而定,以使意图更清晰者为准”。
  • @barakmanos 指针运算,因此将整数添加到指针实际上会添加 (integer * sizeof(pointer)) 到指针。
  • 使用char * 获取指针的字节运算,例如uint32_t *base = (uint32_t *)((char *)gpios + GPIO_INPUT_READ_OFFSET)gpio 的用途是什么?

标签: c pointers casting


【解决方案1】:

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 &lt;&lt; (pinNum % 32) 选择适当的位。

&amp; 将屏蔽掉其他位,保留所需位的状态。

一些注意事项:

结构中的成员被声明为 volatile。这在读取寄存器时非常重要,因为寄存器值可能会在读取调用之间发生变化。如果不包含 volatile ,那么编译器可能会优化读取,因为在编译器看来,该值不会改变。 Volatile 告诉编译器该值可能会意外更改。

只读寄存器被声明为volatile const。这将防止您意外写入内存的只读部分(或者如果这样做至少会引发编译器错误)。

对于保留字段,请使用uint32_t : 32;,这是一个未命名的 32 位位字段。位域很少有用,因为实际上对它们的保证很少,但在这里它们工作得很好。

【讨论】:

  • 我真的很喜欢你的方式,而且我很高兴终于有了一个位域有用的例子!感谢您的详细回答
猜你喜欢
  • 2017-09-27
  • 2016-06-28
  • 1970-01-01
  • 1970-01-01
  • 2011-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多