Hardware Protocols Mastery: I2C, SPI, and UART

Goal: Build a complete mental model of the three foundational serial communication protocols used in embedded systems. You will understand not just how to use libraries, but the actual electrical signals, timing requirements, and protocol state machines that make data flow between chips. By the end of this guide, you will be able to read oscilloscope traces, debug bus failures, design mixed-protocol systems, implement bit-banged versions from scratch, and make informed architectural decisions about when to use each protocol. You will finish with a portfolio of projects including logic analyzers, sensor interfaces, display drivers, protocol bridges, and debugging tools that demonstrate deep protocol mastery.


Introduction: What This Guide Covers

Serial communication protocols are the nervous system of embedded systems. Every sensor, display, memory chip, and peripheral communicates with your microcontroller through one of these three protocols. Understanding them deeply separates engineers who can read datasheets and debug hardware from those who rely on library examples and hope for the best.

What you will build (by the end of this guide):

  • A complete serial terminal with flow control and break detection
  • Multi-sensor I2C networks reading temperature, humidity, and motion data
  • High-speed SPI interfaces to displays and flash memory
  • A protocol analyzer that captures and decodes all three protocols
  • A protocol bridge that translates between I2C and SPI devices
  • Bit-banged implementations that work on any GPIO pins

Scope (what is included):

  • UART: asynchronous serial, baud rates, framing, RS-232 vs TTL
  • I2C: two-wire synchronous, addressing, clock stretching, multi-master
  • SPI: four-wire synchronous, modes, chip select, high-speed operation
  • Level shifting, noise immunity, and bus topology design
  • Hardware debugging with logic analyzers and oscilloscopes
  • Arduino and Raspberry Pi implementations in C/C++ and Python

Out of scope (for this guide):

  • CAN bus, LIN, and automotive protocols (separate guide)
  • Wireless protocols (Bluetooth, WiFi, Zigbee)
  • USB protocol internals (separate guide)
  • High-speed differential signaling (LVDS, RS-485)

The Big Picture (Mental Model)

                    ┌─────────────────────────────────────┐
                    │         MICROCONTROLLER             │
                    │                                     │
                    │  ┌─────────┐ ┌─────────┐ ┌───────┐ │
                    │  │  UART   │ │   I2C   │ │  SPI  │ │
                    │  │Peripheral│ │Peripheral│ │Peripheral│
                    │  └────┬────┘ └────┬────┘ └───┬───┘ │
                    └───────┼───────────┼─────────┼──────┘
                            │           │         │
         ┌──────────────────┼───────────┼─────────┼──────────────┐
         │                  │           │         │              │
         │    ┌─────────────┴───┐   ┌───┴────┐   ┌┴─────────┐   │
         │    │   TX ─────────► │   │ SDA ◄──►│   │ MOSI ───►│   │
         │    │   RX ◄───────── │   │ SCL ───►│   │ MISO ◄───│   │
         │    │  (GND shared)   │   │         │   │ SCK  ───►│   │
         │    └─────────────────┘   └─────────┘   │ CS   ───►│   │
         │                                        └──────────┘   │
         │                                                       │
         │    Point-to-Point       Multi-Drop Bus    Multi-Device│
         │    Full Duplex          Half Duplex       Full Duplex │
         │    Asynchronous         Synchronous       Synchronous │
         │                                                       │
         └───────────────────────────────────────────────────────┘
                              PHYSICAL LAYER

Protocol Comparison:

         UART                I2C                 SPI
         ────                ───                 ───
Wires:   2 (TX/RX)          2 (SDA/SCL)         4+ (MOSI/MISO/SCK/CS)
Speed:   ~1 Mbps max        ~3.4 Mbps max       ~50+ Mbps
Devices: 1:1                Multi-drop (127)     1:N (per CS line)
Duplex:  Full               Half                 Full
Clock:   None (async)       Master provides      Master provides
Address: None               7/10-bit             Chip Select line
Distance:Meters             Short (capacitance)  Short (<1m typ)

Key Terms You Will See Everywhere

  • Baud rate: bits per second (not bytes per second) for UART
  • Clock polarity (CPOL): idle state of the SPI clock (high or low)
  • Clock phase (CPHA): which clock edge samples data in SPI
  • Open-drain: output that can only pull low; needs external pull-up
  • Bit-banging: implementing a protocol in software using GPIO
  • Pull-up resistor: resistor that holds a line high when not driven
  • Chip select: SPI line that activates a specific slave device
  • Arbitration: mechanism for multiple masters to share a bus
  • Clock stretching: slave holding clock low to pause the master

How to Use This Guide

  1. Read the Theory Primer once to build the mental models for all three protocols.
  2. Start with UART projects (1-5) as they are the simplest conceptually.
  3. Move to I2C projects (6-11) to understand addressing and bus management.
  4. Tackle SPI projects (12-16) for high-speed and display interfaces.
  5. Finish with cross-protocol projects (17-20) that integrate everything.
  6. Keep a lab notebook: log wiring, timing measurements, and every bug you fix.
  7. Use a logic analyzer from Project 4 onward; it transforms your debugging ability.

Prerequisites & Background Knowledge

Before starting these projects, you should have foundational understanding in these areas:

Essential Prerequisites (Must Have)

Electronics Fundamentals:

  • Voltage, current, and Ohm’s law
  • Reading basic schematics and datasheets
  • Understanding of logic levels (HIGH/LOW, 3.3V vs 5V)
  • Breadboarding and basic soldering
  • Recommended Reading: “The Art of Electronics” by Horowitz & Hill - Ch. 1-2

Microcontroller Basics:

  • GPIO input/output configuration
  • Basic C programming for embedded systems
  • Interrupt concepts
  • Using a serial terminal (minicom, screen, PuTTY)
  • Recommended Reading: “Making Embedded Systems” by Elecia White - Ch. 1-5

Digital Logic:

  • Binary and hexadecimal number systems
  • Bitwise operations (AND, OR, XOR, shift)
  • Timing diagrams interpretation
  • Recommended Reading: “Digital Design” by Mano & Ciletti - Ch. 1-3

Helpful But Not Required

Advanced topics you can learn during the projects:

  • DMA for high-speed transfers (Projects 14, 16)
  • State machines for protocol implementation (Projects 17-20)
  • Oscilloscope and logic analyzer usage (Project 4)
  • Level shifter design (Project 18)

Self-Assessment Questions

  1. Can you calculate the value of a pull-up resistor given a desired rise time?
  2. Can you explain why 5V signals can damage a 3.3V microcontroller input?
  3. Can you read a timing diagram and identify setup and hold times?
  4. Can you configure a GPIO pin as an open-drain output?
  5. Have you used a serial terminal to communicate with a device?

If you answered “no” to questions 1-3: spend a week with basic electronics and “Making Embedded Systems” Ch. 1-5 before starting.

Development Environment Setup

Required hardware:

  • Arduino Uno/Nano or Raspberry Pi Pico (or both)
  • Raspberry Pi (3B+, 4, or 5) for Linux-based projects
  • USB data cable, breadboard, jumper wires
  • Pull-up resistor assortment (1K, 2.2K, 4.7K, 10K)
  • Level shifter module (3.3V to 5V bidirectional)
  • I2C devices: temperature sensor (TMP102, BME280), OLED display, EEPROM
  • SPI devices: SD card module, TFT display, flash memory

Recommended tools:

  • Logic analyzer (Saleae Logic or cheap 8-channel USB analyzer)
  • Multimeter with continuity beeper
  • Oscilloscope (even a cheap DSO150 helps)

Software setup:

# Arduino CLI installation
$ curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
$ arduino-cli core install arduino:avr
$ arduino-cli lib install "Adafruit BusIO"

# Raspberry Pi setup
$ sudo apt update
$ sudo apt install -y python3-smbus python3-spidev i2c-tools
$ sudo raspi-config  # Enable I2C and SPI interfaces

Time Investment

  • Beginner projects (1-3): weekend each (4-8 hours)
  • Intermediate projects (4-8): 1-2 weeks each
  • Advanced projects (9-14): 2-3 weeks each
  • Expert projects (15-20): 3-4 weeks each
  • Total mastery path: 4-6 months

Important Reality Check

Protocol debugging is 80% of embedded work. You will:

  1. Wire things wrong (swap TX/RX, forget ground)
  2. Use wrong baud rates or SPI modes
  3. Fight with pull-up resistor values
  4. Miss timing requirements in datasheets
  5. See signals that look nothing like the diagrams

This is normal. A logic analyzer and patience are your best tools.


Big Picture / Mental Model

┌────────────────────────────────────────────────────────────────┐
│                    SERIAL COMMUNICATION                        │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  ASYNCHRONOUS                      SYNCHRONOUS                 │
│  (No shared clock)                 (Shared clock signal)       │
│       │                                   │                    │
│       ▼                            ┌──────┴──────┐             │
│    ┌──────┐                        ▼             ▼             │
│    │ UART │                     ┌─────┐       ┌─────┐         │
│    └──────┘                     │ I2C │       │ SPI │         │
│                                 └─────┘       └─────┘         │
│                                                                │
│  Both sides must agree          Master generates clock        │
│  on timing in advance           Slave follows master's pace   │
│                                                                │
└────────────────────────────────────────────────────────────────┘

Communication Topology:

    UART (Point-to-Point)         I2C (Multi-Drop Bus)
    ┌───────┐    ┌───────┐       ┌───────┐
    │ Device├────┤Device │       │Master │
    │   A   │    │   B   │       └───┬───┘
    └───────┘    └───────┘           │ SDA/SCL Bus
                                ┌────┼────┬────┐
                              ┌─┴─┐┌─┴─┐┌─┴─┐┌─┴─┐
                              │S1 ││S2 ││S3 ││S4 │
                              │   ││   ││   ││   │
                              │0x48│0x50│0x68│0x77│
                              └───┘└───┘└───┘└───┘

    SPI (Star Topology)
    ┌───────┐
    │Master ├──────────┬──────────┬──────────┐
    └───────┘          │          │          │
           MOSI/MISO/SCK          │          │
                 ┌─────┴─────┐    │          │
                 │  CS0      │    │          │
               ┌─┴─┐       ┌─┴─┐┌─┴─┐      ┌─┴─┐
               │S1 │       │S2 ││S3 │      │S4 │
               │   │       │   ││   │      │   │
               │CS0│       │CS1││CS2│      │CS3│
               └───┘       └───┘└───┘      └───┘

Theory Primer (Read This Before Coding)

This section is the mini-book. Each chapter builds a mental model you will apply directly in the projects.

Chapter 1: UART - Asynchronous Serial Communication

Fundamentals

UART (Universal Asynchronous Receiver/Transmitter) is the simplest serial protocol to understand because it requires no shared clock. Two devices agree on a baud rate (bits per second) in advance, and each samples the incoming data at that rate. The protocol uses two lines: TX (transmit) and RX (receive), making it full-duplex. Because there is no clock signal, timing accuracy is critical - if the sender and receiver disagree on timing by more than about 3%, communication fails.

UART is everywhere: debug consoles, GPS modules, Bluetooth adapters, WiFi modules, and computer serial ports all use UART. Understanding UART deeply means you can debug any embedded system by accessing its serial console.

Deep Dive

A UART frame consists of:

  1. Idle state: Line held high (mark)
  2. Start bit: Line pulled low (space) for one bit period
  3. Data bits: 5-9 bits, LSB first
  4. Parity bit: Optional error detection (even, odd, or none)
  5. Stop bits: 1, 1.5, or 2 bit periods held high
