The rover

Two prototypes and the software between them

Prototype 1 proved the software and control chain on a cheap, easy-to-change platform. Prototype 2 scaled up the chassis, drivetrain and power so the project could be tested outdoors.

P1 and P2

What changed between prototypes

Prototype 1Prototype 2
Chassis3D-printed PLA box, 270 × 180 × 100 mm4040 aluminium extrusion, about 760 × 740 mm and 50 kg
DriveFour N20 gearmotors (150:1) with Hall encoders, two TB6612FNG driversFour ODrive M8325s motors through 10:1 gearboxes, one ODrive S1 per wheel
Wheels and speed69 mm wheels; about 0.7 m/s at the 200 rpm firmware limit260 mm pneumatic wheels; 6 km/h (1.67 m/s) design speed
Low-level controlESP32 running two speed PID loops over USB serialODrive S1 controllers on a shared CAN bus
PowerTwo 3S LiPo packs, one for logic and one for the motorsTwo 6S packs in series (12S, 50.4 V max), Batrium BMS, DC-DC converters for compute
ComputeRaspberry Pi 5; localisation on a separate laptopRaspberry Pi 5 for the rover software; Jetson Orin for obstacle detection
Positioningu-blox NEO-M9N single antenna, MPU-6050 IMU, HMC5883L magnetometer on a Pico 2 hubUnicore UM982 dual-antenna RTK, ZED 2i IMU, motor encoders
Operator linkWi-Fi hotspot on the Pi, PlayStation controllerLong-range radio to a base station
StoppingUltrasonic sensorsPhysical E-stop and contactors, state-machine stop, remote stop

What Prototype 1 taught us

  • Soil gives a robot nothing to recognise. We dropped LiDAR and visual SLAM for GNSS fused with IMU and wheel odometry, and that choice shapes the whole project.
  • A control loop can be stable while it drives the wrong way. Motor and encoder direction have to agree physically, not be patched in software.
  • Motors and the computer can't share a power rail. Ours corrupted the Pi's SD card on boot.
  • Safety reflexes belong in a layer that keeps working when the main computer crashes.

First numbers

0.22mendpoint error over a 74 m walk with the Prototype 1 sensor stack Field
32of 34waypoints reached in Isaac Sim with two filters, up from 28 Sim

The same walk exposed a magnetometer calibration fault that overstated path length about 2.4 times.

The build

Prototype 2, July to October

The team built Prototype 2 together to Morne Nel's electrical design. It first drove on 3 September.

Bare Prototype 2 chassis with four wheels and motor units on an orange workbench.
21 Aug. Frame and driveline.
Prototype 2 on a workbench with an orange enclosure on top, two antenna masts, a stereo camera, a red E-stop button and a power supply on the side.
9 Sep. Enclosure, antennas and E-stop.
Prototype 2 on the lab floor seen from the front, with camera, antennas, E-stops and wiring.
16 Sep. Sensors and wiring fitted.
Low view of Prototype 2 on an asphalt path beside a kerb, showing the wheels and the power supply on its side.
16 Oct. On an asphalt path.

Specification

Chassis
4040 aluminium T-slot extrusion, T-nuts and gusseted corner brackets; aluminium motor and gearbox plates
Size
About 760 × 740 mm, about 50 kg
Drive
Skid steer. Four ODrive M8325s motors, 10:1 gearboxes, one ODrive S1 driver each
Speed
6 km/h design: 122 rpm at a 260 mm wheel, 1,220 rpm at the motor
Battery
Two 16,000 mAh 6S Tattu packs in series, 50.4 V max
BMS
Batrium Watchmon, Shuntmon and two K9 cell monitors, on CAN
Compute
Raspberry Pi 5 running ROS 2 Jazzy; Jetson Orin for obstacle detection
Positioning
Holybro H-RTK Unicore UM982, dual antenna, through a Pixhawk 6C
Perception
ZED 2i stereo camera, which also supplies the IMU
Link
Holybro long-range telemetry radio to a Pi 5 base station

What broke, and what replaced it

Motor hubs
Printed in PLA for quick iteration, they split along the layer lines inside the gearbox. Replaced with machined aluminium.
Front pivot
An ASA centre pivot with a deep-groove bearing broke as the rover got heavier. Replaced with a stiff joint.
Battery latch
A levered latch became unreachable once the hardware plate was fitted. Replaced with a toothed slide locked by a pin.
Gearbox support
Each motor, hub and gearbox hung from its ends and rubbed at the hub. A bracket along two edges of the gearbox, added mid-September, made the rover run noticeably smoother.
CAN pins
Pins pulled out of the ODrive plug housings and had to be rewired. Locking connectors are needed before field use.

