Peripheral Programming

Writing firmware that controls MCU peripherals. Learn memory-mapped registers and bit manipulation, read-modify-write and atomic access, polling vs interrupts vs DMA, peripheral initialisation patterns, datasheet and reference-manual reading, hardware abstraction layers, and debugging with logic analysers.

Every MCU peripheral — GPIO, timer, UART, ADC — is controlled through registers at fixed addresses. Mastering bit manipulation and the reference manual lets you program any microcontroller, regardless of vendor library.

An analogy

Peripheral registers are like a mixing desk: each fader and switch (bit field) controls one feature, and you must set them in the right order to get sound.

Registers and bits

Each peripheral has control, status and data registers. Configuration usually means enabling the clock, setting mode fields, configuring pins, then enabling the peripheral. Status flags are polled or trigger interrupts.

Read-modify-write hazards

R |= x reads, modifies and writes — an interrupt in between can lose changes. Use atomic set/reset registers, bit-banding (Cortex-M3/M4), or disable interrupts briefly (critical sections).

Polling, interrupts, DMA

Polling is simple but wastes CPU; interrupts react to events; DMA moves bulk data with no CPU per byte. High-rate peripherals (ADC streams, UART at Mbaud, SPI displays) use DMA.

Drivers and debugging

Layered drivers (register → LL → HAL → application) and hardware abstraction layers make code portable. Debug with SWD breakpoints, register views, logic analysers for protocol timing, and oscilloscopes for analog signals.

Formulas

Worked example: Bit operations

Given: R = 0b1010_0000

Find: Set bit 0, clear bit 7, toggle bit 5.

Answer: 0000_0001

Worked example: Field write

Given: 4-bit mode field at bits 20–23, write 0x2

Find: Operation.

Answer: Clears the old field before writing the new value

Portable register access macros (C)

#include <stdint.h>

#define REG32(addr)          (*(volatile uint32_t *)(addr))
#define BIT(n)               (1u << (n))
#define FIELD_SET(r, m, p, v) ((r) = ((r) & ~((m) << (p))) | (((v) & (m)) << (p)))

#define GPIOA_BASE  0x40010800u         /* example: STM32F1 GPIOA */
#define GPIOA_ODR   REG32(GPIOA_BASE + 0x0C)
#define GPIOA_BSRR  REG32(GPIOA_BASE + 0x10)
#define GPIOA_CRL   REG32(GPIOA_BASE + 0x00)

void led_init(void) {
    FIELD_SET(GPIOA_CRL, 0xFu, 5 * 4, 0x2u);   /* PA5: output push-pull 2 MHz */
}
void led_on(void)  { GPIOA_BSRR = BIT(5); }      /* atomic set  (no read-modify-write) */
void led_off(void) { GPIOA_BSRR = BIT(5 + 16); } /* atomic reset */
int  led_is_on(void) { return (GPIOA_ODR >> 5) & 1u; }

volatile forces real hardware accesses. Set/reset registers (BSRR) change single bits atomically — safe even if an interrupt modifies the same port.

Common mistakes

Applications

Key takeaways

Going further