Google Embedded Developer (Mid-Level) Interview Preparation Guide
Google's Embedded Software Engineer interview process for mid-level candidates combines technical depth with practical problem-solving. The process includes an initial recruiter screening, a technical phone screen focused on embedded systems and coding, and multiple onsite rounds covering low-level programming, system design, hardware-software integration, real-time systems optimization, and behavioral assessment. Interviews emphasize C programming proficiency, embedded systems concepts, bit manipulation, driver development, and practical experience with hardware constraints.
Interview Rounds
Recruiter Screening
What to Expect
Initial conversation with a Google recruiter to assess your background, motivation, and fit for the Embedded Developer role. The recruiter will discuss your experience with embedded systems, hardware-software integration projects, and clarify role expectations. This is a soft evaluation to ensure basic qualifications and cultural fit before technical interviews.
Tips & Advice
Prepare a clear 2-3 minute pitch about your embedded systems experience. Highlight specific projects involving microcontrollers, firmware, or hardware interaction. Ask questions about the specific embedded domain at Google (IoT, hardware platforms, performance constraints). Be enthusiastic about low-level systems work and demonstrate genuine interest in embedded development rather than general software engineering.
Focus Topics
Background & Career Journey
Clear articulation of your embedded systems experience, progression, and motivation for Google
Practice Interview
Study Questions
Google Role Understanding
Knowledge of what Google Embedded Developer role involves and alignment with your career goals
Practice Interview
Study Questions
Relevant Project Experience
Concrete examples of embedded projects (microcontrollers, firmware, drivers, IoT) with measurable outcomes
Practice Interview
Study Questions
Technical Phone Screen
What to Expect
A 60-minute technical interview conducted over video/phone with a Google engineer. This round assesses your embedded systems knowledge and coding ability under pressure. Expect a combination of practical embedded problems, bit manipulation questions, basic data structures, and C programming fundamentals. Problems are more practical and hardware-oriented than standard algorithmic challenges. You'll be coding in a shared document or IDE.
Tips & Advice
Focus on C programming correctness and understanding data types. Practice bit manipulation (bit shifting, masking, flags) as this is heavily tested. Think aloud about memory implications and hardware constraints even in simple problems. Be prepared to handle embedded-specific scenarios like register manipulation, interrupt handling concepts, or power optimization. Don't over-engineer solutions; simplicity and correctness matter. If stuck, explain your thought process rather than guessing.
Focus Topics
Arrays, Strings & Basic Data Structures
Practical usage of arrays, strings, linked lists, and simple data structures in embedded context
Practice Interview
Study Questions
Memory Management & Optimization
Stack vs heap, memory constraints in embedded systems, buffer management, and avoiding memory waste
Practice Interview
Study Questions
Embedded Problem Solving
Practical problems involving hardware constraints, register manipulation, interrupt handling concepts, or device communication
Practice Interview
Study Questions
Bit Manipulation & Bitwise Operations
Bit shifting, masking, setting/clearing bits, flag operations, and practical register manipulation
Practice Interview
Study Questions
C Programming & Fundamentals
Core C syntax, pointer manipulation, memory management, and understanding of data types for embedded contexts
Practice Interview
Study Questions
Onsite Round 1: Low-Level Programming & Embedded Fundamentals
What to Expect
First onsite technical round focusing on low-level C programming, microcontroller programming concepts, and embedded systems architecture. You'll work through problems that involve understanding hardware registers, bit-level operations, interrupt handling, and peripheral communication. This round evaluates your comfort with assembly-level thinking and hardware-software interaction.
Tips & Advice
Draw diagrams if discussing register layouts or memory mapping. Explain your understanding of how code translates to hardware operations. Be specific about data types chosen and why (int vs uint32_t, etc.). Discuss trade-offs between performance and code clarity. If you mention RTOS or specific microcontroller experience, be prepared to go deep. Think about edge cases like integer overflow in embedded contexts.
Focus Topics
Peripheral Communication Protocols (I2C, SPI, UART)
Understanding serial communication protocols used in embedded systems, protocol basics, and troubleshooting communication issues
Practice Interview
Study Questions
Interrupts & Exception Handling
Interrupt service routines (ISRs), interrupt priorities, edge cases in interrupt handling, and atomic operations
Practice Interview
Study Questions
Code Optimization for Embedded Constraints
Optimization techniques for limited memory, CPU cycles, and power consumption specific to embedded platforms
Practice Interview
Study Questions
Microcontroller & Processor Fundamentals
Understanding microcontroller architecture, CPU registers, memory layout, and basic processor operation relevant to embedded systems
Practice Interview
Study Questions
Register Manipulation & Hardware Abstraction
Direct register access, volatile qualifiers, memory-mapped I/O, and understanding Hardware Abstraction Layers (HAL)
Practice Interview
Study Questions
Onsite Round 2: Device Drivers & Hardware-Software Integration
What to Expect
Technical interview focused on driver development, hardware-software integration, and practical use-case problems. You may be asked about driver architecture, device initialization sequences, register configuration, handling hardware quirks, and integration patterns. This round tests your ability to work at the interface between software and hardware.
Tips & Advice
If you mentioned specific IP or hardware in your resume, be thoroughly prepared to discuss driver implementations. Discuss real challenges you faced (timing issues, hardware bugs, version differences). Explain how you debug hardware-software integration problems. Be comfortable discussing both simple and complex peripherals. Mention any experience with device trees, kernel drivers, or bootloader code. Show understanding of the full lifecycle from hardware spec to functional driver.
Focus Topics
Device Initialization & Configuration
Sequence of steps to initialize hardware, configuration register settings, clock setup, and reset handling
Practice Interview
Study Questions
Hardware-Software Integration & Debugging
Debugging hardware-software interactions, using oscilloscopes/analyzers, understanding common integration issues, and troubleshooting strategies
Practice Interview
Study Questions
Handling Hardware Quirks & Edge Cases
Working around hardware limitations, version-specific behavior, race conditions, and manufacturing variations
Practice Interview
Study Questions
Device Driver Architecture & Design
Basic driver structure, layered driver design, device discovery, initialization sequences, and driver state management
Practice Interview
Study Questions
Hardware Specification Analysis
Reading and interpreting hardware datasheets, understanding register maps, timing diagrams, and hardware capabilities
Practice Interview
Study Questions
Onsite Round 3: Real-Time Systems & Operating Systems Concepts
What to Expect
Technical interview evaluating understanding of real-time operating systems (RTOS), task scheduling, synchronization primitives, and embedded OS concepts. Questions may involve multi-tasking scenarios, interrupt handling in OS context, mutex/semaphore usage, priority inversion, and real-time constraints. This assesses your ability to design systems that meet timing requirements.
Tips & Advice
Understand the difference between RTOS and general-purpose OS. Be clear about preemption, context switching, and deterministic behavior. Discuss trade-offs between simplicity and robustness. If you have RTOS experience (FreeRTOS, Zephyr, ThreadX, etc.), discuss specific scenarios. Explain how you debug timing issues and race conditions. Show comfort with concurrent programming in embedded context. Discuss priority-based scheduling and how it applies to real systems.
Focus Topics
Interrupt Handling in OS Context
ISR design in OS environments, interrupt priorities, interrupt nesting, and interaction with task scheduling
Practice Interview
Study Questions
Memory Management in Embedded OS
Static vs dynamic allocation in RTOS, memory pools, fragmentation concerns, and stack/heap management
Practice Interview
Study Questions
Real-Time Constraints & Timing Analysis
Understanding deadline requirements, response time analysis, and designing systems to meet timing constraints
Practice Interview
Study Questions
Real-Time Operating Systems (RTOS) Fundamentals
Task scheduling, context switching, preemption, determinism, and real-time constraints in embedded OS
Practice Interview
Study Questions
Synchronization & Concurrency in Embedded Systems
Mutex, semaphore, event flags, message queues, and avoiding race conditions in embedded multi-tasking
Practice Interview
Study Questions
Onsite Round 4: System Design & Architecture
What to Expect
System design round appropriate for mid-level embedded developers. Rather than distributed system design, this focuses on embedded system architecture: designing microcontroller-based solutions, choosing appropriate components, handling data flow, power management strategies, and scaling embedded systems. You'll discuss trade-offs between performance, power, cost, and complexity. Questions may involve designing IoT devices, sensor systems, or embedded subsystems.
Tips & Advice
Structure your answer: clarify requirements, discuss hardware choices, explain software architecture, address power and memory constraints. Draw block diagrams and data flow. Discuss sensor selection, communication protocols, data processing pipeline. Consider edge cases like sensor failures or network outages. Show understanding of the full system from sensors through processing to actuators. Discuss why specific design choices were made (cost, performance, reliability). Be realistic about embedded constraints.
Focus Topics
Scalability & Modularity in Embedded Design
Designing embedded systems that scale from prototype to production, modularity, abstraction layers, and managing complexity
Practice Interview
Study Questions
Sensor Integration & Data Acquisition
Choosing sensors, ADC/DAC configuration, sampling rates, noise filtering, and reliable data collection
Practice Interview
Study Questions
IoT & Connectivity Design
Choosing communication protocols, edge vs cloud processing, data transmission optimization, and connectivity reliability
Practice Interview
Study Questions
Power Management & Optimization
Low-power design, sleep modes, power domains, energy budgeting, and optimizing power consumption
Practice Interview
Study Questions
Embedded System Architecture & Design Patterns
Architectural patterns for embedded systems, component selection, and designing systems with strict resource constraints
Practice Interview
Study Questions
Onsite Round 5: Behavioral, Collaboration & Google Culture
What to Expect
Behavioral interview assessing your communication, teamwork, problem-solving approach, and alignment with Google values (boldness, responsibility, collaboration, user-focus). Expect questions about past projects, handling conflicts, learning from failures, and how you work with hardware engineers and cross-functional teams. This round evaluates cultural fit and soft skills essential for mid-level roles.
Tips & Advice
Prepare 4-5 concrete examples using STAR format (Situation, Task, Action, Result). Focus on collaborative projects, technical challenges you solved, and learning experiences. Emphasize how you communicated with hardware engineers and non-technical stakeholders. Discuss a time you failed and what you learned. Show curiosity and ownership. Talk about your growth as an embedded developer. Mention how you stay current with embedded systems knowledge. Demonstrate understanding of Google's mission and how embedded systems support it.
Focus Topics
Learning & Growth Mindset
Continuous learning, adapting to new technologies, seeking feedback, and mentoring junior engineers
Practice Interview
Study Questions
Ownership & Accountability
Taking responsibility for projects, following through on commitments, and driving solutions to completion
Practice Interview
Study Questions
Problem-Solving & Debugging Methodology
Systematic approach to debugging, hypothesis testing, persistence with tricky bugs, and learning from failures
Practice Interview
Study Questions
Technical Communication & Documentation
Explaining complex embedded concepts clearly, documenting drivers and code, and creating design specifications
Practice Interview
Study Questions
Collaboration with Hardware Engineers
Communication patterns, understanding hardware perspectives, joint problem-solving, and navigating software-hardware disagreements
Practice Interview
Study Questions
Frequently Asked Embedded Developer Interview Questions
Case study: your product intermittently drops Wi-Fi frames and packet checksums occasionally mismatch. Describe a systematic approach to instrument, trace, and isolate potential concurrency races in the network stack of an embedded device. Include which hardware tracing facilities you would use if available (ETM, SWO), what lightweight logging to add, and how to correlate task/ISR execution with packet events.
Sample Answer
Approach overview
- Treat this as a concurrency/IO race: instrument from IRQ/DMA boundaries through driver, OS, and Wi‑stack to find where buffers, lengths, or checksums are corrupted. Combine lightweight in‑target events + high‑bandwidth instruction trace to correlate causality.
Clarify assumptions & reproduce
- Reproduce with deterministic traffic (known packet patterns, sequence numbers).
- Use stress tests: high throughput, mixed RX/TX, different MTUs, interrupt coalescing on/off.
Hardware tracing
- ETM (instruction trace): record CPU execution around failure windows to see preemption points and exact ordering of handler, driver, and stack code.
- SWO / ITM: emit timestamped lightweight async events (packet RX start/finish, DMA descriptors owned bits, checksum computed, ISR enter/exit, task switch) at low overhead.
- DWT cycle counter: capture precise cycle timestamps; toggle a GPIO for oscilloscope if absolute timing needed.
Lightweight logging
- Emit compact ITM/SWO events with:
- Event type (enum), small id (u16), 32‑bit timestamp (DWT->CYCCNT).
- Packet sequence number and buffer pointer (truncated) to detect aliasing.
- Insert boundary checks: buffer length, canary bytes at buffer tail, per‑packet CRC of payload stored separately.
- Avoid heavy printf; batch or ring‑buffer events in RAM if SWO saturated.
Kernel/RTOS hooks
- Add trace points on context switch, task enter/exit, ISR enter/exit, and DMA completion callbacks.
- Log preemption causes: store current task id and EXC_RETURN when ISR preempts.
- Instrument locks: record lock take/release with caller PC to find priority inversion or double free.
Correlating events
- Use monotonic cycle timestamps to align ETM instruction trace with SWO events; ETM provides instruction addresses so you can see exact instruction that mutated buffer.
- Correlate DMA descriptor ownership transitions (hardware view) with CPU writes. Capture and compare descriptor contents before/after via snapshot in logging or memory trace.
- If mismatch occurs, find nearest earlier write to that memory region in ETM trace — likely race.
Deterministic replay / capture
- Record inputs (packets, IRQ timings) and SWO event stream into PC-side logfile. Replay at lower speed in simulator or instrumented board with single‑step ETM to confirm cause.
- If ETM unavailable, use high‑resolution event ring buffer plus GPIO toggles and oscilloscope to infer ordering.
Root-cause patterns to check
- DMA writing into buffer while CPU computes checksum without ownership check.
- ISR modifying shared descriptor without proper synchronization with task.
- Use-after-free or buffer pool aliasing due to insufficient refcounting.
- Non‑atomic updates of multi‑word metadata (length fields) causing transient invalid lengths.
Fix guidance
- Enforce ownership flags, memory barriers, and use atomic operations for multi‑word state.
- Move checksum calculation into DMA/HW where possible or protect with lock or IRQ masking for the critical section.
- Add buffer canaries + periodic assertion failures to catch earlier.
This systematic instrument → correlate → isolate loop using ETM+SWO+DWT plus lightweight in‑firmware events reliably exposes concurrency races in embedded network stacks.
In C for ARM Cortex-M (GCC), write a minimal GPIO interrupt service routine (ISR) for a falling-edge input that: 1) clears the hardware interrupt flag, 2) does not block or call heap allocation, and 3) notifies the main application via a lock-free flag or OS task-notification (assume FreeRTOS if you prefer). Provide the handler signature and any attributes or volatile variables needed.
Sample Answer
Approach (brief)
Provide a minimal, non-blocking ISR that clears the peripheral interrupt flag, avoids heap or blocking calls, and signals the application either via a volatile lock-free flag or via FreeRTOS task notification.
Code (STM32-style register names as example)
#include <stdint.h>
#include "FreeRTOS.h"
#include "task.h"
/* Lock-free flag option */
volatile uint32_t gpio_event_flag = 0;
/* FreeRTOS task handle to notify (set by main) */
TaskHandle_t gpioTaskHandle = NULL;
/* ISR for GPIO pin 0 (example). CMSIS name used by startup vector table. */
void EXTI0_IRQHandler(void)
{
/* 1) Clear hardware interrupt flag (write 1 to pending bit) */
EXTI->PR = (1U << 0); /* platform-specific: clear pending for line 0 */
/* 2a) Simple lock-free flag (no blocking, no heap) */
gpio_event_flag = 1;
/* 2b) OR: notify a FreeRTOS task from ISR (non-blocking) */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (gpioTaskHandle != NULL) {
vTaskNotifyGiveFromISR(gpioTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
Notes:
- Use the correct peripheral register to clear the interrupt for your MCU (some require writing 1, others clearing bits).
- gpio_event_flag must be volatile to prevent compiler optimizations.
- vTaskNotifyGiveFromISR and portYIELD_FROM_ISR are ISR-safe and do not allocate heap.
- Do not call blocking APIs, printf, malloc, or mutexes inside ISR.
Implement functions to pack an array of signed 12-bit ADC samples into a byte stream and to unpack them. Sign-extension must be correct on unpack. Signatures:
void pack12(const int16_t *samples, size_t n, uint8_t *out);
void unpack12(const uint8_t *in, size_t n, int16_t *samples);
Optimize for speed on a 32-bit MCU, handling odd sample counts, and minimize temporary memory.
Sample Answer
Approach (brief)
Pack signed 12-bit samples (two's complement) tightly: 12 bits per sample => 3 bytes for every 2 samples. Work in 32-bit words on a 32-bit MCU for speed: process pairs with bit ops, handle odd final sample. Ensure unpack sign-extends 12-bit values to int16_t.
Code (optimized, portable C)
#include <stdint.h>
#include <stddef.h>
void pack12(const int16_t *samples, size_t n, uint8_t *out) {
size_t i = 0, o = 0;
while (i + 1 < n) {
uint32_t a = (uint16_t)samples[i] & 0x0FFF; // lower 12 bits
uint32_t b = (uint16_t)samples[i+1] & 0x0FFF;
uint32_t w = (a) | (b << 12); // 24 bits: sample0 | sample1<<12
out[o++] = (uint8_t)(w & 0xFF);
out[o++] = (uint8_t)((w >> 8) & 0xFF);
out[o++] = (uint8_t)((w >> 16) & 0xFF);
i += 2;
}
if (i < n) { // odd sample left
uint32_t a = (uint16_t)samples[i] & 0x0FFF;
out[o++] = (uint8_t)(a & 0xFF);
out[o++] = (uint8_t)((a >> 8) & 0xFF);
}
}
void unpack12(const uint8_t *in, size_t n, int16_t *samples) {
size_t i = 0, o = 0;
while (o + 1 < n) {
uint32_t w = (uint32_t)in[i] | ((uint32_t)in[i+1] << 8) | ((uint32_t)in[i+2] << 16);
uint16_t a = w & 0x0FFF;
uint16_t b = (w >> 12) & 0x0FFF;
// sign-extend 12-bit to 16-bit
samples[o] = (int16_t)((a ^ 0x0800) - 0x0800);
samples[o+1] = (int16_t)((b ^ 0x0800) - 0x0800);
i += 3; o += 2;
}
if (o < n) { // last single sample
uint16_t a = (uint16_t)in[i] | ((uint16_t)in[i+1] << 8);
a &= 0x0FFF;
samples[o] = (int16_t)((a ^ 0x0800) - 0x0800);
}
}
Key points
- Use masking 0x0FFF to keep 12 bits.
- Sign-extension trick: (x ^ 0x0800) - 0x0800 converts 12-bit two's-complement to signed 16-bit efficiently without branching.
- Processes pairs to reduce loop overhead; minimal temporaries; works for odd count.
Complexity
- Time O(n), space O(1) extra. Fast on 32-bit MCU due to 32-bit ops and sequential memory access.
Edge cases
- Ensure out buffer size = ceil(n*12/8). Handle unaligned buffers, endianness assumed little-endian byte order for stream; document if big-endian required.
Alternative
- Use compiler intrinsics or unaligned 32-bit writes for slightly faster throughput if MCU supports.
A deployed Cortex-M device shows sporadic HardFault exceptions. Describe how you would use a JTAG/SWD debugger to capture the fault context: locate the stacked registers on the exception stack frame, inspect fault status registers (CFSR/HFSR/MMFAR/BFAR), map the faulting PC back to source code when optimizations are enabled, and outline steps to reproduce the fault without masking timing-dependent causes.
Sample Answer
Situation & immediate goal
I want to capture the CPU state at the moment of HardFault so I can identify the instruction and reason (memory fault, bus fault, usage fault) and map it back to source even when optimizations are enabled.
Steps to capture the fault context with JTAG/SWD
- Halt on fault: configure the debugger to stop on exception entry (e.g., "Stop on HardFault" or enable vector catch / fault halt). If it runs past, set a breakpoint at HardFault_Handler.
- Locate stacked registers: read EXC_RETURN in LR and the current SP (MSP on exception entry for handlers). The 8 pushed registers are at SP on entry:
- r0, r1, r2, r3, r12, lr, pc, xPSR (lowest address = SP)
- Use the debugger’s memory/read registers command to dump 32-bit words at SP and label them.
- Inspect fault status registers (SCB):
- Read SCB->CFSR (Usage/Bus/MemoryFault subfields), SCB->HFSR, SCB->MMFAR, SCB->BFAR
- Interpret bits: e.g., CFSR MemManage Fault Status bits indicate instruction vs data access, MMFAR holds faulting address if valid.
- Map PC to source with optimizations:
- Extract faulting PC from stacked PC. Mask Thumb bit (PC & ~1).
- Use objdump/addr2line with the linked ELF and the exact address: addr2line -e firmware.elf 0xADDRESS
- If inlined/optimized, also use objdump -d to see surrounding function and compiler map file; enable debug-symbol-rich builds (-g) and keep link map to correlate section offsets.
- Reproduce without masking timing:
- Avoid heavy logging or debug I/O that changes timing.
- Use non-invasive trace: enable hardware break-on-fault or SWV/ITM for lightweight logs, or ETM/trace if available.
- Add conditional breakpoints/watchpoints on the suspected fault address or memory region (use hardware watchpoints).
- Create stress test that exercises same code paths with same interrupts and timing (use timers/NMI to mimic).
- If race suspected, use reduced optimization or instrumented builds with minimal impact (cycle-accurate toggles, GPIO toggles) to capture timing without altering it too much.
- Repeat with increased monitoring (watchpoints) until captured.
Notes / best practices
- Remember to clear SCB fault status bits after reading to avoid stale info.
- When reading stacked PC, remember Thumb LSB and pipeline offset; disassemble from (pc - 4) to see the likely faulting instruction.
- Keep a copy of the exact firmware ELF and map file used on the device for reliable addr2line mapping.
Explain why volatile is not a synchronization primitive. Contrast volatile with memory barriers and atomic operations. Provide a short example where volatile alone leads to a race condition between an ISR and a main loop, and show a correct solution using C11 atomics or explicit barriers.
Sample Answer
Why volatile is not a synchronization primitive
- volatile only prevents the compiler from optimizing away accesses and forces reads/writes to memory. It does NOT provide ordering guarantees across CPUs, nor atomicity for read-modify-write, nor visibility across caches on multicore. For ISRs/interrupt contexts on a single core it helps avoid compiler reordering but doesn't replace memory barriers or atomics.
Contrast: volatile vs memory barriers vs atomics
- volatile: compiler-level; no inter-thread/memory-order semantics.
- memory barriers (fence): CPU-level instructions ensuring ordering and visibility (e.g., DMB/DSB on ARM).
- atomic operations (C11 stdatomic): provide atomicity + well-defined memory_order semantics (seq_cst, acquire/release, relaxed).
Bad example — race between ISR and main using volatile
volatile uint8_t flag = 0; // set by ISR
void ISR(void) { flag = 1; }
int main(void) {
while (!flag) {
// busy-wait
}
// race: read-modify-write later would be non-atomic
}
This may work on simple microcontrollers, but if flag were wider than CPU word or if compiler reordered accesses, it can fail.
Correct using C11 atomics
#include <stdatomic.h>
atomic_uint_fast8_t flag = ATOMIC_VAR_INIT(0);
void ISR(void) { atomic_store_explicit(&flag, 1, memory_order_release); }
int main(void) {
while (atomic_load_explicit(&flag, memory_order_acquire) == 0) { }
// safe: acquire/release pairs ensure visibility and ordering
}
Alternate: explicit barrier (ARM example)
- In ISR after write: asm volatile ("dmb ish" ::: "memory");
- In main before read: asm volatile ("dmb ish" ::: "memory");
Use C11 atomics where available; otherwise pair CSR/CPU barriers with volatile for safe ISR synchronization.
Explain SPI protocol fundamentals: master/slave roles, signals (SCK, MOSI, MISO, CS/SS), CPOL/CPHA meaning (modes 0–3), full-duplex vs half-duplex transfers, and considerations for bus topology when multiple slaves are present. Describe how to choose SPI mode for a device and how chip-select setup/hold timing affects reliable transfers.
Sample Answer
SPI fundamentals (brief)
SPI is a synchronous serial bus with a single master and one or more slaves. Master generates SCK (clock) and selects a slave via CS/SS (chip/slave select). Data is exchanged on MOSI (master out, slave in) and MISO (master in, slave out).
Signals & roles
- SCK: clock from master.
- MOSI: master → slave data.
- MISO: slave → master data.
- CS/SS: active-low select per slave; when asserted the selected slave drives MISO.
CPOL/CPHA and modes
- CPOL = clock idle polarity (0 = low, 1 = high).
- CPHA = clock phase: whether sampling occurs on first or second edge after CS.
Modes: - Mode 0: CPOL=0, CPHA=0 (sample rising edge).
- Mode 1: CPOL=0, CPHA=1.
- Mode 2: CPOL=1, CPHA=0.
- Mode 3: CPOL=1, CPHA=1.
Choose mode to match device datasheet; mismatch causes bit shifts or jitter.
Full- vs half-duplex
- Full-duplex: MOSI and MISO simultaneous (standard SPI).
- Half-duplex: single data line toggled direction or tri-stated (used to reduce pins).
Bus topology & multiple slaves
- Use one CS per slave; keep CS asserted only for active transfer. Avoid shared MISO driving—use pull-ups and tri-state or buffers. Consider series resistors for signal integrity and matched trace lengths for high speed.
CS timing
- Observe device setup/hold in datasheet: ensure CS asserted before first clock by tCSS, maintain after last clock for tCSH, and observe required inter-transfer delay. Violating these causes corrupted frames or device misinterpretation. In firmware, assert CS, wait tCSS, clock N bits, wait tCSH, deassert CS.
On an 8-bit AVR microcontroller, show how to safely read and write a 16-bit shared variable that can be modified in an ISR. Provide concise C examples using cli()/sei() (or equivalent atomic block macros) and discuss pros and cons of disabling interrupts briefly versus other synchronization techniques.
Sample Answer
Approach (brief)
On 8-bit AVR a 16-bit variable is not atomic; an ISR can interrupt a multi-byte access. Safest simple method is to disable interrupts around the read/write or use AVR-provided atomic macros.
Example — disable/restore interrupts (cli()/sei())
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint16_t shared = 0;
uint16_t read_shared(void) {
uint16_t val;
uint8_t sreg = SREG; // save global interrupt flag
cli(); // disable interrupts
val = shared; // atomic read of 16-bit
SREG = sreg; // restore interrupts (restores I-bit)
return val;
}
void write_shared(uint16_t v) {
uint8_t sreg = SREG;
cli();
shared = v;
SREG = sreg;
}
Example — using <util/atomic.h>
#include <util/atomic.h>
uint16_t read_shared2(void) {
uint16_t val;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
val = shared;
}
return val;
}
Pros/Cons
- Disabling interrupts: simple, minimal code, deterministic. Con: increases interrupt latency; avoid long critical sections.
- Atomic macros: safer (handles SREG), clearer intent.
- Alternatives: use double-buffering + version counters or message queues/flags to avoid long disable periods; use minimal critical sections or task-level synchronization in RTOS. Those reduce latency but add complexity and possibly more memory.
Write a concise skeleton of a character device driver (pseudo-C) for an embedded RTOS that supports open, close, read, write and handles a hardware interrupt for data-ready. Show how you would defer processing from the ISR to a worker thread and protect shared buffers from concurrent access.
Sample Answer
Approach
- Use a small ring buffer protected by a mutex for reader/writer.
- ISR signals a worker via a semaphore/queue to do non-ISR work.
- Expose open/close/read/write with proper locking and sleep/wakeup when empty/full.
Pseudo-C skeleton
// pseudo-C for RTOS (posix-like primitives)
#include <rtos.h>
#define BUF_SIZE 256
static uint8_t buf[BUF_SIZE];
static size_t head=0, tail=0;
static mutex_t buf_lock;
static sem_t data_sem; // signalled by ISR; worker waits
static cond_t read_wait; // readers wait when buffer empty
static bool open_count=false;
static thread_t worker_thread;
static inline size_t buf_used() { return (head - tail) % BUF_SIZE; }
static inline size_t buf_free() { return BUF_SIZE - buf_used() - 1; }
void isr_data_ready(void)
{
// minimal ISR: acknowledge hardware
hw_ack_interrupt();
// notify worker (use ISR-safe API)
sem_give_from_isr(&data_sem);
}
static void worker(void *arg)
{
while (1) {
sem_take(&data_sem, WAIT_FOREVER);
// do deferred processing
mutex_lock(&buf_lock);
while (hw_has_data() && buf_free()) {
buf[head] = hw_read_byte();
head = (head + 1) % BUF_SIZE;
}
mutex_unlock(&buf_lock);
cond_broadcast(&read_wait); // wake readers
}
}
int dev_open(void)
{
if (!open_count) {
open_count = true;
mutex_init(&buf_lock);
sem_init(&data_sem, 0);
cond_init(&read_wait);
worker_thread = thread_create(worker, NULL);
}
return 0;
}
int dev_close(void) { open_count = false; return 0; }
ssize_t dev_read(uint8_t *dst, size_t len)
{
size_t copied=0;
mutex_lock(&buf_lock);
while (buf_used()==0) {
mutex_unlock(&buf_lock);
cond_wait(&read_wait, &buf_lock); // atomically unlock and wait
mutex_lock(&buf_lock);
}
while (copied < len && buf_used()) {
dst[copied++] = buf[tail];
tail = (tail + 1) % BUF_SIZE;
}
mutex_unlock(&buf_lock);
return copied;
}
ssize_t dev_write(const uint8_t *src, size_t len)
{
size_t written=0;
mutex_lock(&buf_lock);
while (written < len && buf_free()) {
buf[head] = src[written++];
head = (head + 1) % BUF_SIZE;
}
mutex_unlock(&buf_lock);
// optionally notify hardware to send data
return written;
}
Key points
- ISR does minimal work and uses ISR-safe sem give.
- Worker performs slow operations and moves data to buffer.
- Mutex + condition variable protect and coordinate access.
- Ring buffer avoids copies and supports concurrent readers/writers safely.
Explain the difference between a hardware interrupt and an exception (fault) on a CPU such as ARM Cortex-M. Give examples of exceptions and describe how their handling and semantics differ from asynchronous external interrupts.
Sample Answer
Direct answer (difference)
- Hardware interrupt: asynchronous event from external/peripheral source (GPIO, timer, UART) handled by NVIC on Cortex-M. Occurs independent of current instruction flow.
- Exception/fault: synchronous event caused by program execution or core condition (instruction fault, undefined instruction, divide-by-zero, SVC). Deterministic response to instruction or internal error.
Examples
- Exceptions: Reset, NMI, HardFault, MemManage (MPU), BusFault (data access), UsageFault (undefined instruction, divide-by-zero), SVC, PendSV, SysTick.
- Hardware interrupts: external IRQn from timers (TIM), USART RX, external pin (EXTI).
Semantics & handling differences
- Triggering: exceptions are synchronous to the instruction (precise) or to internal detection; interrupts are asynchronous.
- Stacking/entry: both push core registers and jump to vector, but exceptions (faults) often have fixed priorities (HardFault > other faults) and can escalate (e.g., MemManage → BusFault → HardFault). NVIC arbitrates peripheral IRQ priorities for interrupts.
- Return: use special EXC_RETURN value; synchronous faults may leave PC at faulting instruction (or next) so debugger can analyze; interrupt return resumes interrupted context.
- Masking/control: faults often cannot be masked (NMI/HardFault), while interrupts can be masked via PRIMASK/BASEPRI or NVIC settings.
- Diagnostics: faults populate fault status registers (CFSR, HFSR, BFAR, MMFAR) for root-cause; external IRQs usually have peripheral status flags.
Why it matters for embedded developers
- Use faults for detecting bugs/safety violations and inspect fault registers to debug; design ISRs vs. fault handlers with different recovery expectations and priority considerations.
List compiler and linker techniques to reduce firmware binary size for constrained flash devices. Explain flag choices such as -Os, -ffunction-sections, -fdata-sections, link-time GC of sections, link-time optimization, and removal of exceptions/RTTI for C++ as well as runtime and debugging trade-offs.
Sample Answer
Approach summary
List practical compiler/linker techniques and explain trade-offs with runtime, debugability, and performance — from an embedded developer perspective.
Techniques & flags
- -Os — optimize for size: reduces code size by avoiding inlining and heavy optimizations; may slightly reduce performance. Use when flash is tight.
- -ffunction-sections / -fdata-sections — emit each function/data in its own section (.text.func, .data.var). Enables fine-grained removal.
- --gc-sections (linker) — garbage-collect unreachable sections at link time. Requires the above flags and correct visibility (no unintended weak refs).
- -flto / -flto=thin (Link Time Optimization) — enables cross-module inlining and dead-code elimination across translation units; thin LTO reduces memory/compile-time cost. Can further reduce size but increases link time and toolchain complexity.
- Strip symbols (strip, -s) and use -g split-debug or debug info separated to preserve source-level debug without inflating flash.
- Disable exceptions and RTTI in C++ (e.g., -fno-exceptions, -fno-rtti) — removes exception handling tables and typeinfo; significant savings if exceptions not used. Must design error paths via return codes/optional.
- Use compiler options to reduce startup/runtime (no standard library, -ffreestanding, provide minimal crt) and avoid heavy C++ features (virtual tables, iostreams, locale).
- Linker script tuning — place seldom-used code in non-critical regions, control section alignment, remove unused startup code, and reduce .bss via smaller statics.
- Use smaller libc variants (newlib-nano, musl, or a custom minimal HAL) and avoid printf or use minimal printf variants.
Runtime & debugging trade-offs
- Removing exceptions/RTTI and aggressive inlining control: smaller binary but harder to use higher-level C++ patterns and catch bugs without stack unwinding.
- LTO and -Os: better size but longer build/link times; LTO can obscure call stacks in debugging and requires consistent toolchain flags across build.
- --gc-sections: can accidentally remove code reachable by reflection, linker script, or via assembly — need to mark used symbols with KEEP() or used attribute.
- Stripping symbols reduces post-mortem debug; split-debug preserves symbols externally.
- Testing: enable size-reduction flags early in CI; run functional and stress tests and hardware-in-the-loop to catch behavior changes.
Practical checklist
- Build with -Os + -ffunction-sections/-fdata-sections + --gc-sections
- Try thin LTO if toolchain/resources allow
- Remove exceptions/RTTI only after ensuring error strategy
- Use minimal libc and custom allocators
- Separate debug info; run full tests with size flags enabled
This combination gives predictable, verifiable reductions while balancing debugability and runtime needs for constrained flash devices.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths
Browse Embedded Developer jobs
AI-enriched listings across hundreds of company career pages
Explore Jobs