Embedded systems and IoT: Arduino and ESP32

Sefism.

Chapter 01

C for embedded

The parts of C that matter most on hardware: exact-width integers, bitwise operations, pointers to registers, volatile and where your variables live in memory.

Fixed-width integers

Also called stdint.h, uint8_t, uint32_t, int16_t

What
Types from stdint.h such as uint8_t, int16_t and uint32_t whose size is the same on every compiler and chip, unlike int, whose size depends on the platform.
Why
Registers, sensor frames and protocol packets have exact sizes. Using the matching type makes overflow, sign and memory use predictable.
C
#include <stdint.h>

uint8_t  status = 0x80;     // 0 to 255
int16_t  raw_temp = -125;   // sensor value, signed
uint32_t uptime_ms = 0;     // wraps after about 49.7 days

Watch out

Mixing signed and unsigned values in one comparison, so -1 silently becomes a huge positive number.

Bitwise set, clear, toggle and test

Also called bit manipulation, bit masks, bitwise tricks, shift

What
Using OR to set a bit, AND with an inverted mask to clear it, XOR to toggle it and AND to test it, with shifts to build the mask.
Why
Every peripheral is controlled by bits inside registers. Changing one bit without touching its neighbours is the daily work of firmware.
C
#define BIT(n) (1U << (n))

reg |=  BIT(5);             // set bit 5
reg &= ~BIT(5);             // clear bit 5
reg ^=  BIT(5);             // toggle bit 5
if (reg & BIT(5)) { }       // test bit 5
x = (reg >> 4) & 0x0F;      // read the 4-bit field at bit 4

Watch out

Writing reg = BIT(5) when you meant reg |= BIT(5), which wipes every other setting in the register.

Memory-mapped registers

Also called peripheral registers, register map, MMIO

What
Peripheral control and status registers appear at fixed addresses, so C reads and writes them through pointers to those addresses.
Why
Libraries such as the Arduino core, the ESP-IDF drivers and the STM32 HAL are built on exactly this. Knowing it lets you read their source and the reference manual.
C
// A register at an address from the reference manual
#define GPIO_OUT (*(volatile uint32_t *)0x40020014U)

GPIO_OUT |= (1U << 5);      // drive pin 5 high

Watch out

Copying a register address from a different chip family, where the same peripheral lives somewhere else.

volatile

What
A qualifier telling the compiler a variable can change outside the code it can see, so every read and write must really happen.
Why
Hardware registers and variables shared with an interrupt change behind the compiler's back. Without volatile, optimised builds cache them and loops never end.
C
volatile bool button_flag = false;

void button_isr(void) {     // runs on the interrupt
    button_flag = true;
}
// main loop: while (!button_flag) { }  keeps re-reading

Watch out

Believing volatile makes a variable thread-safe: it stops caching, but a read-modify-write can still be interrupted halfway.

Pointers on hardware

Also called pointer, pointer arithmetic, dereference

What
A pointer holds an address. On a microcontroller that address may be RAM, flash or a peripheral, and pointer arithmetic moves in units of the pointed-to type.
Why
Buffers, DMA, driver structs and callbacks all pass pointers. Reading *(p + 2) fluently is required to read any driver code.

Watch out

Returning a pointer to a local variable, whose stack memory is reused as soon as the function returns.

Flash, RAM, stack and heap

Also called .data, .bss, stack overflow, linker script, memory map

What
Code and constants live in flash. Initialised globals (.data) and zeroed globals (.bss) live in RAM, the stack holds local variables and calls, and the heap serves malloc.
Why
Microcontrollers have kilobytes of RAM, not gigabytes. Knowing where each byte goes explains most random crashes and resets.
Example
An Arduino Uno has 2 KB of RAM. A 1,500-byte local array inside a function can overflow the stack and corrupt globals without any error message.

Watch out

Calling malloc and free repeatedly in a long-running device, which fragments the heap until an allocation fails days later.

const and static

Also called static variable, const data, PROGMEM

What
const marks data that never changes, so the toolchain can keep it in flash. static inside a function keeps a value between calls; at file level it hides the name from other files.
Why
Lookup tables and strings in flash save scarce RAM, and file-level static keeps drivers from clashing on names.

Watch out

On AVR boards such as the Uno, assuming const alone keeps a string in flash: it needs PROGMEM or the F() macro.

01 · C for embedded1 / 11