UART Frame Structure (8N1 - 8 data bits, No parity, 1 stop bit):

     Idle  Start  D0   D1   D2   D3   D4   D5   D6   D7  Stop  Idle
      │      │     │    │    │    │    │    │    │    │    │     │
  ────┴──────┴─────┴────┴────┴────┴────┴────┴────┴────┴────┴─────┴────
           ▼                                                  ▼
  ─────┐   ┌──┐  ┌──┐      ┌──┐  ┌──┐  ┌──┐      ┌──┐  ┌──┐   ┌───────
       │   │  │  │  │      │  │  │  │  │  │      │  │  │  │   │
       └───┘  └──┘  └──────┘  └──┘  └──┘  └──────┘  └──┘  └───┘
       Start  1     0        1     1     1        0     1    Stop
             (LSB)                                     (MSB)

  Example: Transmitting byte 0x55 = 0b01010101 at 9600 baud
  Bit period = 1/9600 = 104.17 microseconds

Timing Requirements:
  ┌─────────────────────────────────────────────────────────┐
  │ Baud Rate  │ Bit Period  │ Max Clock Error │ Common Use │
  ├────────────┼─────────────┼─────────────────┼────────────┤
  │ 9600       │ 104.17 µs   │ ±3%             │ Debug      │
  │ 115200     │ 8.68 µs     │ ±3%             │ Fast debug │
  │ 921600     │ 1.09 µs     │ ±2%             │ High-speed │
  │ 1000000    │ 1.00 µs     │ ±2%             │ Maximum    │
  └────────────┴─────────────┴─────────────────┴────────────┘

The receiver samples each bit in the middle of its period. When it detects the falling edge of the start bit, it waits half a bit period and then samples at regular intervals. If the timing drifts too much, the receiver samples the wrong part of each bit and gets garbage. This is why both sides must agree on the exact baud rate.

RS-232 vs TTL Levels:

RS-232 is a voltage standard, not a protocol. Classic RS-232 uses ±12V signaling where +12V is a logic 0 (space) and -12V is a logic 1 (mark). Modern TTL/CMOS UART uses 0V/3.3V or 0V/5V with inverted logic. Never connect RS-232 directly to a microcontroller - you will damage it.

Voltage Level Comparison:

                RS-232                      TTL/CMOS
         ┌────────────────┐           ┌────────────────┐
         │                │           │                │
    +12V │    SPACE (0)   │      5V/  │    MARK (1)    │
         │                │     3.3V  │    (Idle)      │
         ├────────────────┤           ├────────────────┤
      0V │   (Invalid)    │       0V  │    SPACE (0)   │
         ├────────────────┤           │                │
    -12V │    MARK (1)    │           └────────────────┘
         │    (Idle)      │
         └────────────────┘

  RS-232: Logic inverted, high voltage swing
  TTL: Direct logic, microcontroller-friendly
  Conversion: MAX232, MAX3232, or USB-serial adapters

Flow Control:

Hardware flow control uses additional lines (RTS/CTS) to prevent buffer overflow. RTS (Request To Send) signals “I’m ready to receive” and CTS (Clear To Send) signals “I’m ready to transmit”. Software flow control (XON/XOFF) sends special characters to pause and resume transmission.

How This Fits in Projects

  • Project 1: Basic UART configuration and loopback testing
  • Project 2: Building a serial terminal with command parsing
  • Project 3: UART debugging with error detection
  • Project 4: Logic analyzer capture of UART traffic
  • Project 5: RS-232 to TTL level conversion

Definitions & Key Terms

  • Baud rate: bits per second transmitted
  • Frame: start bit + data + optional parity + stop bits
  • Mark: idle state, logic 1, line high (TTL)
  • Space: active state, logic 0, line low (TTL)
  • Framing error: stop bit not detected at expected time
  • Overrun error: new data arrived before old data was read

Check-Your-Understanding Questions

  1. Why does UART require both devices to use the same baud rate?
  2. What happens if you swap TX and RX connections?
  3. Why is the start bit necessary?
  4. How does the receiver know where each bit ends?

Check-Your-Understanding Answers

  1. Without a shared clock, timing must be pre-agreed; mismatched rates cause sampling errors.
  2. No communication occurs; TX must connect to RX and vice versa.
  3. The falling edge of the start bit synchronizes the receiver to the frame.
  4. It counts bit periods from the start bit, sampling in the middle of each.

Chapter 2: I2C - Two-Wire Synchronous Bus

Fundamentals

I2C (Inter-Integrated Circuit, pronounced “eye-squared-see” or “eye-two-see”) uses just two wires to connect multiple devices on a shared bus. SDA (Serial Data) carries bidirectional data, and SCL (Serial Clock) carries the clock from master to slaves. The protocol uses addressing so multiple slaves can share the same two wires, with the master selecting which slave to talk to.

I2C is half-duplex - data flows in one direction at a time. The master initiates all transactions, generates the clock, and selects slaves by address. This makes I2C simpler to wire than SPI (fewer connections) but slower and more complex in software.

Deep Dive

Electrical Characteristics:

Both SDA and SCL are open-drain, meaning devices can only pull the lines low. External pull-up resistors (typically 2.2K to 10K) pull the lines high when no device is driving them low. This allows bidirectional communication on a single wire and enables clock stretching.

I2C Bus Electrical Structure:

        VCC (3.3V or 5V)
         │        │
        ┌┴┐      ┌┴┐
        │ │Rp    │ │Rp     (Pull-up resistors)
        │ │      │ │       (1K - 10K typical)
        └┬┘      └┬┘
         │        │
  ┌──────┼────────┼───────────────────────┐
  │      │        │                       │ SDA
  │   ┌──┴──┐  ┌──┴──┐  ┌──────┐  ┌──────┐│
  │   │  ══ │  │  ══ │  │  ══  │  │  ══  ││
  │   └──┬──┘  └──┬──┘  └──┬───┘  └──┬───┘│
  │      │        │        │         │    │
  │   ┌──┴──┐  ┌──┴──┐  ┌──┴───┐  ┌──┴───┐│
  │   │  ══ │  │  ══ │  │  ══  │  │  ══  ││
  │   └──┬──┘  └──┬──┘  └──┬───┘  └──┬───┘│
  │      │        │        │         │    │ SCL
  └──────┼────────┼────────┼─────────┼────┘
         │        │        │         │
      Master   Slave1   Slave2    Slave3
      (0x---)  (0x48)   (0x50)    (0x68)

  Open-Drain Outputs:
  ┌──────────────────────────────────────┐
  │     VCC                              │
  │      │                               │
  │     ─┴─ Pull-up                      │
  │      │                               │
  │ ─────●───────► Bus line (SDA/SCL)    │
  │      │                               │
  │    ──┴── MOSFET (open drain)         │
  │      │                               │
  │     GND (when active)                │
  └──────────────────────────────────────┘

  Device can pull LOW but not drive HIGH
  Multiple devices can share same line
  If any device pulls LOW, line goes LOW (wired-AND)

Protocol State Machine:

I2C Transaction State Machine:

  ┌─────────┐
  │  IDLE   │ ◄──────────────────────────────────┐
  └────┬────┘                                    │
       │ Start Condition (SDA↓ while SCL high)   │
       ▼                                         │
  ┌─────────┐                                    │
  │ ADDRESS │ ───► Send 7-bit address + R/W bit  │
  └────┬────┘                                    │
       │ Wait for ACK from slave                 │
       ▼                                         │
  ┌─────────┐     ┌─────────┐                    │
  │  ACK?   │─NO─►│  NACK   │──► Error handling ─┤
  └────┬────┘     └─────────┘                    │
       │ YES                                     │
       ▼                                         │
  ┌─────────┐                                    │
  │  DATA   │ ◄────────┐                         │
  └────┬────┘          │                         │
       │               │ More bytes to transfer  │
       ▼               │                         │
  ┌─────────┐     ┌────┴────┐                    │
  │ ACK/NACK│─────┤ Continue│                    │
  └────┬────┘     └─────────┘                    │
       │ Done                                    │
       ▼                                         │
  ┌─────────┐                                    │
  │  STOP   │ ──► Stop Condition (SDA↑ while ────┘
  └─────────┘     SCL high)


I2C Start and Stop Conditions:

       Start Condition              Stop Condition

  SDA ─────┐                   SDA       ┌─────
           │                             │
           └───────────             ─────┘

  SCL ───────────────          SCL ───────────────
         HIGH                        HIGH

  SDA falls while SCL high     SDA rises while SCL high
  (Unique - normally SDA only  (Unique - normally SDA only
   changes when SCL is low)     changes when SCL is low)

7-Bit Addressing:

I2C uses 7-bit addresses, allowing up to 127 devices (address 0 is reserved for general call). The 8th bit indicates read (1) or write (0). Some devices have configurable address pins to allow multiple identical devices on the same bus.

I2C Address Byte:

  Bit:  7   6   5   4   3   2   1   0
       ┌───┬───┬───┬───┬───┬───┬───┬───┐
       │A6 │A5 │A4 │A3 │A2 │A1 │A0 │R/W│
       └───┴───┴───┴───┴───┴───┴───┴───┘
        └───────────┬───────────┘   │
             7-bit address          │
                                    └─ 0 = Write, 1 = Read

  Common Device Addresses:
  ┌────────────────────┬─────────┬────────────────┐
  │ Device             │ Address │ Notes          │
  ├────────────────────┼─────────┼────────────────┤
  │ AT24C02 EEPROM     │ 0x50-57 │ A0-A2 pins     │
  │ TMP102 Temp Sensor │ 0x48-4B │ ADD0 pin       │
  │ MPU6050 Accel/Gyro │ 0x68-69 │ AD0 pin        │
  │ SSD1306 OLED       │ 0x3C-3D │ SA0 pin        │
  │ BMP280 Pressure    │ 0x76-77 │ SDO pin        │
  │ DS3231 RTC         │ 0x68    │ Fixed          │
  │ PCA9685 PWM Driver │ 0x40-7F │ A0-A5 pins     │
  └────────────────────┴─────────┴────────────────┘

Clock Stretching:

Slow slave devices can hold SCL low to pause the master while they process data. The master must check SCL before assuming the clock transition completed. Not all masters support clock stretching, which can cause bus lockups.

Speed Modes:

I2C Speed Modes:
┌─────────────────┬─────────────┬────────────────────┐
│ Mode            │ Max Speed   │ Pull-up Resistance │
├─────────────────┼─────────────┼────────────────────┤
│ Standard Mode   │ 100 kHz     │ 10K typical        │
│ Fast Mode       │ 400 kHz     │ 4.7K typical       │
│ Fast Mode Plus  │ 1 MHz       │ 2K typical         │
│ High Speed Mode │ 3.4 MHz     │ Special drivers    │
└─────────────────┴─────────────┴────────────────────┘

Pull-up Value Calculation:
  Rp(min) = (VCC - VOL(max)) / IOL
  Rp(max) = tr / (0.8473 × Cb)

  Where:
    VOL = Low output voltage (0.4V typical)
    IOL = Low output current (3mA typical)
    tr  = Rise time (1µs for 100kHz, 300ns for 400kHz)
    Cb  = Bus capacitance (typically 10-400pF)

How This Fits in Projects

  • Project 6: I2C scanner to discover devices on a bus
  • Project 7: Reading temperature sensors and accelerometers
  • Project 8: Writing to EEPROMs for data storage
  • Project 9: Driving OLED displays
  • Project 10: Multi-master arbitration experiments
  • Project 11: I2C bus debugging and recovery

