Warm tip: This article is reproduced from stackoverflow.com, please click
c pointers c-preprocessor lpc

Understanding #define preprocessor directive macro syntax

发布于 2020-03-27 10:23:51

The following code is taken from the LPC54618.h header file:

typedef struct {
     //...structure elements
     __IO uint32_t SDIOCLKSEL;
     //...more elements

} SYSCON_Type;

#define SYSCON_BASE         (0x40000000u)
#define SYSCON              ((SYSCON_Type *)SYSCON_BASE)
  1. As far as I can guess the meaning behind the line

    #define SYSCON ((SYSCON_Type *)SYSCON_BASE)

I would assume that it creates a pointer named SYSCON that points to a variable of type SYSCON_Type which is stored at the address 0x40000000u. Is this really what happens? And is there any ressource that explains the syntax that is being used here (i.e. defining pointers inside macros)?

  1. When I try to alter the value of SDIOCLKSEL directly, i.e.:

    SYSCON->SDIOCLKSEL = some value;

    I get an error:

    error: expected ')' error: expected parameter declarator error: expected ')' error: expected function body after function declarator

but if I use it inside a function, e.g.:

void foo(void)
{
   SYSCON->SDIOCLKSEL = some value;  
}

there is no error. Why is that? Why can't I write directly to the structure?

Any answer would be greatly appreciated!

Questioner
Vincent
Viewed
102
Lundin 2019-07-03 22:35
#define SYSCON_BASE         (0x40000000u)

This simply lists that at the physical address 0x40000000.

#define SYSCON ((SYSCON_Type *)SYSCON_BASE)

This converts the integer constant 0x40000000u to a pointer to struct by means of a cast. It doesn't actually allocate anything - the actual registers are already allocated as memory-mapped hardware.

Simply put, it says "at address 0x40000000 there's a hardware peripheral SYSCON" (whatever that is, some timer?). It's a common scenario that you have several hardware peripherals of the same type inside a MCU (many SPI, ADC etc), each with the same register layout, but found at different addresses. We can use the same struct type for each such peripheral, and also the same driver code.

The struct itself will have a memory map which corresponds 100% to the register layout. Here it is important to ensure that padding/alignment doesn't screw things up, but hopefully the MCU manufacturer have thought of that (don't take it for granted though).

Assuming SDIOCLKSEL has a register offset of 0x10, then when you type SYSCON->SDIOCLKSEL = some value;, you get machine code like this (pseudo assembler code):

LOAD 0x40000000 into index register X  
LOAD 0x10 into register A
ADD A to X
MOVE some value into the address of X

(ARM got special instructions that can move etc based on an offset, so it may be fewer instructions in the actual machine code. Subsequent register accesses could keep "X" untouched and use that base address repeatedly, for effective code.)

The __IO qualifier is just code bloat hiding volatile.

The reason why you get an error when you try to "write directly into the structure" is simply that you can't execute code outside all functions, it has nothing to do with this struct.