Dashboard
The dashboard is the e-paper screen the pilot reads while sailing. It is mounted on the left side of the steering wheel in the cockpit, and shows speed, battery power, state of charge, temperatures and the clock. It measures nothing itself: it listens to the whole CAN bus and draws whatever the other nodes are saying.
At a glance
| Property | Value |
|---|---|
| Panel | Waveshare 5.79" e-paper, 792 × 272, black and white, SSD1683 controller |
| Location | Cockpit, left of the steering wheel |
| Controller board | STM32L471RGT6 — the same PCB as the height sensor controller |
| Bus | CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers |
| Transmits | Nothing, except replies to bootloader queries on 0x038
|
| Update rate | At most once per second, and only when the picture actually changed |
| Application type | 0x03 — its address in the bootloader protocol
|
| Firmware | Rust, embassy, binary dashboard
|
| Firmware source | firmware/ in the eoi-can monorepo
|
| Screen source | draw-display/ in the same repo, shared with the simulator
|
| Hardware source | Altium project AKD-CAN_Interface (Altium 365)
|
| Field update | Over CAN, no debug probe needed |
What is on the screen
The screen is a fixed layout — nothing moves, and a value that goes missing is replaced by dashes in the same place rather than disappearing.
| Region | Shows |
|---|---|
| Left column | Net Power in watts as the headline, with Power In and Power Out beneath it |
| Centre | Speed in km/h, large, with the GNSS fix state under it (3D fix, 2D fix, No fix, or --- when the receiver is silent)
|
| Right column | State of Charge in percent as the headline, over a 2 × 2 grid of temperatures: Motor, Driver, the hottest MPPT, and the hottest battery thermistor |
| Under the speed | Four warning icons, each in a fixed slot |
| Bottom row | Current Time, Race Time, Time to Empty |
| Top line, small | Firmware version and git hash, centred; the datalogger's WiFi IP on the left when it reports one |
Power. Currents leaving the battery are negative on the bus, so the three figures are: Power In = charge current × pack voltage, Power Out = (motor + peripherals) × pack voltage, and Net Power = the sum of all three. A positive net power means the battery is charging.
Temperatures. The MPPT cell names the unit it is showing — MPPT F3 forward, MPPT R0 aft — because it always shows whichever MPPT is currently hottest. The Motor cell reads the standalone NTC node on 0x219 only; the VESC reports a motor temperature too, but it is broken on this boat and is deliberately never used as a fallback.
Missing data. Every value has a 5 second staleness timeout. If nothing has arrived for a value within that window it draws as dashes. Dashes therefore mean "not being reported", not "zero".
Warning icons
Each icon appears only while its condition holds, in its own fixed slot, so a warning is always in the same place.
| Icon | Raised when |
|---|---|
| Battery | Battery state, charge FET or discharge FET is anything other than normal |
| Low charge | State of charge below 15 % |
| Over-temperature | Motor above 50 °C, driver above 70 °C, any MPPT above 80 °C, or a battery thermistor above 45 °C |
| Throttle | The throttle reports any error flag |
A stale or missing value never raises an icon — the dashes already show that data is being lost.
Hardware
The board is the Altium project AKD-CAN_Interface, the same PCB as the height sensor controller. Where that board fits four RS-485 channels, this one fits the e-paper panel on SPI2.
| Signal | Pin |
|---|---|
| SPI2 SCK / MOSI / MISO | PB13 / PB15 / PB14 |
| Chip select | PC6 |
| Data/command | PC9 |
| Reset | PC8 |
| Busy | PA8 |
| Panel power | PB12, held on for the life of the firmware |
SPI runs at 2.5 MHz and is DMA-backed, so a full framebuffer transfer (~86 ms) does not block the CAN receive task. Those DMA channels — DMA1_CH4/CH5 — are the only pair SPI2 can use, and they are the ones I²C2 would need, which is why this board has no onboard temperature sensor where its sister boards do.
24 V arrives on the CAN connector and is stepped down on the board, exactly as on the height sensor unit. The safety line passes straight through and is not connected to the microcontroller.
Refreshing
E-paper is slow and wears with every drive cycle, so the firmware avoids refreshes it does not need:
- The framebuffer is hashed after each render. An identical picture is not sent to the panel at all.
- Every 60th refresh is a full refresh, which clears the ghosting that the fast differential refresh accumulates. The other 59 are differential.
- A refresh takes between 0.7 and 2.5 seconds; the render itself costs about half a second. The loop is floored at one second per iteration.
So the screen updates roughly once a second while values are changing, and sits still when they are not.
CAN
The dashboard uses an accept-all hardware filter — it needs the whole bus — and decodes the identifiers it knows about. Everything else is ignored.
| Source | Identifiers | Used for |
|---|---|---|
| BMS | 0x100–0x107 |
State of charge, pack voltage, the three currents, temperatures, battery/charge/discharge states |
| GNSS | 0x200, 0x201, 0x204 |
Fix state, speed, clock |
| VESC | 0x1009 |
Driver (FET) temperature |
| Motor NTC node | 0x219 |
Motor temperature |
| MPPT / GaN MPPT | 0x400–0x4FF, 0x700–0x77F |
MPPT temperatures, per-panel power |
| Throttle | 0x1337 |
Throttle error flags |
| Datalogger | 0x205 |
WiFi IP shown on the stamp line |
The full bus-wide reference is CAN_MESSAGES.md.
The dashboard originates no traffic. The only frames it ever sends are answers to the host's bootloader-protocol queries.
Firmware update over CAN
The first 80 KB of flash holds a CAN bootloader, so the board can be updated in the cockpit without a debug probe. It owns application type 0x03, giving it the identifier block 0x037 (command) / 0x038 (response) / 0x039 (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 dashboard # target comes from the ELF
eoi-flash-tool --board dashboard version # which build is on it?The version and git hash of whatever is running are also printed along the top of the screen, so the running build can be read off the panel without a laptop.
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.
Working on the screen without hardware
The whole layout lives in the draw-display crate and is shared by the firmware, a desktop simulator and a Raspberry Pi framebuffer tool — so a screen change can be seen without flashing anything.
cd eoi-can-display-simulator
cargo run -- -ccan0 # live, against a real bus
EG_SIMULATOR_DUMP=screenshot.png cargo run # write a PNG insteadThe layout is checked at compile time: the positions are const arithmetic, and assertions fail the build if two regions would overlap or a value would not fit its cell. Font metrics are pinned by tests, so a regenerated font that drifts breaks the build rather than the picture.
A second layout, foiling, exists for the foiling trim screen and runs on its own board. Same code, same decoder, different arrangement — pick it with --layout foiling in the simulator.
Diagnostics
| Symptom | Meaning |
|---|---|
| Green LED toggling once per second | Application running normally |
| Blue LED flickering | CAN frames arriving — it toggles once per frame |
| Red LED on briefly | A panel refresh is in progress |
| Red LED double-flashing | Sitting in the bootloader, waiting or flashing |
| Everything reads dashes | Nothing is arriving on the bus. Check bus wiring and termination before suspecting the board. |
| One column reads dashes | That node is silent; the dashboard is fine |
| Screen frozen but LEDs normal | The picture has not changed — identical frames are deliberately skipped |
| Ghosting or grey smears | Normal between full refreshes; cleared by the next one, at most 60 refreshes away |
A 4-second independent hardware watchdog is petted once per second, so wedged firmware resets itself. If no frames arrive for several loop iterations the firmware logs the CAN peripheral state and re-arms the receive interrupts by itself. Detailed logging is available over SWD through defmt.
Known gaps
- Race Time is empty. The block is drawn and labelled, but nothing on the bus signals a race start, so it always shows dashes.
- Time to Empty is empty. Nothing calculates or broadcasts it yet. The BMS field exists but is not fed to the screen.
- The clock is hardcoded to UTC+2. Correct for Dutch summer time and wrong by an hour the rest of the year. There is no time zone handling.
- No onboard temperature sensor. Unavoidable on this board — SPI2's DMA channels are the ones I²C2 would need. The dashboard therefore reports no board temperature of its own.
- No user input. The board has a sealed rotary switch fitted, but no firmware reads it. The screen cannot be paged or configured from the cockpit.
- Per-panel MPPT power is decoded but not shown. The data is collected and available to the layout; there is no room for it on the dashboard screen.
See also
- Height sensors — the same PCB, in its other configuration
- Rudder controller — sister board, and the full bootloader protocol
- CAN-bus — bus wiring, cable pinout and the bus-wide identifier map
- Battery — where the power and state of charge figures come from
- Instrumentation Panel — the switch panel in the same cockpit
- Datalogger — records the same bus the dashboard displays
- Altium PCB — house PCB design conventions