Definitions & Key Terms

  • Master: Device that initiates transactions and generates clock
  • Slave: Device that responds to master requests
  • ACK/NACK: Acknowledgment (0) or No Acknowledgment (1) after each byte
  • Repeated Start: New start without stop, used for read-after-write
  • Clock stretching: Slave holding SCL low to pause communication
  • Arbitration: Multiple masters detecting and yielding to each other

Check-Your-Understanding Questions

  1. Why does I2C use open-drain outputs instead of push-pull?
  2. What happens if two devices have the same address on the bus?
  3. How does a master know if a slave received data correctly?
  4. Why must SDA only change when SCL is low (except for start/stop)?

Check-Your-Understanding Answers

  1. Open-drain allows multiple devices to share lines and enables clock stretching.
  2. Both respond simultaneously, causing data corruption or one device never being accessed.
  3. The slave sends ACK (pulls SDA low) after each byte; NACK indicates a problem.
  4. Changing SDA while SCL is high is reserved for start and stop conditions to mark transaction boundaries.

Chapter 3: SPI - High-Speed Four-Wire Interface

Fundamentals

SPI (Serial Peripheral Interface) is the speed champion of embedded protocols. Using separate lines for data in each direction (MOSI and MISO) plus a clock (SCK), SPI achieves full-duplex communication at speeds often exceeding 10 MHz. A chip select (CS) line for each slave device creates a star topology that scales without address conflicts.

SPI trades wires for speed and simplicity. Where I2C uses two wires for many devices, SPI uses four wires plus one CS per device. The protocol itself is trivial - just shift data in and out on clock edges - but getting the timing right requires understanding SPI modes.

Deep Dive

SPI Signals:

SPI Signal Connections:

                    MASTER                         SLAVE
              ┌──────────────┐               ┌──────────────┐
              │              │     MOSI      │              │
              │         MOSI ├──────────────►│ MOSI (DI)    │
              │              │               │              │
              │         MISO │◄──────────────┤ MISO (DO)    │
              │              │     MISO      │              │
              │          SCK ├──────────────►│ SCK          │
              │              │      SCK      │              │
              │          CS0 ├──────────────►│ CS (SS)      │
              │              │      CS       │              │
              └──────────────┘               └──────────────┘

  MOSI = Master Out Slave In (data from master to slave)
  MISO = Master In Slave Out (data from slave to master)
  SCK  = Serial Clock (always from master)
  CS   = Chip Select (active low, one per slave)

Multiple Slaves:
                         MASTER
                    ┌──────────────┐
                    │              │
              MOSI ─┤              ├─── CS0 ───────► Slave 0
              MISO ─┤              ├─── CS1 ───────► Slave 1
               SCK ─┤              ├─── CS2 ───────► Slave 2
                    │              ├─── CS3 ───────► Slave 3
                    └──────────────┘
                           │
         ┌─────────────────┼─────────────────┐
         ▼                 ▼                 ▼
    ┌────────┐       ┌────────┐       ┌────────┐
    │ Slave0 │       │ Slave1 │       │ Slave2 │
    │  CS0   │       │  CS1   │       │  CS2   │
    └────────┘       └────────┘       └────────┘

  All slaves share MOSI, MISO, SCK
  Only selected slave (CS low) responds
  Unselected slaves tri-state their MISO

SPI Modes:

The four SPI modes define when data is sampled relative to the clock. Getting this wrong is the most common SPI bug.

SPI Modes - Clock Polarity and Phase:

  Mode │ CPOL │ CPHA │ Clock Idle │ Sample Edge │ Shift Edge
  ─────┼──────┼──────┼────────────┼─────────────┼───────────
    0  │  0   │  0   │   LOW      │  Rising     │ Falling
    1  │  0   │  1   │   LOW      │  Falling    │ Rising
    2  │  1   │  0   │   HIGH     │  Falling    │ Rising
    3  │  1   │  1   │   HIGH     │  Rising     │ Falling

  CPOL = Clock POLarity (idle state of clock)
  CPHA = Clock PHAse (which edge samples data)


Mode 0 (CPOL=0, CPHA=0) - Most Common:

  CS   ─────┐                                         ┌─────
            └─────────────────────────────────────────┘

  SCK  ─────────┐   ┌───┐   ┌───┐   ┌───┐   ┌───┐   ──────
                └───┘   └───┘   └───┘   └───┘   └───┘

  MOSI ─────────┬───────┬───────┬───────┬───────┬─────────
       (idle)  │  D7   │  D6   │  D5   │  D4   │ ...
               ▲       ▲       ▲       ▲       ▲
               Sample on rising edge

Mode 3 (CPOL=1, CPHA=1) - Also Common:

  CS   ─────┐                                         ┌─────
            └─────────────────────────────────────────┘

  SCK  ─────┐   ┌───┐   ┌───┐   ┌───┐   ┌───┐   ┌─────────
            └───┘   └───┘   └───┘   └───┘   └───┘

  MOSI ─────────┬───────┬───────┬───────┬───────┬─────────
       (idle)  │  D7   │  D6   │  D5   │  D4   │ ...
                   ▲       ▲       ▲       ▲
                   Sample on rising edge

Bit Order:

Most SPI devices use MSB-first transmission, but some (like certain LED drivers) use LSB-first. Check the datasheet.

Full-Duplex Operation:

SPI is truly full-duplex - data shifts in both directions simultaneously. When the master sends a command byte, it also receives a response byte (often garbage during command phase). A typical read operation sends a command, then continues clocking while reading the response.

SPI Full-Duplex Data Exchange:

  Master TX:  CMD  ADDR  XX   XX   XX   XX
              ──►  ──►   ──►  ──►  ──►  ──►

  Slave TX:   XX   XX    D0   D1   D2   D3
              ◄──  ◄──   ◄──  ◄──  ◄──  ◄──

  Legend:
    CMD  = Command byte sent by master
    ADDR = Address byte sent by master
    XX   = Don't care / dummy byte
    D0-D3 = Data bytes from slave

  Both directions transfer on every clock cycle!
  Master sends 6 bytes, receives 6 bytes simultaneously.
  Only the last 4 received bytes contain useful data.

Speed Considerations:

SPI can run at very high speeds (10-50+ MHz) but layout matters. Long wires, poor grounding, and impedance mismatches cause signal integrity problems at high speeds. For breadboard prototyping, limit speed to 1-4 MHz.

SPI Speed vs Wire Length:

  Speed     │ Max Practical Length │ Notes
  ──────────┼──────────────────────┼─────────────────────
  1 MHz     │ 1 meter              │ Breadboard OK
  4 MHz     │ 30 cm                │ Short jumpers
  10 MHz    │ 10 cm                │ PCB recommended
  25 MHz    │ 5 cm                 │ Careful routing
  50+ MHz   │ PCB only             │ Impedance matched

How This Fits in Projects

  • Project 12: SPI configuration and mode testing
  • Project 13: Driving TFT displays at high speed
  • Project 14: SD card interface and FAT filesystem
  • Project 15: Flash memory read/write operations
  • Project 16: High-speed data acquisition

Definitions & Key Terms

  • MOSI/MISO: Data lines (some devices use SDI/SDO or DI/DO)
  • SCK/SCLK: Clock signal from master
  • CS/SS: Chip select / slave select (active low)
  • CPOL: Clock polarity (idle state)
  • CPHA: Clock phase (sample edge)
  • Bit order: MSB-first or LSB-first

Check-Your-Understanding Questions

  1. Why does SPI need a separate chip select for each slave?
  2. What happens if you use the wrong SPI mode?
  3. Why is SPI faster than I2C?
  4. What is the purpose of dummy bytes in SPI transactions?

Check-Your-Understanding Answers

  1. No addressing scheme exists; CS physically selects which device responds.
  2. Data is sampled on the wrong edge, resulting in bit errors or shifted data.
  3. Full-duplex, no addressing overhead, higher clock speeds, simpler protocol.
  4. Keep the clock running while waiting for slave data or inserting timing delays.

Concept Summary Table

Concept Protocol What You Must Internalize
Asynchronous timing UART Both sides must agree on baud rate beforehand
Framing UART Start bit, data, optional parity, stop bits
Voltage levels UART RS-232 (±12V) vs TTL (0-5V) vs CMOS (0-3.3V)
Open-drain I2C Lines can only be pulled low; pull-ups required
Addressing I2C 7-bit address selects slave; collisions cause problems
Clock stretching I2C Slave can pause master by holding SCL low
SPI modes SPI CPOL and CPHA determine sampling edge
Chip select SPI Active-low signal enables specific slave
Full-duplex SPI Data flows both directions simultaneously
Pull-up values I2C/SPI Affects rise time, power consumption, speed
Level shifting All Voltage domain translation for mixed systems
Bit-banging All Software implementation on GPIO pins

Deep Dive Reading by Concept

Concept Book Chapters
UART fundamentals “Making Embedded Systems” by Elecia White Ch. 6, 9
I2C protocol details “The Book of I2C” by Randall Hyde Ch. 1-5
SPI protocol “Exploring BeagleBone” by Derek Molloy Ch. 8
Signal integrity “High-Speed Digital Design” by Johnson & Graham Ch. 1-3
Protocol debugging “Debugging Embedded and Real-Time Systems” Ch. 4-5
Bit-banging “The Definitive Guide to ARM Cortex-M0” Ch. 12
Level shifting “The Art of Electronics” by Horowitz & Hill Ch. 1.6
Embedded C “C Programming for Embedded Systems” Ch. 8-10

Quick Start Guide (First 48 Hours)

If you are overwhelmed, follow this minimal path:

Day 1 (4 hours):

  1. Set up Arduino IDE or Raspberry Pi development environment
  2. Complete Project 1: UART loopback test
  3. Use a serial terminal to verify communication
  4. Document your baud rate calculation

Day 2 (4 hours):

  1. Wire up an I2C device (temperature sensor or OLED)
  2. Run an I2C scanner to find the device address
  3. Read a single register from the device
  4. Display the value on serial output

After 48 hours: You have working UART and I2C. Continue with projects sequentially.


Path A: Beginner (No Prior Embedded Experience)

  1. Read Chapter 1 (UART) thoroughly
  2. Projects 1-3 (UART fundamentals)
  3. Read Chapter 2 (I2C) thoroughly
  4. Projects 6-8 (I2C basics)
  5. Read Chapter 3 (SPI) thoroughly
  6. Projects 12-13 (SPI basics)
  7. Project 17 (Protocol analyzer) as capstone

Path B: Experienced Embedded Developer

  1. Skim theory chapters for gaps
  2. Project 4 (UART debugging)
  3. Projects 10-11 (I2C advanced)
  4. Projects 14-16 (SPI high-speed)
  5. Projects 18-20 (Cross-protocol)

Path C: Hardware Focus (PCB Designer)

  1. Focus on electrical characteristics in each chapter
  2. Project 5 (Level shifting)
  3. Project 11 (I2C bus debugging)
  4. Project 16 (High-speed SPI)
  5. Project 19 (Bus topology design)

Projects


Project 1: UART Fundamentals - Loopback and Echo

What you will build: A UART echo system that receives characters and transmits them back, demonstrating bidirectional communication. You will verify timing with an oscilloscope or logic analyzer.

Why it teaches the concept: Loopback testing isolates the UART hardware from complex peripherals, letting you verify baud rate, framing, and signal integrity in the simplest possible configuration.

