Assembly Language
Introduction
At the lowest level, a CPU does only a handful of things: move numbers between registers and memory, add, subtract, compare, and jump to a different instruction. Machine code encodes those actions as binary. Assembly language gives each opcode a short name (mnemonic) humans can read: add, mov, ldr, bne.
You will not rewrite your robot firmware in assembly. But when the debugger stops in a HardFault, when you wonder why -O2 broke your timing, or when you read the vector table in a datasheet, assembly is the language those tools speak. This article builds intuition — not replace your C compiler.
Three levels of the same program
| Level | What "Hello" looks like | Who reads it |
|---|---|---|
| C | printf("Hello\n"); |
You |
| Assembly | mov, bl, ldr … |
Experienced engineers, tools |
| Machine | 1110101100… |
CPU only |
The compiler is a translator from C down the ladder. GCC can stop halfway and show you assembly (gcc -S). The linker finishes the job to machine code.
In plain terms
C is like giving directions in full sentences. Assembly is like shorthand notes: "R0 = 5", "jump if equal". Machine code is the GPS coordinates only the car understands.
Why assembly still appears in embedded careers
| Reason | Real situation |
|---|---|
| Boot & vectors | Reset handler jumps to Reset_Handler — assembly label in startup file |
| Debugging | Disassembly shows exact instructions executed before crash |
| Timing | Count cycles for ISR when microsecond jitter matters |
| Hardware quirks | Barriers (DSB, ISB), disable interrupts — sometimes intrinsics/asm |
| Size | Tiny bootloader may hand-write a few dozen instructions |
Application logic stays in C. Assembly is for edges where the machine model is visible.
Structure of an assembly program
Assembly source is divided into sections:
.text— instructions (program code).data— initialized constants and variables.bss— reserved zeroed space (often)
Execution flows top to bottom unless a branch jumps elsewhere. Interrupts preempt normal flow — save registers, run handler, restore, return.
On Cortex-M, startup startup_stm32f4xx.s sets stack pointer, copies .data, zeroes .bss, calls main — all before your C runs.
One instruction, two parts
Every instruction has an opcode (what to do) and operands (what to do it to):
1 | |
Categories you will see everywhere:
| Category | Examples | Purpose |
|---|---|---|
| Arithmetic / logic | add, sub, and, cmp |
Calculate, set flags |
| Load / store | ldr, str |
Move between memory and registers |
| Branches | b, beq, bl |
Jump, call function |
| Special | nop, wfi |
Delay slot, sleep until interrupt |
cmp subtracts invisibly and sets condition flags (equal, less than, …). beq label branches if equal — how while and if become machine code.
Addressing modes — where operands live
| Mode | Example | Meaning |
|---|---|---|
| Register | add r0, r1, r2 |
All values in CPU registers |
| Immediate | mov r0, #42 |
Constant built into instruction |
| Memory (offset) | ldr r0, [r1, #4] |
Load from address r1 + 4 |
| PC-relative | b loop |
Jump relative to current instruction |
In plain terms
Registers are the CPU's hands — only a few, very fast. Memory is the warehouse — huge but slower. RISC machines insist you carry values from warehouse to hands before doing math. CISC machines sometimes let you add directly in the warehouse.
CISC vs RISC — two design philosophies
| CISC (e.g. x86) | RISC (e.g. ARM Cortex-M, RISC-V) | |
|---|---|---|
| Instruction count | Large, powerful | Smaller set |
| Instruction size | Variable length | Often fixed (Thumb-2 mixes 16/32) |
| Memory in ALU ops? | Often yes | No — load/store separate |
| Pipelining | Harder | Designed for it |
Your STM32 / nRF / ESP32 (Xtensa) firmware is mostly RISC Thumb. Laptop lab tools might compile for CISC x86 — same C, different assembly when you use gcc -S.
Same C loop, different assembly flavour
C:
1 2 3 | |
CISC (x86) may compare memory directly:
1 | |
RISC (ARM) loads first, then compares registers:
1 2 | |
Neither is "smarter" — architecture choice. See Computer architecture.
Other ISA families (awareness)
| Type | Typical use |
|---|---|
| DSP | Audio filtering, motor control — multiply-accumulate in one instruction |
| VLIW | Very long instruction words — multiple parallel ops (some TI DSPs) |
Cortex-M4 DSP extensions blur the line with SIMD-ish instructions for signal work.
Mixing C and assembly
When C is enough
The compiler optimizes loops, register allocation, and calling conventions. Trust it until measurement says otherwise.
Inline assembly (GCC)
1 2 3 4 5 6 7 8 9 | |
Operands and clobbers tell GCC which registers change. Easy to get wrong.
Prefer vendor intrinsics on ARM: __disable_irq(), __NOP(), __DSB() — same effect, compiler-aware.
Reading what GCC produced
1 2 | |
In GDB: disassemble main, then si (step instruction). Map a surprising delay to extra loads or a branch in a tight loop.
Calling convention — who owns which register
When main calls foo, rules decide which registers foo may overwrite and where arguments go. On ARM AAPCS, first arguments in r0–r3, return in r0, link register lr holds return address.
If an ISR clobbers registers without saving them, C variables mysteriously corrupt — disassembly + ABI docs explain why.
Relevant topics
Starting points
gcc -Sa function with anifand awhile— draw arrows for each branch label.- Compare x86 vs ARM listings for the same tiny C file (online godbolt.org is excellent).
- In debugger, break in
main, switch to disassembly view, step one instruction. - Find
Reset_Handlerin your startup.sfile — trace path tomain.
Focus points
- Assembly is CPU-specific — skills do not port verbatim.
- Do not optimize until you profile — compiler is good at register allocation.
- ISRs must respect calling convention or save context.
- Use intrinsics before raw inline asm on Cortex-M.
Key points
- Assembly maps mnemonics to machine opcodes; readable face of the CPU.
- Addressing modes say whether operands live in registers, constants, or memory.
- RISC uses load/store + register ops; CISC allows more memory operands.
- Disassembly and startup files connect C abstractions to what the silicon actually runs.