Week 1: Electronics Essentials ──→ You understand the PHYSICAL layer
Week 2: SPI-Focused Protocol Deep Dive ──→ You understand the WIRE layer
Week 3: Zephyr Foundations ──→ You can BUILD firmware
Week 4: Sensors + ZBus + nanopb ──→ You can READ sensors + ENCODE data
Week 5–6: SPI Slave DMA ──→ You can DMA at 100Hz (hardest part)
Week 7: Jetson spidev + ROS2 Node ──→ You can RECEIVE on the Jetson
Week 8: EKF + Full Pipeline ──→ You have a WORKING system
Rule: Finish each week’s checkpoint before moving on. If you can’t answer a checkpoint question from memory, re-read that section.
Study approach: Read the study notes → do the exercises → build on hardware (Week 3+). Each “day” assumes 3–4 hours of focused study. Adjust to your schedule.
zephyr/00-mastery-plan.md Section 3learn/
├── STUDY-PLAN.md ← YOU ARE HERE
├── electronics/ ← Week 1 + 2
│ ├── 00-learning-plan.md Topic dependency graph
│ ├── 01-passive-components.md R, C, L, RLC (870 lines)
│ ├── 02-semiconductors.md Diodes, BJT, MOSFET (591 lines)
│ ├── 03-opamps-adc-sampling.md Op-amps, ADC, Nyquist, MUX, ICs (856 lines)
│ ├── 04-uart-serial-deep-dive.md Bit-level UART (564 lines)
│ ├── 05-spi-deep-dive.md Shift registers → frames (514 lines)
│ ├── 06-i2c-deep-dive.md Open-drain → transactions (571 lines)
│ ├── 07-can-deep-dive.md Differential → arbitration (724 lines)
│ └── exercises/
│ ├── 01-passive-components.md R, C, L, RLC exercises
│ ├── 02-semiconductors.md Diode, BJT, MOSFET exercises
│ ├── 03-opamps-adc-sampling.md Op-amps, ADC, sampling exercises
│ ├── 04-uart-serial.md UART protocol exercises
│ ├── 05-spi.md SPI protocol exercises
│ ├── 06-i2c.md I2C protocol exercises
│ └── 07-can-bus.md CAN bus exercises
│
└── zephyr/ ← Week 3–8
├── 00-mastery-plan.md 11-project hardware plan
├── 01-17 source docs Reference material
├── study-notes/
│ ├── 01-foundations.md Zephyr basics (915 lines)
│ ├── 02-sensors.md I2C/CAN/UART Zephyr APIs (866 lines)
│ ├── 03-zbus-nanopb.md Pub/sub + protobuf (1289 lines)
│ ├── 04-spi-slave-dma.md DMA double-buffer (1231 lines)
│ └── 05-jetson-ros2.md spidev + ROS2 + EKF (1524 lines)
└── exercises/
├── 01-foundations.md (1152 lines)
├── 02-sensors.md (1119 lines)
├── 03-zbus-nanopb.md (910 lines)
├── 04-spi-slave-dma.md (633 lines)
└── 05-jetson-ros2.md (649 lines)
Total content: ~19,000+ lines across 27 files.
Legend: 🎯 = essential for the SPI bridge goal | 📖 = skim-read OK | ⏭️ = skip if behind schedule
No hardware needed. Paper, pen, and a calculator.
Read: electronics/01-passive-components.md — Resistors + Capacitors + RLC sections
Focus on:
- V = IR — compute LED current-limiting resistors (you’ll need this in Week 3)
- Voltage dividers — compute output voltage with and without a load
- Pull-up / pull-down — what “floating” means for CMOS inputs
- τ = RC time constant — the 63% rule and 5τ for “fully charged”
- Why 100nF decoupling caps go next to EVERY IC
- Low-pass RC filter: f₋₃dB = 1/(2πRC)
Checkpoint — answer without looking: - [ ] 3.3V GPIO, LED Vf=2.0V, target 10mA. What resistor? What power does it dissipate? - [ ] 10kΩ + 100nF filter. What’s the cutoff frequency? (159 Hz) - [ ] Why does a 100nF cap next to VCC help during a 10ns switching event but a 10µF doesn’t?
Read: electronics/01-passive-components.md — Inductors section
Read: electronics/02-semiconductors.md (full)
Focus on:
- Back-EMF when switching off inductive loads → flyback diode
- MOSFET as voltage-controlled switch — “logic-level” Vgs(th) < 3.3V
- Rds(on) determines heat. BSS138 level-shifting for 3.3V ↔ 5V I2C
- BJT: base current × β. MOSFET: Vgs > threshold. Know the difference.
Checkpoint: - [ ] You switch off a relay coil. Why does a voltage spike appear? - [ ] Why can’t you use IRLZ44N (Vgs(th)=2-4V) reliably from 3.3V? (Threshold ≠ full enhancement) - [ ] Draw the BSS138 level shifter from memory.
Read: electronics/03-opamps-adc-sampling.md (full)
Focus on:
- Op-amp golden rules. Non-inverting gain = 1 + Rf/Rin. Voltage follower = buffer.
- 12-bit ADC: LSB = Vref/4096. SAR = binary search.
- Nyquist: sample ≥ 2× max frequency. Anti-alias filter BEFORE the ADC.
- Shift registers: the physical core of SPI TX/RX.
Checkpoint: - [ ] Piezo 0–5V → your 0–3.3V ADC. Design the signal chain. - [ ] 150Hz signal sampled at 200Hz. What do you see? (50Hz alias) - [ ] What physically happens inside SPI TX on every clock edge? (Shift register shifts)
Do: electronics/exercises/01-passive-components.md — Sections A, B, C
Do: electronics/exercises/02-semiconductors.md — Sections A, B, C
Do: electronics/exercises/03-opamps-adc-sampling.md — Sections A, B, C
- Re-read any section where you struggled
- Draw from memory: voltage divider, RC filter, BJT switch, MOSFET switch, non-inverting amp
Go through ALL Day 1–3 checkpoints from memory. Mark misses → re-read.
Draw from memory (paper, no peeking): - [ ] RC low-pass filter with cutoff formula - [ ] MOSFET low-side switch with gate resistor - [ ] Non-inverting op-amp with gain formula - [ ] Voltage divider → buffer → ADC chain
You’re ready for Week 2 when: You can answer every checkpoint AND draw all 4 circuits.
Sections D & E of the exercises are deeper-thinking problems. They’re valuable but not blocking. ⏭️ Save them for evening/weekend review if time is tight.
Theory + connect a logic analyzer if you have one.
Read: electronics/04-uart-serial-deep-dive.md — skim (you use UART for Zephyr shell, but it’s not the SPI bridge)
Focus on the 3 things you’ll actually need:
- Bit-level frame: IDLE → START → 8 data → STOP. LSB first.
- Baud rate must match ±2%. 115200 is your debug default.
- DMA + idle-line interrupt for high-throughput (GPS in Week 4)
Checkpoint: - [ ] Draw the waveform for ‘A’ (0x41) at 115200-8N1. - [ ] Why is TX/RX always crossed?
Read: electronics/05-spi-deep-dive.md (full — read every word, this is your target protocol)
Focus on:
- Core insight: SPI = two shift registers in a ring. Always full-duplex.
- The 4 modes (CPOL × CPHA) — draw all 4 timing diagrams
- CS: active-low, pull-up during boot, one per slave
- Master vs slave timing asymmetry — why slave is hard
- Signal integrity: 40MHz SPI on 30cm wires fails (ringing, reflections)
- Protocol framing: magic byte + length + payload + CRC
- The “first byte garbage” problem in slave mode
Checkpoint: - [ ] Master sends 0xA5 in Mode 0. Draw CLK, MOSI with data on each edge. - [ ] Slave reads 0x52 when master sent 0xA5. What’s wrong? (Wrong CPOL/CPHA) - [ ] Why is SPI always full-duplex? (Shift register ring) - [ ] Why does SPI need a framing protocol? (No message boundaries)
Do: electronics/exercises/05-spi.md — all sections (while Day 7 is fresh)
Read: zephyr/study-notes/04-spi-slave-dma.md — PART 1 only (ELI15 concepts)
Focus on:
- Why DMA is necessary (CPU can’t service SPI at 100Hz)
- Double buffering: Buffer A transmitting while Buffer B filled
- The pre-arming race: DMA must be ready BEFORE CS asserts
- D-cache coherency: TX = Clean before DMA (write-back dirty lines), RX = Invalidate after DMA (discard stale cache). Don’t conflate them.
- SCB_CleanDCache_by_Addr() for TX, SCB_InvalidateDCache_by_Addr() for RX
Checkpoint:
- [ ] Why can’t you arm DMA inside the CS ISR? (ISR latency > CS-to-clock gap)
- [ ] GDB shows correct TX buffer but Jetson reads garbage. What’s wrong? (D-cache not cleaned before DMA TX)
- [ ] What’s __aligned(32) for? (Cache line alignment — partial invalidate corrupts adjacent data)
Read: electronics/06-i2c-deep-dive.md (full — you need I2C for the IMU in Week 4)
Read: electronics/07-can-deep-dive.md — skim only (CAN = 1 project in Week 4, not your main protocol)
Focus on I2C:
- Open-drain + pull-up. Wired-AND sharing.
- Complete transaction: START → addr+W → reg → RSTART → addr+R → data → NACK → STOP
- Bus stuck recovery: 9-clock bit-bang
Skim CAN for:
- Differential pair, dominant/recessive, 120Ω termination, frame format
Checkpoint: - [ ] Why does I2C need pull-ups but SPI doesn’t? - [ ] Walk through reading register 0x75 from device 0x68: every byte on the wire - [ ] CAN: what does a multimeter across CANH-CANL read with power off? (60Ω)
Do: electronics/exercises/04-uart-serial.md — Sections A, B only ⏭️ (skip D, E)
Do: electronics/exercises/06-i2c.md — Sections A, B, C
Do: electronics/exercises/07-can-bus.md — Sections A, B only ⏭️ (skip D, E)
Fill in this table from memory, then check:
| Feature | UART | SPI | I2C | CAN |
|---|---|---|---|---|
| Wires | 2 (TX/RX) | 4+ (CLK/MOSI/MISO/CS) | 2 (SDA/SCL) | 2 (CANH/CANL) |
| Clock | Async | Master provides | Master provides | Embedded in data |
| Max speed | ~3 Mbaud | ~50 MHz | 3.4 MHz (HS) | 1 Mbps |
| Duplex | Full | Full | Half | Half |
| Best for | Debug/GPS | High-speed chip-to-chip | Config/sensors | Automotive |
You’re ready for Week 3 when: You can draw the SPI timing diagram for all 4 modes AND the I2C read transaction.
Without opening any file, answer these from Weeks 1–2: - [ ] Calculate: 3.3V, LED Vf=2.0V, 10mA. Resistor = ? Power = ? - [ ] Draw: MOSFET low-side switch - [ ] Draw: SPI waveform for one byte in Mode 0 (CPOL=0, CPHA=0) — show SCLK, MOSI, CS
If you miss ≥2: spend 30 min reviewing the relevant sections before starting Week 3.
Hardware required: STM32 Nucleo + logic analyzer
Read: zephyr/study-notes/01-foundations.md (full, 915 lines)
Do: zephyr/exercises/01-foundations.md — Sections A through C
Build: Projects 1, 2, 3 from zephyr/00-mastery-plan.md
Verify: LED blinks, shell responds, dt_ms=10 logged at 100Hz
Checkpoint:
- [ ] Write a minimal CMakeLists.txt + prj.conf + main.c for Zephyr blinky from memory
- [ ] k_msleep(10) vs k_timer for 100Hz — which drifts and why?
- [ ] Default stack is 1KB. Thread uses LOG_INF. What happens? (Stack overflow, silent death)
Do: zephyr/exercises/01-foundations.md — Sections D + E
Focus on:
- Thread stack sizing (use CONFIG_THREAD_ANALYZER)
- Watchdog timer setup (IWDG — add CONFIG_WDT=y, feed in main loop, system resets on hang)
- Zephyr logging levels and CONFIG_LOG_MODE_DEFERRED for real-time threads
Checkpoint: - [ ] How do you detect stack overflow in Zephyr? (CONFIG_THREAD_ANALYZER or CONFIG_MPU_STACK_GUARD) - [ ] What happens if your 100Hz thread takes 12ms? (Misses deadline, timer fires again immediately)
Projects 4–7 in one week — tight but doable because theory is done
Read: zephyr/study-notes/02-sensors.md — I2C + CAN sections
Do: zephyr/exercises/02-sensors.md — Sections A, B, C
Build: Project 4 (ICM-42688 over I2C at 100Hz)
Build: Project 5 (CAN receive from second node)
Verify: acc_z ≈ 9.81 flat. Logic analyzer shows clean I2C. candump shows CAN frames.
Read: zephyr/study-notes/02-sensors.md — UART section only (skim I2C/CAN parts)
Build: Project 6 (NMEA parser with ring buffer)
Verify: Parses $GNGGA correctly. Handles “no fix” without crashing.
Read: zephyr/study-notes/03-zbus-nanopb.md (full, 1289 lines)
Do: zephyr/exercises/03-zbus-nanopb.md — Sections A, B, C
Build: Project 7 (ZBus publish all sensors → nanopb encode → round-trip verify)
Verify: encoded 47 bytes at 100Hz, size always identical, round-trip decode matches
Checkpoint:
- [ ] proto3 all-zero message — what happens to the size? (Fewer bytes, fields omitted including payload_length if zero)
- [ ] ZBus subscriber vs listener — when to use each?
- [ ] What protects ZBus channel data from races? (Mutex)
Without opening any file, answer from Weeks 1–2: - [ ] Draw SPI Mode 0 timing for 0xA5 (CLK, MOSI, CS) - [ ] What is D-cache clean vs invalidate? Which for TX? Which for RX? - [ ] I2C: read register 0x75 from device 0x68 — all bytes on the wire
Miss ≥2: review before the hardest part (Week 5).
This is the hardest project. Budget 2 full weeks.
Read: zephyr/study-notes/04-spi-slave-dma.md (full, 1231 lines — all of it)
Do: zephyr/exercises/04-spi-slave-dma.md — Sections A through C
Build: Project 8 (SPI slave DMA at 100Hz with double-buffering)
Debug checklist — check these IN ORDER when things break:
1. Logic analyzer: is CS asserting at 100Hz? Is CLK present during CS?
2. Is DMA armed BEFORE CS? (Check pre-arm timing with GPIO toggle)
3. D-cache: TX cleaned? RX invalidated? Buffer __aligned(32) and in D2 SRAM?
4. Is the slot / double-buffer swap atomic? (ISR context vs thread context)
5. nanopb: does decoded output match? (Round-trip test)
Verify with logic analyzer: - [ ] 100 CS assertions/sec, evenly spaced (±0.5ms jitter max) - [ ] No garbage first-bytes (pre-arming race solved) - [ ] nanopb decoded correctly by Python spidev script on second board - [ ] Latency from CS assert to first clock edge < 5µs
Do: zephyr/exercises/04-spi-slave-dma.md — Sections D (lab) + E (timing math)
Checkpoint: - [ ] Why does arming DMA in the CS ISR fail? (ISR latency > CS-to-clock gap) - [ ] GDB shows correct buffer, Jetson reads garbage. Diagnosis? (D-cache not cleaned for TX) - [ ] DMA buffer in DTCM — works? (No. STM32H7 DMA can’t access DTCM. Must be D2 SRAM.) - [ ] What happens if master polls faster than firmware can swap buffers? (Stale data — same frame twice)
Hardware: Jetson Orin + STM32 connected via SPI
Read: zephyr/study-notes/05-jetson-ros2.md — PART 1 sections 1–6
Build: Project 9 (Python spidev on Jetson reads + decodes at 100Hz)
Verify: sudo spidev_test works first, then Python shows values matching STM32
Read: zephyr/study-notes/05-jetson-ros2.md — PART 1 sections 7–15
Do: zephyr/exercises/05-jetson-ros2.md — Sections A, B, C
Build: Project 10 (rclpy node publishing Imu, Odometry, NavSatFix at 100Hz)
Verify: ros2 topic hz /imu → ~100Hz. rviz2 shows rotating IMU axes.
Checkpoint:
- [ ] xfer2() in timer callback → 87Hz. Why? (Blocking SPI call + timer jitter)
- [ ] QoS for sensor topics? (BEST_EFFORT — RELIABLE backpressure kills 100Hz)
- [ ] Timestamps: use ROS clock (self.get_clock().now()), NOT CLOCK_MONOTONIC_RAW
The finish line: end-to-end 100Hz sensor fusion
Read: zephyr/study-notes/05-jetson-ros2.md — PART 2 (code) + PART 3 (gotchas)
Do: zephyr/exercises/05-jetson-ros2.md — Sections D (lab) + E (system design)
Build: Project 11 (robot_localization EKF, TF2 frame tree, rviz2 visualization)
Verify the complete pipeline — all of these must pass:
- [ ] ros2 topic hz /odometry/filtered → 100Hz
- [ ] ros2 run tf2_tools view_frames → complete map→odom→base_link→imu_frame tree
- [ ] Push board sideways → EKF estimates velocity correctly
- [ ] Logic analyzer: SPI CS at 100Hz ±0.5ms, clean clocking, no first-byte garbage
- [ ] Run for 10 minutes continuously — no drift, no crashes, no dropped frames
Answer all of these without any files open: - [ ] Draw the full SPI slave DMA double-buffer data flow - [ ] What’s the D-cache rule? (TX: clean before. RX: invalidate after.) - [ ] Why D2 SRAM, not DTCM? - [ ] I2C read transaction: all bytes for reading reg 0x75 from 0x68 - [ ] Voltage divider 10k+10k on 5V with 1kΩ load: output = ? - [ ] EKF diverges. First thing to check? (Timestamps)
| After | You Can… |
|---|---|
| Week 1–2 | Read a schematic, calculate R/C values, draw SPI/I2C timing, understand D-cache coherency |
| Week 3–4 | Build Zephyr firmware reading real sensors at 100Hz, encode with nanopb |
| Week 5–6 | Run SPI slave DMA at 100Hz with double-buffering — the hardest embedded skill |
| Week 7–8 | Full pipeline: STM32 → SPI → Jetson → ROS2 → EKF at 100Hz |
| Trimmed | Why | Where to find it |
|---|---|---|
| Electronics Sections D, E exercises | Deeper thinking — valuable but not blocking | electronics/exercises/*.md Sections D, E |
| UART deep exercises | You already know serial from ROS2/Python | electronics/exercises/04-uart-serial.md D, E |
| CAN deep exercises | CAN = 1 project, not your main protocol | electronics/exercises/07-can-bus.md D, E |
| Sensor exercises D, E | Deeper sensor analysis — not blocking | zephyr/exercises/02-sensors.md Sections D, E |
| ZBus + nanopb exercises D, E | Advanced encoding patterns — not blocking | zephyr/exercises/03-zbus-nanopb.md Sections D, E |
| C++ Advanced track | Separate 8-week curriculum, run in parallel or after | cpp-advanced/STUDY-PLAN.md |
| When you need… | Go to… |
|---|---|
| Resistor/cap/inductor formulas | electronics/01-passive-components.md → Quick Reference Card |
| MOSFET switch circuit | electronics/02-semiconductors.md → MOSFET section |
| Op-amp gain formulas | electronics/03-opamps-adc-sampling.md → Quick Reference Card |
| UART waveform format | electronics/04-uart-serial-deep-dive.md → Bit-level protocol |
| SPI mode timing diagrams | electronics/05-spi-deep-dive.md → 4 SPI modes |
| I2C pull-up calculation | electronics/06-i2c-deep-dive.md → Pull-up value section |
| CAN arbitration rules | electronics/07-can-deep-dive.md → Arbitration section |
| Zephyr gotcha tables | zephyr/study-notes/0X-*.md → PART 3 in each file |
| Hardware wiring safety | zephyr/00-mastery-plan.md → Section 2 |
| Day-waster failure modes | zephyr/00-mastery-plan.md → Section 3 |