Skip to content

Hardware

PurpleSentinel edited this page Oct 13, 2026 · 5 revisions

Hardware

What the device is made of, how it is wired, and which parts of that are verified rather than assumed. The authoritative detail lives in docs/HARDWARE_BOM.md and docs/HARDWARE_INTEGRATION.md; this page is the orientation and the warnings.

The receiver is connected, configured, and producing fixes

As of 13 October 2026 the Qwiic cable has arrived, the receiver is on the I2C socket, and the device configures it at boot and reads accepted fixes from it: 20 Hz as configured, 49 frames, 49 accepted, 0 rejected, on 15 satellites. Nothing was damaged connecting it.

The firmware configures the receiver itself, because a NEO-M9N in its factory default emits NMEA and this firmware parses UBX. UBX-CFG-VALSET enables NAV-PVT on I2C, silences NMEA and sets the rate — written to RAM, battery-backed RAM and flash, so it survives a power cycle rather than working only until the device is unplugged.

Three things worth noting from bring-up:

  • the antenna is doing its job. A valid fix indoors on 15-16 satellites across multiple constellations, within a second of boot
  • 25 Hz on four constellations is real. Measured at 25.3 frames/s when configured for a 40 ms period, which confirms the part choice below rather than taking it on trust. 20 Hz is what runs, per #16
  • whether a configuration persists cannot be tested by power-cycling the board. The firmware configures on every boot, so a receiver that kept its settings and one that reverted behave identically from the first fix onwards, and the power-on boot overwrites the evidence before anything looks. Looking before writing does not fix it either: that is only evidence if the receiver actually lost power, and a power-on boot's log cannot be captured on this hardware at all. The answer comes from asking the receiver what is in its flash layer with UBX-CFG-VALGET, which needs no power cycle and no listener

The socket warning, and what became of it

The Waveshare has two physically identical SH1.0 4-pin sockets, one UART and one I2C. The concern on #9 was that the I2C block's pin order had never been reported, so if its power ordering differed from the UART block's, a board-to-board cable could put 3.3 V onto a data line.

A standard Qwiic cable has now been used on that socket without incident, so this is no longer a hazard. It is still not an answer: the pin order was established by a connection that happened to work rather than read off the board, which covers the one cable that was used and nothing else. Check the label anyway before using a cable you have not used before, particularly a home-made one.

The parts

Ref Part Why this one
H1 Waveshare ESP32-S3-Touch-AMOLED-2.41-B 600×450 AMOLED, touch, ESP32-S3R8 with 8 MB PSRAM and 16 MB flash, QMI8658 IMU, RTC, microSD, supplied case
H2 SparkFun GPS-17285 — u-blox NEO-M9N, SMA, Qwiic 25 Hz navigation rate on four constellations, 3.3 V logic, UART and I2C, UBX
H3 Taoglas Magma X AA.170.301111 Covers all four constellations the M9N tracks, including BeiDou B1 at 1561 MHz, which common 1575–1610 MHz antennas miss
H4 High-endurance microSD, 8–32 GB Session traces and event logs, FAT32
H6 Fused 5 V vehicle USB supply Never connect raw vehicle 12 V to the Waveshare board

Confirmed from the board itself: chip revision v0.2, 16 MB flash, and the fitted card reports as SN32G, 30 GB, FAT.

Two part choices that look wrong until explained

The antenna band, not its gain, is what decides it. BeiDou B1 sits at 1561.098 MHz, below the 1575–1610 MHz window that most "GPS/GLONASS/Galileo" antennas cover. Fit one of those and BeiDou is quietly absent — the receiver still works, so nothing looks broken, while a quarter of the constellations silently stop contributing.

A newer u-blox part would be a downgrade. The M9N does 25 Hz on four constellations. The M10 manages 10 Hz on four, and 20 Hz only with three and a raised CPU clock. For lap timing, fix rate is the whole point, so the older generation is the right one.

How it is wired

The shared I2C bus

On GPIO47 (SDA) / GPIO48 (SCL)
Touch controller
QMI8658 IMU
RTC
IO expander (AMOLED enable, tearing effect, touch interrupt)
GNSS receiver at 0x42, joining via Qwiic

The receiver is the one participant on that bus with a deadline. At 25 Hz a fix is 40 ms and the receiver's buffer holds roughly 410 ms, so the margin is about ten missed polls — which is less comfortable than it sounds on a device whose UI task has starved before. Bus contention is a real design consideration, not a theoretical one.

Both of the breakout's connectors are Qwiic on the same bus, and its I2C jumper carries the pull-ups.

Other assignments

Function Pins Bus
AMOLED QSPI CS / CLK / D0–D3 GPIO9 / 10 / 11–14 SPI2_HOST
AMOLED reset GPIO21
Touch reset GPIO3
microSD CS / SCLK / MOSI / MISO GPIO2 / 4 / 5 / 6 SPI3_HOST, SPI mode
UART socket GPIO43 / 44 UART0 — also the default console

