Case study 04 · Embedded remote-control boat

KObait

A two-board embedded system for a GPS bait boat, with a handheld remote on a 3.5-inch TFT, a boat controller and a custom framed LoRa protocol, and safety gates in front of the motors.

Status: PrototypeBench-tested, not yet on the water
Role
Hardware integration, firmware for both boards, protocol design and test tooling
Timeline
Mar 2026 to present
Context
Personal hardware project
Layers
Hardware
Links
Private repository

01Overview

KObait is a remote-controlled bait boat for fishing. It carries bait out over the water, remembers Home, A and B waypoints and releases the bait through two hatches. It is also my hardware lab, the place where software habits such as single ownership, explicit contracts and tests meet wiring, radios and batteries.

Where it is now. The system was rebuilt from its first generation (an Arduino Nano remote with an nRF24L01 radio and an OLED) into two Mega 2560 boards with a LoRa link and a TFT remote. It is a bench prototype. The radio, displays, GPS and control logic work on the desk, and it has not yet been in the water.

Specification

Boards
2 × Arduino Mega 2560: boat controller and handheld remote
Radio
UART LoRa modules with my own framing and CRC-16
Protocol
v3: 12-byte control, 34-byte telemetry; paired and sequenced
Timing
Control every 850 ms; failsafe stop after 3.4 s of silence
Propulsion
Two reverse-capable 60 A ESCs behind one propulsion module
Navigation
GPS and BNO055 heading; Home, A and B waypoints
Remote UI
480×320 TFT, custom UI framework, 9 screens
Firmware
C++; about 4,300 lines on the boat, 8,600 on the remote
Tests
196 host-side checks, 22 bench diagnostic sketches
  • Only one module may write motor pulses, and only one may change the boat's mode. Tests enforce both.
  • Every one-shot command is sequenced, retried until the boat echoes it, and executed exactly once.
  • A lost radio link stops the boat. Autonomous return is deliberately not built until it is an explicit decision.
  • The radio timing comes from a measured 808.75 ms round trip, not from a datasheet.

02What I built

Boat controller

  • A six-mode state machine (boot-safe, waiting for neutral, manual, auto, failsafe stop, critical fault) with 13 stated stop reasons.
  • A propulsion module working in a signed −255 to +255 domain, with per-ESC calibration checked at compile time, asymmetric ramps and a pause at neutral before reversing.
  • A GPS and heading autopilot to saved Home, A and B waypoints, with calibration-health gating on the IMU.
  • A latched leak alarm, a home water probe, battery thresholds for a 4S pack, a hardware watchdog and two bait-hatch servos.

Radio and protocol

  • A framing layer for transparent UART LoRa radios (sync bytes, type, length, payload, CRC-16) parsed by a non-blocking state machine that resynchronises on noise.
  • Protocol v3 with a pairing ID, wrap-safe sequence numbers, command IDs echoed back in telemetry, and deduplication.
  • A half-duplex master and slave schedule. The remote transmits, and the boat answers in a jittered reply window or with a heartbeat every 5 s.

Handheld remote

  • A 3.5-inch 480×320 TFT on the Mega, with every other peripheral moved to the analog header because the shield covers the digital pins.
  • A custom UI framework with a priority screen router in which a water alarm outranks every screen except a diagnostic one opened on purpose, dirty-region repaint at 25 Hz and dark and light themes.
  • Hall-effect joystick calibration stored in EEPROM with a version and CRC, an on-device calibration wizard and arcade mixing with reverse.
  • A neutral-to-arm gate that holds output at zero until the stick has been healthy and centred for 300 ms.

Tooling

  • 22 single-purpose bench sketches for ESCs, radio, GPS, IMU, displays, buttons and sensors.
  • 196 host-side Python checks for architecture invariants, control logic and protocol behaviour.

03Architecture

Two boards and one radio link. The remote is the master and the boat only speaks when spoken to. On the boat, one state machine owns the mode and one module owns the motors.

Fig. 04.1

Two boards, one link

Handheld remote

  • HardwareHall joystick

    EEPROM calibration, 45° diagonal mapping

    • connects to Remote firmware
  • HardwareButtons, D-pad

    hold opens a hatch, tap closes it

    • connects to Remote firmware
  • FirmwareRemote firmware

    mixing, neutral-to-arm gate, sequenced commands

    • connects to 3.5 in TFT · 25 Hz
    • connects to LoRa radio
  • Client3.5 in TFT

    custom UI framework, dirty-region repaint

  • HardwareLoRa radio

    UART module, software serial

    • connects to Remote firmware
    • connects to Protocol v3

Radio link

  • StageProtocol v3

    KB frame, CRC-16; paired, sequenced

    • connects to LoRa radio
    • connects to LoRa radio

