Expert · Embedded C, system calls and careers · topic 56 of 60 · 30 min
Embedded C basics
Explanation
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 program | Microcontroller firmware | |
|---|---|---|
| Operating system | Windows/Linux/macOS | Often none ("bare metal") or a small RTOS |
| Memory | Gigabytes | Kilobytes |
| Output | Screen, files | Pins, LEDs, motors, sensors |
| main() | Returns when done | Never returns: an endless loop |
| Build | gcc on the same machine | Cross-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.
#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.
#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 */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.
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
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.
#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)
Why is a hardware status register accessed through a volatile pointer?
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).
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Embedded patterns: interrupts, timing and constraintsEmbedded C, system calls and careers · same module
- C on Linux: files and processes with system callsEmbedded C, system calls and careers · same module
- C interview questionsEmbedded C, system calls and careers · same module
- Addresses and pointersPointers
- Compiling with gccGetting started with C
- Race conditions and synchronizedJava Programming (Core to Spring Boot) · Multithreading and concurrency
Questions people ask about this
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
Which expression clears bit 5 of x?Related
On a typical 64-bit system, sizeof(char *) is…Related
On a little-endian machine, which byte of uint32_t x = 0x11223344 is stored first in memory?Related
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
- 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.
- Module 12 exam: Embedded C, system calls and careers · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- 60 topics left in the course before the final exam.