There are no items in your cart
Add More
Add More
| Item Details | Price | ||
|---|---|---|---|
Preparing for embedded systems interviews? Here are 50 carefully selected Embedded C questions covering the most frequently asked topics. Each question includes a concise answer to help you revise quickly.
Embedded C is a variant of C optimized for microcontrollers and embedded systems. Unlike standard C, it focuses on direct hardware access via memory-mapped I/O, uses volatile for hardware registers, works without an OS (or with minimal RTOS), and demands careful memory and cycle management.
On a 32-bit system: char is 1 byte, short is 2 bytes, int is 4 bytes, long is 4 bytes, float is 4 bytes, and double is 8 bytes. In embedded systems, prefer fixed-width types from stdint.h (uint8_t, uint16_t, uint32_t) for portability across platforms.
Local variables are declared inside a function, stored on the stack, and exist only during function execution. Global variables are declared outside all functions, stored in BSS/data segments, and persist for the entire program. In embedded systems, excessive globals waste limited RAM.
C has four storage classes: auto (default local, stack-based), static (persists across calls or limits scope to file), extern (declares variable defined elsewhere), and register (hints to keep in CPU register). In embedded C, static is crucial for preserving state in ISRs and limiting symbol visibility.
A struct allocates memory for ALL members (total size = sum of member sizes + padding). A union shares memory among members (size = largest member). Unions are used in embedded systems for register overlays and interpreting the same memory differently.
A pointer is a variable that stores the memory address of another variable. In embedded C, pointers are essential for accessing hardware registers, manipulating memory-mapped I/O, and efficient data handling. On a 32-bit system, all pointers are 4 bytes regardless of the data type they point to.
Pointer arithmetic allows incrementing/decrementing pointers by the size of the data type. If int *p points to address 0x1000, then p+1 points to 0x1004 (adding sizeof(int)). This is widely used for array traversal and buffer manipulation in embedded systems.
A NULL pointer holds value 0 and points to no valid memory. Dereferencing NULL causes undefined behavior (usually a crash or HardFault on ARM). Always check pointers before dereferencing, especially in embedded systems where crashes can be dangerous.
Pass by value copies the actual value to the function — changes don't affect the original. Pass by reference (via pointers in C) passes the address, allowing the function to modify the original. Embedded systems prefer pass by reference for large structs to save stack space.
A function pointer stores the address of a function and allows calling functions indirectly. They enable callback mechanisms, jump tables, and state machines in embedded systems. Syntax: void (*fptr)(int) declares a pointer to a function taking int and returning void.
C provides six bitwise operators: AND (&), OR (|), XOR (^), NOT (~), left shift (<<), and right shift (>>). These are essential in embedded C for manipulating individual bits in hardware registers without affecting other bits.
Set bit n: reg |= (1 << n). Clear bit n: reg &= ~(1 << n). Toggle bit n: reg ^= (1 << n). These are the most common operations in embedded C for controlling hardware peripherals through their configuration registers.
Use: if (reg & (1 << n)). This ANDs the register with a mask that has only bit n set. If the result is non-zero, bit n is set. This is used to check status flags, interrupt pending bits, and peripheral ready states.
Bit masking uses bitwise AND with a mask to extract specific bits from a register. For example, (reg & 0x0F) extracts the lower nibble. Masks isolate bit fields in hardware registers where multiple settings share one register.
Use XOR swap: a ^= b; b ^= a; a ^= b. This works because XOR is its own inverse. However, in production embedded code, a temporary variable is preferred for clarity and because XOR swap fails when both variables point to the same memory.
BOOTCAMP 4.0 — EARLY BIRD OPENS APRIL 25
Bootcamp 4.0 covers Embedded C, RTOS, Device Drivers, ARM Architecture, Interview Prep & Live Projects.
Join the WhatsApp group for first access & special pricing.
Join Bootcamp 4.0 Early Bird GroupA C program has five segments: Text (code), Data (initialized globals), BSS (uninitialized globals, zero-filled), Heap (dynamic allocation, grows up), and Stack (local variables, grows down). In embedded systems, these map to Flash (text) and RAM (data, BSS, heap, stack).
Stack is automatically managed (LIFO), fast, limited in size, and used for local variables and function calls. Heap requires manual allocation (malloc/free), is slower, and prone to fragmentation. Most embedded systems avoid heap entirely due to fragmentation risks and non-deterministic timing.
Memory-mapped I/O maps hardware peripheral registers to specific memory addresses. Reading/writing these addresses directly controls hardware. For example, writing to address 0x40021018 might set GPIO pins. Access through volatile pointers: *(volatile uint32_t*)0x40021018 = value.
DMA allows peripherals to transfer data directly to/from memory without CPU involvement. The CPU sets up source, destination, and transfer size, then the DMA controller handles the transfer. This frees the CPU for other tasks and is crucial for high-speed data transfer (ADC, UART, SPI).
Most processors require data to be aligned to boundaries matching their size — 32-bit values at 4-byte boundaries, 16-bit at 2-byte boundaries. Misaligned access causes a HardFault on ARM Cortex-M or performance penalties on other architectures. Compilers add padding in structs for alignment.
volatile tells the compiler that a variable's value can change unexpectedly (by hardware, ISR, or another thread). It prevents the compiler from optimizing away reads/writes to that variable. Essential for hardware registers, ISR-shared variables, and memory-mapped I/O.
const makes a variable read-only after initialization. In embedded C, const variables are stored in Flash (ROM) instead of RAM, saving precious RAM. Use const for lookup tables, configuration data, and string literals that don't change at runtime.
const is a typed, scoped variable stored in memory (Flash for embedded). #define is a preprocessor text replacement with no type checking or scope. const is preferred in modern embedded C because it's type-safe, debugger-visible, and follows scope rules.
volatile int *p means p points to volatile data (the data can change unexpectedly). int *volatile p means the pointer itself is volatile (the address stored in p can change). For hardware registers, use volatile int *p. Both qualifiers can be combined when needed.
restrict (C99) tells the compiler that a pointer is the only way to access that memory, enabling better optimizations. Useful in embedded DSP code where the compiler can vectorize loops. It's a promise by the programmer — violating it causes undefined behavior.
The preprocessor runs before compilation, handling directives starting with #. It performs text substitution (#define), file inclusion (#include), and conditional compilation (#ifdef/#endif). In embedded C, it's crucial for hardware abstraction, configuration management, and platform-specific code.
Angle brackets (#include
#ifdef checks if a macro is defined; #ifndef checks if it's NOT defined. They enable conditional compilation — compiling different code for different hardware platforms, debug vs release builds, or feature toggles. #ifndef with #define creates include guards to prevent double-inclusion of headers.
Function-like macros take parameters: #define MAX(a,b) ((a)>(b)?(a):(b)). They expand inline with zero function-call overhead, useful for embedded systems. Always wrap parameters and the entire macro in parentheses to avoid operator precedence bugs.
Variadic macros accept a variable number of arguments using ... and __VA_ARGS__. Common use: debug logging macros like #define DBG(...) printf(__VA_ARGS__) that can be disabled in release builds with #define DBG(...) to eliminate all debug output and save code space.
An interrupt is a hardware signal that causes the CPU to suspend current execution and jump to an Interrupt Service Routine (ISR). After the ISR completes, the CPU resumes the interrupted code. Interrupts enable real-time response to events like button presses, timer overflows, and data reception.
Interrupt latency is the time between an interrupt being triggered and the first instruction of its ISR executing. It includes time for finishing the current instruction, saving context (registers), and fetching the ISR address from the vector table. ARM Cortex-M has 12-cycle latency with tail-chaining optimization.
Polling continuously checks a flag in a loop, wasting CPU cycles. Interrupt-driven I/O lets the CPU do other work and responds only when the event occurs. Interrupts are more efficient and responsive but add complexity. Use polling for simple, time-critical tight loops; interrupts for everything else.
A nested interrupt occurs when a higher-priority interrupt preempts a currently executing ISR. ARM Cortex-M NVIC supports this natively with priority levels. Each nested interrupt adds a stack frame, so deep nesting can cause stack overflow. Careful priority assignment is essential.
Interrupt priority determines which ISR runs first when multiple interrupts are pending. Lower numbers = higher priority on ARM Cortex-M. The NVIC supports both preempt priority (can interrupt running ISR) and sub-priority (tie-breaking). Configure via NVIC_SetPriority() in CMSIS.
BOOTCAMP 4.0 — EARLY BIRD OPENS APRIL 25
Bootcamp 4.0 covers Embedded C, RTOS, Device Drivers, ARM Architecture, Interview Prep & Live Projects.
Join the WhatsApp group for first access & special pricing.
Join Bootcamp 4.0 Early Bird GroupAn RTOS (Real-Time Operating System) provides deterministic task scheduling, ensuring critical tasks meet their deadlines. Unlike bare-metal (super-loop), an RTOS enables multitasking with priority-based preemption, inter-task communication, and timing services. Common choices: FreeRTOS, Zephyr, ThreadX.
Task priority determines scheduling order — higher priority tasks preempt lower ones. Priority inversion occurs when a high-priority task is blocked waiting for a resource held by a low-priority task, while a medium-priority task runs. Solution: priority inheritance or priority ceiling protocols.
Semaphores are signaling mechanisms: binary semaphores for synchronization, counting semaphores for resource counting. Mutexes provide mutual exclusion with ownership — only the task that locked it can unlock it. Mutexes support priority inheritance to prevent priority inversion. Use mutexes for protecting shared resources.
A message queue enables tasks to send and receive data asynchronously. The sender doesn't block (unless queue is full), and the receiver blocks until data is available. Queues decouple producer and consumer tasks, enabling clean inter-task communication without shared global variables.
A watchdog timer is a hardware counter that resets the system if not periodically refreshed (kicked). If software hangs or crashes, the watchdog expires and forces a reset, recovering the system. Essential for reliability in embedded systems that must run unattended for long periods.
UART (Universal Asynchronous Receiver/Transmitter) is a serial protocol using TX and RX lines with no clock signal. Both sides must agree on baud rate (e.g., 9600, 115200). Data is framed with start bit, 8 data bits, optional parity, and stop bit. Simple and widely used for debugging and sensor communication.
SPI (Serial Peripheral Interface) is a synchronous, full-duplex protocol using 4 wires: MOSI, MISO, SCLK, and CS. It's faster than UART and I2C (up to 50+ MHz). The master controls the clock and selects slaves via individual CS lines. Used for Flash memory, displays, and high-speed sensors.
I2C (Inter-Integrated Circuit) is a synchronous, half-duplex protocol using just 2 wires: SDA (data) and SCL (clock). It supports multiple masters and slaves on one bus, with 7-bit addressing (up to 128 devices). Speeds: 100 KHz (standard), 400 KHz (fast), 3.4 MHz (high-speed). Great for sensors and EEPROMs.
CAN (Controller Area Network) is a robust, multi-master serial protocol designed for automotive and industrial use. It uses differential signaling for noise immunity, supports message prioritization via arbitration, and has built-in error detection. Speeds up to 1 Mbps. Used in vehicles, industrial automation, and medical devices.
Synchronous communication uses a shared clock signal (SPI, I2C) — the receiver samples data on clock edges. Asynchronous communication (UART) has no clock — both sides agree on baud rate and use start/stop bits for framing. Synchronous is faster and more reliable; asynchronous is simpler and needs fewer wires.
MISRA C is a set of coding guidelines for safety-critical embedded C development, originally created for automotive. It defines mandatory and advisory rules covering type safety, pointer usage, control flow, and more. Required in automotive (ISO 26262), aerospace (DO-178C), and medical device software.
Static analysis examines source code without executing it, detecting potential bugs, MISRA violations, and security vulnerabilities. Tools like PC-lint, Polyspace, and Coverity catch issues such as null pointer dereferences, buffer overflows, and unreachable code at compile time, reducing costly runtime debugging.
Big-endian stores the most significant byte at the lowest address; little-endian stores the least significant byte first. ARM Cortex-M is typically little-endian. Endianness matters when communicating between different processors or reading multi-byte values from network protocols (which use big-endian).
A linker script defines how code and data sections are placed in memory. It specifies Flash and RAM regions, where text/data/BSS go, stack size, and heap size. Essential for embedded systems where you must control exact memory layout. ARM GCC uses .ld files with MEMORY and SECTIONS directives.
Low-power design minimizes energy consumption through hardware sleep modes (Sleep, Stop, Standby on STM32), clock gating, peripheral power-down, and voltage scaling. Software strategies include reducing CPU frequency, using interrupts instead of polling, and batching operations. Critical for battery-powered IoT and wearable devices.
BOOTCAMP 4.0 — EARLY BIRD OPENS APRIL 25
Bootcamp 4.0 covers Embedded C, RTOS, Device Drivers, ARM Architecture, Interview Prep & Live Projects.
Join the WhatsApp group for first access & special pricing.
Join Bootcamp 4.0 Early Bird GroupGood luck with your interviews! Bookmark this page and revisit before your next embedded systems interview.