The UART socket can serve the console or the receiver, not both

This is the constraint most likely to catch someone out.

GPIO43/44 is UART0, which is the firmware's primary console. The staged transport plan in ADR-005 moves GNSS to UART for vehicle use, because UART is pushed by DMA while I2C must be polled. That puts the receiver on the same two pins the console speaks on.

If both are used at once, log output is transmitted into the receiver and the console driver owns the pin the receiver answers on. So the console has to move to USB Serial/JTAG before GNSS takes the UART socket.

Two practical consequences:

  • Do not plan to use UART0 as a permanent diagnostic tap. It is wanted for the receiver. Boot logs are obtainable over USB instead — see below.
  • The breakout's UART is plated through-holes only, so the UART stage needs soldering. I2C is solderless, which is why bring-up uses it. Leave the breakout's SPI solder jumper open: closing it disables the UART on those lines.

The module's UART default is 38400 baud, 8N1, per the data sheet. The 115200 figure in SparkFun's hookup guide is their serial monitor, not the module.

Getting diagnostics off the board

make firmware-capture-boot captures a cold boot over the board's own USB-Serial-JTAG port — the full log from the ROM banner to the application, with no extra hardware.

Two traps, both of which produce silence rather than an error:

  • Opening the port and waiting captures nothing. Boot output is written and discarded before a host attaches, so the listener has to hold the port before the reset.
  • Pulsing RTS alone boots the ROM download stub, which prints nothing. RTS is wired to EN (reset) and DTR to GPIO0 (boot select), and pyserial asserts both on open. Releasing RTS with GPIO0 still low starts the download stub. An empty capture then reads as "the output was discarded" when it means "the application never ran".

A genuine power cycle cannot be captured this way at all: the USB device is inside the chip, so cutting power destroys the port the listener is holding. An RTS reset is a chip reset (rst:0x15 USB_UART_CHIP_RESET) and the handle survives it. Only a separate USB-UART adapter on the UART socket could see a post-power-cycle boot, since its own connection to the host is independent of the target's power — and that socket is wanted for the receiver.

Background in #181.

A healthy boot looks like this

TrackSessionTimer GPS bootstrap
imu: ready
sd-card: mounted
sd-card name=SN32G capacity=30424 MB fs-total=30409 MB free=30408 MB
settings: source=0 current-format=1, mode track-day
track catalog: built (26 discovered, 26 loaded, 0 rejected, cap 32)
gnss: transport attached over i2c
ready screen presented on the 600x450 panel
gnss: searching (sats 7, polls 412, bytes 38104, frames 331, ..., 24.9 Hz)
gnss: fix accepted (sats 11, ..., accepted 1, rejected 331, 25.0 Hz)

gnss: transport attached over i2c is weaker than it reads: it means only that the I2C driver accepted an address, since adding a device to the bus never talks to the chip. The stage lines after it are what report a receiver. They are logged on every change, with six named stages ordered the way the link actually fails — bus unreadable, bus ok, receiver silent, bytes arriving, no valid frame, ubx arriving, no nav-pvt, searching, fix accepted — so the cause is named rather than left as "no fix". The third of those also logs the receiver's first bytes as text, because neither NMEA nor a corrupted bus produces a checksum error and the bytes are the only thing that tells them apart.

Note that an NMEA sentence contains a position, so a captured boot log is as sensitive as the place it was captured.

Full detail in DEVELOPMENT_SETUP.md.

settings: source=0 means the stored configuration was read in its current format; a non-zero value means it was migrated from an older one on that boot. The track counts depend on what is on the card, and rejected or a truncation warning means definitions did not make it into the catalog.

What is verified, and what is not

State
Display, touch, gesture UI working on hardware
microSD, settings persistence, session lifecycle working, survives power cycle
QMI8658 IMU, gyro-tracked attitude, G recording working
Boot log capture over USB working
I2C transport and DDC protocol working on hardware. The receiver answers at 0x42 and its bytes reach the pipeline
GNSS fixes working. 20 Hz, configured at boot and acknowledged, every frame accepted
Receiver configuration surviving a power cycle confirmed. Read back from the receiver's flash layer: nav-pvt on, nmea off, 50 ms
RTC present, not driven
Vehicle power path, enclosure, antenna mounting not built

What happens next

  1. Wire the timing engine to the fix queue. Nothing consumes it, so no lap can be timed however good the fixes are. This is the critical path, and it is software (#18).
  2. Check GPS Only on the device and confirm it reads out a live receiver (#178).
  3. Starve the poll deliberately and watch the gap counter trip on the device. The host tests prove the logic; they do not prove the wiring (#18).
  4. Select Donington Park and confirm the timing engine arms (#139).
  5. Drive the Test Circuit and capture its gates on the device (#184, #37).
  6. Only then the UART stage, which needs soldering and the console moved first (ADR-005).