Autopilot
Platform
Ease of development dictated the choice to run the control loop on existing hard- and software, which was chosen to be Ardupilot running on a orange cube.
To make use for our application a PCB has been designed to integrate into the hydrofoil design.
To connect to the ardupilot on the cube for tweaking settings and tuning control loops, Mission Planner is used. The control logic itself runs as a Lua script on the cube (see Lua control script). The following sections describe the tweaked settings and the on-board script.
Hardware
The cube is a plug-and-play unit and therefore does not need setup changes in the underlying ardupilot software. Only necessary settings should be tweaked.
Apart from the settings, tuning needs to be done in Mission Planner; this can only be done with the solar boat in the water and therefore no values are known for now.
The ardupilot PCB supports three servo motors, two front and one back motor. It has two isolated power supplies for the servo motors. Support for CAN and the cube is powered by the 24V input from the CAN connection.
It has its own GPS antenna and four UART serial ports, where Serial1 is used for the MAVLINK connection to Mission Planner running on a laptop. The two ultrasonic height sensors connect over the CAN bus (through an RS485-to-CAN interface PCB), not the cube's UART ports; the Lua control script reads them to keep the boat at its target ride height so it can 'surf' over waves.
For steering on foils an extra MCU is added onto the board, namely the ATSAMD20E18A. This MCU receives the steering position from the rudder and forwards it (it is intended to be put on the CAN bus for the control system to use).
Height Sensors
Currently testing ultrasonic height sensors, the RS485 version of the DYP-A02YY4W-V2.0, with a CAN-interface PCB in between that places the readings on the CAN bus.
Each sensor broadcasts a 3-byte frame at 8 Hz: byte 0 is a status enum (0x02 = Operational, anything else = no valid reading), bytes 1–2 are the measured height in millimetres (uint16, little-endian). The front-left sensor uses CAN ID 0x011 and the front-right 0x012 (see CAN Bus Format).
The sensors are mounted as far forward and outboard as possible to stay clear of the bow wake (CAD: 3.8 m forward, 0.23 m left/right, 0.175 m above the IMU). The trade-off is that their reading changes strongly with roll; the Lua script presents each sensor to ArduPilot as a downward scripting rangefinder (so ArduPilot median-filters and logs them) and applies a roll/pitch + lever-arm correction before using the values for control (see Lua control script).
Lua control script
The foil control logic runs as a Lua script on the cube (in the project git). It is a cascade that leans on ArduPilot's tested C++ attitude loop rather than re-implementing control in Lua:
- Inner loop — ArduPlane FBWA (mode 5): stabilises pitch and roll attitude from the IMU and drives the three foils as control surfaces. The two front foils are configured as elevons and the rear foil as an elevator output.
- Outer loop — the Lua script: reads the two CAN height sensors, computes a roll/pitch + lever-arm-corrected ride height, and turns the height error into a gentle pitch-target bias on the pitch input channel while holding roll level. Vertical-velocity damping uses the EKF's IMU-fused velocity estimate.
The rear foil is driven over CAN: the script reads ArduPilot's elevator output and sends it as a servo command (ID 0x010).
No RC transmitter is fitted — throttle and rudder are handled separately. Attitude targets are supplied via RC channel overrides from the script, refreshed every loop. A hardware enable pin (FMU_CH6) switches between active stabilisation (FBWA) and neutral foils (MANUAL). It is wired active-low and fail-safe: a disconnected or open enable line disables foiling.
On boot the script enforces the parameters it needs (see the settings table) and falls to neutral foils on loss of the enable signal, stale sensor data, or a script error. It also publishes attitude, both height estimates, and a status word on the CAN bus for the datalogger (see CAN Bus Format).
Two height estimates run simultaneously and are both logged: the Lua roll-corrected value (currently used for control) and ArduPilot's EKF/rangefinder height. The first on-water runs will be flown with the foils disabled to collect this data and decide whether the EKF estimate is reliable enough to drive control.
The script is checked with luacheck and has been validated in ArduPilot SITL (software-in-the-loop) with the CAN bus simulated over UDP multicast.
Ardupilot settings
To use the ardupilot as the foil control unit, some settings need to be changed. Most of the settings below are applied automatically at boot by the Lua script's parameter-enforcement routine — only SCR_ENABLE and the physical sensor mounting offsets (RNGFNDx_POS_*) must be set/measured manually. There are more ports available on the ardupilot; whenever more are used or changed the table will be updated.
| Setting | Value | Description | Reason |
|---|---|---|---|
| SCR_ENABLE | 1 | Enable Lua scripting | Required to run the control script; must be set manually before the script can run. |
| INS_GYR_CAL | 0 | Controls the automatic gyro calibration. | Gyro calibration requires that the unit be held as still as possible. |
| INITIAL_MODE | 5 | FBWA | Fly By Wire A, the attitude-stabilisation mode the inner loop uses. More here. |
| SERVO1_FUNCTION | 77 | ElevonLeft | Controls the left front foil assembly (wired to IO-CH1). |
| SERVO2_FUNCTION | 78 | ElevonRight | Controls the right front foil assembly (wired to IO-CH2). |
| SERVO3_FUNCTION | 19 | Elevator | Rear foil pitch command; the script reads this output and forwards it to the rear foil over CAN. |
| SERVO14_FUNCTION | |||
| GPIO | Frees FMU_CH6 so it can be read as the foil enable input. | ||
| BTN_ENABLE | 1 | Enable button (digital pin) function | Enables reading the foil enable/disable input. |
| BTN_PIN1 | 55 | GPIO pin 55 (FMU_CH6) | Maps the enable input to FMU_CH6; active-low and fail-safe. |
| BTN_REPORT_SEND | 2 | Report button state over MAVLink | Surfaces the enable state to the GCS. |
| CAN_P1_DRIVER | 1 | Enable CAN port 1 | CAN carries the height sensors, the rear foil command and telemetry. |
| CAN_D1_PROTOCOL | 10 | Scripting | Lets the Lua script send/receive raw CAN frames on bus 1. |
| RNGFND1_TYPE / RNGFND2_TYPE | 36 | Lua scripting rangefinder | The two height sensors, fed by the script. |
| RNGFND1_ORIENT / RNGFND2_ORIENT | 25 | Pitch270 (downward) | Required for ArduPilot to treat the sensor as a height source. |
| RNGFND1/2_MIN_CM / MAX_CM | 40 / 150 | Valid range (cm) | Range gate for the ultrasonic sensors. |
| RNGFND1/2_POS_X / _POS_Y / _POS_Z | 3.8 / ∓0.23 / −0.175 | Sensor mounting offset from the IMU (m; X fwd, Y right, Z down) | Lets the height correction compensate the roll/pitch lever arm. CAD-measured: 3.8 m forward, 0.23 m left/right, 0.175 m above the IMU. RNGFND1 = left (−Y), RNGFND2 = right (+Y). |
| THR_FAILSAFE | 0 | Disable throttle/RC failsafe | No RC receiver is fitted, so RC failsafe must not trigger. |
| ARSPD_USE | 0 | Do not use airspeed | No airspeed sensor. |
| BRD_SAFETY_DEFLT | 0 | Safety switch disabled at boot | No safety switch fitted; outputs must be live at boot (the enable pin is the safety interlock). |
| TERRAIN_ENABLE | 0 | Disable terrain following | Not applicable; avoids interference with foil height control. |
| GPS_TYPE2 | 5 | NMEA | Sets the GPS data sent onto the serial data port. |
| SERIAL1_PROTOCOL | 2 | MAVLINK2 | Protocol on the Mission Planner serial port. |
| SERIAL1_BAUD | 57 | 57600 | Baudrate of the Mission Planner serial port. |
| SERIAL4_PROTOCOL | 5 | GPS | Protocol on the GPS serial port. |
| SERIAL4_BAUD | 9 | 9600 | Baudrate of the GPS serial port. |
CAN Bus Format
All traffic is classic CAN, standard 11-bit IDs, 1 Mbit/s, little-endian unless noted. Inputs are produced by other nodes; outputs are produced by the Lua control script.
| ID | Direction | Length | Contents |
|---|---|---|---|
| 0x011 | in | 3 | Front-left height sensor: [0] state (0x02 = Operational), [1..2] height in mm (uint16 LE) |
| 0x012 | in | 3 | Front-right height sensor (same layout as 0x011) |
| 0x010 | out | 2 | Rear foil servo command: PWM as uint16, big-endian (1500 = neutral) |
| 0x250 | out | 6 | Telemetry – attitude: [0..1] roll, [2..3] pitch (int16, centidegrees), [4..5] yaw (uint16, centidegrees, 0..36000) |
| 0x251 | out | 8 | Telemetry – state: [0..1] Lua roll-corrected height, [2..3] EKF height (uint16 mm, 0xFFFF = invalid), [4..5] status bits, [6] vehicle mode, [7] downward-rangefinder status |
The 0x251 status bits are: 0 wings enabled, 1 left sensor fresh, 2 right sensor fresh, 3 both fresh, 4 control height valid, 5 control source = EKF, 6 EKF healthy, 7 EKF initialised, 8 home set, 9 EKF origin set, 10 EKF height (HAGL) available, 11 EKF velocity available.
Byte 6 of 0x251 is the ArduPlane flight-mode number as reported by the autopilot. In normal operation the script only commands two: 0 = MANUAL (foils disabled / neutral) and 5 = FBWA (active stabilisation). Any other value means the mode was changed elsewhere (e.g. from the GCS); see the ArduPlane flight-mode list for the full enumeration.
The steering angle come from the rudder controller and is planned to be used as roll input to the controller.
Future plans
As of March 2026 the Lua control script provides ride-height control as an outer loop on top of FBWA, which already gives automatic altitude holding for the front foils; the rear foil is commanded over CAN. The earlier idea of switching to FBWB for altitude holding is therefore no longer required, though it remains an option for comparison.
Control-loop coefficients are still placeholders — tuning requires the boat in the water. The first runs will be passive (foils disabled) to collect attitude, height-sensor and EKF data over CAN and decide whether ArduPilot's EKF height estimate is reliable enough to use as the control input instead of the Lua-computed value.
Remaining integration work: feed the rudder/steering-angle CAN message into the controller and finalise the no-RC failsafe behaviour.
Some additional reading: https://ardupilot.org/plane/docs/common-sensor-testing.html
Relevant links for configuring Ardupilot
https://ardupilot.org/copter/docs/common-gcs-only-operation.html
https://ardupilot.org/copter/docs/common-rangefinder-landingpage.html
https://ardupilot.org/copter/docs/setting-up-for-tuning.html
https://ardupilot.org/copter/docs/terrain-following-manual-modes.html
https://ardupilot.org/copter/docs/common-sensor-offset-compensation.html
https://ardupilot.org/copter/docs/crash_check.html
https://ardupilot.org/copter/docs/flight-modes.html
https://ardupilot.org/plane/docs/fbwb-mode.html
https://ardupilot.org/plane/docs/fixed-wing-faq.html --Disable Gyro calibration on start-up
https://ardupilot.org/rover/docs/sonar-sensors.html --Two sonar sensors
https://ardupilot.org/dev/docs/plane-architecture.html --Plane architecture and files