LabWired The easy way to build hardware
An STM32G0B1 and an Arduino Uno joined by three wires in the LabWired playground.

Two boards, one wire: GPIO nets between MCUs

Category: Engineering

A lot of products have two microcontrollers. They usually talk over a UART. Next to it there are a few plain wires: an interrupt line, a ready line, sometimes one alert line that every board can pull low. LabWired could already run several chips and connect their UARTs. It could not connect a GPIO pin on one chip to a GPIO pin on another. Now it can.

The setup

I used two different chips: an STM32G0B1 (on a NUCLEO-G071RB board in the drawing) and an Arduino Uno. Each runs its own firmware. Three wires connect them:

WireDirectionPullWhat the firmware does
irqUno → STM32downThe Uno pulses it 10 times. The STM32 counts rising and falling edges with an EXTI interrupt.
readySTM32 → UnodownThe STM32 pulses it 7 times. The Uno counts by polling the pin.
alertbothupShared open-drain line. The STM32 pulls it low 5 times, then the Uno 3 times, and each counts the other’s pulses.

Both boards print their counters on their own UART.

How a net works

A gpio_net connects pins of two or more machines into one net. On every edge it works out the level like this: if any pin drives low, the net is low. If none drives low and one drives high, it is high. If nothing drives it, the pull resistor decides. With no driver and no pull it reads 0 and is reported as floating. That covers open-drain too: pins that only pull low or release, plus a pull-up, give you a wired-AND.

An edge reaches the other chips 100 ns later by default, on a defined CPU cycle of each of them. It goes in through the same input path as a button press, so EXTI interrupts and timer captures see it as a normal edge.

If one pin drives 0 and another drives 1 at the same time, the run reports GPIO_NET_CONTENTION. The report has the start and end time and what each pin was driving. The net then reads low.

Results don’t depend on which machine is stepped first or on the simulation round length. The test runs the example in both node orders and with rounds from 10 ns to 100 ns. It compares every counter, every UART line and every delivered edge.

In the playground both boards run in one simulation, the same way the hosted runner does it. The Nets window lists each wire with its level, its edge count and which pin drives it.

Try it

Open the two-boards lab and press Run. In the playground build every delay in the firmware is 20 times longer, so the pulses last hundreds of microseconds. You can see them in the logic analyzer. The counts are the same as in the fast build.

The two-boards lab after a run: the STM32G0B1 and the Uno connected by three wires, with the logic analyzer and the Nets window showing the edge counts.
After the run. The Nets window shows each wire's level, edge count and driver.

The numbers

Serial output from the two boards:

STM irq r=10 f=10 alert r=3 f=3
AVR ready=7 alert f=5 r=5

The STM32 saw 10 rising and 10 falling edges on irq and 3 and 3 on alert. The Uno saw 7 ready pulses and 5 falling and 5 rising edges on alert. The nets count the same from the wire side: irq 20 edges, ready 14, alert 16. A pulse is two edges, and alert carries 5 + 3 pulses.

From the command line:

labwired test --script examples/gpio-net-two-boards/test.yaml --output-dir out

out/result.json has a gpio_nets block with each net’s level, edge count and diagnostics. CI checks these counts on every change.

The second lab, Two boards, one fight, puts both boards on one push-pull wire. The Uno holds it low and the STM32 drives it high for 50 µs. The report names both pins, B1 driving high and D2 driving low, with the time the conflict started and ended.

The contention lab: the Nets window shows GPIO_NET_CONTENTION naming both pins, one driving high and one driving low.
The contention report names both pins.

Limits

Andrii Shylenko
Andrii Shylenko

Founder, LabWired.