<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.engineersofinnovation.nl/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Aran+Dokoupil</id>
	<title>'Engineers of Innovation Wiki' - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.engineersofinnovation.nl/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Aran+Dokoupil"/>
	<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/wiki/Special:Contributions/Aran_Dokoupil"/>
	<updated>2026-09-20T09:44:51Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=File:Vesc2_top.png&amp;diff=351</id>
		<title>File:Vesc2 top.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=File:Vesc2_top.png&amp;diff=351"/>
		<updated>2026-09-15T17:51:58Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=File:Vesc2_nocase.png&amp;diff=350</id>
		<title>File:Vesc2 nocase.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=File:Vesc2_nocase.png&amp;diff=350"/>
		<updated>2026-09-15T17:42:17Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaign&amp;diff=347</id>
		<title>R&amp;S Measurement Campaign</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaign&amp;diff=347"/>
		<updated>2026-09-07T15:15:22Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= GaN MPPT Phase: R&amp;amp;S / ZES Measurement Campaign =&lt;br /&gt;
&lt;br /&gt;
Procedures for the measurement campaign on the '''EOI-A65-6A''' GaN MPPT phase boards&lt;br /&gt;
using the sponsored Rohde &amp;amp; Schwarz and ZES ZIMMER equipment. Each experiment is written&lt;br /&gt;
to be run start-to-finish by one engineer and to produce both an engineering result and a&lt;br /&gt;
publishable figure.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
Three goals, in priority order:&lt;br /&gt;
&lt;br /&gt;
# '''Resolve open engineering questions.''' Idle dissipation, dead-time optimum, and output current-sense accuracy are all unresolved and all measurable with this equipment.&lt;br /&gt;
# '''Calibrate and characterise the product.''' The default configuration ships with &amp;lt;code&amp;gt;calibrated = false&amp;lt;/code&amp;gt; and placeholder sensor gains.&lt;br /&gt;
# '''Produce publishable content.''' Each experiment below defines its hero shot and which instrument capability makes it possible.&lt;br /&gt;
&lt;br /&gt;
An experiment only belongs in this campaign if the instrument is load-bearing, i.e. the&lt;br /&gt;
result is not obtainable with ordinary bench gear. Otherwise it is just a measurement, not&lt;br /&gt;
a story.&lt;br /&gt;
&lt;br /&gt;
== Equipment ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Instrument !! Role !! Capability that matters here&lt;br /&gt;
|-&lt;br /&gt;
| ZES ZIMMER LMG671 || Power / efficiency reference || 0.015%-class accuracy; up to 7 simultaneously-sampled channels; microwatt-resolution DC; transient recorder&lt;br /&gt;
|-&lt;br /&gt;
| R&amp;amp;S MXO34 || Waveform + spectrum || 12-bit always-on; ~4.5 M acquisitions/s; ~45 k FFT/s live spectrum; zone trigger; deep memory&lt;br /&gt;
|-&lt;br /&gt;
| R&amp;amp;S RT-ZISO || Isolated probe || Galvanic isolation with CMRR that survives nanosecond edges. There is no gate node on this board, so its job here is the bootstrap rail, high-side V&amp;lt;sub&amp;gt;DS&amp;lt;/sub&amp;gt; and the current-shunt terminals; see [[#Floating nodes and what the isolated probe is for|Floating nodes]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Before promising numbers publicly''', confirm the exact bandwidth and isolation rating&lt;br /&gt;
of the RT-ZISO model supplied, and confirm that the LMG671 includes the transient-recording&lt;br /&gt;
and data-logger options. Experiments 4, 5 and the tracking-efficiency work depend on those&lt;br /&gt;
options.&lt;br /&gt;
&lt;br /&gt;
== Device under test ==&lt;br /&gt;
&lt;br /&gt;
Values below are taken from &amp;lt;code&amp;gt;Open-SEC Firmware/src/hardware/eoia656a.h&amp;lt;/code&amp;gt; and&lt;br /&gt;
&amp;lt;code&amp;gt;eoia656a.c&amp;lt;/code&amp;gt;. Re-check them against the branch under test before quoting them.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Parameter !! Value !! Source&lt;br /&gt;
|-&lt;br /&gt;
| Hardware name || EOI-A65-6A || &amp;lt;code&amp;gt;HW_NAME&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Topology || Synchronous boost, GaN half bridge (EPC23102) || &amp;lt;code&amp;gt;HW_TOPOLOGY_BOOST&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Gate drive || Integrated in the EPC23102 ePower stage. '''Neither gate is brought out of the package.''' The only accessible signals are two ground-referenced 3.3 V logic inputs || &amp;lt;code&amp;gt;P2_PWM_LS&amp;lt;/code&amp;gt; (PA10), &amp;lt;code&amp;gt;P2_EN_HS&amp;lt;/code&amp;gt; (PA11), &amp;lt;code&amp;gt;HW_NO_AND_GATE&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Switching frequency || 100 kHz || &amp;lt;code&amp;gt;HW_SWITCHINGFREQUENCY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Control loop rate || 20 kHz (T&amp;lt;sub&amp;gt;s&amp;lt;/sub&amp;gt; = 50 us) || &amp;lt;code&amp;gt;HW_CONTROLLERFREQUENCY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Dead time (rising / falling) || 8 ns / 8 ns || &amp;lt;code&amp;gt;HW_DEADTIMERISING&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_DEADTIMEFALLING&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Inductor || &amp;lt;code&amp;gt;HW_L&amp;lt;/code&amp;gt; = 38 uH, the value at the 8 A operating point, not the 47 uH nominal of L1 (7443634700). DCR 12 mOhm || &amp;lt;code&amp;gt;HW_L&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_RLINT&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Declared C&amp;lt;sub&amp;gt;low&amp;lt;/sub&amp;gt; / C&amp;lt;sub&amp;gt;high&amp;lt;/sub&amp;gt; || 100 uF / 70 uF. C&amp;lt;sub&amp;gt;high&amp;lt;/sub&amp;gt; is the per-phase share of the ~560 uF baseboard bus (3 x 180 uF plus ceramics) divided across 8 slots, not the physical bulk || &amp;lt;code&amp;gt;HW_CLOW&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_CHIGH&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Overvoltage fault (both rails) || 63 V || &amp;lt;code&amp;gt;HW_LIMIT_HS_VOLTAGE_HARD&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Overcurrent fault (both rails) || 13 A || &amp;lt;code&amp;gt;HW_LIMIT_HS_CURRENT_HARD&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Soft current limit || 9 A || &amp;lt;code&amp;gt;HighSideCurrentLimitSoft&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Current sensors || 2 x INA253A2, 2 mOhm integrated shunt, 200 mV/A, zero at 0.5 V || Isense.SchDoc&lt;br /&gt;
|-&lt;br /&gt;
| Phase count per baseboard || 8, interleaved via &amp;lt;code&amp;gt;PHASE_EN&amp;lt;/code&amp;gt;; startup staggered 200 ms per CAN ID || &amp;lt;code&amp;gt;mppt.c&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Temperature sensors || NT1 = ambient, NT2 = FET/heatsink, 100 k NTC, B = 4330 || &amp;lt;code&amp;gt;Temperature_B&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The GaN devices are rated 100 V. The project README recommends '''75 V nominal maximum'''&lt;br /&gt;
for system use. Do not exceed that during these experiments regardless of what the firmware&lt;br /&gt;
fault thresholds allow.&lt;br /&gt;
&lt;br /&gt;
== Safety and bench hygiene ==&lt;br /&gt;
&lt;br /&gt;
=== Grounding ===&lt;br /&gt;
&lt;br /&gt;
'''Verify the grounding topology on your specific board before connecting any&lt;br /&gt;
ground-referenced instrument.'''&lt;br /&gt;
&lt;br /&gt;
On the EOI-A65-6A the INA253 sense elements sit '''in the positive rail'''&lt;br /&gt;
(&amp;lt;code&amp;gt;PANEL_IN+ -&amp;gt; Isense+ / Isense- -&amp;gt; Vin&amp;lt;/code&amp;gt;, and likewise on the output), so input&lt;br /&gt;
and output grounds are common. Single-ended, ground-referenced probing of the switch node&lt;br /&gt;
is therefore permissible.&lt;br /&gt;
&lt;br /&gt;
This differs from the older SEC-B80-8A described in the repository README, which uses&lt;br /&gt;
'''ground-path''' current sensing. On that hardware, shorting input and output grounds&lt;br /&gt;
together, which any two ground-referenced scope probes will do, '''can destroy the board'''.&lt;br /&gt;
If there is any doubt which hardware is on the bench, use the RT-ZISO and treat every node&lt;br /&gt;
as floating.&lt;br /&gt;
&lt;br /&gt;
=== Probing rules ===&lt;br /&gt;
&lt;br /&gt;
* '''There is no gate node on this board.''' The EPC23102 integrates both gate drivers, so neither V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; is accessible with any probe. What leaves the MCU is two ground-referenced 3.3 V logic lines, &amp;lt;code&amp;gt;P2_PWM_LS&amp;lt;/code&amp;gt; (PA10) and &amp;lt;code&amp;gt;P2_EN_HS&amp;lt;/code&amp;gt; (PA11), driven by HRTIM TB1/TB2 with hardware dead-time insertion. Since the AND gate was removed (&amp;lt;code&amp;gt;HW_NO_AND_GATE&amp;lt;/code&amp;gt;) these reach the driver directly, so they show the '''commanded''' dead time as the driver receives it. A 10:1 passive probe is sufficient; the isolated probe is wasted here.&lt;br /&gt;
* '''Never''' reference a ground-referenced probe to a node that floats to SW. That rules out the bootstrap rail, high-side V&amp;lt;sub&amp;gt;DS&amp;lt;/sub&amp;gt; and the shunt terminals. Use the RT-ZISO for those, see [[#Floating nodes and what the isolated probe is for|Floating nodes]].&lt;br /&gt;
* At ~55 V with nanosecond edges, probe ground lead length dominates the measurement. Use a ground spring, not a lead, for switch-node captures.&lt;br /&gt;
* The LMG671 inputs are individually isolated; multi-channel connection across the converter is safe.&lt;br /&gt;
* De-skew all channels before any v*i product or timing measurement. Record the de-skew values in the log.&lt;br /&gt;
&lt;br /&gt;
=== Electrical ===&lt;br /&gt;
&lt;br /&gt;
* Treat the DC bus as hazardous above 50 V. Discharge the output bulk capacitance before rework; the baseboard carries substantial bulk on the battery side.&lt;br /&gt;
* Set the source current limit at or below 9 A per phase before enabling the output.&lt;br /&gt;
* Keep a thermal camera or the on-board NTC telemetry visible during any run that changes dead time or disables protections.&lt;br /&gt;
&lt;br /&gt;
== Common bench setup ==&lt;br /&gt;
&lt;br /&gt;
=== Connections ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Node !! Instrument !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Panel input (V, I) || LMG671 ch1 || Kelvin sense at the board connector, not at the supply&lt;br /&gt;
|-&lt;br /&gt;
| Battery output (V, I) || LMG671 ch2 || Same reference plane convention as the input&lt;br /&gt;
|-&lt;br /&gt;
| Individual phase outputs || LMG671 ch3 to ch7 || Up to 5 phases alongside both terminals; see channel budget note&lt;br /&gt;
|-&lt;br /&gt;
| Switch node (SW) || MXO34 ch1 || Ground spring, shortest possible loop. This channel carries the dead-time information, see [[#Experiment 3: Dead-time optimisation|Experiment 3]]&lt;br /&gt;
|-&lt;br /&gt;
| Driver logic input &amp;lt;code&amp;gt;P2_PWM_LS&amp;lt;/code&amp;gt; (PA10) || MXO34 ch2 || Ground-referenced 3.3 V logic, 10:1 passive probe. A driver input, '''not''' a gate&lt;br /&gt;
|-&lt;br /&gt;
| Floating node under test || RT-ZISO on MXO34 ch3 || Bootstrap rail, high-side V&amp;lt;sub&amp;gt;DS&amp;lt;/sub&amp;gt;, shunt terminals or inductor voltage; pick per experiment from [[#Floating nodes and what the isolated probe is for|Floating nodes]]. When the isolated probe is not in use, put &amp;lt;code&amp;gt;P2_EN_HS&amp;lt;/code&amp;gt; (PA11) here to capture both logic edges and read the commanded dead time directly&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; (INA253 output, before R12) || MXO34 ch4 || Pre-filter node; this is where clipping is visible&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Channel budget:''' the LMG671 provides at most 7 channels. With both terminals&lt;br /&gt;
instrumented, 5 phases can be measured simultaneously. To capture all 8 phase currents at&lt;br /&gt;
once, drop the terminal channels and derive totals by summation.&lt;br /&gt;
&lt;br /&gt;
=== Floating nodes and what the isolated probe is for ===&lt;br /&gt;
&lt;br /&gt;
Because no gate is accessible, the RT-ZISO's job on this board is a different set of&lt;br /&gt;
measurements. Ranked by how much the result depends on the probe being isolated rather than&lt;br /&gt;
merely differential:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! # !! Node !! What it yields !! Caveat&lt;br /&gt;
|-&lt;br /&gt;
| 1 || Bootstrap rail, BOOT - SW || The closest available proxy for high-side drive health: C&amp;lt;sub&amp;gt;BOOT&amp;lt;/sub&amp;gt; droop per cycle, refresh behaviour, and margin to the high-side UVLO at the D ~ 0.7 end of the boost range || Only if the bootstrap capacitor is external and probeable on this layout. '''Confirm on the schematic before planning around it''', some ePower stages integrate it. See [[#Open items]]&lt;br /&gt;
|-&lt;br /&gt;
| 2 || High-side V&amp;lt;sub&amp;gt;DS&amp;lt;/sub&amp;gt;, VIN - SW || The high-side device's E&amp;lt;sub&amp;gt;on&amp;lt;/sub&amp;gt; and E&amp;lt;sub&amp;gt;off&amp;lt;/sub&amp;gt;. Ground-referenced probing can only ever reach the low-side device || De-skew against the current channel before any v*i integration, and state the de-skew value with the result&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Shunt terminals, Isense+ - Isense- || Separates &amp;quot;the shunt current genuinely has flat tops&amp;quot; from &amp;quot;the INA253 output stage is clipping or slew-limited&amp;quot;. That is the question [[#Experiment 2: Current-sense verification and calibration|Experiment 2]] currently answers from one side only || 2 mOhm at 13 A is 26 mV differential riding on a 55 V slewing common mode. Needs the RT-ZISO's CMRR; an ordinary 1000:1 HV differential probe will not resolve it. Kelvin access to both terminals must be confirmed&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Inductor voltage, SW - output node || V&amp;lt;sub&amp;gt;L&amp;lt;/sub&amp;gt;/L integrates to inductor current with no current probe and no shunt bandwidth limit, and it measures L '''at the operating point''' || See the note below&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Package PGND - analogue GND || Power-loop ground bounce at nanosecond edges. Turns the ground-spring rule into a number and probably accounts for part of the current-sense noise floor || Small differential signal against a large common mode; average over many acquisitions&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''On item 4:''' &amp;lt;code&amp;gt;HW_L&amp;lt;/code&amp;gt; is set to 38 uH against a 47 uH nominal part, deliberately,&lt;br /&gt;
because the current-limit law's effective per-step gain scales with&lt;br /&gt;
&amp;lt;code&amp;gt;HW_L&amp;lt;/code&amp;gt;/L&amp;lt;sub&amp;gt;actual&amp;lt;/sub&amp;gt;. That derating has never been checked on the bench.&lt;br /&gt;
Measuring inductor voltage gives L at the 8 A operating point directly and either confirms the&lt;br /&gt;
constant or corrects it, which makes this the cheapest firmware-relevant result in the&lt;br /&gt;
campaign.&lt;br /&gt;
&lt;br /&gt;
If gate-level waveforms are wanted for publication, the only route is a discrete GaN plus&lt;br /&gt;
external driver test vehicle, or an EPC evaluation board carrying the same die. It cannot be&lt;br /&gt;
done on EOI-A65-6A, and no probe changes that.&lt;br /&gt;
&lt;br /&gt;
=== Firmware preparation ===&lt;br /&gt;
&lt;br /&gt;
# Build the &amp;lt;code&amp;gt;HW_EOIA65_6A&amp;lt;/code&amp;gt; target in the '''Debug''' configuration. Do not use the Simulation build, which substitutes a model for the power stage.&lt;br /&gt;
# Flash via ST-Link and TAG-Connect (J2, TC2030-NL).&lt;br /&gt;
# Connect USB for the serial terminal. Available commands: &amp;lt;code&amp;gt;help&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ping&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;sens&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;hwinfo&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_read&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_write&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_default&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Record the firmware version and git commit hash in the log before every session.&lt;br /&gt;
&lt;br /&gt;
=== Telemetry available without instruments ===&lt;br /&gt;
&lt;br /&gt;
* Serial terminal: &amp;lt;code&amp;gt;sens&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;calibrate_mppt.py PORT --read-val&amp;lt;/code&amp;gt; polls &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Ihigh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Ilow&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vlow&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vhigh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TempHeatsink&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TempAmbient&amp;lt;/code&amp;gt; at 2 Hz.&lt;br /&gt;
* CAN: status frame every 1000 ms, power frame every 500 ms. See &amp;lt;code&amp;gt;MPPT_ID32+0-4.dbc&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Firmware scope buffer: &amp;lt;code&amp;gt;scope_start()&amp;lt;/code&amp;gt;, up to &amp;lt;code&amp;gt;CONVERTER_SCOPE_CHANNELS&amp;lt;/code&amp;gt; channels sampled at the 20 kHz control rate.&lt;br /&gt;
&lt;br /&gt;
The firmware scope is the natural cross-validation target for the LMG671, see&lt;br /&gt;
[[#Experiment 2: Current-sense verification and calibration|Experiment 2]].&lt;br /&gt;
&lt;br /&gt;
== Experiment 1: Idle power teardown ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' a phase board reaches ~55 C with no power being harvested. Where does every&lt;br /&gt;
milliwatt go?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' LMG671. Microwatt-resolution DC power is what makes the decomposition&lt;br /&gt;
credible.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' waterfall chart attributing the total idle dissipation to each contributor,&lt;br /&gt;
with the measured board temperature alongside each step.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Bring both rails to the nominal operating point (e.g. 35 V in, 55 V out) with the board held in reset. Record LMG671 readings on the 3v3 rail, the 5v2 rail and both HV rails. '''This is the absolute floor''', resistive dividers and leakage only.&lt;br /&gt;
# Release reset with &amp;lt;code&amp;gt;outputEnalbeOnStartup = false&amp;lt;/code&amp;gt;. Delta from step 1 = MCU plus analogue front end.&lt;br /&gt;
# Set the MPPT to disabled (&amp;lt;code&amp;gt;MpptState_Disable&amp;lt;/code&amp;gt;) so &amp;lt;code&amp;gt;DREN&amp;lt;/code&amp;gt; is de-asserted and the bridge is not switching. Confirm via &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;. Delta from step 2 should be near zero; a large delta means something is switching that should not be.&lt;br /&gt;
# Enable the output with no input power available. Delta from step 3 = power-stage idle loss plus any reverse power flow.&lt;br /&gt;
# At each step, log &amp;lt;code&amp;gt;TempHeatsink&amp;lt;/code&amp;gt; (NT2) and &amp;lt;code&amp;gt;TempAmbient&amp;lt;/code&amp;gt; (NT1) after a 20-minute thermal soak, plus a thermal camera frame.&lt;br /&gt;
# '''Check &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; at step 4.''' The default configuration sets &amp;lt;code&amp;gt;LowSideCurrentMinLimitSoft = -300 mA&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;PhaseHighSideEnableCurrent = -500 mA&amp;lt;/code&amp;gt;, which permit reverse inductor current. If &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; sits at -0.3 A, the phase is actively pumping power from the battery back into the panel. Record the value and compute the drain across all 8 phases.&lt;br /&gt;
# Repeat step 4 with &amp;lt;code&amp;gt;LowSideCurrentMinLimitSoft = 0&amp;lt;/code&amp;gt; to quantify the reverse-flow contribution in isolation.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
Roughly 0.5 to 1.2 W per phase total, with the power stage accounting for the large&lt;br /&gt;
majority and the MCU 0.10 to 0.15 W. Reverse-current pumping, if present, is the single&lt;br /&gt;
largest term and shows up as a battery drain far larger than the on-board dissipation.&lt;br /&gt;
&lt;br /&gt;
=== Pass criteria ===&lt;br /&gt;
&lt;br /&gt;
Every measured step accounted for within 10% of the sum of its identified contributors.&lt;br /&gt;
Unattributed residual is itself a finding, so log it rather than hiding it.&lt;br /&gt;
&lt;br /&gt;
== Experiment 2: Current-sense verification and calibration ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' the output current reading is known to run high. Why, and by how much?&lt;br /&gt;
&lt;br /&gt;
'''Hero instruments:''' MXO34 (12-bit, to see the pre-filter waveform), RT-ZISO (to see the&lt;br /&gt;
shunt itself) and LMG671 (as the reference for calibration).&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' two panels. The &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; waveform overlaid on the true shunt&lt;br /&gt;
voltage measured differentially across Isense+ / Isense-, both against the smooth inductor&lt;br /&gt;
current; and a scatter plot of firmware-reported versus LMG671-measured output current,&lt;br /&gt;
before and after calibration.&lt;br /&gt;
&lt;br /&gt;
=== Background ===&lt;br /&gt;
&lt;br /&gt;
The local output capacitance is C18 to C23 (6 x 1 uF 0603) plus C24. The schematic notes&lt;br /&gt;
&amp;quot;each about 130nF left at 55v&amp;quot;, so effective local capacitance at 55 V is roughly 4.8 uF&lt;br /&gt;
(|Z| approximately 0.33 Ohm at 100 kHz). The ~560 uF of bulk (3 x 180 uF, C9/C10/C65) sits on&lt;br /&gt;
the baseboard, on the far side of the output shunt, reachable only through ~2 mOhm plus interconnect&lt;br /&gt;
inductance (|Z| approximately 0.13 Ohm at 200 nH). The lower-impedance path is therefore&lt;br /&gt;
through the shunt, so roughly 70% of the bridge's pulsed current flows through the sense&lt;br /&gt;
element rather than being absorbed locally.&lt;br /&gt;
&lt;br /&gt;
The ADC samples this with a 267 ns aperture at a fixed phase locked to the PWM, so the&lt;br /&gt;
residual ripple contributes a systematic offset rather than averageable noise.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Capture &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; (MXO34 ch4) together with the low-side gate drive. Confirm the pulse train: approximately zero during D, approximately &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; during (1-D).&lt;br /&gt;
# '''Look for flat tops.''' At &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; peaks near 13 A the INA253 output demands 0.5 + 2.6 = 3.1 V, into the supply rail. If the peaks are clipped or slew-limited, the error is nonlinear and '''no amount of downstream averaging will fix it'''. This determines whether the software mitigation is worth implementing at all.&lt;br /&gt;
# '''Cross-check against the shunt itself.''' Put the RT-ZISO across Isense+ / Isense- and capture the true shunt voltage alongside &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt;. If the shunt waveform has clean peaks and &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; is flat-topped, the INA253 is the limiting element and the fix belongs in the amplifier or the sampling instant. If both are flat, the current genuinely is that shape and the fix belongs in the model. '''One capture settles which''', and neither channel alone can.&lt;br /&gt;
# Simultaneously log firmware &amp;lt;code&amp;gt;Ihigh&amp;lt;/code&amp;gt; (via &amp;lt;code&amp;gt;--read-val&amp;lt;/code&amp;gt;) and LMG671 output current across the full current range at several bus voltages. Plot the error.&lt;br /&gt;
# Repeat for &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vlow&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Vhigh&amp;lt;/code&amp;gt;. The input shunt carries continuous inductor current and should show a much smaller error; that contrast is the point.&lt;br /&gt;
# Run the calibration procedure using the LMG671 as reference: &amp;lt;code&amp;gt;python calibrate_mppt.py PORT&amp;lt;/code&amp;gt;, then menu options 3 to 10 (zero offset and gain for each of input voltage, output voltage, input current, output current), option 11 for the NTC, and '''option 12 to store to EEPROM'''.&lt;br /&gt;
# Re-run step 3 and overlay before and after.&lt;br /&gt;
&lt;br /&gt;
=== Note ===&lt;br /&gt;
&lt;br /&gt;
Calibration corrects gain and offset. It '''cannot''' correct the ripple-induced error,&lt;br /&gt;
because that error varies with duty cycle and load. Expect a residual that scales with&lt;br /&gt;
output ripple, and report it as such.&lt;br /&gt;
&lt;br /&gt;
Also worth logging: &amp;lt;code&amp;gt;phase.Ihigh&amp;lt;/code&amp;gt; is unfiltered&lt;br /&gt;
(&amp;lt;code&amp;gt;CURRENT_IN_FORGETING_FACTOR = 0&amp;lt;/code&amp;gt;) and feeds the 13 A hard fault directly, so a&lt;br /&gt;
single sample landing on a ripple peak can cause a nuisance trip. Record any spurious&lt;br /&gt;
&amp;lt;code&amp;gt;Converter_OutputOverCurrent&amp;lt;/code&amp;gt; events observed during the campaign.&lt;br /&gt;
&lt;br /&gt;
== Experiment 3: Dead-time optimisation ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' is 8 ns of dead time causing shoot-through, and what is the optimum?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' MXO34. There is no gate node to probe, so the measurement moves to the&lt;br /&gt;
switch node, and the capability that matters is the 12-bit ADC: the feature of interest is a&lt;br /&gt;
2 to 4 V step sitting on top of the 55 V output rail, resolved with nanosecond timing. An&lt;br /&gt;
8-bit scope cannot do this at full vertical range.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' three stacked panels sharing an x-axis. The switch-node reverse-conduction&lt;br /&gt;
pedestal at 8 ns versus the optimum, LMG671 efficiency versus dead time, and&lt;br /&gt;
&amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt; versus dead time. One image, three independent measurements, one causal&lt;br /&gt;
chain.&lt;br /&gt;
&lt;br /&gt;
=== Why the switch node, and not the gates ===&lt;br /&gt;
&lt;br /&gt;
GaN has no body diode. With the gate off it conducts in the third quadrant at a&lt;br /&gt;
V&amp;lt;sub&amp;gt;SD&amp;lt;/sub&amp;gt; of roughly 2 to 4 V, dependent on gate bias and current, far worse than a&lt;br /&gt;
silicon body diode.&lt;br /&gt;
&lt;br /&gt;
'''Mind the polarity: this is a boost, not a buck.''' Inductor current flows ''into'' the&lt;br /&gt;
switch node, so during either dead-time interval it must leave through the high-side device,&lt;br /&gt;
source to drain, which is reverse conduction. SW therefore '''overshoots to&lt;br /&gt;
V&amp;lt;sub&amp;gt;high&amp;lt;/sub&amp;gt; + V&amp;lt;sub&amp;gt;SD&amp;lt;/sub&amp;gt; for exactly the dead-time window''' — a pedestal sitting on&lt;br /&gt;
top of the output rail, not a dip below PGND as it would be in a buck. Both transitions show&lt;br /&gt;
it.&lt;br /&gt;
&lt;br /&gt;
If inductor current is negative, which the default configuration permits and&lt;br /&gt;
[[#Experiment 1: Idle power teardown|Experiment 1]] step 6 goes looking for, the polarity&lt;br /&gt;
inverts: the low-side device takes the reverse conduction and the pedestal appears below&lt;br /&gt;
PGND instead. That makes the pedestal a free indicator of which way the phase is actually&lt;br /&gt;
pumping.&lt;br /&gt;
&lt;br /&gt;
The pedestal gives three things a gate waveform would not:&lt;br /&gt;
&lt;br /&gt;
* '''Pedestal width = actual effective dead time''', including driver propagation delay and any mismatch between the two channels. This is a bench-side confirmation of the &amp;lt;code&amp;gt;DTxR&amp;lt;/code&amp;gt; value rather than a debugger register read, and it is the only way to see the delay the driver itself adds.&lt;br /&gt;
* '''Pedestal area x I&amp;lt;sub&amp;gt;L&amp;lt;/sub&amp;gt; x f&amp;lt;sub&amp;gt;sw&amp;lt;/sub&amp;gt; = reverse-conduction loss in watts''', computed straight off the waveform and plottable on the same axes as the LMG671 efficiency curve. The loss mechanism and the efficiency penalty are then measured independently and shown to agree, which is a stronger claim than either alone.&lt;br /&gt;
* '''Shoot-through signature''': the pedestal disappears, the SW transition goes soft, and input current steps up at fixed load and fixed output voltage.&lt;br /&gt;
&lt;br /&gt;
=== Prerequisite ===&lt;br /&gt;
&lt;br /&gt;
The dead-time register write in &amp;lt;code&amp;gt;pwm.c&amp;lt;/code&amp;gt; previously OR-ed the computed value onto&lt;br /&gt;
the HAL default, so most settings landed on the wrong value. '''This must be fixed before&lt;br /&gt;
the sweep is meaningful.''' With the fix in place the commanded value is written directly.&lt;br /&gt;
&lt;br /&gt;
Historical behaviour, for reference. Note that 15 ns and 20 ns were previously identical on&lt;br /&gt;
hardware, so any earlier sweep would have shown no difference between them:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Commanded !! Register count !! Actual, before fix !! Actual, after fix&lt;br /&gt;
|-&lt;br /&gt;
| 8 ns || 10 || 8.33 ns || 8.33 ns&lt;br /&gt;
|-&lt;br /&gt;
| 10 ns || 12 || 11.67 ns || 10.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 15 ns || 18 || '''21.67 ns''' || 15.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 20 ns || 24 || '''21.67 ns''' || 20.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 30 ns || 36 || '''38.33 ns''' || 30.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 40 ns || 48 || '''48.33 ns''' || 40.00 ns&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Dead-time resolution is 1/(f&amp;lt;sub&amp;gt;HRTIM&amp;lt;/sub&amp;gt; x 8) = 0.83 ns per count at 150 MHz. If the&lt;br /&gt;
system clock is changed, the quantisation changes with it.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Verify the fix in the register: set &amp;lt;code&amp;gt;HW_DEADTIMERISING&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;HW_DEADTIMEFALLING&amp;lt;/code&amp;gt; to 20 ns, halt, and read &amp;lt;code&amp;gt;HRTIM1-&amp;gt;sTimerxRegs[1].DTxR&amp;lt;/code&amp;gt;. Expect DTR and DTF fields = 24, not 26.&lt;br /&gt;
# Verify it again on the bench: capture SW with a ground spring and measure the pedestal width, with &amp;lt;code&amp;gt;P2_PWM_LS&amp;lt;/code&amp;gt; on ch2 for reference. The two should agree once driver propagation delay is subtracted. '''Record that delay''', it is a fixed offset that applies to every point in the sweep.&lt;br /&gt;
# Establish a fixed operating point (for example 35 V in, 55 V out, 4 A per phase) and let it thermally soak for 20 minutes.&lt;br /&gt;
# For each dead-time value in 5, 8, 10, 15, 20, 25, 30, 40 ns: rebuild, flash, soak 20 min, then record LMG671 efficiency, &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Tambient&amp;lt;/code&amp;gt;, and an MXO34 capture of SW plus the driver logic input plus inductor current.&lt;br /&gt;
# From each SW capture extract pedestal width and pedestal depth, and compute reverse-conduction loss as pedestal area x inductor current x switching frequency.&lt;br /&gt;
# Plot efficiency, computed reverse-conduction loss and temperature against dead time on a shared axis. The computed loss should track the efficiency roll-off at the long-dead-time end. If it does not, something else dominates, and that is itself the finding.&lt;br /&gt;
# At the short-dead-time end, watch for the pedestal vanishing and for a step in input current at fixed load and fixed output. That is shoot-through, visible without ever seeing a gate.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
A U-shaped efficiency curve. Too little dead time causes shoot-through; too much forces the&lt;br /&gt;
inductor current through the high-side device in reverse-conduction mode at a significantly&lt;br /&gt;
higher voltage drop. GaN devices have no body diode, so the reverse-conduction penalty at&lt;br /&gt;
excessive dead time is steeper than for a silicon bridge, and it is directly visible as a&lt;br /&gt;
pedestal on SW that both deepens and widens as dead time increases.&lt;br /&gt;
&lt;br /&gt;
=== Safety ===&lt;br /&gt;
&lt;br /&gt;
Below 8 ns, shoot-through risk is real. Start at reduced bus voltage and reduced current,&lt;br /&gt;
watch &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt; continuously, and abort on any rapid temperature rise.&lt;br /&gt;
&lt;br /&gt;
== Experiment 4: Interleaving ripple cancellation ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' does the 8-phase interleaving actually cancel bus ripple as designed?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' MXO34. Roughly 45 k FFT/s makes this live rather than a slideshow of&lt;br /&gt;
captures.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' video of the live input-ripple spectrum while the interleaving is dialled&lt;br /&gt;
from all-in-phase to fully staggered. The 100 kHz fundamental and its harmonics collapse in&lt;br /&gt;
real time and 800 kHz emerges. This is the most visually striking result available from this&lt;br /&gt;
hardware.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Load all 8 phases at a common operating point.&lt;br /&gt;
# Configure the MXO34 for live FFT of the battery-bus ripple current, span covering 50 kHz to 2 MHz.&lt;br /&gt;
# Baseline: force all phases in phase (identical &amp;lt;code&amp;gt;PHASE_EN&amp;lt;/code&amp;gt; alignment). Capture the spectrum.&lt;br /&gt;
# Step the interleaving toward the designed 8-way stagger. Record continuously.&lt;br /&gt;
# Capture the final staggered spectrum and measure the attenuation of the 100 kHz component and the amplitude of the new 800 kHz component.&lt;br /&gt;
# '''Companion capture:''' LMG671 transient recorder at 10 MS/s on as many phase currents as channels allow, giving 8 staggered triangle waves in one frame. Note the channel budget constraint.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
Substantial attenuation of the 100 kHz fundamental with energy reappearing at&lt;br /&gt;
8 x 100 kHz = 800 kHz. Quantify rather than asserting; imperfect current sharing between&lt;br /&gt;
phases limits the achievable cancellation, and that mismatch is itself a useful result.&lt;br /&gt;
&lt;br /&gt;
== Experiment 5: Efficiency map and phase shedding ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' what is peak efficiency, with what uncertainty, and what is the optimal&lt;br /&gt;
number of active phases at partial load?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' LMG671. At 25 W per phase the differences between shedding schedules&lt;br /&gt;
are fractions of a percent, so accuracy is the whole justification.&lt;br /&gt;
&lt;br /&gt;
'''Hero shots:''' an efficiency contour map over the operating envelope with an explicit&lt;br /&gt;
uncertainty budget; and a family of 8 efficiency curves whose crossing points define the&lt;br /&gt;
shedding schedule.&lt;br /&gt;
&lt;br /&gt;
=== Procedure: efficiency map ===&lt;br /&gt;
&lt;br /&gt;
# Define the reference planes precisely and document them. State whether connector and cable losses are attributed to the converter. '''This decision must be stated in any published figure.'''&lt;br /&gt;
# Sweep input voltage 20 to 55 V and per-phase current 0 to 8 A on a grid. Soak to thermal equilibrium at each point.&lt;br /&gt;
# Record LMG671 input power, output power, efficiency, and both NTC temperatures.&lt;br /&gt;
# Produce a contour map. Compare against the existing plots in &amp;lt;code&amp;gt;Measurements/&amp;lt;/code&amp;gt; (50 V, 64 V, 72 V) as a sanity check on the older hardware.&lt;br /&gt;
&lt;br /&gt;
=== Procedure: phase shedding ===&lt;br /&gt;
&lt;br /&gt;
# Sweep total system power from ~100 W to full rated.&lt;br /&gt;
# At each power level, measure efficiency with N = 1, 2, 4, 6, 8 phases active.&lt;br /&gt;
# Plot the family of curves and locate the crossing points.&lt;br /&gt;
# Derive the optimal shedding schedule and '''implement it in firmware'''.&lt;br /&gt;
# Re-measure to confirm the predicted partial-load gain.&lt;br /&gt;
&lt;br /&gt;
=== Uncertainty budget ===&lt;br /&gt;
&lt;br /&gt;
At 99% efficiency the uncertainty budget is the result. Document:&lt;br /&gt;
&lt;br /&gt;
* Instrument accuracy specification at the actual operating point, not the headline figure.&lt;br /&gt;
* Sense-lead placement and what is included inside the measurement boundary.&lt;br /&gt;
* Thermal drift over a 30-minute soak, measured by repeating one point.&lt;br /&gt;
* Repeatability across at least 3 independent runs of the same point.&lt;br /&gt;
&lt;br /&gt;
A separate write-up on how not to overstate efficiency, derived from this section, would be&lt;br /&gt;
strong content in its own right and costs nothing extra to produce.&lt;br /&gt;
&lt;br /&gt;
== Optional experiments ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Experiment !! Procedure summary !! Hero instrument&lt;br /&gt;
|-&lt;br /&gt;
| '''Rare-event hunt''' || Zone trigger on SW-node overshoot above ~75 V. Run for hours across all 8 phases. Build a histogram of peak SW voltage over ~10&amp;lt;sup&amp;gt;11&amp;lt;/sup&amp;gt; switching cycles and extract the worst outliers. || MXO34, since ~4.5 M acquisitions/s is the only practical way to catch one-in-10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; events&lt;br /&gt;
|-&lt;br /&gt;
| '''True switching energy''' || De-skew carefully, then integrate v*i per transition. The low-side device is ground-referenced (SW to PGND); the high-side device needs VIN - SW on the RT-ZISO. Plot E&amp;lt;sub&amp;gt;on&amp;lt;/sub&amp;gt; and E&amp;lt;sub&amp;gt;off&amp;lt;/sub&amp;gt; against current and compare to the EPC23102 datasheet, noting that the datasheet figures are die-level and this measurement includes package and layout parasitics. || RT-ZISO plus MXO34&lt;br /&gt;
|-&lt;br /&gt;
| '''Bootstrap rail integrity''' || RT-ZISO on BOOT - SW while sweeping duty cycle up to the D = 0.7 ceiling imposed by &amp;lt;code&amp;gt;HW_IOUT_MAXDUTY&amp;lt;/code&amp;gt;. Plot per-cycle C&amp;lt;sub&amp;gt;BOOT&amp;lt;/sub&amp;gt; droop and the margin remaining to the high-side UVLO. Since no gate is observable, this is the only direct evidence that high-side drive stays healthy at high step-up ratio. || RT-ZISO&lt;br /&gt;
|-&lt;br /&gt;
| '''Inductor characterisation in situ''' || RT-ZISO across the inductor, integrate V&amp;lt;sub&amp;gt;L&amp;lt;/sub&amp;gt; to get ripple current and solve for L across the 0 to 8 A range. Compare against the &amp;lt;code&amp;gt;HW_L&amp;lt;/code&amp;gt; = 38 uH constant the current-limit law depends on. || RT-ZISO plus MXO34&lt;br /&gt;
|-&lt;br /&gt;
| '''MPPT tracking efficiency''' || Play a real cloudy-day irradiance profile into a solar array simulator. Compute energy captured divided by theoretical MPP energy, a different metric from conversion efficiency and rarely published honestly. || LMG671 data logger&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The tracking-efficiency experiment requires a solar array simulator, which is '''not&lt;br /&gt;
currently on the bench'''. The firmware contains an &amp;lt;code&amp;gt;Ipvmodel&amp;lt;/code&amp;gt; in&lt;br /&gt;
&amp;lt;code&amp;gt;testing.c&amp;lt;/code&amp;gt; for simulation, but real tracking numbers need real hardware. See&lt;br /&gt;
[[#Open items]].&lt;br /&gt;
&lt;br /&gt;
== Data logging conventions ==&lt;br /&gt;
&lt;br /&gt;
Store raw instrument exports alongside derived plots so results remain reproducible.&lt;br /&gt;
&lt;br /&gt;
 Measurements/&lt;br /&gt;
   YYYY-MM-DD_experiment/&lt;br /&gt;
     raw/          LMG671 and MXO34 exports, unmodified&lt;br /&gt;
     telemetry/    calibrate_mppt.py and CAN logs&lt;br /&gt;
     notes.md      operating point, firmware commit, instrument setup, de-skew values&lt;br /&gt;
     plots/        derived figures&lt;br /&gt;
&lt;br /&gt;
Every session record must include: firmware git commit, hardware serial, ambient&lt;br /&gt;
temperature, source and load configuration, instrument model and options, de-skew values,&lt;br /&gt;
and soak duration. A figure without its operating point is not a result.&lt;br /&gt;
&lt;br /&gt;
== Publication checklist ==&lt;br /&gt;
&lt;br /&gt;
Before posting any measurement:&lt;br /&gt;
&lt;br /&gt;
# Operating point stated on the figure, no exceptions.&lt;br /&gt;
# Uncertainty stated for any efficiency or accuracy claim.&lt;br /&gt;
# Measurement boundary defined for any efficiency claim.&lt;br /&gt;
# Firmware commit recorded, so the result is reproducible.&lt;br /&gt;
# Instrument model and relevant options named.&lt;br /&gt;
# Result independently repeated at least once.&lt;br /&gt;
&lt;br /&gt;
=== Suggested narrative order ===&lt;br /&gt;
&lt;br /&gt;
Posting the unflattering result first is what makes the good result believable later.&lt;br /&gt;
&lt;br /&gt;
# Our MPPT runs at 55 C doing nothing. Experiment 1, opens with the problem.&lt;br /&gt;
# Our own current sensor was lying. Experiment 2, resolves a mystery from post 1.&lt;br /&gt;
# 8 ns of dead time cost us N degrees, measured on a part with no accessible gate pins. Experiment 3, the payoff.&lt;br /&gt;
# What 8 interleaved GaN phases look like. Experiment 4, pure spectacle.&lt;br /&gt;
# 99.x%, and here is our uncertainty budget. Experiment 5, the credibility close.&lt;br /&gt;
&lt;br /&gt;
== Open items ==&lt;br /&gt;
&lt;br /&gt;
* Confirm RT-ZISO model bandwidth and isolation rating. Measure the actual SW edge rate first, which is likely of order 1 ns, then decide whether the probe is adequate for edge-timing work or only for the slower floating measurements.&lt;br /&gt;
* '''Confirm from the schematic whether the EPC23102 bootstrap capacitor is external and probeable.''' Item 1 of the floating-node list, and the bootstrap-integrity experiment, both depend on it.&lt;br /&gt;
* '''Confirm Kelvin access to both INA253 shunt terminals''' (Isense+ / Isense-). Without it the Experiment 2 cross-check cannot be made and the clipping question stays one-sided.&lt;br /&gt;
* Confirm LMG671 includes transient-recording and data-logger options.&lt;br /&gt;
* Source a solar array simulator, or arrange an alternative for tracking-efficiency work.&lt;br /&gt;
* Confirm which hardware revision is on the bench, and therefore which grounding rules apply.&lt;br /&gt;
* Coordinate with R&amp;amp;S applications engineering, who will co-develop the setups and usually have specific product messaging to hit.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Open-SEC Firmware/src/hardware/eoia656a.h&amp;lt;/code&amp;gt;, hardware parameter definitions&lt;br /&gt;
* &amp;lt;code&amp;gt;calibrate_mppt.py&amp;lt;/code&amp;gt;, calibration and live telemetry tool&lt;br /&gt;
* &amp;lt;code&amp;gt;MPPT_ID32+0-4.dbc&amp;lt;/code&amp;gt;, CAN database&lt;br /&gt;
* &amp;lt;code&amp;gt;Measurements/&amp;lt;/code&amp;gt;, prior efficiency and loss plots&lt;br /&gt;
&lt;br /&gt;
[[Category:Measurement]]&lt;br /&gt;
[[Category:GaN MPPT]]&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaign&amp;diff=346</id>
		<title>R&amp;S Measurement Campaign</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaign&amp;diff=346"/>
		<updated>2026-08-18T20:25:31Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* GaN MPPT Phase: R&amp;amp;S / ZES Measurement Campaign */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Procedures for the measurement campaign on the '''EOI-A65-6A''' GaN MPPT phase boards&lt;br /&gt;
using the sponsored Rohde &amp;amp; Schwarz and ZES ZIMMER equipment. Each experiment is written&lt;br /&gt;
to be run start-to-finish by one engineer and to produce both an engineering result and a&lt;br /&gt;
publishable figure.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
Three goals, in priority order:&lt;br /&gt;
&lt;br /&gt;
# '''Resolve open engineering questions.''' Idle dissipation, dead-time optimum, and output current-sense accuracy are all unresolved and all measurable with this equipment.&lt;br /&gt;
# '''Calibrate and characterise the product.''' The default configuration ships with &amp;lt;code&amp;gt;calibrated = false&amp;lt;/code&amp;gt; and placeholder sensor gains.&lt;br /&gt;
# '''Produce publishable content.''' Each experiment below defines its hero shot and which instrument capability makes it possible.&lt;br /&gt;
&lt;br /&gt;
An experiment only belongs in this campaign if the instrument is load-bearing, i.e. the&lt;br /&gt;
result is not obtainable with ordinary bench gear. Otherwise it is just a measurement, not&lt;br /&gt;
a story.&lt;br /&gt;
&lt;br /&gt;
== Equipment ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Instrument !! Role !! Capability that matters here&lt;br /&gt;
|-&lt;br /&gt;
| ZES ZIMMER LMG671 || Power / efficiency reference || 0.015%-class accuracy; up to 7 simultaneously-sampled channels; microwatt-resolution DC; transient recorder&lt;br /&gt;
|-&lt;br /&gt;
| R&amp;amp;S MXO34 || Waveform + spectrum || 12-bit always-on; ~4.5 M acquisitions/s; ~45 k FFT/s live spectrum; zone trigger; deep memory&lt;br /&gt;
|-&lt;br /&gt;
| R&amp;amp;S RT-ZISO || Isolated probe || Galvanic isolation with CMRR that survives nanosecond edges, the only honest way to measure floating high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Before promising numbers publicly''', confirm the exact bandwidth and isolation rating&lt;br /&gt;
of the RT-ZISO model supplied, and confirm that the LMG671 includes the transient-recording&lt;br /&gt;
and data-logger options. Experiments 4, 5 and the tracking-efficiency work depend on those&lt;br /&gt;
options.&lt;br /&gt;
&lt;br /&gt;
== Device under test ==&lt;br /&gt;
&lt;br /&gt;
Values below are taken from &amp;lt;code&amp;gt;Open-SEC Firmware/src/hardware/eoia656a.h&amp;lt;/code&amp;gt; and&lt;br /&gt;
&amp;lt;code&amp;gt;eoia656a.c&amp;lt;/code&amp;gt;. Re-check them against the branch under test before quoting them.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Parameter !! Value !! Source&lt;br /&gt;
|-&lt;br /&gt;
| Hardware name || EOI-A65-6A || &amp;lt;code&amp;gt;HW_NAME&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Topology || Synchronous boost, GaN half bridge (EPC23102) || &amp;lt;code&amp;gt;HW_TOPOLOGY_BOOST&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Switching frequency || 100 kHz || &amp;lt;code&amp;gt;HW_SWITCHINGFREQUENCY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Control loop rate || 20 kHz (T&amp;lt;sub&amp;gt;s&amp;lt;/sub&amp;gt; = 50 us) || &amp;lt;code&amp;gt;HW_CONTROLLERFREQUENCY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Dead time (rising / falling) || 8 ns / 8 ns || &amp;lt;code&amp;gt;HW_DEADTIMERISING&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_DEADTIMEFALLING&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Inductor || 47 uH, DCR 12 mOhm || &amp;lt;code&amp;gt;HW_L&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_RLINT&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Declared C&amp;lt;sub&amp;gt;low&amp;lt;/sub&amp;gt; / C&amp;lt;sub&amp;gt;high&amp;lt;/sub&amp;gt; || 100 uF / 400 uF || &amp;lt;code&amp;gt;HW_CLOW&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_CHIGH&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Overvoltage fault (both rails) || 63 V || &amp;lt;code&amp;gt;HW_LIMIT_HS_VOLTAGE_HARD&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Overcurrent fault (both rails) || 13 A || &amp;lt;code&amp;gt;HW_LIMIT_HS_CURRENT_HARD&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Soft current limit || 9 A || &amp;lt;code&amp;gt;HighSideCurrentLimitSoft&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Current sensors || 2 x INA253A2, 2 mOhm integrated shunt, 200 mV/A, zero at 0.5 V || Isense.SchDoc&lt;br /&gt;
|-&lt;br /&gt;
| Phase count per baseboard || 8, interleaved via &amp;lt;code&amp;gt;PHASE_EN&amp;lt;/code&amp;gt;; startup staggered 200 ms per CAN ID || &amp;lt;code&amp;gt;mppt.c&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Temperature sensors || NT1 = ambient, NT2 = FET/heatsink, 100 k NTC, B = 4330 || &amp;lt;code&amp;gt;Temperature_B&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The GaN devices are rated 100 V. The project README recommends '''75 V nominal maximum'''&lt;br /&gt;
for system use. Do not exceed that during these experiments regardless of what the firmware&lt;br /&gt;
fault thresholds allow.&lt;br /&gt;
&lt;br /&gt;
== Safety and bench hygiene ==&lt;br /&gt;
&lt;br /&gt;
=== Grounding ===&lt;br /&gt;
&lt;br /&gt;
'''Verify the grounding topology on your specific board before connecting any&lt;br /&gt;
ground-referenced instrument.'''&lt;br /&gt;
&lt;br /&gt;
On the EOI-A65-6A the INA253 sense elements sit '''in the positive rail'''&lt;br /&gt;
(&amp;lt;code&amp;gt;PANEL_IN+ -&amp;gt; Isense+ / Isense- -&amp;gt; Vin&amp;lt;/code&amp;gt;, and likewise on the output), so input&lt;br /&gt;
and output grounds are common. Single-ended, ground-referenced probing of the switch node&lt;br /&gt;
is therefore permissible.&lt;br /&gt;
&lt;br /&gt;
This differs from the older SEC-B80-8A described in the repository README, which uses&lt;br /&gt;
'''ground-path''' current sensing. On that hardware, shorting input and output grounds&lt;br /&gt;
together, which any two ground-referenced scope probes will do, '''can destroy the board'''.&lt;br /&gt;
If there is any doubt which hardware is on the bench, use the RT-ZISO and treat every node&lt;br /&gt;
as floating.&lt;br /&gt;
&lt;br /&gt;
=== Probing rules ===&lt;br /&gt;
&lt;br /&gt;
* '''Never''' measure high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; with a ground-referenced probe. Its reference floats to the switch node and slews the full bus voltage in nanoseconds. Use the RT-ZISO.&lt;br /&gt;
* At ~55 V with nanosecond edges, probe ground lead length dominates the measurement. Use a ground spring, not a lead, for switch-node captures.&lt;br /&gt;
* The LMG671 inputs are individually isolated; multi-channel connection across the converter is safe.&lt;br /&gt;
* De-skew all channels before any v*i product or timing measurement. Record the de-skew values in the log.&lt;br /&gt;
&lt;br /&gt;
=== Electrical ===&lt;br /&gt;
&lt;br /&gt;
* Treat the DC bus as hazardous above 50 V. Discharge the output bulk capacitance before rework; the baseboard carries substantial bulk on the battery side.&lt;br /&gt;
* Set the source current limit at or below 9 A per phase before enabling the output.&lt;br /&gt;
* Keep a thermal camera or the on-board NTC telemetry visible during any run that changes dead time or disables protections.&lt;br /&gt;
&lt;br /&gt;
== Common bench setup ==&lt;br /&gt;
&lt;br /&gt;
=== Connections ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Node !! Instrument !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Panel input (V, I) || LMG671 ch1 || Kelvin sense at the board connector, not at the supply&lt;br /&gt;
|-&lt;br /&gt;
| Battery output (V, I) || LMG671 ch2 || Same reference plane convention as the input&lt;br /&gt;
|-&lt;br /&gt;
| Individual phase outputs || LMG671 ch3 to ch7 || Up to 5 phases alongside both terminals; see channel budget note&lt;br /&gt;
|-&lt;br /&gt;
| Switch node (SW) || MXO34 ch1 || Ground spring, shortest possible loop&lt;br /&gt;
|-&lt;br /&gt;
| Low-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; || MXO34 ch2 || &amp;lt;code&amp;gt;BRIDGE_LO&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| High-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; || RT-ZISO on MXO34 ch3 || '''Isolated probe mandatory'''&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; (INA253 output, before R12) || MXO34 ch4 || Pre-filter node; this is where clipping is visible&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Channel budget:''' the LMG671 provides at most 7 channels. With both terminals&lt;br /&gt;
instrumented, 5 phases can be measured simultaneously. To capture all 8 phase currents at&lt;br /&gt;
once, drop the terminal channels and derive totals by summation.&lt;br /&gt;
&lt;br /&gt;
=== Firmware preparation ===&lt;br /&gt;
&lt;br /&gt;
# Build the &amp;lt;code&amp;gt;HW_EOIA65_6A&amp;lt;/code&amp;gt; target in the '''Debug''' configuration. Do not use the Simulation build, which substitutes a model for the power stage.&lt;br /&gt;
# Flash via ST-Link and TAG-Connect (J2, TC2030-NL).&lt;br /&gt;
# Connect USB for the serial terminal. Available commands: &amp;lt;code&amp;gt;help&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ping&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;sens&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;hwinfo&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_read&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_write&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_default&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Record the firmware version and git commit hash in the log before every session.&lt;br /&gt;
&lt;br /&gt;
=== Telemetry available without instruments ===&lt;br /&gt;
&lt;br /&gt;
* Serial terminal: &amp;lt;code&amp;gt;sens&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;calibrate_mppt.py PORT --read-val&amp;lt;/code&amp;gt; polls &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Ihigh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Ilow&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vlow&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vhigh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TempHeatsink&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TempAmbient&amp;lt;/code&amp;gt; at 2 Hz.&lt;br /&gt;
* CAN: status frame every 1000 ms, power frame every 500 ms. See &amp;lt;code&amp;gt;MPPT_ID32+0-4.dbc&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Firmware scope buffer: &amp;lt;code&amp;gt;scope_start()&amp;lt;/code&amp;gt;, up to &amp;lt;code&amp;gt;CONVERTER_SCOPE_CHANNELS&amp;lt;/code&amp;gt; channels sampled at the 20 kHz control rate.&lt;br /&gt;
&lt;br /&gt;
The firmware scope is the natural cross-validation target for the LMG671, see&lt;br /&gt;
[[#Experiment 2: Current-sense verification and calibration|Experiment 2]].&lt;br /&gt;
&lt;br /&gt;
== Experiment 1: Idle power teardown ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' a phase board reaches ~55 C with no power being harvested. Where does every&lt;br /&gt;
milliwatt go?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' LMG671. Microwatt-resolution DC power is what makes the decomposition&lt;br /&gt;
credible.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' waterfall chart attributing the total idle dissipation to each contributor,&lt;br /&gt;
with the measured board temperature alongside each step.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Bring both rails to the nominal operating point (e.g. 35 V in, 55 V out) with the board held in reset. Record LMG671 readings on the 3v3 rail, the 5v2 rail and both HV rails. '''This is the absolute floor''', resistive dividers and leakage only.&lt;br /&gt;
# Release reset with &amp;lt;code&amp;gt;outputEnalbeOnStartup = false&amp;lt;/code&amp;gt;. Delta from step 1 = MCU plus analogue front end.&lt;br /&gt;
# Set the MPPT to disabled (&amp;lt;code&amp;gt;MpptState_Disable&amp;lt;/code&amp;gt;) so &amp;lt;code&amp;gt;DREN&amp;lt;/code&amp;gt; is de-asserted and the bridge is not switching. Confirm via &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;. Delta from step 2 should be near zero; a large delta means something is switching that should not be.&lt;br /&gt;
# Enable the output with no input power available. Delta from step 3 = power-stage idle loss plus any reverse power flow.&lt;br /&gt;
# At each step, log &amp;lt;code&amp;gt;TempHeatsink&amp;lt;/code&amp;gt; (NT2) and &amp;lt;code&amp;gt;TempAmbient&amp;lt;/code&amp;gt; (NT1) after a 20-minute thermal soak, plus a thermal camera frame.&lt;br /&gt;
# '''Check &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; at step 4.''' The default configuration sets &amp;lt;code&amp;gt;LowSideCurrentMinLimitSoft = -300 mA&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;PhaseHighSideEnableCurrent = -500 mA&amp;lt;/code&amp;gt;, which permit reverse inductor current. If &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; sits at -0.3 A, the phase is actively pumping power from the battery back into the panel. Record the value and compute the drain across all 8 phases.&lt;br /&gt;
# Repeat step 4 with &amp;lt;code&amp;gt;LowSideCurrentMinLimitSoft = 0&amp;lt;/code&amp;gt; to quantify the reverse-flow contribution in isolation.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
Roughly 0.5 to 1.2 W per phase total, with the power stage accounting for the large&lt;br /&gt;
majority and the MCU 0.10 to 0.15 W. Reverse-current pumping, if present, is the single&lt;br /&gt;
largest term and shows up as a battery drain far larger than the on-board dissipation.&lt;br /&gt;
&lt;br /&gt;
=== Pass criteria ===&lt;br /&gt;
&lt;br /&gt;
Every measured step accounted for within 10% of the sum of its identified contributors.&lt;br /&gt;
Unattributed residual is itself a finding, so log it rather than hiding it.&lt;br /&gt;
&lt;br /&gt;
== Experiment 2: Current-sense verification and calibration ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' the output current reading is known to run high. Why, and by how much?&lt;br /&gt;
&lt;br /&gt;
'''Hero instruments:''' MXO34 (12-bit, to see the pre-filter waveform) and LMG671 (as the&lt;br /&gt;
reference for calibration).&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' two panels. The &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; waveform showing pulsed shunt current&lt;br /&gt;
against the smooth inductor current, and a scatter plot of firmware-reported versus&lt;br /&gt;
LMG671-measured output current, before and after calibration.&lt;br /&gt;
&lt;br /&gt;
=== Background ===&lt;br /&gt;
&lt;br /&gt;
The local output capacitance is C18 to C23 (6 x 1 uF 0603) plus C24. The schematic notes&lt;br /&gt;
&amp;quot;each about 130nF left at 55v&amp;quot;, so effective local capacitance at 55 V is roughly 4.8 uF&lt;br /&gt;
(|Z| approximately 0.33 Ohm at 100 kHz). The declared 400 uF of bulk sits on the baseboard,&lt;br /&gt;
on the far side of the output shunt, reachable only through ~2 mOhm plus interconnect&lt;br /&gt;
inductance (|Z| approximately 0.13 Ohm at 200 nH). The lower-impedance path is therefore&lt;br /&gt;
through the shunt, so roughly 70% of the bridge's pulsed current flows through the sense&lt;br /&gt;
element rather than being absorbed locally.&lt;br /&gt;
&lt;br /&gt;
The ADC samples this with a 267 ns aperture at a fixed phase locked to the PWM, so the&lt;br /&gt;
residual ripple contributes a systematic offset rather than averageable noise.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Capture &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; (MXO34 ch4) together with the low-side gate drive. Confirm the pulse train: approximately zero during D, approximately &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; during (1-D).&lt;br /&gt;
# '''Look for flat tops.''' At &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; peaks near 13 A the INA253 output demands 0.5 + 2.6 = 3.1 V, into the supply rail. If the peaks are clipped or slew-limited, the error is nonlinear and '''no amount of downstream averaging will fix it'''. This determines whether the software mitigation is worth implementing at all.&lt;br /&gt;
# Simultaneously log firmware &amp;lt;code&amp;gt;Ihigh&amp;lt;/code&amp;gt; (via &amp;lt;code&amp;gt;--read-val&amp;lt;/code&amp;gt;) and LMG671 output current across the full current range at several bus voltages. Plot the error.&lt;br /&gt;
# Repeat for &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vlow&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Vhigh&amp;lt;/code&amp;gt;. The input shunt carries continuous inductor current and should show a much smaller error; that contrast is the point.&lt;br /&gt;
# Run the calibration procedure using the LMG671 as reference: &amp;lt;code&amp;gt;python calibrate_mppt.py PORT&amp;lt;/code&amp;gt;, then menu options 3 to 10 (zero offset and gain for each of input voltage, output voltage, input current, output current), option 11 for the NTC, and '''option 12 to store to EEPROM'''.&lt;br /&gt;
# Re-run step 3 and overlay before and after.&lt;br /&gt;
&lt;br /&gt;
=== Note ===&lt;br /&gt;
&lt;br /&gt;
Calibration corrects gain and offset. It '''cannot''' correct the ripple-induced error,&lt;br /&gt;
because that error varies with duty cycle and load. Expect a residual that scales with&lt;br /&gt;
output ripple, and report it as such.&lt;br /&gt;
&lt;br /&gt;
Also worth logging: &amp;lt;code&amp;gt;phase.Ihigh&amp;lt;/code&amp;gt; is unfiltered&lt;br /&gt;
(&amp;lt;code&amp;gt;CURRENT_IN_FORGETING_FACTOR = 0&amp;lt;/code&amp;gt;) and feeds the 13 A hard fault directly, so a&lt;br /&gt;
single sample landing on a ripple peak can cause a nuisance trip. Record any spurious&lt;br /&gt;
&amp;lt;code&amp;gt;Converter_OutputOverCurrent&amp;lt;/code&amp;gt; events observed during the campaign.&lt;br /&gt;
&lt;br /&gt;
== Experiment 3: Dead-time optimisation ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' is 8 ns of dead time causing shoot-through, and what is the optimum?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' RT-ZISO. High-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; on a node slewing 55 V in&lt;br /&gt;
nanoseconds is exactly what a differential probe cannot measure.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' three stacked panels sharing an x-axis. Gate overlap waveforms at 8 ns&lt;br /&gt;
versus the optimum, LMG671 efficiency versus dead time, and &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt; versus dead&lt;br /&gt;
time. One image, three independent measurements, one causal chain.&lt;br /&gt;
&lt;br /&gt;
=== Prerequisite ===&lt;br /&gt;
&lt;br /&gt;
The dead-time register write in &amp;lt;code&amp;gt;pwm.c&amp;lt;/code&amp;gt; previously OR-ed the computed value onto&lt;br /&gt;
the HAL default, so most settings landed on the wrong value. '''This must be fixed before&lt;br /&gt;
the sweep is meaningful.''' With the fix in place the commanded value is written directly.&lt;br /&gt;
&lt;br /&gt;
Historical behaviour, for reference. Note that 15 ns and 20 ns were previously identical on&lt;br /&gt;
hardware, so any earlier sweep would have shown no difference between them:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Commanded !! Register count !! Actual, before fix !! Actual, after fix&lt;br /&gt;
|-&lt;br /&gt;
| 8 ns || 10 || 8.33 ns || 8.33 ns&lt;br /&gt;
|-&lt;br /&gt;
| 10 ns || 12 || 11.67 ns || 10.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 15 ns || 18 || '''21.67 ns''' || 15.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 20 ns || 24 || '''21.67 ns''' || 20.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 30 ns || 36 || '''38.33 ns''' || 30.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 40 ns || 48 || '''48.33 ns''' || 40.00 ns&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Dead-time resolution is 1/(f&amp;lt;sub&amp;gt;HRTIM&amp;lt;/sub&amp;gt; x 8) = 0.83 ns per count at 150 MHz. If the&lt;br /&gt;
system clock is changed, the quantisation changes with it.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Verify the fix: set &amp;lt;code&amp;gt;HW_DEADTIMERISING&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;HW_DEADTIMEFALLING&amp;lt;/code&amp;gt; to 20 ns, halt, and read &amp;lt;code&amp;gt;HRTIM1-&amp;gt;sTimerxRegs[1].DTxR&amp;lt;/code&amp;gt;. Expect DTR and DTF fields = 24, not 26.&lt;br /&gt;
# Establish a fixed operating point (for example 35 V in, 55 V out, 4 A per phase) and let it thermally soak for 20 minutes.&lt;br /&gt;
# For each dead-time value in 5, 8, 10, 15, 20, 25, 30, 40 ns: rebuild, flash, soak 20 min, then record LMG671 efficiency, &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Tambient&amp;lt;/code&amp;gt;, and an MXO34 capture of high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; plus low-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; plus SW node plus inductor current.&lt;br /&gt;
# Inspect each capture for genuine gate overlap. Overlap plus a current spike on the switch node at the transition is shoot-through.&lt;br /&gt;
# Plot efficiency and temperature against dead time on a shared axis.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
A U-shaped efficiency curve. Too little dead time causes shoot-through; too much forces the&lt;br /&gt;
inductor current through the high-side device in reverse-conduction mode at a significantly&lt;br /&gt;
higher voltage drop. GaN devices have no body diode, so the reverse-conduction penalty at&lt;br /&gt;
excessive dead time is steeper than for a silicon bridge.&lt;br /&gt;
&lt;br /&gt;
=== Safety ===&lt;br /&gt;
&lt;br /&gt;
Below 8 ns, shoot-through risk is real. Start at reduced bus voltage and reduced current,&lt;br /&gt;
watch &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt; continuously, and abort on any rapid temperature rise.&lt;br /&gt;
&lt;br /&gt;
== Experiment 4: Interleaving ripple cancellation ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' does the 8-phase interleaving actually cancel bus ripple as designed?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' MXO34. Roughly 45 k FFT/s makes this live rather than a slideshow of&lt;br /&gt;
captures.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' video of the live input-ripple spectrum while the interleaving is dialled&lt;br /&gt;
from all-in-phase to fully staggered. The 100 kHz fundamental and its harmonics collapse in&lt;br /&gt;
real time and 800 kHz emerges. This is the most visually striking result available from this&lt;br /&gt;
hardware.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Load all 8 phases at a common operating point.&lt;br /&gt;
# Configure the MXO34 for live FFT of the battery-bus ripple current, span covering 50 kHz to 2 MHz.&lt;br /&gt;
# Baseline: force all phases in phase (identical &amp;lt;code&amp;gt;PHASE_EN&amp;lt;/code&amp;gt; alignment). Capture the spectrum.&lt;br /&gt;
# Step the interleaving toward the designed 8-way stagger. Record continuously.&lt;br /&gt;
# Capture the final staggered spectrum and measure the attenuation of the 100 kHz component and the amplitude of the new 800 kHz component.&lt;br /&gt;
# '''Companion capture:''' LMG671 transient recorder at 10 MS/s on as many phase currents as channels allow, giving 8 staggered triangle waves in one frame. Note the channel budget constraint.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
Substantial attenuation of the 100 kHz fundamental with energy reappearing at&lt;br /&gt;
8 x 100 kHz = 800 kHz. Quantify rather than asserting; imperfect current sharing between&lt;br /&gt;
phases limits the achievable cancellation, and that mismatch is itself a useful result.&lt;br /&gt;
&lt;br /&gt;
== Experiment 5: Efficiency map and phase shedding ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' what is peak efficiency, with what uncertainty, and what is the optimal&lt;br /&gt;
number of active phases at partial load?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' LMG671. At 25 W per phase the differences between shedding schedules&lt;br /&gt;
are fractions of a percent, so accuracy is the whole justification.&lt;br /&gt;
&lt;br /&gt;
'''Hero shots:''' an efficiency contour map over the operating envelope with an explicit&lt;br /&gt;
uncertainty budget; and a family of 8 efficiency curves whose crossing points define the&lt;br /&gt;
shedding schedule.&lt;br /&gt;
&lt;br /&gt;
=== Procedure: efficiency map ===&lt;br /&gt;
&lt;br /&gt;
# Define the reference planes precisely and document them. State whether connector and cable losses are attributed to the converter. '''This decision must be stated in any published figure.'''&lt;br /&gt;
# Sweep input voltage 20 to 55 V and per-phase current 0 to 8 A on a grid. Soak to thermal equilibrium at each point.&lt;br /&gt;
# Record LMG671 input power, output power, efficiency, and both NTC temperatures.&lt;br /&gt;
# Produce a contour map. Compare against the existing plots in &amp;lt;code&amp;gt;Measurements/&amp;lt;/code&amp;gt; (50 V, 64 V, 72 V) as a sanity check on the older hardware.&lt;br /&gt;
&lt;br /&gt;
=== Procedure: phase shedding ===&lt;br /&gt;
&lt;br /&gt;
# Sweep total system power from ~100 W to full rated.&lt;br /&gt;
# At each power level, measure efficiency with N = 1, 2, 4, 6, 8 phases active.&lt;br /&gt;
# Plot the family of curves and locate the crossing points.&lt;br /&gt;
# Derive the optimal shedding schedule and '''implement it in firmware'''.&lt;br /&gt;
# Re-measure to confirm the predicted partial-load gain.&lt;br /&gt;
&lt;br /&gt;
=== Uncertainty budget ===&lt;br /&gt;
&lt;br /&gt;
At 99% efficiency the uncertainty budget is the result. Document:&lt;br /&gt;
&lt;br /&gt;
* Instrument accuracy specification at the actual operating point, not the headline figure.&lt;br /&gt;
* Sense-lead placement and what is included inside the measurement boundary.&lt;br /&gt;
* Thermal drift over a 30-minute soak, measured by repeating one point.&lt;br /&gt;
* Repeatability across at least 3 independent runs of the same point.&lt;br /&gt;
&lt;br /&gt;
A separate write-up on how not to overstate efficiency, derived from this section, would be&lt;br /&gt;
strong content in its own right and costs nothing extra to produce.&lt;br /&gt;
&lt;br /&gt;
== Optional experiments ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Experiment !! Procedure summary !! Hero instrument&lt;br /&gt;
|-&lt;br /&gt;
| '''Rare-event hunt''' || Zone trigger on SW-node overshoot above ~75 V. Run for hours across all 8 phases. Build a histogram of peak SW voltage over ~10&amp;lt;sup&amp;gt;11&amp;lt;/sup&amp;gt; switching cycles and extract the worst outliers. || MXO34, since ~4.5 M acquisitions/s is the only practical way to catch one-in-10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; events&lt;br /&gt;
|-&lt;br /&gt;
| '''True switching energy''' || De-skew carefully, then integrate v*i per transition. Plot E&amp;lt;sub&amp;gt;on&amp;lt;/sub&amp;gt; and E&amp;lt;sub&amp;gt;off&amp;lt;/sub&amp;gt; against current and compare to the EPC23102 datasheet. || RT-ZISO plus MXO34&lt;br /&gt;
|-&lt;br /&gt;
| '''MPPT tracking efficiency''' || Play a real cloudy-day irradiance profile into a solar array simulator. Compute energy captured divided by theoretical MPP energy, a different metric from conversion efficiency and rarely published honestly. || LMG671 data logger&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The tracking-efficiency experiment requires a solar array simulator, which is '''not&lt;br /&gt;
currently on the bench'''. The firmware contains an &amp;lt;code&amp;gt;Ipvmodel&amp;lt;/code&amp;gt; in&lt;br /&gt;
&amp;lt;code&amp;gt;testing.c&amp;lt;/code&amp;gt; for simulation, but real tracking numbers need real hardware. See&lt;br /&gt;
[[#Open items]].&lt;br /&gt;
&lt;br /&gt;
== Data logging conventions ==&lt;br /&gt;
&lt;br /&gt;
Store raw instrument exports alongside derived plots so results remain reproducible.&lt;br /&gt;
&lt;br /&gt;
 Measurements/&lt;br /&gt;
   YYYY-MM-DD_experiment/&lt;br /&gt;
     raw/          LMG671 and MXO34 exports, unmodified&lt;br /&gt;
     telemetry/    calibrate_mppt.py and CAN logs&lt;br /&gt;
     notes.md      operating point, firmware commit, instrument setup, de-skew values&lt;br /&gt;
     plots/        derived figures&lt;br /&gt;
&lt;br /&gt;
Every session record must include: firmware git commit, hardware serial, ambient&lt;br /&gt;
temperature, source and load configuration, instrument model and options, de-skew values,&lt;br /&gt;
and soak duration. A figure without its operating point is not a result.&lt;br /&gt;
&lt;br /&gt;
== Publication checklist ==&lt;br /&gt;
&lt;br /&gt;
Before posting any measurement:&lt;br /&gt;
&lt;br /&gt;
# Operating point stated on the figure, no exceptions.&lt;br /&gt;
# Uncertainty stated for any efficiency or accuracy claim.&lt;br /&gt;
# Measurement boundary defined for any efficiency claim.&lt;br /&gt;
# Firmware commit recorded, so the result is reproducible.&lt;br /&gt;
# Instrument model and relevant options named.&lt;br /&gt;
# Result independently repeated at least once.&lt;br /&gt;
&lt;br /&gt;
=== Suggested narrative order ===&lt;br /&gt;
&lt;br /&gt;
Posting the unflattering result first is what makes the good result believable later.&lt;br /&gt;
&lt;br /&gt;
# Our MPPT runs at 55 C doing nothing. Experiment 1, opens with the problem.&lt;br /&gt;
# Our own current sensor was lying. Experiment 2, resolves a mystery from post 1.&lt;br /&gt;
# 8 ns of dead time cost us N degrees. Experiment 3, the payoff.&lt;br /&gt;
# What 8 interleaved GaN phases look like. Experiment 4, pure spectacle.&lt;br /&gt;
# 99.x%, and here is our uncertainty budget. Experiment 5, the credibility close.&lt;br /&gt;
&lt;br /&gt;
== Open items ==&lt;br /&gt;
&lt;br /&gt;
* Confirm RT-ZISO model bandwidth and isolation rating.&lt;br /&gt;
* Confirm LMG671 includes transient-recording and data-logger options.&lt;br /&gt;
* Source a solar array simulator, or arrange an alternative for tracking-efficiency work.&lt;br /&gt;
* Confirm which hardware revision is on the bench, and therefore which grounding rules apply.&lt;br /&gt;
* Coordinate with R&amp;amp;S applications engineering, who will co-develop the setups and usually have specific product messaging to hit.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Open-SEC Firmware/src/hardware/eoia656a.h&amp;lt;/code&amp;gt;, hardware parameter definitions&lt;br /&gt;
* &amp;lt;code&amp;gt;calibrate_mppt.py&amp;lt;/code&amp;gt;, calibration and live telemetry tool&lt;br /&gt;
* &amp;lt;code&amp;gt;MPPT_ID32+0-4.dbc&amp;lt;/code&amp;gt;, CAN database&lt;br /&gt;
* &amp;lt;code&amp;gt;Measurements/&amp;lt;/code&amp;gt;, prior efficiency and loss plots&lt;br /&gt;
&lt;br /&gt;
[[Category:Measurement]]&lt;br /&gt;
[[Category:GaN MPPT]]&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaigne&amp;diff=345</id>
		<title>R&amp;S Measurement Campaigne</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaigne&amp;diff=345"/>
		<updated>2026-08-18T20:25:14Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Aran Dokoupil moved page R&amp;amp;S Measurement Campaigne to R&amp;amp;S Measurement Campaign: spelling&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[R&amp;amp;S Measurement Campaign]]&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaign&amp;diff=344</id>
		<title>R&amp;S Measurement Campaign</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaign&amp;diff=344"/>
		<updated>2026-08-18T20:25:14Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Aran Dokoupil moved page R&amp;amp;S Measurement Campaigne to R&amp;amp;S Measurement Campaign: spelling&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= GaN MPPT Phase: R&amp;amp;S / ZES Measurement Campaign =&lt;br /&gt;
&lt;br /&gt;
Procedures for the measurement campaign on the '''EOI-A65-6A''' GaN MPPT phase boards&lt;br /&gt;
using the sponsored Rohde &amp;amp; Schwarz and ZES ZIMMER equipment. Each experiment is written&lt;br /&gt;
to be run start-to-finish by one engineer and to produce both an engineering result and a&lt;br /&gt;
publishable figure.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
Three goals, in priority order:&lt;br /&gt;
&lt;br /&gt;
# '''Resolve open engineering questions.''' Idle dissipation, dead-time optimum, and output current-sense accuracy are all unresolved and all measurable with this equipment.&lt;br /&gt;
# '''Calibrate and characterise the product.''' The default configuration ships with &amp;lt;code&amp;gt;calibrated = false&amp;lt;/code&amp;gt; and placeholder sensor gains.&lt;br /&gt;
# '''Produce publishable content.''' Each experiment below defines its hero shot and which instrument capability makes it possible.&lt;br /&gt;
&lt;br /&gt;
An experiment only belongs in this campaign if the instrument is load-bearing, i.e. the&lt;br /&gt;
result is not obtainable with ordinary bench gear. Otherwise it is just a measurement, not&lt;br /&gt;
a story.&lt;br /&gt;
&lt;br /&gt;
== Equipment ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Instrument !! Role !! Capability that matters here&lt;br /&gt;
|-&lt;br /&gt;
| ZES ZIMMER LMG671 || Power / efficiency reference || 0.015%-class accuracy; up to 7 simultaneously-sampled channels; microwatt-resolution DC; transient recorder&lt;br /&gt;
|-&lt;br /&gt;
| R&amp;amp;S MXO34 || Waveform + spectrum || 12-bit always-on; ~4.5 M acquisitions/s; ~45 k FFT/s live spectrum; zone trigger; deep memory&lt;br /&gt;
|-&lt;br /&gt;
| R&amp;amp;S RT-ZISO || Isolated probe || Galvanic isolation with CMRR that survives nanosecond edges, the only honest way to measure floating high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Before promising numbers publicly''', confirm the exact bandwidth and isolation rating&lt;br /&gt;
of the RT-ZISO model supplied, and confirm that the LMG671 includes the transient-recording&lt;br /&gt;
and data-logger options. Experiments 4, 5 and the tracking-efficiency work depend on those&lt;br /&gt;
options.&lt;br /&gt;
&lt;br /&gt;
== Device under test ==&lt;br /&gt;
&lt;br /&gt;
Values below are taken from &amp;lt;code&amp;gt;Open-SEC Firmware/src/hardware/eoia656a.h&amp;lt;/code&amp;gt; and&lt;br /&gt;
&amp;lt;code&amp;gt;eoia656a.c&amp;lt;/code&amp;gt;. Re-check them against the branch under test before quoting them.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Parameter !! Value !! Source&lt;br /&gt;
|-&lt;br /&gt;
| Hardware name || EOI-A65-6A || &amp;lt;code&amp;gt;HW_NAME&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Topology || Synchronous boost, GaN half bridge (EPC23102) || &amp;lt;code&amp;gt;HW_TOPOLOGY_BOOST&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Switching frequency || 100 kHz || &amp;lt;code&amp;gt;HW_SWITCHINGFREQUENCY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Control loop rate || 20 kHz (T&amp;lt;sub&amp;gt;s&amp;lt;/sub&amp;gt; = 50 us) || &amp;lt;code&amp;gt;HW_CONTROLLERFREQUENCY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Dead time (rising / falling) || 8 ns / 8 ns || &amp;lt;code&amp;gt;HW_DEADTIMERISING&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_DEADTIMEFALLING&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Inductor || 47 uH, DCR 12 mOhm || &amp;lt;code&amp;gt;HW_L&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_RLINT&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Declared C&amp;lt;sub&amp;gt;low&amp;lt;/sub&amp;gt; / C&amp;lt;sub&amp;gt;high&amp;lt;/sub&amp;gt; || 100 uF / 400 uF || &amp;lt;code&amp;gt;HW_CLOW&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_CHIGH&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Overvoltage fault (both rails) || 63 V || &amp;lt;code&amp;gt;HW_LIMIT_HS_VOLTAGE_HARD&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Overcurrent fault (both rails) || 13 A || &amp;lt;code&amp;gt;HW_LIMIT_HS_CURRENT_HARD&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Soft current limit || 9 A || &amp;lt;code&amp;gt;HighSideCurrentLimitSoft&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Current sensors || 2 x INA253A2, 2 mOhm integrated shunt, 200 mV/A, zero at 0.5 V || Isense.SchDoc&lt;br /&gt;
|-&lt;br /&gt;
| Phase count per baseboard || 8, interleaved via &amp;lt;code&amp;gt;PHASE_EN&amp;lt;/code&amp;gt;; startup staggered 200 ms per CAN ID || &amp;lt;code&amp;gt;mppt.c&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Temperature sensors || NT1 = ambient, NT2 = FET/heatsink, 100 k NTC, B = 4330 || &amp;lt;code&amp;gt;Temperature_B&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The GaN devices are rated 100 V. The project README recommends '''75 V nominal maximum'''&lt;br /&gt;
for system use. Do not exceed that during these experiments regardless of what the firmware&lt;br /&gt;
fault thresholds allow.&lt;br /&gt;
&lt;br /&gt;
== Safety and bench hygiene ==&lt;br /&gt;
&lt;br /&gt;
=== Grounding ===&lt;br /&gt;
&lt;br /&gt;
'''Verify the grounding topology on your specific board before connecting any&lt;br /&gt;
ground-referenced instrument.'''&lt;br /&gt;
&lt;br /&gt;
On the EOI-A65-6A the INA253 sense elements sit '''in the positive rail'''&lt;br /&gt;
(&amp;lt;code&amp;gt;PANEL_IN+ -&amp;gt; Isense+ / Isense- -&amp;gt; Vin&amp;lt;/code&amp;gt;, and likewise on the output), so input&lt;br /&gt;
and output grounds are common. Single-ended, ground-referenced probing of the switch node&lt;br /&gt;
is therefore permissible.&lt;br /&gt;
&lt;br /&gt;
This differs from the older SEC-B80-8A described in the repository README, which uses&lt;br /&gt;
'''ground-path''' current sensing. On that hardware, shorting input and output grounds&lt;br /&gt;
together, which any two ground-referenced scope probes will do, '''can destroy the board'''.&lt;br /&gt;
If there is any doubt which hardware is on the bench, use the RT-ZISO and treat every node&lt;br /&gt;
as floating.&lt;br /&gt;
&lt;br /&gt;
=== Probing rules ===&lt;br /&gt;
&lt;br /&gt;
* '''Never''' measure high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; with a ground-referenced probe. Its reference floats to the switch node and slews the full bus voltage in nanoseconds. Use the RT-ZISO.&lt;br /&gt;
* At ~55 V with nanosecond edges, probe ground lead length dominates the measurement. Use a ground spring, not a lead, for switch-node captures.&lt;br /&gt;
* The LMG671 inputs are individually isolated; multi-channel connection across the converter is safe.&lt;br /&gt;
* De-skew all channels before any v*i product or timing measurement. Record the de-skew values in the log.&lt;br /&gt;
&lt;br /&gt;
=== Electrical ===&lt;br /&gt;
&lt;br /&gt;
* Treat the DC bus as hazardous above 50 V. Discharge the output bulk capacitance before rework; the baseboard carries substantial bulk on the battery side.&lt;br /&gt;
* Set the source current limit at or below 9 A per phase before enabling the output.&lt;br /&gt;
* Keep a thermal camera or the on-board NTC telemetry visible during any run that changes dead time or disables protections.&lt;br /&gt;
&lt;br /&gt;
== Common bench setup ==&lt;br /&gt;
&lt;br /&gt;
=== Connections ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Node !! Instrument !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Panel input (V, I) || LMG671 ch1 || Kelvin sense at the board connector, not at the supply&lt;br /&gt;
|-&lt;br /&gt;
| Battery output (V, I) || LMG671 ch2 || Same reference plane convention as the input&lt;br /&gt;
|-&lt;br /&gt;
| Individual phase outputs || LMG671 ch3 to ch7 || Up to 5 phases alongside both terminals; see channel budget note&lt;br /&gt;
|-&lt;br /&gt;
| Switch node (SW) || MXO34 ch1 || Ground spring, shortest possible loop&lt;br /&gt;
|-&lt;br /&gt;
| Low-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; || MXO34 ch2 || &amp;lt;code&amp;gt;BRIDGE_LO&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| High-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; || RT-ZISO on MXO34 ch3 || '''Isolated probe mandatory'''&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; (INA253 output, before R12) || MXO34 ch4 || Pre-filter node; this is where clipping is visible&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Channel budget:''' the LMG671 provides at most 7 channels. With both terminals&lt;br /&gt;
instrumented, 5 phases can be measured simultaneously. To capture all 8 phase currents at&lt;br /&gt;
once, drop the terminal channels and derive totals by summation.&lt;br /&gt;
&lt;br /&gt;
=== Firmware preparation ===&lt;br /&gt;
&lt;br /&gt;
# Build the &amp;lt;code&amp;gt;HW_EOIA65_6A&amp;lt;/code&amp;gt; target in the '''Debug''' configuration. Do not use the Simulation build, which substitutes a model for the power stage.&lt;br /&gt;
# Flash via ST-Link and TAG-Connect (J2, TC2030-NL).&lt;br /&gt;
# Connect USB for the serial terminal. Available commands: &amp;lt;code&amp;gt;help&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ping&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;sens&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;hwinfo&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_read&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_write&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_default&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Record the firmware version and git commit hash in the log before every session.&lt;br /&gt;
&lt;br /&gt;
=== Telemetry available without instruments ===&lt;br /&gt;
&lt;br /&gt;
* Serial terminal: &amp;lt;code&amp;gt;sens&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;calibrate_mppt.py PORT --read-val&amp;lt;/code&amp;gt; polls &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Ihigh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Ilow&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vlow&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vhigh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TempHeatsink&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TempAmbient&amp;lt;/code&amp;gt; at 2 Hz.&lt;br /&gt;
* CAN: status frame every 1000 ms, power frame every 500 ms. See &amp;lt;code&amp;gt;MPPT_ID32+0-4.dbc&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Firmware scope buffer: &amp;lt;code&amp;gt;scope_start()&amp;lt;/code&amp;gt;, up to &amp;lt;code&amp;gt;CONVERTER_SCOPE_CHANNELS&amp;lt;/code&amp;gt; channels sampled at the 20 kHz control rate.&lt;br /&gt;
&lt;br /&gt;
The firmware scope is the natural cross-validation target for the LMG671, see&lt;br /&gt;
[[#Experiment 2: Current-sense verification and calibration|Experiment 2]].&lt;br /&gt;
&lt;br /&gt;
== Experiment 1: Idle power teardown ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' a phase board reaches ~55 C with no power being harvested. Where does every&lt;br /&gt;
milliwatt go?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' LMG671. Microwatt-resolution DC power is what makes the decomposition&lt;br /&gt;
credible.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' waterfall chart attributing the total idle dissipation to each contributor,&lt;br /&gt;
with the measured board temperature alongside each step.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Bring both rails to the nominal operating point (e.g. 35 V in, 55 V out) with the board held in reset. Record LMG671 readings on the 3v3 rail, the 5v2 rail and both HV rails. '''This is the absolute floor''', resistive dividers and leakage only.&lt;br /&gt;
# Release reset with &amp;lt;code&amp;gt;outputEnalbeOnStartup = false&amp;lt;/code&amp;gt;. Delta from step 1 = MCU plus analogue front end.&lt;br /&gt;
# Set the MPPT to disabled (&amp;lt;code&amp;gt;MpptState_Disable&amp;lt;/code&amp;gt;) so &amp;lt;code&amp;gt;DREN&amp;lt;/code&amp;gt; is de-asserted and the bridge is not switching. Confirm via &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;. Delta from step 2 should be near zero; a large delta means something is switching that should not be.&lt;br /&gt;
# Enable the output with no input power available. Delta from step 3 = power-stage idle loss plus any reverse power flow.&lt;br /&gt;
# At each step, log &amp;lt;code&amp;gt;TempHeatsink&amp;lt;/code&amp;gt; (NT2) and &amp;lt;code&amp;gt;TempAmbient&amp;lt;/code&amp;gt; (NT1) after a 20-minute thermal soak, plus a thermal camera frame.&lt;br /&gt;
# '''Check &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; at step 4.''' The default configuration sets &amp;lt;code&amp;gt;LowSideCurrentMinLimitSoft = -300 mA&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;PhaseHighSideEnableCurrent = -500 mA&amp;lt;/code&amp;gt;, which permit reverse inductor current. If &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; sits at -0.3 A, the phase is actively pumping power from the battery back into the panel. Record the value and compute the drain across all 8 phases.&lt;br /&gt;
# Repeat step 4 with &amp;lt;code&amp;gt;LowSideCurrentMinLimitSoft = 0&amp;lt;/code&amp;gt; to quantify the reverse-flow contribution in isolation.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
Roughly 0.5 to 1.2 W per phase total, with the power stage accounting for the large&lt;br /&gt;
majority and the MCU 0.10 to 0.15 W. Reverse-current pumping, if present, is the single&lt;br /&gt;
largest term and shows up as a battery drain far larger than the on-board dissipation.&lt;br /&gt;
&lt;br /&gt;
=== Pass criteria ===&lt;br /&gt;
&lt;br /&gt;
Every measured step accounted for within 10% of the sum of its identified contributors.&lt;br /&gt;
Unattributed residual is itself a finding, so log it rather than hiding it.&lt;br /&gt;
&lt;br /&gt;
== Experiment 2: Current-sense verification and calibration ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' the output current reading is known to run high. Why, and by how much?&lt;br /&gt;
&lt;br /&gt;
'''Hero instruments:''' MXO34 (12-bit, to see the pre-filter waveform) and LMG671 (as the&lt;br /&gt;
reference for calibration).&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' two panels. The &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; waveform showing pulsed shunt current&lt;br /&gt;
against the smooth inductor current, and a scatter plot of firmware-reported versus&lt;br /&gt;
LMG671-measured output current, before and after calibration.&lt;br /&gt;
&lt;br /&gt;
=== Background ===&lt;br /&gt;
&lt;br /&gt;
The local output capacitance is C18 to C23 (6 x 1 uF 0603) plus C24. The schematic notes&lt;br /&gt;
&amp;quot;each about 130nF left at 55v&amp;quot;, so effective local capacitance at 55 V is roughly 4.8 uF&lt;br /&gt;
(|Z| approximately 0.33 Ohm at 100 kHz). The declared 400 uF of bulk sits on the baseboard,&lt;br /&gt;
on the far side of the output shunt, reachable only through ~2 mOhm plus interconnect&lt;br /&gt;
inductance (|Z| approximately 0.13 Ohm at 200 nH). The lower-impedance path is therefore&lt;br /&gt;
through the shunt, so roughly 70% of the bridge's pulsed current flows through the sense&lt;br /&gt;
element rather than being absorbed locally.&lt;br /&gt;
&lt;br /&gt;
The ADC samples this with a 267 ns aperture at a fixed phase locked to the PWM, so the&lt;br /&gt;
residual ripple contributes a systematic offset rather than averageable noise.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Capture &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; (MXO34 ch4) together with the low-side gate drive. Confirm the pulse train: approximately zero during D, approximately &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; during (1-D).&lt;br /&gt;
# '''Look for flat tops.''' At &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; peaks near 13 A the INA253 output demands 0.5 + 2.6 = 3.1 V, into the supply rail. If the peaks are clipped or slew-limited, the error is nonlinear and '''no amount of downstream averaging will fix it'''. This determines whether the software mitigation is worth implementing at all.&lt;br /&gt;
# Simultaneously log firmware &amp;lt;code&amp;gt;Ihigh&amp;lt;/code&amp;gt; (via &amp;lt;code&amp;gt;--read-val&amp;lt;/code&amp;gt;) and LMG671 output current across the full current range at several bus voltages. Plot the error.&lt;br /&gt;
# Repeat for &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vlow&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Vhigh&amp;lt;/code&amp;gt;. The input shunt carries continuous inductor current and should show a much smaller error; that contrast is the point.&lt;br /&gt;
# Run the calibration procedure using the LMG671 as reference: &amp;lt;code&amp;gt;python calibrate_mppt.py PORT&amp;lt;/code&amp;gt;, then menu options 3 to 10 (zero offset and gain for each of input voltage, output voltage, input current, output current), option 11 for the NTC, and '''option 12 to store to EEPROM'''.&lt;br /&gt;
# Re-run step 3 and overlay before and after.&lt;br /&gt;
&lt;br /&gt;
=== Note ===&lt;br /&gt;
&lt;br /&gt;
Calibration corrects gain and offset. It '''cannot''' correct the ripple-induced error,&lt;br /&gt;
because that error varies with duty cycle and load. Expect a residual that scales with&lt;br /&gt;
output ripple, and report it as such.&lt;br /&gt;
&lt;br /&gt;
Also worth logging: &amp;lt;code&amp;gt;phase.Ihigh&amp;lt;/code&amp;gt; is unfiltered&lt;br /&gt;
(&amp;lt;code&amp;gt;CURRENT_IN_FORGETING_FACTOR = 0&amp;lt;/code&amp;gt;) and feeds the 13 A hard fault directly, so a&lt;br /&gt;
single sample landing on a ripple peak can cause a nuisance trip. Record any spurious&lt;br /&gt;
&amp;lt;code&amp;gt;Converter_OutputOverCurrent&amp;lt;/code&amp;gt; events observed during the campaign.&lt;br /&gt;
&lt;br /&gt;
== Experiment 3: Dead-time optimisation ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' is 8 ns of dead time causing shoot-through, and what is the optimum?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' RT-ZISO. High-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; on a node slewing 55 V in&lt;br /&gt;
nanoseconds is exactly what a differential probe cannot measure.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' three stacked panels sharing an x-axis. Gate overlap waveforms at 8 ns&lt;br /&gt;
versus the optimum, LMG671 efficiency versus dead time, and &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt; versus dead&lt;br /&gt;
time. One image, three independent measurements, one causal chain.&lt;br /&gt;
&lt;br /&gt;
=== Prerequisite ===&lt;br /&gt;
&lt;br /&gt;
The dead-time register write in &amp;lt;code&amp;gt;pwm.c&amp;lt;/code&amp;gt; previously OR-ed the computed value onto&lt;br /&gt;
the HAL default, so most settings landed on the wrong value. '''This must be fixed before&lt;br /&gt;
the sweep is meaningful.''' With the fix in place the commanded value is written directly.&lt;br /&gt;
&lt;br /&gt;
Historical behaviour, for reference. Note that 15 ns and 20 ns were previously identical on&lt;br /&gt;
hardware, so any earlier sweep would have shown no difference between them:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Commanded !! Register count !! Actual, before fix !! Actual, after fix&lt;br /&gt;
|-&lt;br /&gt;
| 8 ns || 10 || 8.33 ns || 8.33 ns&lt;br /&gt;
|-&lt;br /&gt;
| 10 ns || 12 || 11.67 ns || 10.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 15 ns || 18 || '''21.67 ns''' || 15.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 20 ns || 24 || '''21.67 ns''' || 20.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 30 ns || 36 || '''38.33 ns''' || 30.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 40 ns || 48 || '''48.33 ns''' || 40.00 ns&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Dead-time resolution is 1/(f&amp;lt;sub&amp;gt;HRTIM&amp;lt;/sub&amp;gt; x 8) = 0.83 ns per count at 150 MHz. If the&lt;br /&gt;
system clock is changed, the quantisation changes with it.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Verify the fix: set &amp;lt;code&amp;gt;HW_DEADTIMERISING&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;HW_DEADTIMEFALLING&amp;lt;/code&amp;gt; to 20 ns, halt, and read &amp;lt;code&amp;gt;HRTIM1-&amp;gt;sTimerxRegs[1].DTxR&amp;lt;/code&amp;gt;. Expect DTR and DTF fields = 24, not 26.&lt;br /&gt;
# Establish a fixed operating point (for example 35 V in, 55 V out, 4 A per phase) and let it thermally soak for 20 minutes.&lt;br /&gt;
# For each dead-time value in 5, 8, 10, 15, 20, 25, 30, 40 ns: rebuild, flash, soak 20 min, then record LMG671 efficiency, &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Tambient&amp;lt;/code&amp;gt;, and an MXO34 capture of high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; plus low-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; plus SW node plus inductor current.&lt;br /&gt;
# Inspect each capture for genuine gate overlap. Overlap plus a current spike on the switch node at the transition is shoot-through.&lt;br /&gt;
# Plot efficiency and temperature against dead time on a shared axis.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
A U-shaped efficiency curve. Too little dead time causes shoot-through; too much forces the&lt;br /&gt;
inductor current through the high-side device in reverse-conduction mode at a significantly&lt;br /&gt;
higher voltage drop. GaN devices have no body diode, so the reverse-conduction penalty at&lt;br /&gt;
excessive dead time is steeper than for a silicon bridge.&lt;br /&gt;
&lt;br /&gt;
=== Safety ===&lt;br /&gt;
&lt;br /&gt;
Below 8 ns, shoot-through risk is real. Start at reduced bus voltage and reduced current,&lt;br /&gt;
watch &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt; continuously, and abort on any rapid temperature rise.&lt;br /&gt;
&lt;br /&gt;
== Experiment 4: Interleaving ripple cancellation ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' does the 8-phase interleaving actually cancel bus ripple as designed?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' MXO34. Roughly 45 k FFT/s makes this live rather than a slideshow of&lt;br /&gt;
captures.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' video of the live input-ripple spectrum while the interleaving is dialled&lt;br /&gt;
from all-in-phase to fully staggered. The 100 kHz fundamental and its harmonics collapse in&lt;br /&gt;
real time and 800 kHz emerges. This is the most visually striking result available from this&lt;br /&gt;
hardware.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Load all 8 phases at a common operating point.&lt;br /&gt;
# Configure the MXO34 for live FFT of the battery-bus ripple current, span covering 50 kHz to 2 MHz.&lt;br /&gt;
# Baseline: force all phases in phase (identical &amp;lt;code&amp;gt;PHASE_EN&amp;lt;/code&amp;gt; alignment). Capture the spectrum.&lt;br /&gt;
# Step the interleaving toward the designed 8-way stagger. Record continuously.&lt;br /&gt;
# Capture the final staggered spectrum and measure the attenuation of the 100 kHz component and the amplitude of the new 800 kHz component.&lt;br /&gt;
# '''Companion capture:''' LMG671 transient recorder at 10 MS/s on as many phase currents as channels allow, giving 8 staggered triangle waves in one frame. Note the channel budget constraint.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
Substantial attenuation of the 100 kHz fundamental with energy reappearing at&lt;br /&gt;
8 x 100 kHz = 800 kHz. Quantify rather than asserting; imperfect current sharing between&lt;br /&gt;
phases limits the achievable cancellation, and that mismatch is itself a useful result.&lt;br /&gt;
&lt;br /&gt;
== Experiment 5: Efficiency map and phase shedding ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' what is peak efficiency, with what uncertainty, and what is the optimal&lt;br /&gt;
number of active phases at partial load?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' LMG671. At 25 W per phase the differences between shedding schedules&lt;br /&gt;
are fractions of a percent, so accuracy is the whole justification.&lt;br /&gt;
&lt;br /&gt;
'''Hero shots:''' an efficiency contour map over the operating envelope with an explicit&lt;br /&gt;
uncertainty budget; and a family of 8 efficiency curves whose crossing points define the&lt;br /&gt;
shedding schedule.&lt;br /&gt;
&lt;br /&gt;
=== Procedure: efficiency map ===&lt;br /&gt;
&lt;br /&gt;
# Define the reference planes precisely and document them. State whether connector and cable losses are attributed to the converter. '''This decision must be stated in any published figure.'''&lt;br /&gt;
# Sweep input voltage 20 to 55 V and per-phase current 0 to 8 A on a grid. Soak to thermal equilibrium at each point.&lt;br /&gt;
# Record LMG671 input power, output power, efficiency, and both NTC temperatures.&lt;br /&gt;
# Produce a contour map. Compare against the existing plots in &amp;lt;code&amp;gt;Measurements/&amp;lt;/code&amp;gt; (50 V, 64 V, 72 V) as a sanity check on the older hardware.&lt;br /&gt;
&lt;br /&gt;
=== Procedure: phase shedding ===&lt;br /&gt;
&lt;br /&gt;
# Sweep total system power from ~100 W to full rated.&lt;br /&gt;
# At each power level, measure efficiency with N = 1, 2, 4, 6, 8 phases active.&lt;br /&gt;
# Plot the family of curves and locate the crossing points.&lt;br /&gt;
# Derive the optimal shedding schedule and '''implement it in firmware'''.&lt;br /&gt;
# Re-measure to confirm the predicted partial-load gain.&lt;br /&gt;
&lt;br /&gt;
=== Uncertainty budget ===&lt;br /&gt;
&lt;br /&gt;
At 99% efficiency the uncertainty budget is the result. Document:&lt;br /&gt;
&lt;br /&gt;
* Instrument accuracy specification at the actual operating point, not the headline figure.&lt;br /&gt;
* Sense-lead placement and what is included inside the measurement boundary.&lt;br /&gt;
* Thermal drift over a 30-minute soak, measured by repeating one point.&lt;br /&gt;
* Repeatability across at least 3 independent runs of the same point.&lt;br /&gt;
&lt;br /&gt;
A separate write-up on how not to overstate efficiency, derived from this section, would be&lt;br /&gt;
strong content in its own right and costs nothing extra to produce.&lt;br /&gt;
&lt;br /&gt;
== Optional experiments ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Experiment !! Procedure summary !! Hero instrument&lt;br /&gt;
|-&lt;br /&gt;
| '''Rare-event hunt''' || Zone trigger on SW-node overshoot above ~75 V. Run for hours across all 8 phases. Build a histogram of peak SW voltage over ~10&amp;lt;sup&amp;gt;11&amp;lt;/sup&amp;gt; switching cycles and extract the worst outliers. || MXO34, since ~4.5 M acquisitions/s is the only practical way to catch one-in-10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; events&lt;br /&gt;
|-&lt;br /&gt;
| '''True switching energy''' || De-skew carefully, then integrate v*i per transition. Plot E&amp;lt;sub&amp;gt;on&amp;lt;/sub&amp;gt; and E&amp;lt;sub&amp;gt;off&amp;lt;/sub&amp;gt; against current and compare to the EPC23102 datasheet. || RT-ZISO plus MXO34&lt;br /&gt;
|-&lt;br /&gt;
| '''MPPT tracking efficiency''' || Play a real cloudy-day irradiance profile into a solar array simulator. Compute energy captured divided by theoretical MPP energy, a different metric from conversion efficiency and rarely published honestly. || LMG671 data logger&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The tracking-efficiency experiment requires a solar array simulator, which is '''not&lt;br /&gt;
currently on the bench'''. The firmware contains an &amp;lt;code&amp;gt;Ipvmodel&amp;lt;/code&amp;gt; in&lt;br /&gt;
&amp;lt;code&amp;gt;testing.c&amp;lt;/code&amp;gt; for simulation, but real tracking numbers need real hardware. See&lt;br /&gt;
[[#Open items]].&lt;br /&gt;
&lt;br /&gt;
== Data logging conventions ==&lt;br /&gt;
&lt;br /&gt;
Store raw instrument exports alongside derived plots so results remain reproducible.&lt;br /&gt;
&lt;br /&gt;
 Measurements/&lt;br /&gt;
   YYYY-MM-DD_experiment/&lt;br /&gt;
     raw/          LMG671 and MXO34 exports, unmodified&lt;br /&gt;
     telemetry/    calibrate_mppt.py and CAN logs&lt;br /&gt;
     notes.md      operating point, firmware commit, instrument setup, de-skew values&lt;br /&gt;
     plots/        derived figures&lt;br /&gt;
&lt;br /&gt;
Every session record must include: firmware git commit, hardware serial, ambient&lt;br /&gt;
temperature, source and load configuration, instrument model and options, de-skew values,&lt;br /&gt;
and soak duration. A figure without its operating point is not a result.&lt;br /&gt;
&lt;br /&gt;
== Publication checklist ==&lt;br /&gt;
&lt;br /&gt;
Before posting any measurement:&lt;br /&gt;
&lt;br /&gt;
# Operating point stated on the figure, no exceptions.&lt;br /&gt;
# Uncertainty stated for any efficiency or accuracy claim.&lt;br /&gt;
# Measurement boundary defined for any efficiency claim.&lt;br /&gt;
# Firmware commit recorded, so the result is reproducible.&lt;br /&gt;
# Instrument model and relevant options named.&lt;br /&gt;
# Result independently repeated at least once.&lt;br /&gt;
&lt;br /&gt;
=== Suggested narrative order ===&lt;br /&gt;
&lt;br /&gt;
Posting the unflattering result first is what makes the good result believable later.&lt;br /&gt;
&lt;br /&gt;
# Our MPPT runs at 55 C doing nothing. Experiment 1, opens with the problem.&lt;br /&gt;
# Our own current sensor was lying. Experiment 2, resolves a mystery from post 1.&lt;br /&gt;
# 8 ns of dead time cost us N degrees. Experiment 3, the payoff.&lt;br /&gt;
# What 8 interleaved GaN phases look like. Experiment 4, pure spectacle.&lt;br /&gt;
# 99.x%, and here is our uncertainty budget. Experiment 5, the credibility close.&lt;br /&gt;
&lt;br /&gt;
== Open items ==&lt;br /&gt;
&lt;br /&gt;
* Confirm RT-ZISO model bandwidth and isolation rating.&lt;br /&gt;
* Confirm LMG671 includes transient-recording and data-logger options.&lt;br /&gt;
* Source a solar array simulator, or arrange an alternative for tracking-efficiency work.&lt;br /&gt;
* Confirm which hardware revision is on the bench, and therefore which grounding rules apply.&lt;br /&gt;
* Coordinate with R&amp;amp;S applications engineering, who will co-develop the setups and usually have specific product messaging to hit.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Open-SEC Firmware/src/hardware/eoia656a.h&amp;lt;/code&amp;gt;, hardware parameter definitions&lt;br /&gt;
* &amp;lt;code&amp;gt;calibrate_mppt.py&amp;lt;/code&amp;gt;, calibration and live telemetry tool&lt;br /&gt;
* &amp;lt;code&amp;gt;MPPT_ID32+0-4.dbc&amp;lt;/code&amp;gt;, CAN database&lt;br /&gt;
* &amp;lt;code&amp;gt;Measurements/&amp;lt;/code&amp;gt;, prior efficiency and loss plots&lt;br /&gt;
&lt;br /&gt;
[[Category:Measurement]]&lt;br /&gt;
[[Category:GaN MPPT]]&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaign&amp;diff=343</id>
		<title>R&amp;S Measurement Campaign</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=R%26S_Measurement_Campaign&amp;diff=343"/>
		<updated>2026-08-18T20:24:47Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Created page with &amp;quot;= GaN MPPT Phase: R&amp;amp;S / ZES Measurement Campaign =  Procedures for the measurement campaign on the '''EOI-A65-6A''' GaN MPPT phase boards using the sponsored Rohde &amp;amp; Schwarz and ZES ZIMMER equipment. Each experiment is written to be run start-to-finish by one engineer and to produce both an engineering result and a publishable figure.  __TOC__  == Purpose ==  Three goals, in priority order:  # '''Resolve open engineering questions.''' Idle dissipation, dead-time optimum,...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= GaN MPPT Phase: R&amp;amp;S / ZES Measurement Campaign =&lt;br /&gt;
&lt;br /&gt;
Procedures for the measurement campaign on the '''EOI-A65-6A''' GaN MPPT phase boards&lt;br /&gt;
using the sponsored Rohde &amp;amp; Schwarz and ZES ZIMMER equipment. Each experiment is written&lt;br /&gt;
to be run start-to-finish by one engineer and to produce both an engineering result and a&lt;br /&gt;
publishable figure.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
Three goals, in priority order:&lt;br /&gt;
&lt;br /&gt;
# '''Resolve open engineering questions.''' Idle dissipation, dead-time optimum, and output current-sense accuracy are all unresolved and all measurable with this equipment.&lt;br /&gt;
# '''Calibrate and characterise the product.''' The default configuration ships with &amp;lt;code&amp;gt;calibrated = false&amp;lt;/code&amp;gt; and placeholder sensor gains.&lt;br /&gt;
# '''Produce publishable content.''' Each experiment below defines its hero shot and which instrument capability makes it possible.&lt;br /&gt;
&lt;br /&gt;
An experiment only belongs in this campaign if the instrument is load-bearing, i.e. the&lt;br /&gt;
result is not obtainable with ordinary bench gear. Otherwise it is just a measurement, not&lt;br /&gt;
a story.&lt;br /&gt;
&lt;br /&gt;
== Equipment ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Instrument !! Role !! Capability that matters here&lt;br /&gt;
|-&lt;br /&gt;
| ZES ZIMMER LMG671 || Power / efficiency reference || 0.015%-class accuracy; up to 7 simultaneously-sampled channels; microwatt-resolution DC; transient recorder&lt;br /&gt;
|-&lt;br /&gt;
| R&amp;amp;S MXO34 || Waveform + spectrum || 12-bit always-on; ~4.5 M acquisitions/s; ~45 k FFT/s live spectrum; zone trigger; deep memory&lt;br /&gt;
|-&lt;br /&gt;
| R&amp;amp;S RT-ZISO || Isolated probe || Galvanic isolation with CMRR that survives nanosecond edges, the only honest way to measure floating high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Before promising numbers publicly''', confirm the exact bandwidth and isolation rating&lt;br /&gt;
of the RT-ZISO model supplied, and confirm that the LMG671 includes the transient-recording&lt;br /&gt;
and data-logger options. Experiments 4, 5 and the tracking-efficiency work depend on those&lt;br /&gt;
options.&lt;br /&gt;
&lt;br /&gt;
== Device under test ==&lt;br /&gt;
&lt;br /&gt;
Values below are taken from &amp;lt;code&amp;gt;Open-SEC Firmware/src/hardware/eoia656a.h&amp;lt;/code&amp;gt; and&lt;br /&gt;
&amp;lt;code&amp;gt;eoia656a.c&amp;lt;/code&amp;gt;. Re-check them against the branch under test before quoting them.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Parameter !! Value !! Source&lt;br /&gt;
|-&lt;br /&gt;
| Hardware name || EOI-A65-6A || &amp;lt;code&amp;gt;HW_NAME&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Topology || Synchronous boost, GaN half bridge (EPC23102) || &amp;lt;code&amp;gt;HW_TOPOLOGY_BOOST&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Switching frequency || 100 kHz || &amp;lt;code&amp;gt;HW_SWITCHINGFREQUENCY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Control loop rate || 20 kHz (T&amp;lt;sub&amp;gt;s&amp;lt;/sub&amp;gt; = 50 us) || &amp;lt;code&amp;gt;HW_CONTROLLERFREQUENCY&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Dead time (rising / falling) || 8 ns / 8 ns || &amp;lt;code&amp;gt;HW_DEADTIMERISING&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_DEADTIMEFALLING&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Inductor || 47 uH, DCR 12 mOhm || &amp;lt;code&amp;gt;HW_L&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_RLINT&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Declared C&amp;lt;sub&amp;gt;low&amp;lt;/sub&amp;gt; / C&amp;lt;sub&amp;gt;high&amp;lt;/sub&amp;gt; || 100 uF / 400 uF || &amp;lt;code&amp;gt;HW_CLOW&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HW_CHIGH&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Overvoltage fault (both rails) || 63 V || &amp;lt;code&amp;gt;HW_LIMIT_HS_VOLTAGE_HARD&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Overcurrent fault (both rails) || 13 A || &amp;lt;code&amp;gt;HW_LIMIT_HS_CURRENT_HARD&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Soft current limit || 9 A || &amp;lt;code&amp;gt;HighSideCurrentLimitSoft&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Current sensors || 2 x INA253A2, 2 mOhm integrated shunt, 200 mV/A, zero at 0.5 V || Isense.SchDoc&lt;br /&gt;
|-&lt;br /&gt;
| Phase count per baseboard || 8, interleaved via &amp;lt;code&amp;gt;PHASE_EN&amp;lt;/code&amp;gt;; startup staggered 200 ms per CAN ID || &amp;lt;code&amp;gt;mppt.c&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Temperature sensors || NT1 = ambient, NT2 = FET/heatsink, 100 k NTC, B = 4330 || &amp;lt;code&amp;gt;Temperature_B&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The GaN devices are rated 100 V. The project README recommends '''75 V nominal maximum'''&lt;br /&gt;
for system use. Do not exceed that during these experiments regardless of what the firmware&lt;br /&gt;
fault thresholds allow.&lt;br /&gt;
&lt;br /&gt;
== Safety and bench hygiene ==&lt;br /&gt;
&lt;br /&gt;
=== Grounding ===&lt;br /&gt;
&lt;br /&gt;
'''Verify the grounding topology on your specific board before connecting any&lt;br /&gt;
ground-referenced instrument.'''&lt;br /&gt;
&lt;br /&gt;
On the EOI-A65-6A the INA253 sense elements sit '''in the positive rail'''&lt;br /&gt;
(&amp;lt;code&amp;gt;PANEL_IN+ -&amp;gt; Isense+ / Isense- -&amp;gt; Vin&amp;lt;/code&amp;gt;, and likewise on the output), so input&lt;br /&gt;
and output grounds are common. Single-ended, ground-referenced probing of the switch node&lt;br /&gt;
is therefore permissible.&lt;br /&gt;
&lt;br /&gt;
This differs from the older SEC-B80-8A described in the repository README, which uses&lt;br /&gt;
'''ground-path''' current sensing. On that hardware, shorting input and output grounds&lt;br /&gt;
together, which any two ground-referenced scope probes will do, '''can destroy the board'''.&lt;br /&gt;
If there is any doubt which hardware is on the bench, use the RT-ZISO and treat every node&lt;br /&gt;
as floating.&lt;br /&gt;
&lt;br /&gt;
=== Probing rules ===&lt;br /&gt;
&lt;br /&gt;
* '''Never''' measure high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; with a ground-referenced probe. Its reference floats to the switch node and slews the full bus voltage in nanoseconds. Use the RT-ZISO.&lt;br /&gt;
* At ~55 V with nanosecond edges, probe ground lead length dominates the measurement. Use a ground spring, not a lead, for switch-node captures.&lt;br /&gt;
* The LMG671 inputs are individually isolated; multi-channel connection across the converter is safe.&lt;br /&gt;
* De-skew all channels before any v*i product or timing measurement. Record the de-skew values in the log.&lt;br /&gt;
&lt;br /&gt;
=== Electrical ===&lt;br /&gt;
&lt;br /&gt;
* Treat the DC bus as hazardous above 50 V. Discharge the output bulk capacitance before rework; the baseboard carries substantial bulk on the battery side.&lt;br /&gt;
* Set the source current limit at or below 9 A per phase before enabling the output.&lt;br /&gt;
* Keep a thermal camera or the on-board NTC telemetry visible during any run that changes dead time or disables protections.&lt;br /&gt;
&lt;br /&gt;
== Common bench setup ==&lt;br /&gt;
&lt;br /&gt;
=== Connections ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Node !! Instrument !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| Panel input (V, I) || LMG671 ch1 || Kelvin sense at the board connector, not at the supply&lt;br /&gt;
|-&lt;br /&gt;
| Battery output (V, I) || LMG671 ch2 || Same reference plane convention as the input&lt;br /&gt;
|-&lt;br /&gt;
| Individual phase outputs || LMG671 ch3 to ch7 || Up to 5 phases alongside both terminals; see channel budget note&lt;br /&gt;
|-&lt;br /&gt;
| Switch node (SW) || MXO34 ch1 || Ground spring, shortest possible loop&lt;br /&gt;
|-&lt;br /&gt;
| Low-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; || MXO34 ch2 || &amp;lt;code&amp;gt;BRIDGE_LO&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| High-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; || RT-ZISO on MXO34 ch3 || '''Isolated probe mandatory'''&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; (INA253 output, before R12) || MXO34 ch4 || Pre-filter node; this is where clipping is visible&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Channel budget:''' the LMG671 provides at most 7 channels. With both terminals&lt;br /&gt;
instrumented, 5 phases can be measured simultaneously. To capture all 8 phase currents at&lt;br /&gt;
once, drop the terminal channels and derive totals by summation.&lt;br /&gt;
&lt;br /&gt;
=== Firmware preparation ===&lt;br /&gt;
&lt;br /&gt;
# Build the &amp;lt;code&amp;gt;HW_EOIA65_6A&amp;lt;/code&amp;gt; target in the '''Debug''' configuration. Do not use the Simulation build, which substitutes a model for the power stage.&lt;br /&gt;
# Flash via ST-Link and TAG-Connect (J2, TC2030-NL).&lt;br /&gt;
# Connect USB for the serial terminal. Available commands: &amp;lt;code&amp;gt;help&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ping&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;sens&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;hwinfo&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_read&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_write&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;config_default&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Record the firmware version and git commit hash in the log before every session.&lt;br /&gt;
&lt;br /&gt;
=== Telemetry available without instruments ===&lt;br /&gt;
&lt;br /&gt;
* Serial terminal: &amp;lt;code&amp;gt;sens&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;calibrate_mppt.py PORT --read-val&amp;lt;/code&amp;gt; polls &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Ihigh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Ilow&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vlow&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vhigh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TempHeatsink&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;TempAmbient&amp;lt;/code&amp;gt; at 2 Hz.&lt;br /&gt;
* CAN: status frame every 1000 ms, power frame every 500 ms. See &amp;lt;code&amp;gt;MPPT_ID32+0-4.dbc&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Firmware scope buffer: &amp;lt;code&amp;gt;scope_start()&amp;lt;/code&amp;gt;, up to &amp;lt;code&amp;gt;CONVERTER_SCOPE_CHANNELS&amp;lt;/code&amp;gt; channels sampled at the 20 kHz control rate.&lt;br /&gt;
&lt;br /&gt;
The firmware scope is the natural cross-validation target for the LMG671, see&lt;br /&gt;
[[#Experiment 2: Current-sense verification and calibration|Experiment 2]].&lt;br /&gt;
&lt;br /&gt;
== Experiment 1: Idle power teardown ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' a phase board reaches ~55 C with no power being harvested. Where does every&lt;br /&gt;
milliwatt go?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' LMG671. Microwatt-resolution DC power is what makes the decomposition&lt;br /&gt;
credible.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' waterfall chart attributing the total idle dissipation to each contributor,&lt;br /&gt;
with the measured board temperature alongside each step.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Bring both rails to the nominal operating point (e.g. 35 V in, 55 V out) with the board held in reset. Record LMG671 readings on the 3v3 rail, the 5v2 rail and both HV rails. '''This is the absolute floor''', resistive dividers and leakage only.&lt;br /&gt;
# Release reset with &amp;lt;code&amp;gt;outputEnalbeOnStartup = false&amp;lt;/code&amp;gt;. Delta from step 1 = MCU plus analogue front end.&lt;br /&gt;
# Set the MPPT to disabled (&amp;lt;code&amp;gt;MpptState_Disable&amp;lt;/code&amp;gt;) so &amp;lt;code&amp;gt;DREN&amp;lt;/code&amp;gt; is de-asserted and the bridge is not switching. Confirm via &amp;lt;code&amp;gt;status&amp;lt;/code&amp;gt;. Delta from step 2 should be near zero; a large delta means something is switching that should not be.&lt;br /&gt;
# Enable the output with no input power available. Delta from step 3 = power-stage idle loss plus any reverse power flow.&lt;br /&gt;
# At each step, log &amp;lt;code&amp;gt;TempHeatsink&amp;lt;/code&amp;gt; (NT2) and &amp;lt;code&amp;gt;TempAmbient&amp;lt;/code&amp;gt; (NT1) after a 20-minute thermal soak, plus a thermal camera frame.&lt;br /&gt;
# '''Check &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; at step 4.''' The default configuration sets &amp;lt;code&amp;gt;LowSideCurrentMinLimitSoft = -300 mA&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;PhaseHighSideEnableCurrent = -500 mA&amp;lt;/code&amp;gt;, which permit reverse inductor current. If &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; sits at -0.3 A, the phase is actively pumping power from the battery back into the panel. Record the value and compute the drain across all 8 phases.&lt;br /&gt;
# Repeat step 4 with &amp;lt;code&amp;gt;LowSideCurrentMinLimitSoft = 0&amp;lt;/code&amp;gt; to quantify the reverse-flow contribution in isolation.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
Roughly 0.5 to 1.2 W per phase total, with the power stage accounting for the large&lt;br /&gt;
majority and the MCU 0.10 to 0.15 W. Reverse-current pumping, if present, is the single&lt;br /&gt;
largest term and shows up as a battery drain far larger than the on-board dissipation.&lt;br /&gt;
&lt;br /&gt;
=== Pass criteria ===&lt;br /&gt;
&lt;br /&gt;
Every measured step accounted for within 10% of the sum of its identified contributors.&lt;br /&gt;
Unattributed residual is itself a finding, so log it rather than hiding it.&lt;br /&gt;
&lt;br /&gt;
== Experiment 2: Current-sense verification and calibration ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' the output current reading is known to run high. Why, and by how much?&lt;br /&gt;
&lt;br /&gt;
'''Hero instruments:''' MXO34 (12-bit, to see the pre-filter waveform) and LMG671 (as the&lt;br /&gt;
reference for calibration).&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' two panels. The &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; waveform showing pulsed shunt current&lt;br /&gt;
against the smooth inductor current, and a scatter plot of firmware-reported versus&lt;br /&gt;
LMG671-measured output current, before and after calibration.&lt;br /&gt;
&lt;br /&gt;
=== Background ===&lt;br /&gt;
&lt;br /&gt;
The local output capacitance is C18 to C23 (6 x 1 uF 0603) plus C24. The schematic notes&lt;br /&gt;
&amp;quot;each about 130nF left at 55v&amp;quot;, so effective local capacitance at 55 V is roughly 4.8 uF&lt;br /&gt;
(|Z| approximately 0.33 Ohm at 100 kHz). The declared 400 uF of bulk sits on the baseboard,&lt;br /&gt;
on the far side of the output shunt, reachable only through ~2 mOhm plus interconnect&lt;br /&gt;
inductance (|Z| approximately 0.13 Ohm at 200 nH). The lower-impedance path is therefore&lt;br /&gt;
through the shunt, so roughly 70% of the bridge's pulsed current flows through the sense&lt;br /&gt;
element rather than being absorbed locally.&lt;br /&gt;
&lt;br /&gt;
The ADC samples this with a 267 ns aperture at a fixed phase locked to the PWM, so the&lt;br /&gt;
residual ripple contributes a systematic offset rather than averageable noise.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Capture &amp;lt;code&amp;gt;CURR_SE&amp;lt;/code&amp;gt; (MXO34 ch4) together with the low-side gate drive. Confirm the pulse train: approximately zero during D, approximately &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; during (1-D).&lt;br /&gt;
# '''Look for flat tops.''' At &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt; peaks near 13 A the INA253 output demands 0.5 + 2.6 = 3.1 V, into the supply rail. If the peaks are clipped or slew-limited, the error is nonlinear and '''no amount of downstream averaging will fix it'''. This determines whether the software mitigation is worth implementing at all.&lt;br /&gt;
# Simultaneously log firmware &amp;lt;code&amp;gt;Ihigh&amp;lt;/code&amp;gt; (via &amp;lt;code&amp;gt;--read-val&amp;lt;/code&amp;gt;) and LMG671 output current across the full current range at several bus voltages. Plot the error.&lt;br /&gt;
# Repeat for &amp;lt;code&amp;gt;Iind&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Vlow&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Vhigh&amp;lt;/code&amp;gt;. The input shunt carries continuous inductor current and should show a much smaller error; that contrast is the point.&lt;br /&gt;
# Run the calibration procedure using the LMG671 as reference: &amp;lt;code&amp;gt;python calibrate_mppt.py PORT&amp;lt;/code&amp;gt;, then menu options 3 to 10 (zero offset and gain for each of input voltage, output voltage, input current, output current), option 11 for the NTC, and '''option 12 to store to EEPROM'''.&lt;br /&gt;
# Re-run step 3 and overlay before and after.&lt;br /&gt;
&lt;br /&gt;
=== Note ===&lt;br /&gt;
&lt;br /&gt;
Calibration corrects gain and offset. It '''cannot''' correct the ripple-induced error,&lt;br /&gt;
because that error varies with duty cycle and load. Expect a residual that scales with&lt;br /&gt;
output ripple, and report it as such.&lt;br /&gt;
&lt;br /&gt;
Also worth logging: &amp;lt;code&amp;gt;phase.Ihigh&amp;lt;/code&amp;gt; is unfiltered&lt;br /&gt;
(&amp;lt;code&amp;gt;CURRENT_IN_FORGETING_FACTOR = 0&amp;lt;/code&amp;gt;) and feeds the 13 A hard fault directly, so a&lt;br /&gt;
single sample landing on a ripple peak can cause a nuisance trip. Record any spurious&lt;br /&gt;
&amp;lt;code&amp;gt;Converter_OutputOverCurrent&amp;lt;/code&amp;gt; events observed during the campaign.&lt;br /&gt;
&lt;br /&gt;
== Experiment 3: Dead-time optimisation ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' is 8 ns of dead time causing shoot-through, and what is the optimum?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' RT-ZISO. High-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; on a node slewing 55 V in&lt;br /&gt;
nanoseconds is exactly what a differential probe cannot measure.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' three stacked panels sharing an x-axis. Gate overlap waveforms at 8 ns&lt;br /&gt;
versus the optimum, LMG671 efficiency versus dead time, and &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt; versus dead&lt;br /&gt;
time. One image, three independent measurements, one causal chain.&lt;br /&gt;
&lt;br /&gt;
=== Prerequisite ===&lt;br /&gt;
&lt;br /&gt;
The dead-time register write in &amp;lt;code&amp;gt;pwm.c&amp;lt;/code&amp;gt; previously OR-ed the computed value onto&lt;br /&gt;
the HAL default, so most settings landed on the wrong value. '''This must be fixed before&lt;br /&gt;
the sweep is meaningful.''' With the fix in place the commanded value is written directly.&lt;br /&gt;
&lt;br /&gt;
Historical behaviour, for reference. Note that 15 ns and 20 ns were previously identical on&lt;br /&gt;
hardware, so any earlier sweep would have shown no difference between them:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Commanded !! Register count !! Actual, before fix !! Actual, after fix&lt;br /&gt;
|-&lt;br /&gt;
| 8 ns || 10 || 8.33 ns || 8.33 ns&lt;br /&gt;
|-&lt;br /&gt;
| 10 ns || 12 || 11.67 ns || 10.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 15 ns || 18 || '''21.67 ns''' || 15.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 20 ns || 24 || '''21.67 ns''' || 20.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 30 ns || 36 || '''38.33 ns''' || 30.00 ns&lt;br /&gt;
|-&lt;br /&gt;
| 40 ns || 48 || '''48.33 ns''' || 40.00 ns&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Dead-time resolution is 1/(f&amp;lt;sub&amp;gt;HRTIM&amp;lt;/sub&amp;gt; x 8) = 0.83 ns per count at 150 MHz. If the&lt;br /&gt;
system clock is changed, the quantisation changes with it.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Verify the fix: set &amp;lt;code&amp;gt;HW_DEADTIMERISING&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;HW_DEADTIMEFALLING&amp;lt;/code&amp;gt; to 20 ns, halt, and read &amp;lt;code&amp;gt;HRTIM1-&amp;gt;sTimerxRegs[1].DTxR&amp;lt;/code&amp;gt;. Expect DTR and DTF fields = 24, not 26.&lt;br /&gt;
# Establish a fixed operating point (for example 35 V in, 55 V out, 4 A per phase) and let it thermally soak for 20 minutes.&lt;br /&gt;
# For each dead-time value in 5, 8, 10, 15, 20, 25, 30, 40 ns: rebuild, flash, soak 20 min, then record LMG671 efficiency, &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Tambient&amp;lt;/code&amp;gt;, and an MXO34 capture of high-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; plus low-side V&amp;lt;sub&amp;gt;GS&amp;lt;/sub&amp;gt; plus SW node plus inductor current.&lt;br /&gt;
# Inspect each capture for genuine gate overlap. Overlap plus a current spike on the switch node at the transition is shoot-through.&lt;br /&gt;
# Plot efficiency and temperature against dead time on a shared axis.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
A U-shaped efficiency curve. Too little dead time causes shoot-through; too much forces the&lt;br /&gt;
inductor current through the high-side device in reverse-conduction mode at a significantly&lt;br /&gt;
higher voltage drop. GaN devices have no body diode, so the reverse-conduction penalty at&lt;br /&gt;
excessive dead time is steeper than for a silicon bridge.&lt;br /&gt;
&lt;br /&gt;
=== Safety ===&lt;br /&gt;
&lt;br /&gt;
Below 8 ns, shoot-through risk is real. Start at reduced bus voltage and reduced current,&lt;br /&gt;
watch &amp;lt;code&amp;gt;Tfets&amp;lt;/code&amp;gt; continuously, and abort on any rapid temperature rise.&lt;br /&gt;
&lt;br /&gt;
== Experiment 4: Interleaving ripple cancellation ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' does the 8-phase interleaving actually cancel bus ripple as designed?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' MXO34. Roughly 45 k FFT/s makes this live rather than a slideshow of&lt;br /&gt;
captures.&lt;br /&gt;
&lt;br /&gt;
'''Hero shot:''' video of the live input-ripple spectrum while the interleaving is dialled&lt;br /&gt;
from all-in-phase to fully staggered. The 100 kHz fundamental and its harmonics collapse in&lt;br /&gt;
real time and 800 kHz emerges. This is the most visually striking result available from this&lt;br /&gt;
hardware.&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
# Load all 8 phases at a common operating point.&lt;br /&gt;
# Configure the MXO34 for live FFT of the battery-bus ripple current, span covering 50 kHz to 2 MHz.&lt;br /&gt;
# Baseline: force all phases in phase (identical &amp;lt;code&amp;gt;PHASE_EN&amp;lt;/code&amp;gt; alignment). Capture the spectrum.&lt;br /&gt;
# Step the interleaving toward the designed 8-way stagger. Record continuously.&lt;br /&gt;
# Capture the final staggered spectrum and measure the attenuation of the 100 kHz component and the amplitude of the new 800 kHz component.&lt;br /&gt;
# '''Companion capture:''' LMG671 transient recorder at 10 MS/s on as many phase currents as channels allow, giving 8 staggered triangle waves in one frame. Note the channel budget constraint.&lt;br /&gt;
&lt;br /&gt;
=== Expected ===&lt;br /&gt;
&lt;br /&gt;
Substantial attenuation of the 100 kHz fundamental with energy reappearing at&lt;br /&gt;
8 x 100 kHz = 800 kHz. Quantify rather than asserting; imperfect current sharing between&lt;br /&gt;
phases limits the achievable cancellation, and that mismatch is itself a useful result.&lt;br /&gt;
&lt;br /&gt;
== Experiment 5: Efficiency map and phase shedding ==&lt;br /&gt;
&lt;br /&gt;
'''Question:''' what is peak efficiency, with what uncertainty, and what is the optimal&lt;br /&gt;
number of active phases at partial load?&lt;br /&gt;
&lt;br /&gt;
'''Hero instrument:''' LMG671. At 25 W per phase the differences between shedding schedules&lt;br /&gt;
are fractions of a percent, so accuracy is the whole justification.&lt;br /&gt;
&lt;br /&gt;
'''Hero shots:''' an efficiency contour map over the operating envelope with an explicit&lt;br /&gt;
uncertainty budget; and a family of 8 efficiency curves whose crossing points define the&lt;br /&gt;
shedding schedule.&lt;br /&gt;
&lt;br /&gt;
=== Procedure: efficiency map ===&lt;br /&gt;
&lt;br /&gt;
# Define the reference planes precisely and document them. State whether connector and cable losses are attributed to the converter. '''This decision must be stated in any published figure.'''&lt;br /&gt;
# Sweep input voltage 20 to 55 V and per-phase current 0 to 8 A on a grid. Soak to thermal equilibrium at each point.&lt;br /&gt;
# Record LMG671 input power, output power, efficiency, and both NTC temperatures.&lt;br /&gt;
# Produce a contour map. Compare against the existing plots in &amp;lt;code&amp;gt;Measurements/&amp;lt;/code&amp;gt; (50 V, 64 V, 72 V) as a sanity check on the older hardware.&lt;br /&gt;
&lt;br /&gt;
=== Procedure: phase shedding ===&lt;br /&gt;
&lt;br /&gt;
# Sweep total system power from ~100 W to full rated.&lt;br /&gt;
# At each power level, measure efficiency with N = 1, 2, 4, 6, 8 phases active.&lt;br /&gt;
# Plot the family of curves and locate the crossing points.&lt;br /&gt;
# Derive the optimal shedding schedule and '''implement it in firmware'''.&lt;br /&gt;
# Re-measure to confirm the predicted partial-load gain.&lt;br /&gt;
&lt;br /&gt;
=== Uncertainty budget ===&lt;br /&gt;
&lt;br /&gt;
At 99% efficiency the uncertainty budget is the result. Document:&lt;br /&gt;
&lt;br /&gt;
* Instrument accuracy specification at the actual operating point, not the headline figure.&lt;br /&gt;
* Sense-lead placement and what is included inside the measurement boundary.&lt;br /&gt;
* Thermal drift over a 30-minute soak, measured by repeating one point.&lt;br /&gt;
* Repeatability across at least 3 independent runs of the same point.&lt;br /&gt;
&lt;br /&gt;
A separate write-up on how not to overstate efficiency, derived from this section, would be&lt;br /&gt;
strong content in its own right and costs nothing extra to produce.&lt;br /&gt;
&lt;br /&gt;
== Optional experiments ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Experiment !! Procedure summary !! Hero instrument&lt;br /&gt;
|-&lt;br /&gt;
| '''Rare-event hunt''' || Zone trigger on SW-node overshoot above ~75 V. Run for hours across all 8 phases. Build a histogram of peak SW voltage over ~10&amp;lt;sup&amp;gt;11&amp;lt;/sup&amp;gt; switching cycles and extract the worst outliers. || MXO34, since ~4.5 M acquisitions/s is the only practical way to catch one-in-10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; events&lt;br /&gt;
|-&lt;br /&gt;
| '''True switching energy''' || De-skew carefully, then integrate v*i per transition. Plot E&amp;lt;sub&amp;gt;on&amp;lt;/sub&amp;gt; and E&amp;lt;sub&amp;gt;off&amp;lt;/sub&amp;gt; against current and compare to the EPC23102 datasheet. || RT-ZISO plus MXO34&lt;br /&gt;
|-&lt;br /&gt;
| '''MPPT tracking efficiency''' || Play a real cloudy-day irradiance profile into a solar array simulator. Compute energy captured divided by theoretical MPP energy, a different metric from conversion efficiency and rarely published honestly. || LMG671 data logger&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The tracking-efficiency experiment requires a solar array simulator, which is '''not&lt;br /&gt;
currently on the bench'''. The firmware contains an &amp;lt;code&amp;gt;Ipvmodel&amp;lt;/code&amp;gt; in&lt;br /&gt;
&amp;lt;code&amp;gt;testing.c&amp;lt;/code&amp;gt; for simulation, but real tracking numbers need real hardware. See&lt;br /&gt;
[[#Open items]].&lt;br /&gt;
&lt;br /&gt;
== Data logging conventions ==&lt;br /&gt;
&lt;br /&gt;
Store raw instrument exports alongside derived plots so results remain reproducible.&lt;br /&gt;
&lt;br /&gt;
 Measurements/&lt;br /&gt;
   YYYY-MM-DD_experiment/&lt;br /&gt;
     raw/          LMG671 and MXO34 exports, unmodified&lt;br /&gt;
     telemetry/    calibrate_mppt.py and CAN logs&lt;br /&gt;
     notes.md      operating point, firmware commit, instrument setup, de-skew values&lt;br /&gt;
     plots/        derived figures&lt;br /&gt;
&lt;br /&gt;
Every session record must include: firmware git commit, hardware serial, ambient&lt;br /&gt;
temperature, source and load configuration, instrument model and options, de-skew values,&lt;br /&gt;
and soak duration. A figure without its operating point is not a result.&lt;br /&gt;
&lt;br /&gt;
== Publication checklist ==&lt;br /&gt;
&lt;br /&gt;
Before posting any measurement:&lt;br /&gt;
&lt;br /&gt;
# Operating point stated on the figure, no exceptions.&lt;br /&gt;
# Uncertainty stated for any efficiency or accuracy claim.&lt;br /&gt;
# Measurement boundary defined for any efficiency claim.&lt;br /&gt;
# Firmware commit recorded, so the result is reproducible.&lt;br /&gt;
# Instrument model and relevant options named.&lt;br /&gt;
# Result independently repeated at least once.&lt;br /&gt;
&lt;br /&gt;
=== Suggested narrative order ===&lt;br /&gt;
&lt;br /&gt;
Posting the unflattering result first is what makes the good result believable later.&lt;br /&gt;
&lt;br /&gt;
# Our MPPT runs at 55 C doing nothing. Experiment 1, opens with the problem.&lt;br /&gt;
# Our own current sensor was lying. Experiment 2, resolves a mystery from post 1.&lt;br /&gt;
# 8 ns of dead time cost us N degrees. Experiment 3, the payoff.&lt;br /&gt;
# What 8 interleaved GaN phases look like. Experiment 4, pure spectacle.&lt;br /&gt;
# 99.x%, and here is our uncertainty budget. Experiment 5, the credibility close.&lt;br /&gt;
&lt;br /&gt;
== Open items ==&lt;br /&gt;
&lt;br /&gt;
* Confirm RT-ZISO model bandwidth and isolation rating.&lt;br /&gt;
* Confirm LMG671 includes transient-recording and data-logger options.&lt;br /&gt;
* Source a solar array simulator, or arrange an alternative for tracking-efficiency work.&lt;br /&gt;
* Confirm which hardware revision is on the bench, and therefore which grounding rules apply.&lt;br /&gt;
* Coordinate with R&amp;amp;S applications engineering, who will co-develop the setups and usually have specific product messaging to hit.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Open-SEC Firmware/src/hardware/eoia656a.h&amp;lt;/code&amp;gt;, hardware parameter definitions&lt;br /&gt;
* &amp;lt;code&amp;gt;calibrate_mppt.py&amp;lt;/code&amp;gt;, calibration and live telemetry tool&lt;br /&gt;
* &amp;lt;code&amp;gt;MPPT_ID32+0-4.dbc&amp;lt;/code&amp;gt;, CAN database&lt;br /&gt;
* &amp;lt;code&amp;gt;Measurements/&amp;lt;/code&amp;gt;, prior efficiency and loss plots&lt;br /&gt;
&lt;br /&gt;
[[Category:Measurement]]&lt;br /&gt;
[[Category:GaN MPPT]]&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Tuning_parameters&amp;diff=342</id>
		<title>Tuning parameters</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Tuning_parameters&amp;diff=342"/>
		<updated>2026-08-18T17:49:30Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Created page with &amp;quot;The '''tuning parameters''' are the live-adjustable knobs of the autopilot's foiling control system: the roll and pitch attitude loops, the ride-height loop, the rear-foil schedule, coordinated-turn banking, and the operating-mode switches. Every one of them can be read and written over the CAN bus while the boat is running, from the helm keyboard, the datalogger, or any other node — which is what makes on-water tuning possible...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''tuning parameters''' are the live-adjustable knobs of the [[Autopilot|autopilot]]'s foiling control system: the roll and pitch attitude loops, the ride-height loop, the rear-foil schedule, coordinated-turn banking, and the operating-mode switches. Every one of them can be read and written over the [[CAN-bus|CAN bus]] while the boat is running, from the helm keyboard, the [[Datalogger|datalogger]], or any other node — which is what makes on-water tuning possible without a laptop, a cable, or a MAVLink link.&lt;br /&gt;
&lt;br /&gt;
The autopilot side of this is &amp;lt;code&amp;gt;foil_tune.lua&amp;lt;/code&amp;gt;, a small dedicated script on the flight controller that bridges CAN to the parameter system. It is deliberately separate from the control script (&amp;lt;code&amp;gt;hydrofoils.lua&amp;lt;/code&amp;gt;): a bug in the tuning bridge can never take down the control loop, and simply not uploading the file removes the tuning surface entirely. The two never talk to each other — parameters ''are'' the shared state. The keyboard tool (&amp;lt;code&amp;gt;tools/foil_tune.py&amp;lt;/code&amp;gt;) and the [[Instrumentation Panel|display]]'s foiling screen mirror the same map; the hotkeys and cursor grid are documented in &amp;lt;code&amp;gt;FOILING_PARAMETERS.md&amp;lt;/code&amp;gt; of the eoi-can repository.&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Tuning bridge summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Protocol version || '''7''' (both sides check it at connect; index &amp;lt;code&amp;gt;0xFE&amp;lt;/code&amp;gt; returns it)&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x260&amp;lt;/code&amp;gt; set · &amp;lt;code&amp;gt;0x261&amp;lt;/code&amp;gt; value/ack · &amp;lt;code&amp;gt;0x262&amp;lt;/code&amp;gt; request — 11-bit standard IDs&lt;br /&gt;
|-&lt;br /&gt;
| Value encoding || IEEE754 float32, little-endian&lt;br /&gt;
|-&lt;br /&gt;
| Parameters || 50 tunable entries (indices 1–57 with gaps; 37/38 retired)&lt;br /&gt;
|-&lt;br /&gt;
| Persistence || All writes '''volatile''' by default — a power-cycle reverts a bad tune; persisting to flash is an explicit flag&lt;br /&gt;
|-&lt;br /&gt;
| Safety || FC-side whitelist with min/max clamps; envelope and mode entries '''locked while foiling'''&lt;br /&gt;
|-&lt;br /&gt;
| Boot behaviour || One unsolicited full dump ~5 s after every FC boot, so passive listeners repopulate&lt;br /&gt;
|-&lt;br /&gt;
| Pacing || Dumps and bulk restores stream at ≤ 8 frames per 50 ms; senders pace to ~40 frames/s&lt;br /&gt;
|-&lt;br /&gt;
| Bus priority || The &amp;lt;code&amp;gt;0x2xx&amp;lt;/code&amp;gt; tuning IDs lose CAN arbitration to every safety frame (&amp;lt;code&amp;gt;0x010&amp;lt;/code&amp;gt;–&amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;) — tuning can only use idle bus time&lt;br /&gt;
|-&lt;br /&gt;
| Source of truth || The &amp;lt;code&amp;gt;PT&amp;lt;/code&amp;gt; table in &amp;lt;code&amp;gt;foil_tune.lua&amp;lt;/code&amp;gt;; mirrored in &amp;lt;code&amp;gt;tools/foil_tune.py&amp;lt;/code&amp;gt; and this page&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== How a parameter is read and written ==&lt;br /&gt;
&lt;br /&gt;
Three frame types carry everything. A '''set''' (&amp;lt;code&amp;gt;0x260&amp;lt;/code&amp;gt;) names a parameter by its index, carries the new value as a float32, and one flag bit: persist-to-flash, which is normally left off so that everything written during a tuning session evaporates on the next power-cycle unless deliberately saved. A '''request''' (&amp;lt;code&amp;gt;0x262&amp;lt;/code&amp;gt;) asks for one index, or for the whole table (&amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt;), or for the protocol version (&amp;lt;code&amp;gt;0xFE&amp;lt;/code&amp;gt;). Every set and every request is answered by a '''value frame''' (&amp;lt;code&amp;gt;0x261&amp;lt;/code&amp;gt;) that carries a status byte and — crucially — the value ''read back from the flight controller after'' clamping, locking and type-casting. The reply never echoes what was merely requested: it reports what the boat is actually using. A display that simply listens to &amp;lt;code&amp;gt;0x261&amp;lt;/code&amp;gt; therefore mirrors every tuning change made by anyone, in real time, without sending a single frame.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Status byte in the &amp;lt;code&amp;gt;0x261&amp;lt;/code&amp;gt; reply&lt;br /&gt;
|-&lt;br /&gt;
! Code !! Name !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0 || ok || Accepted (or read) as-is&lt;br /&gt;
|-&lt;br /&gt;
| 1 || unknown || No such index (includes the retired indices 37 and 38)&lt;br /&gt;
|-&lt;br /&gt;
| 2 || clamped || The value hit the whitelist's min/max; the reply carries the clamped value in force&lt;br /&gt;
|-&lt;br /&gt;
| 3 || failed || The parameter system refused the write (should not happen in practice)&lt;br /&gt;
|-&lt;br /&gt;
| 4 || unavailable || The parameter does not exist yet — &amp;lt;code&amp;gt;HYD_*&amp;lt;/code&amp;gt; before &amp;lt;code&amp;gt;hydrofoils.lua&amp;lt;/code&amp;gt; has booted, or &amp;lt;code&amp;gt;TRN_*&amp;lt;/code&amp;gt; when &amp;lt;code&amp;gt;foil_turn.lua&amp;lt;/code&amp;gt; is not uploaded at all. For the turn parameters this doubles as the &amp;quot;is the feature installed?&amp;quot; probe&lt;br /&gt;
|-&lt;br /&gt;
| 5 || LOCKED || Refused: the entry is envelope/mode-class and the boat is under autopilot control (see below). The reply carries the unchanged value in force&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Three bus behaviours are worth knowing when building anything that listens:&lt;br /&gt;
&lt;br /&gt;
* '''Boot dump.''' About five seconds after every flight-controller boot, the bridge transmits one unsolicited full dump — one &amp;lt;code&amp;gt;0x261&amp;lt;/code&amp;gt; per entry, then an end marker (index &amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt;, value = entry count). A passive listener is repopulated after every power-cycle without asking, and can always re-request with &amp;lt;code&amp;gt;0x262&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt;.&lt;br /&gt;
* '''Cursor convention.''' When the helm keyboard's cursor settles on a cell, the tool ''re-requests'' that cell's parameter(s). The resulting &amp;lt;code&amp;gt;0x261&amp;lt;/code&amp;gt; is read-only — no value changes — but tells the display where the cursor is: the latest solo value frame is &amp;quot;cursor here&amp;quot;. During active tuning the acks serve the same purpose.&lt;br /&gt;
* '''Congestion is a non-issue by construction.''' CAN arbitration gives numerically ''lower'' IDs priority, so the tuning traffic (&amp;lt;code&amp;gt;0x260&amp;lt;/code&amp;gt;–&amp;lt;code&amp;gt;0x262&amp;lt;/code&amp;gt;) and the [[Throttle|throttle]] beeper (&amp;lt;code&amp;gt;0x338&amp;lt;/code&amp;gt;) always yield to the safety frames — rear foil &amp;lt;code&amp;gt;0x010&amp;lt;/code&amp;gt;, height sensors &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; — bit-for-bit, without delaying them. A full 50-parameter restore plus its 50 acks costs about 13 ms of wire time at 1 Mbit/s, and is paced anyway to protect the FC's receive buffer.&lt;br /&gt;
&lt;br /&gt;
== Safety model ==&lt;br /&gt;
&lt;br /&gt;
Two independent mechanisms protect the boat from the tuning surface itself, because the bridge is reachable from any node on a shared bus and — since the helm keyboard exists — from a single keystroke:&lt;br /&gt;
&lt;br /&gt;
* '''Whitelist and clamps (wrong values).''' Only the entries in the table below exist; everything else is refused. Every accepted value is clamped FC-side to the listed min/max, so no UI bug can write an out-of-range gain. A few floors are deliberately non-zero: the rate-loop P gains cannot go below 0.02, because &amp;lt;code&amp;gt;P = 0&amp;lt;/code&amp;gt; means &amp;quot;inner loop off&amp;quot;, which is not a tune and must not be one key-repeat away on a manned foiler. Likewise &amp;lt;code&amp;gt;HYD_RKP&amp;lt;/code&amp;gt; cannot go below 0.15: it is the artificial tailplane that makes the boat statically stable at all (0.059 exactly neutralises the natural −59 mm stability margin), so &amp;quot;0&amp;quot; would hand back a divergent boat.&lt;br /&gt;
* '''In-flight locks (wrong moments).''' Some entries are refused outright (status 5) whenever the enable pin says the autopilot is controlling the boat. These are the entries that are ''not tuning at all'': they change what the system is doing rather than how well it does it. A wrong gain degrades gradually; these act instantly. The attitude '''envelope''' (&amp;lt;code&amp;gt;ROLL_LIMIT_DEG&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PTCH_LIM_*&amp;lt;/code&amp;gt;) defines what the boat is ever allowed to do — its whole legal range contains capsize territory, so changing it is an ashore decision. The '''mode knobs''' &amp;lt;code&amp;gt;SCR_USER1&amp;lt;/code&amp;gt; (flips to roll-test mode, dropping the height loop on the very next 10 Hz iteration) and &amp;lt;code&amp;gt;SCR_USER4&amp;lt;/code&amp;gt; (open-loop rear-foil jog, a bench calibration function) are flight-mode changes, not parameter tweaks. &amp;lt;code&amp;gt;TRN_REV&amp;lt;/code&amp;gt; flips the steering sense — mid-turn that would drive the bank to the opposite stop. To change any of them: enable off, write, re-enable — a deliberate two-hand action.&lt;br /&gt;
&lt;br /&gt;
The lock's arbiter is the enable pin, and its ambiguity fails safe: if the button configuration is not verifiably in place, the bridge assumes the boat ''is'' under control and locks — the safe default for &amp;quot;may I change this?&amp;quot; is the opposite of the safe default for &amp;quot;should I control the boat?&amp;quot;. Note that &amp;lt;code&amp;gt;TRN_ENABLE&amp;lt;/code&amp;gt; is deliberately ''not'' locked: it is the in-flight off-switch for the banking feature, and switching it off ramps the bank out at &amp;lt;code&amp;gt;TRN_RATE&amp;lt;/code&amp;gt; rather than snapping.&lt;br /&gt;
&lt;br /&gt;
Recovery from a bad (volatile) tune is always the same: enable off, power-cycle — everything not explicitly persisted reverts to the values in flash.&lt;br /&gt;
&lt;br /&gt;
== Parameter map ==&lt;br /&gt;
&lt;br /&gt;
Indices are the wire contract: they are only ever appended, never renumbered, and a change bumps the protocol version. ''Hotkey'' is the helm-display key that jumps to the cell (case matters: &amp;lt;code&amp;gt;P&amp;lt;/code&amp;gt; is the rate loop's P, &amp;lt;code&amp;gt;p&amp;lt;/code&amp;gt; the height loop's). ''Fine'' and ''coarse'' are the increment sizes used by the keyboard's volume keys / &amp;lt;code&amp;gt;+−&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;amp;lt;&amp;amp;gt;&amp;lt;/code&amp;gt; respectively — conventions of the tools, not of the protocol, which always carries absolute values.&lt;br /&gt;
&lt;br /&gt;
=== Roll inner loop ===&lt;br /&gt;
&lt;br /&gt;
The ArduPlane FBWA rate-plus-angle controller, driving the two front foils differentially (elevon mixing). Roll is the high-authority axis — about 14.5 °/s of response per degree of foil — so its gains are numerically small.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Idx !! Hotkey !! Parameter !! Min !! Max !! Fine !! Coarse !! Locked !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1 || &amp;lt;code&amp;gt;P&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_P&amp;lt;/code&amp;gt; || 0.02 || 2 || 0.005 || 0.02 || || Roll rate P gain. Floor is non-zero on purpose: 0 would switch the inner loop off&lt;br /&gt;
|-&lt;br /&gt;
| 2 || &amp;lt;code&amp;gt;I&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_I&amp;lt;/code&amp;gt; || 0 || 2 || 0.005 || 0.02 || || Roll rate integrator gain — holds trim against steady asymmetry (e.g. crew weight)&lt;br /&gt;
|-&lt;br /&gt;
| 3 || &amp;lt;code&amp;gt;D&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_D&amp;lt;/code&amp;gt; || 0 || 0.5 || 0.001 || 0.005 || || Roll rate damping gain&lt;br /&gt;
|-&lt;br /&gt;
| 4 || &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_FF&amp;lt;/code&amp;gt; || 0 || 3 || 0.01 || 0.05 || || Roll rate feed-forward — the workhorse term in ArduPlane's rate loop&lt;br /&gt;
|-&lt;br /&gt;
| 5 || &amp;lt;code&amp;gt;M&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_IMAX&amp;lt;/code&amp;gt; || 0 || 30 || 0.1 || 0.5 || || Integrator clamp, in the rate PID's ''output'' units where full surface = 45. A 75 kg driver leaning 100 mm needs ~20 units at the bottom of the speed envelope, hence the ceiling of 30&lt;br /&gt;
|-&lt;br /&gt;
| 6 || &amp;lt;code&amp;gt;C&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL2SRV_TCONST&amp;lt;/code&amp;gt; || 0.1 || 2 || 0.05 || 0.1 || || Angle→rate time constant (s): how aggressively the angle loop chases a roll target&lt;br /&gt;
|-&lt;br /&gt;
| 7 || &amp;lt;code&amp;gt;R&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL2SRV_RMAX&amp;lt;/code&amp;gt; || 0 || 180 || 5 || 15 || || Maximum commanded roll rate (°/s)&lt;br /&gt;
|-&lt;br /&gt;
| 8 || &amp;lt;code&amp;gt;L&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;ROLL_LIMIT_DEG&amp;lt;/code&amp;gt; || 5 || 20 || 1 || 5 || '''yes''' || Bank-angle envelope (°). 45° of roll on a foiling boat is a capsize, not a limit — locked in flight because it is the airframe envelope, not a gain&lt;br /&gt;
|-&lt;br /&gt;
| 9 || &amp;lt;code&amp;gt;T&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_FLTT&amp;lt;/code&amp;gt; || 0 || 100 || 1 || 5 || || Rate-target low-pass filter (Hz)&lt;br /&gt;
|-&lt;br /&gt;
| 10 || &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_FLTE&amp;lt;/code&amp;gt; || 0 || 100 || 1 || 5 || || Rate-error low-pass filter (Hz)&lt;br /&gt;
|-&lt;br /&gt;
| 11 || &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_FLTD&amp;lt;/code&amp;gt; || 0 || 100 || 1 || 5 || || D-term low-pass filter (Hz) — the first knob against high-frequency oscillation&lt;br /&gt;
|-&lt;br /&gt;
| 12 || &amp;lt;code&amp;gt;S&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;RLL_RATE_SMAX&amp;lt;/code&amp;gt; || 0 || 200 || 5 || 20 || || Output slew-rate limit&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Pitch inner loop ===&lt;br /&gt;
&lt;br /&gt;
The same controller structure for pitch, driving the front foils in common mode plus the rear foil (elevator, forwarded over CAN as &amp;lt;code&amp;gt;0x010&amp;lt;/code&amp;gt;). Pitch is the ''low''-authority axis — about 2.42 °/s per degree of foil, six times weaker than roll — so its gains run roughly six times higher for the same loop bandwidth; the wider P/I ceilings exist because the model-derived pitch P (≈5.65) exceeded the old limit of 2.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Idx !! Hotkey !! Parameter !! Min !! Max !! Fine !! Coarse !! Locked !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 16 || &amp;lt;code&amp;gt;P&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_P&amp;lt;/code&amp;gt; || 0.02 || 8 || 0.02 || 0.1 || || Pitch rate P gain (non-zero floor, as roll)&lt;br /&gt;
|-&lt;br /&gt;
| 17 || &amp;lt;code&amp;gt;I&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_I&amp;lt;/code&amp;gt; || 0 || 8 || 0.02 || 0.1 || || Pitch rate integrator gain&lt;br /&gt;
|-&lt;br /&gt;
| 18 || &amp;lt;code&amp;gt;D&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_D&amp;lt;/code&amp;gt; || 0 || 0.5 || 0.001 || 0.005 || || Pitch rate damping gain&lt;br /&gt;
|-&lt;br /&gt;
| 19 || &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_FF&amp;lt;/code&amp;gt; || 0 || 4 || 0.01 || 0.05 || || Pitch rate feed-forward&lt;br /&gt;
|-&lt;br /&gt;
| 20 || &amp;lt;code&amp;gt;M&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_IMAX&amp;lt;/code&amp;gt; || 0 || 40 || 0.1 || 0.5 || || Integrator clamp, output units (full surface = 45)&lt;br /&gt;
|-&lt;br /&gt;
| 21 || &amp;lt;code&amp;gt;C&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH2SRV_TCONST&amp;lt;/code&amp;gt; || 0.1 || 2 || 0.05 || 0.1 || || Angle→rate time constant (s)&lt;br /&gt;
|-&lt;br /&gt;
| 22 || &amp;lt;code&amp;gt;R&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH2SRV_RMAX_UP&amp;lt;/code&amp;gt; || 0 || 180 || 5 || 15 || || Maximum nose-up pitch rate (°/s). On the display this is one cell with &amp;lt;code&amp;gt;RMAX_DN&amp;lt;/code&amp;gt;: the two are edited together and kept equal&lt;br /&gt;
|-&lt;br /&gt;
| 23 || &amp;lt;code&amp;gt;R&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH2SRV_RMAX_DN&amp;lt;/code&amp;gt; || 0 || 180 || 5 || 15 || || Maximum nose-down pitch rate (°/s); paired with &amp;lt;code&amp;gt;RMAX_UP&amp;lt;/code&amp;gt; as above&lt;br /&gt;
|-&lt;br /&gt;
| 24 || &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH2SRV_RLL&amp;lt;/code&amp;gt; || 0 || 1.5 || 0.01 || 0.05 || || Roll→pitch cross-feed (coordinated-turn pitch compensation). The boat's seed sets 0 — a displacement hull does not pitch in a bank the way an aircraft does; the floor was lowered from ArduPlane's customary 0.7 for exactly that reason&lt;br /&gt;
|-&lt;br /&gt;
| 25 || &amp;lt;code&amp;gt;L&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_LIM_MAX_DEG&amp;lt;/code&amp;gt; || 1 || 10 || 1 || 5 || '''yes''' || Pitch envelope, nose-up (°). One display cell with &amp;lt;code&amp;gt;LIM_MIN&amp;lt;/code&amp;gt;, mirrored: + widens the envelope. Locked in flight; the old ceiling of 30° was, in the audit's words, a backflip demand&lt;br /&gt;
|-&lt;br /&gt;
| 26 || &amp;lt;code&amp;gt;L&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_LIM_MIN_DEG&amp;lt;/code&amp;gt; || −10 || −1 || 1 || 5 || '''yes''' || Pitch envelope, nose-down (°); mirrored pair of &amp;lt;code&amp;gt;LIM_MAX&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 27 || &amp;lt;code&amp;gt;T&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_FLTT&amp;lt;/code&amp;gt; || 0 || 100 || 1 || 5 || || Rate-target filter (Hz)&lt;br /&gt;
|-&lt;br /&gt;
| 28 || &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_FLTE&amp;lt;/code&amp;gt; || 0 || 100 || 1 || 5 || || Rate-error filter (Hz)&lt;br /&gt;
|-&lt;br /&gt;
| 29 || &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_FLTD&amp;lt;/code&amp;gt; || 0 || 100 || 1 || 5 || || D-term filter (Hz)&lt;br /&gt;
|-&lt;br /&gt;
| 30 || &amp;lt;code&amp;gt;S&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;PTCH_RATE_SMAX&amp;lt;/code&amp;gt; || 0 || 200 || 5 || 20 || || Output slew-rate limit&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Speed scaling ===&lt;br /&gt;
&lt;br /&gt;
One parameter stands apart because it silently retunes ''everything above'': ArduPlane multiplies every surface gain on both axes by &amp;lt;code&amp;gt;SCALING_SPEED / airspeed&amp;lt;/code&amp;gt; (clamped to roughly [0.5, 2]). It must be set to the boat's real foiling cruise speed, or none of the seeded gains mean what they say — a boat doing 8 m/s with the firmware default of 15 would run nearly double gains.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Idx !! Hotkey !! Parameter !! Min !! Max !! Fine !! Coarse !! Locked !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 31 || &amp;lt;code&amp;gt;Q&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;SCALING_SPEED&amp;lt;/code&amp;gt; || 4 || 15 || 0.5 || 1 || || Gain-scaling reference speed (m/s). Rescales every roll and pitch surface gain at once&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Height outer loop ===&lt;br /&gt;
&lt;br /&gt;
The ride-height controller in &amp;lt;code&amp;gt;hydrofoils.lua&amp;lt;/code&amp;gt;: it turns the EKF's height-above-water estimate into a pitch-target bias for the inner loop. These parameters are created when that script boots, so they answer ''unavailable'' (status 4) for the first seconds after power-on.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Idx !! Hotkey !! Parameter !! Min !! Max !! Fine !! Coarse !! Locked !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 32 || &amp;lt;code&amp;gt;p&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_KP&amp;lt;/code&amp;gt; || 0 || 2000 || 10 || 50 || || Height-error → pitch-demand proportional gain&lt;br /&gt;
|-&lt;br /&gt;
| 33 || &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_KI&amp;lt;/code&amp;gt; || 0 || 500 || 5 || 20 || || Height integrator gain — trims out steady offsets (loading, speed)&lt;br /&gt;
|-&lt;br /&gt;
| 34 || &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_KD&amp;lt;/code&amp;gt; || 0 || 2000 || 10 || 50 || || Vertical-velocity damping gain, acting on the EKF's fused climb rate (phase-consistent with the P term, and blind to wave-surface motion by design)&lt;br /&gt;
|-&lt;br /&gt;
| 35 || &amp;lt;code&amp;gt;h&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_IMAX&amp;lt;/code&amp;gt; || 0 || 500 || 10 || 50 || || Height integrator clamp&lt;br /&gt;
|-&lt;br /&gt;
| 36 || &amp;lt;code&amp;gt;t&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_TARGET&amp;lt;/code&amp;gt; || 0 || 1 || 0.01 || 0.05 || || Ride-height setpoint (m) — the most-used control on the water&lt;br /&gt;
|-&lt;br /&gt;
| 37 || — || ''retired'' || || || || || || Was &amp;lt;code&amp;gt;HYD_HSRC&amp;lt;/code&amp;gt; (height-source select, removed when control went EKF-exclusive). Answers ''unknown''; the index is reserved forever&lt;br /&gt;
|-&lt;br /&gt;
| 38 || — || ''retired'' || || || || || || Was &amp;lt;code&amp;gt;HYD_HDIV&amp;lt;/code&amp;gt; (EKF-vs-geometric divergence gate, removed — EKF3's own innovation gating covers it). Reserved forever&lt;br /&gt;
|-&lt;br /&gt;
| 39 || &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_ARM&amp;lt;/code&amp;gt; || 0 || 3.8 || 0.05 || 0.2 || || Ride-height reference point: metres from the sensors back to the point whose height is controlled (2.4 = the front foils, 1.4 m forward of the FC). Vehicle geometry, not a gain — only change it if the foil station is re-measured. The ceiling is the true 3.8 m sensor arm&lt;br /&gt;
|-&lt;br /&gt;
| 52 || &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_CMDMAX&amp;lt;/code&amp;gt; || 0.5 || 5 || 0.1 || 0.5 || || The height loop's ''own'' nose-up demand clamp (°) — its authority, distinct from the airframe envelope &amp;lt;code&amp;gt;PTCH_LIM_*&amp;lt;/code&amp;gt; which stays locked. One display cell with &amp;lt;code&amp;gt;CMDMIN&amp;lt;/code&amp;gt;, mirrored&lt;br /&gt;
|-&lt;br /&gt;
| 53 || &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_CMDMIN&amp;lt;/code&amp;gt; || −8 || −0.5 || 0.1 || 0.5 || || Nose-down authority (°): sets how hard the loop can push the bow down, i.e. the fly-up overshoot — the first knob to reach for after watching a fly-up&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Rear-foil schedule ===&lt;br /&gt;
&lt;br /&gt;
The rear foil's static trim and speed schedule, also in &amp;lt;code&amp;gt;hydrofoils.lua&amp;lt;/code&amp;gt;. This group is what makes the boat statically stable in pitch, so it has the map's most protective floor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Idx !! Hotkey !! Parameter !! Min !! Max !! Fine !! Coarse !! Locked !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 54 || &amp;lt;code&amp;gt;K&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_RKP&amp;lt;/code&amp;gt; || 0.15 || 1.2 || 0.02 || 0.1 || || The '''artificial tailplane''' gain. K = 0.059 exactly neutralises the natural −59 mm static margin, so the floor is 0.15, not 0: the tuner must never be able to hand back a divergent boat&lt;br /&gt;
|-&lt;br /&gt;
| 55 || &amp;lt;code&amp;gt;W&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_RSCALE&amp;lt;/code&amp;gt; || 0.5 || 1.2 || 0.02 || 0.1 || || Rear decalage scale — the rear foil runs shallower than the fronts; the floor keeps it from being scheduled to nothing&lt;br /&gt;
|-&lt;br /&gt;
| 56 || &amp;lt;code&amp;gt;Y&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_RSCHED&amp;lt;/code&amp;gt; || 0 || 1200 || 5 || 25 || || Speed-schedule strength (deg·m²/s²)&lt;br /&gt;
|-&lt;br /&gt;
| 57 || &amp;lt;code&amp;gt;V&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;HYD_FRNTFF&amp;lt;/code&amp;gt; || 0 || 0.5 || 0.01 || 0.05 || || Fraction of the rear schedule fed forward to the front-foil trims&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Coordinated-turn banking ===&lt;br /&gt;
&lt;br /&gt;
Served by a third script, &amp;lt;code&amp;gt;foil_turn.lua&amp;lt;/code&amp;gt;, which banks the boat into turns from the steering input. If that script is not uploaded, all six answer ''unavailable'' — which is itself the &amp;quot;is the feature installed?&amp;quot; check.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Idx !! Hotkey !! Parameter !! Min !! Max !! Fine !! Coarse !! Locked !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 40 || &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;TRN_ENABLE&amp;lt;/code&amp;gt; || 0 || 1 || 1 || 1 || || Feature on/off. Deliberately ''not'' locked: this is the in-flight kill switch, and disabling ramps the bank out at &amp;lt;code&amp;gt;TRN_RATE&amp;lt;/code&amp;gt; instead of snapping&lt;br /&gt;
|-&lt;br /&gt;
| 41 || &amp;lt;code&amp;gt;U&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;TRN_ON&amp;lt;/code&amp;gt; || 5 || 60 || 1 || 5 || || Steering input (%) at which banking starts&lt;br /&gt;
|-&lt;br /&gt;
| 42 || &amp;lt;code&amp;gt;A&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;TRN_FULL&amp;lt;/code&amp;gt; || 10 || 100 || 1 || 5 || || Steering input (%) giving full bank&lt;br /&gt;
|-&lt;br /&gt;
| 43 || &amp;lt;code&amp;gt;Z&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;TRN_MAX&amp;lt;/code&amp;gt; || 0 || 20 || 0.5 || 2 || || Maximum bank angle (°); 0 = never bank&lt;br /&gt;
|-&lt;br /&gt;
| 44 || &amp;lt;code&amp;gt;H&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;TRN_RATE&amp;lt;/code&amp;gt; || 1 || 20 || 0.5 || 2 || || Bank slew rate (°/s)&lt;br /&gt;
|-&lt;br /&gt;
| 45 || &amp;lt;code&amp;gt;J&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;TRN_REV&amp;lt;/code&amp;gt; || 0 || 1 || 1 || 1 || '''yes''' || Steering-sense reverse. Locked in flight: flipping it mid-turn would drive the bank to the opposite stop&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Mode and test knobs ===&lt;br /&gt;
&lt;br /&gt;
The scripting user parameters that &amp;lt;code&amp;gt;hydrofoils.lua&amp;lt;/code&amp;gt; reads live. Two of them are flight-mode changes and therefore locked while foiling; the two roll-test demands stay live because sweeping them ''is'' the test.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Idx !! Hotkey !! Parameter !! Min !! Max !! Fine !! Coarse !! Locked !! Description&lt;br /&gt;
|-&lt;br /&gt;
| 48 || &amp;lt;code&amp;gt;y&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;SCR_USER1&amp;lt;/code&amp;gt; || 0 || 1 || 1 || 1 || '''yes''' || Operating mode: 0 = height control (full cascade), 1 = roll-test/wheelie (height loop bypassed, fixed nose-up pitch). Takes effect on the next 10 Hz iteration — a mode change, not a tweak, hence the lock&lt;br /&gt;
|-&lt;br /&gt;
| 49 || &amp;lt;code&amp;gt;q&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;SCR_USER2&amp;lt;/code&amp;gt; || −10 || 10 || 0.5 || 1 || || Roll-test pitch target (°); 0 uses the built-in 3° default&lt;br /&gt;
|-&lt;br /&gt;
| 50 || &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;SCR_USER3&amp;lt;/code&amp;gt; || −20 || 20 || 1 || 5 || || Roll-step demand (°) for step-response tuning; 0 = level. Watch the achieved attitude come back on telemetry &amp;lt;code&amp;gt;0x250&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 51 || &amp;lt;code&amp;gt;B&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;SCR_USER4&amp;lt;/code&amp;gt; || 0 || 2100 || 10 || 50 || '''yes''' || Rear-foil jog (PWM µs, 0 = off): drives the rear actuator open-loop for bench calibration. Only honoured while the boat is disabled — and locked while foiling for the same reason&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Special indices ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Index !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0xFE&amp;lt;/code&amp;gt; || Protocol-version query: the reply's value is the version (currently '''7'''). Any tool should check this before trusting its own copy of this map&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt; || As a request: dump the whole table (one &amp;lt;code&amp;gt;0x261&amp;lt;/code&amp;gt; per entry, paced). As a reply: the end-of-dump marker, value = number of entries sent&lt;br /&gt;
|-&lt;br /&gt;
| 13–15, 46–47 || Unused, free for future entries in their groups&lt;br /&gt;
|-&lt;br /&gt;
| 37, 38 || Retired (&amp;lt;code&amp;gt;HYD_HSRC&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HYD_HDIV&amp;lt;/code&amp;gt;) — reserved forever, never reuse&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Config slots, undo and factory reset ==&lt;br /&gt;
&lt;br /&gt;
The helm keyboard adds a layer ''on top of'' the protocol; none of this exists on the wire beyond the ordinary set/ack frames it generates. Digit keys &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;–&amp;lt;code&amp;gt;9&amp;lt;/code&amp;gt; are configuration slots: '''tap to restore''' a slot (every stored parameter is re-sent over CAN, volatile, so the acks repopulate every listener), '''hold ≈1.2 s to store''' the current acked values into it. Slots are deliberately volatile — held in the tool's memory, wiped on restart, never written to flash — so a slot is a scratchpad for A/B comparisons on the water, not a configuration store; the flash write remains its own explicit action. &amp;lt;code&amp;gt;~&amp;lt;/code&amp;gt; undoes the last change (one step back per press — a slot restore or factory reset counts as a single step), and &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; held down performs a factory reset to the values read at connect. Restoring while foiling is safe by construction: the locked entries simply refuse (status 5) and everything else lands.&lt;br /&gt;
&lt;br /&gt;
== Keeping this page true ==&lt;br /&gt;
&lt;br /&gt;
The authoritative copy of this map is the &amp;lt;code&amp;gt;PT&amp;lt;/code&amp;gt; whitelist in &amp;lt;code&amp;gt;foil_tune.lua&amp;lt;/code&amp;gt; on the flight controller, mirrored by &amp;lt;code&amp;gt;PARAMS&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;tools/foil_tune.py&amp;lt;/code&amp;gt; and by the display's &amp;lt;code&amp;gt;FOILING_PARAMETERS.md&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;.csv&amp;lt;/code&amp;gt;. Any change to indices, ranges or locks bumps &amp;lt;code&amp;gt;PROTO_VERSION&amp;lt;/code&amp;gt; on both sides of the wire — if the version on this page and the one the boat reports at index &amp;lt;code&amp;gt;0xFE&amp;lt;/code&amp;gt; disagree, trust the boat and update this page.&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=341</id>
		<title>Height sensors</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=341"/>
		<updated>2026-08-17T21:12:22Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* The sensors */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''height sensors''' measure the distance between the hull and the water surface, which is what tells the crew and the [[Autopilot]] — how high the boat is flying on its [[Foils|foils]]. Two ultrasonic sensors look down at a slight angle outward from the nose of the hull. They are not read directly by anything: a dedicated RS485-to-[[CAN-bus|CAN]] interface board polls them and rebroadcasts each reading on the bus, where the [[Dashboard|display]],  [[autopilot]], and the [[Datalogger|datalogger]] pick it up.&lt;br /&gt;
&lt;br /&gt;
[[File:Height-Controller.jpeg|thumb|The RS485-to-CAN interface board that polls the height sensors]]&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Height sensing summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Sensors || 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU&lt;br /&gt;
|-&lt;br /&gt;
| Reading || Distance to the water surface, in millimetres&lt;br /&gt;
|-&lt;br /&gt;
| Sensor positions || As far forward as possible, port and starboard of the hull nose&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || 8 Hz per sensor (16 Hz combined, staggered)&lt;br /&gt;
|-&lt;br /&gt;
| Interface board || RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use&lt;br /&gt;
|-&lt;br /&gt;
| Board location || Under the deck, just forward of the [[Throttle|throttle]], near the pilot cabin&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; front left, &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; front right&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;height-sensor-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The sensors ==&lt;br /&gt;
&lt;br /&gt;
We use '''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.&lt;br /&gt;
&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Datasheet.pdf A02 datasheet]&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf A02 output interfaces]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
== Interface board ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== RS-485 channels ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ RS-485 channel allocation&lt;br /&gt;
|-&lt;br /&gt;
! Channel !! USART !! TX !! RX !! DIR (DE) !! Detect !! Used for !! CAN ID&lt;br /&gt;
|-&lt;br /&gt;
| 2 || USART2 || PA2 || PA3 || PA1 || PA0 || Height sensor front '''left''' || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || USART3 || PC4 || PC5 || PB1 || PB2 || Height sensor front '''right''' || &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || UART4 || PC10 || PC11 || PA15 || PA12 || ''unused'' || &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || UART5 || PC12 || PD2 || PB4 || PB5 || ''unused'' || &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sensor connector ===&lt;br /&gt;
&lt;br /&gt;
One 5-contact M8 female panel-mount connector per channel.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || RS-485 A / Y ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || RS-485 B / Z ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Presence detect || 100 kΩ pull-up to 5 V. Pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
=== Power and bus ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
== How the firmware reads them ==&lt;br /&gt;
&lt;br /&gt;
Each sensor is a Modbus RTU slave at address &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;, spoken to at '''9600 baud, 8 data bits, no parity, 2 stop bits'''. One read is a single holding register at &amp;lt;code&amp;gt;0x0101&amp;lt;/code&amp;gt;, and the response is the distance in millimetres.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The presence-detect pin is sampled first. If it reads high the channel is reported as &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt; and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt;. In both cases the height field is sent as 0 — '''0 mm is not a measurement''', always read the state byte first.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the height-sensor board sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; || HeightSensorFrontLeft || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; || HeightSensorFrontRight || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt; || TemperatureHeightSensorsController || 2 || every 1 s&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are reserved for the two unused channels and are not transmitted.&lt;br /&gt;
&lt;br /&gt;
=== 0x011 and 0x012 — height sensor reading ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Height || u16 LE || Distance to the water surface in mm. 0 whenever the state is not Operational.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 0x210 — TemperatureHeightSensorsController ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Received ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt;, its own command identifier &amp;lt;code&amp;gt;0x031 + (0x02 − 1) × 3 = 0x034&amp;lt;/code&amp;gt;, and firmware write data on &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;, which gives it the identifier block &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                        # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash height-sensor-controller              # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board height-sensor-controller version    # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| State byte 1 (ModbusError) || No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor.&lt;br /&gt;
|-&lt;br /&gt;
| State byte 0 (NotPluggedIn) || Detect pin reads high, so the connector is seen as empty&lt;br /&gt;
|-&lt;br /&gt;
| Height plausible but frozen || The sensor is answering with a stale value; power-cycle the channel&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A sensor can be exercised on the bench without any hardware: &amp;lt;code&amp;gt;firmware/tools/height_sensor_sim.py&amp;lt;/code&amp;gt; pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Half the channels are unused.''' Channels 4 and 5 are fitted and wired, and &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are allocated to them, but no position on the boat has been decided yet, so no firmware reads them.&lt;br /&gt;
* '''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 &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt; rather than &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''The bus-wide reference is out of date.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes the height field as &amp;quot;raw, unit undecided&amp;quot;. The firmware sends millimetres. This page follows the firmware.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Foils]] — what the ride height is measured for, and the sensor harness wire colours&lt;br /&gt;
* [[Autopilot]] — the intended consumer of these readings&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[Instrumentation Panel]] — where the readings are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=340</id>
		<title>Height sensors</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=340"/>
		<updated>2026-08-17T21:12:04Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''height sensors''' measure the distance between the hull and the water surface, which is what tells the crew and the [[Autopilot]] — how high the boat is flying on its [[Foils|foils]]. Two ultrasonic sensors look down at a slight angle outward from the nose of the hull. They are not read directly by anything: a dedicated RS485-to-[[CAN-bus|CAN]] interface board polls them and rebroadcasts each reading on the bus, where the [[Dashboard|display]],  [[autopilot]], and the [[Datalogger|datalogger]] pick it up.&lt;br /&gt;
&lt;br /&gt;
[[File:Height-Controller.jpeg|thumb|The RS485-to-CAN interface board that polls the height sensors]]&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Height sensing summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Sensors || 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU&lt;br /&gt;
|-&lt;br /&gt;
| Reading || Distance to the water surface, in millimetres&lt;br /&gt;
|-&lt;br /&gt;
| Sensor positions || As far forward as possible, port and starboard of the hull nose&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || 8 Hz per sensor (16 Hz combined, staggered)&lt;br /&gt;
|-&lt;br /&gt;
| Interface board || RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use&lt;br /&gt;
|-&lt;br /&gt;
| Board location || Under the deck, just forward of the [[Throttle|throttle]], near the pilot cabin&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; front left, &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; front right&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;height-sensor-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The sensors ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Datasheet.pdf A02 datasheet]&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf A02 output interfaces]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
== Interface board ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== RS-485 channels ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ RS-485 channel allocation&lt;br /&gt;
|-&lt;br /&gt;
! Channel !! USART !! TX !! RX !! DIR (DE) !! Detect !! Used for !! CAN ID&lt;br /&gt;
|-&lt;br /&gt;
| 2 || USART2 || PA2 || PA3 || PA1 || PA0 || Height sensor front '''left''' || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || USART3 || PC4 || PC5 || PB1 || PB2 || Height sensor front '''right''' || &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || UART4 || PC10 || PC11 || PA15 || PA12 || ''unused'' || &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || UART5 || PC12 || PD2 || PB4 || PB5 || ''unused'' || &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sensor connector ===&lt;br /&gt;
&lt;br /&gt;
One 5-contact M8 female panel-mount connector per channel.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || RS-485 A / Y ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || RS-485 B / Z ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Presence detect || 100 kΩ pull-up to 5 V. Pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
=== Power and bus ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
== How the firmware reads them ==&lt;br /&gt;
&lt;br /&gt;
Each sensor is a Modbus RTU slave at address &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;, spoken to at '''9600 baud, 8 data bits, no parity, 2 stop bits'''. One read is a single holding register at &amp;lt;code&amp;gt;0x0101&amp;lt;/code&amp;gt;, and the response is the distance in millimetres.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The presence-detect pin is sampled first. If it reads high the channel is reported as &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt; and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt;. In both cases the height field is sent as 0 — '''0 mm is not a measurement''', always read the state byte first.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the height-sensor board sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; || HeightSensorFrontLeft || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; || HeightSensorFrontRight || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt; || TemperatureHeightSensorsController || 2 || every 1 s&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are reserved for the two unused channels and are not transmitted.&lt;br /&gt;
&lt;br /&gt;
=== 0x011 and 0x012 — height sensor reading ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Height || u16 LE || Distance to the water surface in mm. 0 whenever the state is not Operational.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 0x210 — TemperatureHeightSensorsController ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Received ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt;, its own command identifier &amp;lt;code&amp;gt;0x031 + (0x02 − 1) × 3 = 0x034&amp;lt;/code&amp;gt;, and firmware write data on &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;, which gives it the identifier block &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                        # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash height-sensor-controller              # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board height-sensor-controller version    # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| State byte 1 (ModbusError) || No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor.&lt;br /&gt;
|-&lt;br /&gt;
| State byte 0 (NotPluggedIn) || Detect pin reads high, so the connector is seen as empty&lt;br /&gt;
|-&lt;br /&gt;
| Height plausible but frozen || The sensor is answering with a stale value; power-cycle the channel&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A sensor can be exercised on the bench without any hardware: &amp;lt;code&amp;gt;firmware/tools/height_sensor_sim.py&amp;lt;/code&amp;gt; pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Half the channels are unused.''' Channels 4 and 5 are fitted and wired, and &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are allocated to them, but no position on the boat has been decided yet, so no firmware reads them.&lt;br /&gt;
* '''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 &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt; rather than &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''The bus-wide reference is out of date.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes the height field as &amp;quot;raw, unit undecided&amp;quot;. The firmware sends millimetres. This page follows the firmware.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Foils]] — what the ride height is measured for, and the sensor harness wire colours&lt;br /&gt;
* [[Autopilot]] — the intended consumer of these readings&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[Instrumentation Panel]] — where the readings are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=339</id>
		<title>Height sensors</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=339"/>
		<updated>2026-08-17T21:11:54Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''height sensors''' measure the distance between the hull and the water surface, which is what tells the crew and the [[Autopilot]] — how high the boat is flying on its [[Foils|foils]]. Two ultrasonic sensors look down at a slight angle outward from the nose of the hull. They are not read directly by anything: a dedicated RS485-to-[[CAN-bus|CAN]] interface board polls them and rebroadcasts each reading on the bus, where the [[Dashboard|display]],  [[Autopilot]], and the [[Datalogger|datalogger]] pick it up.&lt;br /&gt;
&lt;br /&gt;
[[File:Height-Controller.jpeg|thumb|The RS485-to-CAN interface board that polls the height sensors]]&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Height sensing summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Sensors || 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU&lt;br /&gt;
|-&lt;br /&gt;
| Reading || Distance to the water surface, in millimetres&lt;br /&gt;
|-&lt;br /&gt;
| Sensor positions || As far forward as possible, port and starboard of the hull nose&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || 8 Hz per sensor (16 Hz combined, staggered)&lt;br /&gt;
|-&lt;br /&gt;
| Interface board || RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use&lt;br /&gt;
|-&lt;br /&gt;
| Board location || Under the deck, just forward of the [[Throttle|throttle]], near the pilot cabin&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; front left, &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; front right&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;height-sensor-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The sensors ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Datasheet.pdf A02 datasheet]&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf A02 output interfaces]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
== Interface board ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== RS-485 channels ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ RS-485 channel allocation&lt;br /&gt;
|-&lt;br /&gt;
! Channel !! USART !! TX !! RX !! DIR (DE) !! Detect !! Used for !! CAN ID&lt;br /&gt;
|-&lt;br /&gt;
| 2 || USART2 || PA2 || PA3 || PA1 || PA0 || Height sensor front '''left''' || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || USART3 || PC4 || PC5 || PB1 || PB2 || Height sensor front '''right''' || &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || UART4 || PC10 || PC11 || PA15 || PA12 || ''unused'' || &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || UART5 || PC12 || PD2 || PB4 || PB5 || ''unused'' || &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sensor connector ===&lt;br /&gt;
&lt;br /&gt;
One 5-contact M8 female panel-mount connector per channel.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || RS-485 A / Y ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || RS-485 B / Z ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Presence detect || 100 kΩ pull-up to 5 V. Pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
=== Power and bus ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
== How the firmware reads them ==&lt;br /&gt;
&lt;br /&gt;
Each sensor is a Modbus RTU slave at address &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;, spoken to at '''9600 baud, 8 data bits, no parity, 2 stop bits'''. One read is a single holding register at &amp;lt;code&amp;gt;0x0101&amp;lt;/code&amp;gt;, and the response is the distance in millimetres.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The presence-detect pin is sampled first. If it reads high the channel is reported as &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt; and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt;. In both cases the height field is sent as 0 — '''0 mm is not a measurement''', always read the state byte first.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the height-sensor board sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; || HeightSensorFrontLeft || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; || HeightSensorFrontRight || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt; || TemperatureHeightSensorsController || 2 || every 1 s&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are reserved for the two unused channels and are not transmitted.&lt;br /&gt;
&lt;br /&gt;
=== 0x011 and 0x012 — height sensor reading ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Height || u16 LE || Distance to the water surface in mm. 0 whenever the state is not Operational.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 0x210 — TemperatureHeightSensorsController ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Received ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt;, its own command identifier &amp;lt;code&amp;gt;0x031 + (0x02 − 1) × 3 = 0x034&amp;lt;/code&amp;gt;, and firmware write data on &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;, which gives it the identifier block &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                        # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash height-sensor-controller              # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board height-sensor-controller version    # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| State byte 1 (ModbusError) || No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor.&lt;br /&gt;
|-&lt;br /&gt;
| State byte 0 (NotPluggedIn) || Detect pin reads high, so the connector is seen as empty&lt;br /&gt;
|-&lt;br /&gt;
| Height plausible but frozen || The sensor is answering with a stale value; power-cycle the channel&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A sensor can be exercised on the bench without any hardware: &amp;lt;code&amp;gt;firmware/tools/height_sensor_sim.py&amp;lt;/code&amp;gt; pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Half the channels are unused.''' Channels 4 and 5 are fitted and wired, and &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are allocated to them, but no position on the boat has been decided yet, so no firmware reads them.&lt;br /&gt;
* '''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 &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt; rather than &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''The bus-wide reference is out of date.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes the height field as &amp;quot;raw, unit undecided&amp;quot;. The firmware sends millimetres. This page follows the firmware.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Foils]] — what the ride height is measured for, and the sensor harness wire colours&lt;br /&gt;
* [[Autopilot]] — the intended consumer of these readings&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[Instrumentation Panel]] — where the readings are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=338</id>
		<title>Height sensors</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=338"/>
		<updated>2026-08-17T21:10:33Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;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|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-bus|CAN]] interface board polls them and rebroadcasts each reading on the bus, where the [[Dashboard|display]] and the [[Datalogger|datalogger]] pick it up.&lt;br /&gt;
&lt;br /&gt;
[[File:Height-Controller.jpeg|thumb|The RS485-to-CAN interface board that polls the height sensors]]&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Height sensing summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Sensors || 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU&lt;br /&gt;
|-&lt;br /&gt;
| Reading || Distance to the water surface, in millimetres&lt;br /&gt;
|-&lt;br /&gt;
| Sensor positions || As far forward as possible, port and starboard of the hull nose&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || 8 Hz per sensor (16 Hz combined, staggered)&lt;br /&gt;
|-&lt;br /&gt;
| Interface board || RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use&lt;br /&gt;
|-&lt;br /&gt;
| Board location || Under the deck, just forward of the [[Throttle|throttle]], near the pilot cabin&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; front left, &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; front right&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;height-sensor-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The sensors ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Datasheet.pdf A02 datasheet]&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf A02 output interfaces]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
== Interface board ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== RS-485 channels ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ RS-485 channel allocation&lt;br /&gt;
|-&lt;br /&gt;
! Channel !! USART !! TX !! RX !! DIR (DE) !! Detect !! Used for !! CAN ID&lt;br /&gt;
|-&lt;br /&gt;
| 2 || USART2 || PA2 || PA3 || PA1 || PA0 || Height sensor front '''left''' || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || USART3 || PC4 || PC5 || PB1 || PB2 || Height sensor front '''right''' || &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || UART4 || PC10 || PC11 || PA15 || PA12 || ''unused'' || &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || UART5 || PC12 || PD2 || PB4 || PB5 || ''unused'' || &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sensor connector ===&lt;br /&gt;
&lt;br /&gt;
One 5-contact M8 female panel-mount connector per channel.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || RS-485 A / Y ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || RS-485 B / Z ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Presence detect || 100 kΩ pull-up to 5 V. Pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
=== Power and bus ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
== How the firmware reads them ==&lt;br /&gt;
&lt;br /&gt;
Each sensor is a Modbus RTU slave at address &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;, spoken to at '''9600 baud, 8 data bits, no parity, 2 stop bits'''. One read is a single holding register at &amp;lt;code&amp;gt;0x0101&amp;lt;/code&amp;gt;, and the response is the distance in millimetres.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The presence-detect pin is sampled first. If it reads high the channel is reported as &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt; and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt;. In both cases the height field is sent as 0 — '''0 mm is not a measurement''', always read the state byte first.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the height-sensor board sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; || HeightSensorFrontLeft || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; || HeightSensorFrontRight || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt; || TemperatureHeightSensorsController || 2 || every 1 s&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are reserved for the two unused channels and are not transmitted.&lt;br /&gt;
&lt;br /&gt;
=== 0x011 and 0x012 — height sensor reading ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Height || u16 LE || Distance to the water surface in mm. 0 whenever the state is not Operational.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 0x210 — TemperatureHeightSensorsController ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Received ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt;, its own command identifier &amp;lt;code&amp;gt;0x031 + (0x02 − 1) × 3 = 0x034&amp;lt;/code&amp;gt;, and firmware write data on &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;, which gives it the identifier block &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                        # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash height-sensor-controller              # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board height-sensor-controller version    # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| State byte 1 (ModbusError) || No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor.&lt;br /&gt;
|-&lt;br /&gt;
| State byte 0 (NotPluggedIn) || Detect pin reads high, so the connector is seen as empty&lt;br /&gt;
|-&lt;br /&gt;
| Height plausible but frozen || The sensor is answering with a stale value; power-cycle the channel&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A sensor can be exercised on the bench without any hardware: &amp;lt;code&amp;gt;firmware/tools/height_sensor_sim.py&amp;lt;/code&amp;gt; pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Half the channels are unused.''' Channels 4 and 5 are fitted and wired, and &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are allocated to them, but no position on the boat has been decided yet, so no firmware reads them.&lt;br /&gt;
* '''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 &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt; rather than &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''The bus-wide reference is out of date.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes the height field as &amp;quot;raw, unit undecided&amp;quot;. The firmware sends millimetres. This page follows the firmware.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Foils]] — what the ride height is measured for, and the sensor harness wire colours&lt;br /&gt;
* [[Autopilot]] — the intended consumer of these readings&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[Instrumentation Panel]] — where the readings are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Dashboard&amp;diff=337</id>
		<title>Dashboard</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Dashboard&amp;diff=337"/>
		<updated>2026-08-17T18:59:32Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Created page with &amp;quot;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.  The dashboard showing live data  == At a glance ==  {| class=&amp;quot;wikit...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;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|CAN bus]] and draws whatever the other nodes are saying.&lt;br /&gt;
&lt;br /&gt;
[[File:Dashboard-epaper-interface-with-data.jpeg|thumb|500px|The dashboard showing live data]]&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Dashboard summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Panel || Waveshare 5.79&amp;quot; e-paper, 792 × 272, black and white, SSD1683 controller&lt;br /&gt;
|-&lt;br /&gt;
| Location || Cockpit, left of the steering wheel&lt;br /&gt;
|-&lt;br /&gt;
| Controller board || STM32L471RGT6 — the '''same PCB''' as the [[Height sensors|height sensor controller]]&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| Transmits || Nothing, except replies to bootloader queries on &amp;lt;code&amp;gt;0x038&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || At most once per second, and only when the picture actually changed&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x03&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;dashboard&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Screen source || &amp;lt;code&amp;gt;draw-display/&amp;lt;/code&amp;gt; in the same repo, shared with the simulator&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== What is on the screen ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Screen regions&lt;br /&gt;
|-&lt;br /&gt;
! Region !! Shows&lt;br /&gt;
|-&lt;br /&gt;
| Left column || '''Net Power''' in watts as the headline, with '''Power In''' and '''Power Out''' beneath it&lt;br /&gt;
|-&lt;br /&gt;
| Centre || '''Speed''' in km/h, large, with the GNSS fix state under it (&amp;lt;code&amp;gt;3D fix&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;2D fix&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;No fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;---&amp;lt;/code&amp;gt; when the receiver is silent)&lt;br /&gt;
|-&lt;br /&gt;
| 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&lt;br /&gt;
|-&lt;br /&gt;
| Under the speed || Four '''warning icons''', each in a fixed slot&lt;br /&gt;
|-&lt;br /&gt;
| Bottom row || '''Current Time''', '''Race Time''', '''Time to Empty'''&lt;br /&gt;
|-&lt;br /&gt;
| Top line, small || Firmware version and git hash, centred; the [[Datalogger|datalogger]]'s WiFi IP on the left when it reports one&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.'''&lt;br /&gt;
&lt;br /&gt;
'''Temperatures.''' The MPPT cell names the unit it is showing — &amp;lt;code&amp;gt;MPPT F3&amp;lt;/code&amp;gt; forward, &amp;lt;code&amp;gt;MPPT R0&amp;lt;/code&amp;gt; aft — because it always shows whichever MPPT is currently hottest. The Motor cell reads the standalone NTC node on &amp;lt;code&amp;gt;0x219&amp;lt;/code&amp;gt; only; the VESC reports a motor temperature too, but it is broken on this boat and is deliberately never used as a fallback.&lt;br /&gt;
&lt;br /&gt;
'''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 &amp;quot;not being reported&amp;quot;, not &amp;quot;zero&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Warning icons ===&lt;br /&gt;
&lt;br /&gt;
Each icon appears only while its condition holds, in its own fixed slot, so a warning is always in the same place.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Icon !! Raised when&lt;br /&gt;
|-&lt;br /&gt;
| Battery || Battery state, charge FET or discharge FET is anything other than normal&lt;br /&gt;
|-&lt;br /&gt;
| Low charge || State of charge below '''15 %'''&lt;br /&gt;
|-&lt;br /&gt;
| Over-temperature || Motor above '''50 °C''', driver above '''70 °C''', any MPPT above '''80 °C''', or a battery thermistor above '''45 °C'''&lt;br /&gt;
|-&lt;br /&gt;
| Throttle || The [[Throttle|throttle]] reports any error flag&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A stale or missing value never raises an icon — the dashes already show that data is being lost.&lt;br /&gt;
&lt;br /&gt;
== Hardware ==&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;, the '''same PCB as the [[Height sensors|height sensor controller]]'''. Where that board fits four RS-485 channels, this one fits the e-paper panel on SPI2.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Display connections&lt;br /&gt;
|-&lt;br /&gt;
! Signal !! Pin&lt;br /&gt;
|-&lt;br /&gt;
| SPI2 SCK / MOSI / MISO || PB13 / PB15 / PB14&lt;br /&gt;
|-&lt;br /&gt;
| Chip select || PC6&lt;br /&gt;
|-&lt;br /&gt;
| Data/command || PC9&lt;br /&gt;
|-&lt;br /&gt;
| Reset || PC8&lt;br /&gt;
|-&lt;br /&gt;
| Busy || PA8&lt;br /&gt;
|-&lt;br /&gt;
| Panel power || PB12, held on for the life of the firmware&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 — &amp;lt;code&amp;gt;DMA1_CH4&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;CH5&amp;lt;/code&amp;gt; — 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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Refreshing ==&lt;br /&gt;
&lt;br /&gt;
E-paper is slow and wears with every drive cycle, so the firmware avoids refreshes it does not need:&lt;br /&gt;
&lt;br /&gt;
* The framebuffer is hashed after each render. '''An identical picture is not sent to the panel at all.'''&lt;br /&gt;
* Every 60th refresh is a '''full''' refresh, which clears the ghosting that the fast differential refresh accumulates. The other 59 are differential.&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
So the screen updates roughly once a second while values are changing, and sits still when they are not.&lt;br /&gt;
&lt;br /&gt;
== CAN ==&lt;br /&gt;
&lt;br /&gt;
The dashboard uses an '''accept-all''' hardware filter — it needs the whole bus — and decodes the identifiers it knows about. Everything else is ignored.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ What the screen is built from&lt;br /&gt;
|-&lt;br /&gt;
! Source !! Identifiers !! Used for&lt;br /&gt;
|-&lt;br /&gt;
| [[Battery|BMS]] || &amp;lt;code&amp;gt;0x100&amp;lt;/code&amp;gt;–&amp;lt;code&amp;gt;0x107&amp;lt;/code&amp;gt; || State of charge, pack voltage, the three currents, temperatures, battery/charge/discharge states&lt;br /&gt;
|-&lt;br /&gt;
| GNSS || &amp;lt;code&amp;gt;0x200&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x201&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x204&amp;lt;/code&amp;gt; || Fix state, speed, clock&lt;br /&gt;
|-&lt;br /&gt;
| [[Motordriver|VESC]] || &amp;lt;code&amp;gt;0x1009&amp;lt;/code&amp;gt; || Driver (FET) temperature&lt;br /&gt;
|-&lt;br /&gt;
| Motor NTC node || &amp;lt;code&amp;gt;0x219&amp;lt;/code&amp;gt; || Motor temperature&lt;br /&gt;
|-&lt;br /&gt;
| [[MPPT]] / [[GaN MPPT]] || &amp;lt;code&amp;gt;0x400&amp;lt;/code&amp;gt;–&amp;lt;code&amp;gt;0x4FF&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x700&amp;lt;/code&amp;gt;–&amp;lt;code&amp;gt;0x77F&amp;lt;/code&amp;gt; || MPPT temperatures, per-panel power&lt;br /&gt;
|-&lt;br /&gt;
| [[Throttle]] || &amp;lt;code&amp;gt;0x1337&amp;lt;/code&amp;gt; || Throttle error flags&lt;br /&gt;
|-&lt;br /&gt;
| [[Datalogger]] || &amp;lt;code&amp;gt;0x205&amp;lt;/code&amp;gt; || WiFi IP shown on the stamp line&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The full bus-wide reference is [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md].&lt;br /&gt;
&lt;br /&gt;
'''The dashboard originates no traffic.''' The only frames it ever sends are answers to the host's bootloader-protocol queries.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x03&amp;lt;/code&amp;gt;, giving it the identifier block &amp;lt;code&amp;gt;0x037&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x038&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x039&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                          # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash dashboard               # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board dashboard version     # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Working on the screen without hardware ==&lt;br /&gt;
&lt;br /&gt;
The whole layout lives in the &amp;lt;code&amp;gt;draw-display&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd eoi-can-display-simulator&lt;br /&gt;
cargo run -- -ccan0                          # live, against a real bus&lt;br /&gt;
EG_SIMULATOR_DUMP=screenshot.png cargo run   # write a PNG instead&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The 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.&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;--layout foiling&amp;lt;/code&amp;gt; in the simulator.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Blue LED flickering || CAN frames arriving — it toggles once per frame&lt;br /&gt;
|-&lt;br /&gt;
| Red LED on briefly || A panel refresh is in progress&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| Everything reads dashes || Nothing is arriving on the bus. Check bus wiring and termination before suspecting the board.&lt;br /&gt;
|-&lt;br /&gt;
| One column reads dashes || That node is silent; the dashboard is fine&lt;br /&gt;
|-&lt;br /&gt;
| Screen frozen but LEDs normal || The picture has not changed — identical frames are deliberately skipped&lt;br /&gt;
|-&lt;br /&gt;
| Ghosting or grey smears || Normal between full refreshes; cleared by the next one, at most 60 refreshes away&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Race Time is empty.''' The block is drawn and labelled, but nothing on the bus signals a race start, so it always shows dashes.&lt;br /&gt;
* '''Time to Empty is empty.''' Nothing calculates or broadcasts it yet. The BMS field exists but is not fed to the screen.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Height sensors]] — the same PCB, in its other configuration&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Battery]] — where the power and state of charge figures come from&lt;br /&gt;
* [[Instrumentation Panel]] — the switch panel in the same cockpit&lt;br /&gt;
* [[Datalogger]] — records the same bus the dashboard displays&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=File:Dashboard-epaper-interface-with-data.jpeg&amp;diff=336</id>
		<title>File:Dashboard-epaper-interface-with-data.jpeg</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=File:Dashboard-epaper-interface-with-data.jpeg&amp;diff=336"/>
		<updated>2026-08-17T18:48:58Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=File:Dashboard-epaper-interface.png&amp;diff=335</id>
		<title>File:Dashboard-epaper-interface.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=File:Dashboard-epaper-interface.png&amp;diff=335"/>
		<updated>2026-08-17T18:47:01Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=334</id>
		<title>Height sensors</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=334"/>
		<updated>2026-08-17T18:45:00Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;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|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-bus|CAN]] interface board polls them and rebroadcasts each reading on the bus, where the [[Instrumentation Panel|display]] and the [[Datalogger|datalogger]] pick it up.&lt;br /&gt;
&lt;br /&gt;
[[File:Height-Controller.jpeg|thumb|The RS485-to-CAN interface board that polls the height sensors]]&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Height sensing summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Sensors || 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU&lt;br /&gt;
|-&lt;br /&gt;
| Reading || Distance to the water surface, in millimetres&lt;br /&gt;
|-&lt;br /&gt;
| Sensor positions || As far forward as possible, port and starboard of the hull nose&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || 8 Hz per sensor (16 Hz combined, staggered)&lt;br /&gt;
|-&lt;br /&gt;
| Interface board || RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use&lt;br /&gt;
|-&lt;br /&gt;
| Board location || Under the deck, just forward of the [[Throttle|throttle]], near the pilot cabin&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; front left, &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; front right&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;height-sensor-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The sensors ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Datasheet.pdf A02 datasheet]&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf A02 output interfaces]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
== Interface board ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== RS-485 channels ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ RS-485 channel allocation&lt;br /&gt;
|-&lt;br /&gt;
! Channel !! USART !! TX !! RX !! DIR (DE) !! Detect !! Used for !! CAN ID&lt;br /&gt;
|-&lt;br /&gt;
| 2 || USART2 || PA2 || PA3 || PA1 || PA0 || Height sensor front '''left''' || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || USART3 || PC4 || PC5 || PB1 || PB2 || Height sensor front '''right''' || &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || UART4 || PC10 || PC11 || PA15 || PA12 || ''unused'' || &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || UART5 || PC12 || PD2 || PB4 || PB5 || ''unused'' || &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sensor connector ===&lt;br /&gt;
&lt;br /&gt;
One 5-contact M8 female panel-mount connector per channel.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || RS-485 A / Y ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || RS-485 B / Z ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Presence detect || 100 kΩ pull-up to 5 V. Pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
=== Power and bus ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
== How the firmware reads them ==&lt;br /&gt;
&lt;br /&gt;
Each sensor is a Modbus RTU slave at address &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;, spoken to at '''9600 baud, 8 data bits, no parity, 2 stop bits'''. One read is a single holding register at &amp;lt;code&amp;gt;0x0101&amp;lt;/code&amp;gt;, and the response is the distance in millimetres.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The presence-detect pin is sampled first. If it reads high the channel is reported as &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt; and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt;. In both cases the height field is sent as 0 — '''0 mm is not a measurement''', always read the state byte first.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the height-sensor board sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; || HeightSensorFrontLeft || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; || HeightSensorFrontRight || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt; || TemperatureHeightSensorsController || 2 || every 1 s&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are reserved for the two unused channels and are not transmitted.&lt;br /&gt;
&lt;br /&gt;
=== 0x011 and 0x012 — height sensor reading ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Height || u16 LE || Distance to the water surface in mm. 0 whenever the state is not Operational.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 0x210 — TemperatureHeightSensorsController ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Received ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt;, its own command identifier &amp;lt;code&amp;gt;0x031 + (0x02 − 1) × 3 = 0x034&amp;lt;/code&amp;gt;, and firmware write data on &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;, which gives it the identifier block &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                        # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash height-sensor-controller              # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board height-sensor-controller version    # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| State byte 1 (ModbusError) || No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor.&lt;br /&gt;
|-&lt;br /&gt;
| State byte 0 (NotPluggedIn) || Detect pin reads high, so the connector is seen as empty&lt;br /&gt;
|-&lt;br /&gt;
| Height plausible but frozen || The sensor is answering with a stale value; power-cycle the channel&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A sensor can be exercised on the bench without any hardware: &amp;lt;code&amp;gt;firmware/tools/height_sensor_sim.py&amp;lt;/code&amp;gt; pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Half the channels are unused.''' Channels 4 and 5 are fitted and wired, and &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are allocated to them, but no position on the boat has been decided yet, so no firmware reads them.&lt;br /&gt;
* '''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 &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt; rather than &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''The bus-wide reference is out of date.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes the height field as &amp;quot;raw, unit undecided&amp;quot;. The firmware sends millimetres. This page follows the firmware.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Foils]] — what the ride height is measured for, and the sensor harness wire colours&lt;br /&gt;
* [[Autopilot]] — the intended consumer of these readings&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[Instrumentation Panel]] — where the readings are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=333</id>
		<title>Height sensors</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=333"/>
		<updated>2026-08-17T18:42:12Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* Interface board */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;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|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-bus|CAN]] interface board polls them and rebroadcasts each reading on the bus, where the [[Instrumentation Panel|display]] and the [[Datalogger|datalogger]] pick it up.&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Height sensing summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Sensors || 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU&lt;br /&gt;
|-&lt;br /&gt;
| Reading || Distance to the water surface, in millimetres&lt;br /&gt;
|-&lt;br /&gt;
| Sensor positions || As far forward as possible, port and starboard of the hull nose&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || 8 Hz per sensor (16 Hz combined, staggered)&lt;br /&gt;
|-&lt;br /&gt;
| Interface board || RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use&lt;br /&gt;
|-&lt;br /&gt;
| Board location || Under the deck, just forward of the [[Throttle|throttle]], near the pilot cabin&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; front left, &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; front right&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;height-sensor-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The sensors ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Datasheet.pdf A02 datasheet]&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf A02 output interfaces]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
== Interface board ==&lt;br /&gt;
[[File:Height-Controller.jpeg|thumb|The RS485-to-CAN interface board that polls the height sensors]]&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== RS-485 channels ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ RS-485 channel allocation&lt;br /&gt;
|-&lt;br /&gt;
! Channel !! USART !! TX !! RX !! DIR (DE) !! Detect !! Used for !! CAN ID&lt;br /&gt;
|-&lt;br /&gt;
| 2 || USART2 || PA2 || PA3 || PA1 || PA0 || Height sensor front '''left''' || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || USART3 || PC4 || PC5 || PB1 || PB2 || Height sensor front '''right''' || &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || UART4 || PC10 || PC11 || PA15 || PA12 || ''unused'' || &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || UART5 || PC12 || PD2 || PB4 || PB5 || ''unused'' || &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sensor connector ===&lt;br /&gt;
&lt;br /&gt;
One 5-contact M8 female panel-mount connector per channel.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || RS-485 A / Y ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || RS-485 B / Z ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Presence detect || 100 kΩ pull-up to 5 V. Pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
=== Power and bus ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
== How the firmware reads them ==&lt;br /&gt;
&lt;br /&gt;
Each sensor is a Modbus RTU slave at address &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;, spoken to at '''9600 baud, 8 data bits, no parity, 2 stop bits'''. One read is a single holding register at &amp;lt;code&amp;gt;0x0101&amp;lt;/code&amp;gt;, and the response is the distance in millimetres.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The presence-detect pin is sampled first. If it reads high the channel is reported as &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt; and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt;. In both cases the height field is sent as 0 — '''0 mm is not a measurement''', always read the state byte first.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the height-sensor board sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; || HeightSensorFrontLeft || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; || HeightSensorFrontRight || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt; || TemperatureHeightSensorsController || 2 || every 1 s&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are reserved for the two unused channels and are not transmitted.&lt;br /&gt;
&lt;br /&gt;
=== 0x011 and 0x012 — height sensor reading ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Height || u16 LE || Distance to the water surface in mm. 0 whenever the state is not Operational.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 0x210 — TemperatureHeightSensorsController ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Received ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt;, its own command identifier &amp;lt;code&amp;gt;0x031 + (0x02 − 1) × 3 = 0x034&amp;lt;/code&amp;gt;, and firmware write data on &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;, which gives it the identifier block &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                        # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash height-sensor-controller              # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board height-sensor-controller version    # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| State byte 1 (ModbusError) || No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor.&lt;br /&gt;
|-&lt;br /&gt;
| State byte 0 (NotPluggedIn) || Detect pin reads high, so the connector is seen as empty&lt;br /&gt;
|-&lt;br /&gt;
| Height plausible but frozen || The sensor is answering with a stale value; power-cycle the channel&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A sensor can be exercised on the bench without any hardware: &amp;lt;code&amp;gt;firmware/tools/height_sensor_sim.py&amp;lt;/code&amp;gt; pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Half the channels are unused.''' Channels 4 and 5 are fitted and wired, and &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are allocated to them, but no position on the boat has been decided yet, so no firmware reads them.&lt;br /&gt;
* '''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 &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt; rather than &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''The bus-wide reference is out of date.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes the height field as &amp;quot;raw, unit undecided&amp;quot;. The firmware sends millimetres. This page follows the firmware.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Foils]] — what the ride height is measured for, and the sensor harness wire colours&lt;br /&gt;
* [[Autopilot]] — the intended consumer of these readings&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[Instrumentation Panel]] — where the readings are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=332</id>
		<title>Height sensors</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=332"/>
		<updated>2026-08-17T18:41:22Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* Interface board */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;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|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-bus|CAN]] interface board polls them and rebroadcasts each reading on the bus, where the [[Instrumentation Panel|display]] and the [[Datalogger|datalogger]] pick it up.&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Height sensing summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Sensors || 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU&lt;br /&gt;
|-&lt;br /&gt;
| Reading || Distance to the water surface, in millimetres&lt;br /&gt;
|-&lt;br /&gt;
| Sensor positions || As far forward as possible, port and starboard of the hull nose&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || 8 Hz per sensor (16 Hz combined, staggered)&lt;br /&gt;
|-&lt;br /&gt;
| Interface board || RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use&lt;br /&gt;
|-&lt;br /&gt;
| Board location || Under the deck, just forward of the [[Throttle|throttle]], near the pilot cabin&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; front left, &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; front right&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;height-sensor-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The sensors ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Datasheet.pdf A02 datasheet]&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf A02 output interfaces]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
== Interface board ==&lt;br /&gt;
[[File:Height-Controller.jpeg|thumb|The RS485-to-CAN interface board that polls the height sensors]]&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== RS-485 channels ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ RS-485 channel allocation&lt;br /&gt;
|-&lt;br /&gt;
! Channel !! USART !! TX !! RX !! DIR (DE) !! Detect !! Used for !! CAN ID&lt;br /&gt;
|-&lt;br /&gt;
| 2 || USART2 || PA2 || PA3 || PA1 || PA0 || Height sensor front '''left''' || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || USART3 || PC4 || PC5 || PB1 || PB2 || Height sensor front '''right''' || &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || UART4 || PC10 || PC11 || PA15 || PA12 || ''unused'' || &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || UART5 || PC12 || PD2 || PB4 || PB5 || ''unused'' || &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' the schematic symbol is named &amp;lt;code&amp;gt;ISL83078E&amp;lt;/code&amp;gt;, but the BOM's part choice is the TI &amp;lt;code&amp;gt;SN65HVD3088E&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== Sensor connector ===&lt;br /&gt;
&lt;br /&gt;
One 5-contact M8 female panel-mount connector per channel.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || RS-485 A / Y ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || RS-485 B / Z ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Presence detect || 100 kΩ pull-up to 5 V. Pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
=== Power and bus ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
== How the firmware reads them ==&lt;br /&gt;
&lt;br /&gt;
Each sensor is a Modbus RTU slave at address &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;, spoken to at '''9600 baud, 8 data bits, no parity, 2 stop bits'''. One read is a single holding register at &amp;lt;code&amp;gt;0x0101&amp;lt;/code&amp;gt;, and the response is the distance in millimetres.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The presence-detect pin is sampled first. If it reads high the channel is reported as &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt; and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt;. In both cases the height field is sent as 0 — '''0 mm is not a measurement''', always read the state byte first.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the height-sensor board sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; || HeightSensorFrontLeft || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; || HeightSensorFrontRight || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt; || TemperatureHeightSensorsController || 2 || every 1 s&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are reserved for the two unused channels and are not transmitted.&lt;br /&gt;
&lt;br /&gt;
=== 0x011 and 0x012 — height sensor reading ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Height || u16 LE || Distance to the water surface in mm. 0 whenever the state is not Operational.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 0x210 — TemperatureHeightSensorsController ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Received ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt;, its own command identifier &amp;lt;code&amp;gt;0x031 + (0x02 − 1) × 3 = 0x034&amp;lt;/code&amp;gt;, and firmware write data on &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;, which gives it the identifier block &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                        # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash height-sensor-controller              # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board height-sensor-controller version    # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| State byte 1 (ModbusError) || No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor.&lt;br /&gt;
|-&lt;br /&gt;
| State byte 0 (NotPluggedIn) || Detect pin reads high, so the connector is seen as empty&lt;br /&gt;
|-&lt;br /&gt;
| Height plausible but frozen || The sensor is answering with a stale value; power-cycle the channel&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A sensor can be exercised on the bench without any hardware: &amp;lt;code&amp;gt;firmware/tools/height_sensor_sim.py&amp;lt;/code&amp;gt; pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Half the channels are unused.''' Channels 4 and 5 are fitted and wired, and &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are allocated to them, but no position on the boat has been decided yet, so no firmware reads them.&lt;br /&gt;
* '''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 &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt; rather than &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''The bus-wide reference is out of date.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes the height field as &amp;quot;raw, unit undecided&amp;quot;. The firmware sends millimetres. This page follows the firmware.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Foils]] — what the ride height is measured for, and the sensor harness wire colours&lt;br /&gt;
* [[Autopilot]] — the intended consumer of these readings&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[Instrumentation Panel]] — where the readings are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=331</id>
		<title>Height sensors</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Height_sensors&amp;diff=331"/>
		<updated>2026-08-17T18:39:57Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Created page with &amp;quot;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...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;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|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-bus|CAN]] interface board polls them and rebroadcasts each reading on the bus, where the [[Instrumentation Panel|display]] and the [[Datalogger|datalogger]] pick it up.&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Height sensing summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Sensors || 2 × DYP A02 ultrasonic, RS-485 / Modbus RTU&lt;br /&gt;
|-&lt;br /&gt;
| Reading || Distance to the water surface, in millimetres&lt;br /&gt;
|-&lt;br /&gt;
| Sensor positions || As far forward as possible, port and starboard of the hull nose&lt;br /&gt;
|-&lt;br /&gt;
| Update rate || 8 Hz per sensor (16 Hz combined, staggered)&lt;br /&gt;
|-&lt;br /&gt;
| Interface board || RS485-to-CAN interface, STM32L471RGT6 — 4 channels, 2 in use&lt;br /&gt;
|-&lt;br /&gt;
| Board location || Under the deck, just forward of the [[Throttle|throttle]], near the pilot cabin&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| CAN identifiers || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; front left, &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; front right&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt;, binary &amp;lt;code&amp;gt;height-sensor-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== The sensors ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Datasheet.pdf A02 datasheet]&lt;br /&gt;
* [https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf A02 output interfaces]&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
== Interface board ==&lt;br /&gt;
[[File:Height-Controller.jpeg|thumb|The RS485-to-CAN interface board that polls the height sensors]]&lt;br /&gt;
&lt;br /&gt;
The board is the Altium project &amp;lt;code&amp;gt;AKD-CAN_Interface&amp;lt;/code&amp;gt;. 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, not in the cockpit.&lt;br /&gt;
&lt;br /&gt;
=== RS-485 channels ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ RS-485 channel allocation&lt;br /&gt;
|-&lt;br /&gt;
! Channel !! USART !! TX !! RX !! DIR (DE) !! Detect !! Used for !! CAN ID&lt;br /&gt;
|-&lt;br /&gt;
| 2 || USART2 || PA2 || PA3 || PA1 || PA0 || Height sensor front '''left''' || &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 3 || USART3 || PC4 || PC5 || PB1 || PB2 || Height sensor front '''right''' || &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || UART4 || PC10 || PC11 || PA15 || PA12 || ''unused'' || &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|-&lt;br /&gt;
| 5 || UART5 || PC12 || PD2 || PB4 || PB5 || ''unused'' || &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; (reserved)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''Note:''' the schematic symbol is named &amp;lt;code&amp;gt;ISL83078E&amp;lt;/code&amp;gt;, but the BOM's part choice is the TI &amp;lt;code&amp;gt;SN65HVD3088E&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
&lt;br /&gt;
=== Sensor connector ===&lt;br /&gt;
&lt;br /&gt;
One 5-contact M8 female panel-mount connector per channel.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || RS-485 A / Y ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || RS-485 B / Z ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Presence detect || 100 kΩ pull-up to 5 V. Pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
=== Power and bus ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (PB10/PB11, address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt;, 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.&lt;br /&gt;
&lt;br /&gt;
== How the firmware reads them ==&lt;br /&gt;
&lt;br /&gt;
Each sensor is a Modbus RTU slave at address &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;, spoken to at '''9600 baud, 8 data bits, no parity, 2 stop bits'''. One read is a single holding register at &amp;lt;code&amp;gt;0x0101&amp;lt;/code&amp;gt;, and the response is the distance in millimetres.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The presence-detect pin is sampled first. If it reads high the channel is reported as &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt; and no Modbus traffic is generated at all. Any failure to build, send, receive or parse the Modbus frame reports &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt;. In both cases the height field is sent as 0 — '''0 mm is not a measurement''', always read the state byte first.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s with 11-bit identifiers. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the height-sensor board sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x011&amp;lt;/code&amp;gt; || HeightSensorFrontLeft || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x012&amp;lt;/code&amp;gt; || HeightSensorFrontRight || 3 || every 125 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x210&amp;lt;/code&amp;gt; || TemperatureHeightSensorsController || 2 || every 1 s&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are reserved for the two unused channels and are not transmitted.&lt;br /&gt;
&lt;br /&gt;
=== 0x011 and 0x012 — height sensor reading ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = NotPluggedIn, 1 = ModbusError, 2 = Operational, 0xFF = Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Height || u16 LE || Distance to the water surface in mm. 0 whenever the state is not Operational.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 0x210 — TemperatureHeightSensorsController ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Received ===&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt;, its own command identifier &amp;lt;code&amp;gt;0x031 + (0x02 − 1) × 3 = 0x034&amp;lt;/code&amp;gt;, and firmware write data on &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;, which gives it the identifier block &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; (command) / &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; (response) / &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt; (write data); the bootloader's hardware filter rejects every other block, so a command aimed at another board cannot touch this one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                        # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash height-sensor-controller              # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board height-sensor-controller version    # which build is on it?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| State byte 1 (ModbusError) || No valid reply within 100 ms. Sensor unpowered, A/B swapped, broken cable, or a flooded sensor.&lt;br /&gt;
|-&lt;br /&gt;
| State byte 0 (NotPluggedIn) || Detect pin reads high, so the connector is seen as empty&lt;br /&gt;
|-&lt;br /&gt;
| Height plausible but frozen || The sensor is answering with a stale value; power-cycle the channel&lt;br /&gt;
|-&lt;br /&gt;
| 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.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
A sensor can be exercised on the bench without any hardware: &amp;lt;code&amp;gt;firmware/tools/height_sensor_sim.py&amp;lt;/code&amp;gt; pretends to be the Modbus slave on a USB RS-485 adapter, with a fixed or swept distance.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Half the channels are unused.''' Channels 4 and 5 are fitted and wired, and &amp;lt;code&amp;gt;0x013&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;0x014&amp;lt;/code&amp;gt; are allocated to them, but no position on the boat has been decided yet, so no firmware reads them.&lt;br /&gt;
* '''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 &amp;lt;code&amp;gt;ModbusError&amp;lt;/code&amp;gt; rather than &amp;lt;code&amp;gt;NotPluggedIn&amp;lt;/code&amp;gt;. 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.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''The bus-wide reference is out of date.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes the height field as &amp;quot;raw, unit undecided&amp;quot;. The firmware sends millimetres. This page follows the firmware.&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Foils]] — what the ride height is measured for, and the sensor harness wire colours&lt;br /&gt;
* [[Autopilot]] — the intended consumer of these readings&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Rudder controller]] — sister board, and the full bootloader protocol&lt;br /&gt;
* [[Instrumentation Panel]] — where the readings are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=File:Height-Controller.jpeg&amp;diff=330</id>
		<title>File:Height-Controller.jpeg</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=File:Height-Controller.jpeg&amp;diff=330"/>
		<updated>2026-08-17T18:36:50Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: RS485 to CAN bus height sensor interface PCB&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
RS485 to CAN bus height sensor interface PCB&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=329</id>
		<title>Rudder</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=329"/>
		<updated>2026-08-17T12:23:26Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* Rudder-controller */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Propulsion System History and Overview =&lt;br /&gt;
[[File:Roer_2025.png|thumb|300px|2022 Rudder]]&lt;br /&gt;
&lt;br /&gt;
The main propulsion system is housed inside the rudder. The rudder is mounted at the rear of the boat, approximately 440 mm from the stern. Over the years several different versions of the propulsion system have been developed. A brief overview of the previous systems is given below.&lt;br /&gt;
&lt;br /&gt;
== 2022 – 2025: Underwater Mounted Inrunner ==&lt;br /&gt;
&lt;br /&gt;
This system consisted of a carbon-fiber-wrapped 3D-printed rudder design. The rudder was mounted to the boat using a carbon fiber tube attached through a so-called “well”. The well itself was made using a slightly larger carbon fiber tube integrated into the hull structure.&lt;br /&gt;
&lt;br /&gt;
The propulsion system used a Lehner 30100/12 three-phase inrunner motor combined with a Neugart PLE-6 series single-stage planetary gearbox. Over the years 4:1, 5:1, and 7:1 gearbox ratios were used. The complete drive assembly was mounted inside an aluminium housing, with the motor and gearbox thermally coupled to the housing using thermal paste for cooling.&lt;br /&gt;
&lt;br /&gt;
The propeller was mounted in pulling configuration, meaning the driveshaft protruded through the front side of the housing. The inside of the motor tube was sealed from the surrounding water using a shaft seal, while the rear end of the tube was closed off using a custom threaded cap.&lt;br /&gt;
&lt;br /&gt;
A magnetic encoder was mounted at the rear end of the motor. The motor tube itself was connected to the rudder using aluminium interface pieces and rubber O-rings. Power cables were routed through the rudder to the three-phase motor controller located in the rear compartment of the boat.&lt;br /&gt;
&lt;br /&gt;
Two major versions of this drivetrain were developed, with V2 featuring improved structural reinforcement and mounting features.&lt;br /&gt;
&lt;br /&gt;
Although the system performed well initially, it was ultimately scrapped due to continuous water ingress into the motor tube. The resulting corrosion of the gearbox and motor caused repeated failures and required frequent replacement of expensive components.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 2025: Mini Rudder V1 ==&lt;br /&gt;
&lt;br /&gt;
During the 2025 season a temporary drivetrain was constructed using a Flipsky 65111 waterproof inrunner motor. This system was designed as a backup solution for the heavily troubled main drivetrain and was used during the 2025 Amsterdam Solarboat Race.&lt;br /&gt;
&lt;br /&gt;
The rudder featured a carbon-fiber-reinforced 3D-printed body and focused mainly on reliability and ease of deployment rather than outright performance.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v1.png|thumb|300px|Mini rudder V1]]&lt;br /&gt;
&lt;br /&gt;
= Development of the New Propulsion Platform =&lt;br /&gt;
&lt;br /&gt;
Following the 2025 season, development started on a completely new propulsion platform. The main design goals of the new system were:&lt;br /&gt;
&lt;br /&gt;
* Move expensive and sensitive electronics away from direct water exposure&lt;br /&gt;
* Improve the drivetrain mounting system for future development&lt;br /&gt;
* Increase structural rigidity for future hydrofoil implementation&lt;br /&gt;
* Add integrated features required for hydrofoil compatibility&lt;br /&gt;
* Implement a counter-rotating propeller system for improved wake alignment and efficiency&lt;br /&gt;
&lt;br /&gt;
This redesign required significant modifications to the rear of the boat, including:&lt;br /&gt;
&lt;br /&gt;
* Replacement of the drivetrain well&lt;br /&gt;
* Replacement of the entire steering system&lt;br /&gt;
* Replacement of the well support structure&lt;br /&gt;
&lt;br /&gt;
Construction started in late 2025 and was completed in early 2026, with the first on-water trials taking place in May 2026.&lt;br /&gt;
&lt;br /&gt;
= Current Drive Systems =&lt;br /&gt;
&lt;br /&gt;
At the time of writing two propulsion systems are in active use:&lt;br /&gt;
&lt;br /&gt;
* New Main Propulsion Unit&lt;br /&gt;
* Mini Rudder V2&lt;br /&gt;
&lt;br /&gt;
== New Main Propulsion Unit ==&lt;br /&gt;
&lt;br /&gt;
[[File:Roer_2026.png|thumb|300px|New main drive unit]]&lt;br /&gt;
&lt;br /&gt;
The new main propulsion unit is powered by a Lehner Torqstar 3 7050/10 water-cooled outrunner motor. The motor drives a set of counter-rotating propellers mounted in pulling configuration.&lt;br /&gt;
&lt;br /&gt;
Power is transferred through an 8 mm steel shaft to an angled gearbox mounted at the bottom of the rudder. The rudder itself uses an aluminium clamshell construction housing the following systems:&lt;br /&gt;
&lt;br /&gt;
* Helical bevel gearbox using Graessner P054 2:1 gears&lt;br /&gt;
* Hydrofoil trim system&lt;br /&gt;
* Cooling water inlet system&lt;br /&gt;
&lt;br /&gt;
The transmission consists of an inner and outer shaft, each driving one propeller.&lt;br /&gt;
&lt;br /&gt;
=== Bearings ===&lt;br /&gt;
&lt;br /&gt;
The drivetrain uses the following bearings:&lt;br /&gt;
&lt;br /&gt;
* 2× 63804 (20×32×10 mm) for the outer shaft&lt;br /&gt;
* 1× 6002 (15×32×9 mm) for the inner shaft&lt;br /&gt;
* 2× 688 (8×16×4 mm) for inner/outer shaft support&lt;br /&gt;
* 1× 698 (8×19×6 mm) at the bottom of the main driveshaft&lt;br /&gt;
* 2× 638/8 (8×16×6 mm) for the remaining driveshaft support points&lt;br /&gt;
&lt;br /&gt;
=== Seals ===&lt;br /&gt;
&lt;br /&gt;
Three shaft seals are used throughout the drivetrain:&lt;br /&gt;
&lt;br /&gt;
* 1× NBR R 20×28×7 between the housing and outer propeller shaft&lt;br /&gt;
* 2× NBR R 8×16×7 between:&lt;br /&gt;
** the inner and outer propeller shafts&lt;br /&gt;
** the housing and the upper main driveshaft&lt;br /&gt;
&lt;br /&gt;
=== Keys ===&lt;br /&gt;
&lt;br /&gt;
The following keys are used throughout the transmission:&lt;br /&gt;
&lt;br /&gt;
* 2×2×10 mm keys at both ends of the driveshaft&lt;br /&gt;
* 3×3×25 mm key for the front propeller&lt;br /&gt;
* 6×6×30 mm key for the rear propeller&lt;br /&gt;
* 4×4×10 mm keys for both drive gears&lt;br /&gt;
&lt;br /&gt;
The motor shaft is connected to the main driveshaft using a KTR ROTEX 14 steel coupling.&lt;br /&gt;
&lt;br /&gt;
The clamshell housings are machined from 7021 aluminium and bolted together using low-profile Torx screws according to ISO 14580-1 in various diameters and lengths.&lt;br /&gt;
&lt;br /&gt;
At the time of writing the transmission is lubricated using 40–60 mL of Kroon Oil ATF A gearbox oil.&lt;br /&gt;
&lt;br /&gt;
=== Transmission Specifications ===&lt;br /&gt;
&lt;br /&gt;
* Maximum motor power: 10 kW&lt;br /&gt;
* Maximum motor speed: 7200 RPM&lt;br /&gt;
* Maximum motor torque: 6 Nm&lt;br /&gt;
* Maximum gearbox torque: 18 Nm&lt;br /&gt;
&lt;br /&gt;
== Hydrofoil Trim System ==&lt;br /&gt;
&lt;br /&gt;
The hydrofoil trim system uses a custom aluminium hinge assembly actuated through a carbon fiber pushrod.&lt;br /&gt;
&lt;br /&gt;
The hinge mechanism uses:&lt;br /&gt;
&lt;br /&gt;
* 8×12 mm stainless steel dowel pins&lt;br /&gt;
* 4×8 mm stainless steel dowel pins&lt;br /&gt;
&lt;br /&gt;
The pushrod is actuated using a brass threaded interface driven by a geared stepper motor (StepperOnline 11HS12-0674S-PG5). The rotational axis is redirected using a miniature universal joint.&lt;br /&gt;
&lt;br /&gt;
The system allows hydrofoil adjustment over a range of approximately 12 degrees.&lt;br /&gt;
&lt;br /&gt;
=== Hydrofoil System Bearings and Seals ===&lt;br /&gt;
&lt;br /&gt;
* 1× NBR shaft seal 6×16×4 mm&lt;br /&gt;
* 4× 6701 bearings (12×18×4 mm)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Rudder-controller ==&lt;br /&gt;
''Full documentation: [[Rudder controller]] — board function, connector pinouts, CAN messages, servo behaviour and firmware updates.''&lt;br /&gt;
&lt;br /&gt;
The new rudder design needs a controller of its own to run the integrated cooling system and the back-foil trim. That controller is now a purpose-built board, the '''[[Rudder controller]]''' (Altium project &amp;lt;code&amp;gt;AKD-Rudder_Controller&amp;lt;/code&amp;gt;): a 4-layer STM32L471 design that hangs off the [[CAN-bus]] and drives the trim stepper, the cooling pump and the cooling instrumentation. It replaces the 3D-printer tool boards that were used for prototyping up to late 2025.&lt;br /&gt;
&lt;br /&gt;
It takes commands and reports everything over the [[CAN-bus]] — there are no local controls.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Rudder-controller functions as built&lt;br /&gt;
!Feature&lt;br /&gt;
!Description&lt;br /&gt;
!Electrical connections&lt;br /&gt;
|-&lt;br /&gt;
|Power&lt;br /&gt;
|24 V from the [[CAN-bus]] cable, with an on-board step-down module for the logic rail.&lt;br /&gt;
A design requirement was that the inrush of the connected motors must not drag the [[CAN-bus]] voltage down; this is handled by 150 µF bulk capacitors local to each motor driver rather than by current limiting.&lt;br /&gt;
|Two 5-pin connectors carrying [[CAN-bus]], 24 V and the safety line&lt;br /&gt;
|-&lt;br /&gt;
|Communication&lt;br /&gt;
|CAN 2.0A at 1 Mbit/s, 11-bit identifiers. Firmware can be updated over the bus without a debug probe.&lt;br /&gt;
|Shares the two 5-pin [[CAN-bus]] connectors; wired in parallel so the board sits in a daisy chain&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil control&lt;br /&gt;
|A TMC2209 stepper driver, using [https://www.analog.com/en/lp/001/building-better-stepper-motor-system.html StallGuard™] to find the mechanical end stop during homing. Position is open-loop step counting against that stop.&lt;br /&gt;
|On board&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil motor&lt;br /&gt;
|The [https://www.omc-stepperonline.com/nema-11-stepper-l-31mm-w-rear-shaft-gear-ratio-14-1-planetary-gearbox-11hs12-0674d-pg14 Nema 11 stepper motor] (11HS12-0674D-PG14, 13.73:1 gearbox) drives the back-foil. Commanded as a 1000–2000 setpoint over the full travel, roughly 12 degrees of foil movement.&lt;br /&gt;
|Four coil pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|B+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|A+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #2222EE;color:white;&amp;quot; |Blue&lt;br /&gt;
|B-&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|A-&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Water pump&lt;br /&gt;
|The [https://nl.aliexpress.com/i/32870145384.html ZC-A210] (24 V version), driven through an H-bridge with a firmware-set current limit of 1.0 A. It runs only while the [[Battery|battery]] reports that it is discharging.&lt;br /&gt;
|Two pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|GND&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Flowmeter&lt;br /&gt;
|Two [https://nl.aliexpress.com/item/1005006813605315.html?gatewayAdapt=glo2nld#nav-specification DWS-MH-02] flowmeters, inlet and outlet. Each gives a pulse output, counted in hardware, and an NTC. The TDS pair is not connected on the board. Only the inlet meter is read by the current firmware — the outlet meter's NTC shares a microcontroller pin with the motor NTC.&lt;br /&gt;
|Inlet on four pins of the shared 10-pin connector; outlet on its own 5-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; | Red&lt;br /&gt;
|5-24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; | Black&lt;br /&gt;
| GND&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|50k Temp&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #CDDB00;&amp;quot; |Yellow&lt;br /&gt;
|Pulse out&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #EE2266;color:white;&amp;quot; | Red and Blue&lt;br /&gt;
|TDS sensor&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Steering angle&lt;br /&gt;
|A potentiometer on the steering column, calibrated on the vehicle and reported as a normalized left/centre/right position.&lt;br /&gt;
|4-pin connector: 3.3 V, wiper, presence detect, GND&lt;br /&gt;
|-&lt;br /&gt;
|Board temperature&lt;br /&gt;
|An I²C sensor measuring the board's own temperature.&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
See [[Rudder controller]] for the exact pin numbering of each connector, the CAN message formats, and the outstanding issues.&lt;br /&gt;
&lt;br /&gt;
== Mini Rudder V2 ==&lt;br /&gt;
&lt;br /&gt;
The second-generation mini rudder consists of an aluminium structural skeleton with a 3D-printed hydrodynamic outer shell.&lt;br /&gt;
&lt;br /&gt;
The system is powered by a Flipsky DC6374 140KV 3600W waterproof outrunner motor and uses a custom aluminium propeller.&lt;br /&gt;
&lt;br /&gt;
Because future plans include water cooling for additional electrical systems, the Mini Rudder V2 also includes a cooling water inlet similar to the main propulsion unit.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v2.png|thumb|300px|Mini rudder V2]]&lt;br /&gt;
&lt;br /&gt;
= Rudder Mounting System =&lt;br /&gt;
&lt;br /&gt;
Both rudders are mounted to the steering tube in a similar manner.&lt;br /&gt;
&lt;br /&gt;
The steering tube features M6 mounting holes at the bottom, allowing the propulsion units to be mounted from underneath the boat. The steering tube also contains an integrated cooling water pass-through, making it possible to connect the cooling circuit without installing additional hoses through the hull.&lt;br /&gt;
&lt;br /&gt;
The Mini Rudder can be installed without modification to the steering tube. Only the power cables need to be routed through the tube, allowing quick replacement of propulsion units and ensuring continued operation in case of drivetrain issues.&lt;br /&gt;
```&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=328</id>
		<title>Rudder</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=328"/>
		<updated>2026-08-17T12:22:20Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Propulsion System History and Overview =&lt;br /&gt;
[[File:Roer_2025.png|thumb|300px|2022 Rudder]]&lt;br /&gt;
&lt;br /&gt;
The main propulsion system is housed inside the rudder. The rudder is mounted at the rear of the boat, approximately 440 mm from the stern. Over the years several different versions of the propulsion system have been developed. A brief overview of the previous systems is given below.&lt;br /&gt;
&lt;br /&gt;
== 2022 – 2025: Underwater Mounted Inrunner ==&lt;br /&gt;
&lt;br /&gt;
This system consisted of a carbon-fiber-wrapped 3D-printed rudder design. The rudder was mounted to the boat using a carbon fiber tube attached through a so-called “well”. The well itself was made using a slightly larger carbon fiber tube integrated into the hull structure.&lt;br /&gt;
&lt;br /&gt;
The propulsion system used a Lehner 30100/12 three-phase inrunner motor combined with a Neugart PLE-6 series single-stage planetary gearbox. Over the years 4:1, 5:1, and 7:1 gearbox ratios were used. The complete drive assembly was mounted inside an aluminium housing, with the motor and gearbox thermally coupled to the housing using thermal paste for cooling.&lt;br /&gt;
&lt;br /&gt;
The propeller was mounted in pulling configuration, meaning the driveshaft protruded through the front side of the housing. The inside of the motor tube was sealed from the surrounding water using a shaft seal, while the rear end of the tube was closed off using a custom threaded cap.&lt;br /&gt;
&lt;br /&gt;
A magnetic encoder was mounted at the rear end of the motor. The motor tube itself was connected to the rudder using aluminium interface pieces and rubber O-rings. Power cables were routed through the rudder to the three-phase motor controller located in the rear compartment of the boat.&lt;br /&gt;
&lt;br /&gt;
Two major versions of this drivetrain were developed, with V2 featuring improved structural reinforcement and mounting features.&lt;br /&gt;
&lt;br /&gt;
Although the system performed well initially, it was ultimately scrapped due to continuous water ingress into the motor tube. The resulting corrosion of the gearbox and motor caused repeated failures and required frequent replacement of expensive components.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 2025: Mini Rudder V1 ==&lt;br /&gt;
&lt;br /&gt;
During the 2025 season a temporary drivetrain was constructed using a Flipsky 65111 waterproof inrunner motor. This system was designed as a backup solution for the heavily troubled main drivetrain and was used during the 2025 Amsterdam Solarboat Race.&lt;br /&gt;
&lt;br /&gt;
The rudder featured a carbon-fiber-reinforced 3D-printed body and focused mainly on reliability and ease of deployment rather than outright performance.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v1.png|thumb|300px|Mini rudder V1]]&lt;br /&gt;
&lt;br /&gt;
= Development of the New Propulsion Platform =&lt;br /&gt;
&lt;br /&gt;
Following the 2025 season, development started on a completely new propulsion platform. The main design goals of the new system were:&lt;br /&gt;
&lt;br /&gt;
* Move expensive and sensitive electronics away from direct water exposure&lt;br /&gt;
* Improve the drivetrain mounting system for future development&lt;br /&gt;
* Increase structural rigidity for future hydrofoil implementation&lt;br /&gt;
* Add integrated features required for hydrofoil compatibility&lt;br /&gt;
* Implement a counter-rotating propeller system for improved wake alignment and efficiency&lt;br /&gt;
&lt;br /&gt;
This redesign required significant modifications to the rear of the boat, including:&lt;br /&gt;
&lt;br /&gt;
* Replacement of the drivetrain well&lt;br /&gt;
* Replacement of the entire steering system&lt;br /&gt;
* Replacement of the well support structure&lt;br /&gt;
&lt;br /&gt;
Construction started in late 2025 and was completed in early 2026, with the first on-water trials taking place in May 2026.&lt;br /&gt;
&lt;br /&gt;
= Current Drive Systems =&lt;br /&gt;
&lt;br /&gt;
At the time of writing two propulsion systems are in active use:&lt;br /&gt;
&lt;br /&gt;
* New Main Propulsion Unit&lt;br /&gt;
* Mini Rudder V2&lt;br /&gt;
&lt;br /&gt;
== New Main Propulsion Unit ==&lt;br /&gt;
&lt;br /&gt;
[[File:Roer_2026.png|thumb|300px|New main drive unit]]&lt;br /&gt;
&lt;br /&gt;
The new main propulsion unit is powered by a Lehner Torqstar 3 7050/10 water-cooled outrunner motor. The motor drives a set of counter-rotating propellers mounted in pulling configuration.&lt;br /&gt;
&lt;br /&gt;
Power is transferred through an 8 mm steel shaft to an angled gearbox mounted at the bottom of the rudder. The rudder itself uses an aluminium clamshell construction housing the following systems:&lt;br /&gt;
&lt;br /&gt;
* Helical bevel gearbox using Graessner P054 2:1 gears&lt;br /&gt;
* Hydrofoil trim system&lt;br /&gt;
* Cooling water inlet system&lt;br /&gt;
&lt;br /&gt;
The transmission consists of an inner and outer shaft, each driving one propeller.&lt;br /&gt;
&lt;br /&gt;
=== Bearings ===&lt;br /&gt;
&lt;br /&gt;
The drivetrain uses the following bearings:&lt;br /&gt;
&lt;br /&gt;
* 2× 63804 (20×32×10 mm) for the outer shaft&lt;br /&gt;
* 1× 6002 (15×32×9 mm) for the inner shaft&lt;br /&gt;
* 2× 688 (8×16×4 mm) for inner/outer shaft support&lt;br /&gt;
* 1× 698 (8×19×6 mm) at the bottom of the main driveshaft&lt;br /&gt;
* 2× 638/8 (8×16×6 mm) for the remaining driveshaft support points&lt;br /&gt;
&lt;br /&gt;
=== Seals ===&lt;br /&gt;
&lt;br /&gt;
Three shaft seals are used throughout the drivetrain:&lt;br /&gt;
&lt;br /&gt;
* 1× NBR R 20×28×7 between the housing and outer propeller shaft&lt;br /&gt;
* 2× NBR R 8×16×7 between:&lt;br /&gt;
** the inner and outer propeller shafts&lt;br /&gt;
** the housing and the upper main driveshaft&lt;br /&gt;
&lt;br /&gt;
=== Keys ===&lt;br /&gt;
&lt;br /&gt;
The following keys are used throughout the transmission:&lt;br /&gt;
&lt;br /&gt;
* 2×2×10 mm keys at both ends of the driveshaft&lt;br /&gt;
* 3×3×25 mm key for the front propeller&lt;br /&gt;
* 6×6×30 mm key for the rear propeller&lt;br /&gt;
* 4×4×10 mm keys for both drive gears&lt;br /&gt;
&lt;br /&gt;
The motor shaft is connected to the main driveshaft using a KTR ROTEX 14 steel coupling.&lt;br /&gt;
&lt;br /&gt;
The clamshell housings are machined from 7021 aluminium and bolted together using low-profile Torx screws according to ISO 14580-1 in various diameters and lengths.&lt;br /&gt;
&lt;br /&gt;
At the time of writing the transmission is lubricated using 40–60 mL of Kroon Oil ATF A gearbox oil.&lt;br /&gt;
&lt;br /&gt;
=== Transmission Specifications ===&lt;br /&gt;
&lt;br /&gt;
* Maximum motor power: 10 kW&lt;br /&gt;
* Maximum motor speed: 7200 RPM&lt;br /&gt;
* Maximum motor torque: 6 Nm&lt;br /&gt;
* Maximum gearbox torque: 18 Nm&lt;br /&gt;
&lt;br /&gt;
== Hydrofoil Trim System ==&lt;br /&gt;
&lt;br /&gt;
The hydrofoil trim system uses a custom aluminium hinge assembly actuated through a carbon fiber pushrod.&lt;br /&gt;
&lt;br /&gt;
The hinge mechanism uses:&lt;br /&gt;
&lt;br /&gt;
* 8×12 mm stainless steel dowel pins&lt;br /&gt;
* 4×8 mm stainless steel dowel pins&lt;br /&gt;
&lt;br /&gt;
The pushrod is actuated using a brass threaded interface driven by a geared stepper motor (StepperOnline 11HS12-0674S-PG5). The rotational axis is redirected using a miniature universal joint.&lt;br /&gt;
&lt;br /&gt;
The system allows hydrofoil adjustment over a range of approximately 12 degrees.&lt;br /&gt;
&lt;br /&gt;
=== Hydrofoil System Bearings and Seals ===&lt;br /&gt;
&lt;br /&gt;
* 1× NBR shaft seal 6×16×4 mm&lt;br /&gt;
* 4× 6701 bearings (12×18×4 mm)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Rudder-controller ==&lt;br /&gt;
''Full documentation: [[Rudder controller]] — board function, connector pinouts, CAN messages, servo behaviour and firmware updates.''&lt;br /&gt;
&lt;br /&gt;
The new rudder design needs a controller of its own to run the integrated cooling system and the back-foil trim. That controller is now a purpose-built board, the '''[[Rudder controller]]''' (Altium project &amp;lt;code&amp;gt;AKD-Rudder_Controller&amp;lt;/code&amp;gt;): a 4-layer STM32L471 design that hangs off the [[CAN-bus]] and drives the trim stepper, the cooling pump and the cooling instrumentation. It replaces the 3D-printer tool boards that were used for prototyping up to late 2025.&lt;br /&gt;
&lt;br /&gt;
It takes commands and reports everything over the [[CAN-bus]] — there are no local controls.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Rudder-controller functions as built&lt;br /&gt;
!Feature&lt;br /&gt;
!Description&lt;br /&gt;
!Electrical connections&lt;br /&gt;
|-&lt;br /&gt;
|Power&lt;br /&gt;
|24 V from the [[CAN-bus]] cable, with an on-board step-down module for the logic rail.&lt;br /&gt;
A design requirement was that the inrush of the connected motors must not drag the [[CAN-bus]] voltage down; this is handled by 150 µF bulk capacitors local to each motor driver rather than by current limiting.&lt;br /&gt;
|Two 5-pin connectors carrying [[CAN-bus]], 24 V and the safety line&lt;br /&gt;
|-&lt;br /&gt;
|Communication&lt;br /&gt;
|CAN 2.0A at 1 Mbit/s, 11-bit identifiers. Firmware can be updated over the bus without a debug probe.&lt;br /&gt;
|Shares the two 5-pin [[CAN-bus]] connectors; wired in parallel so the board sits in a daisy chain&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil control&lt;br /&gt;
|A TMC2209 stepper driver, using [https://www.analog.com/en/lp/001/building-better-stepper-motor-system.html StallGuard™] to find the mechanical end stop during homing. Position is open-loop step counting against that stop.&lt;br /&gt;
|On board&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil motor&lt;br /&gt;
|The [https://www.omc-stepperonline.com/nema-11-stepper-l-31mm-w-rear-shaft-gear-ratio-14-1-planetary-gearbox-11hs12-0674d-pg14 Nema 11 stepper motor] (11HS12-0674D-PG14, 13.73:1 gearbox) drives the back-foil. Commanded as a 1000–2000 setpoint over the full travel, roughly 12 degrees of foil movement.&lt;br /&gt;
|Four coil pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|B+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|A+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #2222EE;color:white;&amp;quot; |Blue&lt;br /&gt;
|B-&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|A-&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Water pump&lt;br /&gt;
|The [https://nl.aliexpress.com/i/32870145384.html ZC-A210] (24 V version), driven through an H-bridge with a firmware-set current limit of 1.0 A. It runs only while the [[Battery|battery]] reports that it is discharging.&lt;br /&gt;
|Two pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|GND&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Flowmeter&lt;br /&gt;
|Two [https://nl.aliexpress.com/item/1005006813605315.html?gatewayAdapt=glo2nld#nav-specification DWS-MH-02] flowmeters, inlet and outlet. Each gives a pulse output, counted in hardware, and an NTC. The TDS pair is not connected on the board. Only the inlet meter is read by the current firmware — the outlet meter's NTC shares a microcontroller pin with the motor NTC.&lt;br /&gt;
|Inlet on four pins of the shared 10-pin connector; outlet on its own 5-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; | Red&lt;br /&gt;
|5-24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; | Black&lt;br /&gt;
| GND&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|50k Temp&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #CDDB00;&amp;quot; |Yellow&lt;br /&gt;
|Pulse out&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #EE2266;color:white;&amp;quot; | Red and Blue&lt;br /&gt;
|TDS sensor&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Steering angle&lt;br /&gt;
|A potentiometer on the steering column, calibrated on the vehicle and reported as a normalized left/centre/right position.&lt;br /&gt;
|4-pin connector: 3.3 V, wiper, presence detect, GND&lt;br /&gt;
|-&lt;br /&gt;
|Motor and board temperature&lt;br /&gt;
|An NTC on the motor, plus an I²C sensor measuring the board's own temperature.&lt;br /&gt;
|Motor NTC shares the outlet flowmeter connector&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
See [[Rudder controller]] for the exact pin numbering of each connector, the CAN message formats, and the outstanding issues.&lt;br /&gt;
&lt;br /&gt;
== Mini Rudder V2 ==&lt;br /&gt;
&lt;br /&gt;
The second-generation mini rudder consists of an aluminium structural skeleton with a 3D-printed hydrodynamic outer shell.&lt;br /&gt;
&lt;br /&gt;
The system is powered by a Flipsky DC6374 140KV 3600W waterproof outrunner motor and uses a custom aluminium propeller.&lt;br /&gt;
&lt;br /&gt;
Because future plans include water cooling for additional electrical systems, the Mini Rudder V2 also includes a cooling water inlet similar to the main propulsion unit.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v2.png|thumb|300px|Mini rudder V2]]&lt;br /&gt;
&lt;br /&gt;
= Rudder Mounting System =&lt;br /&gt;
&lt;br /&gt;
Both rudders are mounted to the steering tube in a similar manner.&lt;br /&gt;
&lt;br /&gt;
The steering tube features M6 mounting holes at the bottom, allowing the propulsion units to be mounted from underneath the boat. The steering tube also contains an integrated cooling water pass-through, making it possible to connect the cooling circuit without installing additional hoses through the hull.&lt;br /&gt;
&lt;br /&gt;
The Mini Rudder can be installed without modification to the steering tube. Only the power cables need to be routed through the tube, allowing quick replacement of propulsion units and ensuring continued operation in case of drivetrain issues.&lt;br /&gt;
```&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=327</id>
		<title>Rudder</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=327"/>
		<updated>2026-08-17T12:21:52Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Propulsion System History and Overview =&lt;br /&gt;
&lt;br /&gt;
The main propulsion system is housed inside the rudder. The rudder is mounted at the rear of the boat, approximately 440 mm from the stern. Over the years several different versions of the propulsion system have been developed. A brief overview of the previous systems is given below.&lt;br /&gt;
&lt;br /&gt;
== 2022 – 2025: Underwater Mounted Inrunner ==&lt;br /&gt;
&lt;br /&gt;
This system consisted of a carbon-fiber-wrapped 3D-printed rudder design. The rudder was mounted to the boat using a carbon fiber tube attached through a so-called “well”. The well itself was made using a slightly larger carbon fiber tube integrated into the hull structure.&lt;br /&gt;
&lt;br /&gt;
The propulsion system used a Lehner 30100/12 three-phase inrunner motor combined with a Neugart PLE-6 series single-stage planetary gearbox. Over the years 4:1, 5:1, and 7:1 gearbox ratios were used. The complete drive assembly was mounted inside an aluminium housing, with the motor and gearbox thermally coupled to the housing using thermal paste for cooling.&lt;br /&gt;
&lt;br /&gt;
The propeller was mounted in pulling configuration, meaning the driveshaft protruded through the front side of the housing. The inside of the motor tube was sealed from the surrounding water using a shaft seal, while the rear end of the tube was closed off using a custom threaded cap.&lt;br /&gt;
&lt;br /&gt;
A magnetic encoder was mounted at the rear end of the motor. The motor tube itself was connected to the rudder using aluminium interface pieces and rubber O-rings. Power cables were routed through the rudder to the three-phase motor controller located in the rear compartment of the boat.&lt;br /&gt;
&lt;br /&gt;
Two major versions of this drivetrain were developed, with V2 featuring improved structural reinforcement and mounting features.&lt;br /&gt;
&lt;br /&gt;
Although the system performed well initially, it was ultimately scrapped due to continuous water ingress into the motor tube. The resulting corrosion of the gearbox and motor caused repeated failures and required frequent replacement of expensive components.&lt;br /&gt;
&lt;br /&gt;
[[File:Roer_2025.png|thumb|300px|2022 Rudder]]&lt;br /&gt;
&lt;br /&gt;
== 2025: Mini Rudder V1 ==&lt;br /&gt;
&lt;br /&gt;
During the 2025 season a temporary drivetrain was constructed using a Flipsky 65111 waterproof inrunner motor. This system was designed as a backup solution for the heavily troubled main drivetrain and was used during the 2025 Amsterdam Solarboat Race.&lt;br /&gt;
&lt;br /&gt;
The rudder featured a carbon-fiber-reinforced 3D-printed body and focused mainly on reliability and ease of deployment rather than outright performance.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v1.png|thumb|300px|Mini rudder V1]]&lt;br /&gt;
&lt;br /&gt;
= Development of the New Propulsion Platform =&lt;br /&gt;
&lt;br /&gt;
Following the 2025 season, development started on a completely new propulsion platform. The main design goals of the new system were:&lt;br /&gt;
&lt;br /&gt;
* Move expensive and sensitive electronics away from direct water exposure&lt;br /&gt;
* Improve the drivetrain mounting system for future development&lt;br /&gt;
* Increase structural rigidity for future hydrofoil implementation&lt;br /&gt;
* Add integrated features required for hydrofoil compatibility&lt;br /&gt;
* Implement a counter-rotating propeller system for improved wake alignment and efficiency&lt;br /&gt;
&lt;br /&gt;
This redesign required significant modifications to the rear of the boat, including:&lt;br /&gt;
&lt;br /&gt;
* Replacement of the drivetrain well&lt;br /&gt;
* Replacement of the entire steering system&lt;br /&gt;
* Replacement of the well support structure&lt;br /&gt;
&lt;br /&gt;
Construction started in late 2025 and was completed in early 2026, with the first on-water trials taking place in May 2026.&lt;br /&gt;
&lt;br /&gt;
= Current Drive Systems =&lt;br /&gt;
&lt;br /&gt;
At the time of writing two propulsion systems are in active use:&lt;br /&gt;
&lt;br /&gt;
* New Main Propulsion Unit&lt;br /&gt;
* Mini Rudder V2&lt;br /&gt;
&lt;br /&gt;
== New Main Propulsion Unit ==&lt;br /&gt;
&lt;br /&gt;
[[File:Roer_2026.png|thumb|300px|New main drive unit]]&lt;br /&gt;
&lt;br /&gt;
The new main propulsion unit is powered by a Lehner Torqstar 3 7050/10 water-cooled outrunner motor. The motor drives a set of counter-rotating propellers mounted in pulling configuration.&lt;br /&gt;
&lt;br /&gt;
Power is transferred through an 8 mm steel shaft to an angled gearbox mounted at the bottom of the rudder. The rudder itself uses an aluminium clamshell construction housing the following systems:&lt;br /&gt;
&lt;br /&gt;
* Helical bevel gearbox using Graessner P054 2:1 gears&lt;br /&gt;
* Hydrofoil trim system&lt;br /&gt;
* Cooling water inlet system&lt;br /&gt;
&lt;br /&gt;
The transmission consists of an inner and outer shaft, each driving one propeller.&lt;br /&gt;
&lt;br /&gt;
=== Bearings ===&lt;br /&gt;
&lt;br /&gt;
The drivetrain uses the following bearings:&lt;br /&gt;
&lt;br /&gt;
* 2× 63804 (20×32×10 mm) for the outer shaft&lt;br /&gt;
* 1× 6002 (15×32×9 mm) for the inner shaft&lt;br /&gt;
* 2× 688 (8×16×4 mm) for inner/outer shaft support&lt;br /&gt;
* 1× 698 (8×19×6 mm) at the bottom of the main driveshaft&lt;br /&gt;
* 2× 638/8 (8×16×6 mm) for the remaining driveshaft support points&lt;br /&gt;
&lt;br /&gt;
=== Seals ===&lt;br /&gt;
&lt;br /&gt;
Three shaft seals are used throughout the drivetrain:&lt;br /&gt;
&lt;br /&gt;
* 1× NBR R 20×28×7 between the housing and outer propeller shaft&lt;br /&gt;
* 2× NBR R 8×16×7 between:&lt;br /&gt;
** the inner and outer propeller shafts&lt;br /&gt;
** the housing and the upper main driveshaft&lt;br /&gt;
&lt;br /&gt;
=== Keys ===&lt;br /&gt;
&lt;br /&gt;
The following keys are used throughout the transmission:&lt;br /&gt;
&lt;br /&gt;
* 2×2×10 mm keys at both ends of the driveshaft&lt;br /&gt;
* 3×3×25 mm key for the front propeller&lt;br /&gt;
* 6×6×30 mm key for the rear propeller&lt;br /&gt;
* 4×4×10 mm keys for both drive gears&lt;br /&gt;
&lt;br /&gt;
The motor shaft is connected to the main driveshaft using a KTR ROTEX 14 steel coupling.&lt;br /&gt;
&lt;br /&gt;
The clamshell housings are machined from 7021 aluminium and bolted together using low-profile Torx screws according to ISO 14580-1 in various diameters and lengths.&lt;br /&gt;
&lt;br /&gt;
At the time of writing the transmission is lubricated using 40–60 mL of Kroon Oil ATF A gearbox oil.&lt;br /&gt;
&lt;br /&gt;
=== Transmission Specifications ===&lt;br /&gt;
&lt;br /&gt;
* Maximum motor power: 10 kW&lt;br /&gt;
* Maximum motor speed: 7200 RPM&lt;br /&gt;
* Maximum motor torque: 6 Nm&lt;br /&gt;
* Maximum gearbox torque: 18 Nm&lt;br /&gt;
&lt;br /&gt;
== Hydrofoil Trim System ==&lt;br /&gt;
&lt;br /&gt;
The hydrofoil trim system uses a custom aluminium hinge assembly actuated through a carbon fiber pushrod.&lt;br /&gt;
&lt;br /&gt;
The hinge mechanism uses:&lt;br /&gt;
&lt;br /&gt;
* 8×12 mm stainless steel dowel pins&lt;br /&gt;
* 4×8 mm stainless steel dowel pins&lt;br /&gt;
&lt;br /&gt;
The pushrod is actuated using a brass threaded interface driven by a geared stepper motor (StepperOnline 11HS12-0674S-PG5). The rotational axis is redirected using a miniature universal joint.&lt;br /&gt;
&lt;br /&gt;
The system allows hydrofoil adjustment over a range of approximately 12 degrees.&lt;br /&gt;
&lt;br /&gt;
=== Hydrofoil System Bearings and Seals ===&lt;br /&gt;
&lt;br /&gt;
* 1× NBR shaft seal 6×16×4 mm&lt;br /&gt;
* 4× 6701 bearings (12×18×4 mm)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Rudder-controller ==&lt;br /&gt;
''Full documentation: [[Rudder controller]] — board function, connector pinouts, CAN messages, servo behaviour and firmware updates.''&lt;br /&gt;
&lt;br /&gt;
The new rudder design needs a controller of its own to run the integrated cooling system and the back-foil trim. That controller is now a purpose-built board, the '''[[Rudder controller]]''' (Altium project &amp;lt;code&amp;gt;AKD-Rudder_Controller&amp;lt;/code&amp;gt;): a 4-layer STM32L471 design that hangs off the [[CAN-bus]] and drives the trim stepper, the cooling pump and the cooling instrumentation. It replaces the 3D-printer tool boards that were used for prototyping up to late 2025.&lt;br /&gt;
&lt;br /&gt;
It takes commands and reports everything over the [[CAN-bus]] — there are no local controls.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Rudder-controller functions as built&lt;br /&gt;
!Feature&lt;br /&gt;
!Description&lt;br /&gt;
!Electrical connections&lt;br /&gt;
|-&lt;br /&gt;
|Power&lt;br /&gt;
|24 V from the [[CAN-bus]] cable, with an on-board step-down module for the logic rail.&lt;br /&gt;
A design requirement was that the inrush of the connected motors must not drag the [[CAN-bus]] voltage down; this is handled by 150 µF bulk capacitors local to each motor driver rather than by current limiting.&lt;br /&gt;
|Two 5-pin connectors carrying [[CAN-bus]], 24 V and the safety line&lt;br /&gt;
|-&lt;br /&gt;
|Communication&lt;br /&gt;
|CAN 2.0A at 1 Mbit/s, 11-bit identifiers. Firmware can be updated over the bus without a debug probe.&lt;br /&gt;
|Shares the two 5-pin [[CAN-bus]] connectors; wired in parallel so the board sits in a daisy chain&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil control&lt;br /&gt;
|A TMC2209 stepper driver, using [https://www.analog.com/en/lp/001/building-better-stepper-motor-system.html StallGuard™] to find the mechanical end stop during homing. Position is open-loop step counting against that stop.&lt;br /&gt;
|On board&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil motor&lt;br /&gt;
|The [https://www.omc-stepperonline.com/nema-11-stepper-l-31mm-w-rear-shaft-gear-ratio-14-1-planetary-gearbox-11hs12-0674d-pg14 Nema 11 stepper motor] (11HS12-0674D-PG14, 13.73:1 gearbox) drives the back-foil. Commanded as a 1000–2000 setpoint over the full travel, roughly 12 degrees of foil movement.&lt;br /&gt;
|Four coil pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|B+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|A+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #2222EE;color:white;&amp;quot; |Blue&lt;br /&gt;
|B-&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|A-&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Water pump&lt;br /&gt;
|The [https://nl.aliexpress.com/i/32870145384.html ZC-A210] (24 V version), driven through an H-bridge with a firmware-set current limit of 1.0 A. It runs only while the [[Battery|battery]] reports that it is discharging.&lt;br /&gt;
|Two pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|GND&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Flowmeter&lt;br /&gt;
|Two [https://nl.aliexpress.com/item/1005006813605315.html?gatewayAdapt=glo2nld#nav-specification DWS-MH-02] flowmeters, inlet and outlet. Each gives a pulse output, counted in hardware, and an NTC. The TDS pair is not connected on the board. Only the inlet meter is read by the current firmware — the outlet meter's NTC shares a microcontroller pin with the motor NTC.&lt;br /&gt;
|Inlet on four pins of the shared 10-pin connector; outlet on its own 5-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; | Red&lt;br /&gt;
|5-24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; | Black&lt;br /&gt;
| GND&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|50k Temp&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #CDDB00;&amp;quot; |Yellow&lt;br /&gt;
|Pulse out&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #EE2266;color:white;&amp;quot; | Red and Blue&lt;br /&gt;
|TDS sensor&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Steering angle&lt;br /&gt;
|A potentiometer on the steering column, calibrated on the vehicle and reported as a normalized left/centre/right position.&lt;br /&gt;
|4-pin connector: 3.3 V, wiper, presence detect, GND&lt;br /&gt;
|-&lt;br /&gt;
|Motor and board temperature&lt;br /&gt;
|An NTC on the motor, plus an I²C sensor measuring the board's own temperature.&lt;br /&gt;
|Motor NTC shares the outlet flowmeter connector&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
See [[Rudder controller]] for the exact pin numbering of each connector, the CAN message formats, and the outstanding issues.&lt;br /&gt;
&lt;br /&gt;
== Mini Rudder V2 ==&lt;br /&gt;
&lt;br /&gt;
The second-generation mini rudder consists of an aluminium structural skeleton with a 3D-printed hydrodynamic outer shell.&lt;br /&gt;
&lt;br /&gt;
The system is powered by a Flipsky DC6374 140KV 3600W waterproof outrunner motor and uses a custom aluminium propeller.&lt;br /&gt;
&lt;br /&gt;
Because future plans include water cooling for additional electrical systems, the Mini Rudder V2 also includes a cooling water inlet similar to the main propulsion unit.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v2.png|thumb|300px|Mini rudder V2]]&lt;br /&gt;
&lt;br /&gt;
= Rudder Mounting System =&lt;br /&gt;
&lt;br /&gt;
Both rudders are mounted to the steering tube in a similar manner.&lt;br /&gt;
&lt;br /&gt;
The steering tube features M6 mounting holes at the bottom, allowing the propulsion units to be mounted from underneath the boat. The steering tube also contains an integrated cooling water pass-through, making it possible to connect the cooling circuit without installing additional hoses through the hull.&lt;br /&gt;
&lt;br /&gt;
The Mini Rudder can be installed without modification to the steering tube. Only the power cables need to be routed through the tube, allowing quick replacement of propulsion units and ensuring continued operation in case of drivetrain issues.&lt;br /&gt;
```&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=326</id>
		<title>Rudder</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=326"/>
		<updated>2026-08-17T12:14:16Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* Hydrofoil Trim System */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Propulsion System History and Overview =&lt;br /&gt;
&lt;br /&gt;
The main propulsion system is housed inside the rudder. The rudder is mounted at the rear of the boat, approximately 440 mm from the stern. Over the years several different versions of the propulsion system have been developed. A brief overview of the previous systems is given below.&lt;br /&gt;
&lt;br /&gt;
== 2022 – 2025: Underwater Mounted Inrunner ==&lt;br /&gt;
&lt;br /&gt;
This system consisted of a carbon-fiber-wrapped 3D-printed rudder design. The rudder was mounted to the boat using a carbon fiber tube attached through a so-called “well”. The well itself was made using a slightly larger carbon fiber tube integrated into the hull structure.&lt;br /&gt;
&lt;br /&gt;
The propulsion system used a Lehner 30100/12 three-phase inrunner motor combined with a Neugart PLE-6 series single-stage planetary gearbox. Over the years 4:1, 5:1, and 7:1 gearbox ratios were used. The complete drive assembly was mounted inside an aluminium housing, with the motor and gearbox thermally coupled to the housing using thermal paste for cooling.&lt;br /&gt;
&lt;br /&gt;
The propeller was mounted in pulling configuration, meaning the driveshaft protruded through the front side of the housing. The inside of the motor tube was sealed from the surrounding water using a shaft seal, while the rear end of the tube was closed off using a custom threaded cap.&lt;br /&gt;
&lt;br /&gt;
A magnetic encoder was mounted at the rear end of the motor. The motor tube itself was connected to the rudder using aluminium interface pieces and rubber O-rings. Power cables were routed through the rudder to the three-phase motor controller located in the rear compartment of the boat.&lt;br /&gt;
&lt;br /&gt;
Two major versions of this drivetrain were developed, with V2 featuring improved structural reinforcement and mounting features.&lt;br /&gt;
&lt;br /&gt;
Although the system performed well initially, it was ultimately scrapped due to continuous water ingress into the motor tube. The resulting corrosion of the gearbox and motor caused repeated failures and required frequent replacement of expensive components.&lt;br /&gt;
&lt;br /&gt;
[[File:Roer_2025.png|thumb|300px|2022 Rudder]]&lt;br /&gt;
&lt;br /&gt;
== 2025: Mini Rudder V1 ==&lt;br /&gt;
&lt;br /&gt;
During the 2025 season a temporary drivetrain was constructed using a Flipsky 65111 waterproof inrunner motor. This system was designed as a backup solution for the heavily troubled main drivetrain and was used during the 2025 Amsterdam Solarboat Race.&lt;br /&gt;
&lt;br /&gt;
The rudder featured a carbon-fiber-reinforced 3D-printed body and focused mainly on reliability and ease of deployment rather than outright performance.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v1.png|thumb|300px|Mini rudder V1]]&lt;br /&gt;
&lt;br /&gt;
= Development of the New Propulsion Platform =&lt;br /&gt;
&lt;br /&gt;
Following the 2025 season, development started on a completely new propulsion platform. The main design goals of the new system were:&lt;br /&gt;
&lt;br /&gt;
* Move expensive and sensitive electronics away from direct water exposure&lt;br /&gt;
* Improve the drivetrain mounting system for future development&lt;br /&gt;
* Increase structural rigidity for future hydrofoil implementation&lt;br /&gt;
* Add integrated features required for hydrofoil compatibility&lt;br /&gt;
* Implement a counter-rotating propeller system for improved wake alignment and efficiency&lt;br /&gt;
&lt;br /&gt;
This redesign required significant modifications to the rear of the boat, including:&lt;br /&gt;
&lt;br /&gt;
* Replacement of the drivetrain well&lt;br /&gt;
* Replacement of the entire steering system&lt;br /&gt;
* Replacement of the well support structure&lt;br /&gt;
&lt;br /&gt;
Construction started in late 2025 and was completed in early 2026, with the first on-water trials taking place in May 2026.&lt;br /&gt;
&lt;br /&gt;
= Current Drive Systems =&lt;br /&gt;
&lt;br /&gt;
At the time of writing two propulsion systems are in active use:&lt;br /&gt;
&lt;br /&gt;
* New Main Propulsion Unit&lt;br /&gt;
* Mini Rudder V2&lt;br /&gt;
&lt;br /&gt;
== New Main Propulsion Unit ==&lt;br /&gt;
&lt;br /&gt;
The new main propulsion unit is powered by a Lehner Torqstar 3 7050/10 water-cooled outrunner motor. The motor drives a set of counter-rotating propellers mounted in pulling configuration.&lt;br /&gt;
&lt;br /&gt;
Power is transferred through an 8 mm steel shaft to an angled gearbox mounted at the bottom of the rudder. The rudder itself uses an aluminium clamshell construction housing the following systems:&lt;br /&gt;
&lt;br /&gt;
* Helical bevel gearbox using Graessner P054 2:1 gears&lt;br /&gt;
* Hydrofoil trim system&lt;br /&gt;
* Cooling water inlet system&lt;br /&gt;
&lt;br /&gt;
The transmission consists of an inner and outer shaft, each driving one propeller.&lt;br /&gt;
&lt;br /&gt;
=== Bearings ===&lt;br /&gt;
&lt;br /&gt;
The drivetrain uses the following bearings:&lt;br /&gt;
&lt;br /&gt;
* 2× 63804 (20×32×10 mm) for the outer shaft&lt;br /&gt;
* 1× 6002 (15×32×9 mm) for the inner shaft&lt;br /&gt;
* 2× 688 (8×16×4 mm) for inner/outer shaft support&lt;br /&gt;
* 1× 698 (8×19×6 mm) at the bottom of the main driveshaft&lt;br /&gt;
* 2× 638/8 (8×16×6 mm) for the remaining driveshaft support points&lt;br /&gt;
&lt;br /&gt;
=== Seals ===&lt;br /&gt;
&lt;br /&gt;
Three shaft seals are used throughout the drivetrain:&lt;br /&gt;
&lt;br /&gt;
* 1× NBR R 20×28×7 between the housing and outer propeller shaft&lt;br /&gt;
* 2× NBR R 8×16×7 between:&lt;br /&gt;
** the inner and outer propeller shafts&lt;br /&gt;
** the housing and the upper main driveshaft&lt;br /&gt;
&lt;br /&gt;
=== Keys ===&lt;br /&gt;
&lt;br /&gt;
The following keys are used throughout the transmission:&lt;br /&gt;
&lt;br /&gt;
* 2×2×10 mm keys at both ends of the driveshaft&lt;br /&gt;
* 3×3×25 mm key for the front propeller&lt;br /&gt;
* 6×6×30 mm key for the rear propeller&lt;br /&gt;
* 4×4×10 mm keys for both drive gears&lt;br /&gt;
&lt;br /&gt;
The motor shaft is connected to the main driveshaft using a KTR ROTEX 14 steel coupling.&lt;br /&gt;
&lt;br /&gt;
The clamshell housings are machined from 7021 aluminium and bolted together using low-profile Torx screws according to ISO 14580-1 in various diameters and lengths.&lt;br /&gt;
&lt;br /&gt;
At the time of writing the transmission is lubricated using 40–60 mL of Kroon Oil ATF A gearbox oil.&lt;br /&gt;
&lt;br /&gt;
=== Transmission Specifications ===&lt;br /&gt;
&lt;br /&gt;
* Maximum motor power: 10 kW&lt;br /&gt;
* Maximum motor speed: 7200 RPM&lt;br /&gt;
* Maximum motor torque: 6 Nm&lt;br /&gt;
* Maximum gearbox torque: 18 Nm&lt;br /&gt;
&lt;br /&gt;
== Hydrofoil Trim System ==&lt;br /&gt;
&lt;br /&gt;
The hydrofoil trim system uses a custom aluminium hinge assembly actuated through a carbon fiber pushrod.&lt;br /&gt;
&lt;br /&gt;
The hinge mechanism uses:&lt;br /&gt;
&lt;br /&gt;
* 8×12 mm stainless steel dowel pins&lt;br /&gt;
* 4×8 mm stainless steel dowel pins&lt;br /&gt;
&lt;br /&gt;
The pushrod is actuated using a brass threaded interface driven by a geared stepper motor (StepperOnline 11HS12-0674S-PG5). The rotational axis is redirected using a miniature universal joint.&lt;br /&gt;
&lt;br /&gt;
The system allows hydrofoil adjustment over a range of approximately 12 degrees.&lt;br /&gt;
&lt;br /&gt;
=== Hydrofoil System Bearings and Seals ===&lt;br /&gt;
&lt;br /&gt;
* 1× NBR shaft seal 6×16×4 mm&lt;br /&gt;
* 4× 6701 bearings (12×18×4 mm)&lt;br /&gt;
&lt;br /&gt;
[[File:Roer_2026.png|thumb|300px|New main drive unit]]&lt;br /&gt;
&lt;br /&gt;
== Rudder-controller ==&lt;br /&gt;
''Full documentation: [[Rudder controller]] — board function, connector pinouts, CAN messages, servo behaviour and firmware updates.''&lt;br /&gt;
&lt;br /&gt;
The new rudder design needs a controller of its own to run the integrated cooling system and the back-foil trim. That controller is now a purpose-built board, the '''[[Rudder controller]]''' (Altium project &amp;lt;code&amp;gt;AKD-Rudder_Controller&amp;lt;/code&amp;gt;): a 4-layer STM32L471 design that hangs off the [[CAN-bus]] and drives the trim stepper, the cooling pump and the cooling instrumentation. It replaces the 3D-printer tool boards that were used for prototyping up to late 2025.&lt;br /&gt;
&lt;br /&gt;
It takes commands and reports everything over the [[CAN-bus]] — there are no local controls.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Rudder-controller functions as built&lt;br /&gt;
!Feature&lt;br /&gt;
!Description&lt;br /&gt;
!Electrical connections&lt;br /&gt;
|-&lt;br /&gt;
|Power&lt;br /&gt;
|24 V from the [[CAN-bus]] cable, with an on-board step-down module for the logic rail.&lt;br /&gt;
A design requirement was that the inrush of the connected motors must not drag the [[CAN-bus]] voltage down; this is handled by 150 µF bulk capacitors local to each motor driver rather than by current limiting.&lt;br /&gt;
|Two 5-pin connectors carrying [[CAN-bus]], 24 V and the safety line&lt;br /&gt;
|-&lt;br /&gt;
|Communication&lt;br /&gt;
|CAN 2.0A at 1 Mbit/s, 11-bit identifiers. Firmware can be updated over the bus without a debug probe.&lt;br /&gt;
|Shares the two 5-pin [[CAN-bus]] connectors; wired in parallel so the board sits in a daisy chain&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil control&lt;br /&gt;
|A TMC2209 stepper driver, using [https://www.analog.com/en/lp/001/building-better-stepper-motor-system.html StallGuard™] to find the mechanical end stop during homing. Position is open-loop step counting against that stop.&lt;br /&gt;
|On board&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil motor&lt;br /&gt;
|The [https://www.omc-stepperonline.com/nema-11-stepper-l-31mm-w-rear-shaft-gear-ratio-14-1-planetary-gearbox-11hs12-0674d-pg14 Nema 11 stepper motor] (11HS12-0674D-PG14, 13.73:1 gearbox) drives the back-foil. Commanded as a 1000–2000 setpoint over the full travel, roughly 12 degrees of foil movement.&lt;br /&gt;
|Four coil pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|B+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|A+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #2222EE;color:white;&amp;quot; |Blue&lt;br /&gt;
|B-&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|A-&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Water pump&lt;br /&gt;
|The [https://nl.aliexpress.com/i/32870145384.html ZC-A210] (24 V version), driven through an H-bridge with a firmware-set current limit of 1.0 A. It runs only while the [[Battery|battery]] reports that it is discharging.&lt;br /&gt;
|Two pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|GND&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Flowmeter&lt;br /&gt;
|Two [https://nl.aliexpress.com/item/1005006813605315.html?gatewayAdapt=glo2nld#nav-specification DWS-MH-02] flowmeters, inlet and outlet. Each gives a pulse output, counted in hardware, and an NTC. The TDS pair is not connected on the board. Only the inlet meter is read by the current firmware — the outlet meter's NTC shares a microcontroller pin with the motor NTC.&lt;br /&gt;
|Inlet on four pins of the shared 10-pin connector; outlet on its own 5-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; | Red&lt;br /&gt;
|5-24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; | Black&lt;br /&gt;
| GND&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|50k Temp&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #CDDB00;&amp;quot; |Yellow&lt;br /&gt;
|Pulse out&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #EE2266;color:white;&amp;quot; | Red and Blue&lt;br /&gt;
|TDS sensor&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Steering angle&lt;br /&gt;
|A potentiometer on the steering column, calibrated on the vehicle and reported as a normalized left/centre/right position.&lt;br /&gt;
|4-pin connector: 3.3 V, wiper, presence detect, GND&lt;br /&gt;
|-&lt;br /&gt;
|Motor and board temperature&lt;br /&gt;
|An NTC on the motor, plus an I²C sensor measuring the board's own temperature.&lt;br /&gt;
|Motor NTC shares the outlet flowmeter connector&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
See [[Rudder controller]] for the exact pin numbering of each connector, the CAN message formats, and the outstanding issues.&lt;br /&gt;
&lt;br /&gt;
== Mini Rudder V2 ==&lt;br /&gt;
&lt;br /&gt;
The second-generation mini rudder consists of an aluminium structural skeleton with a 3D-printed hydrodynamic outer shell.&lt;br /&gt;
&lt;br /&gt;
The system is powered by a Flipsky DC6374 140KV 3600W waterproof outrunner motor and uses a custom aluminium propeller.&lt;br /&gt;
&lt;br /&gt;
Because future plans include water cooling for additional electrical systems, the Mini Rudder V2 also includes a cooling water inlet similar to the main propulsion unit.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v2.png|thumb|300px|Mini rudder V2]]&lt;br /&gt;
&lt;br /&gt;
= Rudder Mounting System =&lt;br /&gt;
&lt;br /&gt;
Both rudders are mounted to the steering tube in a similar manner.&lt;br /&gt;
&lt;br /&gt;
The steering tube features M6 mounting holes at the bottom, allowing the propulsion units to be mounted from underneath the boat. The steering tube also contains an integrated cooling water pass-through, making it possible to connect the cooling circuit without installing additional hoses through the hull.&lt;br /&gt;
&lt;br /&gt;
The Mini Rudder can be installed without modification to the steering tube. Only the power cables need to be routed through the tube, allowing quick replacement of propulsion units and ensuring continued operation in case of drivetrain issues.&lt;br /&gt;
```&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=325</id>
		<title>Rudder</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Rudder&amp;diff=325"/>
		<updated>2026-08-17T12:11:15Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Propulsion System History and Overview =&lt;br /&gt;
&lt;br /&gt;
The main propulsion system is housed inside the rudder. The rudder is mounted at the rear of the boat, approximately 440 mm from the stern. Over the years several different versions of the propulsion system have been developed. A brief overview of the previous systems is given below.&lt;br /&gt;
&lt;br /&gt;
== 2022 – 2025: Underwater Mounted Inrunner ==&lt;br /&gt;
&lt;br /&gt;
This system consisted of a carbon-fiber-wrapped 3D-printed rudder design. The rudder was mounted to the boat using a carbon fiber tube attached through a so-called “well”. The well itself was made using a slightly larger carbon fiber tube integrated into the hull structure.&lt;br /&gt;
&lt;br /&gt;
The propulsion system used a Lehner 30100/12 three-phase inrunner motor combined with a Neugart PLE-6 series single-stage planetary gearbox. Over the years 4:1, 5:1, and 7:1 gearbox ratios were used. The complete drive assembly was mounted inside an aluminium housing, with the motor and gearbox thermally coupled to the housing using thermal paste for cooling.&lt;br /&gt;
&lt;br /&gt;
The propeller was mounted in pulling configuration, meaning the driveshaft protruded through the front side of the housing. The inside of the motor tube was sealed from the surrounding water using a shaft seal, while the rear end of the tube was closed off using a custom threaded cap.&lt;br /&gt;
&lt;br /&gt;
A magnetic encoder was mounted at the rear end of the motor. The motor tube itself was connected to the rudder using aluminium interface pieces and rubber O-rings. Power cables were routed through the rudder to the three-phase motor controller located in the rear compartment of the boat.&lt;br /&gt;
&lt;br /&gt;
Two major versions of this drivetrain were developed, with V2 featuring improved structural reinforcement and mounting features.&lt;br /&gt;
&lt;br /&gt;
Although the system performed well initially, it was ultimately scrapped due to continuous water ingress into the motor tube. The resulting corrosion of the gearbox and motor caused repeated failures and required frequent replacement of expensive components.&lt;br /&gt;
&lt;br /&gt;
[[File:Roer_2025.png|thumb|300px|2022 Rudder]]&lt;br /&gt;
&lt;br /&gt;
== 2025: Mini Rudder V1 ==&lt;br /&gt;
&lt;br /&gt;
During the 2025 season a temporary drivetrain was constructed using a Flipsky 65111 waterproof inrunner motor. This system was designed as a backup solution for the heavily troubled main drivetrain and was used during the 2025 Amsterdam Solarboat Race.&lt;br /&gt;
&lt;br /&gt;
The rudder featured a carbon-fiber-reinforced 3D-printed body and focused mainly on reliability and ease of deployment rather than outright performance.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v1.png|thumb|300px|Mini rudder V1]]&lt;br /&gt;
&lt;br /&gt;
= Development of the New Propulsion Platform =&lt;br /&gt;
&lt;br /&gt;
Following the 2025 season, development started on a completely new propulsion platform. The main design goals of the new system were:&lt;br /&gt;
&lt;br /&gt;
* Move expensive and sensitive electronics away from direct water exposure&lt;br /&gt;
* Improve the drivetrain mounting system for future development&lt;br /&gt;
* Increase structural rigidity for future hydrofoil implementation&lt;br /&gt;
* Add integrated features required for hydrofoil compatibility&lt;br /&gt;
* Implement a counter-rotating propeller system for improved wake alignment and efficiency&lt;br /&gt;
&lt;br /&gt;
This redesign required significant modifications to the rear of the boat, including:&lt;br /&gt;
&lt;br /&gt;
* Replacement of the drivetrain well&lt;br /&gt;
* Replacement of the entire steering system&lt;br /&gt;
* Replacement of the well support structure&lt;br /&gt;
&lt;br /&gt;
Construction started in late 2025 and was completed in early 2026, with the first on-water trials taking place in May 2026.&lt;br /&gt;
&lt;br /&gt;
= Current Drive Systems =&lt;br /&gt;
&lt;br /&gt;
At the time of writing two propulsion systems are in active use:&lt;br /&gt;
&lt;br /&gt;
* New Main Propulsion Unit&lt;br /&gt;
* Mini Rudder V2&lt;br /&gt;
&lt;br /&gt;
== New Main Propulsion Unit ==&lt;br /&gt;
&lt;br /&gt;
The new main propulsion unit is powered by a Lehner Torqstar 3 7050/10 water-cooled outrunner motor. The motor drives a set of counter-rotating propellers mounted in pulling configuration.&lt;br /&gt;
&lt;br /&gt;
Power is transferred through an 8 mm steel shaft to an angled gearbox mounted at the bottom of the rudder. The rudder itself uses an aluminium clamshell construction housing the following systems:&lt;br /&gt;
&lt;br /&gt;
* Helical bevel gearbox using Graessner P054 2:1 gears&lt;br /&gt;
* Hydrofoil trim system&lt;br /&gt;
* Cooling water inlet system&lt;br /&gt;
&lt;br /&gt;
The transmission consists of an inner and outer shaft, each driving one propeller.&lt;br /&gt;
&lt;br /&gt;
=== Bearings ===&lt;br /&gt;
&lt;br /&gt;
The drivetrain uses the following bearings:&lt;br /&gt;
&lt;br /&gt;
* 2× 63804 (20×32×10 mm) for the outer shaft&lt;br /&gt;
* 1× 6002 (15×32×9 mm) for the inner shaft&lt;br /&gt;
* 2× 688 (8×16×4 mm) for inner/outer shaft support&lt;br /&gt;
* 1× 698 (8×19×6 mm) at the bottom of the main driveshaft&lt;br /&gt;
* 2× 638/8 (8×16×6 mm) for the remaining driveshaft support points&lt;br /&gt;
&lt;br /&gt;
=== Seals ===&lt;br /&gt;
&lt;br /&gt;
Three shaft seals are used throughout the drivetrain:&lt;br /&gt;
&lt;br /&gt;
* 1× NBR R 20×28×7 between the housing and outer propeller shaft&lt;br /&gt;
* 2× NBR R 8×16×7 between:&lt;br /&gt;
** the inner and outer propeller shafts&lt;br /&gt;
** the housing and the upper main driveshaft&lt;br /&gt;
&lt;br /&gt;
=== Keys ===&lt;br /&gt;
&lt;br /&gt;
The following keys are used throughout the transmission:&lt;br /&gt;
&lt;br /&gt;
* 2×2×10 mm keys at both ends of the driveshaft&lt;br /&gt;
* 3×3×25 mm key for the front propeller&lt;br /&gt;
* 6×6×30 mm key for the rear propeller&lt;br /&gt;
* 4×4×10 mm keys for both drive gears&lt;br /&gt;
&lt;br /&gt;
The motor shaft is connected to the main driveshaft using a KTR ROTEX 14 steel coupling.&lt;br /&gt;
&lt;br /&gt;
The clamshell housings are machined from 7021 aluminium and bolted together using low-profile Torx screws according to ISO 14580-1 in various diameters and lengths.&lt;br /&gt;
&lt;br /&gt;
At the time of writing the transmission is lubricated using 40–60 mL of Kroon Oil ATF A gearbox oil.&lt;br /&gt;
&lt;br /&gt;
=== Transmission Specifications ===&lt;br /&gt;
&lt;br /&gt;
* Maximum motor power: 10 kW&lt;br /&gt;
* Maximum motor speed: 7200 RPM&lt;br /&gt;
* Maximum motor torque: 6 Nm&lt;br /&gt;
* Maximum gearbox torque: 18 Nm&lt;br /&gt;
&lt;br /&gt;
== Hydrofoil Trim System ==&lt;br /&gt;
&lt;br /&gt;
The hydrofoil trim system uses a custom aluminium hinge assembly actuated through a carbon fiber pushrod.&lt;br /&gt;
&lt;br /&gt;
The hinge mechanism uses:&lt;br /&gt;
&lt;br /&gt;
* 8×12 mm stainless steel dowel pins&lt;br /&gt;
* 4×8 mm stainless steel dowel pins&lt;br /&gt;
&lt;br /&gt;
The pushrod is actuated using a brass threaded interface driven by a geared stepper motor (StepperOnline 11HS12-0674S-PG5). The rotational axis is redirected using a miniature universal joint.&lt;br /&gt;
&lt;br /&gt;
The system allows hydrofoil adjustment over a range of approximately 12 degrees.&lt;br /&gt;
&lt;br /&gt;
=== Hydrofoil System Bearings and Seals ===&lt;br /&gt;
&lt;br /&gt;
* 1× NBR shaft seal 6×16×4 mm&lt;br /&gt;
* 4× 6701 bearings (12×18×4 mm)&lt;br /&gt;
&lt;br /&gt;
[[File:Roer_2026.png|thumb|300px|New main drive unit]]&lt;br /&gt;
&lt;br /&gt;
=== Rudder-controller ===&lt;br /&gt;
''Full documentation: [[Rudder controller]] — board function, connector pinouts, CAN messages, servo behaviour and firmware updates.''&lt;br /&gt;
&lt;br /&gt;
The new rudder design needs a controller of its own to run the integrated cooling system and the back-foil trim. That controller is now a purpose-built board, the '''[[Rudder controller]]''' (Altium project &amp;lt;code&amp;gt;AKD-Rudder_Controller&amp;lt;/code&amp;gt;): a 4-layer STM32L471 design that hangs off the [[CAN-bus]] and drives the trim stepper, the cooling pump and the cooling instrumentation. It replaces the 3D-printer tool boards that were used for prototyping up to late 2025.&lt;br /&gt;
&lt;br /&gt;
It takes commands and reports everything over the [[CAN-bus]] — there are no local controls.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Rudder-controller functions as built&lt;br /&gt;
!Feature&lt;br /&gt;
!Description&lt;br /&gt;
!Electrical connections&lt;br /&gt;
|-&lt;br /&gt;
|Power&lt;br /&gt;
|24 V from the [[CAN-bus]] cable, with an on-board step-down module for the logic rail.&lt;br /&gt;
A design requirement was that the inrush of the connected motors must not drag the [[CAN-bus]] voltage down; this is handled by 150 µF bulk capacitors local to each motor driver rather than by current limiting.&lt;br /&gt;
|Two 5-pin connectors carrying [[CAN-bus]], 24 V and the safety line&lt;br /&gt;
|-&lt;br /&gt;
|Communication&lt;br /&gt;
|CAN 2.0A at 1 Mbit/s, 11-bit identifiers. Firmware can be updated over the bus without a debug probe.&lt;br /&gt;
|Shares the two 5-pin [[CAN-bus]] connectors; wired in parallel so the board sits in a daisy chain&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil control&lt;br /&gt;
|A TMC2209 stepper driver, using [https://www.analog.com/en/lp/001/building-better-stepper-motor-system.html StallGuard™] to find the mechanical end stop during homing. Position is open-loop step counting against that stop.&lt;br /&gt;
|On board&lt;br /&gt;
|-&lt;br /&gt;
|Back-foil motor&lt;br /&gt;
|The [https://www.omc-stepperonline.com/nema-11-stepper-l-31mm-w-rear-shaft-gear-ratio-14-1-planetary-gearbox-11hs12-0674d-pg14 Nema 11 stepper motor] (11HS12-0674D-PG14, 13.73:1 gearbox) drives the back-foil. Commanded as a 1000–2000 setpoint over the full travel, roughly 12 degrees of foil movement.&lt;br /&gt;
|Four coil pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|B+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|A+&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #2222EE;color:white;&amp;quot; |Blue&lt;br /&gt;
|B-&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|A-&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Water pump&lt;br /&gt;
|The [https://nl.aliexpress.com/i/32870145384.html ZC-A210] (24 V version), driven through an H-bridge with a firmware-set current limit of 1.0 A. It runs only while the [[Battery|battery]] reports that it is discharging.&lt;br /&gt;
|Two pins on the shared 10-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; |Red&lt;br /&gt;
|24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; |Black&lt;br /&gt;
|GND&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Flowmeter&lt;br /&gt;
|Two [https://nl.aliexpress.com/item/1005006813605315.html?gatewayAdapt=glo2nld#nav-specification DWS-MH-02] flowmeters, inlet and outlet. Each gives a pulse output, counted in hardware, and an NTC. The TDS pair is not connected on the board. Only the inlet meter is read by the current firmware — the outlet meter's NTC shares a microcontroller pin with the motor NTC.&lt;br /&gt;
|Inlet on four pins of the shared 10-pin connector; outlet on its own 5-pin connector&lt;br /&gt;
{| class=&amp;quot;wikitable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+Connection information&lt;br /&gt;
!Color&lt;br /&gt;
!Function&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #ED1D0E;color:white;&amp;quot; | Red&lt;br /&gt;
|5-24v&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #000000;color:white;&amp;quot; | Black&lt;br /&gt;
| GND&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #116902;color:white;&amp;quot; |Green&lt;br /&gt;
|50k Temp&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #CDDB00;&amp;quot; |Yellow&lt;br /&gt;
|Pulse out&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: #EE2266;color:white;&amp;quot; | Red and Blue&lt;br /&gt;
|TDS sensor&lt;br /&gt;
|}&lt;br /&gt;
|-&lt;br /&gt;
|Steering angle&lt;br /&gt;
|A potentiometer on the steering column, calibrated on the vehicle and reported as a normalized left/centre/right position.&lt;br /&gt;
|4-pin connector: 3.3 V, wiper, presence detect, GND&lt;br /&gt;
|-&lt;br /&gt;
|Motor and board temperature&lt;br /&gt;
|An NTC on the motor, plus an I²C sensor measuring the board's own temperature.&lt;br /&gt;
|Motor NTC shares the outlet flowmeter connector&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
See [[Rudder controller]] for the exact pin numbering of each connector, the CAN message formats, and the outstanding issues.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Mini Rudder V2 ==&lt;br /&gt;
&lt;br /&gt;
The second-generation mini rudder consists of an aluminium structural skeleton with a 3D-printed hydrodynamic outer shell.&lt;br /&gt;
&lt;br /&gt;
The system is powered by a Flipsky DC6374 140KV 3600W waterproof outrunner motor and uses a custom aluminium propeller.&lt;br /&gt;
&lt;br /&gt;
Because future plans include water cooling for additional electrical systems, the Mini Rudder V2 also includes a cooling water inlet similar to the main propulsion unit.&lt;br /&gt;
&lt;br /&gt;
[[File:Miniroer_v2.png|thumb|300px|Mini rudder V2]]&lt;br /&gt;
&lt;br /&gt;
= Rudder Mounting System =&lt;br /&gt;
&lt;br /&gt;
Both rudders are mounted to the steering tube in a similar manner.&lt;br /&gt;
&lt;br /&gt;
The steering tube features M6 mounting holes at the bottom, allowing the propulsion units to be mounted from underneath the boat. The steering tube also contains an integrated cooling water pass-through, making it possible to connect the cooling circuit without installing additional hoses through the hull.&lt;br /&gt;
&lt;br /&gt;
The Mini Rudder can be installed without modification to the steering tube. Only the power cables need to be routed through the tube, allowing quick replacement of propulsion units and ensuring continued operation in case of drivetrain issues.&lt;br /&gt;
```&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=324</id>
		<title>Foils</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=324"/>
		<updated>2026-08-14T07:30:14Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The foils of the solar boat is a system that is comprised of the following:&lt;br /&gt;
&lt;br /&gt;
* Wing design&lt;br /&gt;
* Foil assembly&lt;br /&gt;
* [[Autopilot]]&lt;br /&gt;
&lt;br /&gt;
A small overview is given below what any of these signify in the hydrofoils.&lt;br /&gt;
&lt;br /&gt;
== Wing design ==&lt;br /&gt;
[[File:Nick-Wings1.jpeg|thumb|The Nick wings made with prepreg CF used for the first real flying trials]]&lt;br /&gt;
The wing design is based on the calculation of the airfoil database website: http://airfoiltools.com/plotter/index &lt;br /&gt;
&lt;br /&gt;
From this database an implementation has been made into matlab to tweak some parameters as well as a plot for generating the wing shape. &lt;br /&gt;
&lt;br /&gt;
The matlab scripts are on the [https://git.engineersofinnovation.nl git] and can be found [https://git.engineersofinnovation.nl/matlab-scripts/Hydrofoil_Design here].&lt;br /&gt;
&lt;br /&gt;
The Scripts here make an assumption of a Driver weight of 75 kg and a total boat &amp;amp; driver weight of 180 kg. This gives a mass on rudder of 62.3 kg, mass on each of 2 foils of 58.8 kg. &lt;br /&gt;
&lt;br /&gt;
[[File:cenus_lift_aoa.png|thumb|Lift vs Angle of Attack for the Nick wings at different sailing speeds]]&lt;br /&gt;
&lt;br /&gt;
== Foil assembly ==&lt;br /&gt;
The foil assembly connects the wing to the foil mast and then to the hull of the boat.&lt;br /&gt;
[[File:Foil assembly.gif|center|thumb|230x230px|Scaled down for showing moving of the foil assembly.]]&lt;br /&gt;
&lt;br /&gt;
== Autopilot ==&lt;br /&gt;
The [[autopilot]] is the control electronics that controls the servos of the foil assembly. This a signal PCB with that measures the angle of the solar boat and adjust accordingly towards keeping the hull above the water.&lt;br /&gt;
&lt;br /&gt;
== Height sensors ==&lt;br /&gt;
We use ultrasonic height sensors for sensing distance between water and our hull. Currently we use cheap A02 RS485 sensors from DYP. &lt;br /&gt;
&lt;br /&gt;
Datasheets can be found https://www.dypcn.com/uploads/A02-Datasheet.pdf and https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf&lt;br /&gt;
&lt;br /&gt;
Data from this sensor is being processed by our RS485 to CAN interface.&lt;br /&gt;
&lt;br /&gt;
== Servo Specifications == &lt;br /&gt;
The wings have a max positive AoA of 9.5°, and max negative AoA of -7.5°. Note that the neutral lift axis is about -4.3°, so there is not much deflection downward. At the servo this translates to +15° and -12.5° of travel. for servo values +1650uS to -1375uS. Note that the sign is fliped on the other side as the servos are &amp;quot;mirrored&amp;quot;. The rough speed of the wing is 0.4s/60°&lt;br /&gt;
&lt;br /&gt;
==== Pinout ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin#!!Wire Colour !!Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1|| style=&amp;quot;background: #0065B2;color:white;&amp;quot; | Blue||&lt;br /&gt;
|-&lt;br /&gt;
|2|| style=&amp;quot;background: #241F21;color:white;&amp;quot; | Black||&lt;br /&gt;
|-&lt;br /&gt;
| 3|| style=&amp;quot;background: #FFFFFF;&amp;quot; | White||&lt;br /&gt;
|-&lt;br /&gt;
|4|| style=&amp;quot;background: #83603A;&amp;quot; | Brown||&lt;br /&gt;
|-&lt;br /&gt;
|5|| style=&amp;quot;background: #B2B3B7;&amp;quot; | Gray||&lt;br /&gt;
|}&lt;br /&gt;
= CAN Messages =&lt;br /&gt;
All multi-byte values are little-endian. Any state byte not listed maps to &amp;lt;code&amp;gt;Unknown&amp;lt;/code&amp;gt; on the receiver side.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Message&lt;br /&gt;
!CAN ID&lt;br /&gt;
!DLC&lt;br /&gt;
!Byte&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Values / Range&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderSetpoint&lt;br /&gt;
|0x010&lt;br /&gt;
|2&lt;br /&gt;
|0–1&lt;br /&gt;
|Setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontLeft&lt;br /&gt;
|0x011&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontRight&lt;br /&gt;
|0x012&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x013&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x014&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderStatus&lt;br /&gt;
|0x020&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Uninitialized, 1=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Current setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderCommand&lt;br /&gt;
|0x021&lt;br /&gt;
|1&lt;br /&gt;
|0&lt;br /&gt;
|Command&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Initialize&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=File:Nick-Wings1.jpeg&amp;diff=323</id>
		<title>File:Nick-Wings1.jpeg</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=File:Nick-Wings1.jpeg&amp;diff=323"/>
		<updated>2026-08-14T07:29:03Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=322</id>
		<title>Foils</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=322"/>
		<updated>2026-08-14T07:28:47Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The foils of the solar boat is a system that is comprised of the following:&lt;br /&gt;
&lt;br /&gt;
* Wing design&lt;br /&gt;
* Foil assembly&lt;br /&gt;
* [[Autopilot]]&lt;br /&gt;
&lt;br /&gt;
A small overview is given below what any of these signify in the hydrofoils.&lt;br /&gt;
&lt;br /&gt;
== Wing design ==&lt;br /&gt;
[[File:Nick-Wings1.jpeg|thumb|The Nick wings made with prepreg CF used for the first real flying trials]]&lt;br /&gt;
The wing design is based on the calculation of the airfoil database website: http://airfoiltools.com/plotter/index &lt;br /&gt;
&lt;br /&gt;
From this database an implementation has been made into matlab to tweak some parameters as well as a plot for generating the wing shape. &lt;br /&gt;
&lt;br /&gt;
The matlab scripts are on the [https://git.engineersofinnovation.nl git] and can be found [https://git.engineersofinnovation.nl/matlab-scripts/Hydrofoil_Design here].&lt;br /&gt;
&lt;br /&gt;
The Scripts here make an assumption of a Driver weight of 75 kg and a total boat &amp;amp; driver weight of 180 kg. This gives a mass on rudder of 62.3 kg, mass on each of 2 foils of 58.8 kg. &lt;br /&gt;
&lt;br /&gt;
[[File:cenus_lift_aoa.png|thumb|Lift vs Angle of Attack for the first Cenus wing]]&lt;br /&gt;
&lt;br /&gt;
== Foil assembly ==&lt;br /&gt;
The foil assembly connects the wing to the foil mast and then to the hull of the boat.&lt;br /&gt;
[[File:Foil assembly.gif|center|thumb|230x230px|Scaled down for showing moving of the foil assembly.]]&lt;br /&gt;
&lt;br /&gt;
== Autopilot ==&lt;br /&gt;
The [[autopilot]] is the control electronics that controls the servos of the foil assembly. This a signal PCB with that measures the angle of the solar boat and adjust accordingly towards keeping the hull above the water.&lt;br /&gt;
&lt;br /&gt;
== Height sensors ==&lt;br /&gt;
We use ultrasonic height sensors for sensing distance between water and our hull. Currently we use cheap A02 RS485 sensors from DYP. &lt;br /&gt;
&lt;br /&gt;
Datasheets can be found https://www.dypcn.com/uploads/A02-Datasheet.pdf and https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf&lt;br /&gt;
&lt;br /&gt;
Data from this sensor is being processed by our RS485 to CAN interface.&lt;br /&gt;
&lt;br /&gt;
== Servo Specifications == &lt;br /&gt;
The wings have a max positive AoA of 9.5°, and max negative AoA of -7.5°. Note that the neutral lift axis is about -4.3°, so there is not much deflection downward. At the servo this translates to +15° and -12.5° of travel. for servo values +1650uS to -1375uS. Note that the sign is fliped on the other side as the servos are &amp;quot;mirrored&amp;quot;. The rough speed of the wing is 0.4s/60°&lt;br /&gt;
&lt;br /&gt;
==== Pinout ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin#!!Wire Colour !!Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1|| style=&amp;quot;background: #0065B2;color:white;&amp;quot; | Blue||&lt;br /&gt;
|-&lt;br /&gt;
|2|| style=&amp;quot;background: #241F21;color:white;&amp;quot; | Black||&lt;br /&gt;
|-&lt;br /&gt;
| 3|| style=&amp;quot;background: #FFFFFF;&amp;quot; | White||&lt;br /&gt;
|-&lt;br /&gt;
|4|| style=&amp;quot;background: #83603A;&amp;quot; | Brown||&lt;br /&gt;
|-&lt;br /&gt;
|5|| style=&amp;quot;background: #B2B3B7;&amp;quot; | Gray||&lt;br /&gt;
|}&lt;br /&gt;
= CAN Messages =&lt;br /&gt;
All multi-byte values are little-endian. Any state byte not listed maps to &amp;lt;code&amp;gt;Unknown&amp;lt;/code&amp;gt; on the receiver side.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Message&lt;br /&gt;
!CAN ID&lt;br /&gt;
!DLC&lt;br /&gt;
!Byte&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Values / Range&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderSetpoint&lt;br /&gt;
|0x010&lt;br /&gt;
|2&lt;br /&gt;
|0–1&lt;br /&gt;
|Setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontLeft&lt;br /&gt;
|0x011&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontRight&lt;br /&gt;
|0x012&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x013&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x014&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderStatus&lt;br /&gt;
|0x020&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Uninitialized, 1=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Current setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderCommand&lt;br /&gt;
|0x021&lt;br /&gt;
|1&lt;br /&gt;
|0&lt;br /&gt;
|Command&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Initialize&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=File:Cenus_lift_aoa.png&amp;diff=321</id>
		<title>File:Cenus lift aoa.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=File:Cenus_lift_aoa.png&amp;diff=321"/>
		<updated>2026-08-14T07:25:51Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Aran Dokoupil uploaded a new version of File:Cenus lift aoa.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=320</id>
		<title>Foils</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=320"/>
		<updated>2026-08-14T07:24:59Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The foils of the solar boat is a system that is comprised of the following:&lt;br /&gt;
&lt;br /&gt;
* Wing design&lt;br /&gt;
* Foil assembly&lt;br /&gt;
* [[Autopilot]]&lt;br /&gt;
&lt;br /&gt;
A small overview is given below what any of these signify in the hydrofoils.&lt;br /&gt;
&lt;br /&gt;
== Wing design ==&lt;br /&gt;
[[File:Wing.jpg|thumb|The Cenus wing to be used on the solar boat.]]&lt;br /&gt;
The wing design is based on the calculation of the airfoil database website: http://airfoiltools.com/plotter/index &lt;br /&gt;
&lt;br /&gt;
From this database an implementation has been made into matlab to tweak some parameters as well as a plot for generating the wing shape. &lt;br /&gt;
&lt;br /&gt;
The matlab scripts are on the [https://git.engineersofinnovation.nl git] and can be found [https://git.engineersofinnovation.nl/matlab-scripts/Hydrofoil_Design here].&lt;br /&gt;
&lt;br /&gt;
The Scripts here make an assumption of a Driver weight of 75 kg and a total boat &amp;amp; driver weight of 180 kg. This gives a mass on rudder of 62.3 kg, mass on each of 2 foils of 58.8 kg. &lt;br /&gt;
&lt;br /&gt;
[[File:cenus_lift_aoa.png|thumb|Lift vs Angle of Attack for the first Cenus wing]]&lt;br /&gt;
&lt;br /&gt;
== Foil assembly ==&lt;br /&gt;
The foil assembly connects the wing to the foil mast and then to the hull of the boat.&lt;br /&gt;
[[File:Foil assembly.gif|center|thumb|230x230px|Scaled down for showing moving of the foil assembly.]]&lt;br /&gt;
&lt;br /&gt;
== Autopilot ==&lt;br /&gt;
The [[autopilot]] is the control electronics that controls the servos of the foil assembly. This a signal PCB with that measures the angle of the solar boat and adjust accordingly towards keeping the hull above the water.&lt;br /&gt;
&lt;br /&gt;
== Height sensors ==&lt;br /&gt;
We use ultrasonic height sensors for sensing distance between water and our hull. Currently we use cheap A02 RS485 sensors from DYP. &lt;br /&gt;
&lt;br /&gt;
Datasheets can be found https://www.dypcn.com/uploads/A02-Datasheet.pdf and https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf&lt;br /&gt;
&lt;br /&gt;
Data from this sensor is being processed by our RS485 to CAN interface.&lt;br /&gt;
&lt;br /&gt;
== Servo Specifications == &lt;br /&gt;
The wings have a max positive AoA of 9.5°, and max negative AoA of -7.5°. Note that the neutral lift axis is about -4.3°, so there is not much deflection downward. At the servo this translates to +15° and -12.5° of travel. for servo values +1650uS to -1375uS. Note that the sign is fliped on the other side as the servos are &amp;quot;mirrored&amp;quot;. The rough speed of the wing is 0.4s/60°&lt;br /&gt;
&lt;br /&gt;
==== Pinout ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin#!!Wire Colour !!Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1|| style=&amp;quot;background: #0065B2;color:white;&amp;quot; | Blue||&lt;br /&gt;
|-&lt;br /&gt;
|2|| style=&amp;quot;background: #241F21;color:white;&amp;quot; | Black||&lt;br /&gt;
|-&lt;br /&gt;
| 3|| style=&amp;quot;background: #FFFFFF;&amp;quot; | White||&lt;br /&gt;
|-&lt;br /&gt;
|4|| style=&amp;quot;background: #83603A;&amp;quot; | Brown||&lt;br /&gt;
|-&lt;br /&gt;
|5|| style=&amp;quot;background: #B2B3B7;&amp;quot; | Gray||&lt;br /&gt;
|}&lt;br /&gt;
= CAN Messages =&lt;br /&gt;
All multi-byte values are little-endian. Any state byte not listed maps to &amp;lt;code&amp;gt;Unknown&amp;lt;/code&amp;gt; on the receiver side.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Message&lt;br /&gt;
!CAN ID&lt;br /&gt;
!DLC&lt;br /&gt;
!Byte&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Values / Range&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderSetpoint&lt;br /&gt;
|0x010&lt;br /&gt;
|2&lt;br /&gt;
|0–1&lt;br /&gt;
|Setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontLeft&lt;br /&gt;
|0x011&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontRight&lt;br /&gt;
|0x012&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x013&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x014&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderStatus&lt;br /&gt;
|0x020&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Uninitialized, 1=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Current setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderCommand&lt;br /&gt;
|0x021&lt;br /&gt;
|1&lt;br /&gt;
|0&lt;br /&gt;
|Command&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Initialize&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Foiling_information_for_pilots&amp;diff=319</id>
		<title>Foiling information for pilots</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Foiling_information_for_pilots&amp;diff=319"/>
		<updated>2026-08-13T10:29:55Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Hydrofoil status light: pilot's guide =&lt;br /&gt;
&lt;br /&gt;
The boat has '''no screen'''. Everything the foil controller needs to tell you comes through two things:&lt;br /&gt;
&lt;br /&gt;
* the '''status light''' (one bright RGB lamp) — tells you the state, all the time&lt;br /&gt;
* the '''beeper in the throttle unit''' — sounds only when you need to ''do'' something&lt;br /&gt;
&lt;br /&gt;
You do not need to know how the controller works to use this guide. Read the light, listen for the beeps, act on the table.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== The one thing to remember ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border: 3px solid #cc0000; background: #fff0f0; padding: 1em; margin: 1em 0; text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size: 180%; font-weight: bold; color: #cc0000;&amp;quot;&amp;gt;SOLID RED = CUT THE THROTTLE&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ease the throttle right off and let the boat settle down off the foils.&amp;lt;br /&amp;gt;&lt;br /&gt;
Do it first, ask why afterwards. '''The boat will not do this for you.'''&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controller never takes the throttle away and never snaps the foils to neutral at speed — that would be its own hazard. '''You are the safety system.''' The light and the beeper are how it asks you to act.&lt;br /&gt;
&lt;br /&gt;
== How to read the light ==&lt;br /&gt;
&lt;br /&gt;
The light says two separate things at once:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Signal !! What it tells you&lt;br /&gt;
|-&lt;br /&gt;
| '''The colour'''&lt;br /&gt;
| How healthy the system is — always live, whether or not the enable switch is on.&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady or blinking'''&lt;br /&gt;
| Blinking = ''not'' flying the foils (standby or refusing). Steady = ''flying the foils now''.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
So the colour tells you what you'll get '''before''' you flip the switch. Red is deliberately much brighter than green and yellow, so it reads in daylight: green and yellow mean &amp;quot;nothing to do&amp;quot;, red means &amp;quot;act&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== The states ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Light !! Meaning !! What you do&lt;br /&gt;
|-&lt;br /&gt;
| '''Blinking green'''&lt;br /&gt;
| Everything healthy, standby.&lt;br /&gt;
| '''Flip the enable switch when you're ready to foil.'''&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady green'''&lt;br /&gt;
| Foiling under full control.&lt;br /&gt;
| Nothing. This is normal running.&lt;br /&gt;
|-&lt;br /&gt;
| '''Blinking yellow'''&lt;br /&gt;
| Standby, but not fully ready yet.&lt;br /&gt;
| '''Wait.''' Normal for roughly the first half-minute after power-up. If it stays yellow, see [[#Yellow: what it means|Yellow]] below.&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady yellow'''&lt;br /&gt;
| Foiling, but running degraded — a backup has been lost.&lt;br /&gt;
| You can keep going, but '''you have no spare.''' Stay conservative, come off the foils sooner rather than later, and get it looked at before the next run.&lt;br /&gt;
|-&lt;br /&gt;
| '''Blinking red''' + counted beeps&lt;br /&gt;
| '''It is refusing to engage.''' Flipping the switch will do nothing.&lt;br /&gt;
| '''Count the beeps''' and use the [[#Refusing to engage: count the beeps|refusal table]].&lt;br /&gt;
|-&lt;br /&gt;
| '''Blinking red''', no beeps&lt;br /&gt;
| Not ready. Normally just starting up — the attitude estimate isn't running yet.&lt;br /&gt;
| Normal for the first half-minute after power-up; wait. If it stays red, '''flip the enable switch''': if it's a real fault it will start beeping a code you can count. (Safe to try — a refusal leaves the foils neutral.)&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady red''' + long repeated blast&lt;br /&gt;
| '''ALARM.''' Something failed while you were foiling.&lt;br /&gt;
| '''CUT THE THROTTLE.''' Come off the foils. Don't re-engage until it's been checked.&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady red''', no beeps&lt;br /&gt;
| The controller has lost the connection to the throttle unit and rear foil. It cannot beep at you — that's why the light alone has to say it.&lt;br /&gt;
| '''CUT THE THROTTLE.''' Come off the foils. Do not foil again until it's fixed.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Refusing to engage: count the beeps ==&lt;br /&gt;
&lt;br /&gt;
Blinking red with a repeating group of '''short, low beeps'''. The '''number of beeps in the group is the fault code.''' The group repeats after about a second and a half of silence, so you get as many chances to count as you need.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Beeps !! What's wrong !! What you do&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;text-align: center; font-size: 150%;&amp;quot; | '''2'''&lt;br /&gt;
| Its own settings were changed and haven't taken effect yet.&lt;br /&gt;
| '''Power the autopilot off and on''', wait for the light, try again. If it comes back, don't foil.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;text-align: center; font-size: 150%;&amp;quot; | '''3'''&lt;br /&gt;
| Its height reference isn't set up correctly, so it cannot trust the ride-height reading.&lt;br /&gt;
| '''Do not foil.''' Needs a technician — this is a configuration fault, not something to work around.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;text-align: center; font-size: 150%;&amp;quot; | '''4'''&lt;br /&gt;
| It cannot trust the enable switch itself.&lt;br /&gt;
| '''Do not foil.''' Needs a technician. Note this code sounds ''even with the switch off'' — there is no trustworthy switch reading to keep quiet about.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;text-align: center; font-size: 150%;&amp;quot; | '''5'''&lt;br /&gt;
| The autopilot's motion sensors disagree with each other. This is a fault, not a warm-up — waves and wake do not cause it.&lt;br /&gt;
| '''Do not foil.''' Power off and on once to rule out a one-off. If it comes back, needs a technician.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A refusal is always safe: the foils go neutral and stay there. Nothing is happening behind your back.&lt;br /&gt;
&lt;br /&gt;
== The alarm: steady red, long blast ==&lt;br /&gt;
&lt;br /&gt;
A '''long, high, loud''' beep, about a second of tone every three seconds — clearly different from the short low counting beeps. You'll also hear the autopilot's own buzzer.&lt;br /&gt;
&lt;br /&gt;
'''There is only one alarm.''' Whatever caused it, your action is the same, so the controller does not make you tell alarms apart at speed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left: 6px solid #cc0000; background: #fff0f0; padding: 0.8em 1em; margin: 1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size: 150%; font-weight: bold;&amp;quot;&amp;gt;CUT THE THROTTLE. COME OFF THE FOILS.&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Causes are things like the boat losing its sense of attitude, a motion-sensor fault, the autopilot dropping out of its stabilised mode, or both height sensors going quiet. The foils '''keep working on the last good information''' while it alarms — they are not thrown to neutral, because that would be worse at speed. But it is telling you the information it is flying on is no longer trustworthy, so get off the foils.&lt;br /&gt;
&lt;br /&gt;
The alarm stops on its own the moment the fault clears, and the light goes back to green or yellow. '''A self-clearing alarm is still a real event''' — it happened. Come in and have the log read before the next run.&lt;br /&gt;
&lt;br /&gt;
== Yellow: what it means ==&lt;br /&gt;
&lt;br /&gt;
Yellow is &amp;quot;working, but not perfect.&amp;quot; Common and harmless reasons:&lt;br /&gt;
&lt;br /&gt;
* '''Just powered up.''' The height estimate takes roughly 20 seconds to become usable. Yellow then green is the normal start-up sequence.&lt;br /&gt;
* '''One height sensor quiet.''' Two are fitted; either one alone is enough to fly on. Yellow is telling you the spare is gone.&lt;br /&gt;
* '''Roll-test mode selected.''' In this test mode the light '''never goes green''' — that's by design, not a fault. If you didn't intend to be in a test mode, get it switched back.&lt;br /&gt;
* An internal health check not passing.&lt;br /&gt;
&lt;br /&gt;
Yellow while '''standby''' (blinking): wait, or find out why before you launch.&lt;br /&gt;
&lt;br /&gt;
Yellow while '''foiling''' (steady): finish up conservatively; don't start a new run on yellow.&lt;br /&gt;
&lt;br /&gt;
== Normal start-up, in order ==&lt;br /&gt;
&lt;br /&gt;
# Power on. Light comes up '''red or yellow, blinking''' — this is expected.&lt;br /&gt;
# '''Wait.''' Nothing is required of you. The attitude and height estimates come up over roughly the first 20–30 seconds. You do not need to hold the boat still, and chop, wake or being under way do not affect this.&lt;br /&gt;
# Light settles to '''blinking yellow''', then '''blinking green''' after around 20 seconds.&lt;br /&gt;
# '''Blinking green''' = ready. Flip the enable switch.&lt;br /&gt;
# Light goes '''steady green'''. You're foiling under control.&lt;br /&gt;
&lt;br /&gt;
If step 3 never reaches green, do not launch — read the light and the beeps from the tables above.&lt;br /&gt;
&lt;br /&gt;
== If the light itself looks wrong ==&lt;br /&gt;
&lt;br /&gt;
The light is refreshed constantly while the controller is alive, and it always either blinks (standby) or sits steady (engaged).&lt;br /&gt;
&lt;br /&gt;
'''Do not foil if:'''&lt;br /&gt;
&lt;br /&gt;
* the light is '''completely dark''', or&lt;br /&gt;
* the light is '''frozen''' — it never blinks and never changes colour when you flip the enable switch.&lt;br /&gt;
&lt;br /&gt;
Either means the controller isn't running or the lamp is broken. With no controller you have no ride-height control and '''no warnings of any kind''' — nothing will tell you when something goes wrong. Get it checked.&lt;br /&gt;
&lt;br /&gt;
== Pre-launch check: 20 seconds ==&lt;br /&gt;
&lt;br /&gt;
# Light is alive and '''blinking''' (not dark, not frozen).&lt;br /&gt;
# Flip the enable switch '''off''' and '''on''' and confirm the light changes between blinking and steady. That proves the switch is being heard.&lt;br /&gt;
# Launch only on '''green'''.&lt;br /&gt;
# Know before you leave the dock: '''solid red means cut the throttle.'''&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Foiling_information_for_pilots&amp;diff=318</id>
		<title>Foiling information for pilots</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Foiling_information_for_pilots&amp;diff=318"/>
		<updated>2026-08-13T10:25:39Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Created page with &amp;quot;= Hydrofoil status light: pilot's guide =  The boat has '''no screen'''. Everything the foil controller needs to tell you comes through two things:  * the '''status light''' (one bright RGB lamp) — tells you the state, all the time * the '''beeper in the throttle unit''' — sounds only when you need to ''do'' something  You do not need to know how the controller works to use this guide. Read the light, listen for the beeps, act on the table.  __TOC__  == The one thing...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Hydrofoil status light: pilot's guide =&lt;br /&gt;
&lt;br /&gt;
The boat has '''no screen'''. Everything the foil controller needs to tell you comes through two things:&lt;br /&gt;
&lt;br /&gt;
* the '''status light''' (one bright RGB lamp) — tells you the state, all the time&lt;br /&gt;
* the '''beeper in the throttle unit''' — sounds only when you need to ''do'' something&lt;br /&gt;
&lt;br /&gt;
You do not need to know how the controller works to use this guide. Read the light, listen for the beeps, act on the table.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== The one thing to remember ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border: 3px solid #cc0000; background: #fff0f0; padding: 1em; margin: 1em 0; text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size: 180%; font-weight: bold; color: #cc0000;&amp;quot;&amp;gt;SOLID RED = CUT THE THROTTLE&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ease the throttle right off and let the boat settle down off the foils.&amp;lt;br /&amp;gt;&lt;br /&gt;
Do it first, ask why afterwards. '''The boat will not do this for you.'''&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controller never takes the throttle away and never snaps the foils to neutral at speed — that would be its own hazard. '''You are the safety system.''' The light and the beeper are how it asks you to act.&lt;br /&gt;
&lt;br /&gt;
== How to read the light ==&lt;br /&gt;
&lt;br /&gt;
The light says two separate things at once:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Signal !! What it tells you&lt;br /&gt;
|-&lt;br /&gt;
| '''The colour'''&lt;br /&gt;
| How healthy the system is — always live, whether or not the enable switch is on.&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady or blinking'''&lt;br /&gt;
| Blinking = ''not'' flying the foils (standby or refusing). Steady = ''flying the foils now''.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
So the colour tells you what you'll get '''before''' you flip the switch. Red is deliberately much brighter than green and yellow, so it reads in daylight: green and yellow mean &amp;quot;nothing to do&amp;quot;, red means &amp;quot;act&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== The states ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Light !! Meaning !! What you do&lt;br /&gt;
|-&lt;br /&gt;
| '''Blinking green'''&lt;br /&gt;
| Everything healthy, standby.&lt;br /&gt;
| '''Flip the enable switch when you're ready to foil.'''&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady green'''&lt;br /&gt;
| Foiling under full control.&lt;br /&gt;
| Nothing. This is normal running.&lt;br /&gt;
|-&lt;br /&gt;
| '''Blinking yellow'''&lt;br /&gt;
| Standby, but not fully ready yet.&lt;br /&gt;
| '''Wait.''' Normal for roughly the first half-minute after power-up. If it stays yellow, see [[#Yellow: what it means|Yellow]] below.&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady yellow'''&lt;br /&gt;
| Foiling, but running degraded — a backup has been lost.&lt;br /&gt;
| You can keep going, but '''you have no spare.''' Stay conservative, come off the foils sooner rather than later, and get it looked at before the next run.&lt;br /&gt;
|-&lt;br /&gt;
| '''Blinking red''' + counted beeps&lt;br /&gt;
| '''It is refusing to engage.''' Flipping the switch will do nothing.&lt;br /&gt;
| '''Count the beeps''' and use the [[#Refusing to engage: count the beeps|refusal table]].&lt;br /&gt;
|-&lt;br /&gt;
| '''Blinking red''', no beeps&lt;br /&gt;
| Not ready and not able to explain itself — usually still starting up, or the boat has been moved during start-up.&lt;br /&gt;
| Normal for the first seconds after power-up. If it persists, restart with the boat '''held still'''. If it still persists, don't foil.&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady red''' + long repeated blast&lt;br /&gt;
| '''ALARM.''' Something failed while you were foiling.&lt;br /&gt;
| '''CUT THE THROTTLE.''' Come off the foils. Don't re-engage until it's been checked.&lt;br /&gt;
|-&lt;br /&gt;
| '''Steady red''', no beeps&lt;br /&gt;
| The controller has lost the connection to the throttle unit and rear foil. It cannot beep at you — that's why the light alone has to say it.&lt;br /&gt;
| '''CUT THE THROTTLE.''' Come off the foils. Do not foil again until it's fixed.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Refusing to engage: count the beeps ==&lt;br /&gt;
&lt;br /&gt;
Blinking red with a repeating group of '''short, low beeps'''. The '''number of beeps in the group is the fault code.''' The group repeats after about a second and a half of silence, so you get as many chances to count as you need.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Beeps !! What's wrong !! What you do&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;text-align: center; font-size: 150%;&amp;quot; | '''2'''&lt;br /&gt;
| Its own settings were changed and haven't taken effect yet.&lt;br /&gt;
| '''Power the autopilot off and on''', wait for the light, try again. If it comes back, don't foil.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;text-align: center; font-size: 150%;&amp;quot; | '''3'''&lt;br /&gt;
| Its height reference isn't set up correctly, so it cannot trust the ride-height reading.&lt;br /&gt;
| '''Do not foil.''' Needs a technician — this is a configuration fault, not something to work around.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;text-align: center; font-size: 150%;&amp;quot; | '''4'''&lt;br /&gt;
| It cannot trust the enable switch itself.&lt;br /&gt;
| '''Do not foil.''' Needs a technician. Note this code sounds ''even with the switch off'' — there is no trustworthy switch reading to keep quiet about.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;text-align: center; font-size: 150%;&amp;quot; | '''5'''&lt;br /&gt;
| The motion sensors are still settling, or they disagree with each other.&lt;br /&gt;
| '''Keep the boat still''' for half a minute — wake and movement during start-up cause this. If it clears, carry on. If it persists on a still boat after a restart, don't foil.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A refusal is always safe: the foils go neutral and stay there. Nothing is happening behind your back.&lt;br /&gt;
&lt;br /&gt;
== The alarm: steady red, long blast ==&lt;br /&gt;
&lt;br /&gt;
A '''long, high, loud''' beep, about a second of tone every three seconds — clearly different from the short low counting beeps. You'll also hear the autopilot's own buzzer.&lt;br /&gt;
&lt;br /&gt;
'''There is only one alarm.''' Whatever caused it, your action is the same, so the controller does not make you tell alarms apart at speed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left: 6px solid #cc0000; background: #fff0f0; padding: 0.8em 1em; margin: 1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;font-size: 150%; font-weight: bold;&amp;quot;&amp;gt;CUT THE THROTTLE. COME OFF THE FOILS.&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Causes are things like the boat losing its sense of attitude, a motion-sensor fault, the autopilot dropping out of its stabilised mode, or both height sensors going quiet. The foils '''keep working on the last good information''' while it alarms — they are not thrown to neutral, because that would be worse at speed. But it is telling you the information it is flying on is no longer trustworthy, so get off the foils.&lt;br /&gt;
&lt;br /&gt;
The alarm stops on its own the moment the fault clears, and the light goes back to green or yellow. '''A self-clearing alarm is still a real event''' — it happened. Come in and have the log read before the next run.&lt;br /&gt;
&lt;br /&gt;
== Yellow: what it means ==&lt;br /&gt;
&lt;br /&gt;
Yellow is &amp;quot;working, but not perfect.&amp;quot; Common and harmless reasons:&lt;br /&gt;
&lt;br /&gt;
* '''Just powered up.''' The height estimate takes roughly 20 seconds to become usable. Yellow then green is the normal start-up sequence.&lt;br /&gt;
* '''One height sensor quiet.''' Two are fitted; either one alone is enough to fly on. Yellow is telling you the spare is gone.&lt;br /&gt;
* '''Roll-test mode selected.''' In this test mode the light '''never goes green''' — that's by design, not a fault. If you didn't intend to be in a test mode, get it switched back.&lt;br /&gt;
* An internal health check not passing.&lt;br /&gt;
&lt;br /&gt;
Yellow while '''standby''' (blinking): wait, or find out why before you launch.&lt;br /&gt;
&lt;br /&gt;
Yellow while '''foiling''' (steady): finish up conservatively; don't start a new run on yellow.&lt;br /&gt;
&lt;br /&gt;
== Normal start-up, in order ==&lt;br /&gt;
&lt;br /&gt;
# Power on. Light comes up '''red or yellow, blinking''' — this is expected.&lt;br /&gt;
# '''Keep the boat still''' for the first half-minute. Movement during start-up causes the 5-beep sensor code.&lt;br /&gt;
# Light settles to '''blinking yellow''', then '''blinking green''' after around 20 seconds.&lt;br /&gt;
# '''Blinking green''' = ready. Flip the enable switch.&lt;br /&gt;
# Light goes '''steady green'''. You're foiling under control.&lt;br /&gt;
&lt;br /&gt;
If step 3 never reaches green, do not launch — read the light and the beeps from the tables above.&lt;br /&gt;
&lt;br /&gt;
== If the light itself looks wrong ==&lt;br /&gt;
&lt;br /&gt;
The light is refreshed constantly while the controller is alive, and it always either blinks (standby) or sits steady (engaged).&lt;br /&gt;
&lt;br /&gt;
'''Do not foil if:'''&lt;br /&gt;
&lt;br /&gt;
* the light is '''completely dark''', or&lt;br /&gt;
* the light is '''frozen''' — it never blinks and never changes colour when you flip the enable switch.&lt;br /&gt;
&lt;br /&gt;
Either means the controller isn't running or the lamp is broken. With no controller you have no ride-height control and '''no warnings of any kind''' — nothing will tell you when something goes wrong. Get it checked.&lt;br /&gt;
&lt;br /&gt;
== Pre-launch check: 20 seconds ==&lt;br /&gt;
&lt;br /&gt;
# Light is alive and '''blinking''' (not dark, not frozen).&lt;br /&gt;
# Flip the enable switch '''off''' and '''on''' and confirm the light changes between blinking and steady. That proves the switch is being heard.&lt;br /&gt;
# Launch only on '''green'''.&lt;br /&gt;
# Know before you leave the dock: '''solid red means cut the throttle.'''&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Rudder_controller&amp;diff=317</id>
		<title>Rudder controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Rudder_controller&amp;diff=317"/>
		<updated>2026-08-12T13:19:55Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Created page with &amp;quot;The '''rudder controller''' (project name ''staartstuk controller'') is the CAN-bus node that operates everything inside the new main propulsion unit described on the Rudder page. It replaces the off-the-shelf prototyping platform listed in the requirements table there with a purpose-built 4-layer board.  One board handles four jobs: it positions the back hydrofoil actuator, drives and supervises the cooling-water pump, measures the water cooling circuit, and rep...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''rudder controller''' (project name ''staartstuk controller'') is the [[CAN-bus]] node that operates everything inside the new main propulsion unit described on the [[Rudder]] page. It replaces the off-the-shelf prototyping platform listed in the requirements table there with a purpose-built 4-layer board.&lt;br /&gt;
&lt;br /&gt;
One board handles four jobs: it positions the back hydrofoil actuator, drives and supervises the cooling-water pump, measures the water cooling circuit, and reports the steering angle. It has no local user interface — every command arrives over the [[CAN-bus]] and every measurement leaves over it.&lt;br /&gt;
&lt;br /&gt;
== At a glance ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Rudder controller board summary&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Microcontroller || STM32L471RGT6 (Cortex-M4F, LQFP64, 1 MB flash, 96 KB SRAM)&lt;br /&gt;
|-&lt;br /&gt;
| System clock || 80 MHz, from a 16 MHz crystal through the PLL&lt;br /&gt;
|-&lt;br /&gt;
| Supply || 24 V from the [[CAN-bus]] cable; on-board 3.3 V step-down module&lt;br /&gt;
|-&lt;br /&gt;
| Bus || CAN 2.0A, 1 Mbit/s, 11-bit standard identifiers&lt;br /&gt;
|-&lt;br /&gt;
| PCB || 4 copper layers, roughly 88 × 45 mm&lt;br /&gt;
|-&lt;br /&gt;
| Application type || &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt; — its address in the bootloader protocol&lt;br /&gt;
|-&lt;br /&gt;
| Firmware || Rust, &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt; async runtime, binary &amp;lt;code&amp;gt;rudder-controller&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Firmware source || &amp;lt;code&amp;gt;firmware/&amp;lt;/code&amp;gt; in the [https://github.com/Engineers-of-Innovation/eoi-can eoi-can] monorepo&lt;br /&gt;
|-&lt;br /&gt;
| Hardware source || Altium project &amp;lt;code&amp;gt;AKD-Rudder_Controller&amp;lt;/code&amp;gt; (Altium 365)&lt;br /&gt;
|-&lt;br /&gt;
| Bus-wide message list || [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md]&lt;br /&gt;
|-&lt;br /&gt;
| Field update || Over CAN, no debug probe needed — see [[#Firmware update over CAN]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The same firmware repository also builds two other boards, the height-sensor controller and the e-paper dashboard. Those are '''different PCBs'''; they share the microcontroller, the clock setup, the CAN driver and the bootloader, but nothing else. This page covers the rudder controller only.&lt;br /&gt;
&lt;br /&gt;
== What the board controls ==&lt;br /&gt;
&lt;br /&gt;
=== Hydrofoil actuator ===&lt;br /&gt;
&lt;br /&gt;
A geared NEMA 11 stepper drives the hydrofoil trim pushrod through a TMC2209 driver. Position is '''open-loop step counting''': there is no encoder, and the only absolute reference is a mechanical end stop found by StallGuard during homing. The commanded position is a dimensionless 1000–2000 setpoint (servo convention) spanning the full mechanical travel, which is roughly 12 degrees of foil movement.&lt;br /&gt;
&lt;br /&gt;
* Motor: StepperOnline 11HS12-0674D-PG14 — 0.67 A per phase, 200 full steps per revolution, 13.73:1 planetary gearbox&lt;br /&gt;
* Driver: TMC2209 at 8 microsteps, configured over a single-wire UART at 115200 baud&lt;br /&gt;
* Run current about 0.47 A rms, reduced to about 0.31 A rms while homing into the stop&lt;br /&gt;
* Current sensing through 100 mΩ 1 W resistors, giving roughly 1.06 A rms full scale&lt;br /&gt;
* Step rate ramps from 400 Hz to a 2 kHz cruise, about 33 degrees per second at the foil shaft&lt;br /&gt;
* Full travel is 20000 microsteps&lt;br /&gt;
&lt;br /&gt;
Behavior is covered under [[#Back-foil servo behavior]].&lt;br /&gt;
&lt;br /&gt;
=== Cooling-water pump ===&lt;br /&gt;
&lt;br /&gt;
The board switches and current-limits the 24 V circulation pump (ZC-A210) through a TI DRV8256E H-bridge, and reports the driver's fault line.&lt;br /&gt;
&lt;br /&gt;
* The current limit is set by the microcontroller's DAC feeding the driver's VREF input, which is why it can be changed in firmware rather than by swapping resistors. The driver's relation is '''I_max = VREF / 0.66 V per amp'''; the firmware programs 1.0 A currently.&lt;br /&gt;
* The driver's SLEEP pin is held awake and PHASE (direction) is held forward as static configuration. Only ENABLE is switched.&lt;br /&gt;
* '''The pump only runs while the [[Battery|battery]] is actually discharging.''' The board watches BMS message &amp;lt;code&amp;gt;0x107&amp;lt;/code&amp;gt; and enables the pump only while its discharge state reads ''On''. If no BMS frame arrives for 2 seconds the pump is disabled. This is deliberate: there is no point circulating water when the drive is not drawing power, and a silent bus must not leave the pump running unattended.&lt;br /&gt;
* The FAULT output is open-drain and active-low, pulled up inside the microcontroller. Its raw level is broadcast once per second on &amp;lt;code&amp;gt;0x212&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Cooling-circuit and motor instrumentation ===&lt;br /&gt;
&lt;br /&gt;
Two DWS-MH-02 flowmeters were designed in, one on the cooling inlet and one on the outlet. Each provides a pulse output and a built-in NTC. The pulse output is counted in hardware by a timer in external-clock mode, so no pulse is lost to interrupt latency, and the count is converted to mL/min once per second.&lt;br /&gt;
&lt;br /&gt;
'''Only the inlet flowmeter is read by the current firmware.''' The outlet flowmeter's NTC input shares a microcontroller pin with the motor NTC, and the motor temperature won that pin, so &amp;lt;code&amp;gt;0x216&amp;lt;/code&amp;gt; (FlowSensorOut) is not transmitted. The code for it still exists and can be re-enabled if the pin conflict is resolved in a later board revision.&lt;br /&gt;
&lt;br /&gt;
* Flow scaling: 22.9 Hz corresponds to 1880 mL/min (datasheet)&lt;br /&gt;
* Both NTC inputs sit in a divider with 47 kΩ to 3.3 V, a 1 kΩ series resistor and a 100 nF filter capacitor, sized for the flowmeter's 50 kΩ (25 °C, B = 3950 K) thermistor&lt;br /&gt;
* An NTC reading at either rail (open or shorted) reports the sentinel value −32768 rather than an absurd temperature&lt;br /&gt;
&lt;br /&gt;
'''Note:''' see [[#Known gaps]] the motor temperature on address &amp;lt;code&amp;gt;0x217&amp;lt;/code&amp;gt; is deprecated. Currently on the boat, the motor temperature shown on the [[Instrumentation Panel|display]] comes from the separate motor-NTC node on &amp;lt;code&amp;gt;0x219&amp;lt;/code&amp;gt; instead.&lt;br /&gt;
&lt;br /&gt;
=== Steering angle ===&lt;br /&gt;
&lt;br /&gt;
A potentiometer on the steering column is read by ADC1 and reported ten times per second as a normalized position: −1000 fully left, 0 centred, +1000 fully right. The potentiometer has no fixed raw-to-angle relation, so the three reference positions are calibrated on the vehicle and stored in the board's emulated EEPROM. See [[#Steering angle calibration]].&lt;br /&gt;
&lt;br /&gt;
The sensor connector's third pin is a presence detect: the sensor pulls it to ground, so with the internal pull-up a high reading means nothing is plugged in.&lt;br /&gt;
&lt;br /&gt;
Both the 3.3 V feed and the ground return of this connector go through 47 Ω series resistors, so a shorted sensor cable cannot pull the board's rails down.&lt;br /&gt;
&lt;br /&gt;
=== Board temperature ===&lt;br /&gt;
&lt;br /&gt;
A Würth WSEN-TIDS sensor on I²C2 (address &amp;lt;code&amp;gt;0x3F&amp;lt;/code&amp;gt;, its address pin tied low) reports the PCB temperature once per second on &amp;lt;code&amp;gt;0x211&amp;lt;/code&amp;gt; in hundredths of a degree. This is a board health measurement, not a process measurement — it is how you tell whether the sealed compartment is cooking.&lt;br /&gt;
&lt;br /&gt;
== Hardware ==&lt;br /&gt;
&lt;br /&gt;
=== Power ===&lt;br /&gt;
&lt;br /&gt;
24 V arrives on the [[CAN-bus]] connector and splits three ways:&lt;br /&gt;
&lt;br /&gt;
* '''Logic''' — through a resettable PTC fuse (30 V, 100 mA hold) into a Würth 173950336 fixed step-down module that produces the 3.3 V rail.&lt;br /&gt;
* '''Pump''' — straight to the DRV8256E, with a 150 µF aluminium-polymer bulk capacitor local to the driver.&lt;br /&gt;
* '''Stepper''' — straight to the TMC2209, with its own 150 µF bulk capacitor.&lt;br /&gt;
&lt;br /&gt;
The two motor rails are unfused on the board, so the inrush requirement from the [[Rudder]] requirements table is met by the bulk capacitors rather than by current limiting. The USB port is separately powered from its own VBUS through a second PTC fuse and a 3.3 V LDO, so plugging in USB does not back-feed the 24 V side.&lt;br /&gt;
&lt;br /&gt;
=== Key components ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Principal components&lt;br /&gt;
|-&lt;br /&gt;
! Designator !! Part !! Function&lt;br /&gt;
|-&lt;br /&gt;
| U1 || STM32L471RGT6TR || Microcontroller&lt;br /&gt;
|-&lt;br /&gt;
| U2 || DRV8256ERGER || H-bridge for the cooling pump&lt;br /&gt;
|-&lt;br /&gt;
| U3 || TCAN3403DRBRQ1 || 3.3 V CAN FD transceiver with standby control&lt;br /&gt;
|-&lt;br /&gt;
| U4 || FT234XD-R || USB-to-UART bridge on the USB-C port&lt;br /&gt;
|-&lt;br /&gt;
| U5 || Würth 173950336 || 24 V to 3.3 V step-down module&lt;br /&gt;
|-&lt;br /&gt;
| U6 || TMC2209-LA-T || Stepper driver for the trim actuator&lt;br /&gt;
|-&lt;br /&gt;
| U7 || AP2138N-3.3 || 3.3 V LDO for the USB side only&lt;br /&gt;
|-&lt;br /&gt;
| U8 || Würth 2521020222501 || WSEN-TIDS I²C board temperature sensor&lt;br /&gt;
|-&lt;br /&gt;
| X1 || Würth 830069392 || 16 MHz crystal — system clock&lt;br /&gt;
|-&lt;br /&gt;
| X2 || Würth 830062558 || 32.768 kHz crystal — ''not used by the firmware''&lt;br /&gt;
|-&lt;br /&gt;
| SW1 || Würth 428542320816 || WS-ROSV sealed IP67 rotary switch, 4-bit — ''not read by the firmware''&lt;br /&gt;
|-&lt;br /&gt;
| D2 || Würth 156120M173000 || RGB status LED&lt;br /&gt;
|-&lt;br /&gt;
| F1, F2 || 1210L010WR, 0ZCK0010FF2G || Resettable PTC fuses — logic rail and USB VBUS&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Connectors ===&lt;br /&gt;
&lt;br /&gt;
All field connectors are Molex Micro-Lock Plus, 1.25 mm pitch, top entry SMD.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Connector overview&lt;br /&gt;
|-&lt;br /&gt;
! Designator !! Part !! Positions !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| J3, J4 || 505568-0581 || 5 || [[CAN-bus]] in and out — daisy chain&lt;br /&gt;
|-&lt;br /&gt;
| J6 || 505568-1071 || 10 || To the [[Rudder]]. Contains Stepper, pump and inlet flowmeter&lt;br /&gt;
|-&lt;br /&gt;
| J2 || 505568-0581 || 5 || Outlet flowmeter&lt;br /&gt;
|-&lt;br /&gt;
| J5 || 505568-0471 || 4 || Steering angle sensor&lt;br /&gt;
|-&lt;br /&gt;
| J1 || Würth 490107670812 || 8 || Debug — SKEDD solderless connector&lt;br /&gt;
|-&lt;br /&gt;
| USB1 || USB4145-03-0070-C || USB-C || Serial console over USB&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== J3 and J4 — CAN-bus (5-pin) ====&lt;br /&gt;
&lt;br /&gt;
The two connectors are wired in parallel so the board sits in the middle of a daisy chain. All five signals, including the safety line, pass straight through.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal&lt;br /&gt;
|-&lt;br /&gt;
| 1 || Safety&lt;br /&gt;
|-&lt;br /&gt;
| 2 || 24 V&lt;br /&gt;
|-&lt;br /&gt;
| 3 || GND&lt;br /&gt;
|-&lt;br /&gt;
| 4 || CAN L&lt;br /&gt;
|-&lt;br /&gt;
| 5 || CAN H&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Note:''' this is the board's own pin order. It is '''not''' the same order as the 5-pin Binder connector on the [[CAN-bus]] cable — check the harness, do not assume the numbering carries across.&lt;br /&gt;
&lt;br /&gt;
The safety line is a pass-through between J3 and J4 only. It is not connected to the microcontroller, so this board neither reads nor breaks the safety circuit.&lt;br /&gt;
&lt;br /&gt;
==== J6 — stepper, pump and inlet flowmeter (10-pin) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 1 || 3.3 V || Flowmeter supply, through a 47 Ω series resistor&lt;br /&gt;
|-&lt;br /&gt;
| 2 || Inlet flowmeter NTC || 47 kΩ divider to 3.3 V&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Inlet flowmeter pulse || Hardware pulse counter&lt;br /&gt;
|-&lt;br /&gt;
| 4 || GND ||&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Stepper A1 || Coil A&lt;br /&gt;
|-&lt;br /&gt;
| 6 || Stepper A2 || Coil A&lt;br /&gt;
|-&lt;br /&gt;
| 7 || Stepper B1 || Coil B&lt;br /&gt;
|-&lt;br /&gt;
| 8 || Stepper B2 || Coil B&lt;br /&gt;
|-&lt;br /&gt;
| 9 || Pump + ||&lt;br /&gt;
|-&lt;br /&gt;
| 10 || Pump − ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The motor's wire colours are listed in the connection table on the [[Rudder]] page (red B+, black A+, blue B−, green A−). Which end of each coil goes to A1 versus A2 is not fixed by the schematic — check it against the motor datasheet before first power-up, because reversing a coil reverses the direction of travel and the homing direction is fixed in firmware.&lt;br /&gt;
&lt;br /&gt;
Both motor loads share this one connector, so check its per-contact current rating against the 1.0 A pump limit plus the stepper phase current before running both at full load.&lt;br /&gt;
&lt;br /&gt;
==== J2 — outlet flowmeter (5-pin) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal&lt;br /&gt;
|-&lt;br /&gt;
| 1 || 3.3 V, through a 47 Ω series resistor&lt;br /&gt;
|-&lt;br /&gt;
| 2 || Outlet flowmeter pulse&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Outlet flowmeter NTC&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Not connected&lt;br /&gt;
|-&lt;br /&gt;
| 5 || GND&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Not read by the current firmware.&lt;br /&gt;
&lt;br /&gt;
==== J5 — steering angle sensor (4-pin) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal&lt;br /&gt;
|-&lt;br /&gt;
| 1 || 3.3 V, through a 47 Ω series resistor&lt;br /&gt;
|-&lt;br /&gt;
| 2 || Potentiometer wiper — ADC input&lt;br /&gt;
|-&lt;br /&gt;
| 3 || Presence detect — pull to ground to signal &amp;quot;sensor connected&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 4 || GND, through a 47 Ω series resistor&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== J1 — debug ====&lt;br /&gt;
&lt;br /&gt;
Standard SKEDD SWD programming connector: the programmer's cable presses directly into the board, so no header has to be fitted or removed.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Signal !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| 1 || VTREF (3.3 V) ||&lt;br /&gt;
|-&lt;br /&gt;
| 2 || SWDIO ||&lt;br /&gt;
|-&lt;br /&gt;
| 3 || SWCLK ||&lt;br /&gt;
|-&lt;br /&gt;
| 4 || SWO ||&lt;br /&gt;
|-&lt;br /&gt;
| 5 || J-Link RX || Footprint only, not connected&lt;br /&gt;
|-&lt;br /&gt;
| 6 || J-Link TX || Footprint only, not connected&lt;br /&gt;
|-&lt;br /&gt;
| 7 || RESET ||&lt;br /&gt;
|-&lt;br /&gt;
| 8 || GND ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Microcontroller pin map ===&lt;br /&gt;
&lt;br /&gt;
Every assignment below was cross-checked against both the schematic and the firmware source.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ STM32L471RGT6 pin assignment&lt;br /&gt;
|-&lt;br /&gt;
! Pin !! Peripheral !! Net !! Function&lt;br /&gt;
|-&lt;br /&gt;
| PA0 || TIM2_CH1 || COUNT_FLOW_IN || Inlet flowmeter pulse input, hardware counter on rising edges&lt;br /&gt;
|-&lt;br /&gt;
| PA1 || ADC2 || NTC_FLOW_IN || Inlet flowmeter NTC&lt;br /&gt;
|-&lt;br /&gt;
| PA2 || TIM15_CH1 || COUNT_FLOW_OUT || Outlet flowmeter pulse input ''(unused by firmware)''&lt;br /&gt;
|-&lt;br /&gt;
| PA3 || ADC2 || NTC_FLOW_OUT || Used by firmware as the motor NTC ''(see [[#Known gaps]])''&lt;br /&gt;
|-&lt;br /&gt;
| PA4 || DAC1_OUT1 || SET_CURR || Pump driver VREF — sets the current limit&lt;br /&gt;
|-&lt;br /&gt;
| PA5 || GPIO out || SLEEP || Pump driver awake, held high&lt;br /&gt;
|-&lt;br /&gt;
| PA6 || GPIO out || ENABLE || Pump on/off, gated by the BMS discharge state&lt;br /&gt;
|-&lt;br /&gt;
| PA7 || GPIO out || DIR || Pump direction, held low (forward)&lt;br /&gt;
|-&lt;br /&gt;
| PA9 || USART1_TX || TX_UART1 || USB serial console ''(unused by firmware)''&lt;br /&gt;
|-&lt;br /&gt;
| PA10 || USART1_RX || RX_UART1 || USB serial console ''(unused by firmware)''&lt;br /&gt;
|-&lt;br /&gt;
| PA13 || SWDIO || SWDIO || Debug&lt;br /&gt;
|-&lt;br /&gt;
| PA14 || SWCLK || SWCLK || Debug&lt;br /&gt;
|-&lt;br /&gt;
| PB1 || ADC1 || POT_ANALOG_IN || Steering angle potentiometer&lt;br /&gt;
|-&lt;br /&gt;
| PB2 || GPIO in, pull-up || POT_FEEDBACK || Steering sensor presence detect — low means connected&lt;br /&gt;
|-&lt;br /&gt;
| PB3 || — || STEP_INDEX || TMC2209 INDEX ''(unused)''&lt;br /&gt;
|-&lt;br /&gt;
| PB4 || GPIO in || STEP_DIAG || TMC2209 DIAG — high on a StallGuard stall&lt;br /&gt;
|-&lt;br /&gt;
| PB5 || GPIO out || STEP_EN || TMC2209 ENABLE, active low&lt;br /&gt;
|-&lt;br /&gt;
| PB7 || GPIO out || CAN1_STBY || Transceiver standby, held low so the transceiver is active&lt;br /&gt;
|-&lt;br /&gt;
| PB8 || CAN1_RX || CAN1_RX || [[CAN-bus]]&lt;br /&gt;
|-&lt;br /&gt;
| PB9 || CAN1_TX || CAN1_TX || [[CAN-bus]]&lt;br /&gt;
|-&lt;br /&gt;
| PB10 || I2C2_SCL || I2C2_SCL || Board temperature sensor, 10 kΩ pull-up&lt;br /&gt;
|-&lt;br /&gt;
| PB11 || I2C2_SDA || I2C2_SDA || Board temperature sensor, 10 kΩ pull-up&lt;br /&gt;
|-&lt;br /&gt;
| PB12 || GPIO in || CAN_SETTING1 || Rotary switch, weight 1 ''(unused by firmware)''&lt;br /&gt;
|-&lt;br /&gt;
| PB13 || GPIO in || CAN_SETTING4 || Rotary switch, weight 8 ''(unused by firmware)''&lt;br /&gt;
|-&lt;br /&gt;
| PB14 || GPIO in || CAN_SETTING2 || Rotary switch, weight 2 ''(unused by firmware)''&lt;br /&gt;
|-&lt;br /&gt;
| PB15 || GPIO in || CAN_SETTING3 || Rotary switch, weight 4 ''(unused by firmware)''&lt;br /&gt;
|-&lt;br /&gt;
| PC1 || GPIO out || GREENn || Green LED — heartbeat, toggles once per second&lt;br /&gt;
|-&lt;br /&gt;
| PC2 || GPIO out || REDn || Red LED — the bootloader double-flashes it&lt;br /&gt;
|-&lt;br /&gt;
| PC3 || GPIO out || BLUEn || Blue LED&lt;br /&gt;
|-&lt;br /&gt;
| PC5 || GPIO in, pull-up || nFAULT || Pump driver fault, open drain, active low&lt;br /&gt;
|-&lt;br /&gt;
| PC10 || GPIO out || STEP_DIR || TMC2209 DIR&lt;br /&gt;
|-&lt;br /&gt;
| PC11 || GPIO out || STEP || TMC2209 STEP&lt;br /&gt;
|-&lt;br /&gt;
| PC12 || UART5_TX || TX_UART5 || TMC2209 single-wire UART, through 1 kΩ&lt;br /&gt;
|-&lt;br /&gt;
| PD2 || UART5_RX || RX_UART5 || TMC2209 single-wire UART&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
'''Watch the rotary switch bit order.''' The weights are not in pin order: PB12 is 1, PB14 is 2, PB15 is 4 and PB13 is 8. There are no external pull-ups on these lines — the schematic expects the microcontroller's internal pull-ups to be enabled, and the switch pulls each bit to ground.&lt;br /&gt;
&lt;br /&gt;
=== Clocking ===&lt;br /&gt;
&lt;br /&gt;
The board runs at 80 MHz derived from the 16 MHz crystal (×10 PLL, ÷2). The firmware starts the crystal itself with a bounded wait rather than letting the runtime spin on it forever, so a dead crystal produces a diagnosable degraded boot instead of a silent hang. If the crystal does not start, the firmware falls back to the internal 16 MHz oscillator, logs an error and keeps running — but '''CAN is unreliable in that state''', because the internal oscillator's ±1 % accuracy is not good enough for 1 Mbit/s. A board that intermittently drops off the bus is worth checking here first.&lt;br /&gt;
&lt;br /&gt;
The 32.768 kHz crystal X2 is in the design, but the firmware keeps the low-speed oscillator switched off: boards were found to hang waiting for it to start, and nothing in the firmware uses the RTC. The &amp;lt;code&amp;gt;embassy&amp;lt;/code&amp;gt; 32.768 kHz time base is unrelated and is derived from a general-purpose timer.&lt;br /&gt;
&lt;br /&gt;
The ADCs are clocked from the system clock.&lt;br /&gt;
&lt;br /&gt;
== CAN messages ==&lt;br /&gt;
&lt;br /&gt;
The board joins the bus at 1 Mbit/s using 11-bit standard identifiers and an '''accept-all''' hardware filter, so it sees every frame on the bus and ignores what does not concern it. All multi-byte fields are little-endian.&lt;br /&gt;
&lt;br /&gt;
=== Transmitted by the rudder controller ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the rudder controller sends&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Rate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x020&amp;lt;/code&amp;gt; || ServoRudderStatus || 6 || every 100 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x032&amp;lt;/code&amp;gt; || Bootloader response || 2–8 || on request only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x211&amp;lt;/code&amp;gt; || TemperatureRudderController || 2 || every 1 s&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x212&amp;lt;/code&amp;gt; || CoolingPumpStatus || 1 || every 1 s&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x213&amp;lt;/code&amp;gt; || SteeringAngle || 5 || every 100 ms&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x215&amp;lt;/code&amp;gt; || FlowSensorIn || 8 || every 1 s&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x217&amp;lt;/code&amp;gt; || MotorTemperature || 4 || every 1 s&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x218&amp;lt;/code&amp;gt; || SteeringAngleCalibrationAck || 6 || once per accepted calibration command&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x216&amp;lt;/code&amp;gt; (FlowSensorOut) is allocated to this board but not transmitted by the current firmware.&lt;br /&gt;
&lt;br /&gt;
=== Received by the rudder controller ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Frames the rudder controller acts on&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Message !! DLC !! Effect&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x010&amp;lt;/code&amp;gt; || ServoRudderSetpoint || 2 || Commands the back-foil position and feeds the 2 s watchdog&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x021&amp;lt;/code&amp;gt; || ServoRudderCommand || 1 || &amp;lt;code&amp;gt;0x00&amp;lt;/code&amp;gt; = Initialize, starts homing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt; || Bootloader discovery || 1 || Answers with state and version on &amp;lt;code&amp;gt;0x032&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x031&amp;lt;/code&amp;gt; || Bootloader command || 1 || Addressed to this board only&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x033&amp;lt;/code&amp;gt; || Bootloader write data || 8 || Firmware payload during an update&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x107&amp;lt;/code&amp;gt; || BMS TemperaturesAndStates || 8 || Byte 7, the discharge state, gates the cooling pump&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x214&amp;lt;/code&amp;gt; || SteeringAngleCalibration || 1 || Captures a calibration reference point&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Every other frame on the bus is ignored.&lt;br /&gt;
&lt;br /&gt;
=== Message layouts ===&lt;br /&gt;
&lt;br /&gt;
==== 0x010 — ServoRudderSetpoint (received) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Setpoint || u16 LE || 1000–2000. Values outside this range are rejected, and a rejected setpoint does '''not''' feed the communication watchdog.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== 0x020 — ServoRudderStatus (sent, 10 Hz) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || State || u8 enum || 0 = Uninitialized, 1 = Operational, 2 = Homing, 3 = FailSafe, 4 = Fault&lt;br /&gt;
|-&lt;br /&gt;
| 1–2 || Current setpoint || u16 LE || 1000–2000&lt;br /&gt;
|-&lt;br /&gt;
| 3–4 || Actual position || u16 LE || 1000–2000, in setpoint units&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Fault cause || u8 enum || 0 = None, 1 = StallDuringMove, 2 = HomingTimeout, 3 = DriverNoUartResponse, 4 = DriverError&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== 0x021 — ServoRudderCommand (received) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || Command || u8 enum || 0 = Initialize. Starts homing from '''any''' state, and is the only way out of FailSafe or Fault.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== 0x211 — TemperatureRudderController (sent, 1 Hz) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Board temperature || i16 LE || Hundredths of a degree Celsius. 2500 = 25.00 °C.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== 0x212 — CoolingPumpStatus (sent, 1 Hz) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || Driver fault input level || u8 || Raw pin level: 0 = fault asserted, 1 = healthy&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This reports the driver's fault line only. It does '''not''' say whether the pump is currently enabled — that follows from the BMS discharge state on &amp;lt;code&amp;gt;0x107&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== 0x213 — SteeringAngle (sent, 10 Hz) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Position || i16 LE || −1000 full left to +1000 full right, 0 = centre. Held at 0 whenever the calibration is not valid.&lt;br /&gt;
|-&lt;br /&gt;
| 2–3 || Averaged raw ADC code || u16 LE || 0–4095, 12-bit&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Status bits || u8 || See below&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Steering status bits — byte 4 of 0x213, byte 5 of 0x218&lt;br /&gt;
|-&lt;br /&gt;
! Bit !! Name !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 0 || CalValid || Calibration present and plausible; the reported position is meaningful&lt;br /&gt;
|-&lt;br /&gt;
| 1 || CalMissing || Nothing stored yet, or every stored record is corrupt&lt;br /&gt;
|-&lt;br /&gt;
| 2 || CalInvalid || Stored but incomplete or implausible&lt;br /&gt;
|-&lt;br /&gt;
| 3 || OutOfRange || Raw reading is outside the calibrated travel; position is clamped&lt;br /&gt;
|-&lt;br /&gt;
| 4 || StorageError || The last write to persistent storage failed; sticky until one succeeds&lt;br /&gt;
|-&lt;br /&gt;
| 5 || NotConnected || Presence pin reads high, so no sensor is plugged in&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Bits 0, 1 and 2 are mutually exclusive. Bit 5 is independent of the others: a stored calibration stays valid while the sensor is unplugged.&lt;br /&gt;
&lt;br /&gt;
==== 0x214 — SteeringAngleCalibration (received) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || Command || u8 enum || 1 = CaptureLeft, 2 = CaptureCenter, 3 = CaptureRight, 4 = Clear&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Remaining bytes are ignored. No calibration values travel over the bus — the board captures whatever the sensor reads at the moment the command arrives.&lt;br /&gt;
&lt;br /&gt;
==== 0x215 — FlowSensorIn (sent, 1 Hz) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Flow rate || u16 LE || mL/min&lt;br /&gt;
|-&lt;br /&gt;
| 2–3 || Water temperature || i16 LE || Hundredths of a degree Celsius. −32768 means the NTC reads open or shorted.&lt;br /&gt;
|-&lt;br /&gt;
| 4–5 || Raw pulses || u16 LE || Pulses counted in the last one-second window&lt;br /&gt;
|-&lt;br /&gt;
| 6–7 || Raw ADC code || u16 LE || 0–4095, 12-bit NTC reading&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== 0x217 — MotorTemperature (sent, 1 Hz) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0–1 || Motor temperature || i16 LE || Hundredths of a degree Celsius. −32768 means the NTC reads open or shorted. '''See [[#Known gaps]].'''&lt;br /&gt;
|-&lt;br /&gt;
| 2–3 || Raw ADC code || u16 LE || 0–4095, 12-bit&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== 0x218 — SteeringAngleCalibrationAck (sent) ====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 0 || Echoed command || u8 || As received on &amp;lt;code&amp;gt;0x214&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 1 || Result || u8 enum || 0 = ok, 1 = storage error, 2 = stored but the set is not yet usable&lt;br /&gt;
|-&lt;br /&gt;
| 2–3 || Captured raw ADC code || u16 LE || 0 for Clear&lt;br /&gt;
|-&lt;br /&gt;
| 4 || Captured endpoints || u8 bits || 0x01 = left, 0x02 = centre, 0x04 = right&lt;br /&gt;
|-&lt;br /&gt;
| 5 || Status bits || u8 || Same encoding as byte 4 of &amp;lt;code&amp;gt;0x213&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== 0x107 — BMS TemperaturesAndStates (received) ====&lt;br /&gt;
&lt;br /&gt;
Only byte 7 is used by this board.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Byte !! Field !! Type !! Values&lt;br /&gt;
|-&lt;br /&gt;
| 7 || Discharge state || u8 enum || 0 = Init, 1 = Idle, 2 = PreChargeOn, '''3 = On''', 4 = PreChargeTimeout, 5 = Error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The cooling pump is enabled only while this byte reads 3.&lt;br /&gt;
&lt;br /&gt;
== Back-foil servo behaviour ==&lt;br /&gt;
&lt;br /&gt;
=== State machine ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Servo states&lt;br /&gt;
|-&lt;br /&gt;
! State !! Meaning !! Setpoints !! Motor&lt;br /&gt;
|-&lt;br /&gt;
| 0 Uninitialized || Boot state. No absolute position known. || Ignored || Driver disabled, shaft free&lt;br /&gt;
|-&lt;br /&gt;
| 1 Operational || Homed and following setpoints. || Followed || Energized, hold current at standstill&lt;br /&gt;
|-&lt;br /&gt;
| 2 Homing || Running the homing sequence. || Ignored; the latest is picked up on entering Operational || Energized, reduced current&lt;br /&gt;
|-&lt;br /&gt;
| 3 FailSafe || Setpoint watchdog expired; parked at setpoint 1000. || Ignored, latched || Energized, holding&lt;br /&gt;
|-&lt;br /&gt;
| 4 Fault || See the fault causes below. || Ignored, latched || Holding if the recovery re-home succeeded, disabled otherwise&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Transitions:&lt;br /&gt;
&lt;br /&gt;
* '''Initialize''' on &amp;lt;code&amp;gt;0x021&amp;lt;/code&amp;gt; from any state moves to Homing. This is the only way out of FailSafe and Fault — recovery is a deliberate operator action, never automatic.&lt;br /&gt;
* '''Homing success''' moves to Operational and the watchdog starts immediately. If no setpoint arrives within 2 seconds the servo parks in FailSafe.&lt;br /&gt;
* '''Homing failure''' moves to Fault with the driver disabled.&lt;br /&gt;
* '''Watchdog''': in Operational, every valid setpoint re-arms a 2-second timer. On expiry the servo moves to setpoint 1000 and latches FailSafe.&lt;br /&gt;
* '''Stall while moving''': if DIAG trips during a move the step count can no longer be trusted, so the servo latches Fault (StallDuringMove) and immediately re-runs homing to park, holding, at the failsafe position. It stays in Fault until Initialize.&lt;br /&gt;
&lt;br /&gt;
=== Homing sequence ===&lt;br /&gt;
&lt;br /&gt;
# Read the driver's &amp;lt;code&amp;gt;IFCNT&amp;lt;/code&amp;gt; register over UART, three attempts. No response gives Fault (DriverNoUartResponse) with the driver left disabled.&lt;br /&gt;
# Write the five configuration registers, then read &amp;lt;code&amp;gt;IFCNT&amp;lt;/code&amp;gt; again and require it to have advanced by exactly 5. A mismatch gives Fault (DriverError) — this catches a driver that answers but is not actually accepting writes.&lt;br /&gt;
# Enable the driver and step at constant speed toward the home stop until DIAG trips. The StallGuard load value is logged about every 100 ms for threshold tuning.&lt;br /&gt;
# If 1.2× the full travel is stepped without a stall, give up with Fault (HomingTimeout).&lt;br /&gt;
# Back off 200 microsteps from the stop. That position is defined as position 0, which is setpoint 1000. Switch to the normal run current and go Operational.&lt;br /&gt;
&lt;br /&gt;
=== Fault causes ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Fault cause, byte 5 of 0x020&lt;br /&gt;
|-&lt;br /&gt;
! Value !! Cause !! What to check&lt;br /&gt;
|-&lt;br /&gt;
| 0 || None || —&lt;br /&gt;
|-&lt;br /&gt;
| 1 || StallDuringMove || Mechanism jammed, or the StallGuard threshold is too sensitive. The servo re-homed itself and holds at 1000. Clear the jam, then send Initialize.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || HomingTimeout || No stall found: motor unplugged, driver unpowered, threshold too insensitive, or the homing direction inverted.&lt;br /&gt;
|-&lt;br /&gt;
| 3 || DriverNoUartResponse || TMC2209 not answering: no driver supply, UART wiring, or wrong slave address.&lt;br /&gt;
|-&lt;br /&gt;
| 4 || DriverError || UART works but register writes did not stick.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Tuning ===&lt;br /&gt;
&lt;br /&gt;
The tuning constants sit in one block at the top of &amp;lt;code&amp;gt;firmware/app/src/servo_rudder.rs&amp;lt;/code&amp;gt;, with a bring-up checklist in &amp;lt;code&amp;gt;firmware/docs/rudder-servo.md&amp;lt;/code&amp;gt;. The values that must be calibrated against the real mechanics before the servo is trusted are the full travel in microsteps, the StallGuard threshold, and the direction level that moves toward the home stop.&lt;br /&gt;
&lt;br /&gt;
A quick bench sweep:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cansend can0 021#00      # Initialize (home)&lt;br /&gt;
cansend can0 010#E803    # setpoint 1000&lt;br /&gt;
cansend can0 010#D007    # setpoint 2000&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Steering angle calibration ==&lt;br /&gt;
&lt;br /&gt;
=== Procedure ===&lt;br /&gt;
&lt;br /&gt;
Calibration is captured live — no values are sent over the bus. Move the steering to a reference position, then send the matching command on &amp;lt;code&amp;gt;0x214&amp;lt;/code&amp;gt;. The board averages readings over 200 ms and stores the result immediately, so a session can be interrupted and resumed.&lt;br /&gt;
&lt;br /&gt;
# Steer fully left, send &amp;lt;code&amp;gt;0x01&amp;lt;/code&amp;gt;&lt;br /&gt;
# Steer to centre, send &amp;lt;code&amp;gt;0x02&amp;lt;/code&amp;gt;&lt;br /&gt;
# Steer fully right, send &amp;lt;code&amp;gt;0x03&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Order does not matter. Hold the steering still for the 200 ms capture. The calibration becomes active on the next sample once all three points are captured and plausible. &amp;lt;code&amp;gt;0x04&amp;lt;/code&amp;gt; clears it and returns the board to the uncalibrated safe state.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cansend can0 214#01      # capture full left&lt;br /&gt;
cansend can0 214#02      # capture centre&lt;br /&gt;
cansend can0 214#03      # capture full right&lt;br /&gt;
cansend can0 214#04      # clear&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A calibration is '''rejected''', and the reported position forced to 0, when any of the three points is missing, when a raw code exceeds 4095, when the center does not lie strictly between the two endpoints, or when either half of the travel is narrower than 100 ADC codes. Both wiring polarities are accepted — full left may read either above or below full right.&lt;br /&gt;
&lt;br /&gt;
=== Where it is stored ===&lt;br /&gt;
&lt;br /&gt;
The STM32L471 has no data EEPROM, so a dedicated 4 KB block at the very end of flash emulates one. It sits outside the application partition, so a firmware update over CAN leaves the calibration intact, and it is in the other flash bank from the executing code, so writing it never stalls the processor. The block is an append-only log of 256 records, each with a magic number, version and CRC. The newest record that validates wins on load; when the log fills, the block is erased and writing restarts.&lt;br /&gt;
&lt;br /&gt;
=== Noise rejection ===&lt;br /&gt;
&lt;br /&gt;
The potentiometer signal is electrically noisy — the stepper driver and the pump are both close by. Two layers of averaging handle it, because either alone leaves a gap:&lt;br /&gt;
&lt;br /&gt;
* The ADC averages 16 conversions per read in hardware. That suppresses white noise by 4×, but the burst spans only about 52 µs, so it does nothing for interference slower than roughly 20 kHz.&lt;br /&gt;
* Twenty reads are spread evenly across each 100 ms reporting window and box-averaged. Averaging over exactly one window puts nulls at 10 Hz and every harmonic, which is what rejects the periodic interference from the stepper and pump.&lt;br /&gt;
&lt;br /&gt;
Together that is 320 conversions per report for about 1 % ADC duty cycle and roughly 18× white-noise rejection, at the cost of 50 ms of group delay. Calibration captures average 40 reads over 200 ms, so an endpoint is never set by a single noisy moment.&lt;br /&gt;
&lt;br /&gt;
== Firmware update over CAN ==&lt;br /&gt;
&lt;br /&gt;
The first 80 KB of flash holds a CAN bootloader, so the board can be updated in place without opening the compartment or attaching a debug probe. The bootloader validates the application on boot (magic number plus CRC32) and auto-boots it two seconds after the last command addressed to this board.&lt;br /&gt;
&lt;br /&gt;
=== Flash layout ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Region    Address Range               Size   Description&lt;br /&gt;
--------  --------------------------  -----  --------------------------&lt;br /&gt;
BOOT      0x08000000 - 0x08013FFF     80K    Bootloader&lt;br /&gt;
HEADER    0x08014000 - 0x080147FF     2K     Application metadata header&lt;br /&gt;
APP       0x08014800 - 0x080FEFFF     938K   Application firmware&lt;br /&gt;
CONFIG    0x080FF000 - 0x080FFFFF     4K     Emulated EEPROM (steering calibration)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Addressing ===&lt;br /&gt;
&lt;br /&gt;
Because several boards share the bus, each board type owns its own block of three identifiers derived from its application type, and the bootloader's hardware filter rejects the other blocks outright. This is what stops an erase command aimed at one board from wiping the others.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Bootloader identifier allocation&lt;br /&gt;
|-&lt;br /&gt;
! Board !! App type !! Command !! Response !! Write data&lt;br /&gt;
|-&lt;br /&gt;
| '''Rudder controller''' || 0x01 || &amp;lt;code&amp;gt;0x031&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;0x032&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;0x033&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Height-sensor controller || 0x02 || &amp;lt;code&amp;gt;0x034&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;0x035&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;0x036&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Dashboard || 0x03 || &amp;lt;code&amp;gt;0x037&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;0x038&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;0x039&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;0x030&amp;lt;/code&amp;gt; is a discovery broadcast. The host sends a state query on it and every board answers on its '''own''' response identifier, which is how the bus is enumerated. Answering discovery does not extend a board's auto-boot window, so repeated scanning cannot pin boards in the bootloader.&lt;br /&gt;
&lt;br /&gt;
A running application answers state and version queries too, and rejects everything destructive. That is how the host tool tells a bootloader from a running application on one identifier, and why updating a board reboots it first rather than erasing under a live application.&lt;br /&gt;
&lt;br /&gt;
=== Using the flash tool ===&lt;br /&gt;
&lt;br /&gt;
Set up the interface on the host once:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo ip link set can0 type can bitrate 1000000&lt;br /&gt;
sudo ip link set can0 up&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
eoi-flash-tool scan                                  # what is on the bus?&lt;br /&gt;
eoi-flash-tool flash rudder-controller               # target comes from the ELF&lt;br /&gt;
eoi-flash-tool --board rudder-controller version     # which build is on it?&lt;br /&gt;
eoi-flash-tool --board rudder-controller reboot      # reset just this board&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;flash&amp;lt;/code&amp;gt; needs no board argument: the target is read from the firmware image itself, so pointing the tool at a file can only ever address the board that file was built for. The other boards keep running untouched.&lt;br /&gt;
&lt;br /&gt;
The bootloader itself can only be replaced over SWD, so it routinely sits several commits behind the application it boots. The &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt; command reports which of the two answered.&lt;br /&gt;
&lt;br /&gt;
== Diagnostics ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Reading the board without a debugger&lt;br /&gt;
|-&lt;br /&gt;
! Symptom !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| Green LED toggling once per second || Application running normally&lt;br /&gt;
|-&lt;br /&gt;
| Red LED double-flashing || Sitting in the bootloader, waiting or flashing&lt;br /&gt;
|-&lt;br /&gt;
| No LED activity || No power, or a boot hang&lt;br /&gt;
|-&lt;br /&gt;
| On the bus but dropping frames || Suspect the 16 MHz crystal; the firmware falls back to the internal oscillator, which is not accurate enough for 1 Mbit/s&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x020&amp;lt;/code&amp;gt; state stuck at 0 || The servo has never been homed since power-up. Send Initialize.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x020&amp;lt;/code&amp;gt; state 3 (FailSafe) || No setpoint arrived for 2 seconds. Whatever commands the foil has stopped talking.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x213&amp;lt;/code&amp;gt; position stuck at 0 || Steering calibration missing or invalid; read the status byte&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x213&amp;lt;/code&amp;gt; status bit 5 set || Steering sensor not plugged in&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x212&amp;lt;/code&amp;gt; byte 0 reads 0 || Pump driver is reporting a fault&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A 4-second independent watchdog runs in hardware and is petted once per second by the heartbeat task, so wedged firmware resets itself rather than sitting silent on the bus. Detailed logging is available over SWD through &amp;lt;code&amp;gt;defmt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Known gaps ==&lt;br /&gt;
&lt;br /&gt;
* '''Outlet flowmeter is not read.''' &amp;lt;code&amp;gt;0x216&amp;lt;/code&amp;gt; is allocated and J2 is fitted, but its NTC line shares a pin with the motor NTC. Resolving this needs a board change, not a firmware change.&lt;br /&gt;
* '''Servo tuning is not finished.''' The full travel in microsteps, the StallGuard threshold and the homing direction are all still marked for calibration against the real mechanics in the firmware. Until they are measured, homing behaviour on the real mechanism is not guaranteed.&lt;br /&gt;
* '''The rotary switch is not read.''' A sealed 4-bit hex switch is fitted and wired to PB12–PB15, but no firmware uses it. It was presumably intended for a node address or a variant selection.&lt;br /&gt;
* '''The USB console is not used.''' The USB-C port and its FT234XD bridge are wired to USART1 (PA9/PA10), but the firmware logs over SWD instead. The port currently only supplies its own 3.3 V rail.&lt;br /&gt;
* '''The bus-wide reference is out of date for this board.''' [https://github.com/Engineers-of-Innovation/eoi-can/blob/main/CAN_MESSAGES.md CAN_MESSAGES.md] still describes &amp;lt;code&amp;gt;0x213&amp;lt;/code&amp;gt; as an angle in degrees over 4 bytes and &amp;lt;code&amp;gt;0x214&amp;lt;/code&amp;gt; as reserved. The firmware sends a normalized ±1000 position over 5 bytes with a status byte, and the calibration protocol is implemented. This page follows the firmware.&lt;br /&gt;
* '''Motor specification mismatch.''' The [[Rudder]] page lists the trim actuator as an 11HS12-0674S-PG5 (5:1); the firmware is tuned for an 11HS12-0674D-PG14 (13.73:1). One of the two is wrong, and it changes the travel and speed figures.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Rudder]] — the propulsion unit this board lives in&lt;br /&gt;
* [[CAN-bus]] — bus wiring, cable pinout and the bus-wide identifier map&lt;br /&gt;
* [[Battery]] — source of the discharge state that gates the cooling pump&lt;br /&gt;
* [[Foils]] — what the back-foil trim actuator is for&lt;br /&gt;
* [[Instrumentation Panel]] — where these measurements are displayed&lt;br /&gt;
* [[Datalogger]] — where they are recorded&lt;br /&gt;
* [[Altium PCB]] — house PCB design conventions&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Kip_Halen&amp;diff=311</id>
		<title>Kip Halen</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Kip_Halen&amp;diff=311"/>
		<updated>2026-07-04T10:11:33Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Jij bent uitgekozen om de lunch te verzorgen? LEUK! Wij hanteren een lang gewortelde traditie dat er tijdens de klusdag op zaterdag kippeling wordt gegeten. Omdat dit in het bloed (Quinten) van engineers of innovation zit, moet je het halen van de kip niet als kuttaakje zien, maar juist als een grote eer.&lt;br /&gt;
&lt;br /&gt;
==De Heilige Formule==&lt;br /&gt;
&lt;br /&gt;
Zoals geschreven hanteren wij een ratio van 100g kip ''per broodje'' en 3 broodjes per persoon&lt;br /&gt;
&lt;br /&gt;
==Declareren==&lt;br /&gt;
&lt;br /&gt;
Praat met Floris&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Kip_Halen&amp;diff=310</id>
		<title>Kip Halen</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Kip_Halen&amp;diff=310"/>
		<updated>2026-07-04T10:09:36Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Created page with &amp;quot;Jij bent uitgekozen om de lunch te verzorgen? LEUK! Wij hanteren een lang gewortelde traditie dat er tijdens de klusdag op zaterdag koppeling wordt gegeten. Omdat dit in het bloed (Quinten) van engineers of innovation zit, moet je het halen van de kip niet als kuttaakje zien, maar juist als een grote eer.  ==De Heilige Formule==  Zoals geschreven hanteren wij een ratio van 100g kip _per broodje_ en 3 broodjes per persoon  ==Declareren==  Praat met Floris&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Jij bent uitgekozen om de lunch te verzorgen? LEUK! Wij hanteren een lang gewortelde traditie dat er tijdens de klusdag op zaterdag koppeling wordt gegeten. Omdat dit in het bloed (Quinten) van engineers of innovation zit, moet je het halen van de kip niet als kuttaakje zien, maar juist als een grote eer.&lt;br /&gt;
&lt;br /&gt;
==De Heilige Formule==&lt;br /&gt;
&lt;br /&gt;
Zoals geschreven hanteren wij een ratio van 100g kip _per broodje_ en 3 broodjes per persoon&lt;br /&gt;
&lt;br /&gt;
==Declareren==&lt;br /&gt;
&lt;br /&gt;
Praat met Floris&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=CAN-bus&amp;diff=309</id>
		<title>CAN-bus</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=CAN-bus&amp;diff=309"/>
		<updated>2026-06-23T09:41:29Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The communication between the electronics within the [[Solar boat (boii)|solar boat]] is build upon the CAN-bus protocol, where the [[datalogger]] logs the CAN messages. We use 5 pin IP67, [https://www.binder-connector.com/nl/producten/miniature-connectors/snap-in-ip67-1/99-9115-00-05-snap-in-ip67-flensstekker-aantal-polen-5-onafgeschermd-soldeer-ip67-vde Male] and [https://www.binder-connector.com/nl/producten/miniature-connectors/snap-in-ip67-1/99-9116-00-05-snap-in-ip67-flensdoos-aantal-polen-5-onafgeschermd-soldeer-ip67-vde Female] connectors, with the following pinout: &lt;br /&gt;
&lt;br /&gt;
== CAN-bus pinout ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Colours of the Standard Binder CAN Cable and its pinout&lt;br /&gt;
|-&lt;br /&gt;
! Binder Connector pin Number !! Wire Colour !! Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1 ||style=&amp;quot;background: #83603A;&amp;quot;| Brown || 24v Power&lt;br /&gt;
|-&lt;br /&gt;
| 2 ||style=&amp;quot;background: #FFFFFF;&amp;quot;| White || CAN H&lt;br /&gt;
|-&lt;br /&gt;
| 3 ||style=&amp;quot;background: #0065B2;&amp;quot;| Blue  || CAN L&lt;br /&gt;
|-&lt;br /&gt;
| 4 ||style=&amp;quot;background: #241F21;color:white;&amp;quot;| Black || Ground&lt;br /&gt;
|-&lt;br /&gt;
| 5 ||style=&amp;quot;background: #B2B3B7;&amp;quot;| Gray  || Safety&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== CAN-bus ID overview ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;markdown&amp;quot;&amp;gt;&lt;br /&gt;
0x100 EoI Battery start&lt;br /&gt;
0x10F EoI Battery end&lt;br /&gt;
&lt;br /&gt;
0x200 EoI GNNS info, u8 fix (bool); u8 sats; u8 sats used &lt;br /&gt;
0x201 EoI GNNS f32 speed, kmh; f32 direction, degrees&lt;br /&gt;
0x202 EoI GNNS f64 Latitude, degrees&lt;br /&gt;
0x203 EoI GNNS f64 Longitude, degrees&lt;br /&gt;
0x204 EoI GNNS date time, u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second&lt;br /&gt;
&lt;br /&gt;
0x400 Gan MPPT R0 : Vin, Iin,   Vout,   Iout&lt;br /&gt;
0x401 Gan MPPT R0 : Mode,   Fault,   Enabled,   TBoard,   THS&lt;br /&gt;
0x410 Gan MPPT R1 &lt;br /&gt;
0x411 Gan MPPT R1 &lt;br /&gt;
...&lt;br /&gt;
0x470 Gan MPPT R7&lt;br /&gt;
0x471 Gan MPPT R7&lt;br /&gt;
0x480 Gan MPPT F0&lt;br /&gt;
0x481 Gan MPPT F0&lt;br /&gt;
...&lt;br /&gt;
0x4F0 Gan MPPT F7&lt;br /&gt;
0x4F1 Gan MPPT F7&lt;br /&gt;
  &lt;br /&gt;
0x4E6 Gan MPPT Baseboard Front&lt;br /&gt;
0x4EE Gan MPPT Baseboard Rear&lt;br /&gt;
&lt;br /&gt;
0x700 MPPT start&lt;br /&gt;
0x77F MPPT end&lt;br /&gt;
&lt;br /&gt;
0x0900 THROTTLE To VESC&lt;br /&gt;
0x1337 THROTTLE Status&lt;br /&gt;
&lt;br /&gt;
0x0909 VESC Status message 1&lt;br /&gt;
0x0E09 VESC Status message 2&lt;br /&gt;
0x0F09 VESC Status message 3&lt;br /&gt;
0x1009 VESC Status message 4&lt;br /&gt;
0x1B09 VESC Status message 5&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Battery (MG) CAN-format (No longer used) ==&lt;br /&gt;
As of now the MG electronics [[battery]] is used in the [[Solar boat (boii)|solar boat]]. Although a new battery is being developed, this MG battery is still in use (as of September '22). Therefore the battery format is given here below.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Node-ID&lt;br /&gt;
!Index&lt;br /&gt;
!Subindex&lt;br /&gt;
!Data&lt;br /&gt;
!Type&lt;br /&gt;
!Resolution&lt;br /&gt;
|-&lt;br /&gt;
|0x302&lt;br /&gt;
|0x2005&lt;br /&gt;
|0x01&lt;br /&gt;
|Voltage &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x02&lt;br /&gt;
|Current &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x03&lt;br /&gt;
|Current Discharge &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x04&lt;br /&gt;
|Current Charge &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x05&lt;br /&gt;
|State-of-Charge &amp;lt;code&amp;gt;[%]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[%/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x06&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x07&lt;br /&gt;
|Time to Go &amp;lt;code&amp;gt;[min]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[min/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x402&lt;br /&gt;
|0x2005&lt;br /&gt;
|0x09&lt;br /&gt;
|Cell Temperature High &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0A&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0B&lt;br /&gt;
|Cell Temperature Low &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0C&lt;br /&gt;
|Cell Voltage High &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0D&lt;br /&gt;
|Cell Voltage Low &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0E&lt;br /&gt;
|BMS State&lt;br /&gt;
|uint32_t&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0F&lt;br /&gt;
|Temperature Collection &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|4x uint8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x482&lt;br /&gt;
|0x2000&lt;br /&gt;
|Cell nr&lt;br /&gt;
|Cell &amp;lt;code&amp;gt;[Cell_nr]&amp;lt;/code&amp;gt; Voltage &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x202&lt;br /&gt;
|Don't Care&lt;br /&gt;
|Don't Care&lt;br /&gt;
|Power Level &amp;lt;code&amp;gt;[%]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[%/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Autopilot&amp;diff=308</id>
		<title>Autopilot</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Autopilot&amp;diff=308"/>
		<updated>2026-06-16T10:37:21Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* CAN Bus Format */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Platform ==&lt;br /&gt;
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 [https://docs.cubepilot.org/user-guides/autopilot/the-cube-module-overview orange cube].&lt;br /&gt;
[[File:Ardupilot.jpg|thumb|Ardupilot PCB with the orange cube mounted.]]&lt;br /&gt;
To make use for our application a PCB has been designed to integrate into the hydrofoil design.&lt;br /&gt;
&lt;br /&gt;
To connect to the ardupilot on the cube for tweaking settings and tuning control loops, [https://ardupilot.org/planner/docs/mission-planner-overview.html Mission Planner] is used. The control logic itself runs as a Lua script on the cube (see [[#Lua control script|Lua control script]]). The following sections describe the tweaked settings and the on-board script.&lt;br /&gt;
&lt;br /&gt;
== Hardware ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
== Height Sensors ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each sensor broadcasts a 3-byte frame at 8&amp;amp;nbsp;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|CAN Bus Format]]).&lt;br /&gt;
&lt;br /&gt;
The sensors are mounted as far forward and outboard as possible to stay clear of the bow wake (CAD: 3.8&amp;amp;nbsp;m forward, 0.23&amp;amp;nbsp;m left/right, 0.175&amp;amp;nbsp;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]]).&lt;br /&gt;
&lt;br /&gt;
== Lua control script ==&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
The rear foil is driven over CAN: the script reads ArduPilot's elevator output and sends it as a servo command (ID 0x010).&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
On boot the script enforces the parameters it needs (see the [[#Ardupilot settings|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|CAN Bus Format]]).&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Ardupilot settings ==&lt;br /&gt;
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 &amp;lt;code&amp;gt;SCR_ENABLE&amp;lt;/code&amp;gt; and the physical sensor mounting offsets (&amp;lt;code&amp;gt;RNGFNDx_POS_*&amp;lt;/code&amp;gt;) must be set/measured manually. There are more ports available on the ardupilot; whenever more are used or changed the table will be updated.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Configuration of ardupilot&lt;br /&gt;
!Setting&lt;br /&gt;
!Value&lt;br /&gt;
!Description&lt;br /&gt;
!Reason&lt;br /&gt;
|-&lt;br /&gt;
|SCR_ENABLE&lt;br /&gt;
|1&lt;br /&gt;
|Enable Lua scripting&lt;br /&gt;
|Required to run the control script; must be set manually before the script can run.&lt;br /&gt;
|-&lt;br /&gt;
|INS_GYR_CAL&lt;br /&gt;
|0&lt;br /&gt;
|Controls the automatic gyro calibration.&lt;br /&gt;
|Gyro calibration requires that the unit be held as still as possible.&lt;br /&gt;
|-&lt;br /&gt;
|INITIAL_MODE&lt;br /&gt;
|5&lt;br /&gt;
|FBWA&lt;br /&gt;
|Fly By Wire A, the attitude-stabilisation mode the inner loop uses. More [https://ardupilot.org/plane/docs/fbwa-mode.html here].&lt;br /&gt;
|-&lt;br /&gt;
|SERVO1_FUNCTION&lt;br /&gt;
|77&lt;br /&gt;
|ElevonLeft&lt;br /&gt;
|Controls the left front foil assembly (wired to IO-CH1).&lt;br /&gt;
|-&lt;br /&gt;
|SERVO2_FUNCTION&lt;br /&gt;
|78&lt;br /&gt;
|ElevonRight&lt;br /&gt;
|Controls the right front foil assembly (wired to IO-CH2).&lt;br /&gt;
|-&lt;br /&gt;
|SERVO3_FUNCTION&lt;br /&gt;
|19&lt;br /&gt;
|Elevator&lt;br /&gt;
|Rear foil pitch command; the script reads this output and forwards it to the rear foil over CAN.&lt;br /&gt;
|-&lt;br /&gt;
|SERVO14_FUNCTION&lt;br /&gt;
|-1&lt;br /&gt;
|GPIO&lt;br /&gt;
|Frees FMU_CH6 so it can be read as the foil enable input.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_ENABLE&lt;br /&gt;
|1&lt;br /&gt;
|Enable button (digital pin) function&lt;br /&gt;
|Enables reading the foil enable/disable input.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_PIN1&lt;br /&gt;
|55&lt;br /&gt;
|GPIO pin 55 (FMU_CH6)&lt;br /&gt;
|Maps the enable input to FMU_CH6; active-low and fail-safe.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_REPORT_SEND&lt;br /&gt;
|2&lt;br /&gt;
|Report button state over MAVLink&lt;br /&gt;
|Surfaces the enable state to the GCS.&lt;br /&gt;
|-&lt;br /&gt;
|CAN_P1_DRIVER&lt;br /&gt;
|1&lt;br /&gt;
|Enable CAN port 1&lt;br /&gt;
|CAN carries the height sensors, the rear foil command and telemetry.&lt;br /&gt;
|-&lt;br /&gt;
|CAN_D1_PROTOCOL&lt;br /&gt;
|10&lt;br /&gt;
|Scripting&lt;br /&gt;
|Lets the Lua script send/receive raw CAN frames on bus 1.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1_TYPE / RNGFND2_TYPE&lt;br /&gt;
|36&lt;br /&gt;
|Lua scripting rangefinder&lt;br /&gt;
|The two height sensors, fed by the script.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1_ORIENT / RNGFND2_ORIENT&lt;br /&gt;
|25&lt;br /&gt;
|Pitch270 (downward)&lt;br /&gt;
|Required for ArduPilot to treat the sensor as a height source.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1/2_MIN_CM / MAX_CM&lt;br /&gt;
|40 / 150&lt;br /&gt;
|Valid range (cm)&lt;br /&gt;
|Range gate for the ultrasonic sensors.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1/2_POS_X / _POS_Y / _POS_Z&lt;br /&gt;
|3.8 / ∓0.23 / −0.175&lt;br /&gt;
|Sensor mounting offset from the IMU (m; X fwd, Y right, Z down)&lt;br /&gt;
|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).&lt;br /&gt;
|-&lt;br /&gt;
|THR_FAILSAFE&lt;br /&gt;
|0&lt;br /&gt;
|Disable throttle/RC failsafe&lt;br /&gt;
|No RC receiver is fitted, so RC failsafe must not trigger.&lt;br /&gt;
|-&lt;br /&gt;
|ARSPD_USE&lt;br /&gt;
|0&lt;br /&gt;
|Do not use airspeed&lt;br /&gt;
|No airspeed sensor.&lt;br /&gt;
|-&lt;br /&gt;
|BRD_SAFETY_DEFLT&lt;br /&gt;
|0&lt;br /&gt;
|Safety switch disabled at boot&lt;br /&gt;
|No safety switch fitted; outputs must be live at boot (the enable pin is the safety interlock).&lt;br /&gt;
|-&lt;br /&gt;
|TERRAIN_ENABLE&lt;br /&gt;
|0&lt;br /&gt;
|Disable terrain following&lt;br /&gt;
|Not applicable; avoids interference with foil height control.&lt;br /&gt;
|-&lt;br /&gt;
|GPS_TYPE2&lt;br /&gt;
|5&lt;br /&gt;
|NMEA&lt;br /&gt;
|Sets the GPS data sent onto the serial data port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL1_PROTOCOL&lt;br /&gt;
|2&lt;br /&gt;
|MAVLINK2&lt;br /&gt;
|Protocol on the Mission Planner serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL1_BAUD&lt;br /&gt;
|57&lt;br /&gt;
|57600&lt;br /&gt;
|Baudrate of the Mission Planner serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL4_PROTOCOL&lt;br /&gt;
|5&lt;br /&gt;
|GPS&lt;br /&gt;
|Protocol on the GPS serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL4_BAUD&lt;br /&gt;
|9&lt;br /&gt;
|9600&lt;br /&gt;
|Baudrate of the GPS serial port.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== CAN Bus Format ==&lt;br /&gt;
All traffic is classic CAN, standard 11-bit IDs, 1&amp;amp;nbsp;Mbit/s, little-endian unless noted. Inputs are produced by other nodes; outputs are produced by the Lua control script.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ CAN bus messages&lt;br /&gt;
!ID&lt;br /&gt;
!Direction&lt;br /&gt;
!Length&lt;br /&gt;
!Contents&lt;br /&gt;
|-&lt;br /&gt;
|0x011&lt;br /&gt;
|in&lt;br /&gt;
|3&lt;br /&gt;
|Front-left height sensor: [0] state (0x02 = Operational), [1..2] height in mm (uint16 LE)&lt;br /&gt;
|-&lt;br /&gt;
|0x012&lt;br /&gt;
|in&lt;br /&gt;
|3&lt;br /&gt;
|Front-right height sensor (same layout as 0x011)&lt;br /&gt;
|-&lt;br /&gt;
|0x010&lt;br /&gt;
|out&lt;br /&gt;
|2&lt;br /&gt;
|Rear foil servo command: PWM as uint16, '''big-endian''' (1500 = neutral)&lt;br /&gt;
|-&lt;br /&gt;
|0x250&lt;br /&gt;
|out&lt;br /&gt;
|6&lt;br /&gt;
|Telemetry – attitude: [0..1] roll, [2..3] pitch (int16, centidegrees), [4..5] yaw (uint16, centidegrees, 0..36000)&lt;br /&gt;
|-&lt;br /&gt;
|0x251&lt;br /&gt;
|out&lt;br /&gt;
|8&lt;br /&gt;
|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&lt;br /&gt;
|}&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Byte 6 of &amp;lt;code&amp;gt;0x251&amp;lt;/code&amp;gt; is the ArduPlane flight-mode number as reported by the autopilot. In normal operation the script only commands two: &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; = MANUAL (foils disabled / neutral) and &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt; = FBWA (active stabilisation). Any other value means the mode was changed elsewhere (e.g. from the GCS); see the [https://ardupilot.org/plane/docs/flight-modes.html ArduPlane flight-mode list] for the full enumeration.&lt;br /&gt;
&lt;br /&gt;
The steering angle come from the rudder controller and is planned to be used as roll input to the controller.&lt;br /&gt;
&lt;br /&gt;
== Future plans ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Remaining integration work: feed the rudder/steering-angle CAN message into the controller and finalise the no-RC failsafe behaviour.&lt;br /&gt;
&lt;br /&gt;
Some additional reading: https://ardupilot.org/plane/docs/common-sensor-testing.html&lt;br /&gt;
&lt;br /&gt;
== Relevant links for configuring Ardupilot ==&lt;br /&gt;
https://ardupilot.org/copter/docs/common-gcs-only-operation.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/common-rangefinder-landingpage.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/setting-up-for-tuning.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/terrain-following-manual-modes.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/common-sensor-offset-compensation.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/crash_check.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/flight-modes.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/fbwb-mode.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/fixed-wing-faq.html --Disable Gyro calibration on start-up&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/rover/docs/sonar-sensors.html --Two sonar sensors&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/dev/docs/plane-architecture.html --Plane architecture and files&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Autopilot&amp;diff=307</id>
		<title>Autopilot</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Autopilot&amp;diff=307"/>
		<updated>2026-06-16T10:27:44Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* CAN Bus Format */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Platform ==&lt;br /&gt;
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 [https://docs.cubepilot.org/user-guides/autopilot/the-cube-module-overview orange cube].&lt;br /&gt;
[[File:Ardupilot.jpg|thumb|Ardupilot PCB with the orange cube mounted.]]&lt;br /&gt;
To make use for our application a PCB has been designed to integrate into the hydrofoil design.&lt;br /&gt;
&lt;br /&gt;
To connect to the ardupilot on the cube for tweaking settings and tuning control loops, [https://ardupilot.org/planner/docs/mission-planner-overview.html Mission Planner] is used. The control logic itself runs as a Lua script on the cube (see [[#Lua control script|Lua control script]]). The following sections describe the tweaked settings and the on-board script.&lt;br /&gt;
&lt;br /&gt;
== Hardware ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
== Height Sensors ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each sensor broadcasts a 3-byte frame at 8&amp;amp;nbsp;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|CAN Bus Format]]).&lt;br /&gt;
&lt;br /&gt;
The sensors are mounted as far forward and outboard as possible to stay clear of the bow wake (CAD: 3.8&amp;amp;nbsp;m forward, 0.23&amp;amp;nbsp;m left/right, 0.175&amp;amp;nbsp;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]]).&lt;br /&gt;
&lt;br /&gt;
== Lua control script ==&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
The rear foil is driven over CAN: the script reads ArduPilot's elevator output and sends it as a servo command (ID 0x010).&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
On boot the script enforces the parameters it needs (see the [[#Ardupilot settings|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|CAN Bus Format]]).&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Ardupilot settings ==&lt;br /&gt;
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 &amp;lt;code&amp;gt;SCR_ENABLE&amp;lt;/code&amp;gt; and the physical sensor mounting offsets (&amp;lt;code&amp;gt;RNGFNDx_POS_*&amp;lt;/code&amp;gt;) must be set/measured manually. There are more ports available on the ardupilot; whenever more are used or changed the table will be updated.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Configuration of ardupilot&lt;br /&gt;
!Setting&lt;br /&gt;
!Value&lt;br /&gt;
!Description&lt;br /&gt;
!Reason&lt;br /&gt;
|-&lt;br /&gt;
|SCR_ENABLE&lt;br /&gt;
|1&lt;br /&gt;
|Enable Lua scripting&lt;br /&gt;
|Required to run the control script; must be set manually before the script can run.&lt;br /&gt;
|-&lt;br /&gt;
|INS_GYR_CAL&lt;br /&gt;
|0&lt;br /&gt;
|Controls the automatic gyro calibration.&lt;br /&gt;
|Gyro calibration requires that the unit be held as still as possible.&lt;br /&gt;
|-&lt;br /&gt;
|INITIAL_MODE&lt;br /&gt;
|5&lt;br /&gt;
|FBWA&lt;br /&gt;
|Fly By Wire A, the attitude-stabilisation mode the inner loop uses. More [https://ardupilot.org/plane/docs/fbwa-mode.html here].&lt;br /&gt;
|-&lt;br /&gt;
|SERVO1_FUNCTION&lt;br /&gt;
|77&lt;br /&gt;
|ElevonLeft&lt;br /&gt;
|Controls the left front foil assembly (wired to IO-CH1).&lt;br /&gt;
|-&lt;br /&gt;
|SERVO2_FUNCTION&lt;br /&gt;
|78&lt;br /&gt;
|ElevonRight&lt;br /&gt;
|Controls the right front foil assembly (wired to IO-CH2).&lt;br /&gt;
|-&lt;br /&gt;
|SERVO3_FUNCTION&lt;br /&gt;
|19&lt;br /&gt;
|Elevator&lt;br /&gt;
|Rear foil pitch command; the script reads this output and forwards it to the rear foil over CAN.&lt;br /&gt;
|-&lt;br /&gt;
|SERVO14_FUNCTION&lt;br /&gt;
|-1&lt;br /&gt;
|GPIO&lt;br /&gt;
|Frees FMU_CH6 so it can be read as the foil enable input.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_ENABLE&lt;br /&gt;
|1&lt;br /&gt;
|Enable button (digital pin) function&lt;br /&gt;
|Enables reading the foil enable/disable input.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_PIN1&lt;br /&gt;
|55&lt;br /&gt;
|GPIO pin 55 (FMU_CH6)&lt;br /&gt;
|Maps the enable input to FMU_CH6; active-low and fail-safe.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_REPORT_SEND&lt;br /&gt;
|2&lt;br /&gt;
|Report button state over MAVLink&lt;br /&gt;
|Surfaces the enable state to the GCS.&lt;br /&gt;
|-&lt;br /&gt;
|CAN_P1_DRIVER&lt;br /&gt;
|1&lt;br /&gt;
|Enable CAN port 1&lt;br /&gt;
|CAN carries the height sensors, the rear foil command and telemetry.&lt;br /&gt;
|-&lt;br /&gt;
|CAN_D1_PROTOCOL&lt;br /&gt;
|10&lt;br /&gt;
|Scripting&lt;br /&gt;
|Lets the Lua script send/receive raw CAN frames on bus 1.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1_TYPE / RNGFND2_TYPE&lt;br /&gt;
|36&lt;br /&gt;
|Lua scripting rangefinder&lt;br /&gt;
|The two height sensors, fed by the script.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1_ORIENT / RNGFND2_ORIENT&lt;br /&gt;
|25&lt;br /&gt;
|Pitch270 (downward)&lt;br /&gt;
|Required for ArduPilot to treat the sensor as a height source.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1/2_MIN_CM / MAX_CM&lt;br /&gt;
|40 / 150&lt;br /&gt;
|Valid range (cm)&lt;br /&gt;
|Range gate for the ultrasonic sensors.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1/2_POS_X / _POS_Y / _POS_Z&lt;br /&gt;
|3.8 / ∓0.23 / −0.175&lt;br /&gt;
|Sensor mounting offset from the IMU (m; X fwd, Y right, Z down)&lt;br /&gt;
|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).&lt;br /&gt;
|-&lt;br /&gt;
|THR_FAILSAFE&lt;br /&gt;
|0&lt;br /&gt;
|Disable throttle/RC failsafe&lt;br /&gt;
|No RC receiver is fitted, so RC failsafe must not trigger.&lt;br /&gt;
|-&lt;br /&gt;
|ARSPD_USE&lt;br /&gt;
|0&lt;br /&gt;
|Do not use airspeed&lt;br /&gt;
|No airspeed sensor.&lt;br /&gt;
|-&lt;br /&gt;
|BRD_SAFETY_DEFLT&lt;br /&gt;
|0&lt;br /&gt;
|Safety switch disabled at boot&lt;br /&gt;
|No safety switch fitted; outputs must be live at boot (the enable pin is the safety interlock).&lt;br /&gt;
|-&lt;br /&gt;
|TERRAIN_ENABLE&lt;br /&gt;
|0&lt;br /&gt;
|Disable terrain following&lt;br /&gt;
|Not applicable; avoids interference with foil height control.&lt;br /&gt;
|-&lt;br /&gt;
|GPS_TYPE2&lt;br /&gt;
|5&lt;br /&gt;
|NMEA&lt;br /&gt;
|Sets the GPS data sent onto the serial data port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL1_PROTOCOL&lt;br /&gt;
|2&lt;br /&gt;
|MAVLINK2&lt;br /&gt;
|Protocol on the Mission Planner serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL1_BAUD&lt;br /&gt;
|57&lt;br /&gt;
|57600&lt;br /&gt;
|Baudrate of the Mission Planner serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL4_PROTOCOL&lt;br /&gt;
|5&lt;br /&gt;
|GPS&lt;br /&gt;
|Protocol on the GPS serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL4_BAUD&lt;br /&gt;
|9&lt;br /&gt;
|9600&lt;br /&gt;
|Baudrate of the GPS serial port.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== CAN Bus Format ==&lt;br /&gt;
All traffic is classic CAN, standard 11-bit IDs, 1&amp;amp;nbsp;Mbit/s, little-endian unless noted. Inputs are produced by other nodes; outputs are produced by the Lua control script.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ CAN bus messages&lt;br /&gt;
!ID&lt;br /&gt;
!Direction&lt;br /&gt;
!Length&lt;br /&gt;
!Contents&lt;br /&gt;
|-&lt;br /&gt;
|0x011&lt;br /&gt;
|in&lt;br /&gt;
|3&lt;br /&gt;
|Front-left height sensor: [0] state (0x02 = Operational), [1..2] height in mm (uint16 LE)&lt;br /&gt;
|-&lt;br /&gt;
|0x012&lt;br /&gt;
|in&lt;br /&gt;
|3&lt;br /&gt;
|Front-right height sensor (same layout as 0x011)&lt;br /&gt;
|-&lt;br /&gt;
|0x010&lt;br /&gt;
|out&lt;br /&gt;
|2&lt;br /&gt;
|Rear foil servo command: PWM as uint16, '''big-endian''' (1500 = neutral)&lt;br /&gt;
|-&lt;br /&gt;
|0x250&lt;br /&gt;
|out&lt;br /&gt;
|6&lt;br /&gt;
|Telemetry – attitude: [0..1] roll, [2..3] pitch (int16, centidegrees), [4..5] yaw (uint16, centidegrees, 0..36000)&lt;br /&gt;
|-&lt;br /&gt;
|0x251&lt;br /&gt;
|out&lt;br /&gt;
|8&lt;br /&gt;
|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&lt;br /&gt;
|}&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Byte 6 of &amp;lt;code&amp;gt;0x251&amp;lt;/code&amp;gt; is the ArduPlane flight-mode number as reported by the autopilot. In normal operation the script only commands two: &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; = MANUAL (foils disabled / neutral) and &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt; = FBWA (active stabilisation). Any other value means the mode was changed elsewhere (e.g. from the GCS); see the [https://ardupilot.org/plane/docs/flight-modes.html ArduPlane flight-mode list] for the full enumeration.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The steering angle come from the rudder controller and is planned to be used as roll input to the controller.&lt;br /&gt;
&lt;br /&gt;
== Future plans ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Remaining integration work: feed the rudder/steering-angle CAN message into the controller and finalise the no-RC failsafe behaviour.&lt;br /&gt;
&lt;br /&gt;
Some additional reading: https://ardupilot.org/plane/docs/common-sensor-testing.html&lt;br /&gt;
&lt;br /&gt;
== Relevant links for configuring Ardupilot ==&lt;br /&gt;
https://ardupilot.org/copter/docs/common-gcs-only-operation.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/common-rangefinder-landingpage.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/setting-up-for-tuning.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/terrain-following-manual-modes.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/common-sensor-offset-compensation.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/crash_check.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/flight-modes.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/fbwb-mode.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/fixed-wing-faq.html --Disable Gyro calibration on start-up&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/rover/docs/sonar-sensors.html --Two sonar sensors&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/dev/docs/plane-architecture.html --Plane architecture and files&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Autopilot&amp;diff=306</id>
		<title>Autopilot</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Autopilot&amp;diff=306"/>
		<updated>2026-06-11T07:56:32Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: Added new info based on scripting done in ardupilot LUA&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Platform ==&lt;br /&gt;
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 [https://docs.cubepilot.org/user-guides/autopilot/the-cube-module-overview orange cube].&lt;br /&gt;
[[File:Ardupilot.jpg|thumb|Ardupilot PCB with the orange cube mounted.]]&lt;br /&gt;
To make use for our application a PCB has been designed to integrate into the hydrofoil design.&lt;br /&gt;
&lt;br /&gt;
To connect to the ardupilot on the cube for tweaking settings and tuning control loops, [https://ardupilot.org/planner/docs/mission-planner-overview.html Mission Planner] is used. The control logic itself runs as a Lua script on the cube (see [[#Lua control script|Lua control script]]). The following sections describe the tweaked settings and the on-board script.&lt;br /&gt;
&lt;br /&gt;
== Hardware ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
== Height Sensors ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Each sensor broadcasts a 3-byte frame at 8&amp;amp;nbsp;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|CAN Bus Format]]).&lt;br /&gt;
&lt;br /&gt;
The sensors are mounted as far forward and outboard as possible to stay clear of the bow wake (CAD: 3.8&amp;amp;nbsp;m forward, 0.23&amp;amp;nbsp;m left/right, 0.175&amp;amp;nbsp;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]]).&lt;br /&gt;
&lt;br /&gt;
== Lua control script ==&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
* '''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.&lt;br /&gt;
* '''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.&lt;br /&gt;
&lt;br /&gt;
The rear foil is driven over CAN: the script reads ArduPilot's elevator output and sends it as a servo command (ID 0x010).&lt;br /&gt;
&lt;br /&gt;
'''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.&lt;br /&gt;
&lt;br /&gt;
On boot the script enforces the parameters it needs (see the [[#Ardupilot settings|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|CAN Bus Format]]).&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Ardupilot settings ==&lt;br /&gt;
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 &amp;lt;code&amp;gt;SCR_ENABLE&amp;lt;/code&amp;gt; and the physical sensor mounting offsets (&amp;lt;code&amp;gt;RNGFNDx_POS_*&amp;lt;/code&amp;gt;) must be set/measured manually. There are more ports available on the ardupilot; whenever more are used or changed the table will be updated.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Configuration of ardupilot&lt;br /&gt;
!Setting&lt;br /&gt;
!Value&lt;br /&gt;
!Description&lt;br /&gt;
!Reason&lt;br /&gt;
|-&lt;br /&gt;
|SCR_ENABLE&lt;br /&gt;
|1&lt;br /&gt;
|Enable Lua scripting&lt;br /&gt;
|Required to run the control script; must be set manually before the script can run.&lt;br /&gt;
|-&lt;br /&gt;
|INS_GYR_CAL&lt;br /&gt;
|0&lt;br /&gt;
|Controls the automatic gyro calibration.&lt;br /&gt;
|Gyro calibration requires that the unit be held as still as possible.&lt;br /&gt;
|-&lt;br /&gt;
|INITIAL_MODE&lt;br /&gt;
|5&lt;br /&gt;
|FBWA&lt;br /&gt;
|Fly By Wire A, the attitude-stabilisation mode the inner loop uses. More [https://ardupilot.org/plane/docs/fbwa-mode.html here].&lt;br /&gt;
|-&lt;br /&gt;
|SERVO1_FUNCTION&lt;br /&gt;
|77&lt;br /&gt;
|ElevonLeft&lt;br /&gt;
|Controls the left front foil assembly (wired to IO-CH1).&lt;br /&gt;
|-&lt;br /&gt;
|SERVO2_FUNCTION&lt;br /&gt;
|78&lt;br /&gt;
|ElevonRight&lt;br /&gt;
|Controls the right front foil assembly (wired to IO-CH2).&lt;br /&gt;
|-&lt;br /&gt;
|SERVO3_FUNCTION&lt;br /&gt;
|19&lt;br /&gt;
|Elevator&lt;br /&gt;
|Rear foil pitch command; the script reads this output and forwards it to the rear foil over CAN.&lt;br /&gt;
|-&lt;br /&gt;
|SERVO14_FUNCTION&lt;br /&gt;
|-1&lt;br /&gt;
|GPIO&lt;br /&gt;
|Frees FMU_CH6 so it can be read as the foil enable input.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_ENABLE&lt;br /&gt;
|1&lt;br /&gt;
|Enable button (digital pin) function&lt;br /&gt;
|Enables reading the foil enable/disable input.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_PIN1&lt;br /&gt;
|55&lt;br /&gt;
|GPIO pin 55 (FMU_CH6)&lt;br /&gt;
|Maps the enable input to FMU_CH6; active-low and fail-safe.&lt;br /&gt;
|-&lt;br /&gt;
|BTN_REPORT_SEND&lt;br /&gt;
|2&lt;br /&gt;
|Report button state over MAVLink&lt;br /&gt;
|Surfaces the enable state to the GCS.&lt;br /&gt;
|-&lt;br /&gt;
|CAN_P1_DRIVER&lt;br /&gt;
|1&lt;br /&gt;
|Enable CAN port 1&lt;br /&gt;
|CAN carries the height sensors, the rear foil command and telemetry.&lt;br /&gt;
|-&lt;br /&gt;
|CAN_D1_PROTOCOL&lt;br /&gt;
|10&lt;br /&gt;
|Scripting&lt;br /&gt;
|Lets the Lua script send/receive raw CAN frames on bus 1.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1_TYPE / RNGFND2_TYPE&lt;br /&gt;
|36&lt;br /&gt;
|Lua scripting rangefinder&lt;br /&gt;
|The two height sensors, fed by the script.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1_ORIENT / RNGFND2_ORIENT&lt;br /&gt;
|25&lt;br /&gt;
|Pitch270 (downward)&lt;br /&gt;
|Required for ArduPilot to treat the sensor as a height source.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1/2_MIN_CM / MAX_CM&lt;br /&gt;
|40 / 150&lt;br /&gt;
|Valid range (cm)&lt;br /&gt;
|Range gate for the ultrasonic sensors.&lt;br /&gt;
|-&lt;br /&gt;
|RNGFND1/2_POS_X / _POS_Y / _POS_Z&lt;br /&gt;
|3.8 / ∓0.23 / −0.175&lt;br /&gt;
|Sensor mounting offset from the IMU (m; X fwd, Y right, Z down)&lt;br /&gt;
|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).&lt;br /&gt;
|-&lt;br /&gt;
|THR_FAILSAFE&lt;br /&gt;
|0&lt;br /&gt;
|Disable throttle/RC failsafe&lt;br /&gt;
|No RC receiver is fitted, so RC failsafe must not trigger.&lt;br /&gt;
|-&lt;br /&gt;
|ARSPD_USE&lt;br /&gt;
|0&lt;br /&gt;
|Do not use airspeed&lt;br /&gt;
|No airspeed sensor.&lt;br /&gt;
|-&lt;br /&gt;
|BRD_SAFETY_DEFLT&lt;br /&gt;
|0&lt;br /&gt;
|Safety switch disabled at boot&lt;br /&gt;
|No safety switch fitted; outputs must be live at boot (the enable pin is the safety interlock).&lt;br /&gt;
|-&lt;br /&gt;
|TERRAIN_ENABLE&lt;br /&gt;
|0&lt;br /&gt;
|Disable terrain following&lt;br /&gt;
|Not applicable; avoids interference with foil height control.&lt;br /&gt;
|-&lt;br /&gt;
|GPS_TYPE2&lt;br /&gt;
|5&lt;br /&gt;
|NMEA&lt;br /&gt;
|Sets the GPS data sent onto the serial data port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL1_PROTOCOL&lt;br /&gt;
|2&lt;br /&gt;
|MAVLINK2&lt;br /&gt;
|Protocol on the Mission Planner serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL1_BAUD&lt;br /&gt;
|57&lt;br /&gt;
|57600&lt;br /&gt;
|Baudrate of the Mission Planner serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL4_PROTOCOL&lt;br /&gt;
|5&lt;br /&gt;
|GPS&lt;br /&gt;
|Protocol on the GPS serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL4_BAUD&lt;br /&gt;
|9&lt;br /&gt;
|9600&lt;br /&gt;
|Baudrate of the GPS serial port.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== CAN Bus Format ==&lt;br /&gt;
All traffic is classic CAN, standard 11-bit IDs, 1&amp;amp;nbsp;Mbit/s, little-endian unless noted. Inputs are produced by other nodes; outputs are produced by the Lua control script.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ CAN bus messages&lt;br /&gt;
!ID&lt;br /&gt;
!Direction&lt;br /&gt;
!Length&lt;br /&gt;
!Contents&lt;br /&gt;
|-&lt;br /&gt;
|0x011&lt;br /&gt;
|in&lt;br /&gt;
|3&lt;br /&gt;
|Front-left height sensor: [0] state (0x02 = Operational), [1..2] height in mm (uint16 LE)&lt;br /&gt;
|-&lt;br /&gt;
|0x012&lt;br /&gt;
|in&lt;br /&gt;
|3&lt;br /&gt;
|Front-right height sensor (same layout as 0x011)&lt;br /&gt;
|-&lt;br /&gt;
|0x010&lt;br /&gt;
|out&lt;br /&gt;
|2&lt;br /&gt;
|Rear foil servo command: PWM as uint16, '''big-endian''' (1500 = neutral)&lt;br /&gt;
|-&lt;br /&gt;
|0x250&lt;br /&gt;
|out&lt;br /&gt;
|6&lt;br /&gt;
|Telemetry – attitude: [0..1] roll, [2..3] pitch (int16, centidegrees), [4..5] yaw (uint16, centidegrees, 0..36000)&lt;br /&gt;
|-&lt;br /&gt;
|0x251&lt;br /&gt;
|out&lt;br /&gt;
|8&lt;br /&gt;
|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&lt;br /&gt;
|}&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The rudder/steering angle (from the ATSAMD20 MCU) is planned to be added to this bus and fed forward into the controller.&lt;br /&gt;
&lt;br /&gt;
== Future plans ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Remaining integration work: feed the rudder/steering-angle CAN message into the controller and finalise the no-RC failsafe behaviour.&lt;br /&gt;
&lt;br /&gt;
Some additional reading: https://ardupilot.org/plane/docs/common-sensor-testing.html&lt;br /&gt;
&lt;br /&gt;
== Relevant links for configuring Ardupilot ==&lt;br /&gt;
https://ardupilot.org/copter/docs/common-gcs-only-operation.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/common-rangefinder-landingpage.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/setting-up-for-tuning.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/terrain-following-manual-modes.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/common-sensor-offset-compensation.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/crash_check.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/flight-modes.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/fbwb-mode.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/fixed-wing-faq.html --Disable Gyro calibration on start-up&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/rover/docs/sonar-sensors.html --Two sonar sensors&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/dev/docs/plane-architecture.html --Plane architecture and files&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Autopilot&amp;diff=305</id>
		<title>Autopilot</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Autopilot&amp;diff=305"/>
		<updated>2026-06-11T07:28:06Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Platform ==&lt;br /&gt;
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 [https://docs.cubepilot.org/user-guides/autopilot/the-cube-module-overview orange cube].&lt;br /&gt;
[[File:Ardupilot.jpg|thumb|Ardupilot PCB with the orange cube mounted.]]&lt;br /&gt;
To make use for our application a PCB has been designed to integrate into the hydrofoil design. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To connect to the ardupilot on the cube for tweaking settings and tuning control loops, [https://ardupilot.org/planner/docs/mission-planner-overview.html Mission Planner] will be used. The following section have tweaked settings found in Mission Planner.&lt;br /&gt;
&lt;br /&gt;
== Hardware ==&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
It has its own GPS antenna and four UART serial ports, where Serial1 is used for the MAVLINK connect to Mission Planner that is running on a laptop. Two of these serial ports will be used to implement the height sensors that will be mounted on the boat for measuring the height of the water. This ensures that the system correctly responds to waves where the boat can 'surf' over.&lt;br /&gt;
&lt;br /&gt;
For steering on foils an extra MCU is added onto the board namely: ATSAMD20E18A. This MCU can receive data from the rudder that gives the steering position and sends it through to the cube.&lt;br /&gt;
&lt;br /&gt;
== Height Sensors ==&lt;br /&gt;
Currently testing ultrasonic height sensors, the RS485 version of the DYP-A02YY4W-V2.0. with the CAN-interface PCB in between.&lt;br /&gt;
&lt;br /&gt;
== Ardupilot settings ==&lt;br /&gt;
To use the ardupilot as the foil control unit, some settings need to be changed since they do not apply to us or should be disabled. These settings are given below with a small explanation why it has been configured as is.&lt;br /&gt;
&lt;br /&gt;
The settings below are the changed settings used by the ardupilot. There are more ports available on the ardupilot, whenever more are used or are changed the table will be updated.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
Configuration of ardupilot&lt;br /&gt;
!Setting&lt;br /&gt;
!Value&lt;br /&gt;
!Description&lt;br /&gt;
!Reason&lt;br /&gt;
|-&lt;br /&gt;
|INS_GYR_CAL&lt;br /&gt;
|0&lt;br /&gt;
|Controls the automatic gyro calibration.&lt;br /&gt;
|Gyro calibration requires that the unit should be held as still as possible.&lt;br /&gt;
|-&lt;br /&gt;
|SERVO1_FUNCTION&lt;br /&gt;
|78&lt;br /&gt;
|ElevonRight&lt;br /&gt;
|Controls the right foil assembly.&lt;br /&gt;
|-&lt;br /&gt;
|SERVO2_FUNCTION&lt;br /&gt;
|77&lt;br /&gt;
|ElevonLeft&lt;br /&gt;
|Controls the left foil assembly.&lt;br /&gt;
|-&lt;br /&gt;
|INITIAL_MODE&lt;br /&gt;
|5&lt;br /&gt;
|FBWA&lt;br /&gt;
|Fly By Wire A this is a fly mode for the control unit. More can be found [https://ardupilot.org/plane/docs/fbwa-mode.html here].&lt;br /&gt;
|-&lt;br /&gt;
|GPS_TYPE2&lt;br /&gt;
|5&lt;br /&gt;
|NMEA&lt;br /&gt;
|This sets the GPS data that is sent onto the serial data port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL1_PROTOCOL&lt;br /&gt;
|2&lt;br /&gt;
|MAVLINK2&lt;br /&gt;
|Controls what protocol to use on the serial port&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL1_BAUD&lt;br /&gt;
|57&lt;br /&gt;
|57600&lt;br /&gt;
|Specifies the baudrate of the serial port.&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL4_PROTOCOL&lt;br /&gt;
|5&lt;br /&gt;
|GPS&lt;br /&gt;
|Controls what protocol to use on the serial port&lt;br /&gt;
|-&lt;br /&gt;
|SERIAL4_BAUD&lt;br /&gt;
|9&lt;br /&gt;
|9600&lt;br /&gt;
|Specifies the baudrate of the serial port.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== CAN Bus Format ==&lt;br /&gt;
The following data is output on the CAN bus for logging &amp;amp; rudder control:&lt;br /&gt;
&lt;br /&gt;
== Future plans ==&lt;br /&gt;
As of now: 21-03-2023, FBWA mode is used for controlling the front two foil assembly. It would probably be beneficial whenever the whole system is finished to move towards the FBWB mode.&lt;br /&gt;
&lt;br /&gt;
This mode supports &amp;quot;Automatic Altitude Holding&amp;quot;, that makes sure that the same level of altitude is automatically kept. Further testing is needed for determining the control loops and their coefficients.&lt;br /&gt;
&lt;br /&gt;
Implementing the different sensors into ardupilot and using that data to respond to the environment. These sensors are at least the height sensors and the rudder sensor for the steering angle.&lt;br /&gt;
&lt;br /&gt;
As of March 2026, some progress was made with LUA scripting, see git, some additional reading:&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/common-sensor-testing.html&lt;br /&gt;
&lt;br /&gt;
== Relevant links for configuring Ardupilot ==&lt;br /&gt;
https://ardupilot.org/copter/docs/common-gcs-only-operation.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/common-rangefinder-landingpage.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/setting-up-for-tuning.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/terrain-following-manual-modes.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/common-sensor-offset-compensation.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/crash_check.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/flight-modes.html --FBW-A mode seems best fit for front only, FBW-B for finalized system&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/fbwb-mode.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/copter/docs/stabilize-mode.html&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/plane/docs/fixed-wing-faq.html --Disable Gyro calibration on start-up&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/rover/docs/sonar-sensors.html --Two sonar sensors&lt;br /&gt;
&lt;br /&gt;
https://ardupilot.org/dev/docs/plane-architecture.html --Plane architecture and files&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=304</id>
		<title>Foils</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=304"/>
		<updated>2026-05-22T07:30:35Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* Wing design */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The foils of the solar boat is a system that is comprised of the following:&lt;br /&gt;
&lt;br /&gt;
* Wing design&lt;br /&gt;
* Foil assembly&lt;br /&gt;
* [[Autopilot]]&lt;br /&gt;
&lt;br /&gt;
A small overview is given below what any of these signify in the hydrofoils.&lt;br /&gt;
&lt;br /&gt;
== Wing design ==&lt;br /&gt;
[[File:Wing.jpg|thumb|The Cenus wing to be used on the solar boat.]]&lt;br /&gt;
The wing design is based on the calculation of the airfoil database website: http://airfoiltools.com/plotter/index &lt;br /&gt;
&lt;br /&gt;
From this database an implementation has been made into matlab to tweak some parameters as well as a plot for generating the wing shape. &lt;br /&gt;
&lt;br /&gt;
The matlab scripts are on the [https://git.engineersofinnovation.nl git] and can be found [https://git.engineersofinnovation.nl/matlab-scripts/Hydrofoil_Design here].&lt;br /&gt;
&lt;br /&gt;
The Scripts here make an assumption of a Driver weight of 75 kg and a total boat &amp;amp; driver weight of 180 kg. This gives a mass on rudder of 62.3 kg, mass on each of 2 foils of 58.8 kg. &lt;br /&gt;
&lt;br /&gt;
[[File:cenus_lift_aoa.png|thumb|Lift vs Angle of Attack for the first Cenus wing]]&lt;br /&gt;
&lt;br /&gt;
== Foil assembly ==&lt;br /&gt;
The foil assembly connects the wing to the foil mast and then to the hull of the boat.&lt;br /&gt;
[[File:Foil assembly.gif|center|thumb|230x230px|Scaled down for showing moving of the foil assembly.]]&lt;br /&gt;
&lt;br /&gt;
== Autopilot ==&lt;br /&gt;
The [[autopilot]] is the control electronics that controls the servos of the foil assembly. This a signal PCB with that measures the angle of the solar boat and adjust accordingly towards keeping the hull above the water.&lt;br /&gt;
&lt;br /&gt;
== Height sensors ==&lt;br /&gt;
We use ultrasonic height sensors for sensing distance between water and our hull. Currently we use cheap A02 RS485 sensors from DYP. &lt;br /&gt;
&lt;br /&gt;
Datasheets can be found https://www.dypcn.com/uploads/A02-Datasheet.pdf and https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf&lt;br /&gt;
&lt;br /&gt;
Data from this sensor is being processed by our RS485 to CAN interface.&lt;br /&gt;
&lt;br /&gt;
==== Pinout ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin#!!Wire Colour !!Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1|| style=&amp;quot;background: #0065B2;color:white;&amp;quot; | Blue||&lt;br /&gt;
|-&lt;br /&gt;
|2|| style=&amp;quot;background: #241F21;color:white;&amp;quot; | Black||&lt;br /&gt;
|-&lt;br /&gt;
| 3|| style=&amp;quot;background: #FFFFFF;&amp;quot; | White||&lt;br /&gt;
|-&lt;br /&gt;
|4|| style=&amp;quot;background: #83603A;&amp;quot; | Brown||&lt;br /&gt;
|-&lt;br /&gt;
|5|| style=&amp;quot;background: #B2B3B7;&amp;quot; | Gray||&lt;br /&gt;
|}&lt;br /&gt;
= CAN Messages =&lt;br /&gt;
All multi-byte values are little-endian. Any state byte not listed maps to &amp;lt;code&amp;gt;Unknown&amp;lt;/code&amp;gt; on the receiver side.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Message&lt;br /&gt;
!CAN ID&lt;br /&gt;
!DLC&lt;br /&gt;
!Byte&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Values / Range&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderSetpoint&lt;br /&gt;
|0x010&lt;br /&gt;
|2&lt;br /&gt;
|0–1&lt;br /&gt;
|Setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontLeft&lt;br /&gt;
|0x011&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontRight&lt;br /&gt;
|0x012&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x013&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x014&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderStatus&lt;br /&gt;
|0x020&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Uninitialized, 1=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Current setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderCommand&lt;br /&gt;
|0x021&lt;br /&gt;
|1&lt;br /&gt;
|0&lt;br /&gt;
|Command&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Initialize&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=File:Cenus_lift_aoa.png&amp;diff=303</id>
		<title>File:Cenus lift aoa.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=File:Cenus_lift_aoa.png&amp;diff=303"/>
		<updated>2026-05-22T07:30:13Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=302</id>
		<title>Foils</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Foils&amp;diff=302"/>
		<updated>2026-05-22T07:28:17Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The foils of the solar boat is a system that is comprised of the following:&lt;br /&gt;
&lt;br /&gt;
* Wing design&lt;br /&gt;
* Foil assembly&lt;br /&gt;
* [[Autopilot]]&lt;br /&gt;
&lt;br /&gt;
A small overview is given below what any of these signify in the hydrofoils.&lt;br /&gt;
&lt;br /&gt;
== Wing design ==&lt;br /&gt;
[[File:Wing.jpg|thumb|The Cenus wing to be used on the solar boat.]]&lt;br /&gt;
The wing design is based on the calculation of the airfoil database website: http://airfoiltools.com/plotter/index &lt;br /&gt;
&lt;br /&gt;
From this database an implementation has been made into matlab to tweak some parameters as well as a plot for generating the wing shape. &lt;br /&gt;
&lt;br /&gt;
The matlab scripts are on the [https://git.engineersofinnovation.nl git] and can be found [https://git.engineersofinnovation.nl/matlab-scripts/Hydrofoil_Design here].&lt;br /&gt;
&lt;br /&gt;
The Scripts here make an assumption of a Driver weight of 75 kg and a total boat &amp;amp; driver weight of 180 kg. This gives a mass on rudder of 62.3 kg, mass on each of 2 foils of 58.8 kg. &lt;br /&gt;
&lt;br /&gt;
[[File:cenus_lift_aoa.jpg|thumb|Lift vs Angle of Attack for the first Cenus wing]]&lt;br /&gt;
&lt;br /&gt;
== Foil assembly ==&lt;br /&gt;
The foil assembly connects the wing to the foil mast and then to the hull of the boat.&lt;br /&gt;
[[File:Foil assembly.gif|center|thumb|230x230px|Scaled down for showing moving of the foil assembly.]]&lt;br /&gt;
&lt;br /&gt;
== Autopilot ==&lt;br /&gt;
The [[autopilot]] is the control electronics that controls the servos of the foil assembly. This a signal PCB with that measures the angle of the solar boat and adjust accordingly towards keeping the hull above the water.&lt;br /&gt;
&lt;br /&gt;
== Height sensors ==&lt;br /&gt;
We use ultrasonic height sensors for sensing distance between water and our hull. Currently we use cheap A02 RS485 sensors from DYP. &lt;br /&gt;
&lt;br /&gt;
Datasheets can be found https://www.dypcn.com/uploads/A02-Datasheet.pdf and https://www.dypcn.com/uploads/A02-Output-Interfaces.pdf&lt;br /&gt;
&lt;br /&gt;
Data from this sensor is being processed by our RS485 to CAN interface.&lt;br /&gt;
&lt;br /&gt;
==== Pinout ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pin#!!Wire Colour !!Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1|| style=&amp;quot;background: #0065B2;color:white;&amp;quot; | Blue||&lt;br /&gt;
|-&lt;br /&gt;
|2|| style=&amp;quot;background: #241F21;color:white;&amp;quot; | Black||&lt;br /&gt;
|-&lt;br /&gt;
| 3|| style=&amp;quot;background: #FFFFFF;&amp;quot; | White||&lt;br /&gt;
|-&lt;br /&gt;
|4|| style=&amp;quot;background: #83603A;&amp;quot; | Brown||&lt;br /&gt;
|-&lt;br /&gt;
|5|| style=&amp;quot;background: #B2B3B7;&amp;quot; | Gray||&lt;br /&gt;
|}&lt;br /&gt;
= CAN Messages =&lt;br /&gt;
All multi-byte values are little-endian. Any state byte not listed maps to &amp;lt;code&amp;gt;Unknown&amp;lt;/code&amp;gt; on the receiver side.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Message&lt;br /&gt;
!CAN ID&lt;br /&gt;
!DLC&lt;br /&gt;
!Byte&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Values / Range&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderSetpoint&lt;br /&gt;
|0x010&lt;br /&gt;
|2&lt;br /&gt;
|0–1&lt;br /&gt;
|Setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontLeft&lt;br /&gt;
|0x011&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensorFrontRight&lt;br /&gt;
|0x012&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x013&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|HeightSensor (placement TBD)&lt;br /&gt;
|0x014&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=NotPluggedIn, 1=ModbusError, 2=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Height value&lt;br /&gt;
|u16 LE&lt;br /&gt;
|distance in mm&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderStatus&lt;br /&gt;
|0x020&lt;br /&gt;
|3&lt;br /&gt;
|0&lt;br /&gt;
|State&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Uninitialized, 1=Operational, 0xFF=Unknown&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|1–2&lt;br /&gt;
|Current setpoint&lt;br /&gt;
|u16 LE&lt;br /&gt;
|1000–2000&lt;br /&gt;
|-&lt;br /&gt;
|ServoRudderCommand&lt;br /&gt;
|0x021&lt;br /&gt;
|1&lt;br /&gt;
|0&lt;br /&gt;
|Command&lt;br /&gt;
|u8 enum&lt;br /&gt;
|0=Initialize&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=GaN_MPPT&amp;diff=286</id>
		<title>GaN MPPT</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=GaN_MPPT&amp;diff=286"/>
		<updated>2026-05-12T09:34:50Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* PhaseFault_t (Byte 1 of Status) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:MPPT overview.jpg|thumb|Baseboard with two phases populated]]&lt;br /&gt;
Part of a system consisting of 8x Gan Phases and 1x Baseboard.: Picture above is fully populated BaseBoard with 8 GaN boost converters mounted. PCB's can be cooled through Inductors on the bottom using a coldplate, while still being easily replaced from the top by remving the four bolts. Software based on TPEE MPPT&lt;br /&gt;
&lt;br /&gt;
== Can Bus Protocol Format ==&lt;br /&gt;
The CAN bus telemetry is output using Standard CAN IDs (11-bit) by default. The ID structure uses the upper 7 bits for the Node ID (default 64, offset by hardware pins) and the lower 4 bits for the Packet ID.&lt;br /&gt;
&lt;br /&gt;
Here is the documentation for the telemetry messages:&lt;br /&gt;
&lt;br /&gt;
CAN Bus Frame Format&lt;br /&gt;
&lt;br /&gt;
Frame Type: Standard Frame (11-bit ID)&lt;br /&gt;
&lt;br /&gt;
Bitrate: 1000kbps&lt;br /&gt;
&lt;br /&gt;
Node ID: Configurable generalCanId (64) + Hardware ID offset (Pins ID0-ID3)&lt;br /&gt;
&lt;br /&gt;
NB: The ID is based on the position on the mainboard. Where ID0-ID2 is equal to the position 0-7, and ID3 is dependent on the DIP-switch on the mainboard. Front ID3 = 1, Read ID3 = 0.&lt;br /&gt;
&lt;br /&gt;
CAN ID Construction: (NodeID &amp;lt;&amp;lt; 4) | PacketID&lt;br /&gt;
&lt;br /&gt;
=== Power ===&lt;br /&gt;
Packet ID 0x00: Power (2Hz)&lt;br /&gt;
&lt;br /&gt;
Sent every 500ms. Contains the primary voltage and current measurements.&lt;br /&gt;
&lt;br /&gt;
Packet ID: 0x00&lt;br /&gt;
&lt;br /&gt;
Data Length: 8 Bytes&lt;br /&gt;
&lt;br /&gt;
Format: Big Endian (Network Order)&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|'''Byte'''&lt;br /&gt;
|'''Name'''&lt;br /&gt;
|'''Type'''&lt;br /&gt;
|'''Scaling'''&lt;br /&gt;
|'''Description'''&lt;br /&gt;
|-&lt;br /&gt;
|0-1&lt;br /&gt;
|Input  Voltage&lt;br /&gt;
|int16&lt;br /&gt;
|0.01  V/bit&lt;br /&gt;
|Input  Voltage (Vlow)&lt;br /&gt;
|-&lt;br /&gt;
|2-3&lt;br /&gt;
|Input  Current&lt;br /&gt;
|int16&lt;br /&gt;
|0.0005  A/bit&lt;br /&gt;
|Input  Current (Iind). Scaling factor 2000.0f&lt;br /&gt;
|-&lt;br /&gt;
|4-5&lt;br /&gt;
|Output  Voltage&lt;br /&gt;
|int16&lt;br /&gt;
|0.01  V/bit&lt;br /&gt;
|Output  Voltage (Vhigh)&lt;br /&gt;
|-&lt;br /&gt;
|6-7&lt;br /&gt;
|Output  Current&lt;br /&gt;
|int16&lt;br /&gt;
|0.0005  A/bit&lt;br /&gt;
|Output  Current (Ihigh). Scaling factor 2000.0f&lt;br /&gt;
|}&lt;br /&gt;
Note on Scaling:&lt;br /&gt;
&lt;br /&gt;
buffer_append_float16(data, value, scale, &amp;amp;index) multiplies the float value by scale and stores it as an int16.&lt;br /&gt;
&lt;br /&gt;
To decode: float value = (float)((int16_t)received_value) / scale&lt;br /&gt;
&lt;br /&gt;
Input/Output Current Scale: 2000.0 -&amp;gt; Divide received int16 by 2000.0 to get Amps.&lt;br /&gt;
&lt;br /&gt;
Input/Output Voltage Scale: 100.0 -&amp;gt; Divide received int16 by 100.0 to get Volts.&lt;br /&gt;
&lt;br /&gt;
=== Status ===&lt;br /&gt;
Packet ID 0x01: Status (1Hz)&lt;br /&gt;
&lt;br /&gt;
Sent every 1000ms. Contains the operating state and fault codes.&lt;br /&gt;
&lt;br /&gt;
Packet ID: 0x01&lt;br /&gt;
&lt;br /&gt;
Data Length: 5 Bytes&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|'''Byte'''&lt;br /&gt;
|'''Name'''&lt;br /&gt;
|'''Type'''&lt;br /&gt;
|'''Scaling   / Enum'''&lt;br /&gt;
|'''Description'''&lt;br /&gt;
|-&lt;br /&gt;
|0&lt;br /&gt;
|Mode&lt;br /&gt;
|uint8&lt;br /&gt;
|PhaseMode_t&lt;br /&gt;
|Operating  Mode (1=CIV, 2=CIC, 3=MinInputCurrent, 4=COV, 5=COC, 6=Temp Derating,  7=Fault)&lt;br /&gt;
|-&lt;br /&gt;
|1&lt;br /&gt;
|Fault&lt;br /&gt;
|uint8&lt;br /&gt;
|PhaseFault_t&lt;br /&gt;
|Fault  Code (0=OK, 1=Config, 2=Input OV, 3=Output OV, 4=Output OC, 5=Input OC,  6=Input UC, 7=Phase OC)&lt;br /&gt;
|-&lt;br /&gt;
|2&lt;br /&gt;
|Enabled&lt;br /&gt;
|uint8&lt;br /&gt;
|bool&lt;br /&gt;
|1 if  Enabled, 0 if Disabled&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|Board  Temp&lt;br /&gt;
|int8&lt;br /&gt;
|1  °C/bit&lt;br /&gt;
|Ambient  Temperature in °C&lt;br /&gt;
|-&lt;br /&gt;
|4&lt;br /&gt;
|Heat  Sink Temp&lt;br /&gt;
|int8&lt;br /&gt;
|1  °C/bit&lt;br /&gt;
|Heatsink  Temperature in °C&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Sweep Data ===&lt;br /&gt;
Packet ID 0x02: Sweep Data (On Request)&lt;br /&gt;
&lt;br /&gt;
Sent during an MPPT sweep operation.&lt;br /&gt;
&lt;br /&gt;
Packet ID: 0x02&lt;br /&gt;
&lt;br /&gt;
Data Length: 5 Bytes&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|'''Byte'''&lt;br /&gt;
|'''Name'''&lt;br /&gt;
|'''Type'''&lt;br /&gt;
|'''Scaling'''&lt;br /&gt;
|'''Description'''&lt;br /&gt;
|-&lt;br /&gt;
|0&lt;br /&gt;
|Index&lt;br /&gt;
|uint8&lt;br /&gt;
|1&lt;br /&gt;
|Sweep  point index (0-255)&lt;br /&gt;
|-&lt;br /&gt;
|1-2&lt;br /&gt;
|Current&lt;br /&gt;
|int16&lt;br /&gt;
|0.0005  A/bit&lt;br /&gt;
|Current  at this sweep point (Scale 2000.0)&lt;br /&gt;
|-&lt;br /&gt;
|3-4&lt;br /&gt;
|Voltage&lt;br /&gt;
|int16&lt;br /&gt;
|0.01  V/bit&lt;br /&gt;
|Voltage  at this sweep point (Scale 100.0)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Enums ===&lt;br /&gt;
&lt;br /&gt;
=== PhaseMode_t (Byte 0 of Status) ===&lt;br /&gt;
0: None (Start)&lt;br /&gt;
&lt;br /&gt;
1: CIV (Constant Input Voltage)&lt;br /&gt;
&lt;br /&gt;
2: CIC (Constant Input Current)&lt;br /&gt;
&lt;br /&gt;
3: MinInputCurrent&lt;br /&gt;
&lt;br /&gt;
4: COV (Constant Output Voltage)&lt;br /&gt;
&lt;br /&gt;
5: COC (Constant Output Current)&lt;br /&gt;
&lt;br /&gt;
6: TD (Temperature Derating)&lt;br /&gt;
&lt;br /&gt;
7: Fault&lt;br /&gt;
&lt;br /&gt;
==== PhaseFault_t (Byte 1 of Status) ====&lt;br /&gt;
0: OK&lt;br /&gt;
&lt;br /&gt;
1: Config Error&lt;br /&gt;
&lt;br /&gt;
2: Input Over Voltage&lt;br /&gt;
&lt;br /&gt;
3: Output Over Voltage&lt;br /&gt;
&lt;br /&gt;
4: Output Over Current&lt;br /&gt;
&lt;br /&gt;
5: Input Over Current&lt;br /&gt;
&lt;br /&gt;
6: Input Under Current&lt;br /&gt;
&lt;br /&gt;
7: Phase Over Current&lt;br /&gt;
&lt;br /&gt;
8: General Fault&lt;br /&gt;
&lt;br /&gt;
9: Sync lost&lt;br /&gt;
#&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=GaN_MPPT&amp;diff=285</id>
		<title>GaN MPPT</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=GaN_MPPT&amp;diff=285"/>
		<updated>2026-05-12T09:34:22Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* PhaseFault_t (Byte 1 of Status) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:MPPT overview.jpg|thumb|Baseboard with two phases populated]]&lt;br /&gt;
Part of a system consisting of 8x Gan Phases and 1x Baseboard.: Picture above is fully populated BaseBoard with 8 GaN boost converters mounted. PCB's can be cooled through Inductors on the bottom using a coldplate, while still being easily replaced from the top by remving the four bolts. Software based on TPEE MPPT&lt;br /&gt;
&lt;br /&gt;
== Can Bus Protocol Format ==&lt;br /&gt;
The CAN bus telemetry is output using Standard CAN IDs (11-bit) by default. The ID structure uses the upper 7 bits for the Node ID (default 64, offset by hardware pins) and the lower 4 bits for the Packet ID.&lt;br /&gt;
&lt;br /&gt;
Here is the documentation for the telemetry messages:&lt;br /&gt;
&lt;br /&gt;
CAN Bus Frame Format&lt;br /&gt;
&lt;br /&gt;
Frame Type: Standard Frame (11-bit ID)&lt;br /&gt;
&lt;br /&gt;
Bitrate: 1000kbps&lt;br /&gt;
&lt;br /&gt;
Node ID: Configurable generalCanId (64) + Hardware ID offset (Pins ID0-ID3)&lt;br /&gt;
&lt;br /&gt;
NB: The ID is based on the position on the mainboard. Where ID0-ID2 is equal to the position 0-7, and ID3 is dependent on the DIP-switch on the mainboard. Front ID3 = 1, Read ID3 = 0.&lt;br /&gt;
&lt;br /&gt;
CAN ID Construction: (NodeID &amp;lt;&amp;lt; 4) | PacketID&lt;br /&gt;
&lt;br /&gt;
=== Power ===&lt;br /&gt;
Packet ID 0x00: Power (2Hz)&lt;br /&gt;
&lt;br /&gt;
Sent every 500ms. Contains the primary voltage and current measurements.&lt;br /&gt;
&lt;br /&gt;
Packet ID: 0x00&lt;br /&gt;
&lt;br /&gt;
Data Length: 8 Bytes&lt;br /&gt;
&lt;br /&gt;
Format: Big Endian (Network Order)&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|'''Byte'''&lt;br /&gt;
|'''Name'''&lt;br /&gt;
|'''Type'''&lt;br /&gt;
|'''Scaling'''&lt;br /&gt;
|'''Description'''&lt;br /&gt;
|-&lt;br /&gt;
|0-1&lt;br /&gt;
|Input  Voltage&lt;br /&gt;
|int16&lt;br /&gt;
|0.01  V/bit&lt;br /&gt;
|Input  Voltage (Vlow)&lt;br /&gt;
|-&lt;br /&gt;
|2-3&lt;br /&gt;
|Input  Current&lt;br /&gt;
|int16&lt;br /&gt;
|0.0005  A/bit&lt;br /&gt;
|Input  Current (Iind). Scaling factor 2000.0f&lt;br /&gt;
|-&lt;br /&gt;
|4-5&lt;br /&gt;
|Output  Voltage&lt;br /&gt;
|int16&lt;br /&gt;
|0.01  V/bit&lt;br /&gt;
|Output  Voltage (Vhigh)&lt;br /&gt;
|-&lt;br /&gt;
|6-7&lt;br /&gt;
|Output  Current&lt;br /&gt;
|int16&lt;br /&gt;
|0.0005  A/bit&lt;br /&gt;
|Output  Current (Ihigh). Scaling factor 2000.0f&lt;br /&gt;
|}&lt;br /&gt;
Note on Scaling:&lt;br /&gt;
&lt;br /&gt;
buffer_append_float16(data, value, scale, &amp;amp;index) multiplies the float value by scale and stores it as an int16.&lt;br /&gt;
&lt;br /&gt;
To decode: float value = (float)((int16_t)received_value) / scale&lt;br /&gt;
&lt;br /&gt;
Input/Output Current Scale: 2000.0 -&amp;gt; Divide received int16 by 2000.0 to get Amps.&lt;br /&gt;
&lt;br /&gt;
Input/Output Voltage Scale: 100.0 -&amp;gt; Divide received int16 by 100.0 to get Volts.&lt;br /&gt;
&lt;br /&gt;
=== Status ===&lt;br /&gt;
Packet ID 0x01: Status (1Hz)&lt;br /&gt;
&lt;br /&gt;
Sent every 1000ms. Contains the operating state and fault codes.&lt;br /&gt;
&lt;br /&gt;
Packet ID: 0x01&lt;br /&gt;
&lt;br /&gt;
Data Length: 5 Bytes&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|'''Byte'''&lt;br /&gt;
|'''Name'''&lt;br /&gt;
|'''Type'''&lt;br /&gt;
|'''Scaling   / Enum'''&lt;br /&gt;
|'''Description'''&lt;br /&gt;
|-&lt;br /&gt;
|0&lt;br /&gt;
|Mode&lt;br /&gt;
|uint8&lt;br /&gt;
|PhaseMode_t&lt;br /&gt;
|Operating  Mode (1=CIV, 2=CIC, 3=MinInputCurrent, 4=COV, 5=COC, 6=Temp Derating,  7=Fault)&lt;br /&gt;
|-&lt;br /&gt;
|1&lt;br /&gt;
|Fault&lt;br /&gt;
|uint8&lt;br /&gt;
|PhaseFault_t&lt;br /&gt;
|Fault  Code (0=OK, 1=Config, 2=Input OV, 3=Output OV, 4=Output OC, 5=Input OC,  6=Input UC, 7=Phase OC)&lt;br /&gt;
|-&lt;br /&gt;
|2&lt;br /&gt;
|Enabled&lt;br /&gt;
|uint8&lt;br /&gt;
|bool&lt;br /&gt;
|1 if  Enabled, 0 if Disabled&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|Board  Temp&lt;br /&gt;
|int8&lt;br /&gt;
|1  °C/bit&lt;br /&gt;
|Ambient  Temperature in °C&lt;br /&gt;
|-&lt;br /&gt;
|4&lt;br /&gt;
|Heat  Sink Temp&lt;br /&gt;
|int8&lt;br /&gt;
|1  °C/bit&lt;br /&gt;
|Heatsink  Temperature in °C&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Sweep Data ===&lt;br /&gt;
Packet ID 0x02: Sweep Data (On Request)&lt;br /&gt;
&lt;br /&gt;
Sent during an MPPT sweep operation.&lt;br /&gt;
&lt;br /&gt;
Packet ID: 0x02&lt;br /&gt;
&lt;br /&gt;
Data Length: 5 Bytes&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|'''Byte'''&lt;br /&gt;
|'''Name'''&lt;br /&gt;
|'''Type'''&lt;br /&gt;
|'''Scaling'''&lt;br /&gt;
|'''Description'''&lt;br /&gt;
|-&lt;br /&gt;
|0&lt;br /&gt;
|Index&lt;br /&gt;
|uint8&lt;br /&gt;
|1&lt;br /&gt;
|Sweep  point index (0-255)&lt;br /&gt;
|-&lt;br /&gt;
|1-2&lt;br /&gt;
|Current&lt;br /&gt;
|int16&lt;br /&gt;
|0.0005  A/bit&lt;br /&gt;
|Current  at this sweep point (Scale 2000.0)&lt;br /&gt;
|-&lt;br /&gt;
|3-4&lt;br /&gt;
|Voltage&lt;br /&gt;
|int16&lt;br /&gt;
|0.01  V/bit&lt;br /&gt;
|Voltage  at this sweep point (Scale 100.0)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Enums ===&lt;br /&gt;
&lt;br /&gt;
=== PhaseMode_t (Byte 0 of Status) ===&lt;br /&gt;
0: None (Start)&lt;br /&gt;
&lt;br /&gt;
1: CIV (Constant Input Voltage)&lt;br /&gt;
&lt;br /&gt;
2: CIC (Constant Input Current)&lt;br /&gt;
&lt;br /&gt;
3: MinInputCurrent&lt;br /&gt;
&lt;br /&gt;
4: COV (Constant Output Voltage)&lt;br /&gt;
&lt;br /&gt;
5: COC (Constant Output Current)&lt;br /&gt;
&lt;br /&gt;
6: TD (Temperature Derating)&lt;br /&gt;
&lt;br /&gt;
7: Fault&lt;br /&gt;
&lt;br /&gt;
==== PhaseFault_t (Byte 1 of Status) ====&lt;br /&gt;
0: OK&lt;br /&gt;
&lt;br /&gt;
1: Config Error&lt;br /&gt;
&lt;br /&gt;
2: Input Over Voltage&lt;br /&gt;
&lt;br /&gt;
3: Output Over Voltage&lt;br /&gt;
&lt;br /&gt;
4: Output Over Current&lt;br /&gt;
&lt;br /&gt;
5: Input Over Current&lt;br /&gt;
&lt;br /&gt;
6: Input Under Current&lt;br /&gt;
&lt;br /&gt;
7: Phase Over Current&lt;br /&gt;
&lt;br /&gt;
8: General Fault&lt;br /&gt;
&lt;br /&gt;
9: Sync lost&lt;br /&gt;
#&lt;br /&gt;
Value	Name&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=CAN-bus&amp;diff=284</id>
		<title>CAN-bus</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=CAN-bus&amp;diff=284"/>
		<updated>2026-05-06T14:31:10Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* CAN-bus pinout */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The communication between the electronics within the [[Solar boat (boii)|solar boat]] is build upon the CAN-bus protocol, where the [[datalogger]] logs the CAN messages. Pinout of our Binder connectors are: &lt;br /&gt;
&lt;br /&gt;
== CAN-bus ID overview ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;markdown&amp;quot;&amp;gt;&lt;br /&gt;
0x100 EoI Battery start&lt;br /&gt;
0x10F EoI Battery end&lt;br /&gt;
&lt;br /&gt;
0x200 EoI GNNS info, u8 fix (bool); u8 sats; u8 sats used &lt;br /&gt;
0x201 EoI GNNS f32 speed, kmh; f32 direction, degrees&lt;br /&gt;
0x202 EoI GNNS f64 Latitude, degrees&lt;br /&gt;
0x203 EoI GNNS f64 Longitude, degrees&lt;br /&gt;
0x204 EoI GNNS date time, u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second&lt;br /&gt;
&lt;br /&gt;
0x400 Gan MPPT R0 : Vin, Iin,   Vout,   Iout&lt;br /&gt;
0x401 Gan MPPT R0 : Mode,   Fault,   Enabled,   TBoard,   THS&lt;br /&gt;
0x410 Gan MPPT R1 &lt;br /&gt;
0x411 Gan MPPT R1 &lt;br /&gt;
...&lt;br /&gt;
0x470 Gan MPPT R7&lt;br /&gt;
0x471 Gan MPPT R7&lt;br /&gt;
0x480 Gan MPPT F0&lt;br /&gt;
0x481 Gan MPPT F0&lt;br /&gt;
...&lt;br /&gt;
0x4F0 Gan MPPT F7&lt;br /&gt;
0x4F1 Gan MPPT F7&lt;br /&gt;
  &lt;br /&gt;
0x4E6 Gan MPPT Baseboard Front&lt;br /&gt;
0x4EE Gan MPPT Baseboard Rear&lt;br /&gt;
&lt;br /&gt;
0x700 MPPT start&lt;br /&gt;
0x77F MPPT end&lt;br /&gt;
&lt;br /&gt;
0x0900 THROTTLE To VESC&lt;br /&gt;
0x1337 THROTTLE Status&lt;br /&gt;
&lt;br /&gt;
0x0909 VESC Status message 1&lt;br /&gt;
0x0E09 VESC Status message 2&lt;br /&gt;
0x0F09 VESC Status message 3&lt;br /&gt;
0x1009 VESC Status message 4&lt;br /&gt;
0x1B09 VESC Status message 5&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== CAN-bus pinout ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Colours of the Standard Binder CAN Cable and its pinout&lt;br /&gt;
|-&lt;br /&gt;
! Binder Connector pin Number !! Wire Colour !! Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1 ||style=&amp;quot;background: #83603A;&amp;quot;| Brown || 24v Power&lt;br /&gt;
|-&lt;br /&gt;
| 2 ||style=&amp;quot;background: #FFFFFF;&amp;quot;| White || CAN H&lt;br /&gt;
|-&lt;br /&gt;
| 3 ||style=&amp;quot;background: #0065B2;&amp;quot;| Blue  || CAN L&lt;br /&gt;
|-&lt;br /&gt;
| 4 ||style=&amp;quot;background: #241F21;color:white;&amp;quot;| Black || Ground&lt;br /&gt;
|-&lt;br /&gt;
| 5 ||style=&amp;quot;background: #B2B3B7;&amp;quot;| Gray  || Safety&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Battery (MG) CAN-format (No longer used) ==&lt;br /&gt;
As of now the MG electronics [[battery]] is used in the [[Solar boat (boii)|solar boat]]. Although a new battery is being developed, this MG battery is still in use (as of September '22). Therefore the battery format is given here below.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Node-ID&lt;br /&gt;
!Index&lt;br /&gt;
!Subindex&lt;br /&gt;
!Data&lt;br /&gt;
!Type&lt;br /&gt;
!Resolution&lt;br /&gt;
|-&lt;br /&gt;
|0x302&lt;br /&gt;
|0x2005&lt;br /&gt;
|0x01&lt;br /&gt;
|Voltage &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x02&lt;br /&gt;
|Current &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x03&lt;br /&gt;
|Current Discharge &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x04&lt;br /&gt;
|Current Charge &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x05&lt;br /&gt;
|State-of-Charge &amp;lt;code&amp;gt;[%]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[%/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x06&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x07&lt;br /&gt;
|Time to Go &amp;lt;code&amp;gt;[min]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[min/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x402&lt;br /&gt;
|0x2005&lt;br /&gt;
|0x09&lt;br /&gt;
|Cell Temperature High &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0A&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0B&lt;br /&gt;
|Cell Temperature Low &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0C&lt;br /&gt;
|Cell Voltage High &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0D&lt;br /&gt;
|Cell Voltage Low &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0E&lt;br /&gt;
|BMS State&lt;br /&gt;
|uint32_t&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0F&lt;br /&gt;
|Temperature Collection &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|4x uint8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x482&lt;br /&gt;
|0x2000&lt;br /&gt;
|Cell nr&lt;br /&gt;
|Cell &amp;lt;code&amp;gt;[Cell_nr]&amp;lt;/code&amp;gt; Voltage &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x202&lt;br /&gt;
|Don't Care&lt;br /&gt;
|Don't Care&lt;br /&gt;
|Power Level &amp;lt;code&amp;gt;[%]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[%/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=CAN-bus&amp;diff=283</id>
		<title>CAN-bus</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=CAN-bus&amp;diff=283"/>
		<updated>2026-05-06T14:29:20Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* CAN-bus ID overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The communication between the electronics within the [[Solar boat (boii)|solar boat]] is build upon the CAN-bus protocol, where the [[datalogger]] logs the CAN messages. Pinout of our Binder connectors are: &lt;br /&gt;
&lt;br /&gt;
== CAN-bus ID overview ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;markdown&amp;quot;&amp;gt;&lt;br /&gt;
0x100 EoI Battery start&lt;br /&gt;
0x10F EoI Battery end&lt;br /&gt;
&lt;br /&gt;
0x200 EoI GNNS info, u8 fix (bool); u8 sats; u8 sats used &lt;br /&gt;
0x201 EoI GNNS f32 speed, kmh; f32 direction, degrees&lt;br /&gt;
0x202 EoI GNNS f64 Latitude, degrees&lt;br /&gt;
0x203 EoI GNNS f64 Longitude, degrees&lt;br /&gt;
0x204 EoI GNNS date time, u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second&lt;br /&gt;
&lt;br /&gt;
0x400 Gan MPPT R0 : Vin, Iin,   Vout,   Iout&lt;br /&gt;
0x401 Gan MPPT R0 : Mode,   Fault,   Enabled,   TBoard,   THS&lt;br /&gt;
0x410 Gan MPPT R1 &lt;br /&gt;
0x411 Gan MPPT R1 &lt;br /&gt;
...&lt;br /&gt;
0x470 Gan MPPT R7&lt;br /&gt;
0x471 Gan MPPT R7&lt;br /&gt;
0x480 Gan MPPT F0&lt;br /&gt;
0x481 Gan MPPT F0&lt;br /&gt;
...&lt;br /&gt;
0x4F0 Gan MPPT F7&lt;br /&gt;
0x4F1 Gan MPPT F7&lt;br /&gt;
  &lt;br /&gt;
0x4E6 Gan MPPT Baseboard Front&lt;br /&gt;
0x4EE Gan MPPT Baseboard Rear&lt;br /&gt;
&lt;br /&gt;
0x700 MPPT start&lt;br /&gt;
0x77F MPPT end&lt;br /&gt;
&lt;br /&gt;
0x0900 THROTTLE To VESC&lt;br /&gt;
0x1337 THROTTLE Status&lt;br /&gt;
&lt;br /&gt;
0x0909 VESC Status message 1&lt;br /&gt;
0x0E09 VESC Status message 2&lt;br /&gt;
0x0F09 VESC Status message 3&lt;br /&gt;
0x1009 VESC Status message 4&lt;br /&gt;
0x1B09 VESC Status message 5&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== CAN-bus pinout ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Colours of the Standard Binder CAN Cable and its pinout&lt;br /&gt;
|-&lt;br /&gt;
! Binder Connector pin Number !! Wire Colour !! Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1 ||style=&amp;quot;background: #83603A;&amp;quot;| Brown || 24v Power&lt;br /&gt;
|-&lt;br /&gt;
| 2 ||style=&amp;quot;background: #FFFFFF;&amp;quot;| White || CAN H&lt;br /&gt;
|-&lt;br /&gt;
| 3 ||style=&amp;quot;background: #0065B2;&amp;quot;| Blue  || CAN L&lt;br /&gt;
|-&lt;br /&gt;
| 4 ||style=&amp;quot;background: #241F21;color:white;&amp;quot;| Black || Ground&lt;br /&gt;
|-&lt;br /&gt;
| 5 ||style=&amp;quot;background: #B2B3B7;&amp;quot;| Gray  || Safety&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the M12 connectors have a different pinout and thus different colour wires. To make compatibility between systems easier the binder connectors are soldered with the wrong colored wires so that plugging in a M12 device results in the right signals being passed to the Binder connector. This Compatibility pinout is shown below:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ NON-STANDARD!! Colours when connecting an M12 cable to a binder connector NON-STANDARD!!&lt;br /&gt;
|-&lt;br /&gt;
! Binder Connector pin Number !! Wire Colour !! Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1 ||style=&amp;quot;background: #FFFFFF;&amp;quot;| White || 24v Power&lt;br /&gt;
|-&lt;br /&gt;
| 2 ||style=&amp;quot;background: #241F21;color:white;&amp;quot;| Black || CAN H&lt;br /&gt;
|-&lt;br /&gt;
| 3 ||style=&amp;quot;background: #B2B3B7;&amp;quot;| Gray  || CAN L&lt;br /&gt;
|-&lt;br /&gt;
| 4 ||style=&amp;quot;background: #0065B2;&amp;quot;| Blue  || Ground&lt;br /&gt;
|-&lt;br /&gt;
| 5 ||style=&amp;quot;background: #83603A;&amp;quot;| Brown || Safety&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Battery (MG) CAN-format (No longer used) ==&lt;br /&gt;
As of now the MG electronics [[battery]] is used in the [[Solar boat (boii)|solar boat]]. Although a new battery is being developed, this MG battery is still in use (as of September '22). Therefore the battery format is given here below.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Node-ID&lt;br /&gt;
!Index&lt;br /&gt;
!Subindex&lt;br /&gt;
!Data&lt;br /&gt;
!Type&lt;br /&gt;
!Resolution&lt;br /&gt;
|-&lt;br /&gt;
|0x302&lt;br /&gt;
|0x2005&lt;br /&gt;
|0x01&lt;br /&gt;
|Voltage &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x02&lt;br /&gt;
|Current &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x03&lt;br /&gt;
|Current Discharge &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x04&lt;br /&gt;
|Current Charge &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x05&lt;br /&gt;
|State-of-Charge &amp;lt;code&amp;gt;[%]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[%/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x06&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x07&lt;br /&gt;
|Time to Go &amp;lt;code&amp;gt;[min]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[min/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x402&lt;br /&gt;
|0x2005&lt;br /&gt;
|0x09&lt;br /&gt;
|Cell Temperature High &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0A&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0B&lt;br /&gt;
|Cell Temperature Low &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0C&lt;br /&gt;
|Cell Voltage High &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0D&lt;br /&gt;
|Cell Voltage Low &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0E&lt;br /&gt;
|BMS State&lt;br /&gt;
|uint32_t&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0F&lt;br /&gt;
|Temperature Collection &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|4x uint8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x482&lt;br /&gt;
|0x2000&lt;br /&gt;
|Cell nr&lt;br /&gt;
|Cell &amp;lt;code&amp;gt;[Cell_nr]&amp;lt;/code&amp;gt; Voltage &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x202&lt;br /&gt;
|Don't Care&lt;br /&gt;
|Don't Care&lt;br /&gt;
|Power Level &amp;lt;code&amp;gt;[%]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[%/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=CAN-bus&amp;diff=279</id>
		<title>CAN-bus</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=CAN-bus&amp;diff=279"/>
		<updated>2026-05-01T05:41:15Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* CAN-bus ID overview */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The communication between the electronics within the [[Solar boat (boii)|solar boat]] is build upon the CAN-bus protocol, where the [[datalogger]] logs the CAN messages. Pinout of our Binder connectors are: &lt;br /&gt;
&lt;br /&gt;
== CAN-bus ID overview ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;markdown&amp;quot;&amp;gt;&lt;br /&gt;
0x100 EoI Battery start&lt;br /&gt;
0x10F EoI Battery end&lt;br /&gt;
&lt;br /&gt;
0x200 EoI GNNS info, u8 fix (bool); u8 sats; u8 sats used &lt;br /&gt;
0x201 EoI GNNS f32 speed, kmh; f32 direction, degrees&lt;br /&gt;
0x202 EoI GNNS f64 Latitude, degrees&lt;br /&gt;
0x203 EoI GNNS f64 Longitude, degrees&lt;br /&gt;
0x204 EoI GNNS date time, u16 year, u8 month, u8 day, u8 hour, u8 minute, u8 second&lt;br /&gt;
&lt;br /&gt;
0x400 Gan MPPT R0 : Vin, Iin,   Vout,   Iout&lt;br /&gt;
0x401 Gan MPPT R0 : Mode,   Fault,   Enabled,   TBoard,   THS&lt;br /&gt;
0x410 Gan MPPT R1 &lt;br /&gt;
0x411 Gan MPPT R1 &lt;br /&gt;
...&lt;br /&gt;
0x470 Gan MPPT R7&lt;br /&gt;
0x471 Gan MPPT R7&lt;br /&gt;
0x480 Gan MPPT F0&lt;br /&gt;
0x481 Gan MPPT F0&lt;br /&gt;
...&lt;br /&gt;
0x4F0 Gan MPPT F0&lt;br /&gt;
0x4F1 Gan MPPT F0&lt;br /&gt;
  &lt;br /&gt;
0x4E6 Gan MPPT Baseboard Front&lt;br /&gt;
0x4EE Gan MPPT Baseboard Rear&lt;br /&gt;
&lt;br /&gt;
0x700 MPPT start&lt;br /&gt;
0x77F MPPT end&lt;br /&gt;
&lt;br /&gt;
0x0900 THROTTLE To VESC&lt;br /&gt;
0x1337 THROTTLE Status&lt;br /&gt;
&lt;br /&gt;
0x0909 VESC Status message 1&lt;br /&gt;
0x0E09 VESC Status message 2&lt;br /&gt;
0x0F09 VESC Status message 3&lt;br /&gt;
0x1009 VESC Status message 4&lt;br /&gt;
0x1B09 VESC Status message 5&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== CAN-bus pinout ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Colours of the Standard Binder CAN Cable and its pinout&lt;br /&gt;
|-&lt;br /&gt;
! Binder Connector pin Number !! Wire Colour !! Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1 ||style=&amp;quot;background: #83603A;&amp;quot;| Brown || 24v Power&lt;br /&gt;
|-&lt;br /&gt;
| 2 ||style=&amp;quot;background: #FFFFFF;&amp;quot;| White || CAN H&lt;br /&gt;
|-&lt;br /&gt;
| 3 ||style=&amp;quot;background: #0065B2;&amp;quot;| Blue  || CAN L&lt;br /&gt;
|-&lt;br /&gt;
| 4 ||style=&amp;quot;background: #241F21;color:white;&amp;quot;| Black || Ground&lt;br /&gt;
|-&lt;br /&gt;
| 5 ||style=&amp;quot;background: #B2B3B7;&amp;quot;| Gray  || Safety&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Note that the M12 connectors have a different pinout and thus different colour wires. To make compatibility between systems easier the binder connectors are soldered with the wrong colored wires so that plugging in a M12 device results in the right signals being passed to the Binder connector. This Compatibility pinout is shown below:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ NON-STANDARD!! Colours when connecting an M12 cable to a binder connector NON-STANDARD!!&lt;br /&gt;
|-&lt;br /&gt;
! Binder Connector pin Number !! Wire Colour !! Signal on wire&lt;br /&gt;
|-&lt;br /&gt;
| 1 ||style=&amp;quot;background: #FFFFFF;&amp;quot;| White || 24v Power&lt;br /&gt;
|-&lt;br /&gt;
| 2 ||style=&amp;quot;background: #241F21;color:white;&amp;quot;| Black || CAN H&lt;br /&gt;
|-&lt;br /&gt;
| 3 ||style=&amp;quot;background: #B2B3B7;&amp;quot;| Gray  || CAN L&lt;br /&gt;
|-&lt;br /&gt;
| 4 ||style=&amp;quot;background: #0065B2;&amp;quot;| Blue  || Ground&lt;br /&gt;
|-&lt;br /&gt;
| 5 ||style=&amp;quot;background: #83603A;&amp;quot;| Brown || Safety&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Battery (MG) CAN-format (No longer used) ==&lt;br /&gt;
As of now the MG electronics [[battery]] is used in the [[Solar boat (boii)|solar boat]]. Although a new battery is being developed, this MG battery is still in use (as of September '22). Therefore the battery format is given here below.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Node-ID&lt;br /&gt;
!Index&lt;br /&gt;
!Subindex&lt;br /&gt;
!Data&lt;br /&gt;
!Type&lt;br /&gt;
!Resolution&lt;br /&gt;
|-&lt;br /&gt;
|0x302&lt;br /&gt;
|0x2005&lt;br /&gt;
|0x01&lt;br /&gt;
|Voltage &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x02&lt;br /&gt;
|Current &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x03&lt;br /&gt;
|Current Discharge &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x04&lt;br /&gt;
|Current Charge &amp;lt;code&amp;gt;[A]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int16_t&lt;br /&gt;
|10 &amp;lt;code&amp;gt;[mA/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x05&lt;br /&gt;
|State-of-Charge &amp;lt;code&amp;gt;[%]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[%/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x06&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x07&lt;br /&gt;
|Time to Go &amp;lt;code&amp;gt;[min]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[min/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x402&lt;br /&gt;
|0x2005&lt;br /&gt;
|0x09&lt;br /&gt;
|Cell Temperature High &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0A&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0B&lt;br /&gt;
|Cell Temperature Low &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|int8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0C&lt;br /&gt;
|Cell Voltage High &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0D&lt;br /&gt;
|Cell Voltage Low &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0E&lt;br /&gt;
|BMS State&lt;br /&gt;
|uint32_t&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|0x0F&lt;br /&gt;
|Temperature Collection &amp;lt;code&amp;gt;[°C]&amp;lt;/code&amp;gt;&lt;br /&gt;
|4x uint8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[°C/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x482&lt;br /&gt;
|0x2000&lt;br /&gt;
|Cell nr&lt;br /&gt;
|Cell &amp;lt;code&amp;gt;[Cell_nr]&amp;lt;/code&amp;gt; Voltage &amp;lt;code&amp;gt;[V]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint16_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[mV/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|0x202&lt;br /&gt;
|Don't Care&lt;br /&gt;
|Don't Care&lt;br /&gt;
|Power Level &amp;lt;code&amp;gt;[%]&amp;lt;/code&amp;gt;&lt;br /&gt;
|uint8_t&lt;br /&gt;
|1  &amp;lt;code&amp;gt;[%/LSB]&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Throttle&amp;diff=274</id>
		<title>Throttle</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Throttle&amp;diff=274"/>
		<updated>2026-04-06T11:35:24Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* Setting the Control Mode over CAN */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The throttle controls the motor and is operated by the pilot during sailing. It controls the duty cycle of the motor via the [[CAN-bus|CAN bus]] using the standard VESC message format. &lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
[[File:Throttle Schematic.png|thumb|Throttle shown in the inactive (green led) neutral position]]&lt;br /&gt;
To use the throttle to control the motor the green illuminated ''arm-button'' will have to be pressed for a few seconds, however some conditions need to be met to ensure a safe start of the motor. &lt;br /&gt;
&lt;br /&gt;
# The ''dead-man cord'' needs to be applied to the bottom of the throttle body, should snap in place magnetically&lt;br /&gt;
# The ''throttle lever'' Should be in its neutral position, pointing straight up with a noticeable tactile bump&lt;br /&gt;
# The [[CAN-bus|CAN bus]] need to be actively acknowledging packets, which means other devices need to be active on the bus&lt;br /&gt;
# No other error should have occurred inside the electronics&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If one of these conditions is not met, the throttle will beep the number of times indicated above. &lt;br /&gt;
&lt;br /&gt;
== Programming Throttle Position ==&lt;br /&gt;
The throttle position is read out by a magnet attached tot the ''throttle lever'', because the orientation of this magnet is not know in the firmware, this position can be programmed into the EEPROM. To enter programming mode hold down the arm button during power-on. A four beep descending tone should be played, and the LED should do a double blink in red continuously. To program the throttle push lever as far forward as possible and press the button, a single beep be heard. Repeat this twice with the lever in the center and finally with the lever all the way backwards. After three beeps the throttle is reprogrammed and normal operation is resumed. To summarize:  &lt;br /&gt;
&lt;br /&gt;
# Lever in the forward position  &lt;br /&gt;
# Lever in the center position &lt;br /&gt;
# Lever in de backward position &lt;br /&gt;
#  &lt;br /&gt;
&lt;br /&gt;
If no programming is desired leave the lever alone for 10 seconds and the lever will reboot into normal mode.&lt;br /&gt;
&lt;br /&gt;
== Setting the Control Mode over CAN ==&lt;br /&gt;
The Throttle controls the VESC motodriver at 40Hz messaging rate with the default CAN format implemented inside the VESC. [https://dongilc.gitbook.io/openrobot-inc/tutorials/control-with-can More info on the format.] and [https://triforce-docs.readthedocs.io/en/latest/canbus/canbus.html Here]. The throttle supports the following modes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Different control modes of the throttle&lt;br /&gt;
|-&lt;br /&gt;
! Control Mode !! Configuration Byte Value !! Unit !! Additional data required&lt;br /&gt;
|-&lt;br /&gt;
| Filtered Duty Cycle || 0x01 || Percentage || None lever in the maximum forward position is 100%&lt;br /&gt;
|-&lt;br /&gt;
|Duty Cycle|| 0x02 || Percentage  || None lever in the maximum forward position is 100% &lt;br /&gt;
|-&lt;br /&gt;
|Current Control || 0x03 || 100mA  || Specify the max current for forward and backwards in two int16_t's&lt;br /&gt;
|-&lt;br /&gt;
|RPM Control || 0x04 || RPM  || Specify the rpm for forward and backwards in two int16_t's &lt;br /&gt;
|-&lt;br /&gt;
|Current Control Relative&lt;br /&gt;
|0x05&lt;br /&gt;
|Percentage&lt;br /&gt;
|None lever in the maximum forward position is 100%&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
These modes can be send to the throttle when the throttle is off. If the throttle is on the configuration will be ignored. A sound will play when a message is received. The message has to be send to the Throttle address 0x337, same as the reboot command. the format is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Can Message for Setting the control mode&lt;br /&gt;
|-&lt;br /&gt;
! Byte 0 !! Byte 1 !! Byte 2 !! Byte 3 !! Byte 4 !! Byte 5&lt;br /&gt;
|-&lt;br /&gt;
| 0xAA || Control type || MSB int16_t  || LSB int16_t|| MSB int16_t  || LSB int16_t&lt;br /&gt;
|-&lt;br /&gt;
| Hardcoded command|| See Table Above || colspan=2 | Max value for lever completely forward || colspan=2 | Same but for backward (Positive for reverse)&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Example command: ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;69&amp;quot;&amp;gt;&lt;br /&gt;
# duty cycle&lt;br /&gt;
cansend can0 337#AA0200000000&lt;br /&gt;
&lt;br /&gt;
# 120A current control&lt;br /&gt;
cansend can0 337#AA0304B004B0&lt;br /&gt;
&lt;br /&gt;
# relative current control&lt;br /&gt;
cansend can0 337#AA0400000000&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== CAN Diagnostics messages ==&lt;br /&gt;
The throttle also send out messages to help debugging every 200 ms. It is send on address 0x337 and has the following format:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Can Message for Diagnostic Purposes&lt;br /&gt;
|-&lt;br /&gt;
! Byte 0 !! Byte 1 !! Byte 2 !! Byte 3 !! Byte 4 !! Byte 5 !! Byte 6 !! Byte 7&lt;br /&gt;
|-&lt;br /&gt;
| MSB int16_t  || LSB int16_t || MSB int16_t  || LSB int16_t || MSB int16_t  || LSB int16_t || uint8_t || Error status&lt;br /&gt;
|-&lt;br /&gt;
| colspan=2 | Filtered lever position, from +512 to -512 || colspan=2 | Raw position reported by the magnetic encoder || colspan=2 | Raw data from the deadmanswitch ADC || Gain that is required to readout the lever position || Enum of current error state. 0x00 is good&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If any of the error status bits are set, the throttle is set into IDLE mode and will shut off the motor. The following table shows what the bitfield represents:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Different control modes of the throttle&lt;br /&gt;
|-&lt;br /&gt;
! Bit !! Enum Name !! Explanation&lt;br /&gt;
|-&lt;br /&gt;
| 0 to 2 || twiError || See next table for possible values&lt;br /&gt;
|-&lt;br /&gt;
| 3      || noLimitsLoaded || Reading of EEPROM for throttle limits failed&lt;br /&gt;
|-&lt;br /&gt;
| 4      || gainClipping || The Magnetic encoder detected no good magnetic field present&lt;br /&gt;
|-&lt;br /&gt;
| 5      || gainInvalid || The gain value read from the magnetic encoder is beyond the range specified in software&lt;br /&gt;
|-&lt;br /&gt;
| 6      || deadmanMissing || The hall sensor of the deadman-switch doesn't detect a strong enough magnet&lt;br /&gt;
|-&lt;br /&gt;
| 7      || impeadanceHigh || The impeadance of the deadman signal is too high, indicating a bad connection&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ TWI error states&lt;br /&gt;
|-&lt;br /&gt;
! Value !! Error name !! Error Description&lt;br /&gt;
|-&lt;br /&gt;
| 0 ||	TWI_ERROR_NoError              || Indicates that the command completed successfully.&lt;br /&gt;
|-&lt;br /&gt;
| 1 ||	TWI_ERROR_BusFault             || A TWI bus fault occurred while attempting to capture the bus. &lt;br /&gt;
|-&lt;br /&gt;
| 2 ||	TWI_ERROR_BusCaptureTimeout    || A timeout occurred whilst waiting for the bus to be ready. &lt;br /&gt;
|-&lt;br /&gt;
| 3 ||	TWI_ERROR_SlaveResponseTimeout || No ACK received at the nominated slave address within the timeout period.&lt;br /&gt;
|-&lt;br /&gt;
| 4 ||	TWI_ERROR_SlaveNotReady        || Slave NAKed the TWI bus START condition. &lt;br /&gt;
|-&lt;br /&gt;
| 5 ||	TWI_ERROR_SlaveNAK             || Slave NAKed whilst attempting to send data to the device. &lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
	<entry>
		<id>https://wiki.engineersofinnovation.nl/index.php?title=Throttle&amp;diff=273</id>
		<title>Throttle</title>
		<link rel="alternate" type="text/html" href="https://wiki.engineersofinnovation.nl/index.php?title=Throttle&amp;diff=273"/>
		<updated>2026-04-05T14:11:08Z</updated>

		<summary type="html">&lt;p&gt;Aran Dokoupil: /* Setting the Control Mode over CAN */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The throttle controls the motor and is operated by the pilot during sailing. It controls the duty cycle of the motor via the [[CAN-bus|CAN bus]] using the standard VESC message format. &lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
[[File:Throttle Schematic.png|thumb|Throttle shown in the inactive (green led) neutral position]]&lt;br /&gt;
To use the throttle to control the motor the green illuminated ''arm-button'' will have to be pressed for a few seconds, however some conditions need to be met to ensure a safe start of the motor. &lt;br /&gt;
&lt;br /&gt;
# The ''dead-man cord'' needs to be applied to the bottom of the throttle body, should snap in place magnetically&lt;br /&gt;
# The ''throttle lever'' Should be in its neutral position, pointing straight up with a noticeable tactile bump&lt;br /&gt;
# The [[CAN-bus|CAN bus]] need to be actively acknowledging packets, which means other devices need to be active on the bus&lt;br /&gt;
# No other error should have occurred inside the electronics&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If one of these conditions is not met, the throttle will beep the number of times indicated above. &lt;br /&gt;
&lt;br /&gt;
== Programming Throttle Position ==&lt;br /&gt;
The throttle position is read out by a magnet attached tot the ''throttle lever'', because the orientation of this magnet is not know in the firmware, this position can be programmed into the EEPROM. To enter programming mode hold down the arm button during power-on. A four beep descending tone should be played, and the LED should do a double blink in red continuously. To program the throttle push lever as far forward as possible and press the button, a single beep be heard. Repeat this twice with the lever in the center and finally with the lever all the way backwards. After three beeps the throttle is reprogrammed and normal operation is resumed. To summarize:  &lt;br /&gt;
&lt;br /&gt;
# Lever in the forward position  &lt;br /&gt;
# Lever in the center position &lt;br /&gt;
# Lever in de backward position &lt;br /&gt;
#  &lt;br /&gt;
&lt;br /&gt;
If no programming is desired leave the lever alone for 10 seconds and the lever will reboot into normal mode.&lt;br /&gt;
&lt;br /&gt;
== Setting the Control Mode over CAN ==&lt;br /&gt;
The Throttle controls the VESC motodriver at 40Hz messaging rate with the default CAN format implemented inside the VESC. [https://dongilc.gitbook.io/openrobot-inc/tutorials/control-with-can More info on the format.] The throttle supports the following modes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Different control modes of the throttle&lt;br /&gt;
|-&lt;br /&gt;
! Control Mode !! Configuration Byte Value !! Unit !! Additional data required&lt;br /&gt;
|-&lt;br /&gt;
| Filtered Duty Cycle || 0x01 || Percentage || None lever in the maximum forward position is 100%&lt;br /&gt;
|-&lt;br /&gt;
|Duty Cycle|| 0x02 || Percentage  || None lever in the maximum forward position is 100% &lt;br /&gt;
|-&lt;br /&gt;
|Current Control || 0x03 || 100mA  || Specify the max current for forward and backwards in two int16_t's&lt;br /&gt;
|-&lt;br /&gt;
|RPM Control || 0x04 || RPM  || Specify the rpm for forward and backwards in two int16_t's &lt;br /&gt;
|-&lt;br /&gt;
|Current Control Relative&lt;br /&gt;
|0x05&lt;br /&gt;
|Percentage&lt;br /&gt;
|None lever in the maximum forward position is 100%&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
These modes can be send to the throttle when the throttle is off. If the throttle is on the configuration will be ignored. A sound will play when a message is received. The message has to be send to the Throttle address 0x337, same as the reboot command. the format is as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Can Message for Setting the control mode&lt;br /&gt;
|-&lt;br /&gt;
! Byte 0 !! Byte 1 !! Byte 2 !! Byte 3 !! Byte 4 !! Byte 5&lt;br /&gt;
|-&lt;br /&gt;
| 0xAA || Control type || MSB int16_t  || LSB int16_t|| MSB int16_t  || LSB int16_t&lt;br /&gt;
|-&lt;br /&gt;
| Hardcoded command|| See Table Above || colspan=2 | Max value for lever completely forward || colspan=2 | Same but for backward (Positive for reverse)&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Example command: ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;69&amp;quot;&amp;gt;&lt;br /&gt;
# duty cycle&lt;br /&gt;
cansend can0 337#AA0200000000&lt;br /&gt;
&lt;br /&gt;
# 120A current control&lt;br /&gt;
cansend can0 337#AA0304B004B0&lt;br /&gt;
&lt;br /&gt;
# relative current control&lt;br /&gt;
cansend can0 337#AA0400000000&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== CAN Diagnostics messages ==&lt;br /&gt;
The throttle also send out messages to help debugging every 200 ms. It is send on address 0x337 and has the following format:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Can Message for Diagnostic Purposes&lt;br /&gt;
|-&lt;br /&gt;
! Byte 0 !! Byte 1 !! Byte 2 !! Byte 3 !! Byte 4 !! Byte 5 !! Byte 6 !! Byte 7&lt;br /&gt;
|-&lt;br /&gt;
| MSB int16_t  || LSB int16_t || MSB int16_t  || LSB int16_t || MSB int16_t  || LSB int16_t || uint8_t || Error status&lt;br /&gt;
|-&lt;br /&gt;
| colspan=2 | Filtered lever position, from +512 to -512 || colspan=2 | Raw position reported by the magnetic encoder || colspan=2 | Raw data from the deadmanswitch ADC || Gain that is required to readout the lever position || Enum of current error state. 0x00 is good&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If any of the error status bits are set, the throttle is set into IDLE mode and will shut off the motor. The following table shows what the bitfield represents:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Different control modes of the throttle&lt;br /&gt;
|-&lt;br /&gt;
! Bit !! Enum Name !! Explanation&lt;br /&gt;
|-&lt;br /&gt;
| 0 to 2 || twiError || See next table for possible values&lt;br /&gt;
|-&lt;br /&gt;
| 3      || noLimitsLoaded || Reading of EEPROM for throttle limits failed&lt;br /&gt;
|-&lt;br /&gt;
| 4      || gainClipping || The Magnetic encoder detected no good magnetic field present&lt;br /&gt;
|-&lt;br /&gt;
| 5      || gainInvalid || The gain value read from the magnetic encoder is beyond the range specified in software&lt;br /&gt;
|-&lt;br /&gt;
| 6      || deadmanMissing || The hall sensor of the deadman-switch doesn't detect a strong enough magnet&lt;br /&gt;
|-&lt;br /&gt;
| 7      || impeadanceHigh || The impeadance of the deadman signal is too high, indicating a bad connection&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ TWI error states&lt;br /&gt;
|-&lt;br /&gt;
! Value !! Error name !! Error Description&lt;br /&gt;
|-&lt;br /&gt;
| 0 ||	TWI_ERROR_NoError              || Indicates that the command completed successfully.&lt;br /&gt;
|-&lt;br /&gt;
| 1 ||	TWI_ERROR_BusFault             || A TWI bus fault occurred while attempting to capture the bus. &lt;br /&gt;
|-&lt;br /&gt;
| 2 ||	TWI_ERROR_BusCaptureTimeout    || A timeout occurred whilst waiting for the bus to be ready. &lt;br /&gt;
|-&lt;br /&gt;
| 3 ||	TWI_ERROR_SlaveResponseTimeout || No ACK received at the nominated slave address within the timeout period.&lt;br /&gt;
|-&lt;br /&gt;
| 4 ||	TWI_ERROR_SlaveNotReady        || Slave NAKed the TWI bus START condition. &lt;br /&gt;
|-&lt;br /&gt;
| 5 ||	TWI_ERROR_SlaveNAK             || Slave NAKed whilst attempting to send data to the device. &lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Aran Dokoupil</name></author>
	</entry>
</feed>