Core Challenges

  1. Calculate baud rate register values from clock frequency
  2. Configure frame format (8N1 vs other configurations)
  3. Handle receive buffer overflow conditions
  4. Measure actual baud rate with an oscilloscope
  5. Compare theoretical vs measured bit timing

Key Concepts

Concept Book Reference
UART framing “Making Embedded Systems” Ch. 6
Baud rate calculation “The Definitive Guide to ARM Cortex-M0” Ch. 12
Oscilloscope measurements “The Art of Electronics” Ch. 13

Difficulty & Time Estimate

  • Difficulty: Beginner
  • Time: 4-6 hours
  • Prerequisites: Basic C programming, ability to use a serial terminal

Real-World Outcome

When complete, you will have:

Terminal Output (minicom/screen):
$ screen /dev/ttyUSB0 9600
Connected to Arduino UART Echo

Type characters and they appear back:
> Hello World!
Hello World!
> Testing 123
Testing 123

Baud rate verification (logic analyzer):
Start bit duration: 104.2 µs (expected 104.17 µs)
Error: 0.03% - WITHIN TOLERANCE

Frame capture:
  'H' = 0x48 = 01001000
  [Idle]─┐  ┌─┐  ┌─────┐     ┌─┐  [Stop]
         └──┘  └─┘     └─────┘  └──
         Start 0  0  0  1  0  0  1  0

The Core Question You’re Answering

How does asynchronous serial communication work without a shared clock, and what determines whether receiver and transmitter will understand each other?

Concepts You Must Understand First

Before starting, answer these questions:

  1. What is a baud rate and how is it different from bits per second?
    • For 8N1 framing, what is the relationship?
    • Reference: “Making Embedded Systems” Ch. 6
  2. How does the receiver find the start of each byte?
    • What makes the start bit detectable?
    • Reference: Any UART tutorial
  3. What happens if sender and receiver use different baud rates?
    • Calculate the maximum tolerable mismatch
    • Reference: UART timing specification

Questions to Guide Your Design

Hardware Setup:

  • How will you create a loopback connection?
  • Do you need a level shifter for your USB-serial adapter?
  • What test equipment will you use to verify timing?

Software Architecture:

  • Will you use polling or interrupts for receive?
  • How will you handle buffer overflow?
  • How will you test different baud rates?

Thinking Exercise

Before writing code, trace through the transmission of the character ‘A’ (0x41 = 01000001):

  1. Draw the complete UART frame (start, 8 data bits LSB first, stop)
  2. Calculate the total frame duration at 9600 baud
  3. Identify when the receiver will sample each bit
  4. Predict what happens if the receiver’s clock is 5% faster

The Interview Questions They’ll Ask

  1. “How does a UART receiver synchronize to incoming data?”
  2. “What causes framing errors and how do you detect them?”
  3. “Why is UART limited in maximum baud rate?”
  4. “Explain the difference between hardware and software flow control.”
  5. “How would you debug a UART that works at 9600 but fails at 115200?”

Hints in Layers

Hint 1 (Conceptual): Start with the simplest loopback: wire TX directly to RX on the same device. This tests the UART peripheral without any external complications.

Hint 2 (Direction): Use a baud rate that divides evenly from your clock. If the error is too high, consider adjusting the clock or using a different baud rate.

Hint 3 (Technical): On Arduino, the hardware serial is connected to USB. Use SoftwareSerial or a separate hardware UART for loopback testing without USB interference.

Hint 4 (Debugging): If you see garbage characters, the baud rate is wrong. If you see nothing, check TX/RX connections and pull-ups. Use an LED on TX to verify transmission is occurring.

Common Pitfalls & Debugging

Problem Root Cause Fix Verification
No characters received TX not connected to RX Verify wiring Scope shows TX activity
Garbage characters Baud rate mismatch Verify both sides match Measure bit duration
Missing characters Buffer overflow Add flow control Monitor buffer level
Extra characters Noise on line Add filtering, shorter wires Clean signal on scope
Works once then stops Interrupt not cleared Check ISR code Add debug output

Learning Milestones

  1. Basic: Echo works at 9600 baud with correct characters
  2. Intermediate: Works at 115200, can measure timing with logic analyzer
  3. Advanced: Handles continuous high-speed data without loss, implements flow control

Project 2: Building a Serial Command Terminal

What you will build: A command-line interface over UART that accepts text commands, parses them, and executes actions. Commands like “LED ON”, “READ ADC”, and “SET BAUD 115200” will control the microcontroller.

Why it teaches the concept: A command terminal requires line buffering, string parsing, and bidirectional communication - all fundamental UART operations. It also teaches you to design robust protocols.

Core Challenges

  1. Implement line buffering with backspace handling
  2. Parse commands into tokens (command, arguments)
  3. Implement a command dispatch table
  4. Handle command errors gracefully
  5. Support command history (advanced)

Key Concepts

Concept Book Reference
Line buffering “C Programming for Embedded Systems” Ch. 8
State machines “Making Embedded Systems” Ch. 5
String parsing “C Programming Language” by K&R Ch. 5

Difficulty & Time Estimate

  • Difficulty: Beginner-Intermediate
  • Time: 8-12 hours
  • Prerequisites: Project 1, basic string handling in C

Real-World Outcome

Terminal Session:
> help
Available commands:
  LED <ON|OFF>     - Control onboard LED
  READ <ADC|TEMP>  - Read sensor values
  BAUD <rate>      - Change baud rate
  ECHO <ON|OFF>    - Enable/disable echo
  INFO             - System information
  RESET            - Reset device

> led on
LED: ON

> read adc
ADC Channel 0: 2.45V (raw: 752)

> baud 115200
Changing baud rate to 115200...
Reconnect at new rate.

> invalid command
ERROR: Unknown command 'invalid'
Type 'help' for available commands.

The Core Question You’re Answering

How do you build a reliable human-machine interface over a serial link, handling the complexities of line editing, command parsing, and error recovery?

Questions to Guide Your Design

Buffer Management:

  • How large should your line buffer be?
  • What happens when the buffer overflows?
  • How do you handle backspace at position 0?

Command Parsing:

  • How will you separate command from arguments?
  • How will you handle quoted strings with spaces?
  • What errors need to be detected and reported?

Thinking Exercise

Design a state machine for line input that handles:

  • Normal character accumulation
  • Backspace (delete previous character)
  • Escape sequences (arrow keys)
  • Carriage return (end of line)

Draw the state diagram before implementing.

The Interview Questions They’ll Ask

  1. “How would you implement command history with up/down arrows?”
  2. “What happens if the user sends binary data to your text parser?”
  3. “How would you add authentication to your command interface?”
  4. “Describe the buffer overflow vulnerabilities in command parsing.”
  5. “How would you test this interface automatically?”

Hints in Layers

Hint 1: Start with the simplest command - just echo back what was typed.

Hint 2: Use a circular buffer for input to handle variable-length lines.

Hint 3: Implement commands as function pointers in a table for clean dispatch.

Hint 4: Handle the case where a command takes longer than the UART buffer fill time.


Project 3: UART Error Detection and Diagnostics

What you will build: A diagnostic tool that detects and reports UART errors including framing errors, parity errors, overrun errors, and noise detection. It will display statistics and help identify wiring or timing problems.

Why it teaches the concept: Real-world serial communication fails in subtle ways. Understanding error conditions and their causes is essential for debugging.

Core Challenges

  1. Enable and monitor all UART error flags
  2. Create an error statistics structure
  3. Inject errors intentionally to verify detection
  4. Correlate errors with physical conditions
  5. Build an error recovery mechanism

Key Concepts

Concept Book Reference
Error detection “Making Embedded Systems” Ch. 9
Noise immunity “The Art of Electronics” Ch. 9
Statistics “C Programming for Embedded Systems” Ch. 10

Difficulty & Time Estimate

  • Difficulty: Intermediate
  • Time: 8-12 hours
  • Prerequisites: Projects 1-2

Real-World Outcome

UART Diagnostics Report
========================
Baud Rate: 115200
Frame: 8N1

Statistics (last 60 seconds):
  Bytes received:     45,230
  Bytes transmitted:  45,230
  Framing errors:     0
  Parity errors:      0
  Overrun errors:     2
  Noise errors:       15
  Break detected:     0

Error rate: 0.037%
Signal quality: GOOD

Last error: Overrun at byte 23,456
  Cause: Receive buffer full, ISR delayed
  Fix: Increase buffer or priority

Noise analysis:
  Errors cluster at 12Hz intervals
  Possible cause: Switching power supply

The Core Question You’re Answering

How do you build robust serial communication that can detect, report, and recover from real-world transmission errors?

Questions to Guide Your Design

  • What error conditions can your UART hardware detect?
  • How do you distinguish between wiring problems and software problems?
  • When should you discard data vs attempt recovery?

The Interview Questions They’ll Ask

  1. “What causes framing errors and how do you prevent them?”
  2. “How would you implement error correction over a noisy link?”
  3. “Describe the difference between overrun and overflow.”
  4. “How do you handle break conditions in your protocol?”

Hints in Layers

Hint 1: Read your microcontroller’s UART status register documentation carefully.

Hint 2: Create a test mode that intentionally causes each error type.

Hint 3: Correlate error timing with external events (motor starts, power supply load).

Hint 4: Consider adding a watchdog that resets the UART if errors exceed a threshold.


Project 4: Logic Analyzer Capture of UART Traffic

What you will build: A software logic analyzer running on a second microcontroller that captures UART traffic, decodes frames, and displays the data stream. This is your most important debugging tool.

Why it teaches the concept: Seeing actual waveforms transforms your understanding. You will debug protocol issues visually instead of guessing.

Core Challenges

  1. Sample GPIO at rates high enough to capture UART
  2. Detect start bits reliably
  3. Decode frames from timing measurements
  4. Store and display captured data
  5. Handle edge cases (noise, glitches)

Key Concepts

Concept Book Reference
Sampling theory “Digital Signal Processing” basics
Edge detection “Making Embedded Systems” Ch. 8
Timing measurement “The Definitive Guide to ARM Cortex-M0” Ch. 12

Difficulty & Time Estimate

  • Difficulty: Intermediate-Advanced
  • Time: 2-3 weeks
  • Prerequisites: Projects 1-3, interrupt handling

Real-World Outcome

Logic Analyzer Capture
======================
Sample rate: 1 MHz
Capture depth: 4096 samples
Trigger: Falling edge (start bit)

Time (µs)  │ Level │ Decoded
───────────┼───────┼─────────────
0          │ HIGH  │ Idle
104        │ LOW   │ Start bit
208        │ HIGH  │ Bit 0 = 1
312        │ LOW   │ Bit 1 = 0
416        │ HIGH  │ Bit 2 = 1
520        │ LOW   │ Bit 3 = 0
624        │ HIGH  │ Bit 4 = 1
728        │ LOW   │ Bit 5 = 0
832        │ HIGH  │ Bit 6 = 1
936        │ LOW   │ Bit 7 = 0
1040       │ HIGH  │ Stop bit

Decoded byte: 0x55 = 'U' (valid frame)
Measured baud: 9615 (+0.16% error)

The Core Question You’re Answering

How do you build a tool that lets you see exactly what is happening on a serial bus, turning invisible electrical signals into understandable data?

Questions to Guide Your Design

  • What sample rate is needed to reliably capture your target baud rate?
  • How do you synchronize to the start bit without a hardware UART?
  • How much memory do you need for useful capture depth?

The Interview Questions They’ll Ask

  1. “What is the Nyquist rate and why does it matter here?”
  2. “How would you trigger on a specific byte pattern?”
  3. “How do you handle captures longer than your memory?”
  4. “What are the limitations of software-based logic analysis?”

