Mark II is alive.
GearSense gets a face.

Mark I was a dev board wrapped in a weatherproof LED headlight case with a leather pouch. Mark II is a purpose-designed ABS enclosure with a 2" touchscreen showing a live GearSense dashboard. Same concept. Completely different presence.

The hardware arrives

The Waveshare ESP32-S3-Touch-LCD-2 is a capable board: ESP32-S3 processor, 8MB PSRAM, 16MB Flash, a 2" capacitive touchscreen, onboard battery management, and a CS8501 hardware protection circuit. It showed up with the factory demo running — display sharp, touch responsive, battery reading 3.86V on arrival.

The 2000mAh 103450 LiPo batteries arrived the same day. Six print iterations to get the case right — tolerances, fitment, cable routing. By the sixth, everything landed where it needed to.

ESP32-S3
processor — dual-core, 240MHz
8MB
PSRAM for LVGL frame buffer
16MB
Flash for firmware and data
2"
capacitive touchscreen, 320×240

The case

The enclosure is a single-piece PLA print: 62mm × 41mm × 25mm outer. The battery fits cleanly alongside the board — no dividing wall needed, the geometry just works.

Cable strain relief is zip ties — looped through holes in the case walls so the wires can't be yanked out from outside. Simple, reliable, costs nothing, and already in the parts bin.

Board in case showing zip ties through case walls as cable strain relief
Zip ties through the case walls — the wire stops at the tie, not at the solder joint

The front face is angled for handlebar ergonomics. The USB-C cutout is accessible without removing the lid for charging. First print needed minor dimensional tweaks — nothing unexpected on a first print. The design is still evolving: a third cable egress hole and a removable back cover are in the next revision. PLA now, ABS once the geometry is settled.

Print details PLA on a Bambu A1. Same machine that produced the RackRobo wire tool head — proven on precision parts. Case dimensions were driven by the board footprint, not by aesthetics. The result is compact without being fiddly.

Wiring — the detective work

The GPIO assignments on the Waveshare board aren't fully documented for our use case. The factory examples are the ground truth. A grep session through the factory source files turned up what we needed.

The interesting find: GPIO5 is the battery ADC — but it's not broken out on the headers. It's an internal connection. That means battery monitoring comes for free, no soldering required. We would have found this eventually; finding it early saves a rework.

GPIO Wire Function Note
4 Blue PAS cadence input Camera pin — camera not used
6 Yellow Wheel speed KY-003 Camera pin — camera not used
5 Battery ADC Internal — not on headers
17 Throttle DAC output Phase 3, reserved

Colour convention settled: Red = 3.3V, Black = GND, Blue = PAS, Yellow = wheel speed. The M6 connectors ordered for sensor leads, M8 for the throttle — different sizes mean different connectors can't be swapped by mistake in the dark on a wet trail.

The connector architecture

The throttle override solution avoids any permanent modification to the bike. An M8 1-female-to-2-male splitter taps the throttle line: one output passes straight through to the existing throttle, one comes from the GearSense DAC on GPIO17.

The control logic is deliberately conservative: the physical throttle always overrides GearSense when the rider twists it harder. The brakes override everything — that's hardwired in the BAFANG controller, not something we need to implement. GearSense fills the gap when the rider isn't actively overriding. Fully reversible, plug-in, no splicing.

The LVGL battle

Getting the LVGL toolchain working took longer than the wiring. The Library Manager installs LVGL 9.5.0 by default. The Waveshare BSP is incompatible with LVGL 9. Downgraded to 8.4.0. That fixed the build — until it didn't.

The next failure was a missing # — specifically #if 1 in lv_conf.h had become if 1. A single character. The compiler was entirely confused. These are the bugs that teach patience.

The BSP function signatures weren't in the docs in the form we needed them. Found by reading the factory source directly:

// What the docs implied: bsp_touch_init(); // What the source actually required: bsp_touch_init(&Wire, rotation, width, height); bsp_touch_read(); bsp_touch_get_coordinates(&x, &y);

Each compile got further than the last. Methodical peeling of the onion — this is what embedded development actually looks like. Eventually the example compiled and ran. Then we wrote the GearSense firmware from scratch.

Mark II firmware v0.1.1

Written from scratch using the correct LVGL 8.4.0 three-argument API. The dashboard shows what matters: the current gear (large, centre-screen), cadence Hz, wheel Hz, the computed ratio, battery voltage, and state.

// GearSense brand colour throughout lv_obj_set_style_text_color(title, lv_color_hex(0x1D9E75), 0); // Large gear display — the primary output lv_obj_set_style_bg_color(gear_box, lv_color_hex(0x1D9E75), 0); lv_obj_set_style_text_font(label_gear, &lv_font_montserrat_48, 0);

State machine: STOPPED / RIDING / COASTING / PEDALLING. Serial output in Mark I CSV format for backwards compatibility with the analysis scripts. Deep sleep kicks in at 3.2V battery for hardware protection (the CS8501 handles the hard cutoff; the firmware adds a graceful shutdown above that).

Confirmed working on hardware Display rendering, touch input, battery ADC (3.60V displayed correctly), PSRAM allocation, sensor interrupt wiring. All confirmed before the session ended. The firmware ran on the actual target hardware, in its case, on the first upload.

The money shot

Where we came from, and where we are.

GearSense Mark I — dev board in a cardboard box with weatherproof enclosure alongside
Mark I — dev board, weatherproof box, leather pouch
GearSense Mark II — PLA case with 2-inch touchscreen showing calibration screen
Mark II — PLA case, 2" touchscreen, live firmware

That's the proof made physical. Mark I told us the concept worked in data. Mark II tells you it works by showing you a screen.

A picture is worth 1000 words.

Paul O'Rorke, on seeing the working display

What comes next

Mark II goes on the bike. The engineering questions are settled — now we find out how the firmware performs under real conditions.

  1. Mount Mark II on the cargo bike and log a real ride
  2. Verify cadence and wheel speed on the live display while riding
  3. Implement noise calibration state machine
  4. DBSCAN gear clustering on the ESP32-S3
  5. Touch UI for ride mode switching
  6. Phase 3: throttle DAC output on GPIO17