Height sensors
The height sensors measure the distance between the hull and the water surface, which is what tells the crew — and eventually the Autopilot — how high the boat is flying on its foils. Two ultrasonic sensors look straight down from the nose of the hull. They are not read directly by anything: a dedicated RS485-to-CAN interface board polls them and rebroadcasts each reading on the bus, where the display and the datalogger pick it up.
At a glance
| Property | Value |
|---|---|
| Sensors | 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU |
| Reading | Distance to the water surface, in millimetres |
| Sensor positions | As far forward as possible, port and starboard of the hull nose |
| Update rate | 8 Hz per sensor (16 Hz combined, staggered) |
| Interface board | RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use |
| Board location | Under the deck, just forward of the throttle, near the pilot cabin |
| Bus | CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers |
| CAN identifiers | 0x011 front left, 0x012 front right
|
| Application type | 0x02 — its address in the bootloader protocol
|
| Firmware | Rust, embassy, binary height-sensor-controller
|
| Firmware source | firmware/ in the eoi-can monorepo
|
| Hardware source | Altium project AKD-CAN_Interface (Altium 365)
|
| Field update | Over CAN, no debug probe needed |
The sensors
We use cheap DYP A02 RS-485 ultrasonic sensors. The sensor does all the timing itself: it is a Modbus RTU slave that answers a register read with a distance in millimetres, so the interface board never sees a raw echo.
Both sensors are mounted as far forward as they fit, one to port and one to starboard of the nose, pointing down at the water. Each one passes through the hull on a miniature 5-pin Binder connector which is watertight even when unmated, so a sensor can be unplugged without opening the hull up. Wire colours for that harness are listed on the Foils page.
What the number means. It is the raw distance from the sensor face to whatever the ultrasonic pulse hit — nothing more. It is not corrected for pitch, roll or sensor offset, and a wave crest passing under the nose reads as the boat dropping. Treat it as a ride-height indication, not a calibrated altitude.
Interface board
The board is the Altium project AKD-CAN_Interface. It is the same PCB as the e-paper dashboard in the cockpit — that board fits a display on SPI2 where this one fits the RS-485 channels. The height-sensor unit itself lives under the deck, a little forward of the throttle near the pilot cabin.
RS-485 channels
The schematic repeats one RS-485 channel sheet four times, so there are four identical sensor channels, numbered 2 to 5 after the USART each one lands on. The firmware only uses two of them.
| Channel | USART | TX | RX | DIR (DE) | Detect | Used for | CAN ID |
|---|---|---|---|---|---|---|---|
| 2 | USART2 | PA2 | PA3 | PA1 | PA0 | Height sensor front left | 0x011
|
| 3 | USART3 | PC4 | PC5 | PB1 | PB2 | Height sensor front right | 0x012
|
| 4 | UART4 | PC10 | PC11 | PA15 | PA12 | unused | 0x013 (reserved)
|
| 5 | UART5 | PC12 | PD2 | PB4 | PB5 | unused | 0x014 (reserved)
|
Each channel is one half-duplex transceiver (TI SN65HVD3088E, 20 Mbit/s, 1/8 unit load) running from the board's 5 V rail, with DE and RE tied together so the part either drives or listens. The microcontroller runs at 3.3 V, so the transceiver's receiver output and the detect line both present 5 V logic to it — every pin in the table above is a 5 V-tolerant FT pin, which is why this is allowed rather than merely lucky.
Note: the schematic symbol is named ISL83078E, but the BOM's part choice is the TI SN65HVD3088E. Both are 5 V, 8-pin, pin-compatible RS-485 transceivers, so the sheet is right either way — but check the fitted part before quoting a datasheet number.
Sensor connector
One 5-contact M8 female panel-mount connector per channel.
| Pin | Signal | Notes |
|---|---|---|
| 1 | Sensor supply | 5 V. Selected by a 0603 link: one jumper to 5 V, an alternative to 24 V. Fit one — populating both shorts 5 V to 24 V. |
| 2 | RS-485 A / Y | |
| 3 | RS-485 B / Z | |
| 4 | Presence detect | 100 kΩ pull-up to 5 V. Pull to ground to signal "sensor connected" |
| 5 | GND |
This is the board's own pin order. It is not necessarily the order of the Binder connector in the hull, and it is not the A02's own cable colour code — check the harness rather than assuming the numbering carries across.
Power and bus
24 V arrives on the CAN-bus connector through a resettable PTC fuse (30 V, 100 mA hold) and feeds two Würth fixed step-down modules, one producing 5 V for the RS-485 side and one producing 3.3 V for the microcontroller. The CAN transceiver is an MCP2542FD. The safety line passes straight through between the two CAN connectors and is not connected to the microcontroller, so this board neither reads nor breaks the safety circuit.
Board temperature
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address 0x3F) reports the PCB temperature once per second on 0x210, in hundredths of a degree. This is a board health measurement — it is how you tell whether the sealed compartment under the deck is cooking.
How the firmware reads them
Each sensor is a Modbus RTU slave at address 0x01, spoken to at 9600 baud, 8 data bits, no parity, 2 stop bits. One read is a single holding register at 0x0101, and the response is the distance in millimetres.
Sending the request is what starts the ultrasonic ping, so the request and the measurement are the same event. A read normally completes in about 100 ms, which is also used as the response timeout.
The two sensors are never pinged at the same time. A sequencer alternates front-left, front-right, front-left, … and only starts the next sensor once the previous read has finished, because two overlapping ultrasonic pings at the front of the hull can hear each other and produce a plausible but wrong distance. When reads complete promptly the sequencer paces itself to a 62.5 ms slot, giving 8 Hz per sensor and 16 Hz combined; if reads run to the full timeout the combined rate falls back toward 10 Hz on its own.
Before each request the receive buffer is drained, so a late response from a previous timed-out read cannot be mistaken for the current one.
The presence-detect pin is sampled first. If it reads high the channel is reported as NotPluggedIn and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports ModbusError. In both cases the height field is sent as 0 — 0 mm is not a measurement, always read the state byte first.
CAN messages
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.
| ID | Message | DLC | Rate |
|---|---|---|---|
0x011 |
HeightSensorFrontLeft | 3 | every 125 ms |
0x012 |
HeightSensorFrontRight | 3 | every 125 ms |
0x035 |
Bootloader response | 2–8 | on request only |
0x210 |
TemperatureHeightSensorsController | 2 | every 1 s |
0x013 and 0x014 are reserved for the two unused channels and are not transmitted.
0x011 and 0x012 — height sensor reading
| Byte | Field | Type | Values |
|---|---|---|---|
| 0 | State | u8 enum | 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown |
| 1–2 | Height | u16 LE | Distance to the water surface in mm. 0 whenever the state is not Operational. |
0x210 — TemperatureHeightSensorsController
| Byte | Field | Type | Values |
|---|---|---|---|
| 0–1 | Board temperature | i16 LE | Hundredths of a degree Celsius. 2500 = 25.00 °C. |
Received
The application uses an accept-all hardware filter and ignores everything that does not concern it. The only frames it acts on are the bootloader ones: the discovery broadcast 0x030, its own command identifier 0x031 + (0x02 − 1) × 3 = 0x034, and firmware write data on 0x036.
Firmware update over CAN
The first 80 KB of flash holds a CAN bootloader, so the board can be updated without pulling it out from under the deck. It owns application type 0x02, which gives it the identifier block 0x034 (command) / 0x035 (response) / 0x036 (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.
eoi-flash-tool scan # what is on the bus?
eoi-flash-tool flash height-sensor-controller # target comes from the ELF
eoi-flash-tool --board height-sensor-controller version # which build is on it?The protocol, the flash layout and the addressing scheme are shared with the other boards and are documented in full on the Rudder controller page.
Diagnostics
| Symptom | Meaning |
|---|---|
| Green LED toggling once per second | Application running normally |
| Red LED double-flashing | Sitting in the bootloader, waiting or flashing |
| No LED activity | No power, or a boot hang |
| State byte 1 (ModbusError) | No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor. |
| State byte 0 (NotPluggedIn) | Detect pin reads high, so the connector is seen as empty |
| Height plausible but frozen | The sensor is answering with a stale value; power-cycle the channel |
| On the bus but dropping frames | Suspect the 16 MHz crystal. The firmware falls back to the internal oscillator, whose ±1 % accuracy is not good enough for 1 Mbit/s. |
A 4-second independent hardware watchdog is petted once per second by the heartbeat task, so wedged firmware resets itself instead of sitting silent on the bus. Detailed logging is available over SWD through defmt.
A sensor can be exercised on the bench without any hardware: firmware/tools/height_sensor_sim.py pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.
Known gaps
- Half the channels are unused. Channels 4 and 5 are fitted and wired, and
0x013/0x014are allocated to them, but no position on the boat has been decided yet, so no firmware reads them. - The detect pin's pull direction is contradictory. The board pulls the line up to 5 V through 100 kΩ, so an empty connector should read high — but the firmware also enables the microcontroller's internal pull-down on that pin. That pull-down is 40 kΩ typical (25–55 kΩ), which drags the line to somewhere around 1.4 V, below the input threshold, so an absent sensor is likely to be reported as
ModbusErrorrather thanNotPluggedIn. The pin wants no internal pull at all — the microcontroller datasheet requires the internal pulls to be disabled on any pin held above VDD anyway. Worth confirming on the bench. - No filtering. Each frame is one ping. There is no averaging, no rate limiting and no outlier rejection, so single bad echoes go straight onto the bus. Any smoothing has to happen in the consumer.
- The bus-wide reference is out of date. CAN_MESSAGES.md still describes the height field as "raw, unit undecided". The firmware sends millimetres. This page follows the firmware.
- The rotary switch is not read. A sealed 4-bit hex switch is fitted and wired, but no firmware uses it. It was presumably intended for a node address or a variant selection.
- No temperature compensation. The speed of sound varies with air temperature by roughly 0.17 % per °C, which is about 2 mm per °C at a 1 m range. The A02 is left to its own internal compensation, whatever that is.
See also
- Foils — what the ride height is measured for, and the sensor harness wire colours
- Autopilot — the intended consumer of these readings
- CAN-bus — bus wiring, cable pinout and the bus-wide identifier map
- Rudder controller — sister board, and the full bootloader protocol
- Instrumentation Panel — where the readings are displayed
- Datalogger — where they are recorded
- Altium PCB — house PCB design conventions