Skip to content

2. C, learned properly

Time: 3 to 4 weeks · Board: none, your computer · Prerequisites: none

Why this matters

C is still the language of firmware. It is small, which is why you can learn it in a month, and unforgiving, which is why people who half-learn it spend years chasing bugs. Embedded C in particular leans on the parts general programming courses skip: pointers to fixed addresses, bit manipulation, integer widths, and the idea that a variable can change without your code touching it.

Do this module without AI assistance

This is the one place we ask you to turn the assistants off. Write every exercise yourself, read every compiler error yourself, and use the debugger instead of print statements when something is wrong. The point is not the exercises. The point is building the mental model of memory that makes everything after this possible. You get the tools back in Module 3.

You will be able to

  • Set up a C compiler and debugger on your own machine and use them from the command line.
  • Write programs with functions, structs, arrays, and pointers, and explain what each one looks like in memory.
  • Use bitwise operators to set, clear, toggle, and test individual bits, and explain integer promotion.
  • Say what volatile, static, const, and fixed-width types like uint32_t mean and when each is needed.
  • Recognize undefined behavior and know why the compiler is allowed to surprise you.

Learn

Pick one main course from the first two. Do all of the "then" items.

  • Book Beej's Guide to C Programming by Brian "Beej" Hall ~25 h, free
    Friendly, complete, current (C11 and later), and it does not skip pointers or memory. Our default recommendation.
  • Course CS50x by Harvard Weeks 1 to 5, ~40 h, free
    If you prefer lectures and graded problem sets. Weeks 1 through 5 are C. Stop before Python.

Then, in this order:

  • Interactive Exercism C track ~10 h, free
    Small exercises with automated tests. Do at least 15. Mentoring is free too.
  • Video Jacob Sorber's channel Pick 5 to 10 videos, ~3 h
    A CS professor explaining C, pointers, memory, and systems programming in 10-minute pieces. Search his channel for "pointers", "bit fields", "volatile", "memory layout".
  • Video Bit Fields in C. What are they, and how do I use them? by Jacob Sorber ~12 min
    Because registers are bit fields, and you will use them constantly.
  • Article C Keywords to Use Often in Embedded Systems by Barr Group ~15 min
    static, volatile, const: what each really does and when firmware needs it.
  • Article How to Use C's volatile Keyword by Barr Group ~15 min
    The keyword that makes hardware registers work. Read it twice.
  • Article A Guide to Undefined Behavior in C and C++ by John Regehr ~30 min
    Why the compiler is allowed to delete your null check, and how to stop writing code that invites it.
  • Interactive Compiler Explorer ~30 min to explore
    Paste C, see the assembly. Try -O0 vs -O2 on a loop that toggles a volatile variable and one that toggles a plain one. You will understand volatile for life.

The embedded-specific bits

Do

Use GCC or Clang from a terminal, and GDB or LLDB to debug. On Windows, use WSL or MSYS2 so your commands match Linux; the firmware toolchain in Module 3 will feel identical.

  • Checkpoint 2.1: Bit toolkit. Write bits.h with functions or macros to set, clear, toggle, and test bit n of a uint32_t, plus one to insert a value into a bit field given a shift and width. Write a main() that tests every one and prints the results in binary. Compile with -Wall -Wextra -Werror and fix every warning.
  • Checkpoint 2.2: A fake peripheral. Define a struct that mirrors a made-up hardware register block: a control register, a status register, and a 16-byte data buffer. Create one instance, take a volatile pointer to it, and write a "driver" with functions like periph_enable(), periph_write_byte(), and periph_is_busy() that only touch the hardware through that pointer. This is exactly how real drivers work.
  • Checkpoint 2.3: Debugger, not printf. Introduce a deliberate off-by-one bug in the buffer code from 2.2. Find it using only the debugger: breakpoints, watch a variable, step through, inspect memory. Then write down what you saw.

Check yourself

  • What is the difference between char *p and char p[] as function parameters? As global definitions?
  • Why does uint8_t a = 200, b = 100; uint8_t c = a + b; not overflow the way you might expect? What is the type of a + b?
  • What does volatile promise, and what does it not promise about concurrency?
  • Why is int a bad choice for a value that must be exactly 32 bits on every platform?
  • Give one example of undefined behavior and one of implementation-defined behavior.

Go deeper

Optional extras
  • Book Modern C by Jens Gustedt Free PDF
    The best second book on C. Rigorous, current, and honest about the language's sharp edges.
  • Book Paid Effective C, 2nd Edition by Robert Seacord
    By a member of the C standards committee. Excellent on undefined behavior and security.
  • Article Scope Regions in C and C++ by Dan Saks ~20 min
    Scope, storage duration, and linkage are three different things. Saks untangles them.
  • Docs GCC attributes: syntax, function attributes, variable attributes and Pragmas
    __attribute__((section(".vectors"))), aligned, packed, weak, naked: you will meet all of these in startup code.
  • Article Inline Functions in C by Richard Kettlewell ~15 min
    inline means something different in C than most people think.
  • Course The Missing Semester of Your CS Education by MIT ~10 h
    Shell, editors, version control, debugging tools. The stuff nobody teaches and everyone needs.