Logic Probe 16 Demo: Multi-Protocol FPGA Test with TinyFPGA BX

Application Note • Open Source

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

# Install the FPGA build tool and programmer $ pip install apio tinyprog # Install the required FPGA packages (ice40 toolchain) $ apio packages install

Build the design

$ cd demo/tinyfpga-bx-counter $ apio build # You should see: "BUILD SUCCESSFUL" and a file count # The output bitstream is at: _build/default/hardware.bin

Program the TinyFPGA BX

# Plug in the TinyFPGA BX via USB (boot LED should pulse) # If the boot LED is not pulsing, press the reset button $ tinyprog -p "_build/default/hardware.bin" # If the board isn't found automatically, specify the COM port: $ tinyprog -p "_build/default/hardware.bin" -c COM3 # Output: "Programming... Success!" # The on-board LED will start blinking at ~1 Hz

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.

top.v — SPI Master (D0–D3) Verilog
// 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#
top.v — UART Transmitters (D4–D5) Verilog
// 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
top.v — I2C Bus (D6–D7) Verilog
// 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
top.v — Parallel Counter (D8–D15) Verilog
// 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
pins.pcf — Pin Constraints PCF
# 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
apio.ini — Build Configuration INI
[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.

P1 Header — Channels D0–D7
PIN_1 → D0 SPI SCK
PIN_2 → D1 SPI MOSI
PIN_3 → D2 SPI MISO
PIN_4 → D3 SPI CS#
PIN_5 → D4 UART 9600
PIN_6 → D5 UART 115200
PIN_7 → D6 I2C SCL
PIN_8 → D7 I2C SDA
P2 Header — Channels D8–D15
PIN_9 → D8 BUS Bit 0
PIN_10 → D9 BUS Bit 1
PIN_11 → D10 BUS Bit 2
PIN_12 → D11 BUS Bit 3
PIN_13 → D12 BUS Bit 4
PIN_14 → D13 BUS Bit 5
PIN_25 → D14 BUS Bit 6
PIN_26 → D15 BUS Bit 7

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.

SPI Decode — D0–D3
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
Expected decode: MOSI shows 0xA5, 0x3C, 0x55, 0xF0, 0x0F, 0x69, 0x96, 0xCC repeating. MISO shows the bitwise inverse of each byte.
UART Decode — D4 (9600 baud)
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
Expected decode: ASCII characters S, A, M followed by CR (0x0D) and LF (0x0A), repeating continuously.
UART Decode — D5 (115200 baud)
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
Expected decode: Hex value 0x55 (ASCII "U") repeating. The alternating 01010101 bit pattern is often used for baud rate auto-detection.
I2C Decode — D6–D7
Navigate to Decode → Decode2 → I2C
SCL source D6
SDA source D7
Expected decode: Write transaction to address 0x50 with a single data byte that increments each frame (0x00, 0x01, 0x02...). Each frame shows START, address+W, ACK, data, ACK, STOP.
Parallel Bus — D8–D15
Navigate to Logic Analyzer → Bus Setup
Bus channels D8 (bit 0) through D15 (bit 7)
Display format Hex
Timebase 50–100 μs/div
Expected display: Hex values scrolling 00, 01, 02 ... FE, FF, 00 ... continuously at ~50 kHz. The scope's bus display should show an incrementing count.

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.

Ready to unlock all 16 channels?

The Logic Probe 16 gives your Rigol MSO5000 or DHO900 full 16-channel logic analysis capability. From $30 bare PCB to $195 complete kit.