Skip to content

Integration: merge wingsail, rudder and pdb module branches into main - #62

Merged
georgesleen merged 245 commits into
mainfrom
integration/modules-to-main
Aug 16, 2026
Merged

Integration: merge wingsail, rudder and pdb module branches into main#62
georgesleen merged 245 commits into
mainfrom
integration/modules-to-main

Conversation

@georgesleen

@georgesleen georgesleen commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Single integration branch bringing all four module branches into main
together, so the conflicts between them are resolved once, deliberately,
rather than four times by whoever merges last.

Supersedes #59 (wingsail), #60 (rudder) and #61 (pdb). All four tips are
contained here:

Branch Tip Merge
rudder-working-branch f062937 clean
pdb-working-branch e31d2c2 clean
sense-integrated e70c420 clean
wingsail-working-branch 334ee7f 19 conflicts, resolved below

sense-integrated is used rather than sense-working-branch. The two are
siblings forked from the same commit on main, not ancestor and descendant.
sense-working-branch is a five-day prototype from October 2025, dead since;
sense-integrated is the line that continued through to the new SENSE board.

It also adds CI and a flake, described at the end.

Merged in the order rudder -> pdb -> wingsail -> sense. Rudder, pdb and sense do not conflict
with each other or with the result at all; every conflict is wingsail against the others.

How conflicts were resolved

By ownership: each controller project goes to the branch that develops it, and
shared modules go to the version that is actually safe to share.

firmware/com-modules/CANFD/can.c -> rudder. This file does not exist on
main and was added independently on the branches, so it was an add/add
conflict. Rudder's copy wins for two concrete reasons:

  • It is the only one that keeps __weak on HAL_FDCAN_RxFifo0Callback and
    HAL_TIM_PeriodElapsedCallback (rudder 2, wingsail 0, pdb 0). Without
    __weak, any controller project defining its own callback gets a duplicate
    symbol at link time, which defeats the purpose of a shared module.
  • It is the only one that counts a heartbeat TX failure
    (can_heartbeat_tx_failures++) rather than calling Error_Handler(). On the
    bench with no CAN ACK, the other two fault the whole board every 10 seconds.

library-test-project can.h / can.c -> wingsail. Note for reviewers:
rudder's committed copies of these two files contain unresolved conflict
markers
(2 in can.h, 12 in can.c). Wingsail's are clean, so ownership was
overridden here. Worth fixing on rudder-working-branch independently of this
PR.

firmware/PLRS-IMU -> rudder (e01ef3b). Wingsail carried a 160000
gitlink with no .gitmodules file at all, so the submodule was unusable
from a fresh clone and git reported "no merge base". Rudder ships a valid
.gitmodules and a newer commit, and wingsail does not reference PLRS-IMU.