Hints in Layers

Hint 1: You need to sample at least 8x the baud rate for reliable decoding.

Hint 2: Use timer capture interrupts for precise edge timing.

Hint 3: Implement circular buffers with pre-trigger and post-trigger modes.

Hint 4: Start with oscilloscope-verified test signals before trusting your decoder.


Project 5: RS-232 to TTL Level Converter with Protection

What you will build: A bidirectional level shifter that safely converts RS-232 signals (±12V) to TTL/CMOS levels (0-3.3V or 0-5V) with ESD and overvoltage protection.

Why it teaches the concept: Understanding voltage domains is essential for interfacing with legacy equipment and protecting your microcontroller from damage.

Core Challenges

  1. Design the level conversion circuit
  2. Add ESD protection components
  3. Test with actual RS-232 equipment
  4. Verify signal integrity at different speeds
  5. Document operating limits

Key Concepts

Concept Book Reference
Level shifting “The Art of Electronics” Ch. 1.6
ESD protection “The Art of Electronics” Ch. 12
RS-232 standard “Making Embedded Systems” Ch. 9

Difficulty & Time Estimate

  • Difficulty: Intermediate
  • Time: 8-12 hours
  • Prerequisites: Basic electronics, Projects 1-3

Real-World Outcome

Level Converter Specification:
==============================
Input: RS-232 (±12V)
Output: CMOS 3.3V

Protection:
  ESD: ±15kV contact, ±8kV air
  Overvoltage: Survives ±25V input
  Reverse polarity: Protected

Tested performance:
  Baud rates: 300 - 115200
  Rise time: < 1µs
  Propagation delay: 0.2µs
  Power: 5mA typical

Schematic saved, BOM created, tested with vintage terminal.

The Core Question You’re Answering

How do you safely interface between different voltage domains while preserving signal integrity and protecting sensitive components?

Questions to Guide Your Design

  • Why does RS-232 use ±12V when TTL uses 0-5V?
  • What is the difference between a level shifter and a transceiver?
  • What happens if you connect RS-232 directly to a 3.3V GPIO?

The Interview Questions They’ll Ask

  1. “Explain how a MAX232 charge pump works.”
  2. “What is the slew rate specification for RS-232 and why?”
  3. “How would you design for higher speeds than RS-232 supports?”
  4. “What protection do you need for industrial environments?”

Hints in Layers

Hint 1: Use a dedicated IC like MAX232 or MAX3232 for charge pump voltage generation.

Hint 2: Add TVS diodes for ESD protection on all signal lines.

Hint 3: Test with a known-working serial port before connecting to valuable equipment.

Hint 4: Consider optoisolation for noisy industrial environments.


Project 6: I2C Bus Scanner and Device Discovery

What you will build: A tool that scans all 127 possible I2C addresses and reports which devices respond. It will also attempt to identify common devices by their typical addresses and behavior.

Why it teaches the concept: Before you can communicate with an I2C device, you must find it. This project teaches addressing, ACK/NACK detection, and bus debugging.

Core Challenges

  1. Implement I2C start, stop, and address transmission
  2. Detect ACK vs NACK responses
  3. Handle bus errors (stuck bus, no pull-ups)
  4. Identify common devices by address
  5. Verify with known devices

Key Concepts

Concept Book Reference
I2C addressing “The Book of I2C” Ch. 2
ACK/NACK “The Book of I2C” Ch. 3
Bus debugging “Making Embedded Systems” Ch. 9

Difficulty & Time Estimate

  • Difficulty: Beginner-Intermediate
  • Time: 6-8 hours
  • Prerequisites: Basic I2C understanding

Real-World Outcome

I2C Bus Scanner
===============
Bus: I2C1, SDA=GPIO2, SCL=GPIO3
Pull-ups: 4.7K detected
Speed: 100 kHz

Scanning 0x03-0x77...

   0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
00:       -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3C -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77

Found 5 devices:
  0x3C: SSD1306 OLED display (probable)
  0x48: TMP102/LM75 temperature sensor (probable)
  0x50: AT24C02 EEPROM (probable)
  0x68: DS3231/MPU6050 (RTC or accelerometer)
  0x77: BMP280/BME280 pressure sensor (probable)

Bus health: GOOD
  SDA rise time: 450ns (OK)
  SCL rise time: 420ns (OK)
  No stuck conditions detected

The Core Question You’re Answering

How do you discover what devices are present on an I2C bus and verify that the bus is functioning correctly?

Questions to Guide Your Design

  • What addresses are reserved and should not be scanned?
  • What happens if you scan an address with no device?
  • How do you handle a device that NACKs its own address?

The Interview Questions They’ll Ask

  1. “Why are addresses 0x00-0x07 and 0x78-0x7F reserved?”
  2. “How would you detect if pull-up resistors are missing?”
  3. “What causes a ‘bus stuck’ condition and how do you recover?”
  4. “How would you scan a bus with clock stretching devices?”

Hints in Layers

Hint 1: Start by sending just a start condition and address byte, then stop.

Hint 2: An ACK means something pulled SDA low on the 9th clock.

Hint 3: Add a timeout for devices that stretch the clock too long.

Hint 4: Compare your results with i2cdetect on Linux for verification.


Project 7: I2C Temperature and Motion Sensor Network

What you will build: A multi-sensor I2C network reading temperature (TMP102 or BME280), humidity, and motion (MPU6050) data, with all devices sharing the same bus.

Why it teaches the concept: Real I2C systems have multiple devices. You will learn addressing, register-based protocols, and managing multiple devices.

Core Challenges

  1. Address multiple devices on the same bus
  2. Implement device-specific register protocols
  3. Handle different data formats (12-bit, 16-bit, signed)
  4. Create a unified sensor abstraction layer
  5. Sample at different rates per device

Key Concepts

Concept Book Reference
Register protocols “The Book of I2C” Ch. 4
Sensor datasheets Device documentation
Data conversion “Making Embedded Systems” Ch. 6

Difficulty & Time Estimate

  • Difficulty: Intermediate
  • Time: 1-2 weeks
  • Prerequisites: Project 6, ability to read datasheets

Real-World Outcome

Sensor Network Monitor
======================
Update rate: 10 Hz

TMP102 @ 0x48:
  Temperature: 23.45°C
  Alert: OFF (threshold: 30°C)

BME280 @ 0x77:
  Temperature: 23.3°C
  Humidity: 45.2%
  Pressure: 1013.25 hPa

MPU6050 @ 0x68:
  Accel X: 0.02g
  Accel Y: -0.01g
  Accel Z: 1.00g
  Gyro X: 0.5°/s
  Gyro Y: -0.2°/s
  Gyro Z: 0.1°/s
  Device Temp: 24.1°C

Motion events: 3 detected (tap)
Last motion: 2 seconds ago

The Core Question You’re Answering

How do you build a system that communicates with multiple I2C devices, each with different register layouts and data formats, on a shared bus?

Questions to Guide Your Design

  • How do you organize code for devices with different register maps?
  • How do you handle devices with configurable addresses?
  • What is the best data structure for heterogeneous sensor data?

The Interview Questions They’ll Ask

  1. “How do you handle sensors with different update rates on the same bus?”
  2. “Describe the register read sequence for a typical I2C sensor.”
  3. “How would you add a new sensor type without modifying existing code?”
  4. “What is the bus bandwidth limit with multiple sensors?”

Hints in Layers

Hint 1: Read each sensor’s datasheet register map carefully.

Hint 2: Use a common I2C read/write wrapper, with sensor-specific layers above.

Hint 3: Implement pointer-based register addressing (write pointer, then read data).

Hint 4: Add calibration data reading and compensation for sensors that require it (BME280).


Project 8: I2C EEPROM Data Logger

What you will build: A data logging system that stores timestamped sensor readings to an I2C EEPROM, with read-back and wear-leveling capabilities.

Why it teaches the concept: EEPROM has limited write cycles and page-based writing. You will learn byte-level vs page-level operations and data persistence strategies.

Core Challenges

  1. Implement EEPROM byte read/write
  2. Implement page write with boundary handling
  3. Design a wear-leveling strategy
  4. Create a log format with timestamps
  5. Implement log retrieval and clear operations

Key Concepts

Concept Book Reference
EEPROM protocols AT24C02 datasheet
Wear leveling “Making Embedded Systems” Ch. 11
Data structures “C Programming for Embedded Systems” Ch. 7

Difficulty & Time Estimate

  • Difficulty: Intermediate
  • Time: 1-2 weeks
  • Prerequisites: Projects 6-7

Real-World Outcome

EEPROM Data Logger
==================
Device: AT24C256 (32KB)
Organization: 512 pages x 64 bytes

Status:
  Total entries: 1,247
  Oldest entry: 2024-01-15 08:23:45
  Newest entry: 2024-01-16 14:52:12
  Free space: 18,432 bytes (56%)
  Write cycles (page 0): 4,521 / 100,000

Commands:
> log read 10
Entry 1247: 2024-01-16 14:52:12 T=23.4C H=45% P=1013hPa
Entry 1246: 2024-01-16 14:42:12 T=23.3C H=44% P=1013hPa
...

> log stats
Entries per hour: 6
Estimated time to full: 8.2 days
Estimated EEPROM life: 22 years at current rate

> log export csv
Exporting 1247 entries to serial...
timestamp,temperature,humidity,pressure
2024-01-15 08:23:45,22.1,48,1015
...

The Core Question You’re Answering

How do you persistently store data on I2C EEPROM while managing page boundaries, write timing, and limited write endurance?

Questions to Guide Your Design

  • How do you handle writes that cross page boundaries?
  • What happens if power fails during a write?
  • How do you know where the next free location is after power-up?

The Interview Questions They’ll Ask

  1. “Explain the EEPROM page write cycle and timing requirements.”
  2. “How would you implement a circular buffer in EEPROM?”
  3. “What is wear leveling and why is it necessary?”
  4. “How do you ensure data integrity after unexpected power loss?”

Hints in Layers

Hint 1: EEPROM write takes 5-10ms - poll for completion or delay.

Hint 2: Split multi-byte writes at page boundaries into multiple writes.

Hint 3: Reserve a header page for metadata (entry count, next write location).

Hint 4: Add a simple checksum to detect corrupted entries.


Project 9: I2C OLED Display Driver

What you will build: A driver for the SSD1306 128x64 OLED display that can draw text, shapes, and bitmap graphics over I2C.

Why it teaches the concept: Displays require sending large amounts of data efficiently. You will learn command vs data modes, frame buffers, and display memory organization.

Core Challenges

  1. Initialize the SSD1306 with correct command sequence
  2. Understand display memory organization (pages and columns)
  3. Implement a frame buffer in microcontroller RAM
  4. Create text rendering with fonts
  5. Optimize refresh rate

Key Concepts

Concept Book Reference
Display protocols SSD1306 datasheet
Graphics primitives “Computer Graphics” basics
Memory management “Making Embedded Systems” Ch. 4

Difficulty & Time Estimate

  • Difficulty: Intermediate
  • Time: 2-3 weeks
  • Prerequisites: Projects 6-7

Real-World Outcome

OLED Display Driver
===================
Display: SSD1306 128x64
Interface: I2C @ 400kHz
Frame buffer: 1024 bytes

Performance:
  Full refresh: 12ms (83 FPS max)
  Partial update: 2ms per page

