4

Absolute noob to embedded system. Right now I am trying to program a device called nRF52832 in C. I know that the base address for the GPIO is 0x50000000 and the offset for the OUT register is 0x504. How do I create pointers to the addresses of the GPIO OUT and print the address?

Do I do

volatile unsigned int* GPIO_BASE = (unsigned int*) 0x50000000;
volatile unsigned int* GPIO_OUT = GPIO_BASE + 0x504; ?
kiran Biradar
  • 12,700
  • 3
  • 19
  • 44
user3084686
  • 75
  • 1
  • 6

4 Answers4

3

Remember that pointer arithmetic is always done in the units of the pointer base type.

That means the expression GPIO_BASE + 0x504 will add a byte-offset of 0x504 * sizeof *GPIO_BASE, which is 0x504 * sizeof(int) (which on most systems will be an offset of 5136 bytes, not 1284 bytes).

If you want to add an offset of 0x504 bytes you need to divide it by the size of the pointer base type:

volatile unsigned int* GPIO_OUT = GPIO_BASE + (0x504 / sizeof *GPIO_BASE);

Except this problem your code is correct, and a very common way to access hardware registers.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 1
    `GPIO_BASE + (0x504 / sizeof *GPIO_BASE);` is gibberish though, you would never write code like that in the calling application. The offset should be calculated inside macros and definitely not in run-time, as in this example. – Lundin Sep 16 '19 at 08:36
  • 1
    I think something like `#define GPIO_OFFSET(x, y) (unsigned int *) ((char *) (x) + (char *) (y))` and then simply `volatile unsigned int* GPIO_OUT = GPIO_OFFSET(GPIO_BASE, 0x504)` should do the trick. – gstukelj Sep 16 '19 at 17:32
2

Do I do

volatile unsigned int* GPIO_BASE = (unsigned int*) 0x50000000;
volatile unsigned int* GPIO_OUT = GPIO_BASE + 0x504;

No, you don't. That would mean that the address gets calculated in run-time, because the volatile qualifier means that the program has to update the variable in run-time whenever it is used.

This is what you should do instead:

// assuming 32 bit registers and 0x504 being the offset in bytes
#define GPIO_BASE 0x50000000u
#define GPIO_OUT  (*(volatile uint32_t*)(GPIO_BASE + 0x504u))
#define GPIO_X    (*(volatile uint32_t*)(GPIO_BASE + 0x508u)) // some other related register 

...
GPIO_OUT = something;

In this example, the register addresses are calculated at compile-time, which is the only correct way of doing it.

By de-referencing the address inside the macro, we can now use GPIO_OUT almost as if it was an ordinary variable.

It does however seem unlikely that a GPIO peripheral has hundreds of memory-mapped registers, so the value 0x504 is strange.


Also please note that using the default C types like int and char in embedded systems is naive. These types come with various portability problems and also have various forms of poorly-defined behavior associated with them. Professional systems only use the types from stdint.h and never anything else.

Lundin
  • 195,001
  • 40
  • 254
  • 396
0
volatile unsigned int* GPIO_BASE = (unsigned int*) 0x50000000;
volatile unsigned int* GPIO_OUT = GPIO_BASE + 0x504; 

If offset for the OUT register is 0x504 bytes away from BASE, then you need to typecast GPIO_BASE to char *

volatile unsigned int* GPIO_OUT = ((char *)GPIO_BASE) + 0x504; 

Otherwise all ok.

kiran Biradar
  • 12,700
  • 3
  • 19
  • 44
0

Because of pointer arithmetic, the value of the pointer GPIO_OUT will be GPIO_BASE + (0x504 * sizeof(int)). If sizeof(int) is 4 bytes, you will get pointer to 0x50000000 + (0x504 * 4) = 0x50001410, while the actual address for the OUT register is 0x50000504. You can type-cast the pointer to unsigned char* or better yet use macro.

#define GPIO_BASE 0x50000000

#define GPIO_OUT GPIO_BASE + 0x504

volatile unsigned int* gpio_out = GPIO_OUT;