Rest: wingsail-controller/* and drv-modules/nmea0183/* to wingsail;
rudder-controller/.cproject, BNO055-imu, motor-base-PID to rudder;
.gitignore is the union of both sides; the two stm32u5xx_hal_tim*.c rename
collisions were restored from clean vendored copies.

Verified afterwards: zero conflict markers anywhere in the tree, can.c has
rudder's 2 __weak markers and the non-fatal heartbeat, wingsail's main.c
retains its BRITER integration, and .gitmodules is present.

Known issues carried in, not introduced here

  • can.c is duplicated: 3 copies on rudder and wingsail, 4 on pdb. pdb's
    commit e31d2c2 "Remove hard fault on heart beat CAN error" fixed a
    project-local copy, while its shared copy still calls Error_Handler().
    Consolidating these should be follow-up work.
  • Wingsail's gated CAN TX/HB debug logging is dropped by taking rudder's
    can.c. Worth porting back on top, it is genuinely useful.
  • Two compiled .exe binaries (~346 KB each) are tracked under
    rudder-control-model/, and nmea-sample.txt is 85,193 lines.

Per the README this needs two sign-offs, one from an ELEC lead.

CI and toolchain (added on this branch)

There was no CI in this repo before. Two jobs now run on every PR, both from
the flake so local and CI use the same environment:

  • ioc-sync compares each .ioc's declared peripherals against the
    MX_<PERIPH>_Init functions in its generated main.c, and pins the CubeMX
    version. Running CubeMX itself in CI is not viable: it raises a modal
    migrate/continue/cancel dialog on any version mismatch, even in -q script
    mode, so a headless run just blocks.
  • host-tests builds the drv-modules against stub HAL headers with plain gcc
    and runs all six suites.

canfd_test previously did not compile, so the CANFD module, the one piece of
code shared by every controller, had no coverage. The stub HAL now provides
__weak, the TIM types and the FDCAN bus-error notification bits, and all six
suites pass under -Werror.

flake.nix pins STM32CubeMX to 6.15.0 (nixpkgs ships 6.17.0) via
nix/stm32cubemx.nix. Four of seven projects are already on 6.15.0. The three
that are not are recorded in tools/ioc-version-exceptions.txt with reasons;
that file is scaffolding and should be deleted once every project is
regenerated on the pinned version.

Firmware verification status

Board Verified against branch
wingsail yes, read back and byte-matched after flashing the branch tip
pdb yes, string fingerprint, 29 authored literals all present
rudder no, not read back; assumed from the local build
sense no, board carries a Heartbeat!! literal that exists nowhere in sense-integrated at any commit, so it is running an unpushed local build

MichaelGreenough and others added 30 commits April 21, 2024 23:04
Signed-off-by: Michael Greenough <43833591+MichaelGreenough@users.noreply.github.com>
Signed-off-by: Michael Greenough <43833591+MichaelGreenough@users.noreply.github.com>
Signed-off-by: Michael Greenough <43833591+MichaelGreenough@users.noreply.github.com>
Signed-off-by: Michael Greenough <43833591+MichaelGreenough@users.noreply.github.com>
Signed-off-by: Michael Greenough <43833591+MichaelGreenough@users.noreply.github.com>
Should work? unless I overlooked something or tested it wrong

Signed-off-by: alisha1697 <143849351+alisha1697@users.noreply.github.com>
Signed-off-by: alisha1697 <143849351+alisha1697@users.noreply.github.com>
Signed-off-by: alisha1697 <143849351+alisha1697@users.noreply.github.com>
Signed-off-by: alisha1697 <143849351+alisha1697@users.noreply.github.com>
Signed-off-by: alisha1697 <143849351+alisha1697@users.noreply.github.com>
Signed-off-by: hml04 <160178026+hml04@users.noreply.github.com>
Signed-off-by: ELEC_lead-ubcsailbot <electrical@ubcsailbot.org>
Signed-off-by: ELEC_lead-ubcsailbot <electrical@ubcsailbot.org>
Signed-off-by: ELEC_lead-ubcsailbot <electrical@ubcsailbot.org>
Signed-off-by: ELEC_lead-ubcsailbot <electrical@ubcsailbot.org>
Signed-off-by: alisha1697 <143849351+alisha1697@users.noreply.github.com>
Added how to power the encoder on the PCB and adjusted a couple of settings in order to get the encoder working,

Signed-off-by: JoshuaO956 <148372526+JoshuaO956@users.noreply.github.com>
georgesleen and others added 9 commits August 2, 2026 19:02
Bring in generic rudder param tuning (#56) and the CANFD heartbeat.
Conflicts resolved toward the IMU branch's control model: kept the real
irons() and the RudderSM_Update state machine over the working branch's
return-0 stub, and adopted the param-tuning CAN IDs (PID coeffs 0x200,
generic params 0x210). Pin the PLRS-IMU submodule to main (now ae4b540).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- getBinaryBits() accumulated raw AIS chars into a uint32_t before masking
- Fields spanning 6 chars (36 bits) overflowed it, including MMSI (bits 8-37)
  and longitude for message types 18/19
- Overflow silently dropped bits the final mask still needed
- Result: MMSI top bits zeroed (316xxxxxx read as 047xxxxxx), lat/lon corrupted
- Fix: widen accumulator to uint64_t, truncate to uint32_t only after masking
The CV7 sends MWV (wind angle/speed) and XDR (temperature) back-to-back
each cycle. The scheduler was calling WIND_SENSOR__CAN_transmit for both,
causing 0x040 to be sent twice per cycle — the XDR-triggered transmission
sent stale wind data since XDR only updates temp, which isn't in 0x040.
The link pointed at an absolute path in a specific contributor's home
directory, so CubeIDE dropped the folder on every other machine. The
generated makefile then had no briter-encoders entry and the build failed
with "BRITER.h: No such file or directory".

Use the same ${PARENT-2-PROJECT_LOC} form as CANFD, MDB961WP and nmea0183.
Rudder control model, state machine and PLRS-IMU integration.
Power distribution board controller and CAN heartbeat.
Resolves the three-way divergence between the module branches. Conflicts were
resolved by ownership: each controller project goes to the branch that
develops it, shared modules go to the version that is safe to share.

firmware/com-modules/CANFD/can.c -> rudder
  Only rudder's copy keeps __weak on HAL_FDCAN_RxFifo0Callback and
  HAL_TIM_PeriodElapsedCallback, which a controller project must be able to
  override without a duplicate-symbol link error. It is also the only copy
  that counts a heartbeat TX failure instead of calling Error_Handler(), which
  on the bench (no CAN ACK) would fault the board every 10s.

library-test-project can.h / can.c -> wingsail
  Rudder's committed copies contain unresolved conflict markers (2 in can.h,
  12 in can.c). Wingsail's are clean.

wingsail-controller/*, drv-modules/nmea0183/* -> wingsail
rudder-controller/.cproject, BNO055-imu, motor-base-PID -> rudder

firmware/PLRS-IMU -> rudder (e01ef3b)
  Wingsail carried a gitlink with no .gitmodules at all, so the submodule was
  unusable from a fresh clone. Rudder ships a valid .gitmodules and a newer
  commit, and wingsail does not reference PLRS-IMU.

.gitignore -> union of both sides
stm32u5xx_hal_tim.c / _tim_ex.c -> clean vendored copies (rename collision)
georgesleen and others added 8 commits August 15, 2026 22:21
First CI in this repo. Two jobs on every PR.

ioc-sync compares each .ioc's declared peripherals (Mcu.IPn) against the
MX_<PERIPH>_Init functions in the generated Core/Src/main.c. The dangerous
direction is an init present in main.c but absent from the .ioc: it sits
outside the USER CODE guards, so the next CubeMX regeneration deletes it
silently. This runs on a plain checkout with no CubeMX installed.

It does not verify pin assignments or clock trees; only CubeMX can do that.
A full regenerate-and-diff is impractical here anyway, because the six
projects were generated by five different CubeMX versions (6.10.0 through
6.16.1), so a single CI version would produce spurious diffs everywhere.

One real drift exists today and is recorded in tools/ioc-sync-ignore.txt:
library-test-project hand-writes MX_I2C3_Init and MX_I2C4_Init while its .ioc
declares only I2C1/I2C2. Documented rather than hidden, so CI stays actionable.

host-tests builds the drv-modules against the stub HAL with plain gcc and runs
the five working suites. canfd_test is excluded because it does not compile:
the stubs lack TIM_HandleTypeDef, HAL_TIM_Base_Start_IT,
FDCAN_IT_DATA_PROTOCOL_ERROR, FDCAN_IT_ARB_PROTOCOL_ERROR and __weak. That
breakage predates this branch (it fails on wingsail-working-branch too).
canfd_test never compiled, so the CANFD module, the one piece of code shared by
all three controllers, had no test coverage in CI. The stub HAL was missing
everything can.c touches outside FDCAN transmit/receive.

Added to stubs/stm32u5xx_hal.h:
  - __weak, expanding to nothing on host (target gets it from CMSIS)
  - TIM_TypeDef, TIM_HandleTypeDef and HAL_TIM_Base_Start_IT
  - TIM7 as a memory-mapped address, mirroring CMSIS, so the
    htim->Instance == TIM7 comparison works with no linker symbol

Added to stubs/stm32u5xx_hal_fdcan.h the five bus-error notification bits
CAN_Init activates: ERROR_WARNING, ERROR_PASSIVE, BUS_OFF, ARB_PROTOCOL_ERROR
and DATA_PROTOCOL_ERROR.

canfd_test.c now defines hfdcan1 and htim7, which CubeMX generates into main.c
on target and can.c references as extern.

All six suites now pass under -Werror, so CI runs `make -C tests test` directly
instead of an allowlist of the ones that happened to build.
Standardises on 6.15.0: three of six projects are already there
(FDCAN_Library_CB, pdb-controller, rudder-controller), all on STM32Cube
FW_U5 V1.8.0.

Running CubeMX itself in CI is not viable and this check exists instead.
CubeMX raises a modal migrate/continue/cancel dialog whenever a project's
version differs from the installed one, and it does so even in -q script mode
on a headless machine, so the run just blocks. It also needs a ~1GB unfree
download and a license click-through. A static check gives the guarantee that
matters (nobody silently changes the generator version) at no runtime cost.

Three projects cannot move yet and are recorded in
tools/ioc-version-exceptions.txt rather than being silently tolerated:

  wingsail-controller  6.16.1  CubeMX cannot downgrade a project, so getting
                               back to 6.15.0 needs a deliberate .ioc revert
                               and a re-verify against the flashed board
  FDCAN_Serial         6.12.1  upgrade also moves FW_U5 V1.6.0 -> V1.8.0
  Communication Module 6.10.0  two firmware packs behind, and carries the
                               I2C3/I2C4 drift that must be fixed in the same
                               regeneration
One source of truth for the toolchain. nix/stm32cubemx.nix is the nixpkgs
derivation pinned back to 6.15.0, the version this repo's .ioc files target;
nixpkgs itself ships 6.17.0. ST still serves the 6.15.0 archive, so the pin is
a version and hash change, nothing exotic.

Pinning matters because CubeMX raises a modal migrate/continue/cancel dialog
whenever a project's MxCube.Version differs from the running CubeMX, and it
does so even in -q script mode, which hangs a headless run. Everyone being on
the same CubeMX is what stops that happening.

Two devshells:
  default  CubeMX, arm-none-eabi, openocd, stlink, host test tooling
  ci       gcc, make, python3 only

CI uses .#ci rather than .#default on purpose. Neither the host tests nor the
.ioc check need CubeMX or the cross toolchain, and CubeMX alone is a ~1GB
unfree download that would dominate every run.

Verified locally through the flake: both CI jobs pass, all six test suites
green.
Placeholder hash replaced with the value nix reported. Verified: the package
builds, and loading a 6.15.0 project returns OK with no migrate dialog.
Atlas sensor board firmware. Merges clean against the other three module
branches, no conflicts.

sense-integrated rather than sense-working-branch: the two are siblings forked
from the same commit on main, not ancestor and descendant.
sense-working-branch is Michael Greenough's five-day prototype from October
2025 and has been dead since; sense-integrated is the line that continued
(Alisha Siddiqui, then Aayush Kolharkar) through to the new SENSE board, and
is what the hardware evidence points at.

Note the flashed sense module still does not match this branch. Its firmware
contains a "Heartbeat!!" literal that exists nowhere in sense-integrated at
any commit, so the board is running an unpushed local build. Reflashing from
this branch would settle it.
The host-tests job failed on the runner while passing locally:

  features.h:435:4: error: #warning _FORTIFY_SOURCE requires compiling with
  optimization (-O) [-Werror=cpp]

firmware/tests builds at -O0 with -Werror, and nixpkgs' cc-wrapper adds
-D_FORTIFY_SOURCE by default. glibc then warns that fortify needs
optimisation, and -Werror promotes that to an error.

It did not reproduce on the NixOS host even with --ignore-environment, because
the ambient hardening defaults differ from a plain Ubuntu runner. Setting
hardeningDisable explicitly makes both behave the same rather than depending
on where the shell happens to be entered.

Verified: NIX_HARDENING_ENABLE no longer lists fortify, and all six suites
pass in a clean shell.
CubeMX hardcodes $HOME/.stm32cubemx (device database, ~1.2GB) and
$HOME/STM32Cube (firmware packs). Redirecting HOME does not move them: the JDK
resolves user.home from the password database, not the environment.

Bind-mounting inside the bubblewrap sandbox the package already uses does
work. State now lives under $XDG_DATA_HOME/stm32cubemx. All that remains in
the home directory is two empty mount points, which bwrap has to create inside
the bound /home and cannot be avoided while CubeMX insists on those paths.

The wrapper that creates the source directories is single-quoted so $HOME
expands when it runs rather than during the build, where HOME is
/homeless-shelter.

Verified: config load returns OK, writes land in XDG, and the home directory
gains only two 0-byte directories.
Firmware flashed for an on-water test must come from a tagged commit on main,
not a working branch or a local build. A tag is the only reliable answer to
"what was running?" after the fact.

This session found the concrete case: the sense board runs code containing a
string literal that exists on no branch, so it cannot be reconstructed.
@georgesleen
georgesleen force-pushed the integration/modules-to-main branch from 7f2c783 to 1eb479e Compare August 16, 2026 06:35
@georgesleen
georgesleen merged commit eb4e617 into main Aug 16, 2026
2 checks passed
@sailbot-eleclead
sailbot-eleclead deleted the integration/modules-to-main branch August 16, 2026 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.