Skip to main content

v2n-gd32-bridge-loopback

Jumpered Tier-B loopback validation of the GD32 supervisor bridge.

Where the sibling v2n-gd32-bridge-functional drives each bridge surface against a known-exact software answer — and reads ADC pads that float on the bench — this tier closes the loop in copper: three physical jumpers on the E1M-X V2 carrier route a bridge output back into a bridge input, so the analog and timer signal paths get validated end to end on real silicon.

Source: examples/v2n/v2n-gd32-bridge-loopback/.

:::warning Bench-only, and jumper-only This is a maintainer bench tool in example form. Like the functional tier it exercises the gd32g553 chip driver directly — the documented exception to the portable-API rule for dedicated bridge demos.

On an unwired board every value assertion fails. That is expected. The suite is only meaningful on a board jumpered per the table below. :::

It runs the three tests once, publishes a verdict block the V2N DAP reads over SWD (there is no console on this SoM), then idles forever. Every stimulus is parked at 0 on the way out of its test.

Bench wiring (E1M-X V2 carrier)

JumperFromToSignal path
ARaw DAC0 net (E1M-X pin A19)P7.1 (CK_ANA)Direct 1:1 analog loopback → raw passthrough to E1M-X pin A17 = ANA_S0 = bridge ADC channel 0.
BJ26.14 (CK_PWM1)J18.7 (ENC1_X)PWM bridge ch1 → encoder index 1 X input. Bidirectional level translation on both sides (transparent to the signal). Y floats with a firmware pull-up = static HIGH.
CJ26.10 (CK_PWM2)J26.8 (CK_PWM3)PWM bridge ch2 output → PWM bridge ch3 rebound as input capture. Both pins ride the same bidirectional level translator, so no contention.

Safety — read before plugging anything in

  1. The carrier's buffered DAC output path (J15.2) is inoperable on this carrier revision (carrier erratum, fixed next rev). The raw DAC0 net is the only usable DAC0 source for this validation path on configured bench carriers.

    The loopback is same-rail 1.8 V → 1.8 V and physically cannot overdrive the ADC pad. DAC_MAX_SAFE_MV (1500 mV) in the source is a linearity bound — stay off the rail-clip region — not an electrical cap. If a future carrier rev restores the buffered J15.2 path, its ×2 gain makes everything above ~850 mV an over-rail hazard. Re-derive the bound before rewiring.

  2. No physical rotary encoder may be plugged into J18 during the qenc test. Jumper B drives ENC1_X from the PWM bridge; an external encoder would contend the line.

The three tests

1. t_dac_adc_loopback (Jumper A)

For each setpoint in {150, 450, 900, 1350} mV: command DAC0, settle 3 ms, then read ADC channel 0 — a burst of 4 independent samples. The assertion takes the first; the burst exists so a noisy connection is visible in the forensics.

The expected reading equals the command (direct 1:1 wiring, both converters on the same 1.8 V VREF). Tolerance is ±(25 mV + 2 % of expected) — offset/INL of the converter pair plus scale error. Tighter than a buffered path, because no external gain resistors remain in the loop. The DAC is parked at 0 on every exit path, including failures.

2. t_pwm_capture_loopback (Jumper C)

Drive PWM ch2 at 200 Hz, 50 % duty (5 ms period, 2.5 ms high), rebind ch3 as a both-edges input-capture source, settle 10 ms, then read in a tight poll loop (up to 80 reads, no inter-read delay) that treats ALP_ERR_NOSUPPORT as the documented "no fresh edge yet, poll again" sentinel. Asserts pulse width in [2400000, 2600000] ns — 2.5 ms ± 100 µs.

Two choices make this robust on a shared-timer loopback (ch2 and ch3 both ride TIMER0):

  • 50 % duty — the both-edges machine measures the delta between adjacent edges. At 50 % the high and low times are equal (period/2 = 2.5 ms), so the pulse width is the same regardless of which edge armed the capture. No phase ambiguity.
  • Slow rate + tight polling — at 200 Hz the edges are 2.5 ms apart, far wider than one bridge transaction (~150 µs), so the host catches three consecutive edges. At the old 1 kHz with a 5 ms retry ladder the three samples were non-consecutive edges and the delta was meaningless — the bug that made this read 0 before bridge firmware v0.2.7.

The period is deliberately not asserted. Stimulus and capture share TIMER0, so the same-edge "period" delta is exactly one counter wrap and reads ~0 — a documented degeneracy, not a fault. The raw period_ns and pulse_width_ns are still recorded for forensics. Firmware v0.2.7 takes all edge deltas modulo the counter period, so the wrap underflow no longer poisons the pulse-width reading.

3. t_pwm_qenc_stimulus (Jumper B)

Reset encoder 1, drive ENC1_X with a 1 kHz 50 % square from PWM ch1, wait 100 ms, read pos1, wait 10 ms, read pos2, park ch1. Asserts all statuses OK and |pos1| ≤ 8 and |pos2| ≤ 8.

In X4 quadrature decode with Y held static HIGH, a lone toggling X cannot accumulate net position — each X edge with an unchanging Y is an ambiguous transition the decoder treats as ±1 dither about the origin. The bound is deliberately loose for this first silicon pass: it exists to catch the failure mode this loopback guards against — a genuinely floating ENC input free-ran to thousands of counts. The raw pos1 / pos2 are recorded so the bound can be tightened from silicon truth.

Verdict block

A static volatile uint32_t loopback_results[32] the bench reads over SWD.

SlotMeaning
[0]Magic 0xB10CBAC4 — sanity-check the symbol + image before trusting the rest
[1]State: 0 = init, 1 = running, 2 = done (idle forever after), 0xDEAD = SPI never opened
[2]Pass count
[3]Fail count
[4..11]Per-record code (cursor order: 4× DAC setpoints, then capture, then qenc). 0 = PASS, 0x7E = transport OK but value assertion failed, anything else = the failing alp_status_t (two's complement)
[12..15]The four raw DAC → ADC readings (mV), in {150, 450, 900, 1350} setpoint order
[16]Raw capture period_ns — forensics, not asserted (shared-timer wrap degeneracy)
[17]Raw capture pulse_width_ns
[18] / [19]Raw qenc pos1 / pos2 (cast to u32 from int32_t)
[20..31]Reserved (0)

Check the magic in [0] before trusting [2] / [3] and the raw forensics slots.

board.yaml

Same SoM/board declarations as the sibling functional example by design — one PCB, variant-populated V2N family.

som:
sku: E1M-V2M101

preset: e1m-x-evk

cores:
a55_cluster:
os: "off"
m33_sm:
app: ./src
peripherals:
- spi
- i2c

chips:
- gd32g553

diagnostics:
log_level: info

Status

build_only: true on native_sim/native/64. It builds clean everywhere as a CI artifact, but only produces meaningful results on an E1M-X V2 carrier wired per the table above. Every result on this page is a jumpered-bench signal.

See also

Questions about this page? Discuss in Community Forum