API:
  display_init()
  display_clear()
  display_pixel(x, y, on)
  display_line(x0, y0, x1, y1)
  display_rect(x, y, w, h, filled)
  display_circle(cx, cy, r, filled)
  display_text(x, y, "Hello World")
  display_bitmap(x, y, bitmap, w, h)
  display_update()  // Flush buffer to display

Demo running: Shows sensor data with graphics and scrolling text.

The Core Question You’re Answering

How do you efficiently send display data over I2C while managing frame buffers and understanding display controller architecture?

Questions to Guide Your Design

  • Why does the SSD1306 use page-based memory organization?
  • Should you update the entire display or just changed regions?
  • How do you balance memory usage against refresh speed?

The Interview Questions They’ll Ask

  1. “Explain the command/data mode switching in SSD1306.”
  2. “How would you implement hardware scrolling?”
  3. “What is the maximum refresh rate achievable over I2C?”
  4. “How do you render proportional fonts efficiently?”

Hints in Layers

Hint 1: Read the SSD1306 initialization sequence from a working library first.

Hint 2: The display is organized as 8 horizontal pages of 128 columns each.

Hint 3: Use a local frame buffer and send the whole thing for simplicity first.

Hint 4: For text, pre-render fonts as bitmaps indexed by ASCII value.


Project 10: I2C Multi-Master Configuration

What you will build: A system with two I2C masters that can both initiate transactions without collision, using I2C arbitration.

Why it teaches the concept: Multi-master is the most complex I2C scenario. Understanding arbitration and bus contention is essential for robust systems.

Core Challenges

  1. Configure two microcontrollers as I2C masters
  2. Implement and observe arbitration
  3. Handle bus busy conditions
  4. Design a communication protocol between masters
  5. Measure and log collision events

Key Concepts

Concept Book Reference
I2C arbitration “The Book of I2C” Ch. 5
Multi-master I2C specification v6
Contention handling “Making Embedded Systems” Ch. 9

Difficulty & Time Estimate

  • Difficulty: Advanced
  • Time: 2-3 weeks
  • Prerequisites: Projects 6-9

Real-World Outcome

Multi-Master I2C Test
=====================
Master 1: Arduino Uno @ 0x10
Master 2: Raspberry Pi Pico @ 0x11
Slave: EEPROM @ 0x50

Test: Simultaneous write attempts

Cycle 1: Master 1 started first, Master 2 detected busy, retried
Cycle 2: Master 2 started first, Master 1 detected busy, retried
Cycle 3: Simultaneous start, Master 1 lost arbitration at bit 3
         Master 1 retried after backoff

Statistics (1000 cycles):
  Master 1 won: 487
  Master 2 won: 498
  Arbitration events: 15
  Failed transactions: 0

Arbitration is working correctly!
Average retry delay: 2.3ms

The Core Question You’re Answering

How does I2C handle multiple masters trying to use the bus simultaneously, and how do you build reliable systems with this capability?

Questions to Guide Your Design

  • What makes I2C arbitration non-destructive?
  • How does a master know it lost arbitration?
  • What is clock synchronization in multi-master?

The Interview Questions They’ll Ask

  1. “Explain the I2C arbitration mechanism step by step.”
  2. “What happens if two masters send to the same slave address?”
  3. “How would you implement a priority scheme with multi-master?”
  4. “Why is multi-master rarely used in practice?”

Hints in Layers

Hint 1: Both masters must monitor SDA during transmission.

Hint 2: A master loses arbitration when it transmits 1 but reads 0.

Hint 3: After losing, the master should wait for stop and retry.

Hint 4: Add random backoff to prevent repeated collisions.


Project 11: I2C Bus Debugging and Recovery

What you will build: A diagnostic tool that can detect and recover from stuck I2C bus conditions, identify problematic devices, and measure bus health.

Why it teaches the concept: I2C buses can lock up. Knowing how to diagnose and fix bus problems is essential for debugging real systems.

Core Challenges

  1. Detect stuck SDA or SCL conditions
  2. Implement bus recovery (9 clock pulses)
  3. Measure rise times and compare to specification
  4. Identify slow or non-compliant devices
  5. Generate a bus health report

Key Concepts

Concept Book Reference
Bus recovery “The Book of I2C” Ch. 6
Signal integrity “High-Speed Digital Design” Ch. 2
Debugging “Making Embedded Systems” Ch. 9

Difficulty & Time Estimate

  • Difficulty: Advanced
  • Time: 2 weeks
  • Prerequisites: Projects 6-10

Real-World Outcome

I2C Bus Diagnostic Report
=========================
Bus: I2C1, SDA=GPIO2, SCL=GPIO3
Test date: 2024-01-16 15:30:00

Electrical Analysis:
  Pull-up detected: Yes
  SDA rise time: 650ns (MARGINAL - spec 1000ns max for 100kHz)
  SCL rise time: 480ns (OK)
  Bus capacitance: ~180pF (estimated)
  Recommended pull-up: 2.2K (currently ~4.7K detected)

Device Health:
  0x48: OK (responds in 45µs)
  0x50: OK (responds in 38µs)
  0x68: SLOW (clock stretch 1.2ms detected)
  0x77: OK (responds in 52µs)

Bus Stress Test:
  1000 transactions @ 100kHz: 0 errors
  1000 transactions @ 400kHz: 3 errors (0.3%)
  Recommendation: Use 100kHz for reliability

Recovery Test:
  Simulated stuck SDA: Recovered after 9 clocks
  Simulated stuck SCL: Timeout (manual intervention needed)

The Core Question You’re Answering

How do you diagnose I2C bus problems and recover from stuck conditions without power cycling the system?

Questions to Guide Your Design

  • What causes a bus to get stuck?
  • Why do 9 clock pulses help recovery?
  • How do you measure rise time without an oscilloscope?

The Interview Questions They’ll Ask

  1. “How do you detect if the I2C bus is stuck?”
  2. “Explain the 9-clock recovery procedure.”
  3. “What causes excessive rise times and how do you fix them?”
  4. “How would you isolate a misbehaving device on a shared bus?”

Hints in Layers

Hint 1: A stuck bus usually means a slave is holding SDA low.

Hint 2: Toggle SCL 9 times to shift out any partial byte in the slave.

Hint 3: Measure rise time by toggling and sampling with a timer.

Hint 4: Consider adding a bus switch IC for hardware isolation.


Project 12: SPI Mode Configuration and Testing

What you will build: A test framework that verifies communication in all four SPI modes, helping you determine the correct settings for any device.

Why it teaches the concept: Wrong SPI mode is the most common SPI bug. Systematic testing teaches you to identify and fix mode mismatches.

Core Challenges

  1. Implement all four SPI modes
  2. Create a test pattern that reveals mode errors
  3. Visualize timing with logic analyzer
  4. Document mode behavior differences
  5. Build a mode auto-detection feature

Key Concepts

Concept Book Reference
SPI modes “Exploring BeagleBone” Ch. 8
Clock polarity Device datasheets
Timing diagrams “The Art of Electronics” Ch. 10

Difficulty & Time Estimate

  • Difficulty: Intermediate
  • Time: 1 week
  • Prerequisites: Basic SPI understanding

Real-World Outcome

SPI Mode Tester
===============
Master: Raspberry Pi Pico
Test device: 74HC595 shift register
Clock: 1 MHz

Mode 0 (CPOL=0, CPHA=0):
  Sent:     0xAA 0x55
  Received: 0xAA 0x55
  Status: PASS

Mode 1 (CPOL=0, CPHA=1):
  Sent:     0xAA 0x55
  Received: 0x54 0xAA
  Status: FAIL (data shifted)

Mode 2 (CPOL=1, CPHA=0):
  Sent:     0xAA 0x55
  Received: 0x55 0x2A
  Status: FAIL (data inverted/shifted)

Mode 3 (CPOL=1, CPHA=1):
  Sent:     0xAA 0x55
  Received: 0xAA 0x55
  Status: PASS

Conclusion: Device works in Mode 0 and Mode 3
            Recommended: Mode 0 (most common)

The Core Question You’re Answering

How do clock polarity and phase affect SPI communication, and how do you determine the correct settings for any device?

Questions to Guide Your Design

  • What test patterns reveal mode errors?
  • How do you verify correct operation without a known-good device?
  • Can you auto-detect the correct mode?

The Interview Questions They’ll Ask

  1. “Draw the timing diagram for all four SPI modes.”
  2. “Why might a device work in two different modes?”
  3. “How would you debug SPI communication that only fails intermittently?”
  4. “What is the relationship between mode and maximum speed?”

Hints in Layers

Hint 1: Use 0xAA and 0x55 - these create alternating bit patterns.

Hint 2: A shift register provides loopback capability.

Hint 3: Wrong mode typically shifts data by one bit.

Hint 4: Capture with logic analyzer to see actual clock edges.


Project 13: SPI TFT Display Driver

What you will build: A driver for an ILI9341 or ST7789 TFT display using SPI, capable of drawing graphics and images at high speed.

Why it teaches the concept: Displays require high bandwidth and efficient data transfer. You will push SPI to its limits and optimize for speed.

Core Challenges

  1. Initialize the display controller
  2. Implement color format conversion (RGB565)
  3. Draw primitives (pixels, lines, rectangles)
  4. Optimize for maximum frame rate
  5. Implement double buffering (if memory permits)

Key Concepts

Concept Book Reference
Display controllers ILI9341/ST7789 datasheets
Color formats “Computer Graphics” basics
DMA transfers “Making Embedded Systems” Ch. 8

Difficulty & Time Estimate

  • Difficulty: Intermediate-Advanced
  • Time: 2-3 weeks
  • Prerequisites: Project 12

Real-World Outcome

TFT Display Driver
==================
Display: ILI9341 320x240
Interface: SPI @ 40 MHz
Color: RGB565 (16-bit)

Performance:
  Full screen clear: 15ms (67 FPS)
  Pixel write: 0.5µs
  Line draw: 2ms (diagonal 320px)
  Image blit: 25ms (100x100 px)

With DMA:
  Full screen clear: 8ms (125 FPS)
  Image blit: 12ms (100x100 px)

Demo running: Animated graphics, smooth scrolling text,
              and image display from SD card.

The Core Question You’re Answering

How do you drive a high-resolution display over SPI while maximizing frame rate and minimizing CPU usage?

Questions to Guide Your Design

  • What is the maximum theoretical frame rate for your display/SPI combo?
  • Should you use a frame buffer or direct drawing?
  • How do you handle the command/data mode pin?

The Interview Questions They’ll Ask

  1. “Calculate the SPI bandwidth needed for 60 FPS on a 320x240 RGB565 display.”
  2. “How would you implement partial screen updates?”
  3. “What is the advantage of DMA for display updates?”
  4. “How do you synchronize display updates to avoid tearing?”

Hints in Layers

Hint 1: The ILI9341 initialization sequence is complex - start from a working example.

Hint 2: Set the drawing window before sending pixel data.

Hint 3: Use DMA for large transfers to free the CPU.

Hint 4: Consider hardware-accelerated scrolling for text applications.


Project 14: SPI SD Card Interface and FAT Filesystem

What you will build: A complete SD card interface using SPI mode, with FAT16/32 filesystem support for reading and writing files.

Why it teaches the concept: SD cards are complex SPI devices with initialization sequences, command protocols, and block-based access. FAT filesystem adds another layer of abstraction.

Core Challenges

  1. Implement SD card SPI initialization
  2. Send SD commands and parse responses
  3. Read and write 512-byte sectors
  4. Integrate FatFs or similar filesystem library
  5. Handle card detect and write protect

