Skip to content

Expert · Embedded C, system calls and careers · topic 56 of 60 · 30 min

Embedded C basics

Explanation

Embedded C runs on microcontrollers: small chips with a processor, a little RAM and flash memory, and pins that control the outside world. Hardware is controlled through memory-mapped registers, accessed with volatile pointers to fixed addresses, using bit masks to set individual pins. Code is cross-compiled on a PC (for example with arm-none-eabi-gcc) and flashed onto the chip.

Microcontrollers

A microcontroller (MCU) is a complete small computer on one chip: CPU, RAM (often a few KB to a few hundred KB), flash memory for the program, and peripherals such as GPIO pins, timers, ADCs (analogue-to-digital converters) and UARTs (serial ports). The Arduino Uno uses an 8-bit AVR chip (ATmega328P, 2 KB of RAM); STM32 and many others use 32-bit ARM Cortex-M cores. They run in washing machines, car engine controllers, glucometers, smart meters and drones.

PC programMicrocontroller firmware
Operating systemWindows/Linux/macOSOften none ("bare metal") or a small RTOS
MemoryGigabytesKilobytes
OutputScreen, filesPins, LEDs, motors, sensors
main()Returns when doneNever returns: an endless loop
Buildgcc on the same machineCross-compiler on a PC, then flash the chip

Memory-mapped registers

Each peripheral is controlled by registers: special memory addresses where reading or writing bits controls the hardware. Writing a 1 to a bit in a GPIO output register drives a pin high (for example turning on an LED). In C, you access such an address through a pointer to a fixed address, declared volatile.

c
#include <stdint.h>

/* ILLUSTRATIVE addresses only: real ones come from your chip's reference manual or vendor headers */
#define GPIOA_ODR  (*(volatile uint32_t *) 0x40020014u)   /* output data register */
#define LED_PIN    5u

void led_on(void)     { GPIOA_ODR |=  (1u << LED_PIN); }   /* set bit 5 */
void led_off(void)    { GPIOA_ODR &= ~(1u << LED_PIN); }   /* clear bit 5 */
void led_toggle(void) { GPIOA_ODR ^=  (1u << LED_PIN); }

Why volatile?

Normally the compiler may keep a variable in a CPU register, skip repeated reads, or delete writes whose results it never sees used. For a hardware register, every read and write matters: the value can change on its own (a status bit set by hardware), and writes have effects outside the program. volatile tells the compiler: perform every access exactly as written, never cache it.

c
#define UART_SR  (*(volatile uint32_t *) 0x40011000u)   /* illustrative */
#define RXNE     (1u << 5)                               /* "received a byte" flag */

while ((UART_SR & RXNE) == 0) { }   /* without volatile, the compiler could read once and loop forever */
Careful: volatile does NOT make code thread-safe or atomic, and it is not a fix for data races between threads on a PC. Its job is narrow: memory-mapped hardware, and variables shared with interrupt handlers (with extra care; next topic). Also never invent register addresses: use the vendor's headers (CMSIS, HAL) generated from the chip's datasheet.

Fixed-width types and the build

Registers have exact widths, so embedded code uses uint8_t, uint16_t and uint32_t from <stdint.h> rather than int. Code is compiled on a PC with a cross-compiler such as arm-none-eabi-gcc (for ARM Cortex-M) or avr-gcc (for AVR), linked with a linker script that describes the chip's memory, and flashed through a programmer/debugger (ST-Link, J-Link) or a USB bootloader, as the Arduino IDE does.

Think of it like this: Memory-mapped registers are like the switchboard in a house where each switch is wired to a real appliance. Flipping a switch (writing a bit) turns on the fan; volatile is the rule that you must actually walk to the switchboard every time, instead of assuming the fan is still in the state you last remember.
Tip: Start with an Arduino or an inexpensive STM32 "Nucleo" or "Blue Pill" board. Blink an LED with direct register access once, so you understand what the friendly library functions (digitalWrite, HAL_GPIO_WritePin) do underneath.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Embedded C basics · 1/6

Embedded C

In this lesson you will see how C controls hardware on microcontrollers.

Prefer a video by a teacher? Find free videos on “embedded c tutorial microcontroller registers volatile stm32 arduino” on YouTube ↗

In real life: Blinking an LED on a microcontroller

The "hello world" of embedded systems is blinking an LED. The program configures a pin as output, then loops forever toggling it and waiting. The register names and addresses come from the chip vendor's header file; the structure below is the same on almost every microcontroller.

c
#include "stm32f4xx.h"            /* vendor header with real register definitions */

static void delay(volatile uint32_t n) { while (n--) { } }   /* crude busy wait */

int main(void) {
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;          /* enable the clock for port A */
    GPIOA->MODER |= (1u << (5 * 2));              /* pin 5 as output */
    for (;;) {                                    /* firmware never returns */
        GPIOA->ODR ^= (1u << 5);                  /* toggle the LED */
        delay(500000);
    }
}

Practice (0/3 done)

Task 1 · Question

Why is a hardware status register accessed through a volatile pointer?

Task 2 · Code · Javascript

Simulate a GPIO output register in JavaScript, which runs here: write createGpio() returning { setPin(n), clearPin(n), togglePin(n), isHigh(n), value() } that keeps a 32-bit register value (start at 0) and uses only bitwise operators. value() returns the register as an unsigned number (use >>> 0).

Task 3 · Practice

If you have an Arduino or STM32 board: blink an LED first with the library function, then with direct register access using the datasheet or vendor header, and measure the blink rate. If you have no board, use an online simulator (for example Wokwi for Arduino/STM32) and do the same.

Quick quiz

Question 1

1.What is a microcontroller?

Question 2

2.What does arm-none-eabi-gcc do?

Question 3

3.Why does firmware main() usually never return?

Question 4

4.Does volatile make a variable safe to share between threads?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is a microcontroller?

Answer: A small computer on one chip with CPU, RAM, flash and peripherals

Used in embedded devices.

What does arm-none-eabi-gcc do?

Answer: Cross-compiles C for bare-metal ARM microcontrollers

Compiling for a different target.

Why does firmware main() usually never return?

Answer: There is no operating system to return to; the device runs until power-off

An endless super loop.

Does volatile make a variable safe to share between threads?

Answer: No: it only prevents the compiler from optimising away accesses

Use atomics or locks for threads.

Which is a real-time operating system often used on microcontrollers?Related

Answer: FreeRTOS

Zephyr is another.

From Embedded patterns: interrupts, timing and constraints

Which expression clears bit 5 of x?Related

Answer: x &= ~(1u << 5)

AND with a mask that has a 0 only at bit 5.

From Masks and flags

On a typical 64-bit system, sizeof(char *) is…Related

Answer: 8

All data pointers are 8 bytes on 64-bit systems.

From Addresses and pointers

On a little-endian machine, which byte of uint32_t x = 0x11223344 is stored first in memory?Related

Answer: 0x44

The least significant byte comes first.

From C interview questions

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Embedded C, system calls and careers” before its exam: Embedded patterns: interrupts, timing and constraints, C on Linux: files and processes with system calls, C interview questions, Projects and careers with C.
  2. Module 12 exam: Embedded C, system calls and careers · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. 60 topics left in the course before the final exam.

← Previous: A method for debugging