Boat controller

  • HardwareLoRa radio

    UART module, hardware serial

    • connects to Protocol v3
    • connects to BoatLink
  • FirmwareBoatLink

    accept, dedup, build telemetry

    • connects to LoRa radio
    • connects to Boat state machine · accepted
    • connects to Propulsion authority · manual
    • connects to Bait hatches · hatch
  • FirmwareBoat state machine

    sole mode owner: 6 modes, 13 stop reasons, failsafe stop after 3.4 s without control

    • connects to Propulsion authority · gate
    • connects to Autopilot · AUTO
  • FirmwarePropulsion authority

    only PWM writer; ramp, dwell, per-ESC calib.

    • connects to 2 × ESC + motors · PWM µs
  • Hardware2 × ESC + motors

    reverse-capable, 60 A

  • FirmwareAutopilot

    heading P-control to Home, A or B

  • HardwareGPS + BNO055

    fix age, heading with calibration health

    • connects to Autopilot
  • HardwareWater, leak, battery

    latched leak alarm, 4S thresholds

    • connects to Boat state machine · stop
  • HardwareBait hatches

    two servos, closed before attach

The remote is the radio master and transmits a control frame every 850 ms; the boat replies only inside a jittered window or with a 5 s heartbeat. On the boat, one state machine owns the mode and one propulsion module owns every motor pulse, and host-side tests enforce both. Losing the link for 3.4 s stops the boat.

04Hard problems

  1. Radio collisions that looked like dead hardware

    Problem

    The first LoRa link looked like a dead uplink, even though both modules powered up and answered.

    What I did

    Separate beacon, ping and pong runs showed that every loss was a collision between two free-running transmitters on half-duplex radios. A master and slave schedule gave full delivery with zero CRC errors on the bench.

  2. Timing from measured air time

    Problem

    A measured round trip of 808.75 ms made the original 100 ms control rate and 500 ms failsafe physically impossible.

    What I did

    I re-derived the schedule from the measurement. The remote sends every 850 ms, the boat stops after 3.4 s without control, and the remote shows signal lost after 4.25 s. The trade-off, an estimated 6 to 7 m of coasting at an assumed 2 m/s before the failsafe stop, is documented as an estimate, not a measurement.

  3. A display shield that covers every digital pin

    Problem

    The TFT shield took the whole digital header, and its library would drive analog pins shared with the joystick and the radio.

    What I did

    All peripherals moved to A0 to A15, the software serial receive pin moved to a pin-change-interrupt pin, and build-time and runtime guards stop the display driver from touching anything else.

  4. From forward-only to reverse-capable ESCs

    Problem

    A single legacy pulse value meant stop, neutral and minimum throttle at once. On bidirectional ESCs it could fall in the reverse band.

    What I did

    A signed control domain, per-ESC calibration with compile-time ordering checks, and a mandatory pause at neutral before any change of direction.

  5. Stale commands after a reboot

    Problem

    A remote reboot restarted its sequence and command counters, so the boat rejected frames or echoed an old command back as a success.

    What I did

    The boat resets its sequence baseline once per outage and the remote ignores stale echoes. A session-aware v4 protocol is designed but not yet built.

05Decisions

Sub-GHz LoRa instead of nRF24L01, with my own framing.
Better range over water, and the display shield covers the SPI pins. A transparent UART radio has no packets, checksums or acknowledgements, so the firmware provides them.
Stop, not return-to-home, when the link is lost.
Autonomous movement after losing contact is a product decision that needs an explicit yes, not a default.
A custom UI framework instead of LVGL.
LVGL's recommended memory far exceeds the Mega's 8 KB of SRAM, and there is no framebuffer. The framework repaints only the regions that changed.
The OLED and the IMU are non-critical.
A display failure once left the boat in an infinite loop with the ESCs attached. Now the boat runs without a display, or without AUTO, instead of halting.
The pairing ID is isolation, not authentication.
It keeps two boats from answering each other's remote. It is not a security boundary, and the protocol documentation says so.

06Status

Works today

  • Both boards run their firmware on the bench, and the LoRa link has been verified in both directions on a desk.
  • GPS fix, displays, the remote UI, joystick calibration and sensor baselines are verified on the bench.
  • AUTO is gated off in firmware because the IMU does not respond on the current bench wiring.

Not built yet

  • ESC characterisation with measured endpoints, then staged tests with motors, props and restraint, and a tethered water test.
  • Outdoor range testing and reading back the radio's configuration.
  • Level shifting between the 5 V board and the radio's 3.3 V inputs.
  • A session-aware protocol v4 and a decision on return-to-home.

No photos of the hardware are published yet. The plate at the top of this page is an illustration of the system, not a render of the boat.

07What I learned

  • Measure before you tune. The radio's real round trip rewrote every timing constant.
  • On a machine with propellers, safety is structure. One writer for the motors, one owner of the mode, and tests that keep it that way.
  • Writing down what has not been tested yet (no water test, no range test) is part of the engineering, not an admission.

08Stack

  • C++
  • Arduino
  • LoRa radio
  • GPS navigation
  • BNO055 IMU
  • Python