How it works
Embedded C uses standard C with hardware-specific extensions: memory-mapped registers are declared as volatile unsigned int* or via macros (e.g., DDRB, PORTB in AVR headers). Bit manipulation: set bit n: REG |= (1<<n); clear bit n: REG &= ~(1<<n); toggle bit n: REG ^= (1<<n); read bit n: (REG >> n) & 1. The volatile qualifier tells the compiler not to optimise away repeated reads or writes to a variable, essential for hardware registers and ISR-shared variables. An ISR in GCC-AVR is declared as ISR(TIMER1_COMPA_vect){...} with the avr/interrupt.h header; global interrupt enable requires sei() and ISR entry automatically clears the global interrupt flag. ARM Cortex-M CMSIS header defines GPIO registers: GPIOA->ODR |= (1<<5) sets PA5 on STM32.
Key points to remember
The volatile keyword is the most commonly examined embedded C concept: without it, the compiler may read a hardware register once and keep reusing the cached value even when the hardware changes it — a peripheral status register that the CPU is polling will never appear to change. Fixed-width integer types from <stdint.h> — uint8_t, uint16_t, uint32_t — are mandatory in embedded C because int size varies across architectures (16-bit on AVR, 32-bit on ARM). Interrupt latency on AVR ATmega at 16 MHz is approximately 4–5 clock cycles (250–312 ns) from interrupt assertion to first ISR instruction. Watchdog timer programming (wdt_enable(WDTO_2S) in AVR) resets the MCU if the software fails to clear it within the specified period, providing fault tolerance. Stack overflow is a common embedded bug because MCU SRAM is small (2 KB on ATmega328P) — deep function nesting or large local arrays in ISRs cause silent corruption.
Exam tip
The examiner always asks you to write an embedded C code snippet to configure a GPIO pin as output and toggle it inside a timer ISR, then explain why volatile is used for the flag shared between ISR and main — write the full ISR function, the main loop, and mark the volatile variable declaration clearly.