A logic analyzer is the single most valuable tool for embedded debugging after a multimeter. This guide covers how to use Saleae Logic 2 productively — not just how to click buttons, but how to build discipline so you capture the right data the first time.
This guide is referenced throughout sessions 01–11 wherever physical bus verification is needed.
The most expensive mistake in embedded development: spending two days debugging firmware before discovering the hardware was never working.
Before writing a single line of peripheral driver code:
| Bus | Verification command | Expected output |
|---|---|---|
| CAN | sudo ip link set can0 up type can bitrate 500000 && candump can0 |
Frames visible from other nodes |
| UART/GPS | minicom -D /dev/ttyUSB0 -b 9600 |
Raw NMEA sentences scrolling |
| I2C | i2cdetect -y 1 |
Device address visible in grid |
| SPI | Connect loopback (MOSI→MISO), run spidev_test |
Echo bytes match |
If the bus shows nothing at this stage, the problem is hardware — wiring, power, termination, or chip selection. No firmware fix will help. Resolve it at this layer first.
This is not optional. It is Step 0 for every session in this curriculum.
Use a consistent channel numbering scheme across all projects. Consistency means your saved
.logicdata files are reusable and your mental model is stable.
CH0 — SCLK (SPI clock / I2C SCL)
CH1 — CS (SPI chip select, active low)
CH2 — MOSI (SPI master-out / I2C SDA)
CH3 — MISO (SPI master-in)
CH4 — TX (Target TX → Saleae: what the TARGET SENDS, what the host receives)
CH5 — RX (Target RX → Saleae: what the HOST SENDS, what the target receives)
CH6 — CAN_TX (before transceiver)
CH7 — trigger / user-defined
⚠️ TX/RX direction reminder: Names are from the target’s perspective. - Physically wire:
target TX pin→Saleae CH4(you see what the target transmits) - Physically wire:target RX pin→Saleae CH5(you see what the host transmits to the target) The most common first-time mistake is swapping these. If the UART decoder shows no data or garbage, flip CH4/CH5.
GND wire placement: Run a ground wire from the Saleae GND pin to the target board GND at the point closest to your signal source. A long GND loop introduces inductive coupling that appears as glitches on the logic analyzer.
Each Saleae probe tip presents ~10pF and a 1MΩ input impedance. On fast SPI signals (≥10MHz), this can round edges enough to cause false captures. Use the Saleae hook tips and keep leads short (<15cm).
For 20MHz+ SPI: solder a 220Ω series resistor at the target pad, probe after the resistor.
| Signal speed | Recommended sample rate | Why |
|---|---|---|
| UART 9600 baud | 1 MSPS | 100× oversampling |
| UART 115200 baud | 8–12 MSPS | Detect framing errors |
| I2C 100kHz | 8 MSPS | Capture ACK bits cleanly |
| I2C 400kHz fast-mode | 8–16 MSPS | |
| SPI 1MHz | 8 MSPS | |
| SPI 10MHz | 100 MSPS | Nyquist + margin |
| SPI 20MHz | 200 MSPS (Logic Pro 8 required) | |
| CAN 500kbps | 8 MSPS | |
| CAN 1Mbps | 16 MSPS |
Rule: Capture at least 8× the bit rate. Lower than 4× causes frequent decode errors.
Always trigger on a CS falling edge for SPI. Never trigger on SCLK — SCLK toggles during every transfer and gives you random frame alignment.
For I2C: trigger on SDA falling edge while SCL is high (START condition).
For UART: trigger on the falling edge of the start bit (TX or RX channel falling edge,
since idle state is HIGH).
For CAN: trigger on a long dominant bit sequence (SOF + arbitration field).
Set a pre-trigger buffer of ~10ms when debugging intermittent faults. This lets you see what was happening before the trigger condition — crucial for: - CAN bus-off events (you want the frames that caused it, not the aftermath) - SPI CS glitches (you want the clean transfers before the fault) - ESD-induced resets
In Logic 2: Capture Settings → “Pre-trigger buffer” → 10–50ms.
After adding a SPI decoder, you can trigger on a specific byte value:
0xDEThis is invaluable for finding a specific command byte in a high-frequency stream.
| Setting | Common mistake | Correct approach |
|---|---|---|
| CPOL | Default 0 | Read your MCU/sensor datasheet — CPOL=1 means idle-high clock |
| CPHA | Default 0 | CPHA=0 = sample on first edge, CPHA=1 = sample on second edge |
| CS polarity | Active-low assumed | Some SPI sensors use active-high CS |
| Bit order | MSB first assumed | Some sensors send LSB first |
| Enable line | Channel 1 (CS) | Must match your wiring |
Verification: After setting up the SPI decoder, look at the decoded bytes in the “Data” column. If you see random-looking bytes but the waveform shows clean edges, you have CPOL/CPHA wrong. Try all 4 mode combinations (Mode 0–3) if unsure.
Baud rate: must match exactly (9600, 115200, etc.)
Data bits: 8 (almost always)
Stop bits: 1
Parity: None (unless your target uses it)
Bit order: LSB first (UART standard)
Inverted UART: RS-232 signals are inverted (1 = negative voltage). If you probe an RS-232 line with a 3.3V logic analyzer probe, enable “Inverted” in the decoder settings. If your decoded bytes look like garbage but the waveform is clean, the idle state (HIGH vs LOW) is wrong — try toggling inversion.
I2C only needs two channels: SCL and SDA. The decoder automatically identifies: - START/STOP conditions - Address byte (7-bit + R/W bit) - ACK/NACK bits - Data bytes
ACK vs NACK: A NACK (logic 1 during ACK slot) appears as [NAK] in the decoded
stream. This is the primary signal for:
- Wrong I2C address
- Device not ready
- Peripheral power not applied
Bit rate: 500000 (500kbps typical for AMR) or 1000000
Sample point: 75% (default works for most cases)
CAN uses differential signaling; most logic analyzers probe CAN_H relative to ground. The CAN transceiver converts differential to single-ended — probe after the transceiver at CANH or at the RX pin of your MCU.
Tip: If you see “bit error” in the decoded stream, your sample point or bit rate is slightly off. Adjust the sample point by ±5% and re-decode.
This is the workflow for session 02 and beyond.
Step 1: Verify idle state Before any data flows, with CS high: - SCLK should be low (SPI mode 0) or high (SPI mode 3) - MOSI/MISO can be any level - If CS is floating (not high), your pull-up resistor is missing
Step 2: Capture a single transaction - Trigger: CH1 (CS) falling edge - Duration: 50ms - Examine: count the SCLK pulses. For an 8-bit byte: 8 pulses. For 64 bytes: 512 pulses.
Step 3: Verify byte count In Logic 2, after decoding: select all SPI annotations. The annotation count ÷ 2 = number of bytes transferred (each byte generates one MOSI + one MISO annotation).
Step 4: Verify CS timing Measure the time from CS falling edge to first SCLK edge. On STM32 SPI, this “CS setup time” should be < 1µs. If it’s > 5µs, your master has a slow GPIO initialization path.
Step 5: Look for CS glitches Zoom in on the CS line between transactions. Any high-to-low-to-high pulse shorter than one byte period is a glitch. These cause phantom transactions in your slave handler.
Use Logic 2 timing markers:
1. Press A key to drop Marker A at cursor position
2. Navigate to end of transaction
3. Press B key to drop Marker B
4. Logic 2 shows Δt between A and B in the toolbar
For a 64-byte SPI transfer at 10MHz: - Expected: 64 × 8 bits / 10,000,000 Hz = 51.2µs - Measure your actual value and compare
CS deassertion to next CS assertion = inter-frame gap. For a 100Hz loop with 51.2µs transfer, the gap should be 10ms - 51.2µs ≈ 9948µs. If the gap is 0 (back-to-back transfers), your master doesn’t respect the minimum CS deassert time.
To measure end-to-end latency:
1. Add a GPIO “timestamp” output on the STM32: toggle a pin at the start of DMA TX
2. Probe that pin on CH7 of the logic analyzer
3. Measure CH7 pulse → CS assertion on CH1 = STM32-side processing latency
4. The ROS2 end can’t be measured on a logic analyzer — use ros2 topic echo --once
with header.stamp comparison
For session 08 (UART GPS NMEA):
Step 1: Verify baud rate Before decoding, measure the width of the narrowest pulse in the UART data stream. For 9600 baud: 1 bit = 1/9600 = 104.2µs. If your measurement shows 104µs, the baud rate is correct.
Step 2: Find NMEA sentence boundaries
In the decoded stream, look for the $ character (0x24) — it marks the start of every
NMEA sentence. The CRLF (0x0D, 0x0A) marks the end.
Step 3: Measure sentence duration
Trigger on the $ start. Measure to the final \n. For $GNGGA at 9600 baud:
- Sentence is ~70 characters × (1 start + 8 data + 1 stop) bits = 700 bits
- At 9600 baud: 700 / 9600 ≈ 72.9ms
This tells you that at 9600 baud, the GPS sentence takes 73ms to arrive — relevant for understanding why ring-buffer accumulation is necessary.
For session 07 (CAN bus encoder):
Step 1: Verify bus state Idle CAN bus: CANH ≈ 2.5V, CANL ≈ 2.5V (recessive). Probe CAN_H relative to GND. At logic level: recessive = 1, dominant = 0.
Step 2: Capture CAN frames Trigger: falling edge on CH6 (CAN_TX from MCU, before transceiver). Look for the Start Of Frame bit (always dominant = 0) followed by 11-bit arbitration ID.
Step 3: Verify frame contents After enabling CAN decoder: the annotation should show:
ID: 0x201 DLC: 8 Data: [FF D8 00 C8 AB CD EF 01] ACK
If you see Error: stuff bit annotations, your bitrate setting in the decoder is wrong
or there’s a physical problem (termination).
Step 4: Look for error frames
CAN error frames appear as a 6-bit dominant sequence after a bit error. Logic 2 marks
these as [Error frame]. If you see more than 1 error per 100 frames, investigate the
physical layer (termination, cabling, ground loops).
Save every non-trivial capture as .logicdata:
- debug_<date>_<project>_<what>.logicdata
- Example: debug_20260412_spi-slave_cs-glitch.logicdata
Logic 2 files include all channel data, decoders, and timing markers.
To embed a capture in a bug report: 1. Logic 2 → File → Export Data → CSV (for analysis in Python/Excel) 2. Logic 2 → Screenshot → Copy to clipboard (for quick paste into Slack/ADO)
For session documentation: export a CSV of decoded SPI bytes and include the hex dump in your notes to prove the framing is correct.
| Symptom | Likely cause | Check |
|---|---|---|
All decoded bytes are 0xFF |
MISO/MOSI swapped | Swap CH2 and CH3 leads |
Decoder shows Error: Frame error |
Wrong CPOL/CPHA | Try SPI Mode 1, 2, 3 |
| UART shows garbage but waveform clean | Wrong baud rate | Measure bit width manually |
| CAN shows no frames, candump silent | Missing termination OR wrong bitrate | Measure CANH-CANL with multimeter |
| Logic shows CS glitch < 1µs | PCB ground bounce | Add 100nF cap near CS driver pin |
| SPI bytes look right but STM slave misses first 1-2 bytes | DMA not armed before CS | See session 02 arming sequence |
| I2C shows [NAK] on every address | Device not powered or wrong address | Check VCC on target, read datasheet for address pins |
| Trigger fires but capture is empty | Pre-trigger buffer too small | Increase to 50ms and retrigger |