static
Round Display: how it evolved
Written by staging Claude on 2026-10-09 and approved by Paul. It's built from staging Claude's verbatim transcripts, web Claude's condensed session notes, the inbox handoffs between them, and the commit history of Enoch/gearsense-firmware. Where a source was condensed rather than verbatim, it says so. The sources themselves, and the browser polar solver, are in this idea's record repo under history/round-display/. The full list is at the bottom.
In one paragraph
The round display started as a question: would the LCD-2 firmware run on Waveshare's round ESP32-S3-Touch-LCD-2.1? It wouldn't, because the hardware is different underneath. So Paul and web Claude designed the face in a browser prototype first. Its heart is a "polar solver" that places every element safely inside a circle, whatever the thickness of the compass and speedometer rings. Staging Claude then ported the solver and the face to LVGL in numbered steps. The board-side work was hard won. Getting fonts to build took a stale-cache and library-shadowing hunt. The add-on magnetometer turned out to be a different chip from the one on the label. Three days of "the board won't hear k" came down to using the wrong one of two identical USB-C ports. The result is v0.8.0: a rotating, calibrated compass ring; a speedometer arc; both rings resizable by touch; a HERO readout that you swipe between heading, gear and speed; and a Settings screen. The tilt-compensation fix is still unconfirmed. The board doesn't yet run any of GearSense's actual gear-learning code: it's still a display.
1. Why a round board (8–15 August)
- The round board is for two builds. One is Roman's unit. The other is Paul's touring bike (the Tofino), which is GearSense-only, with no TIM (the throttle-control module). The 2026-08-08 note added a compass and an incline display as "future work, gated behind bring-up". It also chose a cheap "GY-87" sensor module for a magnetometer and a barometer.
- The hardware is not the LCD-2. Web Claude checked both Waveshare wikis. The round board has an ST7701S display on a 16-bit RGB parallel bus, where every earlier board used SPI. It has a single-touch CST820 touch controller and a TCA9554 I/O expander carrying reset and chip-select lines. So the existing firmware would not run as-is. All of this is recorded in
docs/design/ROUND_FACE_LCD21_DESIGN.md. - The rule was the same as on earlier boards: flash Waveshare's own demo unmodified first. Paul did, on the board's UART port, and touch worked. That port choice comes back in section 6.
2. The face, designed in the browser (15 August, Paul with web Claude)
Web Claude logged this session condensed, not verbatim. Its own note says so, and says the 1–5 August material before it was never reconstructed.
Rather than attempt a risky blind bring-up, web Claude built a series of interactive SVG/React tools. It checked each fix numerically in node before showing it.
- The existing solver,
resolvePolarAnchor, was swept continuously from a flat rectangle to a true circle, not just at the two end points. - The compass ring rotates with heading and the speedometer arc fills with speed. Both are deliberately decoupled from the layout maths. Paul asked for three tiers of tick marks: cardinals, 15° and 5°. He also asked for an automotive sweep from 8 o'clock through 12 to 4 o'clock, leaving a 120° gap at the bottom.
- Paul noticed that a thinner speed ring left space in that gap going unused. The fix was an angle-aware safe boundary,
maxRadiusAtAngle, which lets elements reach out to the compass ring inside the gap. Paul called it "a stroke of genuine genius". - Nine layout bugs were found and fixed, each one checked against numbers first:
- MODE BAR collapsed to the centre (fixed with
resolveWithShrink). - HERO now floats midway between its neighbours.
- The scale was computed from the wrong reference radius (fixed with
localScaleAt). - MODE BAR is now derived from GEAR PIPS' real edge (fixed with
resolveAtTarget). - CADENCE sat lower than SPEED only because their boxes had different widths.
- The rest are listed in the web log.
- MODE BAR collapsed to the centre (fixed with
- The compass was restyled after a classic black-faced analog compass. It is deliberately generic and doesn't copy any brand's design.
- The push-bike variant has no MODE BAR. Paul asked why it had one, since it will never have a TIM. MODE BAR selects an assist ceiling, so without a TIM it does nothing, and it was removed from that layout.
- Paul declared it "NAILED IT". The final tool was committed as
REFERENCE_polar_solver.jsx, the ground truth for the port and explicitly not for flashing. A build spec went to staging Claude in2026-08-15-round-face-lvgl-port.md. - Paul's question to staging Claude the same day was how anyone resizes the rings on a round screen with no sliders: "maybe it should be a long touch and slide?" That became gesture 1 in section 7.
3. The incline horizon (20–21 August)
Web Claude logged this session condensed, and retroactively.
- Two ways to show incline were researched: 4WD inclinometer dashboards and cycling computers (Garmin ClimbPro, Wahoo, Karoo). The 4WD approach suited GearSense better, because there is no route data, only live sensors. Paul picked a tilting horizon.
- The first version followed a photo of an aircraft attitude indicator: blue over brown, with a pitch ladder. Then Paul changed the reading direction. A bike glyph shows where you are and a degree number shows the grade ahead, read left to right like a road sign. The ladder went.
- The bug that mattered: placing the glyphs at a fixed screen x let them leave the circle at steep pitch. The fix places each one at a fixed distance along the horizon. A point at a fixed radius can't leave the circle however it's rotated.
- Where the pitch angle comes from (staging Claude, 21 August): use both sources, rather than choosing one.
- The onboard QMI8658 gyro is fast and isn't fooled by braking or sprinting.
- The barometer divided by wheel speed is slow but unbiased, and corrects the gyro's drift.
- The accelerometer only gives the starting value. The 2026-08-08 warning against using the accelerometer was right, but the gyro is the other half of the same IMU and isn't affected.
- Roman's circumference is 2196 mm, from his Garmin's auto-calibration after months of riding. A seated roll-out wasn't reliable.
- Status: designed and in the reference file, not yet in the firmware.
4. Porting to the board (20–23 August)
- The port followed numbered steps.
- The solver went to plain C (
polar_solver.c) and was tested on the host with gcc: 1908 checks, 0 failures. - v0.1.0, the first real firmware on the board, drew only the speed arc. Paul: "We have a live display!"
- v0.2.0 drew all six solver-placed elements.
- The solver went to plain C (
- The MODE BAR overlap came back on hardware, and the fix was to derive it above GEAR PIPS on the round panel. Later the version label overlapped the pips, and was also derived from PIPS' real edge (v0.2.1).
- HERO is a fixed box with a scaling font. Paul asked for its content to be switchable between speed, heading and gear, because Roman uses his Garmin as a speedometer. HERO speed is road speed. The small SPEED element at top right is a sensor-presence indicator, not a second speed readout.
- The font saga. HERO wanted about 46 px, but the build kept rendering 16 px. There were four separate causes:
- The
lv_conf.hfont lines were missing their leading#. - A stale Arduino build cache meant nothing re-uploaded.
- An old
lvgl-backfolder was shadowing the real library. - The font search in our own code was capped at about 37 px (fixed in v0.2.2).
#include <demos/lv_demos.h>in Waveshare's driver. Paul said "dO IT", and it became the one documented change to Waveshare's vendored files. - The
- The Console code was moved into a shared GearSenseCore library so the boards can share it. Staging Claude got
arduino-cliinstalled, though it wasn't routinely used for compile checks until 27 September.
5. The compass hardware (3–5 September)
- The module that arrived wasn't labelled GY-87. It was an "HW-290", and photos of the chips couldn't settle what was on it. So the firmware was made to report what was on the I2C bus.
- An address scan at boot.
- A build marker as the first line, to prove which build was running.
- A 2-second delay before any Serial output, because native USB reconnects after a reset and the start of the log was being lost.
- Reading the chip-ID registers proved an MPU6050 and a BMP180. The barometer worked immediately.
- The magnetometer only appeared after the MPU6050's bypass mode was switched on, at an unusual address, 0x2C. It answered neither the HMC5883L nor the QMC5883L register map. Two separately soldered units behaved identically. On the night of 4 September it was declared dead and parked as "eye candy".
- The next day, web Claude found a forum thread about this exact board. The chip is a QMC5883P, which uses a different register map. The retest was alive.
- A turntable spin showed clean cycles, apart from a steady wobble.
- Paul then put a spirit level on the "level" turntable and found it wasn't level. In his words, that was confirmation of accuracy, not an error.
6. Making heading work: a crash, a blank screen and the wrong port (5–6 September)
Hard- and soft-iron calibration (sent with k) and a tilt-compensated heading were wired into HERO in v0.3.0. Then came a chain of failures, each with its own cause:
- A task-watchdog reboot every ~10 s. LVGL calls were being made from
Driver_Loop, which runs on core 0, while LVGL runs in the main loop on core 1. The sensor task now only writes a plainvolatilevalue, and the main loop draws it (v0.3.1). That wasn't enough on its own. Reading Serial from the same task had to move to the main loop too (v0.3.2). - A blank screen, with the backlight on and heading values printing fine. Heading updates were throttled from about 200 a second to 10 a second (v0.3.3). Confirmed: "We have a heading being displayed in the hero!"
- A flood of
0xFFbytes came fromSerial.available()/read()returning −1 and that being treated as data (v0.3.4). Then it turned outSerial.begin()had never been called (v0.3.5). - Still no
k, with any cable and any terminal. The real cause was the port. The board has two identical USB-C connectors.- One goes through the CH343P UART bridge, where the
printfconsole output appears. - The other is the S3's native USB, which is what Arduino's
Seriallistens on.
- One goes through the CH343P UART bridge, where the
7. The ring turns, and touch arrives (6–8 September)
- The rotating compass ring (v0.4.0) is drawn with a custom LVGL draw callback rather than
lv_meter. N/E/S/W stay upright, because LVGL 8 can't rotate glyphs freely and upright text is easier to read at a glance. It worked first time. Paul: "We have a rotating compass display. And it looks GOOD!" - v0.5.0 added heading smoothing and a "north" calibration (
n), both saved to NVS. Paul's brief: "it's a bezel you glance at while riding." - The whole-screen pan bug. Dragging anywhere slid the whole display, locked to one axis. It was LVGL's default scrolling on the screen object showing through. One line fixed it: clearing
LV_OBJ_FLAG_SCROLLABLE. - Gesture 1, ring resize. Long-press a ring to arm it, then drag along the radius: outward makes it thinner, inward thicker. Pinch was never possible, because the CST820 is single-touch.
- The board's touch input goes through LVGL's own input device, so the LCD-2's raw-polling gesture code couldn't be reused. The gesture was rebuilt on LVGL events.
- Paul: "delightful. Intuitive and easy to use!" The cardinal letters now grow to fill the ring (v0.5.1).
- Gestures 2 and 3. Long-press HERO to open Settings. Swipe HERO to cycle its source, which is saved to NVS (v0.6.0).
- v0.6.1 fixed a resize that armed from anywhere on screen.
LV_OBJ_FLAG_ADV_HITTESTwas never set, so the custom hit test was silently ignored. - The Settings screen (v0.7.0) has hold-to-select rows:
- Calibrate Compass, as a guided flow with a progress bar.
- Set North.
- Wheel Circumference, which honestly says "not yet available" because there's no number entry on the round face yet.
lv_timer_get_user_data(), an LVGL 9 function that doesn't exist in 8.3.10.
8. On the Tofino, and the tilt problem (27–28 September)
- Paul: "The UI is looking spectacular. The resize of both compass and speedometer rings is intuitive and a pleasure to use."
- v0.8.0 fixed Settings rows that dropped back to the driving screen. At Paul's request, it also gave HERO's hold the same filling progress bar as the rows.
- From here on, staging Claude compile-checks every firmware change before it reaches Paul, using
arduino-cliwith 16 MB flash and theapp3M_fat9M_16MBpartition scheme. Earlier compile errors had reached Paul's bench first. - On Paul's machine the IDE was set for 32 MB flash, which made the board reboot-loop. 16 MB fixed it. Paul then calibrated the compass through the new Settings screen.
- The tilt problem. Held flat, north was roughly consistent. But tilting the puck about one axis swung heading +90°, and about the other axis −90°. There were two fixes:
- Roll sign. The accelerometer reads −1 g when at rest, and the formula assumed +1 g.
- Axis mismatch. The magnetometer breakout sits in the case on wires, rotated relative to the main board's QMI8658. A brute-force search of all 24 possible orientations, run against Paul's tilt logs, found the mapping. It cut the error from 150°+ swings to about ±14° on those logs.
- Paul flashed it and said it "doesn't look any different." Rather than keep copying and pasting from the serial monitor, the GearSense Console gained a Save Log button (v1.3.0) to capture a proper log. That log hasn't been taken yet, so the fix is unconfirmed.
Where it stands (firmware v0.8.0-lcd21round)
- Working on hardware:
- the solver layout (eBike variant)
- speedometer arc
- rotating, calibrated compass ring
- ring resize by touch
- HERO swipe and long-press
- Settings, with guided calibration and Set North
- Open:
- Confirm the tilt fix (
08451e0) with a Console log. - A way to enter wheel circumference on the round face.
- Port the horizon incline display.
- MODE BAR still shows TIM modes (ECO/TOUR/BOOST). Paul has asked whether it should reflect Speed/Cadence/Gear instead.
- Label the two USB-C ports in the new FreeCAD case.
- Confirm the tilt fix (
- The big one: no GearSense function has been ported yet. There's no BLE sensor link, gear classifier, ride logging or sleep/wake. The numbers on screen apart from heading are demo values. The gear-learning brain still lives only in the LCD-2 firmware (
GearSense_Central_LCD.ino).
Lessons worth keeping
- Design in a prototype you can sweep and measure, then port it. Commit the prototype as the ground truth.
- Trust the bus, not the label. Address scans and ID registers settled what photos and listings couldn't, twice.
- Put a build marker at the top of every boot log. "Is this even the new build?" cost hours more than once.
- On ESP32, LVGL and Serial both belong to the main loop. Sensor tasks hand over plain values.
- Two identical ports should never be unlabelled.
- Compile before it reaches the bench.
Sources
Everything above can be checked against these. Unless noted, they are in Enoch/claude-context.
- Staging Claude, verbatim (
transcripts/staging/):- 2026-08-15_cdd57aac
- 2026-08-20_34e009f7
- 2026-08-21_0b9421c4, 2026-08-21_7458a218
- 2026-09-03_387876b9
- 2026-09-04_484a01d5, 2026-09-04_904d5d7e (which runs on through 6 September)
- 2026-09-07_4aa18ca5, 2026-09-07_6683a566
- 2026-09-08_a859e7b4
- 2026-09-27_2a246eba, 2026-09-27_69f5566c
- Web Claude, condensed (
transcripts/web/):- 2026-08-15-2_round-face-ui-exploration
- 2026-08-15-2b_round-board-flashing-usb-port
- 2026-08-20_incline-horizon-round-face
- Handoffs (
inbox/for-staging-claude/):- 2026-08-08-round-board-compass-incline
- 2026-08-15-round-face-lvgl-port
- 2026-08-20-incline-horizon-readout
- 2026-09-07-round-face-touch-gestures
- Firmware (
Enoch/gearsense-firmware):GearSense_Central_LCD21_Round/: 77 commits, many with the reasoning in the messageREFERENCE_polar_solver.jsxdocs/design/ROUND_FACE_LCD21_DESIGN.mddocs/design/HEAD_UNIT_DESIGN*.md
- Known gaps: web Claude's 1–5 August and 15 August sessions were condensed, not verbatim. Some September progress, such as the ring hardware confirmation, reached the design doc late because pushes failed on session limits.