Power and interlocks

Two contactors, two kinds of stop

Every contactor and relay contact in the chain is normally open, so losing coil power opens the circuit. The only normally closed contact is the E-stop button itself.

Contactor 1 feeds everything that thinks. Its coil runs through the BMS relay in series with the physical E-stop. When it closes, the main bus powers the 5 V, 12 V and 24 V converters, and through them the Pi, the Jetson, the radio and the GNSS receiver. Opening it switches off every computer on the rover, and also removes the coil supply for contactor 2.

Contactor 2 feeds the motors. Its coil is switched through two relays in series plus a pre-charge path. Opening it removes motor power but keeps the computers running, so the rover can still report what happened.

Flyback diodes protect both coils, and the Pi reaches the relay board through optoisolators so no ground path links the voltage domains. The BMS sits on its own 12 V supply ahead of both contactors, so it stays awake whatever they do.

Arming sequence

any safety event →E-STOP, latched
  1. A fresh arm request arrives from the base station. A request still held after a reboot does nothing, so the rover can't arm itself.
  2. A solid-state relay closes through a 50 Ω resistor, limiting inrush to about 1 A at 50.4 V, and the drive capacitors charge for 5 s.
  3. If battery and bus voltage agree within 2 V, contactor 2 closes and the pre-charge relay releases. A bigger gap means a short or a failed drive, and the sequence aborts to DISARMED.

BMS limits

From one of Batrium's pre-mapped LiPo profiles, not set by hand. Any breach opens the BMS relay and contactor 1 with no software involved.

Cell over-voltage4.05 V
Cell under-voltage3.00 V
Discharge temperature−20 to 60 °C
Peak current80 A

On top of that, the state machine stops the rover if pack or bus voltage falls below 40.8 V (12 × 3.4 V) while armed.

Software layers

Each job runs in its own ROS 2 node

A valid velocity command can exist in the ROS graph without being allowed to reach the motors. Where motion comes from, whether it's permitted, and how a wheel turns are separate responsibilities.

Alongside the command path

State machine/active_mode

Owns the operating mode, arming and pre-charge over GPIO. Drive permission stays false in DISARMED and PRECHARGE.

Localisationekf_odom · navsat_transform · ekf_map

UM982 RTK, ZED 2i IMU and wheel encoders fused into one pose.

Obstacle detectionJetson Orin → UDP → Pi

ZED 2i depth; holds the rover for anything inside 3 m or a silent camera.

Designed, not all fielded

This is the designed architecture. In the 14 October field runs a simpler route executor steered straight from the GNSS fix, in place of the mission node and Nav2.

A restored radio link never counts as permission to move. After a communication loss or an E-stop the rover returns to DISARMED and needs a new, deliberate arm sequence. The same rule applies to command streams: re-establishing /cmd_vel_tele or /cmd_vel_nav doesn't recreate drive permission.

Mission node

The rover keeps its own plan

Nav2 drives to a goal but has no idea of a mission. The base station holds the mission, but reaches the rover over a link that drops packets. So the rover sequences the mission on board.

The node receives the plan over the radio, sends Nav2 one waypoint at a time, cancels the goal on an E-stop, obstacle or fault, and reports to the base station at 2 Hz. Each waypoint travels as latitude, longitude and heading: 20 bytes, against about 56 for a full pose, on a link that carries about 250 bytes a packet.

It sits outside every stop path. It never publishes velocity and can't reach the contactors or the CAN bus, so if it fails the rover loses mission progress and telemetry, and all three stop paths stay intact. For the same reason the remote E-stop runs from the radio bridge straight to the state machine.

It saves the plan and progress at every waypoint with an atomic write. On start-up it treats the saved file as untrusted, and a restored mission always comes up latched: nobody may be near the rover when power returns.

Bench only so far

30 logic tests and bench tests T1–T10 pass against a stubbed Nav2. Bench The node hasn't run against the real Nav2 or on the rover yet.

Nine mission states

StateMeaning
IDLENo plan loaded
READYPlan loaded, not driving
RUNNINGA Nav2 goal is in flight
HOLD_MANUALTele-op has the rover; clears when the mode returns to auto
HOLD_OBSTACLESomething is in the way; resumes the same waypoint when it clears
HOLD_BATTERYBelow the 42.5 V reserve; finishes the current leg, starts no new one
ESTOPLatched; needs the E-stop released and a deliberate clear
FAULTLatched; an input went silent or retries ran out
COMPLETEEvery waypoint reached

