Ground Embedded KO Subscribe

Notes / C Language

Advanced pointers and function pointers

Pointer arithmetic, double pointers, and the function pointers used for callbacks and state machines.

GROUND EMBEDDED · NOTEC Language 07Advanced pointers and function pointers 1volatile uint32_t *ODR 2 = (uint32_t*)0x40020014; 3*ODR |= (1u << 5); 4while (1) { 5 if (flag) toggle(); 6}

Pointer arithmetic

p + 1 does not add 1 to the address; it moves by the size of the pointed-to type. For uint32_t *p it steps over 4 bytes. Because an array name behaves like the address of its first element, buf[i] and *(buf + i) are the same.

Double pointers

A pointer that holds the address of a pointer. Use it when a function needs to change the pointer itself, for example to allocate a buffer and hand it back.

Function pointers

Functions have addresses too. Store that address in a variable and you can choose later which function to run.

typedef void (*handler_t)(uint8_t);
handler_t on_rx = parse_command;
on_rx(byte);   // runs parse_command(byte)

In firmware they are used in these places.

  • Callbacks: a driver telling the code above it that something happened, such as "receive complete"
  • State machines: put each state's handler in a table and call the function for the current state
  • Vector table: the array of interrupt handler addresses is itself a table of function pointers

Functions have addresses too

A function is code placed at some address in Flash. A function pointer is a variable holding that address, and you can jump to the stored address and run it.

void led_on(void)  { /* ... */ }
void led_off(void) { /* ... */ }

void (*action)(void) = led_on;     // store the address
action();                          // run the function at that address
action = led_off;                  // can be changed at run time

If the declaration looks unfamiliar, give it a name with typedef.

typedef void (*handler_t)(uint8_t arg);

Use 1: a command table

Instead of a switch that keeps growing, pair command numbers with handler functions in a table.

typedef struct { uint8_t id; void (*fn)(const uint8_t *data, uint8_t len); } cmd_t;

static const cmd_t table[] = {
    { 0x01, cmd_read_status },
    { 0x02, cmd_set_speed   },
    { 0x10, cmd_reset       },
};

void dispatch(uint8_t id, const uint8_t *data, uint8_t len) {
    for (size_t i = 0; i < sizeof(table) / sizeof(table[0]); i++)
        if (table[i].id == id) { table[i].fn(data, len); return; }
    send_nack(id);                 // command not in the table
}

Adding a command means adding one line to the table. The table is const, so it sits in Flash and uses no RAM.

Use 2: separating layers with callbacks

If a driver calls application code directly, the two become tangled. Instead, the application registers "call this function when the work is done".

static void (*rx_cb)(uint8_t byte);
void uart_set_rx_callback(void (*cb)(uint8_t)) { rx_cb = cb; }

void USART1_IRQHandler(void) {
    uint8_t b = (uint8_t)USART1->RDR;
    if (rx_cb) rx_cb(b);           // check for NULL, then call
}

The UART driver does not know who uses the data. The same driver can be carried into another project unchanged.

Use 3: a state machine

typedef void (*state_fn)(void);
static state_fn state = state_idle;

void state_idle(void)    { if (start_pressed()) state = state_running; }
void state_running(void) { if (fault())         state = state_error;   }

while (1) { state(); }

The current state is the function to run. However many states there are, the main loop stays one line.

The vector table is an array of function pointers

An MCU's interrupt vector table is an array of function pointers listing handler addresses in order. When an interrupt fires, the CPU reads the address in the slot for that number and jumps there.

Problems that come up in real work

  • Calling NULL: calling a callback that was never registered jumps to address 0 and hard-faults. Check before calling.
  • Assigning a function of a different type: casting in a function with a different number or kind of arguments compiles, but the stack no longer lines up.
  • A long callback called from an ISR: the callback runs in interrupt context. Agree that it must finish quickly.
  • Table order and numbers out of step: a table looked up directly by index needs numbers and positions to match. Check the range first.
FREE PDFGet the 3 free ebooksIf you want to keep reading this topic as one book: BLDC motor control, serial communication and sensors at book length. PDFs are in Korean.Get them free →