Logic Probe 16 Demo: Multi-Protocol FPGA Test with TinyFPGA BX
16-Channel Multi-Protocol Demo
with TinyFPGA BX
SPI, UART, I2C, and a parallel bus — all running simultaneously on 16 channels. One FPGA, one probe, one scope screenshot that proves it all works.
Proving 16 channels of real-world capability
A logic probe that only shows square waves proves nothing. This demo generates real, decodable protocols that your oscilloscope can identify and display as data.
Real Protocols, Not Test Patterns
The FPGA generates standard SPI, UART, and I2C frames with correct timing and framing. Your scope's protocol decoder will actually read "SAM" from the UART and decode address 0x50 from the I2C bus.
All 16 Channels Simultaneously
Every channel is active at the same time. This is the real test — proving there's no crosstalk between adjacent LVDS pairs and that all 16 signal paths work under load.
Exactly What the Probe Is For
Embedded systems run SPI, I2C, UART, and GPIO simultaneously. This demo mirrors a real debugging scenario — multiple buses captured and decoded in a single acquisition.
What each channel generates
16 channels split across 4 protocol groups, running simultaneously from a single TinyFPGA BX.
| Channel | Protocol | Signal | Details |
|---|---|---|---|
| D0 | SPI | SCK (Clock) | ~500 kHz, Mode 0 (CPOL=0, CPHA=0) |
| D1 | SPI | MOSI (Data Out) | Sends 0xA5, 0x3C, 0x55, 0xF0, 0x0F, 0x69, 0x96, 0xCC |
| D2 | SPI | MISO (Data In) | Echoes MOSI inverted (simulated slave) |
| D3 | SPI | CS# (Chip Select) | Active low, frames each 8-bit transfer |
| D4 | UART | TX @ 9600 baud | Sends "SAM\r\n" repeating, 8N1 |
| D5 | UART | TX @ 115200 baud | Sends 0x55 ("U") repeating, 8N1 |
| D6 | I2C | SCL (Clock) | ~100 kHz standard mode |
| D7 | I2C | SDA (Data) | Address 0x50 write + incrementing data byte |
| D8 | BUS | Bit 0 (LSB) | 8-bit binary counter at ~50 kHz — all bits changing simultaneously, scrolling 0x00–0xFF |
| D9 | BUS | Bit 1 | |
| D10 | BUS | Bit 2 | |
| D11 | BUS | Bit 3 | |
| D12 | BUS | Bit 4 | |
| D13 | BUS | Bit 5 | |
| D14 | BUS | Bit 6 | |
| D15 | BUS | Bit 7 (MSB) |
What you need
Hardware
- TinyFPGA BX board ($38 from tinyfpga.com or Crowd Supply)
- Micro-USB cable (data cable, not charge-only)
- Logic Probe 16 connected to your oscilloscope
- Jumper wires — 16 signal + 1 ground (Dupont M-F)
Software
- Python 3.10+ (3.13 recommended)
- apio 1.3.0+ — FPGA build toolchain
- tinyprog — TinyFPGA BX programmer
- Rigol MSO5000 or DHO900 oscilloscope
USB Cable Warning
Many micro-USB cables are charge-only (no data lines). If your TinyFPGA BX doesn't appear as a COM port when plugged in, try a different cable. The board's boot LED should pulse when the bootloader is active.
Install tools and build the bitstream
Install the FPGA toolchain, build the Verilog design, and program the board.
Install apio and tinyprog
Build the design
Program the TinyFPGA BX
Verify the FPGA is running
After programming, the on-board LED should blink at approximately 1 Hz. This is the heartbeat indicator built into the design. If the LED is not blinking, press the reset button on the board.
The Verilog design
The complete design fits in a single file. Four independent signal generators run in parallel on the iCE40 FPGA.
// SPI clock divider: 16 MHz / 32 = 500 kHz reg [4:0] spi_clk_div = 0; wire spi_tick = (spi_clk_div == 0); always @(posedge CLK) spi_clk_div <= spi_clk_div + 1; // Rotating data pattern: 0xA5, 0x3C, 0x55, 0xF0, 0x0F, 0x69, 0x96, 0xCC // SPI Mode 0 (CPOL=0, CPHA=0), MSB first // CS# goes low for each byte, high between transfers // MISO echoes MOSI inverted (simulated slave response) assign PIN_1 = spi_sck; // D0 — SCK assign PIN_2 = spi_mosi; // D1 — MOSI assign PIN_3 = spi_miso; // D2 — MISO assign PIN_4 = spi_cs_n; // D3 — CS#
// 9600 baud: 16,000,000 / 9600 = 1667 clocks per bit // Sends "SAM\r\n" repeating, 8N1 format // Start bit (0), 8 data bits LSB first, Stop bit (1) reg [7:0] uart_9600_msg [0:4]; initial begin uart_9600_msg[0] = 8'h53; // 'S' uart_9600_msg[1] = 8'h41; // 'A' uart_9600_msg[2] = 8'h4D; // 'M' uart_9600_msg[3] = 8'h0D; // CR uart_9600_msg[4] = 8'h0A; // LF end assign PIN_5 = uart_9600_tx; // D4 — UART 9600 // ───────────────────────────────────────── // 115200 baud: 16,000,000 / 115200 ≈ 139 clocks per bit // Sends 0x55 ("U") repeating — alternating bits // Perfect pattern for scope auto-baud detection assign PIN_6 = uart_fast_tx; // D5 — UART 115200
// I2C clock: 16 MHz / 160 = 100 kHz (standard mode) // Generates real I2C sequences: // START → Address 0x50 (Write) → ACK → Data byte → ACK → STOP // Data byte increments each transaction (0x00, 0x01, 0x02...) // Simulated ACKs (SDA pulled low) so decoder sees clean frames reg [7:0] i2c_addr = 8'hA0; // 0x50 << 1 | W = 0xA0 reg [7:0] i2c_data = 8'h00; // Increments each frame assign PIN_7 = i2c_scl; // D6 — I2C SCL assign PIN_8 = i2c_sda; // D7 — I2C SDA
// 8-bit free-running counter at ~50 kHz // 16 MHz / 320 = 50 kHz update rate // Counts 0x00 → 0xFF continuously reg [7:0] bus_counter = 0; assign PIN_9 = bus_counter[0]; // D8 — Bit 0 assign PIN_10 = bus_counter[1]; // D9 — Bit 1 assign PIN_11 = bus_counter[2]; // D10 — Bit 2 assign PIN_12 = bus_counter[3]; // D11 — Bit 3 assign PIN_13 = bus_counter[4]; // D12 — Bit 4 assign PIN_14 = bus_counter[5]; // D13 — Bit 5 assign PIN_25 = bus_counter[6]; // D14 — Bit 6 assign PIN_26 = bus_counter[7]; // D15 — Bit 7
# Clock & LED set_io --warn-no-port CLK B2 set_io --warn-no-port LED B3 set_io --warn-no-port USBPU A3 # Left header: D0–D12 set_io --warn-no-port PIN_1 A2 # D0 — SPI SCK set_io --warn-no-port PIN_2 A1 # D1 — SPI MOSI set_io --warn-no-port PIN_3 B1 # D2 — SPI MISO set_io --warn-no-port PIN_4 C2 # D3 — SPI CS# set_io --warn-no-port PIN_5 C1 # D4 — UART 9600 set_io --warn-no-port PIN_6 D2 # D5 — UART 115200 set_io --warn-no-port PIN_7 D1 # D6 — I2C SCL set_io --warn-no-port PIN_8 E2 # D7 — I2C SDA set_io --warn-no-port PIN_9 E1 # D8 — Bus bit 0 set_io --warn-no-port PIN_10 G2 # D9 — Bus bit 1 set_io --warn-no-port PIN_11 H1 # D10 — Bus bit 2 set_io --warn-no-port PIN_12 J1 # D11 — Bus bit 3 set_io --warn-no-port PIN_13 H2 # D12 — Bus bit 4 # Right header: D13 set_io --warn-no-port PIN_14 H9 # D13 — Bus bit 5 # Bottom pads: D14, D15 set_io --warn-no-port PIN_25 G1 # D14 — Bus bit 6 set_io --warn-no-port PIN_26 J3 # D15 — Bus bit 7
[env:default] board = tinyfpga-bx top-module = top
Wire the TinyFPGA BX to the Logic Probe 16
Connect the FPGA outputs to the probe's P1 and P2 input headers using jumper wires.
Ground connection is critical
Connect a ground wire between the TinyFPGA BX GND pin and the Logic Probe 16 ground pin. Without a common ground reference, the LVDS drivers cannot interpret the logic levels correctly.
PIN_25 and PIN_26 are bottom pads
These two pins are on the bottom of the TinyFPGA BX board, not on the header. You'll need to solder wires or use pogo pins. If you want to skip these, you still get 14 channels covering all 4 protocols — only the top 2 bits of the parallel counter are missing.
Configure the oscilloscope
Set up your Rigol MSO5000 or DHO900 to capture and decode all protocols simultaneously.
Enable the Logic Analyzer
Press Logic Analyzer on the front panel (or find it under Math / Logic on DHO900). Turn on digital channels D0 through D15. Set the digital channel display to show all 16 channels stacked vertically.
Set the trigger
Set trigger source to D3 (SPI CS#), trigger type to Falling Edge. This captures the start of each SPI transaction and gives you a stable, repeating trigger point. Alternatively, trigger on D4 (UART 9600) falling edge to capture the start bit of each "SAM" message.
Set the timebase
Start at 20 μs/div to see SPI and I2C frames clearly. Zoom out to 500 μs/div to see UART characters, or 5 ms/div to see the full "SAM" UART message. The parallel counter is visible at any timebase.
- SPI detail: 5–10 μs/div
- I2C detail: 20–50 μs/div
- UART 115200: 20–50 μs/div
- UART 9600 ("SAM"): 500 μs – 5 ms/div
- Parallel counter: 50–100 μs/div
Add protocol decoders
Navigate to Decode on the scope. You can set up multiple decoders simultaneously. Here's how to configure each one:
Configure protocol decoders
The Rigol MSO5000 supports two simultaneous decode buses. Set up each protocol to verify the probe channels.
| Navigate to | Decode → Decode1 → SPI |
| CLK source | D0 |
| MOSI source | D1 |
| MISO source | D2 |
| CS source | D3 (active low) |
| Clock edge | Rising (Mode 0) |
| Bit order | MSB first |
| Word size | 8 bits |
| Navigate to | Decode → Decode2 → UART |
| TX source | D4 |
| Baud rate | 9600 |
| Data bits | 8 |
| Parity | None |
| Stop bits | 1 |
| Idle level | High |
| Bit order | LSB first |
| Navigate to | Decode → Decode1 → UART |
| TX source | D5 |
| Baud rate | 115200 |
| Data bits | 8 |
| Parity | None |
| Stop bits | 1 |
| Idle level | High |
| Bit order | LSB first |
| Navigate to | Decode → Decode2 → I2C |
| SCL source | D6 |
| SDA source | D7 |
| Navigate to | Logic Analyzer → Bus Setup |
| Bus channels | D8 (bit 0) through D15 (bit 7) |
| Display format | Hex |
| Timebase | 50–100 μs/div |
Decode bus limitations on MSO5000
The Rigol MSO5000 supports 2 simultaneous decode buses. To see all 4 protocols decoded, capture screenshots of SPI+I2C first, then switch to UART+parallel bus. The DHO900 series also supports 2 decode buses. All 16 digital channels are always captured regardless of which decoders are active.
What you should see on the scope
When everything is wired correctly and the decoders are configured, here's what each channel group shows.
D0–D3: SPI Bus
D0 (SCK) shows a clean 500 kHz clock during transfers, idle between bytes. D3 (CS#) frames each byte — low during transfer, high during gaps. D1 (MOSI) shows data bits synchronized to SCK rising edges. The decoder overlay displays hex values: A5, 3C, 55, F0, 0F, 69, 96, CC repeating.
D4–D5: UART
D4 shows UART frames at 9600 baud — the decoder displays "S", "A", "M" followed by CR/LF. Each character takes ~1.04 ms (10 bits at 9600 baud). D5 shows 115200 baud frames — much faster, with 0x55 repeating. At 20 μs/div you can see individual bits of the 115200 stream.
D6–D7: I2C Bus
D6 (SCL) shows a 100 kHz clock during transactions, high during idle. D7 (SDA) shows the characteristic I2C start condition (SDA falls while SCL high), followed by address 0x50 (write), ACK, one data byte, ACK, and stop condition (SDA rises while SCL high). The data byte increments each frame.
D8–D15: Parallel Counter
Eight digital traces showing a binary count pattern. D8 (LSB) toggles fastest at ~25 kHz. D15 (MSB) toggles slowest. With the bus decoder active, you see hex values scrolling 00 → FF continuously. This verifies all 8 upper channels are connected and phase-aligned.
Common issues
No signals on any channel
Check that the TinyFPGA BX LED is blinking (~1 Hz). If not, the FPGA isn't running — re-program it. Verify the ground wire is connected between the FPGA board and the probe. Check that the 50-pin ribbon cable is fully seated in the oscilloscope.
Some channels work, others don't
Check individual jumper wire connections. A loose Dupont connector is the most common cause. Verify you're connected to the correct probe header pins (P1 for D0–D7, P2 for D8–D15).
Protocol decoder shows errors or garbage
Verify the decoder settings match exactly (baud rate, bit order, clock edge, active level). For SPI, confirm Mode 0 (CPOL=0, CPHA=0) and MSB first. For UART, confirm LSB first and idle-high. For I2C, no special settings beyond SCL/SDA assignment.
Probe power LED is off
The probe draws power from the oscilloscope through the 50-pin connector. If the LED doesn't light, the ribbon cable may not be fully seated, or the cable could be damaged. Try reseating the connector or using a different ribbon cable.
TinyFPGA BX not detected by tinyprog
Many micro-USB cables are charge-only with no data lines. Try a different cable. The board should appear as a COM port in Device Manager. If the boot LED isn't pulsing, press the reset button to re-enter the bootloader.
Get the source files
Everything you need to reproduce this demo. Open source under the MIT license.
top.v
Verilog source — complete multi-protocol signal generator
pins.pcf
Pin constraint file — TinyFPGA BX to Logic Probe 16 mapping
apio.ini
Build config for apio 1.3.0+ toolchain
Download all source files (ZIP, 6 KB)
Contains top.v, pins.pcf, apio.ini and the README.