Key Concepts

Concept Book Reference
SD card protocol SD Simplified Specification
Block devices “Operating Systems” basics
FAT filesystem “File Systems” documentation

Difficulty & Time Estimate

  • Difficulty: Advanced
  • Time: 3-4 weeks
  • Prerequisites: Projects 12-13

Real-World Outcome

SD Card Interface
=================
Card: 16GB SDHC Class 10
Mode: SPI @ 25 MHz
Filesystem: FAT32

Card Info:
  Manufacturer: SanDisk
  Capacity: 15,931,539,456 bytes
  Sector size: 512 bytes
  Total sectors: 31,116,288

Filesystem:
  Cluster size: 32KB
  Free space: 12.4 GB
  Files: 247

Benchmark:
  Sequential read: 1.8 MB/s
  Sequential write: 0.9 MB/s
  Random read: 0.3 MB/s
  Random write: 0.1 MB/s

File operations:
  f_open("data.txt", "w") - OK
  f_write(buffer, 1024) - OK, 1024 bytes written
  f_close() - OK

The Core Question You’re Answering

How do you interface with block storage devices over SPI and implement filesystem access for persistent data storage?

Questions to Guide Your Design

  • What is the SD card initialization sequence in SPI mode?
  • How do you handle different card types (SD, SDHC, SDXC)?
  • What is the role of CRC in SD commands?

The Interview Questions They’ll Ask

  1. “Explain the SD card command structure.”
  2. “How do you detect if a card is SDHC vs SD?”
  3. “What causes write performance to be slower than read?”
  4. “How would you implement wear leveling on SD card?”

Hints in Layers

Hint 1: SD initialization must occur at slow speed (400 kHz) before switching to high speed.

Hint 2: Use CMD0 with chip select asserted to enter SPI mode.

Hint 3: FatFs is a well-tested library - use it instead of implementing FAT yourself.

Hint 4: Add retry logic for failed commands - SD cards can be temperamental.


Project 15: SPI Flash Memory Driver

What you will build: A driver for SPI NOR flash memory (like W25Q32) supporting read, write, erase, and protection operations.

Why it teaches the concept: Flash memory has different semantics than RAM - write requires erase, and operations are page-based. This project teaches flash-specific concepts.

Core Challenges

  1. Implement flash ID detection
  2. Read data at high speed
  3. Implement page program with alignment
  4. Implement sector and block erase
  5. Handle write protection and status registers

Key Concepts

Concept Book Reference
Flash technology W25Q32 datasheet
NOR vs NAND “Making Embedded Systems” Ch. 11
Wear considerations “Flash Memory” documentation

Difficulty & Time Estimate

  • Difficulty: Intermediate-Advanced
  • Time: 2 weeks
  • Prerequisites: Projects 12-13

Real-World Outcome

SPI Flash Driver
================
Device: W25Q32JV (32 Mbit)
Interface: SPI @ 50 MHz

Device Info:
  Manufacturer: Winbond (0xEF)
  Device ID: 0x4016
  Capacity: 4,194,304 bytes (4 MB)
  Page size: 256 bytes
  Sector size: 4,096 bytes
  Block size: 65,536 bytes

Performance:
  Read (sequential): 6.2 MB/s
  Page program: 200 µs typical
  Sector erase: 50 ms typical
  Bulk erase: 40 seconds

API:
  flash_read(addr, buffer, len)
  flash_write(addr, buffer, len)  // handles alignment
  flash_erase_sector(addr)
  flash_erase_block(addr)
  flash_erase_chip()
  flash_get_status()
  flash_set_protection(level)

The Core Question You’re Answering

How do you work with flash memory’s unique constraints including erase-before-write, page boundaries, and limited write cycles?

Questions to Guide Your Design

  • Why must you erase before writing to flash?
  • What happens if you write across a page boundary?
  • How do you handle wear leveling for frequently updated data?

The Interview Questions They’ll Ask

  1. “Explain the difference between NOR and NAND flash.”
  2. “What is a write-erase cycle and why does it matter?”
  3. “How would you implement a configuration storage area with flash?”
  4. “What is the purpose of the write enable command?”

Hints in Layers

Hint 1: Always read the JEDEC ID to verify correct device communication.

Hint 2: Enable write enable (WREN) before every write or erase operation.

Hint 3: Poll the status register to detect operation completion.

Hint 4: For frequent updates, implement a log-structured approach to reduce wear.


Project 16: High-Speed SPI Data Acquisition

What you will build: A data acquisition system that reads ADC samples at maximum SPI speed and transfers them to a host for analysis.

Why it teaches the concept: High-speed SPI requires understanding of DMA, buffering, and data throughput optimization. This project pushes hardware limits.

Core Challenges

  1. Configure SPI for maximum speed
  2. Implement DMA for continuous transfer
  3. Design a ring buffer for data storage
  4. Stream data to host over USB or UART
  5. Measure and optimize throughput

Key Concepts

Concept Book Reference
DMA operation “Making Embedded Systems” Ch. 8
ADC sampling ADC datasheet
Buffer management “C Programming for Embedded Systems” Ch. 9

Difficulty & Time Estimate

  • Difficulty: Advanced
  • Time: 3-4 weeks
  • Prerequisites: Projects 12-15

Real-World Outcome

High-Speed Data Acquisition
===========================
ADC: MCP3208 (12-bit, 8-channel)
Interface: SPI @ 2 MHz
Sample rate: 100 kSPS (single channel)

Configuration:
  Channels: 1 (continuous)
  Trigger: Free-running
  Buffer: 16K samples ring buffer
  Transfer: USB CDC @ 921600 baud

Performance:
  Samples/second: 100,000
  Throughput: 200 KB/s (16-bit samples)
  CPU usage: 12% (DMA handles transfers)
  Latency: 160 µs (buffer to host)

Streaming to host:
  Format: Binary (2 bytes per sample)
  Timestamp: Every 1000 samples
  Host software: Python with matplotlib

Captured 1M samples, FFT shows 1kHz test signal at -3dB.

The Core Question You’re Answering

How do you achieve maximum throughput from an SPI ADC while minimizing CPU usage and maintaining continuous data flow?

Questions to Guide Your Design

  • What limits the maximum sample rate?
  • How do you avoid buffer overrun?
  • What is the CPU-to-DMA handoff overhead?

The Interview Questions They’ll Ask

  1. “Calculate the maximum sample rate for a given SPI clock.”
  2. “How do you handle the gap between DMA transfers?”
  3. “What causes aliasing and how do you prevent it?”
  4. “How would you add real-time triggering to this system?”

Hints in Layers

Hint 1: Check the ADC’s maximum SPI clock and sample rate specifications.

Hint 2: Use ping-pong DMA buffers for continuous capture.

Hint 3: Decouple capture from transmission using a large ring buffer.

Hint 4: Add simple compression if bandwidth is limited.


Project 17: Multi-Protocol Analyzer

What you will build: A logic analyzer that captures and decodes UART, I2C, and SPI traffic simultaneously, displaying transactions in a unified view.

Why it teaches the concept: Understanding all three protocols deeply enough to decode them in software represents true mastery. This is the ultimate debugging tool.

Core Challenges

  1. Sample multiple channels simultaneously
  2. Implement protocol decoders for each type
  3. Handle different speeds on different channels
  4. Create a unified timeline display
  5. Support triggering on protocol events

Key Concepts

Concept Book Reference
Protocol decoding “Debugging Embedded Systems”
State machines “Making Embedded Systems” Ch. 5
Real-time capture “High-Speed Digital Design” Ch. 1

Difficulty & Time Estimate

  • Difficulty: Expert
  • Time: 4-6 weeks
  • Prerequisites: Projects 4, 11, 12

Real-World Outcome

Multi-Protocol Analyzer
=======================
Sample rate: 10 MHz
Channels: 8 (GPIO0-7)
Buffer: 64K samples per channel

Channel Configuration:
  CH0: UART TX (9600 baud)
  CH1: UART RX (9600 baud)
  CH2: I2C SDA
  CH3: I2C SCL
  CH4: SPI MOSI
  CH5: SPI MISO
  CH6: SPI SCK
  CH7: SPI CS

Capture Timeline:
0.00ms: [I2C] Start, Addr=0x48 Write
0.05ms: [I2C] Data=0x00 (register pointer)
0.10ms: [I2C] Repeated Start, Addr=0x48 Read
0.15ms: [I2C] Data=0x1C (temperature high)
0.20ms: [I2C] Data=0x80 (temperature low)
0.25ms: [I2C] NACK, Stop
0.30ms: [SPI] CS↓, Mode 0
0.32ms: [SPI] TX=0x0B (read command)
0.34ms: [SPI] TX=0x00 (address high)
0.36ms: [SPI] TX=0x10 (address low)
0.38ms: [SPI] RX=0xAA (data)
0.40ms: [SPI] CS↑
0.50ms: [UART] TX='T' 'e' 'm' 'p' ':' '2' '5' 'C'

Export options: CSV, VCD, JSON

The Core Question You’re Answering

How do you build a universal protocol analysis tool that can capture, decode, and display multiple serial protocols simultaneously?

Questions to Guide Your Design

  • What sample rate covers all target protocols?
  • How do you decode protocols with different speeds?
  • What is the trigger architecture?

The Interview Questions They’ll Ask

  1. “How do you synchronize decoding across multiple protocols?”
  2. “What is the minimum sample rate needed for each protocol?”
  3. “How would you add a new protocol decoder?”
  4. “How do you handle protocol errors in the decoded output?”

Hints in Layers

Hint 1: Sample all channels at the highest rate needed (typically SPI clock * 4).

Hint 2: Run each protocol decoder as a separate state machine.

Hint 3: Use timestamps to correlate events across protocols.

Hint 4: Consider PIO on RP2040 for parallel capture.


Project 18: Protocol Bridge (I2C to SPI Translator)

What you will build: A bridge device that translates I2C transactions to SPI and vice versa, allowing devices on different buses to communicate.

Why it teaches the concept: Building a bridge requires deep understanding of both protocols, including timing constraints and addressing models.

Core Challenges

  1. Design the address/command mapping scheme
  2. Handle different data rates between buses
  3. Implement buffering for asynchronous operation
  4. Handle error conditions on both sides
  5. Minimize latency through the bridge

Key Concepts

Concept Book Reference
Protocol translation “Making Embedded Systems” Ch. 9
Buffering strategies “C Programming for Embedded Systems” Ch. 9
Embedded architecture “Making Embedded Systems” Ch. 3

Difficulty & Time Estimate

  • Difficulty: Expert
  • Time: 4-6 weeks
  • Prerequisites: Projects 7, 8, 12-15

Real-World Outcome

I2C to SPI Bridge
=================
I2C Slave Address: 0x50
SPI Master: Mode 0, 1 MHz

Protocol Mapping:
  I2C Write [addr][cmd][data] -> SPI [cmd][data]
  I2C Read [addr][cmd] -> SPI [cmd], return [data]

Example Transaction:
  Host sends I2C: Start, 0x50 Write, 0x03, 0xAA, 0xBB, Stop
  Bridge sends SPI: CS↓, 0x03, 0xAA, 0xBB, CS↑

  Host sends I2C: Start, 0x50 Write, 0x05, Repeated Start, 0x50 Read
  Bridge sends SPI: CS↓, 0x05, (reads 2 bytes), CS↑
  Bridge responds I2C: Data=0xCC, Data=0xDD, NACK, Stop

Statistics:
  Transactions bridged: 12,456
  Errors: 0
  Average latency: 45µs
  Maximum latency: 120µs