Checked ten times a second, top rule wins

  1. A new plan arrived: replace the waypoints and restart (ignored while latched)
  2. E-stop asserted: cancel the goal and latch ESTOP
  3. A monitored input went silent: cancel and latch FAULT
  4. Already latched: stay latched until a clear
  5. Nav2 returned a result: advance, retry, or fault when retries run out
  6. Obstacle detected: cancel and hold on the same waypoint
  7. Mode isn't autonomous: cancel and hold for tele-op
  8. Battery below reserve: finish this leg, then hold
  9. Otherwise: send the goal for the current waypoint

Obstacles sit above the mode check, so a mode change can't mask a detection. The battery rule sits below the goal check, so the rover stops at a waypoint rather than halfway along a tramline.

Localisation

Smooth for the controller, anchored for the tramline

A path controller needs a pose that doesn't jump. A tramline follower needs one that doesn't drift. Two robot_localization filters give one of each, and the map-to-odom transform between them updates in small steps as GNSS corrections arrive.

The rover runs the same three nodes, with the same topic and frame names, as the Isaac Sim model. Both filters run in 2D mode.

NodeFusesPublishes
ekf_odomWheel odometry, ZED 2i IMUSmooth local pose; odom → base_link
navsat_transform_nodeGNSS fix, IMU heading, filtered odometryThe fix as odometry in the world frame
ekf_mapWheel odometry, IMU, GNSS odometryGlobal pose; map → odom

When GNSS drops out, ekf_odom carries on and ekf_map's uncertainty grows. Published results show error can pass the 20–50 mm tolerance within seconds, so the rover can only ride out brief dropouts.

Base station

The operator's end of the link

The base station supervises. Navigation and every safety decision stay on the rover, because a rural radio link can't be assumed to stay up.

A dark dashboard with a wheel-speed diagram, a satellite position plot, a map of Argyle Square with planned points, and live traces for speed, compass, cross-track error and motor current.
Field-test interface showing the I–P row plan over Argyle Square, captured in simulation before the field test. Aerial imagery City of Melbourne 2020, CC BY 4.0.
A base-station screen in sunlight showing 52 numbered green waypoints joined in a four-row serpentine pattern.
16 October. A 52-waypoint, four-row plan loaded on the base station.

Dry run

Faults we found before the wheels turned

Before the motor-control and safety nodes touched live outputs, a dry run fed them normal and deliberately broken inputs with CAN transmission and GPIO disabled. Each of these could have weakened a safety behaviour without any obvious symptom in ordinary driving. Bench

What we sawWhyFix
A malformed packet kept a dead link "alive"The receive time updated before validationThe last-valid timestamp updates only after a packet parses and validates
A recovered link kept the old arm requestReconnecting could recreate the armed stateArming became edge-triggered: arm must drop, then rise again
A mode change and an E-stop in the same cycleNormal mode logic could run before the stopStop and link-loss checks run ahead of every normal transition
Yaw commands gave almost equal wheel speedsTrack width was still a placeholderTrack width became a required parameter; ± yaw became a mandatory test
One side needed the opposite motor signMirrored motor mountingPer-wheel direction signs, checked with forward and reverse vectors
CAN payload decoded wronglyByte order and type, not the speed mathsExplicit little-endian float32 velocity and torque feed-forward
Link timeout fired at start-upNo link had been established yetThe bridge tells "not yet established" from "established then lost"

Known limitations

  • The command link between base station and rover isn't authenticated.
  • No visual or audible alarm is fitted.
  • One sensing technology, a stereo camera, detects obstacles.
  • The Pi gets no electrical feedback from the pre-charge and contactor hardware, so it treats its own commands as fact.
  • The 0.5 s link timeout and 5 s pre-charge window are configuration settings, not validated values.
  • No functional-safety lifecycle was carried out.

Checked on the built rover

  • All four wheels held 36.7 rpm (0.50 m/s) on the stand, measured-to-commanded ratio 1.00, 13 October. Rover
  • The remote E-stop stopped the wheels on 8 and 13 October.
  • The physical E-stop removed motor power, and the Pi, Jetson and radio went down with it, as designed.
  • After either E-stop the rover stayed still until a new arm request.
  • Indoors, it stopped for an obstacle, waited, and carried on once it cleared.
  • The under-voltage stop opened the contactor, with the voltage simulated in software.

Apart from the stand test, these were observations, not logged measurements.