The Core Question You’re Answering

How do you translate between different serial protocols while preserving data integrity and minimizing latency?

Questions to Guide Your Design

  • How do you map I2C addresses to SPI chip selects?
  • What happens when SPI is faster than I2C?
  • How do you handle transaction boundaries?

The Interview Questions They’ll Ask

  1. “What is the maximum throughput of your bridge?”
  2. “How do you handle clock stretching on the I2C side?”
  3. “What happens if the SPI device is slow to respond?”
  4. “How would you add support for I2C register-style reads?”

Hints in Layers

Hint 1: Act as an I2C slave and SPI master simultaneously.

Hint 2: Buffer the entire I2C write before starting the SPI transaction.

Hint 3: Use clock stretching to pause the I2C master during SPI operations.

Hint 4: Define a clear command set that maps to SPI operations.


Project 19: Bit-Banging Protocol Implementations

What you will build: Software implementations of I2C and SPI using GPIO pins only, without hardware peripherals.

Why it teaches the concept: Bit-banging forces you to understand every signal transition. It also lets you use any pins and add debugging visibility.

Core Challenges

  1. Generate precise timing using delays or timers
  2. Implement I2C open-drain behavior in software
  3. Handle clock stretching detection
  4. Match hardware peripheral performance
  5. Make the code portable across platforms

Key Concepts

Concept Book Reference
Bit-banging “The Definitive Guide to ARM Cortex-M0” Ch. 12
Timing accuracy “Making Embedded Systems” Ch. 8
GPIO manipulation “Making Embedded Systems” Ch. 6

Difficulty & Time Estimate

  • Difficulty: Advanced
  • Time: 3-4 weeks
  • Prerequisites: All previous projects

Real-World Outcome

Bit-Banged Protocols
====================
Platform: Any GPIO (tested on AVR, ARM, ESP32)

I2C Implementation:
  Pins: Any GPIO (configured as open-drain)
  Speed: Up to 100 kHz
  Features: Start, Stop, Repeated Start, ACK/NACK
  Clock stretch: Supported

  Test: Scanned bus, found device at 0x48
  Test: Read 2 bytes from TMP102: OK

SPI Implementation:
  Pins: Any GPIO (MOSI, MISO, SCK, CS)
  Modes: All 4 modes supported
  Speed: Up to 500 kHz (software limited)
  Features: MSB/LSB first configurable

  Test: Read JEDEC ID from flash: 0xEF4016 (correct)

Comparison vs Hardware:
  I2C 100 kHz: Software 98 kHz actual
  SPI 1 MHz: Hardware 1 MHz, Software 480 kHz
  CPU usage: Software ~10x higher

Use case: Extra I2C buses on MCUs with limited hardware.

The Core Question You’re Answering

How do you implement serial protocols purely in software, and what are the trade-offs compared to hardware peripherals?

Questions to Guide Your Design

  • How do you achieve accurate timing without a timer interrupt?
  • How do you implement open-drain in software?
  • What is the maximum achievable speed?

The Interview Questions They’ll Ask

  1. “When would you choose bit-banging over hardware?”
  2. “How do you handle timing when interrupts can fire?”
  3. “What is the CPU cost of bit-banged I2C vs hardware?”
  4. “How would you make bit-banging work at higher speeds?”

Hints in Layers

Hint 1: Measure your delay loop or NOP cycles against an oscilloscope.

Hint 2: For I2C open-drain, set pin to input for high, output-low for low.

Hint 3: Disable interrupts during timing-critical sections.

Hint 4: Consider using inline assembly for the inner loops.


Project 20: Bus Topology Design and Level Shifting

What you will build: A multi-voltage embedded system with proper level shifting between 3.3V and 5V domains, demonstrating best practices for bus topology.

Why it teaches the concept: Real systems mix voltage domains. Understanding level shifting, signal integrity, and topology design is essential for production hardware.

Core Challenges

  1. Design level shifters for I2C (bidirectional)
  2. Design level shifters for SPI (unidirectional)
  3. Calculate and implement proper pull-ups
  4. Handle different ground references
  5. Verify signal integrity at the target speed

Key Concepts

Concept Book Reference
Level shifting “The Art of Electronics” Ch. 1.6
Signal integrity “High-Speed Digital Design” Ch. 2-3
Grounding “The Art of Electronics” Ch. 11

Difficulty & Time Estimate

  • Difficulty: Expert
  • Time: 4-6 weeks
  • Prerequisites: All previous projects

Real-World Outcome

Multi-Voltage Bus Design
========================

System Configuration:
  MCU: 3.3V (STM32)
  Sensors: 3.3V (BME280, MPU6050)
  Display: 3.3V (SSD1306 OLED)
  Legacy ADC: 5V (MCP3008)
  Legacy UART: RS-232 (PC serial port)

Level Shifters:
  I2C (3.3V MCU to 5V domain):
    Type: MOSFET bidirectional
    Parts: BSS138 x2 + pull-ups
    Pull-ups: 4.7K on each side
    Tested: 400 kHz OK, rise time 450ns

  SPI (3.3V MCU to 5V ADC):
    MOSI: Direct (5V tolerant input)
    MISO: Resistor divider (10K/20K)
    SCK: Direct
    CS: Direct
    Tested: 1 MHz OK

  UART (3.3V MCU to RS-232):
    Type: MAX3232 transceiver
    Tested: 115200 baud OK

Bus Topology:
  ┌─────────────────────────────────────────────┐
  │                                             │
  │  3.3V Domain         5V Domain             │
  │  ┌───────┐          ┌───────┐              │
  │  │  MCU  ├──LEVEL───┤Legacy │              │
  │  │ 3.3V  │  SHIFT   │ ADC   │              │
  │  └───┬───┘          └───────┘              │
  │      │                                      │
  │  ┌───┴───┐ ┌───────┐ ┌───────┐            │
  │  │BME280 │ │MPU6050│ │ OLED  │            │
  │  │ 3.3V  │ │ 3.3V  │ │ 3.3V  │            │
  │  └───────┘ └───────┘ └───────┘            │
  │                                             │
  └─────────────────────────────────────────────┘

Signal Integrity Report:
  All signals within spec at 400 kHz I2C, 1 MHz SPI
  Ground loops: None detected
  EMI: Minimal (short traces)

The Core Question You’re Answering

How do you design a robust embedded system that operates across multiple voltage domains while maintaining signal integrity and reliability?

Questions to Guide Your Design

  • What determines the choice of level shifter topology?
  • How do you handle bidirectional signals like I2C SDA?
  • What are the speed limitations of different level shifting methods?

The Interview Questions They’ll Ask

  1. “Explain how a MOSFET level shifter works for I2C.”
  2. “What are the pros and cons of resistor-divider level shifting?”
  3. “How do you handle I2C addresses when bridging voltage domains?”
  4. “What causes ground loops and how do you prevent them?”

Hints in Layers

Hint 1: For I2C, use the classic BSS138 MOSFET circuit.

Hint 2: Check if your 3.3V device has 5V-tolerant inputs before adding level shifters.

Hint 3: Keep level shifters close to the voltage domain boundary.

Hint 4: Use an oscilloscope to verify rise times after adding level shifters.


Project Comparison Table

# Project Protocol Difficulty Time Depth Key Skill
1 UART Loopback UART Beginner 4h Fundamentals Timing
2 Command Terminal UART Beg-Int 8h Protocol Parsing
3 Error Detection UART Intermediate 8h Debugging Diagnostics
4 Logic Analyzer UART Int-Adv 2w Deep Capture
5 Level Converter UART Intermediate 8h Hardware Electronics
6 I2C Scanner I2C Beg-Int 6h Fundamentals Addressing
7 Sensor Network I2C Intermediate 1w Protocol Datasheets
8 EEPROM Logger I2C Intermediate 1w Storage Persistence
9 OLED Driver I2C Int-Adv 2w Graphics Displays
10 Multi-Master I2C Advanced 2w Advanced Arbitration
11 Bus Debugging I2C Advanced 2w Debugging Recovery
12 SPI Modes SPI Intermediate 1w Fundamentals Timing
13 TFT Display SPI Int-Adv 2w Graphics Performance
14 SD Card SPI Advanced 3w Storage Filesystem
15 Flash Memory SPI Int-Adv 2w Storage Flash
16 High-Speed DAQ SPI Advanced 3w Performance DMA
17 Multi-Protocol All Expert 4w Mastery Integration
18 Protocol Bridge I2C/SPI Expert 4w Translation Architecture
19 Bit-Banging All Advanced 3w Deep Software
20 Bus Topology All Expert 4w Hardware Design

Summary

Project Path Recommendation Expected Outcome
1 Start here UART basics verified
2 After 1 Interactive command interface
3 After 2 Error detection and handling
4 After 3 Build your own debug tool
5 After 1-3 Voltage domain understanding
6 After UART section I2C fundamentals
7 After 6 Multi-device I2C
8 After 7 Persistent storage
9 After 7 Display interfaces
10 After 6-9 Advanced I2C
11 After 10 Bus troubleshooting
12 After I2C section SPI fundamentals
13 After 12 High-speed displays
14 After 12-13 Filesystem access
15 After 12 Flash storage
16 After 14-15 Maximum throughput
17 After all protocol sections Universal debug tool
18 After 17 Protocol translation
19 After 17 Deep understanding
20 Capstone Production-ready design

References

Primary Books

Book Author Relevant Chapters
“The Book of I2C” Randall Hyde Ch. 1-6 (all I2C projects)
“Making Embedded Systems” (2nd Ed) Elecia White Ch. 5-9 (architecture, protocols)
“The Art of Electronics” (3rd Ed) Horowitz & Hill Ch. 1, 9, 11, 12, 13
“High-Speed Digital Design” Johnson & Graham Ch. 1-3 (signal integrity)
“Exploring BeagleBone” Derek Molloy Ch. 8 (SPI examples)
“The Definitive Guide to ARM Cortex-M0” Joseph Yiu Ch. 12 (bit-banging)

Datasheets (Essential Reading)

  • UART: 16550 UART datasheet, FT232 datasheet
  • I2C: I2C-bus specification v6 (NXP), TMP102/BME280/MPU6050/SSD1306 datasheets
  • SPI: SPI Block Guide (Motorola), W25Q32/ILI9341/SD card specifications

Online Resources

  • I2C specification: https://www.nxp.com/docs/en/user-guide/UM10204.pdf
  • SD card specification: https://www.sdcard.org/downloads/pls/
  • Arduino Wire/SPI library source code
  • Raspberry Pi I2C/SPI documentation

Final Words

Serial protocols are the common language of embedded systems. Every sensor, every display, every memory chip speaks one of these three protocols. By completing these projects, you will not just know how to use libraries - you will understand what the libraries do, why they do it, and how to debug when things go wrong.

The progression from UART to I2C to SPI mirrors the evolution of embedded communication: from simple point-to-point to addressed buses to high-speed parallel transfers. Each protocol teaches lessons that apply to all digital communication.

When you finish, you will be able to:

  • Read an oscilloscope trace and understand exactly what is happening
  • Debug protocol problems that stump other engineers
  • Design mixed-protocol systems with proper level shifting
  • Implement protocols from scratch when needed
  • Make informed architectural decisions about communication

The skills you build here transfer to every embedded project you will ever work on. Start with Project 1, build systematically, and soon these protocols will feel like old friends.


Last updated: 2025-01 Total projects: 20 | Estimated completion: 4-6 months | Protocols covered: UART, I2C, SPI