diff --git a/.github/workflows/CICD_build.yml b/.github/workflows/CICD_build.yml index d95163b03..8fb4438f7 100644 --- a/.github/workflows/CICD_build.yml +++ b/.github/workflows/CICD_build.yml @@ -8,7 +8,7 @@ name: CI Build Linux # FLASH; native SITL on Windows is covered by the SITL workflow.) # # - Push/PR on ark-release: build + artifact. -# - Version tags (vMAJOR.MINOR[.PATCH][-suffix]): GitHub Release with obj/*.hex. +# - Version tags (vMAJOR.MINOR[.PATCH][-suffix]): GitHub Release with obj/*.hex and the G431 *.uavcan.bin. # - schedule / optional dispatch: rolling `nightly` prerelease (assets replaced). on: @@ -112,6 +112,7 @@ jobs: name: ARK32-binaries path: | obj/*.hex + obj/*.uavcan.bin retention-days: 60 if-no-files-found: error @@ -122,7 +123,9 @@ jobs: github.repository == 'ARK-Electronics/ARK32' uses: softprops/action-gh-release@v2 with: - files: obj/*.hex + files: | + obj/*.hex + obj/*.uavcan.bin fail_on_unmatched_files: true generate_release_notes: true env: @@ -155,7 +158,9 @@ jobs: prerelease: true make_latest: false target_commitish: ${{ github.sha }} - files: obj/*.hex + files: | + obj/*.hex + obj/*.uavcan.bin fail_on_unmatched_files: true body: | Rolling nightly firmware build for the ARK32 configurator and production line. diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 51009c92a..e88b12bb7 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -88,10 +88,12 @@ jobs: - name: Build ARK + codegen check run: make codegen-check-ark -j$(nproc) - # Production full-flash image (BL + app + factory EEPROM). Fails if the - # 32 KiB layout or ARK 4IN1 ship defaults regress. See factory/README.md. + # Production full-flash images (BL + app + factory EEPROM). Fails if the + # flash layout or ARK ship defaults regress. See factory/README.md. + # Covers ARK_4IN1_F051 (32 KiB + committed BL) and ARK_G431_CAN (128 KiB + # + committed AM32_G431_BOOTLOADER_ARKG4_CAN under Bootloaders/). factory-image: - name: factory image (ARK 4IN1 full flash) + name: factory image (ARK 4IN1 + G431 CAN) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -101,17 +103,52 @@ jobs: - name: Install Arm toolchain uses: ./.github/actions/setup-arm-sdk - - name: Build + verify factory full-flash image + - name: Build + verify factory full-flash images run: make factory-image-check -j$(nproc) - name: Upload factory artifacts if: always() uses: actions/upload-artifact@v4 with: - name: ark-4in1-factory-image + name: ark-factory-images path: | - obj/ARK32_ARK_4IN1_F051_*.factory.bin + obj/ARK32_ARK_4IN1_F051_*.factory.img obj/ARK32_ARK_4IN1_F051_*.factory.hex obj/ARK32_ARK_4IN1_F051_*.eeprom.bin + obj/ARK32_ARK_G431_CAN_*.factory.img + obj/ARK32_ARK_G431_CAN_*.factory.hex + obj/ARK32_ARK_G431_CAN_*.eeprom.bin + obj/*.uavcan.bin if-no-files-found: error retention-days: 30 + + # Keep ARK_G431_CAN building under LTO so shared control-path PRs cannot + # silently break the 12S CAN target (no size gate yet — flash is ample). + build-ark-g431-can: + name: build ARK_G431_CAN + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Arm toolchain + run: | + make arm_sdk_install + make arm_sdk_check + + - name: Build ARK_G431_CAN + run: make ARK_G431_CAN -j$(nproc) + + - name: Upload G431 CAN binary + if: always() + uses: actions/upload-artifact@v4 + with: + name: ark-g431-can + path: | + obj/ARK32_ARK_G431_CAN_*.elf + obj/ARK32_ARK_G431_CAN_*.bin + obj/ARK32_ARK_G431_CAN_*.hex + obj/*.uavcan.bin + if-no-files-found: ignore + retention-days: 14 diff --git a/.gitignore b/.gitignore index 48aa92ea0..b79c5ab10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ obj/ +bootloader/obj/ downloads/ tools/ .vscode diff --git a/Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin b/Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin new file mode 100644 index 000000000..f0c9a1e1c Binary files /dev/null and b/Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin differ diff --git a/Bootloaders/README.md b/Bootloaders/README.md index 5092b9529..7c576cfb3 100644 --- a/Bootloaders/README.md +++ b/Bootloaders/README.md @@ -1,15 +1,19 @@ # Bootloader images -Prebuilt **[ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader)** binaries, committed so the app can embed one and rewrite the bootloader region at boot (see `Src/bootloader_update.c`). +Prebuilt **[ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader)** binaries, committed so: -[ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader) is ARK’s fork of [upstream AM32-bootloader](https://github.com/am32-firmware/AM32-bootloader). For ARK hardware (ARK 4IN1 and related), always take images from **ARK32-bootloader releases**, not from the upstream bootloader repo alone. +1. **F051 (ARK 4IN1):** the app embeds one and rewrites the bootloader region at boot (see `Src/bootloader_update.c`). +2. **G431 CAN (ARK 12S CAN):** the app always embeds one (including `HWCI_PERF=1`) and rewrites the on-chip BL if it differs — so a manual or PX4 app-only flash still lands UAVCAN hw 0.71. `make factory-image-g431-can` also places the same image at `0x08000000` for blank-chip programming. -These files are release artifacts converted from the published Intel HEX to a flat binary and checked in. They are stored as `.bin` rather than a C array so the bytes stay comparable against a release asset (a hex dump in a header cannot). +[ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader) is ARK’s fork of [upstream AM32-bootloader](https://github.com/am32-firmware/AM32-bootloader). For ARK hardware, always take images from **ARK32-bootloader**, not from the upstream bootloader repo alone. + +These files are release (or branch) artifacts as flat binaries. They are stored as `.bin` rather than a C array so the bytes stay comparable against a build or release asset (a hex dump in a header cannot). | File | Target | Signal pin | Source | |---|---|---|---| | `AM32_F051_BOOTLOADER_ARK4IN1_V18.bin` | STM32F051, ARK 4IN1 (default embed) | PB4; **PA15 nSLEEP low** | [ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader) `master` (`cdce0a2`, #4); product `AM32_F051_BOOTLOADER_ARK4IN1` | | `AM32_F051_BOOTLOADER_PB4_V18.bin` | STM32F051 generic PB4 (reference only) | PB4 | [ARK32-bootloader v18.0.0](https://github.com/ARK-Electronics/ARK32-bootloader/releases/tag/v18.0.0) (`0d667c5`), asset `AM32_F051_BOOTLOADER_PB4_V18.hex` | +| `AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin` | STM32G431/G491, ARK 12S CAN ESC (`ARK_G431_CAN`) | PB4 (also FDCAN TX PB9 / RX PA11); **DRV ENABLE (PC9) held low** | **In-tree** [`bootloader/`](../bootloader/) (`make bootloader-g431-can`). From ARK32-bootloader `feat/ark-g431-can-dual-protocol` (`060c417`, UAVCAN hw **0.71**) plus CAN FD. | | Resource | URL | |---|---| @@ -19,20 +23,37 @@ These files are release artifacts converted from the published Intel HEX to a fl `.gitignore` ignores `*.bin` repo-wide; this directory is un-ignored explicitly. -## How the image is used +## How the images are used + +### F051 app-side embed `Src/bl_image.S` pulls the file in with `.incbin` and pads it to the bootloader region size with `0xFF`. The linker script places it in a dedicated `.bl_image` section and asserts that it is exactly the region size and that it does not live inside the region it will erase. -The image is linked for **all F051 targets by default**, including `HWCI_PERF=1` -(LTO leaves enough flash for the image and the perf struct). To strip it for a -size emergency or pure perf A/B: +**F051** embeds on release builds by default. `HWCI_PERF=1` does **not** embed it (flash goes to the perf struct; the HWCI rig already has a bootloader on-chip). + +**G431 CAN** always embeds (16 KiB region, including `HWCI_PERF=1`). First app boot rewrites `0x08000000..0x08003FFF` if the on-chip BL differs. + +To strip the embed: ```bash make ARK_4IN1_F051 EMBED_BOOTLOADER=0 -make ARK_4IN1_F051 HWCI_PERF=1 NO_EMBED_BL=1 +make ARK_G431_CAN NO_EMBED_BL=1 ``` Either kill switch disables embed (`EMBED_BOOTLOADER=0` or `NO_EMBED_BL=1`). +`HWCI_PERF=1` also disables embed automatically on F051 only. + +### G431 CAN factory image + +`BL_IMAGE_G431_CAN` in the `Makefile` points at `AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin`. The same file is app-side embedded (above) and consumed by: + +```bash +make factory-image-g431-can +# or +make factory-image-check +``` + +The factory builder places it at `0x08000000` (16 KiB region) and pads with `0xFF` to the app base at `0x08004000`. See [factory/README.md](../factory/README.md). ## Risk / recovery @@ -45,6 +66,8 @@ App-side BL rewrite erases flash pages at `0x08000000` (reset vectors and the on ## Updating +### F051 + 1. Build or download the ARK 4IN1 product from [ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader) (`make AM32_F051_BOOTLOADER_ARK4IN1`), or convert a release `.hex` with the pinned toolchain. 2. Install the flat binary: @@ -59,7 +82,20 @@ App-side BL rewrite erases flash pages at `0x08000000` (reset vectors and the on 3. Point `BL_IMAGE_F051` in the `Makefile` at the new path if the filename changed (default is already the ARK4IN1 blob). 4. Update the table above with the release tag / source commit. -5. Rebuild — the `.bin` is an explicit prerequisite of the `.elf`, so the change is picked up. +5. Rebuild — the `.bin` is an explicit prerequisite of the F051 `.elf`, so the change is picked up. + +### G431 CAN + +Source of truth is [`bootloader/`](../bootloader/), not a separate checkout: + +```sh +make bootloader-g431-can +# installs Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin +make ARK_G431_CAN +make factory-image-g431-can +``` + +`make ARK_G431_CAN` rebuilds the image when `bootloader/` sources change. Point `BL_IMAGE_G431_CAN` in the `Makefile` at the new path only if the filename changed. **Do not embed the generic `…_PB4` image on ARK 4IN1** if the chip has ARK4IN1 BL: first boot would rewrite PA15 nSLEEP-off back to stock. @@ -76,4 +112,11 @@ cmp -n "$(wc -c < Bootloaders/AM32_F051_BOOTLOADER_ARK4IN1_V18.bin)" \ /tmp/embedded.bin Bootloaders/AM32_F051_BOOTLOADER_ARK4IN1_V18.bin ``` -The compare is limited to the source file's length because `.bl_image` is padded out to the full 4096-byte region with `0xFF`. +The compare is limited to the source file's length because `.bl_image` is padded out to the full region (F051 4096, G431 CAN 16384) with `0xFF`. For G431: + +```sh +arm-none-eabi-objcopy -O binary --only-section=.bl_image \ + obj/AM32_ARK_G431_CAN_*.elf /tmp/embedded.bin +cmp -n "$(wc -c < Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin)" \ + /tmp/embedded.bin Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin +``` diff --git a/Inc/bootloader_update.h b/Inc/bootloader_update.h index f8286c266..9547e4aa0 100644 --- a/Inc/bootloader_update.h +++ b/Inc/bootloader_update.h @@ -3,7 +3,8 @@ /* * Optionally rewrite the AM32 bootloader from an image embedded in the app. * No-op when EMBED_BOOTLOADER is not defined (e.g. EMBED_BOOTLOADER=0 / - * NO_EMBED_BL=1 kill switches). + * NO_EMBED_BL=1 kill switches, or F051 HWCI_PERF=1). G431 CAN embeds + * by default including HWCI so a field app flash still refreshes the BL. * * Call once early after clocks/flash are usable and before motors run. * Arms IWDG only when an update is about to run. Calls NVIC_SystemReset() diff --git a/Inc/common.h b/Inc/common.h index a110d9818..d63935e1b 100644 --- a/Inc/common.h +++ b/Inc/common.h @@ -79,7 +79,12 @@ typedef struct fastPID { } fastPID; /* - input signal types + * input signal types (eeprom input_type / DroneCAN INPUT_SIGNAL_TYPE) + * + * AUTO_IN: detect first wire protocol (DShot or PWM). DroneCAN may run in + * parallel; while a RawCommand stream is live it owns throttle, otherwise + * the detected wire input is used. + * DRONECAN_IN: exclusive CAN — DShot/PWM capture IRQs disabled at init. */ enum inputType { AUTO_IN = 0, diff --git a/Inc/debug_uart.h b/Inc/debug_uart.h new file mode 100644 index 000000000..23b1b4ab0 --- /dev/null +++ b/Inc/debug_uart.h @@ -0,0 +1,62 @@ +/* + * debug_uart.h — ARK 12S CAN debug console (USART2 TX on PB3 AF7 @ 115200) + * + * Same pinout as AM32-bootloader#60 / ARK bootloader USE_DEBUG_UART. + * App uses polled TX (not DMA1 CH1 — that is the DShot capture channel). + * + * Opt in with USE_DEBUG_UART on the target (ARK_G431_CAN). + * + * ISR-safe path: debugUartLog*() queues events; debugUartService() drains + * them from the main loop with blocking UART writes. + */ +#ifndef DEBUG_UART_H_ +#define DEBUG_UART_H_ + +#include +#include "targets.h" + +/* Event codes for debugUartLogEvent() — always defined so call sites compile + * on targets without USE_DEBUG_UART (stubs are no-ops). */ +enum { + DBG_EVT_BOOT = 1, + DBG_EVT_NFAULT, /* gate-driver nFAULT, unclassified (GDF etc.) */ + DBG_EVT_NFAULT_UVLO, /* nFAULT + low bus → undervoltage class */ + DBG_EVT_NFAULT_OCP, /* nFAULT + high current/drive → overcurrent class */ + DBG_EVT_NFAULT_OTW, /* nFAULT + high temp → overtemperature class */ + DBG_EVT_STUCK, + DBG_EVT_LVC, + DBG_EVT_SIGNAL, + DBG_EVT_DESYNC, + DBG_EVT_ACQ_DESYNC, + DBG_EVT_STALL, + DBG_EVT_GD_WAKE, + DBG_EVT_GD_SLEEP, +}; + +#ifdef USE_DEBUG_UART + +void debugUartInit(void); +/* Blocking print (main / init only — not from ISRs). */ +void debugUartPrint(const char *msg); +void debugUartPrintf(const char *fmt, ...); +/* Queue a state transition (ISR-safe). */ +void debugUartLogState(uint8_t from, uint8_t to); +/* Queue a named event (ISR-safe). */ +void debugUartLogEvent(uint8_t event); +/* Drain the queue — call once per main-loop tick. */ +void debugUartService(void); + +#else /* !USE_DEBUG_UART */ + +/* Macros (not empty inlines): format-string args are not evaluated and + * do not land in F051 .rodata when the target has no debug UART. */ +# define debugUartInit() ((void)0) +# define debugUartPrint(msg) ((void)0) +# define debugUartPrintf(...) ((void)0) +# define debugUartLogState(from, to) ((void)0) +# define debugUartLogEvent(event) ((void)0) +# define debugUartService() ((void)0) + +#endif /* USE_DEBUG_UART */ + +#endif /* DEBUG_UART_H_ */ diff --git a/Inc/eeprom.h b/Inc/eeprom.h index c014521f4..cb1cc0b95 100644 --- a/Inc/eeprom.h +++ b/Inc/eeprom.h @@ -82,7 +82,17 @@ typedef union EEprom_u { uint8_t filter_hz; // 181 uint8_t debug_rate; // 182 uint8_t term_enable; // 183 - uint8_t reserved[8]; // 184-191 + /* Width in C of the thermal foldback ramp below full + * derate; the onset is limits.temperature. Lives in the + * can block because that is where the free bytes are - + * the limiter itself is target-agnostic, and 0xFF on a + * board that never wrote this page is coerced in + * settings.c. */ + uint8_t temp_derate_band; // 184 + /* CAN FD data-phase bitrate in Mbps: 0 = auto-match + * the host (1/2/4/5), 1/2/4/5 = pinned. Byte 185. */ + uint8_t fd_mbps; // 185 + uint8_t reserved[6]; // 186-191 } can; }; uint8_t buffer[192]; diff --git a/Inc/esc_state.h b/Inc/esc_state.h index 14acb55d4..25a604b03 100644 --- a/Inc/esc_state.h +++ b/Inc/esc_state.h @@ -36,7 +36,9 @@ extern volatile esc_state_t esc_state; /* Saturating count of named transitions that violated the edge table. */ extern volatile uint16_t esc_illegal_edge_count; +#ifdef USE_DEBUG_UART const char *escStateName(esc_state_t s); +#endif esc_state_t escGetState(void); /* --- predicates (flag-backed; ISR may update flags before reconcile) --- */ diff --git a/Inc/faults.h b/Inc/faults.h index 0950638e1..12d572ce2 100644 --- a/Inc/faults.h +++ b/Inc/faults.h @@ -9,14 +9,32 @@ #include -/* Optional fault IDs for future telemetry / logging (not yet exposed). */ +/* Optional fault IDs for telemetry / logging. */ typedef enum { FAULT_NONE = 0, FAULT_STUCK_ROTOR, FAULT_SIGNAL_TIMEOUT, FAULT_BEMF_STALL, + /* + * Gate-driver nFAULT classes (DRV8350H / DRV8328). The pin is a single + * open-drain OR of UVLO/OCP/OTW/GDF — no SPI status on hardware-interface + * parts — so these are ADC guesses at the latch edge (V / I / temp). + */ + FAULT_GD_UVLO, /* low bus voltage guess */ + FAULT_GD_OCP, /* high current / was-driving guess */ + FAULT_GD_OTW, /* high temperature guess */ + FAULT_GD_UNKNOWN, /* nFAULT with no clear ADC signature */ } fault_id_t; +/* + * Latched gate-driver fault cause while nFAULT is active or sticky. + * FAULT_NONE when healthy. Best-effort only — not a DRV status register. + */ +fault_id_t faultGateDriverCause(void); + +/* Short name for logs ("UVLO", "OCP", "OTW", "nFAULT", or ""). */ +const char *faultGateDriverCauseName(fault_id_t cause); + /* * Stuck-rotor protection (was the top of setInput after throttle map). * If bemf_timeout_happened has exceeded the threshold, cut drive and latch. @@ -115,6 +133,27 @@ uint8_t faultDesyncRestartHoldoffActive(void); */ extern volatile uint32_t fault_stall_trips; +/* + * Set once the loop has genuinely established (zero_crosses > 100) while + * the motor is driving; cleared when running stops and with the other + * counters in faultErrorCountReset(). Not the ESC `armed` flag — that + * stays 1 across PX4 arm/disarm. + * + * The error-count rails cannot gate on the instantaneous zero_crosses: + * that counter is reset in ten places, including by the desync and stall + * handling being measured. An established run that desyncs therefore + * re-enters the jump check with a count rebuilt from zero (observed: 75 on + * a steady 900-throttle spool), and a > 100 test would misfile it as an + * acquisition kick and drop it from esc.Status.error_count / NodeStatus. + * A latch has the arm-cycle lifetime the DSDL error_count wants, so the + * gate keeps its intent - a start that NEVER got going never sets it - + * without depending on a counter the fault path clears. + * + * volatile: written from tenKhzRoutine (ISR context), read from the main + * loop (telemetry). + */ +extern volatile uint8_t fault_run_established; + /* * Acquisition-rail episode charges this power cycle ("start resisted": * batches of early desyncs that never reached acquisition - see @@ -145,9 +184,9 @@ extern volatile uint8_t fault_acq_resist_events; * - desync_happened : the jump-desync check in runtimeProcessDesyncCheck * - fault_stall_trips : the INTERVAL_TIMER stall rail * - * Both are zeroed on arm (0->1) via faultErrorCountReset() so a clean re-arm - * reports error_count 0; the flight controller then sees only faults that - * occurred after this arm. Do not clear only one addend - lifetimes must match. + * Both are zeroed on ESC armed 0->1 and on FC ArmingStatus 0->1 via + * faultErrorCountReset() so a clean re-arm reports error_count 0. Do not + * clear only one addend - lifetimes must match. * * Deliberately NOT additional addends, because each already funnels into the * stall rail and would double-count one physical failure: @@ -161,7 +200,28 @@ extern volatile uint8_t fault_acq_resist_events; */ uint32_t faultErrorCount(void); -/* Zero both error_count addends. Call only on the armed 0->1 edge. */ +/* Zero both error_count addends and the established-run latch. Call on + * ESC armed 0->1 and on FC ArmingStatus 0->1. */ void faultErrorCountReset(void); +/* + * Gate-driver nFAULT poll (DRV8350H FAULT_N on ARK_G431_CAN, DRV8328 on + * ARK_4IN1_F051 when the pin is defined). + * + * The DRV pin is a single open-drain OR of VDS OCP, UVLO, OTW, and GDF — + * hardware interface parts (…H) do not expose SPI status. On assert we + * classify a best-effort cause from MCU bus voltage / current / temp + * (faultGateDriverCause), cut PWM, latch ESC_FAULT_STUCK, log the specific + * cause, and at zero throttle pulse DRV ENABLE (or clear the software latch + * after ENABLE-low sleep) so latched trips can clear without reboot. + * + * Call from the main loop (not the 20 kHz path). No-op without the pin. + */ +void faultPollGateDriver(void); + +/* 1 while a real gate-driver trip is latched, or nFAULT is low while the + * driver is awake and past post-wake settle. Sleep (ENABLE/nSLEEP low) does + * not count — the pin is asserted by VCP UVLO then. */ +uint8_t faultGateDriverFaultActive(void); + #endif /* FAULTS_H_ */ diff --git a/Inc/gate_driver.h b/Inc/gate_driver.h index 4d13ee82d..0ff2a2218 100644 --- a/Inc/gate_driver.h +++ b/Inc/gate_driver.h @@ -1,8 +1,11 @@ /* * gate_driver.h - sleep/wake for DRV8350 ENABLE / DRV8328 nSLEEP * - * Active-high run: low = sleep, high = awake. After rising edge, block ~1 ms - * (datasheet tWAKE) before PWM/commutation — IC bias must settle; pin stays high. + * Active-high run: low = sleep, high = awake. After rising edge, block for + * t_WAKE settle (1 ms DRV8328; 3 ms DRV8350H) before PWM/commutation. + * + * DRV8350H (ARK 12S CAN, USE_DRV_ENABLE): wake forces bridge inputs inactive + * (allOff + duty 0) before ENABLE rises — avoids the common sleep-exit blip. */ #ifndef GATE_DRIVER_H_ #define GATE_DRIVER_H_ @@ -20,6 +23,10 @@ void gateDriverWakeBlocking(void); void gateDriverSleep(void); void gateDriverFaultResetPulse(void); void gateDriverPoll(void); +/* 1 when ENABLE/nSLEEP is high and nFAULT is past the post-wake settle. + * Pin is asserted in sleep (VCP UVLO) and can glitch on the first PWM. */ +uint8_t gateDriverNfaultPinTrusted(void); +void gateDriverNfaultGraceTick(void); static inline uint8_t gateDriverIsAwake(void) { @@ -46,6 +53,11 @@ static inline uint8_t gateDriverIsAwake(void) { return 1; } +static inline uint8_t gateDriverNfaultPinTrusted(void) +{ + return 1; +} +static inline void gateDriverNfaultGraceTick(void) {} static inline void gateDriverEnsure(void) {} #endif diff --git a/Inc/motor_runtime.h b/Inc/motor_runtime.h index 98012f511..1e981e6c9 100644 --- a/Inc/motor_runtime.h +++ b/Inc/motor_runtime.h @@ -126,6 +126,28 @@ extern fastPID stallPid; extern char use_speed_control_loop; extern char use_current_limit; extern int16_t use_current_limit_adjust; +/* Protection ceilings (duty units) + the filtered die temperature the + * thermal one derates from. See runtimeThermalLimitTick() / setInput(). */ +extern volatile uint16_t thermal_duty_ceiling; +extern volatile uint16_t duty_limit_ceiling; +extern volatile int16_t degrees_celsius_filtered; +/* + * Thermal foldback ramp width, in C below full derate. The onset is + * eepromBuffer.limits.temperature; authority falls linearly from there to + * THERMAL_CEIL_FLOOR over this many degrees. Tunable (DroneCAN + * TEMP_DERATE_BAND) because the right slope depends on how hard the + * airframe loads the ESC: a wide band trades a longer partial-power + * excursion for a gentler thrust change, a narrow one the reverse. + * + * Default 15 C keeps the whole ramp inside the die sensor's useful range + * on ARK_G431_CAN: the G4 factory calibration points are 30 C and 110 C + * (stm32g4xx_ll_adc.h), so 105 -> 120 C only extrapolates over the last + * 10 C, and full derate lands below the part's 125 C junction limit. + */ +#define THERMAL_DERATE_BAND_DEFAULT 15 +#define THERMAL_DERATE_BAND_MIN 5 +#define THERMAL_DERATE_BAND_MAX 40 +extern uint8_t temp_derate_band_c; extern int32_t input_override; extern int32_t stall_protection_adjust; extern uint16_t stall_protect_target_interval; diff --git a/Inc/runtime_loop.h b/Inc/runtime_loop.h index e79f23057..645aac4ed 100644 --- a/Inc/runtime_loop.h +++ b/Inc/runtime_loop.h @@ -31,6 +31,10 @@ void runtimeSendTelemetryIfNeeded(void); /* ADC sample + battery LVC + optional ADC input path. */ void runtimeProcessAdcAndProtections(void); +/* Filter the die temperature and recompute thermal_duty_ceiling. Must be + * called at 1 kHz (from the ADC block) - the IIR time constant assumes it. */ +void runtimeThermalLimitTick(void); + /* * Brushless running path (limits, filter, stall) or sine/stepper branch, * matching the previous if (stepper_sine == 0) / else structure. diff --git a/Inc/settings.h b/Inc/settings.h index 396973c0a..f3646cb95 100644 --- a/Inc/settings.h +++ b/Inc/settings.h @@ -10,5 +10,7 @@ void loadEEpromSettings(void); void saveEEpromSettings(void); void checkDeviceInfo(void); +/* Recompute kV/poles-derived runtime tables after a live param change. */ +void applyMotorIdentitySettings(void); #endif /* SETTINGS_H_ */ diff --git a/Inc/targets.h b/Inc/targets.h index 646524cc3..38e66c93f 100644 --- a/Inc/targets.h +++ b/Inc/targets.h @@ -53,6 +53,192 @@ # define TARGET_MIN_BEMF_COUNTS 3 #endif +#ifdef ARK_G431_CAN +# define FIRMWARE_NAME "ARK_G431_CAN" +# define FILE_NAME "ARK_G431_CAN" +# define DRONECAN_SUPPORT 1 +# define DRONECAN_NODE_NAME "com.ark_12s.esc" +/* UAVCAN hw version: PX4 board_id = (major << 8) | minor. + * 0.71 → 71. Away from stock AM32 2.3 (515), ARK cannode 0.80-0.93, + * and ArduPilot/AP_Periph's reserved 1000-19999 block. */ +# define DRONECAN_HW_VERSION_MAJOR 0 +# define DRONECAN_HW_VERSION_MINOR 71 +# define DEAD_TIME 80 /* 500 ns @ 160 MHz TIM1 (CKD=1) */ + +# define HARDWARE_GROUP_G4_E +# define TARGET_STALL_PROTECTION_INTERVAL 20000 +/* + * Comparator output blanking window, in TIM1 ticks (6.25 ns @ 160 MHz). + * The G4 COMP can gate its own output from TIM1 OC5 while OC5REF is high; + * the F051's COMP cannot, so this has no 4IN1 equivalent. Armed per + * commutation step in changeCompInput() — ST force-low blanking is only + * safe on falling-BEMF steps (see doc/g431-comp-blanking.md). + * + * Sizing. OC5 with CCR5=N holds OC5REF high for CNT < N, so the window is + * the first N ticks of every PWM period, and the question is where the + * switching transient actually sits inside that period: + * + * CNT == 0 OCxREF rises; the low side turns OFF immediately. + * CNT == DEAD_TIME the high side turns ON. This is the hard event — + * the phase node slews a 50 V bus in 55-81 ns + * (measured), ~500 ns to settle including the gate + * transition, into low-side body-diode recovery. + * + * So the transient is at 500 ns, not at 0. The original 80 ticks blanked + * [0, 500 ns) — the quiet dead-time interval — and un-gated the comparator + * at the exact instant of the edge it was meant to reject. 160 ticks = + * 1.0 us = DEAD_TIME plus the ~500 ns edge-and-settle, which is the + * smallest window that actually covers it. + * + * Upper bound. Crossings that physically occur in the PWM off-window are + * invisible to the comparator and are registered after turn-on (see the + * turn-on-pileup compensation in bemf_zc.c); bench puts that detection + * peak at 1.3-2.6 us, so 1.0 us clears it. On the steps that blank at all, + * a crossing inside the window is not suppressed anyway — it is delayed to + * the window close, at most 1.0 us late. + * + * Duty headroom. At the target's default 24 kHz (arr 6665) the 6% startup + * tier is 400 ticks of on-time, so the window is 40% of it and the + * comparator is live over [1.0, 2.5] us — which only just contains the + * pile-up peak. That tier is the first thing to check on the bench, and + * this is the single knob to sweep. + */ +# define COMP_BLANK_TICKS 160 +/* + * Post-commutation search blank, in 64ths of the commutation interval. An + * edge before this is the freewheel demag clamp on the phase that was just + * released, not a crossing, so COMP1_2_3_IRQHandler discards it. + * + * 32 (= interval/2) is the historical value and the default; this exists to + * make it sweepable, because a longer software blank is the closest thing G4 + * offers to the "run the comparator slow during startup" trick that other + * families get from a COMP power mode. G4 comparators are fixed ultra-fast + * and there is no speed/consumption field to trade away, so rejecting noise + * in TIME is the only remaining axis. + * + * There is a hard ceiling and it is worth knowing before turning this up. + * The schedule commutates waitTime = interval/2 - advance after a crossing, + * so the NEXT crossing is due at interval/2 + advance, i.e. (32 + advance) + * 64ths after commutation. auto_advance runs 13..23, putting the expected + * crossing at 45..55/64. A blank past ~40 starts eating the margin at the + * low-advance end, and past 45 it rejects the crossing it is waiting for. + * Raise this only with bench data, and only against the advance schedule + * actually in use. + * + * #ifndef so an A/B sweep can override it with -DZC_SEARCH_BLANK_64THS=N + * the same way the ZC_FILTER_* tiers are overridden (runtime_loop.c). The + * bare #define collided with -Werror on redefinition, which is why the + * knob this comment calls sweepable was not actually sweepable. + * + * A 32/36/40 sweep was attempted on the RCINPOWER GTS 4715-360KV + 14x5.5 + * at 12S and its result was WITHDRAWN as invalid - see below - so this + * stays at the long-standing 32 until someone re-runs it properly. + * + * How it went wrong, because the trap is easy to repeat: the override was + * applied with `make ARK_G431_CAN EXTRA_CFLAGS=-DZC_SEARCH_BLANK_64THS=N` + * and the board was then programmed with `hwci flash`, which calls + * build_firmware() -> plain `make `. That recompiles WITHOUT the + * override (verified: the .bin md5 reverts), so all three "different" + * builds were the same default binary and the differences between runs + * were run-to-run variance. Flash a swept build with + * hwci flash --bin obj/AM32_ARK_G431_CAN_.bin + * which skips the rebuild, and confirm the value actually took before + * trusting any comparison. + * + * Still valid from that session, measured on the default build: 13 cold + * starts at 10/12/15/20% all acquired in 10-30 ms, and a 25% cold start + * stalls (rotor reaches ~370 rpm, loses sync, 29-37 A while barely + * turning). That stall is torque-vs-inertia at the open-loop handoff, not + * crossing detection - the lever is startup_power / the startup duty + * ceiling, and no blank value was ever shown to change it. + */ +# ifndef ZC_SEARCH_BLANK_64THS +# define ZC_SEARCH_BLANK_64THS 32 +# endif +/* USART2 TX on PB3 @ 115200 — matches bootloader USE_DEBUG_UART (AM32-bootloader#60). */ +# define USE_DEBUG_UART +# define USE_SERIAL_TELEMETRY +# define VOLTAGE_ADC_PIN LL_GPIO_PIN_11 +# define VOLTAGE_ADC_CHANNEL LL_ADC_CHANNEL_14 +# define CURRENT_ADC_PIN LL_GPIO_PIN_3 +# define CURRENT_ADC_PORT GPIOC +# define CURRENT_ADC_CHANNEL LL_ADC_CHANNEL_9 +# define VOLTAGE_ADC_PORT GPIOB +# define USE_CURRENT_SENSE +# define MILLIVOLT_PER_AMP 10 +/* No CURRENT_OFFSET: idle sense is ~0 V (uses global default 0). */ +# define TARGET_VOLTAGE_DIVIDER 310 +/* Match ARK_4IN1_F051 vehicle policy (voltage-comp ramp + eeprom max_ramp + * can only lower these ceilings). Was 1/1 (SLO-style) before the F051 + * ramp/governor work was ported to this target. */ +# define RAMP_SPEED_LOW_RPM 3 +# define RAMP_SPEED_HIGH_RPM 8 +/* Unconfigured-eeprom max_ramp default; matches the factory JSON. 5 = fine + * mode, 0.5 %/ms, full scale in 200 ms - what larger 12S ESCs ship + * (APD/Hargrave default 50 % per 100 ms) rather than the 4IN1's 2.0 %/ms. + * Fine mode (<10) applies to every regime including startup, so spool-up + * slews at this rate too; that is the part to watch on the bench. */ +# define TARGET_DEFAULT_MAX_RAMP 5 +/* Protection envelope a DroneCAN "restore defaults" must land on. Same + * values as factory/ARK_G431_CAN_eeprom_defaults.json, enforced by + * scripts/check-erase-defaults.py: an erase has to leave the ESC with the + * protection it shipped with, not the AM32 configurator's disabled pair. + * 105 C foldback onset over a 15 C band, 100 = 200 A. */ +# define TARGET_DEFAULT_TEMPERATURE_LIMIT 105 +# define TARGET_DEFAULT_CURRENT_LIMIT 100 +# define TARGET_DEFAULT_TEMP_DERATE_BAND 15 +/* Closed-loop earlier at low RPM (same bench rationale as ARK_4IN1_F051). */ +# ifndef POLLING_MODE_THRESHOLD +# define POLLING_MODE_THRESHOLD 5000 +# endif +// #define NO_POLLING_START +# define USE_RGB_LED +# define RED_PORT GPIOC +# define RED_PIN LL_GPIO_PIN_6 +# define GREEN_PORT GPIOC +# define GREEN_PIN LL_GPIO_PIN_7 +# define BLUE_PORT GPIOC +# define BLUE_PIN LL_GPIO_PIN_8 + +# define CAN_TERM_PIN GPIO_PORT_PIN(2, 12) // PC12 +# define CAN_TERM_POLARITY 1 // active high + +# define EEPROM_START_ADD (uint32_t)0x0801F800 + +# define CAN_TX_PIN LL_GPIO_PIN_9 +# define CAN_TX_PORT GPIOB +# define CAN_RX_PIN LL_GPIO_PIN_11 +# define CAN_RX_PORT GPIOA + +/* + * DRV8350H (hardware interface): + * ENABLE = PC9 (DRV_ENABLE) — high = awake, low = sleep (~µA on VM). + * Gated by gate_driver.c: asleep when disarmed / armed-idle, + * awake for beeps, drive, and brake. Wake: PWM inactive → + * ENABLE high → ~3 ms settle (charge pump) → then PWM. + * nFAULT = PA12 (FAULT_N) — open-drain, external 20k to 3.3V; + * asserts on VDS OCP (resistor-set on VDS pin), UVLO, OTW, GDF + * (single wire OR — no SPI status). Firmware guesses cause + * from bus V / current / temp ADC at latch (faults.c). + * Mode / IDRIVE / VDS thresholds are hardwired on the board; firmware + * only enables the driver and reacts to FAULT_N. + */ +# define USE_DRV_ENABLE +# define DRV_ENABLE_PORT GPIOC +# define DRV_ENABLE_PIN LL_GPIO_PIN_9 +# define USE_DRV_NFAULT +# define NFAULT_PORT GPIOA +# define NFAULT_PIN LL_GPIO_PIN_12 + +# define COM_TIMER TIM7 +# define COM_TIMER_IRQ TIM7_IRQn + +# define USE_HSE +# undef HSE_VALUE +# define HSE_VALUE 8000000 +# define USE_HSE_BYPASS 0 +#endif + #ifdef ARK_4IN1_F051 # define FILE_NAME "ARK_4IN1_F051" # define FIRMWARE_NAME "ARK 4IN1" @@ -146,6 +332,56 @@ # define TARGET_DEFAULT_MAX_RAMP 160 #endif +/* + * Protection defaults restored by a DroneCAN param ERASE, and the single + * source of truth for default_settings[] bytes 43/44 plus the + * post-skeleton band byte (184). These must equal what the product's + * factory eeprom defaults JSON ships, or "restore defaults" in the field + * silently changes a shipped ESC's protection envelope - the one config + * change nobody re-checks afterwards. check-factory-image-ark.sh gates the + * JSON against the built image; scripts/check-erase-defaults.py gates + * these macros against the JSON. + * + * Values are the eeprom storage encoding: temperature in C, current in + * 2 A counts (so 100 = 200 A, the largest settings.c will arm), band in C. + * 255 in the temperature slot means "no thermal derate". + */ +#ifndef TARGET_DEFAULT_TEMPERATURE_LIMIT +# define TARGET_DEFAULT_TEMPERATURE_LIMIT 255 +#endif +#ifndef TARGET_DEFAULT_CURRENT_LIMIT +# define TARGET_DEFAULT_CURRENT_LIMIT 0 +#endif +#ifndef TARGET_DEFAULT_TEMP_DERATE_BAND +# define TARGET_DEFAULT_TEMP_DERATE_BAND THERMAL_DERATE_BAND_DEFAULT +#endif + +/* + * Lowest thermal ceiling the firmware will arm. TWO independent places + * enforce this and they must agree, or the one you did not change silently + * wins: settings.c coerces an out-of-range limits.temperature to 255 + * (disabled), and the DroneCAN parameter table re-validates the same byte + * at boot and substitutes TEMPERATURE_LIMIT's default (105) instead. A + * bench that lowered only settings.c saw its 30 C write come back as 105. + * + * Build-time override for bench verification ONLY - the derate runs from + * limits.temperature upward to +temp_derate_band_c, so exercising it at + * the shipped 70 C floor needs the die driven past 70, which a propped ESC + * in its own slipstream may never reach: + * make ARK_G431_CAN HWCI_PERF=1 EXTRA_CFLAGS=-DTHERMAL_LIMIT_MIN_C=30 + * Never lower it in a shipping build. + */ +#ifndef THERMAL_LIMIT_MIN_C +# define THERMAL_LIMIT_MIN_C 70 +#endif +/* Upper end of the same window. 255 is NOT part of it - that is the + * explicit "no thermal derate" sentinel and is preserved on purpose. */ +#ifndef THERMAL_LIMIT_MAX_C +# define THERMAL_LIMIT_MAX_C 140 +#endif +/* Value the temperature slot is disabled with. */ +#define THERMAL_LIMIT_DISABLED 255 + #ifndef RAMP_SPEED_STARTUP # define RAMP_SPEED_STARTUP 2 // adjusted 2.14 to match duty cycle change between mcu targets. #endif @@ -1187,6 +1423,59 @@ #endif +#ifdef HARDWARE_GROUP_G4_E + +# define MCU_G431 +# define USE_TIMER_16_CHANNEL_1 +# define INPUT_PIN LL_GPIO_PIN_4 +# define INPUT_PIN_PORT GPIOB +# define IC_TIMER_CHANNEL LL_TIM_CHANNEL_CH1 +# define IC_TIMER_REGISTER TIM16 +# define IC_TIMER_POINTER htim16 + +# define INPUT_DMA_CHANNEL LL_DMA_CHANNEL_1 +# define DMA_HANDLE_TYPE_DEF hdma_tim16_ch1 +# define IC_DMA_IRQ_NAME DMA1_Channel1_IRQn + +# define PHASE_A_GPIO_LOW LL_GPIO_PIN_15 +# define PHASE_A_GPIO_PORT_LOW GPIOB +# define AF_A_LOW LL_GPIO_AF_4 +# define PHASE_A_GPIO_HIGH LL_GPIO_PIN_10 +# define PHASE_A_GPIO_PORT_HIGH GPIOA + +# define PHASE_B_GPIO_LOW LL_GPIO_PIN_14 +# define PHASE_B_GPIO_PORT_LOW GPIOB +# define PHASE_B_GPIO_HIGH LL_GPIO_PIN_9 +# define PHASE_B_GPIO_PORT_HIGH GPIOA + +# define PHASE_C_GPIO_LOW LL_GPIO_PIN_13 +# define PHASE_C_GPIO_PORT_LOW GPIOB +# define PHASE_C_GPIO_HIGH LL_GPIO_PIN_8 +# define PHASE_C_GPIO_PORT_HIGH GPIOA + +# define PHASE_C_COMP LL_COMP_INPUT_MINUS_IO2 // pa0 +# define PHASE_B_COMP LL_COMP_INPUT_MINUS_IO1 // pa4 +# define PHASE_A_COMP LL_COMP_INPUT_MINUS_IO1 // pa5 + +/* Virtual neutral. B and C are on COMP1 and take it from PA1; A is on COMP2, + * whose IO1 is not PA1, so it takes it from PA3. PA1 and PA3 are the same + * SENS_COMMON net on the board, so all three phases share one reference - + * the differing IO index is a pin-mux artifact, not a second divider. */ +# define PHASE_C_INPUT_PLUS LL_COMP_INPUT_PLUS_IO1 //pa1 +# define PHASE_B_INPUT_PLUS LL_COMP_INPUT_PLUS_IO1 //pa1 +# define PHASE_A_INPUT_PLUS LL_COMP_INPUT_PLUS_IO2 //pa3 + +# define PHASE_C_EXTI_LINE LL_EXTI_LINE_21 +# define PHASE_C_COMP_NUMBER COMP1 + +# define PHASE_B_EXTI_LINE LL_EXTI_LINE_21 +# define PHASE_B_COMP_NUMBER COMP1 + +# define PHASE_A_EXTI_LINE LL_EXTI_LINE_22 +# define PHASE_A_COMP_NUMBER COMP2 + +#endif + /************************************ G031 Hardware Groups * ************************************************/ @@ -2243,7 +2532,9 @@ # define INTERVAL_TIMER TIM2 # define TEN_KHZ_TIMER TIM6 # define UTILITY_TIMER TIM17 -# define COM_TIMER TIM16 +# ifndef COM_TIMER +# define COM_TIMER TIM16 +# endif # define APPLICATION_ADDRESS 0x08001000 # define MAIN_COMP COMP2 # define EXTI_LINE LL_EXTI_LINE_22 @@ -2265,9 +2556,25 @@ # ifndef AF_C_LOW # define AF_C_LOW LL_GPIO_AF_6 # endif +# ifndef CAN_TX_PIN +# define CAN_TX_PIN LL_GPIO_PIN_12 +# define CAN_TX_PORT GPIOA +# endif +# ifndef CAN_RX_PIN +# define CAN_RX_PIN LL_GPIO_PIN_11 +# define CAN_RX_PORT GPIOA +# endif +# ifndef VOLTAGE_ADC_PORT +# define VOLTAGE_ADC_PORT GPIOA +# endif +# ifndef CURRENT_ADC_PORT +# define CURRENT_ADC_PORT GPIOA +# endif # define DSHOT_PRIORITY_THRESHOLD 60 -# define COM_TIMER_IRQ TIM1_UP_TIM16_IRQn +# ifndef COM_TIMER_IRQ +# define COM_TIMER_IRQ TIM1_UP_TIM16_IRQn +# endif #endif #ifdef MCU_GDE23 @@ -2491,6 +2798,19 @@ # define NOMINAL_PWM 24000U #endif +#ifndef ZC_SEARCH_BLANK_64THS +// half the commutation interval - the long-standing post-commutation blank +# define ZC_SEARCH_BLANK_64THS 32 +#endif +// 64ths of the interval. The default is spelled as the shift it replaces so +// that leaving the knob alone is provably a no-op - and so the general form's +// intermediate product cannot overflow on an interval it never sees anyway. +#if ZC_SEARCH_BLANK_64THS == 32 +# define ZC_SEARCH_BLANK(interval) ((interval) >> 1) +#else +# define ZC_SEARCH_BLANK(interval) (((uint32_t)(interval) * ZC_SEARCH_BLANK_64THS) >> 6) +#endif + #ifndef TIM1_AUTORELOAD // calculate commutation timer ARR based on a nominal 24kHz PWM # define TIM1_AUTORELOAD ((uint16_t)(CPU_FREQUENCY_MHZ * 1000U * 1000U / NOMINAL_PWM) - 1) diff --git a/Makefile b/Makefile index 1dbe574c0..4700ecf1c 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,8 @@ VERSION_TAG := $(shell $(FGREP) "define VERSION_TAG" $(MAIN_INC_DIR)/version.h | # Artifact version: MAJOR.MINOR[.PATCH][-TAG] FIRMWARE_VERSION := $(VERSION_MAJOR).$(VERSION_MINOR)$(if $(VERSION_PATCH),.$(VERSION_PATCH))$(if $(VERSION_TAG),-$(VERSION_TAG)) +# PX4 SD-card .uavcan.bin uses the numeric ship version (no -ark) plus git. +UAVCAN_VERSION := $(VERSION_MAJOR).$(VERSION_MINOR)$(if $(VERSION_PATCH),.$(VERSION_PATCH)) # Compiler options # @@ -114,19 +116,21 @@ SRC_COMMON_ALL := $(foreach dir,$(SRC_DIRS_COMMON),$(wildcard $(dir)/*.[cs])) # (Empty #ifdef stubs still cost flash/link time on F051.) SRC_OPTIONAL_BRUSHED := $(MAIN_SRC_DIR)/brushed.c SRC_OPTIONAL_HWCI := $(MAIN_SRC_DIR)/hwci_perf.c -SRC_COMMON_BASE := $(filter-out $(SRC_OPTIONAL_BRUSHED) $(SRC_OPTIONAL_HWCI),$(SRC_COMMON_ALL)) +SRC_OPTIONAL_DEBUG_UART := $(MAIN_SRC_DIR)/debug_uart.c +SRC_COMMON_BASE := $(filter-out $(SRC_OPTIONAL_BRUSHED) $(SRC_OPTIONAL_HWCI) $(SRC_OPTIONAL_DEBUG_UART),$(SRC_COMMON_ALL)) # App-side bootloader update. The image is a committed bootloader .bin pulled in # with .incbin (Src/bl_image.S) rather than a generated C array, so the linked # bytes stay verifiable against the ARK32-bootloader release they came from # (see Bootloaders/README.md). .S, not .[cs]: the wildcard above skips it. -# F051 embeds by default (including HWCI_PERF=1); LTO leaves enough flash for -# both the 4 KiB image and the perf struct. Kill switches for size A/Bs: +# F051 release embeds by default. HWCI_PERF=1 does not embed on F051 (4 KiB +# goes to the perf struct; the rig already has a BL on-chip). +# G431 CAN always embeds (128 KiB flash; needed so a manual/PX4 app flash +# still auto-updates the on-chip BL to UAVCAN hw 0.71). Kill switches: # make ARK_4IN1_F051 EMBED_BOOTLOADER=0 -# make ARK_4IN1_F051 NO_EMBED_BL=1 -# make ARK_4IN1_F051 HWCI_PERF=1 NO_EMBED_BL=1 +# make ARK_G431_CAN NO_EMBED_BL=1 # Bumping the bootloader means dropping the new .bin (from the release .hex) -# and editing this one line if the name changes. +# and editing the matching BL_IMAGE_* line if the name changes. SRC_OPTIONAL_BL_IMAGE := $(MAIN_SRC_DIR)/bl_image.S # ARK 4IN1: PB4 signal + PA15 nSLEEP low in BL (ARK32-bootloader #4). # Do not use the generic …_PB4 blob here — first boot would rewrite the @@ -134,7 +138,24 @@ SRC_OPTIONAL_BL_IMAGE := $(MAIN_SRC_DIR)/bl_image.S BL_IMAGE_F051 := Bootloaders/AM32_F051_BOOTLOADER_ARK4IN1_V18.bin # 0x08000000..ORIGIN(FLASH_VECTAB); the F051 linker script asserts the match. BL_REGION_SIZE_F051 := 4096 -# Default on; set EMBED_BOOTLOADER=0 or NO_EMBED_BL=1 to strip the image. +# ARK 12S CAN: 16 KiB region, app at 0x08004000 (ldscript_CAN.ld). +# Built from in-tree bootloader/ (make bootloader-g431-can). +BL_IMAGE_G431_CAN ?= Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin +BL_REGION_SIZE_G431_CAN := 16384 +BOOTLOADER_G431_SRCS := \ + $(wildcard bootloader/Makefile) \ + $(wildcard bootloader/Inc/*.[ch]) \ + $(wildcard bootloader/bootloader/*.[cs]) \ + $(wildcard bootloader/bootloader/*.ld) \ + $(wildcard bootloader/bootloader/DroneCAN/*.[ch]) \ + $(wildcard bootloader/bootloader/DroneCAN/libcanard/*.[ch]) \ + $(wildcard bootloader/bootloader/DroneCAN/dsdl_generated/src/*.c) \ + $(wildcard bootloader/bootloader/DroneCAN/dsdl_generated/include/*.h) \ + $(wildcard bootloader/Mcu/g431/Inc/*.h) \ + $(wildcard bootloader/Mcu/g431/Src/*.c) \ + $(wildcard bootloader/Mcu/g431/Startup/*.s) +# Default on; set EMBED_BOOTLOADER=0 or NO_EMBED_BL=1 to strip. +# F051 is also stripped automatically when HWCI_PERF=1 (see xEMBED_* below). EMBED_BOOTLOADER ?= 1 # configure some directories that are relative to wherever ROOT_DIR is located @@ -181,8 +202,10 @@ $$($(2)_BASENAME).bin: $$($(2)_BASENAME).elf echo building BIN $$@ @$(ECHO) Generating $$(notdir $$@) $(QUIET)$(xOBJCOPY) -O binary $$(<) $$@ + $(QUIET)python3 scripts/px4_uavcan_image.py sign $$@ $$(<) $(QUIET)python3 Src/DroneCAN/set_app_signature.py $$@ $$(<) $(QUIET)$(xOBJCOPY) $$(<) -O ihex $$(@:.bin=.hex) + $(QUIET)python3 scripts/px4_uavcan_image.py --version $(UAVCAN_VERSION) emit $$@ $(QUIET)$(CP) -f $$(<) $(OBJ)$(DSEP)debug.elf > $(NUL) # check for CAN support @@ -190,12 +213,15 @@ $(eval xLDSCRIPT := $$(if $$(call has_can_suffix,$$(2)),$(LDSCRIPT_CAN_$(1)),$(L $(eval xCFLAGS := $$(if $$(call has_can_suffix,$$(2)),$(CFLAGS_CAN_$(1)))) $(eval xSRC := $$(if $$(call has_can_suffix,$$(2)),$(SRC_CAN_$(1)))) -# Embed the bootloader image on F051 by default (release and HWCI_PERF). Either -# EMBED_BOOTLOADER=0 or NO_EMBED_BL=1 strips it for size emergencies / pure A/Bs. -$(eval xEMBED_BL := $(if $(filter F051,$(1)),$(if $(or $(filter 0,$(EMBED_BOOTLOADER)),$(filter 1,$(NO_EMBED_BL))),,1))) +# Embed the bootloader image: +# F051 — release default; strip on kill switch or HWCI_PERF=1 +# G431 CAN — always (including HWCI_PERF); strip only on kill switch +$(eval xEMBED_F051 := $(if $(filter F051,$(1)),$(if $(or $(filter 0,$(EMBED_BOOTLOADER)),$(filter 1,$(NO_EMBED_BL)),$(filter 1,$(HWCI_PERF))),,1))) +$(eval xEMBED_G431 := $(if $(and $(filter G431,$(1)),$(call has_can_suffix,$(2))),$(if $(or $(filter 0,$(EMBED_BOOTLOADER)),$(filter 1,$(NO_EMBED_BL))),,1))) +$(eval xEMBED_BL := $(or $(xEMBED_F051),$(xEMBED_G431))) # Per-target app sources: drop brushed/hwci unless the product asks for them -$(eval SRC_APP_$(2) := $(SRC_COMMON_BASE)$(if $(call has_brushed_suffix,$(2)), $(SRC_OPTIONAL_BRUSHED))$(if $(filter 1,$(HWCI_PERF)), $(SRC_OPTIONAL_HWCI))$(if $(xEMBED_BL), $(SRC_OPTIONAL_BL_IMAGE))) +$(eval SRC_APP_$(2) := $(SRC_COMMON_BASE)$(if $(call has_brushed_suffix,$(2)), $(SRC_OPTIONAL_BRUSHED))$(if $(filter 1,$(HWCI_PERF)), $(SRC_OPTIONAL_HWCI))$(if $(xEMBED_BL), $(SRC_OPTIONAL_BL_IMAGE))$(if $(filter G431,$(1)), $(SRC_OPTIONAL_DEBUG_UART))) # allow an MCU type to override the common compiler/linker flags (used by SITL # for a native build) and to have no linker script @@ -205,8 +231,14 @@ $(eval xLDFLAGS_COMMON := $(if $(LDFLAGS_COMMON_$(1)),$(LDFLAGS_COMMON_$(1)),$(L # BL_IMAGE_FILE is repo-relative and resolved by the assembler against the cwd, # which make always sets to the repo root. The 4 KiB image only fits because LTO # is on by default (see CFLAGS_COMMON above); it does not turn LTO on itself. -CFLAGS_$(2) = -DAM32_MCU=\"$(MCU)\" $(MCU_$(1)) -D$(2) $(CFLAGS_$(1)) $(xCFLAGS_COMMON) $(xCFLAGS) \ - $(if $(xEMBED_BL),-DEMBED_BOOTLOADER -DBL_IMAGE_FILE=\"$(BL_IMAGE_F051)\" -DBL_REGION_SIZE=$(BL_REGION_SIZE_F051)) +# EXTRA_CFLAGS is the user hook for A/B sweeps, e.g. +# make ARK_G431_CAN EXTRA_CFLAGS=-DZC_SEARCH_BLANK_64THS=40 +# Do NOT use xCFLAGS for this: it is populated above with the CAN include +# paths for *_CAN targets, so assigning it on the command line silently +# drops them and the DroneCAN headers stop resolving. +CFLAGS_$(2) = -DAM32_MCU=\"$(MCU)\" $(MCU_$(1)) -D$(2) $(CFLAGS_$(1)) $(xCFLAGS_COMMON) $(xCFLAGS) $(EXTRA_CFLAGS) \ + $(if $(xEMBED_F051),-DEMBED_BOOTLOADER -DBL_IMAGE_FILE=\"$(BL_IMAGE_F051)\" -DBL_REGION_SIZE=$(BL_REGION_SIZE_F051)) \ + $(if $(xEMBED_G431),-DEMBED_BOOTLOADER -DBL_IMAGE_FILE=\"$(BL_IMAGE_G431_CAN)\" -DBL_REGION_SIZE=$(BL_REGION_SIZE_G431_CAN)) LDFLAGS_$(2) = $(xLDFLAGS_COMMON) $(LDFLAGS_$(1)) $(if $(xLDSCRIPT),-T$(xLDSCRIPT)) -include $$($(2)_BASENAME).d @@ -215,7 +247,7 @@ LDFLAGS_$(2) = $(xLDFLAGS_COMMON) $(LDFLAGS_$(1)) $(if $(xLDSCRIPT),-T$(xLDSCRIP # The bootloader .bin is listed explicitly: .incbin happens in the assembler, so # it never shows up in the -MMD depfile and swapping the image would otherwise # not rebuild anything. -$$($(2)_BASENAME).elf: $(if $(NATIVE_$(1)),,arm_sdk_check) $$(SRC_APP_$(2)) $$(SRC_$(1)) $(xSRC) $(if $(xEMBED_BL),$(BL_IMAGE_F051)) +$$($(2)_BASENAME).elf: $(if $(NATIVE_$(1)),,arm_sdk_check) $$(SRC_APP_$(2)) $$(SRC_$(1)) $(xSRC) $(if $(xEMBED_F051),$(BL_IMAGE_F051)) $(if $(xEMBED_G431),$(BL_IMAGE_G431_CAN)) @$(ECHO) Compiling $$(notdir $$@) $(QUIET)$(MKDIR) -p $(OBJ) $(QUIET)$(xCC) $$(CFLAGS_$(2)) $$(LDFLAGS_$(2)) -MMD -MP -MF $$(@:.elf=.d) -o $$(@) $$(SRC_APP_$(2)) $$(SRC_$(1)) $(xSRC) $(LDLIBS_$(1)) @@ -237,6 +269,14 @@ include $(ROOT)/make/tools_install.mk arm_sdk_check: $(QUIET)bash scripts/check-arm-sdk.sh "$(ARM_SDK_PREFIX)" "$(XPACK_GCC_VER)" +# In-tree G431 CAN bootloader. Rebuilds Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin +# from bootloader/ so app embed / factory images pick up CAN FD and pinout changes. +.PHONY: bootloader-g431-can +bootloader-g431-can: $(BL_IMAGE_G431_CAN) + +$(BL_IMAGE_G431_CAN): $(BOOTLOADER_G431_SRCS) + $(QUIET)$(MAKE) -C bootloader install + # useful target to list all of the board targets so you can see what # make target to use for your board targets: @@ -262,41 +302,68 @@ codegen-check-ark: # Build ARK F051 and enforce flash/RAM headroom (F051 is tight). # -B forces a rebuild so a prior image is not size-checked by mistake. .PHONY : size-check-ark -# Worst case is HWCI_PERF=1 with the embedded bootloader (default): it carries -# both the perf struct and the 4 KiB .bl_image. That bounds release flash/RAM. -# A second release-only build still runs so a pure release link regression is -# not hidden by HWCI-only code paths. Strip the image with NO_EMBED_BL=1 / -# EMBED_BOOTLOADER=0 if you need a headroom A/B outside this gate. +# Two bounds: HWCI_PERF (no .bl_image) and release (with .bl_image). Each is +# the production shape of that product path; neither is forced to carry both +# the 4 KiB BL blob and the HWCI perf struct. size-check-ark: - $(QUIET)$(ECHO) "--- ARK_4IN1_F051 HWCI_PERF=1 (embedded bootloader, default) ---" + $(QUIET)$(ECHO) "--- ARK_4IN1_F051 HWCI_PERF=1 (no embedded bootloader) ---" $(QUIET)$(MAKE) -B ARK_4IN1_F051 HWCI_PERF=1 $(QUIET)bash scripts/check-size-ark.sh $(QUIET)$(ECHO) "--- ARK_4IN1_F051 release (embedded bootloader) ---" $(QUIET)$(MAKE) -B ARK_4IN1_F051 $(QUIET)bash scripts/check-size-ark.sh -# Production full-flash image for ARK 4IN1: bootloader + app + factory EEPROM -# defaults in one 32 KiB binary (see factory/README.md). Replaces the old -# flash-BL / flash-app / configurator / ST-Link dump release flow. -.PHONY : factory-image factory-image-check -FACTORY_PRODUCT := ARK_4IN1_F051 -FACTORY_APP_BASENAME := $(OBJ)/$(IDENTIFIER)_$(FACTORY_PRODUCT)_$(FIRMWARE_VERSION) -FACTORY_DEFAULTS := factory/ARK_4IN1_F051_eeprom_defaults.json -factory-image: $(FACTORY_PRODUCT) - $(QUIET)$(ECHO) "Building factory full-flash image for $(FACTORY_PRODUCT)" +# Production full-flash images: bootloader + app + factory EEPROM defaults. +# See factory/README.md. Replaces flash-BL / flash-app / configurator / dump. +.PHONY : factory-image factory-image-f051 factory-image-g431-can factory-image-check +FACTORY_F051_PRODUCT := ARK_4IN1_F051 +FACTORY_F051_BASENAME := $(OBJ)/$(IDENTIFIER)_$(FACTORY_F051_PRODUCT)_$(FIRMWARE_VERSION) +FACTORY_F051_DEFAULTS := factory/ARK_4IN1_F051_eeprom_defaults.json +FACTORY_G431_PRODUCT := ARK_G431_CAN +FACTORY_G431_BASENAME := $(OBJ)/$(IDENTIFIER)_$(FACTORY_G431_PRODUCT)_$(FIRMWARE_VERSION) +FACTORY_G431_DEFAULTS := factory/ARK_G431_CAN_eeprom_defaults.json + +factory-image-f051: $(FACTORY_F051_PRODUCT) + $(QUIET)$(ECHO) "Building factory full-flash image for $(FACTORY_F051_PRODUCT)" $(QUIET)python3 scripts/build_factory_image.py \ - --defaults $(FACTORY_DEFAULTS) \ + --defaults $(FACTORY_F051_DEFAULTS) \ --bootloader $(BL_IMAGE_F051) \ - --app $(FACTORY_APP_BASENAME).bin \ + --app $(FACTORY_F051_BASENAME).bin \ --version-h $(MAIN_INC_DIR)/version.h \ - --out-bin $(FACTORY_APP_BASENAME).factory.bin \ - --out-hex $(FACTORY_APP_BASENAME).factory.hex \ - --out-eeprom $(FACTORY_APP_BASENAME).eeprom.bin + --out-bin $(FACTORY_F051_BASENAME).factory.img \ + --out-hex $(FACTORY_F051_BASENAME).factory.hex \ + --out-eeprom $(FACTORY_F051_BASENAME).eeprom.bin + +factory-image-g431-can: $(FACTORY_G431_PRODUCT) + $(QUIET)$(ECHO) "Building factory full-flash image for $(FACTORY_G431_PRODUCT)" + $(QUIET)python3 scripts/build_factory_image.py \ + --defaults $(FACTORY_G431_DEFAULTS) \ + $(if $(wildcard $(BL_IMAGE_G431_CAN)),--bootloader $(BL_IMAGE_G431_CAN),--allow-empty-bootloader) \ + --app $(FACTORY_G431_BASENAME).bin \ + --version-h $(MAIN_INC_DIR)/version.h \ + --out-bin $(FACTORY_G431_BASENAME).factory.img \ + --out-hex $(FACTORY_G431_BASENAME).factory.hex \ + --out-eeprom $(FACTORY_G431_BASENAME).eeprom.bin + +# Default target builds both ARK production images. +factory-image: factory-image-f051 factory-image-g431-can # Build + layout/defaults gate used by CI (.github/workflows/static-analysis.yml). factory-image-check: factory-image - $(QUIET)BL_IMAGE_F051=$(BL_IMAGE_F051) FACTORY_DEFAULTS=$(FACTORY_DEFAULTS) \ + $(QUIET)$(ECHO) "--- factory check $(FACTORY_F051_PRODUCT) ---" + $(QUIET)FACTORY_PRODUCT=$(FACTORY_F051_PRODUCT) \ + FACTORY_DEFAULTS=$(FACTORY_F051_DEFAULTS) \ + BL_IMAGE=$(BL_IMAGE_F051) \ + bash scripts/check-factory-image-ark.sh + $(QUIET)$(ECHO) "--- factory check $(FACTORY_G431_PRODUCT) ---" + $(QUIET)FACTORY_PRODUCT=$(FACTORY_G431_PRODUCT) \ + FACTORY_DEFAULTS=$(FACTORY_G431_DEFAULTS) \ + BL_IMAGE="$(BL_IMAGE_G431_CAN)" \ bash scripts/check-factory-image-ark.sh + $(QUIET)$(ECHO) "--- erase-defaults check ---" + $(QUIET)python3 scripts/check-erase-defaults.py $(FACTORY_G431_PRODUCT) + $(QUIET)$(ECHO) "--- PX4 SD-card UAVCAN image ---" + $(QUIET)python3 scripts/px4_uavcan_image.py --version $(UAVCAN_VERSION) check $(FACTORY_G431_BASENAME).bin # Code formatting (clang-format ≈ PX4 astyle/Linux look; see .clang-format). # Same target names as PX4: diff --git a/Mcu/SITL/README.md b/Mcu/SITL/README.md index eb15ff58f..23eb99a9c 100644 --- a/Mcu/SITL/README.md +++ b/Mcu/SITL/README.md @@ -191,16 +191,12 @@ obj/ARK32_AM32_SITL_CAN_*.elf --can-uri none --input-type 1 python3 Mcu/SITL/dshot_test.py --type dshot600 --bidir --edt --throttle 800 ``` -Note that the eeprom default `INPUT_SIGNAL_TYPE` is DRONECAN_IN, which -disables the PWM/DShot input interrupts at startup — set it to 0/1/2 -first (via `--input-type`, the GUI parameter panel, or -`dshot_test.py --input-type`). Also be aware of the current firmware -input arbitration: once any `esc.RawCommand` has been received, the 1kHz -DroneCAN input keep-alive overrides the `dshot`/`inputSet` flags and -PWM/DShot input is dead until a reboot (signal timeout after the CAN -stream stops) followed by zero-throttle re-arming. Running both inputs -at once exercises exactly this behaviour, which is what the input -priority/failover parameter work is developing against. +Default `INPUT_SIGNAL_TYPE` is **AUTO (0)**: wire protocols (DShot/PWM) +are auto-detected; DroneCAN may run in parallel. While `esc.RawCommand` +is live (~250 ms failsafe window), CAN owns throttle; when the stream +stops, wire input can take over without a reboot. Exclusive +`DRONECAN_IN` (5) still disables PWM/DShot IRQs at startup — use +`--input-type 5` (or the parameter panel) only when you want CAN-only. ## macOS diff --git a/Mcu/SITL/Src/sitl_state.c b/Mcu/SITL/Src/sitl_state.c index 32903dcd8..226a882cd 100644 --- a/Mcu/SITL/Src/sitl_state.c +++ b/Mcu/SITL/Src/sitl_state.c @@ -74,6 +74,10 @@ u8 ramp_divider, u8 max_ramp_startup_vcomp, u8 acq_resist, u8 desync_episode_bucket (v6 ramp settings + episode observability) + u16 thermal_duty_ceiling, u16 current_duty_ceiling, + u16 duty_limit_ceiling, i16 degrees_celsius, + i16 degrees_celsius_filtered, i16 actual_current (v7 protection + ceilings) Fields are only ever APPENDED and the version is bumped; clients that unpack a shorter prefix keep working unchanged (they length-check with >=). @@ -608,9 +612,19 @@ void sitl_state_poll(void) uint8_t max_ramp_startup_vcomp_v; uint8_t acq_resist_v; uint8_t desync_episode_bucket_v; + /* v7: protection ceilings. Both are smooth derates that + * only ever LOWER duty, so a test cannot tell them from a + * weak plant by watching rpm alone - it has to read the + * ceiling that produced the derate. */ + uint16_t thermal_duty_ceiling_v; + uint16_t current_duty_ceiling_v; + uint16_t duty_limit_ceiling_v; + int16_t degrees_celsius_v; + int16_t degrees_celsius_filt_v; + int16_t actual_current_v; } reply = { .magic = 0x5356, - .version = 6, + .version = 7, .zero_crosses = zero_crosses, .commutation_interval = commutation_interval, .dropped_edges = motor_zc_dropped(), @@ -643,6 +657,14 @@ void sitl_state_poll(void) .max_ramp_startup_vcomp_v = max_ramp_startup_vcomp, .acq_resist_v = fault_acq_resist_events, .desync_episode_bucket_v = desync_episode_bucket, + .thermal_duty_ceiling_v = thermal_duty_ceiling, + /* 2000 when the limiter is disabled, so the field reads as + * "no cap" rather than as a stale integrator value. */ + .current_duty_ceiling_v = use_current_limit ? (uint16_t)use_current_limit_adjust : 2000u, + .duty_limit_ceiling_v = duty_limit_ceiling, + .degrees_celsius_v = degrees_celsius, + .degrees_celsius_filt_v = degrees_celsius_filtered, + .actual_current_v = actual_current, }; sendto(fd, &reply, sizeof(reply), 0, (struct sockaddr *)&src, sizeof(src)); } else if (cmd == 10 && ret >= 8) { diff --git a/Mcu/SITL/sitl_params.py b/Mcu/SITL/sitl_params.py index d8e002d42..ecdd30f6a 100755 --- a/Mcu/SITL/sitl_params.py +++ b/Mcu/SITL/sitl_params.py @@ -73,8 +73,8 @@ def _version_defines(): (40, 'SINE_MODE_CHANGEOVER', 0, 255, 5, 'throttle level leaving sine start'), (41, 'DRAG_BRAKE_STRENGTH', 0, 10, 10, 'brake strength at zero throttle'), (42, 'DRIVING_BRAKE_STRENGTH', 0, 10, 10, 'brake strength while driving'), - (43, 'TEMPERATURE_LIMIT', 70, 255, 255, 'thermal limit, C (255 = off)'), - (44, 'CURRENT_LIMIT', 0, 100, 102, 'current limit in 2A steps (>100 = off)'), + (43, 'TEMPERATURE_LIMIT', 70, 255, 105, 'derate onset, C (outside 70..140 = off)'), + (44, 'CURRENT_LIMIT', 0, 100, 100, 'current limit in 2A steps (>100 = off)'), (45, 'SINE_MODE_POWER', 1, 10, 5, 'sine startup power'), (46, 'INPUT_SIGNAL_TYPE', 0, 5, 5, '0=auto 1=dshot 2=servo 5=dronecan only'), (47, 'AUTO_ADVANCE', 0, 1, 0, 'automatic timing advance'), @@ -86,6 +86,7 @@ def _version_defines(): (181, 'FILTER_HZ', 0, 255, 20, 'DroneCAN input filter cutoff, Hz'), (182, 'DEBUG_RATE', 0, 200, 0, 'FlexDebug rate, Hz'), (183, 'TERM_ENABLE', 0, 1, 0, 'serial terminal'), + (184, 'TEMP_DERATE_BAND', 5, 40, 15, 'C from thermal onset to full derate'), ] PARAMS_BY_NAME = dict((p[1], p) for p in PARAMS) @@ -114,6 +115,7 @@ def _onoff(v): if v < 3 else '?', 'CURRENT_LIMIT': lambda v: '%d A' % (v * 2) if v <= 100 else 'off', 'TEMPERATURE_LIMIT': lambda v: '%d C' % v if 70 <= v <= 140 else 'off', + 'TEMP_DERATE_BAND': lambda v: '%d C ramp' % v if 5 <= v <= 40 else '15 C (default)', 'TELEM_RATE': lambda v: '%d Hz' % v, 'DEBUG_RATE': lambda v: '%d Hz' % v, 'FILTER_HZ': lambda v: '%d Hz' % v, diff --git a/Mcu/SITL/tests/test_acq_desync_rail.py b/Mcu/SITL/tests/test_acq_desync_rail.py index a7e77a78c..187947b80 100644 --- a/Mcu/SITL/tests/test_acq_desync_rail.py +++ b/Mcu/SITL/tests/test_acq_desync_rail.py @@ -82,7 +82,6 @@ def test_racer_hard_spool_cannot_desync_forever_below_acquisition( # ~20 desyncs/s, 20 early events per JUMP charge) would either get # ramp-backoff help past acquisition, or pile enough charges to latch. max_zc = 0 - max_desync = 0 saw_running = False end = None t0 = time.time() @@ -93,7 +92,6 @@ def test_racer_hard_spool_cannot_desync_forever_below_acquisition( time.sleep(0.05) continue max_zc = max(max_zc, s['zero_crosses']) - max_desync = max(max_desync, s['desync_happened']) if s['running']: saw_running = True end = s @@ -106,13 +104,15 @@ def test_racer_hard_spool_cannot_desync_forever_below_acquisition( assert end is not None acquired = max_zc > 100 - # Stuck latch / fault path: drive no longer running after many desyncs - latched_off = (max_desync >= 20 and end['running'] == 0) + # Acq desyncs no longer increment desync_happened (established-only + # error_count). Success is acquire past zc 100, or drive stopped by + # the acq-rail episode latch — not free-running forever below acq. + latched_off = (end['running'] == 0 and not acquired) assert acquired or latched_off, ( 'acquisition rail did not break free-desync-below-zc100: ' - 'max_zc=%d max_desync=%d end=%r\n%s' - % (max_zc, max_desync, end, sitl.log_tail())) + 'max_zc=%d end=%r\n%s' + % (max_zc, end, sitl.log_tail())) finally: tx.value = 0 time.sleep(0.5) diff --git a/Mcu/SITL/tests/test_dshot.py b/Mcu/SITL/tests/test_dshot.py index 52eb93348..88aff0924 100644 --- a/Mcu/SITL/tests/test_dshot.py +++ b/Mcu/SITL/tests/test_dshot.py @@ -115,3 +115,86 @@ def test_bad_crc_frames_do_not_arm_or_spin(sitl_factory, state_stream): assert rpm < 200, 'motor spun on bad-CRC frames: rpm=%.0f' % rpm finally: port.close() + + +def test_single_full_throttle_frame_from_idle_is_ignored(sitl_factory, state_stream): + '''one isolated full-scale frame at armed idle must not start the motor. + + This is the fault the idle-exit holdoff exists for, and it is NOT a CRC + problem: an all-ones capture (0xFFFF, every pulse read long) is a + CRC-VALID DShot frame carrying throttle 2047, and its frame span lands + only +1.63% off nominal - inside the +-6.25% span gate. So one lost edge + on the signal line turns a commanded stop into commanded full throttle + and both integrity checks pass it. Observed on an ARK G431 CAN bench as a + 30-90 ms spin-up kick at armed idle, roughly once a minute. + + The isolated frame is only half of it: newinput LATCHES, because it is + only ever overwritten by the next ACCEPTED frame. On the bench the same + marginal link that corrupted one frame dropped the next several, so the + bogus demand survived for the length of the dropout - that latch is what + turns a 2 us decode error into a 30-90 ms burst of drive. A lone bogus + frame inside an otherwise healthy stream is harmless (the next zero frame + clears it ~2 ms later, before the plant moves), so the sequence + reproduced here is bogus-frame-then-silence, which is what was actually + measured. + ''' + sitl = sitl_factory(extra_args=['--input-type', '1'], can_uri='none') + sim = state_stream(sitl) + assert wait_for_state(sim), 'state stream dead\n' + sitl.log_tail() + port = sd.InputPort('127.0.0.1', sitl.input_port) + try: + # arm on sustained zero, as an FC does while disarmed + t0 = time.time() + while time.time() - t0 < 2.5: + port.send_dshot(0, ptype=sd.TYPE_DSHOT300) + time.sleep(1.0 / 500.0) + # one full-scale frame, then a dropout that lets it latch, then the + # zero stream resumes. 120 ms stays well inside the 500 ms armed + # signal-loss failsafe, so the ESC holds the stale demand rather + # than resetting - exactly the bench case. + t0 = time.time() + injected = 0 + while time.time() - t0 < 5.0: + port.send_dshot(2047, ptype=sd.TYPE_DSHOT300) + injected += 1 + time.sleep(0.120) + tq = time.time() + while time.time() - tq < 0.25: + port.send_dshot(0, ptype=sd.TYPE_DSHOT300) + time.sleep(1.0 / 500.0) + assert injected >= 5, 'injector did not run: %d' % injected + rpm = rpm_from_state(sim, 0.5) + assert rpm < 200, ( + 'latched single-frame 2047 spun the motor: rpm=%.0f after %d ' + 'injections\n%s' % (rpm, injected, sitl.log_tail())) + finally: + port.close() + + +def test_holdoff_delays_but_does_not_block_spool_up(sitl_factory, state_stream): + '''a real, repeated demand must still spin up - the holdoff only delays. + + Guards the other side of the idle-exit holdoff: it discards + DSHOT_IDLE_EXIT_HOLDOFF_FRAMES frames, so a genuine spool-up pays two + frame periods (2.5 ms at 800 Hz) and nothing more. The 2 s deadline is + far looser than that on purpose - it is here to catch a holdoff that + blocks outright or never clears its counter, not to measure latency. + ''' + sitl = sitl_factory(extra_args=['--input-type', '1'], can_uri='none') + sim = state_stream(sitl) + assert wait_for_state(sim), 'state stream dead\n' + sitl.log_tail() + tx = Sender('127.0.0.1', sitl.input_port, sd.TYPE_DSHOT300) + try: + tx.value = 0 + time.sleep(2.5) + tx.value = 1000 + deadline = time.time() + 2.0 + rpm = 0.0 + while time.time() < deadline: + rpm = rpm_from_state(sim, 0.3) + if rpm > 1500: + break + assert rpm > 1500, ( + 'holdoff blocked a sustained demand: rpm=%.0f\n%s' % (rpm, sitl.log_tail())) + finally: + tx.stop() diff --git a/Mcu/SITL/tests/test_duty_limits.py b/Mcu/SITL/tests/test_duty_limits.py new file mode 100644 index 000000000..164b206de --- /dev/null +++ b/Mcu/SITL/tests/test_duty_limits.py @@ -0,0 +1,419 @@ +'''Thermal + current duty limiters: smooth, composable, and ARMED by default. + +Three things are asserted here, and each is a bug this tree has shipped: + +1. THE DERATE IS CONTINUOUS. Upstream mapped [limit-10, limit+10] onto + [throttle_max_at_high_rpm/2, 1] but only entered the map at temp > limit, + so the ceiling went 2000 -> ~450 for ONE degree of overshoot, and back on + one degree of recovery: a bang-bang across the whole derate range, no + hysteresis, cycling on the board's thermal time constant. The per-degree + step assertion is the mutation check - restore the old map and the step + at the limit is ~1550 counts. + +2. THE LIMITS ARE ON. Both factory JSONs and DroneCAN default_settings[] + inherited 141/102 from the AM32 configurator skeleton, values that sit + just OUTSIDE the ranges settings.c arms, so every ARK ESC shipped with + both limiters silently disabled. A default-eeprom run must now come up + derating at 105 C, and 255 must still disable it deliberately. + +3. THE CEILINGS COMPOSE BY min(). Two limiters that multiplied would derate + to 25% when each asked for 50%. + +4. THE CURRENT LOOP HOLDS ITS SETPOINT. Softening its gains looks reasonable + and is not - the /10000 in the ceiling update is a dead zone. Asserted on + regulated current, on a plant that pulls twice the limit. + +Observed through ZC_STATS v7, not through rpm: these limiters only ever +LOWER duty, so from outside a derating ESC and a weak plant look identical. +Internal state no test can see is how the dcm_hold no-op survived a full +hardware suite (see test_ceiling_hold.py). + +NOTE every test here keeps a zero-throttle DShot stream running. Without an +input signal the firmware resets itself after ~3.4 s (faults.c signal-loss +NVIC_SystemReset), which re-seeds the temperature filter mid-measurement. +''' + +from __future__ import annotations + +import json +import os +import socket +import struct +import time + +import sitl_dshot as sd +from sitl_harness import SITL_DIR, Sender, wait_for_state + +MODELS = os.path.join(SITL_DIR, 'models') + + +def _tick(n, dt): + '''n pauses of dt, so a sampling loop reads as one expression''' + for _ in range(n): + time.sleep(dt) + yield None + +STATE_MAGIC_CMD = 0x5353 +ZC_STATS_MAGIC = 0x5356 + +# v7 layout (Mcu/SITL/Src/sitl_state.c). Appended-only; a short read is a +# firmware that predates the protection ceilings. +STATS_FMT = ('= band: + return CEIL_FLOOR + return FULL_CEIL - ((FULL_CEIL - CEIL_FLOOR) * over) // band + + +def _open_ctl(sitl): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('127.0.0.1', sitl.state_port)) + s.settimeout(0.5) + return s + + +def _zc_stats(ctl, retries=8): + want = struct.calcsize(STATS_FMT) + for _ in range(retries): + ctl.send(struct.pack('= want: + vals = struct.unpack_from(STATS_FMT, pkt) + if vals[0] == ZC_STATS_MAGIC: + assert vals[1] >= 7, ( + 'ZC_STATS version %d lacks duty-limit fields' % vals[1]) + return dict(zip(STATS_FIELDS, vals[3:])) + raise AssertionError('no v7 ZC_STATS reply from SITL state port') + + +def _hot_model(workdir, temp_c): + '''an unloaded plant reporting a chosen ESC temperature''' + spec = { + 'motor': {'kv': 900, 'poles': 14, 'resistance': 0.045, + 'inductance': 2.1e-5, 'inertia': 2.0e-5, 'damping': 1.0e-6, + 'static_friction': 0.003, 'load_k_omega2': 2.0e-9}, + 'battery': {'voltage': 16.8, 'resistance': 0.012}, + 'esc': {'temperature_c': float(temp_c)}, + } + path = os.path.join(workdir, 'temp_%d.json' % temp_c) + with open(path, 'w') as f: + json.dump(spec, f) + return os.path.abspath(path) + + +def _settle_at(sim, ctl, workdir, temp_c, timeout=10.0): + '''load a plant at temp_c and wait for the die filter to reach it''' + sim.load_model(_hot_model(workdir, temp_c)) + deadline = time.time() + timeout + st = None + while time.time() < deadline: + time.sleep(0.2) + st = _zc_stats(ctl) + if st['degrees_celsius_filt'] == int(temp_c): + # Let the Q12 residue drain, then require the reading to still be + # there: one sample can land mid-transition on a loaded host. + time.sleep(0.4) + st = _zc_stats(ctl) + if st['degrees_celsius_filt'] == int(temp_c): + return st + raise AssertionError( + 'die temperature never settled at %d C (raw=%s filt=%s)' + % (temp_c, st and st['degrees_celsius'], + st and st['degrees_celsius_filt'])) + + +def _keepalive(sitl, value=0): + '''DShot frames so the signal-loss reset never fires mid-test''' + tx = Sender('127.0.0.1', sitl.input_port, sd.TYPE_DSHOT600) + tx.value = value # Sender starts its thread in __init__ + return tx + + +def _assert_on_curve(st, temp_c, band=DERATE_BAND_C): + '''the ceiling must lie on the derate curve for the temperature the + firmware is actually reporting - not for the one the test asked for. + Under host load the filter can still be a degree away from the request, + which is a slow SITL, not a wrong derate.''' + filt = st['degrees_celsius_filt'] + want = _expected_ceiling(filt, band=band) + got = st['thermal_duty_ceiling'] + assert want - CEIL_TOL <= got <= want + CEIL_TOL, ( + 'at %d C (asked %d) ceiling %d, expected ~%d (%s)' + % (filt, temp_c, got, want, st)) + + +def test_thermal_derate_is_continuous_and_armed_by_default( + sitl_factory, state_stream, workdir): + '''the ceiling follows temperature smoothly, on the shipped defaults''' + sitl = sitl_factory(extra_args=['--input-type', '1'], can_uri='none') + sim = state_stream(sitl) + assert wait_for_state(sim), sitl.log_tail() + ctl = _open_ctl(sitl) + tx = _keepalive(sitl) + try: + # Cool: no derate. Also proves the limiter is not simply stuck low. + st = _settle_at(sim, ctl, workdir, DEFAULT_TEMP_LIMIT_C - 10) + assert st['thermal_duty_ceiling'] == FULL_CEIL, st + + # Walk the band a degree at a time. The old threshold implementation + # drops ~1550 counts on the first of these steps. + prev = FULL_CEIL + steps = [] + for temp in range(DEFAULT_TEMP_LIMIT_C, DEFAULT_TEMP_LIMIT_C + 6): + st = _settle_at(sim, ctl, workdir, temp) + ceil = st['thermal_duty_ceiling'] + assert ceil <= prev, ( + 'ceiling rose at %d C: %d > %d' % (temp, ceil, prev)) + _assert_on_curve(st, temp) + steps.append(prev - ceil) + prev = ceil + assert max(steps) <= 200, ( + 'derate is a cliff, not a ramp: per-degree steps %s' % steps) + assert sum(steps) > 250, 'no derate across the band: steps %s' % steps + + # Deep in the band the floor holds - authority, not a stop. + st = _settle_at(sim, ctl, workdir, + DEFAULT_TEMP_LIMIT_C + DERATE_BAND_C + 8) + assert st['thermal_duty_ceiling'] == CEIL_FLOOR, st + finally: + tx.stop() + + +def test_ceilings_compose_by_min_under_throttle( + sitl_factory, state_stream, workdir): + '''the applied ceiling is the binding limiter, not a product of both''' + sitl = sitl_factory(extra_args=['--input-type', '1'], can_uri='none') + sim = state_stream(sitl) + assert wait_for_state(sim), sitl.log_tail() + ctl = _open_ctl(sitl) + + tx = _keepalive(sitl, value=0) + try: + # duty_limit_ceiling is only recomputed on a throttle pass, so the + # ESC has to arm first (a second of confirmed zero) and then be + # given real throttle - otherwise the field reads its 2000 init. + deadline = time.time() + 8.0 + while time.time() < deadline and not _zc_stats(ctl)['armed']: + time.sleep(0.2) + assert _zc_stats(ctl)['armed'], 'never armed on a zero stream' + tx.value = 1200 + time.sleep(0.3) + st = _settle_at(sim, ctl, workdir, DEFAULT_TEMP_LIMIT_C + 10) + expect = min(st['thermal_duty_ceiling'], st['current_duty_ceiling']) + assert st['duty_limit_ceiling'] == expect, ( + 'applied %d != min(thermal %d, current %d)' + % (st['duty_limit_ceiling'], st['thermal_duty_ceiling'], + st['current_duty_ceiling'])) + # Thermal is mid-band and binding; the current loop is slack on an + # unloaded plant. A product of the two factors would sit well below. + assert st['duty_limit_ceiling'] == st['thermal_duty_ceiling'], st + assert st['current_duty_ceiling'] == FULL_CEIL, ( + 'current ceiling should be slack unloaded: %s' % (st,)) + assert CEIL_FLOOR < st['duty_limit_ceiling'] < FULL_CEIL, ( + 'thermal ceiling should be mid-band here: %s' % (st,)) + finally: + tx.value = 0 + time.sleep(0.2) + tx.stop() + + +def test_derate_band_is_tunable(sitl_factory, state_stream, workdir): + '''TEMP_DERATE_BAND changes the slope, not just the endpoint + + A narrow band is the closest thing to the "hard" response style other + vendors expose, and it must stay a ramp: 5 C still gives 5 observable + steps, not a cliff. + ''' + import sitl_params + + band = 5 + with open(os.path.join(workdir, 'am32_eeprom.bin'), 'wb') as f: + f.write(sitl_params.build_image({ + 'TEMPERATURE_LIMIT': DEFAULT_TEMP_LIMIT_C, + 'TEMP_DERATE_BAND': band, + })) + + sitl = sitl_factory(extra_args=['--input-type', '1'], can_uri='none') + sim = state_stream(sitl) + assert wait_for_state(sim), sitl.log_tail() + ctl = _open_ctl(sitl) + tx = _keepalive(sitl) + try: + # Same degree, steeper slope: 3 C in must be far lower than it is on + # the 15 C default, and the floor must arrive at onset+5 not onset+15. + st = _settle_at(sim, ctl, workdir, DEFAULT_TEMP_LIMIT_C + 3) + _assert_on_curve(st, DEFAULT_TEMP_LIMIT_C + 3, band=band) + assert st['thermal_duty_ceiling'] < _expected_ceiling( + DEFAULT_TEMP_LIMIT_C + 3, band=DERATE_BAND_C), ( + 'band setting had no effect on the slope: %s' % (st,)) + + st = _settle_at(sim, ctl, workdir, DEFAULT_TEMP_LIMIT_C + band + 2) + assert st['thermal_duty_ceiling'] == CEIL_FLOOR, st + finally: + tx.stop() + + +def test_current_limiter_holds_the_limit_under_load( + sitl_factory, state_stream, workdir): + '''the current loop actually HOLDS the setpoint on a loaded plant + + This is the gain check, and it exists because reasoning about the loop + got it backwards once already. The ceiling moves by + doPidCalculations()/10000 duty units per tick, and that integer divide + is a dead zone: nothing moves until the overshoot exceeds 5000/Kp + centiamps. "Softening" the loop by lowering current_P widens that dead + zone until the limiter is inert. Same plant, 8 A limit, measured: + P=100 -> 7.7 A, P=50 -> 7.1 A, P=25 -> 6.5 A, P=5 -> 14.7 A. + + So the assertion is on the REGULATED CURRENT, not on the ceiling: a + limiter that lets current run at twice the setpoint is the failure + mode, and only a current assertion catches it. + ''' + import pytest + import sitl_params + + limit_raw = 4 # 2 A per count -> 8 A + limit_ca = limit_raw * 2 * 100 + with open(os.path.join(workdir, 'am32_eeprom.bin'), 'wb') as f: + f.write(sitl_params.build_image({ + 'CURRENT_LIMIT': limit_raw, + 'TEMPERATURE_LIMIT': 255, # isolate the current loop + 'MOTOR_KV': (380 - 20) // 40, + 'MOTOR_POLES': 22, + })) + + sitl = sitl_factory(extra_args=['--input-type', '1'], can_uri='none') + sim = state_stream(sitl) + assert wait_for_state(sim), sitl.log_tail() + ctl = _open_ctl(sitl) + # heavy_13inch: pulls ~16 A unlimited here, i.e. 2x the limit above. + sim.load_model(os.path.join(MODELS, 'heavy_13inch.json')) + time.sleep(0.6) + + tx = _keepalive(sitl, value=0) + try: + deadline = time.time() + 8.0 + while time.time() < deadline and not _zc_stats(ctl)['armed']: + time.sleep(0.2) + assert _zc_stats(ctl)['armed'], 'never armed on a zero stream' + + tx.value = 1800 # ask for far more than 8 A of load + time.sleep(6.0) # spool the heavy prop and let the loop settle + + samples = [_zc_stats(ctl) for _ in _tick(40, 0.05)] + # Ignore samples taken while the motor was not driving: the ceiling + # is released to 2000 there by design, and current is 0. + driving = [s for s in samples if s['running'] and s['actual_current'] > 0] + if len(driving) < 10: + pytest.skip('plant never ran steadily on this host: %d/%d samples' + % (len(driving), len(samples))) + + avg_ca = sum(s['actual_current'] for s in driving) / len(driving) + peak_ca = max(s['actual_current'] for s in driving) + assert avg_ca <= limit_ca * 1.25, ( + 'current not held: avg %.1f A against a %.1f A limit ' + '(gains too low - see the dead-zone note in control_loop.c)' + % (avg_ca / 100.0, limit_ca / 100.0)) + assert peak_ca <= limit_ca * 1.6, ( + 'current peak %.1f A against a %.1f A limit' + % (peak_ca / 100.0, limit_ca / 100.0)) + # It must be the limiter doing this, not a plant that cannot pull + # the current: the ceiling has to be binding well below full duty. + assert min(s['current_duty_ceiling'] for s in driving) < FULL_CEIL - 100, ( + 'ceiling never bound; the current assertion above is vacuous: %s' + % ([s['current_duty_ceiling'] for s in driving][:8],)) + st = driving[-1] + assert st['duty_limit_ceiling'] == st['current_duty_ceiling'], st + finally: + tx.value = 0 + time.sleep(0.2) + tx.stop() + + +def test_thermal_limit_can_still_be_disabled( + sitl_factory, state_stream, workdir): + '''255 keeps the escape hatch: no derate however hot it gets''' + import sitl_params + + with open(os.path.join(workdir, 'am32_eeprom.bin'), 'wb') as f: + f.write(sitl_params.build_image({'TEMPERATURE_LIMIT': 255})) + + sitl = sitl_factory(extra_args=['--input-type', '1'], can_uri='none') + sim = state_stream(sitl) + assert wait_for_state(sim), sitl.log_tail() + ctl = _open_ctl(sitl) + tx = _keepalive(sitl) + try: + st = _settle_at(sim, ctl, workdir, 130) + assert st['thermal_duty_ceiling'] == FULL_CEIL, ( + 'derate engaged with the limit disabled: %s' % (st,)) + finally: + tx.stop() + + +def test_fine_ramp_keeps_the_startup_rate(sitl_factory, state_stream, workdir): + '''max_ramp < 10 must not hand the spool-up ramp to a cruise setting + + ARK_G431_CAN ships max_ramp 5 (0.5 %/ms, matching larger 12S ESCs). That + is fine mode: one step every 10th 20 kHz tick, so the stored number means + a tenth of what it means in coarse mode. It used to be applied to all + three regimes, which handed RAMP_SPEED_STARTUP to the cruise value and + left the racer plant unable to start at all (test_acq_desync_rail, 12 s, + deterministic). settings.c now scales the coarse startup ceiling into + fine-cadence units instead. + + Read straight out of ZC_STATS: no motor, no timing, so this cannot go + flaky and cannot pass by accident. + ''' + import sitl_params + + with open(os.path.join(workdir, 'am32_eeprom.bin'), 'wb') as f: + f.write(sitl_params.build_image({'MAX_RAMP': 5})) + + sitl = sitl_factory(extra_args=['--input-type', '1'], can_uri='none') + sim = state_stream(sitl) + assert wait_for_state(sim), sitl.log_tail() + ctl = _open_ctl(sitl) + tx = _keepalive(sitl) + try: + st = _zc_stats(ctl) + assert st['ramp_divider'] == 9, 'not in fine mode: %s' % (st,) + # RAMP_SPEED_STARTUP (2, coarse) x10 == the same %/ms at this cadence. + assert st['max_ramp_startup'] == 20, ( + 'startup ramp took the cruise value: %s' % (st,)) + assert st['max_ramp_low'] == 5 and st['max_ramp_high'] == 5, ( + 'cruise regimes did not take the eeprom value: %s' % (st,)) + finally: + tx.stop() diff --git a/Mcu/SITL/tests/test_params.py b/Mcu/SITL/tests/test_params.py index 65e8413c5..644f37188 100644 --- a/Mcu/SITL/tests/test_params.py +++ b/Mcu/SITL/tests/test_params.py @@ -138,3 +138,68 @@ def test_param_save_opcode(sitl_can_factory, state_stream, mcast_uri): assert ok, 'OPCODE_SAVE failed\n' + sitl.log_tail() finally: node.close() + + +def test_param_erase_restores_armed_protections(sitl_can_factory, state_stream, + mcast_uri): + '''a "restore defaults" must not disarm the protections + + OPCODE_ERASE memcpy's default_settings[] over the whole eeprom page, and + that array began life as the AM32 configurator skeleton whose bytes 43/44 + (141/102) sit just OUTSIDE the ranges settings.c arms. So a field param + erase used to silently turn the thermal foldback and the current limiter + off on a shipped ESC — the one config change nobody re-checks afterwards. + + Drives the real opcode and reads the parameters back, so it covers the + array, the post-skeleton band byte written by + apply_post_skeleton_defaults(), and the settings.c coercion together. + scripts/check-erase-defaults.py gates the same values against the + product's factory JSON at build time. + ''' + sitl = sitl_can_factory( + extra_args=['--node-id', '10'], + can_uri=mcast_uri, + wait_s=1.0) + sim = state_stream(sitl) + node, _found = _require_mcast_node(sitl, sim, mcast_uri, our_id=113) + try: + # Move all three away from the shipped values first, so a no-op erase + # cannot pass this test. + for name, value in (('TEMPERATURE_LIMIT', 255), + ('CURRENT_LIMIT', 0), + ('TEMP_DERATE_BAND', 40)): + assert _set_param(node, 10, name, value) is not None, \ + 'setup: could not clear %s\n%s' % (name, sitl.log_tail()) + + ok = False + for _ in range(5): + req = dronecan.uavcan.protocol.param.ExecuteOpcode.Request() + req.opcode = req.OPCODE_ERASE + rsp = _request_wait(node, 10, req, timeout=3.0) + if rsp is not None and rsp.ok: + ok = True + break + time.sleep(0.3) + assert ok, 'OPCODE_ERASE failed\n' + sitl.log_tail() + + temp = _get_param(node, 10, 'TEMPERATURE_LIMIT') + current = _get_param(node, 10, 'CURRENT_LIMIT') + band = _get_param(node, 10, 'TEMP_DERATE_BAND') + for name, rsp in (('TEMPERATURE_LIMIT', temp), ('CURRENT_LIMIT', current), + ('TEMP_DERATE_BAND', band)): + assert rsp is not None, 'read-back of %s failed\n%s' % (name, sitl.log_tail()) + + t = int(temp.value.integer_value) + c = int(current.value.integer_value) # reported in amps (2x the byte) + b = int(band.value.integer_value) + # Src/settings.c arms only inside these ranges; outside is "off". + assert 70 <= t <= 140, \ + 'erase left the thermal foldback disabled: TEMPERATURE_LIMIT=%d' % t + assert 0 < c <= 200, \ + 'erase left the current limiter disabled: CURRENT_LIMIT=%d' % c + assert 5 <= b <= 40, 'erase left a nonsense foldback band: %d' % b + # Shipped ARK_G431_CAN envelope (TARGET_DEFAULT_* in targets.h). + assert (t, c, b) == (105, 200, 15), \ + 'erase restored %s, expected (105, 200, 15)' % ((t, c, b),) + finally: + node.close() diff --git a/Mcu/g431/Inc/peripherals.h b/Mcu/g431/Inc/peripherals.h index dabcc5dfe..0bc15de32 100644 --- a/Mcu/g431/Inc/peripherals.h +++ b/Mcu/g431/Inc/peripherals.h @@ -39,6 +39,7 @@ void MX_TIM17_Init(void); void MX_TIM16_Init(void); void MX_IWDG_Init(void); void MX_TIM6_Init(void); +void MX_TIM7_Init(void); void MX_TIM15_Init(void); void resetInputCaptureTimer(); void setPWMCompare1(uint16_t compareone); @@ -47,3 +48,5 @@ void setPWMCompare3(uint16_t comparethree); void enableCorePeripherals(void); void reloadWatchDogCounter(void); void generatePwmTimerEvent(void); +void LED_GPIO_init(void); +void setIndividualRGBLed(uint8_t red, uint8_t green, uint8_t blue); diff --git a/Mcu/g431/Src/ADC.c b/Mcu/g431/Src/ADC.c index 5b763aec5..2ce8d4ef2 100644 --- a/Mcu/g431/Src/ADC.c +++ b/Mcu/g431/Src/ADC.c @@ -332,18 +332,18 @@ void ADC_Init(void) LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC12); LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); - /**ADC1 GPIO Configuration - PA3 ------> ADC1_IN4 - */ + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC); + /**ADC1 GPIO Configuration */ GPIO_InitStruct.Pin = VOLTAGE_ADC_PIN; GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; - LL_GPIO_Init(GPIOA, &GPIO_InitStruct); + LL_GPIO_Init(VOLTAGE_ADC_PORT, &GPIO_InitStruct); # ifdef USE_CURRENT_SENSE GPIO_InitStruct.Pin = CURRENT_ADC_PIN; GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; - LL_GPIO_Init(GPIOA, &GPIO_InitStruct); + LL_GPIO_Init(CURRENT_ADC_PORT, &GPIO_InitStruct); # endif /* ADC1 DMA Init */ @@ -375,14 +375,29 @@ void ADC_Init(void) ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; LL_ADC_Init(ADC1, &ADC_InitStruct); ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; +# ifdef USE_CURRENT_SENSE + ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_3RANKS; +# else ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS; +# endif ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_LIMITED; ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED; LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct); LL_ADC_SetGainCompensation(ADC1, 0); - LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE); + /* + * Hardware oversampling on the regular sequence (temp / Vbus / current). + * Ratio 16 + right-shift 4 keeps a 12-bit DR result so existing scale + * math (adc_app / temp macros) is unchanged, while averaging 16 SAR + * conversions per rank to cut broadband shunt noise. Continuous mode: + * one software StartConversion still produces one DMA word per rank. + * ARK_G431_CAN current sense is the primary beneficiary; sample time + * stays 47.5 cycles (PWM-sync is a separate step if still noisy). + */ + LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_GRP_REGULAR_CONTINUED); + LL_ADC_SetOverSamplingDiscont(ADC1, LL_ADC_OVS_REG_CONT); + LL_ADC_ConfigOverSamplingRatioShift(ADC1, LL_ADC_OVS_RATIO_16, LL_ADC_OVS_SHIFT_RIGHT_4); ADC_CommonInitStruct.CommonClock = LL_ADC_CLOCK_SYNC_PCLK_DIV4; ADC_CommonInitStruct.Multimode = LL_ADC_MULTI_INDEPENDENT; LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(ADC1), &ADC_CommonInitStruct); diff --git a/Mcu/g431/Src/IO.c b/Mcu/g431/Src/IO.c index 647a9d5ae..d07bb736c 100644 --- a/Mcu/g431/Src/IO.c +++ b/Mcu/g431/Src/IO.c @@ -25,12 +25,18 @@ void receiveDshotDma() RCC->APB1RSTR1 &= ~LL_APB1_GRP1_PERIPH_TIM3; IC_TIMER_REGISTER->CCMR1 = 0x41; IC_TIMER_REGISTER->CCER = 0xa; -#else - - LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_TIM15); // de-init timer 2 +#endif +#ifdef USE_TIMER_15_CHANNEL_1 + LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_TIM15); LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_TIM15); IC_TIMER_REGISTER->CCMR1 = 0x41; IC_TIMER_REGISTER->CCER = 0xa; +#endif +#ifdef USE_TIMER_16_CHANNEL_1 + LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_TIM16); + LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_TIM16); + IC_TIMER_REGISTER->CCMR1 = 0x41; + IC_TIMER_REGISTER->CCER = 0xa; #endif IC_TIMER_REGISTER->PSC = ic_timer_prescaler; IC_TIMER_REGISTER->ARR = 0xFFFF; @@ -54,11 +60,18 @@ void sendDshotDma() IC_TIMER_REGISTER->CCMR1 = 0x60; IC_TIMER_REGISTER->CCER = 0x3; #endif - - LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_TIM15); // de-init timer 2 +#ifdef USE_TIMER_15_CHANNEL_1 + LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_TIM15); LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_TIM15); IC_TIMER_REGISTER->CCMR1 = 0x60; IC_TIMER_REGISTER->CCER = 0x3; +#endif +#ifdef USE_TIMER_16_CHANNEL_1 + LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_TIM16); + LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_TIM16); + IC_TIMER_REGISTER->CCMR1 = 0x60; + IC_TIMER_REGISTER->CCER = 0x3; +#endif IC_TIMER_REGISTER->PSC = output_timer_prescaler; IC_TIMER_REGISTER->ARR = 108; diff --git a/Mcu/g431/Src/comparator.c b/Mcu/g431/Src/comparator.c index b43912a5b..a04fab32e 100644 --- a/Mcu/g431/Src/comparator.c +++ b/Mcu/g431/Src/comparator.c @@ -32,6 +32,7 @@ #include "comparator.h" #include "common.h" +#include "motor_runtime.h" #include "targets.h" COMP_TypeDef *active_COMP = COMP2; @@ -81,6 +82,51 @@ void changeCompInput() LL_COMP_ConfigInputs(active_COMP, PHASE_B_COMP, PHASE_B_INPUT_PLUS); } + /* + * G4 dual-COMP path: F051 enables hyst whenever CI is slow. On this 12S + * free-run high-KV article, pure free-run BEMF can sit near/under 10 mV + * at crawl RPM — always-on 10 mV hyst blocked real edges and latched + * stuck-rotor at 5–6% (bench 2026-08). Only add hyst when the interval + * is slow AND duty is already into a real drive (loaded start / prop), + * where F051 is clean and BEMF is larger. Free-run crawl stays NONE. + * + * Turning hysteresis UP is a standing suggestion for the low-rpm noise + * and it is the wrong direction on this part. DS13122 Rev 4 Table 73: + * LL_COMP_HYSTERESIS_10MV is HYST = 1, which is 9 mV typical but up to + * 16 mV, on top of an input offset of -9..+3 mV. The next step up + * (HYST = 2) is 18 mV typical and up to 32 mV. Against a signal of + * ~132 mV at 1000 rpm falling linearly with speed, code 1 already + * consumes a worst-case 25 mV of the crawl budget - which is what the + * 2026-08 bench saw as blocked edges and a latched stuck rotor. The + * hysteresis knob is out of room here; noise has to be rejected in + * time (ZC_SEARCH_BLANK_64THS, COMP_BLANK_TICKS) or the signal has to + * be read with something that has a lower floor than the comparator. + */ + if (average_interval >= 400 && duty_cycle > 200) { + LL_COMP_SetInputHysteresis(COMP1, LL_COMP_HYSTERESIS_10MV); + LL_COMP_SetInputHysteresis(COMP2, LL_COMP_HYSTERESIS_10MV); + } else { + LL_COMP_SetInputHysteresis(COMP1, LL_COMP_HYSTERESIS_NONE); + LL_COMP_SetInputHysteresis(COMP2, LL_COMP_HYSTERESIS_NONE); + } +#ifdef COMP_BLANK_TICKS + /* + * ST G4 COMP blanking forces the output LOW (not hold-last). That only + * blanks cleanly on falling-BEMF steps (pre-cross low / rising EXTI). On + * rising-BEMF it manufactures falling edges into EXTI. Arm TIM1 OC5 only + * when rising == 0; leave rising steps ungated. + * + * Full design note (ideal hold-last vs ST force-low, Alka, sizing, + * alternatives): doc/g431-comp-blanking.md + */ + if (rising) { + LL_COMP_SetOutputBlankingSource(COMP1, LL_COMP_BLANKINGSRC_NONE); + LL_COMP_SetOutputBlankingSource(COMP2, LL_COMP_BLANKINGSRC_NONE); + } else { + LL_COMP_SetOutputBlankingSource(COMP1, LL_COMP_BLANKINGSRC_TIM1_OC5_COMP1); + LL_COMP_SetOutputBlankingSource(COMP2, LL_COMP_BLANKINGSRC_TIM1_OC5_COMP2); + } +#endif if (rising) { LL_EXTI_DisableRisingTrig_0_31(LL_EXTI_LINE_22); LL_EXTI_DisableRisingTrig_0_31(LL_EXTI_LINE_21); diff --git a/Mcu/g431/Src/eeprom.c b/Mcu/g431/Src/eeprom.c index 23973d599..3d922b81e 100644 --- a/Mcu/g431/Src/eeprom.c +++ b/Mcu/g431/Src/eeprom.c @@ -33,10 +33,14 @@ void save_flash_nolib(uint8_t *data, int length, uint32_t add) FLASH->KEYR = FLASH_FKEY2; } - // erase page if address even divisable by 1024 + // erase page if address is page-aligned (2 KiB). Assign PER rather + // than OR so a leftover PNB from the previous page is not sticky — + // page N then page N+1 would otherwise erase page (N | N+1). if ((add % 2048) == 0) { - FLASH->CR |= FLASH_CR_PER; - FLASH->CR |= (add / 2048) << 3; + FLASH->SR = FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | FLASH_SR_PGSERR | FLASH_SR_MISERR | + FLASH_SR_FASTERR | FLASH_SR_OPERR | FLASH_SR_EOP; + FLASH->CR = FLASH_CR_PER; + FLASH->CR |= ((add / 2048) << FLASH_CR_PNB_Pos) & FLASH_CR_PNB; FLASH->CR |= FLASH_CR_STRT; while ((FLASH->SR & FLASH_SR_BSY) != 0) { /* add time-out */ diff --git a/Mcu/g431/Src/peripherals.c b/Mcu/g431/Src/peripherals.c index 45b65fca9..d8c994fb8 100644 --- a/Mcu/g431/Src/peripherals.c +++ b/Mcu/g431/Src/peripherals.c @@ -9,6 +9,8 @@ #include "peripherals.h" +#include "debug_uart.h" +#include "gate_driver.h" #include "serial_telemetry.h" #include "targets.h" @@ -34,11 +36,18 @@ void initCorePeripherals(void) MX_COMP2_Init(); MX_TIM1_Init(); MX_TIM2_Init(); +#ifdef USE_TIMER_16_CHANNEL_1 MX_TIM16_Init(); + MX_TIM7_Init(); +#else + MX_TIM16_Init(); +#endif MX_COMP1_Init(); MX_TIM17_Init(); MX_TIM6_Init(); +#ifdef USE_TIMER_15_CHANNEL_1 MX_TIM15_Init(); +#endif #ifdef USE_TIMER_3_CHANNEL_1 MX_TIM3_Init(); #endif @@ -148,8 +157,11 @@ void MX_COMP1_Init(void) LL_GPIO_Init(GPIOA, &GPIO_InitStruct); COMP_InitStruct.InputPlus = LL_COMP_INPUT_PLUS_IO1; COMP_InitStruct.InputMinus = LL_COMP_INPUT_MINUS_IO1; + /* Default none; changeCompInput() enables 10 mV when slow+loaded. */ COMP_InitStruct.InputHysteresis = LL_COMP_HYSTERESIS_NONE; COMP_InitStruct.OutputPolarity = LL_COMP_OUTPUTPOL_NONINVERTED; + /* Default none; changeCompInput() arms TIM1 OC5 blanking on the steps + * where it is safe (falling BEMF only — see the comment there). */ COMP_InitStruct.OutputBlankingSource = LL_COMP_BLANKINGSRC_NONE; LL_COMP_Init(COMP1, &COMP_InitStruct); __IO uint32_t wait_loop_index = 0; @@ -179,8 +191,11 @@ void MX_COMP2_Init(void) LL_GPIO_Init(GPIOA, &GPIO_InitStruct); COMP_InitStruct.InputPlus = LL_COMP_INPUT_PLUS_IO2; COMP_InitStruct.InputMinus = LL_COMP_INPUT_MINUS_IO1; + /* Default none; changeCompInput() enables 10 mV when slow+loaded. */ COMP_InitStruct.InputHysteresis = LL_COMP_HYSTERESIS_NONE; COMP_InitStruct.OutputPolarity = LL_COMP_OUTPUTPOL_NONINVERTED; + /* Default none; changeCompInput() arms TIM1 OC5 blanking on the steps + * where it is safe (falling BEMF only — see the comment there). */ COMP_InitStruct.OutputBlankingSource = LL_COMP_BLANKINGSRC_NONE; LL_COMP_Init(COMP2, &COMP_InitStruct); __IO uint32_t wait_loop_index = 0; @@ -249,6 +264,20 @@ void MX_TIM1_Init(void) LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH3); LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH3, &TIM_OC_InitStruct); LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH3); +#ifdef COMP_BLANK_TICKS + /* + * CH5 has no pin - it exists here only to generate OC5REF as the + * comparator blanking source (COMP_BLANK_TICKS in targets.h; + * doc/g431-comp-blanking.md). PWM1 with CCR5=N holds OC5REF high for + * CNT < N, i.e. the first N ticks of every PWM period, spanning the + * dead time and the high-side turn-on transient that ends it. + * No preload: the window is constant and must be live from the first + * period, and it is never rewritten at runtime. + */ + LL_TIM_OC_Init(TIM1, LL_TIM_CHANNEL_CH5, &TIM_OC_InitStruct); + LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH5); + LL_TIM_OC_SetCompareCH5(TIM1, COMP_BLANK_TICKS); +#endif LL_TIM_SetTriggerOutput(TIM1, LL_TIM_TRGO_RESET); LL_TIM_SetTriggerOutput2(TIM1, LL_TIM_TRGO2_RESET); LL_TIM_DisableMasterSlaveMode(TIM1); @@ -386,6 +415,8 @@ void MX_TIM3_Init(void) LL_TIM_IC_SetFilter(TIM3, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); LL_TIM_IC_SetPolarity(TIM3, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_BOTHEDGE); } + +#ifdef USE_TIMER_15_CHANNEL_1 void MX_TIM15_Init(void) { LL_TIM_InitTypeDef TIM_InitStruct = {0}; @@ -430,7 +461,64 @@ void MX_TIM15_Init(void) LL_TIM_IC_SetFilter(TIM15, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); LL_TIM_IC_SetPolarity(TIM15, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_BOTHEDGE); } +#endif +#ifdef USE_TIMER_16_CHANNEL_1 +void MX_TIM16_Init(void) +{ + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; + + LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM16); + + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); + /**TIM16 GPIO Configuration + PB4 ------> TIM16_CH1 (AF1) + */ + GPIO_InitStruct.Pin = INPUT_PIN; + GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; + GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; + GPIO_InitStruct.Alternate = LL_GPIO_AF_1; + LL_GPIO_Init(INPUT_PIN_PORT, &GPIO_InitStruct); + + LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_TIM16_CH1); + LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY); + LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW); + LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL); + LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT); + LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT); + LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD); + LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_WORD); + + TIM_InitStruct.Prescaler = 10; + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct.Autoreload = 65535; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + TIM_InitStruct.RepetitionCounter = 0; + LL_TIM_Init(TIM16, &TIM_InitStruct); + LL_TIM_DisableARRPreload(TIM16); + LL_TIM_IC_SetActiveInput(TIM16, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_IC_SetPrescaler(TIM16, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetFilter(TIM16, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1); + LL_TIM_IC_SetPolarity(TIM16, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_BOTHEDGE); +} + +void MX_TIM7_Init(void) +{ + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM7); + NVIC_SetPriority(TIM7_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0)); + NVIC_EnableIRQ(TIM7_IRQn); + TIM_InitStruct.Prescaler = (PCLK_MHZ / 2) - 1; + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct.Autoreload = 65535; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + LL_TIM_Init(TIM7, &TIM_InitStruct); + LL_TIM_DisableARRPreload(TIM7); +} +#else void MX_TIM16_Init(void) { LL_TIM_InitTypeDef TIM_InitStruct = {0}; @@ -445,6 +533,7 @@ void MX_TIM16_Init(void) LL_TIM_Init(TIM16, &TIM_InitStruct); LL_TIM_DisableARRPreload(TIM16); } +#endif /** * @brief TIM17 Initialization Function @@ -523,6 +612,21 @@ void resetInputCaptureTimer() IC_TIMER_REGISTER->CNT = 0; } +#ifdef USE_DRV_NFAULT +static void initDrvNfault(void) +{ + LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); + GPIO_InitStruct.Pin = NFAULT_PIN; + GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT; + GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; + /* Schematic has 20k to 3.3V; keep weak pull-up as a fail-safe if the + * external network is open. Active low = fault. */ + GPIO_InitStruct.Pull = LL_GPIO_PULL_UP; + LL_GPIO_Init(NFAULT_PORT, &GPIO_InitStruct); +} +#endif + void enableCorePeripherals() { LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1); @@ -531,6 +635,10 @@ void enableCorePeripherals() LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N); LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH2N); LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH3N); +#ifdef COMP_BLANK_TICKS + /* Pinless: drives OC5REF for comparator blanking only. */ + LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH5); +#endif /* Enable counter */ LL_TIM_EnableCounter(TIM1); @@ -548,9 +656,13 @@ void enableCorePeripherals() #ifdef USE_RGB_LED LED_GPIO_init(); - GPIOB->BRR = LL_GPIO_PIN_8; // turn on red - GPIOB->BSRR = LL_GPIO_PIN_5; - GPIOB->BSRR = LL_GPIO_PIN_3; // + RED_PORT->BRR = RED_PIN; // turn on red + GREEN_PORT->BSRR = GREEN_PIN; + BLUE_PORT->BSRR = BLUE_PIN; +#endif + +#ifdef USE_DRV_NFAULT + initDrvNfault(); #endif #ifndef BRUSHED_MODE @@ -561,6 +673,13 @@ void enableCorePeripherals() #endif LL_TIM_EnableCounter(UTILITY_TIMER); LL_TIM_GenerateEvent_UPDATE(UTILITY_TIMER); +#if GATE_DRIVER_SLEEP_SUPPORT + /* After UTILITY_TIMER: wake timing uses get_timer_us16(). */ + gateDriverInit(); +#endif +#ifdef USE_DEBUG_UART + debugUartInit(); +#endif // LL_TIM_EnableCounter(INTERVAL_TIMER); LL_TIM_GenerateEvent_UPDATE(INTERVAL_TIMER); @@ -583,3 +702,51 @@ void enableCorePeripherals() LL_GPIO_SetPinMode(RPM_PULSE_PORT, RPM_PULSE_PIN, LL_GPIO_MODE_OUTPUT); #endif } + +#ifdef USE_RGB_LED + +# define GPIO_PORT_AHB2_CLK(port) (1U << (((uint32_t)(port) - (uint32_t)GPIOA) / ((uint32_t)GPIOB - (uint32_t)GPIOA))) + +void LED_GPIO_init() +{ + LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; + + LL_AHB2_GRP1_EnableClock(GPIO_PORT_AHB2_CLK(RED_PORT) | GPIO_PORT_AHB2_CLK(GREEN_PORT) | GPIO_PORT_AHB2_CLK(BLUE_PORT)); + + LL_GPIO_ResetOutputPin(RED_PORT, RED_PIN); + LL_GPIO_ResetOutputPin(GREEN_PORT, GREEN_PIN); + LL_GPIO_ResetOutputPin(BLUE_PORT, BLUE_PIN); + + GPIO_InitStruct.Pin = RED_PIN; + GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT; + GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; + LL_GPIO_Init(RED_PORT, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GREEN_PIN; + LL_GPIO_Init(GREEN_PORT, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = BLUE_PIN; + LL_GPIO_Init(BLUE_PORT, &GPIO_InitStruct); +} + +void setIndividualRGBLed(uint8_t red, uint8_t green, uint8_t blue) +{ + if (red > 0) { + RED_PORT->BRR = RED_PIN; + } else { + RED_PORT->BSRR = RED_PIN; + } + if (green > 0) { + GREEN_PORT->BRR = GREEN_PIN; + } else { + GREEN_PORT->BSRR = GREEN_PIN; + } + if (blue > 0) { + BLUE_PORT->BRR = BLUE_PIN; + } else { + BLUE_PORT->BSRR = BLUE_PIN; + } +} +#endif diff --git a/Mcu/g431/Src/phaseouts.c b/Mcu/g431/Src/phaseouts.c index 9b048caad..541e9696c 100644 --- a/Mcu/g431/Src/phaseouts.c +++ b/Mcu/g431/Src/phaseouts.c @@ -6,6 +6,7 @@ */ #include "phaseouts.h" +#include "gate_driver.h" #include "targets.h" extern char prop_brake_active; @@ -36,6 +37,7 @@ void proportionalBrake() // alternate mode and turn upper OFF for each // channel // turn all HIGH channels off for ABC + gateDriverEnsure(); LL_GPIO_SetPinMode(PHASE_A_GPIO_PORT_HIGH, PHASE_A_GPIO_HIGH, LL_GPIO_MODE_OUTPUT); PHASE_A_GPIO_PORT_HIGH->HIGH_BITREG_OFF = PHASE_A_GPIO_HIGH; @@ -264,6 +266,7 @@ void allOff() void comStep(int newStep) { + gateDriverEnsure(); // TIM14->CNT = 0; switch (newStep) { case 1: // A-B @@ -308,6 +311,7 @@ void comStep(int newStep) void fullBrake() { // full braking shorting all low sides + gateDriverEnsure(); phaseALOW(); phaseBLOW(); phaseCLOW(); @@ -315,6 +319,7 @@ void fullBrake() void allpwm() { // for stepper_sine + gateDriverEnsure(); phaseAPWM(); phaseBPWM(); phaseCPWM(); diff --git a/Mcu/g431/Src/stm32g4xx_it.c b/Mcu/g431/Src/stm32g4xx_it.c index 851134cda..a185baf94 100644 --- a/Mcu/g431/Src/stm32g4xx_it.c +++ b/Mcu/g431/Src/stm32g4xx_it.c @@ -91,7 +91,7 @@ void DMA1_Channel1_IRQHandler(void) void COMP1_2_3_IRQHandler(void) { - if (INTERVAL_TIMER->CNT > (commutation_interval >> 1)) { + if (INTERVAL_TIMER->CNT > ZC_SEARCH_BLANK(commutation_interval)) { interrupt++; if (LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_22)) { LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_22); @@ -118,11 +118,19 @@ void TIM6_DAC_IRQHandler(void) } } +#ifdef USE_TIMER_16_CHANNEL_1 +void TIM7_IRQHandler(void) +{ + PeriodElapsedCallback(); + LL_TIM_ClearFlag_UPDATE(TIM7); +} +#else void TIM1_UP_TIM16_IRQHandler(void) { PeriodElapsedCallback(); LL_TIM_ClearFlag_UPDATE(TIM16); } +#endif void EXTI15_10_IRQHandler(void) { diff --git a/Mcu/g431/ldscript_CAN.ld b/Mcu/g431/ldscript_CAN.ld index 94477aba7..7166f4925 100644 --- a/Mcu/g431/ldscript_CAN.ld +++ b/Mcu/g431/ldscript_CAN.ld @@ -27,6 +27,17 @@ SECTIONS KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); KEEP(*(.app_signature)) + /* Trailing ALIGN so 0x00 pad to the 8-byte APDescriptor lives in this + section. A leading ALIGN on the next section would be a gap: objcopy + -O binary fills it with 0x00, -O ihex omits it, and the AM32 CRC + (crc2 starts at the end of .app_signature) would fail on a .hex flash. */ + . = ALIGN(8); + } > FLASH1 + + /* APDesc00, 8-byte aligned. PX4 scans a *.bin to EOF for this block. */ + .px4_app_descriptor : + { + KEEP(*(.px4_app_descriptor)) . = ALIGN(4); } > FLASH1 @@ -111,22 +122,34 @@ SECTIONS { . = ALIGN(4); BYTE(0x00); + /* Trailing ALIGN: pad after the odd BYTE stays in this section so + objcopy -O ihex covers the same addresses as -O binary. */ + . = ALIGN(4); + } > FLASH + + /* Bootloader image embedded for the app-side bootloader update (Src/bl_image.S). + Empty unless the build defines EMBED_BOOTLOADER. Own section so the 16 KiB + blob stays attributable in the map and the size asserts have symbols. + Packed after code, not at the end of the FLASH region, so objcopy -O + binary does not pad tens of KiB of unused flash into the app image. */ + .bl_image : + { + _bl_image_start = .; + KEEP (*(.bl_image)) + . = ALIGN(4); + _bl_image_end = .; } > FLASH - /* The file name */ + /* The file name. motor_runtime.c places a 32-byte object here + (FILE_NAME\0VERSION\0 plus zero fill). Do not append BYTE/fill: + that overflowed the 32-byte region by 2 after the version embed. */ .file_name : { - FILL(0x00); . = ALIGN(4); KEEP (*(.file_name)) - BYTE(0x00) + . = ALIGN(4); } >FILE_NAME - .filename_fill : - { - BYTE(0x00); - } > FILE_NAME - /* Used by the startup to initialize data */ _sidata = LOADADDR(.data); @@ -189,3 +212,17 @@ SECTIONS .ARM.attributes 0 : { *(.ARM.attributes) } } + +/* The embedded bootloader image is written over the bootloader region, which + runs from the start of flash up to the app at ORIGIN(FLASH1). Size 0 is the + non-embedding build. A future MEMORY edit that puts the blob inside the + region it erases, or changes the region size, must fail the link. */ +_bl_region_start = 0x08000000; +_bl_region_size = ORIGIN(FLASH1) - _bl_region_start; + +ASSERT(_bl_image_end - _bl_image_start == 0 || + _bl_image_end - _bl_image_start == _bl_region_size, + "embedded bootloader image must be exactly the size of the bootloader region") + +ASSERT(_bl_image_start >= ORIGIN(FLASH1), + "embedded bootloader image must not live inside the region it erases") diff --git a/README.md b/README.md index b86a17f85..a4abea0eb 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,10 @@ Source: [`Src/runtime_loop.c`](Src/runtime_loop.c) (governor), [`Src/faults.c`]( On the ARK 4IN1 the DRV8328 is put to sleep (`nSLEEP`) when the ESC is not driving, braking, or beeping. See [`Src/gate_driver.c`](Src/gate_driver.c). +### G431 COMP blanking (turn-on noise) + +On **ARK 12S CAN** (STM32G4), TIM1 OC5 can blank the comparator output over the PWM turn-on transient. ST’s blanking **forces the output low** (it does not hold the last level), so it is only safe on **falling-BEMF** steps; rising-BEMF steps run ungated. Design note, Alka/ST semantics, knobs, and bench checks: [doc/g431-comp-blanking.md](doc/g431-comp-blanking.md). + ### Global refactor Large control-path split out of a monolithic `main.c` into focused modules (runtime, settings, motor control helpers, and related MCU/F051 work). The goal is safer changes, clearer ownership of hot paths, and room for instrumentation without growing one file forever. @@ -108,7 +112,7 @@ make -j$(nproc) ARK_4IN1_F051 # Production full-flash image (bootloader + app + factory EEPROM defaults) make factory-image -# -> obj/ARK32_ARK_4IN1_F051_.factory.bin (flash at 0x08000000) +# -> obj/ARK32_ARK_4IN1_F051_.factory.img (flash at 0x08000000) make factory-image-check # same + layout/defaults gate (CI) ``` @@ -130,7 +134,7 @@ That links the release app, then runs [`scripts/build_factory_image.py`](scripts | Application @ `0x08001000` | `make ARK_4IN1_F051` | | EEPROM @ `0x08007C00` | [`factory/ARK_4IN1_F051_eeprom_defaults.json`](factory/ARK_4IN1_F051_eeprom_defaults.json) | -Ship/program `obj/ARK32_ARK_4IN1_F051_*.factory.bin` (or `.factory.hex`). Defaults (variable PWM, 1020 kV, 2 %/ms ramp, 15° fixed advance, PWM min/max 1020/1980 µs) are documented in [`factory/README.md`](factory/README.md). +Ship/program `obj/ARK32_ARK_4IN1_F051_*.factory.img` (or `.factory.hex`). Defaults (variable PWM, 1020 kV, 2 %/ms ramp, 15° fixed advance, PWM min/max 1020/1980 µs) are documented in [`factory/README.md`](factory/README.md). Optional static analysis / size / format helpers: @@ -252,15 +256,45 @@ Other DShot commands (direction, bi-dir, EDT, programming mode, etc.) do **not** ## Bootloader -ARK ESCs use **[ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader)** (fork of upstream [AM32-bootloader](https://github.com/am32-firmware/AM32-bootloader)). Use ARK release images for ARK hardware — not the stock upstream bootloader alone when you need ARK-specific fixes (e.g. bidirectional DShot idle detection). +ARK ESCs use **[ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader)** (fork of upstream [AM32-bootloader](https://github.com/am32-firmware/AM32-bootloader)). The **ARK 12S CAN** bootloader is built from in-tree [`bootloader/`](bootloader/) (`make bootloader-g431-can`). F051 still uses committed ARK32-bootloader images. | | | |--|--| -| Source / releases | [ARK-Electronics/ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader) · [releases](https://github.com/ARK-Electronics/ARK32-bootloader/releases) | -| Committed F051 image for app embed | [`Bootloaders/`](Bootloaders/) (see [Bootloaders/README.md](Bootloaders/README.md)) | -| App-side BL update | F051 builds embed the image by default (including `HWCI_PERF=1`) and rewrite the on-chip BL if it differs (`Src/bootloader_update.c`). Success soft-resets; the next boot plays **`playBootloaderUpdatedTone`** (two rising beeps) then the normal startup tune. Strip with `EMBED_BOOTLOADER=0` or `NO_EMBED_BL=1`. | +| G431 CAN (ARK 12S) source | [`bootloader/`](bootloader/) — `make bootloader-g431-can` | +| Other images / releases | [ARK-Electronics/ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader) · [releases](https://github.com/ARK-Electronics/ARK32-bootloader/releases) | +| Committed images for app embed | [`Bootloaders/`](Bootloaders/) (see [Bootloaders/README.md](Bootloaders/README.md)) | +| App-side BL update | F051 release and **every G431 CAN** build embed the image and rewrite the on-chip BL if it differs (`Src/bootloader_update.c`). Success soft-resets; the next boot plays **`playBootloaderUpdatedTone`** (two rising beeps) then the normal startup tune. Strip with `EMBED_BOOTLOADER=0` or `NO_EMBED_BL=1`. F051 `HWCI_PERF=1` omits the blob (flash is tight); G431 CAN keeps it. | + +To put ARK32 on a **blank production ESC**, flash the full-chip factory image (`make factory-image` → `obj/*.factory.img` at `0x08000000`) so bootloader, app, and EEPROM defaults land in one step — see [factory/README.md](factory/README.md). For development or field app-only updates, flash the application `.bin`/`.hex` at the app base (F051 `0x08001000`, G431 CAN `0x08004000`). The first boot applies a newer BL via the embed path above if the on-chip image differs. + +Do **not** copy a factory image to a PX4 SD card. PX4 globs every `*.bin` on the card root and scans the **whole file** for an APDescriptor (the documented 1 KiB window is a no-op). A G431 `*.factory.bin` would be staged as `/ufw/71.bin` and written at the app base. Factory artifacts are `.factory.img` / `.factory.hex` so that cannot happen. + +### PX4 SD-card update (ARK 12S CAN) + +`make ARK_G431_CAN` signs the application image with a PX4 APDescriptor and emits a second copy named for the SD-card workflow: + +```text +obj/-..uavcan.bin +``` + +`board_id` is `(hw_major << 8) | hw_minor`. This target reports hardware version **0.71**, so `board_id` is **71**. The rest of the name is the numeric ship version (no `-ark`) and the 8-digit git hash from the APDescriptor, e.g. `71-3.0.2.59efc137.uavcan.bin`. + +1. Copy **only** that `.uavcan.bin` to the **root** of the PX4 SD card (or to `ufw_staging/`). Do not copy `*.factory.img`, `*.factory.hex`, or any other `*.bin`. +2. In QGC **Actuators**, unassign this ESC (clear it as a motor). An assigned motor makes PX4 publish `esc.RawCommand` and that blocks the update — see below. +3. Reboot the flight controller. PX4 copies the file to `/ufw/71.bin`. That is **staging**, not the flash. +4. Keep the vehicle **disarmed**. Wait until the ESC is `OPERATIONAL` and GetNodeInfo shows the new `image_crc` / git hash, then re-assign the motor. + +The same bytes are in `obj/AM32_ARK_G431_CAN_.bin`. The `.uavcan.bin` name is what PX4's firmware database records; the APDescriptor inside is what makes PX4 accept the file. The factory full-flash image is a different artifact (`.factory.img`) and must not go on the SD card. + +**How PX4 actually updates this ESC** + +- **Staging is not flashing.** `/ufw/71.bin` only means PX4 ingested a signed image for board_id 71. The ESC does not change until it accepts `BeginFirmwareUpdate` and the bootloader writes the app. +- **CRC match skips the node.** PX4 compares the file's APDescriptor `image_crc` to GetNodeInfo. It sends `BeginFirmwareUpdate` only if the node's CRC is 0, the reported software version is 0.0, or the CRCs differ. Same CRC → file sits in `/ufw/` and nothing is written. +- **The bootloader does not scan the SD card.** It never starts an update on its own. It only flashes when it receives `BeginFirmwareUpdate`, or when the app accepted that request and left an RTC flag before reset. +- **An assigned motor boots the app first.** The bootloader stays in `MAINTENANCE` until it sees a valid signal. PX4 `esc.RawCommand` on this ESC's index (the usual result of mapping it as a motor in Actuators) sets that signal and the bootloader jumps to the app. PX4's updater typically runs *after* actuators are already publishing, so the node it talks to is the app, not the bootloader. The bootloader will not "catch" a pending `/ufw/71.bin` on power-up while that motor is assigned. +- **The app refuses the request unless idle.** `BeginFirmwareUpdate` is dropped with no reply when the motor is running and `newinput != 0` (`safe_to_write_settings()` in `Src/DroneCAN/DroneCAN.c`). PX4 times out and retries; the flash never starts. Unassign the ESC (or otherwise stop `RawCommand`) and leave the vehicle disarmed so throttle is zero. -To put ARK32 on a **blank production ESC**, flash the full-chip factory image (`make factory-image` → `obj/*factory.bin` at `0x08000000`) so bootloader, app, and EEPROM defaults land in one step — see [factory/README.md](factory/README.md). For development or field app-only updates, flash a matching **ARK32-bootloader** with ST-LINK (if needed), then the application `.bin`/`.hex` at `0x08001000` (or use a configurator / one-wire serial). Later app flashes can also carry and apply a newer BL via the embed path above. +Field sequence that works: copy the `.uavcan.bin` → unassign the ESC in Actuators → reboot the FC → wait for `OPERATIONAL` + new CRC → re-assign. ## EEPROM settings diff --git a/Src/DroneCAN/DroneCAN.c b/Src/DroneCAN/DroneCAN.c index 958804e5b..f54344cce 100644 --- a/Src/DroneCAN/DroneCAN.c +++ b/Src/DroneCAN/DroneCAN.c @@ -23,11 +23,44 @@ # include "phaseouts.h" # include "functions.h" # include "filter.h" +# include "debug_uart.h" +# include "settings.h" +# include "motor_runtime.h" +# include "esc_state.h" // include the headers for the generated DroneCAN messages from the // dronecan_dsdlc compiler # include "dsdl_generated/dronecan_msgs.h" +# if CANARD_ENABLE_CANFD +static bool dronecan_tx_canfd; + +static void dc_note_rx_frame(const CanardCANFrame *f) +{ + /* Latch CAN FD TX on the first FD frame. Classic DNA/NodeStatus on + * an FD bus must not flip Status and telemetry back to classic. */ + if (f->canfd) { + dronecan_tx_canfd = true; + } +} + +# define DC_BROADCAST(...) canardBroadcast(__VA_ARGS__, dronecan_tx_canfd) +# define DC_RESPOND(...) canardRequestOrRespond(__VA_ARGS__, dronecan_tx_canfd) +# else +static void dc_note_rx_frame(const CanardCANFrame *f) +{ + (void)f; +} +# define DC_BROADCAST canardBroadcast +# define DC_RESPOND canardRequestOrRespond +# endif + +# if CANARD_ENABLE_TAO_OPTION +# define DC_ENCODE(fn, pkt, buf) fn((pkt), (buf), !dronecan_tx_canfd) +# else +# define DC_ENCODE(fn, pkt, buf) fn((pkt), (buf)) +# endif + # ifndef PREFERRED_NODE_ID # define PREFERRED_NODE_ID 0 # endif @@ -50,6 +83,11 @@ static bool done_startup; # define APP_SIGNATURE_MAGIC1 0x68f058e6 # define APP_SIGNATURE_MAGIC2 0xafcee5a0 +# ifndef DRONECAN_HW_VERSION_MAJOR +# define DRONECAN_HW_VERSION_MAJOR 0 +# define DRONECAN_HW_VERSION_MINOR 71 +# endif + /* application signature, filled in by set_app_signature.py @@ -75,6 +113,57 @@ const struct { .mcu = AM32_MCU, }; +/* + PX4 APDescriptor (APDesc00). PX4 copies any SD-card-root .bin whose first + 1 KiB contains this block to /ufw/.bin and flashes nodes whose + GetNodeInfo hardware_version matches board_id = (hw_major << 8) | hw_minor. + + CRCs / image_size / git_hash are filled by scripts/px4_uavcan_image.py + *before* set_app_signature.py so the AM32 bootloader CRC stays valid. + GetNodeInfo reads image_crc back so PX4 does not re-flash every boot. + volatile: those fields are 0 at compile time; without it LTO folds + GetNodeInfo into literal zeros and PX4 sees image_crc == 0 forever. + */ +# define PX4_APDESC_SIGNATURE_0 0x40 +# define PX4_APDESC_SIGNATURE_1 0xa2 +# define PX4_APDESC_SIGNATURE_2 0xe4 +# define PX4_APDESC_SIGNATURE_3 0xf1 +# define PX4_APDESC_SIGNATURE_4 0x64 +# define PX4_APDESC_SIGNATURE_5 0x68 +# define PX4_APDESC_SIGNATURE_6 0x91 +# define PX4_APDESC_SIGNATURE_7 0x06 + +struct px4_app_descriptor { + uint8_t signature[8]; + union { + uint64_t image_crc; + struct { + uint32_t crc32_block1; + uint32_t crc32_block2; + }; + }; + uint32_t image_size; + uint32_t git_hash; + uint8_t major_version; + uint8_t minor_version; + uint16_t board_id; + uint8_t reserved[8]; +} __attribute__((packed)); + +_Static_assert(sizeof(struct px4_app_descriptor) == 36, "PX4 APDescriptor must be 36 bytes"); + +const volatile struct px4_app_descriptor px4_app_descriptor __attribute__((used, aligned(8))) AM32_FLASH_SECTION(".px4_app_descriptor") = { + .signature = {PX4_APDESC_SIGNATURE_0, PX4_APDESC_SIGNATURE_1, PX4_APDESC_SIGNATURE_2, PX4_APDESC_SIGNATURE_3, + PX4_APDESC_SIGNATURE_4, PX4_APDESC_SIGNATURE_5, PX4_APDESC_SIGNATURE_6, PX4_APDESC_SIGNATURE_7}, + .image_crc = 0, + .image_size = 0, + .git_hash = 0, + .major_version = VERSION_MAJOR, + .minor_version = VERSION_MINOR, + .board_id = (uint16_t)((DRONECAN_HW_VERSION_MAJOR << 8) | DRONECAN_HW_VERSION_MINOR), + .reserved = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, +}; + enum VarType { T_BOOL = 0, T_UINT8, @@ -110,6 +199,7 @@ static struct PACKED { } debug1; static void can_printf(const char *fmt, ...); +static uint32_t millis32(void); // some convenience macros # define MIN(a, b) ((a) < (b) ? (a) : (b)) @@ -175,25 +265,82 @@ static const struct parameter { {"BEEP_VOLUME", T_UINT8, 0, 11, 5, &eepromBuffer.beep_volume}, {"VARIABLE_PWM", T_UINT8, 0, 2, 1, &eepromBuffer.variable_pwm}, {"PWM_FREQUENCY", T_UINT8, 8, 144, 24, &eepromBuffer.pwm_frequency}, + /* Still upstream's 160 (16 %/ms), NOT the 5 the 12S board ships. Moving + * it means an erase also changes the SITL seed image, and six + * fault-injection tests provoke desync by slewing fast - at 0.5 %/ms + * they cannot create the condition they assert about. Fixing that + * means pinning the ramp inside those tests, which is the right change + * but not this one. Until then an erase widens the ramp 32x. */ {"MAX_RAMP", T_UINT8, 1, 200, 160, &eepromBuffer.max_ramp}, {"MIN_DUTY_CYCLE", T_UINT8, 0, 50, 4, &eepromBuffer.minimum_duty_cycle}, - {"USE_SIN_START", T_BOOL, 0, 1, 0, &eepromBuffer.use_sine_start}, {"COMP_PWM", T_BOOL, 0, 1, 1, &eepromBuffer.comp_pwm}, - {"STUCK_ROTOR_PROTECTION", T_BOOL, 0, 1, 1, &eepromBuffer.stuck_rotor_protection}, {"ADVANCE_LEVEL", T_UINT8, 0, 30, 26, &eepromBuffer.advance_level}, {"AUTO_ADVANCE", T_BOOL, 0, 1, 0, &eepromBuffer.auto_advance}, - {"STARTUP_POWER", T_UINT8, 50, 150, 10, &eepromBuffer.startup_power}, - {"CURRENT_LIMIT", T_UINT8, 0, 200, 0, &eepromBuffer.limits.current}, - {"TEMPERATURE_LIMIT", T_UINT8, 70, 255, 255, &eepromBuffer.limits.temperature}, - {"LOW_VOLTAGE_CUTOFF", T_BOOL, 0, 1, 0, &eepromBuffer.low_voltage_cut_off}, + /* Was 10, below this parameter's own minimum of 50: an out-of-range + * stored byte got "repaired" to another out-of-range value. */ + {"STARTUP_POWER", T_UINT8, 50, 150, 100, &eepromBuffer.startup_power}, + {"STALL_PROTECTION", T_UINT8, 0, 3, 0, &eepromBuffer.stall_protection}, + {"STUCK_ROTOR_PROTECTION", T_BOOL, 0, 1, 1, &eepromBuffer.stuck_rotor_protection}, + {"DISABLE_STICK_CALIBRATION", T_BOOL, 0, 1, 0, &eepromBuffer.disable_stick_calibration}, + {"SERVO_LOW_THRESHOLD", T_UINT8, 0, 250, 128, &eepromBuffer.servo.low_threshold}, + {"SERVO_HIGH_THRESHOLD", T_UINT8, 0, 250, 128, &eepromBuffer.servo.high_threshold}, + {"SERVO_NEUTRAL", T_UINT8, 0, 250, 128, &eepromBuffer.servo.neutral}, + {"SERVO_DEAD_BAND", T_UINT8, 0, 250, 50, &eepromBuffer.servo.dead_band}, + /* 0 = off, 1 = per-cell, 2 = absolute. settings.c reads all three, but + * this was exposed as a bool, so mode 2 was unreachable over CAN. */ + {"LOW_VOLTAGE_CUTOFF", T_UINT8, 0, 2, 0, &eepromBuffer.low_voltage_cut_off}, {"CELL_VOLTAGE_THRESHOLD", T_UINT16, 250, 350, 300, &low_cell_volt_cutoff}, - {"BRAKE_ON_STOP", T_BOOL, 0, 1, 1, &eepromBuffer.brake_on_stop}, + {"ABSOLUTE_VOLTAGE_CUTOFF", T_UINT8, 1, 100, 10, &eepromBuffer.absolute_voltage_cutoff}, + /* Defaults are the stored encoding, which for CURRENT_LIMIT is 2 A per + * count: 100 = 200 A (also the largest value settings.c will arm). Both + * match default_settings[43]/[44] - load_settings() falls back to these + * when the stored byte is out of range, and an out-of-range byte must + * not be a back door to a disabled limiter. 255 remains settable to + * turn the thermal derate off deliberately. */ + {"CURRENT_LIMIT", T_UINT8, 0, 200, 100, &eepromBuffer.limits.current}, + /* The current-limit PID in the units the loop uses: settings.c computes + * Kp = current_P * 2 and Kd = current_D * 2, so these scale x2 on read + * and /2 on write exactly like CURRENT_LIMIT - hence max 510 for a + * byte-backed field, and the uint16 max_value in load_settings. + * + * Read the dead-zone note in control_loop.c before lowering Kp: the + * ceiling moves by pid_output/10000 duty units per tick, and that + * integer divide means the loop is inert until the overshoot exceeds + * 5000/Kp centiamps. Kp 200 resolves ~0.5 A; Kp 10 needs 10 A, and + * measured in SITL it fails to hold the limit at all. */ + {"CURRENT_P", T_UINT8, 0, 510, 200, &eepromBuffer.current_P}, + {"CURRENT_I", T_UINT8, 0, 255, 0, &eepromBuffer.current_I}, + {"CURRENT_D", T_UINT8, 0, 510, 100, &eepromBuffer.current_D}, + {"TEMPERATURE_LIMIT", T_UINT8, THERMAL_LIMIT_MIN_C, 255, TARGET_DEFAULT_TEMPERATURE_LIMIT, &eepromBuffer.limits.temperature}, + /* Foldback slope: degrees from the onset to full derate. The response + * is always a ramp - there is no hard-cut mode to select, because + * dropping a motor outright on a multirotor is worse than flying on a + * derated one. This is the knob that sets how abrupt the ramp is. */ + {"TEMP_DERATE_BAND", T_UINT8, 5, 40, 15, &eepromBuffer.can.temp_derate_band}, + /* 0 = off, 1 = brake, 2 = active brake (requires arming) - also was a + * bool, so mode 2 could not be selected over CAN. */ + {"BRAKE_ON_STOP", T_UINT8, 0, 2, 0, &eepromBuffer.brake_on_stop}, {"DRIVING_BRAKE_STRENGTH", T_UINT8, 1, 10, 10, &eepromBuffer.driving_brake_strength}, {"DRAG_BRAKE_STRENGTH", T_UINT8, 1, 10, 10, &eepromBuffer.drag_brake_strength}, - {"INPUT_SIGNAL_TYPE", T_UINT8, 0, 5, 5, &eepromBuffer.input_type}, + {"ACTIVE_BRAKE_POWER", T_UINT8, 0, 5, 2, &eepromBuffer.active_brake_power}, + {"RC_CAR_REVERSE", T_BOOL, 0, 1, 0, &eepromBuffer.rc_car_reverse}, + {"USE_SIN_START", T_BOOL, 0, 1, 0, &eepromBuffer.use_sine_start}, + {"SINE_MODE_CHANGEOVER_THROTTLE", T_UINT8, 5, 25, 15, &eepromBuffer.sine_mode_changeover_thottle_level}, + {"SINE_MODE_POWER", T_UINT8, 1, 10, 6, &eepromBuffer.sine_mode_power}, + {"USE_HALL_SENSORS", T_BOOL, 0, 1, 0, &eepromBuffer.use_hall_sensors}, + {"SERIAL_TELEM_INTERVAL", T_UINT8, 0, 255, 0, &eepromBuffer.telemetry_on_interval}, + /* 0=AUTO (detect DShot/PWM; DroneCAN wins while RawCommand is live), + * 1=DSHOT, 2=SERVO, 3=SERIAL, 4=EDT_ARM, 5=DRONECAN-only (DShot/PWM IRQ off). */ + {"INPUT_SIGNAL_TYPE", T_UINT8, 0, 5, 0, &eepromBuffer.input_type}, {"INPUT_FILTER_HZ", T_UINT8, 0, 100, 0, &eepromBuffer.can.filter_hz}, # ifdef CAN_TERM_PIN {"CAN_TERM_ENABLE", T_BOOL, 0, 1, 0, &eepromBuffer.can.term_enable}, +# endif +# ifdef MCU_G431 + /* 0 = auto-match host CAN FD data bitrate (1/2/4/5 Mbps). 1/2/4/5 pins + * the data phase. Product max is 5 Mbps. Status/telemetry TX is CAN FD + * once locked; DNA stays classic. */ + {"CAN_FD_MBPS", T_UINT8, 0, 5, 0, &eepromBuffer.can.fd_mbps}, # endif {"STARTUP_TUNE", T_STRING, 0, 4, 0, &eepromBuffer.tune}, }; @@ -218,7 +365,8 @@ static void load_settings(void) case T_BOOL: case T_UINT8: { uint8_t *pvalue = (uint8_t *)p->ptr; - uint8_t max_value = p->max_value; + /* uint16: CURRENT_P/D advertise 510 (Kp = byte * 2). */ + uint16_t max_value = p->max_value; if (pvalue == &eepromBuffer.limits.current) { max_value = max_value / 2; } @@ -244,6 +392,25 @@ static void save_settings(void) { saveEEpromSettings(); can_printf("saved settings"); + debugUartPrint("param: saved settings\r\n"); +} + +/* + pending deferred save state. Param sets only mark settings dirty; + the main DroneCAN_update loop coalesces bursts into a single flash + write once the bus has been quiet for SETTINGS_SAVE_QUIET_MS. + (from am32-firmware/AM32#359) + */ +# define SETTINGS_SAVE_QUIET_MS 500 +static struct { + bool dirty; + uint32_t last_change_ms; +} pending_save; + +static void mark_settings_dirty(void) +{ + pending_save.dirty = true; + pending_save.last_change_ms = millis32(); } /* @@ -311,9 +478,55 @@ static uint32_t millis32(void) default settings, based on public/assets/eeprom_default.bin in AM32 configurator update to 2.19 default */ +/* Byte 46 = input_type: AUTO_IN (0) — first available of DShot/PWM; DroneCAN + * prioritised while RawCommand stream is live (see DroneCAN_active). + * + * Bytes 43/44 = temperature/current limit, and they are ARK values, not the + * configurator's. Upstream ships 0x8d (141) and 0x66 (102), which are both + * just OUTSIDE the ranges settings.c arms (70..140 C, 1..100 raw = 2..200 A), + * so the stock eeprom silently disables both limiters. That is where the + * factory JSONs got their disabled values from, by copying this skeleton. + * A DroneCAN param ERASE memcpy's this array over the whole page, so leaving + * it stock would mean a "restore defaults" quietly turns the protections off + * on a shipped ESC - the one config change nobody would think to re-check. + * + * 105 C: foldback onset, in line with professional 12S practice (APD/T-Motor + * derate around 110) and inside the G4 die sensor's factory calibration span + * (30..110 C). CAVEAT worth carrying: those vendors read an NTC on the power + * stage, this reads the MCU die, and the die-to-FET delta on this board has + * not been benched - if it turns out large, the onset belongs lower. + * 100 raw = 200 A: the highest the enable gate accepts, ~80% of the ARK 12S + * shunt rating, and above anything a healthy craft draws - a backstop, not a + * flight limiter. + * + * The foldback WIDTH (byte 184) is past the end of this array, so an erase + * leaves it 0xFF and settings.c coerces it to the 15 C default. + * + * NOTE this array is still upstream's everywhere else, so an erase also + * reverts e.g. byte 5 max_ramp to 0xa0 (16 %/ms) rather than the ARK 2 %/ms. + * That is a pre-existing hole in the erase path and wants its own fix. + */ static const uint8_t default_settings[] = {0x01, 0x03, 0x01, 0x01, 0x23, 0xa0, 0x04, 0x00, 0x0a, 0x64, 0x00, 0x32, 0x02, 0x30, 0x35, 0x31, 0x20, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x1a, 0x18, 0x64, 0x37, 0x0e, 0x00, 0x00, 0x05, 0x00, - 0x80, 0x80, 0x80, 0x32, 0x00, 0x32, 0x00, 0x00, 0x0f, 0x0a, 0x0a, 0x8d, 0x66, 0x06, 0x01, 0x00}; + 0x80, 0x80, 0x80, 0x32, 0x00, 0x32, 0x00, 0x00, 0x0f, 0x0a, 0x0a, 0x69, 0x64, 0x06, 0x00, 0x00}; +/* Bytes 43/44 above are TARGET_DEFAULT_TEMPERATURE_LIMIT / _CURRENT_LIMIT + (105, 100) spelled as literals on purpose: Mcu/SITL/sitl_params.py parses + this array to build eeprom images, so it has to stay pure hex. Agreement + with the macros and with each product JSON is gated by + scripts/check-erase-defaults.py in CI. */ + +/* + Bytes the erase has to restore that live PAST the 48-byte configurator + skeleton. Without this the band would come back 0xFF and settings.c would + coerce it to the compile-time default - which happens to equal what + ARK_G431_CAN ships, so the behaviour is right today by coincidence and + would break silently the first time a product shipped a different band. + Keep the skeleton itself 48 bytes so it stays configurator-compatible. + */ +static void apply_post_skeleton_defaults(void) +{ + eepromBuffer.can.temp_derate_band = TARGET_DEFAULT_TEMP_DERATE_BAND; +} # ifdef MCU_SITL // let the SITL eeprom emulation seed a missing eeprom file with defaults @@ -329,24 +542,107 @@ static const uint8_t advance_level_v3_remap[] = { 0x00, 0x08, 0x10, 0x16 // old values 0-3 map to new values 0,8,16,22 }; -// printf to CAN LogMessage for debugging -static void can_printf(const char *fmt, ...) +/* + * Broadcast uavcan.protocol.debug.LogMessage. + * level: UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_* + */ +static void can_log(uint8_t level, const char *fmt, ...) { struct uavcan_protocol_debug_LogMessage pkt; memset(&pkt, 0, sizeof(pkt)); + pkt.level.value = level; + /* Short fixed source so GUI tools group ESC logs. */ + static const char src[] = "AM32"; + pkt.source.len = (uint8_t)(sizeof(src) - 1u); + memcpy(pkt.source.data, src, pkt.source.len); + uint8_t buffer[UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_MAX_SIZE]; va_list ap; va_start(ap, fmt); uint32_t n = vsnprintf((char *)pkt.text.data, sizeof(pkt.text.data), fmt, ap); va_end(ap); - pkt.text.len = MIN(n, sizeof(pkt.text.data)); + pkt.text.len = (uint8_t)MIN(n, sizeof(pkt.text.data)); - uint32_t len = uavcan_protocol_debug_LogMessage_encode(&pkt, buffer); + uint32_t len = DC_ENCODE(uavcan_protocol_debug_LogMessage_encode, &pkt, buffer); static uint8_t logmsg_transfer_id; - canardBroadcast(&canard, UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_SIGNATURE, UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_ID, &logmsg_transfer_id, - CANARD_TRANSFER_PRIORITY_LOW, buffer, len); + DC_BROADCAST(&canard, UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_SIGNATURE, UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_ID, &logmsg_transfer_id, + CANARD_TRANSFER_PRIORITY_LOW, buffer, len); +} + +/* printf-style LogMessage at INFO (param/save notices, etc.). */ +static void can_printf(const char *fmt, ...) +{ + /* Wrap through can_log so source/level stay consistent. */ + char buf[90]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_INFO, "%s", buf); +} + +/* + * One-shot fault LogMessages: rising edge of nFAULT latch or stuck-rotor + * latch. nFAULT also forces ESC_FAULT_STUCK — prefer the classified + * gate-driver cause ("UVLO"/"OCP"/"OTW"/"nFAULT") when both rise together. + */ +static void DroneCAN_pollFaultLogMessages(void) +{ + static uint8_t prev_nfault; + static uint8_t prev_stuck; + + const uint8_t nfault = faultGateDriverFaultActive(); + const uint8_t stuck = (uint8_t)(escGetState() == ESC_FAULT_STUCK); + + if (nfault && !prev_nfault) { + /* ADC-guessed class (DRV pin is OR-only on hardware-interface parts). */ + const fault_id_t cause = faultGateDriverCause(); + switch (cause) { + case FAULT_GD_UVLO: + can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT UVLO"); + break; + case FAULT_GD_OCP: + can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT OCP"); + break; + case FAULT_GD_OTW: + can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT OTW"); + break; + default: + can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT"); + break; + } + } else if (stuck && !prev_stuck) { + can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "stuck"); + } + + prev_nfault = nfault; + prev_stuck = stuck; +} + +/* Map ESC / gate state into NodeStatus.health for 1 Hz NodeStatus. */ +static uint8_t DroneCAN_nodeHealth(void) +{ + const esc_state_t st = escGetState(); + + /* Cannot drive: stuck latch, gate-driver trip, or LVC. */ + if (st == ESC_FAULT_STUCK || faultGateDriverFaultActive() || st == ESC_FAULT_LVC) { + return UAVCAN_PROTOCOL_NODESTATUS_HEALTH_CRITICAL; + } + /* Signal loss is usually followed by reset — still an error while held. */ + if (st == ESC_FAULT_SIGNAL) { + return UAVCAN_PROTOCOL_NODESTATUS_HEALTH_ERROR; + } + /* + * WARNING only for established hard trips (faultErrorCount: stall + + * established jump desync) or active post-desync holdoff. Acquisition + * roughness and commanded-stop coast (PX4 disarm) must stay OK. + */ + if (faultErrorCount() > 0 || faultDesyncRestartHoldoffActive()) { + return UAVCAN_PROTOCOL_NODESTATUS_HEALTH_WARNING; + } + return UAVCAN_PROTOCOL_NODESTATUS_HEALTH_OK; } /* @@ -374,6 +670,8 @@ static void handle_param_GetSet(CanardInstance *ins, CanardRxTransfer *transfer) if (p != NULL && req.name.len != 0 && req.value.union_tag != UAVCAN_PROTOCOL_PARAM_VALUE_EMPTY) { const char last_dir_reversed = eepromBuffer.dir_reversed; const char last_bi_direction = eepromBuffer.bi_direction; + int32_t set_log_val = 0; + uint8_t set_log_is_str = 0; /* a parameter set command @@ -381,21 +679,32 @@ static void handle_param_GetSet(CanardInstance *ins, CanardRxTransfer *transfer) switch (p->vtype) { case T_UINT8: { uint8_t *ptr8 = (uint8_t *)p->ptr; - if (ptr8 == &eepromBuffer.limits.current) { + if (ptr8 == &eepromBuffer.limits.current || ptr8 == &eepromBuffer.current_P || + ptr8 == &eepromBuffer.current_D) { *ptr8 = req.value.integer_value / 2; + set_log_val = (int32_t)req.value.integer_value; /* user-facing amps */ } else { *ptr8 = req.value.integer_value; + set_log_val = (int32_t)req.value.integer_value; } if (ptr8 == &eepromBuffer.advance_level) { *ptr8 = req.value.integer_value + 10; // adjust for advance level offset for eeprom v3 + set_log_val = (int32_t)req.value.integer_value; + } + if (ptr8 == &eepromBuffer.motor_poles) { + applyMotorIdentitySettings(); } break; } case T_UINT16: { uint16_t *ptr16 = (uint16_t *)p->ptr; *ptr16 = req.value.integer_value; + set_log_val = (int32_t)req.value.integer_value; if (ptr16 == &motor_kv) { eepromBuffer.motor_kv = (uint8_t)((*(uint16_t *)p->ptr - 20) / 40); + /* Advance / RPM envelopes were computed at boot from + * old kV — refresh them now (AM32 identity tables). */ + applyMotorIdentitySettings(); } else if (ptr16 == &low_cell_volt_cutoff) { eepromBuffer.low_cell_volt_cutoff = (uint8_t)(*ptr16 - 250); } @@ -403,6 +712,7 @@ static void handle_param_GetSet(CanardInstance *ins, CanardRxTransfer *transfer) } case T_BOOL: *(uint8_t *)p->ptr = req.value.boolean_value ? 1 : 0; + set_log_val = req.value.boolean_value ? 1 : 0; break; case T_STRING: if (req.value.union_tag == UAVCAN_PROTOCOL_PARAM_VALUE_STRING_VALUE) { @@ -414,6 +724,7 @@ static void handle_param_GetSet(CanardInstance *ins, CanardRxTransfer *transfer) eepromBuffer.tune[i] = 0xFF; } } + set_log_is_str = 1; } } break; @@ -421,6 +732,22 @@ static void handle_param_GetSet(CanardInstance *ins, CanardRxTransfer *transfer) return; } +# ifdef USE_DEBUG_UART + if (set_log_is_str) { + debugUartPrintf("param: %s=\r\n", p->name, (unsigned)req.value.string_value.len); + } else { + debugUartPrintf("param: %s=%ld\r\n", p->name, (long)set_log_val); + } + if (p->ptr == (void *)&motor_kv || p->ptr == (void *)&eepromBuffer.motor_poles) { + debugUartPrintf("param: applied kv=%u poles=%u erpm_lo=%u erpm_hi=%u adv_q12=%u\r\n", (unsigned)motor_kv, + (unsigned)eepromBuffer.motor_poles, (unsigned)low_rpm_level, (unsigned)high_rpm_level, + (unsigned)advance_erpm_scale_q12); + } +# else + (void)set_log_val; + (void)set_log_is_str; +# endif + if (last_dir_reversed != eepromBuffer.dir_reversed || last_bi_direction != eepromBuffer.bi_direction) { // make dir_reversed and bi_direction change work without // reboot @@ -429,6 +756,9 @@ static void handle_param_GetSet(CanardInstance *ins, CanardRxTransfer *transfer) armed = 0; set_input(0); } + + /* Coalesce flash writes: deferred save in DroneCAN_update (AM32#359). */ + mark_settings_dirty(); } /* @@ -455,7 +785,8 @@ static void handle_param_GetSet(CanardInstance *ins, CanardRxTransfer *transfer) pkt.min_value.integer_value = p->min_value; // special case scaling - if ((uint8_t *)p->ptr == &eepromBuffer.limits.current) { + if ((uint8_t *)p->ptr == &eepromBuffer.limits.current || (uint8_t *)p->ptr == &eepromBuffer.current_P || + (uint8_t *)p->ptr == &eepromBuffer.current_D) { pkt.default_value.integer_value *= 2; pkt.value.integer_value *= 2; } @@ -510,10 +841,10 @@ static void handle_param_GetSet(CanardInstance *ins, CanardRxTransfer *transfer) } uint8_t buffer[UAVCAN_PROTOCOL_PARAM_GETSET_RESPONSE_MAX_SIZE]; - uint16_t total_size = uavcan_protocol_param_GetSetResponse_encode(&pkt, buffer); + uint16_t total_size = DC_ENCODE(uavcan_protocol_param_GetSetResponse_encode, &pkt, buffer); - canardRequestOrRespond(ins, transfer->source_node_id, UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE, UAVCAN_PROTOCOL_PARAM_GETSET_ID, - &transfer->transfer_id, transfer->priority, CanardResponse, &buffer[0], total_size); + DC_RESPOND(ins, transfer->source_node_id, UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE, UAVCAN_PROTOCOL_PARAM_GETSET_ID, + &transfer->transfer_id, transfer->priority, CanardResponse, &buffer[0], total_size); } /* @@ -537,9 +868,11 @@ static void handle_param_ExecuteOpcode(CanardInstance *ins, CanardRxTransfer *tr can_printf("resetting to defaults"); memset(eepromBuffer.buffer, 0xff, sizeof(eepromBuffer.buffer)); memcpy(eepromBuffer.buffer, default_settings, sizeof(default_settings)); + apply_post_skeleton_defaults(); save_flash_nolib(eepromBuffer.buffer, sizeof(eepromBuffer.buffer), eeprom_address); loadEEpromSettings(); load_settings(); + pending_save.dirty = false; pkt.ok = true; } } @@ -547,17 +880,17 @@ static void handle_param_ExecuteOpcode(CanardInstance *ins, CanardRxTransfer *tr if (!safe_to_write_settings()) { can_printf("No save while running"); } else { + pending_save.dirty = false; save_settings(); pkt.ok = true; } } uint8_t buffer[UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_RESPONSE_MAX_SIZE]; - uint16_t total_size = uavcan_protocol_param_ExecuteOpcodeResponse_encode(&pkt, buffer); + uint16_t total_size = DC_ENCODE(uavcan_protocol_param_ExecuteOpcodeResponse_encode, &pkt, buffer); - canardRequestOrRespond(ins, transfer->source_node_id, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_SIGNATURE, - UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_ID, &transfer->transfer_id, transfer->priority, CanardResponse, - &buffer[0], total_size); + DC_RESPOND(ins, transfer->source_node_id, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_SIGNATURE, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_ID, + &transfer->transfer_id, transfer->priority, CanardResponse, &buffer[0], total_size); } /* @@ -582,17 +915,19 @@ static void handle_GetNodeInfo(CanardInstance *ins, CanardRxTransfer *transfer) memset(&pkt, 0, sizeof(pkt)); node_status.uptime_sec = micros64() / 1000000ULL; + node_status.health = DroneCAN_nodeHealth(); pkt.status = node_status; // fill in your major and minor firmware version pkt.software_version.major = VERSION_MAJOR; pkt.software_version.minor = VERSION_MINOR; - pkt.software_version.optional_field_flags = 0; - pkt.software_version.vcs_commit = 0; // should put git hash in here + pkt.software_version.optional_field_flags = UAVCAN_PROTOCOL_SOFTWAREVERSION_OPTIONAL_FIELD_FLAG_IMAGE_CRC | + UAVCAN_PROTOCOL_SOFTWAREVERSION_OPTIONAL_FIELD_FLAG_VCS_COMMIT; + pkt.software_version.vcs_commit = px4_app_descriptor.git_hash; + pkt.software_version.image_crc = px4_app_descriptor.image_crc; - // should fill in hardware version - pkt.hardware_version.major = 2; - pkt.hardware_version.minor = 3; + pkt.hardware_version.major = DRONECAN_HW_VERSION_MAJOR; + pkt.hardware_version.minor = DRONECAN_HW_VERSION_MINOR; sys_can_getUniqueID(pkt.hardware_version.unique_id); @@ -603,10 +938,10 @@ static void handle_GetNodeInfo(CanardInstance *ins, CanardRxTransfer *transfer) # endif pkt.name.len = strnlen((char *)pkt.name.data, sizeof(pkt.name.data)); - uint16_t total_size = uavcan_protocol_GetNodeInfoResponse_encode(&pkt, buffer); + uint16_t total_size = DC_ENCODE(uavcan_protocol_GetNodeInfoResponse_encode, &pkt, buffer); - canardRequestOrRespond(ins, transfer->source_node_id, UAVCAN_PROTOCOL_GETNODEINFO_SIGNATURE, UAVCAN_PROTOCOL_GETNODEINFO_ID, - &transfer->transfer_id, transfer->priority, CanardResponse, &buffer[0], total_size); + DC_RESPOND(ins, transfer->source_node_id, UAVCAN_PROTOCOL_GETNODEINFO_SIGNATURE, UAVCAN_PROTOCOL_GETNODEINFO_ID, + &transfer->transfer_id, transfer->priority, CanardResponse, &buffer[0], total_size); } extern void transfercomplete(); @@ -630,8 +965,17 @@ static void set_input(uint16_t input) last_can_input = unfiltered_input; inputSet = 1; - // we must set dshot for bi_direction to work - dshot = eepromBuffer.bi_direction; + /* + * RawCommand is already mapped onto the AM32 11-bit range. + * Bidirectional CAN needs the DShot mapper in setInput(). Never + * clear a detected wire protocol: AUTO keeps computeDshotDMA() + * running so DShot can resume after the 250 ms RawCommand failsafe. + * `dshot = bi_direction` used to force dshot=0 whenever reverse + * was off and permanently stole the wire path. + */ + if (eepromBuffer.bi_direction) { + dshot = 1; + } transfercomplete(); setInput(); @@ -696,7 +1040,15 @@ static void handle_ArmingStatus(CanardInstance *ins, CanardRxTransfer *transfer) return; } + const uint8_t was_armed = dronecan_armed; dronecan_armed = (cmd.status == UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_STATUS_FULLY_ARMED); + /* ESC `armed` stays 1 after the first zero-throttle arm, so the + * escToArmedIdle 0->1 reset never runs again. Each FC arm is a new + * drive session — clear error_count so NodeStatus is OK until a real + * in-session stall/desync. */ + if (dronecan_armed && !was_armed) { + faultErrorCountReset(); + } if (!dronecan_armed && eepromBuffer.can.require_arming && canstats.last_raw_command_us != 0) { set_input(0); } @@ -730,11 +1082,11 @@ static void handle_begin_firmware_update(CanardInstance *ins, CanardRxTransfer * memset(&reply, 0, sizeof(reply)); reply.error = UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_ERROR_OK; - uint32_t total_size = uavcan_protocol_file_BeginFirmwareUpdateResponse_encode(&reply, buffer); + uint32_t total_size = DC_ENCODE(uavcan_protocol_file_BeginFirmwareUpdateResponse_encode, &reply, buffer); - canardRequestOrRespond(ins, transfer->source_node_id, UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_SIGNATURE, - UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_ID, &transfer->transfer_id, transfer->priority, - CanardResponse, &buffer[0], total_size); + DC_RESPOND(ins, transfer->source_node_id, UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_SIGNATURE, + UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_ID, &transfer->transfer_id, transfer->priority, CanardResponse, + &buffer[0], total_size); while (canardPeekTxQueue(&canard) != NULL) { DroneCAN_processTxQueue(); @@ -850,9 +1202,16 @@ static void request_DNA() memmove(&allocation_request[1], &my_unique_id[DNA.node_id_allocation_unique_id_offset], uid_size); - // Broadcasting the request + /* DNA is a classic 8-byte anonymous transfer. If we echo CAN FD + * from RawCommand here, the allocator never completes. */ +# if CANARD_ENABLE_CANFD + canardBroadcast(&canard, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_SIGNATURE, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID, + &node_id_allocation_transfer_id, CANARD_TRANSFER_PRIORITY_LOW, &allocation_request[0], (uint16_t)(uid_size + 1), + false); +# else canardBroadcast(&canard, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_SIGNATURE, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID, &node_id_allocation_transfer_id, CANARD_TRANSFER_PRIORITY_LOW, &allocation_request[0], (uint16_t)(uid_size + 1)); +# endif // Preparing for timeout; if response is received, this value will be updated from the callback. DNA.node_id_allocation_unique_id_offset = 0; @@ -973,14 +1332,15 @@ static bool shouldAcceptTransfer(const CanardInstance *ins, uint64_t *out_data_t /* send the 1Hz NodeStatus message. This is what allows a node to show - up in the DroneCAN GUI tool and in the flight controller logs + up in the DroneCAN GUI tool and in the flight controller logs. + health reflects latched faults / recent hard errors (see DroneCAN_nodeHealth). */ static void send_NodeStatus(void) { uint8_t buffer[UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE]; node_status.uptime_sec = micros64() / 1000000ULL; - node_status.health = UAVCAN_PROTOCOL_NODESTATUS_HEALTH_OK; + node_status.health = DroneCAN_nodeHealth(); node_status.mode = UAVCAN_PROTOCOL_NODESTATUS_MODE_OPERATIONAL; node_status.sub_mode = 0; @@ -989,15 +1349,15 @@ static void send_NodeStatus(void) node_status.vendor_specific_status_code = canstats.num_commands; canstats.num_commands = 0; - uint32_t len = uavcan_protocol_NodeStatus_encode(&node_status, buffer); + uint32_t len = DC_ENCODE(uavcan_protocol_NodeStatus_encode, &node_status, buffer); // we need a static variable for the transfer ID. This is // incremeneted on each transfer, allowing for detection of packet // loss static uint8_t transfer_id; - canardBroadcast(&canard, UAVCAN_PROTOCOL_NODESTATUS_SIGNATURE, UAVCAN_PROTOCOL_NODESTATUS_ID, &transfer_id, - CANARD_TRANSFER_PRIORITY_LOW, buffer, len); + DC_BROADCAST(&canard, UAVCAN_PROTOCOL_NODESTATUS_SIGNATURE, UAVCAN_PROTOCOL_NODESTATUS_ID, &transfer_id, + CANARD_TRANSFER_PRIORITY_LOW, buffer, len); } /* @@ -1042,18 +1402,25 @@ static void send_ESCStatus(void) pkt.temperature = C_TO_KELVIN(degrees_celsius); pkt.rpm = (e_rpm * 200) / eepromBuffer.motor_poles; - pkt.power_rating_pct = 0; // how do we get this? + /* Instant demand factor: applied duty 0..2000 → 0..100% of full scale. */ + { + uint16_t pct = duty_cycle / 20u; + if (pct > 100u) { + pct = 100u; + } + pkt.power_rating_pct = (uint8_t)pct; + } pkt.esc_index = eepromBuffer.can.esc_index; - uint32_t len = uavcan_equipment_esc_Status_encode(&pkt, buffer); + uint32_t len = DC_ENCODE(uavcan_equipment_esc_Status_encode, &pkt, buffer); // we need a static variable for the transfer ID. This is // incremeneted on each transfer, allowing for detection of packet // loss static uint8_t transfer_id; - canardBroadcast(&canard, UAVCAN_EQUIPMENT_ESC_STATUS_SIGNATURE, UAVCAN_EQUIPMENT_ESC_STATUS_ID, &transfer_id, - CANARD_TRANSFER_PRIORITY_LOW, buffer, len); + DC_BROADCAST(&canard, UAVCAN_EQUIPMENT_ESC_STATUS_SIGNATURE, UAVCAN_EQUIPMENT_ESC_STATUS_ID, &transfer_id, + CANARD_TRANSFER_PRIORITY_LOW, buffer, len); } /* @@ -1093,12 +1460,12 @@ static void send_FlexDebug(void) pkt.id = DRONECAN_PROTOCOL_FLEXDEBUG_AM32_RESERVE_START + 0; pkt.u8.len = sizeof(debug1); memcpy(pkt.u8.data, (const uint8_t *)&debug1, sizeof(debug1)); - uint32_t len = dronecan_protocol_FlexDebug_encode(&pkt, buffer); + uint32_t len = DC_ENCODE(dronecan_protocol_FlexDebug_encode, &pkt, buffer); static uint8_t transfer_id; - canardBroadcast(&canard, DRONECAN_PROTOCOL_FLEXDEBUG_SIGNATURE, DRONECAN_PROTOCOL_FLEXDEBUG_ID, &transfer_id, - CANARD_TRANSFER_PRIORITY_LOW, buffer, len); + DC_BROADCAST(&canard, DRONECAN_PROTOCOL_FLEXDEBUG_SIGNATURE, DRONECAN_PROTOCOL_FLEXDEBUG_ID, &transfer_id, + CANARD_TRANSFER_PRIORITY_LOW, buffer, len); } /* @@ -1109,6 +1476,7 @@ void DroneCAN_receiveFrame(void) CanardCANFrame rx_frame = {0}; while (sys_can_receive(&rx_frame) > 0) { canstats.num_receive++; + dc_note_rx_frame(&rx_frame); int ecode = canardHandleRxFrame(&canard, &rx_frame, micros64()); if (ecode != CANARD_OK && ecode != -CANARD_ERROR_RX_NOT_WANTED) { canstats.rx_ecode = ecode; @@ -1120,6 +1488,7 @@ void DroneCAN_receiveFrame(void) void DroneCAN_handleFrame(const CanardCANFrame *rx_frame) { canstats.num_receive++; + dc_note_rx_frame(rx_frame); int ecode = canardHandleRxFrame(&canard, rx_frame, micros64()); if (ecode != CANARD_OK && ecode != -CANARD_ERROR_RX_NOT_WANTED) { canstats.rx_ecode = ecode; @@ -1160,11 +1529,17 @@ static void DroneCAN_Startup(void) // initialise low level CAN peripheral hardware sys_can_init(); +# if CANARD_ENABLE_CANFD + dronecan_tx_canfd = sys_can_prefer_canfd_tx(); +# endif + /* + * DRONECAN_IN (5) is exclusive: disable DShot/PWM IRQs so noise on the + * signal pin cannot fight CAN. AUTO (0) and the fixed wire types keep + * capture live; when both CAN and wire are present, DroneCAN_active() + * makes RawCommand win until the stream times out (~250 ms). + */ if (eepromBuffer.input_type == DRONECAN_IN) { - /* - disable interrupts for DShot and PWM - */ # ifdef MCU_L431 NVIC_DisableIRQ(DMA1_Channel5_IRQn); NVIC_DisableIRQ(EXTI15_10_IRQn); @@ -1188,6 +1563,7 @@ static void DroneCAN_Startup(void) void DroneCAN_update() { sys_can_disable_IRQ(); + sys_can_service(); static uint64_t next_1hz_service_at; static uint64_t next_telem_service_at; @@ -1217,6 +1593,9 @@ void DroneCAN_update() const uint64_t ts = micros64(); + /* Rising-edge stuck / nFAULT → one LogMessage (not rate-limited spam). */ + DroneCAN_pollFaultLogMessages(); + if (ts >= next_1hz_service_at) { next_1hz_service_at += 1000000ULL; process1HzTasks(ts); @@ -1232,6 +1611,12 @@ void DroneCAN_update() DroneCAN_processTxQueue(); + /* Deferred settings save: quiet window + safe to write flash (AM32#359). */ + if (pending_save.dirty && (millis32() - pending_save.last_change_ms) >= SETTINGS_SAVE_QUIET_MS && safe_to_write_settings()) { + pending_save.dirty = false; + save_settings(); + } + if (canstats.last_raw_command_us != 0 && ts - canstats.last_raw_command_us > 250000ULL) { /* we have stopped getting CAN RawCommand, zero input. @@ -1268,7 +1653,13 @@ void DroneCAN_update() bool DroneCAN_active(void) { - return canstats.total_commands != 0; + /* + * True while a RawCommand stream is live (refreshed by handle_RawCommand + * and cleared by the 250 ms failsafe above). Sticky total_commands is + * NOT used: after CAN drops out, DShot/PWM under AUTO must be able to + * take over without a reboot. + */ + return canstats.last_raw_command_us != 0; } #endif // DRONECAN_SUPPORT diff --git a/Src/DroneCAN/DroneCAN.h b/Src/DroneCAN/DroneCAN.h index d3dbfd42f..ec3b7a419 100644 --- a/Src/DroneCAN/DroneCAN.h +++ b/Src/DroneCAN/DroneCAN.h @@ -5,6 +5,7 @@ #if DRONECAN_SUPPORT void DroneCAN_Init(void); void DroneCAN_update(); -bool DroneCAN_active(); +/* True while esc.RawCommand is live (~250 ms window). Wire inputs defer. */ +bool DroneCAN_active(void); #endif // DRONECAN_SUPPORT diff --git a/Src/DroneCAN/sys_can.h b/Src/DroneCAN/sys_can.h index 64755500f..2b9a3f225 100644 --- a/Src/DroneCAN/sys_can.h +++ b/Src/DroneCAN/sys_can.h @@ -43,6 +43,18 @@ void sys_can_getUniqueID(uint8_t id[16]); */ void sys_can_init(void); +/* + periodic FDCAN housekeeping (CAN FD data-rate auto-match). No-op on + classic-CAN backends. + */ +void sys_can_service(void); + +/* + True when telemetry/status should go out as CAN FD (pinned CAN_FD_MBPS, + or auto-hunt locked onto a host FD data rate). Classic backends: false. + */ +bool sys_can_prefer_canfd_tx(void); + /* called from CAN IRQ indicating we may have a free TX slot */ diff --git a/Src/DroneCAN/sys_can_SITL.c b/Src/DroneCAN/sys_can_SITL.c index 71638d7fa..790271ad0 100644 --- a/Src/DroneCAN/sys_can_SITL.c +++ b/Src/DroneCAN/sys_can_SITL.c @@ -339,6 +339,13 @@ void sys_can_disable_IRQ(void) sitl_nvic_disable_irq(SITL_IRQ_CAN); } +void sys_can_service(void) {} + +bool sys_can_prefer_canfd_tx(void) +{ + return false; +} + void sys_can_enable_IRQ(void) { sitl_nvic_enable_irq(SITL_IRQ_CAN); diff --git a/Src/DroneCAN/sys_can_at32.c b/Src/DroneCAN/sys_can_at32.c index 379b71cf7..0b190f89f 100644 --- a/Src/DroneCAN/sys_can_at32.c +++ b/Src/DroneCAN/sys_can_at32.c @@ -126,6 +126,13 @@ void sys_can_init(void) can_interrupt_enable(CAN1, CAN_EOIEN_INT, TRUE); } +void sys_can_service(void) {} + +bool sys_can_prefer_canfd_tx(void) +{ + return false; +} + void sys_can_enable_IRQ(void) { NVIC_EnableIRQ(CAN1_SE_IRQn); diff --git a/Src/DroneCAN/sys_can_stm32.c b/Src/DroneCAN/sys_can_stm32.c index 7087f5c07..eb6dfdedf 100644 --- a/Src/DroneCAN/sys_can_stm32.c +++ b/Src/DroneCAN/sys_can_stm32.c @@ -153,6 +153,13 @@ void sys_can_disable_IRQ(void) NVIC_DisableIRQ(CAN1_TX_IRQn); } +void sys_can_service(void) {} + +bool sys_can_prefer_canfd_tx(void) +{ + return false; +} + /* enable CAN IRQs */ diff --git a/Src/DroneCAN/sys_can_stm32_CANFD.c b/Src/DroneCAN/sys_can_stm32_CANFD.c index 420a038e0..12d68fff5 100644 --- a/Src/DroneCAN/sys_can_stm32_CANFD.c +++ b/Src/DroneCAN/sys_can_stm32_CANFD.c @@ -1,26 +1,25 @@ /* - sys_can_stm32_CANFD.c - MCU specific CAN FD code for STM32 FDCAN - - based on ArduPilot CANFDIface.cpp driver + * sys_can_stm32_CANFD.c - STM32G4 FDCAN, classic CAN and CAN FD. + * + * Based on ArduPilot CANFDIface.cpp. Nominal (arbitration) bit timing is + * 1 Mbps at 80 MHz. Data-phase timings are the Kvaser/ArduPilot 80 MHz table + * (1/2/4/5 Mbps). EEPROM CAN_FD_MBPS 0 auto-matches the host data rate; + * 1/2/4/5 pins it. Classic DNA is expected on an FD bus and does not + * abort auto-hunt. TX FDF/BRS for Status/telemetry latches on once an + * FD frame is received (or CAN_FD_MBPS is pinned). */ #include "targets.h" #if DRONECAN_SUPPORT && defined(MCU_G431) -// #pragma GCC optimize("O0") - -# include "sys_can.h" +# include "eeprom.h" # include "functions.h" +# include "sys_can.h" # include -// FDCAN Frame buffer - 18 words per frame # define FDCAN_FRAME_BUFFER_SIZE 18 -// Message RAM Allocations for STM32G4 -# define MAX_FILTER_LIST_SIZE 80U -# define FDCAN_NUM_RXFIFO0_SIZE 104U -# define FDCAN_TX_FIFO_BUFFER_SIZE 128U # define FDCAN_MESSAGERAM_STRIDE 0x350 # define FDCAN_EXFILTER_OFFSET 0x70 # define FDCAN_RXFIFO0_OFFSET 0xB0 @@ -29,30 +28,33 @@ # define FDCAN_RXF0S_F0GI_SHIFT 8 # define FDCAN_RXF1S_F1GI_SHIFT 8 -# define FDCAN_TXFQS_TFGI_SHIFT 8 -// CAN ID masks (used for frame parsing) # define MaskStdID 0x7FFU # define MaskExtID 0x1FFFFFFFU -// Message RAM element structures +# define FDCAN_ELMT_FDF (1U << 21) +# define FDCAN_ELMT_BRS (1U << 20) + +/* TDC offset 10 × 12.5 ns = 125 ns (MCP2557FD-class transceiver delay). */ +# define FDCAN_TDCO_TICKS 10U +# define FD_ERR_STREAK_RETUNE 32U + typedef struct { uint32_t id_flags; uint32_t dlc_timestamp; - uint32_t data[16]; // Up to 64 bytes for CANFD + uint32_t data[16]; } RxMessageRAM; typedef struct { uint32_t id_flags; uint32_t dlc_flags; - uint32_t data[16]; // Up to 64 bytes for CANFD + uint32_t data[16]; } TxMessageRAM; volatile struct { uint32_t rx_overflow; } can_stats; -// Message RAM addresses (for STM32G4, these are at fixed offsets) static uint32_t MessageRam_StandardFilterSA; static uint32_t MessageRam_ExtendedFilterSA; static uint32_t MessageRam_RxFIFO0SA; @@ -60,48 +62,117 @@ static uint32_t MessageRam_RxFIFO1SA; static uint32_t MessageRam_TxFIFOQSA; /* - setup message RAM for FDCAN1 on STM32G4 -*/ + * Data-phase timings at 80 MHz FDCAN kernel clock. + * Values are real (prescaler, bs1, bs2, sjw); registers store minus one. + * Auto-scan order: 5 Mbps (product max) then 4, 2, 1. + */ +static const struct { + uint8_t mbps; + uint8_t prescaler; + uint8_t bs1; + uint8_t bs2; + uint8_t sjw; +} fd_timings[] = { + {5, 1, 11, 4, 4}, + {4, 1, 14, 5, 5}, + {2, 2, 14, 5, 5}, + {1, 4, 14, 5, 5}, +}; + +static uint8_t fd_idx; +static uint8_t fd_auto; +static uint8_t fd_locked; +static uint16_t fd_err_streak; +static volatile uint8_t fd_need_retune; + +static uint8_t dlc_to_len(uint8_t dlc) +{ + static const uint8_t map[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}; + return map[dlc & 0xF]; +} + +static uint8_t len_to_dlc(uint8_t len) +{ + if (len <= 8) { + return len; + } + if (len <= 12) { + return 9; + } + if (len <= 16) { + return 10; + } + if (len <= 20) { + return 11; + } + if (len <= 24) { + return 12; + } + if (len <= 32) { + return 13; + } + if (len <= 48) { + return 14; + } + return 15; +} + +static int8_t fd_idx_for_mbps(uint8_t mbps) +{ + for (uint8_t i = 0; i < (uint8_t)(sizeof(fd_timings) / sizeof(fd_timings[0])); i++) { + if (fd_timings[i].mbps == mbps) { + return (int8_t)i; + } + } + return -1; +} + +static void apply_fd_timings(uint8_t idx) +{ + const uint8_t n = (uint8_t)(sizeof(fd_timings) / sizeof(fd_timings[0])); + if (idx >= n) { + idx = 0; + } + const uint8_t presc = fd_timings[idx].prescaler; + const uint8_t bs1 = fd_timings[idx].bs1; + const uint8_t bs2 = fd_timings[idx].bs2; + const uint8_t sjw = fd_timings[idx].sjw; + FDCAN1->DBTP = (((uint32_t)(sjw - 1) << FDCAN_DBTP_DSJW_Pos) | ((uint32_t)(bs1 - 1) << FDCAN_DBTP_DTSEG1_Pos) | + ((uint32_t)(bs2 - 1) << FDCAN_DBTP_DTSEG2_Pos) | ((uint32_t)(presc - 1) << FDCAN_DBTP_DBRP_Pos) | FDCAN_DBTP_TDC); + FDCAN1->TDCR = (FDCAN_TDCO_TICKS << FDCAN_TDCR_TDCO_Pos); + fd_idx = idx; +} + static void setupMessageRam(void) { - // For FDCAN1, self_index = 0, so base = SRAMCAN_BASE + 0 const uint32_t base = SRAMCAN_BASE + FDCAN_MESSAGERAM_STRIDE * 0; - // Clear message RAM for this interface memset((void *)base, 0, FDCAN_MESSAGERAM_STRIDE); - // Store message RAM addresses at fixed offsets MessageRam_StandardFilterSA = base; MessageRam_ExtendedFilterSA = base + FDCAN_EXFILTER_OFFSET; MessageRam_RxFIFO0SA = base + FDCAN_RXFIFO0_OFFSET; MessageRam_RxFIFO1SA = base + FDCAN_RXFIFO1_OFFSET; MessageRam_TxFIFOQSA = base + FDCAN_TXFIFO_OFFSET; - // Set TXBC to 0 for FIFO mode (STM32G4 specific) FDCAN1->TXBC = 0; + (void)MessageRam_StandardFilterSA; + (void)MessageRam_ExtendedFilterSA; } -/* - send a CAN frame -*/ static bool can_send(const CanardCANFrame *frame) { - // Check if Tx FIFO is full if ((FDCAN1->TXFQS & FDCAN_TXFQS_TFQF) != 0) { - return false; // No space + return false; } - // Get put index uint32_t put_index = (FDCAN1->TXFQS & FDCAN_TXFQS_TFQPI) >> FDCAN_TXFQS_TFQPI_Pos; - // Calculate address in message RAM using stored base address volatile TxMessageRAM *tx_mailbox = (volatile TxMessageRAM *)(MessageRam_TxFIFOQSA + (put_index * FDCAN_FRAME_BUFFER_SIZE * 4)); - // Setup ID and flags if (frame->id & CANARD_CAN_FRAME_EFF) { tx_mailbox->id_flags = (frame->id & MaskExtID) | (1U << 30); } else { - // Standard ID tx_mailbox->id_flags = (frame->id & MaskStdID) << 18; } @@ -109,16 +180,25 @@ static bool can_send(const CanardCANFrame *frame) tx_mailbox->id_flags |= (1U << 29); } - // Setup DLC - tx_mailbox->dlc_flags = (frame->data_len << 16); + const uint8_t dlc = len_to_dlc(frame->data_len); + uint32_t dlc_flags = ((uint32_t)dlc << 16); +# if CANARD_ENABLE_CANFD + if (frame->canfd) { + dlc_flags |= FDCAN_ELMT_FDF | FDCAN_ELMT_BRS; + } +# endif + tx_mailbox->dlc_flags = dlc_flags; - // Copy data + uint8_t nbytes = frame->data_len; + if (nbytes > 64) { + nbytes = 64; + } + const uint8_t nwords = (uint8_t)((nbytes + 3) / 4); const uint32_t *data_ptr = (const uint32_t *)frame->data; - for (int i = 0; i < 2; i++) { + for (uint8_t i = 0; i < nwords; i++) { tx_mailbox->data[i] = data_ptr[i]; } - // Request transmission FDCAN1->TXBAR = (1U << put_index); return true; @@ -133,19 +213,15 @@ static void handleRxInterrupt(uint8_t fifo_index) uint32_t get_index_mask = (fifo_index == 0) ? FDCAN_RXF0S_F0GI : FDCAN_RXF1S_F1GI; uint32_t get_index_shift = (fifo_index == 0) ? FDCAN_RXF0S_F0GI_SHIFT : FDCAN_RXF1S_F1GI_SHIFT; - // Check if FIFO has messages if ((*fifo_status_reg & fifo_level_mask) == 0) { return; } - // Get the get index uint32_t get_index = (*fifo_status_reg & get_index_mask) >> get_index_shift; - // Calculate address in message RAM uint32_t rx_fifo_addr = (fifo_index == 0) ? MessageRam_RxFIFO0SA : MessageRam_RxFIFO1SA; volatile RxMessageRAM *rx_mailbox = (volatile RxMessageRAM *)(rx_fifo_addr + (get_index * FDCAN_FRAME_BUFFER_SIZE * 4)); - // Read the frame CanardCANFrame frame = {}; uint32_t id_flags = rx_mailbox->id_flags; @@ -159,55 +235,68 @@ static void handleRxInterrupt(uint8_t fifo_index) frame.id |= CANARD_CAN_FRAME_RTR; } - // Get DLC - uint32_t dlc = (rx_mailbox->dlc_timestamp >> 16) & 0xF; - frame.data_len = dlc; - - // Copy data + const uint32_t dlc_word = rx_mailbox->dlc_timestamp; + const uint8_t dlc = (uint8_t)((dlc_word >> 16) & 0xF); + const bool is_fd = (dlc_word & FDCAN_ELMT_FDF) != 0; +# if CANARD_ENABLE_CANFD + frame.canfd = is_fd; + frame.data_len = is_fd ? dlc_to_len(dlc) : (dlc > 8 ? 8 : dlc); +# else + (void)is_fd; + frame.data_len = dlc > 8 ? 8 : dlc; +# endif + + uint8_t nbytes = frame.data_len; + if (nbytes > (uint8_t)sizeof(frame.data)) { + nbytes = (uint8_t)sizeof(frame.data); + frame.data_len = nbytes; + } + const uint8_t nwords = (uint8_t)((nbytes + 3) / 4); uint32_t *data_ptr = (uint32_t *)frame.data; - for (int i = 0; i < 2; i++) { + for (uint8_t i = 0; i < nwords; i++) { data_ptr[i] = rx_mailbox->data[i]; } - // Acknowledge the read *fifo_ack_reg = get_index; - // Process the frame + fd_err_streak = 0; + if (is_fd) { + fd_locked = 1; + } + DroneCAN_handleFrame(&frame); } -static void handleTxCompleteInterrupt(void) -{ - // Nothing to do in simple bootloader mode -} +static void handleTxCompleteInterrupt(void) {} static void pollErrorFlagsFromISR(void) { - // Simple error handling - uint32_t ecr = FDCAN1->ECR; - (void)ecr; + const uint8_t cel = (uint8_t)(FDCAN1->ECR >> 16); + if (cel == 0) { + return; + } + if (fd_auto && !fd_locked) { + if (fd_err_streak < 0xFFFF) { + fd_err_streak++; + } + if (fd_err_streak >= FD_ERR_STREAK_RETUNE) { + fd_need_retune = 1; + fd_err_streak = 0; + } + } } -/* - get a 16 byte unique ID for this node -*/ void sys_can_getUniqueID(uint8_t id[16]) { - // Use CMSIS UID_BASE definition const uint8_t *uidbase = (const uint8_t *)UID_BASE; memcpy(id, uidbase, 12); - // put CPU ID in last 4 bytes const uint32_t cpuid = SCB->CPUID; memcpy(&id[12], &cpuid, 4); } -/* - interrupt handlers for FDCAN1 -*/ void FDCAN1_IT0_IRQHandler(void) { - // Rx FIFO interrupts if ((FDCAN1->IR & FDCAN_IR_RF0N) || (FDCAN1->IR & FDCAN_IR_RF0F)) { FDCAN1->IR = FDCAN_IR_RF0N | FDCAN_IR_RF0F; handleRxInterrupt(0); @@ -223,62 +312,42 @@ void FDCAN1_IT0_IRQHandler(void) void FDCAN1_IT1_IRQHandler(void) { - // Tx complete interrupt if (FDCAN1->IR & FDCAN_IR_TC) { FDCAN1->IR = FDCAN_IR_TC; handleTxCompleteInterrupt(); } - // Bus off interrupt if (FDCAN1->IR & FDCAN_IR_BO) { FDCAN1->IR = FDCAN_IR_BO; - // Try to recover from bus off FDCAN1->CCCR &= ~FDCAN_CCCR_INIT; } pollErrorFlagsFromISR(); } -/* - try to transmit a frame. - return 1 for success, 0 for no space, -ve for failure - */ int16_t sys_can_transmit(const CanardCANFrame *txf) { return can_send(txf) ? 1 : 0; } -/* - check for an incoming frame - return 1 on new frame, 0 for no frame, -ve for error - */ int16_t sys_can_receive(CanardCANFrame *rx_frame) { - // not used on STM32 with interrupt-driven receive + (void)rx_frame; return -1; } -/* - disable CAN IRQs - */ void sys_can_disable_IRQ(void) { NVIC_DisableIRQ(FDCAN1_IT0_IRQn); NVIC_DisableIRQ(FDCAN1_IT1_IRQn); } -/* - enable CAN IRQs - */ void sys_can_enable_IRQ(void) { NVIC_EnableIRQ(FDCAN1_IT0_IRQn); NVIC_EnableIRQ(FDCAN1_IT1_IRQn); } -/* - wait for register bit to change state -*/ static bool waitForBitState(volatile uint32_t *reg, uint32_t mask, bool target_state) { while (true) { @@ -290,54 +359,38 @@ static bool waitForBitState(volatile uint32_t *reg, uint32_t mask, bool target_s return false; } -/* - init code should be small, not fast - */ # pragma GCC optimize("Os") static void can_init(void) { - // Enable FDCAN clock RCC->APB1ENR1 |= RCC_APB1ENR1_FDCANEN; - // Wait for clock to stabilize for (volatile int i = 0; i < 10000; i++) { __NOP(); } - // Perform reset RCC->APB1RSTR1 |= RCC_APB1RSTR1_FDCANRST; RCC->APB1RSTR1 &= ~RCC_APB1RSTR1_FDCANRST; - // Wait after reset for (volatile int i = 0; i < 100; i++) { __NOP(); } - // Note: PLL_Q must be configured to output 80 MHz - // This should be done in bl_clock_config() by enabling PLL Q domain - // with Q divider = 4 (VCO 320 MHz / 4 = 80 MHz) - - // Exit sleep mode FDCAN1->CCCR &= ~FDCAN_CCCR_CSR; - // Wait for sleep mode to exit if (!waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_CSA, false)) { - return; // Failed to exit sleep mode + return; } - // Enter initialization mode FDCAN1->CCCR |= FDCAN_CCCR_INIT; - // Wait for initialization mode (with timeout) if (!waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_INIT, true)) { - return; // Failed to enter init mode + return; } - // Enable configuration change FDCAN1->CCCR |= FDCAN_CCCR_CCE; - // Configure bit timing for 1 Mbps with 80 MHz FDCAN clock + /* 1 Mbps nominal, 80 MHz kernel clock, 10 tq, 90% sample point. */ const uint8_t sjw = 1; const uint8_t bs1 = 8; const uint8_t bs2 = 1; @@ -346,57 +399,91 @@ static void can_init(void) FDCAN1->NBTP = (((sjw - 1) << FDCAN_NBTP_NSJW_Pos) | ((bs1 - 1) << FDCAN_NBTP_NTSEG1_Pos) | ((bs2 - 1) << FDCAN_NBTP_NTSEG2_Pos) | ((prescaler - 1) << FDCAN_NBTP_NBRP_Pos)); - // Setup message RAM + apply_fd_timings(fd_idx); + + FDCAN1->CCCR |= FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE; + setupMessageRam(); - // Clear all interrupts FDCAN1->IR = 0x3FFFFFFF; - // Configure interrupts - FDCAN1->IE = FDCAN_IE_RF0NE | // Rx FIFO 0 new message - FDCAN_IE_RF0FE | // Rx FIFO 0 Full - FDCAN_IE_RF1NE | // Rx FIFO 1 new message - FDCAN_IE_RF1FE | // Rx FIFO 1 Full - FDCAN_IE_TCE | // Transmission complete - FDCAN_IE_BOE; // Bus off + FDCAN1->IE = FDCAN_IE_RF0NE | FDCAN_IE_RF0FE | FDCAN_IE_RF1NE | FDCAN_IE_RF1FE | FDCAN_IE_TCE | FDCAN_IE_BOE; - // Route interrupts FDCAN1->ILS = FDCAN_ILS_PERR | FDCAN_ILS_SMSG; - // Configure Tx Buffer Transmission Interrupt Enable for STM32G4 FDCAN1->TXBTIE = 0x7; - // Enable both interrupt lines FDCAN1->ILE = 0x3; - // Leave initialization mode FDCAN1->CCCR &= ~FDCAN_CCCR_INIT; - // Wait for normal mode waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_INIT, false); } -/* - initialise CAN hardware - */ -void sys_can_init(void) +static void fd_select_initial_rate(void) { - // Setup CAN RX and TX pins - // assumes PA11/PA12 for FDCAN1 - LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); + const uint8_t want = eepromBuffer.can.fd_mbps; + const int8_t idx = fd_idx_for_mbps(want); + if (want == 0 || idx < 0) { + fd_auto = 1; + fd_locked = 0; + fd_idx = 0; /* 5 Mbps, product max */ + } else { + fd_auto = 0; + fd_locked = 1; + fd_idx = (uint8_t)idx; + } + fd_err_streak = 0; + fd_need_retune = 0; +} + +bool sys_can_prefer_canfd_tx(void) +{ + return fd_locked != 0; +} + +void sys_can_service(void) +{ + if (!fd_need_retune || !fd_auto || fd_locked) { + fd_need_retune = 0; + return; + } + fd_need_retune = 0; + const uint8_t n = (uint8_t)(sizeof(fd_timings) / sizeof(fd_timings[0])); + const uint8_t next = (uint8_t)((fd_idx + 1) % n); + + FDCAN1->CCCR |= FDCAN_CCCR_INIT; + if (!waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_INIT, true)) { + return; + } + FDCAN1->CCCR |= FDCAN_CCCR_CCE; + apply_fd_timings(next); + FDCAN1->CCCR |= FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE; + FDCAN1->CCCR &= ~FDCAN_CCCR_INIT; + waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_INIT, false); +} +void sys_can_init(void) +{ LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; - GPIO_InitStruct.Pin = LL_GPIO_PIN_11 | LL_GPIO_PIN_12; GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = LL_GPIO_AF_9; // AF9 for FDCAN1 - LL_GPIO_Init(GPIOA, &GPIO_InitStruct); + GPIO_InitStruct.Alternate = LL_GPIO_AF_9; + + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); + + GPIO_InitStruct.Pin = CAN_RX_PIN; + LL_GPIO_Init(CAN_RX_PORT, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = CAN_TX_PIN; + LL_GPIO_Init(CAN_TX_PORT, &GPIO_InitStruct); + fd_select_initial_rate(); can_init(); - // Enable interrupt for CAN receive and transmit NVIC_SetPriority(FDCAN1_IT0_IRQn, 5); NVIC_SetPriority(FDCAN1_IT1_IRQn, 5); NVIC_EnableIRQ(FDCAN1_IT0_IRQn); @@ -415,4 +502,32 @@ void set_rtc_backup_register(uint8_t idx, uint32_t value) bkp[idx] = value; } -#endif // DRONECAN_SUPPORT && defined(MCU_G431) +void setup_portpin(uint16_t portpin, bool enable) +{ + const uint8_t port = portpin >> 8; + const uint8_t pin = portpin & 0xff; + const uint32_t pinshift = 1U << pin; + GPIO_TypeDef *const ports[] = {GPIOA, GPIOB, GPIOC}; + if (port >= sizeof(ports) / sizeof(ports[0])) { + return; + } + GPIO_TypeDef *pport = ports[port]; + + LL_AHB2_GRP1_EnableClock(1U << port); + + if (enable) { + LL_GPIO_SetOutputPin(pport, pinshift); + } else { + LL_GPIO_ResetOutputPin(pport, pinshift); + } + + LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Pin = pinshift; + GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT; + GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; + LL_GPIO_Init(pport, &GPIO_InitStruct); +} + +#endif /* DRONECAN_SUPPORT && defined(MCU_G431) */ diff --git a/Src/adc_app.c b/Src/adc_app.c index 3e9d5984b..040234c5d 100644 --- a/Src/adc_app.c +++ b/Src/adc_app.c @@ -7,6 +7,8 @@ #include "adc_app.h" +#include + #include "ADC.h" #include "motor_runtime.h" #include "control_loop.h" @@ -61,12 +63,28 @@ void adcAppServiceConversion(void) const int32_t smoothed_raw_current = getSmoothedCurrent(); #ifdef NXP battery_voltage = ((7 * battery_voltage) + ((ADC_raw_volts * 3300 / 65535 * VOLTAGE_DIVIDER) / 100)) / 8; - actual_current = (((smoothed_raw_current * 3300 / 65535) - CURRENT_OFFSET) * 100) / (MILLIVOLT_PER_AMP); + int32_t current_ca = (((smoothed_raw_current * 3300 / 65535) - CURRENT_OFFSET) * 100) / (MILLIVOLT_PER_AMP); #else battery_voltage = ((7 * battery_voltage) + ((ADC_raw_volts * 3300 / 4095 * VOLTAGE_DIVIDER) / 100)) >> 3; - actual_current = ((smoothed_raw_current * 3300 / 41) - (CURRENT_OFFSET * 100)) / (MILLIVOLT_PER_AMP); + int32_t current_ca = ((smoothed_raw_current * 3300 / 41) - (CURRENT_OFFSET * 100)) / (MILLIVOLT_PER_AMP); #endif - if (actual_current < 0) { - actual_current = 0; + /* + * SATURATE, do not wrap. actual_current is int16_t centiamps, so it + * tops out at 327.67 A - and on a low-gain shunt the sense chain can + * read past that: at MILLIVOLT_PER_AMP 10 (ARK_G431_CAN, ARK_4IN1_F051) + * full ADC scale is (4095*3300/41)/10 = 329.6 A, so raw >= 4071 used to + * overflow negative and land on the "< 0" clamp below as ZERO amps. + * + * That inverts every consumer at the one moment they matter: the + * current limiter would release its ceiling, the nFAULT classifier + * would stop calling it OCP, and telemetry would report an idle ESC, + * all at >326 A. Clamping to INT16_MAX keeps the reading pinned high + * instead, which is the direction that fails safe. + */ + if (current_ca < 0) { + current_ca = 0; + } else if (current_ca > INT16_MAX) { + current_ca = INT16_MAX; } + actual_current = (int16_t)current_ca; } diff --git a/Src/bemf_zc.c b/Src/bemf_zc.c index 5a6e6f93f..c3bd60c07 100644 --- a/Src/bemf_zc.c +++ b/Src/bemf_zc.c @@ -221,7 +221,9 @@ RAM_FUNC void PeriodElapsedCallback() RAM_FUNC void interruptRoutine() { HWCI_PERF_ZC_PHASE_CAPTURE(); // TIM1 phase at ISR entry, pre-confirm -#ifdef MCU_F051 + /* ARK F051 + G431: glitch-tolerant confirm + turn-on-grid compensation + * (ported from the ARK_4IN1_F051 bench work). Other MCUs keep stock. */ +#if defined(MCU_F051) || defined(MCU_G431) // PWM phase (TIM1 CNT) at the edge, sampled before the confirm loop's // wall-clock window advances it: the turn-on-pileup compensation below // keys off where the comparator edge actually appeared, not where it was @@ -246,9 +248,9 @@ RAM_FUNC void interruptRoutine() // companion RAM-execution change the loop is faster still; filter_level // is scaled (42/10/7 on F051) to keep the same wall-clock window as the // stock ~56-cycle sampling cadence. -#ifdef MCU_F051 - // Glitch-tolerant variant: the ~16-cycle cadence lands on a brief - // comparator glitch ~3x more often than stock sampling, and a strict +#if defined(MCU_F051) || defined(MCU_G431) + // Glitch-tolerant variant: a fast sampling cadence lands on a brief + // comparator glitch more often than stock, and a strict // all-samples-must-agree confirm defers detection to the NEXT // comparator edge - up to a PWM period late. Bench-measured on the // ARK 4IN1: 2-3x higher commutation jitter at 15-20 kHz commutation @@ -257,6 +259,8 @@ RAM_FUNC void interruptRoutine() // through isolated glitches while a genuinely un-crossed level still // rejects via the early-out; the full window length (and so the // sustained-noise immunity of the filter_level retune) is unchanged. + // Same path on ARK_G431_CAN (MCU_G431); G431 filter_level schedule is + // scaled in runtime_loop.c for 160 MHz wall-clock. { int bad = 0; const int tolerance = filter_level >> 2; @@ -281,7 +285,7 @@ RAM_FUNC void interruptRoutine() } } #endif -#ifdef MCU_F051 +#if defined(MCU_F051) || defined(MCU_G431) // Turn-on-pileup timestamp compensation. A zero-cross that physically // occurs during the PWM off-window (freewheel) is invisible to the // comparator and is only registered at the next turn-on, quantizing the @@ -308,9 +312,25 @@ RAM_FUNC void interruptRoutine() // interval dwarfs the PWM period, there is no hunting to break, and the // correction only adds variance // - clamp to interval/8 as a backstop against any degenerate state - // Units: INTERVAL_TIMER (TIM2) is 2 MHz and TIM1 is 48 MHz, so one INTERVAL - // tick is 24 TIM1 ticks; half an off-window of N TIM1 ticks is N/48 INTERVAL - // ticks, computed as (N * 1365) >> 16 to keep a soft divide out of the ISR. + // + // Clocking (INTERVAL_TIMER TIM2 is 2 MHz on both ARK targets): + // - F051: TIM1 @ 48 MHz => 24 TIM1 ticks / INTERVAL tick; half off-window + // of N TIM1 ticks is N/48 INTERVAL ticks => (N * 1365) >> 16 (~1/48 Q16). + // Hump band: ci < 2 * pwm_period => ci * 12 < arr. + // - G431: TIM1 @ 160 MHz => 80 TIM1 ticks / INTERVAL tick; half off-window + // is N/160 INTERVAL ticks => (N * 409) >> 16 (~1/160 Q16). + // Hump band was ci * 40 < arr (~2 PWM periods), matching the F051 + // scale. Free-run 1800 KV map (noprop_zc_map, 48 kHz) put the measured + // jitter peak at ~2.3–3.2 PWM periods (50–70% thr, ~150–210k eRPM) — + // outside that band so the compensator never armed. Use ~3.5 PWM + // periods: ci * 24 < arr (at 48 kHz arr≈3332 → ci < ~139 ticks). +# ifdef MCU_G431 + const uint32_t zc_hump_mult = 24u; + const uint32_t zc_half_off_q16 = 409u; +# else + const uint32_t zc_hump_mult = 12u; + const uint32_t zc_half_off_q16 = 1365u; +# endif uint16_t zc_grid_comp = 0; { static uint16_t zc_prev_duty; @@ -323,9 +343,9 @@ RAM_FUNC void interruptRoutine() if (zero_crosses >= 100 && zc_duty < zc_arr /* off-window exists */ && zc_pwm_cnt >= (uint16_t)(zc_arr >> 5) /* pile-up bins 1-3 */ && zc_pwm_cnt < (uint16_t)(zc_arr >> 3) && zc_slew <= (uint16_t)(zc_arr >> 8) /* duty steady */ - && (zc_ci * 12u) < (uint32_t)zc_arr + 1u /* hump band only */ + && (zc_ci * zc_hump_mult) < (uint32_t)zc_arr + 1u /* hump band only */ ) { - uint32_t comp = ((uint32_t)(zc_arr - zc_duty) * 1365u) >> 16; + uint32_t comp = ((uint32_t)(zc_arr - zc_duty) * zc_half_off_q16) >> 16; const uint32_t cap = zc_ci >> 3; if (comp > cap) { comp = cap; @@ -387,7 +407,7 @@ RAM_FUNC void interruptRoutine() */ zc_blind_ticks = 0; lastzctime = thiszctime; -#ifdef MCU_F051 +#if defined(MCU_F051) || defined(MCU_G431) thiszctime = (uint16_t)(INTERVAL_TIMER_COUNT - zc_grid_comp); #else thiszctime = INTERVAL_TIMER_COUNT; diff --git a/Src/bootloader_update.c b/Src/bootloader_update.c index 86dd5df3b..0e2edf34a 100644 --- a/Src/bootloader_update.c +++ b/Src/bootloader_update.c @@ -6,7 +6,8 @@ * only on full success. * * The image itself is a committed .bin pulled in by Src/bl_image.S; this file - * only sees its bounds. F051 embeds by default (including HWCI_PERF=1); use + * only sees its bounds. F051 embeds on release builds; G431 CAN always embeds + * so a manual or PX4 app flash still refreshes the on-chip BL. Use * EMBED_BOOTLOADER=0 or NO_EMBED_BL=1 to omit the image and this logic. */ @@ -21,7 +22,7 @@ #include #include -#if defined(EMBED_BOOTLOADER) && defined(MCU_F051) +#if defined(EMBED_BOOTLOADER) && (defined(MCU_F051) || defined(MCU_G431)) /* * Bounds of the image assembled by Src/bl_image.S, defined by the .bl_image @@ -37,12 +38,45 @@ extern const uint8_t _bl_image_end[]; # define MCU_FLASH_START 0x08000000u # endif -/* F051 page = 1 KiB; erase runs when address is page-aligned in save_flash_nolib. */ -# define BL_PAGE_BYTES 1024u +/* + * Erase runs when address is page-aligned in save_flash_nolib. + * F051: 1 KiB pages. G431: 2 KiB pages (single-bank 128 KiB). + */ +# if defined(MCU_G431) +# define BL_PAGE_BYTES 2048u +# else +# define BL_PAGE_BYTES 1024u +# endif # define BL_CHUNK_BYTES 256u /* Per-page program/verify attempts before aborting the whole update. */ # define BL_MAX_PAGE_ATTEMPTS 4u +/* + * G431 I/D flash caches are on at reset (FLASH_ACR ICEN|DCEN). A verify + * memcmp after program can hit a stale D-cache line; the retry path then + * re-reads the same addresses and turns one transient fail into a 4-strike + * abort with a half-written BL page. F051 has no D-cache. + */ +# if defined(MCU_G431) +static void flash_flush_caches(void) +{ + if ((FLASH->ACR & FLASH_ACR_DCEN) != 0u) { + FLASH->ACR &= ~FLASH_ACR_DCEN; + FLASH->ACR |= FLASH_ACR_DCRST; + FLASH->ACR &= ~FLASH_ACR_DCRST; + FLASH->ACR |= FLASH_ACR_DCEN; + } + if ((FLASH->ACR & FLASH_ACR_ICEN) != 0u) { + FLASH->ACR &= ~FLASH_ACR_ICEN; + FLASH->ACR |= FLASH_ACR_ICRST; + FLASH->ACR &= ~FLASH_ACR_ICRST; + FLASH->ACR |= FLASH_ACR_ICEN; + } +} +# else +static void flash_flush_caches(void) {} +# endif + void maybe_update_bootloader(void) { const uint32_t len = (uint32_t)(_bl_image_end - _bl_image_start); @@ -66,9 +100,10 @@ void maybe_update_bootloader(void) RELOAD_WATCHDOG_COUNTER(); /* - * Same flash bank as this code on F051, but we only erase/program the - * bootloader pages below the app. Disable IRQs so no vector fetch races - * a half-written state mid-chunk (mirrors AM32-bootloader bl_update). + * Same flash bank as this code (F051 32 KiB; G431 128 KiB single-bank), + * but we only erase/program the bootloader pages below the app. + * Disable IRQs so no vector fetch races a half-written state mid-chunk + * (mirrors AM32-bootloader bl_update). */ __disable_irq(); @@ -94,6 +129,7 @@ void maybe_update_bootloader(void) RELOAD_WATCHDOG_COUNTER(); save_flash_nolib(src, (int)chunk, addr); + flash_flush_caches(); if (memcmp((const void *)(uintptr_t)addr, &want[off], chunk) != 0) { /* @@ -116,6 +152,7 @@ void maybe_update_bootloader(void) } /* Reset only when the full BL region matches the embedded image. */ + flash_flush_caches(); if (memcmp(have, want, len) == 0) { /* Next boot plays a short success chirp before the normal tune. */ bootSoundMarkBootloaderUpdated(); @@ -127,11 +164,11 @@ void maybe_update_bootloader(void) RELOAD_WATCHDOG_COUNTER(); } -#else /* !EMBED_BOOTLOADER || !MCU_F051 */ +#else /* !EMBED_BOOTLOADER || !(MCU_F051 || MCU_G431) */ void maybe_update_bootloader(void) { - /* Not embedded (HWCI) or MCU not supported in this prototype. */ + /* Not embedded (kill switch / F051 HWCI) or MCU not supported. */ } #endif diff --git a/Src/commutation.c b/Src/commutation.c index 54b14090e..cf2bf81e0 100644 --- a/Src/commutation.c +++ b/Src/commutation.c @@ -92,9 +92,20 @@ RAM_FUNC void commutate() // it too). Established closed loop never exits here - a missed // crossing is one blind step (PeriodElapsedCallback) and false locks // are the trust rail's job (runtimeProcessDesyncCheck). +# if defined(MCU_G431) + /* Re-enter poll more readily during acquisition after stall/desync. + * Mask COMP IRQs so interrupt-ZC and poll-ZC cannot fight. */ + if (zero_crosses < 100 && (average_interval > polling_mode_changeover + 500 || zero_crosses < 40)) { + if (!old_routine) { + maskPhaseInterrupts(); + } + old_routine = 1; + } +# else if (zero_crosses < 100 && average_interval > polling_mode_changeover + 500) { old_routine = 1; } +# endif #endif bemfcounter = 0; zcfound = 0; @@ -197,10 +208,27 @@ void zcfoundroutine() enableCompInterrupts(); // enable interrupt } } else { +# if defined(MCU_G431) + /* + * ARK 12S CAN (G4 dual-COMP): PWM/comparator noise can shrink + * commutation_interval below POLLING_MODE_THRESHOLD before the + * rotor is actually spinning. F051 (single COMP + runtime hyst) + * does not show that false-CI handoff. Require a longer clean + * poll run before interrupt mode, else closed-loop arms on a + * noise CI (~1.3k ticks vs true ~3k+ free-run), stalls, and + * OPEN↔CLOSED stutters (stand: 55 closed→open / 31 desync per + * startup matrix). + */ + if (zero_crosses >= 40 && commutation_interval < polling_mode_changeover) { + old_routine = 0; + enableCompInterrupts(); // enable interrupt + } +# else if (commutation_interval < polling_mode_changeover) { old_routine = 0; enableCompInterrupts(); // enable interrupt } +# endif } #endif if (!old_routine) { diff --git a/Src/control_loop.c b/Src/control_loop.c index 3d150465a..34298f636 100644 --- a/Src/control_loop.c +++ b/Src/control_loop.c @@ -12,6 +12,7 @@ #include "common.h" #include "comparator.h" #include "phaseouts.h" +#include "gate_driver.h" #include "targets.h" #include "IO.h" #include "peripherals.h" @@ -317,8 +318,10 @@ void setInput() // faultHandleStuckRotorIfNeeded gate above honors that flag; // the episode rail is independent of it). if (!escIsDriving() && !escIsFault() && !faultDesyncRestartHoldoffActive()) { - /* comStep/fullBrake gateDriverEnsure() wakes DRV once. */ + /* Wake DRV with IRQs enabled (ENABLE settle is multi-ms on + * DRV8350H). comStep gateDriverEnsure() is then a no-op. */ allOff(); + gateDriverWakeBlocking(); if (!old_routine) { startMotor(); } @@ -466,25 +469,58 @@ void setInput() if (duty_cycle_setpoint > duty_cycle_maximum) { duty_cycle_setpoint = duty_cycle_maximum; } - if (use_current_limit) { - if (duty_cycle_setpoint > use_current_limit_adjust) { - duty_cycle_setpoint = use_current_limit_adjust; - } - } if (stall_protection_adjust > 0 && input > DSHOT_CMD_MAX) { duty_cycle_setpoint = duty_cycle_setpoint + (uint16_t)(stall_protection_adjust / 10000); } + + /* + * Protection ceilings, composed and applied last. + * + * min(), not a product: each ceiling is an independent "do + * not exceed", so the binding one wins. Scaling them + * together would double-derate - thermal at 50% and current + * at 50% would give 25%, when 50% is what either one asked + * for. min() also makes them safe to run simultaneously + * without any cross-coupling: whichever is slack sits at + * 2000 and contributes nothing, and the current loop's + * integrator cannot wind up behind a binding thermal + * ceiling because it is clamped to 2000 either way. + * + * Neither can step thrust. The thermal ceiling moves with a + * filtered die temperature (64 ms IIR over a thermal mass + * measured in seconds), the current ceiling is itself an + * integrator moving at most 10 duty units per 1 kHz tick, + * and the 20 kHz ramp limiter slews applied duty toward + * whatever they allow at max_duty_cycle_change in BOTH + * directions (control_loop.c ~line 750). + * + * After the stall-protection boost, not before: a boost that + * can push duty back through a protection ceiling is not a + * ceiling. This is a deliberate change from upstream, where + * the current clamp ran first and the crawler boost could + * overshoot it. + */ + uint16_t protect_ceiling = thermal_duty_ceiling; + if (use_current_limit && use_current_limit_adjust < (int16_t)protect_ceiling) { + protect_ceiling = (uint16_t)use_current_limit_adjust; + } + duty_limit_ceiling = protect_ceiling; + if (duty_cycle_setpoint > protect_ceiling) { + duty_cycle_setpoint = protect_ceiling; + } } } // Missed-ZC power cut (BLHeli-style): while commutating blind the // rotor position is unknown - bound the energy driven into a possibly - // wrong phase (DRV8328 boards have no VDS trip). Applied here in - // setInput (on F051 that is the DShot EXTI IRQ, not the main loop) - // rather than inside the O3 RAM_FUNC 20 kHz body (bench bisect showed - // adding code there disturbs F051 startup). Pulling last_duty_cycle - // down as well makes the cut immediate: the 20 kHz slew limiter ramps - // from last_duty_cycle, so capping only the setpoint would let up to + // wrong phase. On DRV8328 (F051 4IN1) there is no VDS trip; on + // DRV8350H (ARK_G431_CAN) the board has a resistor-set VDS limit and + // FAULT_N is polled in faultPollGateDriver. Applied here in setInput + // (on F051 that is the DShot EXTI IRQ, not the main loop) rather than + // inside the O3 RAM_FUNC 20 kHz body (bench bisect showed adding code + // there disturbs F051 startup). Pulling last_duty_cycle down as well + // makes the cut immediate: the 20 kHz slew limiter ramps from + // last_duty_cycle, so capping only the setpoint would let up to // 37.5 ms of ramp-down stand between a blind step and the cap // (max_ramp_startup while zero_crosses < 150 - live here, since blind // stepping arms at 100). The 20 kHz tick may overwrite last_duty_cycle @@ -579,7 +615,21 @@ RAM_FUNC void tenKhzRoutine() if (adjusted_input == 0) { armed_timeout_count++; if (armed_timeout_count > LOOP_FREQUENCY_HZ) { // one second - if (zero_input_count > 30) { + /* + * DShot/PWM: zero_input_count confirms many true-zero + * frames (noisy edges). DroneCAN: ESCRaw may be only + * 10 Hz; holding adjusted_input==0 for this full + * second is already the safety gate — do not also + * require >30 transfercomplete samples (that needs + * ~30 Hz or the 1 kHz reinject path). + */ + const uint8_t zero_ok = (zero_input_count > 30) +#if DRONECAN_SUPPORT + /* Pure CAN mode, or AUTO with a live RawCommand stream. */ + || (eepromBuffer.input_type == DRONECAN_IN) || DroneCAN_active() +#endif + ; + if (zero_ok) { escToArmedIdle(); #ifdef USE_LED_STRIP // send_LED_RGB(0,0,0); @@ -658,6 +708,18 @@ RAM_FUNC void tenKhzRoutine() one_khz_loop_counter = 0; faultDesyncEpisodeTick1kHz(); if (use_current_limit && escIsDriving()) { + /* + * The /10000 is a DEAD ZONE, not just a scale: it is an + * integer divide, so the ceiling does not move at all + * until the PID output exceeds 10000 - i.e. until the + * overshoot exceeds 5000/Kp centiamps. At the shipped + * Kp 200 (current_P 100) that is ~0.5 A of resolution; + * at Kp 10 it would be 10 A, and the limiter would sit + * inert while current ran to twice the setpoint. + * Measured, SITL heavy_13inch at an 8 A limit: P=100 + * holds 7.7 A, P=50 7.1 A, P=25 6.5 A, P=5 14.7 A. + * Lower these gains only with a bench number in hand. + */ use_current_limit_adjust -= (int16_t)(doPidCalculations(¤tPid, actual_current, eepromBuffer.limits.current * 2 * 100) / 10000); @@ -667,6 +729,28 @@ RAM_FUNC void tenKhzRoutine() if (use_current_limit_adjust > 2000) { use_current_limit_adjust = 2000; } + } else { + /* + * Release the ceiling whenever the loop that owns it is + * not running. It is an integrator with no other reset: + * a run that ended while current-limited used to leave + * it low, and the NEXT start then found its setpoint + * capped below min_startup_duty (the clamp is applied + * after the startup floor) until the PID walked it back + * up - hundreds of ms of weak spool-up inherited from + * the previous run. + * + * This does release across a desync coast too, so a + * restart begins with full authority and the loop needs + * ~100 ms to re-bind if the load is still over the + * limit. That is the right trade here: this limiter is + * a 50 ms-averaged backstop against SUSTAINED current + * and never caught that transient anyway, the ramp + * limiter still bounds how fast duty can return, and on + * ARK_G431_CAN the DRV8350 VDS trip is what covers the + * instantaneous case. + */ + use_current_limit_adjust = 2000; } if (eepromBuffer.stall_protection && escIsDriving()) { // this boosts throttle as the rpm gets lower, for crawlers // and rc cars only, do not use for multirotors. diff --git a/Src/debug_uart.c b/Src/debug_uart.c new file mode 100644 index 000000000..0f1f3eee9 --- /dev/null +++ b/Src/debug_uart.c @@ -0,0 +1,210 @@ +/* + * debug_uart.c — USART2 TX PB3 debug console for ARK 12S CAN + */ + +#include "targets.h" +#include "debug_uart.h" + +#ifdef USE_DEBUG_UART + +# include "main.h" +# include "esc_state.h" + +# include +# include +# include + +# include "stm32g4xx_ll_bus.h" +# include "stm32g4xx_ll_gpio.h" +# include "stm32g4xx_ll_usart.h" +# include "stm32g4xx_ll_rcc.h" + +/* + * Event queue: small fixed records so ISRs never block on UART. + * Main loop formats and transmits. + */ +# define DBG_Q_LEN 16 + +typedef struct { + uint8_t kind; /* 0=empty, 1=state, 2=event */ + uint8_t a; + uint8_t b; +} dbg_rec_t; + +static volatile uint8_t q_head; +static volatile uint8_t q_tail; +static dbg_rec_t q[DBG_Q_LEN]; +static uint8_t q_overflow; + +static void dbg_putc(char c) +{ + while (!LL_USART_IsActiveFlag_TXE(USART2)) {} + LL_USART_TransmitData8(USART2, (uint8_t)c); +} + +static void dbg_write(const char *s, uint32_t n) +{ + while (n--) { + dbg_putc(*s++); + } + while (!LL_USART_IsActiveFlag_TC(USART2)) {} +} + +void debugUartInit(void) +{ + LL_GPIO_InitTypeDef gpio = {0}; + LL_USART_InitTypeDef usart = {0}; + + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2); + LL_RCC_SetUSARTClockSource(LL_RCC_USART2_CLKSOURCE_PCLK1); + + /* PB3 = USART2_TX AF7 (same as bootloader USE_DEBUG_UART) */ + gpio.Pin = LL_GPIO_PIN_3; + gpio.Mode = LL_GPIO_MODE_ALTERNATE; + gpio.Speed = LL_GPIO_SPEED_FREQ_HIGH; + gpio.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + gpio.Pull = LL_GPIO_PULL_UP; + gpio.Alternate = LL_GPIO_AF_7; + LL_GPIO_Init(GPIOB, &gpio); + + /* 115200 8N1, TX only. Sysclk 160 MHz, APB1 undivided. */ + usart.PrescalerValue = LL_USART_PRESCALER_DIV1; + usart.BaudRate = 115200; + usart.DataWidth = LL_USART_DATAWIDTH_8B; + usart.StopBits = LL_USART_STOPBITS_1; + usart.Parity = LL_USART_PARITY_NONE; + usart.TransferDirection = LL_USART_DIRECTION_TX; + usart.HardwareFlowControl = LL_USART_HWCONTROL_NONE; + usart.OverSampling = LL_USART_OVERSAMPLING_16; + LL_USART_Init(USART2, &usart); + LL_USART_DisableFIFO(USART2); + LL_USART_Enable(USART2); + while (!LL_USART_IsActiveFlag_TEACK(USART2)) {} + + q_head = q_tail = 0; + q_overflow = 0; + + debugUartPrint("\r\nARK_G431_CAN debug UART @ 115200 (PB3/USART2)\r\n"); +} + +void debugUartPrint(const char *msg) +{ + if (!msg) { + return; + } + dbg_write(msg, (uint32_t)strlen(msg)); +} + +void debugUartPrintf(const char *fmt, ...) +{ + char buf[96]; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (n <= 0) { + return; + } + if (n >= (int)sizeof(buf)) { + n = (int)sizeof(buf) - 1; + } + dbg_write(buf, (uint32_t)n); +} + +static void q_push(uint8_t kind, uint8_t a, uint8_t b) +{ + uint8_t next = (uint8_t)((q_head + 1u) % DBG_Q_LEN); + if (next == q_tail) { + q_overflow = 1; + return; + } + q[q_head].kind = kind; + q[q_head].a = a; + q[q_head].b = b; + q_head = next; +} + +void debugUartLogState(uint8_t from, uint8_t to) +{ + if (from == to) { + return; + } + q_push(1, from, to); +} + +void debugUartLogEvent(uint8_t event) +{ + if (event == 0) { + return; + } + /* + * Drop duplicate consecutive fault events so a latched rail does not + * flood the UART (e.g. stuck polled every main-loop tick). + */ + if (q_head != q_tail) { + uint8_t last = (uint8_t)((q_head + DBG_Q_LEN - 1u) % DBG_Q_LEN); + if (q[last].kind == 2 && q[last].a == event) { + return; + } + } + q_push(2, event, 0); +} + +static const char *evt_name(uint8_t e) +{ + switch (e) { + case DBG_EVT_BOOT: + return "boot"; + case DBG_EVT_NFAULT: + return "nFAULT"; + case DBG_EVT_NFAULT_UVLO: + return "nFAULT UVLO"; + case DBG_EVT_NFAULT_OCP: + return "nFAULT OCP"; + case DBG_EVT_NFAULT_OTW: + return "nFAULT OTW"; + case DBG_EVT_STUCK: + return "stuck"; + case DBG_EVT_LVC: + return "LVC"; + case DBG_EVT_SIGNAL: + return "signal_lost"; + case DBG_EVT_DESYNC: + return "desync"; + case DBG_EVT_ACQ_DESYNC: + return "acq_desync"; + case DBG_EVT_STALL: + return "stall"; + case DBG_EVT_GD_WAKE: + return "gd_wake"; + case DBG_EVT_GD_SLEEP: + return "gd_sleep"; + default: + return "event"; + } +} + +void debugUartService(void) +{ + if (q_overflow) { + q_overflow = 0; + debugUartPrint("dbg: queue overflow\r\n"); + } + + while (q_tail != q_head) { + dbg_rec_t rec = q[q_tail]; + q_tail = (uint8_t)((q_tail + 1u) % DBG_Q_LEN); + + if (rec.kind == 1) { + /* state change */ + const char *from = escStateName((esc_state_t)rec.a); + const char *to = escStateName((esc_state_t)rec.b); + debugUartPrintf("esc: %s -> %s\r\n", from, to); + } else if (rec.kind == 2) { + debugUartPrintf("fault: %s\r\n", evt_name(rec.a)); + } + } +} + +#endif /* USE_DEBUG_UART */ diff --git a/Src/dshot.c b/Src/dshot.c index b7c9bd15e..13dd3d782 100644 --- a/Src/dshot.c +++ b/Src/dshot.c @@ -56,6 +56,17 @@ uint32_t gcr[37] = {0}; uint16_t dshot_frametime; uint16_t dshot_goodcounts; uint16_t dshot_badcounts; +/* Consecutive drive-demand frames discarded before throttle may leave zero. + * 2 discards = the 3rd corroborating frame is the one accepted, costing two + * frame periods (2.5 ms at 800 Hz) on a genuine spool-up and nothing at all + * once moving. See the idle-exit holdoff in computeDshotDMA(). */ +#define DSHOT_IDLE_EXIT_HOLDOFF_FRAMES 2 +static uint8_t dshot_idle_exit_holdoff; +/* Diagnostic, alongside good/badcounts: frames rejected by the holdoff. A + * healthy link ticks this by exactly DSHOT_IDLE_EXIT_HOLDOFF_FRAMES per real + * spool-up; anything beyond that is single-frame drive demands that never + * corroborated, i.e. the fault the holdoff exists to absorb. */ +uint16_t dshot_idle_exit_blocked; uint8_t dshot_extended_telemetry = 0; uint16_t processtime = 0; uint16_t halfpulsetime = 0; @@ -138,6 +149,42 @@ void computeDshotDMA() } if (tocheck > DSHOT_CMD_MAX) { if (EDT_ARMED) { +#if DRONECAN_SUPPORT + /* AUTO dual-path: live RawCommand owns throttle. */ + if (DroneCAN_active()) { + return; + } +#endif + /* Idle-exit holdoff. Neither integrity check in this + * function can reject a single corrupted frame that + * decodes to full throttle: an all-ones capture + * (0xFFFF) is a CRC-VALID DShot frame carrying + * throttle 2047, and its frame span lands only + * +1.63% off nominal - inside the +-6.25% span gate + * above. One lost edge on the signal line inverts + * the pulse/gap pairing, so a commanded stop + * (0x0000, every pulse short) becomes commanded + * full throttle. Worse, newinput then LATCHES: it + * is only overwritten by the next accepted frame, + * and the same marginal link that corrupted this + * one tends to drop the next several, so the bogus + * demand survives for as long as the dropout. + * Measured on an ARK G431 CAN bench as a 30-90 ms, + * ~29% duty spin-up kick at armed idle, roughly + * once a minute (hwci/runs/gate_blip_watch.log). + * + * So require the demand to repeat before acting on + * it, but only while throttle is still at zero - + * once moving this costs nothing, and in-flight + * slew stays governed by max_duty_cycle_change / + * max_ramp in the duty domain. */ + if (newinput == 0 && dshot_idle_exit_holdoff < DSHOT_IDLE_EXIT_HOLDOFF_FRAMES) { + dshot_idle_exit_holdoff++; + dshot_idle_exit_blocked++; + dshotcommand = 0; + command_count = 0; + return; + } newinput = tocheck; dshotcommand = 0; command_count = 0; @@ -146,16 +193,26 @@ void computeDshotDMA() } if ((tocheck <= DSHOT_CMD_MAX) && (tocheck > DSHOT_CMD_MOTOR_STOP)) { + /* Any non-drive frame breaks the run of drive demands the + * holdoff above is counting - reset ahead of the DroneCAN + * early-out so the run always reflects the wire. */ + dshot_idle_exit_holdoff = 0; +#if DRONECAN_SUPPORT + if (DroneCAN_active()) { + return; + } +#endif newinput = 0; dshotcommand = tocheck; } if (tocheck == DSHOT_CMD_MOTOR_STOP) { + dshot_idle_exit_holdoff = 0; if (EDT_ARM_ENABLE == 1) { EDT_ARMED = 0; } #if DRONECAN_SUPPORT if (DroneCAN_active()) { - // allow DroneCAN to override DShot input + /* Keep signaltimeout fed; do not zero over CAN demand. */ return; } #endif diff --git a/Src/esc_state.c b/Src/esc_state.c index 3ff21daa9..9594ffe7f 100644 --- a/Src/esc_state.c +++ b/Src/esc_state.c @@ -10,6 +10,8 @@ #include "signal.h" #include "eeprom.h" #include "faults.h" +#include "debug_uart.h" +#include "comparator.h" volatile esc_state_t esc_state = ESC_DISARMED; volatile uint16_t esc_illegal_edge_count = 0; @@ -58,6 +60,7 @@ static const uint16_t esc_allowed[ESC_STATE_COUNT] = { [ESC_FAULT_LVC] = (1u << ESC_FAULT_LVC), }; +#ifdef USE_DEBUG_UART const char *escStateName(esc_state_t s) { switch (s) { @@ -85,6 +88,7 @@ const char *escStateName(esc_state_t s) return "?"; } } +#endif esc_state_t escGetState(void) { @@ -120,19 +124,33 @@ static void escCommitState(esc_state_t next) } #endif } + if (from != next) { + debugUartLogState((uint8_t)from, (uint8_t)next); + } esc_state = next; } /* Force without edge check (reconcile / recovery). */ static void escForceState(esc_state_t next) { + if (esc_state != next) { + debugUartLogState((uint8_t)esc_state, (uint8_t)next); + } esc_state = next; } void escReconcileFromFlags(void) { + /* A stopped motor is a new run next time. The established-run latch + * must not survive PX4 disarm / zero throttle: ESC `armed` stays 1 + * across FC arm/disarm, so leftover established made the next + * idle-start jump look like an in-flight desync (error_count/WARN). */ + if (!running && !stepper_sine) { + fault_run_established = 0; + } + /* Latched faults win over drive mode. */ - if (bemf_timeout_happened == ESC_STUCK_LATCH) { + if (bemf_timeout_happened == ESC_STUCK_LATCH || faultGateDriverFaultActive()) { escForceState(ESC_FAULT_STUCK); return; } @@ -208,6 +226,10 @@ void escToOpenLoop(void) running = 1; old_routine = 1; stepper_sine = 0; +#if defined(MCU_G431) + /* Drop interrupt-ZC while back in poll; otherwise dual-path thrash. */ + maskPhaseInterrupts(); +#endif escCommitState(ESC_OPEN_LOOP); } diff --git a/Src/faults.c b/Src/faults.c index eaf529919..1e08daccd 100644 --- a/Src/faults.c +++ b/Src/faults.c @@ -17,8 +17,12 @@ #include "bemf_zc.h" #include "targets.h" #include "esc_state.h" +#include "gate_driver.h" +#include "debug_uart.h" #include "IO.h" #include "sounds.h" +/* commutation_interval for stall debug line */ +#include "motor_runtime.h" #ifdef USE_RGB_LED extern void setIndividualRGBLed(uint8_t, uint8_t, uint8_t); @@ -30,6 +34,7 @@ extern void resetInputCaptureTimer(void); /* See the comments on the declarations in faults.h. */ volatile uint32_t fault_stall_trips = 0; +volatile uint8_t fault_run_established = 0; /* Acquisition-rail early-desync count (see faultNoteEarlyDesync). */ static uint8_t acq_fail_desyncs; @@ -37,6 +42,119 @@ static uint8_t acq_fail_desyncs; /* "Start resisted" counter (see the declaration in faults.h). */ volatile uint8_t fault_acq_resist_events; +#if defined(MCU_G431) +/* + * ARK 12S CAN acquisition grace budget (see faultUpdateBemfTimeoutPolicy). + * Sized above one startup-matrix start segment (3.0 s of continuous + * throttle) so a tier that never acquires is still measured, while any + * genuinely locked rotor latches once the budget runs out. Throttle + * returns to zero between starts, which rearms the full budget. + * Clocked in faultDesyncEpisodeTick1kHz. + */ +# define ACQ_GRACE_MS_MAX 4000u +/* Above this, acquisition should be near instant; a jam here is the + * dangerous case, so it gets no grace at all. Well above the 447 seen at + * the matrix's top 20% tier. */ +# define ACQ_GRACE_MAX_INPUT 1000 +static uint16_t acq_grace_ms; +#endif + +/* + * DRV nFAULT is a single open-drain OR (VDS OCP, UVLO, OTW, GDF). Hardware + * interface (DRV8350H / DRV8328 without SPI status) cannot report which bit + * fired — classify from MCU ADC context at the rising edge of the latch. + */ +#if defined(USE_DRV_NFAULT) || defined(USE_DRV8328_NFAULT) +# define FAULT_HAS_DRV_NFAULT 1 +#else +# define FAULT_HAS_DRV_NFAULT 0 +#endif + +#if FAULT_HAS_DRV_NFAULT +/* Sticky until nFAULT releases; keeps FAULT_STUCK if bemf latch is cleared + * by the zero-throttle path while the DRV is still asserting. */ +static uint8_t drv_nfault_latched; +static fault_id_t drv_nfault_cause; +# if defined(USE_DRV_ENABLE) +/* Rate-limit ENABLE recovery pulses (~main-loop iterations). */ +static uint16_t drv_enable_retry_div; +# endif + +/* + * Best-effort guess: the DRV does not tell us which OR-term pulled nFAULT. + * Use MCU ADC at the latch edge (call BEFORE clearing running/duty context). + * + * Priority: UVLO → OTW → OCP → unknown (VDS/GDF/etc.). + * Units: battery_voltage 10 mV, actual_current 10 mA, degrees_celsius °C. + * + * Thresholds are deliberately coarse — a wrong-but-specific label is more + * useful on the bench/FC than a generic "nFAULT". They are not datasheet + * proofs of the DRV comparator that fired. + */ +static fault_id_t fault_classify_drv_nfault(void) +{ + const uint16_t v_cv = battery_voltage; + const int16_t i_ca = actual_current; + const int16_t t_c = degrees_celsius; + + /* ~8 V: below useful pack voltage for these ESCs; near DRV VM UVLO. */ + if (v_cv > 0u && v_cv < 800u) { + return FAULT_GD_UVLO; + } + /* FET NTC / motor path hot — OTW class. */ + if (t_c >= 100) { + return FAULT_GD_OTW; + } + /* High current, or was driving with meaningful duty when the pin fell. */ + if (i_ca >= 2000 || (running != 0 && duty_cycle > 200u)) { + return FAULT_GD_OCP; + } + return FAULT_GD_UNKNOWN; +} + +# ifdef USE_DEBUG_UART +static uint8_t fault_drv_nfault_dbg_event(fault_id_t cause) +{ + switch (cause) { + case FAULT_GD_UVLO: + return DBG_EVT_NFAULT_UVLO; + case FAULT_GD_OCP: + return DBG_EVT_NFAULT_OCP; + case FAULT_GD_OTW: + return DBG_EVT_NFAULT_OTW; + default: + return DBG_EVT_NFAULT; + } +} +# endif +#endif /* FAULT_HAS_DRV_NFAULT */ + +const char *faultGateDriverCauseName(fault_id_t cause) +{ + switch (cause) { + case FAULT_GD_UVLO: + return "UVLO"; + case FAULT_GD_OCP: + return "OCP"; + case FAULT_GD_OTW: + return "OTW"; + case FAULT_GD_UNKNOWN: + return "nFAULT"; + default: + return ""; + } +} + +fault_id_t faultGateDriverCause(void) +{ +#if FAULT_HAS_DRV_NFAULT + if (drv_nfault_latched || ((NFAULT_PORT->IDR & NFAULT_PIN) == 0u)) { + return drv_nfault_cause; + } +#endif + return FAULT_NONE; +} + uint32_t faultErrorCount(void) { return desync_happened + fault_stall_trips; @@ -48,14 +166,126 @@ void faultErrorCountReset(void) fault_stall_trips = 0; desync_happened = 0; acq_fail_desyncs = 0; + /* Lifetimes must match the counters this latch gates. */ + fault_run_established = 0; +} + +uint8_t faultGateDriverFaultActive(void) +{ +#if FAULT_HAS_DRV_NFAULT + if (drv_nfault_latched) { + return 1; + } + /* ENABLE/nSLEEP low asserts nFAULT (VCP UVLO). That is sleep, not a + * trip — treating it as one made every arm/beep/idle publish a CAN + * ERROR LogMessage and NodeStatus CRITICAL while the motor was fine. */ + if (!gateDriverNfaultPinTrusted()) { + return 0; + } + return (uint8_t)((NFAULT_PORT->IDR & NFAULT_PIN) == 0u); +#else + return 0; +#endif +} + +void faultPollGateDriver(void) +{ +#if FAULT_HAS_DRV_NFAULT + /* + * While asleep ENABLE is held low (gate_driver sleep). That already + * satisfies DRV8350H t_RST for latched VDS/GDF faults — but we used to + * return here without ever clearing drv_nfault_latched, so FAULT_STUCK + * survived zero throttle + VM restore until reboot. Clear the software + * sticky at zero demand while asleep; nFAULT is not meaningful with + * ENABLE low, so do not re-assert from the pin in that state. + */ + if (!gateDriverIsAwake()) { + if (drv_nfault_latched && adjusted_input == 0) { + drv_nfault_latched = 0; + drv_nfault_cause = FAULT_NONE; +# if defined(USE_DRV_ENABLE) + drv_enable_retry_div = 0; +# endif + } + return; + } + + gateDriverNfaultGraceTick(); + if (!gateDriverNfaultPinTrusted()) { + return; + } + + /* Active low (open-drain). High = healthy. */ + const uint8_t pin_ok = (NFAULT_PORT->IDR & NFAULT_PIN) != 0u; + + if (!pin_ok) { + if (!drv_nfault_latched) { + /* Classify while running/duty still reflect the trip context. */ + drv_nfault_cause = fault_classify_drv_nfault(); + drv_nfault_latched = 1; +# ifdef USE_DEBUG_UART + debugUartLogEvent(fault_drv_nfault_dbg_event(drv_nfault_cause)); +# endif + } + allOff(); + maskPhaseInterrupts(); + SET_DUTY_CYCLE_ALL(0); + running = 0; + stepper_sine = 0; + if (escGetState() != ESC_FAULT_STUCK) { + /* Reuses stuck latch: reconcile holds FAULT_STUCK via + * bemf_timeout_happened == ESC_STUCK_LATCH until zero + * throttle clears it — same pilot-clear semantics. */ + escToFaultStuck(); +# ifdef USE_RGB_LED + setIndividualRGBLed(1, 0, 0); +# endif + } +# if defined(USE_DRV_ENABLE) + /* + * DRV8350H: latched VDS / gate faults stay asserted until + * ENABLE is driven low for t_RST then high again. Only retry + * at zero demand so we never re-arm into a short. + */ + if (adjusted_input == 0) { + if (++drv_enable_retry_div >= 2000u) { + drv_enable_retry_div = 0; + gateDriverFaultResetPulse(); + } + } else { + drv_enable_retry_div = 0; + } +# endif + } else if (drv_nfault_latched && adjusted_input == 0) { + /* Pin recovered and pilot at zero: drop sticky so arm can proceed. */ + drv_nfault_latched = 0; + drv_nfault_cause = FAULT_NONE; +# if defined(USE_DRV_ENABLE) + drv_enable_retry_div = 0; +# endif + } +#endif } uint8_t faultHandleStuckRotorIfNeeded(void) { #ifndef BRUSHED_MODE +# if FAULT_HAS_DRV_NFAULT + /* Gate-driver trip: do not map throttle back onto the bridge while + * nFAULT is low or still latched (cause in faultGateDriverCause()). */ + if (faultGateDriverFaultActive()) { + allOff(); + maskPhaseInterrupts(); + return 1; + } +# endif if ((bemf_timeout_happened > bemf_timeout) && eepromBuffer.stuck_rotor_protection) { allOff(); maskPhaseInterrupts(); + /* Log once on entry — this path runs every main-loop tick while latched. */ + if (escGetState() != ESC_FAULT_STUCK) { + debugUartLogEvent(DBG_EVT_STUCK); + } escToFaultStuck(); # ifdef USE_RGB_LED setIndividualRGBLed(1, 0, 0); @@ -89,6 +319,8 @@ void faultPollSignalTimeout(void) if (signaltimeout > (LOOP_FREQUENCY_HZ >> 1)) { // half second timeout when armed; if (escIsArmed()) { allOff(); + debugUartLogEvent(DBG_EVT_SIGNAL); + debugUartService(); /* flush before reset */ escToFaultSignal(); zero_input_count = 0; SET_DUTY_CYCLE_ALL(0); @@ -101,6 +333,8 @@ void faultPollSignalTimeout(void) } if (signaltimeout > LOOP_FREQUENCY_HZ << 1) { // 2 second when not armed allOff(); + debugUartLogEvent(DBG_EVT_SIGNAL); + debugUartService(); escToFaultSignal(); zero_input_count = 0; SET_DUTY_CYCLE_ALL(0); @@ -135,6 +369,32 @@ void faultUpdateBemfTimeoutPolicy(void) desync_restart_holdoff_ms = 0; acq_fail_desyncs = 0; } +# if defined(MCU_G431) + /* + * ARK 12S CAN free-run restarts thrash in the acquisition window + * (zero_crosses reset each stall) while still commanded throttle. + * Accumulating bemf_timeout_happened across those kicks latched + * stuck_rotor after a few start* cycles on the stand. F051 4IN1 does + * not show this with the same shared rails. Forgive acquisition-only + * stalls; keep the counter once the loop is established (zc > 100). + * + * Time-bounded on purpose. A truly locked rotor never reaches + * zc >= 100, so an unbounded clear disables stuck_rotor entirely + * during acquisition -- and, because ESC_STUCK_LATCH is itself carried + * in bemf_timeout_happened, silently wipes an already-latched stuck + * fault too (bench 2026-08: 16 FAULT_STUCK entries that all recovered + * within one tick). A jammed 12S prop would re-kick forever. + * + * A plain throttle gate does not work either: it has to sit above the + * highest tier being characterized, and the startup matrix reaches + * input 447 at its 20% tier, so any threshold low enough to be useful + * lands mid-sweep. Bound the grace in time instead -- start attempts + * are short and separated by zero throttle, a jam is continuous. + */ + if (zero_crosses < 100 && acq_grace_ms < ACQ_GRACE_MS_MAX && adjusted_input < ACQ_GRACE_MAX_INPUT) { + bemf_timeout_happened = 0; + } +# endif if (zero_crosses > 100 && adjusted_input < 200) { bemf_timeout_happened = 0; } @@ -142,11 +402,26 @@ void faultUpdateBemfTimeoutPolicy(void) bemf_timeout_happened = 0; } +# if defined(MCU_G431) + /* + * ARK 12S CAN (G4): free-run high-KV crawl lives above DShot~150 while + * BEMF is still weak. F051 gets clean edges from COMP hysteresis there; + * until G4 hysteresis settles, keep the soft stall budget (100) out to + * input 400 so restart thrash does not latch stuck_rotor in 10 kicks. + * Established high-throttle stuck detection still uses 10. + */ + if (adjusted_input < 400) { + bemf_timeout = 100; + } else { + bemf_timeout = 10; + } +# else if (adjusted_input < 150) { // startup duty cycle should be low enough to not burn motor bemf_timeout = 100; } else { bemf_timeout = 10; } +# endif #endif } @@ -290,6 +565,9 @@ void faultDesyncEpisodeCharge(desync_episode_kind_t kind) if (desync_episode_bucket >= DESYNC_EPISODE_LIMIT) { allOff(); maskPhaseInterrupts(); + if (escGetState() != ESC_FAULT_STUCK) { + debugUartLogEvent(DBG_EVT_STUCK); + } escToFaultStuck(); # ifdef USE_RGB_LED setIndividualRGBLed(1, 0, 0); @@ -307,6 +585,7 @@ void faultNoteEarlyDesync(void) return; } acq_fail_desyncs = 0; + debugUartLogEvent(DBG_EVT_ACQ_DESYNC); faultDesyncEpisodeCharge(DESYNC_EPISODE_ACQ_FAIL); #endif } @@ -319,6 +598,15 @@ void faultDesyncEpisodeTick1kHz(void) if (zero_crosses > ACQ_FAIL_CLEAR_ZC) { acq_fail_desyncs = 0; } +# if defined(MCU_G431) + /* Acquisition grace clock. Runs only while throttle is commanded and + * the loop has not established; pilot cut or a real acquire rearms it. */ + if (zero_crosses >= 100 || adjusted_input == 0) { + acq_grace_ms = 0; + } else if (acq_grace_ms < ACQ_GRACE_MS_MAX) { + acq_grace_ms++; + } +# endif if (desync_restart_holdoff_ms > 0) { desync_restart_holdoff_ms--; } @@ -350,28 +638,53 @@ void faultHandleBemfIntervalStall(void) { /* Six-step only (not sine soft-start). */ if (INTERVAL_TIMER_COUNT > BEMF_STALL_TICKS && (escInOpenLoop() || escInClosedLoop())) { - bemf_timeout_happened++; + /* Commanded stop (input < 48 == escNoteStallOrDesync): duty already + * zero, running still set while BEMF dies — INTERVAL_TIMER expiry + * is expected, not a stall. Skip trip count / log / episode charge. */ + const uint8_t commanded_stop = (input < 48); maskPhaseInterrupts(); - // Charge the episode rail only when this run was ESTABLISHED - // before it died (the bad-tune restart->spool->desync cycle - // always reaches closed loop first). A start attempt that never - // got going is the legacy stuck-rotor rail's job, with its - // throttle-scaled tolerance (bemf_timeout 100 below input 150) - - // heavy props legitimately kick many times at low throttle, and - // charging those latched the ESC on the 4th kick. This gate also - // covers the blind/miss-limit handoff (bemf_zc kicks - // INTERVAL_TIMER past BEMF_STALL_TICKS with comparator interrupts masked, so - // this rail is guaranteed to run next pass): blind stepping only - // arms at zero_crosses >= 100, so those episodes always charge. - if (zero_crosses > 100) { - /* Established run died here. This is the ONLY place the - * stall rail is counted, and it is the aggregation point - * for the dead-reckoning budget handoff in bemf_zc.c, - * both of which reach the loop through this trip - see - * faultErrorCount(). */ - fault_stall_trips++; - faultDesyncEpisodeCharge(DESYNC_EPISODE_STALL_RAIL); + if (!commanded_stop) { + bemf_timeout_happened++; + // Charge the episode rail only when this run was ESTABLISHED + // before it died (the bad-tune restart->spool->desync cycle + // always reaches closed loop first). A start attempt that never + // got going is the legacy stuck-rotor rail's job, with its + // throttle-scaled tolerance (bemf_timeout 100 below input 150) - + // heavy props legitimately kick many times at low throttle, and + // charging those latched the ESC on the 4th kick. This gate also + // covers the blind/miss-limit handoff (bemf_zc kicks + // INTERVAL_TIMER past BEMF_STALL_TICKS with comparator interrupts masked, so + // this rail is guaranteed to run next pass): blind stepping only + // arms at zero_crosses >= 100, so those episodes always charge. + // + // Reporting and escalation take DIFFERENT gates, for the same + // reason as the jump check in runtimeProcessDesyncCheck(): + // - fault_stall_trips feeds esc.Status.error_count, so it asks + // "did an established run fault this arm cycle?" -> latch, + // which survives the zero_crosses reset this rail performs. + // - the episode charge asks "is THIS trip an established-run + // failure or acquisition thrash?" -> live count. After the + // first trip the loop is back in acquisition and kicks + // repeatedly; charging those would fill the bucket in a few + // events and latch the ESC during its own recovery. + if (fault_run_established) { + /* Established run died this arm cycle. This is the ONLY + * place the stall rail is counted, and it is the + * aggregation point for the grind rail and the blind/ + * miss-limit handoff, both of which reach the loop through + * this trip - see faultErrorCount(). */ + fault_stall_trips++; +#ifdef USE_DEBUG_UART + /* Single UART line (LogEvent would print "fault: stall" twice). */ + debugUartPrintf("fault: stall zc=%lu bemf_to=%u/%u e_com=%lu\r\n", (unsigned long)zero_crosses, + (unsigned)bemf_timeout_happened, (unsigned)bemf_timeout, + (unsigned long)commutation_interval); +#endif + } + if (zero_crosses > 100) { + faultDesyncEpisodeCharge(DESYNC_EPISODE_STALL_RAIL); + } } if (escIsFault()) { /* Episode rail latched: do not re-enter startup. */ @@ -383,8 +696,8 @@ void faultHandleBemfIntervalStall(void) escNoteStallOrDesync(1); zero_crosses = 0; bemfZcResetTrend(); - if (faultDesyncRestartHoldoffActive()) { - /* Coast until holdoff expires; main loop will re-arm. */ + if (commanded_stop || faultDesyncRestartHoldoffActive()) { + /* Coast / commanded stop: do not zcfound re-enter. */ running = 0; allOff(); return; diff --git a/Src/gate_driver.c b/Src/gate_driver.c index 9d25a8863..130e16147 100644 --- a/Src/gate_driver.c +++ b/Src/gate_driver.c @@ -7,14 +7,30 @@ #if GATE_DRIVER_SLEEP_SUPPORT # include "functions.h" -# include "main.h" +# include "main.h" /* GPIO_TypeDef / BRR / BSRR */ # include "motor_runtime.h" # include "targets.h" +# if defined(USE_DRV_ENABLE) +/* DRV8350H on ARK 12S CAN: force bridge inputs inactive before ENABLE. */ +# include "peripherals.h" +# include "phaseouts.h" +# endif -/* Datasheet tWAKE: after nSLEEP/ENABLE rises, wait before gate inputs are valid. - * Pin stays high; this is not a toggle hold — bias/charge pump must come up. */ -# define GD_WAKE_US 1000u +/* + * After nSLEEP/ENABLE rises, wait before gate inputs are valid (pin stays high). + * DRV8350H (USE_DRV_ENABLE): datasheet t_WAKE ~1 ms is a minimum; charge pump + * and bootstrap need longer or a half-bridge mismatch blips the motor. Use 3 ms. + * DRV8328 nSLEEP (ARK 4in1): 1 ms is enough in practice. + */ +# if defined(USE_DRV_ENABLE) +# define GD_WAKE_US 3000u +# else +# define GD_WAKE_US 1000u +# endif # define GD_FAULT_RST_US 50u +/* Main-loop polls to ignore nFAULT after ENABLE/nSLEEP rises. Covers the + * same-loop DroneCAN read after wake, and the first PWM edges. */ +# define GD_NFAULT_GRACE_POLLS 8u # if defined(USE_DRV_ENABLE) # define GD_PORT DRV_ENABLE_PORT @@ -25,6 +41,7 @@ # endif volatile uint8_t gate_driver_awake; +static uint8_t gd_nfault_grace; void gateDriverInit(void) { @@ -37,6 +54,7 @@ void gateDriverInit(void) LL_GPIO_Init(GD_PORT, &s); GD_PORT->BRR = GD_PIN; gate_driver_awake = 0; + gd_nfault_grace = 0; } void gateDriverWakeBlocking(void) @@ -44,25 +62,63 @@ void gateDriverWakeBlocking(void) if (gate_driver_awake) { return; /* already high; no delay */ } +# if defined(USE_DRV_ENABLE) + /* + * DRV8350H wake sequence (required to avoid motor blip): + * 1) Force all INHx/INLx inactive (6x mode: both low via allOff) + * 2) Zero TIM CCRs so AF re-entry cannot drive non-zero duty + * 3) Assert ENABLE high + * 4) Wait GD_WAKE_US for charge pump / VGLS / bootstrap settle + * 5) Caller may then apply PWM / commutation + */ + allOff(); + SET_DUTY_CYCLE_ALL(0); +# endif GD_PORT->BSRR = GD_PIN; - delayMicros(GD_WAKE_US); /* tWAKE — see GD_WAKE_US */ + delayMicros(GD_WAKE_US); gate_driver_awake = 1; + gd_nfault_grace = GD_NFAULT_GRACE_POLLS; +} + +uint8_t gateDriverNfaultPinTrusted(void) +{ + return (uint8_t)(gate_driver_awake && gd_nfault_grace == 0u); +} + +void gateDriverNfaultGraceTick(void) +{ + if (gd_nfault_grace) { + gd_nfault_grace--; + } } void gateDriverSleep(void) { + if (!gate_driver_awake) { + return; + } +# if defined(USE_DRV_ENABLE) + /* Leave bridge inputs inactive while ENABLE is low (internal gate PDs). */ + allOff(); + SET_DUTY_CYCLE_ALL(0); +# endif GD_PORT->BRR = GD_PIN; gate_driver_awake = 0; + gd_nfault_grace = 0; } void gateDriverFaultResetPulse(void) { + /* DRV8350H ENABLE pulse only; nSLEEP boards do not need t_RST. */ # if defined(USE_DRV_ENABLE) + /* PWM inactive for the whole reset+wake; wake re-does force-low + t_settle. */ + allOff(); + SET_DUTY_CYCLE_ALL(0); GD_PORT->BRR = GD_PIN; delayMicros(GD_FAULT_RST_US); gate_driver_awake = 0; -# endif gateDriverWakeBlocking(); +# endif } void gateDriverPoll(void) diff --git a/Src/main.c b/Src/main.c index 04c6629e3..679ad2988 100644 --- a/Src/main.c +++ b/Src/main.c @@ -237,6 +237,7 @@ an settings option) #include "runtime_loop.h" #include "motor_runtime.h" #include "esc_state.h" +#include "debug_uart.h" /* esc_state is also snapshotted by HWCI_PERF_MAIN_LOOP (needs esc_state.h). */ /* Control path modules + motor_runtime state + esc_state machine. */ @@ -489,6 +490,7 @@ int main(void) runtimeUpdateVariablePwm(&last_tim1_arr); faultPollSignalTimeout(); + faultPollGateDriver(); #ifdef USE_CUSTOM_LED if ((input >= DSHOT_CMD_MAX) && (input < 1947)) { if (ledcounter > (2000 >> forward)) { @@ -520,6 +522,7 @@ int main(void) runtimeSendTelemetryIfNeeded(); runtimeProcessAdcAndProtections(); runtimeMotorModeTick(); + debugUartService(); #ifdef BRUSHED_MODE runBrushedLoop(); #endif diff --git a/Src/motor_runtime.c b/Src/motor_runtime.c index 1affef92b..8cc7a1159 100644 --- a/Src/motor_runtime.c +++ b/Src/motor_runtime.c @@ -139,6 +139,16 @@ char use_speed_control_loop = 0; int32_t input_override = 0; int16_t use_current_limit_adjust = 2000; char use_current_limit = 0; +/* Protection ceilings, both 0..2000 duty units, both min-combined onto + * duty_cycle_setpoint in setInput(). thermal_duty_ceiling is written at + * 1 kHz by runtimeThermalLimitTick(); duty_limit_ceiling is whichever of + * the two was actually binding on the last setInput() pass, published so + * a bench/SITL run can tell a derating ESC from a weak motor. */ +volatile uint16_t thermal_duty_ceiling = 2000; +volatile uint16_t duty_limit_ceiling = 2000; +volatile int16_t degrees_celsius_filtered = 0; +/* Foldback ramp width in C, sanitized from eeprom in settings.c. */ +uint8_t temp_derate_band_c = THERMAL_DERATE_BAND_DEFAULT; int32_t stall_protection_adjust = 0; uint32_t MCU_Id = 0; uint32_t REV_Id = 0; diff --git a/Src/runtime_loop.c b/Src/runtime_loop.c index 16d80fc0d..c40531106 100644 --- a/Src/runtime_loop.c +++ b/Src/runtime_loop.c @@ -10,6 +10,7 @@ #include "faults.h" #include "esc_state.h" #include "gate_driver.h" +#include "debug_uart.h" #include "control_loop.h" #include "commutation.h" #include "bemf_zc.h" @@ -34,15 +35,75 @@ # include "serial_telemetry.h" #endif -/* Zero-cross filter levels (were file-local defines in main.c) */ -#ifdef MCU_F051 -# define ZC_FILTER_MAX 42 -# define ZC_FILTER_RUN_MIN 10 -# define ZC_FILTER_FAST 7 +/* Zero-cross filter levels (were file-local defines in main.c). + * + * F051 (48 MHz): 42/10/7 — proven on ARK 4IN1 free-run and prop benches. + * + * G431 (160 MHz / ARK 12S CAN): keep the same RUN_MIN/FAST as F051 (A/B + * 2026-08: raising to 14/10 hurt 15–20% free-run jitter). Slightly higher + * MAX only for the startup/slow tier (zero_crosses < 100, CI > 500) where + * the G4 dual-COMP path is noisier than F051's single muxed COMP. + * Turn-on grid hump band is G431-specific in bemf_zc.c (mult 24). + */ +#if defined(MCU_G431) +/* + * Confirm-window lengths (samples). Free-run pack 48/12/9 (2026-08, then + * post-blanking re-check 2026-08-07 on AOS 2207 1980 KV no-prop, optical RPM): + * - MAX 48: dual-COMP acquisition is noisier than F051's single muxed COMP + * - RUN/FAST 12/9: one step above F051 for 160 MHz wall-clock + mid-eRPM + * hump (w/ zc_hump_mult 24 in bemf_zc.c) + * + * Post-blanking noprop_zc_map A/B (single-run each, blanking on): + * pack mid(t30-70) t50 hump blind_zc notes + * 48/12/9 4.61% 6.28% 334 incumbent + * 42/10/7 5.00% 6.39% 302 worse mid (F051-scale) + * 48/13/9 4.64% 6.08% 333 noise / reshuffle + * 48/14/10 4.63% 6.08% 327 reshuffles band, no net win + * 48/16/12 4.55% 6.05% 347 tiny mid win, more blind + * 56/12/9 4.58% 6.18% 275 jitter flat; fewer blind on + * staircase (MAX helps ramps) + * Startup matrix (noprop_startup_matrix_hiI, same article, complete 21 starts): + * 48/12/9 0 fail, mean ttr 21 ms, max 90 ms, open% ~1.1–1.5 at 6–12% + * 56/12/9 0 fail, mean ttr 37 ms, max 213 ms, open% ~1.7–2.1 at 6–12% + * So the map's blind-step win for MAX=56 does not become better acquisition + * residency or time-to-run — if anything 56 is slightly worse at low tiers. + * No pack beats 48/12/9. Raising RUN_MIN reshapes the hump; MAX only moves + * the acq/ramp tier. Leave 48/12/9 and spend the next knobs on time-domain + * reject (ZC_SEARCH_BLANK_64THS) / hump compensator, not more confirm counts. + * + * At the free-run jitter hump (t40–t60, CI ~100–150) the live level sits + * near RUN_MIN; FAST only arms when CI < 50 (above this article's free-run + * ceiling at 48 kHz PWM). Override via -DZC_FILTER_MAX=N etc. for A/B. + */ +# ifndef ZC_FILTER_MAX +# define ZC_FILTER_MAX 48 +# endif +# ifndef ZC_FILTER_RUN_MIN +# define ZC_FILTER_RUN_MIN 12 +# endif +# ifndef ZC_FILTER_FAST +# define ZC_FILTER_FAST 9 +# endif +#elif defined(MCU_F051) +# ifndef ZC_FILTER_MAX +# define ZC_FILTER_MAX 42 +# endif +# ifndef ZC_FILTER_RUN_MIN +# define ZC_FILTER_RUN_MIN 10 +# endif +# ifndef ZC_FILTER_FAST +# define ZC_FILTER_FAST 7 +# endif #else -# define ZC_FILTER_MAX 12 -# define ZC_FILTER_RUN_MIN 3 -# define ZC_FILTER_FAST 2 +# ifndef ZC_FILTER_MAX +# define ZC_FILTER_MAX 12 +# endif +# ifndef ZC_FILTER_RUN_MIN +# define ZC_FILTER_RUN_MIN 3 +# endif +# ifndef ZC_FILTER_FAST +# define ZC_FILTER_FAST 2 +# endif #endif /* @@ -130,6 +191,12 @@ void runtimeProcessDesyncCheck(void) if (zero_crosses <= 10) { slow_avg_revs = 0; } + /* Latch "this arm cycle reached an established loop" while the count is + * still intact. Runs at 10 kHz, so it always samples the peak before a + * desync/stall reset can clear it - see fault_run_established. */ + if (zero_crosses > 100) { + fault_run_established = 1; + } if (desync_check && zero_crosses > 10) { uint8_t desynced = (getAbsDif(last_average_interval, average_interval) > average_interval >> 1) && (average_interval < 2000); // throttle resitricted before zc 20. @@ -164,6 +231,7 @@ void runtimeProcessDesyncCheck(void) if (desynced) { slow_avg_revs = 0; const uint32_t zc_at_desync = zero_crosses; + const uint8_t commanded_stop = (input <= DSHOT_CMD_MAX); // Freeze the throttle ceiling briefly: k_erpm is about to // collapse because the ESTIMATE died, not because the rotor // did, and re-deriving the ceiling from the collapsed @@ -183,34 +251,54 @@ void runtimeProcessDesyncCheck(void) } zero_crosses = 0; bemfZcResetTrend(); - desync_happened++; - // Same established-run gate as the stall rail (see - // faultHandleBemfIntervalStall): interval jumps while the - // loop is still acquiring (zc 11..100) are normal startup - // roughness on light motors - charging them stacks holdoff - // onto honest starts until the bucket - // latches a motor that never got going (SITL racer model - // reproduces this under plain dshot spool). Legacy desync - // handling below still restarts; only the episode - // accounting is established-runs-only. +#ifdef USE_DEBUG_UART + /* Single UART line (LogEvent would print "fault: desync" twice). */ + debugUartPrintf("fault: desync zc=%lu e_com=%lu input=%u duty=%u\r\n", (unsigned long)zc_at_desync, + (unsigned long)average_interval, (unsigned)input, (unsigned)duty_cycle); +#endif + // Established-run gate (zc > 100): same as stall rail. Interval + // jumps while still acquiring are normal startup roughness — + // charge early desyncs softly (faultNoteEarlyDesync), and do + // not full-stop (running=0). Established desyncs charge the + // episode bucket and may stop the motor. + // REPORTING (esc.Status.error_count / NodeStatus WARNING) is + // gated on the arm-cycle latch, not on zc_at_desync: this path + // zeroes zero_crosses itself, so an established run that + // desyncs comes back through here with a rebuilt count and + // would otherwise be misfiled as an acquisition kick and never + // reported. A start that never got going never sets the latch. + // + // Commanded stop (input < 48, PX4 disarm / zero throttle): BEMF + // dies while running is still 1, so interval jumps look like a + // desync. The stall rail already skips those; counting them + // here left error_count/WARN set on every arm/disarm. + if (fault_run_established && !commanded_stop) { + desync_happened++; + } + // ESCALATION is deliberately NOT on the latch: it must ask "is + // THIS event an established-run failure, or acquisition thrash?" + // After a stall the loop drops back to acquisition and kicks + // repeatedly; charging those (latch still set for the arm cycle) + // fills the bucket in a few events and latches the ESC during + // the recovery it should be riding out. Live regime test only. if (zc_at_desync > 100) { - faultDesyncEpisodeCharge(DESYNC_EPISODE_JUMP); - } else { - // Acquisition-regime desyncs are not charged directly - // (that is what the gate above exists to prevent), but - // they must not be free either: without this the loop - // can desync forever below zc 100 with every rail and - // counter reading zero. See faultNoteEarlyDesync. + if (!commanded_stop) { + faultDesyncEpisodeCharge(DESYNC_EPISODE_JUMP); + } + if ((!eepromBuffer.bi_direction && (input > DSHOT_CMD_MAX)) || commutation_interval > 1000) { + running = 0; + } +#ifndef MCU_F051 + /* F051: skip — historical post-clear reseed was dead + * (zero_crosses already 0) and LTO dropped it; restoring + * live reseed overflows the 99.8% flash gate. */ + average_interval = 5000; +#endif + } else if (!commanded_stop) { faultNoteEarlyDesync(); } - if ((!eepromBuffer.bi_direction && (input > DSHOT_CMD_MAX)) || commutation_interval > 1000) { - running = 0; - } /* Always fall back to poll-ZC path after a desync event. */ escNoteStallOrDesync(0); - if (zero_crosses > 100) { - average_interval = 5000; - } last_duty_cycle = min_startup_duty / 2; if (faultDesyncRestartHoldoffActive() || escIsFault()) { running = 0; @@ -304,6 +392,85 @@ void runtimeSendTelemetryIfNeeded(void) } } +/* + * Thermal duty limiter, 1 kHz (main-loop context, fresh degrees_celsius). + * + * A CONTINUOUS derate, not a threshold. Upstream mapped + * [limit-10, limit+10] onto [throttle_max_at_high_rpm/2, 1] but only + * entered that map at temp > limit, so the first degree over the limit + * stepped the ceiling from full authority to ~22% and one degree back + * restored it in full: a bang-bang whose amplitude is the entire derate + * range, cycling on the board's thermal time constant. It also + * OVERWROTE duty_cycle_maximum, discarding the low-rpm map and the + * post-desync ceiling hold (dcm_hold, PR #62) in the process. + * + * Here the derate starts AT the limit and falls linearly to + * THERMAL_CEIL_FLOOR over temp_derate_band_c degrees, and it lands in its own + * ceiling that setInput() min-combines with every other one. A + * proportional derate settles where a threshold cannot: shedding duty + * sheds heat, so the ESC finds an equilibrium inside the band instead of + * oscillating across its edge. + * + * The input is filtered because it is not smooth. degrees_celsius is a + * whole-degree integer from a single unaveraged conversion of the MCU die + * sensor (every ARK target - no board NTC), so it dithers +-1 C and a + * derate driven straight off it would step by 1/20th of the band at the + * dither rate. The Q12 IIR (tau ~64 ms at 1 kHz) removes the dither and + * gives the derate the sub-degree resolution the integer input lacks. + * Q12 rather than Q8 so the >> in the filter never rounds a small delta + * to zero: at Q8 a positive delta under 64 (0.25 C) stalls the filter and + * biases it permanently cold, which is the direction that under-protects. + * + * The floor is authority, not protection: BAND_C over the limit the ESC + * still allows 10% duty rather than the 0.05% upstream allowed, because a + * multirotor that loses a motor outright is worse off than one flying on + * a derated motor, and the gate driver's own thermal shutdown is the real + * backstop below this. Placed correctly the floor is unreachable anyway - + * equilibrium lands inside the band. + * + * Nothing here is gated on limits.temperature being in range: settings.c + * coerces anything outside 70..140 to 255, which no die reading reaches, + * so a disabled limit falls out of the arithmetic as a constant 2000. + */ +#define THERMAL_CEIL_FLOOR 200 /* 10% of full scale; see above */ +#define THERMAL_FILT_Q 12 +#define THERMAL_FILT_SHIFT 6 /* tau ~64 ms at 1 kHz */ + +void runtimeThermalLimitTick(void) +{ + static int32_t temp_q; + static uint8_t temp_seeded; + + const int32_t sample_q = (int32_t)degrees_celsius << THERMAL_FILT_Q; + if (!temp_seeded) { + /* Seed on the first sample: ramping up from 0 C would read cold + * (i.e. unprotected) for the filter's settling time at boot. */ + temp_q = sample_q; + temp_seeded = 1; + } else { + temp_q += (sample_q - temp_q) >> THERMAL_FILT_SHIFT; + } + /* ROUND, do not truncate. The IIR stalls once the remaining delta is + * under one shift step (1/64 C here), so temp_q settles just short of + * the input - and truncating 99.98 to 99 would report a whole degree + * cold forever. The residue is far too small to matter to the derate + * (which uses full-resolution temp_q below), but the published degree + * is what a bench or a test compares against. */ + degrees_celsius_filtered = (int16_t)((temp_q + (1 << (THERMAL_FILT_Q - 1))) >> THERMAL_FILT_Q); + + const int32_t over_q = temp_q - ((int32_t)eepromBuffer.limits.temperature << THERMAL_FILT_Q); + if (over_q <= 0) { + thermal_duty_ceiling = 2000; + return; + } + const int32_t band_q = (int32_t)temp_derate_band_c << THERMAL_FILT_Q; + if (over_q >= band_q) { + thermal_duty_ceiling = THERMAL_CEIL_FLOOR; + return; + } + thermal_duty_ceiling = (uint16_t)(2000 - (((2000 - THERMAL_CEIL_FLOOR) * over_q) / band_q)); +} + /* * Transient governor, 1 kHz (main-loop context, fresh battery_voltage). * @@ -535,6 +702,9 @@ void runtimeProcessAdcAndProtections(void) { if (PROCESS_ADC_FLAG == 1) { // for adc and telemetry set adc counter at 1khz loop rate adcAppServiceConversion(); + /* Fresh degrees_celsius: the derate filter needs a fixed rate, and + * this 1 kHz block is the only one the ADC actually updates at. */ + runtimeThermalLimitTick(); #ifndef BRUSHED_MODE runtimeTransientGovernorTick(); #endif @@ -560,6 +730,7 @@ void runtimeProcessAdcAndProtections(void) allOff(); maskPhaseInterrupts(); zero_input_count = 0; + debugUartLogEvent(DBG_EVT_LVC); escToFaultLvc(); } @@ -660,10 +831,10 @@ void runtimeMotorModeTick(void) duty_cycle_maximum = 2000; } - if (degrees_celsius > eepromBuffer.limits.temperature) { - duty_cycle_maximum = map(degrees_celsius, eepromBuffer.limits.temperature - 10, - eepromBuffer.limits.temperature + 10, throttle_max_at_high_rpm / 2, 1); - } + /* The thermal derate used to overwrite duty_cycle_maximum here. + * It is now its own ceiling (runtimeThermalLimitTick, 1 kHz) that + * setInput() min-combines, so duty_cycle_maximum stays what its + * name says - the rpm-based ceiling plus the post-desync hold. */ if (zero_crosses < 100 && commutation_interval > 500) { filter_level = ZC_FILTER_MAX; } else { diff --git a/Src/settings.c b/Src/settings.c index d5494f047..1cbca8281 100644 --- a/Src/settings.c +++ b/Src/settings.c @@ -118,9 +118,53 @@ void loadEEpromSettings(void) startup_max_duty_cycle = startup_max_duty_cycle + dead_time_override; setPwmDeadTime(dead_time_override); } - if (eepromBuffer.limits.temperature < 70 || eepromBuffer.limits.temperature > 140) { - eepromBuffer.limits.temperature = 255; + /* Valid window for the thermal ceiling. Outside it resolves to the + * target's shipped limit (see the fail-safe note below); only the + * explicit 255 sentinel means "derate disabled". + * + * The lower bound is a build-time override ONLY + * so a bench can exercise the derate at a temperature the board + * actually reaches: the derate starts AT limits.temperature and + * hits THERMAL_CEIL_FLOOR at +temp_derate_band_c, so verifying it + * against the shipped 70 C floor needs the die driven past 70, + * which a propped ESC in its own slipstream may never do (a 5-inch + * 6S attempt plateaued at 57 C). Do not lower this in a shipping + * build - 70 C is the floor the product's protection envelope + * assumes, and check-erase-defaults.py gates the shipped pair. + * + * THERMAL_LIMIT_MIN_C lives in targets.h because this is NOT the + * only floor: the DroneCAN parameter table re-validates this same + * byte at boot against TEMPERATURE_LIMIT's own min and substitutes + * its default, so lowering only this one gets silently overridden + * (observed: a 30 C write came back as 105). Both now share the + * macro; keep it that way. + */ + if (eepromBuffer.limits.temperature != THERMAL_LIMIT_DISABLED && + (eepromBuffer.limits.temperature < THERMAL_LIMIT_MIN_C || eepromBuffer.limits.temperature > THERMAL_LIMIT_MAX_C)) { + /* FAIL SAFE, not fail open. This used to coerce to 255 + * (= derate disabled), which also defeated the DroneCAN + * parameter layer's own safety net: load_settings() resets + * an out-of-range value to TEMPERATURE_LIMIT's default, but + * 255 is INSIDE its accepted 70..255 window, so the 255 + * written here always survived and the ESC came up with no + * thermal protection at all. A garbled or mis-set byte now + * lands on the target's shipped limit instead. 255 itself + * still means "deliberately disabled" and is preserved. + * + * Non-DroneCAN targets are unaffected: their + * TARGET_DEFAULT_TEMPERATURE_LIMIT is 255, so out-of-range + * still resolves to disabled exactly as before. */ + eepromBuffer.limits.temperature = TARGET_DEFAULT_TEMPERATURE_LIMIT; + } + + /* Foldback ramp width. 0xFF on any page that never wrote the CAN + * block (every non-DroneCAN product) lands here, so the coercion + * is the normal path, not an error case. */ + if (eepromBuffer.can.temp_derate_band < THERMAL_DERATE_BAND_MIN || + eepromBuffer.can.temp_derate_band > THERMAL_DERATE_BAND_MAX) { + eepromBuffer.can.temp_derate_band = THERMAL_DERATE_BAND_DEFAULT; } + temp_derate_band_c = eepromBuffer.can.temp_derate_band; if (eepromBuffer.limits.current > 0 && eepromBuffer.limits.current <= 100) { use_current_limit = 1; @@ -138,6 +182,8 @@ void loadEEpromSettings(void) if (eepromBuffer.input_type < 10) { switch (eepromBuffer.input_type) { case AUTO_IN: + /* Detect first wire protocol (DShot/PWM). DroneCAN still + * runs; while RawCommand is live it wins (DroneCAN_active). */ dshot = 0; servoPwm = 0; EDT_ARMED = 1; @@ -156,6 +202,12 @@ void loadEEpromSettings(void) EDT_ARMED = 0; dshot = 1; break; + case DRONECAN_IN: + /* Wire capture left idle; DroneCAN_Init disables its IRQs. */ + dshot = 0; + servoPwm = 0; + EDT_ARMED = 1; + break; }; } else { dshot = 0; @@ -178,8 +230,28 @@ void loadEEpromSettings(void) max_ramp_low_rpm = RAMP_SPEED_LOW_RPM; max_ramp_high_rpm = RAMP_SPEED_HIGH_RPM; if (eepromBuffer.max_ramp < 10) { + /* + * Fine mode: one step every 10th 20 kHz tick, so a step of N + * is N counts per 500 us = 0.1*N %/ms - a tenth of what the + * same number means in coarse mode. + * + * STARTUP KEEPS ITS COARSE RATE. It used to take the eeprom + * value like the other two regimes, which silently handed the + * spool-up ramp to a cruise setting: ARK_G431_CAN's 0.5 %/ms + * left the racer plant unable to start at all in SITL + * (test_acq_desync_rail: "motor never entered running", 12 s, + * deterministic - clean at the old 16 %/ms default). That + * contradicts the schedule's own documented intent, which is + * that RAMP_SPEED_STARTUP governs spool-up reliability and is + * deliberately NOT a vehicle-tuning knob (see targets.h). + * + * x10 converts the coarse ceiling into fine-cadence units, so + * startup slews at exactly the rate it would in coarse mode + * while low/high rpm honour the requested value. Nothing here + * lets the eeprom RAISE a regime past its targets.h ceiling. + */ ramp_divider = 9; - max_ramp_startup = eepromBuffer.max_ramp; + max_ramp_startup = RAMP_SPEED_STARTUP * 10; max_ramp_low_rpm = eepromBuffer.max_ramp; max_ramp_high_rpm = eepromBuffer.max_ramp; } else { @@ -271,6 +343,38 @@ void loadEEpromSettings(void) } } +/* + * Re-apply tables that depend on motor_kv / motor_poles after a DroneCAN (or + * other) live change. Does not re-read flash. motor_kv is the live decoded + * value (e.g. 360); eepromBuffer.motor_kv may be the quantised byte form. + */ +void applyMotorIdentitySettings(void) +{ + if (motor_kv < 300) { + low_rpm_throttle_limit = 0; + } else { + low_rpm_throttle_limit = 1; + } + if (eepromBuffer.motor_poles >= 2 && eepromBuffer.motor_poles <= 64) { + low_rpm_level = ((uint32_t)motor_kv * eepromBuffer.motor_poles) / (100U * 32U); + high_rpm_level = ((uint32_t)motor_kv * eepromBuffer.motor_poles) / (17U * 32U); + } else { + low_rpm_level = 0; + high_rpm_level = 0; + } + advance_erpm_scale_q12 = 0; + if (motor_kv >= 300 && eepromBuffer.motor_poles >= 2 && eepromBuffer.motor_poles <= 64) { + uint16_t scale = (uint16_t)(((uint32_t)motor_kv * eepromBuffer.motor_poles * 4096u) / 200000u); + advance_erpm_scale_q12 = (uint16_t)(scale - (scale >> 4)); /* * 15/16 */ + } + reverse_speed_threshold = map(motor_kv, 300, 3000, 1000, 500); + if (eepromBuffer.bi_direction) { + polling_mode_changeover = POLLING_MODE_THRESHOLD / 2; + } else { + polling_mode_changeover = POLLING_MODE_THRESHOLD; + } +} + void saveEEpromSettings(void) { save_flash_nolib(eepromBuffer.buffer, sizeof(eepromBuffer.buffer), eeprom_address); diff --git a/Src/signal.c b/Src/signal.c index c7f90ce72..1bce86f16 100644 --- a/Src/signal.c +++ b/Src/signal.c @@ -13,6 +13,9 @@ #include "serial_telemetry.h" #include "sounds.h" #include "targets.h" +#if DRONECAN_SUPPORT +# include "DroneCAN/DroneCAN.h" +#endif int max_servo_deviation = 250; int servorawinput; uint16_t smallestnumber = 20000; @@ -90,6 +93,12 @@ void computeServoInput() zero_input_count = 0; // reset if out of range } +#if DRONECAN_SUPPORT + /* AUTO dual-path: live RawCommand owns throttle; still refreshed signaltimeout above. */ + if (DroneCAN_active()) { + return; + } +#endif if (servorawinput - newinput > max_servo_deviation) { newinput += max_servo_deviation; } else if (newinput - servorawinput > max_servo_deviation) { diff --git a/bootloader/Inc/targets.h b/bootloader/Inc/targets.h new file mode 100644 index 000000000..90dcd889a --- /dev/null +++ b/bootloader/Inc/targets.h @@ -0,0 +1,46 @@ +/* + * Per-board bootloader configuration for the in-tree G431 CAN bootloader. + * + * Only the ARK 12S CAN ESC is built from this tree. Force-included into every + * translation unit via -include, with -DARK_G431_CAN from the Makefile. + */ + +#define GPIO_PORT_PIN(portnum, pinnum) ((portnum) << 8 | (pinnum)) + +#ifdef ARK_G431_CAN +# define FILE_NAME "ARK_G431_CAN" +# define TARGET_TAG ARKG4 +# define USE_PB4 +/* Must match ARK32 Inc/targets.h. PX4 board_id = (major << 8) | minor = 71. */ +# define DRONECAN_HW_VERSION_MAJOR 0 +# define DRONECAN_HW_VERSION_MINOR 71 + +/* FDCAN1: RX PA11, TX PB9 (AF9) */ +# define CAN_RX_PORT GPIOA +# define CAN_RX_PIN LL_GPIO_PIN_11 +# define CAN_TX_PORT GPIOB +# define CAN_TX_PIN LL_GPIO_PIN_9 + +# define USE_RGB_LED +# define RED_PORT GPIOC +# define RED_PIN LL_GPIO_PIN_6 +# define GREEN_PORT GPIOC +# define GREEN_PIN LL_GPIO_PIN_7 +# define BLUE_PORT GPIOC +# define BLUE_PIN LL_GPIO_PIN_8 + +/* DRV8350H ENABLE (PC9): hold low so the gate driver stays in sleep in BL */ +# define GATE_DRIVER_OFF_PORT GPIOC +# define GATE_DRIVER_OFF_PIN LL_GPIO_PIN_9 + +# define CAN_TERM_PIN GPIO_PORT_PIN(2, 12) /* PC12 */ +# define CAN_TERM_POLARITY 1 + +# define USE_HSE +# undef HSE_VALUE +# define HSE_VALUE 8000000 +# define USE_HSE_BYPASS 0 + +/* G491 has 112KB RAM; allow app stack pointer above the 64KB default */ +# define RAM_LIMIT_KB 112 +#endif diff --git a/bootloader/Inc/version.h b/bootloader/Inc/version.h new file mode 100644 index 000000000..a87822528 --- /dev/null +++ b/bootloader/Inc/version.h @@ -0,0 +1,5 @@ +/* + update this file for new releases + */ + +#define BOOTLOADER_VERSION 18 diff --git a/bootloader/LICENSE b/bootloader/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/bootloader/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 000000000..2821b18b9 --- /dev/null +++ b/bootloader/Makefile @@ -0,0 +1,132 @@ +# In-tree G431 CAN bootloader for the ARK 12S CAN ESC (ARK_G431_CAN). +# Built as AM32_G431_BOOTLOADER_ARKG4_CAN_V18 and installed into ../Bootloaders/. +# +# HAL/CMSIS come from the app tree (../Mcu/g431/Drivers). Local Mcu/g431 holds +# only bootloader-specific files (blutil, eeprom, startup, fault stubs). + +QUIET ?= @ + +ROOT := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))) +REPO := $(ROOT)/.. + +include $(REPO)/make/tools.mk +# tools.mk paths are repo-relative; this Makefile is invoked from bootloader/. +ifeq ($(filter /%,$(ARM_SDK_PREFIX)),) +ARM_SDK_PREFIX := $(REPO)/$(ARM_SDK_PREFIX) +endif + +IDENTIFIER := AM32 +MCU := G431 +PART := STM32G431xx +BOARD := ARK_G431_CAN +PIN := PB4 +TAG := ARKG4 +BOOTLOADER_VERSION := $(shell $(FGREP) "define BOOTLOADER_VERSION" $(ROOT)/Inc/version.h | $(CUT) -d" " -f3) + +CC := $(ARM_SDK_PREFIX)gcc +OBJCOPY := $(ARM_SDK_PREFIX)objcopy + +HAL := $(REPO)/Mcu/g431 +BL_MCU := $(ROOT)/Mcu/g431 + +OBJ := $(ROOT)/obj +BASENAME := $(IDENTIFIER)_$(MCU)_BOOTLOADER_$(TAG)_CAN_V$(BOOTLOADER_VERSION) +ELF := $(OBJ)/$(BASENAME).elf +BIN := $(OBJ)/$(BASENAME).bin +HEX := $(OBJ)/$(BASENAME).hex +MAP := $(OBJ)/$(BASENAME).map +DEP := $(OBJ)/$(BASENAME).d +INSTALL_BIN := $(REPO)/Bootloaders/$(BASENAME).bin + +SRC_HAL := \ + $(HAL)/Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_ll_gpio.c \ + $(HAL)/Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_ll_rcc.c \ + $(HAL)/Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_ll_utils.c \ + $(HAL)/Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_ll_pwr.c \ + $(HAL)/Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_ll_tim.c \ + $(HAL)/Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_ll_exti.c +SRC_MCU := $(wildcard $(BL_MCU)/Src/*.c) $(wildcard $(BL_MCU)/Startup/*.s) +SRC_BL := $(wildcard $(ROOT)/bootloader/*.[cs]) +DSDL_SRC := $(addprefix $(ROOT)/bootloader/DroneCAN/dsdl_generated/src/, \ + uavcan.equipment.esc.RawCommand.c \ + uavcan.protocol.debug.LogLevel.c \ + uavcan.protocol.debug.LogMessage.c \ + uavcan.protocol.dynamic_node_id.Allocation.c \ + uavcan.protocol.file.BeginFirmwareUpdate_req.c \ + uavcan.protocol.file.BeginFirmwareUpdate_res.c \ + uavcan.protocol.file.Error.c \ + uavcan.protocol.file.Path.c \ + uavcan.protocol.file.Read_req.c \ + uavcan.protocol.file.Read_res.c \ + uavcan.protocol.GetNodeInfo_req.c \ + uavcan.protocol.GetNodeInfo_res.c \ + uavcan.protocol.HardwareVersion.c \ + uavcan.protocol.NodeStatus.c \ + uavcan.protocol.param.Empty.c \ + uavcan.protocol.param.ExecuteOpcode_req.c \ + uavcan.protocol.param.ExecuteOpcode_res.c \ + uavcan.protocol.param.GetSet_req.c \ + uavcan.protocol.param.GetSet_res.c \ + uavcan.protocol.param.NumericValue.c \ + uavcan.protocol.param.Value.c \ + uavcan.protocol.SoftwareVersion.c) +SRC_CAN := $(wildcard $(ROOT)/bootloader/DroneCAN/*.[cs]) \ + $(DSDL_SRC) \ + $(wildcard $(ROOT)/bootloader/DroneCAN/libcanard/*.c) +SRC := $(SRC_HAL) $(SRC_MCU) $(SRC_BL) $(SRC_CAN) + +CFLAGS := \ + -mcpu=cortex-m4 -mthumb \ + -fsingle-precision-constant -fomit-frame-pointer -ffast-math --specs=nosys.specs \ + -g3 -Os -ffunction-sections -fdata-sections -funsigned-char \ + -flto \ + -Wall -Wextra -Wundef -Werror -Wno-unused-parameter \ + -include $(ROOT)/Inc/targets.h \ + -I$(ROOT)/Inc \ + -I$(BL_MCU)/Inc \ + -I$(HAL)/Drivers/STM32G4xx_HAL_Driver/Inc \ + -I$(HAL)/Drivers/CMSIS/Include \ + -I$(HAL)/Drivers/CMSIS/Device/ST/STM32G4xx/Include \ + -I$(ROOT)/bootloader/DroneCAN \ + -I$(ROOT)/bootloader/DroneCAN/libcanard \ + -I$(ROOT)/bootloader/DroneCAN/dsdl_generated/include \ + -D$(PART) -DMCU_$(MCU) -DAM32_MCU=\"$(MCU)\" \ + -DHSE_VALUE=8000000 \ + -DUSE_FULL_LL_DRIVER -DPREFETCH_ENABLE=1 \ + -DBOOTLOADER -D$(BOARD) \ + -DDRONECAN_SUPPORT=1 -DBOARD_FLASH_SIZE=128 \ + -DCANARD_ENABLE_CANFD=1 + +LDFLAGS := \ + -specs=nano.specs -lnosys \ + -Wl,--gc-sections -Wl,--print-memory-usage -flto \ + -T$(ROOT)/bootloader/ldscript_bl_CAN.ld \ + -Wl,-Map=$(MAP) + +.PHONY: all install clean AM32_G431_BOOTLOADER_ARKG4_CAN + +all: install + +AM32_G431_BOOTLOADER_ARKG4_CAN: install + +$(OBJ): + $(QUIET)$(MKDIR) -p $(OBJ) + +$(ELF): $(SRC) | $(OBJ) + $(QUIET)echo building $(BASENAME) + $(QUIET)$(CC) $(CFLAGS) $(LDFLAGS) -MMD -MP -MF $(DEP) -o $@ $(SRC) + +$(BIN): $(ELF) + $(QUIET)$(OBJCOPY) -O binary $< $@ + $(QUIET)$(OBJCOPY) $< -O ihex $(HEX) + +$(INSTALL_BIN): $(BIN) + $(QUIET)echo installing $(notdir $@) + $(QUIET)$(CP) -f $< $@ + +install: $(INSTALL_BIN) + +clean: + $(QUIET)$(RM) -rf $(OBJ) + +-include $(DEP) diff --git a/bootloader/Mcu/g431/Inc/blutil.h b/bootloader/Mcu/g431/Inc/blutil.h new file mode 100644 index 000000000..fe9458680 --- /dev/null +++ b/bootloader/Mcu/g431/Inc/blutil.h @@ -0,0 +1,309 @@ +/* + MCU specific utility functions for the bootloader + */ +/* + based on https://github.com/AlkaMotors/AM32_Bootloader_F051/blob/main/Core/ + */ +#pragma once + +#include + +/* + 64k ram + */ +#define RAM_BASE 0x20000000 +#define RAM_SIZE 32*1024 + +/* + we have up to 512k of flash, but only use 64k for now + */ +#ifndef BOARD_FLASH_SIZE +#define BOARD_FLASH_SIZE 64 +#endif + +#define GPIO_PIN(n) (1U<<(n)) + +#define GPIO_PULL_NONE LL_GPIO_PULL_NO +#define GPIO_PULL_UP LL_GPIO_PULL_UP +#define GPIO_PULL_DOWN LL_GPIO_PULL_DOWN + +#define GPIO_OUTPUT_PUSH_PULL LL_GPIO_OUTPUT_PUSHPULL + +#ifdef PORT_LETTER +static inline void gpio_mode_set_input(uint32_t pin, uint32_t pull_up_down) +{ + LL_GPIO_SetPinMode(input_port, pin, LL_GPIO_MODE_INPUT); + LL_GPIO_SetPinPull(input_port, pin, pull_up_down); +} + +static inline void gpio_mode_set_output(uint32_t pin, uint32_t output_mode) +{ + LL_GPIO_SetPinMode(input_port, pin, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(input_port, pin, output_mode); +} + +static inline void gpio_set(uint32_t pin) +{ + LL_GPIO_SetOutputPin(input_port, pin); +} + +static inline void gpio_clear(uint32_t pin) +{ + LL_GPIO_ResetOutputPin(input_port, pin); +} + +static inline bool gpio_read(uint32_t pin) +{ + return LL_GPIO_IsInputPinSet(input_port, pin); +} +#endif // PORT_LETTER + +#define BL_TIMER TIM2 + +/* + initialise timer for 1us per tick + */ +static inline void bl_timer_init(void) +{ + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + + /* Peripheral clock enable */ + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); + + TIM_InitStruct.Prescaler = (160-1); // HSI PLL clock is 160Mz, want 1MHz timer + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct.Autoreload = 0xFFFFFFFF; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + LL_TIM_Init(BL_TIMER, &TIM_InitStruct); + LL_TIM_DisableARRPreload(BL_TIMER); + LL_TIM_SetClockSource(BL_TIMER, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_SetTriggerOutput(BL_TIMER, LL_TIM_TRGO_RESET); + LL_TIM_DisableMasterSlaveMode(BL_TIMER); + + LL_TIM_SetCounterMode(BL_TIMER, LL_TIM_COUNTERMODE_UP); + LL_TIM_EnableCounter(BL_TIMER); +} + +/* + disable timer ready for app start + */ +static inline void bl_timer_disable(void) +{ + LL_TIM_DeInit(BL_TIMER); +} + +static inline uint16_t bl_timer_us(void) +{ + return LL_TIM_GetCounter(BL_TIMER); +} + +/* + initialise clocks + */ +static inline void bl_clock_config(void) +{ + LL_FLASH_SetLatency(LL_FLASH_LATENCY_4); + while (LL_FLASH_GetLatency()!= LL_FLASH_LATENCY_4) ; + LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1); + while (LL_PWR_IsActiveFlag_VOS() != 0) ; + +#ifdef USE_HSE + // setup to use HSE +#if defined(USE_HSE_BYPASS) && (USE_HSE_BYPASS == 0) + LL_RCC_HSE_DisableBypass(); // Use crystal mode +#else + LL_RCC_HSE_EnableBypass(); // Default: Use external oscillator +#endif + LL_RCC_HSE_Enable(); + while (LL_RCC_HSE_IsReady() != 1) ; + #define PLL_SRC LL_RCC_PLLSOURCE_HSE +#if HSE_VALUE == 8000000 + #define PLLM_DIV LL_RCC_PLLM_DIV_1 +#elif HSE_VALUE == 16000000 + #define PLLM_DIV LL_RCC_PLLM_DIV_2 +#elif HSE_VALUE == 24000000 + #define PLLM_DIV LL_RCC_PLLM_DIV_3 +#else + #error "Unsupported HSE_VALUE" +#endif +#else // default to HSI (16MHz) + LL_RCC_HSI_Enable(); + while (LL_RCC_HSI_IsReady() != 1) ; + #define PLLM_DIV LL_RCC_PLLM_DIV_2 + #define PLL_SRC LL_RCC_PLLSOURCE_HSI +#endif + + LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1); + LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1); + LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1); + + LL_RCC_PLL_ConfigDomain_SYS(PLL_SRC, PLLM_DIV, 40, LL_RCC_PLLR_DIV_2); + LL_RCC_PLL_EnableDomain_SYS(); + +#if DRONECAN_SUPPORT + LL_RCC_SetFDCANClockSource(LL_RCC_FDCAN_CLKSOURCE_PLL); + LL_RCC_PLL_EnableDomain_48M(); + MODIFY_REG(RCC->PLLCFGR, RCC_PLLCFGR_PLLQ, LL_RCC_PLLQ_DIV_4); + + // also enable the backup domain registers in TAMP for communication + // between bootloader and main firmware + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); + PWR->CR1 |= PWR_CR1_DBP; + LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_NONE); + RCC->BDCR |= RCC_BDCR_RTCEN; +#endif + + LL_RCC_PLL_Enable(); + while (LL_RCC_PLL_IsReady() != 1) ; + + LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL); + while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL) ; +} + +#ifdef PORT_LETTER +static inline void bl_gpio_init(void) +{ + LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; + + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); + + GPIO_InitStruct.Pin = input_pin; + GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT; + GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; + + LL_GPIO_Init(input_port, &GPIO_InitStruct); +} + +/* + Hold the smart gate-driver run pin low (DRV8350 ENABLE, etc.) for the + whole bootloader so the charge pump / gate rails stay in sleep. + */ +static inline void bl_gate_driver_off(void) +{ +#ifdef GATE_DRIVER_OFF_PORT + LL_GPIO_InitTypeDef s = {0}; + /* Port may be C (ENABLE) while bl_gpio_init only clocks A/B. */ + if (GATE_DRIVER_OFF_PORT == GPIOA) { + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); + } else if (GATE_DRIVER_OFF_PORT == GPIOB) { + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); + } else if (GATE_DRIVER_OFF_PORT == GPIOC) { + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC); + } + s.Pin = GATE_DRIVER_OFF_PIN; + s.Mode = LL_GPIO_MODE_OUTPUT; + s.Speed = LL_GPIO_SPEED_FREQ_LOW; + s.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + s.Pull = LL_GPIO_PULL_NO; + LL_GPIO_Init(GATE_DRIVER_OFF_PORT, &s); + LL_GPIO_ResetOutputPin(GATE_DRIVER_OFF_PORT, GATE_DRIVER_OFF_PIN); +#endif +} + +/* + RGB LED support, driven by per-board RED/GREEN/BLUE_PORT/_PIN from + Inc/targets.h (active low, open drain). Pins are LL_GPIO_PIN_x masks. + */ +#ifdef USE_RGB_LED +static inline void bl_led_port_clock(GPIO_TypeDef *port) +{ +#ifdef GPIOA + if (port == GPIOA) { LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); return; } +#endif +#ifdef GPIOB + if (port == GPIOB) { LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); return; } +#endif +#ifdef GPIOC + if (port == GPIOC) { LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC); return; } +#endif +#ifdef GPIOD + if (port == GPIOD) { LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOD); return; } +#endif +#ifdef GPIOF + if (port == GPIOF) { LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOF); return; } +#endif +} + +static inline void bl_led_init(void) +{ + bl_led_port_clock(RED_PORT); + bl_led_port_clock(GREEN_PORT); + bl_led_port_clock(BLUE_PORT); + LL_GPIO_SetPinMode(RED_PORT, RED_PIN, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(RED_PORT, RED_PIN, LL_GPIO_OUTPUT_OPENDRAIN); + LL_GPIO_SetPinMode(GREEN_PORT, GREEN_PIN, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(GREEN_PORT, GREEN_PIN, LL_GPIO_OUTPUT_OPENDRAIN); + LL_GPIO_SetPinMode(BLUE_PORT, BLUE_PIN, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(BLUE_PORT, BLUE_PIN, LL_GPIO_OUTPUT_OPENDRAIN); + // LEDs off (open drain high = hi-Z = off) + LL_GPIO_SetOutputPin(RED_PORT, RED_PIN); + LL_GPIO_SetOutputPin(GREEN_PORT, GREEN_PIN); + LL_GPIO_SetOutputPin(BLUE_PORT, BLUE_PIN); +} + +static inline void bl_led_on(void) +{ + LL_GPIO_ResetOutputPin(RED_PORT, RED_PIN); + LL_GPIO_ResetOutputPin(GREEN_PORT, GREEN_PIN); + LL_GPIO_ResetOutputPin(BLUE_PORT, BLUE_PIN); +} + +static inline void bl_led_off(void) +{ + LL_GPIO_SetOutputPin(RED_PORT, RED_PIN); + LL_GPIO_SetOutputPin(GREEN_PORT, GREEN_PIN); + LL_GPIO_SetOutputPin(BLUE_PORT, BLUE_PIN); +} + +static inline void bl_led_red_on(void) +{ + LL_GPIO_ResetOutputPin(RED_PORT, RED_PIN); + LL_GPIO_SetOutputPin(GREEN_PORT, GREEN_PIN); + LL_GPIO_SetOutputPin(BLUE_PORT, BLUE_PIN); +} +#endif // USE_RGB_LED + +/* + return true if the MCU booted under a software reset + */ +static inline bool bl_was_software_reset(void) +{ + return (RCC->CSR & RCC_CSR_SFTRSTF) != 0; +} + +void Error_Handler() +{ + while (1) {} +} + +/* + jump from the bootloader to the application code + */ +static inline void jump_to_application(void) +{ + __disable_irq(); + bl_timer_disable(); + const uint32_t app_address = MCU_FLASH_START + FIRMWARE_RELATIVE_START; + const uint32_t *app_data = (const uint32_t *)app_address; + const uint32_t stack_top = app_data[0]; + const uint32_t JumpAddress = app_data[1]; + + // setup vector table + SCB->VTOR = app_address; + + // setup sp, msp and jump + asm volatile( + "mov sp, %0 \n" + "msr msp, %0 \n" + "bx %1 \n" + : : "r"(stack_top), "r"(JumpAddress) :); +} + +/* + nothing to do in SystemInit() + */ +void SystemInit() +{ +} +#endif // PORT_LETTER diff --git a/bootloader/Mcu/g431/Inc/eeprom.h b/bootloader/Mcu/g431/Inc/eeprom.h new file mode 100644 index 000000000..ab51b3504 --- /dev/null +++ b/bootloader/Mcu/g431/Inc/eeprom.h @@ -0,0 +1,13 @@ +/* + * bootloader.h + * + * Created on: Mar. 25, 2020 + * Author: Alka + */ +#pragma once + +#include "main.h" +#include + +void read_flash_bin(uint8_t* data, uint32_t add, int out_buff_len); +bool save_flash_nolib(const uint8_t* data, uint32_t length, uint32_t add); diff --git a/bootloader/Mcu/g431/Inc/main.h b/bootloader/Mcu/g431/Inc/main.h new file mode 100644 index 000000000..0b11ac37f --- /dev/null +++ b/bootloader/Mcu/g431/Inc/main.h @@ -0,0 +1,99 @@ +/* USER CODE BEGIN Header */ +/** + ****************************************************************************** + * @file : main.h + * @brief : Header for main.c file. + * This file contains the common defines of the application. + ****************************************************************************** + * @attention + * + * Copyright (c) 2024 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ +/* USER CODE END Header */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __MAIN_H +#define __MAIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include "stm32g4xx_ll_bus.h" +#include "stm32g4xx_ll_cortex.h" +#include "stm32g4xx_ll_crs.h" +#include "stm32g4xx_ll_gpio.h" +#include "stm32g4xx_ll_iwdg.h" +#include "stm32g4xx_ll_pwr.h" +#include "stm32g4xx_ll_rcc.h" +#include "stm32g4xx_ll_system.h" +#include "stm32g4xx_ll_tim.h" +#include "stm32g4xx_ll_utils.h" + +#if defined(USE_FULL_ASSERT) +#include "stm32_assert.h" +#endif /* USE_FULL_ASSERT */ + +/* Private includes ----------------------------------------------------------*/ +/* USER CODE BEGIN Includes */ + +/* USER CODE END Includes */ + +/* Exported types ------------------------------------------------------------*/ +/* USER CODE BEGIN ET */ + +/* USER CODE END ET */ + +/* Exported constants --------------------------------------------------------*/ +/* USER CODE BEGIN EC */ + +/* USER CODE END EC */ + +/* Exported macro ------------------------------------------------------------*/ +/* USER CODE BEGIN EM */ + +/* USER CODE END EM */ + +/* Exported functions prototypes ---------------------------------------------*/ +void Error_Handler(void); + +/* USER CODE BEGIN EFP */ + +/* USER CODE END EFP */ + +/* Private defines -----------------------------------------------------------*/ +#ifndef NVIC_PRIORITYGROUP_0 +#define NVIC_PRIORITYGROUP_0 \ + ((uint32_t)0x00000007) /*!< 0 bit for pre-emption priority, \ + 4 bits for subpriority */ +#define NVIC_PRIORITYGROUP_1 \ + ((uint32_t)0x00000006) /*!< 1 bit for pre-emption priority, \ + 3 bits for subpriority */ +#define NVIC_PRIORITYGROUP_2 \ + ((uint32_t)0x00000005) /*!< 2 bits for pre-emption priority, \ + 2 bits for subpriority */ +#define NVIC_PRIORITYGROUP_3 \ + ((uint32_t)0x00000004) /*!< 3 bits for pre-emption priority, \ + 1 bit for subpriority */ +#define NVIC_PRIORITYGROUP_4 \ + ((uint32_t)0x00000003) /*!< 4 bits for pre-emption priority, \ + 0 bit for subpriority */ +#endif + +/* USER CODE BEGIN Private defines */ + +/* USER CODE END Private defines */ + +#ifdef __cplusplus +} +#endif + +#endif /* __MAIN_H */ diff --git a/bootloader/Mcu/g431/Inc/stm32g4xx_it.h b/bootloader/Mcu/g431/Inc/stm32g4xx_it.h new file mode 100644 index 000000000..7ef739025 --- /dev/null +++ b/bootloader/Mcu/g431/Inc/stm32g4xx_it.h @@ -0,0 +1,66 @@ +/* USER CODE BEGIN Header */ +/** + ****************************************************************************** + * @file stm32g4xx_it.h + * @brief This file contains the headers of the interrupt handlers. + ****************************************************************************** + * @attention + * + * Copyright (c) 2024 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ +/* USER CODE END Header */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32G4xx_IT_H +#define __STM32G4xx_IT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Private includes ----------------------------------------------------------*/ +/* USER CODE BEGIN Includes */ + +/* USER CODE END Includes */ + +/* Exported types ------------------------------------------------------------*/ +/* USER CODE BEGIN ET */ + +/* USER CODE END ET */ + +/* Exported constants --------------------------------------------------------*/ +/* USER CODE BEGIN EC */ + +/* USER CODE END EC */ + +/* Exported macro ------------------------------------------------------------*/ +/* USER CODE BEGIN EM */ + +/* USER CODE END EM */ + +/* Exported functions prototypes ---------------------------------------------*/ +void NMI_Handler(void); +void HardFault_Handler(void); +void MemManage_Handler(void); +void BusFault_Handler(void); +void UsageFault_Handler(void); +void SVC_Handler(void); +void DebugMon_Handler(void); +void PendSV_Handler(void); +void SysTick_Handler(void); +/* USER CODE BEGIN EFP */ + +/* USER CODE END EFP */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32G4xx_IT_H */ diff --git a/bootloader/Mcu/g431/Src/eeprom.c b/bootloader/Mcu/g431/Src/eeprom.c new file mode 100644 index 000000000..a79ec2c50 --- /dev/null +++ b/bootloader/Mcu/g431/Src/eeprom.c @@ -0,0 +1,85 @@ +/* + * bootloader.c + * + * Created on: Mar. 25, 2020 + * Author: Alka + * + */ + +#include "eeprom.h" +#include + +#define page_size 0x800 // 2 kb for l431 in dual-bank mode + +static const uint32_t FLASH_FKEY1 =0x45670123; +static const uint32_t FLASH_FKEY2 =0xCDEF89AB; + +// #pragma GCC optimize("O0") + +bool save_flash_nolib(const uint8_t *data, uint32_t length, uint32_t add) +{ + if ((add & 0x7) != 0 || (length & 0x7)) { + // address and length must be on 8 byte boundary + return false; + } + // we need to flash on 32 bit boundaries + uint32_t data_length = length / 4; + volatile FLASH_TypeDef *flash = FLASH; + + // clear errors + flash->SR |= FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | + FLASH_SR_SIZERR | FLASH_SR_PGSERR | FLASH_SR_MISERR | FLASH_SR_FASTERR | + FLASH_SR_RDERR | FLASH_SR_OPTVERR; + + // unlock flash + while ((flash->SR & FLASH_SR_BSY) != 0) ; + + if ((flash->CR & FLASH_CR_LOCK) != 0) { + flash->KEYR = FLASH_FKEY1; + flash->KEYR = FLASH_FKEY2; + } + + // erase page if address is divisable by page size + if ((add % page_size) == 0) { + flash->CR = FLASH_CR_PER; + flash->CR |= (add/page_size) << 3; + flash->CR |= FLASH_CR_STRT; + while ((flash->SR & FLASH_SR_BSY) != 0) ; + } + + uint32_t index = 0; + volatile uint32_t *fdata = (volatile uint32_t *)add; + + while (index < data_length) { + // flash two words at a time + uint32_t words[2]; + memcpy((void*)&words[0], &data[index*4], sizeof(words)); + + flash->CR = FLASH_CR_PG; + + fdata[index] = words[0]; + fdata[index+1] = words[1]; + + while ((flash->SR & FLASH_SR_BSY) != 0) ; + + flash->SR |= FLASH_SR_EOP; + flash->CR = 0; + index += 2; + } + + // lock flash again + SET_BIT(flash->CR, FLASH_CR_LOCK); + + // ensure data is correct + return memcmp(data, (const void *)add, length) == 0; +} + + + + +void read_flash_bin(uint8_t* data, uint32_t add, int out_buff_len) +{ + memcpy(data, (void*)add, out_buff_len); +} + + diff --git a/bootloader/Mcu/g431/Src/stm32g4xx_it.c b/bootloader/Mcu/g431/Src/stm32g4xx_it.c new file mode 100644 index 000000000..c9f23e077 --- /dev/null +++ b/bootloader/Mcu/g431/Src/stm32g4xx_it.c @@ -0,0 +1,41 @@ + +#include "stm32g4xx_it.h" +#include "main.h" + +void NMI_Handler(void) +{ + while (1) { + } +} + +void HardFault_Handler(void) +{ + while (1) { + } +} + +void MemManage_Handler(void) +{ + while (1) { + } +} + +void BusFault_Handler(void) +{ + while (1) { + } +} + +void UsageFault_Handler(void) +{ + while (1) { + } +} + +void SVC_Handler(void) { } + +void DebugMon_Handler(void) { } + +void PendSV_Handler(void) { } + +void SysTick_Handler(void) { } diff --git a/bootloader/Mcu/g431/Startup/startup_stm32g431xx.s b/bootloader/Mcu/g431/Startup/startup_stm32g431xx.s new file mode 100644 index 000000000..8c5923701 --- /dev/null +++ b/bootloader/Mcu/g431/Startup/startup_stm32g431xx.s @@ -0,0 +1,498 @@ +/** + ****************************************************************************** + * @file startup_stm32g431xx.s + * @author MCD Application Team + * @brief STM32G431xx devices vector table GCC toolchain. + * This module performs: + * - Set the initial SP + * - Set the initial PC == Reset_Handler, + * - Set the vector table entries with the exceptions ISR address, + * - Configure the clock system + * - Branches to main in the C library (which eventually + * calls main()). + * After Reset the Cortex-M4 processor is in Thread mode, + * priority is Privileged, and the Stack is set to Main. + ****************************************************************************** + * @attention + * + * Copyright (c) 2019 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + + .syntax unified + .cpu cortex-m4 + .fpu softvfp + .thumb + +.global g_pfnVectors +.global Default_Handler + +/* start address for the initialization values of the .data section. +defined in linker script */ +.word _sidata +/* start address for the .data section. defined in linker script */ +.word _sdata +/* end address for the .data section. defined in linker script */ +.word _edata +/* start address for the .bss section. defined in linker script */ +.word _sbss +/* end address for the .bss section. defined in linker script */ +.word _ebss + +.equ BootRAM, 0xF1E0F85F +/** + * @brief This is the code that gets called when the processor first + * starts execution following a reset event. Only the absolutely + * necessary set is performed, after which the application + * supplied main() routine is called. + * @param None + * @retval : None +*/ + + .section .text.Reset_Handler + .weak Reset_Handler + .type Reset_Handler, %function +Reset_Handler: + ldr r0, =_estack + mov sp, r0 /* set stack pointer */ + +/* Call the clock system initialization function.*/ + bl SystemInit + +/* Copy the data segment initializers from flash to SRAM */ + ldr r0, =_sdata + ldr r1, =_edata + ldr r2, =_sidata + movs r3, #0 + b LoopCopyDataInit + +CopyDataInit: + ldr r4, [r2, r3] + str r4, [r0, r3] + adds r3, r3, #4 + +LoopCopyDataInit: + adds r4, r0, r3 + cmp r4, r1 + bcc CopyDataInit + +/* Zero fill the bss segment. */ + ldr r2, =_sbss + ldr r4, =_ebss + movs r3, #0 + b LoopFillZerobss + +FillZerobss: + str r3, [r2] + adds r2, r2, #4 + +LoopFillZerobss: + cmp r2, r4 + bcc FillZerobss +/* Call static constructors */ + bl __libc_init_array +/* Call the application's entry point.*/ + bl main + +LoopForever: + b LoopForever + +.size Reset_Handler, .-Reset_Handler + +/** + * @brief This is the code that gets called when the processor receives an + * unexpected interrupt. This simply enters an infinite loop, preserving + * the system state for examination by a debugger. + * + * @param None + * @retval : None +*/ + .section .text.Default_Handler,"ax",%progbits +Default_Handler: +Infinite_Loop: + b Infinite_Loop + .size Default_Handler, .-Default_Handler +/****************************************************************************** +* +* The minimal vector table for a Cortex-M4. Note that the proper constructs +* must be placed on this to ensure that it ends up at physical address +* 0x0000.0000. +* +******************************************************************************/ + .section .isr_vector,"a",%progbits + .type g_pfnVectors, %object + + +g_pfnVectors: + .word _estack + .word Reset_Handler + .word NMI_Handler + .word HardFault_Handler + .word MemManage_Handler + .word BusFault_Handler + .word UsageFault_Handler + .word 0 + .word 0 + .word 0 + .word 0 + .word SVC_Handler + .word DebugMon_Handler + .word 0 + .word PendSV_Handler + .word SysTick_Handler + .word WWDG_IRQHandler + .word PVD_PVM_IRQHandler + .word RTC_TAMP_LSECSS_IRQHandler + .word RTC_WKUP_IRQHandler + .word FLASH_IRQHandler + .word RCC_IRQHandler + .word EXTI0_IRQHandler + .word EXTI1_IRQHandler + .word EXTI2_IRQHandler + .word EXTI3_IRQHandler + .word EXTI4_IRQHandler + .word DMA1_Channel1_IRQHandler + .word DMA1_Channel2_IRQHandler + .word DMA1_Channel3_IRQHandler + .word DMA1_Channel4_IRQHandler + .word DMA1_Channel5_IRQHandler + .word DMA1_Channel6_IRQHandler + .word 0 + .word ADC1_2_IRQHandler + .word USB_HP_IRQHandler + .word USB_LP_IRQHandler + .word FDCAN1_IT0_IRQHandler + .word FDCAN1_IT1_IRQHandler + .word EXTI9_5_IRQHandler + .word TIM1_BRK_TIM15_IRQHandler + .word TIM1_UP_TIM16_IRQHandler + .word TIM1_TRG_COM_TIM17_IRQHandler + .word TIM1_CC_IRQHandler + .word TIM2_IRQHandler + .word TIM3_IRQHandler + .word TIM4_IRQHandler + .word I2C1_EV_IRQHandler + .word I2C1_ER_IRQHandler + .word I2C2_EV_IRQHandler + .word I2C2_ER_IRQHandler + .word SPI1_IRQHandler + .word SPI2_IRQHandler + .word USART1_IRQHandler + .word USART2_IRQHandler + .word USART3_IRQHandler + .word EXTI15_10_IRQHandler + .word RTC_Alarm_IRQHandler + .word USBWakeUp_IRQHandler + .word TIM8_BRK_IRQHandler + .word TIM8_UP_IRQHandler + .word TIM8_TRG_COM_IRQHandler + .word TIM8_CC_IRQHandler + .word 0 + .word 0 + .word LPTIM1_IRQHandler + .word 0 + .word SPI3_IRQHandler + .word UART4_IRQHandler + .word 0 + .word TIM6_DAC_IRQHandler + .word TIM7_IRQHandler + .word DMA2_Channel1_IRQHandler + .word DMA2_Channel2_IRQHandler + .word DMA2_Channel3_IRQHandler + .word DMA2_Channel4_IRQHandler + .word DMA2_Channel5_IRQHandler + .word 0 + .word 0 + .word UCPD1_IRQHandler + .word COMP1_2_3_IRQHandler + .word COMP4_IRQHandler + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word CRS_IRQHandler + .word SAI1_IRQHandler + .word 0 + .word 0 + .word 0 + .word 0 + .word FPU_IRQHandler + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word RNG_IRQHandler + .word LPUART1_IRQHandler + .word I2C3_EV_IRQHandler + .word I2C3_ER_IRQHandler + .word DMAMUX_OVR_IRQHandler + .word 0 + .word 0 + .word DMA2_Channel6_IRQHandler + .word 0 + .word 0 + .word CORDIC_IRQHandler + .word FMAC_IRQHandler + + .size g_pfnVectors, .-g_pfnVectors + +/******************************************************************************* +* +* Provide weak aliases for each Exception handler to the Default_Handler. +* As they are weak aliases, any function with the same name will override +* this definition. +* +*******************************************************************************/ + + .weak NMI_Handler + .thumb_set NMI_Handler,Default_Handler + + .weak HardFault_Handler + .thumb_set HardFault_Handler,Default_Handler + + .weak MemManage_Handler + .thumb_set MemManage_Handler,Default_Handler + + .weak BusFault_Handler + .thumb_set BusFault_Handler,Default_Handler + + .weak UsageFault_Handler + .thumb_set UsageFault_Handler,Default_Handler + + .weak SVC_Handler + .thumb_set SVC_Handler,Default_Handler + + .weak DebugMon_Handler + .thumb_set DebugMon_Handler,Default_Handler + + .weak PendSV_Handler + .thumb_set PendSV_Handler,Default_Handler + + .weak SysTick_Handler + .thumb_set SysTick_Handler,Default_Handler + + .weak WWDG_IRQHandler + .thumb_set WWDG_IRQHandler,Default_Handler + + .weak PVD_PVM_IRQHandler + .thumb_set PVD_PVM_IRQHandler,Default_Handler + + .weak RTC_TAMP_LSECSS_IRQHandler + .thumb_set RTC_TAMP_LSECSS_IRQHandler,Default_Handler + + .weak RTC_WKUP_IRQHandler + .thumb_set RTC_WKUP_IRQHandler,Default_Handler + + .weak FLASH_IRQHandler + .thumb_set FLASH_IRQHandler,Default_Handler + + .weak RCC_IRQHandler + .thumb_set RCC_IRQHandler,Default_Handler + + .weak EXTI0_IRQHandler + .thumb_set EXTI0_IRQHandler,Default_Handler + + .weak EXTI1_IRQHandler + .thumb_set EXTI1_IRQHandler,Default_Handler + + .weak EXTI2_IRQHandler + .thumb_set EXTI2_IRQHandler,Default_Handler + + .weak EXTI3_IRQHandler + .thumb_set EXTI3_IRQHandler,Default_Handler + + .weak EXTI4_IRQHandler + .thumb_set EXTI4_IRQHandler,Default_Handler + + .weak DMA1_Channel1_IRQHandler + .thumb_set DMA1_Channel1_IRQHandler,Default_Handler + + .weak DMA1_Channel2_IRQHandler + .thumb_set DMA1_Channel2_IRQHandler,Default_Handler + + .weak DMA1_Channel3_IRQHandler + .thumb_set DMA1_Channel3_IRQHandler,Default_Handler + + .weak DMA1_Channel4_IRQHandler + .thumb_set DMA1_Channel4_IRQHandler,Default_Handler + + .weak DMA1_Channel5_IRQHandler + .thumb_set DMA1_Channel5_IRQHandler,Default_Handler + + .weak DMA1_Channel6_IRQHandler + .thumb_set DMA1_Channel6_IRQHandler,Default_Handler + + .weak ADC1_2_IRQHandler + .thumb_set ADC1_2_IRQHandler,Default_Handler + + .weak USB_HP_IRQHandler + .thumb_set USB_HP_IRQHandler,Default_Handler + + .weak USB_LP_IRQHandler + .thumb_set USB_LP_IRQHandler,Default_Handler + + .weak FDCAN1_IT0_IRQHandler + .thumb_set FDCAN1_IT0_IRQHandler,Default_Handler + + .weak FDCAN1_IT1_IRQHandler + .thumb_set FDCAN1_IT1_IRQHandler,Default_Handler + + .weak EXTI9_5_IRQHandler + .thumb_set EXTI9_5_IRQHandler,Default_Handler + + .weak TIM1_BRK_TIM15_IRQHandler + .thumb_set TIM1_BRK_TIM15_IRQHandler,Default_Handler + + .weak TIM1_UP_TIM16_IRQHandler + .thumb_set TIM1_UP_TIM16_IRQHandler,Default_Handler + + .weak TIM1_TRG_COM_TIM17_IRQHandler + .thumb_set TIM1_TRG_COM_TIM17_IRQHandler,Default_Handler + + .weak TIM1_CC_IRQHandler + .thumb_set TIM1_CC_IRQHandler,Default_Handler + + .weak TIM2_IRQHandler + .thumb_set TIM2_IRQHandler,Default_Handler + + .weak TIM3_IRQHandler + .thumb_set TIM3_IRQHandler,Default_Handler + + .weak TIM4_IRQHandler + .thumb_set TIM4_IRQHandler,Default_Handler + + .weak I2C1_EV_IRQHandler + .thumb_set I2C1_EV_IRQHandler,Default_Handler + + .weak I2C1_ER_IRQHandler + .thumb_set I2C1_ER_IRQHandler,Default_Handler + + .weak I2C2_EV_IRQHandler + .thumb_set I2C2_EV_IRQHandler,Default_Handler + + .weak I2C2_ER_IRQHandler + .thumb_set I2C2_ER_IRQHandler,Default_Handler + + .weak SPI1_IRQHandler + .thumb_set SPI1_IRQHandler,Default_Handler + + .weak SPI2_IRQHandler + .thumb_set SPI2_IRQHandler,Default_Handler + + .weak USART1_IRQHandler + .thumb_set USART1_IRQHandler,Default_Handler + + .weak USART2_IRQHandler + .thumb_set USART2_IRQHandler,Default_Handler + + .weak USART3_IRQHandler + .thumb_set USART3_IRQHandler,Default_Handler + + .weak EXTI15_10_IRQHandler + .thumb_set EXTI15_10_IRQHandler,Default_Handler + + .weak RTC_Alarm_IRQHandler + .thumb_set RTC_Alarm_IRQHandler,Default_Handler + + .weak USBWakeUp_IRQHandler + .thumb_set USBWakeUp_IRQHandler,Default_Handler + + .weak TIM8_BRK_IRQHandler + .thumb_set TIM8_BRK_IRQHandler,Default_Handler + + .weak TIM8_UP_IRQHandler + .thumb_set TIM8_UP_IRQHandler,Default_Handler + + .weak TIM8_TRG_COM_IRQHandler + .thumb_set TIM8_TRG_COM_IRQHandler,Default_Handler + + .weak TIM8_CC_IRQHandler + .thumb_set TIM8_CC_IRQHandler,Default_Handler + + .weak LPTIM1_IRQHandler + .thumb_set LPTIM1_IRQHandler,Default_Handler + + .weak SPI3_IRQHandler + .thumb_set SPI3_IRQHandler,Default_Handler + + .weak UART4_IRQHandler + .thumb_set UART4_IRQHandler,Default_Handler + + .weak TIM6_DAC_IRQHandler + .thumb_set TIM6_DAC_IRQHandler,Default_Handler + + .weak TIM7_IRQHandler + .thumb_set TIM7_IRQHandler,Default_Handler + + .weak DMA2_Channel1_IRQHandler + .thumb_set DMA2_Channel1_IRQHandler,Default_Handler + + .weak DMA2_Channel2_IRQHandler + .thumb_set DMA2_Channel2_IRQHandler,Default_Handler + + .weak DMA2_Channel3_IRQHandler + .thumb_set DMA2_Channel3_IRQHandler,Default_Handler + + .weak DMA2_Channel4_IRQHandler + .thumb_set DMA2_Channel4_IRQHandler,Default_Handler + + .weak DMA2_Channel5_IRQHandler + .thumb_set DMA2_Channel5_IRQHandler,Default_Handler + + .weak UCPD1_IRQHandler + .thumb_set UCPD1_IRQHandler,Default_Handler + + .weak COMP1_2_3_IRQHandler + .thumb_set COMP1_2_3_IRQHandler,Default_Handler + + .weak COMP4_IRQHandler + .thumb_set COMP4_IRQHandler,Default_Handler + + .weak CRS_IRQHandler + .thumb_set CRS_IRQHandler,Default_Handler + + .weak SAI1_IRQHandler + .thumb_set SAI1_IRQHandler,Default_Handler + + .weak FPU_IRQHandler + .thumb_set FPU_IRQHandler,Default_Handler + + .weak RNG_IRQHandler + .thumb_set RNG_IRQHandler,Default_Handler + + .weak LPUART1_IRQHandler + .thumb_set LPUART1_IRQHandler,Default_Handler + + .weak I2C3_EV_IRQHandler + .thumb_set I2C3_EV_IRQHandler,Default_Handler + + .weak I2C3_ER_IRQHandler + .thumb_set I2C3_ER_IRQHandler,Default_Handler + + .weak DMAMUX_OVR_IRQHandler + .thumb_set DMAMUX_OVR_IRQHandler,Default_Handler + + .weak DMA2_Channel6_IRQHandler + .thumb_set DMA2_Channel6_IRQHandler,Default_Handler + + .weak CORDIC_IRQHandler + .thumb_set CORDIC_IRQHandler,Default_Handler + + .weak FMAC_IRQHandler + .thumb_set FMAC_IRQHandler,Default_Handler + diff --git a/bootloader/Mcu/g431/openocd.cfg b/bootloader/Mcu/g431/openocd.cfg new file mode 100644 index 000000000..6b5611d0b --- /dev/null +++ b/bootloader/Mcu/g431/openocd.cfg @@ -0,0 +1,7 @@ +# config for openocd for STM32G431 MCUs +source [find interface/stlink.cfg] +source [find target/stm32g4x.cfg] + +# use -gdb-max-connections to allow for live watch in vscode +$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 -gdb-max-connections 4 +init diff --git a/bootloader/README.md b/bootloader/README.md new file mode 100644 index 000000000..39d77e3a3 --- /dev/null +++ b/bootloader/README.md @@ -0,0 +1,34 @@ +# ARK 12S CAN bootloader + +In-tree STM32G431 DroneCAN bootloader for the **ARK 12S CAN ESC** (`ARK_G431_CAN`). + +This is the G431 CAN slice of [ARK32-bootloader](https://github.com/ARK-Electronics/ARK32-bootloader) `feat/ark-g431-can-dual-protocol` (`060c417`, UAVCAN hw **0.71** / board_id 71), plus CAN FD. HAL/CMSIS is the app tree at `Mcu/g431/Drivers`. Other AM32 bootloader targets (F051, L431, …) stay in ARK32-bootloader. + +## Build + +From the repo root (uses the same pinned Arm GCC as the app): + +```bash +make bootloader-g431-can +``` + +or `make -C bootloader`. Output: + +- `bootloader/obj/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.{elf,bin,hex}` +- copied to `Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin` for app embed and factory images + +`make ARK_G431_CAN` rebuilds this image when bootloader sources change. + +## Hardware + +| Item | Value | +|---|---| +| Signal pin | PB4 | +| FDCAN | TX PB9 / RX PA11, AF9 | +| Gate driver | DRV8350H ENABLE PC9 held low | +| CAN terminator | PC12, EEPROM byte 183 | +| Region | 16 KiB at `0x08000000`, app at `0x08004000` | + +## CAN FD + +The FDCAN peripheral runs in FD mode (FDOE + BRSE). Nominal (arbitration) bit timing is 1 Mbps. Data-phase bit timing uses the same 80 MHz Kvaser/ArduPilot table as the app (1/2/4/5 Mbps, 70–75% sample point, TDC). EEPROM byte 185 (`CAN_FD_MBPS`) is 0 = auto-match the host data rate, or 1/2/4/5 to pin it. TX FDF/BRS follows the last received host frame. diff --git a/bootloader/bootloader/DroneCAN/DroneCAN.c b/bootloader/bootloader/DroneCAN/DroneCAN.c new file mode 100644 index 000000000..bd6862cdd --- /dev/null +++ b/bootloader/bootloader/DroneCAN/DroneCAN.c @@ -0,0 +1,1454 @@ +/* + * DroneCAN.c - support for DroneCAN protocol for ESC control and telemetry + */ + +//#pragma GCC optimize("O0") + +#if DRONECAN_SUPPORT + +#include +#include +#include +#include +#include +#include "sys_can.h" +#include +#include +#include + +// include the headers for the generated DroneCAN messages from the +// dronecan_dsdlc compiler +#include "dsdl_generated/dronecan_msgs.h" + +#if CANARD_ENABLE_CANFD +static bool dronecan_tx_canfd; + +static void dc_note_rx_frame(const CanardCANFrame *f) +{ + /* Latch CAN FD TX on the first FD frame. Classic DNA must not flip + * bootloader transfers back to classic on an FD bus. */ + if (f->canfd) { + dronecan_tx_canfd = true; + } +} + +#define DC_BROADCAST(...) canardBroadcast(__VA_ARGS__, dronecan_tx_canfd) +#define DC_RESPOND(...) canardRequestOrRespond(__VA_ARGS__, dronecan_tx_canfd) +#else +static void dc_note_rx_frame(const CanardCANFrame *f) +{ + (void)f; +} +#define DC_BROADCAST canardBroadcast +#define DC_RESPOND canardRequestOrRespond +#endif + +#if CANARD_ENABLE_TAO_OPTION +#define DC_ENCODE(fn, pkt, buf) fn((pkt), (buf), !dronecan_tx_canfd) +#else +#define DC_ENCODE(fn, pkt, buf) fn((pkt), (buf)) +#endif + + +#ifndef PREFERRED_NODE_ID +#define PREFERRED_NODE_ID 0 +#endif + +#ifndef CANARD_POOL_SIZE +#define CANARD_POOL_SIZE 4096 +#endif + +#ifndef DRONECAN_DEBUG +#define DRONECAN_DEBUG 0 +#endif + +/* + DRONECAN_PARAM_SUPPORT_ENABLED gates the whole + uavcan.protocol.param.{GetSet,ExecuteOpcode} interface in the + bootloader: the parameter table, the handlers, the dispatch in + onTransferReceived(), and the shouldAcceptTransfer() entries. + Default on; a custom bootloader that needs the ~1 KB of flash for + something else can build with -DDRONECAN_PARAM_SUPPORT_ENABLED=0 + and the DroneCAN GUI will just see an empty parameter list. + */ +#ifndef DRONECAN_PARAM_SUPPORT_ENABLED +#define DRONECAN_PARAM_SUPPORT_ENABLED 1 +#endif + +#ifndef DRONECAN_CHECK_SIGNATURE +#define DRONECAN_CHECK_SIGNATURE 1 +#endif + +// assume that main fw starts at 16k +#define MAIN_FW_START_ADDR 0x08004000 + +static CanardInstance canard; +static uint8_t canard_memory_pool[CANARD_POOL_SIZE]; + +#ifndef MCU_FLASH_START +#define MCU_FLASH_START 0x08000000 +#endif + +#if BOARD_FLASH_SIZE == 128 +#define EEPROM_START_ADD (MCU_FLASH_START+0x1f800) +#else +#error "Only 128K flash size supported for DroneCAN" +#endif + +/* + the EEPROM region we preserve on a single-byte update. This must cover + the highest parameter offset we touch (currently ESC_INDEX at byte 177) + and matches the main firmware's EEPROM_MAX_SIZE so we don't truncate + state the application has stored above the default_settings block. + Must be a multiple of the flash word granularity (8 on L431/G431). + */ +#define EEPROM_PRESERVE_SIZE 1024 + +/* + keep the state for firmware update +*/ +static struct { + char path[256]; + uint8_t node_id; + uint8_t transfer_id; + uint32_t last_read_ms; + uint32_t offset; +} fwupdate; + +// set from the CAN RX ISR (RawCommand) and read in the boot decision, so +// volatile (especially with -flto cross-TU optimisation) +static volatile bool have_raw_command; + +// no-CAN-cable fallback: behave like a non-CAN build when no CAN frame is +// seen within NONCAN_FALLBACK_MS. can_seen is set RX-only; noncan_fallback +// relaxes the boot gate once the deadline passes with no traffic. +static volatile bool can_seen; +static bool noncan_fallback; +#define NONCAN_FALLBACK_MS 250 +#define DRONECAN_INPUT_TYPE 5 // mirrors DRONECAN_IN in AM32 Inc/common.h (EEPROM byte 46) + +void DroneCAN_set_have_signal(void) +{ + have_raw_command = true; +} + +// some convenience macros +#define MIN(a,b) ((a)<(b)?(a):(b)) +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +/* + hold our node status as a static variable. It will be updated on any errors +*/ +static struct uavcan_protocol_NodeStatus node_status; + +enum boot_code { + CHECK_FW_OK = 0, + FAIL_REASON_NO_APP_SIG = 10, + FAIL_REASON_BAD_LENGTH_APP = 11, + FAIL_REASON_BAD_BOARD_ID = 12, + FAIL_REASON_BAD_CRC = 13, + FAIL_REASON_IN_UPDATE = 14, + FAIL_REASON_WATCHDOG = 15, + FAIL_REASON_BAD_LENGTH_DESCRIPTOR = 16, + FAIL_REASON_BAD_FIRMWARE_SIGNATURE = 17, + FAIL_REASON_VERIFICATION = 18, + FAIL_REASON_NO_SIGNAL = 19, +}; + +#define APP_SIGNATURE_MAGIC1 0x68f058e6 +#define APP_SIGNATURE_MAGIC2 0xafcee5a0 + +/* + application signature + */ +struct app_signature { + uint32_t magic1; + uint32_t magic2; + uint32_t fwlen; // total fw length in bytes + uint32_t crc1; // crc32 up to start of app_signature + uint32_t crc2; // crc32 from end of app_signature to end of fw + char mcu[16]; + uint32_t unused[2]; +}; + +/* + simple 16 bit random number generator +*/ +static uint16_t get_random16(void) +{ + static uint32_t m_z = 1234; + static uint32_t m_w = 76542; + m_z = 36969 * (m_z & 0xFFFFu) + (m_z >> 16); + m_w = 18000 * (m_w & 0xFFFFu) + (m_w >> 16); + return ((m_z << 16) + m_w) & 0xFFFF; +} + +/* + monotonic microseconds since start. uint32 wraps at ~71min, far beyond the + bootloader lifetime, and avoids the libgcc 64-bit divide. libcanard takes + uint64 timestamps - cast at the call sites. +*/ +static uint32_t micros32(void) +{ + static uint32_t base_us; + static uint16_t last_cnt; + uint16_t cnt = bl_timer_us(); + if (cnt < last_cnt) { + base_us += 0x10000; + } + last_cnt = cnt; + return base_us + cnt; +} + +/* + get monotonic time in milliseconds since startup. 32-bit divide compiles + to a single UDIV on cortex-m4/m33 -- no libgcc helper needed. +*/ +static uint32_t millis32(void) +{ + return micros32() / 1000U; +} + +/* + default settings, based on public/assets/eeprom_default.bin in AM32 configurator + */ +static const uint8_t default_settings[] = { + 0x01, 0x02, BOOTLOADER_VERSION, 0x01, 0x23, 0x4e, 0x45, 0x4f, 0x45, 0x53, 0x43, 0x20, 0x66, 0x30, 0x35, 0x31, + 0x20, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x18, 0x64, 0x37, 0x0e, 0x00, 0x00, 0x05, 0x00, + 0x80, 0x80, 0x80, 0x32, 0x00, 0x32, 0x00, 0x00, 0x0f, 0x0a, 0x0a, 0x8d, 0x66, 0x06, 0x00, 0x00 +}; + +#if DRONECAN_CHECK_SIGNATURE +// crc32 implementation, slow method for small flash cost +static uint32_t crc32(const uint8_t *buf, uint32_t size) +{ + uint32_t crc = 0; + while (size--) { + const uint8_t byte = *buf++; + crc ^= byte; + for (uint8_t i=0; i<8; i++) { + const uint32_t mask = -(crc & 1); + crc >>= 1; + crc ^= (0xEDB88320 & mask); + } + } + return crc; +} +#endif + +/* + print to CAN LogMessage for debugging. Compiled out unless DRONECAN_DEBUG; + the no-op inline stub lets the linker drop LogMessage_encode and the strings. + */ +#if DRONECAN_DEBUG +static void can_print(const char *s) +{ + struct uavcan_protocol_debug_LogMessage pkt; + memset(&pkt, 0, sizeof(pkt)); + + uint8_t buffer[UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_MAX_SIZE]; + pkt.text.len = strlen(s); + memcpy(pkt.text.data, s, pkt.text.len); + + uint32_t len = DC_ENCODE(uavcan_protocol_debug_LogMessage_encode, &pkt, buffer); + static uint8_t logmsg_transfer_id; + + DC_BROADCAST(&canard, + UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_SIGNATURE, + UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_ID, + &logmsg_transfer_id, + CANARD_TRANSFER_PRIORITY_LOW, + buffer, len); +} +#else +static inline void can_print(const char *s) { (void)s; } +#endif + +#if DRONECAN_PARAM_SUPPORT_ENABLED +/* + parameter type tags. These mirror what the main firmware exposes over + the wire via uavcan.protocol.param.Value so a DroneCAN GUI sees the + same value type (and therefore the same widget) for a given parameter + whether it's talking to the bootloader or the application. + */ +enum bl_param_type { + BL_T_UINT8 = 0, + BL_T_BOOL, + BL_T_UINT16, // stored as a single EEPROM byte, scaled on the wire + BL_T_STRING, // STARTUP_TUNE only, at eepromBuffer.tune (offset 48..175) +}; + +/* + EEPROM byte offsets that need a non-trivial uint8 <-> wire mapping. + Kept as named constants here so the special-case branches in + bl_param_*_value() and the set path read sensibly. + These match ../AM32/Inc/eeprom.h::eepromBuffer member offsets. + */ +#define EEPROM_OFS_ADVANCE_LEVEL 23 +#define EEPROM_OFS_MOTOR_KV 26 +#define EEPROM_OFS_LOW_CELL_VOLT_CUTOFF 37 +#define EEPROM_OFS_LIMITS_CURRENT 44 +#define EEPROM_OFS_TUNE 48 +#define EEPROM_TUNE_LEN 128 + +/* + remap table for legacy pre-v3 EEPROM advance_level encodings. Matches + ../AM32/Src/DroneCAN/DroneCAN.c::advance_level_v3_remap so a DroneCAN + client reading ADVANCE_LEVEL via the bootloader sees the same value + it would see via the application on an ESC that hasn't been rewritten + since the v3 migration. + */ +static const uint8_t advance_level_v3_remap[] = { 0x00, 0x08, 0x10, 0x16 }; + +/* + parameters the bootloader exposes via uavcan.protocol.param.GetSet. + Name / vtype / min / max / default mirror the main firmware's + parameters[] table in ../AM32/Src/DroneCAN/DroneCAN.c so a DroneCAN GUI + shows the same values and ranges whether it talks to the application + or to the bootloader. + + default_value is what we report on the wire when the raw EEPROM byte + is uninitialised (0xFF) or out of range. For parameters whose EEPROM + offset is inside the default_settings[] block (offsets 0..47) the + response's default_value is taken from default_settings instead, to + match the main firmware's behaviour exactly. + */ +static const struct bl_param { + const char *name; + uint16_t min_value; + uint16_t max_value; + uint16_t default_value; + uint8_t vtype; // enum bl_param_type + uint8_t eeprom_offset; +} bl_parameters[] = { + /* CAN identity and bus setup only. Motor/tune params need the app and + * the 16 KiB CAN FD image does not have room for the full table. */ + { "CAN_NODE", 0, 127, 0, BL_T_UINT8, 176 }, + { "ESC_INDEX", 0, 32, 0, BL_T_UINT8, 177 }, + { "REQUIRE_ARMING", 0, 1, 1, BL_T_BOOL, 178 }, + { "REQUIRE_ZERO_THROTTLE", 0, 1, 1, BL_T_BOOL, 180 }, + { "INPUT_SIGNAL_TYPE", 0, 5, 0, BL_T_UINT8, 46 }, +#ifdef CAN_TERM_PIN + { "CAN_TERM_ENABLE", 0, 1, 0, BL_T_BOOL, 183 }, +#endif + { "CAN_FD_MBPS", 0, 5, 0, BL_T_UINT8, 185 }, +}; + +#define NUM_BL_PARAMS (sizeof(bl_parameters)/sizeof(bl_parameters[0])) + +/* + patch a contiguous run of bytes in the EEPROM page while preserving + the rest. STM32 flash is page-erase-then-program: save_flash_nolib() + erases the whole 2 KB page at EEPROM_START_ADD and writes back only + the bytes we hand it, so we copy out enough of the live page to cover + every application setting, splice in the new bytes, and write the lot + back. EEPROM_PRESERVE_SIZE matches the main firmware's EEPROM_MAX_SIZE. + Returns true on success. + */ +static bool set_eeprom_bytes(uint16_t offset, const uint8_t *data, uint16_t len) +{ + static uint8_t buf[EEPROM_PRESERVE_SIZE]; + if ((uint32_t)offset + len > sizeof(buf)) { + return false; + } + memcpy(buf, (const void *)EEPROM_START_ADD, sizeof(buf)); + if (memcmp(buf + offset, data, len) == 0) { + // nothing to do; avoid an unnecessary erase cycle. + return true; + } + memcpy(buf + offset, data, len); + return save_flash_nolib(buf, sizeof(buf), EEPROM_START_ADD); +} + +static bool set_eeprom_byte(uint16_t offset, uint8_t value) +{ + return set_eeprom_bytes(offset, &value, 1); +} + +/* + read the live (effective) wire value of a numeric parameter from + EEPROM, applying the same default-and-clamp rules and scaling the + main firmware uses. Sets *out_val and returns true on success; + returns false if the parameter is BL_T_STRING (caller must handle + strings separately) or if no parameter exists at p_idx. + */ +static bool bl_param_read_numeric(uint8_t p_idx, uint16_t *out_val) +{ + const uint8_t *eeprom = (const uint8_t *)EEPROM_START_ADD; + const struct bl_param *p = &bl_parameters[p_idx]; + if (p->vtype == BL_T_STRING) { + return false; + } + if (eeprom[0] != 0x01) { + *out_val = p->default_value; + return true; + } + const uint8_t raw = eeprom[p->eeprom_offset]; + uint16_t v; + switch (p->vtype) { + case BL_T_UINT16: + if (p->eeprom_offset == EEPROM_OFS_MOTOR_KV) { + v = (uint16_t)raw * 40U + 20U; + } else if (p->eeprom_offset == EEPROM_OFS_LOW_CELL_VOLT_CUTOFF) { + v = (uint16_t)raw + 250U; + } else { + v = raw; + } + break; + case BL_T_UINT8: + case BL_T_BOOL: + default: + v = raw; + if (p->eeprom_offset == EEPROM_OFS_LIMITS_CURRENT) { + v = (uint16_t)(v * 2U); + } else if (p->eeprom_offset == EEPROM_OFS_ADVANCE_LEVEL) { + if (v < sizeof(advance_level_v3_remap)) { + v = advance_level_v3_remap[v]; + } + if (v >= 10) { + v -= 10; + } + } + break; + } + // Clamp to range; an out-of-range raw byte (typically 0xFF on + // uninitialised EEPROM) maps to the parameter's default_value. + if (v < p->min_value || v > p->max_value) { + v = p->default_value; + } + *out_val = v; + return true; +} + +/* + write the wire value of a numeric parameter through to EEPROM, applying + the inverse of the scaling bl_param_read_numeric() applies. v is + pre-clamped to [min_value, max_value]; returns the result of the + flash write. + */ +static bool bl_param_write_numeric(uint8_t p_idx, uint16_t v) +{ + const struct bl_param *p = &bl_parameters[p_idx]; + if (v < p->min_value) v = p->min_value; + if (v > p->max_value) v = p->max_value; + uint8_t raw; + switch (p->vtype) { + case BL_T_UINT16: + if (p->eeprom_offset == EEPROM_OFS_MOTOR_KV) { + raw = (uint8_t)((v - 20U) / 40U); + } else if (p->eeprom_offset == EEPROM_OFS_LOW_CELL_VOLT_CUTOFF) { + raw = (uint8_t)(v - 250U); + } else { + raw = (uint8_t)v; + } + break; + case BL_T_UINT8: + case BL_T_BOOL: + default: + if (p->eeprom_offset == EEPROM_OFS_LIMITS_CURRENT) { + raw = (uint8_t)(v / 2U); + } else if (p->eeprom_offset == EEPROM_OFS_ADVANCE_LEVEL) { + raw = (uint8_t)(v + 10U); + } else { + raw = (uint8_t)v; + } + break; + } + return set_eeprom_byte(p->eeprom_offset, raw); +} + +/* + populate pkt.value / pkt.default_value / pkt.min_value / pkt.max_value + for parameter p_idx. For BL_T_UINT8/BL_T_BOOL parameters that live in + the default_settings[] region (offsets 0..47), the response's + default_value comes from default_settings (matching the main firmware); + for everything else we report the parameter table's default_value. + */ +static void bl_param_fill_response(struct uavcan_protocol_param_GetSetResponse *pkt, uint8_t p_idx) +{ + const struct bl_param *p = &bl_parameters[p_idx]; + const uint8_t off = p->eeprom_offset; + uint16_t cur_val = 0; + const bool have_numeric = bl_param_read_numeric(p_idx, &cur_val); + + switch (p->vtype) { + case BL_T_UINT8: { + pkt->value.union_tag = UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE; + pkt->value.integer_value = cur_val; + pkt->default_value.union_tag = UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE; + if (off < sizeof(default_settings)) { + uint16_t d = default_settings[off]; + if (off == EEPROM_OFS_LIMITS_CURRENT) d = (uint16_t)(d * 2U); + pkt->default_value.integer_value = d; + } else { + pkt->default_value.integer_value = p->default_value; + } + pkt->min_value.union_tag = UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_INTEGER_VALUE; + pkt->min_value.integer_value = p->min_value; + pkt->max_value.union_tag = UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_INTEGER_VALUE; + pkt->max_value.integer_value = p->max_value; + break; + } + case BL_T_BOOL: { + pkt->value.union_tag = UAVCAN_PROTOCOL_PARAM_VALUE_BOOLEAN_VALUE; + pkt->value.boolean_value = cur_val ? 1 : 0; + pkt->default_value.union_tag = UAVCAN_PROTOCOL_PARAM_VALUE_BOOLEAN_VALUE; + if (off < sizeof(default_settings)) { + pkt->default_value.boolean_value = default_settings[off] ? 1 : 0; + } else { + pkt->default_value.boolean_value = p->default_value ? 1 : 0; + } + break; + } + case BL_T_UINT16: { + pkt->value.union_tag = UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE; + pkt->value.integer_value = cur_val; + pkt->default_value.union_tag = UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE; + pkt->default_value.integer_value = p->default_value; + pkt->min_value.union_tag = UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_INTEGER_VALUE; + pkt->min_value.integer_value = p->min_value; + pkt->max_value.union_tag = UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_INTEGER_VALUE; + pkt->max_value.integer_value = p->max_value; + break; + } + case BL_T_STRING: { + pkt->value.union_tag = UAVCAN_PROTOCOL_PARAM_VALUE_STRING_VALUE; + const uint8_t *eeprom = (const uint8_t *)EEPROM_START_ADD; + const uint16_t maxlen = sizeof(pkt->value.string_value.data); + uint16_t slen = EEPROM_TUNE_LEN; + if (slen > maxlen) slen = maxlen; + pkt->value.string_value.len = slen; + memcpy(pkt->value.string_value.data, &eeprom[off], slen); + break; + } + } + (void)have_numeric; +} + +/* + handle uavcan.protocol.param.GetSet request. Supports lookup by name + or by index. The set path is taken when the caller passes a name AND + a non-empty Value union; integers/booleans go through the scaling + helpers, strings (STARTUP_TUNE) go straight to EEPROM. + + Set is refused when the EEPROM magic isn't set: we'd otherwise be + writing into a page full of 0xFF, and the application would treat + the result as uninitialised on next boot. +*/ +static void handle_param_GetSet(CanardInstance* ins, CanardRxTransfer* transfer) +{ + struct uavcan_protocol_param_GetSetRequest req; + if (uavcan_protocol_param_GetSetRequest_decode(transfer, &req)) { + return; + } + + const uint8_t *eeprom = (const uint8_t *)EEPROM_START_ADD; + int p_idx = -1; + + if (req.name.len != 0) { + for (uint8_t i = 0; i < NUM_BL_PARAMS; i++) { + const char *pname = bl_parameters[i].name; + const uint32_t plen = strlen(pname); + if (req.name.len == plen && + memcmp(req.name.data, pname, plen) == 0) { + p_idx = (int)i; + break; + } + } + } else if (req.index < NUM_BL_PARAMS) { + p_idx = req.index; + } + + // Set path. Refuse if EEPROM is unprovisioned; refuse the EMPTY union + // (that's how a pure Get-by-name is signalled). + if (p_idx >= 0 && req.name.len != 0 && + req.value.union_tag != UAVCAN_PROTOCOL_PARAM_VALUE_EMPTY && + eeprom[0] == 0x01) { + const struct bl_param *p = &bl_parameters[p_idx]; + switch (p->vtype) { + case BL_T_BOOL: { + uint16_t v; + if (req.value.union_tag == UAVCAN_PROTOCOL_PARAM_VALUE_BOOLEAN_VALUE) { + v = req.value.boolean_value ? 1 : 0; + } else if (req.value.union_tag == UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE) { + v = req.value.integer_value ? 1 : 0; + } else { + break; + } + bl_param_write_numeric((uint8_t)p_idx, v); + break; + } + case BL_T_UINT8: + case BL_T_UINT16: { + if (req.value.union_tag != UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE) { + break; + } + int64_t v = req.value.integer_value; + if (v < 0) v = 0; + if (v > 0xFFFF) v = 0xFFFF; + bl_param_write_numeric((uint8_t)p_idx, (uint16_t)v); + break; + } + case BL_T_STRING: { + if (req.value.union_tag != UAVCAN_PROTOCOL_PARAM_VALUE_STRING_VALUE) { + break; + } + // STARTUP_TUNE: 128-byte slot at offset 48. Bytes beyond the + // request length are padded to 0xFF, matching the main firmware. + uint8_t tune[EEPROM_TUNE_LEN]; + const uint16_t slen = req.value.string_value.len; + for (uint16_t i = 0; i < sizeof(tune); i++) { + tune[i] = (i < slen) ? req.value.string_value.data[i] : 0xFF; + } + set_eeprom_bytes(EEPROM_OFS_TUNE, tune, sizeof(tune)); + break; + } + } + } + + // Build response. + struct uavcan_protocol_param_GetSetResponse pkt; + memset(&pkt, 0, sizeof(pkt)); + + if (p_idx >= 0) { + bl_param_fill_response(&pkt, (uint8_t)p_idx); + const char *pname = bl_parameters[p_idx].name; + pkt.name.len = strlen(pname); + memcpy(pkt.name.data, pname, pkt.name.len); + } + + uint8_t buffer[UAVCAN_PROTOCOL_PARAM_GETSET_RESPONSE_MAX_SIZE]; + uint16_t total_size = DC_ENCODE(uavcan_protocol_param_GetSetResponse_encode, &pkt, buffer); + + DC_RESPOND(ins, + transfer->source_node_id, + UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE, + UAVCAN_PROTOCOL_PARAM_GETSET_ID, + &transfer->transfer_id, + transfer->priority, + CanardResponse, + &buffer[0], + total_size); +} + +/* + handle parameter executeopcode request +*/ +static void handle_param_ExecuteOpcode(CanardInstance* ins, CanardRxTransfer* transfer) +{ + struct uavcan_protocol_param_ExecuteOpcodeRequest req; + if (uavcan_protocol_param_ExecuteOpcodeRequest_decode(transfer, &req)) { + return; + } + struct uavcan_protocol_param_ExecuteOpcodeResponse pkt; + memset(&pkt, 0, sizeof(pkt)); + + pkt.ok = false; + + if (req.opcode == UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_OPCODE_ERASE) { + can_print("resetting to defaults"); + save_flash_nolib(default_settings, sizeof(default_settings), EEPROM_START_ADD); + pkt.ok = true; + } else if (req.opcode == UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_OPCODE_SAVE) { + // GetSet writes parameters straight to flash, so SAVE is a no-op; ack it + // so DroneCAN parameter clients that send SAVE see success. + pkt.ok = true; + } + + + uint8_t buffer[UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_RESPONSE_MAX_SIZE]; + uint16_t total_size = DC_ENCODE(uavcan_protocol_param_ExecuteOpcodeResponse_encode, &pkt, buffer); + + DC_RESPOND(ins, + transfer->source_node_id, + UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_SIGNATURE, + UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_ID, + &transfer->transfer_id, + transfer->priority, + CanardResponse, + &buffer[0], + total_size); +} +#endif // DRONECAN_PARAM_SUPPORT_ENABLED + +/* + handle RestartNode request +*/ +static void handle_RestartNode(CanardInstance* ins, CanardRxTransfer* transfer) +{ + set_rtc_backup_register(0, 0); + // reboot the ESC + NVIC_SystemReset(); +} + +/* + handle a GetNodeInfo request +*/ +static void handle_GetNodeInfo(CanardInstance *ins, CanardRxTransfer *transfer) +{ + uint8_t buffer[UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE]; + struct uavcan_protocol_GetNodeInfoResponse pkt; + + memset(&pkt, 0, sizeof(pkt)); + + // uptime_sec is maintained by process1HzTasks via a 1Hz tick counter, + // not derived from the 71-minute-wrapping microsecond timer. + pkt.status = node_status; + + // fill in your major and minor firmware version + pkt.software_version.major = BOOTLOADER_VERSION; + pkt.software_version.minor = 0; + pkt.software_version.optional_field_flags = 0; + pkt.software_version.vcs_commit = 0; // should put git hash in here + +#ifdef DRONECAN_HW_VERSION_MAJOR + pkt.hardware_version.major = DRONECAN_HW_VERSION_MAJOR; + pkt.hardware_version.minor = DRONECAN_HW_VERSION_MINOR; +#else + pkt.hardware_version.major = 2; + pkt.hardware_version.minor = 3; +#endif + + sys_can_getUniqueID(pkt.hardware_version.unique_id); + + strncpy((char*)pkt.name.data, "AM32_BOOTLOADER_" AM32_MCU, sizeof(pkt.name.data)); + pkt.name.len = strnlen((char*)pkt.name.data, sizeof(pkt.name.data)); + + uint16_t total_size = DC_ENCODE(uavcan_protocol_GetNodeInfoResponse_encode, &pkt, buffer); + + DC_RESPOND(ins, + transfer->source_node_id, + UAVCAN_PROTOCOL_GETNODEINFO_SIGNATURE, + UAVCAN_PROTOCOL_GETNODEINFO_ID, + &transfer->transfer_id, + transfer->priority, + CanardResponse, + &buffer[0], + total_size); +} + +/* + handle a BeginFirmwareUpdate request from a management tool like + DroneCAN GUI tool or MissionPlanner + */ +static void handle_begin_firmware_update(CanardInstance* ins, CanardRxTransfer* transfer) +{ + /* + decode the request + */ + struct uavcan_protocol_file_BeginFirmwareUpdateRequest req; + if (uavcan_protocol_file_BeginFirmwareUpdateRequest_decode(transfer, &req)) { + return; + } + + /* + check for a repeated BeginFirmwareUpdateRequest + */ + if (fwupdate.node_id == transfer->source_node_id && + memcmp(fwupdate.path, req.image_file_remote_path.path.data, req.image_file_remote_path.path.len) == 0) { + /* ignore duplicate request */ + return; + } + + fwupdate.offset = 0; + fwupdate.node_id = transfer->source_node_id; + strncpy(fwupdate.path, (char*)req.image_file_remote_path.path.data, req.image_file_remote_path.path.len); + + uint8_t buffer[UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_MAX_SIZE]; + struct uavcan_protocol_file_BeginFirmwareUpdateResponse reply; + memset(&reply, 0, sizeof(reply)); + reply.error = UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_ERROR_OK; + + uint32_t total_size = DC_ENCODE(uavcan_protocol_file_BeginFirmwareUpdateResponse_encode, &reply, buffer); + + DC_RESPOND(ins, + transfer->source_node_id, + UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_SIGNATURE, + UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_ID, + &transfer->transfer_id, + transfer->priority, + CanardResponse, + &buffer[0], + total_size); + + can_print("Started firmware update"); +} + +/* + send a read for a firmware update. This asks the client (firmware + server) for a piece of the new firmware + */ +static void send_firmware_read(void) +{ + uint32_t now = millis32(); + if (fwupdate.last_read_ms != 0 && now - fwupdate.last_read_ms < 750) { + // the server may still be responding + return; + } + fwupdate.last_read_ms = now; + + uint8_t buffer[UAVCAN_PROTOCOL_FILE_READ_REQUEST_MAX_SIZE]; + + struct uavcan_protocol_file_ReadRequest pkt; + memset(&pkt, 0, sizeof(pkt)); + + pkt.path.path.len = strlen((const char *)fwupdate.path); + pkt.offset = fwupdate.offset; + memcpy(pkt.path.path.data, fwupdate.path, pkt.path.path.len); + + uint16_t total_size = DC_ENCODE(uavcan_protocol_file_ReadRequest_encode, &pkt, buffer); + + DC_RESPOND(&canard, + fwupdate.node_id, + UAVCAN_PROTOCOL_FILE_READ_SIGNATURE, + UAVCAN_PROTOCOL_FILE_READ_ID, + &fwupdate.transfer_id, + CANARD_TRANSFER_PRIORITY_HIGH, + CanardRequest, + &buffer[0], + total_size); +} + +/* + handle response to send_firmware_read() + */ +static void handle_file_read_response(CanardInstance* ins, CanardRxTransfer* transfer) +{ + if ((transfer->transfer_id+1)%32 != fwupdate.transfer_id || + transfer->source_node_id != fwupdate.node_id) { + /* not for us */ + can_print("Firmware update: not for us"); + return; + } + struct uavcan_protocol_file_ReadResponse pkt; + if (uavcan_protocol_file_ReadResponse_decode(transfer, &pkt)) { + /* bad packet */ + can_print("Firmware update: bad packet"); + return; + } + if (pkt.error.value != UAVCAN_PROTOCOL_FILE_ERROR_OK) { + if (fwupdate.offset == 0) { + // MissionPlanner does this sometimes, ignore + return; + } + /* read failed */ + fwupdate.node_id = 0; + can_print("Firmware update read failure"); + return; + } + + uint32_t len = pkt.data.len; + len = (len+7U) & ~7U; + save_flash_nolib(pkt.data.data, len, (uint32_t)MAIN_FW_START_ADDR + fwupdate.offset); + + fwupdate.offset += pkt.data.len; + + if (pkt.data.len < 256) { + /* firmware updare done */ + can_print("Firmwate update complete"); + fwupdate.node_id = 0; + set_rtc_backup_register(0, 0); + NVIC_SystemReset(); + return; + } + + /* trigger a new read */ + fwupdate.last_read_ms = 0; + send_firmware_read(); + + DroneCAN_processTxQueue(); +} + +/* + data for dynamic node allocation process +*/ +static struct { + uint32_t send_next_node_id_allocation_request_at_ms; + uint32_t node_id_allocation_unique_id_offset; +} DNA; + +/* + handle a DNA allocation packet +*/ +static void handle_DNA_Allocation(CanardInstance *ins, CanardRxTransfer *transfer) +{ + if (canardGetLocalNodeID(&canard) != CANARD_BROADCAST_NODE_ID) { + // already allocated + return; + } + + // Rule C - updating the randomized time interval + DNA.send_next_node_id_allocation_request_at_ms = + millis32() + UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MIN_REQUEST_PERIOD_MS + + (get_random16() % UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_FOLLOWUP_DELAY_MS); + + if (transfer->source_node_id == CANARD_BROADCAST_NODE_ID) { + DNA.node_id_allocation_unique_id_offset = 0; + return; + } + + // Copying the unique ID from the message + struct uavcan_protocol_dynamic_node_id_Allocation msg; + + if (uavcan_protocol_dynamic_node_id_Allocation_decode(transfer, &msg)) { + /* bad packet */ + return; + } + + // Obtaining the local unique ID + uint8_t my_unique_id[sizeof(msg.unique_id.data)]; + sys_can_getUniqueID(my_unique_id); + + // Matching the received UID against the local one + if (memcmp(msg.unique_id.data, my_unique_id, msg.unique_id.len) != 0) { + DNA.node_id_allocation_unique_id_offset = 0; + // No match, return + return; + } + + if (msg.unique_id.len < sizeof(msg.unique_id.data)) { + // The allocator has confirmed part of unique ID, switching to + // the next stage and updating the timeout. + DNA.node_id_allocation_unique_id_offset = msg.unique_id.len; + DNA.send_next_node_id_allocation_request_at_ms -= UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MIN_REQUEST_PERIOD_MS; + + } else { + // Allocation complete - copying the allocated node ID from the message + canardSetLocalNodeID(ins, msg.node_id); + } +} + +/* + ask for a dynamic node allocation +*/ +static void request_DNA() +{ + const uint32_t now = millis32(); + static uint8_t node_id_allocation_transfer_id = 0; + + DNA.send_next_node_id_allocation_request_at_ms = + now + UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MIN_REQUEST_PERIOD_MS + + (get_random16() % UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_FOLLOWUP_DELAY_MS); + + // Structure of the request is documented in the DSDL definition + // See http://uavcan.org/Specification/6._Application_level_functions/#dynamic-node-id-allocation + uint8_t allocation_request[CANARD_CAN_FRAME_MAX_DATA_LEN - 1]; + allocation_request[0] = (uint8_t)(PREFERRED_NODE_ID << 1U); + + if (DNA.node_id_allocation_unique_id_offset == 0) { + allocation_request[0] |= 1; // First part of unique ID + } + + uint8_t my_unique_id[16]; + sys_can_getUniqueID(my_unique_id); + + static const uint8_t MaxLenOfUniqueIDInRequest = 6; + uint8_t uid_size = (uint8_t)(16 - DNA.node_id_allocation_unique_id_offset); + + if (uid_size > MaxLenOfUniqueIDInRequest) { + uid_size = MaxLenOfUniqueIDInRequest; + } + + memmove(&allocation_request[1], &my_unique_id[DNA.node_id_allocation_unique_id_offset], uid_size); + + /* DNA is a classic 8-byte anonymous transfer. */ +#if CANARD_ENABLE_CANFD + canardBroadcast(&canard, + UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_SIGNATURE, + UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID, + &node_id_allocation_transfer_id, + CANARD_TRANSFER_PRIORITY_LOW, + &allocation_request[0], + (uint16_t) (uid_size + 1), + false); +#else + canardBroadcast(&canard, + UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_SIGNATURE, + UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID, + &node_id_allocation_transfer_id, + CANARD_TRANSFER_PRIORITY_LOW, + &allocation_request[0], + (uint16_t) (uid_size + 1)); +#endif + + // Preparing for timeout; if response is received, this value will be updated from the callback. + DNA.node_id_allocation_unique_id_offset = 0; +} + +/* + This callback is invoked by the library when a new message or request or response is received. +*/ +static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) +{ + // switch on data type ID to pass to the right handler function + if (transfer->transfer_type == CanardTransferTypeRequest) { + // check if we want to handle a specific service request + switch (transfer->data_type_id) { + case UAVCAN_PROTOCOL_GETNODEINFO_ID: { + handle_GetNodeInfo(ins, transfer); + break; + } + case UAVCAN_PROTOCOL_RESTARTNODE_ID: { + handle_RestartNode(ins, transfer); + break; + } + case UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_ID: { + handle_begin_firmware_update(ins, transfer); + break; + } +#if DRONECAN_PARAM_SUPPORT_ENABLED + case UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_ID: { + handle_param_ExecuteOpcode(ins, transfer); + break; + } + case UAVCAN_PROTOCOL_PARAM_GETSET_ID: { + handle_param_GetSet(ins, transfer); + break; + } +#endif + } + } + if (transfer->transfer_type == CanardTransferTypeResponse) { + switch (transfer->data_type_id) { + case UAVCAN_PROTOCOL_FILE_READ_ID: + handle_file_read_response(ins, transfer); + break; + } + } + if (transfer->transfer_type == CanardTransferTypeBroadcast) { + // check if we want to handle a specific broadcast message + switch (transfer->data_type_id) { + case UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID: { + handle_DNA_Allocation(ins, transfer); + break; + } + } + } +} + + +/* + This callback is invoked by the library when it detects beginning of a new transfer on the bus that can be received + by the local node. + If the callback returns true, the library will receive the transfer. + If the callback returns false, the library will ignore the transfer. + All transfers that are addressed to other nodes are always ignored. + + This function must fill in the out_data_type_signature to be the signature of the message. +*/ +static bool shouldAcceptTransfer(const CanardInstance *ins, + uint64_t *out_data_type_signature, + uint16_t data_type_id, + CanardTransferType transfer_type, + uint8_t source_node_id) +{ + if (transfer_type == CanardTransferTypeRequest) { + // check if we want to handle a specific service request + switch (data_type_id) { + case UAVCAN_PROTOCOL_GETNODEINFO_ID: { + *out_data_type_signature = UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_SIGNATURE; + return true; + } + case UAVCAN_PROTOCOL_RESTARTNODE_ID: { + *out_data_type_signature = UAVCAN_PROTOCOL_RESTARTNODE_SIGNATURE; + return true; + } + case UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_ID: { + *out_data_type_signature = UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_SIGNATURE; + return true; + } +#if DRONECAN_PARAM_SUPPORT_ENABLED + case UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_ID: { + *out_data_type_signature = UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_SIGNATURE; + return true; + } + case UAVCAN_PROTOCOL_PARAM_GETSET_ID: { + *out_data_type_signature = UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE; + return true; + } +#endif + } + } + if (transfer_type == CanardTransferTypeResponse) { + // check if we want to handle a specific service request + switch (data_type_id) { + case UAVCAN_PROTOCOL_FILE_READ_ID: + *out_data_type_signature = UAVCAN_PROTOCOL_FILE_READ_SIGNATURE; + return true; + } + } + if (transfer_type == CanardTransferTypeBroadcast) { + // see if we want to handle a specific broadcast packet + switch (data_type_id) { + case UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID: { + *out_data_type_signature = UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_SIGNATURE; + return true; + } + case UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_ID: { + // we are receiving RawCommand, we should boot to the main firmware + have_raw_command = true; + } + } + } + // we don't want any other messages + return false; +} + +/* + send the 1Hz NodeStatus message. This is what allows a node to show + up in the DroneCAN GUI tool and in the flight controller logs +*/ +static void send_NodeStatus(void) +{ + uint8_t buffer[UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE]; + + // uptime_sec is maintained by process1HzTasks (1Hz tick counter), not + // derived from the wrapping microsecond timer. + node_status.health = UAVCAN_PROTOCOL_NODESTATUS_HEALTH_OK; + node_status.mode = UAVCAN_PROTOCOL_NODESTATUS_MODE_MAINTENANCE; + node_status.sub_mode = 0; + + /* + when doing a firmware update put the size in kbytes in VSSC so + the user can see how far it has reached + */ + if (fwupdate.node_id != 0) { + node_status.vendor_specific_status_code = fwupdate.offset / 1024; + node_status.mode = UAVCAN_PROTOCOL_NODESTATUS_MODE_SOFTWARE_UPDATE; + } + + uint32_t len = DC_ENCODE(uavcan_protocol_NodeStatus_encode, &node_status, buffer); + + // we need a static variable for the transfer ID. This is + // incremeneted on each transfer, allowing for detection of packet + // loss + static uint8_t transfer_id; + + DC_BROADCAST(&canard, + UAVCAN_PROTOCOL_NODESTATUS_SIGNATURE, + UAVCAN_PROTOCOL_NODESTATUS_ID, + &transfer_id, + CANARD_TRANSFER_PRIORITY_LOW, + buffer, + len); +} + +/* + This function is called at 1 Hz rate from the main loop. +*/ +static void process1HzTasks(uint32_t timestamp_usec) +{ + /* + Purge transfers that are no longer transmitted. This can free up some memory. + Canard's API is uint64_t; zero-extend here. Stale-transfer timeout is + CANARD_TRANSFER_TIMEOUT_USEC (~2s), well below the uint32 wrap window. + */ + canardCleanupStaleTransfers(&canard, (uint64_t)timestamp_usec); + + /* + Transmit the node status message. uptime_sec is bumped AFTER the + broadcast so the first NodeStatus (which fires almost immediately + after boot) reports uptime=0, matching the old micros64()-based + semantics. + */ + send_NodeStatus(); + + /* + wraps at 136 years + */ + node_status.uptime_sec++; +} + +/* + receive one frame, only called from interrupt context +*/ +void DroneCAN_receiveFrame(void) +{ + CanardCANFrame rx_frame = {0}; + while (sys_can_receive(&rx_frame) > 0) { + can_seen = true; + dc_note_rx_frame(&rx_frame); + canardHandleRxFrame(&canard, &rx_frame, (uint64_t)micros32()); + } +} + +/* + handle a frame from interrupt context +*/ +void DroneCAN_handleFrame(CanardCANFrame *frame) +{ + can_seen = true; + dc_note_rx_frame(frame); + canardHandleRxFrame(&canard, frame, (uint64_t)micros32()); +} + +/* + Transmits all frames from the TX queue +*/ +void DroneCAN_processTxQueue(void) +{ + for (const CanardCANFrame* txf = NULL; (txf = canardPeekTxQueue(&canard)) != NULL;) { + const int16_t tx_res = sys_can_transmit(txf); + if (tx_res == 0) { + // no space, stop trying + break; + } + // success or error, remove frame + canardPopTxQueue(&canard); + } +} + +//#pragma GCC optimize("O0") + +static void DroneCAN_Startup(void) +{ + canardInit(&canard, + canard_memory_pool, // Raw memory chunk used for dynamic allocation + sizeof(canard_memory_pool), + onTransferReceived, // Callback, see CanardOnTransferReception + shouldAcceptTransfer, // Callback, see CanardShouldAcceptTransfer + NULL); + + /* + if doing fw update get node ID from main firmware via RTC backup + register + */ + uint8_t node_id = 0; + const uint32_t rtc0 = get_rtc_backup_register(0); + if ((rtc0 & 0xFFFFU) == RTC_BKUP0_FWUPDATE) { + node_id = rtc0 >> 24; + uint8_t src_node = (rtc0 >> 16) & 0xFF; + uint32_t path[2]; + path[0] = get_rtc_backup_register(1); + path[1] = get_rtc_backup_register(2); + if (path[0] != 0 && src_node <= 127) { + // we have update path, can start immediately + fwupdate.node_id = src_node; + memset(fwupdate.path, 0, sizeof(fwupdate.path)); + memcpy(fwupdate.path, path, sizeof(path)); + } + } + + if (node_id == 0) { + // check for valid node ID in settings + const uint8_t *eeprom = (const uint8_t *)EEPROM_START_ADD; + if (eeprom[0] == 1 && eeprom[176] <= 127) { + node_id = eeprom[176]; + } + } + + canardSetLocalNodeID(&canard, node_id); + + // initialise low level CAN peripheral hardware + sys_can_init(); +#if CANARD_ENABLE_CANFD + dronecan_tx_canfd = sys_can_prefer_canfd_tx(); +#endif + +#ifdef CAN_TERM_PIN + /* + apply the CAN bus termination setting from the EEPROM. Byte 183 is + eepromBuffer.can.term_enable (declared in ../AM32/Inc/eeprom.h). Mirror the + main firmware's load_settings(): an out-of-[0,1]-range byte falls back to the + default. default_settings[] only covers the first 48 bytes, so on a + defaults-seeded or erased EEPROM byte 183 reads 0xff -> default 0 (disabled), + matching the main firmware's default. + */ + { + const uint8_t *eeprom = (const uint8_t *)EEPROM_START_ADD; + uint8_t term = eeprom[183]; + if (term > 1) { + term = 0; // out of range -> default (disabled) + } + const bool term_enable = (eeprom[0] == 1) && (term != 0); + setup_portpin(CAN_TERM_PIN, term_enable ? CAN_TERM_POLARITY : !CAN_TERM_POLARITY); + } +#endif + +#if 0 + if (fwupdate.node_id != 0) { + can_print("fwupdate startup"); + can_print(fwupdate.path); + } else { + can_print("bootloader startup"); + } +#endif +} + +/* + handle DroneCAN packets. Return true if we are ready to boot the main firmware + */ +bool DroneCAN_update() +{ + static uint32_t next_1hz_service_at; + static bool done_startup; + if (!done_startup) { + done_startup = true; + DroneCAN_Startup(); + } + + sys_can_disable_IRQ(); + sys_can_service(); + + DroneCAN_processTxQueue(); + + /* + arm the no-CAN fallback. Must run before the DNA early-return below (with no + cable the node never gets an ID, so that returns false forever). After + NONCAN_FALLBACK_MS with no raw command and no CAN seen, relax the boot gate + unless INPUT_SIGNAL_TYPE is DroneCAN. A blank EEPROM arms, but the + short-circuit below still refuses to boot it. + */ + if (!have_raw_command && !noncan_fallback && !can_seen && + millis32() > NONCAN_FALLBACK_MS) { + const uint8_t *ee = (const uint8_t *)EEPROM_START_ADD; + const bool wait_can = (ee[0] == 0x01 && ee[46] == DRONECAN_INPUT_TYPE); + if (!wait_can) { + noncan_fallback = true; + } + } + if (noncan_fallback && !can_seen) { + // fallen back and still no CAN: boot like a non-CAN build. Like the non-CAN + // build, refuse a blank/unprogrammed EEPROM (byte0 != 0x01) rather than let + // DroneCAN_boot_ok() seed defaults and boot it. If a CAN frame arrives + // later, can_seen vetoes this and DNA/CAN handling resumes below. + sys_can_enable_IRQ(); + if (*(const uint8_t *)EEPROM_START_ADD != 0x01) { + return false; + } + return DroneCAN_boot_ok(); + } + + // see if we are still doing DNA + if (canardGetLocalNodeID(&canard) == CANARD_BROADCAST_NODE_ID) { + // we're still waiting for a DNA allocation of our node ID + if (millis32() > DNA.send_next_node_id_allocation_request_at_ms) { + request_DNA(); + } + sys_can_enable_IRQ(); + return false; + } + + const uint32_t ts = micros32(); + + /* + 1Hz tick + */ + if ((int32_t)(ts - next_1hz_service_at) >= 0) { + next_1hz_service_at = ts + 1000000U; + process1HzTasks(ts); + } + + if (fwupdate.node_id != 0) { + send_firmware_read(); + } + + DroneCAN_processTxQueue(); + + sys_can_enable_IRQ(); + + return DroneCAN_boot_ok(); +} + +/* + implementation of memmem() for finding app signature + + not called memmem() as newer newlib declares that in string.h and a + static definition of the same name is a conflicting redeclaration + */ +static void *bl_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) +{ + while (haystacklen >= needlelen) { + char *p = (char *)memchr(haystack, *(const char *)needle, haystacklen-(needlelen-1)); + if (!p) { + return NULL; + } + if (memcmp(p, needle, needlelen) == 0) { + return p; + } + haystacklen -= (p - (const char *)haystack) + 1; + haystack = p+1; + } + return NULL; +} + +static void set_reason(enum boot_code code, const char *reason) +{ + static uint32_t last_msg_us; + node_status.vendor_specific_status_code = code; + // unsigned subtraction handles uint32 wrap + const uint32_t now_us = micros32(); + if (now_us - last_msg_us > 5000000U) { + last_msg_us = now_us; + can_print(reason); + } +} + +/* + see if we are OK to boot + */ +bool DroneCAN_boot_ok(void) +{ + if (fwupdate.node_id != 0) { + // in fw update + return false; + } + if ((get_rtc_backup_register(0) & 0xFFFFU) == RTC_BKUP0_FWUPDATE) { + // waiting for firmware update + node_status.vendor_specific_status_code = FAIL_REASON_IN_UPDATE; + return false; + } + /* + check application signature + */ + uint32_t sig[2] = { APP_SIGNATURE_MAGIC1, APP_SIGNATURE_MAGIC2 }; + const uint32_t app_max_len = (128-18)*1024; + const uint8_t *fw_base = (const uint8_t *)MAIN_FW_START_ADDR; + struct app_signature *appsig = bl_memmem(fw_base, app_max_len, sig, sizeof(sig)); + if (appsig == NULL || (((uint32_t)appsig) & 3) != 0) { + set_reason(FAIL_REASON_NO_APP_SIG, "no app signature"); + return false; + } + if (appsig->fwlen > app_max_len) { + set_reason(FAIL_REASON_BAD_LENGTH_APP, "bad app length"); + return false; + } + if (memcmp(AM32_MCU, appsig->mcu, strlen(AM32_MCU)) != 0) { + set_reason(FAIL_REASON_BAD_BOARD_ID, "bad board type"); + return false; + } + +#if DRONECAN_CHECK_SIGNATURE + const uint8_t *appsigend = (const uint8_t *)(appsig+1); + const uint32_t crc1 = crc32(fw_base, (uint32_t)((uint8_t*)appsig - fw_base)); + const uint32_t crc2 = crc32(appsigend, appsig->fwlen - (uint32_t)(appsigend - fw_base)); + if (appsig->crc1 != crc1 || + appsig->crc2 != crc2) { + set_reason(FAIL_REASON_BAD_CRC, "bad firmware CRC"); + return false; + } +#endif + + // waive the raw-command requirement under no-CAN fallback. The !can_seen + // re-check closes the late-frame race: a frame arriving after fallback armed + // re-blocks the boot and reverts to CAN behaviour. + if (!have_raw_command && !(noncan_fallback && !can_seen)) { + set_reason(FAIL_REASON_NO_SIGNAL, "no signal"); + return false; + } + + node_status.vendor_specific_status_code = CHECK_FW_OK; + + const uint8_t eeprom_magic = *(uint8_t*)(EEPROM_START_ADD); + if (eeprom_magic == 0 || eeprom_magic == 0xff) { + can_print("resetting to defaults"); + save_flash_nolib(default_settings, sizeof(default_settings), EEPROM_START_ADD); + } + if (eeprom_magic != 0x01) { + set_reason(FAIL_REASON_BAD_FIRMWARE_SIGNATURE, "bad eeprom header"); + } + + return true; +} + +#endif // DRONECAN_SUPPORT diff --git a/bootloader/bootloader/DroneCAN/DroneCAN.h b/bootloader/bootloader/DroneCAN/DroneCAN.h new file mode 100644 index 000000000..ce28aa085 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/DroneCAN.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#if DRONECAN_SUPPORT +void DroneCAN_Init(void); +bool DroneCAN_update(); +bool DroneCAN_boot_ok(void); +void DroneCAN_set_have_signal(void); + +#endif // DRONECAN_SUPPORT diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/dronecan_msgs.h b/bootloader/bootloader/DroneCAN/dsdl_generated/dronecan_msgs.h new file mode 100644 index 000000000..97830f1c6 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/dronecan_msgs.h @@ -0,0 +1,57 @@ +#pragma once +#include "uavcan.equipment.esc.RawCommand.h" +#include "uavcan.equipment.esc.RPMCommand.h" +#include "uavcan.equipment.esc.StatusExtended.h" +#include "uavcan.equipment.esc.Status.h" +#include "uavcan.equipment.safety.ArmingStatus.h" +#include "uavcan.protocol.debug.KeyValue.h" +#include "uavcan.protocol.debug.LogLevel.h" +#include "uavcan.protocol.debug.LogMessage.h" +#include "uavcan.protocol.dynamic_node_id.Allocation.h" +#include "uavcan.protocol.dynamic_node_id.server.AppendEntries.h" +#include "uavcan.protocol.dynamic_node_id.server.AppendEntries_req.h" +#include "uavcan.protocol.dynamic_node_id.server.AppendEntries_res.h" +#include "uavcan.protocol.dynamic_node_id.server.Discovery.h" +#include "uavcan.protocol.dynamic_node_id.server.Entry.h" +#include "uavcan.protocol.dynamic_node_id.server.RequestVote.h" +#include "uavcan.protocol.dynamic_node_id.server.RequestVote_req.h" +#include "uavcan.protocol.dynamic_node_id.server.RequestVote_res.h" +#include "uavcan.protocol.file.BeginFirmwareUpdate.h" +#include "uavcan.protocol.file.BeginFirmwareUpdate_req.h" +#include "uavcan.protocol.file.BeginFirmwareUpdate_res.h" +#include "uavcan.protocol.file.Delete.h" +#include "uavcan.protocol.file.Delete_req.h" +#include "uavcan.protocol.file.Delete_res.h" +#include "uavcan.protocol.file.EntryType.h" +#include "uavcan.protocol.file.Error.h" +#include "uavcan.protocol.file.GetDirectoryEntryInfo.h" +#include "uavcan.protocol.file.GetDirectoryEntryInfo_req.h" +#include "uavcan.protocol.file.GetDirectoryEntryInfo_res.h" +#include "uavcan.protocol.file.GetInfo.h" +#include "uavcan.protocol.file.GetInfo_req.h" +#include "uavcan.protocol.file.GetInfo_res.h" +#include "uavcan.protocol.file.Path.h" +#include "uavcan.protocol.file.Read.h" +#include "uavcan.protocol.file.Read_req.h" +#include "uavcan.protocol.file.Read_res.h" +#include "uavcan.protocol.file.Write.h" +#include "uavcan.protocol.file.Write_req.h" +#include "uavcan.protocol.file.Write_res.h" +#include "uavcan.protocol.GetNodeInfo.h" +#include "uavcan.protocol.GetNodeInfo_req.h" +#include "uavcan.protocol.GetNodeInfo_res.h" +#include "uavcan.protocol.HardwareVersion.h" +#include "uavcan.protocol.NodeStatus.h" +#include "uavcan.protocol.param.Empty.h" +#include "uavcan.protocol.param.ExecuteOpcode.h" +#include "uavcan.protocol.param.ExecuteOpcode_req.h" +#include "uavcan.protocol.param.ExecuteOpcode_res.h" +#include "uavcan.protocol.param.GetSet.h" +#include "uavcan.protocol.param.GetSet_req.h" +#include "uavcan.protocol.param.GetSet_res.h" +#include "uavcan.protocol.param.NumericValue.h" +#include "uavcan.protocol.param.Value.h" +#include "uavcan.protocol.RestartNode.h" +#include "uavcan.protocol.RestartNode_req.h" +#include "uavcan.protocol.RestartNode_res.h" +#include "uavcan.protocol.SoftwareVersion.h" diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.RPMCommand.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.RPMCommand.h new file mode 100644 index 000000000..b66bbc656 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.RPMCommand.h @@ -0,0 +1,96 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_EQUIPMENT_ESC_RPMCOMMAND_MAX_SIZE 46 +#define UAVCAN_EQUIPMENT_ESC_RPMCOMMAND_SIGNATURE (0xCE0F9F621CF7E70BULL) +#define UAVCAN_EQUIPMENT_ESC_RPMCOMMAND_ID 1031 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_equipment_esc_RPMCommand_cxx_iface; +#endif + +struct uavcan_equipment_esc_RPMCommand { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_equipment_esc_RPMCommand_cxx_iface; +#endif + struct { uint8_t len; int32_t data[20]; }rpm; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_equipment_esc_RPMCommand_encode(struct uavcan_equipment_esc_RPMCommand* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_equipment_esc_RPMCommand_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_esc_RPMCommand* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_equipment_esc_RPMCommand_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_esc_RPMCommand* msg, bool tao); +static inline bool _uavcan_equipment_esc_RPMCommand_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_esc_RPMCommand* msg, bool tao); +void _uavcan_equipment_esc_RPMCommand_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_esc_RPMCommand* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t rpm_len = msg->rpm.len > 20 ? 20 : msg->rpm.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 5, &rpm_len); + *bit_ofs += 5; + } + for (size_t i=0; i < rpm_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 18, &msg->rpm.data[i]); + *bit_ofs += 18; + } +} + +/* + decode uavcan_equipment_esc_RPMCommand, return true on failure, false on success +*/ +bool _uavcan_equipment_esc_RPMCommand_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_esc_RPMCommand* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 5, false, &msg->rpm.len); + *bit_ofs += 5; + } else { + msg->rpm.len = ((transfer->payload_len*8)-*bit_ofs)/18; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->rpm.len > 20) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->rpm.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 18, true, &msg->rpm.data[i]); + *bit_ofs += 18; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_esc_RPMCommand sample_uavcan_equipment_esc_RPMCommand_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_equipment_esc_RPMCommand, UAVCAN_EQUIPMENT_ESC_RPMCOMMAND_ID, UAVCAN_EQUIPMENT_ESC_RPMCOMMAND_SIGNATURE, UAVCAN_EQUIPMENT_ESC_RPMCOMMAND_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.RawCommand.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.RawCommand.h new file mode 100644 index 000000000..8c1cc56bc --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.RawCommand.h @@ -0,0 +1,96 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_MAX_SIZE 36 +#define UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_SIGNATURE (0x217F5C87D7EC951DULL) +#define UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_ID 1030 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_equipment_esc_RawCommand_cxx_iface; +#endif + +struct uavcan_equipment_esc_RawCommand { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_equipment_esc_RawCommand_cxx_iface; +#endif + struct { uint8_t len; int16_t data[20]; }cmd; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_equipment_esc_RawCommand_encode(struct uavcan_equipment_esc_RawCommand* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_equipment_esc_RawCommand_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_esc_RawCommand* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_equipment_esc_RawCommand_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_esc_RawCommand* msg, bool tao); +static inline bool _uavcan_equipment_esc_RawCommand_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_esc_RawCommand* msg, bool tao); +void _uavcan_equipment_esc_RawCommand_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_esc_RawCommand* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t cmd_len = msg->cmd.len > 20 ? 20 : msg->cmd.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 5, &cmd_len); + *bit_ofs += 5; + } + for (size_t i=0; i < cmd_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 14, &msg->cmd.data[i]); + *bit_ofs += 14; + } +} + +/* + decode uavcan_equipment_esc_RawCommand, return true on failure, false on success +*/ +bool _uavcan_equipment_esc_RawCommand_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_esc_RawCommand* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 5, false, &msg->cmd.len); + *bit_ofs += 5; + } else { + msg->cmd.len = ((transfer->payload_len*8)-*bit_ofs)/14; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->cmd.len > 20) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->cmd.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 14, true, &msg->cmd.data[i]); + *bit_ofs += 14; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_esc_RawCommand sample_uavcan_equipment_esc_RawCommand_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_equipment_esc_RawCommand, UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_ID, UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_SIGNATURE, UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.Status.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.Status.h new file mode 100644 index 000000000..cef7bb647 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.Status.h @@ -0,0 +1,128 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_EQUIPMENT_ESC_STATUS_MAX_SIZE 14 +#define UAVCAN_EQUIPMENT_ESC_STATUS_SIGNATURE (0xA9AF28AEA2FBB254ULL) +#define UAVCAN_EQUIPMENT_ESC_STATUS_ID 1034 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_equipment_esc_Status_cxx_iface; +#endif + +struct uavcan_equipment_esc_Status { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_equipment_esc_Status_cxx_iface; +#endif + uint32_t error_count; + float voltage; + float current; + float temperature; + int32_t rpm; + uint8_t power_rating_pct; + uint8_t esc_index; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_equipment_esc_Status_encode(struct uavcan_equipment_esc_Status* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_equipment_esc_Status_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_esc_Status* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_equipment_esc_Status_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_esc_Status* msg, bool tao); +static inline bool _uavcan_equipment_esc_Status_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_esc_Status* msg, bool tao); +void _uavcan_equipment_esc_Status_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_esc_Status* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->error_count); + *bit_ofs += 32; + { + uint16_t float16_val = canardConvertNativeFloatToFloat16(msg->voltage); + canardEncodeScalar(buffer, *bit_ofs, 16, &float16_val); + } + *bit_ofs += 16; + { + uint16_t float16_val = canardConvertNativeFloatToFloat16(msg->current); + canardEncodeScalar(buffer, *bit_ofs, 16, &float16_val); + } + *bit_ofs += 16; + { + uint16_t float16_val = canardConvertNativeFloatToFloat16(msg->temperature); + canardEncodeScalar(buffer, *bit_ofs, 16, &float16_val); + } + *bit_ofs += 16; + canardEncodeScalar(buffer, *bit_ofs, 18, &msg->rpm); + *bit_ofs += 18; + canardEncodeScalar(buffer, *bit_ofs, 7, &msg->power_rating_pct); + *bit_ofs += 7; + canardEncodeScalar(buffer, *bit_ofs, 5, &msg->esc_index); + *bit_ofs += 5; +} + +/* + decode uavcan_equipment_esc_Status, return true on failure, false on success +*/ +bool _uavcan_equipment_esc_Status_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_esc_Status* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->error_count); + *bit_ofs += 32; + + { + uint16_t float16_val; + canardDecodeScalar(transfer, *bit_ofs, 16, true, &float16_val); + msg->voltage = canardConvertFloat16ToNativeFloat(float16_val); + } + *bit_ofs += 16; + + { + uint16_t float16_val; + canardDecodeScalar(transfer, *bit_ofs, 16, true, &float16_val); + msg->current = canardConvertFloat16ToNativeFloat(float16_val); + } + *bit_ofs += 16; + + { + uint16_t float16_val; + canardDecodeScalar(transfer, *bit_ofs, 16, true, &float16_val); + msg->temperature = canardConvertFloat16ToNativeFloat(float16_val); + } + *bit_ofs += 16; + + canardDecodeScalar(transfer, *bit_ofs, 18, true, &msg->rpm); + *bit_ofs += 18; + + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->power_rating_pct); + *bit_ofs += 7; + + canardDecodeScalar(transfer, *bit_ofs, 5, false, &msg->esc_index); + *bit_ofs += 5; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_esc_Status sample_uavcan_equipment_esc_Status_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_equipment_esc_Status, UAVCAN_EQUIPMENT_ESC_STATUS_ID, UAVCAN_EQUIPMENT_ESC_STATUS_SIGNATURE, UAVCAN_EQUIPMENT_ESC_STATUS_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.StatusExtended.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.StatusExtended.h new file mode 100644 index 000000000..bbf9d4ef3 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.esc.StatusExtended.h @@ -0,0 +1,101 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_EQUIPMENT_ESC_STATUSEXTENDED_MAX_SIZE 7 +#define UAVCAN_EQUIPMENT_ESC_STATUSEXTENDED_SIGNATURE (0x2DC203C50960EDCULL) +#define UAVCAN_EQUIPMENT_ESC_STATUSEXTENDED_ID 1036 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_equipment_esc_StatusExtended_cxx_iface; +#endif + +struct uavcan_equipment_esc_StatusExtended { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_equipment_esc_StatusExtended_cxx_iface; +#endif + uint8_t input_pct; + uint8_t output_pct; + int16_t motor_temperature_degC; + uint16_t motor_angle; + uint32_t status_flags; + uint8_t esc_index; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_equipment_esc_StatusExtended_encode(struct uavcan_equipment_esc_StatusExtended* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_equipment_esc_StatusExtended_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_esc_StatusExtended* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_equipment_esc_StatusExtended_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_esc_StatusExtended* msg, bool tao); +static inline bool _uavcan_equipment_esc_StatusExtended_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_esc_StatusExtended* msg, bool tao); +void _uavcan_equipment_esc_StatusExtended_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_esc_StatusExtended* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 7, &msg->input_pct); + *bit_ofs += 7; + canardEncodeScalar(buffer, *bit_ofs, 7, &msg->output_pct); + *bit_ofs += 7; + canardEncodeScalar(buffer, *bit_ofs, 9, &msg->motor_temperature_degC); + *bit_ofs += 9; + canardEncodeScalar(buffer, *bit_ofs, 9, &msg->motor_angle); + *bit_ofs += 9; + canardEncodeScalar(buffer, *bit_ofs, 19, &msg->status_flags); + *bit_ofs += 19; + canardEncodeScalar(buffer, *bit_ofs, 5, &msg->esc_index); + *bit_ofs += 5; +} + +/* + decode uavcan_equipment_esc_StatusExtended, return true on failure, false on success +*/ +bool _uavcan_equipment_esc_StatusExtended_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_esc_StatusExtended* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->input_pct); + *bit_ofs += 7; + + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->output_pct); + *bit_ofs += 7; + + canardDecodeScalar(transfer, *bit_ofs, 9, true, &msg->motor_temperature_degC); + *bit_ofs += 9; + + canardDecodeScalar(transfer, *bit_ofs, 9, false, &msg->motor_angle); + *bit_ofs += 9; + + canardDecodeScalar(transfer, *bit_ofs, 19, false, &msg->status_flags); + *bit_ofs += 19; + + canardDecodeScalar(transfer, *bit_ofs, 5, false, &msg->esc_index); + *bit_ofs += 5; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_esc_StatusExtended sample_uavcan_equipment_esc_StatusExtended_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_equipment_esc_StatusExtended, UAVCAN_EQUIPMENT_ESC_STATUSEXTENDED_ID, UAVCAN_EQUIPMENT_ESC_STATUSEXTENDED_SIGNATURE, UAVCAN_EQUIPMENT_ESC_STATUSEXTENDED_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.safety.ArmingStatus.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.safety.ArmingStatus.h new file mode 100644 index 000000000..d6232ae22 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.equipment.safety.ArmingStatus.h @@ -0,0 +1,74 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_MAX_SIZE 1 +#define UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_SIGNATURE (0x8700F375556A8003ULL) +#define UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_ID 1100 + +#define UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_STATUS_DISARMED 0 +#define UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_STATUS_FULLY_ARMED 255 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_equipment_safety_ArmingStatus_cxx_iface; +#endif + +struct uavcan_equipment_safety_ArmingStatus { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_equipment_safety_ArmingStatus_cxx_iface; +#endif + uint8_t status; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_equipment_safety_ArmingStatus_encode(struct uavcan_equipment_safety_ArmingStatus* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_equipment_safety_ArmingStatus_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_safety_ArmingStatus* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_equipment_safety_ArmingStatus_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_safety_ArmingStatus* msg, bool tao); +static inline bool _uavcan_equipment_safety_ArmingStatus_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_safety_ArmingStatus* msg, bool tao); +void _uavcan_equipment_safety_ArmingStatus_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_equipment_safety_ArmingStatus* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->status); + *bit_ofs += 8; +} + +/* + decode uavcan_equipment_safety_ArmingStatus, return true on failure, false on success +*/ +bool _uavcan_equipment_safety_ArmingStatus_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_equipment_safety_ArmingStatus* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->status); + *bit_ofs += 8; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_safety_ArmingStatus sample_uavcan_equipment_safety_ArmingStatus_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_equipment_safety_ArmingStatus, UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_ID, UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_SIGNATURE, UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo.h new file mode 100644 index 000000000..242c1bfe3 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_GETNODEINFO_ID 1 +#define UAVCAN_PROTOCOL_GETNODEINFO_SIGNATURE (0xEE468A8121C46A9EULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_GetNodeInfo, UAVCAN_PROTOCOL_GETNODEINFO_ID, UAVCAN_PROTOCOL_GETNODEINFO_SIGNATURE, UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo_req.h new file mode 100644 index 000000000..c778974ad --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo_req.h @@ -0,0 +1,64 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_MAX_SIZE 0 +#define UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_SIGNATURE (0xEE468A8121C46A9EULL) +#define UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_ID 1 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_GetNodeInfo_cxx_iface; +#endif + +struct uavcan_protocol_GetNodeInfoRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_GetNodeInfo_cxx_iface; +#endif +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_GetNodeInfoRequest_encode(struct uavcan_protocol_GetNodeInfoRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_GetNodeInfoRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_GetNodeInfoRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_GetNodeInfoRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_GetNodeInfoRequest* msg, bool tao); +static inline bool _uavcan_protocol_GetNodeInfoRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_GetNodeInfoRequest* msg, bool tao); +void _uavcan_protocol_GetNodeInfoRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_GetNodeInfoRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + +} + +/* + decode uavcan_protocol_GetNodeInfoRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_GetNodeInfoRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_GetNodeInfoRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_GetNodeInfoRequest sample_uavcan_protocol_GetNodeInfoRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo_res.h new file mode 100644 index 000000000..0fe1abf83 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.GetNodeInfo_res.h @@ -0,0 +1,110 @@ +#pragma once +#include +#include +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE 377 +#define UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_SIGNATURE (0xEE468A8121C46A9EULL) +#define UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_ID 1 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_GetNodeInfo_cxx_iface; +#endif + +struct uavcan_protocol_GetNodeInfoResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_GetNodeInfo_cxx_iface; +#endif + struct uavcan_protocol_NodeStatus status; + struct uavcan_protocol_SoftwareVersion software_version; + struct uavcan_protocol_HardwareVersion hardware_version; + struct { uint8_t len; uint8_t data[80]; }name; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_GetNodeInfoResponse_encode(struct uavcan_protocol_GetNodeInfoResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_GetNodeInfoResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_GetNodeInfoResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_GetNodeInfoResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_GetNodeInfoResponse* msg, bool tao); +static inline bool _uavcan_protocol_GetNodeInfoResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_GetNodeInfoResponse* msg, bool tao); +void _uavcan_protocol_GetNodeInfoResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_GetNodeInfoResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + _uavcan_protocol_NodeStatus_encode(buffer, bit_ofs, &msg->status, false); + _uavcan_protocol_SoftwareVersion_encode(buffer, bit_ofs, &msg->software_version, false); + _uavcan_protocol_HardwareVersion_encode(buffer, bit_ofs, &msg->hardware_version, false); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t name_len = msg->name.len > 80 ? 80 : msg->name.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 7, &name_len); + *bit_ofs += 7; + } + for (size_t i=0; i < name_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->name.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_GetNodeInfoResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_GetNodeInfoResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_GetNodeInfoResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (_uavcan_protocol_NodeStatus_decode(transfer, bit_ofs, &msg->status, false)) {return true;} + + if (_uavcan_protocol_SoftwareVersion_decode(transfer, bit_ofs, &msg->software_version, false)) {return true;} + + if (_uavcan_protocol_HardwareVersion_decode(transfer, bit_ofs, &msg->hardware_version, false)) {return true;} + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->name.len); + *bit_ofs += 7; + } else { + msg->name.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->name.len > 80) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->name.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->name.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_GetNodeInfoResponse sample_uavcan_protocol_GetNodeInfoResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.HardwareVersion.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.HardwareVersion.h new file mode 100644 index 000000000..a3d14b3e1 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.HardwareVersion.h @@ -0,0 +1,110 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_HARDWAREVERSION_MAX_SIZE 274 +#define UAVCAN_PROTOCOL_HARDWAREVERSION_SIGNATURE (0xAD5C4C933F4A0C4ULL) + + +struct uavcan_protocol_HardwareVersion { + uint8_t major; + uint8_t minor; + uint8_t unique_id[16]; + struct { uint8_t len; uint8_t data[255]; }certificate_of_authenticity; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_HardwareVersion_encode(struct uavcan_protocol_HardwareVersion* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_HardwareVersion_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_HardwareVersion* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_HardwareVersion_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_HardwareVersion* msg, bool tao); +static inline bool _uavcan_protocol_HardwareVersion_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_HardwareVersion* msg, bool tao); +void _uavcan_protocol_HardwareVersion_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_HardwareVersion* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->major); + *bit_ofs += 8; + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->minor); + *bit_ofs += 8; + for (size_t i=0; i < 16; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->unique_id[i]); + *bit_ofs += 8; + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t certificate_of_authenticity_len = msg->certificate_of_authenticity.len > 255 ? 255 : msg->certificate_of_authenticity.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 8, &certificate_of_authenticity_len); + *bit_ofs += 8; + } + for (size_t i=0; i < certificate_of_authenticity_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->certificate_of_authenticity.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_HardwareVersion, return true on failure, false on success +*/ +bool _uavcan_protocol_HardwareVersion_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_HardwareVersion* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->major); + *bit_ofs += 8; + + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->minor); + *bit_ofs += 8; + + for (size_t i=0; i < 16; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->unique_id[i]); + *bit_ofs += 8; + } + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->certificate_of_authenticity.len); + *bit_ofs += 8; + } else { + msg->certificate_of_authenticity.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->certificate_of_authenticity.len > 255) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->certificate_of_authenticity.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->certificate_of_authenticity.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_HardwareVersion sample_uavcan_protocol_HardwareVersion_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.NodeStatus.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.NodeStatus.h new file mode 100644 index 000000000..854469ddf --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.NodeStatus.h @@ -0,0 +1,108 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_NODESTATUS_MAX_SIZE 7 +#define UAVCAN_PROTOCOL_NODESTATUS_SIGNATURE (0xF0868D0C1A7C6F1ULL) +#define UAVCAN_PROTOCOL_NODESTATUS_ID 341 + +#define UAVCAN_PROTOCOL_NODESTATUS_MAX_BROADCASTING_PERIOD_MS 1000 +#define UAVCAN_PROTOCOL_NODESTATUS_MIN_BROADCASTING_PERIOD_MS 2 +#define UAVCAN_PROTOCOL_NODESTATUS_OFFLINE_TIMEOUT_MS 3000 +#define UAVCAN_PROTOCOL_NODESTATUS_HEALTH_OK 0 +#define UAVCAN_PROTOCOL_NODESTATUS_HEALTH_WARNING 1 +#define UAVCAN_PROTOCOL_NODESTATUS_HEALTH_ERROR 2 +#define UAVCAN_PROTOCOL_NODESTATUS_HEALTH_CRITICAL 3 +#define UAVCAN_PROTOCOL_NODESTATUS_MODE_OPERATIONAL 0 +#define UAVCAN_PROTOCOL_NODESTATUS_MODE_INITIALIZATION 1 +#define UAVCAN_PROTOCOL_NODESTATUS_MODE_MAINTENANCE 2 +#define UAVCAN_PROTOCOL_NODESTATUS_MODE_SOFTWARE_UPDATE 3 +#define UAVCAN_PROTOCOL_NODESTATUS_MODE_OFFLINE 7 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_NodeStatus_cxx_iface; +#endif + +struct uavcan_protocol_NodeStatus { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_NodeStatus_cxx_iface; +#endif + uint32_t uptime_sec; + uint8_t health; + uint8_t mode; + uint8_t sub_mode; + uint16_t vendor_specific_status_code; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_NodeStatus_encode(struct uavcan_protocol_NodeStatus* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_NodeStatus_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_NodeStatus* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_NodeStatus_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_NodeStatus* msg, bool tao); +static inline bool _uavcan_protocol_NodeStatus_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_NodeStatus* msg, bool tao); +void _uavcan_protocol_NodeStatus_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_NodeStatus* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->uptime_sec); + *bit_ofs += 32; + canardEncodeScalar(buffer, *bit_ofs, 2, &msg->health); + *bit_ofs += 2; + canardEncodeScalar(buffer, *bit_ofs, 3, &msg->mode); + *bit_ofs += 3; + canardEncodeScalar(buffer, *bit_ofs, 3, &msg->sub_mode); + *bit_ofs += 3; + canardEncodeScalar(buffer, *bit_ofs, 16, &msg->vendor_specific_status_code); + *bit_ofs += 16; +} + +/* + decode uavcan_protocol_NodeStatus, return true on failure, false on success +*/ +bool _uavcan_protocol_NodeStatus_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_NodeStatus* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->uptime_sec); + *bit_ofs += 32; + + canardDecodeScalar(transfer, *bit_ofs, 2, false, &msg->health); + *bit_ofs += 2; + + canardDecodeScalar(transfer, *bit_ofs, 3, false, &msg->mode); + *bit_ofs += 3; + + canardDecodeScalar(transfer, *bit_ofs, 3, false, &msg->sub_mode); + *bit_ofs += 3; + + canardDecodeScalar(transfer, *bit_ofs, 16, false, &msg->vendor_specific_status_code); + *bit_ofs += 16; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_NodeStatus sample_uavcan_protocol_NodeStatus_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_protocol_NodeStatus, UAVCAN_PROTOCOL_NODESTATUS_ID, UAVCAN_PROTOCOL_NODESTATUS_SIGNATURE, UAVCAN_PROTOCOL_NODESTATUS_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode.h new file mode 100644 index 000000000..db56f6f53 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_RESTARTNODE_ID 5 +#define UAVCAN_PROTOCOL_RESTARTNODE_SIGNATURE (0x569E05394A3017F0ULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_RestartNode, UAVCAN_PROTOCOL_RESTARTNODE_ID, UAVCAN_PROTOCOL_RESTARTNODE_SIGNATURE, UAVCAN_PROTOCOL_RESTARTNODE_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_RESTARTNODE_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode_req.h new file mode 100644 index 000000000..953302b7d --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode_req.h @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_RESTARTNODE_REQUEST_MAX_SIZE 5 +#define UAVCAN_PROTOCOL_RESTARTNODE_REQUEST_SIGNATURE (0x569E05394A3017F0ULL) +#define UAVCAN_PROTOCOL_RESTARTNODE_REQUEST_ID 5 + +#define UAVCAN_PROTOCOL_RESTARTNODE_REQUEST_MAGIC_NUMBER 742196058910 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_RestartNode_cxx_iface; +#endif + +struct uavcan_protocol_RestartNodeRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_RestartNode_cxx_iface; +#endif + uint64_t magic_number; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_RestartNodeRequest_encode(struct uavcan_protocol_RestartNodeRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_RestartNodeRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_RestartNodeRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_RestartNodeRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_RestartNodeRequest* msg, bool tao); +static inline bool _uavcan_protocol_RestartNodeRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_RestartNodeRequest* msg, bool tao); +void _uavcan_protocol_RestartNodeRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_RestartNodeRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 40, &msg->magic_number); + *bit_ofs += 40; +} + +/* + decode uavcan_protocol_RestartNodeRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_RestartNodeRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_RestartNodeRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 40, false, &msg->magic_number); + *bit_ofs += 40; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_RestartNodeRequest sample_uavcan_protocol_RestartNodeRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode_res.h new file mode 100644 index 000000000..fe653f861 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.RestartNode_res.h @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_RESTARTNODE_RESPONSE_MAX_SIZE 1 +#define UAVCAN_PROTOCOL_RESTARTNODE_RESPONSE_SIGNATURE (0x569E05394A3017F0ULL) +#define UAVCAN_PROTOCOL_RESTARTNODE_RESPONSE_ID 5 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_RestartNode_cxx_iface; +#endif + +struct uavcan_protocol_RestartNodeResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_RestartNode_cxx_iface; +#endif + bool ok; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_RestartNodeResponse_encode(struct uavcan_protocol_RestartNodeResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_RestartNodeResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_RestartNodeResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_RestartNodeResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_RestartNodeResponse* msg, bool tao); +static inline bool _uavcan_protocol_RestartNodeResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_RestartNodeResponse* msg, bool tao); +void _uavcan_protocol_RestartNodeResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_RestartNodeResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 1, &msg->ok); + *bit_ofs += 1; +} + +/* + decode uavcan_protocol_RestartNodeResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_RestartNodeResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_RestartNodeResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 1, false, &msg->ok); + *bit_ofs += 1; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_RestartNodeResponse sample_uavcan_protocol_RestartNodeResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.SoftwareVersion.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.SoftwareVersion.h new file mode 100644 index 000000000..c965cc0c0 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.SoftwareVersion.h @@ -0,0 +1,90 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_SOFTWAREVERSION_MAX_SIZE 15 +#define UAVCAN_PROTOCOL_SOFTWAREVERSION_SIGNATURE (0xDD46FD376527FEA1ULL) + +#define UAVCAN_PROTOCOL_SOFTWAREVERSION_OPTIONAL_FIELD_FLAG_VCS_COMMIT 1 +#define UAVCAN_PROTOCOL_SOFTWAREVERSION_OPTIONAL_FIELD_FLAG_IMAGE_CRC 2 + + +struct uavcan_protocol_SoftwareVersion { + uint8_t major; + uint8_t minor; + uint8_t optional_field_flags; + uint32_t vcs_commit; + uint64_t image_crc; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_SoftwareVersion_encode(struct uavcan_protocol_SoftwareVersion* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_SoftwareVersion_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_SoftwareVersion* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_SoftwareVersion_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_SoftwareVersion* msg, bool tao); +static inline bool _uavcan_protocol_SoftwareVersion_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_SoftwareVersion* msg, bool tao); +void _uavcan_protocol_SoftwareVersion_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_SoftwareVersion* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->major); + *bit_ofs += 8; + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->minor); + *bit_ofs += 8; + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->optional_field_flags); + *bit_ofs += 8; + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->vcs_commit); + *bit_ofs += 32; + canardEncodeScalar(buffer, *bit_ofs, 64, &msg->image_crc); + *bit_ofs += 64; +} + +/* + decode uavcan_protocol_SoftwareVersion, return true on failure, false on success +*/ +bool _uavcan_protocol_SoftwareVersion_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_SoftwareVersion* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->major); + *bit_ofs += 8; + + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->minor); + *bit_ofs += 8; + + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->optional_field_flags); + *bit_ofs += 8; + + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->vcs_commit); + *bit_ofs += 32; + + canardDecodeScalar(transfer, *bit_ofs, 64, false, &msg->image_crc); + *bit_ofs += 64; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_SoftwareVersion sample_uavcan_protocol_SoftwareVersion_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.KeyValue.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.KeyValue.h new file mode 100644 index 000000000..acd1aa235 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.KeyValue.h @@ -0,0 +1,102 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DEBUG_KEYVALUE_MAX_SIZE 63 +#define UAVCAN_PROTOCOL_DEBUG_KEYVALUE_SIGNATURE (0xE02F25D6E0C98AE0ULL) +#define UAVCAN_PROTOCOL_DEBUG_KEYVALUE_ID 16370 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_debug_KeyValue_cxx_iface; +#endif + +struct uavcan_protocol_debug_KeyValue { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_debug_KeyValue_cxx_iface; +#endif + float value; + struct { uint8_t len; uint8_t data[58]; }key; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_debug_KeyValue_encode(struct uavcan_protocol_debug_KeyValue* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_debug_KeyValue_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_debug_KeyValue* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_debug_KeyValue_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_debug_KeyValue* msg, bool tao); +static inline bool _uavcan_protocol_debug_KeyValue_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_debug_KeyValue* msg, bool tao); +void _uavcan_protocol_debug_KeyValue_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_debug_KeyValue* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->value); + *bit_ofs += 32; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t key_len = msg->key.len > 58 ? 58 : msg->key.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 6, &key_len); + *bit_ofs += 6; + } + for (size_t i=0; i < key_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->key.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_debug_KeyValue, return true on failure, false on success +*/ +bool _uavcan_protocol_debug_KeyValue_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_debug_KeyValue* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, true, &msg->value); + *bit_ofs += 32; + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 6, false, &msg->key.len); + *bit_ofs += 6; + } else { + msg->key.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->key.len > 58) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->key.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->key.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_debug_KeyValue sample_uavcan_protocol_debug_KeyValue_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_protocol_debug_KeyValue, UAVCAN_PROTOCOL_DEBUG_KEYVALUE_ID, UAVCAN_PROTOCOL_DEBUG_KEYVALUE_SIGNATURE, UAVCAN_PROTOCOL_DEBUG_KEYVALUE_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.LogLevel.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.LogLevel.h new file mode 100644 index 000000000..237b8a345 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.LogLevel.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_MAX_SIZE 1 +#define UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_SIGNATURE (0x711BF141AF572346ULL) + +#define UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_DEBUG 0 +#define UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_INFO 1 +#define UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_WARNING 2 +#define UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR 3 + + +struct uavcan_protocol_debug_LogLevel { + uint8_t value; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_debug_LogLevel_encode(struct uavcan_protocol_debug_LogLevel* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_debug_LogLevel_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_debug_LogLevel* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_debug_LogLevel_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_debug_LogLevel* msg, bool tao); +static inline bool _uavcan_protocol_debug_LogLevel_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_debug_LogLevel* msg, bool tao); +void _uavcan_protocol_debug_LogLevel_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_debug_LogLevel* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 3, &msg->value); + *bit_ofs += 3; +} + +/* + decode uavcan_protocol_debug_LogLevel, return true on failure, false on success +*/ +bool _uavcan_protocol_debug_LogLevel_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_debug_LogLevel* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 3, false, &msg->value); + *bit_ofs += 3; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_debug_LogLevel sample_uavcan_protocol_debug_LogLevel_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.LogMessage.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.LogMessage.h new file mode 100644 index 000000000..034ba674e --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.debug.LogMessage.h @@ -0,0 +1,125 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_MAX_SIZE 123 +#define UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_SIGNATURE (0xD654A48E0C049D75ULL) +#define UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_ID 16383 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_debug_LogMessage_cxx_iface; +#endif + +struct uavcan_protocol_debug_LogMessage { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_debug_LogMessage_cxx_iface; +#endif + struct uavcan_protocol_debug_LogLevel level; + struct { uint8_t len; uint8_t data[31]; }source; + struct { uint8_t len; uint8_t data[90]; }text; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_debug_LogMessage_encode(struct uavcan_protocol_debug_LogMessage* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_debug_LogMessage_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_debug_LogMessage* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_debug_LogMessage_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_debug_LogMessage* msg, bool tao); +static inline bool _uavcan_protocol_debug_LogMessage_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_debug_LogMessage* msg, bool tao); +void _uavcan_protocol_debug_LogMessage_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_debug_LogMessage* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + _uavcan_protocol_debug_LogLevel_encode(buffer, bit_ofs, &msg->level, false); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t source_len = msg->source.len > 31 ? 31 : msg->source.len; +#pragma GCC diagnostic pop + canardEncodeScalar(buffer, *bit_ofs, 5, &source_len); + *bit_ofs += 5; + for (size_t i=0; i < source_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->source.data[i]); + *bit_ofs += 8; + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t text_len = msg->text.len > 90 ? 90 : msg->text.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 7, &text_len); + *bit_ofs += 7; + } + for (size_t i=0; i < text_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->text.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_debug_LogMessage, return true on failure, false on success +*/ +bool _uavcan_protocol_debug_LogMessage_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_debug_LogMessage* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (_uavcan_protocol_debug_LogLevel_decode(transfer, bit_ofs, &msg->level, false)) {return true;} + + canardDecodeScalar(transfer, *bit_ofs, 5, false, &msg->source.len); + *bit_ofs += 5; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->source.len > 31) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->source.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->source.data[i]); + *bit_ofs += 8; + } + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->text.len); + *bit_ofs += 7; + } else { + msg->text.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->text.len > 90) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->text.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->text.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_debug_LogMessage sample_uavcan_protocol_debug_LogMessage_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_protocol_debug_LogMessage, UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_ID, UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_SIGNATURE, UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.Allocation.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.Allocation.h new file mode 100644 index 000000000..b68f930ce --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.Allocation.h @@ -0,0 +1,116 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_SIZE 18 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_SIGNATURE (0xB2A812620A11D40ULL) +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID 1 + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_REQUEST_PERIOD_MS 1000 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MIN_REQUEST_PERIOD_MS 600 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_FOLLOWUP_DELAY_MS 400 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MIN_FOLLOWUP_DELAY_MS 0 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_FOLLOWUP_TIMEOUT_MS 500 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_LENGTH_OF_UNIQUE_ID_IN_REQUEST 6 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ANY_NODE_ID 0 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_dynamic_node_id_Allocation_cxx_iface; +#endif + +struct uavcan_protocol_dynamic_node_id_Allocation { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_dynamic_node_id_Allocation_cxx_iface; +#endif + uint8_t node_id; + bool first_part_of_unique_id; + struct { uint8_t len; uint8_t data[16]; }unique_id; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_dynamic_node_id_Allocation_encode(struct uavcan_protocol_dynamic_node_id_Allocation* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_dynamic_node_id_Allocation_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_Allocation* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_dynamic_node_id_Allocation_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_Allocation* msg, bool tao); +static inline bool _uavcan_protocol_dynamic_node_id_Allocation_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_Allocation* msg, bool tao); +void _uavcan_protocol_dynamic_node_id_Allocation_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_Allocation* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 7, &msg->node_id); + *bit_ofs += 7; + canardEncodeScalar(buffer, *bit_ofs, 1, &msg->first_part_of_unique_id); + *bit_ofs += 1; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t unique_id_len = msg->unique_id.len > 16 ? 16 : msg->unique_id.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 5, &unique_id_len); + *bit_ofs += 5; + } + for (size_t i=0; i < unique_id_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->unique_id.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_dynamic_node_id_Allocation, return true on failure, false on success +*/ +bool _uavcan_protocol_dynamic_node_id_Allocation_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_Allocation* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->node_id); + *bit_ofs += 7; + + canardDecodeScalar(transfer, *bit_ofs, 1, false, &msg->first_part_of_unique_id); + *bit_ofs += 1; + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 5, false, &msg->unique_id.len); + *bit_ofs += 5; + } else { + msg->unique_id.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->unique_id.len > 16) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->unique_id.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->unique_id.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_Allocation sample_uavcan_protocol_dynamic_node_id_Allocation_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_protocol_dynamic_node_id_Allocation, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_SIGNATURE, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries.h new file mode 100644 index 000000000..a3ad5688b --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_ID 30 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_SIGNATURE (0x8032C7097B48A3CCULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_dynamic_node_id_server_AppendEntries, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_ID, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_SIGNATURE, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries_req.h new file mode 100644 index 000000000..0676005fc --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries_req.h @@ -0,0 +1,130 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_REQUEST_MAX_SIZE 32 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_REQUEST_SIGNATURE (0x8032C7097B48A3CCULL) +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_REQUEST_ID 30 + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_REQUEST_DEFAULT_MIN_ELECTION_TIMEOUT_MS 2000 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_REQUEST_DEFAULT_MAX_ELECTION_TIMEOUT_MS 4000 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_dynamic_node_id_server_AppendEntries_cxx_iface; +#endif + +struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_dynamic_node_id_server_AppendEntries_cxx_iface; +#endif + uint32_t term; + uint32_t prev_log_term; + uint8_t prev_log_index; + uint8_t leader_commit; + struct { uint8_t len; struct uavcan_protocol_dynamic_node_id_server_Entry data[1]; }entries; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_encode(struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest* msg, bool tao); +static inline bool _uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest* msg, bool tao); +void _uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->term); + *bit_ofs += 32; + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->prev_log_term); + *bit_ofs += 32; + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->prev_log_index); + *bit_ofs += 8; + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->leader_commit); + *bit_ofs += 8; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t entries_len = msg->entries.len > 1 ? 1 : msg->entries.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 1, &entries_len); + *bit_ofs += 1; + } + for (size_t i=0; i < entries_len; i++) { + _uavcan_protocol_dynamic_node_id_server_Entry_encode(buffer, bit_ofs, &msg->entries.data[i], false); + } +} + +/* + decode uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->term); + *bit_ofs += 32; + + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->prev_log_term); + *bit_ofs += 32; + + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->prev_log_index); + *bit_ofs += 8; + + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->leader_commit); + *bit_ofs += 8; + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 1, false, &msg->entries.len); + *bit_ofs += 1; + } + + + if (tao) { + msg->entries.len = 0; + size_t max_len = 1; + uint32_t max_bits = (transfer->payload_len*8)-7; // TAO elements must be >= 8 bits + while (max_bits > *bit_ofs) { + if (!max_len-- || _uavcan_protocol_dynamic_node_id_server_Entry_decode(transfer, bit_ofs, &msg->entries.data[msg->entries.len], false)) {return true;} + msg->entries.len++; + } + } else { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->entries.len > 1) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->entries.len; i++) { + if (_uavcan_protocol_dynamic_node_id_server_Entry_decode(transfer, bit_ofs, &msg->entries.data[i], false)) {return true;} + } + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest sample_uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries_res.h new file mode 100644 index 000000000..bd99a0acf --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.AppendEntries_res.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_RESPONSE_MAX_SIZE 5 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_RESPONSE_SIGNATURE (0x8032C7097B48A3CCULL) +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_RESPONSE_ID 30 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_dynamic_node_id_server_AppendEntries_cxx_iface; +#endif + +struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_dynamic_node_id_server_AppendEntries_cxx_iface; +#endif + uint32_t term; + bool success; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_encode(struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse* msg, bool tao); +static inline bool _uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse* msg, bool tao); +void _uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->term); + *bit_ofs += 32; + canardEncodeScalar(buffer, *bit_ofs, 1, &msg->success); + *bit_ofs += 1; +} + +/* + decode uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->term); + *bit_ofs += 32; + + canardDecodeScalar(transfer, *bit_ofs, 1, false, &msg->success); + *bit_ofs += 1; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse sample_uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.Discovery.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.Discovery.h new file mode 100644 index 000000000..8596b5eb6 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.Discovery.h @@ -0,0 +1,104 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_MAX_SIZE 7 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_SIGNATURE (0x821AE2F525F69F21ULL) +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_ID 390 + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_BROADCASTING_PERIOD_MS 1000 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_dynamic_node_id_server_Discovery_cxx_iface; +#endif + +struct uavcan_protocol_dynamic_node_id_server_Discovery { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_dynamic_node_id_server_Discovery_cxx_iface; +#endif + uint8_t configured_cluster_size; + struct { uint8_t len; uint8_t data[5]; }known_nodes; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_Discovery_encode(struct uavcan_protocol_dynamic_node_id_server_Discovery* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_dynamic_node_id_server_Discovery_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_Discovery* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_dynamic_node_id_server_Discovery_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_Discovery* msg, bool tao); +static inline bool _uavcan_protocol_dynamic_node_id_server_Discovery_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_Discovery* msg, bool tao); +void _uavcan_protocol_dynamic_node_id_server_Discovery_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_Discovery* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->configured_cluster_size); + *bit_ofs += 8; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t known_nodes_len = msg->known_nodes.len > 5 ? 5 : msg->known_nodes.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 3, &known_nodes_len); + *bit_ofs += 3; + } + for (size_t i=0; i < known_nodes_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->known_nodes.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_dynamic_node_id_server_Discovery, return true on failure, false on success +*/ +bool _uavcan_protocol_dynamic_node_id_server_Discovery_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_Discovery* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->configured_cluster_size); + *bit_ofs += 8; + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 3, false, &msg->known_nodes.len); + *bit_ofs += 3; + } else { + msg->known_nodes.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->known_nodes.len > 5) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->known_nodes.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->known_nodes.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_Discovery sample_uavcan_protocol_dynamic_node_id_server_Discovery_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +BROADCAST_MESSAGE_CXX_IFACE(uavcan_protocol_dynamic_node_id_server_Discovery, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_ID, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_SIGNATURE, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_MAX_SIZE); +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.Entry.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.Entry.h new file mode 100644 index 000000000..bbb05d1b6 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.Entry.h @@ -0,0 +1,82 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_ENTRY_MAX_SIZE 21 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_ENTRY_SIGNATURE (0x7FAA779D64FA75C2ULL) + + +struct uavcan_protocol_dynamic_node_id_server_Entry { + uint32_t term; + uint8_t unique_id[16]; + uint8_t node_id; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_Entry_encode(struct uavcan_protocol_dynamic_node_id_server_Entry* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_dynamic_node_id_server_Entry_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_Entry* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_dynamic_node_id_server_Entry_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_Entry* msg, bool tao); +static inline bool _uavcan_protocol_dynamic_node_id_server_Entry_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_Entry* msg, bool tao); +void _uavcan_protocol_dynamic_node_id_server_Entry_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_Entry* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->term); + *bit_ofs += 32; + for (size_t i=0; i < 16; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->unique_id[i]); + *bit_ofs += 8; + } + *bit_ofs += 1; + canardEncodeScalar(buffer, *bit_ofs, 7, &msg->node_id); + *bit_ofs += 7; +} + +/* + decode uavcan_protocol_dynamic_node_id_server_Entry, return true on failure, false on success +*/ +bool _uavcan_protocol_dynamic_node_id_server_Entry_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_Entry* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->term); + *bit_ofs += 32; + + for (size_t i=0; i < 16; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->unique_id[i]); + *bit_ofs += 8; + } + + *bit_ofs += 1; + + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->node_id); + *bit_ofs += 7; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_Entry sample_uavcan_protocol_dynamic_node_id_server_Entry_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote.h new file mode 100644 index 000000000..8a4da271d --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_ID 31 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_SIGNATURE (0xCDDE07BB89A56356ULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_dynamic_node_id_server_RequestVote, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_ID, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_SIGNATURE, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote_req.h new file mode 100644 index 000000000..c51d44205 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote_req.h @@ -0,0 +1,82 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_REQUEST_MAX_SIZE 9 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_REQUEST_SIGNATURE (0xCDDE07BB89A56356ULL) +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_REQUEST_ID 31 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_dynamic_node_id_server_RequestVote_cxx_iface; +#endif + +struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_dynamic_node_id_server_RequestVote_cxx_iface; +#endif + uint32_t term; + uint32_t last_log_term; + uint8_t last_log_index; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_encode(struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest* msg, bool tao); +static inline bool _uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest* msg, bool tao); +void _uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->term); + *bit_ofs += 32; + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->last_log_term); + *bit_ofs += 32; + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->last_log_index); + *bit_ofs += 8; +} + +/* + decode uavcan_protocol_dynamic_node_id_server_RequestVoteRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->term); + *bit_ofs += 32; + + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->last_log_term); + *bit_ofs += 32; + + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->last_log_index); + *bit_ofs += 8; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest sample_uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote_res.h new file mode 100644 index 000000000..a1e79cacb --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.dynamic_node_id.server.RequestVote_res.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_RESPONSE_MAX_SIZE 5 +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_RESPONSE_SIGNATURE (0xCDDE07BB89A56356ULL) +#define UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_RESPONSE_ID 31 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_dynamic_node_id_server_RequestVote_cxx_iface; +#endif + +struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_dynamic_node_id_server_RequestVote_cxx_iface; +#endif + uint32_t term; + bool vote_granted; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_encode(struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse* msg, bool tao); +static inline bool _uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse* msg, bool tao); +void _uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->term); + *bit_ofs += 32; + canardEncodeScalar(buffer, *bit_ofs, 1, &msg->vote_granted); + *bit_ofs += 1; +} + +/* + decode uavcan_protocol_dynamic_node_id_server_RequestVoteResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->term); + *bit_ofs += 32; + + canardDecodeScalar(transfer, *bit_ofs, 1, false, &msg->vote_granted); + *bit_ofs += 1; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse sample_uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate.h new file mode 100644 index 000000000..ab0678e67 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_ID 40 +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_SIGNATURE (0xB7D725DF72724126ULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_file_BeginFirmwareUpdate, UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_ID, UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_SIGNATURE, UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate_req.h new file mode 100644 index 000000000..7e012776d --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate_req.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_REQUEST_MAX_SIZE 202 +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_REQUEST_SIGNATURE (0xB7D725DF72724126ULL) +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_REQUEST_ID 40 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_BeginFirmwareUpdate_cxx_iface; +#endif + +struct uavcan_protocol_file_BeginFirmwareUpdateRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_BeginFirmwareUpdate_cxx_iface; +#endif + uint8_t source_node_id; + struct uavcan_protocol_file_Path image_file_remote_path; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_BeginFirmwareUpdateRequest_encode(struct uavcan_protocol_file_BeginFirmwareUpdateRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_BeginFirmwareUpdateRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_BeginFirmwareUpdateRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_BeginFirmwareUpdateRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_BeginFirmwareUpdateRequest* msg, bool tao); +static inline bool _uavcan_protocol_file_BeginFirmwareUpdateRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_BeginFirmwareUpdateRequest* msg, bool tao); +void _uavcan_protocol_file_BeginFirmwareUpdateRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_BeginFirmwareUpdateRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->source_node_id); + *bit_ofs += 8; + _uavcan_protocol_file_Path_encode(buffer, bit_ofs, &msg->image_file_remote_path, tao); +} + +/* + decode uavcan_protocol_file_BeginFirmwareUpdateRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_file_BeginFirmwareUpdateRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_BeginFirmwareUpdateRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->source_node_id); + *bit_ofs += 8; + + if (_uavcan_protocol_file_Path_decode(transfer, bit_ofs, &msg->image_file_remote_path, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_BeginFirmwareUpdateRequest sample_uavcan_protocol_file_BeginFirmwareUpdateRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate_res.h new file mode 100644 index 000000000..510920d49 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.BeginFirmwareUpdate_res.h @@ -0,0 +1,106 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_MAX_SIZE 129 +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_SIGNATURE (0xB7D725DF72724126ULL) +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_ID 40 + +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_ERROR_OK 0 +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_ERROR_INVALID_MODE 1 +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_ERROR_IN_PROGRESS 2 +#define UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_ERROR_UNKNOWN 255 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_BeginFirmwareUpdate_cxx_iface; +#endif + +struct uavcan_protocol_file_BeginFirmwareUpdateResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_BeginFirmwareUpdate_cxx_iface; +#endif + uint8_t error; + struct { uint8_t len; uint8_t data[127]; }optional_error_message; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_BeginFirmwareUpdateResponse_encode(struct uavcan_protocol_file_BeginFirmwareUpdateResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_BeginFirmwareUpdateResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_BeginFirmwareUpdateResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_BeginFirmwareUpdateResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_BeginFirmwareUpdateResponse* msg, bool tao); +static inline bool _uavcan_protocol_file_BeginFirmwareUpdateResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_BeginFirmwareUpdateResponse* msg, bool tao); +void _uavcan_protocol_file_BeginFirmwareUpdateResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_BeginFirmwareUpdateResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->error); + *bit_ofs += 8; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t optional_error_message_len = msg->optional_error_message.len > 127 ? 127 : msg->optional_error_message.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 7, &optional_error_message_len); + *bit_ofs += 7; + } + for (size_t i=0; i < optional_error_message_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->optional_error_message.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_file_BeginFirmwareUpdateResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_file_BeginFirmwareUpdateResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_BeginFirmwareUpdateResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->error); + *bit_ofs += 8; + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->optional_error_message.len); + *bit_ofs += 7; + } else { + msg->optional_error_message.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->optional_error_message.len > 127) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->optional_error_message.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->optional_error_message.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_BeginFirmwareUpdateResponse sample_uavcan_protocol_file_BeginFirmwareUpdateResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete.h new file mode 100644 index 000000000..f248e1812 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_FILE_DELETE_ID 47 +#define UAVCAN_PROTOCOL_FILE_DELETE_SIGNATURE (0x78648C99170B47AAULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_file_Delete, UAVCAN_PROTOCOL_FILE_DELETE_ID, UAVCAN_PROTOCOL_FILE_DELETE_SIGNATURE, UAVCAN_PROTOCOL_FILE_DELETE_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_FILE_DELETE_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete_req.h new file mode 100644 index 000000000..2de184e04 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete_req.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_DELETE_REQUEST_MAX_SIZE 201 +#define UAVCAN_PROTOCOL_FILE_DELETE_REQUEST_SIGNATURE (0x78648C99170B47AAULL) +#define UAVCAN_PROTOCOL_FILE_DELETE_REQUEST_ID 47 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_Delete_cxx_iface; +#endif + +struct uavcan_protocol_file_DeleteRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_Delete_cxx_iface; +#endif + struct uavcan_protocol_file_Path path; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_DeleteRequest_encode(struct uavcan_protocol_file_DeleteRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_DeleteRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_DeleteRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_DeleteRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_DeleteRequest* msg, bool tao); +static inline bool _uavcan_protocol_file_DeleteRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_DeleteRequest* msg, bool tao); +void _uavcan_protocol_file_DeleteRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_DeleteRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + _uavcan_protocol_file_Path_encode(buffer, bit_ofs, &msg->path, tao); +} + +/* + decode uavcan_protocol_file_DeleteRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_file_DeleteRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_DeleteRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (_uavcan_protocol_file_Path_decode(transfer, bit_ofs, &msg->path, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_DeleteRequest sample_uavcan_protocol_file_DeleteRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete_res.h new file mode 100644 index 000000000..28e2d7402 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Delete_res.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_DELETE_RESPONSE_MAX_SIZE 2 +#define UAVCAN_PROTOCOL_FILE_DELETE_RESPONSE_SIGNATURE (0x78648C99170B47AAULL) +#define UAVCAN_PROTOCOL_FILE_DELETE_RESPONSE_ID 47 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_Delete_cxx_iface; +#endif + +struct uavcan_protocol_file_DeleteResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_Delete_cxx_iface; +#endif + struct uavcan_protocol_file_Error error; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_DeleteResponse_encode(struct uavcan_protocol_file_DeleteResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_DeleteResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_DeleteResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_DeleteResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_DeleteResponse* msg, bool tao); +static inline bool _uavcan_protocol_file_DeleteResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_DeleteResponse* msg, bool tao); +void _uavcan_protocol_file_DeleteResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_DeleteResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + _uavcan_protocol_file_Error_encode(buffer, bit_ofs, &msg->error, tao); +} + +/* + decode uavcan_protocol_file_DeleteResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_file_DeleteResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_DeleteResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (_uavcan_protocol_file_Error_decode(transfer, bit_ofs, &msg->error, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_DeleteResponse sample_uavcan_protocol_file_DeleteResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.EntryType.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.EntryType.h new file mode 100644 index 000000000..ae475bc60 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.EntryType.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_ENTRYTYPE_MAX_SIZE 1 +#define UAVCAN_PROTOCOL_FILE_ENTRYTYPE_SIGNATURE (0x6924572FBB2086E5ULL) + +#define UAVCAN_PROTOCOL_FILE_ENTRYTYPE_FLAG_FILE 1 +#define UAVCAN_PROTOCOL_FILE_ENTRYTYPE_FLAG_DIRECTORY 2 +#define UAVCAN_PROTOCOL_FILE_ENTRYTYPE_FLAG_SYMLINK 4 +#define UAVCAN_PROTOCOL_FILE_ENTRYTYPE_FLAG_READABLE 8 +#define UAVCAN_PROTOCOL_FILE_ENTRYTYPE_FLAG_WRITEABLE 16 + + +struct uavcan_protocol_file_EntryType { + uint8_t flags; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_EntryType_encode(struct uavcan_protocol_file_EntryType* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_EntryType_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_EntryType* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_EntryType_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_EntryType* msg, bool tao); +static inline bool _uavcan_protocol_file_EntryType_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_EntryType* msg, bool tao); +void _uavcan_protocol_file_EntryType_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_EntryType* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->flags); + *bit_ofs += 8; +} + +/* + decode uavcan_protocol_file_EntryType, return true on failure, false on success +*/ +bool _uavcan_protocol_file_EntryType_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_EntryType* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->flags); + *bit_ofs += 8; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_EntryType sample_uavcan_protocol_file_EntryType_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Error.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Error.h new file mode 100644 index 000000000..f5f6176b3 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Error.h @@ -0,0 +1,74 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_ERROR_MAX_SIZE 2 +#define UAVCAN_PROTOCOL_FILE_ERROR_SIGNATURE (0xA83071FFEA4FAE15ULL) + +#define UAVCAN_PROTOCOL_FILE_ERROR_OK 0 +#define UAVCAN_PROTOCOL_FILE_ERROR_UNKNOWN_ERROR 32767 +#define UAVCAN_PROTOCOL_FILE_ERROR_NOT_FOUND 2 +#define UAVCAN_PROTOCOL_FILE_ERROR_IO_ERROR 5 +#define UAVCAN_PROTOCOL_FILE_ERROR_ACCESS_DENIED 13 +#define UAVCAN_PROTOCOL_FILE_ERROR_IS_DIRECTORY 21 +#define UAVCAN_PROTOCOL_FILE_ERROR_INVALID_VALUE 22 +#define UAVCAN_PROTOCOL_FILE_ERROR_FILE_TOO_LARGE 27 +#define UAVCAN_PROTOCOL_FILE_ERROR_OUT_OF_SPACE 28 +#define UAVCAN_PROTOCOL_FILE_ERROR_NOT_IMPLEMENTED 38 + + +struct uavcan_protocol_file_Error { + int16_t value; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_Error_encode(struct uavcan_protocol_file_Error* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_Error_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_Error* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_Error_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_Error* msg, bool tao); +static inline bool _uavcan_protocol_file_Error_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_Error* msg, bool tao); +void _uavcan_protocol_file_Error_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_Error* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 16, &msg->value); + *bit_ofs += 16; +} + +/* + decode uavcan_protocol_file_Error, return true on failure, false on success +*/ +bool _uavcan_protocol_file_Error_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_Error* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 16, true, &msg->value); + *bit_ofs += 16; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_Error sample_uavcan_protocol_file_Error_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo.h new file mode 100644 index 000000000..f04fe6fd0 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_ID 46 +#define UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_SIGNATURE (0x8C46E8AB568BDA79ULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_file_GetDirectoryEntryInfo, UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_ID, UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_SIGNATURE, UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo_req.h new file mode 100644 index 000000000..465324178 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo_req.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_REQUEST_MAX_SIZE 205 +#define UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_REQUEST_SIGNATURE (0x8C46E8AB568BDA79ULL) +#define UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_REQUEST_ID 46 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_GetDirectoryEntryInfo_cxx_iface; +#endif + +struct uavcan_protocol_file_GetDirectoryEntryInfoRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_GetDirectoryEntryInfo_cxx_iface; +#endif + uint32_t entry_index; + struct uavcan_protocol_file_Path directory_path; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_GetDirectoryEntryInfoRequest_encode(struct uavcan_protocol_file_GetDirectoryEntryInfoRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_GetDirectoryEntryInfoRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_GetDirectoryEntryInfoRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_GetDirectoryEntryInfoRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetDirectoryEntryInfoRequest* msg, bool tao); +static inline bool _uavcan_protocol_file_GetDirectoryEntryInfoRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetDirectoryEntryInfoRequest* msg, bool tao); +void _uavcan_protocol_file_GetDirectoryEntryInfoRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetDirectoryEntryInfoRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->entry_index); + *bit_ofs += 32; + _uavcan_protocol_file_Path_encode(buffer, bit_ofs, &msg->directory_path, tao); +} + +/* + decode uavcan_protocol_file_GetDirectoryEntryInfoRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_file_GetDirectoryEntryInfoRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetDirectoryEntryInfoRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 32, false, &msg->entry_index); + *bit_ofs += 32; + + if (_uavcan_protocol_file_Path_decode(transfer, bit_ofs, &msg->directory_path, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_GetDirectoryEntryInfoRequest sample_uavcan_protocol_file_GetDirectoryEntryInfoRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo_res.h new file mode 100644 index 000000000..ad9432dc5 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetDirectoryEntryInfo_res.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_RESPONSE_MAX_SIZE 204 +#define UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_RESPONSE_SIGNATURE (0x8C46E8AB568BDA79ULL) +#define UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_RESPONSE_ID 46 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_GetDirectoryEntryInfo_cxx_iface; +#endif + +struct uavcan_protocol_file_GetDirectoryEntryInfoResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_GetDirectoryEntryInfo_cxx_iface; +#endif + struct uavcan_protocol_file_Error error; + struct uavcan_protocol_file_EntryType entry_type; + struct uavcan_protocol_file_Path entry_full_path; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_GetDirectoryEntryInfoResponse_encode(struct uavcan_protocol_file_GetDirectoryEntryInfoResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_GetDirectoryEntryInfoResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_GetDirectoryEntryInfoResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_GetDirectoryEntryInfoResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetDirectoryEntryInfoResponse* msg, bool tao); +static inline bool _uavcan_protocol_file_GetDirectoryEntryInfoResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetDirectoryEntryInfoResponse* msg, bool tao); +void _uavcan_protocol_file_GetDirectoryEntryInfoResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetDirectoryEntryInfoResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + _uavcan_protocol_file_Error_encode(buffer, bit_ofs, &msg->error, false); + _uavcan_protocol_file_EntryType_encode(buffer, bit_ofs, &msg->entry_type, false); + _uavcan_protocol_file_Path_encode(buffer, bit_ofs, &msg->entry_full_path, tao); +} + +/* + decode uavcan_protocol_file_GetDirectoryEntryInfoResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_file_GetDirectoryEntryInfoResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetDirectoryEntryInfoResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (_uavcan_protocol_file_Error_decode(transfer, bit_ofs, &msg->error, false)) {return true;} + + if (_uavcan_protocol_file_EntryType_decode(transfer, bit_ofs, &msg->entry_type, false)) {return true;} + + if (_uavcan_protocol_file_Path_decode(transfer, bit_ofs, &msg->entry_full_path, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_GetDirectoryEntryInfoResponse sample_uavcan_protocol_file_GetDirectoryEntryInfoResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo.h new file mode 100644 index 000000000..96ef75069 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_FILE_GETINFO_ID 45 +#define UAVCAN_PROTOCOL_FILE_GETINFO_SIGNATURE (0x5004891EE8A27531ULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_file_GetInfo, UAVCAN_PROTOCOL_FILE_GETINFO_ID, UAVCAN_PROTOCOL_FILE_GETINFO_SIGNATURE, UAVCAN_PROTOCOL_FILE_GETINFO_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_FILE_GETINFO_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo_req.h new file mode 100644 index 000000000..b629fc3e8 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo_req.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_GETINFO_REQUEST_MAX_SIZE 201 +#define UAVCAN_PROTOCOL_FILE_GETINFO_REQUEST_SIGNATURE (0x5004891EE8A27531ULL) +#define UAVCAN_PROTOCOL_FILE_GETINFO_REQUEST_ID 45 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_GetInfo_cxx_iface; +#endif + +struct uavcan_protocol_file_GetInfoRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_GetInfo_cxx_iface; +#endif + struct uavcan_protocol_file_Path path; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_GetInfoRequest_encode(struct uavcan_protocol_file_GetInfoRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_GetInfoRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_GetInfoRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_GetInfoRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetInfoRequest* msg, bool tao); +static inline bool _uavcan_protocol_file_GetInfoRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetInfoRequest* msg, bool tao); +void _uavcan_protocol_file_GetInfoRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetInfoRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + _uavcan_protocol_file_Path_encode(buffer, bit_ofs, &msg->path, tao); +} + +/* + decode uavcan_protocol_file_GetInfoRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_file_GetInfoRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetInfoRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (_uavcan_protocol_file_Path_decode(transfer, bit_ofs, &msg->path, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_GetInfoRequest sample_uavcan_protocol_file_GetInfoRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo_res.h new file mode 100644 index 000000000..cc408fcfe --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.GetInfo_res.h @@ -0,0 +1,80 @@ +#pragma once +#include +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_GETINFO_RESPONSE_MAX_SIZE 8 +#define UAVCAN_PROTOCOL_FILE_GETINFO_RESPONSE_SIGNATURE (0x5004891EE8A27531ULL) +#define UAVCAN_PROTOCOL_FILE_GETINFO_RESPONSE_ID 45 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_GetInfo_cxx_iface; +#endif + +struct uavcan_protocol_file_GetInfoResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_GetInfo_cxx_iface; +#endif + uint64_t size; + struct uavcan_protocol_file_Error error; + struct uavcan_protocol_file_EntryType entry_type; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_GetInfoResponse_encode(struct uavcan_protocol_file_GetInfoResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_GetInfoResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_GetInfoResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_GetInfoResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetInfoResponse* msg, bool tao); +static inline bool _uavcan_protocol_file_GetInfoResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetInfoResponse* msg, bool tao); +void _uavcan_protocol_file_GetInfoResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetInfoResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 40, &msg->size); + *bit_ofs += 40; + _uavcan_protocol_file_Error_encode(buffer, bit_ofs, &msg->error, false); + _uavcan_protocol_file_EntryType_encode(buffer, bit_ofs, &msg->entry_type, tao); +} + +/* + decode uavcan_protocol_file_GetInfoResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_file_GetInfoResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_GetInfoResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 40, false, &msg->size); + *bit_ofs += 40; + + if (_uavcan_protocol_file_Error_decode(transfer, bit_ofs, &msg->error, false)) {return true;} + + if (_uavcan_protocol_file_EntryType_decode(transfer, bit_ofs, &msg->entry_type, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_GetInfoResponse sample_uavcan_protocol_file_GetInfoResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Path.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Path.h new file mode 100644 index 000000000..025cfb1f6 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Path.h @@ -0,0 +1,90 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_PATH_MAX_SIZE 201 +#define UAVCAN_PROTOCOL_FILE_PATH_SIGNATURE (0x12AEFC50878A43E2ULL) + +#define UAVCAN_PROTOCOL_FILE_PATH_SEPARATOR 47 + + +struct uavcan_protocol_file_Path { + struct { uint8_t len; uint8_t data[200]; }path; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_Path_encode(struct uavcan_protocol_file_Path* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_Path_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_Path* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_Path_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_Path* msg, bool tao); +static inline bool _uavcan_protocol_file_Path_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_Path* msg, bool tao); +void _uavcan_protocol_file_Path_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_Path* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t path_len = msg->path.len > 200 ? 200 : msg->path.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 8, &path_len); + *bit_ofs += 8; + } + for (size_t i=0; i < path_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->path.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_file_Path, return true on failure, false on success +*/ +bool _uavcan_protocol_file_Path_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_Path* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->path.len); + *bit_ofs += 8; + } else { + msg->path.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->path.len > 200) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->path.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->path.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_Path sample_uavcan_protocol_file_Path_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read.h new file mode 100644 index 000000000..8d0d55e72 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_FILE_READ_ID 48 +#define UAVCAN_PROTOCOL_FILE_READ_SIGNATURE (0x8DCDCA939F33F678ULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_file_Read, UAVCAN_PROTOCOL_FILE_READ_ID, UAVCAN_PROTOCOL_FILE_READ_SIGNATURE, UAVCAN_PROTOCOL_FILE_READ_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_FILE_READ_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read_req.h new file mode 100644 index 000000000..d2978a6aa --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read_req.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_READ_REQUEST_MAX_SIZE 206 +#define UAVCAN_PROTOCOL_FILE_READ_REQUEST_SIGNATURE (0x8DCDCA939F33F678ULL) +#define UAVCAN_PROTOCOL_FILE_READ_REQUEST_ID 48 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_Read_cxx_iface; +#endif + +struct uavcan_protocol_file_ReadRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_Read_cxx_iface; +#endif + uint64_t offset; + struct uavcan_protocol_file_Path path; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_ReadRequest_encode(struct uavcan_protocol_file_ReadRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_ReadRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_ReadRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_ReadRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_ReadRequest* msg, bool tao); +static inline bool _uavcan_protocol_file_ReadRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_ReadRequest* msg, bool tao); +void _uavcan_protocol_file_ReadRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_ReadRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 40, &msg->offset); + *bit_ofs += 40; + _uavcan_protocol_file_Path_encode(buffer, bit_ofs, &msg->path, tao); +} + +/* + decode uavcan_protocol_file_ReadRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_file_ReadRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_ReadRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 40, false, &msg->offset); + *bit_ofs += 40; + + if (_uavcan_protocol_file_Path_decode(transfer, bit_ofs, &msg->path, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_ReadRequest sample_uavcan_protocol_file_ReadRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read_res.h new file mode 100644 index 000000000..f2a814e47 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Read_res.h @@ -0,0 +1,100 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_READ_RESPONSE_MAX_SIZE 260 +#define UAVCAN_PROTOCOL_FILE_READ_RESPONSE_SIGNATURE (0x8DCDCA939F33F678ULL) +#define UAVCAN_PROTOCOL_FILE_READ_RESPONSE_ID 48 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_Read_cxx_iface; +#endif + +struct uavcan_protocol_file_ReadResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_Read_cxx_iface; +#endif + struct uavcan_protocol_file_Error error; + struct { uint16_t len; uint8_t data[256]; }data; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_ReadResponse_encode(struct uavcan_protocol_file_ReadResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_ReadResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_ReadResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_ReadResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_ReadResponse* msg, bool tao); +static inline bool _uavcan_protocol_file_ReadResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_ReadResponse* msg, bool tao); +void _uavcan_protocol_file_ReadResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_ReadResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + _uavcan_protocol_file_Error_encode(buffer, bit_ofs, &msg->error, false); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint16_t data_len = msg->data.len > 256 ? 256 : msg->data.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 9, &data_len); + *bit_ofs += 9; + } + for (size_t i=0; i < data_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->data.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_file_ReadResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_file_ReadResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_ReadResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (_uavcan_protocol_file_Error_decode(transfer, bit_ofs, &msg->error, false)) {return true;} + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 9, false, &msg->data.len); + *bit_ofs += 9; + } else { + msg->data.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->data.len > 256) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->data.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->data.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_ReadResponse sample_uavcan_protocol_file_ReadResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write.h new file mode 100644 index 000000000..348e174cc --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_FILE_WRITE_ID 49 +#define UAVCAN_PROTOCOL_FILE_WRITE_SIGNATURE (0x515AA1DC77E58429ULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_file_Write, UAVCAN_PROTOCOL_FILE_WRITE_ID, UAVCAN_PROTOCOL_FILE_WRITE_SIGNATURE, UAVCAN_PROTOCOL_FILE_WRITE_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_FILE_WRITE_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write_req.h new file mode 100644 index 000000000..022a9da89 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write_req.h @@ -0,0 +1,106 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_WRITE_REQUEST_MAX_SIZE 399 +#define UAVCAN_PROTOCOL_FILE_WRITE_REQUEST_SIGNATURE (0x515AA1DC77E58429ULL) +#define UAVCAN_PROTOCOL_FILE_WRITE_REQUEST_ID 49 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_Write_cxx_iface; +#endif + +struct uavcan_protocol_file_WriteRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_Write_cxx_iface; +#endif + uint64_t offset; + struct uavcan_protocol_file_Path path; + struct { uint8_t len; uint8_t data[192]; }data; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_WriteRequest_encode(struct uavcan_protocol_file_WriteRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_WriteRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_WriteRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_WriteRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_WriteRequest* msg, bool tao); +static inline bool _uavcan_protocol_file_WriteRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_WriteRequest* msg, bool tao); +void _uavcan_protocol_file_WriteRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_WriteRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 40, &msg->offset); + *bit_ofs += 40; + _uavcan_protocol_file_Path_encode(buffer, bit_ofs, &msg->path, false); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t data_len = msg->data.len > 192 ? 192 : msg->data.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 8, &data_len); + *bit_ofs += 8; + } + for (size_t i=0; i < data_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->data.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_file_WriteRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_file_WriteRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_WriteRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 40, false, &msg->offset); + *bit_ofs += 40; + + if (_uavcan_protocol_file_Path_decode(transfer, bit_ofs, &msg->path, false)) {return true;} + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->data.len); + *bit_ofs += 8; + } else { + msg->data.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->data.len > 192) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->data.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->data.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_WriteRequest sample_uavcan_protocol_file_WriteRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write_res.h new file mode 100644 index 000000000..76049df9a --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.file.Write_res.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_FILE_WRITE_RESPONSE_MAX_SIZE 2 +#define UAVCAN_PROTOCOL_FILE_WRITE_RESPONSE_SIGNATURE (0x515AA1DC77E58429ULL) +#define UAVCAN_PROTOCOL_FILE_WRITE_RESPONSE_ID 49 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_file_Write_cxx_iface; +#endif + +struct uavcan_protocol_file_WriteResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_file_Write_cxx_iface; +#endif + struct uavcan_protocol_file_Error error; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_file_WriteResponse_encode(struct uavcan_protocol_file_WriteResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_file_WriteResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_WriteResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_file_WriteResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_WriteResponse* msg, bool tao); +static inline bool _uavcan_protocol_file_WriteResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_WriteResponse* msg, bool tao); +void _uavcan_protocol_file_WriteResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_file_WriteResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + _uavcan_protocol_file_Error_encode(buffer, bit_ofs, &msg->error, tao); +} + +/* + decode uavcan_protocol_file_WriteResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_file_WriteResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_file_WriteResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + if (_uavcan_protocol_file_Error_decode(transfer, bit_ofs, &msg->error, tao)) {return true;} + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_WriteResponse sample_uavcan_protocol_file_WriteResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.Empty.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.Empty.h new file mode 100644 index 000000000..e05acccc8 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.Empty.h @@ -0,0 +1,57 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_PARAM_EMPTY_MAX_SIZE 0 +#define UAVCAN_PROTOCOL_PARAM_EMPTY_SIGNATURE (0x6C4D0E8EF37361DFULL) + + +struct uavcan_protocol_param_Empty { +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_param_Empty_encode(struct uavcan_protocol_param_Empty* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_param_Empty_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_Empty* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_param_Empty_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_Empty* msg, bool tao); +static inline bool _uavcan_protocol_param_Empty_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_Empty* msg, bool tao); +void _uavcan_protocol_param_Empty_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_Empty* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + +} + +/* + decode uavcan_protocol_param_Empty, return true on failure, false on success +*/ +bool _uavcan_protocol_param_Empty_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_Empty* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_Empty sample_uavcan_protocol_param_Empty_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode.h new file mode 100644 index 000000000..a15c5af2e --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_ID 10 +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_SIGNATURE (0x3B131AC5EB69D2CDULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_param_ExecuteOpcode, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_ID, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_SIGNATURE, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode_req.h new file mode 100644 index 000000000..d01e3fa18 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode_req.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_MAX_SIZE 7 +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_SIGNATURE (0x3B131AC5EB69D2CDULL) +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_ID 10 + +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_OPCODE_SAVE 0 +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_OPCODE_ERASE 1 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_param_ExecuteOpcode_cxx_iface; +#endif + +struct uavcan_protocol_param_ExecuteOpcodeRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_param_ExecuteOpcode_cxx_iface; +#endif + uint8_t opcode; + int64_t argument; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_param_ExecuteOpcodeRequest_encode(struct uavcan_protocol_param_ExecuteOpcodeRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_param_ExecuteOpcodeRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_ExecuteOpcodeRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_param_ExecuteOpcodeRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_ExecuteOpcodeRequest* msg, bool tao); +static inline bool _uavcan_protocol_param_ExecuteOpcodeRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_ExecuteOpcodeRequest* msg, bool tao); +void _uavcan_protocol_param_ExecuteOpcodeRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_ExecuteOpcodeRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->opcode); + *bit_ofs += 8; + canardEncodeScalar(buffer, *bit_ofs, 48, &msg->argument); + *bit_ofs += 48; +} + +/* + decode uavcan_protocol_param_ExecuteOpcodeRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_param_ExecuteOpcodeRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_ExecuteOpcodeRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->opcode); + *bit_ofs += 8; + + canardDecodeScalar(transfer, *bit_ofs, 48, true, &msg->argument); + *bit_ofs += 48; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_ExecuteOpcodeRequest sample_uavcan_protocol_param_ExecuteOpcodeRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode_res.h new file mode 100644 index 000000000..3b6a85d03 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.ExecuteOpcode_res.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include + + +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_RESPONSE_MAX_SIZE 7 +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_RESPONSE_SIGNATURE (0x3B131AC5EB69D2CDULL) +#define UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_RESPONSE_ID 10 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_param_ExecuteOpcode_cxx_iface; +#endif + +struct uavcan_protocol_param_ExecuteOpcodeResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_param_ExecuteOpcode_cxx_iface; +#endif + int64_t argument; + bool ok; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_param_ExecuteOpcodeResponse_encode(struct uavcan_protocol_param_ExecuteOpcodeResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_param_ExecuteOpcodeResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_ExecuteOpcodeResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_param_ExecuteOpcodeResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_ExecuteOpcodeResponse* msg, bool tao); +static inline bool _uavcan_protocol_param_ExecuteOpcodeResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_ExecuteOpcodeResponse* msg, bool tao); +void _uavcan_protocol_param_ExecuteOpcodeResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_ExecuteOpcodeResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 48, &msg->argument); + *bit_ofs += 48; + canardEncodeScalar(buffer, *bit_ofs, 1, &msg->ok); + *bit_ofs += 1; +} + +/* + decode uavcan_protocol_param_ExecuteOpcodeResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_param_ExecuteOpcodeResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_ExecuteOpcodeResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 48, true, &msg->argument); + *bit_ofs += 48; + + canardDecodeScalar(transfer, *bit_ofs, 1, false, &msg->ok); + *bit_ofs += 1; + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_ExecuteOpcodeResponse sample_uavcan_protocol_param_ExecuteOpcodeResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet.h new file mode 100644 index 000000000..21ae8cb5f --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +#define UAVCAN_PROTOCOL_PARAM_GETSET_ID 11 +#define UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE (0xA7B622F939D1A4D5ULL) + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +#include +SERVICE_MESSAGE_CXX_IFACE(uavcan_protocol_param_GetSet, UAVCAN_PROTOCOL_PARAM_GETSET_ID, UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE, UAVCAN_PROTOCOL_PARAM_GETSET_REQUEST_MAX_SIZE, UAVCAN_PROTOCOL_PARAM_GETSET_RESPONSE_MAX_SIZE); +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet_req.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet_req.h new file mode 100644 index 000000000..108a4fec9 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet_req.h @@ -0,0 +1,106 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_PARAM_GETSET_REQUEST_MAX_SIZE 224 +#define UAVCAN_PROTOCOL_PARAM_GETSET_REQUEST_SIGNATURE (0xA7B622F939D1A4D5ULL) +#define UAVCAN_PROTOCOL_PARAM_GETSET_REQUEST_ID 11 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_param_GetSet_cxx_iface; +#endif + +struct uavcan_protocol_param_GetSetRequest { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_param_GetSet_cxx_iface; +#endif + uint16_t index; + struct uavcan_protocol_param_Value value; + struct { uint8_t len; uint8_t data[92]; }name; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_param_GetSetRequest_encode(struct uavcan_protocol_param_GetSetRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_param_GetSetRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_GetSetRequest* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_param_GetSetRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_GetSetRequest* msg, bool tao); +static inline bool _uavcan_protocol_param_GetSetRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_GetSetRequest* msg, bool tao); +void _uavcan_protocol_param_GetSetRequest_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_GetSetRequest* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + canardEncodeScalar(buffer, *bit_ofs, 13, &msg->index); + *bit_ofs += 13; + _uavcan_protocol_param_Value_encode(buffer, bit_ofs, &msg->value, false); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t name_len = msg->name.len > 92 ? 92 : msg->name.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 7, &name_len); + *bit_ofs += 7; + } + for (size_t i=0; i < name_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->name.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_param_GetSetRequest, return true on failure, false on success +*/ +bool _uavcan_protocol_param_GetSetRequest_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_GetSetRequest* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + canardDecodeScalar(transfer, *bit_ofs, 13, false, &msg->index); + *bit_ofs += 13; + + if (_uavcan_protocol_param_Value_decode(transfer, bit_ofs, &msg->value, false)) {return true;} + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->name.len); + *bit_ofs += 7; + } else { + msg->name.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->name.len > 92) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->name.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->name.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_GetSetRequest sample_uavcan_protocol_param_GetSetRequest_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet_res.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet_res.h new file mode 100644 index 000000000..0e29be837 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.GetSet_res.h @@ -0,0 +1,125 @@ +#pragma once +#include +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_PARAM_GETSET_RESPONSE_MAX_SIZE 371 +#define UAVCAN_PROTOCOL_PARAM_GETSET_RESPONSE_SIGNATURE (0xA7B622F939D1A4D5ULL) +#define UAVCAN_PROTOCOL_PARAM_GETSET_RESPONSE_ID 11 + +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) +class uavcan_protocol_param_GetSet_cxx_iface; +#endif + +struct uavcan_protocol_param_GetSetResponse { +#if defined(__cplusplus) && defined(DRONECAN_CXX_WRAPPERS) + using cxx_iface = uavcan_protocol_param_GetSet_cxx_iface; +#endif + struct uavcan_protocol_param_Value value; + struct uavcan_protocol_param_Value default_value; + struct uavcan_protocol_param_NumericValue max_value; + struct uavcan_protocol_param_NumericValue min_value; + struct { uint8_t len; uint8_t data[92]; }name; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_param_GetSetResponse_encode(struct uavcan_protocol_param_GetSetResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_param_GetSetResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_GetSetResponse* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_param_GetSetResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_GetSetResponse* msg, bool tao); +static inline bool _uavcan_protocol_param_GetSetResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_GetSetResponse* msg, bool tao); +void _uavcan_protocol_param_GetSetResponse_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_GetSetResponse* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + *bit_ofs += 5; + _uavcan_protocol_param_Value_encode(buffer, bit_ofs, &msg->value, false); + *bit_ofs += 5; + _uavcan_protocol_param_Value_encode(buffer, bit_ofs, &msg->default_value, false); + *bit_ofs += 6; + _uavcan_protocol_param_NumericValue_encode(buffer, bit_ofs, &msg->max_value, false); + *bit_ofs += 6; + _uavcan_protocol_param_NumericValue_encode(buffer, bit_ofs, &msg->min_value, false); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t name_len = msg->name.len > 92 ? 92 : msg->name.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 7, &name_len); + *bit_ofs += 7; + } + for (size_t i=0; i < name_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->name.data[i]); + *bit_ofs += 8; + } +} + +/* + decode uavcan_protocol_param_GetSetResponse, return true on failure, false on success +*/ +bool _uavcan_protocol_param_GetSetResponse_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_GetSetResponse* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + *bit_ofs += 5; + + if (_uavcan_protocol_param_Value_decode(transfer, bit_ofs, &msg->value, false)) {return true;} + + *bit_ofs += 5; + + if (_uavcan_protocol_param_Value_decode(transfer, bit_ofs, &msg->default_value, false)) {return true;} + + *bit_ofs += 6; + + if (_uavcan_protocol_param_NumericValue_decode(transfer, bit_ofs, &msg->max_value, false)) {return true;} + + *bit_ofs += 6; + + if (_uavcan_protocol_param_NumericValue_decode(transfer, bit_ofs, &msg->min_value, false)) {return true;} + + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 7, false, &msg->name.len); + *bit_ofs += 7; + } else { + msg->name.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->name.len > 92) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->name.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->name.data[i]); + *bit_ofs += 8; + } + + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_GetSetResponse sample_uavcan_protocol_param_GetSetResponse_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.NumericValue.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.NumericValue.h new file mode 100644 index 000000000..462f1da80 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.NumericValue.h @@ -0,0 +1,120 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_MAX_SIZE 9 +#define UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_SIGNATURE (0xDA6D6FEA22E3587ULL) + +enum uavcan_protocol_param_NumericValue_type_t { + UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_EMPTY, + UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_INTEGER_VALUE, + UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_REAL_VALUE, +}; + + +struct uavcan_protocol_param_NumericValue { + enum uavcan_protocol_param_NumericValue_type_t union_tag; + union { + struct uavcan_protocol_param_Empty empty; + int64_t integer_value; + float real_value; + }; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_param_NumericValue_encode(struct uavcan_protocol_param_NumericValue* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_param_NumericValue_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_NumericValue* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_param_NumericValue_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_NumericValue* msg, bool tao); +static inline bool _uavcan_protocol_param_NumericValue_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_NumericValue* msg, bool tao); +void _uavcan_protocol_param_NumericValue_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_NumericValue* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + uint8_t union_tag = msg->union_tag; + canardEncodeScalar(buffer, *bit_ofs, 2, &union_tag); + *bit_ofs += 2; + + switch(msg->union_tag) { + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_EMPTY: { + _uavcan_protocol_param_Empty_encode(buffer, bit_ofs, &msg->empty, tao); + break; + } + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_INTEGER_VALUE: { + canardEncodeScalar(buffer, *bit_ofs, 64, &msg->integer_value); + *bit_ofs += 64; + break; + } + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_REAL_VALUE: { + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->real_value); + *bit_ofs += 32; + break; + } + } +} + +/* + decode uavcan_protocol_param_NumericValue, return true on failure, false on success +*/ +bool _uavcan_protocol_param_NumericValue_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_NumericValue* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + uint8_t union_tag; + canardDecodeScalar(transfer, *bit_ofs, 2, false, &union_tag); + *bit_ofs += 2; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (union_tag >= 3) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + msg->union_tag = (enum uavcan_protocol_param_NumericValue_type_t)union_tag; + + switch(msg->union_tag) { + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_EMPTY: { + if (_uavcan_protocol_param_Empty_decode(transfer, bit_ofs, &msg->empty, tao)) {return true;} + break; + } + + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_INTEGER_VALUE: { + canardDecodeScalar(transfer, *bit_ofs, 64, true, &msg->integer_value); + *bit_ofs += 64; + break; + } + + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_REAL_VALUE: { + canardDecodeScalar(transfer, *bit_ofs, 32, true, &msg->real_value); + *bit_ofs += 32; + break; + } + + } + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_NumericValue sample_uavcan_protocol_param_NumericValue_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.Value.h b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.Value.h new file mode 100644 index 000000000..a46b3ea09 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/include/uavcan.protocol.param.Value.h @@ -0,0 +1,171 @@ +#pragma once +#include +#include +#include +#include + + +#define UAVCAN_PROTOCOL_PARAM_VALUE_MAX_SIZE 130 +#define UAVCAN_PROTOCOL_PARAM_VALUE_SIGNATURE (0x29F14BF484727267ULL) + +enum uavcan_protocol_param_Value_type_t { + UAVCAN_PROTOCOL_PARAM_VALUE_EMPTY, + UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE, + UAVCAN_PROTOCOL_PARAM_VALUE_REAL_VALUE, + UAVCAN_PROTOCOL_PARAM_VALUE_BOOLEAN_VALUE, + UAVCAN_PROTOCOL_PARAM_VALUE_STRING_VALUE, +}; + + +struct uavcan_protocol_param_Value { + enum uavcan_protocol_param_Value_type_t union_tag; + union { + struct uavcan_protocol_param_Empty empty; + int64_t integer_value; + float real_value; + uint8_t boolean_value; + struct { uint8_t len; uint8_t data[128]; }string_value; + }; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +uint32_t uavcan_protocol_param_Value_encode(struct uavcan_protocol_param_Value* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +); +bool uavcan_protocol_param_Value_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_Value* msg); + +#if defined(CANARD_DSDLC_INTERNAL) +static inline void _uavcan_protocol_param_Value_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_Value* msg, bool tao); +static inline bool _uavcan_protocol_param_Value_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_Value* msg, bool tao); +void _uavcan_protocol_param_Value_encode(uint8_t* buffer, uint32_t* bit_ofs, struct uavcan_protocol_param_Value* msg, bool tao) { + (void)buffer; + (void)bit_ofs; + (void)msg; + (void)tao; + + uint8_t union_tag = msg->union_tag; + canardEncodeScalar(buffer, *bit_ofs, 3, &union_tag); + *bit_ofs += 3; + + switch(msg->union_tag) { + case UAVCAN_PROTOCOL_PARAM_VALUE_EMPTY: { + _uavcan_protocol_param_Empty_encode(buffer, bit_ofs, &msg->empty, tao); + break; + } + case UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE: { + canardEncodeScalar(buffer, *bit_ofs, 64, &msg->integer_value); + *bit_ofs += 64; + break; + } + case UAVCAN_PROTOCOL_PARAM_VALUE_REAL_VALUE: { + canardEncodeScalar(buffer, *bit_ofs, 32, &msg->real_value); + *bit_ofs += 32; + break; + } + case UAVCAN_PROTOCOL_PARAM_VALUE_BOOLEAN_VALUE: { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->boolean_value); + *bit_ofs += 8; + break; + } + case UAVCAN_PROTOCOL_PARAM_VALUE_STRING_VALUE: { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + const uint8_t string_value_len = msg->string_value.len > 128 ? 128 : msg->string_value.len; +#pragma GCC diagnostic pop + if (!tao) { + canardEncodeScalar(buffer, *bit_ofs, 8, &string_value_len); + *bit_ofs += 8; + } + for (size_t i=0; i < string_value_len; i++) { + canardEncodeScalar(buffer, *bit_ofs, 8, &msg->string_value.data[i]); + *bit_ofs += 8; + } + break; + } + } +} + +/* + decode uavcan_protocol_param_Value, return true on failure, false on success +*/ +bool _uavcan_protocol_param_Value_decode(const CanardRxTransfer* transfer, uint32_t* bit_ofs, struct uavcan_protocol_param_Value* msg, bool tao) { + (void)transfer; + (void)bit_ofs; + (void)msg; + (void)tao; + uint8_t union_tag; + canardDecodeScalar(transfer, *bit_ofs, 3, false, &union_tag); + *bit_ofs += 3; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (union_tag >= 5) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + msg->union_tag = (enum uavcan_protocol_param_Value_type_t)union_tag; + + switch(msg->union_tag) { + case UAVCAN_PROTOCOL_PARAM_VALUE_EMPTY: { + if (_uavcan_protocol_param_Empty_decode(transfer, bit_ofs, &msg->empty, tao)) {return true;} + break; + } + + case UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE: { + canardDecodeScalar(transfer, *bit_ofs, 64, true, &msg->integer_value); + *bit_ofs += 64; + break; + } + + case UAVCAN_PROTOCOL_PARAM_VALUE_REAL_VALUE: { + canardDecodeScalar(transfer, *bit_ofs, 32, true, &msg->real_value); + *bit_ofs += 32; + break; + } + + case UAVCAN_PROTOCOL_PARAM_VALUE_BOOLEAN_VALUE: { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->boolean_value); + *bit_ofs += 8; + break; + } + + case UAVCAN_PROTOCOL_PARAM_VALUE_STRING_VALUE: { + if (!tao) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->string_value.len); + *bit_ofs += 8; + } else { + msg->string_value.len = ((transfer->payload_len*8)-*bit_ofs)/8; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if (msg->string_value.len > 128) { + return true; /* invalid value */ + } +#pragma GCC diagnostic pop + for (size_t i=0; i < msg->string_value.len; i++) { + canardDecodeScalar(transfer, *bit_ofs, 8, false, &msg->string_value.data[i]); + *bit_ofs += 8; + } + break; + } + + } + return false; /* success */ +} +#endif +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_Value sample_uavcan_protocol_param_Value_msg(void); +#endif +#ifdef __cplusplus +} // extern "C" + +#ifdef DRONECAN_CXX_WRAPPERS +#include +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.RPMCommand.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.RPMCommand.c new file mode 100644 index 000000000..c6c12491f --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.RPMCommand.c @@ -0,0 +1,67 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_equipment_esc_RPMCommand_encode(struct uavcan_equipment_esc_RPMCommand* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_EQUIPMENT_ESC_RPMCOMMAND_MAX_SIZE); + _uavcan_equipment_esc_RPMCommand_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_equipment_esc_RPMCommand_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_esc_RPMCommand* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_EQUIPMENT_ESC_RPMCOMMAND_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_equipment_esc_RPMCommand_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_esc_RPMCommand sample_uavcan_equipment_esc_RPMCommand_msg(void) { + struct uavcan_equipment_esc_RPMCommand msg; + + msg.rpm.len = (uint8_t)random_range_unsigned_val(0, 20); + for (size_t i=0; i < msg.rpm.len; i++) { + msg.rpm.data[i] = (int32_t)random_bitlen_signed_val(18); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.RawCommand.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.RawCommand.c new file mode 100644 index 000000000..40e6e1ff4 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.RawCommand.c @@ -0,0 +1,67 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_equipment_esc_RawCommand_encode(struct uavcan_equipment_esc_RawCommand* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_MAX_SIZE); + _uavcan_equipment_esc_RawCommand_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_equipment_esc_RawCommand_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_esc_RawCommand* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_EQUIPMENT_ESC_RAWCOMMAND_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_equipment_esc_RawCommand_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_esc_RawCommand sample_uavcan_equipment_esc_RawCommand_msg(void) { + struct uavcan_equipment_esc_RawCommand msg; + + msg.cmd.len = (uint8_t)random_range_unsigned_val(0, 20); + for (size_t i=0; i < msg.cmd.len; i++) { + msg.cmd.data[i] = (int16_t)random_bitlen_signed_val(14); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.Status.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.Status.c new file mode 100644 index 000000000..3a7d8530e --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.Status.c @@ -0,0 +1,70 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_equipment_esc_Status_encode(struct uavcan_equipment_esc_Status* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_EQUIPMENT_ESC_STATUS_MAX_SIZE); + _uavcan_equipment_esc_Status_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_equipment_esc_Status_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_esc_Status* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_EQUIPMENT_ESC_STATUS_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_equipment_esc_Status_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_esc_Status sample_uavcan_equipment_esc_Status_msg(void) { + struct uavcan_equipment_esc_Status msg; + + msg.error_count = (uint32_t)random_bitlen_unsigned_val(32); + msg.voltage = random_float16_val(); + msg.current = random_float16_val(); + msg.temperature = random_float16_val(); + msg.rpm = (int32_t)random_bitlen_signed_val(18); + msg.power_rating_pct = (uint8_t)random_bitlen_unsigned_val(7); + msg.esc_index = (uint8_t)random_bitlen_unsigned_val(5); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.StatusExtended.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.StatusExtended.c new file mode 100644 index 000000000..46dbac5bb --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.esc.StatusExtended.c @@ -0,0 +1,69 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_equipment_esc_StatusExtended_encode(struct uavcan_equipment_esc_StatusExtended* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_EQUIPMENT_ESC_STATUSEXTENDED_MAX_SIZE); + _uavcan_equipment_esc_StatusExtended_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_equipment_esc_StatusExtended_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_esc_StatusExtended* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_EQUIPMENT_ESC_STATUSEXTENDED_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_equipment_esc_StatusExtended_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_esc_StatusExtended sample_uavcan_equipment_esc_StatusExtended_msg(void) { + struct uavcan_equipment_esc_StatusExtended msg; + + msg.input_pct = (uint8_t)random_bitlen_unsigned_val(7); + msg.output_pct = (uint8_t)random_bitlen_unsigned_val(7); + msg.motor_temperature_degC = (int16_t)random_bitlen_signed_val(9); + msg.motor_angle = (uint16_t)random_bitlen_unsigned_val(9); + msg.status_flags = (uint32_t)random_bitlen_unsigned_val(19); + msg.esc_index = (uint8_t)random_bitlen_unsigned_val(5); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.safety.ArmingStatus.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.safety.ArmingStatus.c new file mode 100644 index 000000000..e5f4b6be7 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.equipment.safety.ArmingStatus.c @@ -0,0 +1,64 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_equipment_safety_ArmingStatus_encode(struct uavcan_equipment_safety_ArmingStatus* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_MAX_SIZE); + _uavcan_equipment_safety_ArmingStatus_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_equipment_safety_ArmingStatus_decode(const CanardRxTransfer* transfer, struct uavcan_equipment_safety_ArmingStatus* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_EQUIPMENT_SAFETY_ARMINGSTATUS_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_equipment_safety_ArmingStatus_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_equipment_safety_ArmingStatus sample_uavcan_equipment_safety_ArmingStatus_msg(void) { + struct uavcan_equipment_safety_ArmingStatus msg; + + msg.status = (uint8_t)random_bitlen_unsigned_val(8); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.GetNodeInfo_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.GetNodeInfo_req.c new file mode 100644 index 000000000..f10b60bb6 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.GetNodeInfo_req.c @@ -0,0 +1,64 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_GetNodeInfoRequest_encode(struct uavcan_protocol_GetNodeInfoRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_MAX_SIZE); + _uavcan_protocol_GetNodeInfoRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_GetNodeInfoRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_GetNodeInfoRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_GetNodeInfoRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_GetNodeInfoRequest sample_uavcan_protocol_GetNodeInfoRequest_msg(void) { + struct uavcan_protocol_GetNodeInfoRequest msg; + + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.GetNodeInfo_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.GetNodeInfo_res.c new file mode 100644 index 000000000..bdfd1af36 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.GetNodeInfo_res.c @@ -0,0 +1,70 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_GetNodeInfoResponse_encode(struct uavcan_protocol_GetNodeInfoResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE); + _uavcan_protocol_GetNodeInfoResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_GetNodeInfoResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_GetNodeInfoResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_GetNodeInfoResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_GetNodeInfoResponse sample_uavcan_protocol_GetNodeInfoResponse_msg(void) { + struct uavcan_protocol_GetNodeInfoResponse msg; + + msg.status = sample_uavcan_protocol_NodeStatus_msg(); + msg.software_version = sample_uavcan_protocol_SoftwareVersion_msg(); + msg.hardware_version = sample_uavcan_protocol_HardwareVersion_msg(); + msg.name.len = (uint8_t)random_range_unsigned_val(0, 80); + for (size_t i=0; i < msg.name.len; i++) { + msg.name.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.HardwareVersion.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.HardwareVersion.c new file mode 100644 index 000000000..ead23919b --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.HardwareVersion.c @@ -0,0 +1,72 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_HardwareVersion_encode(struct uavcan_protocol_HardwareVersion* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_HARDWAREVERSION_MAX_SIZE); + _uavcan_protocol_HardwareVersion_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_HardwareVersion_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_HardwareVersion* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_HARDWAREVERSION_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_HardwareVersion_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_HardwareVersion sample_uavcan_protocol_HardwareVersion_msg(void) { + struct uavcan_protocol_HardwareVersion msg; + + msg.major = (uint8_t)random_bitlen_unsigned_val(8); + msg.minor = (uint8_t)random_bitlen_unsigned_val(8); + for (size_t i=0; i < 16; i++) { + msg.unique_id[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + msg.certificate_of_authenticity.len = (uint8_t)random_range_unsigned_val(0, 255); + for (size_t i=0; i < msg.certificate_of_authenticity.len; i++) { + msg.certificate_of_authenticity.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.NodeStatus.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.NodeStatus.c new file mode 100644 index 000000000..6d5ece0db --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.NodeStatus.c @@ -0,0 +1,68 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_NodeStatus_encode(struct uavcan_protocol_NodeStatus* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_NODESTATUS_MAX_SIZE); + _uavcan_protocol_NodeStatus_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_NodeStatus_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_NodeStatus* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_NODESTATUS_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_NodeStatus_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_NodeStatus sample_uavcan_protocol_NodeStatus_msg(void) { + struct uavcan_protocol_NodeStatus msg; + + msg.uptime_sec = (uint32_t)random_bitlen_unsigned_val(32); + msg.health = (uint8_t)random_bitlen_unsigned_val(2); + msg.mode = (uint8_t)random_bitlen_unsigned_val(3); + msg.sub_mode = (uint8_t)random_bitlen_unsigned_val(3); + msg.vendor_specific_status_code = (uint16_t)random_bitlen_unsigned_val(16); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.RestartNode_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.RestartNode_req.c new file mode 100644 index 000000000..19b13a4af --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.RestartNode_req.c @@ -0,0 +1,65 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_RestartNodeRequest_encode(struct uavcan_protocol_RestartNodeRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_RESTARTNODE_REQUEST_MAX_SIZE); + _uavcan_protocol_RestartNodeRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_RestartNodeRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_RestartNodeRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_RESTARTNODE_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_RestartNodeRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_RestartNodeRequest sample_uavcan_protocol_RestartNodeRequest_msg(void) { + struct uavcan_protocol_RestartNodeRequest msg; + + msg.magic_number = (uint64_t)random_bitlen_unsigned_val(40); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.RestartNode_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.RestartNode_res.c new file mode 100644 index 000000000..42d5105a0 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.RestartNode_res.c @@ -0,0 +1,64 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_RestartNodeResponse_encode(struct uavcan_protocol_RestartNodeResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_RESTARTNODE_RESPONSE_MAX_SIZE); + _uavcan_protocol_RestartNodeResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_RestartNodeResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_RestartNodeResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_RESTARTNODE_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_RestartNodeResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_RestartNodeResponse sample_uavcan_protocol_RestartNodeResponse_msg(void) { + struct uavcan_protocol_RestartNodeResponse msg; + + msg.ok = (bool)random_bitlen_unsigned_val(1); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.SoftwareVersion.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.SoftwareVersion.c new file mode 100644 index 000000000..6fe34196b --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.SoftwareVersion.c @@ -0,0 +1,68 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_SoftwareVersion_encode(struct uavcan_protocol_SoftwareVersion* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_SOFTWAREVERSION_MAX_SIZE); + _uavcan_protocol_SoftwareVersion_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_SoftwareVersion_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_SoftwareVersion* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_SOFTWAREVERSION_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_SoftwareVersion_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_SoftwareVersion sample_uavcan_protocol_SoftwareVersion_msg(void) { + struct uavcan_protocol_SoftwareVersion msg; + + msg.major = (uint8_t)random_bitlen_unsigned_val(8); + msg.minor = (uint8_t)random_bitlen_unsigned_val(8); + msg.optional_field_flags = (uint8_t)random_bitlen_unsigned_val(8); + msg.vcs_commit = (uint32_t)random_bitlen_unsigned_val(32); + msg.image_crc = (uint64_t)random_bitlen_unsigned_val(64); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.KeyValue.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.KeyValue.c new file mode 100644 index 000000000..006796d50 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.KeyValue.c @@ -0,0 +1,68 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_debug_KeyValue_encode(struct uavcan_protocol_debug_KeyValue* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DEBUG_KEYVALUE_MAX_SIZE); + _uavcan_protocol_debug_KeyValue_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_debug_KeyValue_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_debug_KeyValue* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DEBUG_KEYVALUE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_debug_KeyValue_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_debug_KeyValue sample_uavcan_protocol_debug_KeyValue_msg(void) { + struct uavcan_protocol_debug_KeyValue msg; + + msg.value = random_float_val(); + msg.key.len = (uint8_t)random_range_unsigned_val(0, 58); + for (size_t i=0; i < msg.key.len; i++) { + msg.key.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.LogLevel.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.LogLevel.c new file mode 100644 index 000000000..9259de442 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.LogLevel.c @@ -0,0 +1,64 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_debug_LogLevel_encode(struct uavcan_protocol_debug_LogLevel* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_MAX_SIZE); + _uavcan_protocol_debug_LogLevel_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_debug_LogLevel_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_debug_LogLevel* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_debug_LogLevel_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_debug_LogLevel sample_uavcan_protocol_debug_LogLevel_msg(void) { + struct uavcan_protocol_debug_LogLevel msg; + + msg.value = (uint8_t)random_bitlen_unsigned_val(3); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.LogMessage.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.LogMessage.c new file mode 100644 index 000000000..85e2cd58f --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.debug.LogMessage.c @@ -0,0 +1,72 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_debug_LogMessage_encode(struct uavcan_protocol_debug_LogMessage* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_MAX_SIZE); + _uavcan_protocol_debug_LogMessage_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_debug_LogMessage_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_debug_LogMessage* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DEBUG_LOGMESSAGE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_debug_LogMessage_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_debug_LogMessage sample_uavcan_protocol_debug_LogMessage_msg(void) { + struct uavcan_protocol_debug_LogMessage msg; + + msg.level = sample_uavcan_protocol_debug_LogLevel_msg(); + msg.source.len = (uint8_t)random_range_unsigned_val(0, 31); + for (size_t i=0; i < msg.source.len; i++) { + msg.source.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + msg.text.len = (uint8_t)random_range_unsigned_val(0, 90); + for (size_t i=0; i < msg.text.len; i++) { + msg.text.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.Allocation.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.Allocation.c new file mode 100644 index 000000000..7029c2cf4 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.Allocation.c @@ -0,0 +1,69 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_dynamic_node_id_Allocation_encode(struct uavcan_protocol_dynamic_node_id_Allocation* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_SIZE); + _uavcan_protocol_dynamic_node_id_Allocation_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_dynamic_node_id_Allocation_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_Allocation* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_dynamic_node_id_Allocation_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_Allocation sample_uavcan_protocol_dynamic_node_id_Allocation_msg(void) { + struct uavcan_protocol_dynamic_node_id_Allocation msg; + + msg.node_id = (uint8_t)random_bitlen_unsigned_val(7); + msg.first_part_of_unique_id = (bool)random_bitlen_unsigned_val(1); + msg.unique_id.len = (uint8_t)random_range_unsigned_val(0, 16); + for (size_t i=0; i < msg.unique_id.len; i++) { + msg.unique_id.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.AppendEntries_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.AppendEntries_req.c new file mode 100644 index 000000000..1a88f2a88 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.AppendEntries_req.c @@ -0,0 +1,72 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_encode(struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_REQUEST_MAX_SIZE); + _uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest sample_uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest_msg(void) { + struct uavcan_protocol_dynamic_node_id_server_AppendEntriesRequest msg; + + msg.term = (uint32_t)random_bitlen_unsigned_val(32); + msg.prev_log_term = (uint32_t)random_bitlen_unsigned_val(32); + msg.prev_log_index = (uint8_t)random_bitlen_unsigned_val(8); + msg.leader_commit = (uint8_t)random_bitlen_unsigned_val(8); + msg.entries.len = (uint8_t)random_range_unsigned_val(0, 1); + for (size_t i=0; i < msg.entries.len; i++) { + msg.entries.data[i] = sample_uavcan_protocol_dynamic_node_id_server_Entry_msg(); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.AppendEntries_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.AppendEntries_res.c new file mode 100644 index 000000000..ffb13308c --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.AppendEntries_res.c @@ -0,0 +1,65 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_encode(struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_RESPONSE_MAX_SIZE); + _uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_APPENDENTRIES_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse sample_uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse_msg(void) { + struct uavcan_protocol_dynamic_node_id_server_AppendEntriesResponse msg; + + msg.term = (uint32_t)random_bitlen_unsigned_val(32); + msg.success = (bool)random_bitlen_unsigned_val(1); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.Discovery.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.Discovery.c new file mode 100644 index 000000000..c5a539978 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.Discovery.c @@ -0,0 +1,68 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_Discovery_encode(struct uavcan_protocol_dynamic_node_id_server_Discovery* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_MAX_SIZE); + _uavcan_protocol_dynamic_node_id_server_Discovery_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_dynamic_node_id_server_Discovery_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_Discovery* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_DISCOVERY_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_dynamic_node_id_server_Discovery_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_Discovery sample_uavcan_protocol_dynamic_node_id_server_Discovery_msg(void) { + struct uavcan_protocol_dynamic_node_id_server_Discovery msg; + + msg.configured_cluster_size = (uint8_t)random_bitlen_unsigned_val(8); + msg.known_nodes.len = (uint8_t)random_range_unsigned_val(0, 5); + for (size_t i=0; i < msg.known_nodes.len; i++) { + msg.known_nodes.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.Entry.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.Entry.c new file mode 100644 index 000000000..9766f42fe --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.Entry.c @@ -0,0 +1,68 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_Entry_encode(struct uavcan_protocol_dynamic_node_id_server_Entry* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_ENTRY_MAX_SIZE); + _uavcan_protocol_dynamic_node_id_server_Entry_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_dynamic_node_id_server_Entry_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_Entry* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_ENTRY_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_dynamic_node_id_server_Entry_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_Entry sample_uavcan_protocol_dynamic_node_id_server_Entry_msg(void) { + struct uavcan_protocol_dynamic_node_id_server_Entry msg; + + msg.term = (uint32_t)random_bitlen_unsigned_val(32); + for (size_t i=0; i < 16; i++) { + msg.unique_id[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + msg.node_id = (uint8_t)random_bitlen_unsigned_val(7); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.RequestVote_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.RequestVote_req.c new file mode 100644 index 000000000..5bf5c2b1f --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.RequestVote_req.c @@ -0,0 +1,67 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_encode(struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_REQUEST_MAX_SIZE); + _uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest sample_uavcan_protocol_dynamic_node_id_server_RequestVoteRequest_msg(void) { + struct uavcan_protocol_dynamic_node_id_server_RequestVoteRequest msg; + + msg.term = (uint32_t)random_bitlen_unsigned_val(32); + msg.last_log_term = (uint32_t)random_bitlen_unsigned_val(32); + msg.last_log_index = (uint8_t)random_bitlen_unsigned_val(8); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.RequestVote_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.RequestVote_res.c new file mode 100644 index 000000000..f4e35e749 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.dynamic_node_id.server.RequestVote_res.c @@ -0,0 +1,65 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_encode(struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_RESPONSE_MAX_SIZE); + _uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_SERVER_REQUESTVOTE_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse sample_uavcan_protocol_dynamic_node_id_server_RequestVoteResponse_msg(void) { + struct uavcan_protocol_dynamic_node_id_server_RequestVoteResponse msg; + + msg.term = (uint32_t)random_bitlen_unsigned_val(32); + msg.vote_granted = (bool)random_bitlen_unsigned_val(1); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.BeginFirmwareUpdate_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.BeginFirmwareUpdate_req.c new file mode 100644 index 000000000..fa7f4b867 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.BeginFirmwareUpdate_req.c @@ -0,0 +1,66 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_BeginFirmwareUpdateRequest_encode(struct uavcan_protocol_file_BeginFirmwareUpdateRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_REQUEST_MAX_SIZE); + _uavcan_protocol_file_BeginFirmwareUpdateRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_BeginFirmwareUpdateRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_BeginFirmwareUpdateRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_BeginFirmwareUpdateRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_BeginFirmwareUpdateRequest sample_uavcan_protocol_file_BeginFirmwareUpdateRequest_msg(void) { + struct uavcan_protocol_file_BeginFirmwareUpdateRequest msg; + + msg.source_node_id = (uint8_t)random_bitlen_unsigned_val(8); + msg.image_file_remote_path = sample_uavcan_protocol_file_Path_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.BeginFirmwareUpdate_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.BeginFirmwareUpdate_res.c new file mode 100644 index 000000000..898feaf54 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.BeginFirmwareUpdate_res.c @@ -0,0 +1,68 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_BeginFirmwareUpdateResponse_encode(struct uavcan_protocol_file_BeginFirmwareUpdateResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_MAX_SIZE); + _uavcan_protocol_file_BeginFirmwareUpdateResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_BeginFirmwareUpdateResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_BeginFirmwareUpdateResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_BEGINFIRMWAREUPDATE_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_BeginFirmwareUpdateResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_BeginFirmwareUpdateResponse sample_uavcan_protocol_file_BeginFirmwareUpdateResponse_msg(void) { + struct uavcan_protocol_file_BeginFirmwareUpdateResponse msg; + + msg.error = (uint8_t)random_bitlen_unsigned_val(8); + msg.optional_error_message.len = (uint8_t)random_range_unsigned_val(0, 127); + for (size_t i=0; i < msg.optional_error_message.len; i++) { + msg.optional_error_message.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Delete_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Delete_req.c new file mode 100644 index 000000000..43f9414dc --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Delete_req.c @@ -0,0 +1,65 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_DeleteRequest_encode(struct uavcan_protocol_file_DeleteRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_DELETE_REQUEST_MAX_SIZE); + _uavcan_protocol_file_DeleteRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_DeleteRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_DeleteRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_DELETE_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_DeleteRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_DeleteRequest sample_uavcan_protocol_file_DeleteRequest_msg(void) { + struct uavcan_protocol_file_DeleteRequest msg; + + msg.path = sample_uavcan_protocol_file_Path_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Delete_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Delete_res.c new file mode 100644 index 000000000..37508d2d0 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Delete_res.c @@ -0,0 +1,64 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_DeleteResponse_encode(struct uavcan_protocol_file_DeleteResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_DELETE_RESPONSE_MAX_SIZE); + _uavcan_protocol_file_DeleteResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_DeleteResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_DeleteResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_DELETE_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_DeleteResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_DeleteResponse sample_uavcan_protocol_file_DeleteResponse_msg(void) { + struct uavcan_protocol_file_DeleteResponse msg; + + msg.error = sample_uavcan_protocol_file_Error_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.EntryType.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.EntryType.c new file mode 100644 index 000000000..971bc0783 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.EntryType.c @@ -0,0 +1,64 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_EntryType_encode(struct uavcan_protocol_file_EntryType* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_ENTRYTYPE_MAX_SIZE); + _uavcan_protocol_file_EntryType_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_EntryType_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_EntryType* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_ENTRYTYPE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_EntryType_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_EntryType sample_uavcan_protocol_file_EntryType_msg(void) { + struct uavcan_protocol_file_EntryType msg; + + msg.flags = (uint8_t)random_bitlen_unsigned_val(8); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Error.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Error.c new file mode 100644 index 000000000..c3c2a2b20 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Error.c @@ -0,0 +1,64 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_Error_encode(struct uavcan_protocol_file_Error* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_ERROR_MAX_SIZE); + _uavcan_protocol_file_Error_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_Error_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_Error* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_ERROR_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_Error_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_Error sample_uavcan_protocol_file_Error_msg(void) { + struct uavcan_protocol_file_Error msg; + + msg.value = (int16_t)random_bitlen_signed_val(16); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetDirectoryEntryInfo_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetDirectoryEntryInfo_req.c new file mode 100644 index 000000000..deeb8f17f --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetDirectoryEntryInfo_req.c @@ -0,0 +1,66 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_GetDirectoryEntryInfoRequest_encode(struct uavcan_protocol_file_GetDirectoryEntryInfoRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_REQUEST_MAX_SIZE); + _uavcan_protocol_file_GetDirectoryEntryInfoRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_GetDirectoryEntryInfoRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_GetDirectoryEntryInfoRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_GetDirectoryEntryInfoRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_GetDirectoryEntryInfoRequest sample_uavcan_protocol_file_GetDirectoryEntryInfoRequest_msg(void) { + struct uavcan_protocol_file_GetDirectoryEntryInfoRequest msg; + + msg.entry_index = (uint32_t)random_bitlen_unsigned_val(32); + msg.directory_path = sample_uavcan_protocol_file_Path_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetDirectoryEntryInfo_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetDirectoryEntryInfo_res.c new file mode 100644 index 000000000..67c411587 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetDirectoryEntryInfo_res.c @@ -0,0 +1,66 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_GetDirectoryEntryInfoResponse_encode(struct uavcan_protocol_file_GetDirectoryEntryInfoResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_RESPONSE_MAX_SIZE); + _uavcan_protocol_file_GetDirectoryEntryInfoResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_GetDirectoryEntryInfoResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_GetDirectoryEntryInfoResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_GETDIRECTORYENTRYINFO_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_GetDirectoryEntryInfoResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_GetDirectoryEntryInfoResponse sample_uavcan_protocol_file_GetDirectoryEntryInfoResponse_msg(void) { + struct uavcan_protocol_file_GetDirectoryEntryInfoResponse msg; + + msg.error = sample_uavcan_protocol_file_Error_msg(); + msg.entry_type = sample_uavcan_protocol_file_EntryType_msg(); + msg.entry_full_path = sample_uavcan_protocol_file_Path_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetInfo_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetInfo_req.c new file mode 100644 index 000000000..161917fb9 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetInfo_req.c @@ -0,0 +1,65 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_GetInfoRequest_encode(struct uavcan_protocol_file_GetInfoRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_GETINFO_REQUEST_MAX_SIZE); + _uavcan_protocol_file_GetInfoRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_GetInfoRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_GetInfoRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_GETINFO_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_GetInfoRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_GetInfoRequest sample_uavcan_protocol_file_GetInfoRequest_msg(void) { + struct uavcan_protocol_file_GetInfoRequest msg; + + msg.path = sample_uavcan_protocol_file_Path_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetInfo_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetInfo_res.c new file mode 100644 index 000000000..c250739f1 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.GetInfo_res.c @@ -0,0 +1,66 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_GetInfoResponse_encode(struct uavcan_protocol_file_GetInfoResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_GETINFO_RESPONSE_MAX_SIZE); + _uavcan_protocol_file_GetInfoResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_GetInfoResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_GetInfoResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_GETINFO_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_GetInfoResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_GetInfoResponse sample_uavcan_protocol_file_GetInfoResponse_msg(void) { + struct uavcan_protocol_file_GetInfoResponse msg; + + msg.size = (uint64_t)random_bitlen_unsigned_val(40); + msg.error = sample_uavcan_protocol_file_Error_msg(); + msg.entry_type = sample_uavcan_protocol_file_EntryType_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Path.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Path.c new file mode 100644 index 000000000..a6857f945 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Path.c @@ -0,0 +1,67 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_Path_encode(struct uavcan_protocol_file_Path* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_PATH_MAX_SIZE); + _uavcan_protocol_file_Path_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_Path_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_Path* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_PATH_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_Path_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_Path sample_uavcan_protocol_file_Path_msg(void) { + struct uavcan_protocol_file_Path msg; + + msg.path.len = (uint8_t)random_range_unsigned_val(0, 200); + for (size_t i=0; i < msg.path.len; i++) { + msg.path.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Read_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Read_req.c new file mode 100644 index 000000000..f57e4360c --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Read_req.c @@ -0,0 +1,66 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_ReadRequest_encode(struct uavcan_protocol_file_ReadRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_READ_REQUEST_MAX_SIZE); + _uavcan_protocol_file_ReadRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_ReadRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_ReadRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_READ_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_ReadRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_ReadRequest sample_uavcan_protocol_file_ReadRequest_msg(void) { + struct uavcan_protocol_file_ReadRequest msg; + + msg.offset = (uint64_t)random_bitlen_unsigned_val(40); + msg.path = sample_uavcan_protocol_file_Path_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Read_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Read_res.c new file mode 100644 index 000000000..b7cdf92d6 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Read_res.c @@ -0,0 +1,68 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_ReadResponse_encode(struct uavcan_protocol_file_ReadResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_READ_RESPONSE_MAX_SIZE); + _uavcan_protocol_file_ReadResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_ReadResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_ReadResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_READ_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_ReadResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_ReadResponse sample_uavcan_protocol_file_ReadResponse_msg(void) { + struct uavcan_protocol_file_ReadResponse msg; + + msg.error = sample_uavcan_protocol_file_Error_msg(); + msg.data.len = (uint16_t)random_range_unsigned_val(0, 256); + for (size_t i=0; i < msg.data.len; i++) { + msg.data.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Write_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Write_req.c new file mode 100644 index 000000000..78ead5705 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Write_req.c @@ -0,0 +1,70 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_WriteRequest_encode(struct uavcan_protocol_file_WriteRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_WRITE_REQUEST_MAX_SIZE); + _uavcan_protocol_file_WriteRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_WriteRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_WriteRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_WRITE_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_WriteRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_WriteRequest sample_uavcan_protocol_file_WriteRequest_msg(void) { + struct uavcan_protocol_file_WriteRequest msg; + + msg.offset = (uint64_t)random_bitlen_unsigned_val(40); + msg.path = sample_uavcan_protocol_file_Path_msg(); + msg.data.len = (uint8_t)random_range_unsigned_val(0, 192); + for (size_t i=0; i < msg.data.len; i++) { + msg.data.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Write_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Write_res.c new file mode 100644 index 000000000..2b434abf4 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.file.Write_res.c @@ -0,0 +1,64 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_file_WriteResponse_encode(struct uavcan_protocol_file_WriteResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_FILE_WRITE_RESPONSE_MAX_SIZE); + _uavcan_protocol_file_WriteResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_file_WriteResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_file_WriteResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_FILE_WRITE_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_file_WriteResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_file_WriteResponse sample_uavcan_protocol_file_WriteResponse_msg(void) { + struct uavcan_protocol_file_WriteResponse msg; + + msg.error = sample_uavcan_protocol_file_Error_msg(); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.Empty.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.Empty.c new file mode 100644 index 000000000..245e7aa71 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.Empty.c @@ -0,0 +1,63 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_param_Empty_encode(struct uavcan_protocol_param_Empty* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_PARAM_EMPTY_MAX_SIZE); + _uavcan_protocol_param_Empty_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_param_Empty_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_Empty* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_PARAM_EMPTY_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_param_Empty_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_Empty sample_uavcan_protocol_param_Empty_msg(void) { + struct uavcan_protocol_param_Empty msg; + + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.ExecuteOpcode_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.ExecuteOpcode_req.c new file mode 100644 index 000000000..173bba3ce --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.ExecuteOpcode_req.c @@ -0,0 +1,66 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_param_ExecuteOpcodeRequest_encode(struct uavcan_protocol_param_ExecuteOpcodeRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_MAX_SIZE); + _uavcan_protocol_param_ExecuteOpcodeRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_param_ExecuteOpcodeRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_ExecuteOpcodeRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_param_ExecuteOpcodeRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_ExecuteOpcodeRequest sample_uavcan_protocol_param_ExecuteOpcodeRequest_msg(void) { + struct uavcan_protocol_param_ExecuteOpcodeRequest msg; + + msg.opcode = (uint8_t)random_bitlen_unsigned_val(8); + msg.argument = (int64_t)random_bitlen_signed_val(48); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.ExecuteOpcode_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.ExecuteOpcode_res.c new file mode 100644 index 000000000..bed38ecd4 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.ExecuteOpcode_res.c @@ -0,0 +1,65 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_param_ExecuteOpcodeResponse_encode(struct uavcan_protocol_param_ExecuteOpcodeResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_RESPONSE_MAX_SIZE); + _uavcan_protocol_param_ExecuteOpcodeResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_param_ExecuteOpcodeResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_ExecuteOpcodeResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_PARAM_EXECUTEOPCODE_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_param_ExecuteOpcodeResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_ExecuteOpcodeResponse sample_uavcan_protocol_param_ExecuteOpcodeResponse_msg(void) { + struct uavcan_protocol_param_ExecuteOpcodeResponse msg; + + msg.argument = (int64_t)random_bitlen_signed_val(48); + msg.ok = (bool)random_bitlen_unsigned_val(1); + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.GetSet_req.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.GetSet_req.c new file mode 100644 index 000000000..dbbb2d5b9 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.GetSet_req.c @@ -0,0 +1,70 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_param_GetSetRequest_encode(struct uavcan_protocol_param_GetSetRequest* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_PARAM_GETSET_REQUEST_MAX_SIZE); + _uavcan_protocol_param_GetSetRequest_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_param_GetSetRequest_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_GetSetRequest* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_PARAM_GETSET_REQUEST_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_param_GetSetRequest_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_GetSetRequest sample_uavcan_protocol_param_GetSetRequest_msg(void) { + struct uavcan_protocol_param_GetSetRequest msg; + + msg.index = (uint16_t)random_bitlen_unsigned_val(13); + msg.value = sample_uavcan_protocol_param_Value_msg(); + msg.name.len = (uint8_t)random_range_unsigned_val(0, 92); + for (size_t i=0; i < msg.name.len; i++) { + msg.name.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.GetSet_res.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.GetSet_res.c new file mode 100644 index 000000000..ca0676f4d --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.GetSet_res.c @@ -0,0 +1,71 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_param_GetSetResponse_encode(struct uavcan_protocol_param_GetSetResponse* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_PARAM_GETSET_RESPONSE_MAX_SIZE); + _uavcan_protocol_param_GetSetResponse_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_param_GetSetResponse_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_GetSetResponse* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_PARAM_GETSET_RESPONSE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_param_GetSetResponse_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_GetSetResponse sample_uavcan_protocol_param_GetSetResponse_msg(void) { + struct uavcan_protocol_param_GetSetResponse msg; + + msg.value = sample_uavcan_protocol_param_Value_msg(); + msg.default_value = sample_uavcan_protocol_param_Value_msg(); + msg.max_value = sample_uavcan_protocol_param_NumericValue_msg(); + msg.min_value = sample_uavcan_protocol_param_NumericValue_msg(); + msg.name.len = (uint8_t)random_range_unsigned_val(0, 92); + for (size_t i=0; i < msg.name.len; i++) { + msg.name.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.NumericValue.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.NumericValue.c new file mode 100644 index 000000000..439d816fd --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.NumericValue.c @@ -0,0 +1,79 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_param_NumericValue_encode(struct uavcan_protocol_param_NumericValue* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_MAX_SIZE); + _uavcan_protocol_param_NumericValue_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_param_NumericValue_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_NumericValue* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_param_NumericValue_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_NumericValue sample_uavcan_protocol_param_NumericValue_msg(void) { + struct uavcan_protocol_param_NumericValue msg; + + msg.union_tag = random_range_unsigned_val(0, 2); + + switch(msg.union_tag) { + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_EMPTY: { + msg.empty = sample_uavcan_protocol_param_Empty_msg(); + break; + } + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_INTEGER_VALUE: { + msg.integer_value = (int64_t)random_bitlen_signed_val(64); + break; + } + case UAVCAN_PROTOCOL_PARAM_NUMERICVALUE_REAL_VALUE: { + msg.real_value = random_float_val(); + break; + } + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.Value.c b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.Value.c new file mode 100644 index 000000000..b8ad26463 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/dsdl_generated/src/uavcan.protocol.param.Value.c @@ -0,0 +1,90 @@ +#define CANARD_DSDLC_INTERNAL +#include +#include + +#ifdef CANARD_DSDLC_TEST_BUILD +#include +#endif + +uint32_t uavcan_protocol_param_Value_encode(struct uavcan_protocol_param_Value* msg, uint8_t* buffer +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif +) { + uint32_t bit_ofs = 0; + memset(buffer, 0, UAVCAN_PROTOCOL_PARAM_VALUE_MAX_SIZE); + _uavcan_protocol_param_Value_encode(buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); + return ((bit_ofs+7)/8); +} + +/* + return true if the decode is invalid + */ +bool uavcan_protocol_param_Value_decode(const CanardRxTransfer* transfer, struct uavcan_protocol_param_Value* msg) { +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > UAVCAN_PROTOCOL_PARAM_VALUE_MAX_SIZE)) { + return true; /* invalid payload length */ + } +#endif + uint32_t bit_ofs = 0; + if (_uavcan_protocol_param_Value_decode(transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { + return true; /* invalid payload */ + } + + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; +} + +#ifdef CANARD_DSDLC_TEST_BUILD +struct uavcan_protocol_param_Value sample_uavcan_protocol_param_Value_msg(void) { + struct uavcan_protocol_param_Value msg; + + msg.union_tag = random_range_unsigned_val(0, 4); + + switch(msg.union_tag) { + case UAVCAN_PROTOCOL_PARAM_VALUE_EMPTY: { + msg.empty = sample_uavcan_protocol_param_Empty_msg(); + break; + } + case UAVCAN_PROTOCOL_PARAM_VALUE_INTEGER_VALUE: { + msg.integer_value = (int64_t)random_bitlen_signed_val(64); + break; + } + case UAVCAN_PROTOCOL_PARAM_VALUE_REAL_VALUE: { + msg.real_value = random_float_val(); + break; + } + case UAVCAN_PROTOCOL_PARAM_VALUE_BOOLEAN_VALUE: { + msg.boolean_value = (uint8_t)random_bitlen_unsigned_val(8); + break; + } + case UAVCAN_PROTOCOL_PARAM_VALUE_STRING_VALUE: { + msg.string_value.len = (uint8_t)random_range_unsigned_val(0, 128); + for (size_t i=0; i < msg.string_value.len; i++) { + msg.string_value.data[i] = (uint8_t)random_bitlen_unsigned_val(8); + } + break; + } + } + return msg; +} +#endif diff --git a/bootloader/bootloader/DroneCAN/libcanard/LICENSE b/bootloader/bootloader/DroneCAN/libcanard/LICENSE new file mode 100644 index 000000000..9c6ac2ed5 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/libcanard/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 UAVCAN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/bootloader/bootloader/DroneCAN/libcanard/README.md b/bootloader/bootloader/DroneCAN/libcanard/README.md new file mode 100644 index 000000000..491202537 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/libcanard/README.md @@ -0,0 +1,4 @@ +# Libcanard + +This is a subset of +https://github.com/dronecan/libcanard diff --git a/bootloader/bootloader/DroneCAN/libcanard/canard.c b/bootloader/bootloader/DroneCAN/libcanard/canard.c new file mode 100644 index 000000000..5a27deea2 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/libcanard/canard.c @@ -0,0 +1,1938 @@ +/* + * Copyright (c) 2016-2019 UAVCAN Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Contributors: https://github.com/UAVCAN/libcanard/contributors + * + * Documentation: http://uavcan.org/Implementations/Libcanard + */ + +#include "canard_internals.h" +#include + + +#undef MIN +#undef MAX +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + + +#define TRANSFER_TIMEOUT_USEC 2000000U +#define IFACE_SWITCH_DELAY_USEC 1000000U + +#define TRANSFER_ID_BIT_LEN 5U +#define ANON_MSG_DATA_TYPE_ID_BIT_LEN 2U + +#define SOURCE_ID_FROM_ID(x) ((uint8_t) (((x) >> 0U) & 0x7FU)) +#define SERVICE_NOT_MSG_FROM_ID(x) ((bool) (((x) >> 7U) & 0x1U)) +#define REQUEST_NOT_RESPONSE_FROM_ID(x) ((bool) (((x) >> 15U) & 0x1U)) +#define DEST_ID_FROM_ID(x) ((uint8_t) (((x) >> 8U) & 0x7FU)) +#define PRIORITY_FROM_ID(x) ((uint8_t) (((x) >> 24U) & 0x1FU)) +#define MSG_TYPE_FROM_ID(x) ((uint16_t)(((x) >> 8U) & 0xFFFFU)) +#define SRV_TYPE_FROM_ID(x) ((uint8_t) (((x) >> 16U) & 0xFFU)) + +#define MAKE_TRANSFER_DESCRIPTOR(data_type_id, transfer_type, src_node_id, dst_node_id) \ + (((uint32_t)(data_type_id)) | (((uint32_t)(transfer_type)) << 16U) | \ + (((uint32_t)(src_node_id)) << 18U) | (((uint32_t)(dst_node_id)) << 25U)) + +#define TRANSFER_ID_FROM_TAIL_BYTE(x) ((uint8_t)((x) & 0x1FU)) + +// The extra cast to unsigned is needed to squelch warnings from clang-tidy +#define IS_START_OF_TRANSFER(x) ((bool)(((uint32_t)(x) >> 7U) & 0x1U)) +#define IS_END_OF_TRANSFER(x) ((bool)(((uint32_t)(x) >> 6U) & 0x1U)) +#define TOGGLE_BIT(x) ((bool)(((uint32_t)(x) >> 5U) & 0x1U)) + + + +/* + * API functions + */ +void canardInit(CanardInstance* out_ins, + void* mem_arena, + size_t mem_arena_size, + CanardOnTransferReception on_reception, + CanardShouldAcceptTransfer should_accept, + void* user_reference) +{ + CANARD_ASSERT(out_ins != NULL); + + /* + * Checking memory layout. + * This condition is supposed to be true for all 32-bit and smaller platforms. + * If your application fails here, make sure it's not built in 64-bit mode. + * Refer to the design documentation for more info. + */ + CANARD_ASSERT(CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE >= 5); + + memset(out_ins, 0, sizeof(*out_ins)); + + out_ins->node_id = CANARD_BROADCAST_NODE_ID; + out_ins->on_reception = on_reception; + out_ins->should_accept = should_accept; + out_ins->rx_states = NULL; + out_ins->tx_queue = NULL; + out_ins->user_reference = user_reference; +#if CANARD_ENABLE_TAO_OPTION + out_ins->tao_disabled = false; +#endif + size_t pool_capacity = mem_arena_size / CANARD_MEM_BLOCK_SIZE; + if (pool_capacity > 0xFFFFU) + { + pool_capacity = 0xFFFFU; + } + + initPoolAllocator(&out_ins->allocator, mem_arena, (uint16_t)pool_capacity); +} + +void* canardGetUserReference(const CanardInstance* ins) +{ + CANARD_ASSERT(ins != NULL); + return ins->user_reference; +} + +void canardSetLocalNodeID(CanardInstance* ins, uint8_t self_node_id) +{ + CANARD_ASSERT(ins != NULL); + + if ((ins->node_id == CANARD_BROADCAST_NODE_ID) && + (self_node_id >= CANARD_MIN_NODE_ID) && + (self_node_id <= CANARD_MAX_NODE_ID)) + { + ins->node_id = self_node_id; + } + else + { + CANARD_ASSERT(false); + } +} + +uint8_t canardGetLocalNodeID(const CanardInstance* ins) +{ + return ins->node_id; +} + +void canardForgetLocalNodeID(CanardInstance* ins) { + ins->node_id = CANARD_BROADCAST_NODE_ID; +} + +void canardInitTxTransfer(CanardTxTransfer* transfer) +{ + CANARD_ASSERT(transfer != NULL); + memset(transfer, 0, sizeof(*transfer)); +} + + +int16_t canardBroadcast(CanardInstance* ins, + uint64_t data_type_signature, + uint16_t data_type_id, + uint8_t* inout_transfer_id, + uint8_t priority, + const void* payload, + uint16_t payload_len +#if CANARD_ENABLE_DEADLINE + ,uint64_t tx_deadline +#endif +#if CANARD_MULTI_IFACE + ,uint8_t iface_mask +#endif +#if CANARD_ENABLE_CANFD + ,bool canfd +#endif +) +{ + // create transfer object + CanardTxTransfer transfer_object = { + .data_type_signature = data_type_signature, + .data_type_id = data_type_id, + .inout_transfer_id = inout_transfer_id, + .priority = priority, + .payload = (uint8_t*)payload, + .payload_len = payload_len, +#if CANARD_ENABLE_DEADLINE + .deadline_usec = tx_deadline, +#endif +#if CANARD_MULTI_IFACE + .iface_mask = iface_mask, +#endif +#if CANARD_ENABLE_CANFD + .canfd = canfd, +#endif + }; + + return canardBroadcastObj(ins, &transfer_object); +} + +int16_t canardBroadcastObj(CanardInstance* ins, CanardTxTransfer* transfer_object) +{ + if (transfer_object->payload == NULL && transfer_object->payload_len > 0) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + if (transfer_object->priority > CANARD_TRANSFER_PRIORITY_LOWEST) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + + uint32_t can_id = 0; + uint16_t crc = 0xFFFFU; + + if (canardGetLocalNodeID(ins) == 0) + { + if (transfer_object->payload_len > 7) + { + return -CANARD_ERROR_NODE_ID_NOT_SET; + } + + static const uint16_t DTIDMask = (1U << ANON_MSG_DATA_TYPE_ID_BIT_LEN) - 1U; + + if ((transfer_object->data_type_id & DTIDMask) != transfer_object->data_type_id) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + + // anonymous transfer, random discriminator + const uint16_t discriminator = (uint16_t)((crcAdd(0xFFFFU, transfer_object->payload, transfer_object->payload_len)) & 0x7FFEU); + can_id = ((uint32_t) transfer_object->priority << 24U) | ((uint32_t) discriminator << 9U) | + ((uint32_t) (transfer_object->data_type_id & DTIDMask) << 8U) | (uint32_t) canardGetLocalNodeID(ins); + } + else + { + can_id = ((uint32_t) transfer_object->priority << 24U) | ((uint32_t) transfer_object->data_type_id << 8U) | (uint32_t) canardGetLocalNodeID(ins); + crc = calculateCRC(transfer_object); + } + + const int16_t result = enqueueTxFrames(ins, can_id, crc, transfer_object); + + if (result > 0) { + incrementTransferID(transfer_object->inout_transfer_id); + } + + return result; +} + +/* + the following FromIdx and ToIdx functions allow for the + CanardBufferBlock and CanartRxState structures to have the same size + on 32 bit and 64 bit platforms, which allows for easier testing in + simulator environments + */ +CANARD_INTERNAL CanardBufferBlock *canardBufferFromIdx(CanardPoolAllocator* allocator, canard_buffer_idx_t idx) +{ +#if CANARD_64_BIT + if (idx == CANARD_BUFFER_IDX_NONE) { + return NULL; + } + return (CanardBufferBlock *)(uintptr_t)&((uint8_t *)allocator->arena)[idx-1]; +#else + (void)allocator; + return (CanardBufferBlock *)idx; +#endif +} + +CANARD_INTERNAL canard_buffer_idx_t canardBufferToIdx(CanardPoolAllocator* allocator, const CanardBufferBlock *buf) +{ +#if CANARD_64_BIT + if (buf == NULL) { + return CANARD_BUFFER_IDX_NONE; + } + return 1U+((canard_buffer_idx_t)((uint8_t *)buf - (uint8_t *)allocator->arena)); +#else + (void)allocator; + return (canard_buffer_idx_t)buf; +#endif +} + +CANARD_INTERNAL CanardRxState *canardRxFromIdx(CanardPoolAllocator* allocator, canard_buffer_idx_t idx) +{ +#if CANARD_64_BIT + if (idx == CANARD_BUFFER_IDX_NONE) { + return NULL; + } + return (CanardRxState *)(uintptr_t)&((uint8_t *)allocator->arena)[idx-1]; +#else + (void)allocator; + return (CanardRxState *)idx; +#endif +} + +CANARD_INTERNAL canard_buffer_idx_t canardRxToIdx(CanardPoolAllocator* allocator, const CanardRxState *rx) +{ +#if CANARD_64_BIT + if (rx == NULL) { + return CANARD_BUFFER_IDX_NONE; + } + return 1U+((canard_buffer_idx_t)((uint8_t *)rx - (uint8_t *)allocator->arena)); +#else + (void)allocator; + return (canard_buffer_idx_t)rx; +#endif +} + +CANARD_INTERNAL uint16_t calculateCRC(const CanardTxTransfer* transfer_object) +{ + uint16_t crc = 0xFFFFU; +#if CANARD_ENABLE_CANFD + if ((transfer_object->payload_len > 7 && !transfer_object->canfd) || + (transfer_object->payload_len > 63 && transfer_object->canfd)) +#else + if (transfer_object->payload_len > 7) +#endif + { + crc = crcAddSignature(crc, transfer_object->data_type_signature); + crc = crcAdd(crc, transfer_object->payload, transfer_object->payload_len); +#if CANARD_ENABLE_CANFD + if (transfer_object->payload_len > 63 && transfer_object->canfd) { + uint8_t empty = 0; + uint8_t padding = (uint8_t)dlcToDataLength(dataLengthToDlc((uint16_t)((transfer_object->payload_len+2) % 63)+1))-1; + padding -= (uint8_t)((transfer_object->payload_len+2) % 63); + for (uint8_t i=0; ipayload == NULL && transfer_object->payload_len > 0) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + if (transfer_object->priority > CANARD_TRANSFER_PRIORITY_LOWEST) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + if (canardGetLocalNodeID(ins) == 0) + { + return -CANARD_ERROR_NODE_ID_NOT_SET; + } + + const uint32_t can_id = ((uint32_t) transfer_object->priority << 24U) | ((uint32_t) transfer_object->data_type_id << 16U) | + ((uint32_t) transfer_object->transfer_type << 15U) | ((uint32_t) destination_node_id << 8U) | + (1U << 7U) | (uint32_t) canardGetLocalNodeID(ins); + + uint16_t crc = calculateCRC(transfer_object); + + + const int16_t result = enqueueTxFrames(ins, can_id, crc, transfer_object); + + if (result > 0 && transfer_object->transfer_type == CanardTransferTypeRequest) // Response Transfer ID must not be altered + { + incrementTransferID(transfer_object->inout_transfer_id); + } + + return result; +} + +CanardCANFrame* canardPeekTxQueue(const CanardInstance* ins) +{ + if (ins->tx_queue == NULL) + { + return NULL; + } + return &ins->tx_queue->frame; +} + +void canardPopTxQueue(CanardInstance* ins) +{ + CanardTxQueueItem* item = ins->tx_queue; + ins->tx_queue = item->next; + freeBlock(&ins->allocator, item); +} + +int16_t canardHandleRxFrame(CanardInstance* ins, const CanardCANFrame* frame, uint64_t timestamp_usec) +{ + const CanardTransferType transfer_type = extractTransferType(frame->id); + const uint8_t destination_node_id = (transfer_type == CanardTransferTypeBroadcast) ? + (uint8_t)CANARD_BROADCAST_NODE_ID : + DEST_ID_FROM_ID(frame->id); + + // TODO: This function should maintain statistics of transfer errors and such. + + if ((frame->id & CANARD_CAN_FRAME_EFF) == 0 || + (frame->id & CANARD_CAN_FRAME_RTR) != 0 || + (frame->id & CANARD_CAN_FRAME_ERR) != 0 || + (frame->data_len < 1)) + { + return -CANARD_ERROR_RX_INCOMPATIBLE_PACKET; + } + + if (transfer_type != CanardTransferTypeBroadcast && + destination_node_id != canardGetLocalNodeID(ins)) + { + return -CANARD_ERROR_RX_WRONG_ADDRESS; + } + + const uint8_t priority = PRIORITY_FROM_ID(frame->id); + const uint8_t source_node_id = SOURCE_ID_FROM_ID(frame->id); + const uint16_t data_type_id = extractDataType(frame->id); + const uint32_t transfer_descriptor = + MAKE_TRANSFER_DESCRIPTOR(data_type_id, transfer_type, source_node_id, destination_node_id); + + const uint8_t tail_byte = frame->data[frame->data_len - 1]; + + uint64_t data_type_signature = 0; + CanardRxState* rx_state = NULL; + + if (IS_START_OF_TRANSFER(tail_byte)) + { + + if (ins->should_accept(ins, &data_type_signature, data_type_id, transfer_type, source_node_id)) + { + rx_state = traverseRxStates(ins, transfer_descriptor); + + if(rx_state == NULL) + { + return -CANARD_ERROR_OUT_OF_MEMORY; + } + } + else + { + return -CANARD_ERROR_RX_NOT_WANTED; + } + } + else + { + rx_state = findRxState(ins, transfer_descriptor); + + if (rx_state == NULL) + { + return -CANARD_ERROR_RX_MISSED_START; + } + } + + CANARD_ASSERT(rx_state != NULL); // All paths that lead to NULL should be terminated with return above + + // Resolving the state flags: + const bool not_initialized = rx_state->timestamp_usec == 0; + const bool tid_timed_out = (timestamp_usec - rx_state->timestamp_usec) > TRANSFER_TIMEOUT_USEC; + const bool same_iface = frame->iface_id == rx_state->iface_id; + const bool first_frame = IS_START_OF_TRANSFER(tail_byte); + const bool not_previous_tid = + computeTransferIDForwardDistance((uint8_t) rx_state->transfer_id, TRANSFER_ID_FROM_TAIL_BYTE(tail_byte)) > 1; + const bool iface_switch_allowed = (timestamp_usec - rx_state->timestamp_usec) > IFACE_SWITCH_DELAY_USEC; + const bool non_wrapped_tid = computeTransferIDForwardDistance(TRANSFER_ID_FROM_TAIL_BYTE(tail_byte), (uint8_t) rx_state->transfer_id) < (1 << (TRANSFER_ID_BIT_LEN-1)); + const bool incomplete_frame = rx_state->buffer_blocks != CANARD_BUFFER_IDX_NONE; + + const bool need_restart = + (not_initialized) || + (tid_timed_out) || + (same_iface && first_frame && (not_previous_tid || incomplete_frame)) || + (iface_switch_allowed && first_frame && non_wrapped_tid); + + if (need_restart) + { + rx_state->transfer_id = TRANSFER_ID_FROM_TAIL_BYTE(tail_byte); + rx_state->next_toggle = 0; + releaseStatePayload(ins, rx_state); + rx_state->iface_id = frame->iface_id; + if (!IS_START_OF_TRANSFER(tail_byte)) + { + rx_state->transfer_id++; + return -CANARD_ERROR_RX_MISSED_START; + } + } + + if (frame->iface_id != rx_state->iface_id) + { + // drop frame if coming from unexpected interface + return CANARD_OK; + } + + if (IS_START_OF_TRANSFER(tail_byte) && IS_END_OF_TRANSFER(tail_byte)) // single frame transfer + { + rx_state->timestamp_usec = timestamp_usec; + CanardRxTransfer rx_transfer = { + .timestamp_usec = timestamp_usec, + .payload_head = frame->data, + .payload_len = (uint8_t)(frame->data_len - 1U), + .data_type_id = data_type_id, + .transfer_type = (uint8_t)transfer_type, + .transfer_id = TRANSFER_ID_FROM_TAIL_BYTE(tail_byte), + .priority = priority, + .source_node_id = source_node_id, +#if CANARD_ENABLE_CANFD + .canfd = frame->canfd, + .tao = !(frame->canfd || ins->tao_disabled) +#elif CANARD_ENABLE_TAO_OPTION + .tao = !ins->tao_disabled +#endif + }; + + ins->on_reception(ins, &rx_transfer); + + prepareForNextTransfer(rx_state); + return CANARD_OK; + } + + if (TOGGLE_BIT(tail_byte) != rx_state->next_toggle) + { + return -CANARD_ERROR_RX_WRONG_TOGGLE; + } + + if (TRANSFER_ID_FROM_TAIL_BYTE(tail_byte) != rx_state->transfer_id) + { + return -CANARD_ERROR_RX_UNEXPECTED_TID; + } + + if (IS_START_OF_TRANSFER(tail_byte) && !IS_END_OF_TRANSFER(tail_byte)) // Beginning of multi frame transfer + { + if (frame->data_len <= 3) + { + return -CANARD_ERROR_RX_SHORT_FRAME; + } + + // take off the crc and store the payload + rx_state->timestamp_usec = timestamp_usec; + rx_state->payload_len = 0; + const int16_t ret = bufferBlockPushBytes(&ins->allocator, rx_state, frame->data + 2, + (uint8_t) (frame->data_len - 3)); + if (ret < 0) + { + releaseStatePayload(ins, rx_state); + prepareForNextTransfer(rx_state); + return -CANARD_ERROR_OUT_OF_MEMORY; + } + rx_state->payload_crc = (uint16_t)(((uint16_t) frame->data[0]) | (uint16_t)((uint16_t) frame->data[1] << 8U)); + rx_state->calculated_crc = crcAddSignature(0xFFFFU, data_type_signature); + rx_state->calculated_crc = crcAdd((uint16_t)rx_state->calculated_crc, + frame->data + 2, (uint8_t)(frame->data_len - 3)); + } + else if (!IS_START_OF_TRANSFER(tail_byte) && !IS_END_OF_TRANSFER(tail_byte)) // Middle of a multi-frame transfer + { + const int16_t ret = bufferBlockPushBytes(&ins->allocator, rx_state, frame->data, + (uint8_t) (frame->data_len - 1)); + if (ret < 0) + { + releaseStatePayload(ins, rx_state); + prepareForNextTransfer(rx_state); + return -CANARD_ERROR_OUT_OF_MEMORY; + } + rx_state->calculated_crc = crcAdd((uint16_t)rx_state->calculated_crc, + frame->data, (uint8_t)(frame->data_len - 1)); + } + else // End of a multi-frame transfer + { + const uint8_t frame_payload_size = (uint8_t)(frame->data_len - 1); + + uint8_t tail_offset = 0; + + if (rx_state->payload_len < CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE) + { + // Copy the beginning of the frame into the head, point the tail pointer to the remainder + for (size_t i = rx_state->payload_len; + (i < CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE) && (tail_offset < frame_payload_size); + i++, tail_offset++) + { + rx_state->buffer_head[i] = frame->data[tail_offset]; + } + } + else + { + // Like above, except that the beginning goes into the last block of the storage + CanardBufferBlock* block = canardBufferFromIdx(&ins->allocator, rx_state->buffer_blocks); + if (block != NULL) + { + size_t offset = CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE; // Payload offset of the first block + while (block->next != NULL) + { + block = block->next; + offset += CANARD_BUFFER_BLOCK_DATA_SIZE; + } + CANARD_ASSERT(block != NULL); + + const size_t offset_within_block = rx_state->payload_len - offset; + CANARD_ASSERT(offset_within_block <= CANARD_BUFFER_BLOCK_DATA_SIZE); + + for (size_t i = offset_within_block; + (i < CANARD_BUFFER_BLOCK_DATA_SIZE) && (tail_offset < frame_payload_size); + i++, tail_offset++) + { + block->data[i] = frame->data[tail_offset]; + } + } + } + + CanardRxTransfer rx_transfer = { + .timestamp_usec = timestamp_usec, + .payload_head = rx_state->buffer_head, + .payload_middle = canardBufferFromIdx(&ins->allocator, rx_state->buffer_blocks), + .payload_tail = (tail_offset >= frame_payload_size) ? NULL : (&frame->data[tail_offset]), + .payload_len = (uint16_t)(rx_state->payload_len + frame_payload_size), + .data_type_id = data_type_id, + .transfer_type = (uint8_t)transfer_type, + .transfer_id = TRANSFER_ID_FROM_TAIL_BYTE(tail_byte), + .priority = priority, + .source_node_id = source_node_id, + +#if CANARD_ENABLE_CANFD + .canfd = frame->canfd, + .tao = !(frame->canfd || ins->tao_disabled) +#elif CANARD_ENABLE_TAO_OPTION + .tao = !ins->tao_disabled +#endif + }; + + rx_state->buffer_blocks = CANARD_BUFFER_IDX_NONE; // Block list ownership has been transferred to rx_transfer! + + // CRC validation + rx_state->calculated_crc = crcAdd((uint16_t)rx_state->calculated_crc, frame->data, frame->data_len - 1U); + if (rx_state->calculated_crc == rx_state->payload_crc) + { + ins->on_reception(ins, &rx_transfer); + } + + // Making sure the payload is released even if the application didn't bother with it + canardReleaseRxTransferPayload(ins, &rx_transfer); + prepareForNextTransfer(rx_state); + + if (rx_state->calculated_crc == rx_state->payload_crc) + { + return CANARD_OK; + } + else + { + return -CANARD_ERROR_RX_BAD_CRC; + } + } + + rx_state->next_toggle = rx_state->next_toggle ? 0 : 1; + return CANARD_OK; +} + +void canardCleanupStaleTransfers(CanardInstance* ins, uint64_t current_time_usec) +{ + CanardRxState* prev = ins->rx_states, * state = ins->rx_states; + + while (state != NULL) + { + if ((current_time_usec - state->timestamp_usec) > TRANSFER_TIMEOUT_USEC) + { + if (state == ins->rx_states) + { + releaseStatePayload(ins, state); + ins->rx_states = canardRxFromIdx(&ins->allocator, ins->rx_states->next); + freeBlock(&ins->allocator, state); + state = ins->rx_states; + prev = state; + } + else + { + releaseStatePayload(ins, state); + prev->next = state->next; + freeBlock(&ins->allocator, state); + state = canardRxFromIdx(&ins->allocator, prev->next); + } + } + else + { + prev = state; + state = canardRxFromIdx(&ins->allocator, state->next); + } + } + +#if CANARD_MULTI_IFACE || CANARD_ENABLE_DEADLINE + // remove stale TX transfers + CanardTxQueueItem* prev_item = ins->tx_queue, * item = ins->tx_queue; + while (item != NULL) + { +#if CANARD_MULTI_IFACE && CANARD_ENABLE_DEADLINE + if ((current_time_usec > item->frame.deadline_usec) || item->frame.iface_mask == 0) +#elif CANARD_MULTI_IFACE + if (item->frame.iface_mask == 0) +#else + if (current_time_usec > item->frame.deadline_usec) +#endif + { + if (item == ins->tx_queue) + { + ins->tx_queue = ins->tx_queue->next; + freeBlock(&ins->allocator, item); + item = ins->tx_queue; + prev_item = item; + } + else + { + prev_item->next = item->next; + freeBlock(&ins->allocator, item); + item = prev_item->next; + } + } + else + { + prev_item = item; + item = item->next; + } + } +#endif +} + +int16_t canardDecodeScalar(const CanardRxTransfer* transfer, + uint32_t bit_offset, + uint8_t bit_length, + bool value_is_signed, + void* out_value) +{ + if (transfer == NULL || out_value == NULL) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + + if (bit_length < 1 || bit_length > 64) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + + if (bit_length == 1 && value_is_signed) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + + /* + * Reading raw bytes into the temporary storage. + * Luckily, C guarantees that every element is aligned at the beginning (lower address) of the union. + */ + union + { + bool boolean; ///< sizeof(bool) is implementation-defined, so it has to be handled separately + uint8_t u8; ///< Also char + int8_t s8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; ///< Also float, possibly double, possibly long double (depends on implementation) + uint64_t u64; + int64_t s64; ///< Also double, possibly float, possibly long double (depends on implementation) + uint8_t bytes[8]; + } storage; + + memset(&storage, 0, sizeof(storage)); // This is important + + const int16_t result = descatterTransferPayload(transfer, bit_offset, bit_length, &storage.bytes[0]); + if (result <= 0) + { + return result; + } + + CANARD_ASSERT((result > 0) && (result <= 64) && (result <= bit_length)); + + /* + * The bit copy algorithm assumes that more significant bits have lower index, so we need to shift some. + * Extra most significant bits will be filled with zeroes, which is fine. + * Coverity Scan mistakenly believes that the array may be overrun if bit_length == 64; however, this branch will + * not be taken if bit_length == 64, because 64 % 8 == 0. + */ + if ((bit_length % 8) != 0) + { + // coverity[overrun-local] + storage.bytes[bit_length / 8U] = (uint8_t)(storage.bytes[bit_length / 8U] >> ((8U - (bit_length % 8U)) & 7U)); + } + + /* + * Determining the closest standard byte length - this will be needed for byte reordering and sign bit extension. + */ + uint8_t std_byte_length = 0; + if (bit_length == 1) { std_byte_length = sizeof(bool); } + else if (bit_length <= 8) { std_byte_length = 1; } + else if (bit_length <= 16) { std_byte_length = 2; } + else if (bit_length <= 32) { std_byte_length = 4; } + else if (bit_length <= 64) { std_byte_length = 8; } + else + { + CANARD_ASSERT(false); + return -CANARD_ERROR_INTERNAL; + } + + CANARD_ASSERT((std_byte_length > 0) && (std_byte_length <= 8)); + + /* + * Flipping the byte order if needed. + */ + if (isBigEndian()) + { + swapByteOrder(&storage.bytes[0], std_byte_length); + } + +#if WORD_ADDRESSING_IS_16BITS + /* + * Copying 8-bit array to 64-bit storage + */ + { + uint64_t temp = 0; + for(uint16_t i=0; i 0); + return result; +} + +void canardEncodeScalar(void* destination, + uint32_t bit_offset, + uint8_t bit_length, + const void* value) +{ + /* + * This function can only fail due to invalid arguments, so it was decided to make it return void, + * and in the case of bad arguments try the best effort or just trigger an CANARD_ASSERTion failure. + * Maybe not the best solution, but it simplifies the API. + */ + CANARD_ASSERT(destination != NULL); + CANARD_ASSERT(value != NULL); + + if (bit_length > 64) + { + CANARD_ASSERT(false); + bit_length = 64; + } + + if (bit_length < 1) + { + CANARD_ASSERT(false); + bit_length = 1; + } + + /* + * Preparing the data in the temporary storage. + */ + union + { + bool boolean; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uint8_t bytes[8]; + } storage; + + memset(&storage, 0, sizeof(storage)); + + uint8_t std_byte_length = 0; + + // Extra most significant bits can be safely ignored here. + if (bit_length == 1) { std_byte_length = sizeof(bool); storage.boolean = (*((bool*) value) != 0); } + else if (bit_length <= 8) { std_byte_length = 1; storage.u8 = *((uint8_t*) value); } + else if (bit_length <= 16) { std_byte_length = 2; storage.u16 = *((uint16_t*) value); } + else if (bit_length <= 32) { std_byte_length = 4; storage.u32 = *((uint32_t*) value); } + else if (bit_length <= 64) { std_byte_length = 8; storage.u64 = *((uint64_t*) value); } + else + { + CANARD_ASSERT(false); + } + + CANARD_ASSERT(std_byte_length > 0); + +#if WORD_ADDRESSING_IS_16BITS + /* + * Copying 64-bit storage to 8-bit array + */ + { + uint64_t temp = storage.u64; + for(uint16_t i=0; i> (8*i)) & 0xFFU; + } + } +#endif + + if (isBigEndian()) + { + swapByteOrder(&storage.bytes[0], std_byte_length); + } + + /* + * The bit copy algorithm assumes that more significant bits have lower index, so we need to shift some. + * Extra least significant bits will be filled with zeroes, which is fine. + * Extra most significant bits will be discarded here. + * Coverity Scan mistakenly believes that the array may be overrun if bit_length == 64; however, this branch will + * not be taken if bit_length == 64, because 64 % 8 == 0. + */ + if ((bit_length % 8) != 0) + { + // coverity[overrun-local] + storage.bytes[bit_length / 8U] = (uint8_t)(storage.bytes[bit_length / 8U] << ((8U - (bit_length % 8U)) & 7U)); + } + + /* + * Now, the storage contains properly serialized scalar. Copying it out. + */ + copyBitArray(&storage.bytes[0], 0, bit_length, (uint8_t*) destination, bit_offset); +} + +void canardReleaseRxTransferPayload(CanardInstance* ins, CanardRxTransfer* transfer) +{ + while (transfer->payload_middle != NULL) + { + CanardBufferBlock* const temp = transfer->payload_middle->next; + freeBlock(&ins->allocator, transfer->payload_middle); + transfer->payload_middle = temp; + } + + transfer->payload_middle = NULL; + transfer->payload_head = NULL; + transfer->payload_tail = NULL; + transfer->payload_len = 0; +} + +CanardPoolAllocatorStatistics canardGetPoolAllocatorStatistics(CanardInstance* ins) +{ + return ins->allocator.statistics; +} + +uint16_t canardConvertNativeFloatToFloat16(float value) +{ + CANARD_ASSERT(sizeof(float) == CANARD_SIZEOF_FLOAT); + + union FP32 + { + uint32_t u; + float f; + }; + + const union FP32 f32inf = { 255UL << 23U }; + const union FP32 f16inf = { 31UL << 23U }; + const union FP32 magic = { 15UL << 23U }; + const uint32_t sign_mask = 0x80000000UL; + const uint32_t round_mask = 0xFFFFF000UL; + + union FP32 in; + in.f = value; + uint32_t sign = in.u & sign_mask; + in.u ^= sign; + + uint16_t out = 0; + + if (in.u >= f32inf.u) + { + out = (in.u > f32inf.u) ? (uint16_t)0x7FFFU : (uint16_t)0x7C00U; + } + else + { + in.u &= round_mask; + in.f *= magic.f; + in.u -= round_mask; + if (in.u > f16inf.u) + { + in.u = f16inf.u; + } + out = (uint16_t)(in.u >> 13U); + } + + out |= (uint16_t)(sign >> 16U); + + return out; +} + +float canardConvertFloat16ToNativeFloat(uint16_t value) +{ + CANARD_ASSERT(sizeof(float) == CANARD_SIZEOF_FLOAT); + + union FP32 + { + uint32_t u; + float f; + }; + + const union FP32 magic = { (254UL - 15UL) << 23U }; + const union FP32 was_inf_nan = { (127UL + 16UL) << 23U }; + union FP32 out; + + out.u = (value & 0x7FFFU) << 13U; + out.f *= magic.f; + if (out.f >= was_inf_nan.f) + { + out.u |= 255UL << 23U; + } + out.u |= (value & 0x8000UL) << 16U; + + return out.f; +} + +/* + * Internal (static functions) + */ +CANARD_INTERNAL int16_t computeTransferIDForwardDistance(uint8_t a, uint8_t b) +{ + int16_t d = (int16_t)(a - b); + if (d < 0) + { + d = (int16_t)(d + (int16_t)(1U << TRANSFER_ID_BIT_LEN)); + } + return d; +} + +CANARD_INTERNAL void incrementTransferID(uint8_t* transfer_id) +{ + CANARD_ASSERT(transfer_id != NULL); + + (*transfer_id)++; + if (*transfer_id >= 32) + { + *transfer_id = 0; + } +} + +CANARD_INTERNAL uint16_t dlcToDataLength(uint16_t dlc) { + /* + Data Length Code 9 10 11 12 13 14 15 + Number of data bytes 12 16 20 24 32 48 64 + */ + if (dlc <= 8) { + return dlc; + } else if (dlc == 9) { + return 12; + } else if (dlc == 10) { + return 16; + } else if (dlc == 11) { + return 20; + } else if (dlc == 12) { + return 24; + } else if (dlc == 13) { + return 32; + } else if (dlc == 14) { + return 48; + } + return 64; +} + +CANARD_INTERNAL uint16_t dataLengthToDlc(uint16_t data_length) { + if (data_length <= 8) { + return data_length; + } else if (data_length <= 12) { + return 9; + } else if (data_length <= 16) { + return 10; + } else if (data_length <= 20) { + return 11; + } else if (data_length <= 24) { + return 12; + } else if (data_length <= 32) { + return 13; + } else if (data_length <= 48) { + return 14; + } + return 15; +} + +CANARD_INTERNAL int16_t enqueueTxFrames(CanardInstance* ins, + uint32_t can_id, + uint16_t crc, + CanardTxTransfer* transfer +) +{ + CANARD_ASSERT(ins != NULL); + CANARD_ASSERT((can_id & CANARD_CAN_EXT_ID_MASK) == can_id); // Flags must be cleared + + if (transfer->inout_transfer_id == NULL) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + + if ((transfer->payload_len > 0) && (transfer->payload == NULL)) + { + return -CANARD_ERROR_INVALID_ARGUMENT; + } + + int16_t result = 0; +#if CANARD_ENABLE_CANFD + uint8_t frame_max_data_len = transfer->canfd ? CANARD_CANFD_FRAME_MAX_DATA_LEN:CANARD_CAN_FRAME_MAX_DATA_LEN; +#else + uint8_t frame_max_data_len = CANARD_CAN_FRAME_MAX_DATA_LEN; +#endif + if (transfer->payload_len < frame_max_data_len) // Single frame transfer + { + CanardTxQueueItem* queue_item = createTxItem(&ins->allocator); + if (queue_item == NULL) + { + return -CANARD_ERROR_OUT_OF_MEMORY; + } + + memcpy(queue_item->frame.data, transfer->payload, transfer->payload_len); + + transfer->payload_len = dlcToDataLength(dataLengthToDlc(transfer->payload_len+1))-1; + queue_item->frame.data_len = (uint8_t)(transfer->payload_len + 1); + queue_item->frame.data[transfer->payload_len] = (uint8_t)(0xC0U | (*transfer->inout_transfer_id & 31U)); + queue_item->frame.id = can_id | CANARD_CAN_FRAME_EFF; +#if CANARD_ENABLE_DEADLINE + queue_item->frame.deadline_usec = transfer->deadline_usec; +#endif +#if CANARD_MULTI_IFACE + queue_item->frame.iface_mask = transfer->iface_mask; +#endif +#if CANARD_ENABLE_CANFD + queue_item->frame.canfd = transfer->canfd; +#endif + pushTxQueue(ins, queue_item); + result++; + } + else // Multi frame transfer + { + uint16_t data_index = 0; + uint8_t toggle = 0; + uint8_t sot_eot = 0x80; + + CanardTxQueueItem* queue_item = NULL; + + while (transfer->payload_len - data_index != 0) + { + queue_item = createTxItem(&ins->allocator); + if (queue_item == NULL) + { + CANARD_ASSERT(false); + return -CANARD_ERROR_OUT_OF_MEMORY; // TODO: Purge all frames enqueued so far + } + + uint16_t i = 0; + if (data_index == 0) + { + // add crc + queue_item->frame.data[0] = (uint8_t) (crc); + queue_item->frame.data[1] = (uint8_t) (crc >> 8U); + i = 2; + } + else + { + i = 0; + } + + for (; i < (frame_max_data_len - 1) && data_index < transfer->payload_len; i++, data_index++) + { + queue_item->frame.data[i] = transfer->payload[data_index]; + } + // tail byte + sot_eot = (data_index == transfer->payload_len) ? (uint8_t)0x40 : sot_eot; + + i = dlcToDataLength(dataLengthToDlc(i+1))-1; + queue_item->frame.data[i] = (uint8_t)(sot_eot | ((uint32_t)toggle << 5U) | ((uint32_t)*transfer->inout_transfer_id & 31U)); + queue_item->frame.id = can_id | CANARD_CAN_FRAME_EFF; + queue_item->frame.data_len = (uint8_t)(i + 1); +#if CANARD_ENABLE_DEADLINE + queue_item->frame.deadline_usec = transfer->deadline_usec; +#endif +#if CANARD_MULTI_IFACE + queue_item->frame.iface_mask = transfer->iface_mask; +#endif +#if CANARD_ENABLE_CANFD + queue_item->frame.canfd = transfer->canfd; +#endif + pushTxQueue(ins, queue_item); + + result++; + toggle ^= 1; + sot_eot = 0; + } + } + + return result; +} + +/** + * Puts frame on on the TX queue. Higher priority placed first + */ +CANARD_INTERNAL void pushTxQueue(CanardInstance* ins, CanardTxQueueItem* item) +{ + CANARD_ASSERT(ins != NULL); + CANARD_ASSERT(item->frame.data_len > 0); // UAVCAN doesn't allow zero-payload frames + + if (ins->tx_queue == NULL) + { + ins->tx_queue = item; + return; + } + + CanardTxQueueItem* queue = ins->tx_queue; + CanardTxQueueItem* previous = ins->tx_queue; + + while (queue != NULL) + { + if (isPriorityHigher(queue->frame.id, item->frame.id)) // lower number wins + { + if (queue == ins->tx_queue) + { + item->next = queue; + ins->tx_queue = item; + } + else + { + previous->next = item; + item->next = queue; + } + return; + } + else + { + if (queue->next == NULL) + { + queue->next = item; + return; + } + else + { + previous = queue; + queue = queue->next; + } + } + } +} + +/** + * Creates new tx queue item from allocator + */ +CANARD_INTERNAL CanardTxQueueItem* createTxItem(CanardPoolAllocator* allocator) +{ + CanardTxQueueItem* item = (CanardTxQueueItem*) allocateBlock(allocator); + if (item == NULL) + { + return NULL; + } + memset(item, 0, sizeof(*item)); + return item; +} + +/** + * Returns true if priority of rhs is higher than id + */ +CANARD_INTERNAL bool isPriorityHigher(uint32_t rhs, uint32_t id) +{ + const uint32_t clean_id = id & CANARD_CAN_EXT_ID_MASK; + const uint32_t rhs_clean_id = rhs & CANARD_CAN_EXT_ID_MASK; + + /* + * STD vs EXT - if 11 most significant bits are the same, EXT loses. + */ + const bool ext = (id & CANARD_CAN_FRAME_EFF) != 0; + const bool rhs_ext = (rhs & CANARD_CAN_FRAME_EFF) != 0; + if (ext != rhs_ext) + { + uint32_t arb11 = ext ? (clean_id >> 18U) : clean_id; + uint32_t rhs_arb11 = rhs_ext ? (rhs_clean_id >> 18U) : rhs_clean_id; + if (arb11 != rhs_arb11) + { + return arb11 < rhs_arb11; + } + else + { + return rhs_ext; + } + } + + /* + * RTR vs Data frame - if frame identifiers and frame types are the same, RTR loses. + */ + const bool rtr = (id & CANARD_CAN_FRAME_RTR) != 0; + const bool rhs_rtr = (rhs & CANARD_CAN_FRAME_RTR) != 0; + if (clean_id == rhs_clean_id && rtr != rhs_rtr) + { + return rhs_rtr; + } + + /* + * Plain ID arbitration - greater value loses. + */ + return clean_id < rhs_clean_id; +} + +/** + * preps the rx state for the next transfer. does not delete the state + */ +CANARD_INTERNAL void prepareForNextTransfer(CanardRxState* state) +{ + CANARD_ASSERT(state->buffer_blocks == CANARD_BUFFER_IDX_NONE); + state->transfer_id++; + state->payload_len = 0; + state->next_toggle = 0; +} + +/** + * returns data type from id + */ +uint16_t extractDataType(uint32_t id) +{ + if (extractTransferType(id) == CanardTransferTypeBroadcast) + { + uint16_t dtid = MSG_TYPE_FROM_ID(id); + if (SOURCE_ID_FROM_ID(id) == CANARD_BROADCAST_NODE_ID) + { + dtid &= (1U << ANON_MSG_DATA_TYPE_ID_BIT_LEN) - 1U; + } + return dtid; + } + else + { + return (uint16_t) SRV_TYPE_FROM_ID(id); + } +} + +/** + * returns transfer type from id + */ +CanardTransferType extractTransferType(uint32_t id) +{ + const bool is_service = SERVICE_NOT_MSG_FROM_ID(id); + if (!is_service) + { + return CanardTransferTypeBroadcast; + } + else if (REQUEST_NOT_RESPONSE_FROM_ID(id) == 1) + { + return CanardTransferTypeRequest; + } + else + { + return CanardTransferTypeResponse; + } +} + +/* + * CanardRxState functions + */ + +/** + * Traverses the list of CanardRxState's and returns a pointer to the CanardRxState + * with either the Id or a new one at the end + */ +CANARD_INTERNAL CanardRxState* traverseRxStates(CanardInstance* ins, uint32_t transfer_descriptor) +{ + CanardRxState* states = ins->rx_states; + + if (states == NULL) // initialize CanardRxStates + { + states = createRxState(&ins->allocator, transfer_descriptor); + + if(states == NULL) + { + return NULL; + } + + ins->rx_states = states; + return states; + } + + states = findRxState(ins, transfer_descriptor); + if (states != NULL) + { + return states; + } + else + { + return prependRxState(ins, transfer_descriptor); + } +} + +/** + * returns pointer to the rx state of transfer descriptor or null if not found + */ +CANARD_INTERNAL CanardRxState* findRxState(CanardInstance *ins, uint32_t transfer_descriptor) +{ + CanardRxState *state = ins->rx_states; + while (state != NULL) + { + if (state->dtid_tt_snid_dnid == transfer_descriptor) + { + return state; + } + state = canardRxFromIdx(&ins->allocator, state->next); + } + return NULL; +} + +/** + * prepends rx state to the canard instance rx_states + */ +CANARD_INTERNAL CanardRxState* prependRxState(CanardInstance* ins, uint32_t transfer_descriptor) +{ + CanardRxState* state = createRxState(&ins->allocator, transfer_descriptor); + + if(state == NULL) + { + return NULL; + } + + state->next = canardRxToIdx(&ins->allocator, ins->rx_states); + ins->rx_states = state; + return state; +} + +CANARD_INTERNAL CanardRxState* createRxState(CanardPoolAllocator* allocator, uint32_t transfer_descriptor) +{ + CanardRxState init = { + .next = CANARD_BUFFER_IDX_NONE, + .buffer_blocks = CANARD_BUFFER_IDX_NONE, + .dtid_tt_snid_dnid = transfer_descriptor + }; + + CanardRxState* state = (CanardRxState*) allocateBlock(allocator); + if (state == NULL) + { + return NULL; + } + memcpy(state, &init, sizeof(*state)); + + return state; +} + +CANARD_INTERNAL uint64_t releaseStatePayload(CanardInstance* ins, CanardRxState* rxstate) +{ + while (rxstate->buffer_blocks != CANARD_BUFFER_IDX_NONE) + { + CanardBufferBlock* block = canardBufferFromIdx(&ins->allocator, rxstate->buffer_blocks); + CanardBufferBlock* const temp = block->next; + freeBlock(&ins->allocator, block); + rxstate->buffer_blocks = canardBufferToIdx(&ins->allocator, temp); + } + rxstate->payload_len = 0; + return CANARD_OK; +} + +/* + * CanardBufferBlock functions + */ + +/** + * pushes data into the rx state. Fills the buffer head, then appends data to buffer blocks + */ +CANARD_INTERNAL int16_t bufferBlockPushBytes(CanardPoolAllocator* allocator, + CanardRxState* state, + const uint8_t* data, + uint8_t data_len) +{ + uint16_t data_index = 0; + + // if head is not full, add data to head + if ((CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE - state->payload_len) > 0) + { + for (uint16_t i = (uint16_t)state->payload_len; + i < CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE && data_index < data_len; + i++, data_index++) + { + state->buffer_head[i] = data[data_index]; + } + if (data_index >= data_len) + { + state->payload_len = + (uint16_t)(state->payload_len + data_len) & ((1U << CANARD_TRANSFER_PAYLOAD_LEN_BITS) - 1U); + return 1; + } + } // head is full. + + uint16_t index_at_nth_block = + (uint16_t)(((state->payload_len) - CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE) % CANARD_BUFFER_BLOCK_DATA_SIZE); + + // get to current block + CanardBufferBlock* block = NULL; + + // buffer blocks uninitialized + if (state->buffer_blocks == CANARD_BUFFER_IDX_NONE) + { + block = createBufferBlock(allocator); + state->buffer_blocks = canardBufferToIdx(allocator, block); + if (block == NULL) + { + return -CANARD_ERROR_OUT_OF_MEMORY; + } + + index_at_nth_block = 0; + } + else + { + uint16_t nth_block = 1; + + // get to block + block = canardBufferFromIdx(allocator, state->buffer_blocks); + while (block->next != NULL) + { + nth_block++; + block = block->next; + } + + const uint16_t num_buffer_blocks = + (uint16_t) (((((uint32_t)state->payload_len + data_len) - CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE) / + CANARD_BUFFER_BLOCK_DATA_SIZE) + 1U); + + if (num_buffer_blocks > nth_block && index_at_nth_block == 0) + { + block->next = createBufferBlock(allocator); + if (block->next == NULL) + { + return -CANARD_ERROR_OUT_OF_MEMORY; + } + block = block->next; + } + } + + // add data to current block until it becomes full, add new block if necessary + while (data_index < data_len) + { + for (uint16_t i = index_at_nth_block; + i < CANARD_BUFFER_BLOCK_DATA_SIZE && data_index < data_len; + i++, data_index++) + { + block->data[i] = data[data_index]; + } + + if (data_index < data_len) + { + block->next = createBufferBlock(allocator); + if (block->next == NULL) + { + return -CANARD_ERROR_OUT_OF_MEMORY; + } + block = block->next; + index_at_nth_block = 0; + } + } + + state->payload_len = (uint16_t)(state->payload_len + data_len) & ((1U << CANARD_TRANSFER_PAYLOAD_LEN_BITS) - 1U); + + return 1; +} + +CANARD_INTERNAL CanardBufferBlock* createBufferBlock(CanardPoolAllocator* allocator) +{ + CanardBufferBlock* block = (CanardBufferBlock*) allocateBlock(allocator); + if (block == NULL) + { + return NULL; + } + block->next = NULL; + return block; +} + +/** + * Bit array copy routine, originally developed by Ben Dyer for Libuavcan. Thanks Ben. + */ +void copyBitArray(const uint8_t* src, uint32_t src_offset, uint32_t src_len, + uint8_t* dst, uint32_t dst_offset) +{ + CANARD_ASSERT(src_len > 0U); + + // Normalizing inputs + src += src_offset / 8U; + dst += dst_offset / 8U; + + src_offset %= 8U; + dst_offset %= 8U; + + const size_t last_bit = src_offset + src_len; + while (last_bit - src_offset) + { + const uint8_t src_bit_offset = (uint8_t)(src_offset % 8U); + const uint8_t dst_bit_offset = (uint8_t)(dst_offset % 8U); + + const uint8_t max_offset = MAX(src_bit_offset, dst_bit_offset); + const uint32_t copy_bits = (uint32_t)MIN(last_bit - src_offset, 8U - max_offset); + +#if WORD_ADDRESSING_IS_16BITS + /* + * (uint8_t) same as (uint16_t) + * Mask 0xFF must be used + */ + const uint8_t write_mask = (uint8_t)((uint8_t)((0xFF00U >> copy_bits)&0xFF) >> dst_bit_offset)&0xFF; + const uint8_t src_data = (uint8_t)(((uint32_t)src[src_offset / 8U] << src_bit_offset) >> dst_bit_offset)&0xFF; + + dst[dst_offset / 8U] = + (uint8_t)(((uint32_t)dst[dst_offset / 8U] & (uint32_t)~write_mask) | (uint32_t)(src_data & write_mask))&0xFF; +#else + const uint8_t write_mask = (uint8_t)((uint8_t)(0xFF00U >> copy_bits) >> dst_bit_offset); + const uint8_t src_data = (uint8_t)(((uint32_t)src[src_offset / 8U] << src_bit_offset) >> dst_bit_offset); + + dst[dst_offset / 8U] = + (uint8_t)(((uint32_t)dst[dst_offset / 8U] & (uint32_t)~write_mask) | (uint32_t)(src_data & write_mask)); +#endif + + src_offset += copy_bits; + dst_offset += copy_bits; + } +} + +CANARD_INTERNAL int16_t descatterTransferPayload(const CanardRxTransfer* transfer, + uint32_t bit_offset, + uint8_t bit_length, + void* output) +{ + CANARD_ASSERT(transfer != 0); + + if (bit_offset >= transfer->payload_len * 8) + { + return 0; // Out of range, reading zero bits + } + + if (bit_offset + bit_length > transfer->payload_len * 8) + { + bit_length = (uint8_t)(transfer->payload_len * 8U - bit_offset); + } + + CANARD_ASSERT(bit_length > 0); + + if ((transfer->payload_middle != NULL) || (transfer->payload_tail != NULL)) // Multi frame + { + /* + * This part is hideously complicated and probably should be redesigned. + * The objective here is to copy the requested number of bits from scattered storage into the temporary + * local storage. We go through great pains to ensure that all corner cases are handled correctly. + */ + uint32_t input_bit_offset = bit_offset; + uint8_t output_bit_offset = 0; + uint8_t remaining_bit_length = bit_length; + + // Reading head + if (input_bit_offset < CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE * 8) + { + const uint8_t amount = (uint8_t)MIN(remaining_bit_length, + CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE * 8U - input_bit_offset); + + copyBitArray(&transfer->payload_head[0], input_bit_offset, amount, (uint8_t*) output, 0); + + input_bit_offset += amount; + output_bit_offset = (uint8_t)(output_bit_offset + amount); + remaining_bit_length = (uint8_t)(remaining_bit_length - amount); + } + + // Reading middle + uint32_t remaining_bits = (uint32_t)(transfer->payload_len * 8U - CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE * 8U); + uint32_t block_bit_offset = CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE * 8U; + const CanardBufferBlock* block = transfer->payload_middle; + + while ((block != NULL) && (remaining_bit_length > 0)) + { + CANARD_ASSERT(remaining_bits > 0); + const uint32_t block_end_bit_offset = block_bit_offset + MIN(CANARD_BUFFER_BLOCK_DATA_SIZE * 8, + remaining_bits); + + // Perform copy if we've reached the requested offset, otherwise jump over this block and try next + if (block_end_bit_offset > input_bit_offset) + { + const uint8_t amount = (uint8_t) MIN(remaining_bit_length, block_end_bit_offset - input_bit_offset); + + CANARD_ASSERT(input_bit_offset >= block_bit_offset); + const uint32_t bit_offset_within_block = input_bit_offset - block_bit_offset; + + copyBitArray(&block->data[0], bit_offset_within_block, amount, (uint8_t*) output, output_bit_offset); + + input_bit_offset += amount; + output_bit_offset = (uint8_t)(output_bit_offset + amount); + remaining_bit_length = (uint8_t)(remaining_bit_length - amount); + } + + CANARD_ASSERT(block_end_bit_offset > block_bit_offset); + remaining_bits -= block_end_bit_offset - block_bit_offset; + block_bit_offset = block_end_bit_offset; + block = block->next; + } + + CANARD_ASSERT(remaining_bit_length <= remaining_bits); + + // Reading tail + if ((transfer->payload_tail != NULL) && (remaining_bit_length > 0)) + { + CANARD_ASSERT(input_bit_offset >= block_bit_offset); + const uint32_t offset = input_bit_offset - block_bit_offset; + + copyBitArray(&transfer->payload_tail[0], offset, remaining_bit_length, (uint8_t*) output, + output_bit_offset); + + input_bit_offset += remaining_bit_length; + output_bit_offset = (uint8_t)(output_bit_offset + remaining_bit_length); + remaining_bit_length = 0; + } + + CANARD_ASSERT(input_bit_offset <= transfer->payload_len * 8); + CANARD_ASSERT(output_bit_offset <= 64); + CANARD_ASSERT(remaining_bit_length == 0); + } + else // Single frame + { + copyBitArray(&transfer->payload_head[0], bit_offset, bit_length, (uint8_t*) output, 0); + } + + return bit_length; +} + +CANARD_INTERNAL bool isBigEndian(void) +{ +#if defined(BYTE_ORDER) && defined(BIG_ENDIAN) + return BYTE_ORDER == BIG_ENDIAN; // Some compilers offer this neat shortcut +#else + union + { +#if WORD_ADDRESSING_IS_16BITS + /* + * with 16-bit memory addressing u8b[0]=u16a, u8b[1]=NOTHING + */ + uint32_t a; + uint16_t b[2]; +#else + uint16_t a; + uint8_t b[2]; +#endif + } u; + u.a = 1; + return u.b[1] == 1; // Some don't... +#endif +} + +CANARD_INTERNAL void swapByteOrder(void* data, unsigned size) +{ + CANARD_ASSERT(data != NULL); + + uint8_t* const bytes = (uint8_t*) data; + + size_t fwd = 0; + size_t rev = size - 1; + + while (fwd < rev) + { + const uint8_t x = bytes[fwd]; + bytes[fwd] = bytes[rev]; + bytes[rev] = x; + fwd++; + rev--; + } +} + +/* + * CRC functions + */ +CANARD_INTERNAL uint16_t crcAddByte(uint16_t crc_val, uint8_t byte) +{ + crc_val ^= (uint16_t) ((uint16_t) (byte) << 8U); + for (uint8_t j = 0; j < 8; j++) + { + if (crc_val & 0x8000U) + { + crc_val = (uint16_t) ((uint16_t) (crc_val << 1U) ^ 0x1021U); + } + else + { + crc_val = (uint16_t) (crc_val << 1U); + } + } + return crc_val; +} + +CANARD_INTERNAL uint16_t crcAddSignature(uint16_t crc_val, uint64_t data_type_signature) +{ + for (uint16_t shift_val = 0; shift_val < 64; shift_val = (uint16_t)(shift_val + 8U)) + { + crc_val = crcAddByte(crc_val, (uint8_t) (data_type_signature >> shift_val)); + } + return crc_val; +} + +CANARD_INTERNAL uint16_t crcAdd(uint16_t crc_val, const uint8_t* bytes, size_t len) +{ + while (len--) + { + crc_val = crcAddByte(crc_val, *bytes++); + } + return crc_val; +} + +/* + * Pool Allocator functions + */ +CANARD_INTERNAL void initPoolAllocator(CanardPoolAllocator* allocator, + void* buf, + uint16_t buf_len) +{ + size_t current_index = 0; + CanardPoolAllocatorBlock *abuf = buf; + allocator->arena = buf; + CanardPoolAllocatorBlock** current_block = &(allocator->free_list); + while (current_index < buf_len) + { + *current_block = &abuf[current_index]; + current_block = &((*current_block)->next); + current_index++; + } + *current_block = NULL; + + allocator->statistics.capacity_blocks = buf_len; + allocator->statistics.current_usage_blocks = 0; + allocator->statistics.peak_usage_blocks = 0; + // user should initialize semaphore after the canardInit + // or at first call of canard_allocate_sem_take + allocator->semaphore = NULL; +} + +CANARD_INTERNAL void* allocateBlock(CanardPoolAllocator* allocator) +{ +#if CANARD_ALLOCATE_SEM + canard_allocate_sem_take(allocator); +#endif + // Check if there are any blocks available in the free list. + if (allocator->free_list == NULL) + { +#if CANARD_ALLOCATE_SEM + canard_allocate_sem_give(allocator); +#endif + return NULL; + } + + // Take first available block and prepares next block for use. + void* result = allocator->free_list; + allocator->free_list = allocator->free_list->next; + + // Update statistics + allocator->statistics.current_usage_blocks++; + if (allocator->statistics.peak_usage_blocks < allocator->statistics.current_usage_blocks) + { + allocator->statistics.peak_usage_blocks = allocator->statistics.current_usage_blocks; + } +#if CANARD_ALLOCATE_SEM + canard_allocate_sem_give(allocator); +#endif + return result; +} + +CANARD_INTERNAL void freeBlock(CanardPoolAllocator* allocator, void* p) +{ +#if CANARD_ALLOCATE_SEM + canard_allocate_sem_take(allocator); +#endif + CanardPoolAllocatorBlock* block = (CanardPoolAllocatorBlock*) p; + + block->next = allocator->free_list; + allocator->free_list = block; + + CANARD_ASSERT(allocator->statistics.current_usage_blocks > 0); + allocator->statistics.current_usage_blocks--; +#if CANARD_ALLOCATE_SEM + canard_allocate_sem_give(allocator); +#endif +} diff --git a/bootloader/bootloader/DroneCAN/libcanard/canard.h b/bootloader/bootloader/DroneCAN/libcanard/canard.h new file mode 100644 index 000000000..78b738adf --- /dev/null +++ b/bootloader/bootloader/DroneCAN/libcanard/canard.h @@ -0,0 +1,737 @@ +/* + * Copyright (c) 2016-2019 UAVCAN Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Contributors: https://github.com/UAVCAN/libcanard/contributors + * + * Documentation: http://uavcan.org/Implementations/Libcanard + */ + +#ifndef CANARD_H +#define CANARD_H + +#include +#include +#include +#include + +/// Build configuration header. Use it to provide your overrides. +#if defined(CANARD_ENABLE_CUSTOM_BUILD_CONFIG) && CANARD_ENABLE_CUSTOM_BUILD_CONFIG +# include "canard_build_config.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/// Libcanard version. API will be backwards compatible within the same major version. +#define CANARD_VERSION_MAJOR 0 +#define CANARD_VERSION_MINOR 2 + + +#ifndef CANARD_ENABLE_CANFD +#define CANARD_ENABLE_CANFD 0 +#endif + +#ifndef CANARD_MULTI_IFACE +#define CANARD_MULTI_IFACE 0 +#endif + +#ifndef CANARD_ENABLE_DEADLINE +#define CANARD_ENABLE_DEADLINE 0 +#endif + +#ifndef CANARD_ENABLE_TAO_OPTION +#if CANARD_ENABLE_CANFD +#define CANARD_ENABLE_TAO_OPTION 1 +#else +#define CANARD_ENABLE_TAO_OPTION 0 +#endif +#endif + +/// By default this macro resolves to the standard assert(). The user can redefine this if necessary. +#ifndef CANARD_ASSERT +#ifdef CANARD_ENABLE_ASSERTS +# define CANARD_ASSERT(x) assert(x) +#else +# define CANARD_ASSERT(x) +#endif +#endif // CANARD_ASSERT + +#define CANARD_GLUE(a, b) CANARD_GLUE_IMPL_(a, b) +#define CANARD_GLUE_IMPL_(a, b) a##b + +/// By default this macro expands to static_assert if supported by the language (C11, C++11, or newer). +/// The user can redefine this if necessary. +#ifndef CANARD_STATIC_ASSERT +# if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) ||\ + (defined(__cplusplus) && (__cplusplus >= 201103L)) +# define CANARD_STATIC_ASSERT(...) static_assert(__VA_ARGS__) +# else +# define CANARD_STATIC_ASSERT(x, ...) typedef char CANARD_GLUE(_static_assertion_, __LINE__)[(x) ? 1 : -1] +# endif +#endif + +#ifndef CANARD_ALLOCATE_SEM +#define CANARD_ALLOCATE_SEM 0 +#endif +/// Error code definitions; inverse of these values may be returned from API calls. +#define CANARD_OK 0 +// Value 1 is omitted intentionally, since -1 is often used in 3rd party code +#define CANARD_ERROR_INVALID_ARGUMENT 2 +#define CANARD_ERROR_OUT_OF_MEMORY 3 +#define CANARD_ERROR_NODE_ID_NOT_SET 4 +#define CANARD_ERROR_INTERNAL 9 +#define CANARD_ERROR_RX_INCOMPATIBLE_PACKET 10 +#define CANARD_ERROR_RX_WRONG_ADDRESS 11 +#define CANARD_ERROR_RX_NOT_WANTED 12 +#define CANARD_ERROR_RX_MISSED_START 13 +#define CANARD_ERROR_RX_WRONG_TOGGLE 14 +#define CANARD_ERROR_RX_UNEXPECTED_TID 15 +#define CANARD_ERROR_RX_SHORT_FRAME 16 +#define CANARD_ERROR_RX_BAD_CRC 17 + +/// The size of a memory block in bytes. +#if CANARD_ENABLE_CANFD +#define CANARD_MEM_BLOCK_SIZE 128U +#elif CANARD_ENABLE_DEADLINE +#define CANARD_MEM_BLOCK_SIZE 40U +#else +#define CANARD_MEM_BLOCK_SIZE 32U +#endif + +#define CANARD_CAN_FRAME_MAX_DATA_LEN 8U +#if CANARD_ENABLE_CANFD +#define CANARD_CANFD_FRAME_MAX_DATA_LEN 64U +#endif + +/// Node ID values. Refer to the specification for more info. +#define CANARD_BROADCAST_NODE_ID 0 +#define CANARD_MIN_NODE_ID 1 +#define CANARD_MAX_NODE_ID 127 + +/// Refer to the type CanardRxTransfer +#define CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE (CANARD_MEM_BLOCK_SIZE - offsetof(CanardRxState, buffer_head)) + +/// Refer to the type CanardBufferBlock +#define CANARD_BUFFER_BLOCK_DATA_SIZE (CANARD_MEM_BLOCK_SIZE - offsetof(CanardBufferBlock, data)) + +/// Refer to canardCleanupStaleTransfers() for details. +#define CANARD_RECOMMENDED_STALE_TRANSFER_CLEANUP_INTERVAL_USEC 1000000U + +/// Transfer priority definitions +#define CANARD_TRANSFER_PRIORITY_HIGHEST 0 +#define CANARD_TRANSFER_PRIORITY_HIGH 8 +#define CANARD_TRANSFER_PRIORITY_MEDIUM 16 +#define CANARD_TRANSFER_PRIORITY_LOW 24 +#define CANARD_TRANSFER_PRIORITY_LOWEST 31 + +/// Related to CanardCANFrame +#define CANARD_CAN_EXT_ID_MASK 0x1FFFFFFFU +#define CANARD_CAN_STD_ID_MASK 0x000007FFU +#define CANARD_CAN_FRAME_EFF (1UL << 31U) ///< Extended frame format +#define CANARD_CAN_FRAME_RTR (1UL << 30U) ///< Remote transmission (not used by UAVCAN) +#define CANARD_CAN_FRAME_ERR (1UL << 29U) ///< Error frame (not used by UAVCAN) + +#define CANARD_TRANSFER_PAYLOAD_LEN_BITS 10U +#define CANARD_MAX_TRANSFER_PAYLOAD_LEN ((1U << CANARD_TRANSFER_PAYLOAD_LEN_BITS) - 1U) + +#ifndef CANARD_64_BIT +#ifdef __WORDSIZE +#define CANARD_64_BIT (__WORDSIZE == 64) +#else +#define CANARD_64_BIT 0 +#endif +#endif + +/* + canard_buffer_idx_t is used to avoid pointers in data structures + that have to have the same size on both 32 bit and 64 bit + platforms. It is an index into mem_arena passed to canardInit + treated as a uint8_t array + + A value of CANARD_BUFFER_IDX_NONE means a NULL pointer + */ +#if CANARD_64_BIT +typedef uint32_t canard_buffer_idx_t; +#define CANARD_BUFFER_IDX_NONE 0U +#else +typedef void* canard_buffer_idx_t; +#define CANARD_BUFFER_IDX_NONE NULL +#endif + + + +/** + * This data type holds a standard CAN 2.0B data frame with 29-bit ID. + */ +typedef struct +{ + /** + * Refer to the following definitions: + * - CANARD_CAN_FRAME_EFF + * - CANARD_CAN_FRAME_RTR + * - CANARD_CAN_FRAME_ERR + */ + uint32_t id; +#if CANARD_ENABLE_DEADLINE + uint64_t deadline_usec; +#endif +#if CANARD_ENABLE_CANFD + uint8_t data[CANARD_CANFD_FRAME_MAX_DATA_LEN]; +#else + uint8_t data[CANARD_CAN_FRAME_MAX_DATA_LEN]; +#endif + uint8_t data_len; + uint8_t iface_id; +#if CANARD_MULTI_IFACE + uint8_t iface_mask; +#endif +#if CANARD_ENABLE_CANFD + bool canfd; +#endif +} CanardCANFrame; + +/** + * Transfer types are defined by the UAVCAN specification. + */ +typedef enum +{ + CanardTransferTypeResponse = 0, + CanardTransferTypeRequest = 1, + CanardTransferTypeBroadcast = 2 +} CanardTransferType; + +/** + * Types of service transfers. These are not applicable to message transfers. + */ +typedef enum +{ + CanardResponse, + CanardRequest +} CanardRequestResponse; + +/* + * Forward declarations. + */ +typedef struct CanardInstance CanardInstance; +typedef struct CanardRxTransfer CanardRxTransfer; +typedef struct CanardRxState CanardRxState; +typedef struct CanardTxQueueItem CanardTxQueueItem; + +/** + * This struture provides information about encoded dronecan frame that needs + * to be put on the wire. + * + * In case of broadcast or request pointer to the Transfer ID should point to a persistent variable + * (e.g. static or heap allocated, not on the stack); it will be updated by the library after every transmission. + * The Transfer ID value cannot be shared between transfers that have different descriptors! + * More on this in the transport layer specification. + * + * For the case of response, the pointer to the Transfer ID is treated as const and generally points to transfer id + * in CanardRxTransfer structure. + * + */ +typedef struct { + CanardTransferType transfer_type; ///< Type of transfer: CanardTransferTypeBroadcast, CanardTransferTypeRequest, CanardTransferTypeResponse + uint64_t data_type_signature; ///< Signature of the message/service + uint16_t data_type_id; ///< ID of the message/service + uint8_t* inout_transfer_id; ///< Transfer ID reference + uint8_t priority; ///< Priority of the transfer + const uint8_t* payload; ///< Pointer to the payload + uint16_t payload_len; ///< Length of the payload +#if CANARD_ENABLE_CANFD + bool canfd; ///< True if CAN FD is enabled +#endif +#if CANARD_ENABLE_DEADLINE + uint64_t deadline_usec; ///< Deadline in microseconds +#endif +#if CANARD_MULTI_IFACE + uint8_t iface_mask; ///< Bitmask of interfaces to send the transfer on +#endif +#if CANARD_ENABLE_TAO_OPTION + bool tao; ///< True if tail array optimization is enabled +#endif +} CanardTxTransfer; + +struct CanardTxQueueItem +{ + CanardTxQueueItem* next; + CanardCANFrame frame; +}; +CANARD_STATIC_ASSERT(sizeof(CanardTxQueueItem) <= CANARD_MEM_BLOCK_SIZE, "Unexpected memory block size"); +/** + * The application must implement this function and supply a pointer to it to the library during initialization. + * The library calls this function to determine whether the transfer should be received. + * + * If the application returns true, the value pointed to by 'out_data_type_signature' must be initialized with the + * correct data type signature, otherwise transfer reception will fail with CRC mismatch error. Please refer to the + * specification for more details about data type signatures. Signature for any data type can be obtained in many + * ways; for example, using the command line tool distributed with Libcanard (see the repository). + */ +typedef bool (* CanardShouldAcceptTransfer)(const CanardInstance* ins, ///< Library instance + uint64_t* out_data_type_signature, ///< Must be set by the application! + uint16_t data_type_id, ///< Refer to the specification + CanardTransferType transfer_type, ///< Refer to CanardTransferType + uint8_t source_node_id); ///< Source node ID or Broadcast (0) + +/** + * This function will be invoked by the library every time a transfer is successfully received. + * If the application needs to send another transfer from this callback, it is highly recommended + * to call canardReleaseRxTransferPayload() first, so that the memory that was used for the block + * buffer can be released and re-used by the TX queue. + */ +typedef void (* CanardOnTransferReception)(CanardInstance* ins, ///< Library instance + CanardRxTransfer* transfer); ///< Ptr to temporary transfer object + +/** + * INTERNAL DEFINITION, DO NOT USE DIRECTLY. + * A memory block used in the memory block allocator. + */ +typedef union CanardPoolAllocatorBlock_u +{ + char bytes[CANARD_MEM_BLOCK_SIZE]; + union CanardPoolAllocatorBlock_u* next; +} CanardPoolAllocatorBlock; + +/** + * This structure provides usage statistics of the memory pool allocator. + * This data helps to evaluate whether the allocated memory is sufficient for the application. + */ +typedef struct +{ + uint16_t capacity_blocks; ///< Pool capacity in number of blocks + uint16_t current_usage_blocks; ///< Number of blocks that are currently allocated by the library + uint16_t peak_usage_blocks; ///< Maximum number of blocks used since initialization +} CanardPoolAllocatorStatistics; + +/** + * INTERNAL DEFINITION, DO NOT USE DIRECTLY. + * Buffer block for received data. + */ +typedef struct CanardBufferBlock +{ + struct CanardBufferBlock* next; + uint8_t data[]; +} CanardBufferBlock; + +/** + * INTERNAL DEFINITION, DO NOT USE DIRECTLY. + */ +typedef struct +{ + // user should initialize semaphore after the canardInit + // or at first call of canard_allocate_sem_take + void *semaphore; + CanardPoolAllocatorBlock* free_list; + CanardPoolAllocatorStatistics statistics; + void *arena; +} CanardPoolAllocator; + + +/** + * INTERNAL DEFINITION, DO NOT USE DIRECTLY. + */ +struct CanardRxState +{ + canard_buffer_idx_t next; + canard_buffer_idx_t buffer_blocks; + + uint64_t timestamp_usec; + + const uint32_t dtid_tt_snid_dnid; + + // We're using plain 'unsigned' here, because C99 doesn't permit explicit field type specification + unsigned calculated_crc : 16; + unsigned payload_len : CANARD_TRANSFER_PAYLOAD_LEN_BITS; + unsigned transfer_id : 5; + unsigned next_toggle : 1; // 16+10+5+1 = 32, aligned. + + uint16_t payload_crc; + uint8_t iface_id; + uint8_t buffer_head[]; +}; +CANARD_STATIC_ASSERT(offsetof(CanardRxState, buffer_head) <= 27, "Invalid memory layout"); +CANARD_STATIC_ASSERT(CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE >= 5, "Invalid memory layout"); + +/** + * This is the core structure that keeps all of the states and allocated resources of the library instance. + * The application should never access any of the fields directly! Instead, API functions should be used. + */ +struct CanardInstance +{ + uint8_t node_id; ///< Local node ID; may be zero if the node is anonymous + + CanardShouldAcceptTransfer should_accept; ///< Function to decide whether the application wants this transfer + CanardOnTransferReception on_reception; ///< Function the library calls after RX transfer is complete + + CanardPoolAllocator allocator; ///< Pool allocator + + CanardRxState* rx_states; ///< RX transfer states + CanardTxQueueItem* tx_queue; ///< TX frames awaiting transmission + + void* user_reference; ///< User pointer that can link this instance with other objects + +#if CANARD_ENABLE_TAO_OPTION + bool tao_disabled; ///< True if TAO is disabled +#endif +}; + +/** + * This structure represents a received transfer for the application. + * An instance of it is passed to the application via callback when the library receives a new transfer. + * Pointers to the structure and all its fields are invalidated after the callback returns. + */ +struct CanardRxTransfer +{ + /** + * Timestamp at which the first frame of this transfer was received. + */ + uint64_t timestamp_usec; + + /** + * Payload is scattered across three storages: + * - Head points to CanardRxState.buffer_head (length of which is up to CANARD_PAYLOAD_HEAD_SIZE), or to the + * payload field (possibly with offset) of the last received CAN frame. + * + * - Middle is located in the linked list of dynamic blocks (only for multi-frame transfers). + * + * - Tail points to the payload field (possibly with offset) of the last received CAN frame + * (only for multi-frame transfers). + * + * The tail offset depends on how much data of the last frame was accommodated in the last allocated block. + * + * For single-frame transfers, middle and tail will be NULL, and the head will point at first byte + * of the payload of the CAN frame. + * + * In simple cases it should be possible to get data directly from the head and/or tail pointers. + * Otherwise it is advised to use canardDecodeScalar(). + */ + const uint8_t* payload_head; ///< Always valid, i.e. not NULL. + ///< For multi frame transfers, the maximum size is defined in the constant + ///< CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE. + ///< For single-frame transfers, the size is defined in the + ///< field payload_len. + CanardBufferBlock* payload_middle; ///< May be NULL if the buffer was not needed. Always NULL for single-frame + ///< transfers. + const uint8_t* payload_tail; ///< Last bytes of multi-frame transfers. Always NULL for single-frame + ///< transfers. + uint16_t payload_len; ///< Effective length of the payload in bytes. + + /** + * These fields identify the transfer for the application. + */ + uint16_t data_type_id; ///< 0 to 255 for services, 0 to 65535 for messages + uint8_t transfer_type; ///< See CanardTransferType + uint8_t transfer_id; ///< 0 to 31 + uint8_t priority; ///< 0 to 31 + uint8_t source_node_id; ///< 1 to 127, or 0 if the source is anonymous +#if CANARD_ENABLE_TAO_OPTION + bool tao; +#endif +#if CANARD_ENABLE_CANFD + bool canfd; ///< frame canfd +#endif +}; + +/** + * Initializes a library instance. + * Local node ID will be set to zero, i.e. the node will be anonymous. + * + * Typically, size of the memory pool should not be less than 1K, although it depends on the application. The + * recommended way to detect the required pool size is to measure the peak pool usage after a stress-test. Refer to + * the function canardGetPoolAllocatorStatistics(). + */ +void canardInit(CanardInstance* out_ins, ///< Uninitialized library instance + void* mem_arena, ///< Raw memory chunk used for dynamic allocation + size_t mem_arena_size, ///< Size of the above, in bytes + CanardOnTransferReception on_reception, ///< Callback, see CanardOnTransferReception + CanardShouldAcceptTransfer should_accept, ///< Callback, see CanardShouldAcceptTransfer + void* user_reference); ///< Optional pointer for user's convenience, can be NULL + +/** + * Returns the value of the user pointer. + * The user pointer is configured once during initialization. + * It can be used to store references to any user-specific data, or to link the instance object with C++ objects. + */ +void* canardGetUserReference(const CanardInstance* ins); + +/** + * Assigns a new node ID value to the current node. + * Node ID can be assigned only once. + */ +void canardSetLocalNodeID(CanardInstance* ins, + uint8_t self_node_id); + +/** + * Returns node ID of the local node. + * Returns zero (broadcast) if the node ID is not set, i.e. if the local node is anonymous. + */ +uint8_t canardGetLocalNodeID(const CanardInstance* ins); + +/** + * Forgets the current node ID value so that a new Node ID can be assigned. + */ +void canardForgetLocalNodeID(CanardInstance* ins); + +/** + * Initialise TX transfer object. + * Should be called at least once before using transfer object to send transmissions. +*/ +void canardInitTxTransfer(CanardTxTransfer* transfer); + +/** + * Sends a broadcast transfer. + * If the node is in passive mode, only single frame transfers will be allowed (they will be transmitted as anonymous). + * + * For anonymous transfers, maximum data type ID (CanardTxTransfer::data_type_id) is limited to 3 (see specification for details). + * + * Please refer to the specification for more details about data type signatures (CanardTxTransfer::data_type_signature). Signature for + * any data type can be obtained in many ways; for example, using the generated code generated using dronecan_dsdlc (see the repository). + * + * Use CanardTxTransfer structure to pass the transfer parameters. The structure is initialized by the + * canardInitTxTransfer() function. + * + * Pointer to the Transfer ID (CanardTxTransfer::inout_transfer_id) should point to a persistent variable + * (e.g. static or heap allocated, not on the stack); it will be updated by the library after every transmission. + * The Transfer ID value cannot be shared between transfers that have different descriptors! + * More on this in the transport layer specification. + * + * Returns the number of frames enqueued, or negative error code. + */ + +int16_t canardBroadcastObj(CanardInstance* ins, ///< Library instance + CanardTxTransfer* transfer ///< Transfer object + ); + +// Legacy API, try to avoid using it, as this will not be extended with new features +int16_t canardBroadcast(CanardInstance* ins, ///< Library instance + uint64_t data_type_signature, ///< See above + uint16_t data_type_id, ///< Refer to the specification + uint8_t* inout_transfer_id, ///< Pointer to a persistent variable containing the transfer ID + uint8_t priority, ///< Refer to definitions CANARD_TRANSFER_PRIORITY_* + const void* payload, ///< Transfer payload + uint16_t payload_len ///< Length of the above, in bytes +#if CANARD_ENABLE_DEADLINE + ,uint64_t tx_deadline ///< Transmission deadline, microseconds +#endif +#if CANARD_MULTI_IFACE + ,uint8_t iface_mask ///< Bitmask of interfaces to transmit on +#endif +#if CANARD_ENABLE_CANFD + ,bool canfd ///< Is the frame canfd +#endif + ); +/** + * Sends a request or a response transfer. + * Fails if the node is in passive mode. + * + * Please refer to the specification for more details about data type signatures (CanardTxTransfer::data_type_signature). Signature for + * any data type can be obtained in many ways; for example, using the generated code generated using dronecan_dsdlc (see the repository). + * + * Pointer to the Transfer ID (CanardTxTransfer::inout_transfer_id) should point to a persistent variable + * (e.g. static or heap allocated, not on the stack); it will be updated by the library after every request. + * The Transfer ID value cannot be shared between requests that have different descriptors! + * More on this in the transport layer specification. + * + * For Response transfers, the pointer to the Transfer ID(CanardTxTransfer::inout_transfer_id) will be treated as const (i.e. read-only), + * and normally it should point to the transfer_id field of the structure CanardRxTransfer. + * + * Returns the number of frames enqueued, or negative error code. + */ + +int16_t canardRequestOrRespondObj(CanardInstance* ins, ///< Library instance + uint8_t destination_node_id, ///< Node ID of the server/client + CanardTxTransfer* transfer ///< Transfer object + ); +// Legacy API, try to avoid using it, as this will not be extended with new features +int16_t canardRequestOrRespond(CanardInstance* ins, ///< Library instance + uint8_t destination_node_id, ///< Node ID of the server/client + uint64_t data_type_signature, ///< See above + uint8_t data_type_id, ///< Refer to the specification + uint8_t* inout_transfer_id, ///< Pointer to a persistent variable with transfer ID + uint8_t priority, ///< Refer to definitions CANARD_TRANSFER_PRIORITY_* + CanardRequestResponse kind, ///< Refer to CanardRequestResponse + const void* payload, ///< Transfer payload + uint16_t payload_len ///< Length of the above, in bytes +#if CANARD_ENABLE_DEADLINE + ,uint64_t tx_deadline ///< Transmission deadline, microseconds +#endif +#if CANARD_MULTI_IFACE + ,uint8_t iface_mask ///< Bitmask of interfaces to transmit on +#endif +#if CANARD_ENABLE_CANFD + ,bool canfd ///< Is the frame canfd +#endif + ); +/** + * Returns a pointer to the top priority frame in the TX queue. + * Returns NULL if the TX queue is empty. + * The application will call this function after canardBroadcast() or canardRequestOrRespond() to transmit generated + * frames over the CAN bus. + */ +CanardCANFrame* canardPeekTxQueue(const CanardInstance* ins); + +/** + * Returns the timeout for the frame on top of TX queue. + * Returns zero if the TX queue is empty. + * The application will call this function after canardPeekTxQueue() to determine when to call canardPopTxQueue(), if + * the frame is not transmitted. + */ +#if CANARD_ENABLE_DEADLINE +uint64_t canardPeekTxQueueDeadline(const CanardInstance* ins); +#endif +/** + * Removes the top priority frame from the TX queue. + * The application will call this function after canardPeekTxQueue() once the obtained frame has been processed. + * Calling canardBroadcast() or canardRequestOrRespond() between canardPeekTxQueue() and canardPopTxQueue() + * is NOT allowed, because it may change the frame at the top of the TX queue. + */ +void canardPopTxQueue(CanardInstance* ins); + +/** + * Processes a received CAN frame with a timestamp. + * The application will call this function when it receives a new frame from the CAN bus. + * + * Return value will report any errors in decoding packets. + */ +int16_t canardHandleRxFrame(CanardInstance* ins, + const CanardCANFrame* frame, + uint64_t timestamp_usec); + +/** + * Traverses the list of transfers and removes those that were last updated more than timeout_usec microseconds ago. + * This function must be invoked by the application periodically, about once a second. + * Also refer to the constant CANARD_RECOMMENDED_STALE_TRANSFER_CLEANUP_INTERVAL_USEC. + */ +void canardCleanupStaleTransfers(CanardInstance* ins, + uint64_t current_time_usec); + +/** + * This function can be used to extract values from received UAVCAN transfers. It decodes a scalar value - + * boolean, integer, character, or floating point - from the specified bit position in the RX transfer buffer. + * Simple single-frame transfers can also be parsed manually. + * + * Returns the number of bits successfully decoded, which may be less than requested if operation ran out of + * buffer boundaries, or negated error code, such as invalid argument. + * + * Caveat: This function works correctly only on platforms that use two's complement signed integer representation. + * I am not aware of any modern microarchitecture that uses anything else than two's complement, so it should + * not affect portability in any way. + * + * The type of value pointed to by 'out_value' is defined as follows: + * + * | bit_length | value_is_signed | out_value points to | + * |------------|-----------------|------------------------------------------| + * | 1 | false | bool (may be incompatible with uint8_t!) | + * | 1 | true | N/A | + * | [2, 8] | false | uint8_t, or char | + * | [2, 8] | true | int8_t, or char | + * | [9, 16] | false | uint16_t | + * | [9, 16] | true | int16_t | + * | [17, 32] | false | uint32_t | + * | [17, 32] | true | int32_t, or 32-bit float | + * | [33, 64] | false | uint64_t | + * | [33, 64] | true | int64_t, or 64-bit float | + */ +int16_t canardDecodeScalar(const CanardRxTransfer* transfer, ///< The RX transfer where the data will be copied from + uint32_t bit_offset, ///< Offset, in bits, from the beginning of the transfer + uint8_t bit_length, ///< Length of the value, in bits; see the table + bool value_is_signed, ///< True if the value can be negative; see the table + void* out_value); ///< Pointer to the output storage; see the table + +/** + * This function can be used to encode values for later transmission in a UAVCAN transfer. It encodes a scalar value - + * boolean, integer, character, or floating point - and puts it to the specified bit position in the specified + * contiguous buffer. + * Simple single-frame transfers can also be encoded manually. + * + * Caveat: This function works correctly only on platforms that use two's complement signed integer representation. + * I am not aware of any modern microarchitecture that uses anything else than two's complement, so it should + * not affect portability in any way. + * + * The type of value pointed to by 'value' is defined as follows: + * + * | bit_length | value points to | + * |------------|------------------------------------------| + * | 1 | bool (may be incompatible with uint8_t!) | + * | [2, 8] | uint8_t, int8_t, or char | + * | [9, 16] | uint16_t, int16_t | + * | [17, 32] | uint32_t, int32_t, or 32-bit float | + * | [33, 64] | uint64_t, int64_t, or 64-bit float | + */ +void canardEncodeScalar(void* destination, ///< Destination buffer where the result will be stored + uint32_t bit_offset, ///< Offset, in bits, from the beginning of the destination buffer + uint8_t bit_length, ///< Length of the value, in bits; see the table + const void* value); ///< Pointer to the value; see the table + +/** + * This function can be invoked by the application to release pool blocks that are used + * to store the payload of the transfer. + * + * If the application needs to send new transfers from the transfer reception callback, this function should be + * invoked right before calling canardBroadcast() or canardRequestOrRespond(). Not releasing the buffers before + * transmission may cause higher peak usage of the memory pool. + * + * If the application didn't call this function before returning from the callback, the library will do that, + * so it is guaranteed that the memory will not leak. + */ +void canardReleaseRxTransferPayload(CanardInstance* ins, + CanardRxTransfer* transfer); + +/** + * Returns a copy of the pool allocator usage statistics. + * Refer to the type CanardPoolAllocatorStatistics. + * Use this function to determine worst case memory needs of your application. + */ +CanardPoolAllocatorStatistics canardGetPoolAllocatorStatistics(CanardInstance* ins); + +/** + * Float16 marshaling helpers. + * These functions convert between the native float and 16-bit float. + * It is assumed that the native float is IEEE 754 single precision float, otherwise results will be unpredictable. + * Vast majority of modern computers and microcontrollers use IEEE 754, so this limitation should not affect + * portability. + */ +uint16_t canardConvertNativeFloatToFloat16(float value); +float canardConvertFloat16ToNativeFloat(uint16_t value); + +uint16_t extractDataType(uint32_t id); +CanardTransferType extractTransferType(uint32_t id); + +/// Abort the build if the current platform is not supported. +#if CANARD_ENABLE_CANFD +CANARD_STATIC_ASSERT(((uint32_t)CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE) < 128, + "Please define CANARD_64_BIT=1 for 64 bit builds"); +#else +CANARD_STATIC_ASSERT(((uint32_t)CANARD_MULTIFRAME_RX_PAYLOAD_HEAD_SIZE) < 32, + "Please define CANARD_64_BIT=1 for 64 bit builds"); +#endif + +#if CANARD_ALLOCATE_SEM +// user implemented functions for taking and freeing semaphores +void canard_allocate_sem_take(CanardPoolAllocator *allocator); +void canard_allocate_sem_give(CanardPoolAllocator *allocator); +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/libcanard/canard_internals.h b/bootloader/bootloader/DroneCAN/libcanard/canard_internals.h new file mode 100644 index 000000000..c147c9414 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/libcanard/canard_internals.h @@ -0,0 +1,201 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2017 UAVCAN Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Contributors: https://github.com/UAVCAN/libcanard/contributors + */ + +/* + * This file holds function declarations that expose the library's internal definitions for unit testing. + * It is NOT part of the library's API and should not even be looked at by the user. + */ + +#ifndef CANARD_INTERNALS_H +#define CANARD_INTERNALS_H + +#include "canard.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// This macro is needed only for testing and development. Do not redefine this in production. +#ifndef CANARD_INTERNAL +# define CANARD_INTERNAL static +#endif + +/* + * Some MCUs like TMS320 have 16-bits addressing, so + * 1. (uint8_t) same (uint16_t) + * 2. sizeof(float) is 2 + * 3. union not same like STM32, because type uint8_t does not exist in hardware + * + * union + * { + * uint64_t u8; + * uint64_t u16; + * uint64_t u32; + * uint64_t u64; + * uint8_t bytes[8]; + * } storage; + * + * address:| bytes: | u64: | u32: | u16: | u8: + * 0x00 | bytes[0] | (u64 )&0xFF | (u32 )&0xFF | u16 | u8 + * 0x01 | bytes[1] | (u64>>16)&0xFF | (u32>>16)&0xFF | + * 0x02 | bytes[2] | (u64>>32)&0xFF | + * 0x03 | bytes[3] | (u64>>48)&0xFF | + * 0x04 | bytes[4] | + * 0x05 | bytes[5] | + * 0x06 | bytes[6] | + * 0x07 | bytes[7] | + * + */ +#ifndef WORD_ADDRESSING_IS_16BITS +#if defined(__TI_COMPILER_VERSION__) || defined(__TMS320C2000__) +#define WORD_ADDRESSING_IS_16BITS 1 +#else +#define WORD_ADDRESSING_IS_16BITS 0 +#endif +#endif + +#if WORD_ADDRESSING_IS_16BITS +# define uint8_t uint16_t +# define int8_t int16_t +# define CANARD_SIZEOF_FLOAT 2 +#else +# define CANARD_SIZEOF_FLOAT 4 +#endif + +CANARD_INTERNAL CanardRxState* traverseRxStates(CanardInstance* ins, + uint32_t transfer_descriptor); + +CANARD_INTERNAL CanardRxState* createRxState(CanardPoolAllocator* allocator, + uint32_t transfer_descriptor); + +CANARD_INTERNAL CanardRxState* prependRxState(CanardInstance* ins, + uint32_t transfer_descriptor); + +CANARD_INTERNAL CanardRxState* findRxState(CanardInstance *ins, + uint32_t transfer_descriptor); + +CANARD_INTERNAL int16_t bufferBlockPushBytes(CanardPoolAllocator* allocator, + CanardRxState* state, + const uint8_t* data, + uint8_t data_len); + +CANARD_INTERNAL CanardBufferBlock* createBufferBlock(CanardPoolAllocator* allocator); + +CANARD_INTERNAL void pushTxQueue(CanardInstance* ins, + CanardTxQueueItem* item); + +CANARD_INTERNAL bool isPriorityHigher(uint32_t id, + uint32_t rhs); + +CANARD_INTERNAL CanardTxQueueItem* createTxItem(CanardPoolAllocator* allocator); + +CANARD_INTERNAL void prepareForNextTransfer(CanardRxState* state); + +CANARD_INTERNAL int16_t computeTransferIDForwardDistance(uint8_t a, + uint8_t b); + +CANARD_INTERNAL void incrementTransferID(uint8_t* transfer_id); + +CANARD_INTERNAL uint64_t releaseStatePayload(CanardInstance* ins, + CanardRxState* rxstate); + +CANARD_INTERNAL uint16_t dlcToDataLength(uint16_t dlc); +CANARD_INTERNAL uint16_t dataLengthToDlc(uint16_t data_length); + +/// Returns the number of frames enqueued +CANARD_INTERNAL int16_t enqueueTxFrames(CanardInstance* ins, + uint32_t can_id, + uint16_t crc, + CanardTxTransfer* transfer); + +CANARD_INTERNAL void copyBitArray(const uint8_t* src, + uint32_t src_offset, + uint32_t src_len, + uint8_t* dst, + uint32_t dst_offset); + +/** + * Moves specified bits from the scattered transfer storage to a specified contiguous buffer. + * Returns the number of bits copied, or negated error code. + */ +CANARD_INTERNAL int16_t descatterTransferPayload(const CanardRxTransfer* transfer, + uint32_t bit_offset, + uint8_t bit_length, + void* output); + +CANARD_INTERNAL bool isBigEndian(void); + +CANARD_INTERNAL void swapByteOrder(void* data, unsigned size); + +/* + * Transfer CRC + */ +CANARD_INTERNAL uint16_t crcAddByte(uint16_t crc_val, + uint8_t byte); + +CANARD_INTERNAL uint16_t crcAddSignature(uint16_t crc_val, + uint64_t data_type_signature); + +CANARD_INTERNAL uint16_t crcAdd(uint16_t crc_val, + const uint8_t* bytes, + size_t len); + +/** + * Inits a memory allocator. + * + * @param [in] allocator The memory allocator to initialize. + * @param [in] buf The buffer used by the memory allocator. + * @param [in] buf_len The number of blocks in buf. + */ +CANARD_INTERNAL void initPoolAllocator(CanardPoolAllocator* allocator, + void *buf, + uint16_t buf_len); + +/** + * Allocates a block from the given pool allocator. + */ +CANARD_INTERNAL void* allocateBlock(CanardPoolAllocator* allocator); + +/** + * Frees a memory block previously returned by canardAllocateBlock. + */ +CANARD_INTERNAL void freeBlock(CanardPoolAllocator* allocator, + void* p); + +CANARD_INTERNAL uint16_t calculateCRC(const CanardTxTransfer* transfer_object); + +CANARD_INTERNAL CanardBufferBlock *canardBufferFromIdx(CanardPoolAllocator* allocator, canard_buffer_idx_t idx); + +CANARD_INTERNAL canard_buffer_idx_t canardBufferToIdx(CanardPoolAllocator* allocator, const CanardBufferBlock *buf); + +CANARD_INTERNAL CanardRxState *canardRxFromIdx(CanardPoolAllocator* allocator, canard_buffer_idx_t idx); + +CANARD_INTERNAL canard_buffer_idx_t canardRxToIdx(CanardPoolAllocator* allocator, const CanardRxState *rx); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bootloader/bootloader/DroneCAN/sys_can.h b/bootloader/bootloader/DroneCAN/sys_can.h new file mode 100644 index 000000000..bbe2c4728 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/sys_can.h @@ -0,0 +1,96 @@ +/* + sys_can.h - MCU specific CAN API + */ + +#pragma once + +#include + +/* + CAN statistics shared by low level and high level code + */ +struct CANStats { + uint32_t num_commands; + uint32_t total_commands; + uint32_t num_receive; + uint32_t num_tx_interrupts; + uint32_t num_rx_interrupts; + uint32_t rx_errors; + uint32_t esr; + uint32_t rxframe_error; + int32_t rx_ecode; + uint32_t should_accept; + uint32_t on_receive; + uint64_t last_raw_command_us; +}; + +extern struct CANStats canstats; + + +/* + disable/enable CAN interripts + */ +void sys_can_disable_IRQ(void); +void sys_can_enable_IRQ(void); + +/* + get a 16 byte unique ID for this node +*/ +void sys_can_getUniqueID(uint8_t id[16]); + +/* + initialise CAN hardware + */ +void sys_can_init(void); + +void sys_can_service(void); + +bool sys_can_prefer_canfd_tx(void); + +/* + drive a static GPIO output (used for CAN bus termination). The portpin is + encoded with GPIO_PORT_PIN(portnum, pinnum) where portnum is 0=A,1=B,2=C. + Only defined for CAN builds; matches the same-named helper in the main AM32 + firmware so the bootloader and firmware drive CAN_TERM_PIN identically. + */ +void setup_portpin(uint16_t portpin, bool enable); + +/* + called from CAN IRQ indicating we may have a free TX slot + */ +extern void DroneCAN_processTxQueue(); + +/* + called from CAN IRQ indicating we have a new frame waiting + */ +extern void DroneCAN_receiveFrame(); + +/* + called from IRQ to handle incoming frame + */ +void DroneCAN_handleFrame(CanardCANFrame *frame); + +/* + try to transmit a frame. + return 1 for success, 0 for no space, -ve for error + */ +int16_t sys_can_transmit(const CanardCANFrame* txf); + +/* + check for an incoming frame + return 1 for success, 0 for no frame, -ve for error + */ +int16_t sys_can_receive(CanardCANFrame *rx_frame); + +/* + get/set RTC backup registers. Used to detect firmware update state + */ +uint32_t get_rtc_backup_register(uint8_t idx); +void set_rtc_backup_register(uint8_t idx, uint32_t value); + +/* + magic values for RTC backup registers + */ +#define RTC_BKUP0_FWUPDATE 0x42c7 // top byte is CAN node number, 2nd byte is file server node +#define RTC_BKUP0_BOOTED 0x8c42c8 // set when main started +#define RTC_BKUP0_SIGNAL 0x8c42c9 // set on 5 DroneCAN messages processed diff --git a/bootloader/bootloader/DroneCAN/sys_can_stm32_CANFD.c b/bootloader/bootloader/DroneCAN/sys_can_stm32_CANFD.c new file mode 100644 index 000000000..b0e269a77 --- /dev/null +++ b/bootloader/bootloader/DroneCAN/sys_can_stm32_CANFD.c @@ -0,0 +1,544 @@ +/* + * sys_can_stm32_CANFD.c - STM32G4 FDCAN, classic CAN and CAN FD. + * + * Based on ArduPilot CANFDIface.cpp. Nominal (arbitration) bit timing is + * 1 Mbps at 80 MHz. Data-phase timings are the Kvaser/ArduPilot 80 MHz table + * (1/2/4/5 Mbps). EEPROM CAN_FD_MBPS 0 auto-matches the host data rate; + * 1/2/4/5 pins it. Classic DNA does not abort auto-hunt. TX FDF/BRS + * latches on once an FD frame is received (or CAN_FD_MBPS is pinned). + */ + +#include "targets.h" + +#if DRONECAN_SUPPORT && defined(MCU_G431) + +# include "main.h" +# include "sys_can.h" +# include +# include + +# ifndef MCU_FLASH_START +# define MCU_FLASH_START 0x08000000 +# endif +# ifndef EEPROM_START_ADD +# define EEPROM_START_ADD (MCU_FLASH_START + 0x1f800) +# endif +# define EEPROM_FD_MBPS_OFF 185 + +# define FDCAN_FRAME_BUFFER_SIZE 18 + +# define FDCAN_MESSAGERAM_STRIDE 0x350 +# define FDCAN_EXFILTER_OFFSET 0x70 +# define FDCAN_RXFIFO0_OFFSET 0xB0 +# define FDCAN_RXFIFO1_OFFSET 0x188 +# define FDCAN_TXFIFO_OFFSET 0x278 + +# define FDCAN_RXF0S_F0GI_SHIFT 8 +# define FDCAN_RXF1S_F1GI_SHIFT 8 + +# define MaskStdID 0x7FFU +# define MaskExtID 0x1FFFFFFFU + +# define FDCAN_ELMT_FDF (1U << 21) +# define FDCAN_ELMT_BRS (1U << 20) + +/* TDC offset 10 × 12.5 ns = 125 ns (MCP2557FD-class transceiver delay). */ +# define FDCAN_TDCO_TICKS 10U +# define FD_ERR_STREAK_RETUNE 32U + +typedef struct { + uint32_t id_flags; + uint32_t dlc_timestamp; + uint32_t data[16]; +} RxMessageRAM; + +typedef struct { + uint32_t id_flags; + uint32_t dlc_flags; + uint32_t data[16]; +} TxMessageRAM; + +volatile struct { + uint32_t rx_overflow; +} can_stats; + +static uint32_t MessageRam_StandardFilterSA; +static uint32_t MessageRam_ExtendedFilterSA; +static uint32_t MessageRam_RxFIFO0SA; +static uint32_t MessageRam_RxFIFO1SA; +static uint32_t MessageRam_TxFIFOQSA; + +/* + * Data-phase timings at 80 MHz FDCAN kernel clock. + * Values are real (prescaler, bs1, bs2, sjw); registers store minus one. + * Auto-scan order: 5 Mbps (product max) then 4, 2, 1. + */ +static const struct { + uint8_t mbps; + uint8_t prescaler; + uint8_t bs1; + uint8_t bs2; + uint8_t sjw; +} fd_timings[] = { + {5, 1, 11, 4, 4}, + {4, 1, 14, 5, 5}, + {2, 2, 14, 5, 5}, + {1, 4, 14, 5, 5}, +}; + +static uint8_t fd_idx; +static uint8_t fd_auto; +static uint8_t fd_locked; +static uint16_t fd_err_streak; +static volatile uint8_t fd_need_retune; + +static uint8_t dlc_to_len(uint8_t dlc) +{ + static const uint8_t map[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}; + return map[dlc & 0xF]; +} + +static uint8_t len_to_dlc(uint8_t len) +{ + if (len <= 8) { + return len; + } + if (len <= 12) { + return 9; + } + if (len <= 16) { + return 10; + } + if (len <= 20) { + return 11; + } + if (len <= 24) { + return 12; + } + if (len <= 32) { + return 13; + } + if (len <= 48) { + return 14; + } + return 15; +} + +static int8_t fd_idx_for_mbps(uint8_t mbps) +{ + for (uint8_t i = 0; i < (uint8_t)(sizeof(fd_timings) / sizeof(fd_timings[0])); i++) { + if (fd_timings[i].mbps == mbps) { + return (int8_t)i; + } + } + return -1; +} + +static void apply_fd_timings(uint8_t idx) +{ + const uint8_t n = (uint8_t)(sizeof(fd_timings) / sizeof(fd_timings[0])); + if (idx >= n) { + idx = 0; + } + const uint8_t presc = fd_timings[idx].prescaler; + const uint8_t bs1 = fd_timings[idx].bs1; + const uint8_t bs2 = fd_timings[idx].bs2; + const uint8_t sjw = fd_timings[idx].sjw; + FDCAN1->DBTP = (((uint32_t)(sjw - 1) << FDCAN_DBTP_DSJW_Pos) | ((uint32_t)(bs1 - 1) << FDCAN_DBTP_DTSEG1_Pos) | + ((uint32_t)(bs2 - 1) << FDCAN_DBTP_DTSEG2_Pos) | ((uint32_t)(presc - 1) << FDCAN_DBTP_DBRP_Pos) | FDCAN_DBTP_TDC); + FDCAN1->TDCR = (FDCAN_TDCO_TICKS << FDCAN_TDCR_TDCO_Pos); + fd_idx = idx; +} + +static void setupMessageRam(void) +{ + const uint32_t base = SRAMCAN_BASE + FDCAN_MESSAGERAM_STRIDE * 0; + + memset((void *)base, 0, FDCAN_MESSAGERAM_STRIDE); + + MessageRam_StandardFilterSA = base; + MessageRam_ExtendedFilterSA = base + FDCAN_EXFILTER_OFFSET; + MessageRam_RxFIFO0SA = base + FDCAN_RXFIFO0_OFFSET; + MessageRam_RxFIFO1SA = base + FDCAN_RXFIFO1_OFFSET; + MessageRam_TxFIFOQSA = base + FDCAN_TXFIFO_OFFSET; + + FDCAN1->TXBC = 0; + (void)MessageRam_StandardFilterSA; + (void)MessageRam_ExtendedFilterSA; +} + +static bool can_send(const CanardCANFrame *frame) +{ + if ((FDCAN1->TXFQS & FDCAN_TXFQS_TFQF) != 0) { + return false; + } + + uint32_t put_index = (FDCAN1->TXFQS & FDCAN_TXFQS_TFQPI) >> FDCAN_TXFQS_TFQPI_Pos; + + volatile TxMessageRAM *tx_mailbox = (volatile TxMessageRAM *)(MessageRam_TxFIFOQSA + (put_index * FDCAN_FRAME_BUFFER_SIZE * 4)); + + if (frame->id & CANARD_CAN_FRAME_EFF) { + tx_mailbox->id_flags = (frame->id & MaskExtID) | (1U << 30); + } else { + tx_mailbox->id_flags = (frame->id & MaskStdID) << 18; + } + + if (frame->id & CANARD_CAN_FRAME_RTR) { + tx_mailbox->id_flags |= (1U << 29); + } + + const uint8_t dlc = len_to_dlc(frame->data_len); + uint32_t dlc_flags = ((uint32_t)dlc << 16); +# if CANARD_ENABLE_CANFD + if (frame->canfd) { + dlc_flags |= FDCAN_ELMT_FDF | FDCAN_ELMT_BRS; + } +# endif + tx_mailbox->dlc_flags = dlc_flags; + + uint8_t nbytes = frame->data_len; + if (nbytes > 64) { + nbytes = 64; + } + const uint8_t nwords = (uint8_t)((nbytes + 3) / 4); + const uint32_t *data_ptr = (const uint32_t *)frame->data; + for (uint8_t i = 0; i < nwords; i++) { + tx_mailbox->data[i] = data_ptr[i]; + } + + FDCAN1->TXBAR = (1U << put_index); + + return true; +} + +static void handleRxInterrupt(uint8_t fifo_index) +{ + volatile uint32_t *fifo_status_reg = (fifo_index == 0) ? &FDCAN1->RXF0S : &FDCAN1->RXF1S; + volatile uint32_t *fifo_ack_reg = (fifo_index == 0) ? &FDCAN1->RXF0A : &FDCAN1->RXF1A; + + uint32_t fifo_level_mask = (fifo_index == 0) ? FDCAN_RXF0S_F0FL : FDCAN_RXF1S_F1FL; + uint32_t get_index_mask = (fifo_index == 0) ? FDCAN_RXF0S_F0GI : FDCAN_RXF1S_F1GI; + uint32_t get_index_shift = (fifo_index == 0) ? FDCAN_RXF0S_F0GI_SHIFT : FDCAN_RXF1S_F1GI_SHIFT; + + if ((*fifo_status_reg & fifo_level_mask) == 0) { + return; + } + + uint32_t get_index = (*fifo_status_reg & get_index_mask) >> get_index_shift; + + uint32_t rx_fifo_addr = (fifo_index == 0) ? MessageRam_RxFIFO0SA : MessageRam_RxFIFO1SA; + volatile RxMessageRAM *rx_mailbox = (volatile RxMessageRAM *)(rx_fifo_addr + (get_index * FDCAN_FRAME_BUFFER_SIZE * 4)); + + CanardCANFrame frame = {}; + + uint32_t id_flags = rx_mailbox->id_flags; + if (id_flags & (1U << 30)) { + frame.id = (id_flags & MaskExtID) | CANARD_CAN_FRAME_EFF; + } else { + frame.id = (id_flags >> 18) & MaskStdID; + } + + if (id_flags & (1U << 29)) { + frame.id |= CANARD_CAN_FRAME_RTR; + } + + const uint32_t dlc_word = rx_mailbox->dlc_timestamp; + const uint8_t dlc = (uint8_t)((dlc_word >> 16) & 0xF); + const bool is_fd = (dlc_word & FDCAN_ELMT_FDF) != 0; +# if CANARD_ENABLE_CANFD + frame.canfd = is_fd; + frame.data_len = is_fd ? dlc_to_len(dlc) : (dlc > 8 ? 8 : dlc); +# else + (void)is_fd; + frame.data_len = dlc > 8 ? 8 : dlc; +# endif + + uint8_t nbytes = frame.data_len; + if (nbytes > (uint8_t)sizeof(frame.data)) { + nbytes = (uint8_t)sizeof(frame.data); + frame.data_len = nbytes; + } + const uint8_t nwords = (uint8_t)((nbytes + 3) / 4); + uint32_t *data_ptr = (uint32_t *)frame.data; + for (uint8_t i = 0; i < nwords; i++) { + data_ptr[i] = rx_mailbox->data[i]; + } + + *fifo_ack_reg = get_index; + + fd_err_streak = 0; + if (is_fd) { + fd_locked = 1; + } + + DroneCAN_handleFrame(&frame); +} + +static void handleTxCompleteInterrupt(void) {} + +static void pollErrorFlagsFromISR(void) +{ + const uint8_t cel = (uint8_t)(FDCAN1->ECR >> 16); + if (cel == 0) { + return; + } + if (fd_auto && !fd_locked) { + if (fd_err_streak < 0xFFFF) { + fd_err_streak++; + } + if (fd_err_streak >= FD_ERR_STREAK_RETUNE) { + fd_need_retune = 1; + fd_err_streak = 0; + } + } +} + +void sys_can_getUniqueID(uint8_t id[16]) +{ + const uint8_t *uidbase = (const uint8_t *)UID_BASE; + memcpy(id, uidbase, 12); + + const uint32_t cpuid = SCB->CPUID; + memcpy(&id[12], &cpuid, 4); +} + +void FDCAN1_IT0_IRQHandler(void) +{ + if ((FDCAN1->IR & FDCAN_IR_RF0N) || (FDCAN1->IR & FDCAN_IR_RF0F)) { + FDCAN1->IR = FDCAN_IR_RF0N | FDCAN_IR_RF0F; + handleRxInterrupt(0); + } + + if ((FDCAN1->IR & FDCAN_IR_RF1N) || (FDCAN1->IR & FDCAN_IR_RF1F)) { + FDCAN1->IR = FDCAN_IR_RF1N | FDCAN_IR_RF1F; + handleRxInterrupt(1); + } + + pollErrorFlagsFromISR(); +} + +void FDCAN1_IT1_IRQHandler(void) +{ + if (FDCAN1->IR & FDCAN_IR_TC) { + FDCAN1->IR = FDCAN_IR_TC; + handleTxCompleteInterrupt(); + } + + if (FDCAN1->IR & FDCAN_IR_BO) { + FDCAN1->IR = FDCAN_IR_BO; + FDCAN1->CCCR &= ~FDCAN_CCCR_INIT; + } + + pollErrorFlagsFromISR(); +} + +int16_t sys_can_transmit(const CanardCANFrame *txf) +{ + return can_send(txf) ? 1 : 0; +} + +int16_t sys_can_receive(CanardCANFrame *rx_frame) +{ + (void)rx_frame; + return -1; +} + +void sys_can_disable_IRQ(void) +{ + NVIC_DisableIRQ(FDCAN1_IT0_IRQn); + NVIC_DisableIRQ(FDCAN1_IT1_IRQn); +} + +void sys_can_enable_IRQ(void) +{ + NVIC_EnableIRQ(FDCAN1_IT0_IRQn); + NVIC_EnableIRQ(FDCAN1_IT1_IRQn); +} + +static bool waitForBitState(volatile uint32_t *reg, uint32_t mask, bool target_state) +{ + while (true) { + bool current_state = ((*reg) & mask) != 0; + if (current_state == target_state) { + return true; + } + } + return false; +} + +# pragma GCC optimize("Os") + +static void can_init(void) +{ + RCC->APB1ENR1 |= RCC_APB1ENR1_FDCANEN; + + for (volatile int i = 0; i < 10000; i++) { + __NOP(); + } + + RCC->APB1RSTR1 |= RCC_APB1RSTR1_FDCANRST; + RCC->APB1RSTR1 &= ~RCC_APB1RSTR1_FDCANRST; + + for (volatile int i = 0; i < 100; i++) { + __NOP(); + } + + FDCAN1->CCCR &= ~FDCAN_CCCR_CSR; + + if (!waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_CSA, false)) { + return; + } + + FDCAN1->CCCR |= FDCAN_CCCR_INIT; + + if (!waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_INIT, true)) { + return; + } + + FDCAN1->CCCR |= FDCAN_CCCR_CCE; + + /* 1 Mbps nominal, 80 MHz kernel clock, 10 tq, 90% sample point. */ + const uint8_t sjw = 1; + const uint8_t bs1 = 8; + const uint8_t bs2 = 1; + const uint8_t prescaler = 8; + + FDCAN1->NBTP = (((sjw - 1) << FDCAN_NBTP_NSJW_Pos) | ((bs1 - 1) << FDCAN_NBTP_NTSEG1_Pos) | ((bs2 - 1) << FDCAN_NBTP_NTSEG2_Pos) | + ((prescaler - 1) << FDCAN_NBTP_NBRP_Pos)); + + apply_fd_timings(fd_idx); + + FDCAN1->CCCR |= FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE; + + setupMessageRam(); + + FDCAN1->IR = 0x3FFFFFFF; + + FDCAN1->IE = FDCAN_IE_RF0NE | FDCAN_IE_RF0FE | FDCAN_IE_RF1NE | FDCAN_IE_RF1FE | FDCAN_IE_TCE | FDCAN_IE_BOE; + + FDCAN1->ILS = FDCAN_ILS_PERR | FDCAN_ILS_SMSG; + + FDCAN1->TXBTIE = 0x7; + + FDCAN1->ILE = 0x3; + + FDCAN1->CCCR &= ~FDCAN_CCCR_INIT; + + waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_INIT, false); +} + +static void fd_select_initial_rate(void) +{ + const uint8_t *eeprom = (const uint8_t *)EEPROM_START_ADD; + uint8_t want = eeprom[EEPROM_FD_MBPS_OFF]; + if (eeprom[0] != 1) { + want = 0; + } + const int8_t idx = fd_idx_for_mbps(want); + if (want == 0 || idx < 0) { + fd_auto = 1; + fd_locked = 0; + fd_idx = 0; /* 5 Mbps, product max */ + } else { + fd_auto = 0; + fd_locked = 1; + fd_idx = (uint8_t)idx; + } + fd_err_streak = 0; + fd_need_retune = 0; +} + +bool sys_can_prefer_canfd_tx(void) +{ + return fd_locked != 0; +} + +void sys_can_service(void) +{ + if (!fd_need_retune || !fd_auto || fd_locked) { + fd_need_retune = 0; + return; + } + fd_need_retune = 0; + const uint8_t n = (uint8_t)(sizeof(fd_timings) / sizeof(fd_timings[0])); + const uint8_t next = (uint8_t)((fd_idx + 1) % n); + + FDCAN1->CCCR |= FDCAN_CCCR_INIT; + if (!waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_INIT, true)) { + return; + } + FDCAN1->CCCR |= FDCAN_CCCR_CCE; + apply_fd_timings(next); + FDCAN1->CCCR |= FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE; + FDCAN1->CCCR &= ~FDCAN_CCCR_INIT; + waitForBitState(&FDCAN1->CCCR, FDCAN_CCCR_INIT, false); +} + +void sys_can_init(void) +{ + LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; + GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; + GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = LL_GPIO_AF_9; + + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA); + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); + + GPIO_InitStruct.Pin = CAN_RX_PIN; + LL_GPIO_Init(CAN_RX_PORT, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = CAN_TX_PIN; + LL_GPIO_Init(CAN_TX_PORT, &GPIO_InitStruct); + + fd_select_initial_rate(); + can_init(); + + NVIC_SetPriority(FDCAN1_IT0_IRQn, 5); + NVIC_SetPriority(FDCAN1_IT1_IRQn, 5); + NVIC_EnableIRQ(FDCAN1_IT0_IRQn); + NVIC_EnableIRQ(FDCAN1_IT1_IRQn); +} + +uint32_t get_rtc_backup_register(uint8_t idx) +{ + const volatile uint32_t *bkp = &TAMP->BKP0R; + return bkp[idx]; +} + +void set_rtc_backup_register(uint8_t idx, uint32_t value) +{ + volatile uint32_t *bkp = &TAMP->BKP0R; + bkp[idx] = value; +} + +void setup_portpin(uint16_t portpin, bool enable) +{ + const uint8_t port = portpin >> 8; + const uint8_t pin = portpin & 0xff; + const uint32_t pinshift = 1U << pin; + GPIO_TypeDef *const ports[] = {GPIOA, GPIOB, GPIOC}; + if (port >= sizeof(ports) / sizeof(ports[0])) { + return; + } + GPIO_TypeDef *pport = ports[port]; + + LL_AHB2_GRP1_EnableClock(1U << port); + + if (enable) { + LL_GPIO_SetOutputPin(pport, pinshift); + } else { + LL_GPIO_ResetOutputPin(pport, pinshift); + } + + LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; + GPIO_InitStruct.Pin = pinshift; + GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT; + GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; + LL_GPIO_Init(pport, &GPIO_InitStruct); +} + +#endif /* DRONECAN_SUPPORT && defined(MCU_G431) */ diff --git a/bootloader/bootloader/ldscript_bl_CAN.ld b/bootloader/bootloader/ldscript_bl_CAN.ld new file mode 100644 index 000000000..439387a43 --- /dev/null +++ b/bootloader/bootloader/ldscript_bl_CAN.ld @@ -0,0 +1,135 @@ +/* + linker file for AM32 bootloader +*/ + +/* Entry Point */ +ENTRY(Reset_Handler) + +_estack = 0x20004000; /* end of RAM */ +_Min_Heap_Size = 0x200; /* required amount of heap */ +_Min_Stack_Size = 0x2000; /* required amount of stack */ + +/* Specify the memory areas */ +MEMORY +{ +FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K +RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K +} + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into FLASH */ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + . = ALIGN(4); + } >FLASH + + /* The program code and other data goes into FLASH */ + .text : + { + . = ALIGN(4); + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + /* Constant data goes into FLASH */ + .rodata : + { + . = ALIGN(4); + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + . = ALIGN(4); + } >FLASH + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } >FLASH + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + } >FLASH + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array*)) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* used by the startup to initialize data */ + _sidata = LOADADDR(.data); + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >RAM AT> FLASH + + /* Uninitialized data section */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + __bss_end__ = _ebss; + } >RAM + + /* User_heap_stack section, used to check that there is enough RAM left */ + ._user_heap_stack : + { + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + . = . + _Min_Heap_Size; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } >RAM + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/bootloader/bootloader/main.c b/bootloader/bootloader/main.c new file mode 100644 index 000000000..4a997bbda --- /dev/null +++ b/bootloader/bootloader/main.c @@ -0,0 +1,1516 @@ +/* + bootloader for AM32 ESC firmware + + based on https://github.com/AlkaMotors/AT32F421_AM32_Bootloader + */ +#include +#include + +#include + +/* Includes ------------------------------------------------------------------*/ +#include +#include + +//#pragma GCC optimize("O0") + +#include + +//#define USE_ADC_INPUT // will go right to application and ignore eeprom +//#define UPDATE_EEPROM_ENABLE + +// use this to check the clock config for the MCU (with a logic +// analyser on the input pin) +//#define BOOTLOADER_TEST_CLOCK + +// use this to check the string output code. When enabled +// the string HELLO_WORLD is output every 10ms +//#define BOOTLOADER_TEST_STRING + +// use this to check the backup domain registers work +//#define BOOTLOADER_TEST_BKUP + +// when there is no app fw yet, disable jump() +//#define DISABLE_JUMP + +// optionally enable stats on serial bit-banging +//#define SERIAL_STATS + +/* + enable checking for software reset for jump. + generally on a software reset we do want to stay in the bootloader + if the signal pin is floating, but disabling this can be useful for + CAN testing +*/ +#define CHECK_SOFTWARE_RESET 1 + +/* + enable checking for eeprom configured before jump + disabling this can be useful for CAN development +*/ +#define CHECK_EEPROM_BEFORE_JUMP 1 + +/* + should we update the bootloader version in eeprom? + */ +#define UPDATE_EEPROM_ENABLE 1 + +/* + should checkForSignal() look for a bootloader client that is already + sending when we power up? Without this the level checks read 19200 + traffic as a pin that has been pulled low and boot the application + instead of answering. Targets with no room left in a 4k bootloader + region turn it off in their makefile. + */ +#ifndef DETECT_SERIAL_CLIENT +#define DETECT_SERIAL_CLIENT 1 +#endif + +#include + +#ifndef MCU_FLASH_START +#define MCU_FLASH_START 0x08000000 +#endif + +#ifndef FIRMWARE_RELATIVE_START +// note: DRONECAN_SUPPORT is always defined (0 or 1) by the build, so test its +// value rather than defined(), to match the convention used elsewhere +#if defined(MCXA153) || DRONECAN_SUPPORT +#define FIRMWARE_RELATIVE_START 0x4000 +#else +#define FIRMWARE_RELATIVE_START 0x1000 +#endif +#endif + +#ifndef EEPROM_MAX_SIZE +#define EEPROM_MAX_SIZE 1024 // must be multiple of 256 +#endif + +#ifdef USE_PA2 +#define input_pin GPIO_PIN(2) +#define input_port GPIOA +#define PIN_NUMBER 2 +#define PORT_LETTER 0 +#elif defined(USE_PB4) +#define input_pin GPIO_PIN(4) +#define input_port GPIOB +#define PIN_NUMBER 4 +#define PORT_LETTER 1 +#elif defined(USE_PA15) +#define input_pin GPIO_PIN(15) +#define input_port GPIOA +#define PIN_NUMBER 15 +#define PORT_LETTER 0 +#elif defined(USE_PA6) +#define input_pin GPIO_PIN(6) +#define input_port GPIOA +#define PIN_NUMBER 6 +#define PORT_LETTER 0 +#elif defined(USE_PA0) +#define input_pin GPIO_PIN(0) +#define input_port GPIOA +#define PIN_NUMBER 0 +#define PORT_LETTER 0 +#elif defined(USE_PB2) +#define input_pin GPIO_PIN(2) +#define input_port GPIOB +#define PIN_NUMBER 2 +#define PORT_LETTER 1 +#elif defined(USE_PC6) +#define input_pin GPIO_PIN(6) +#define input_port GPIOC +#define PIN_NUMBER 6 +#define PORT_LETTER 2 +#else +#error "Bootloader comms pin not defined" +#endif + +static uint16_t invalid_command; + +#include + +// default no-op LED functions if not provided by blutil.h (USE_RGB_LED) +#ifndef USE_RGB_LED +static inline void bl_led_init(void) {} +static inline void bl_led_on(void) {} +static inline void bl_led_off(void) {} +static inline void bl_led_red_on(void) {} +#endif + +#ifdef USE_RGB_LED +/* + blink RGB LEDs while stuck in the bootloader + - normal: blink all LEDs at ~2.5Hz + - error: blink red only + */ +static uint16_t led_timer_start; +static uint8_t led_blink_counter; +static bool led_error_mode; + +static void __attribute__((unused)) bl_led_set_error(bool error) +{ + led_error_mode = error; +} + +static void bl_led_update(void) +{ + const uint16_t now = bl_timer_us(); + if ((uint16_t)(now - led_timer_start) < 25000U) { + return; + } + led_timer_start = now; + led_blink_counter++; + if (led_blink_counter & 0x08) { + if (led_error_mode) { + bl_led_red_on(); + } else { + bl_led_on(); + } + } else { + bl_led_off(); + } +} +#endif // USE_RGB_LED + +#if DRONECAN_SUPPORT +#include "DroneCAN/DroneCAN.h" +#include "DroneCAN/sys_can.h" +#endif + +#ifndef BOARD_FLASH_SIZE +#error "must define BOARD_FLASH_SIZE" +#endif + +#define PIN_CODE (PORT_LETTER << 4 | PIN_NUMBER) + +/* + currently only support 32, 64 or 128 k flash + */ +#ifdef NXP +#ifndef FLASH_SECTOR_SIZE +#error "must define FLASH_SECTOR_SIZE" +#endif + +#if BOARD_FLASH_SIZE == 64 +#define EEPROM_START_ADD (0x10000 - FLASH_SECTOR_SIZE) +#define FLASH_SIZE_CODE 0x15 +#define ADDRESS_SHIFT 0 + +#elif BOARD_FLASH_SIZE == 128 +#define EEPROM_START_ADD (0x20000 - FLASH_SECTOR_SIZE) +//#define FLASH_SIZE_CODE 0x2B +#define FLASH_SIZE_CODE 0x16 +#define ADDRESS_SHIFT 2 // addresses from the bl client are shifted 2 bits before being used +#else +#error "unsupported BOARD_FLASH_SIZE" +#endif + +#else +#if BOARD_FLASH_SIZE == 32 +#define EEPROM_START_ADD (MCU_FLASH_START+0x7c00) +#define FLASH_SIZE_CODE 0x1f +#define ADDRESS_SHIFT 0 + +#elif BOARD_FLASH_SIZE == 64 +#define EEPROM_START_ADD (MCU_FLASH_START+0xf800) +#define FLASH_SIZE_CODE 0x35 +#define ADDRESS_SHIFT 0 + +#elif BOARD_FLASH_SIZE == 128 +#define EEPROM_START_ADD (MCU_FLASH_START+0x1f800) +#define FLASH_SIZE_CODE 0x2B +#define ADDRESS_SHIFT 2 // addresses from the bl client are shifted 2 bits before being used +#else +#error "unsupported BOARD_FLASH_SIZE" +#endif + +#endif + +/* + a bootloader protocol version, sent as byte 8 in the deviceInfo + this should change when the configurator applications need to know + about a changed feature set in the bootloader + + v2: magic flash addresses (ADDRESS_MAGIC_EEPROM, ADDRESS_MAGIC_FILE_NAME) supported + v3: supports ADDRESS_MAGIC_DEVINFO + */ +#define BOOTLOADER_PROTOCOL_VERSION 3 + +/* + the devinfo structure tells the configuration client our pin code, + flash size and device type. It can also be used by the main firmware + to confirm we have the right eeprom address and pin code. We have 2 + 32bit magic values so the main firmware can confirm the bootloader + supports this feature + */ +#define DEVINFO_MAGIC1 0x5925e3da +#define DEVINFO_MAGIC2 0x4eb863d9 + +static const struct __attribute__((packed)) { + uint32_t magic1; + uint32_t magic2; + /* + deviceInfo bytes: '4','7','1', pin code, flash size code, 0x06, 0x06, + protocol version, 0x30 + */ + const uint8_t deviceInfo[9]; + /* + for (protocol version >= 3) we have additional information which can be fetched via ADDRESS_MAGIC_DEVINFO + */ + uint8_t length; + uint8_t address_shift; // this is 2 on some MCUs + /* + the following uint16_t start addresses are the addresses that need + to be passed to CMD_SET_ADDRESS to get each of the respective + areas. Note that these are shifted addresses if address_shift is + non-zero. This keeps the values within the limitation of the 16 + bit address in the protocol + */ + uint16_t firmware_start; + uint16_t filename_start; + uint16_t eeprom_start; + uint16_t tune_start; +} devinfo __attribute__((section(".devinfo"))) = { + .magic1 = DEVINFO_MAGIC1, + .magic2 = DEVINFO_MAGIC2, + .deviceInfo = {'4','7','1',PIN_CODE,FLASH_SIZE_CODE,0x06,0x06,BOOTLOADER_PROTOCOL_VERSION,0x30}, + sizeof(devinfo), + ADDRESS_SHIFT, + (uint16_t)(FIRMWARE_RELATIVE_START >> ADDRESS_SHIFT), // firmware_start + (uint16_t)((EEPROM_START_ADD - 32) >> ADDRESS_SHIFT), // filename_start + (uint16_t)(EEPROM_START_ADD >> ADDRESS_SHIFT), // eeprom_start + (uint16_t)((EEPROM_START_ADD + 48U) >> ADDRESS_SHIFT) // tune_start +}; + +typedef void (*pFunction)(void); + +#define APPLICATION_ADDRESS (uint32_t)(MCU_FLASH_START + FIRMWARE_RELATIVE_START) + +/* + a magic value for CMD_SET_ADDRESS which sets the address to + EEPROM_START_ADD. Supported for BOOTLOADER_PROTOCOL_VERSION 2 and + later + */ +#define ADDRESS_MAGIC_EEPROM 0x20 + +// magic address for FILE_NAME +#define ADDRESS_MAGIC_FILE_NAME 0x21 + +// magic address for continue transfer from last read +#define ADDRESS_MAGIC_CONTINUE 0x22 + +/* + magic address that maps to the devinfo structure in flash, so a + configuration client can READ the full deviceInfo (including the protocol + version and firmware start) even over a 4-way passthrough that only forwards + a short signature in the InitFlash reply. The read returns magic1, magic2 + then the deviceInfo bytes, so the client can confirm support via the magic + values. Supported for BOOTLOADER_PROTOCOL_VERSION 3 and later. + */ +#define ADDRESS_MAGIC_DEVINFO 0x23 + + +#define CMD_RUN 0x00 +#define CMD_PROG_FLASH 0x01 +#define CMD_ERASE_FLASH 0x02 +#define CMD_READ_FLASH_SIL 0x03 +#define CMD_VERIFY_FLASH 0x03 +#define CMD_VERIFY_FLASH_ARM 0x04 +#define CMD_READ_EEPROM 0x04 +#define CMD_PROG_EEPROM 0x05 +#define CMD_READ_SRAM 0x06 +#define CMD_READ_FLASH_ATM 0x07 +#define CMD_KEEP_ALIVE 0xFD +#define CMD_SET_ADDRESS 0xFF +#define CMD_SET_BUFFER 0xFE + +static char receiveByte; +static bool messagereceived; +static int cmd; +static int received; +static bool initialized; +/* + set whenever a validated (good-CRC, known) 4-way command is processed. While + set we stop polling DroneCAN, because DroneCAN_boot_ok() does a multi-ms + crc32 over the whole firmware that blocks the bit-banged serial and corrupts + 4-way reads. Marking on every good command (not just the deviceInfo probe) + protects even a client that skips the deviceInfo handshake from the first + command onward. The start-bit-wait loop clears this after + BL_SERIAL_IDLE_THRESHOLD ticks (~10s) without a good command, so DroneCAN + polling resumes quickly once the client goes away - no reset required. See + serialreadChar(), mark_serial_active() and BL_SERIAL_IDLE_THRESHOLD. + */ +static bool bl_serial_active; +#if DRONECAN_SUPPORT +// counter of consecutive ~50ms idle ticks while bl_serial_active is set. +// Zeroed by mark_serial_active() on every good 4-way command and by any +// received byte, so it only climbs during true post-session silence. +static uint16_t bl_serial_idle_count; +// ~10 seconds of silence after the last good 4-way command before we assume the +// client has gone and resume DroneCAN polling. Short enough for fast recovery, +// long enough to bridge the gaps between commands in an active session. +// 200 ticks * 50ms = 10s. +#define BL_SERIAL_IDLE_THRESHOLD 200U +#endif + +// mark that a validated 4-way command was just handled: suppress DroneCAN +// polling for this config session and restart the idle timer. +static void mark_serial_active(void) +{ + bl_serial_active = true; +#if DRONECAN_SUPPORT + bl_serial_idle_count = 0; +#endif +} +static uint8_t rxBuffer[258]; +static uint8_t payLoadBuffer[256]; +static uint8_t rxbyte; +static uint32_t address; +static uint32_t continue_address; + +typedef union __attribute__ ((packed)) +{ + uint8_t bytes[2]; + uint16_t word; +} uint8_16_u; + +static uint16_t len; +static uint16_t payload_buffer_size; +static char incoming_payload_no_command; + +/* USER CODE BEGIN PFP */ +static void sendString(const uint8_t data[], int len); +static void receiveBuffer(); +static void serialwriteChar(uint8_t data); +static void serialwriteOneChar(uint8_t data); + +#define BAUDRATE 19200 +#define BITTIME 52 // 1000000/BAUDRATE +#define HALFBITTIME 26 // 500000/BAUDRATE + +// used for timing bytes +static uint16_t us_start; + +static void bl_timer_reset(void) +{ + us_start = bl_timer_us(); +} + +static uint16_t bl_timer_elapsed(void) +{ + return bl_timer_us() - us_start; +} + +static void delayMicroseconds(uint16_t micros) +{ + bl_timer_reset(); + while (bl_timer_elapsed() < micros) { + } +} + +/* + jump to the application firmware + */ +static void jump() +{ +#ifndef DISABLE_JUMP +#if CHECK_EEPROM_BEFORE_JUMP +#ifdef NXP + uint8_t eeprom[EEPROM_MAX_SIZE]; + read_flash_bin(eeprom, EEPROM_START_ADD, EEPROM_MAX_SIZE); + uint8_t value = eeprom[0]; +#else + uint8_t value = *(uint8_t*)(EEPROM_START_ADD); +#endif + /* + byte 0 of the eeprom is set to 0x01 once the settings have been + written, so a different value means the board has not been + configured. A blank byte is the exception: that is either a board + that has never been configured or an update_EEPROM() that lost + power during the page erase, and in both cases there can still be + perfectly good firmware in flash. The application header checks + below are what actually prove that, so refusing on a blank byte + only serves to trap a working board in the bootloader with no way + out except a configurator. + */ + if (value != 0x01 && value != 0xFF) { + invalid_command = 0; + return; + } +#endif +#ifndef DISABLE_APP_HEADER_CHECKS + /* + first word of the app is the stack pointer - make sure that it is in range + */ + const uint32_t *app = (uint32_t*)(MCU_FLASH_START + FIRMWARE_RELATIVE_START); + const uint32_t ram_start = 0x20000000; +#ifndef RAM_LIMIT_KB +#define RAM_LIMIT_KB 64 +#endif + const uint32_t ram_limit_kb = RAM_LIMIT_KB; + const uint32_t ram_end = ram_start+ram_limit_kb*1024; + if (app[0] < ram_start || app[0] > ram_end) { + invalid_command = 0; + return; + } + /* + 2nd word is the entry point of the main app. Ensure that is in range + */ + const uint32_t flash_limit_kb = 256; + if (app[1] < APPLICATION_ADDRESS || app[1] > APPLICATION_ADDRESS+flash_limit_kb*1024) { + // outside a 256k range, really unlikely to be a valid + // application, don't jump + invalid_command = 0; + return; + } +#endif // DISABLE_APP_HEADER_CHECKS + +#if DRONECAN_SUPPORT + if (!DroneCAN_boot_ok()) { + invalid_command = 0; +#ifdef USE_RGB_LED + bl_led_set_error(true); +#endif + return; + } + + sys_can_disable_IRQ(); +#endif + + bl_led_off(); + jump_to_application(); +#endif +} + +/* + 16 bit CRC + */ +uint16_t crc16(const uint8_t* pBuff, uint16_t length) +{ + uint16_t ret = 0; + + for (int i = 0; i < length; i++) { + uint8_t xb = pBuff[i]; + for (uint8_t j = 0; j < 8; j++) { + if (((xb & 0x01) ^ (ret & 0x0001)) != 0) { + ret >>= 1; + ret ^= 0xA001; + } else { + ret >>= 1; + } + xb = xb >> 1; + } + } + return ret; +} + +static bool checkCrc(uint8_t* pBuff, uint16_t length) +{ + const uint16_t crcin = pBuff[length] | (pBuff[length+1]<<8); + const uint16_t crc2 = crc16(pBuff, length); + + return crcin == crc2; +} + + +static void setReceive() +{ + gpio_mode_set_input(input_pin, GPIO_PULL_UP); + received = 0; +} + +static void setTransmit() +{ + // set high before we set as output to guarantee idle high + gpio_set(input_pin); + gpio_mode_set_output(input_pin, GPIO_OUTPUT_PUSH_PULL); + + // delay a bit to let the sender get setup for receiving + // only delay if device info has been sent, this prevents + // an issue with iNAV + if (initialized){ + delayMicroseconds(BITTIME); + } +} + +static void serialwriteOneChar(uint8_t c) +{ +#if DRONECAN_SUPPORT + sys_can_disable_IRQ(); +#endif + setTransmit(); + serialwriteChar(c); + setReceive(); +#if DRONECAN_SUPPORT + sys_can_enable_IRQ(); +#endif +} + +static void send_ACK() +{ + serialwriteOneChar(0x30); // good ack! + invalid_command = 0; + // an ACK is only sent for a validated command; keep DroneCAN suppressed + mark_serial_active(); +} + +static void send_BAD_ACK() +{ + serialwriteOneChar(0xC1); // bad command message. + invalid_command++; +} + +static void send_BAD_CRC_ACK() +{ + serialwriteOneChar(0xC2); // bad command message. + invalid_command++; +} + +static void sendDeviceInfo() +{ + sendString(devinfo.deviceInfo,sizeof(devinfo.deviceInfo)); + initialized = true; + // a config client is connected; stop DroneCAN polling so its firmware-CRC + // scan can't block the bit-banged serial during this session + mark_serial_active(); +} + +static bool checkAddressWritable(uint32_t address) +{ + return address >= APPLICATION_ADDRESS; +} + +static void decodeInput() +{ + if (incoming_payload_no_command) { + len = payload_buffer_size; + + if (checkCrc(rxBuffer,len)) { + memset(payLoadBuffer, 0, sizeof(payLoadBuffer)); // reset buffer + + for (int i = 0; i < len; i++) { + payLoadBuffer[i]= rxBuffer[i]; + } + send_ACK(); + incoming_payload_no_command = 0; + + return; + } else { + send_BAD_CRC_ACK(); + return; + } + } + + cmd = rxBuffer[0]; + + if (rxBuffer[16] == 0x7d) { + if (rxBuffer[8] == 13 && rxBuffer[9] == 66) { + sendDeviceInfo(); + rxBuffer[20]= 0; + + } + return; + } + + if (rxBuffer[20] == 0x7d) { + if (rxBuffer[12] == 13 && rxBuffer[13] == 66) { + sendDeviceInfo(); + rxBuffer[20]= 0; + return; + } + + } + if (rxBuffer[40] == 0x7d) { + if (rxBuffer[32] == 13 && rxBuffer[33] == 66) { + sendDeviceInfo(); + rxBuffer[20]= 0; + return; + } + } + + if (cmd == CMD_RUN) { + // starts the main app + if ((rxBuffer[1] == 0) && (rxBuffer[2] == 0) && (rxBuffer[3] == 0)) { + invalid_command = 101; + } + } + + if (cmd == CMD_PROG_FLASH) { + len = 2; + if (!checkCrc((uint8_t*)rxBuffer, len)) { + send_BAD_CRC_ACK(); + + return; + } + + if (!checkAddressWritable(address)) { + send_BAD_ACK(); + + return; + } + + if (address == EEPROM_START_ADD && payload_buffer_size > 2) { + /* + if the configuration client is writing the eeprom area + then replace the bootloader version byte in the buffer + with the right version. This makes the update_EEPROM() + less likely to need to erase any flash + */ + payLoadBuffer[2] = BOOTLOADER_VERSION; + } + + if (!save_flash_nolib((uint8_t*)payLoadBuffer, payload_buffer_size,address)) { + send_BAD_ACK(); + } else { + send_ACK(); + } + + return; + } + + if (cmd == CMD_SET_ADDRESS) { + + // command set addressinput format is: CMD, 00 , High byte + // address, Low byte address, crclb ,crchb + len = 4; // package without 2 byte crc + if (!checkCrc((uint8_t*)rxBuffer, len)) { + send_BAD_CRC_ACK(); + return; + } + + invalid_command = 0; + + address = rxBuffer[2] << 8 | rxBuffer[3]; + + /* + check for magic addresses that map to specific areas + */ + if (address == ADDRESS_MAGIC_EEPROM) { + // config app has requested access to the eeprom region + address = EEPROM_START_ADD; + } else if (address == ADDRESS_MAGIC_FILE_NAME) { + // config app has requested access to the FILE_NAME. Assume eeprom-32 for now, + // this may change for some MCUs in the future + address = EEPROM_START_ADD - 32; + } else if (address == ADDRESS_MAGIC_CONTINUE) { + // allow easy continue from last address, for breaking up eeprom into multiple small reads + address = continue_address; + } else if (address == ADDRESS_MAGIC_DEVINFO) { + // config app has requested the devinfo structure (magic1, magic2, + // deviceInfo). Lets the client read the protocol version and firmware + // start over a 4-way link that doesn't forward the full deviceInfo. + address = (uint32_t)(uintptr_t)&devinfo; + } else if (address < 1024) { + // other addresses below 1024 are reserved for future magic values + send_BAD_ACK(); + return; + } else { + // cope with ADDRESS_SHIFT for 128k flash boards, and add + // in MCU base flash address + address = MCU_FLASH_START + (address << ADDRESS_SHIFT); + } + + send_ACK(); + return; + } + + if (cmd == CMD_SET_BUFFER) { + // for writing buffer rx buffer 0 = command byte. command set + // address, input , format is CMD, 00 , 00 or 01 (if buffer is 256), + // buffer_size, + len = 4; // package without 2 byte crc + if (!checkCrc((uint8_t*)rxBuffer, len)) { + send_BAD_CRC_ACK(); + + return; + } + mark_serial_active(); + + // no ack with command set buffer; + if (rxBuffer[2] == 0x01) { + payload_buffer_size = 256; // if nothing in this buffer + } else { + payload_buffer_size = rxBuffer[3]; + } + incoming_payload_no_command = 1; + setReceive(); + + return; + } + + if (cmd == CMD_KEEP_ALIVE) { + + len = 2; + if (!checkCrc((uint8_t*)rxBuffer, len)) { + send_BAD_CRC_ACK(); + + return; + } + mark_serial_active(); + + serialwriteOneChar(0xC1); // bad command message. + return; + } + + if (cmd == CMD_ERASE_FLASH) { + len = 2; + if (!checkCrc((uint8_t*)rxBuffer, len)) { + send_BAD_CRC_ACK(); + + return; + } + + if (!checkAddressWritable(address)) { + send_BAD_ACK(); + + return; + } + + send_ACK(); + return; + } + + if (cmd == CMD_READ_FLASH_SIL) { + + // for sending contents of flash memory at the memory location set in + // bootloader.c need to still set memory with data from set mem + // command + len = 2; + if (!checkCrc((uint8_t*)rxBuffer, len)) { + send_BAD_CRC_ACK(); + + return; + } + mark_serial_active(); + + if (address == 0) { + // must send SET_ADDRESS first + send_BAD_ACK(); + return; + } + + uint16_t out_buffer_size = rxBuffer[1];// + if (out_buffer_size == 0) { + out_buffer_size = 256; + } + + uint8_t read_data[out_buffer_size + 3]; // make buffer 3 larger to fit CRC and ACK + memset(read_data, 0, sizeof(read_data)); + // read_flash((uint8_t*)read_data , address); // make sure read_flash reads two less than buffer. + read_flash_bin((uint8_t*)read_data, address, out_buffer_size); + + const uint16_t crc = crc16(read_data,out_buffer_size); + read_data[out_buffer_size] = crc&0xFF; + read_data[out_buffer_size + 1] = crc>>8; + read_data[out_buffer_size + 2] = 0x30; + sendString(read_data, out_buffer_size+3); + + // allow the client to continue to the next address with ADDRESS_MAGIC_CONTINUE + continue_address = address + out_buffer_size; + + // ensure client sends a SET_ADDRESS each time + address = 0; + + return; + } + + serialwriteOneChar(0xC1); // bad command message. + invalid_command++; +} + +#ifdef SERIAL_STATS +// stats for debugging serial protocol +struct { + uint32_t no_idle; + uint32_t no_start; + uint32_t bad_start; + uint32_t bad_stop; + uint32_t good; +} stats; +#endif + +/* + read one byte from the input pin, 19200, not inverted, one stop bit + + return false if we can't get a byte, or the byte has bad framing + */ +static bool serialreadChar() +{ + rxbyte=0; + bl_timer_reset(); + + // UART is idle high, wait for it to be in the idle state + while (!gpio_read(input_pin)) { // wait for rx to go high + if (bl_timer_elapsed() > 20000U) { + /* + if we don't get a command for 20ms then assume we should + be trying to boot the main firmware, invalid_command 101 + triggers the jump immediately + */ + invalid_command = 101; +#ifdef SERIAL_STATS + stats.no_idle++; +#endif + return false; + } + } + + // now we need to wait for the start bit leading edge, which is low + bl_timer_reset(); + while (gpio_read(input_pin)) { + uint16_t elapsed = bl_timer_elapsed(); + if (messagereceived && elapsed > 5*BITTIME) { + // we've been waiting too long, don't allow for long gaps + // between bytes +#ifdef SERIAL_STATS + stats.no_start++; +#endif + return false; + } +#if DRONECAN_SUPPORT + // Check DroneCAN every ~50ms when waiting for first byte. + // DroneCAN_boot_ok() scans flash (~2-5ms per call), so we + // rate-limit to avoid blocking serial start-bit detection. + if (!messagereceived && elapsed > 50000) { + if (bl_serial_active) { + /* + a config client is mid-session: a good 4-way command set + bl_serial_active (see mark_serial_active()) so DroneCAN polling + stays suppressed. The idle counter is zeroed on every good command + and any received byte, so it only climbs during real silence. After + BL_SERIAL_IDLE_THRESHOLD ticks (~10s) without a command we assume the + client is gone and resume polling - no reset required. + */ + if (++bl_serial_idle_count >= BL_SERIAL_IDLE_THRESHOLD) { + bl_serial_active = false; + bl_serial_idle_count = 0; + } + } else if (DroneCAN_update()) { + jump(); + } + bl_timer_reset(); + } +#endif +#ifdef USE_RGB_LED + bl_led_update(); +#endif + } + + // start bit detected - disable CAN IRQs to protect bit-banged timing +#if DRONECAN_SUPPORT + sys_can_disable_IRQ(); +#endif + + // wait to get the center of bit time. We want to sample at the + // middle of each bit + delayMicroseconds(HALFBITTIME); + if (gpio_read(input_pin)) { + // bad framing, we should be half-way through the start bit + // which should still be low +#ifdef SERIAL_STATS + stats.bad_start++; +#endif +#if DRONECAN_SUPPORT + sys_can_enable_IRQ(); +#endif + return false; + } + + /* + now sample the 8 data bits + */ + int bits_to_read = 0; + while (bits_to_read < 8) { + delayMicroseconds(BITTIME); + rxbyte = rxbyte | gpio_read(input_pin) << bits_to_read; + bits_to_read++; + } + + // wait till middle of stop bit, so we can check that too + delayMicroseconds(BITTIME); + if (!gpio_read(input_pin)) { + // bad framing, stop bit should be high +#ifdef SERIAL_STATS + stats.bad_stop++; +#endif +#if DRONECAN_SUPPORT + sys_can_enable_IRQ(); +#endif + return false; + } + + // re-enable CAN IRQs after byte is complete +#if DRONECAN_SUPPORT + sys_can_enable_IRQ(); +#endif + + // we got a good byte + messagereceived = true; +#if DRONECAN_SUPPORT + bl_serial_idle_count = 0; +#endif + receiveByte = rxbyte; +#ifdef SERIAL_STATS + stats.good++; +#endif + return true; +} + + +static void serialwriteChar(uint8_t data) +{ + // start bit is low + gpio_clear(input_pin); + delayMicroseconds(BITTIME); + + // send data bits + uint8_t bits_written = 0; + while (bits_written < 8) { + if (data & 0x01) { + gpio_set(input_pin); + } else { + // GPIO_BC(input_port) = input_pin; + gpio_clear(input_pin); + } + bits_written++; + data = data >> 1; + delayMicroseconds(BITTIME); + } + + // send stop bit + gpio_set(input_pin); + + /* + note that we skip the delay by BITTIME for the full stop bit and + do it in sendString() instead to ensure when sending an ACK + immediately followed by a setReceive() on a slow MCU that we + start on the receive as soon as possible. + */ +} + + +static void sendString(const uint8_t *data, int len) +{ +#if DRONECAN_SUPPORT + sys_can_disable_IRQ(); +#endif + setTransmit(); + for (int i = 0; i < len; i++) { + serialwriteChar(data[i]); + // for multi-byte writes we add the stop bit delay + delayMicroseconds(BITTIME); + } + setReceive(); +#if DRONECAN_SUPPORT + sys_can_enable_IRQ(); +#endif +} + +static void receiveBuffer() +{ + uint16_t count = 0; + messagereceived = false; + memset(rxBuffer, 0, sizeof(rxBuffer)); + + setReceive(); + + for (uint32_t i = 0; i < sizeof(rxBuffer); i++) { + if (!serialreadChar()) { + break; + } + + if (incoming_payload_no_command) { + if (count == payload_buffer_size+2U) { + break; + } + + rxBuffer[i] = rxbyte; + count++; + } else { + if (bl_timer_elapsed() > 250) { + + count = 0; + + break; + } else { + rxBuffer[i] = rxbyte; + if (i == 257) { + invalid_command+=20; // needs one hundred to trigger a jump but will be reset on next set address commmand + + } + } + } + } + if (messagereceived) { + decodeInput(); + }else{ + invalid_command++; + } +} + +#ifdef UPDATE_EEPROM_ENABLE +/* + Updating the version means erasing the eeprom and writing it back, + and there is no second page to stage a copy in, so for as long as + that takes the settings exist only in ram. Two things stop that from + being a board that comes back wrong: + + - it only runs on a software reset, which in practice means the user + asked the firmware to enter the bootloader, so the board is on a + bench rather than in the air + - if power is lost anyway, jump() still boots a blank eeprom, so the + board runs its firmware on defaults instead of stopping + */ +static void update_EEPROM() +{ + if (!bl_was_software_reset()) { + // we only update the bootloader version on a software reset to reduce the chances + // of a brownout or spark causing a eeprom write that corrupts the settings + return; + } +#ifdef NXP + uint8_t eeprom[EEPROM_MAX_SIZE]; + read_flash_bin(eeprom, EEPROM_START_ADD, EEPROM_MAX_SIZE); +#else + const uint8_t *eeprom = (const uint8_t *)EEPROM_START_ADD; +#endif + if (BOOTLOADER_VERSION == eeprom[2] || eeprom[2] == 0xFF || eeprom[2] == 0x00) { + return; + } + + // update only the bootloader version, preserve every other byte + uint8_t data[EEPROM_MAX_SIZE]; + memcpy(data, eeprom, EEPROM_MAX_SIZE); + data[2] = BOOTLOADER_VERSION; + +#ifdef NXP + /* + the NXP driver erases a whole 8k flash sector per write regardless + of length, so there is nothing to gain by writing less than the + whole region, and EEPROM_MAX_SIZE need not be chunk aligned here. + */ + save_flash_nolib(data, EEPROM_MAX_SIZE, EEPROM_START_ADD); +#else + /* + On the STM32 style driver the write erases one page and programs + it back a halfword at a time, so the settings are exposed for the + length of that write. Rewrite only the first chunk, which is where + AM32 keeps its settings: the erase leaves the rest of the page at + 0xFF, which is what it already was, so the programming part of the + window shrinks to a quarter. save_flash_nolib() may also not accept + a write larger than one chunk. + + That is only lossless if nothing is stored past the first chunk. If + something is, leave the version alone rather than destroy settings + for a version byte. The skip is silent: such a unit keeps its old + version and reads back like one that was never updated. AM32 keeps + its settings well inside the first chunk, so this does not arise in + practice, but it is worth knowing when reading a version byte in + the field. + */ + #define EEPROM_UPDATE_SIZE 256 + _Static_assert(EEPROM_MAX_SIZE >= EEPROM_UPDATE_SIZE, + "eeprom region smaller than one update chunk"); + + for (uint32_t i = EEPROM_UPDATE_SIZE; i < EEPROM_MAX_SIZE; i++) { + if (eeprom[i] != 0xFF) { + return; + } + } + + // this write is also what erases the page, so it has to come last + save_flash_nolib(data, EEPROM_UPDATE_SIZE, EEPROM_START_ADD); +#endif +} +#endif // UPDATE_EEPROM_ENABLE + +/* + the shortest pulse a 19200 baud bootloader UART can produce is one + bit time, so anything much shorter than that cannot be bootloader + traffic + */ +#define FAST_SIGNAL_MAX_PULSE_US (BITTIME/2) + +// number of short pulses needed before we declare a fast signal +#define FAST_SIGNAL_MIN_PULSES 8 + +/* + how long to watch the pin for. The slowest DShot rate a flight + controller will use repeats well inside this window + */ +#define FAST_SIGNAL_WINDOW_US 2000 + +/* + detect a fast digital signal on the input pin + + DShot, bidirectional DShot, Multishot and Oneshot all contain pulses + far shorter than one 19200 baud bit time. If we see enough of them + then a flight controller is driving the pin and we should be running + the main firmware rather than sitting in the bootloader. + + This has to be done with edge timing rather than by sampling the pin + level. Normal DShot idles low, but bidirectional DShot idles high, + which is indistinguishable from an idle bootloader UART if you only + look at levels. + */ +static bool detect_fast_input_signal(void) +{ + uint8_t short_pulses = 0; + bool last_level = gpio_read(input_pin); + uint16_t last_edge = 0; + + bl_timer_reset(); + + while (bl_timer_elapsed() < FAST_SIGNAL_WINDOW_US) { + const bool level = gpio_read(input_pin); + if (level == last_level) { + continue; + } + last_level = level; + + const uint16_t now = bl_timer_elapsed(); + if (now - last_edge < FAST_SIGNAL_MAX_PULSE_US && + ++short_pulses >= FAST_SIGNAL_MIN_PULSES) { + return true; + } + last_edge = now; + } + + return false; +} + +/* + look for 19200 framing on the input pin + + A bootloader client that is already sending when we power up holds + the line low for most of every byte, and the level checks in + checkForSignal() read that as a pin that has been pulled low, so we + boot the firmware instead of answering. Framing tells the two apart: + take a falling edge as a start bit and check that the stop bit of + that byte and of the several after it all land high on the 19200 + grid. + + Being wrong in the direction of staying here is cheap. If an input + protocol happens to fool this, the main loop boots the firmware a + few ms later through detect_fast_input_signal() or the invalid + command count. Being wrong the other way is what leaves a board that + cannot be flashed. + */ +#if DETECT_SERIAL_CLIENT +static bool detect_serial_framing(void) +{ + for (uint8_t tries = 0; tries < 8; tries++) { + bool was_high = false; + bool falling_edge = false; + + bl_timer_reset(); + while (bl_timer_elapsed() < 20U*BITTIME) { + if (gpio_read(input_pin)) { + was_high = true; + } else if (was_high) { + falling_edge = true; + break; + } + } + + if (!falling_edge) { + return false; // nothing is switching, so nothing is sending + } + + /* + Treat the edge as a start bit and look for a run of stop bits + on the 19200 grid, never resyncing. A client sends its packet + back to back, so every tenth bit from here has to be high. + + The run has to be several bytes long. Oneshot125 repeats every + 500us against a byte time of 520us, so it lines up with one or + two stop bits often enough to matter, but 20us of drift per + byte pulls it off the grid before the run is done. + + Picking the signal up mid byte lands on a data bit rather than + a start bit, which is what the retries are for. + */ + uint8_t stop_bits = 0; + + delayMicroseconds(9U*BITTIME + HALFBITTIME); + while (gpio_read(input_pin)) { + if (++stop_bits >= 5) { + return true; + } + delayMicroseconds(10U*BITTIME); + } + } + + return false; +} +#else +static bool detect_serial_framing(void) +{ + return false; +} +#endif // DETECT_SERIAL_CLIENT + +#define low_pin_count_threshold 450 // count signal pin is low before determining jump to main firmware +#define pull_down_pin_count_interations 4000 // greater interations extend grace period for input devices booting with signal pin high +static void checkForSignal() +{ + uint16_t low_pin_count = 0; + + gpio_mode_set_input(input_pin, GPIO_PULL_DOWN); + + delayMicroseconds(500); + + /* + if a fast signal is already being driven onto the pin then a + flight controller is talking to us and we should boot the main + firmware. The level based checks below cannot see an inverted + (bidirectional) DShot signal, as that idles high and so looks the + same as a bootloader client holding the line high. + + Skipped on a software reset for the same reason the level checks + are (see below): a software reset generally means the firmware + asked to stay in the bootloader, and we must let update_EEPROM() + run. A board that really is being driven with DShot still recovers + through the main loop detector, which is not gated this way. + */ +#if CHECK_SOFTWARE_RESET + if (!bl_was_software_reset() && detect_fast_input_signal()) { + jump(); + } +#else + if (detect_fast_input_signal()) { + jump(); + } +#endif + + for (int i = 0 ; i < pull_down_pin_count_interations ; i ++) { + if (!gpio_read(input_pin)) { + low_pin_count++; + } + + delayMicroseconds(10); + if (low_pin_count > low_pin_count_threshold) { + i = pull_down_pin_count_interations ; // end for loop if low_pin_count_threshold has already been exceeded + } + } + if (low_pin_count > low_pin_count_threshold) { // pulled low & majority stayed low - jump to application + if (detect_serial_framing()) { + return; // a client is sending to us, the pin is not pulled low + } +#if CHECK_SOFTWARE_RESET + if (!bl_was_software_reset()) { + jump(); + } +#else + jump(); +#endif + } + + gpio_mode_set_input(input_pin, GPIO_PULL_UP); + + delayMicroseconds(500); + + for (int i = 0 ; i < 500; i++) { + if ( !(gpio_read(input_pin))) { + low_pin_count++; + } else { + + } + delayMicroseconds(10); + } + if (low_pin_count == 0) { + return; // pulled high & never low in history - stay in bootloader only + } + + low_pin_count = 0; + + gpio_mode_set_input(input_pin, GPIO_PULL_NONE); + + delayMicroseconds(500); + + for (int i = 0 ; i < 500; i ++) { + if ( !(gpio_read(input_pin))) { + low_pin_count++; + } + + delayMicroseconds(10); + } + + /* + floating and low at least once - jump to application, unless the + line is actually a client sending to us. + + detect_serial_framing() reads a steadily high line as a run of stop + bits, so a floating pin that took one low glitch and then sat high + now stays in the bootloader where before it would boot. That is the + safe direction on purpose: with no flight controller attached there + are no motors to leave unspun, and the main loop still boots the + firmware once a real signal appears. Booting on a noise glitch is + the worse outcome. This bias is best confirmed on hardware with a + floating-pin power up, which the SITL suite cannot model faithfully. + */ + if (low_pin_count > 0 && !detect_serial_framing()) { + jump(); + } +} + +#ifdef BOOTLOADER_TEST_CLOCK +/* + this should provide a 2ms low followed by a 1ms high if the clock is correct + */ +static void test_clock(void) +{ + setTransmit(); + + while (1) { + gpio_clear(input_pin); + bl_timer_reset(); + while (bl_timer_elapsed() < 2000) ; + gpio_set(input_pin); + bl_timer_reset(); + while (bl_timer_elapsed() < 1000) ; + } +} +#endif // BOOTLOADER_TEST_CLOCK + +#ifdef BOOTLOADER_TEST_STRING +/* + this should send HELLO_WORLD continuously + */ +static void test_string(void) +{ + while (1) { + delayMicroseconds(10000); + sendString((uint8_t*)"HELLO_WORLD",11); + } +} +#endif // BOOTLOADER_TEST_STRING + + +#ifdef BOOTLOADER_TEST_BKUP +/* + test operation of backup domain registers + */ +volatile struct { + uint32_t value; + uint32_t fail; +} bkup; + +static void test_rtc_backup(void) +{ + const uint8_t idx = 1; + while (true) { + bkup.value++; + set_rtc_backup_register(idx, bkup.value); + const uint32_t bkup_value2 = get_rtc_backup_register(idx); + if (bkup_value2 != bkup.value) { + bkup.fail++; + } + delayMicroseconds(1000); + } +} +#endif + +int main(void) +{ + bl_clock_config(); + bl_timer_init(); + bl_gpio_init(); + bl_led_init(); +#if defined(GATE_DRIVER_OFF_PORT) && (defined(GATE_DRIVER_OFF_PIN) || defined(GATE_DRIVER_OFF_PIN_NUM)) + /* DRV8350 ENABLE / DRV8328 nSLEEP — keep gate driver asleep in BL */ + bl_gate_driver_off(); +#endif + +#ifdef BOOTLOADER_TEST_CLOCK + test_clock(); +#endif +#ifdef BOOTLOADER_TEST_STRING + test_string(); +#endif +#ifdef BOOTLOADER_TEST_BKUP + test_rtc_backup(); +#endif + +#if DRONECAN_SUPPORT + /* + If the signal pin is driven (e.g. DShot), tell DroneCAN so boot_ok() accepts + the jump. Must run before checkForSignal(), whose float+low path calls jump() + unconditionally - otherwise jump() is rejected with have_raw_command false + and a DShot-only boot bounces. + + Prefer detect_fast_input_signal() so bidirectional DShot (idle high) is + recognised; fall back to a coarse "any low within ~5ms" sample for a + line held low without fast edges. + */ + { + gpio_mode_set_input(input_pin, GPIO_PULL_UP); + delayMicroseconds(500); + bool has_pin_signal = detect_fast_input_signal(); + if (!has_pin_signal) { + for (int i = 0; i < 500; i++) { + if (!gpio_read(input_pin)) { + has_pin_signal = true; + break; + } + delayMicroseconds(10); + } + } + if (has_pin_signal) { + DroneCAN_set_have_signal(); + } + } + + // bring CAN RX live before checkForSignal() so the no-CAN fallback window + // (NONCAN_FALLBACK_MS) observes the bus from boot. Return value ignored: it + // is false this early (no raw command, deadline not yet reached). + (void)DroneCAN_update(); +#endif + + checkForSignal(); + + gpio_mode_set_input(input_pin, GPIO_PULL_UP); + +#ifdef USE_ADC_INPUT // go right to application + jump(); +#endif + +#ifdef UPDATE_EEPROM_ENABLE + update_EEPROM(); +#endif + + while (1) { + receiveBuffer(); + + if (invalid_command > 100) { + jump(); + } + + /* + the flight controller may only start driving the pin after we + have finished checkForSignal(), which is common with + bidirectional DShot as that leaves the line idle high while the + flight controller boots. Once we start failing to decode bytes, + check whether that is because a fast signal has appeared. + + Gated a little above zero so a single bad CRC in an otherwise + healthy configurator session does not spend 2ms staring at the + pin, which would drop the bytes arriving during that window and + turn one error into several. A real flight controller drives the + pin continuously and trips this within a few bytes regardless. + */ + /* + Skip the multi-ms DShot sample while a 4-way serial client is + active - same reason we pause DroneCAN: staring at the pin would + drop bytes mid-transfer. + */ + if (!bl_serial_active && invalid_command > 2 && detect_fast_input_signal()) { + jump(); + } +#if DRONECAN_SUPPORT + if (!bl_serial_active && DroneCAN_update()) { + jump(); + } +#endif + } +} diff --git a/doc/g431-comp-blanking.md b/doc/g431-comp-blanking.md new file mode 100644 index 000000000..4b7dc624a --- /dev/null +++ b/doc/g431-comp-blanking.md @@ -0,0 +1,116 @@ +# G431 comparator output blanking + +Design notes for **STM32G431/G491** (`ARK_G431_CAN`) hardware COMP blanking +used to reject PWM turn-on switching noise on the BEMF zero-cross path. + +| | | +|--|--| +| Targets | `ARK_G431_CAN` (and any G4 target that defines `COMP_BLANK_TICKS`) | +| Knobs | `COMP_BLANK_TICKS` (TIM1 OC5 window), `ZC_SEARCH_BLANK_64THS` (software post-commutation blank) in [`Inc/targets.h`](../Inc/targets.h) | +| Code | [`Mcu/g431/Src/comparator.c`](../Mcu/g431/Src/comparator.c) (`changeCompInput`), [`Mcu/g431/Src/peripherals.c`](../Mcu/g431/Src/peripherals.c) (TIM1 CH5), EXTI path in [`Mcu/g431/Src/stm32g4xx_it.c`](../Mcu/g431/Src/stm32g4xx_it.c) | + +## Ideal blanking vs ST blanking + +Two different meanings of “blank the comparator during the PWM edge” matter +for motor control: + +| Model | Behavior while blank is active | Effect on EXTI (edge-triggered) | +|--------|--------------------------------|----------------------------------| +| **Hold-last (ideal for BEMF ZC)** | Output stays at its **previous** level until the blank ends | No manufactured edge; a real crossing is either delayed cleanly to unblank or never seen until the line actually changes after blank | +| **Force-low (STM32G4 COMP)** | Output is forced **low** for the blank window | Only transparent if the line was already low; if it was high, **enabling blank creates a falling edge** | + +Alka (AM32) summarized the motor-control preference clearly: blanking that +**stops the output from changing** for the blank period (hold last state) is +what you want for ZC. ST Micro implemented the other thing — force the gated +output low while the blanking timer source is active. + +On G4 this is not a configurable mode. The COMP block’s blanking source only +implements force-low. There is no hardware “sample-and-hold the last COMP +output until the PWM blank ends” path. + +Also: `COMPx_CSR.VALUE` / `LL_COMP_ReadOutputLevel()` / `getCompOutputLevel()` +are taken **before** polarity and blanking are applied (see ST LL headers). +Firmware confirm loops that read CSR.VALUE therefore see the **true analog +decision**, while EXTI is wired to the **gated** output. False edges from +force-low can still be rejected in software; real crossings destroyed by +force-low never appear as edges at all (they go blind instead). + +## Why force-low is polarity-asymmetric for AM32 ZC + +AM32 arms EXTI for the **next** expected BEMF polarity each step +(`changeCompInput()` / `rising`): + +| Step polarity | Pre-crossing COMP level | Awaited EXTI edge | Force-low blanking | +|---------------|-------------------------|-------------------|--------------------| +| Falling BEMF (`rising == 0`) | Low | Rising | **Safe.** Force-low is a no-op before the cross. Noise cannot invent a rising edge during the window. A true cross inside the window becomes a **late** rising edge when blank ends (at most `COMP_BLANK_TICKS` late). | +| Rising BEMF (`rising == 1`) | High | Falling | **Hostile.** Force-low **manufactures** a falling edge at every blank open (once per PWM period for the whole ZC search). A real cross **inside** the window can be **destroyed**: output is already low; when blank ends the raw level is also low → no edge → blind step. | + +So ST blanking only works cleanly when you are trying to blank a **rising** +event (pre-cross low). That matches Alka’s statement: *“it only works if you +are trying to blank a rising event.”* + +Manufactured falling edges on rising-BEMF steps tend to show up as a **PWM-rate +ISR + confirm-reject storm** (confirm still reads ungated CSR.VALUE and rejects +the false edge). Destroyed real crossings are silent and show up as blind +steps / open-loop residency, not as rejects. + +## What ARK32 does + +1. **TIM1 CH5 (pinless) → OC5REF** as the COMP blanking source. PWM1 with + `CCR5 = COMP_BLANK_TICKS` holds OC5REF high for the first N ticks of every + PWM period (see peripherals init). +2. **Window sizing:** default `COMP_BLANK_TICKS = 160` (1.0 µs @ 160 MHz TIM1). + Complementary PWM turns the high side on one dead-time after `CNT == 0`, so + the hard switching transient is near **dead time**, not at zero. The window + is sized past that edge and still clears the turn-on pile-up peak used by + bemf_zc compensation (see comments on `COMP_BLANK_TICKS` in `targets.h`). +3. **Arm blanking only on falling-BEMF steps** in `changeCompInput()`: + - `rising == 0` → `LL_COMP_BLANKINGSRC_TIM1_OC5_COMPx` + - `rising == 1` → `LL_COMP_BLANKINGSRC_NONE` +4. **Software post-commutation blank** (`ZC_SEARCH_BLANK_64THS`, default 32 = + half interval) still discards freewheel demag edges in the IRQ regardless + of hardware blanking. That is an ignore-window, not hold-last, but it is the + main remaining time-domain noise axis on G4 (no COMP power/speed mode). + +Do **not** latch blanking on for all six steps at init. That was tried and is +wrong on rising-BEMF for the reasons above. + +## What we are *not* doing (yet) + +| Idea | Status | +|------|--------| +| Hardware hold-last blanking | **Unavailable** on G4 COMP | +| Invert POLARITY on rising-BEMF so pre-cross is low, then blank all six steps | Speculative: only correct if polarity is applied **before** the blank gate. RM0440 §24.3 does not state the order; needs a scope on a redirected COMP output before trusting it | +| Software EXTI mask / “ignore until CNT > N” as synthetic hold | Possible refinement; partially covered by `ZC_SEARCH_BLANK_*` and confirm | +| Longer software blank at low RPM | Sweepable via `ZC_SEARCH_BLANK_64THS`; hard ceiling near ~40/64 before eating the expected ZC (see `targets.h`) | + +## Bench checks + +Useful SWD peeks while free-running (app at `0x08004000`): + +| Register | Expectation | +|----------|-------------| +| `TIM1_CCR5` (`0x40012C48`) | `COMP_BLANK_TICKS` (160) | +| `TIM1_CCMR3` OC5M | PWM1 mode | +| `COMPx_CSR` BLANKSEL `[21:19]` while spinning | Toggles between **0** (NONE) and **1** (TIM1 OC5) on successive steps | +| BLANKSEL at idle / after stop | Dominantly **0** | + +Functional free-run checks (no prop): low-throttle startup matrix should show +short open-loop residency and no stuck latch once blanking is correctly +polarity-armed; full free-run smoke to high duty should stay in closed loop +without bemf stuck latch. + +**Flight Stand note:** high free-run RPM (e.g. 1980 kV @ ~6S) can exceed a +stand optical **max RPM user cutoff** (historically 45k on the ARK bench) and +latch the stand even when the ESC is healthy. Raise that cutoff for no-prop +characterization, or gate optical cutoffs when using ESC eRPM over SWD. + +## References + +- ST: RM0440 (STM32G4) comparator chapter — blanking source, output path; + LL `stm32g4xx_ll_comp.h` (`LL_COMP_ReadOutputLevel` notes VALUE before + polarity/blanking). +- AM32 / Alka: design preference for hold-last blanking; observation that ST + force-low only blanks rising events cleanly when EXTI is edge-triggered. +- ARK commits on this tree: TIM1 OC5 blank enable; per-step arming + 1.0 µs + window; sweepable `ZC_SEARCH_BLANK_64THS`. diff --git a/factory/ARK_G431_CAN_eeprom_defaults.json b/factory/ARK_G431_CAN_eeprom_defaults.json new file mode 100644 index 000000000..a91af3588 --- /dev/null +++ b/factory/ARK_G431_CAN_eeprom_defaults.json @@ -0,0 +1,66 @@ +{ + "_comment": "ARK G431 12S CAN production EEPROM defaults. Same ARK vehicle policy as ARK_4IN1_F051 (2%/ms ramp, 1020 kV, 15° fixed, PWM-by-RPM) with input_type=AUTO_IN (detect DShot/PWM; DroneCAN prioritised while RawCommand is live). Encoded by scripts/build_factory_image.py into the EEprom_t page (Inc/eeprom.h).", + "product": "ARK_G431_CAN", + "eeprom_version": 3, + "flash_map": { + "_comment": "STM32G431 CAN map (Mcu/g431/ldscript_CAN.ld): 16 KiB BL, app @ 0x08004000, 2 KiB EEPROM @ 0x0801F800, 128 KiB total flash", + "flash_size": 131072, + "bl_offset": 0, + "bl_region_size": 16384, + "app_offset": 16384, + "eeprom_offset": 129024, + "eeprom_page_size": 2048 + }, + "firmware_version": { + "major": "from_version_h", + "minor": "from_version_h" + }, + "settings": { + "max_ramp_percent_per_ms": 0.5, + "max_ramp_meaning": "Throttle slew ceiling, both directions, applied to duty by the 20 kHz ramp limiter. 0.5 %/ms = full scale in 200 ms, matching what larger 12S ESCs ship (APD/Hargrave default 50 % per 100 ms). The ARK 4IN1 stays at 2.0. Stored as percent*10, so 5 - under 10 the firmware switches to fine mode (ramp_divider 9, steps every 500 us) and applies the value to ALL THREE regimes including startup, rather than min-ing against the targets.h ceilings.", + "motor_kv": 1020, + "motor_poles": 14, + "variable_pwm": 1, + "variable_pwm_meaning": "1 = PWM frequency scaled by RPM (configurator: PWM by RPM)", + "advance_degrees": 15, + "auto_advance": 0, + "auto_advance_meaning": "0 = fixed advance_level (configurator: fixed timing)", + "pwm_input_min_us": 1020, + "pwm_input_max_us": 1980, + "pwm_input_neutral_us": 1500, + "pwm_input_deadband": 50, + "minimum_duty_cycle": 4, + "comp_pwm": 1, + "stuck_rotor_protection": 1, + "pwm_frequency_khz": 24, + "startup_power": 100, + "beep_volume": 5, + "input_type": 0, + "input_type_meaning": "0 = AUTO_IN — first wire protocol detected; DroneCAN wins while RawCommand is live (Inc/common.h). Use 5=DRONECAN_IN to disable DShot/PWM IRQs.", + "dir_reversed": 0, + "bi_direction": 0, + "use_sine_start": 0, + "brake_on_stop": 0, + "stall_protection": 0, + "telemetry_on_interval": 0, + "low_voltage_cut_off": 0, + "low_cell_volt_cutoff_offset": 50, + "sine_mode_changeover_throttle_level": 15, + "drag_brake_strength": 10, + "driving_brake_strength": 10, + "temperature_limit": 105, + "temperature_limit_meaning": "foldback ONSET in C, read from the MCU die sensor. Duty ceiling falls linearly from full authority at 105 C to 10% at 105+temperature_derate_band (runtimeThermalLimitTick) - a soft derate, never a cut. Must be 70..140 or settings.c disables it; 141/102 (the AM32 configurator skeleton) is what 'off' looked like. NOTE APD/T-Motor's comparable ~110 C is an NTC on the power stage; this is the die, and the die-to-FET delta on this board is not yet benched.", + "temperature_derate_band": 15, + "temperature_derate_band_meaning": "C from onset to full derate (eeprom byte 184, DroneCAN TEMP_DERATE_BAND, range 5..40). 15 puts full derate at 120 C: past the G4 sensor's 110 C upper calibration point by only 10 C, and below the part's 125 C junction limit.", + "current_limit": 100, + "current_limit_meaning": "stored in 2 A steps -> 200 A. Highest value settings.c arms, ~80% of the 250 A shunt rating: a backstop under the DRV8350 VDS trip, not a flight limiter.", + "sine_mode_power": 6, + "absolute_voltage_cutoff": 10, + "current_P": 100, + "current_I": 0, + "current_D": 50, + "current_pid_meaning": "Kp = current_P*2, Kd = current_D*2, error in centiamps. Do NOT lower these: the ceiling moves by doPidCalculations()/10000 duty units per 1 kHz tick, and that integer divide is a dead zone - the loop does nothing until the PID output exceeds 10000, i.e. until the overshoot exceeds 5000/Kp centiamps. Kp 200 resolves ~0.5 A; Kp 10 would need 10 A of overshoot before the ceiling moved at all. SITL heavy_13inch, 8 A limit: P=100 regulates to 7.7 A, P=50 to 7.1 A, P=25 to 6.5 A, P=5 to 14.7 A (does not hold the limit). Keep I=0: use_current_limit_adjust is itself the integrator.", + "active_brake_power": 2, + "disable_stick_calibration": 0 + } +} diff --git a/factory/README.md b/factory/README.md index 0abaf6a7a..210903ac8 100644 --- a/factory/README.md +++ b/factory/README.md @@ -1,6 +1,6 @@ # Factory / production flash images -Production programming of **ARK 4IN1** ESCs used to be a multi-step bench flow: +Production programming of ARK ESCs used to be a multi-step bench flow: 1. Flash the bootloader (ST-Link) 2. Flash the application @@ -8,14 +8,17 @@ Production programming of **ARK 4IN1** ESCs used to be a multi-step bench flow: 4. Hand-tune settings (kV, ramp, timing, PWM endpoints) 5. Dump the whole flash and ship that binary on the line -That dump is now built from this repo. +That dump is now built from this repo for **ARK 4IN1 (F051)** and **ARK G431 12S CAN**. ## One command From the repository root (pinned Arm GCC required, same as any firmware build): ```bash -make factory-image +make factory-image # both products +make factory-image-f051 # ARK_4IN1_F051 only +make factory-image-g431-can # ARK_G431_CAN only +make factory-image-check # both + layout/defaults gate (CI) ``` Artifacts under `obj/`: @@ -23,23 +26,25 @@ Artifacts under `obj/`: | File | Contents | |------|----------| | `ARK32_ARK_4IN1_F051_.bin` / `.hex` | Application only (existing make target) | -| `ARK32_ARK_4IN1_F051_.factory.bin` | **Full 32 KiB flash** — BL + app + EEPROM | -| `ARK32_ARK_4IN1_F051_.factory.hex` | Same image as Intel HEX | -| `ARK32_ARK_4IN1_F051_.eeprom.bin` | 1 KiB EEPROM page alone (debug / SWD poke) | +| `ARK32_ARK_4IN1_F051_.factory.img` | **Full 32 KiB flash** — BL + app + EEPROM | +| `ARK32_ARK_G431_CAN_.factory.img` | **Full 128 KiB flash** — BL region + app + EEPROM | +| `*.factory.hex` / `*.eeprom.bin` | HEX of full image / EEPROM page alone | -Flash the `.factory.bin` (or `.factory.hex`) at **`0x08000000`** with ST-Link / OpenOCD / production fixture. No configurator step is required for stock defaults. +Flash the `.factory.img` (or `.factory.hex`) at **`0x08000000`**. No configurator step is required for stock defaults. The extension is **`.img` on purpose**: PX4's SD-card updater globs every `*.bin` on the card root and scans the **whole file** for an APDescriptor, so a G431 factory image named `.bin` would be copied to `/ufw/71.bin` and written at the app base (off the end of flash). -Example OpenOCD (ST-Link + STM32F0): +Example OpenOCD (ST-Link + STM32F0 for 4IN1): ```bash openocd -f interface/stlink.cfg -f target/stm32f0x.cfg \ -c "init; halt; flash erase_sector 0 0 last; \ - flash write_bank 0 obj/ARK32_ARK_4IN1_F051_3.0.3.factory.bin 0; \ - flash verify_bank 0 obj/ARK32_ARK_4IN1_F051_3.0.3.factory.bin 0; \ + flash write_bank 0 obj/ARK32_ARK_4IN1_F051_3.0.3.factory.img 0; \ + flash verify_bank 0 obj/ARK32_ARK_4IN1_F051_3.0.3.factory.img 0; \ reset run; exit" ``` -## Flash map (STM32F051, 32 KiB) +## Flash maps + +### STM32F051 ARK 4IN1 (32 KiB) | Region | Address | Size | Source | |--------|---------|------|--------| @@ -47,20 +52,78 @@ openocd -f interface/stlink.cfg -f target/stm32f0x.cfg \ | Application | `0x08001000` | 27 KiB | `make ARK_4IN1_F051` app `.bin` (0xFF padded) | | EEPROM | `0x08007C00` | 1 KiB | Defaults from [`ARK_4IN1_F051_eeprom_defaults.json`](ARK_4IN1_F051_eeprom_defaults.json) | -## Production EEPROM defaults - -Defined in [`ARK_4IN1_F051_eeprom_defaults.json`](ARK_4IN1_F051_eeprom_defaults.json) (human units). Current ARK 4IN1 ship settings: +### STM32G431 ARK 12S CAN (128 KiB) -| Setting | Value | -|---------|-------| -| Variable PWM | **PWM by RPM** (`variable_pwm = 1`) | -| Motor kV | **1020** | -| Motor poles | 14 | -| Throttle ramp | **2 %/ms** (`max_ramp = 20`) | -| Timing advance | **15° fixed** (`advance_level = 26`, `auto_advance = 0`) | -| PWM input min | **1020 µs** | -| PWM input max | **1980 µs** | -| Input type | DShot (servo endpoints still stored for PWM mode) | +| Region | Address | Size | Source | +|--------|---------|------|--------| +| Bootloader | `0x08000000` | 16 KiB | `Bootloaders/AM32_G431_BOOTLOADER_ARKG4_CAN_V18.bin` (16 KiB region, file ~14 KiB + 0xFF pad) | +| Application | `0x08004000` | ~111.5 KiB | `make ARK_G431_CAN` (`ldscript_CAN.ld`) | +| EEPROM | `0x0801F800` | 2 KiB | [`ARK_G431_CAN_eeprom_defaults.json`](ARK_G431_CAN_eeprom_defaults.json) | + +## Production EEPROM defaults (shared ARK vehicle policy) + +Both products share the timing/kV policy; the throttle ramp and the +protection limits differ by product: + +| Setting | ARK 4IN1 F051 | ARK G431 CAN | +|---------|---------------|--------------| +| Variable PWM | PWM by RPM | PWM by RPM | +| Motor kV | 1020 | 1020 | +| Throttle ramp | **2 %/ms** | **0.5 %/ms** (200 ms full scale) | +| Timing advance | **15° fixed** | **15° fixed** | +| PWM input min/max | 1020 / 1980 µs | 1020 / 1980 µs | +| Input type | DShot (`1`) | **DroneCAN** (`5`) | +| Thermal derate | off (`141`) | **105 → 120 °C die** | +| Derate band | — (15 °C default) | **15 °C** (byte 184) | +| Current limit | off (`102`) | **200 A** (stored `100`, 2 A/count) | +| Current PID (P/I/D) | 100 / 0 / 50 | 100 / 0 / 50 | + +The 12S CAN board ramps at **0.5 %/ms** (full scale in 200 ms), matching what +larger 12S ESCs ship — APD and Hargrave default to 50 % per 100 ms — rather +than the 4IN1's 2.0 %/ms. Two consequences worth knowing: + +- Below 10 the stored byte selects the firmware's *fine* mode + (`ramp_divider 9`, a step every 500 µs) and applies the rate to **all three + regimes including startup**, instead of min-ing against the `RAMP_SPEED_*` + ceilings in `targets.h`. So spool-up slews at 0.5 %/ms too; that is the part + to watch on the bench. +- It is a rate limit, not a bandwidth limit: only commands large enough to hit + the limit are slewed, so ordinary attitude corrections are untouched while a + full-stick step takes 200 ms. + +The two protection limits are the one place the products differ on policy. +Both are stored as a single byte whose *armed* range is narrow — 70..140 °C +and 1..100 (2..200 A), per `Src/settings.c` — and the AM32 configurator +skeleton both JSONs were seeded from carries 141/102, which sit just outside +those ranges and therefore mean OFF. The G431 CAN board arms both: + +- **Thermal**: a soft foldback, never a cutoff. The duty ceiling falls + linearly from full authority at the onset (`temperature_limit`) to 10% + one band later (`temperature_derate_band`, DroneCAN `TEMP_DERATE_BAND`, + 5..40 °C), off a 64 ms-filtered die temperature. 105 → 120 °C by default. +- **Current**: a 200 A backstop below the DRV8350 VDS trip, ~80% of the + shunt rating. It regulates 50 ms-averaged current, so it protects against + sustained draw, not against a short. +- The two compose by `min()` in `setInput()` and both only ever lower the + ceiling; the 20 kHz ramp limiter slews applied duty toward it either way. +- **Do not lower `current_P`.** The ceiling moves by `pid_output / 10000` + duty units per tick and that integer divide is a dead zone: the loop is + inert until the overshoot exceeds `5000/Kp` centiamps. At the shipped + Kp 200 that is ~0.5 A of resolution. Measured in SITL on `heavy_13inch` + with an 8 A limit: P=100 holds 7.7 A, P=50 7.1 A, P=25 6.5 A, and P=5 + fails outright at 14.7 A. + +The 105 °C onset follows professional 12S practice (APD and industrial +T-Motor fold back around 110 °C), with one caveat to settle on the bench: +**those vendors read an NTC on the power stage, this reads the MCU die.** +The G4 die sensor is factory-calibrated at 30 °C and 110 °C, so the default +band keeps almost the whole ramp inside the calibrated span and puts full +derate below the part's 125 °C junction limit — but if the die-to-FET delta +on this board turns out large, the onset belongs lower. + +The 4IN1 stays off: its shunt is shared by all four ESCs, so per-motor +current limiting is not meaningful there, and its thermal placement has not +been benched. Encodings follow `Inc/eeprom.h` and `Src/settings.c`. What each setting does, including the ones not listed above, is in [`doc/eeprom-settings.md`](../doc/eeprom-settings.md). Rebuild after editing the JSON: @@ -70,12 +133,13 @@ make factory-image ## Field updates vs factory image -- **Production / blank chip:** flash the `.factory.bin` once. -- **In-field app update:** flash the normal app `.bin` / `.hex` at `0x08001000` (or use the configurator / BL one-wire path). EEPROM settings on the chip are left alone. F051 builds also embed the bootloader for optional app-side BL refresh (`EMBED_BOOTLOADER`, see [Bootloaders/README.md](../Bootloaders/README.md)). +- **Production / blank chip:** flash the `.factory.img` (or `.factory.hex`) once (includes BL + app + EEPROM for both products). Never put this file on a PX4 SD card. +- **In-field app update:** flash the normal app `.bin` / `.hex` at the app base (F051 `0x08001000`, G431 CAN `0x08004000`). EEPROM is left alone. Both products embed the bootloader and rewrite the on-chip BL if it differs (`EMBED_BOOTLOADER`, see [Bootloaders/README.md](../Bootloaders/README.md)). G431 CAN always embeds (including HWCI), so a manual or PX4 app flash still lands the 0.71 BL. +- **PX4 SD-card update (ARK G431 CAN):** `make ARK_G431_CAN` (or `make factory-image-g431-can`) also writes a PX4-signed copy named `-..uavcan.bin` under `obj/` — e.g. `71-3.0.3.59efc137.uavcan.bin`. Copy **only** that file to the **root of the flight-controller SD card**. Staging to `/ufw/71.bin` is not the flash: PX4 only sends `BeginFirmwareUpdate` if GetNodeInfo `image_crc` differs, the app drops the request unless idle, and the bootloader does not scan the SD card — an assigned motor's `RawCommand` boots the app before PX4 asks. Unassign the ESC in QGC Actuators, reboot the FC, keep the vehicle disarmed until the node is `OPERATIONAL` with the new CRC, then re-assign. Details: [README.md](../README.md#px4-sd-card-update-ark-12s-can). ## Script -[`scripts/build_factory_image.py`](../scripts/build_factory_image.py) — pure Python 3, no third-party deps. Invoked by `make factory-image`. +[`scripts/build_factory_image.py`](../scripts/build_factory_image.py) — pure Python 3. Layout comes from `flash_map` in the product JSON (F051 defaults if omitted). ## CI @@ -85,4 +149,4 @@ Every PR/push runs **`factory-image`** in [`.github/workflows/static-analysis.ym make factory-image-check # build + scripts/check-factory-image-ark.sh ``` -The job fails if the 32 KiB layout is wrong or the EEPROM page drifts from `ARK_4IN1_F051_eeprom_defaults.json`. Artifacts (`*.factory.bin` / `.hex` / `.eeprom.bin`) are uploaded as `ark-4in1-factory-image`. +The job fails if the flash layout is wrong, the EEPROM page drifts from the product defaults JSON (F051 or G431), the G431 PX4 SD-card `.uavcan.bin` is missing / unsigned, or the app `.hex` does not cover every byte of the app `.bin`. Artifacts (`*.factory.img` / `.factory.hex` / `.eeprom.bin` / `*.uavcan.bin`) are uploaded as `ark-factory-images`. diff --git a/g431makefile.mk b/g431makefile.mk index ad156cc8a..546b7a245 100644 --- a/g431makefile.mk +++ b/g431makefile.mk @@ -36,7 +36,8 @@ SRC_$(MCU) := $(foreach dir,$(SRC_DIR_$(MCU)),$(wildcard $(dir)/*.[cs])) CFLAGS_CAN_$(MCU) = \ -ISrc/DroneCAN \ -ISrc/DroneCAN/libcanard \ - -ISrc/DroneCAN/dsdl_generated/include + -ISrc/DroneCAN/dsdl_generated/include \ + -DCANARD_ENABLE_CANFD=1 SRC_DIR_CAN_$(MCU) = Src/DroneCAN \ Src/DroneCAN/dsdl_generated/src \ diff --git a/hwci/README.md b/hwci/README.md index 6486d6c0e..0f1a4f453 100644 --- a/hwci/README.md +++ b/hwci/README.md @@ -1,16 +1,19 @@ -# AM32 Hardware-CI Harness (ARK 4IN1 ESC) +# AM32 Hardware-CI Harness (ARK 4IN1 + ARK 12S CAN ESC) -A hardware-in-the-loop test harness for improving AM32 on the **ARK 4IN1 ESC**. -It builds and flashes firmware, drives a motor through a **Tyto Robotics Flight -Stand 50**, and at the same time reads firmware **CPU-load / loop-time** -instrumentation off the MCU over **SWD**, then turns it all into metrics, a -baseline, and a pass/fail report you can gate pull requests on. +A hardware-in-the-loop test harness for improving AM32 on **ARK ESC** products +(4IN1 F051 and **12S CAN G431/G491**). It builds and flashes firmware, drives a +motor through a **Tyto Robotics Flight Stand 50**, and at the same time reads +firmware **CPU-load / loop-time** instrumentation off the MCU over **SWD**, then +turns it all into metrics, a baseline, and a pass/fail report you can gate pull +requests on. ``` ┌───────────────────────── Ubuntu 24.04 host ─────────────────────────┐ │ │ ST-Link ◄─SWD─┤ OpenOCD ──background RAM reads──► hwci_perf struct (loop µs, iters) │ │ │ + ST-Link VCP ◄──┤ G4 debug UART (PB3 @ 115200) ── state / nFAULT / desync text │ + │ │ USB-serial ◄───┤ KISS telemetry reader ◄──────── ESC telem wire (eRPM, V, A, °C) │ │ │ Flight Stand◄──┤ gRPC client ──throttle──► ESC signal ──measures──► thrust/torque/│ @@ -18,7 +21,7 @@ baseline, and a pass/fail report you can gate pull requests on. │ │ │ runner → metrics → baseline compare → report.md + plots + exit code │ └───────────────────────────────────────────────────────────────────────┘ - ARK 4IN1 ESC (4× STM32F051 / Cortex-M0) ── motor ── prop ── Flight Stand 50 + ARK 4IN1 (F051) or ARK 12S CAN ESC (G431/G491) ── motor ── prop ── Flight Stand 50 ``` ## Two exclusive bench setups @@ -27,20 +30,23 @@ baseline, and a pass/fail report you can gate pull requests on. |--|----------------------------|------------------------------| | ESC signal | Stand ESC out (uni DShot) | FPV motor out (BDShot) | | Motor command | `hwci run --profile noprop_…` | `scripts/px4_motor_stream.py` | -| Rig file | `rig.yaml` / `config/rig.flightstand.yaml` | `config/rig.px4_bdshot.yaml` | +| Rig file | `rig.yaml` / `config/rig.g431_can.yaml` / `config/rig.flightstand.yaml` | `config/rig.px4_bdshot.yaml` | | Docs | this README | [docs/BENCH_SETUPS.md](docs/BENCH_SETUPS.md), [docs/setup_px4_bdshot.md](docs/setup_px4_bdshot.md) | Use **one setup at a time** (only one host on the signal wire). Full table and -switch procedure: **[docs/BENCH_SETUPS.md](docs/BENCH_SETUPS.md)**. +switch procedure: **[docs/BENCH_SETUPS.md](docs/BENCH_SETUPS.md)** (includes +**F051 vs G4** OpenOCD/app-base and debug-UART wiring). ## Why it's built this way (read this first) -The ARK 4IN1 runs **four independent STM32F051 MCUs** (one per channel), each a -**Cortex-M0 (ARMv6-M)**. The M0 has **no DWT cycle counter, no ITM, and no SWO** -— so the usual "profile over SWO/ITM trace" approach is **impossible on any -debug probe**, ST-Link or J-Link alike. +The original ARK 4IN1 runs **four independent STM32F051 MCUs** (one per channel), +each a **Cortex-M0 (ARMv6-M)**. The M0 has **no DWT cycle counter, no ITM, and no +SWO** — so the usual "profile over SWO/ITM trace" approach is **impossible on +that part**. The **ARK 12S CAN ESC (G431/G491, Cortex-M4)** *does* have DWT/ITM, +but the harness still uses the same non-halting `hwci_perf` RAM poll so both +products share one code path (with a higher SWD clock and correct app base on G4). -So CPU load and loop times are recovered a different way: +CPU load and loop times are recovered this way on both MCUs: 1. The firmware keeps a tiny instrumentation struct (`hwci_perf`) in RAM, updated from the 20 kHz control loop and the main loop using the existing @@ -86,15 +92,18 @@ make ARK_4IN1_F051 HWCI_PERF=1 Files: `Inc/hwci_perf.h`, `Src/hwci_perf.c`, three hooks in `Src/main.c` (`tenKhzRoutine` enter/exit, `while(1)` top), one `Makefile` flag. -## Embedded bootloader (F051) +## Embedded bootloader + +**F051 release** and **every G431 CAN** build (including `HWCI_PERF=1`) embed +the ARK32-bootloader image. At boot the app compares the on-chip BL region to +the embedded image and rewrites it if they differ (`Src/bootloader_update.c`). -F051 builds **embed the ARK32-bootloader image by default**, including -`HWCI_PERF=1` (LTO leaves room for both). At boot the app compares the on-chip -BL region to the embedded image and rewrites it if they differ -(`Src/bootloader_update.c`). +F051 `HWCI_PERF=1` does **not** embed the BL (flash is reserved for the perf +struct; the rig already has a bootloader). G431 CAN always embeds — a 16 KiB +blob fits, and a bench/PX4 app flash must still be able to refresh the BL. -**After a BL version bump** (new `.bin` in `Bootloaders/`, or a board still on -an older BL): +**After a BL version bump** on a **release** image (new `.bin` in +`Bootloaders/`, or a board still on an older BL): 1. First app boot may erase/program `0x08000000`, soft-reset, and play the short rising **BL-updated** chirp before the normal startup tune. @@ -102,12 +111,11 @@ an older BL): or re-run the profile is fine once the on-chip BL matches. 3. Later boots are a no-op `memcmp` (no rewrite thrash). -To strip the embed for size emergencies or pure perf A/Bs (no app-side BL -update in that binary): +To strip the embed: ``` -make ARK_4IN1_F051 HWCI_PERF=1 NO_EMBED_BL=1 -# or: EMBED_BOOTLOADER=0 +make ARK_4IN1_F051 EMBED_BOOTLOADER=0 +make ARK_G431_CAN NO_EMBED_BL=1 ``` Details: [Bootloaders/README.md](../Bootloaders/README.md). @@ -204,9 +212,10 @@ python -m pytest # 75 offline tests ```bash hwci profiles # list test profiles -hwci build --config rig.yaml # make ARK_4IN1_F051 HWCI_PERF=1 -hwci flash --config rig.yaml # OpenOCD program @ 0x08001000 -hwci run --profile efficiency_sweep --config rig.yaml --battery-cells 6 --out runs/r1 +hwci build --config rig.yaml # make HWCI_PERF=1 +hwci flash --config rig.yaml # OpenOCD @ app_load_addr (F0: 0x08001000, G4: 0x08004000) +hwci debug-uart --config rig.yaml # G4: tail PB3 console via ST-Link VCP +hwci run --profile noprop_smoke --config rig.yaml --battery-cells 6 --out runs/r1 hwci analyze runs/r1 # -> metrics.json hwci report runs/r1 --baseline baselines/ARK_4IN1_F051.json hwci ci --profile ci_smoke --config rig.yaml --battery-cells 6 \ @@ -385,10 +394,13 @@ hwci/tests/ 75 offline tests (sim, DWARF layout, fail-clo magic, commands zero throttle, resets the MCU, and waits for the app — see `_ensure_app_alive` in `hwci/runner.py`. OpenOCD flash also force-starts the app via the vector table so a stuck BL does not block settings trials. -* **App-side BL update** (default on F051, including `HWCI_PERF=1`): if the - embedded image does not match the on-chip BL, the first boot after flash may - rewrite the BL region, soft-reset, and chirp before the normal startup tune. - Re-run once matched; see [Embedded bootloader](#embedded-bootloader-f051). +* **App-side BL update** (F051 release and every G431 CAN build, including + G431 `HWCI_PERF=1`): if the embedded image does not match the on-chip BL, + the first boot after flash may rewrite the BL region, soft-reset, and chirp + before the normal startup tune. `_ensure_app_alive` polls for 3 s before + the first `reset_run` when the ELF carries `.bl_image`, so that reset + cannot land mid-erase. Re-run once matched; see + [Embedded bootloader](#embedded-bootloader). * `HWCI_PERF` is validated for the STM32F0 (ARK 4IN1). The timestamp macro uses the shared `get_timer_us16()` helper, so STM32/GigaDevice/Artery targets compile with `HWCI_PERF=1`; NXP and WCH are `#error`-gated (no usable diff --git a/hwci/config/rig.example.yaml b/hwci/config/rig.example.yaml index 0dfb3ed21..658609a98 100644 --- a/hwci/config/rig.example.yaml +++ b/hwci/config/rig.example.yaml @@ -8,27 +8,40 @@ # `hwci run/ci --sim` (no rig file needed). # --- firmware / build --- -target: ARK_4IN1_F051 # AM32 build target (FILE_NAME in Inc/targets.h) +# AM32 build target (FILE_NAME in Inc/targets.h): +# ARK_4IN1_F051 — 4-in-1 F051 (4 KiB BL, app @ 0x08001000, stm32f0x) +# ARK_G431_CAN — 12S CAN ESC G431/G491 (16 KiB BL, app @ 0x08004000, stm32g4x) +target: ARK_4IN1_F051 # repo_root: /home/test/AM32 # defaults to the repo this package lives in # obj_dir: /home/test/AM32/obj # elf_path: # auto: newest obj/AM32__*.elf -# app_load_addr: 0x08001000 # default matches Mcu/f051 ldscript (app above bootloader) +# +# app_load_addr / openocd_configs / adapter_speed_khz are filled from +# TARGET_PRESETS for known targets when omitted. Override only if needed: +# app_load_addr: 0x08001000 # F051 +# app_load_addr: 0x08004000 # G431 CAN # --- debug probe (reads CPU load / loop times over SWD) --- debugger_backend: openocd # openocd | none openocd_bin: openocd -# openocd_configs: # default: interface/stlink.cfg + target/stm32f0x.cfg -# - interface/stlink.cfg # (one channel of the ARK 4IN1 10-pin debug header: -# - target/stm32f0x.cfg # wire ST-Link to the SWDIO/SWCLK pair you test) -# openocd_search_dirs: [] # extra -s dirs if using custom cfgs -# For J-Link instead, supply interface/jlink.cfg here (M0 has no SWO, so this is -# still background-memory-read based, not trace). +# openocd_configs: # preset by target if omitted +# - interface/stlink.cfg +# - target/stm32f0x.cfg # F051 +# - target/stm32g4x.cfg # G431/G491 +# adapter_speed_khz: 2000 # F051 typical; G4 preset uses 8000 +# openocd_search_dirs: [] +# For J-Link: supply interface/jlink.cfg (still background memory-read). -# --- ESC telemetry (KISS serial, the ARK target has USE_SERIAL_TELEMETRY) --- +# --- ESC telemetry (KISS serial wire; optional) --- telem_backend: serial # serial | none telem_port: /dev/esc-telem # udev symlink recommended (see scripts/99-hwci.rules) telem_baud: 115200 +# --- G4 debug console (USART2 TX PB3 → ST-Link VCP); ignore on F051 --- +# debug_uart_backend: serial # serial | none (default none) +# debug_uart_port: /dev/esc-debug-uart +# debug_uart_baud: 115200 + # --- throttle source (what drives the ESC signal wire) --- # SETUP A: flightstand (or external serial bridge) # SETUP B: none — ARK FPV / PX4 owns the pin (see docs/BENCH_SETUPS.md) diff --git a/hwci/config/rig.g431_1980kv_noprop.yaml b/hwci/config/rig.g431_1980kv_noprop.yaml new file mode 100644 index 000000000..33ebcd329 --- /dev/null +++ b/hwci/config/rig.g431_1980kv_noprop.yaml @@ -0,0 +1,38 @@ +# ARK 12S CAN ESC + AOS 2207 1980KV free-run (no prop) on Flight Stand SETUP A. +# Optical RPM (stand signal 15) works on this bell — independent mechanical +# truth for demag/RPM-collapse gates. ESC e_rpm over SWD is still on the perf +# channel for desync / estimate cross-checks during BEMF filter tuning. +# hwci run --config config/rig.g431_1980kv_noprop.yaml --profile noprop_zc_map --battery-cells 6 +target: ARK_G431_CAN +debugger_backend: openocd +openocd_bin: openocd +openocd_configs: + - interface/stlink.cfg + - target/stm32g4x.cfg +app_load_addr: 0x08004000 +adapter_speed_khz: 8000 +telem_backend: none +debug_uart_backend: serial +debug_uart_port: /dev/ttyACM0 +debug_uart_baud: 115200 +throttle_backend: flightstand +stand_backend: grpc +stand_host: 192.168.7.22 +stand_port: 50051 +stand_signals: + thrust: 11 + torque: 14 + rpm: 15 + voltage: 6 + current: 8 + esc_output: 0 + esc_zero: 0.0 + esc_min: 48.0 + esc_max: 2047.0 + thrust_is_grams: false + rpm_is_rad_per_s: true + motor_temp: "/boards/COM3/inputs/29" + fet_temp: "/boards/COM3/inputs/31" +motor_name: "AOS Supernova 2207 1980KV" +pole_pairs: 7 +prop: "none" diff --git a/hwci/config/rig.g431_can.yaml b/hwci/config/rig.g431_can.yaml new file mode 100644 index 000000000..fada80094 --- /dev/null +++ b/hwci/config/rig.g431_can.yaml @@ -0,0 +1,64 @@ +# ============================================================================= +# ARK 12S CAN ESC (STM32G431/G491) — Flight Stand SETUP A +# ============================================================================= +# Target: ARK_G431_CAN (USE_DEBUG_UART on USART2 TX PB3 @ 115200) +# +# SWD: ST-Link → SWDIO/SWCLK/GND (do NOT feed 3V3 from ST-Link when bus-powered) +# UART: PB3 debug console → ST-Link V3 VCP (usually /dev/ttyACM0 / if01) +# udev: /dev/esc-debug-uart (scripts/99-hwci.rules) +# Signal: Flight Stand ESC out → ESC signal (uni DShot/PWM); no BDShot parallel +# +# Target presets (auto-applied when omitted): +# openocd: interface/stlink.cfg + target/stm32g4x.cfg +# app_load_addr: 0x08004000 (16 KiB bootloader) +# adapter_speed_khz: 8000 +# +# Copy to ../rig.yaml on the G4 bench, or: +# hwci run --config config/rig.g431_can.yaml --profile noprop_smoke --battery-cells 6 +# ============================================================================= + +target: ARK_G431_CAN + +debugger_backend: openocd +openocd_bin: openocd +# Explicit G4 SWD (also filled by TARGET_PRESETS if omitted): +openocd_configs: + - interface/stlink.cfg + - target/stm32g4x.cfg +app_load_addr: 0x08004000 +adapter_speed_khz: 8000 + +# KISS ESC telemetry wire (optional; leave none if not connected) +telem_backend: none +# telem_port: /dev/esc-telem +# telem_baud: 115200 + +# Debug console via ST-Link VCP (PB3 USART2 TX) — state + nFAULT + desync +debug_uart_backend: serial +debug_uart_port: /dev/esc-debug-uart +debug_uart_baud: 115200 + +throttle_backend: flightstand + +stand_backend: grpc +stand_host: 192.168.7.22 +stand_port: 50051 +stand_signals: + thrust: 11 + torque: 14 + rpm: 15 + voltage: 6 + current: 8 + esc_output: 0 + esc_zero: 0.0 + esc_min: 48.0 + esc_max: 2047.0 + thrust_is_grams: false + rpm_is_rad_per_s: true + motor_temp: "/boards/COM3/inputs/29" + fet_temp: "/boards/COM3/inputs/31" + +# Phase 1 default article: 1800 KV free-run (no prop) +motor_name: "JS Technology 2306 1800KV" +pole_pairs: 7 +prop: "none" diff --git a/hwci/docs/BENCH_SETUPS.md b/hwci/docs/BENCH_SETUPS.md index 398f232d7..7bef68615 100644 --- a/hwci/docs/BENCH_SETUPS.md +++ b/hwci/docs/BENCH_SETUPS.md @@ -10,10 +10,39 @@ only one device may drive the ESC signal wire. | **BDShot replies** | No (stand is not a BDShot master) | Yes (eRPM / EDT on signal wire) | | **Thrust / torque / optical RPM** | Flight Stand gRPC | Optional (stand sensors only; pin disconnected from stand ESC out) | | **SWD `hwci_perf`** | Yes (ST-Link) | Optional (same ST-Link) | -| **Primary rig file** | `rig.yaml` or `config/rig.flightstand.yaml` | `config/rig.px4_bdshot.yaml` | +| **Primary rig file** | `rig.yaml` / `config/rig.g431_can.yaml` / `config/rig.flightstand.yaml` | `config/rig.px4_bdshot.yaml` | | **How to command motor** | `hwci run --profile noprop_…` | `scripts/px4_motor_stream.py` (+ capture) | | **Typical profiles** | `noprop_smoke*`, `efficiency_sweep`, demag, … | `bdshot_smoke` (SWD log only; throttle is PX4) | +## MCU family (F051 vs G4) + +| | **ARK_4IN1_F051** | **ARK_G431_CAN** (12S CAN ESC) | +|--|-------------------|--------------------------------| +| Core | Cortex-M0 | Cortex-M4 (DWT available; harness still uses `hwci_perf` RAM poll) | +| OpenOCD target | `target/stm32f0x.cfg` | `target/stm32g4x.cfg` | +| App load address | `0x08001000` (4 KiB BL) | `0x08004000` (16 KiB BL) | +| Typical SWD clock | 2 MHz | 8 MHz | +| Debug console | none | USART2 TX **PB3** @ 115200 → ST-Link VCP | +| Rig template | `config/rig.flightstand.yaml` | `config/rig.g431_can.yaml` (active `rig.yaml` on G4 bench) | + +Known targets fill `openocd_configs`, `app_load_addr`, and `adapter_speed_khz` +automatically via `TARGET_PRESETS` when those keys are omitted from the YAML. + +### G4 debug UART (ST-Link VCP) + +Firmware (`USE_DEBUG_UART`) prints state transitions and faults on PB3. +Wire PB3 → ST-Link V3 VCP RX (and common GND). Install udev rules so the VCP +appears as `/dev/esc-debug-uart` (`scripts/99-hwci.rules`, interface 01). + +```bash +# Live console (boot banner, esc: A -> B, fault: nFAULT / desync, …) +hwci debug-uart --config rig.yaml + +# Each hardware run with debug_uart_backend: serial also writes +# /debug_uart.log +# and aborts on fault: nFAULT / desync / stuck / stall / acq_desync. +``` + ## SETUP A — Flight Stand throttle (no PX4 / no BDShot) ``` diff --git a/hwci/hwci/build.py b/hwci/hwci/build.py index faad79a9f..db1b986c3 100644 --- a/hwci/hwci/build.py +++ b/hwci/hwci/build.py @@ -13,15 +13,29 @@ class BuildArtifacts: hex: Path +# Sidecar images share the ``ARK32__`` prefix. A glob of ``*.bin`` +# must not pick ``.eeprom.bin`` / ``.uavcan.bin`` (or ``.factory.hex`` for +# ``*.hex``) — flashing those at the app base overwrites vectors. Observed +# 2026-08-14: hwci ci programmed the 2 KiB EEPROM dump at 0x08004000. +_SIDECAR_MARKERS = (".eeprom.", ".factory.", ".uavcan.") + + def find_artifact(obj_dir: Path, target: str, ext: str) -> Path | None: - """Newest ``obj/ARK32__*.`` build artifact, or None. + """Newest ``obj/ARK32__*.`` firmware image, or None. - The single place that knows the Makefile's artifact naming; RigConfig's - ELF resolution reuses it so the flashed binary and the parsed ELF can - never be picked by two different rules. + Ignores factory/EEPROM/UAVCAN sidecars. Newest is by mtime so a rebuild + wins over an older version sitting in ``obj/``. RigConfig ELF resolution + uses this too, so the flashed binary and the parsed ELF cannot diverge. """ - hits = sorted(obj_dir.glob(f"ARK32_{target}_*.{ext}")) - return hits[-1] if hits else None + ext = ext.lstrip(".") + hits = [ + p for p in Path(obj_dir).glob(f"ARK32_{target}_*.{ext}") + if p.is_file() and not any(m in p.name for m in _SIDECAR_MARKERS) + ] + if not hits: + return None + hits.sort(key=lambda p: p.stat().st_mtime) + return hits[-1] def build_firmware(repo_root: str | Path, target: str, *, diff --git a/hwci/hwci/cli.py b/hwci/hwci/cli.py index b48495758..eb7cd86e5 100644 --- a/hwci/hwci/cli.py +++ b/hwci/hwci/cli.py @@ -218,7 +218,7 @@ def cmd_build(args) -> int: def cmd_flash(args) -> int: from .build import build_firmware - from .debugger.openocd import OpenOcdDebugger + from .debugger.factory import openocd_from_rig rig = load_rig(args.config) if args.bin: binf = Path(args.bin) @@ -227,10 +227,10 @@ def cmd_flash(args) -> int: hwci_perf=not args.no_perf, arm_sdk_prefix=args.arm_sdk_prefix) binf = arts.bin - dbg = OpenOcdDebugger(rig.openocd_configs, openocd_bin=rig.openocd_bin, - search_dirs=rig.openocd_search_dirs) + dbg = openocd_from_rig(rig) dbg.flash(str(binf), rig.app_load_addr) - print(f"flashed {binf} @ 0x{rig.app_load_addr:08x}") + print(f"flashed {binf} @ 0x{rig.app_load_addr:08x} " + f"(target {rig.target}, openocd {rig.openocd_configs})") return 0 @@ -245,6 +245,9 @@ def cmd_run(args) -> int: tare=not args.no_tare) out = Path(args.out) if args.out else _default_out(profile.name, sim) result.save(out) + if result.debug_uart_text: + print(f" debug UART log: {out / 'debug_uart.log'} " + f"({result.meta.get('debug_uart_line_count', 0)} lines)") print(f"saved {len(result.rows)} samples to {out}") return _verdict_rc(result, None) @@ -286,9 +289,8 @@ def cmd_ci(args) -> int: arts = build_firmware(rig.repo_root, rig.target, hwci_perf=True, arm_sdk_prefix=args.arm_sdk_prefix) if rig.debugger_backend == "openocd": - from .debugger.openocd import OpenOcdDebugger - dbg = OpenOcdDebugger(rig.openocd_configs, openocd_bin=rig.openocd_bin, - search_dirs=rig.openocd_search_dirs) + from .debugger.factory import openocd_from_rig + dbg = openocd_from_rig(rig) dbg.flash(str(arts.bin), rig.app_load_addr) dbg.close() else: @@ -426,7 +428,7 @@ def cmd_settings(args) -> int: closer = lambda: None # noqa: E731 - kept alive across invocations flash = dbg.flash else: - from .debugger.openocd import OpenOcdDebugger + from .debugger.factory import openocd_from_rig rig = load_rig(args.config) elf = rig.resolved_elf() if elf is None: @@ -434,9 +436,7 @@ def cmd_settings(args) -> int: "eeprom_address is resolved from the flashed ELF", file=sys.stderr) return 2 - make = lambda: OpenOcdDebugger( # noqa: E731 - rig.openocd_configs, openocd_bin=rig.openocd_bin, - search_dirs=rig.openocd_search_dirs) + make = lambda: openocd_from_rig(rig) # noqa: E731 dbg = make().open() closer = dbg.close addr = st.resolve_eeprom_address(dbg, str(elf)) @@ -486,6 +486,46 @@ def flash(bin_path: str, load_addr: int) -> None: closer() +def cmd_debug_uart(args) -> int: + """Live tail of the G4 debug console (state transitions + fault events).""" + import time as time_mod + + from .debug_uart import DebugUartReader, resolve_stlink_vcp + + port = args.port + baud = args.baud or 115200 + if args.config: + rig = load_rig(args.config) + if not port: + port = rig.debug_uart_port + if not args.baud: + baud = rig.debug_uart_baud + resolved = resolve_stlink_vcp(port) if port else resolve_stlink_vcp(None) + if not resolved: + print("error: no ST-Link VCP found; set --port or install udev rule " + "for /dev/esc-debug-uart (see scripts/99-hwci.rules)", + file=sys.stderr) + return 2 + print(f"debug UART on {resolved} @ {baud} (Ctrl-C to stop)", + file=sys.stderr) + reader = DebugUartReader(resolved, baud).open() + t0 = time_mod.monotonic() + seen = 0 + try: + while args.duration <= 0 or (time_mod.monotonic() - t0) < args.duration: + lines = reader.lines() + while seen < len(lines): + rec = lines[seen] + seen += 1 + print(rec.text, flush=True) + time_mod.sleep(0.05) + except KeyboardInterrupt: + pass + finally: + reader.close() + return 0 + + def build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser(prog="hwci", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) @@ -603,6 +643,17 @@ def add_preflight(sp): sp.add_argument("--no-plots", action="store_true") sp.set_defaults(func=cmd_ci) + sp = sub.add_parser( + "debug-uart", + help="tail ARK_G431_CAN debug console (ST-Link VCP / USART2 PB3)") + add_common(sp) + sp.add_argument("--port", help="serial device (default: rig or auto ST-Link VCP)") + sp.add_argument("--baud", type=int, default=0, + help="baud (default: rig debug_uart_baud or 115200)") + sp.add_argument("--duration", type=float, default=0, + help="exit after N seconds (0 = until Ctrl-C)") + sp.set_defaults(func=cmd_debug_uart) + return p diff --git a/hwci/hwci/config.py b/hwci/hwci/config.py index 38c7973e4..5038547e6 100644 --- a/hwci/hwci/config.py +++ b/hwci/hwci/config.py @@ -14,7 +14,8 @@ import yaml from .build import find_artifact -from .debugger.openocd import APP_LOAD_ADDR, DEFAULT_CONFIGS +from .debugger.openocd import (APP_LOAD_ADDR, DEFAULT_CONFIGS, G4_APP_LOAD_ADDR, + G4_CONFIGS) from .flightstand.base import SafetyLimits PROFILES_DIR = Path(__file__).parent / "profiles" @@ -25,6 +26,8 @@ BACKEND_CHOICES: dict[str, set[str]] = { "debugger_backend": {"openocd", "sim", "none"}, "telem_backend": {"serial", "sim", "none"}, + # Text console on ARK_G431_CAN (USART2 TX PB3 → ST-Link VCP). Not KISS. + "debug_uart_backend": {"serial", "sim", "none"}, # "none" = ESC signal driven outside the harness (e.g. ARK FPV BDShot); # use only with setup B — see docs/BENCH_SETUPS.md. "throttle_backend": {"flightstand", "external", "none", "sim"}, @@ -33,6 +36,31 @@ _SIM_ONLY = {"sim"} +@dataclass(frozen=True) +class TargetPreset: + """MCU-family defaults applied when a rig omits openocd / load-address keys.""" + openocd_configs: tuple[str, ...] + app_load_addr: int + adapter_speed_khz: int | None = None + + +# Keys that load_rig() fills from TARGET_PRESETS when absent from the YAML. +_PRESET_FILL_KEYS = ("openocd_configs", "app_load_addr", "adapter_speed_khz") + +TARGET_PRESETS: dict[str, TargetPreset] = { + "ARK_4IN1_F051": TargetPreset( + openocd_configs=tuple(DEFAULT_CONFIGS), + app_load_addr=APP_LOAD_ADDR, + adapter_speed_khz=2000, + ), + "ARK_G431_CAN": TargetPreset( + openocd_configs=tuple(G4_CONFIGS), + app_load_addr=G4_APP_LOAD_ADDR, + # G4 SWD is solid well above F0 rates; 8 MHz cuts perf-poll latency. + adapter_speed_khz=8000, + ), +} + @dataclass class Segment: """One phase of a test profile.""" @@ -188,11 +216,19 @@ class RigConfig: openocd_search_dirs: list[str] = field(default_factory=list) openocd_bin: str = "openocd" app_load_addr: int = APP_LOAD_ADDR + # None = OpenOCD default for the target cfg; set via target preset or YAML. + adapter_speed_khz: int | None = None telem_backend: str = "sim" # "serial" | "none" (| "sim" offline) telem_port: str = "/dev/ttyUSB0" telem_baud: int = 115200 + # ARK_G431_CAN debug console (USART2 TX PB3 @ 115200 via ST-Link VCP). + # Not KISS telemetry — state transitions and fault events as text lines. + debug_uart_backend: str = "none" # "serial" | "none" (| "sim" offline) + debug_uart_port: str = "/dev/esc-debug-uart" + debug_uart_baud: int = 115200 + # flightstand = SETUP A; external = serial bridge; none = SETUP B (PX4/BDShot owns pin) throttle_backend: str = "sim" throttle_port: str = "/dev/ttyACM0" @@ -234,6 +270,26 @@ def validate(self, *, allow_sim_backends: bool = True) -> None: "stand-less bench") +def apply_target_preset(cfg: RigConfig, *, explicit_keys: set[str] | None = None + ) -> RigConfig: + """Fill openocd/app_load/adapter defaults from :data:`TARGET_PRESETS`. + + Only keys *not* listed in ``explicit_keys`` are overwritten, so a rig YAML + can pin any of them. Call with ``explicit_keys=set(yaml)`` after load. + """ + preset = TARGET_PRESETS.get(cfg.target) + if preset is None: + return cfg + explicit = explicit_keys or set() + if "openocd_configs" not in explicit: + cfg.openocd_configs = list(preset.openocd_configs) + if "app_load_addr" not in explicit: + cfg.app_load_addr = preset.app_load_addr + if "adapter_speed_khz" not in explicit: + cfg.adapter_speed_khz = preset.adapter_speed_khz + return cfg + + def load_rig(path: str | None) -> RigConfig: if not path: return RigConfig() @@ -247,6 +303,9 @@ def load_rig(path: str | None) -> RigConfig: cfg = RigConfig() for key, value in data.items(): setattr(cfg, key, value) + # Target-family SWD defaults (G4 app @ 0x08004000, stm32g4x, …) unless + # the YAML pinned those keys explicitly. + apply_target_preset(cfg, explicit_keys=set(data.keys()) & set(_PRESET_FILL_KEYS)) # A rig FILE describes hardware: simulator backends in it are almost # certainly a typo'd or half-edited config, and silently simulating a # "hardware" run is the worst possible failure mode. diff --git a/hwci/hwci/debug_uart.py b/hwci/hwci/debug_uart.py new file mode 100644 index 000000000..56d2cb4e0 --- /dev/null +++ b/hwci/hwci/debug_uart.py @@ -0,0 +1,168 @@ +"""ARK_G431_CAN debug UART (USART2 TX PB3 @ 115200 via ST-Link VCP). + +Firmware (``Src/debug_uart.c``, ``USE_DEBUG_UART``) emits a small text console: + +* boot banner: ``ARK_G431_CAN debug UART @ 115200 (PB3/USART2)`` +* state: ``esc: -> `` +* faults: ``fault: nFAULT`` / ``fault: desync`` / … + +This is **not** KISS telemetry (that is a separate optional wire). On the +thrust-stand bench the TX pin is wired to the ST-Link V3 Virtual COM Port; +udev can name it ``/dev/esc-debug-uart`` (see ``scripts/99-hwci.rules``). +""" +from __future__ import annotations + +import collections +import re +import threading +import time +from dataclasses import dataclass, field +from pathlib import Path + +# Host hard-abort on console faults. Free-run BEMF/startup campaigns log +# many "fault: desync" / "fault: stall" lines during open-loop acquire and +# map-tier crawl — those are data, not aborts. LiveDesyncWatch + stand +# safety remain authoritative. Only gate-driver latch aborts the run. +_ABORT_FAULTS = frozenset({ + "nFAULT", +}) + +_FAULT_RE = re.compile(r"^fault:\s*(\S+)", re.IGNORECASE) +_STATE_RE = re.compile(r"^esc:\s*(.+?)\s*->\s*(.+)\s*$", re.IGNORECASE) + + +@dataclass +class DebugUartLine: + t_mono: float + text: str + kind: str = "raw" # raw | fault | state | boot + fault: str | None = None + state_from: str | None = None + state_to: str | None = None + + +@dataclass +class DebugUartReader: + """Background reader for the ST-Link VCP debug console.""" + + port: str + baud: int = 115200 + max_lines: int = 5000 + _ser: object | None = field(default=None, init=False, repr=False) + _stop: threading.Event = field(default_factory=threading.Event, init=False) + _thread: threading.Thread | None = field(default=None, init=False, repr=False) + _lock: threading.Lock = field(default_factory=threading.Lock, init=False) + _lines: collections.deque = field(default=None, init=False, repr=False) + _abort_fault: str | None = field(default=None, init=False) + errors: int = field(default=0, init=False) + last_error: Exception | None = field(default=None, init=False) + + def __post_init__(self) -> None: + self._lines = collections.deque(maxlen=self.max_lines) + + def open(self) -> "DebugUartReader": + import serial # pyserial — already a harness dependency for KISS telem + + self._ser = serial.Serial(self.port, self.baud, timeout=0.1) + self._stop.clear() + self._thread = threading.Thread( + target=self._loop, daemon=True, name="debug-uart") + self._thread.start() + return self + + def _loop(self) -> None: + assert self._ser is not None + buf = bytearray() + while not self._stop.is_set(): + try: + chunk = self._ser.read(256) + except Exception as e: + self.errors += 1 + self.last_error = e + time.sleep(0.05) + continue + if not chunk: + continue + buf.extend(chunk) + while True: + nl = buf.find(b"\n") + if nl < 0: + break + raw = bytes(buf[:nl]) + del buf[: nl + 1] + text = raw.decode("utf-8", errors="replace").rstrip("\r") + if not text: + continue + rec = _classify(text, time.monotonic()) + with self._lock: + self._lines.append(rec) + if rec.kind == "fault" and rec.fault in _ABORT_FAULTS: + self._abort_fault = rec.fault + + def drain_abort_fault(self) -> str | None: + """Return and clear a latched abort-worthy fault name, if any.""" + with self._lock: + f = self._abort_fault + self._abort_fault = None + return f + + def lines(self) -> list[DebugUartLine]: + with self._lock: + return list(self._lines) + + def save_log(self, path: str | Path) -> Path: + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + with open(path, "w") as fh: + for rec in self.lines(): + fh.write(f"{rec.t_mono:.3f}\t{rec.text}\n") + return path + + def close(self) -> None: + self._stop.set() + if self._thread is not None: + self._thread.join(timeout=1.0) + self._thread = None + if self._ser is not None: + try: + self._ser.close() + except Exception: + pass + self._ser = None + + +def _classify(text: str, t_mono: float) -> DebugUartLine: + m = _FAULT_RE.match(text) + if m: + return DebugUartLine( + t_mono=t_mono, text=text, kind="fault", fault=m.group(1)) + m = _STATE_RE.match(text) + if m: + return DebugUartLine( + t_mono=t_mono, text=text, kind="state", + state_from=m.group(1).strip(), state_to=m.group(2).strip()) + if "debug UART" in text or text.startswith("ARK_G431"): + return DebugUartLine(t_mono=t_mono, text=text, kind="boot") + return DebugUartLine(t_mono=t_mono, text=text, kind="raw") + + +def resolve_stlink_vcp(prefer: str | None = None) -> str | None: + """Best-effort path to the ST-Link V3 VCP (interface 01). + + Order: explicit prefer if it exists → ``/dev/esc-debug-uart`` → + ``/dev/serial/by-id/*STLINK*if01*``. + """ + candidates: list[str] = [] + if prefer: + candidates.append(prefer) + candidates.append("/dev/esc-debug-uart") + by_id = Path("/dev/serial/by-id") + if by_id.is_dir(): + for p in sorted(by_id.glob("usb-STMicroelectronics_STLINK*if01*")): + candidates.append(str(p)) + for p in sorted(by_id.glob("usb-STMicroelectronics_ST-LINK*if01*")): + candidates.append(str(p)) + for c in candidates: + if Path(c).exists(): + return c + return None diff --git a/hwci/hwci/debugger/__init__.py b/hwci/hwci/debugger/__init__.py index f83a8ab6c..96d3c7413 100644 --- a/hwci/hwci/debugger/__init__.py +++ b/hwci/hwci/debugger/__init__.py @@ -1,3 +1,4 @@ """Debugger backends for reading firmware instrumentation over SWD.""" from .base import Debugger, DebuggerError, MockDebugger # noqa: F401 +from .factory import openocd_from_rig # noqa: F401 from .openocd import OpenOcdDebugger # noqa: F401 diff --git a/hwci/hwci/debugger/base.py b/hwci/hwci/debugger/base.py index 894b2a45a..e25b68580 100644 --- a/hwci/hwci/debugger/base.py +++ b/hwci/hwci/debugger/base.py @@ -1,9 +1,12 @@ """Debugger backend abstraction. A :class:`Debugger` flashes firmware and performs *background* (non-halting) -memory reads/writes over SWD while the MCU runs. That background access is the -only way to read CPU-load / loop-time data off the STM32F051, whose Cortex-M0 -core has no SWO/ITM/DWT trace hardware. +memory reads/writes over SWD while the MCU runs. + +On STM32F051 (Cortex-M0) there is no SWO/ITM/DWT, so instrumentation is a +RAM ``hwci_perf`` struct polled over AHB-AP. On STM32G4 (Cortex-M4) the same +poll path still works and is faster (higher SWD clock); SWO/ITM remain +optional extras — the harness does not require them. Both the OpenOCD (ST-Link) backend and a J-Link backend would implement this interface; :class:`MockDebugger` implements it in-process for offline tests and diff --git a/hwci/hwci/debugger/factory.py b/hwci/hwci/debugger/factory.py new file mode 100644 index 000000000..a48a3ebd4 --- /dev/null +++ b/hwci/hwci/debugger/factory.py @@ -0,0 +1,27 @@ +"""Construct OpenOCD debuggers from :class:`~hwci.config.RigConfig`.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .openocd import OpenOcdDebugger + +if TYPE_CHECKING: + from ..config import RigConfig + + +def openocd_from_rig(rig: "RigConfig", **overrides) -> OpenOcdDebugger: + """Build an :class:`OpenOcdDebugger` with target-correct SWD settings. + + Pulls ``openocd_configs``, ``app_load_addr``, and ``adapter_speed_khz`` + from the rig (already filled by :func:`hwci.config.apply_target_preset` + for known targets like ``ARK_G431_CAN``). + """ + kwargs = dict( + configs=list(rig.openocd_configs), + openocd_bin=rig.openocd_bin, + search_dirs=list(rig.openocd_search_dirs), + app_load_addr=int(rig.app_load_addr), + adapter_speed_khz=rig.adapter_speed_khz, + ) + kwargs.update(overrides) + return OpenOcdDebugger(**kwargs) diff --git a/hwci/hwci/debugger/openocd.py b/hwci/hwci/debugger/openocd.py index c10a31463..c71756a90 100644 --- a/hwci/hwci/debugger/openocd.py +++ b/hwci/hwci/debugger/openocd.py @@ -4,11 +4,16 @@ read/write target memory while the firmware runs. Cortex-M memory access goes through the AHB-AP and does not require halting the core, which is what makes non-intrusive CPU-load/loop-time sampling possible (the same mechanism VS Code -"live watch" uses; note ``-gdb-max-connections`` in Mcu/f051/openocd.cfg). +"live watch" uses; note ``-gdb-max-connections`` in Mcu/f051/openocd.cfg and +Mcu/g431/openocd.cfg). Flashing is done with a separate one-shot ``openocd`` invocation so the persistent read session is never left in a halted state. +App base is target-dependent (F051 4 KiB BL → 0x08001000; G431 CAN 16 KiB BL → +0x08004000). Pass ``app_load_addr`` so post-flash vector handoff and BL escape +use the correct image base. + This backend cannot be unit-tested without hardware; the offline test path uses :class:`hwci.debugger.base.MockDebugger`. The Tcl-RPC framing and command set here follow the documented OpenOCD interface. @@ -28,9 +33,13 @@ # OpenOCD Tcl-RPC terminates every command and reply with this byte. _RPC_SEP = b"\x1a" -# Default config matching Mcu/f051/openocd.cfg (ST-Link + STM32F0 target). +# F051 defaults (historical). Prefer RigConfig / target presets for G4. DEFAULT_CONFIGS = ["interface/stlink.cfg", "target/stm32f0x.cfg"] -APP_LOAD_ADDR = 0x08001000 # AM32 app sits above the bootloader +APP_LOAD_ADDR = 0x08001000 # F051 app above 4 KiB bootloader + +# G431 / G491 CAN (16 KiB bootloader — Mcu/g431/ldscript_CAN.ld). +G4_CONFIGS = ["interface/stlink.cfg", "target/stm32g4x.cfg"] +G4_APP_LOAD_ADDR = 0x08004000 class OpenOcdDebugger(Debugger): @@ -42,12 +51,18 @@ def __init__( tcl_port: int = 6666, search_dirs: list[str] | None = None, connect_timeout: float = 10.0, + app_load_addr: int = APP_LOAD_ADDR, + adapter_speed_khz: int | None = None, ): self.configs = configs or list(DEFAULT_CONFIGS) self.openocd_bin = openocd_bin self.tcl_port = tcl_port self.search_dirs = search_dirs or [] self.connect_timeout = connect_timeout + # Vector-table base for post-flash boot handoff and BL escape. + self.app_load_addr = int(app_load_addr) + # Optional adapter kHz (G4 is happy at 4–8 MHz; leave None for OpenOCD default). + self.adapter_speed_khz = adapter_speed_khz self._proc: subprocess.Popen | None = None self._sock: socket.socket | None = None self._log_tail: collections.deque[str] = collections.deque(maxlen=200) @@ -58,7 +73,6 @@ def __init__( self._rpc_lock = threading.Lock() if shutil.which(openocd_bin) is None: raise DebuggerError(f"{openocd_bin!r} not found on PATH") - # --- config helpers ---------------------------------------------- def _base_args(self) -> list[str]: args = [self.openocd_bin] @@ -66,10 +80,12 @@ def _base_args(self) -> list[str]: args += ["-s", d] for c in self.configs: args += ["-f", c] + if self.adapter_speed_khz is not None: + args += ["-c", f"adapter speed {int(self.adapter_speed_khz)}"] return args # --- flashing (one-shot) ----------------------------------------- - def flash(self, bin_path: str, load_addr: int = APP_LOAD_ADDR) -> None: + def flash(self, bin_path: str, load_addr: int | None = None) -> None: # After program+reset the AM32 bootloader can stick in programming # mode when the Flight Stand holds DShot idle on the signal line # (observed 2026-07-13: eeprom_address stays 0, PC in BL @ 0x08000b42). @@ -77,7 +93,9 @@ def flash(self, bin_path: str, load_addr: int = APP_LOAD_ADDR) -> None: # trials (and firmware flashes) always leave the core running main. # Vectors live at the app base even when ``load_addr`` is the EEPROM # page (settings-only program). - app = APP_LOAD_ADDR + if load_addr is None: + load_addr = self.app_load_addr + app = self.app_load_addr boot_app = ( f"reset halt; " f"set _sp [mrw 0x{app:08x}]; " @@ -155,7 +173,7 @@ def ensure_app_running(self) -> None: break except ValueError: continue - if pc is not None and pc < APP_LOAD_ADDR: + if pc is not None and pc < self.app_load_addr: self._boot_app_from_vectors() return except DebuggerError: @@ -175,8 +193,8 @@ def ensure_app_running(self) -> None: self._boot_app_from_vectors() def _boot_app_from_vectors(self) -> None: - """Load MSP/PC from the app vector table at APP_LOAD_ADDR and resume.""" - app = APP_LOAD_ADDR + """Load MSP/PC from the app vector table at app_load_addr and resume.""" + app = self.app_load_addr self._rpc("halt") sp = struct.unpack(" None: self._rpc(f"reg pc 0x{pc:08x}") self._rpc("resume") time.sleep(0.3) # let loadEEpromSettings run - def _drain_stdout(self) -> None: proc = self._proc if proc is None or proc.stdout is None: diff --git a/hwci/hwci/model.py b/hwci/hwci/model.py index 433568849..d749fab34 100644 --- a/hwci/hwci/model.py +++ b/hwci/hwci/model.py @@ -137,6 +137,9 @@ def make_row(t: float, segment: str, throttle_cmd: float, class RunResult: meta: dict = field(default_factory=dict) rows: list[dict] = field(default_factory=list) + # Optional G4 debug-UART transcript (timestamped lines); written as + # debug_uart.log next to samples.csv when present. + debug_uart_text: str | None = None def save(self, run_dir: str | Path) -> Path: run_dir = Path(run_dir) @@ -147,6 +150,8 @@ def save(self, run_dir: str | Path) -> Path: writer.writerows(self.rows) with open(run_dir / "meta.json", "w") as fh: json.dump(self.meta, fh, indent=2, sort_keys=True) + if self.debug_uart_text: + (run_dir / "debug_uart.log").write_text(self.debug_uart_text) return run_dir @classmethod diff --git a/hwci/hwci/profiles/noprop_low_rpm_crawl.yaml b/hwci/hwci/profiles/noprop_low_rpm_crawl.yaml new file mode 100644 index 000000000..17feadcdc --- /dev/null +++ b/hwci/hwci/profiles/noprop_low_rpm_crawl.yaml @@ -0,0 +1,29 @@ +name: noprop_low_rpm_crawl +description: > + Free-run weak-BEMF crawl for ZC filter / map-tier data. Spin up to 12%, + then descend through 10/8/6/5/4/3% holds. Steady points log zc_jitter, + confirm_rejects_per_zc, phase hist, blind steps. No prop; ~25 V brick. + ~55 s. +sample_rate_hz: 100 +arm_settle_s: 3.0 +steady_tail_fraction: 0.5 +demag_rpm_drop_fraction: 0.30 +demag_commutation_spike: 3.0 + +safety: + max_current_a: 8.0 + max_thrust_n: 3.0 + max_rpm: 20000 + max_motor_temp_c: 90.0 + +segments: + - {label: idle, throttle: 0.00, duration_s: 2.0} + - {label: spinup, throttle: 0.12, duration_s: 4.0, ramp: true} + - {label: t10, throttle: 0.10, duration_s: 6.0, steady: true} + - {label: t08, throttle: 0.08, duration_s: 6.0, steady: true} + - {label: t06, throttle: 0.06, duration_s: 6.0, steady: true} + - {label: t05, throttle: 0.05, duration_s: 6.0, steady: true} + - {label: t04, throttle: 0.04, duration_s: 6.0, steady: true} + - {label: t03, throttle: 0.03, duration_s: 6.0, steady: true} + - {label: rampdn, throttle: 0.00, duration_s: 3.0, ramp: true} + - {label: stop, throttle: 0.00, duration_s: 2.0} diff --git a/hwci/hwci/profiles/noprop_startup_matrix.yaml b/hwci/hwci/profiles/noprop_startup_matrix.yaml new file mode 100644 index 000000000..8b0dcb075 --- /dev/null +++ b/hwci/hwci/profiles/noprop_startup_matrix.yaml @@ -0,0 +1,78 @@ +name: noprop_startup_matrix +description: > + Free-run BEMF/startup data collection: repeated cold starts at several + low throttle levels (6/8/10/12/15/20%). No propeller — 25 V / 3 A brick + friendly. Host metrics (startup_stats) count success and time-to-run per + start*; pair with debug_uart.log for OPEN_LOOP/CLOSED_LOOP/desync lines. + ~4.5 min. +sample_rate_hz: 100 +arm_settle_s: 3.0 +steady_tail_fraction: 0.5 +demag_rpm_drop_fraction: 0.30 +demag_commutation_spike: 3.0 + +safety: + max_current_a: 8.0 + max_thrust_n: 3.0 + max_rpm: 25000 + max_motor_temp_c: 90.0 + +# start* labels drive metrics.startup_stats (success = running + eRPM>=1000) +segments: + - {label: idle, throttle: 0.00, duration_s: 2.0} + + # --- 6% (weakest BEMF start) × 4 --- + - {label: start06a, throttle: 0.06, duration_s: 3.0} + - {label: off06a, throttle: 0.00, duration_s: 2.5} + - {label: start06b, throttle: 0.06, duration_s: 3.0} + - {label: off06b, throttle: 0.00, duration_s: 2.5} + - {label: start06c, throttle: 0.06, duration_s: 3.0} + - {label: off06c, throttle: 0.00, duration_s: 2.5} + - {label: start06d, throttle: 0.06, duration_s: 3.0} + - {label: off06d, throttle: 0.00, duration_s: 2.5} + + # --- 8% × 4 --- + - {label: start08a, throttle: 0.08, duration_s: 3.0} + - {label: off08a, throttle: 0.00, duration_s: 2.5} + - {label: start08b, throttle: 0.08, duration_s: 3.0} + - {label: off08b, throttle: 0.00, duration_s: 2.5} + - {label: start08c, throttle: 0.08, duration_s: 3.0} + - {label: off08c, throttle: 0.00, duration_s: 2.5} + - {label: start08d, throttle: 0.08, duration_s: 3.0} + - {label: off08d, throttle: 0.00, duration_s: 2.5} + + # --- 10% × 4 --- + - {label: start10a, throttle: 0.10, duration_s: 3.0} + - {label: off10a, throttle: 0.00, duration_s: 2.5} + - {label: start10b, throttle: 0.10, duration_s: 3.0} + - {label: off10b, throttle: 0.00, duration_s: 2.5} + - {label: start10c, throttle: 0.10, duration_s: 3.0} + - {label: off10c, throttle: 0.00, duration_s: 2.5} + - {label: start10d, throttle: 0.10, duration_s: 3.0} + - {label: off10d, throttle: 0.00, duration_s: 2.5} + + # --- 12% × 3 --- + - {label: start12a, throttle: 0.12, duration_s: 3.0} + - {label: off12a, throttle: 0.00, duration_s: 2.5} + - {label: start12b, throttle: 0.12, duration_s: 3.0} + - {label: off12b, throttle: 0.00, duration_s: 2.5} + - {label: start12c, throttle: 0.12, duration_s: 3.0} + - {label: off12c, throttle: 0.00, duration_s: 2.5} + + # --- 15% × 3 --- + - {label: start15a, throttle: 0.15, duration_s: 3.0} + - {label: off15a, throttle: 0.00, duration_s: 2.5} + - {label: start15b, throttle: 0.15, duration_s: 3.0} + - {label: off15b, throttle: 0.00, duration_s: 2.5} + - {label: start15c, throttle: 0.15, duration_s: 3.0} + - {label: off15c, throttle: 0.00, duration_s: 2.5} + + # --- 20% × 3 --- + - {label: start20a, throttle: 0.20, duration_s: 3.0} + - {label: off20a, throttle: 0.00, duration_s: 2.5} + - {label: start20b, throttle: 0.20, duration_s: 3.0} + - {label: off20b, throttle: 0.00, duration_s: 2.5} + - {label: start20c, throttle: 0.20, duration_s: 3.0} + - {label: off20c, throttle: 0.00, duration_s: 2.5} + + - {label: stop, throttle: 0.00, duration_s: 2.0} diff --git a/hwci/hwci/profiles/noprop_startup_matrix_hiI.yaml b/hwci/hwci/profiles/noprop_startup_matrix_hiI.yaml new file mode 100644 index 000000000..716b0774f --- /dev/null +++ b/hwci/hwci/profiles/noprop_startup_matrix_hiI.yaml @@ -0,0 +1,78 @@ +name: noprop_startup_matrix_hiI +description: > + Free-run BEMF/startup data collection: repeated cold starts at several + low throttle levels (6/8/10/12/15/20%). No propeller — 1980 KV free-run: host current abort raised (start + spikes exceed 8 A for a few samples). Host metrics (startup_stats) count success and time-to-run per + start*; pair with debug_uart.log for OPEN_LOOP/CLOSED_LOOP/desync lines. + ~4.5 min. +sample_rate_hz: 100 +arm_settle_s: 3.0 +steady_tail_fraction: 0.5 +demag_rpm_drop_fraction: 0.30 +demag_commutation_spike: 3.0 + +safety: + max_current_a: 20.0 + max_thrust_n: 3.0 + max_rpm: 55000 + max_motor_temp_c: 90.0 + +# start* labels drive metrics.startup_stats (success = running + eRPM>=1000) +segments: + - {label: idle, throttle: 0.00, duration_s: 2.0} + + # --- 6% (weakest BEMF start) × 4 --- + - {label: start06a, throttle: 0.06, duration_s: 3.0} + - {label: off06a, throttle: 0.00, duration_s: 2.5} + - {label: start06b, throttle: 0.06, duration_s: 3.0} + - {label: off06b, throttle: 0.00, duration_s: 2.5} + - {label: start06c, throttle: 0.06, duration_s: 3.0} + - {label: off06c, throttle: 0.00, duration_s: 2.5} + - {label: start06d, throttle: 0.06, duration_s: 3.0} + - {label: off06d, throttle: 0.00, duration_s: 2.5} + + # --- 8% × 4 --- + - {label: start08a, throttle: 0.08, duration_s: 3.0} + - {label: off08a, throttle: 0.00, duration_s: 2.5} + - {label: start08b, throttle: 0.08, duration_s: 3.0} + - {label: off08b, throttle: 0.00, duration_s: 2.5} + - {label: start08c, throttle: 0.08, duration_s: 3.0} + - {label: off08c, throttle: 0.00, duration_s: 2.5} + - {label: start08d, throttle: 0.08, duration_s: 3.0} + - {label: off08d, throttle: 0.00, duration_s: 2.5} + + # --- 10% × 4 --- + - {label: start10a, throttle: 0.10, duration_s: 3.0} + - {label: off10a, throttle: 0.00, duration_s: 2.5} + - {label: start10b, throttle: 0.10, duration_s: 3.0} + - {label: off10b, throttle: 0.00, duration_s: 2.5} + - {label: start10c, throttle: 0.10, duration_s: 3.0} + - {label: off10c, throttle: 0.00, duration_s: 2.5} + - {label: start10d, throttle: 0.10, duration_s: 3.0} + - {label: off10d, throttle: 0.00, duration_s: 2.5} + + # --- 12% × 3 --- + - {label: start12a, throttle: 0.12, duration_s: 3.0} + - {label: off12a, throttle: 0.00, duration_s: 2.5} + - {label: start12b, throttle: 0.12, duration_s: 3.0} + - {label: off12b, throttle: 0.00, duration_s: 2.5} + - {label: start12c, throttle: 0.12, duration_s: 3.0} + - {label: off12c, throttle: 0.00, duration_s: 2.5} + + # --- 15% × 3 --- + - {label: start15a, throttle: 0.15, duration_s: 3.0} + - {label: off15a, throttle: 0.00, duration_s: 2.5} + - {label: start15b, throttle: 0.15, duration_s: 3.0} + - {label: off15b, throttle: 0.00, duration_s: 2.5} + - {label: start15c, throttle: 0.15, duration_s: 3.0} + - {label: off15c, throttle: 0.00, duration_s: 2.5} + + # --- 20% × 3 --- + - {label: start20a, throttle: 0.20, duration_s: 3.0} + - {label: off20a, throttle: 0.00, duration_s: 2.5} + - {label: start20b, throttle: 0.20, duration_s: 3.0} + - {label: off20b, throttle: 0.00, duration_s: 2.5} + - {label: start20c, throttle: 0.20, duration_s: 3.0} + - {label: off20c, throttle: 0.00, duration_s: 2.5} + + - {label: stop, throttle: 0.00, duration_s: 2.0} diff --git a/hwci/hwci/profiles/noprop_step_stress.yaml b/hwci/hwci/profiles/noprop_step_stress.yaml new file mode 100644 index 000000000..402f36002 --- /dev/null +++ b/hwci/hwci/profiles/noprop_step_stress.yaml @@ -0,0 +1,31 @@ +name: noprop_step_stress +description: > + Free-run step stress for BEMF re-acquire after snaps (no prop). Instant + steps 10↔60/80/100 — low bus current, exercises commutation handoff and + demag detectors without prop inertia. ~45 s. +sample_rate_hz: 200 +arm_settle_s: 3.0 +demag_rpm_drop_fraction: 0.30 +demag_commutation_spike: 3.0 +steady_tail_fraction: 0.5 + +safety: + max_current_a: 8.0 + max_thrust_n: 3.0 + max_rpm: 55000 + max_motor_temp_c: 90.0 + +segments: + - {label: idle, throttle: 0.00, duration_s: 2.0} + - {label: pre, throttle: 0.10, duration_s: 3.0, ramp: true} + - {label: step60a, throttle: 0.60, duration_s: 2.5} + - {label: drop10a, throttle: 0.10, duration_s: 2.5} + - {label: step80a, throttle: 0.80, duration_s: 2.5} + - {label: drop10b, throttle: 0.10, duration_s: 2.5} + - {label: step100, throttle: 1.00, duration_s: 2.5} + - {label: drop10c, throttle: 0.10, duration_s: 2.5} + - {label: step80b, throttle: 0.80, duration_s: 2.5} + - {label: drop05, throttle: 0.05, duration_s: 2.5} + - {label: step60b, throttle: 0.60, duration_s: 2.5} + - {label: rampdn, throttle: 0.00, duration_s: 3.0, ramp: true} + - {label: stop, throttle: 0.00, duration_s: 2.0} diff --git a/hwci/hwci/profiles/noprop_zc_map.yaml b/hwci/hwci/profiles/noprop_zc_map.yaml new file mode 100644 index 000000000..1286905eb --- /dev/null +++ b/hwci/hwci/profiles/noprop_zc_map.yaml @@ -0,0 +1,34 @@ +name: noprop_zc_map +description: > + Free-run zero-cross map across eRPM: steady staircase 5%→100% with long + enough holds for zc_jitter / confirm_reject / phase histogram stats. + Primary filter-tuning dataset for ARK G431 CAN + high-KV no-prop. + ~25 V / 3 A; max free-run ~41k RPM. ~2 min. +sample_rate_hz: 100 +arm_settle_s: 3.0 +steady_tail_fraction: 0.5 +demag_rpm_drop_fraction: 0.30 +demag_commutation_spike: 3.0 + +safety: + max_current_a: 8.0 + max_thrust_n: 3.0 + max_rpm: 55000 + max_motor_temp_c: 90.0 + +segments: + - {label: idle, throttle: 0.00, duration_s: 2.0} + - {label: t05, throttle: 0.05, duration_s: 6.0, ramp: true, steady: true} + - {label: t10, throttle: 0.10, duration_s: 6.0, steady: true} + - {label: t15, throttle: 0.15, duration_s: 6.0, steady: true} + - {label: t20, throttle: 0.20, duration_s: 6.0, steady: true} + - {label: t30, throttle: 0.30, duration_s: 6.0, steady: true} + - {label: t40, throttle: 0.40, duration_s: 6.0, steady: true} + - {label: t50, throttle: 0.50, duration_s: 6.0, steady: true} + - {label: t60, throttle: 0.60, duration_s: 6.0, steady: true} + - {label: t70, throttle: 0.70, duration_s: 6.0, steady: true} + - {label: t80, throttle: 0.80, duration_s: 6.0, steady: true} + - {label: t90, throttle: 0.90, duration_s: 6.0, steady: true} + - {label: t100, throttle: 1.00, duration_s: 6.0, steady: true} + - {label: rampdn, throttle: 0.00, duration_s: 5.0, ramp: true} + - {label: stop, throttle: 0.00, duration_s: 2.0} diff --git a/hwci/hwci/profiles/prop14_startup_matrix_12s.yaml b/hwci/hwci/profiles/prop14_startup_matrix_12s.yaml new file mode 100644 index 000000000..1ca51c4b6 --- /dev/null +++ b/hwci/hwci/profiles/prop14_startup_matrix_12s.yaml @@ -0,0 +1,75 @@ +name: prop14_startup_matrix_12s +description: > + Cold-start matrix for the 14x5.5 (MRP 365x140) + RCINPOWER GTS 4715-360KV + at 12S. Three starts at each of 10/12/15/20/25%, for tuning the STARTUP + zero-cross behaviour on a high-inertia prop. + + Why this article needs its own startup data: the 48/12/9 filter pack was + validated on a 1980KV 5-inch free-run bell, whose operating band (~147-168k + eRPM) this setup never reaches, and whose rotational inertia is a fraction + of a 365 mm prop's. Steady-state zero-cross health is already confirmed + fine here (jitter 1.8-4.5% across the loaded sweep, 0 demag, 0 bemf + timeouts), so the open-loop -> closed-loop handoff under inertia is the one + regime genuinely changed by the new article and the only one worth tuning. + + Lowest rung is 10%, not the noprop matrix's 6%: minimum_duty_cycle is 18 + (9%), so commands below that floor test the clamp rather than acquisition. + + Off segments are 5 s, not the noprop matrix's 2.5 s. A 365 mm prop with + brake_on_stop=0 coasts far longer than a bare 5-inch bell, and a start that + begins on a still-spinning rotor is not a cold start. Verify rpm actually + reaches ~0 before each start* segment when reading the results; if it does + not, lengthen these before trusting the numbers. + + Safety from the measured 60% run (Ct 0.0663, P ~ n^3.15): 25% steady is + ~2 A / ~4 N, far under these limits, which are sized for the start + transient rather than the steady point. +sample_rate_hz: 100 +arm_settle_s: 3.0 +steady_tail_fraction: 0.5 +demag_rpm_drop_fraction: 0.30 +demag_commutation_spike: 3.0 + +safety: + max_current_a: 40.0 + max_thrust_n: 45.0 + max_rpm: 12000 + max_motor_temp_c: 80.0 + +segments: + - {label: idle, throttle: 0.00, duration_s: 2.0} + + - {label: start10a, throttle: 0.10, duration_s: 3.0} + - {label: off10a, throttle: 0.00, duration_s: 5.0} + - {label: start10b, throttle: 0.10, duration_s: 3.0} + - {label: off10b, throttle: 0.00, duration_s: 5.0} + - {label: start10c, throttle: 0.10, duration_s: 3.0} + - {label: off10c, throttle: 0.00, duration_s: 5.0} + + - {label: start12a, throttle: 0.12, duration_s: 3.0} + - {label: off12a, throttle: 0.00, duration_s: 5.0} + - {label: start12b, throttle: 0.12, duration_s: 3.0} + - {label: off12b, throttle: 0.00, duration_s: 5.0} + - {label: start12c, throttle: 0.12, duration_s: 3.0} + - {label: off12c, throttle: 0.00, duration_s: 5.0} + + - {label: start15a, throttle: 0.15, duration_s: 3.0} + - {label: off15a, throttle: 0.00, duration_s: 5.0} + - {label: start15b, throttle: 0.15, duration_s: 3.0} + - {label: off15b, throttle: 0.00, duration_s: 5.0} + - {label: start15c, throttle: 0.15, duration_s: 3.0} + - {label: off15c, throttle: 0.00, duration_s: 5.0} + + - {label: start20a, throttle: 0.20, duration_s: 3.0} + - {label: off20a, throttle: 0.00, duration_s: 5.0} + - {label: start20b, throttle: 0.20, duration_s: 3.0} + - {label: off20b, throttle: 0.00, duration_s: 5.0} + - {label: start20c, throttle: 0.20, duration_s: 3.0} + - {label: off20c, throttle: 0.00, duration_s: 5.0} + + - {label: start25a, throttle: 0.25, duration_s: 3.0} + - {label: off25a, throttle: 0.00, duration_s: 5.0} + - {label: start25b, throttle: 0.25, duration_s: 3.0} + - {label: off25b, throttle: 0.00, duration_s: 5.0} + - {label: start25c, throttle: 0.25, duration_s: 3.0} + - {label: stop, throttle: 0.00, duration_s: 3.0} diff --git a/hwci/hwci/runner.py b/hwci/hwci/runner.py index 25243748d..467ec54db 100644 --- a/hwci/hwci/runner.py +++ b/hwci/hwci/runner.py @@ -49,6 +49,7 @@ class Sources: perf_source: PerfSourceFn telem_source: TelemSourceFn perf_reader: Optional[PerfReader] = None + debug_uart: object | None = None # DebugUartReader when G4 VCP is wired closers: list = field(default_factory=list) def close(self) -> None: @@ -417,6 +418,12 @@ def perf_get(): # Cut throttle immediately on live desync — do not finish the # remaining profile segments into a freewheeling/desynced rotor. desync_watch.check(throttle, stand, pf) + # G4 debug UART (ST-Link VCP): nFAULT / desync text lines. + if sources.debug_uart is not None: + fault = sources.debug_uart.drain_abort_fault() + if fault is not None: + raise DesyncTripped( + f"debug UART fault: {fault}") tick += 1 prev_throttle = seg.throttle except DesyncTripped as e: @@ -440,9 +447,19 @@ def perf_get(): } if perf_poller is not None and perf_poller.last_error is not None: full_meta["perf_last_error"] = repr(perf_poller.last_error) + debug_uart_text: str | None = None + if sources.debug_uart is not None: + du_lines = sources.debug_uart.lines() + full_meta["debug_uart_line_count"] = len(du_lines) + full_meta["debug_uart_faults"] = sorted({ + r.fault for r in du_lines if r.kind == "fault" and r.fault}) + full_meta["debug_uart_errors"] = getattr(sources.debug_uart, "errors", 0) + debug_uart_text = "\n".join( + f"{r.t_mono:.3f}\t{r.text}" for r in du_lines) + ( + "\n" if du_lines else "") if meta: full_meta.update(meta) - return RunResult(meta=full_meta, rows=rows) + return RunResult(meta=full_meta, rows=rows, debug_uart_text=debug_uart_text) def _safe(fn): @@ -488,8 +505,30 @@ def build_sim_sources(rig: RigConfig, profile: Profile, *, ) +# First-boot app-side BL rewrite holds IRQs off for ~0.5 s (16 KiB G431). +# Poll this long before the first reset_run so we do not halt mid-erase. +_BL_REWRITE_GRACE_S = 3.0 + + +def _elf_has_embedded_bl(elf_path: str) -> bool: + """True when the ELF carries a real .bl_image (not the empty ALIGN stub). + + Fail closed: if the ELF cannot be read, assume a blob is present and + take the 3 s grace rather than reset_run mid-erase. + """ + try: + from elftools.elf.elffile import ELFFile + with open(elf_path, "rb") as fh: + sec = ELFFile(fh).get_section_by_name(".bl_image") + except Exception: + return True + return sec is not None and sec["sh_size"] >= 1024 + + def _ensure_app_alive(dbg, perf_reader: PerfReader, - throttle: ThrottleSource) -> None: + throttle: ThrottleSource, *, + embed_bl: bool | None = None, + elf: str | None = None) -> None: """Get the ESC out of the AM32 bootloader and into the app. The bootloader only jumps to the app when the throttle signal line idles @@ -500,6 +539,10 @@ def _ensure_app_alive(dbg, perf_reader: PerfReader, throttle source (signal dropped, line driven low), reset, and wait for the app to publish the magic. The throttle is re-activated later by ``arm()``. + + When the flashed ELF embeds a bootloader image, first boot may rewrite + the BL region with IRQs off. A reset_run in that window bricks the page. + Poll for ``_BL_REWRITE_GRACE_S`` before the first reset in that case. """ from .perf import PerfDecodeError @@ -530,6 +573,18 @@ def app_alive() -> bool: if app_alive(): return + + if embed_bl is None and elf: + embed_bl = _elf_has_embedded_bl(elf) + if embed_bl: + # Let a first-boot BL rewrite finish (or the app come up after its + # own post-rewrite reset) before we halt the core. + grace_deadline = time.monotonic() + _BL_REWRITE_GRACE_S + while time.monotonic() < grace_deadline: + time.sleep(0.25) + if app_alive(): + return + for _attempt in range(2): throttle.quiesce() # drop the signal so the line is driven low time.sleep(0.2) # let the output state settle @@ -637,15 +692,15 @@ def build_live_sources(rig: RigConfig, profile: Profile, *, # --- perf struct via debugger --- perf_reader = None perf_source: PerfSourceFn = lambda: None + dbg = None if rig.debugger_backend == "openocd": - from .debugger.openocd import OpenOcdDebugger + from .debugger.factory import openocd_from_rig elf = rig.resolved_elf() if elf is None: raise FileNotFoundError( f"no ELF for target {rig.target} in {rig.resolved_obj_dir()}; " "build with HWCI_PERF=1 first") - dbg = OpenOcdDebugger(rig.openocd_configs, openocd_bin=rig.openocd_bin, - search_dirs=rig.openocd_search_dirs).open() + dbg = openocd_from_rig(rig).open() perf_reader = PerfReader(dbg, str(elf)) perf_source = perf_reader.read closers.append(dbg.close) @@ -654,7 +709,7 @@ def build_live_sources(rig: RigConfig, profile: Profile, *, f"debugger_backend {rig.debugger_backend!r} is not a live backend " "(expected 'openocd' or 'none')") - # --- ESC telemetry --- + # --- ESC telemetry (KISS wire; optional) --- telem_source: TelemSourceFn = lambda: None if rig.telem_backend == "serial": telem = _SerialTelemetry(rig.telem_port, rig.telem_baud) @@ -665,12 +720,24 @@ def build_live_sources(rig: RigConfig, profile: Profile, *, f"telem_backend {rig.telem_backend!r} is not a live backend " "(expected 'serial' or 'none')") + # --- G4 debug UART via ST-Link VCP (text console; not KISS) --- + debug_uart = None + if rig.debug_uart_backend == "serial": + from .debug_uart import DebugUartReader, resolve_stlink_vcp + port = resolve_stlink_vcp(rig.debug_uart_port) or rig.debug_uart_port + debug_uart = DebugUartReader(port, rig.debug_uart_baud).open() + closers.append(debug_uart.close) + elif rig.debug_uart_backend not in ("none", "sim"): + raise ValueError( + f"debug_uart_backend {rig.debug_uart_backend!r} is not a live " + "backend (expected 'serial' or 'none')") + sources = Sources(throttle=throttle, stand=stand, perf_source=perf_source, telem_source=telem_source, perf_reader=perf_reader, - closers=closers) + debug_uart=debug_uart, closers=closers) try: if perf_reader is not None: - _ensure_app_alive(dbg, perf_reader, throttle) + _ensure_app_alive(dbg, perf_reader, throttle, elf=str(elf)) if battery_cells is not None: check_battery(_live_voltage(stand, perf_source), battery_cells, min_cell_voltage) diff --git a/hwci/hwci/settings.py b/hwci/hwci/settings.py index 1c7236c94..d52bc110d 100644 --- a/hwci/hwci/settings.py +++ b/hwci/hwci/settings.py @@ -240,7 +240,7 @@ def default_blob() -> bytes: d[37] = 30 # low_cell_volt_cutoff (3.0v) d[43], d[44] = 141, 102 # limits (off) d[45] = 5 # sine_mode_power - d[46] = 1 # input_type + d[46] = 0 # input_type AUTO_IN (detect DShot/PWM; CAN prioritised if live) return bytes(d) diff --git a/hwci/hwci/tuner/backends.py b/hwci/hwci/tuner/backends.py index bf1f5fa1b..7a5032284 100644 --- a/hwci/hwci/tuner/backends.py +++ b/hwci/hwci/tuner/backends.py @@ -130,7 +130,7 @@ class HwTuneBackend(TuneBackend): mode = "hw" def __init__(self, rig: RigConfig, *, tare: bool = True): - from ..debugger.openocd import OpenOcdDebugger + from ..debugger.factory import openocd_from_rig self.rig = rig self.tare = tare elf = rig.resolved_elf() @@ -142,9 +142,7 @@ def __init__(self, rig: RigConfig, *, tare: bool = True): # Layout drift between EEPROM_FIELDS and the flashed firmware is # caught HERE, before any page is written. check_eeprom_layout(self.elf_path) - self._make_dbg = lambda: OpenOcdDebugger( - rig.openocd_configs, openocd_bin=rig.openocd_bin, - search_dirs=rig.openocd_search_dirs) + self._make_dbg = lambda: openocd_from_rig(rig) dbg = self._make_dbg().open() try: self.eeprom_address = resolve_eeprom_address(dbg, self.elf_path) diff --git a/hwci/scripts/99-hwci.rules b/hwci/scripts/99-hwci.rules index 46329dcca..d331aada6 100644 --- a/hwci/scripts/99-hwci.rules +++ b/hwci/scripts/99-hwci.rules @@ -25,3 +25,12 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="1366", MODE="0660", TAG+="uaccess" # SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ATTRS{serial}=="0001", SYMLINK+="esc-telem" # Example (FTDI with serial FT1ABCDE): # SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="FT1ABCDE", SYMLINK+="esc-throttle" + +# --- ST-Link V3 Virtual COM Port → G4 debug UART (USART2 TX PB3) ---------- +# Interface 01 is the bridge VCP on STLINK-V3 (VID 0483, PIDs below). +# ARK_G431_CAN firmware prints state/fault lines here @ 115200 8N1. +# Also matches by-id: usb-STMicroelectronics_STLINK-V3_*-if01 +SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374e", ENV{ID_USB_INTERFACE_NUM}=="01", MODE="0660", TAG+="uaccess", SYMLINK+="esc-debug-uart" +SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374f", ENV{ID_USB_INTERFACE_NUM}=="01", MODE="0660", TAG+="uaccess", SYMLINK+="esc-debug-uart" +SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3753", ENV{ID_USB_INTERFACE_NUM}=="01", MODE="0660", TAG+="uaccess", SYMLINK+="esc-debug-uart" +SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3754", ENV{ID_USB_INTERFACE_NUM}=="01", MODE="0660", TAG+="uaccess", SYMLINK+="esc-debug-uart" diff --git a/hwci/scripts/bemf_campaign_summary.py b/hwci/scripts/bemf_campaign_summary.py new file mode 100644 index 000000000..78c1aaddd --- /dev/null +++ b/hwci/scripts/bemf_campaign_summary.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python3 +"""Summarize free-run BEMF / startup HWCI runs into one markdown table set. + +Usage: + python scripts/bemf_campaign_summary.py runs/g491-bemf-* runs/g491-1800kv-* + python scripts/bemf_campaign_summary.py --out runs/bemf_summary.md runs/... +""" +from __future__ import annotations + +import argparse +import bisect +import json +import re +import sys +from collections import Counter, defaultdict +from pathlib import Path + +# Allow running from hwci/ without install +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from hwci import metrics as metricsmod # noqa: E402 +from hwci.config import profile_from_dict # noqa: E402 +from hwci.model import RunResult # noqa: E402 + + +def _profile(result: RunResult): + pd = result.meta.get("profile_def") + if pd: + return profile_from_dict(pd) + from hwci.config import load_profile + return load_profile(result.meta.get("profile", "ci_smoke")) + + +# esc_state_t values (Inc/esc_state.h). Only the two drive states matter here. +ESC_OPEN_LOOP = 4 +ESC_CLOSED_LOOP = 5 + +_FAULT_RE = re.compile(r"fault:\s*(\S+)") +_TS_RE = re.compile(r"^(\d+\.\d+)\t(.*)$") + + +def _tier_label(throttle) -> str: + """Group start* segments by commanded throttle, e.g. 0.08 -> '8%'.""" + try: + return f"{round(float(throttle) * 100)}%" + except (TypeError, ValueError): + return "?" + + +def residency_by_tier(rows: list[dict]) -> list[dict]: + """Open- vs closed-loop sample residency within each start* throttle tier. + + This is the metric that actually tracks sync quality. Raw desync-line + counts do not: three G431 tuning iterations differed 22/12/12 on desync + lines while their open-loop residency was identical to within counting + noise (2026-08). Reported per tier because the deficit vs the F051 + reference is concentrated at the low-BEMF tiers. + """ + agg: dict[str, Counter] = defaultdict(Counter) + segs: dict[str, set] = defaultdict(set) + for r in rows: + seg = str(r.get("segment") or "") + if not seg.startswith("start"): + continue + state = r.get("perf_esc_state") + if state is None: + continue + tier = _tier_label(r.get("throttle_cmd")) + segs[tier].add(seg) + if int(state) == ESC_OPEN_LOOP: + agg[tier]["open"] += 1 + elif int(state) == ESC_CLOSED_LOOP: + agg[tier]["closed"] += 1 + out = [] + for tier, c in agg.items(): + drive = c["open"] + c["closed"] + out.append({ + "tier": tier, + "segments": len(segs[tier]), + "open": c["open"], + "closed": c["closed"], + "pct_open": (100.0 * c["open"] / drive) if drive else None, + }) + out.sort(key=lambda d: float(d["tier"].rstrip("%")) if d["tier"] != "?" else 1e9) + return out + + +def faults_by_tier(rows: list[dict], debug_text: str | None) -> list[dict]: + """Attribute each debug-UART ``fault:`` line to the segment it landed in. + + ``perf_host_t`` in samples.csv and the debug_uart.log timestamps are the + same host monotonic clock, so this is an exact lookup rather than a + reconstruction from nominal segment timing. + """ + if not debug_text: + return [] + stamps: list[float] = [] + labels: list[str] = [] + for r in rows: + ht = r.get("perf_host_t") + if ht is None: + continue + stamps.append(float(ht)) + seg = str(r.get("segment") or "") + labels.append(_tier_label(r.get("throttle_cmd")) + if seg.startswith("start") else "coast/idle") + if not stamps: + return [] + + counts: dict[str, Counter] = defaultdict(Counter) + kinds: set[str] = set() + for line in debug_text.splitlines(): + m = _TS_RE.match(line) + if not m: + continue + ts, body = float(m.group(1)), m.group(2) + fm = _FAULT_RE.search(body) + if not fm: + continue + kind = fm.group(1) + # Ignore the idle signal_lost reboot loop between flash and run start. + if kind == "signal_lost": + continue + i = bisect.bisect_left(stamps, ts) + if i >= len(stamps): + i = len(stamps) - 1 + if i > 0 and abs(stamps[i - 1] - ts) < abs(stamps[i] - ts): + i -= 1 + # Outside the sampled window entirely (pre-run backlog). + if not (stamps[0] - 1.0 <= ts <= stamps[-1] + 1.0): + continue + counts[labels[i]][kind] += 1 + kinds.add(kind) + + order = sorted(counts, key=lambda t: (t == "coast/idle", + float(t.rstrip("%")) if t != "coast/idle" else 0)) + return [{"tier": t, "kinds": dict(counts[t]), + "total": sum(counts[t].values())} for t in order] + + +def summarize_run(run_dir: Path) -> dict | None: + if not (run_dir / "samples.csv").exists(): + return None + result = RunResult.load(run_dir) + profile = _profile(result) + m = metricsmod.compute(result, profile) + log_path = run_dir / "debug_uart.log" + debug_text = log_path.read_text(errors="replace") if log_path.exists() else None + summary = m.get("summary") or {} + starts = m.get("startup") or {} + steady = m.get("steady_points") or [] + demag = m.get("demag") or {} + return { + "dir": str(run_dir), + "name": run_dir.name, + "profile": result.meta.get("profile"), + "target": result.meta.get("target"), + "motor": result.meta.get("motor"), + "aborted": result.meta.get("aborted"), + "n_samples": result.meta.get("n_samples"), + "summary": summary, + "startup": starts, + "steady": steady, + "demag": demag, + "debug_faults": result.meta.get("debug_uart_faults"), + "debug_lines": result.meta.get("debug_uart_line_count"), + "residency": residency_by_tier(result.rows), + "faults_by_tier": faults_by_tier(result.rows, debug_text), + } + + +def md_escape(s) -> str: + return str(s).replace("|", "\\|") + + +def render(runs: list[dict]) -> str: + lines = [ + "# BEMF / startup free-run data collection", + "", + f"Runs: **{len(runs)}**", + "", + "## Run index", + "", + "| run | profile | aborted | demag | blind_zc | max_rpm | max_I | starts fail/att |", + "|---|---|---|---|---|---|---|---|", + ] + for r in runs: + s = r["summary"] + st = r["startup"] + d = r["demag"] + att = st.get("attempts") or 0 + fail = st.get("failures") or 0 + lines.append( + f"| `{r['name']}` | {r['profile']} | {r['aborted'] or '—'} | " + f"{d.get('event_count', s.get('demag_events', '—'))} | " + f"{s.get('zc_blind_steps_total', '—')} | " + f"{_rpm(r)} | {s.get('max_current_a', '—')} | " + f"{fail}/{att} |" + ) + + lines += ["", "## Startup reliability (start* segments)", ""] + for r in runs: + st = r["startup"] + if not st.get("attempts"): + continue + lines += [ + f"### `{r['name']}` — {st.get('failures', 0)} fail / " + f"{st.get('attempts', 0)} att " + f"(mean ttr {st.get('time_to_run_ms_mean')} ms, " + f"max {st.get('time_to_run_ms_max')} ms)", + "", + "| segment | ok | time_to_run_ms |", + "|---|---|---|", + ] + for a in st.get("per_attempt") or []: + lines.append( + f"| {a['segment']} | {'✅' if a['success'] else '❌'} | " + f"{a.get('time_to_run_ms') if a['success'] else '—'} |" + ) + lines.append("") + + lines += [ + "", "## Closed-loop retention by start tier", "", + "Share of *driving* samples spent in OPEN_LOOP within each start\\* " + "tier (lower is better). Reference: ARK 4IN1 F051, JS2306 1800 KV, " + "no prop, 8% tier = **0.77%** " + "(`ark-release-1800kv-startup-reliability-20260727_185930`).", + "", + ] + for r in runs: + if not r["residency"]: + continue + lines += [ + f"### `{r['name']}` ({r['target']})", + "", + "| tier | segs | open | closed | % open |", + "|---|---|---|---|---|", + ] + for d in r["residency"]: + lines.append( + f"| {d['tier']} | {d['segments']} | {d['open']} | {d['closed']} | " + f"{_fmt(d['pct_open'], 2)}% |" + ) + lines.append("") + + lines += [ + "", "## Faults by start tier", "", + "Each `fault:` console line mapped to the segment it occurred in via " + "the shared `perf_host_t` clock. Pre-run `signal_lost` reboot backlog " + "is excluded.", + "", + ] + for r in runs: + if not r["faults_by_tier"]: + continue + kinds = sorted({k for d in r["faults_by_tier"] for k in d["kinds"]}) + lines += [ + f"### `{r['name']}`", + "", + "| tier | " + " | ".join(kinds) + " | total |", + "|---" * (len(kinds) + 2) + "|", + ] + for d in r["faults_by_tier"]: + cells = " | ".join(str(d["kinds"].get(k, 0)) for k in kinds) + lines.append(f"| {d['tier']} | {cells} | {d['total']} |") + lines.append("") + + lines += ["", "## Steady-state ZC / filter metrics", ""] + for r in runs: + if not r["steady"]: + continue + lines += [ + f"### `{r['name']}` ({r['profile']})", + "", + "| seg | thr | rpm | eRPM~ | I_A | zc_jit% | zc_jit_max% | " + "confirm/zc | phase_peak | phase_bin |", + "|---|---|---|---|---|---|---|---|---|---|", + ] + for p in r["steady"]: + rpm = p.get("rpm") or 0 + erpm = rpm * 7 # 14-pole free-run article + lines.append( + f"| {p.get('segment')} | {p.get('throttle')} | " + f"{_fmt(rpm, 0)} | {_fmt(erpm, 0)} | {_fmt(p.get('current_a'), 3)} | " + f"{_fmt(p.get('zc_jitter_pct'), 3)} | " + f"{_fmt(p.get('zc_jitter_max_pct'), 2)} | " + f"{_fmt(p.get('confirm_rejects_per_zc'), 2)} | " + f"{_fmt(p.get('zc_phase_peak_ratio'), 2)} | " + f"{p.get('zc_phase_peak_bin')} |" + ) + lines.append("") + + lines += [ + "## Tuning notes (fill from data)", + "", + "- **Reliable min start throttle:** (lowest start* level with 0 failures)", + "- **Weak-BEMF floor:** (lowest crawl hold that stays running, jitter bound)", + "- **High-eRPM jitter hump:** (throttle/RPM where zc_jitter_pct peaks)", + "- **Confirm reject pressure:** (where confirm_rejects_per_zc rises)", + "- **Startup power / min duty candidates:** …", + "- **PWM frequency / advance trade:** …", + "", + ] + return "\n".join(lines) + + +def _fmt(v, nd=2): + if v is None: + return "—" + try: + return f"{float(v):.{nd}f}" + except (TypeError, ValueError): + return str(v) + + +def _rpm(r: dict): + s = r["summary"] + # prefer max from steady + pts = r["steady"] + if pts: + return max((p.get("rpm") or 0) for p in pts) + return s.get("max_rpm") # may be absent + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("runs", nargs="+", type=Path, help="run directories") + ap.add_argument("--out", type=Path, help="write markdown here") + args = ap.parse_args() + + runs = [] + for p in args.runs: + if p.is_dir(): + s = summarize_run(p) + if s: + runs.append(s) + else: + print(f"skip missing {p}", file=sys.stderr) + + text = render(runs) + if args.out: + args.out.parent.mkdir(parents=True, exist_ok=True) + args.out.write_text(text) + print(f"wrote {args.out}") + else: + print(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/hwci/scripts/watch_dshot_gate.py b/hwci/scripts/watch_dshot_gate.py new file mode 100644 index 000000000..417377f36 --- /dev/null +++ b/hwci/scripts/watch_dshot_gate.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +"""Read-only SWD capture that localises DShot frame loss on ARK G431 CAN. + +WHY: signaltimeout climbing tells us frames are not being accepted, but not +WHERE they die. computeDshotDMA() zeroes signaltimeout the instant the frame +span gate passes (Src/dshot.c, before the CRC compare), so a climbing +signaltimeout proves the frame never cleared the SPAN GATE - CRC is not +involved and dshot_badcounts cannot see it. Two very different faults produce +that same symptom: + + (a) edges never arrived / arrived corrupted -> wire or stand-side problem + (b) edges arrived, transfer completed, but the + measured span fell outside the learned + +-0.78% window -> firmware gate too tight + +This script separates them. It samples the cheap scalars fast, and when a gap +opens (or a bogus throttle appears) it grabs the raw input-capture DMA buffer +plus DMA1_Channel1->CNDTR and re-runs the firmware's own arithmetic on those +exact words: + + CNDTR == 32, buffer frozen -> no edges at all => (a) frames absent + CNDTR stuck mid-count -> edges stopped mid-frame => (a) edge loss + buffer moving, span outside + [dshot_frametime_low, high] -> transfer completed, + gate rejected it => (b) gate + +Everything here is a memory read. No throttle is commanded, nothing is +halted, and no firmware is flashed - safe to run against an armed, idling +bench exactly as watch_gate_blip.py is. + +Addresses are resolved from the ELF at startup (watch_gate_blip.py hardcodes +them; that drifts silently across rebuilds). + +Usage: + python3 hwci/scripts/watch_dshot_gate.py --duration 600 --elf obj/AM32_ARK_G431_CAN_3.0.1-ark.elf +""" +from __future__ import annotations + +import argparse +import struct +import sys +import time +from pathlib import Path + +_HWCI = Path(__file__).resolve().parents[1] +if str(_HWCI) not in sys.path: + sys.path.insert(0, str(_HWCI)) + +from hwci.debugger.openocd import G4_APP_LOAD_ADDR, G4_CONFIGS, OpenOcdDebugger +from hwci.elf import find_symbol + +# STM32G4: PERIPH_BASE 0x40000000 + AHB1 0x20000; channel 1 regs at +0x08, +# CNDTR at +0x04 within the channel block (stm32g431xx.h DMA1_Channel1_BASE). +DMA1_CHANNEL1_BASE = 0x40020008 +DMA1_C1_CNDTR = DMA1_CHANNEL1_BASE + 0x04 + +DSHOT_BUFFERSIZE = 32 # edges per frame capture (16 bits, both edges) + +# Symbols read every sample (cheap) and only on trigger (dma_buffer). +FAST_SYMS = ( + "zero_input_count", + "signaltimeout", + "send_telemetry", + "newinput", + "adjusted_input", + "running", + "armed", + "dshot_goodcounts", + "dshot_badcounts", + "dshot_idle_exit_blocked", + "dshot_frametime_low", +) +# NOT static: every one of these is written by detectInput()/checkDshot() and +# the learning block in transfercomplete(), all of which run AFTER attach. +# Sampling them once at t=0 reads post-boot defaults (dshot=0, inputSet=0, +# ic_psc=CPU/6, frametime_high=50000) and makes the gate window look wide +# open, which is exactly the bug the first version of this script had. +SLOW_SYMS = ( + "dshot_frametime_high", + "average_count", + "average_packet_length", + "ic_timer_prescaler", + "buffersize", + "smallestnumber", + "inputSet", + "dshot", +) +WIDTH = { + "zero_input_count": 2, + "signaltimeout": 2, + "send_telemetry": 1, + "newinput": 2, + "adjusted_input": 2, + "running": 1, + "armed": 1, + "dshot_goodcounts": 2, + "dshot_badcounts": 2, + "dshot_frametime_low": 2, + "dshot_idle_exit_blocked": 2, + "dshot_frametime_high": 2, + "average_count": 1, + "average_packet_length": 4, + "ic_timer_prescaler": 1, + "buffersize": 1, + "smallestnumber": 2, + "inputSet": 1, + "dshot": 1, +} + + +def _u(raw: bytes) -> int: + if len(raw) == 1: + return raw[0] + if len(raw) == 2: + return struct.unpack(" dict[str, int | bool]: + """Mirror computeDshotDMA()'s arithmetic exactly (Src/dshot.c:69-108).""" + span = (buf[31] - buf[0]) & 0xFFFF + half = span >> 5 + frame = 0 + for i in range(16): + pdiff = (buf[(i << 1) + 1] - buf[i << 1]) & 0xFFFF + frame = (frame << 1) | (1 if pdiff > half else 0) + calc_crc = ((frame >> 4) ^ (frame >> 8) ^ (frame >> 12)) & 0xF + return { + "span": span, + "half": half, + "frame": frame, + "crc_ok": calc_crc == (frame & 0xF), + "tocheck": frame >> 5, + "telem_req": bool(frame & 0x10), + } + + +class Watcher: + def __init__(self, dbg: OpenOcdDebugger, elf: str) -> None: + self.dbg = dbg + self.addr = {} + for name in FAST_SYMS + SLOW_SYMS + ("dma_buffer",): + self.addr[name] = find_symbol(elf, name).address + + def slow(self) -> dict[str, int]: + return {n: _u(self.dbg.read_memory(self.addr[n], WIDTH[n])) for n in SLOW_SYMS} + + def fast(self) -> dict[str, int]: + out = {n: _u(self.dbg.read_memory(self.addr[n], WIDTH[n])) for n in FAST_SYMS} + out["cndtr"] = _u(self.dbg.read_memory(DMA1_C1_CNDTR, 4)) + return out + + def dma_snapshot(self) -> list[int]: + raw = self.dbg.read_memory(self.addr["dma_buffer"], DSHOT_BUFFERSIZE * 4) + return list(struct.unpack("<%dI" % DSHOT_BUFFERSIZE, raw)) + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--duration", type=float, default=600.0) + ap.add_argument("--hz", type=float, default=120.0) + ap.add_argument( + "--elf", + default="obj/AM32_ARK_G431_CAN_3.0.1-ark.elf", + help="ELF to resolve symbols from", + ) + ap.add_argument( + "--gap-ticks", + type=int, + default=60, + help="signaltimeout (50us units) above which we grab the DMA buffer; " + "60 = ~3 ms = more than two frame periods at 800 Hz", + ) + ap.add_argument( + "--settle", + type=float, + default=4.0, + help="seconds to wait before trusting the detection/window globals", + ) + ap.add_argument("--log", type=Path, default=Path("hwci/runs/dshot_gate_watch.log")) + args = ap.parse_args() + + args.log.parent.mkdir(parents=True, exist_ok=True) + period = 1.0 / max(args.hz, 1.0) + + dbg = OpenOcdDebugger( + configs=list(G4_CONFIGS), + app_load_addr=G4_APP_LOAD_ADDR, + adapter_speed_khz=4000, + ) + print(f"opening SWD (G4)… elf={args.elf} log={args.log}") + dbg.open() + try: + w = Watcher(dbg, args.elf) + # Attach itself can re-enter the app (ensure_app_running rewrites + # pc/msp when the BL is stuck, which re-runs startup and zeroes BSS), + # so sample the detection state twice: immediately, then after the + # input-detect + 8-frame learning window has had time to complete. + # If these two differ, the app (re)started under us at attach. + at_attach = w.slow() + time.sleep(args.settle) + settled = w.slow() + hdr = ( + f"# start {time.strftime('%Y-%m-%d %H:%M:%S')} duration={args.duration} " + f"hz={args.hz} gap_ticks={args.gap_ticks} settle={args.settle}\n" + f"# at_attach: {at_attach}\n" + f"# settled : {settled}\n" + f"# restarted_at_attach={at_attach != settled}\n" + ) + print(hdr, end="", flush=True) + + t0 = time.monotonic() + n = 0 + triggers = 0 + prev_blocked: int | None = None + verdicts: dict[str, int] = {} + spans: dict[int, int] = {} + with args.log.open("a", buffering=1) as log: + log.write(hdr) + while time.monotonic() - t0 < args.duration: + now = time.monotonic() + try: + s = w.fast() + except Exception as e: # noqa: BLE001 - probe hiccup, keep going + log.write(f"{now - t0:8.3f} READ_ERR {e}\n") + time.sleep(0.05) + continue + n += 1 + # Once the idle-exit holdoff is in the firmware, newinput no + # longer reaches 2047 on this fault - the guard absorbs it. So + # trigger on the guard firing too, otherwise the very event we + # are hunting becomes invisible the moment it is fixed. + blocked = s["dshot_idle_exit_blocked"] + guard_fired = prev_blocked is not None and blocked != prev_blocked + prev_blocked = blocked + hot = ( + s["signaltimeout"] > args.gap_ticks + or s["newinput"] > 47 + or guard_fired + ) + if hot: + triggers += 1 + b1 = w.dma_snapshot() + c1 = _u(dbg.read_memory(DMA1_C1_CNDTR, 4)) + b2 = w.dma_snapshot() + sl_now = w.slow() # window is learned at runtime; re-read it + d = decode_frame(b1) + lo = s["dshot_frametime_low"] + hi = sl_now["dshot_frametime_high"] + in_win = lo < d["span"] < hi + moving = b1 != b2 + if not moving and s["cndtr"] == DSHOT_BUFFERSIZE: + verdict = "NO_EDGES" + elif not moving and 0 < s["cndtr"] < DSHOT_BUFFERSIZE: + verdict = "EDGES_STALLED_MIDFRAME" + elif in_win: + verdict = "GATE_PASS" + else: + verdict = "GATE_REJECT_SPAN" + verdicts[verdict] = verdicts.get(verdict, 0) + 1 + spans[d["span"]] = spans.get(d["span"], 0) + 1 + line = ( + f"{now - t0:8.3f} {verdict:22s} sigto={s['signaltimeout']:5d} " + f"cndtr={s['cndtr']:2d}/{c1:2d} moving={int(moving)} " + f"span={d['span']:5d} win=[{lo},{hi}] in_win={int(in_win)} " + f"frame=0x{d['frame']:04X} crc={int(d['crc_ok'])} " + f"tocheck={d['tocheck']:4d} telem={int(d['telem_req'])} " + f"new={s['newinput']:4d} adj={s['adjusted_input']:4d} " + f"run={s['running']} armed={s['armed']} " + f"good={s['dshot_goodcounts']} bad={s['dshot_badcounts']} " + f"blocked={s['dshot_idle_exit_blocked']} " + f"dshot={sl_now['dshot']} inSet={sl_now['inputSet']} " + f"psc={sl_now['ic_timer_prescaler']}\n" + ) + log.write(line) + if guard_fired: + verdicts["IDLE_EXIT_BLOCKED"] = verdicts.get("IDLE_EXIT_BLOCKED", 0) + 1 + if s["newinput"] > 47 or guard_fired: + print(line, end="", flush=True) + sl = period - (time.monotonic() - now) + if sl > 0: + time.sleep(sl) + + top = sorted(spans.items(), key=lambda kv: -kv[1])[:12] + summary = ( + f"# done samples={n} triggers={triggers} " + f"elapsed={time.monotonic() - t0:.1f}s\n" + f"# verdicts: {verdicts}\n" + f"# top spans (span:count): {top}\n" + ) + print(summary, end="", flush=True) + log.write(summary) + finally: + try: + dbg.close() + except Exception: # noqa: BLE001 + pass + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/hwci/tests/test_app_alive.py b/hwci/tests/test_app_alive.py index 2d5805632..6864dd3cd 100644 --- a/hwci/tests/test_app_alive.py +++ b/hwci/tests/test_app_alive.py @@ -122,6 +122,52 @@ def test_never_alive_raises_actionable_error(monkeypatch): assert reader.resets == 2 # both attempts exhausted +def test_elf_has_embedded_bl_fails_closed_on_unreadable_elf(): + from hwci.runner import _elf_has_embedded_bl + assert _elf_has_embedded_bl("/no/such/firmware.elf") is True + + +def test_embedded_bl_grace_skips_reset_if_app_comes_up(monkeypatch): + # G431 HWCI keeps .bl_image. After flash the app may be rewriting the + # on-chip BL with IRQs off; a reset_run in that window is a brick. + # If the app becomes alive during the grace poll, never reset. + import hwci.runner as runner + monkeypatch.setattr(runner.time, "sleep", lambda s: None) + clock = (i * 0.2 for i in range(10_000)) + monkeypatch.setattr(runner.time, "monotonic", lambda: next(clock)) + + class DelayedAliveReader: + def __init__(self): + self.reads = 0 + self._iters = 0 + + def read(self): + self.reads += 1 + if self.reads >= 4: + self._iters += 1 + return FakeSample(self._iters, 25.2) + raise PerfDecodeError("rewriting") + + reader = DelayedAliveReader() + dbg, throttle = FakeDbg(FakeReader(0)), FakeThrottle() + _ensure_app_alive(dbg, reader, throttle, embed_bl=True) + assert dbg._reader.resets == 0 + assert throttle.commands == [] + + +def test_embedded_bl_grace_then_reset_if_still_dead(monkeypatch): + import hwci.runner as runner + monkeypatch.setattr(runner.time, "sleep", lambda s: None) + clock = iter(range(0, 10_000)) + monkeypatch.setattr(runner.time, "monotonic", lambda: float(next(clock))) + + reader = FakeReader(alive_after_resets=1) + dbg, throttle = FakeDbg(reader), FakeThrottle() + _ensure_app_alive(dbg, reader, throttle, embed_bl=True) + assert reader.resets == 1 + assert throttle.commands == ["quiesce"] + + def test_debugger_error_propagates(): class DeadProbeReader: def read(self): diff --git a/hwci/tests/test_build.py b/hwci/tests/test_build.py new file mode 100644 index 000000000..0d8c895b8 --- /dev/null +++ b/hwci/tests/test_build.py @@ -0,0 +1,29 @@ +"""Firmware artifact picker: never flash EEPROM/factory sidecars as the app.""" +import time + +from hwci.build import find_artifact + + +def test_find_artifact_skips_eeprom_and_factory_sidecars(tmp_path): + target = "ARK_G431_CAN" + # Makefile IDENTIFIER := ARK32 → obj/ARK32__.{bin,hex,elf} + prefix = f"ARK32_{target}" + eeprom = tmp_path / f"{prefix}_3.0.3-ark.eeprom.bin" + factory_hex = tmp_path / f"{prefix}_3.0.3-ark.factory.hex" + uavcan = tmp_path / f"{prefix}_3.0.3-ark.uavcan.bin" + app_bin = tmp_path / f"{prefix}_3.0.3-ark.bin" + app_hex = tmp_path / f"{prefix}_3.0.3-ark.hex" + older = tmp_path / f"{prefix}_3.0.2-ark.bin" + for p, n in ((eeprom, 2048), (factory_hex, 64), (uavcan, 64), + (older, 64), (app_bin, 128), (app_hex, 64)): + p.write_bytes(b"\x00" * n) + time.sleep(0.01) + older.write_bytes(b"\x00" * 64) # older mtime if the loop order varies + # Ensure the 3.0.3 app is newest among the real images. + time.sleep(0.01) + app_bin.write_bytes(b"\x11" * 128) + app_hex.write_bytes(b"\x11" * 64) + + assert find_artifact(tmp_path, target, "bin") == app_bin + assert find_artifact(tmp_path, target, "hex") == app_hex + assert find_artifact(tmp_path, target, "elf") is None diff --git a/hwci/tests/test_config.py b/hwci/tests/test_config.py index e7a5a7820..6874fb2f3 100644 --- a/hwci/tests/test_config.py +++ b/hwci/tests/test_config.py @@ -1,8 +1,10 @@ """Tests for strict rig-config validation and profile (de)serialization.""" import pytest -from hwci.config import (RigConfig, load_profile, load_rig, +from hwci.config import (RigConfig, apply_target_preset, load_profile, load_rig, profile_from_dict, profile_to_dict) +from hwci.debugger.openocd import (APP_LOAD_ADDR, DEFAULT_CONFIGS, + G4_APP_LOAD_ADDR, G4_CONFIGS) VALID_RIG = """\ target: ARK_4IN1_F051 @@ -24,6 +26,9 @@ def test_valid_rig_loads(tmp_path): rig = load_rig(_write(tmp_path, VALID_RIG)) assert rig.debugger_backend == "openocd" assert rig.pole_pairs == 11 + # F051 preset fills SWD settings when omitted from YAML. + assert rig.app_load_addr == APP_LOAD_ADDR + assert list(rig.openocd_configs) == list(DEFAULT_CONFIGS) def test_unknown_key_rejected(tmp_path): @@ -70,6 +75,48 @@ def test_no_config_gives_sim_defaults(): rig.validate() # sim allowed for the built-in default +def test_g431_preset_fills_swd_settings(tmp_path): + text = """\ +target: ARK_G431_CAN +debugger_backend: openocd +telem_backend: none +debug_uart_backend: serial +debug_uart_port: /dev/esc-debug-uart +throttle_backend: flightstand +stand_backend: grpc +stand_host: 127.0.0.1 +pole_pairs: 7 +""" + rig = load_rig(_write(tmp_path, text)) + assert rig.app_load_addr == G4_APP_LOAD_ADDR + assert list(rig.openocd_configs) == list(G4_CONFIGS) + assert rig.adapter_speed_khz == 8000 + assert rig.debug_uart_backend == "serial" + assert rig.debug_uart_baud == 115200 + + +def test_g431_explicit_app_load_not_overwritten(tmp_path): + text = """\ +target: ARK_G431_CAN +debugger_backend: openocd +telem_backend: none +throttle_backend: external +stand_backend: none +app_load_addr: 0x08005000 +""" + rig = load_rig(_write(tmp_path, text)) + assert rig.app_load_addr == 0x08005000 + # openocd still comes from preset + assert list(rig.openocd_configs) == list(G4_CONFIGS) + + +def test_apply_target_preset_manual(): + cfg = RigConfig(target="ARK_G431_CAN") + apply_target_preset(cfg, explicit_keys=set()) + assert cfg.app_load_addr == G4_APP_LOAD_ADDR + assert list(cfg.openocd_configs) == list(G4_CONFIGS) + + def test_profile_roundtrips_through_dict(): p = load_profile("demag_step_stress") q = profile_from_dict(profile_to_dict(p)) diff --git a/hwci/tests/test_debug_uart.py b/hwci/tests/test_debug_uart.py new file mode 100644 index 000000000..9bbfa9016 --- /dev/null +++ b/hwci/tests/test_debug_uart.py @@ -0,0 +1,48 @@ +"""Unit tests for G4 debug UART line classification (no serial hardware).""" +from hwci.debug_uart import DebugUartLine, _classify, resolve_stlink_vcp + + +def test_classify_fault(): + r = _classify("fault: nFAULT", 1.0) + assert r.kind == "fault" + assert r.fault == "nFAULT" + + +def test_classify_desync(): + r = _classify("fault: desync", 2.0) + assert r.kind == "fault" + assert r.fault == "desync" + + +def test_classify_state(): + r = _classify("esc: DISARMED -> ARMED", 3.0) + assert r.kind == "state" + assert r.state_from == "DISARMED" + assert r.state_to == "ARMED" + + +def test_classify_boot_banner(): + r = _classify("ARK_G431_CAN debug UART @ 115200 (PB3/USART2)", 0.1) + assert r.kind == "boot" + + +def test_classify_raw(): + r = _classify("dbg: queue overflow", 4.0) + assert r.kind == "raw" + + +def test_resolve_stlink_vcp_missing_returns_none(tmp_path, monkeypatch): + # Prefer a non-existent path; if no real ST-Link by-id either, None. + missing = str(tmp_path / "nope") + # Force by-id empty by pointing resolve only at prefer. + got = resolve_stlink_vcp(missing) + # Prefer does not exist; may still find a real ST-Link on this host. + if got is not None: + assert "STLINK" in got.upper() or got.endswith("esc-debug-uart") or "ttyACM" in got + else: + assert got is None + + +def test_debug_uart_line_fields(): + r = DebugUartLine(t_mono=1.5, text="fault: stuck", kind="fault", fault="stuck") + assert r.fault == "stuck" diff --git a/scripts/build_factory_image.py b/scripts/build_factory_image.py index e13a723c1..43764520b 100755 --- a/scripts/build_factory_image.py +++ b/scripts/build_factory_image.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 -"""Build a production full-flash image for ARK F051 ESCs. +"""Build a production full-flash image for ARK ESCs. -Assembles a single binary that covers the entire STM32F051 32 KiB flash map: +Assembles a single binary that covers the whole MCU flash map: - 0x08000000 bootloader region (4 KiB) — ARK32-bootloader .bin, 0xFF padded - 0x08001000 application region (27 KiB) — make ARK_4IN1_F051 .bin, 0xFF padded - 0x08007C00 EEPROM settings page (1 KiB) — factory defaults, 0xFF padded + bootloader region — optional .bin (0xFF padded if omitted / short) + application region — make .bin, 0xFF padded + EEPROM page — factory defaults from JSON, 0xFF padded -Production used to: flash BL, flash app, write EEPROM via AM32 configurator, -tweak settings, then ST-Link dump the whole chip. This script produces that -same image from the repo so a release is one `make factory-image` artifact. +Default layout is the STM32F051 32 KiB ARK 4IN1 map. Products with a +``flash_map`` object in their defaults JSON (e.g. ARK_G431_CAN) override it. EEPROM field encodings match Inc/eeprom.h and Src/settings.c. """ @@ -23,19 +22,22 @@ import sys from pathlib import Path +_SCRIPTS = Path(__file__).resolve().parent +if str(_SCRIPTS) not in sys.path: + sys.path.insert(0, str(_SCRIPTS)) +from px4_uavcan_image import DESC_LEN, find_descriptor, unpack_desc + # STM32F051 flash map used by ARK_4IN1_F051 (Inc/targets.h, STM32F051K6TX_FLASH.ld) -FLASH_SIZE = 32 * 1024 -BL_OFFSET = 0x0000 -BL_REGION_SIZE = 4 * 1024 -APP_OFFSET = 0x1000 -APP_REGION_SIZE = 27 * 1024 # ends at EEPROM_OFFSET -EEPROM_OFFSET = 0x7C00 -EEPROM_PAGE_SIZE = 1 * 1024 -EEPROM_SETTINGS_SIZE = 192 +DEFAULT_FLASH_MAP = { + "flash_size": 32 * 1024, + "bl_offset": 0x0000, + "bl_region_size": 4 * 1024, + "app_offset": 0x1000, + "eeprom_offset": 0x7C00, + "eeprom_page_size": 1 * 1024, +} -assert BL_REGION_SIZE + APP_REGION_SIZE + EEPROM_PAGE_SIZE == FLASH_SIZE -assert APP_OFFSET == BL_OFFSET + BL_REGION_SIZE -assert EEPROM_OFFSET == APP_OFFSET + APP_REGION_SIZE +EEPROM_SETTINGS_SIZE = 192 class FactoryImageError(SystemExit): @@ -54,13 +56,43 @@ def one(name: str) -> int: return one("VERSION_MAJOR"), one("VERSION_MINOR"), one("EEPROM_VERSION") +def resolve_flash_map(defaults: dict) -> dict: + """Merge product flash_map over F051 defaults; validate sizes.""" + m = dict(DEFAULT_FLASH_MAP) + raw = defaults.get("flash_map") or {} + for k in DEFAULT_FLASH_MAP: + if k in raw: + m[k] = int(raw[k]) + flash = m["flash_size"] + bl_off = m["bl_offset"] + bl_sz = m["bl_region_size"] + app_off = m["app_offset"] + ee_off = m["eeprom_offset"] + ee_sz = m["eeprom_page_size"] + if bl_off != 0: + raise FactoryImageError("flash_map.bl_offset must be 0") + if app_off != bl_off + bl_sz: + raise FactoryImageError( + f"flash_map: app_offset 0x{app_off:X} != bl_offset+bl_region_size " + f"0x{bl_off + bl_sz:X}" + ) + if ee_off + ee_sz > flash: + raise FactoryImageError( + f"flash_map: eeprom ends past flash_size " + f"(0x{ee_off + ee_sz:X} > 0x{flash:X})" + ) + if ee_off < app_off: + raise FactoryImageError("flash_map: eeprom_offset before app_offset") + m["app_region_size"] = ee_off - app_off + return m + + def encode_motor_kv(kv: int) -> int: """eeprom motor_kv byte: runtime kv = eeprom * 40 + 20.""" if kv < 20 or kv > 10220: raise FactoryImageError(f"motor_kv {kv} out of range 20..10220") stored = (kv - 20) // 40 if stored * 40 + 20 != kv: - # nearest legal value that encodes exactly nearest = stored * 40 + 20 raise FactoryImageError( f"motor_kv {kv} is not representable exactly " @@ -73,12 +105,8 @@ def encode_max_ramp_percent_per_ms(pct: float) -> int: """eeprom max_ramp: for values >= 10, storage is percent_per_ms * 10.""" if pct <= 0 or pct > 25.5: raise FactoryImageError(f"max_ramp_percent_per_ms {pct} out of range") - # Prefer the coarse form (value >= 10) used by the configurator for - # whole-percent rates: 2.0 %/ms -> 20. stored = int(round(pct * 10)) if stored < 10: - # fine mode is < 10 raw units (0.1 %/ms with ramp_divider); allow - # explicit sub-1%/ms via fractional pct if someone needs it. fine = int(round(pct * 10)) if fine < 1 or fine > 9: raise FactoryImageError( @@ -101,12 +129,10 @@ def encode_advance_degrees(degrees: int) -> int: """ if degrees < 0 or degrees > 30: raise FactoryImageError(f"advance_degrees {degrees} out of range 0..30") - # exact integer temp that best matches: temp = round(deg * 64 / 60) temp = int(round(degrees * 64 / 60.0)) if temp > 32: temp = 32 - stored = temp + 10 - return stored + return temp + 10 def encode_servo_low_us(us: int) -> int: @@ -136,10 +162,14 @@ def encode_servo_neutral_us(us: int) -> int: def build_eeprom_page(defaults: dict, version_major: int, version_minor: int, - eeprom_version: int) -> bytes: - """Build a 1 KiB EEPROM page (192-byte settings + 0xFF pad).""" + eeprom_version: int, page_size: int) -> bytes: + """Build an EEPROM page (192-byte settings + 0xFF pad to page_size).""" + if page_size < EEPROM_SETTINGS_SIZE: + raise FactoryImageError( + f"eeprom_page_size {page_size} < settings size {EEPROM_SETTINGS_SIZE}" + ) s = defaults["settings"] - buf = bytearray([0xFF] * EEPROM_PAGE_SIZE) + buf = bytearray([0xFF] * page_size) # Offsets from Inc/eeprom.h. Start from upstream configurator skeleton # (Src/DroneCAN/DroneCAN.c default_settings) then overlay ARK production @@ -200,6 +230,11 @@ def put(off: int, val: int) -> None: put(45, int(s["sine_mode_power"])) put(46, int(s["input_type"])) put(47, int(s["auto_advance"])) + # 184: thermal foldback width. Outside the 48-byte configurator skeleton, + # so a product that omits it keeps 0xFF and settings.c applies the 15 C + # default - the same value a DroneCAN param erase leaves behind. + if "temperature_derate_band" in s: + put(184, int(s["temperature_derate_band"])) return bytes(buf) @@ -234,21 +269,26 @@ def place_region(image: bytearray, offset: int, region_size: int, data: bytes, f"(offset 0x{offset:04X})" ) image[offset:offset + len(data)] = data - # remainder of region already 0xFF -def build_full_image(bootloader: bytes, app: bytes, eeprom_page: bytes) -> bytes: - image = bytearray([0xFF] * FLASH_SIZE) - place_region(image, BL_OFFSET, BL_REGION_SIZE, bootloader, "bootloader") - place_region(image, APP_OFFSET, APP_REGION_SIZE, app, "application") - place_region(image, EEPROM_OFFSET, EEPROM_PAGE_SIZE, eeprom_page, "eeprom") +def build_full_image(bootloader: bytes, app: bytes, eeprom_page: bytes, + fmap: dict) -> bytes: + image = bytearray([0xFF] * fmap["flash_size"]) + place_region( + image, fmap["bl_offset"], fmap["bl_region_size"], bootloader, "bootloader" + ) + place_region( + image, fmap["app_offset"], fmap["app_region_size"], app, "application" + ) + place_region( + image, fmap["eeprom_offset"], fmap["eeprom_page_size"], eeprom_page, "eeprom" + ) return bytes(image) def write_intel_hex(path: Path, data: bytes, base: int = 0x08000000) -> None: """Minimal I32HEX writer for a contiguous flash image.""" lines: list[str] = [] - # extended linear address for 0x08000000 ela = (base >> 16) & 0xFFFF lines.append(_hex_record(0, 0x04, struct.pack(">H", ela))) offset = base & 0xFFFF @@ -256,7 +296,6 @@ def write_intel_hex(path: Path, data: bytes, base: int = 0x08000000) -> None: while i < len(data): chunk = data[i:i + 16] addr = (offset + i) & 0xFFFF - # cross 64K boundary abs_addr = base + i if i > 0 and (abs_addr & 0xFFFF) == 0: ela = (abs_addr >> 16) & 0xFFFF @@ -278,20 +317,23 @@ def main(argv: list[str] | None = None) -> int: p = argparse.ArgumentParser(description=__doc__) p.add_argument("--defaults", type=Path, required=True, help="factory/*_eeprom_defaults.json") - p.add_argument("--bootloader", type=Path, required=True, - help="Bootloaders/AM32_F051_BOOTLOADER_*.bin") + p.add_argument("--bootloader", type=Path, default=None, + help="Bootloaders/*.bin (optional: region filled with 0xFF)") p.add_argument("--app", type=Path, required=True, - help="obj/ARK32_ARK_4IN1_F051_*.bin application image") + help="obj/ARK32__*.bin application image") p.add_argument("--version-h", type=Path, default=Path("Inc/version.h")) p.add_argument("--out-bin", type=Path, required=True, - help="full 32 KiB flash image (.bin)") + help="full flash image (.factory.img — not .bin; PX4 globs every *.bin)") p.add_argument("--out-hex", type=Path, default=None, help="optional Intel HEX of the full image") p.add_argument("--out-eeprom", type=Path, default=None, - help="optional 1 KiB eeprom page dump") + help="optional eeprom page dump") + p.add_argument("--allow-empty-bootloader", action="store_true", + help="permit missing --bootloader (0xFF-padded BL region)") args = p.parse_args(argv) defaults = json.loads(args.defaults.read_text(encoding="utf-8")) + fmap = resolve_flash_map(defaults) ver_maj, ver_min, eeprom_ver_h = _read_version_h(args.version_h) eeprom_ver = int(defaults.get("eeprom_version", eeprom_ver_h)) if eeprom_ver != eeprom_ver_h: @@ -301,11 +343,44 @@ def main(argv: list[str] | None = None) -> int: file=sys.stderr, ) - eeprom_page = build_eeprom_page(defaults, ver_maj, ver_min, eeprom_ver) - bootloader = args.bootloader.read_bytes() - app = args.app.read_bytes() + eeprom_page = build_eeprom_page( + defaults, ver_maj, ver_min, eeprom_ver, fmap["eeprom_page_size"] + ) + + if args.bootloader is not None: + if not args.bootloader.is_file(): + raise FactoryImageError(f"bootloader not found: {args.bootloader}") + bootloader = args.bootloader.read_bytes() + elif args.allow_empty_bootloader: + bootloader = b"" + print( + "warning: no bootloader; BL region will be 0xFF " + "(flash BL separately before shipping)", + file=sys.stderr, + ) + else: + raise FactoryImageError( + "bootloader required (pass --bootloader or --allow-empty-bootloader)" + ) - image = build_full_image(bootloader, app, eeprom_page) + app = args.app.read_bytes() + image = build_full_image(bootloader, app, eeprom_page, fmap) + + # PX4 migrateFWFromRoot globs strstr(name, ".bin") on the SD-card root and + # scans the whole file (its "1 KiB" limit is a no-op). A factory image + # named *.bin that carries a live APDescriptor is copied to /ufw/.bin + # and flashed at the *app* base — 128 KiB off the end of G431 flash. + if args.out_bin.name.endswith(".bin"): + off = find_descriptor(image) + if off >= 0: + d = unpack_desc(image[off : off + DESC_LEN]) + if d["image_crc"] != 0: + raise FactoryImageError( + f"{args.out_bin}: PX4 will ingest any SD-card-root *.bin " + f"with a live APDescriptor (found at 0x{off:x}, " + f"board_id={d['board_id']}). Write this image as " + f".factory.img (or .factory.hex), never .bin." + ) args.out_bin.parent.mkdir(parents=True, exist_ok=True) args.out_bin.write_bytes(image) @@ -325,8 +400,10 @@ def main(argv: list[str] | None = None) -> int: for line in decode_summary(eeprom_page): print(f" {line}") print( - f"regions: bl={len(bootloader)}B app={len(app)}B " - f"eeprom_settings={EEPROM_SETTINGS_SIZE}B" + f"regions: bl={len(bootloader)}B/{fmap['bl_region_size']}B " + f"app={len(app)}B/{fmap['app_region_size']}B " + f"eeprom@0x{fmap['eeprom_offset']:X} " + f"settings={EEPROM_SETTINGS_SIZE}B page={fmap['eeprom_page_size']}B" ) return 0 diff --git a/scripts/check-codegen-ark.sh b/scripts/check-codegen-ark.sh index 892d9d7b4..e14c1353e 100755 --- a/scripts/check-codegen-ark.sh +++ b/scripts/check-codegen-ark.sh @@ -136,6 +136,7 @@ check_section() { # them without __attribute__((used)). check_section .file_name required "LTO dropped its contents (object needs __attribute__((used)))" check_section .app_signature optional "LTO dropped its contents (object needs __attribute__((used)))" +check_section .px4_app_descriptor optional "LTO dropped its contents (object needs __attribute__((used)))" # Soft-reset cookie / retained SRAM (read+written from C). Empty here usually # means the cookie was removed or the linker script changed — not LTO DCE. check_section .noinit required "expected boot_sound_cookie (or other .noinit) — cookie removed or linker script changed?" diff --git a/scripts/check-erase-defaults.py b/scripts/check-erase-defaults.py new file mode 100755 index 000000000..a15a68190 --- /dev/null +++ b/scripts/check-erase-defaults.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +"""Gate: a DroneCAN "restore defaults" must land on the shipped protection. + +Three copies of the same numbers have to agree, or a field param-erase +silently changes a shipped ESC's protection envelope: + + 1. Inc/targets.h TARGET_DEFAULT_{TEMPERATURE_LIMIT,CURRENT_LIMIT, + TEMP_DERATE_BAND} for the product + 2. Src/DroneCAN/DroneCAN.c default_settings[43]/[44] - the bytes an ERASE + memcpy's back over the page (must be literals: + Mcu/SITL/sitl_params.py parses this array) + 3. factory/_eeprom_defaults.json what production actually flashes + +Byte 184 (the foldback band) lives past the 48-byte skeleton and is written +by apply_post_skeleton_defaults() from the macro, so it is checked against +the macro and the JSON but not against the array. + +Only DroneCAN products can reach the erase path, so only those are checked; +the array is compiled into DroneCAN.c and is unreachable on the 4IN1. + +Usage: scripts/check-erase-defaults.py [PRODUCT ...] (default: ARK_G431_CAN) +""" + +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + +# (macro suffix, DroneCAN.c eeprom offset or None, JSON key, JSON scale) +# scale converts the human JSON unit to the stored byte; max_ramp is the one +# field the JSON expresses in %/ms rather than raw counts. +FIELDS = ( + ("TEMPERATURE_LIMIT", 43, "temperature_limit", 1), + ("CURRENT_LIMIT", 44, "current_limit", 1), + ("TEMP_DERATE_BAND", None, "temperature_derate_band", 1), + # offset None: default_settings[5] still carries upstream's 160 on + # purpose (see the MAX_RAMP note in DroneCAN.c), so only the macro and + # the product JSON are held together here. + ("MAX_RAMP", None, "max_ramp_percent_per_ms", 10), +) + + +def target_macros(product: str) -> dict[str, int]: + """macros defined inside the product's #ifdef block in targets.h""" + text = (ROOT / "Inc" / "targets.h").read_text(encoding="utf-8") + start = text.find("#ifdef %s\n" % product) + if start < 0: + raise SystemExit("error: no #ifdef %s in Inc/targets.h" % product) + # the block ends at the next top-level #ifdef, which is how the file is + # organised (one block per board, no nesting at column 0) + nxt = text.find("\n#ifdef ", start + 1) + block = text[start:nxt if nxt > 0 else len(text)] + out = {} + for suffix, _off, _key, _scale in FIELDS: + m = re.search(r"#\s*define\s+TARGET_DEFAULT_%s\s+(\d+)" % suffix, block) + if m: + out[suffix] = int(m.group(1)) + return out + + +def erase_bytes() -> bytes: + text = (ROOT / "Src" / "DroneCAN" / "DroneCAN.c").read_text(encoding="utf-8") + m = re.search(r"default_settings\[\]\s*=\s*\{(.*?)\}\s*;", text, re.S) + if not m: + raise SystemExit("error: no default_settings[] in Src/DroneCAN/DroneCAN.c") + return bytes(int(v, 16) for v in re.findall(r"0x([0-9a-fA-F]+)", m.group(1))) + + +def check(product: str) -> list[str]: + errors: list[str] = [] + macros = target_macros(product) + arr = erase_bytes() + jpath = ROOT / "factory" / ("%s_eeprom_defaults.json" % product) + if not jpath.is_file(): + return ["%s: missing %s" % (product, jpath)] + settings = json.loads(jpath.read_text(encoding="utf-8"))["settings"] + + for suffix, off, key, scale in FIELDS: + macro = "TARGET_DEFAULT_%s" % suffix + if suffix not in macros: + errors.append( + "%s: %s not defined in its targets.h block - an erase would " + "restore the generic default, not the shipped value" + % (product, macro)) + continue + want = macros[suffix] + if key not in settings: + errors.append("%s: factory JSON has no %s (macro says %d)" + % (product, key, want)) + else: + shipped = int(round(float(settings[key]) * scale)) + if shipped != want: + errors.append("%s: factory JSON %s=%s (stored %d) but %s=%d" + % (product, key, settings[key], shipped, macro, want)) + if off is not None: + if off >= len(arr): + errors.append("%s: default_settings[] too short for byte %d" + % (product, off)) + elif arr[off] != want: + errors.append( + "%s: default_settings[%d]=%d but %s=%d - a param ERASE " + "would not restore the shipped value" + % (product, off, arr[off], macro, want)) + + # An erase must leave both limiters ARMED, per Src/settings.c ranges. + t = macros.get("TEMPERATURE_LIMIT") + c = macros.get("CURRENT_LIMIT") + if t is not None and not (70 <= t <= 140): + errors.append("%s: TARGET_DEFAULT_TEMPERATURE_LIMIT=%d is outside the " + "70..140 settings.c arms - erase would disable the " + "thermal derate" % (product, t)) + if c is not None and not (0 < c <= 100): + errors.append("%s: TARGET_DEFAULT_CURRENT_LIMIT=%d is outside the " + "1..100 settings.c arms - erase would disable the " + "current limiter" % (product, c)) + return errors + + +def main(argv: list[str]) -> int: + products = argv[1:] or ["ARK_G431_CAN"] + errors: list[str] = [] + for p in products: + errors += check(p) + if errors: + print("erase-defaults check FAILED:", file=sys.stderr) + for e in errors: + print(" - %s" % e, file=sys.stderr) + return 1 + arr = erase_bytes() + for p in products: + m = target_macros(p) + print("OK %s: erase restores thermal %d C over %d C band, current %d A" + % (p, m["TEMPERATURE_LIMIT"], m["TEMP_DERATE_BAND"], + m["CURRENT_LIMIT"] * 2)) + # Said separately because it is NOT restored: the array still carries + # upstream's value, so an erase changes the ramp the product ships. + print(" ramp: ships %.1f %%/ms, erase writes %.1f %%/ms%s" + % (m["MAX_RAMP"] / 10.0, arr[5] / 10.0, + "" if arr[5] == m["MAX_RAMP"] else " <-- not restored")) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/scripts/check-factory-image-ark.sh b/scripts/check-factory-image-ark.sh index e9946ed33..9b9b1fd51 100755 --- a/scripts/check-factory-image-ark.sh +++ b/scripts/check-factory-image-ark.sh @@ -1,93 +1,221 @@ #!/usr/bin/env bash -# Verify the ARK 4IN1 factory full-flash image built by `make factory-image`. +# Verify an ARK factory full-flash image built by `make factory-image*`. # Intended for CI: fails on wrong size, missing regions, or ship-default drift. +# +# Env overrides (or defaults for ARK_4IN1_F051): +# FACTORY_PRODUCT e.g. ARK_4IN1_F051 or ARK_G431_CAN +# FACTORY_DEFAULTS path to factory/*_eeprom_defaults.json +# BL_IMAGE optional bootloader .bin (omit / empty = allow 0xFF BL) +# BL_IMAGE_F051 fallback default for ARK_4IN1_F051 when BL_IMAGE unset set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" OBJ="${OBJ:-obj}" +PRODUCT="${FACTORY_PRODUCT:-ARK_4IN1_F051}" +DEFAULTS_JSON="${FACTORY_DEFAULTS:-factory/${PRODUCT}_eeprom_defaults.json}" +BL="${BL_IMAGE:-}" + +# Default BL for the classic F051 product when caller did not set BL_IMAGE. # Must match Makefile BL_IMAGE_F051 (ARK 4IN1 nSLEEP-off BL, not generic PB4). -BL="${BL_IMAGE_F051:-Bootloaders/AM32_F051_BOOTLOADER_ARK4IN1_V18.bin}" -DEFAULTS_JSON="${FACTORY_DEFAULTS:-factory/ARK_4IN1_F051_eeprom_defaults.json}" +if [[ -z "$BL" && "$PRODUCT" == "ARK_4IN1_F051" ]]; then + BL="${BL_IMAGE_F051:-Bootloaders/AM32_F051_BOOTLOADER_ARK4IN1_V18.bin}" +fi shopt -s nullglob -factory_bins=("$OBJ"/ARK32_ARK_4IN1_F051_*.factory.bin) +factory_imgs=("$OBJ"/ARK32_"${PRODUCT}"_*.factory.img) # App .bin only: exclude factory, eeprom, and local experiment names (*nsleep*). app_bins=() -for f in "$OBJ"/ARK32_ARK_4IN1_F051_*.bin; do +for f in "$OBJ"/ARK32_"${PRODUCT}"_*.bin; do base="$(basename "$f")" case "$base" in - *.factory.bin|*.eeprom.bin|*factory*|*nsleep*|*hwci*) ;; - ARK32_ARK_4IN1_F051_*.bin) app_bins+=("$f") ;; + *.factory.bin|*.factory.img|*.eeprom.bin|*factory*|*nsleep*|*hwci*) ;; + ARK32_"${PRODUCT}"_*.bin) app_bins+=("$f") ;; esac done -if [[ ${#factory_bins[@]} -ne 1 ]]; then - echo "error: expected exactly one factory.bin under $OBJ, found ${#factory_bins[@]}" >&2 - printf ' %s\n' "${factory_bins[@]:-}" >&2 +if [[ ${#factory_imgs[@]} -ne 1 ]]; then + echo "error: expected exactly one ${PRODUCT} factory.img under $OBJ, found ${#factory_imgs[@]}" >&2 + printf ' %s\n' "${factory_imgs[@]:-}" >&2 exit 1 fi if [[ ${#app_bins[@]} -ne 1 ]]; then - echo "error: expected exactly one app .bin under $OBJ, found ${#app_bins[@]}" >&2 + echo "error: expected exactly one ${PRODUCT} app .bin under $OBJ, found ${#app_bins[@]}" >&2 printf ' %s\n' "${app_bins[@]:-}" >&2 exit 1 fi -if [[ ! -f "$BL" ]]; then - echo "error: bootloader image missing: $BL" >&2 - exit 1 -fi if [[ ! -f "$DEFAULTS_JSON" ]]; then echo "error: defaults JSON missing: $DEFAULTS_JSON" >&2 exit 1 fi +if [[ -n "$BL" && ! -f "$BL" ]]; then + echo "error: bootloader image missing: $BL" >&2 + exit 1 +fi -FACTORY="${factory_bins[0]}" +FACTORY="${factory_imgs[0]}" APP="${app_bins[0]}" +APP_HEX="${APP%.bin}.hex" -python3 - "$FACTORY" "$APP" "$BL" "$DEFAULTS_JSON" <<'PY' +python3 - "$FACTORY" "$APP" "$DEFAULTS_JSON" "${BL:-}" "$APP_HEX" <<'PY' import json import sys from pathlib import Path -FLASH_SIZE = 32 * 1024 -BL_OFFSET = 0x0000 -APP_OFFSET = 0x1000 -EEPROM_OFFSET = 0x7C00 -EEPROM_PAGE = 1024 +factory_path = Path(sys.argv[1]) +app_path = Path(sys.argv[2]) +defaults_path = Path(sys.argv[3]) +bl_path = Path(sys.argv[4]) if sys.argv[4] else None +app_hex_path = Path(sys.argv[5]) if len(sys.argv) > 5 and sys.argv[5] else None -factory_path, app_path, bl_path, defaults_path = map(Path, sys.argv[1:5]) -img = factory_path.read_bytes() -app = app_path.read_bytes() -bl = bl_path.read_bytes() defaults = json.loads(defaults_path.read_text(encoding="utf-8")) s = defaults["settings"] +# Default F051 map; product JSON may override (ARK_G431_CAN). +fmap = { + "flash_size": 32 * 1024, + "bl_offset": 0, + "bl_region_size": 4 * 1024, + "app_offset": 0x1000, + "eeprom_offset": 0x7C00, + "eeprom_page_size": 1 * 1024, +} +for k, v in (defaults.get("flash_map") or {}).items(): + if k.startswith("_"): + continue + if k in fmap or k in ( + "flash_size", + "bl_offset", + "bl_region_size", + "app_offset", + "eeprom_offset", + "eeprom_page_size", + ): + fmap[k] = int(v) + +FLASH_SIZE = fmap["flash_size"] +BL_OFFSET = fmap["bl_offset"] +BL_REGION = fmap["bl_region_size"] +APP_OFFSET = fmap["app_offset"] +EEPROM_OFFSET = fmap["eeprom_offset"] +EEPROM_PAGE = fmap["eeprom_page_size"] +APP_REGION = EEPROM_OFFSET - APP_OFFSET + +img = factory_path.read_bytes() +app = app_path.read_bytes() +bl = bl_path.read_bytes() if bl_path is not None else b"" + errors: list[str] = [] if len(img) != FLASH_SIZE: errors.append(f"factory image size {len(img)} != {FLASH_SIZE}") -if len(bl) > APP_OFFSET: - errors.append(f"bootloader {len(bl)} exceeds region {APP_OFFSET}") -if img[BL_OFFSET:BL_OFFSET + len(bl)] != bl: +if len(bl) > BL_REGION: + errors.append(f"bootloader {len(bl)} exceeds region {BL_REGION}") +if bl: + if img[BL_OFFSET : BL_OFFSET + len(bl)] != bl: + bl_name = bl_path.name if bl_path is not None else "Bootloaders/*.bin" + errors.append( + f"bootloader region does not match {bl_name} " + f"(factory image BL must equal BL_IMAGE)" + ) + if any(b != 0xFF for b in img[BL_OFFSET + len(bl) : APP_OFFSET]): + errors.append("gap between bootloader and app is not 0xFF-padded") +else: + if any(b != 0xFF for b in img[BL_OFFSET:APP_OFFSET]): + errors.append("empty-bootloader mode: BL region is not all 0xFF") + +if factory_path.name.endswith(".bin"): errors.append( - f"bootloader region does not match {bl_path.name} " - f"(factory image BL must equal BL_IMAGE_F051)" + f"{factory_path.name} ends in .bin; PX4 migrateFWFromRoot globs every " + "SD-card-root *.bin and would flash this full-chip image at app base. " + "Use .factory.img" ) -if any(b != 0xFF for b in img[len(bl):APP_OFFSET]): - errors.append("gap between bootloader and app is not 0xFF-padded") -if len(app) > EEPROM_OFFSET - APP_OFFSET: - errors.append(f"app {len(app)} exceeds app region") -if img[APP_OFFSET:APP_OFFSET + len(app)] != app: +if len(app) > APP_REGION: + errors.append(f"app {len(app)} exceeds app region {APP_REGION}") +if img[APP_OFFSET : APP_OFFSET + len(app)] != app: errors.append("app region does not match release .bin") -ee = img[EEPROM_OFFSET:EEPROM_OFFSET + EEPROM_PAGE] +# G431 CAN (and any product that embeds): the app .bin must carry the +# committed bootloader so first boot can rewrite the on-chip BL region. +if bl and fmap["bl_region_size"] >= 16 * 1024: + if app.find(bl) < 0: + errors.append( + "app .bin does not contain the committed bootloader image " + "(EMBED_BOOTLOADER missing or wrong BL_IMAGE)" + ) + +# objcopy -O binary fills section-gap ALIGN padding with 0x00; -O ihex omits +# those addresses. A .hex flash onto erased flash then reads 0xFF in the holes +# and the AM32 app_signature CRC fails. Gate on the signature itself (set only +# on *_CAN_* bins) — that is what makes a gap dangerous — not flash-map size. +APP_SIGNATURE_MAGIC = bytes.fromhex("e658f068a0e5ceaf") # LE 0x68f058e6, 0xafcee5a0 +has_am32_sig = app.find(APP_SIGNATURE_MAGIC) >= 0 + +def parse_ihex(text: str) -> dict[int, int]: + mem: dict[int, int] = {} + ela = 0 + for raw in text.splitlines(): + line = raw.strip() + if not line or line[0] != ":": + continue + rec = bytes.fromhex(line[1:]) + n = rec[0] + addr = (rec[1] << 8) | rec[2] + rtype = rec[3] + data = rec[4 : 4 + n] + csum = rec[4 + n] + if ((sum(rec[: 4 + n]) + csum) & 0xFF) != 0: + raise ValueError(f"bad Intel HEX checksum in {line[:20]}...") + if rtype == 0x00: + base = (ela << 16) + addr + for i, b in enumerate(data): + mem[base + i] = b + elif rtype == 0x01: + break + elif rtype == 0x04: + ela = int.from_bytes(data, "big") + return mem + +if has_am32_sig and (app_hex_path is None or not app_hex_path.is_file()): + errors.append(f"missing app hex ({app_path.with_suffix('.hex').name})") +elif has_am32_sig: + try: + hex_mem = parse_ihex(app_hex_path.read_text(encoding="ascii")) + except ValueError as exc: + errors.append(f"{app_hex_path.name}: {exc}") + hex_mem = None + if hex_mem is not None: + load_addr = 0x08000000 + APP_OFFSET + missing: list[int] = [] + mismatch: list[int] = [] + for i, b in enumerate(app): + a = load_addr + i + if a not in hex_mem: + missing.append(a) + elif hex_mem[a] != b: + mismatch.append(a) + if missing: + preview = ", ".join(f"0x{a:08X}" for a in missing[:8]) + more = f" (+{len(missing) - 8} more)" if len(missing) > 8 else "" + errors.append( + f"{app_hex_path.name} omits {len(missing)} byte(s) present in " + f"{app_path.name} (first: {preview}{more}); " + "ALIGN padding is in the .bin as 0x00 but missing from the .hex" + ) + if mismatch: + preview = ", ".join(f"0x{a:08X}" for a in mismatch[:8]) + errors.append( + f"{app_hex_path.name} differs from {app_path.name} at " + f"{len(mismatch)} address(es) (first: {preview})" + ) + +ee = img[EEPROM_OFFSET : EEPROM_OFFSET + EEPROM_PAGE] if len(ee) != EEPROM_PAGE: errors.append("eeprom page truncated") -# Encodings must match scripts/build_factory_image.py / Src/settings.c def expect(off: int, want: int, name: str) -> None: got = ee[off] if got != want: @@ -110,11 +238,33 @@ expect(23, adv_level, "advance_level") expect(26, kv_byte, "motor_kv") expect(32, servo_lo, "servo_low") expect(33, servo_hi, "servo_high") +expect(46, int(s["input_type"]), "input_type") expect(47, int(s["auto_advance"]), "auto_advance") +# Protection limits and the current loop that enforces one of them. Gated +# because "armed" and "disabled" differ by a single byte just outside a +# range check (settings.c), which is how both products shipped with the +# limiters silently off. The gate is equality with the JSON; whether that +# JSON arms them is a product decision, reported below either way. +expect(9, int(s["current_P"]), "current_P") +expect(10, int(s["current_I"]), "current_I") +expect(11, int(s["current_D"]), "current_D") +expect(43, int(s["temperature_limit"]), "temperature_limit") +expect(44, int(s["current_limit"]), "current_limit") +if "temperature_derate_band" in s: + expect(184, int(s["temperature_derate_band"]), "temp_derate_band") +elif ee[184] != 0xFF: + errors.append(f"eeprom[184] temp_derate_band: got {ee[184]} want 255 (JSON omits it)") + +t_lim = int(s["temperature_limit"]) +i_lim = int(s["current_limit"]) +band = int(s.get("temperature_derate_band", 15)) +thermal_desc = ( + f"derate {t_lim}->{t_lim + band} C" if 70 <= t_lim <= 140 else f"OFF ({t_lim})" +) +current_desc = f"{i_lim * 2} A" if 0 < i_lim <= 100 else f"OFF ({i_lim})" -# Trailing pad of the page after the 192-byte settings union should stay erased if any(b != 0xFF for b in ee[192:]): - errors.append("eeprom page bytes 192..1023 are not 0xFF") + errors.append(f"eeprom page bytes 192..{EEPROM_PAGE - 1} are not 0xFF") if errors: print("factory image check FAILED:", file=sys.stderr) @@ -123,11 +273,18 @@ if errors: sys.exit(1) print(f"OK {factory_path} ({len(img)} bytes)") -print(f" bl={len(bl)}B app={len(app)}B eeprom@0x{EEPROM_OFFSET:04X}") +print( + f" bl={len(bl)}B/{BL_REGION}B app={len(app)}B/{APP_REGION}B " + f"eeprom@0x{EEPROM_OFFSET:04X} page={EEPROM_PAGE}" +) print( f" defaults: kv={kv} max_ramp={max_ramp} var_pwm={s['variable_pwm']} " f"advance={adv_level} (~{s['advance_degrees']}°) " f"pwm={s['pwm_input_min_us']}/{s['pwm_input_max_us']} " - f"auto_advance={s['auto_advance']}" + f"input_type={s['input_type']} auto_advance={s['auto_advance']}" +) +print( + f" limits: thermal={thermal_desc} current={current_desc} " + f"current_pid={s['current_P']}/{s['current_I']}/{s['current_D']}" ) PY diff --git a/scripts/check-size-ark.sh b/scripts/check-size-ark.sh index be1cbee90..d5033c374 100755 --- a/scripts/check-size-ark.sh +++ b/scripts/check-size-ark.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Fail if the ARK F051 image exceeds flash/RAM budgets. -# Typical gate input: HWCI_PERF=1 with the default embedded bootloader (worst case). +# Gate inputs (see make size-check-ark): HWCI_PERF=1 without .bl_image, and +# release with embedded bootloader — each checked separately. # # STM32F051K6 application region (from linker script / build map): # FLASH (app): 27424 bytes diff --git a/scripts/esc_settings.py b/scripts/esc_settings.py index c7d508dfa..a2f77c673 100644 --- a/scripts/esc_settings.py +++ b/scripts/esc_settings.py @@ -69,6 +69,8 @@ (181, 'can_filter_hz', 'Hz', None), (182, 'can_debug_rate', 'Hz', None), (183, 'can_term_enable', '', None), + (184, 'temp_derate_band', 'C', None), + (185, 'can_fd_mbps', 'Mbps', None), ] EEPROM_SIZE = 192 diff --git a/scripts/px4_uavcan_image.py b/scripts/px4_uavcan_image.py new file mode 100755 index 000000000..ae380a32f --- /dev/null +++ b/scripts/px4_uavcan_image.py @@ -0,0 +1,592 @@ +#!/usr/bin/env python3 +"""PX4 SD-card DroneCAN image: sign APDescriptor and emit the UAVCAN filename. + +PX4's UAVCAN firmware updater (src/drivers/uavcan/uavcan_servers.cpp): + + * Accepts any SD-card-root (or ufw_staging/) file whose *name* contains + ".bin" (strstr) if the file contains an APDescriptor whose 8-byte + signature is {0x40,0xa2,0xe4,0xf1,0x64,0x68,0x91,0x06} and image_crc != 0. + * The getFileInfo "limit" argument is a no-op: it scans 512-byte chunks + to EOF. A 128 KiB factory image named *.bin is accepted. Do not name + full-flash images .bin — emit them as .factory.img. + * Copies a matching file to /ufw/.bin, where + board_id = (hardware_version.major << 8) | hardware_version.minor. + * Flashes any node whose GetNodeInfo hw version matches and whose + reported image_crc differs (or is 0). + +The app .uavcan.bin still keeps the descriptor in the first 1 KiB (FLASH1) +so a short read finds it; that is our layout, not a PX4 limit. + +The filename uses the numeric ship version from Inc/version.h +(MAJOR.MINOR[.PATCH], no -ark) plus the 8-hex-digit git hash already +stored in the APDescriptor (same as PX4 cannode's third field): + + -..uavcan.bin + +e.g. 71-3.0.2.59efc137.uavcan.bin (tag v3.0.2-ark, commit 59efc137) + +PX4 matches the file by the APDescriptor board_id, not by parsing the +name. The version and hash are for humans and for FW.db. + +Sign *before* Src/DroneCAN/set_app_signature.py. The AM32 bootloader CRC +covers the whole image except its own 44-byte block; filling the PX4 CRC +fields after that would invalidate the AM32 signature. PX4 itself only +compares the stored image_crc to GetNodeInfo — it does not recompute it — +so AM32-signing last (which changes bytes inside the PX4 CRC coverage) is +safe for the SD-card workflow. + +Usage: + scripts/px4_uavcan_image.py sign + scripts/px4_uavcan_image.py emit + scripts/px4_uavcan_image.py check + scripts/px4_uavcan_image.py --selftest +""" + +from __future__ import annotations + +import argparse +import struct +import subprocess +import sys +import re +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +VERSION_H = REPO_ROOT / "Inc" / "version.h" + +SIGNATURE = bytes((0x40, 0xA2, 0xE4, 0xF1, 0x64, 0x68, 0x91, 0x06)) +RESERVED = b"\xFF" * 8 +# signature[8] + crc1 + crc2 + size + git + maj + min + board + reserved[8] +DESC_LEN = 36 +# crc32_block1, crc32_block2, image_size, git_hash +CRC_FIELDS_OFF = 8 +CRC_FIELDS_LEN = 16 +# PX4 migrateFWFromRoot only scans this many bytes of an SD-card-root .bin. +PX4_SCAN_LIMIT = 1024 + +# Same table as PX4 src/drivers/bootloaders/make_can_boot_descriptor.py +_CRCTAB = ( + 0x00000000, + 0x77073096, + 0xEE0E612C, + 0x990951BA, + 0x076DC419, + 0x706AF48F, + 0xE963A535, + 0x9E6495A3, + 0x0EDB8832, + 0x79DCB8A4, + 0xE0D5E91E, + 0x97D2D988, + 0x09B64C2B, + 0x7EB17CBD, + 0xE7B82D07, + 0x90BF1D91, + 0x1DB71064, + 0x6AB020F2, + 0xF3B97148, + 0x84BE41DE, + 0x1ADAD47D, + 0x6DDDE4EB, + 0xF4D4B551, + 0x83D385C7, + 0x136C9856, + 0x646BA8C0, + 0xFD62F97A, + 0x8A65C9EC, + 0x14015C4F, + 0x63066CD9, + 0xFA0F3D63, + 0x8D080DF5, + 0x3B6E20C8, + 0x4C69105E, + 0xD56041E4, + 0xA2677172, + 0x3C03E4D1, + 0x4B04D447, + 0xD20D85FD, + 0xA50AB56B, + 0x35B5A8FA, + 0x42B2986C, + 0xDBBBC9D6, + 0xACBCF940, + 0x32D86CE3, + 0x45DF5C75, + 0xDCD60DCF, + 0xABD13D59, + 0x26D930AC, + 0x51DE003A, + 0xC8D75180, + 0xBFD06116, + 0x21B4F4B5, + 0x56B3C423, + 0xCFBA9599, + 0xB8BDA50F, + 0x2802B89E, + 0x5F058808, + 0xC60CD9B2, + 0xB10BE924, + 0x2F6F7C87, + 0x58684C11, + 0xC1611DAB, + 0xB6662D3D, + 0x76DC4190, + 0x01DB7106, + 0x98D220BC, + 0xEFD5102A, + 0x71B18589, + 0x06B6B51F, + 0x9FBFE4A5, + 0xE8B8D433, + 0x7807C9A2, + 0x0F00F934, + 0x9609A88E, + 0xE10E9818, + 0x7F6A0DBB, + 0x086D3D2D, + 0x91646C97, + 0xE6635C01, + 0x6B6B51F4, + 0x1C6C6162, + 0x856530D8, + 0xF262004E, + 0x6C0695ED, + 0x1B01A57B, + 0x8208F4C1, + 0xF50FC457, + 0x65B0D9C6, + 0x12B7E950, + 0x8BBEB8EA, + 0xFCB9887C, + 0x62DD1DDF, + 0x15DA2D49, + 0x8CD37CF3, + 0xFBD44C65, + 0x4DB26158, + 0x3AB551CE, + 0xA3BC0074, + 0xD4BB30E2, + 0x4ADFA541, + 0x3DD895D7, + 0xA4D1C46D, + 0xD3D6F4FB, + 0x4369E96A, + 0x346ED9FC, + 0xAD678846, + 0xDA60B8D0, + 0x44042D73, + 0x33031DE5, + 0xAA0A4C5F, + 0xDD0D7CC9, + 0x5005713C, + 0x270241AA, + 0xBE0B1010, + 0xC90C2086, + 0x5768B525, + 0x206F85B3, + 0xB966D409, + 0xCE61E49F, + 0x5EDEF90E, + 0x29D9C998, + 0xB0D09822, + 0xC7D7A8B4, + 0x59B33D17, + 0x2EB40D81, + 0xB7BD5C3B, + 0xC0BA6CAD, + 0xEDB88320, + 0x9ABFB3B6, + 0x03B6E20C, + 0x74B1D29A, + 0xEAD54739, + 0x9DD277AF, + 0x04DB2615, + 0x73DC1683, + 0xE3630B12, + 0x94643B84, + 0x0D6D6A3E, + 0x7A6A5AA8, + 0xE40ECF0B, + 0x9309FF9D, + 0x0A00AE27, + 0x7D079EB1, + 0xF00F9344, + 0x8708A3D2, + 0x1E01F268, + 0x6906C2FE, + 0xF762575D, + 0x806567CB, + 0x196C3671, + 0x6E6B06E7, + 0xFED41B76, + 0x89D32BE0, + 0x10DA7A5A, + 0x67DD4ACC, + 0xF9B9DF6F, + 0x8EBEEFF9, + 0x17B7BE43, + 0x60B08ED5, + 0xD6D6A3E8, + 0xA1D1937E, + 0x38D8C2C4, + 0x4FDFF252, + 0xD1BB67F1, + 0xA6BC5767, + 0x3FB506DD, + 0x48B2364B, + 0xD80D2BDA, + 0xAF0A1B4C, + 0x36034AF6, + 0x41047A60, + 0xDF60EFC3, + 0xA867DF55, + 0x316E8EEF, + 0x4669BE79, + 0xCB61B38C, + 0xBC66831A, + 0x256FD2A0, + 0x5268E236, + 0xCC0C7795, + 0xBB0B4703, + 0x220216B9, + 0x5505262F, + 0xC5BA3BBE, + 0xB2BD0B28, + 0x2BB45A92, + 0x5CB36A04, + 0xC2D7FFA7, + 0xB5D0CF31, + 0x2CD99E8B, + 0x5BDEAE1D, + 0x9B64C2B0, + 0xEC63F226, + 0x756AA39C, + 0x026D930A, + 0x9C0906A9, + 0xEB0E363F, + 0x72076785, + 0x05005713, + 0x95BF4A82, + 0xE2B87A14, + 0x7BB12BAE, + 0x0CB61B38, + 0x92D28E9B, + 0xE5D5BE0D, + 0x7CDCEFB7, + 0x0BDBDF21, + 0x86D3D2D4, + 0xF1D4E242, + 0x68DDB3F8, + 0x1FDA836E, + 0x81BE16CD, + 0xF6B9265B, + 0x6FB077E1, + 0x18B74777, + 0x88085AE6, + 0xFF0F6A70, + 0x66063BCA, + 0x11010B5C, + 0x8F659EFF, + 0xF862AE69, + 0x616BFFD3, + 0x166CCF45, + 0xA00AE278, + 0xD70DD2EE, + 0x4E048354, + 0x3903B3C2, + 0xA7672661, + 0xD06016F7, + 0x4969474D, + 0x3E6E77DB, + 0xAED16A4A, + 0xD9D65ADC, + 0x40DF0B66, + 0x37D83BF0, + 0xA9BCAE53, + 0xDEBB9EC5, + 0x47B2CF7F, + 0x30B5FFE9, + 0xBDBDF21C, + 0xCABAC28A, + 0x53B39330, + 0x24B4A3A6, + 0xBAD03605, + 0xCDD70693, + 0x54DE5729, + 0x23D967BF, + 0xB3667A2E, + 0xC4614AB8, + 0x5D681B02, + 0x2A6F2B94, + 0xB40BBE37, + 0xC30C8EA1, + 0x5A05DF1B, + 0x2D02EF8D, +) + + +def crc32_px4(data: bytes, crc: int = 0) -> int: + for byte in data: + crc = _CRCTAB[(crc ^ byte) & 0xFF] ^ (crc >> 8) + return crc & 0xFFFFFFFF + + +def unpack_desc(blob: bytes) -> dict: + if len(blob) != DESC_LEN: + raise ValueError("descriptor length") + sig, c1, c2, size, git, maj, mn, board, reserved = struct.unpack("<8sIIIIBBH8s", blob) + return { + "signature": sig, + "crc32_block1": c1, + "crc32_block2": c2, + "image_crc": c1 | (c2 << 32), + "image_size": size, + "git_hash": git, + "major_version": maj, + "minor_version": mn, + "board_id": board, + "reserved": reserved, + } + + +def pack_desc(d: dict) -> bytes: + return struct.pack( + "<8sIIIIBBH8s", + d["signature"], + d["crc32_block1"], + d["crc32_block2"], + d["image_size"], + d["git_hash"], + d["major_version"], + d["minor_version"], + d["board_id"], + d["reserved"], + ) + + +def find_descriptor(img: bytes, limit: int | None = None) -> int: + """Return 8-byte-aligned offset of APDesc00, or -1.""" + end = len(img) if limit is None else min(len(img), limit) + # Signature is 8 bytes; search 8-aligned so we match PX4's uint64 scan. + last = end - DESC_LEN + off = 0 + while off <= last: + if img[off : off + 8] == SIGNATURE: + return off + off += 8 + return -1 + + +def firmware_version(version_h: Path = VERSION_H) -> str: + """MAJOR.MINOR[.PATCH] — numeric ship version, no -ark suffix.""" + text = version_h.read_text(encoding="utf-8") + + def num(name: str) -> str | None: + m = re.search(rf"^\s*#define\s+{name}\s+(\d+)\s*$", text, re.M) + return m.group(1) if m else None + + major = num("VERSION_MAJOR") + minor = num("VERSION_MINOR") + if major is None or minor is None: + raise SystemExit(f"{version_h}: missing VERSION_MAJOR / VERSION_MINOR") + ver = f"{major}.{minor}" + patch = num("VERSION_PATCH") + if patch is not None: + ver += f".{patch}" + return ver + + +def uavcan_name(d: dict, version: str | None = None) -> str: + ver = version if version is not None else firmware_version() + return f"{d['board_id']}-{ver}.{d['git_hash']:08x}.uavcan.bin" + + +def git_abbrev8(cwd: str | None = None) -> int: + try: + out = subprocess.check_output( + ["git", "rev-list", "HEAD", "--max-count=1", "--abbrev=8", "--abbrev-commit"], + cwd=cwd, + stderr=subprocess.DEVNULL, + text=True, + ).strip() + if out: + return int(out, 16) + except (subprocess.CalledProcessError, FileNotFoundError, ValueError): + pass + return 0 + + +def apply_descriptor(img: bytearray, off: int, git_hash: int) -> dict: + d = unpack_desc(bytes(img[off : off + DESC_LEN])) + if d["signature"] != SIGNATURE: + raise ValueError("bad APDescriptor signature") + if d["reserved"] != RESERVED: + raise ValueError("APDescriptor reserved bytes must be 0xFF") + if d["board_id"] == 0: + raise ValueError("APDescriptor board_id is 0") + + d["git_hash"] = git_hash + d["image_size"] = len(img) + d["crc32_block1"] = 0 + d["crc32_block2"] = 0 + img[off : off + DESC_LEN] = pack_desc(d) + + d["crc32_block1"] = crc32_px4(bytes(img[: off + 8])) + d["crc32_block2"] = crc32_px4(bytes(img[off + CRC_FIELDS_OFF + CRC_FIELDS_LEN :])) + d["image_crc"] = d["crc32_block1"] | (d["crc32_block2"] << 32) + img[off : off + DESC_LEN] = pack_desc(d) + return d + + +def patch_elf(elf_path: Path, img: bytes, off: int) -> None: + """Copy the APDescriptor from the signed bin into the ELF.""" + elf = bytearray(elf_path.read_bytes()) + elf_off = find_descriptor(elf) + if elf_off < 0: + raise SystemExit(f"No APDescriptor found in {elf_path}") + elf[elf_off : elf_off + DESC_LEN] = img[off : off + DESC_LEN] + elf_path.write_bytes(elf) + + +def cmd_sign(bin_path: Path, elf_path: Path, git_hash: int | None) -> int: + img = bytearray(bin_path.read_bytes()) + off = find_descriptor(img) + if off < 0: + return 0 + if off + DESC_LEN > PX4_SCAN_LIMIT: + raise SystemExit( + f"APDescriptor at 0x{off:x} is past the {PX4_SCAN_LIMIT}-byte " + "window PX4 scans on the SD-card root" + ) + if git_hash is None: + git_hash = git_abbrev8() + d = apply_descriptor(img, off, git_hash) + bin_path.write_bytes(img) + patch_elf(elf_path, img, off) + print( + f"Signed PX4 APDescriptor board_id={d['board_id']} " + f"sw={d['major_version']}.{d['minor_version']} " + f"git={d['git_hash']:08x} crc={d['image_crc']:016x} " + f"at 0x{off:x} in {bin_path}" + ) + return 0 + + +def cmd_emit(bin_path: Path, version: str | None = None) -> int: + img = bin_path.read_bytes() + off = find_descriptor(img) + if off < 0: + return 0 + d = unpack_desc(img[off : off + DESC_LEN]) + if d["image_crc"] == 0: + raise SystemExit(f"{bin_path}: APDescriptor image_crc is 0 (not signed)") + dest = bin_path.parent / uavcan_name(d, version) + dest.write_bytes(img) + print(f"PX4 SD-card image {dest}") + return 0 + + +def cmd_check(bin_path: Path, version: str | None = None) -> int: + img = bin_path.read_bytes() + off = find_descriptor(img, PX4_SCAN_LIMIT) + if off < 0: + raise SystemExit(f"{bin_path}: no APDescriptor in first {PX4_SCAN_LIMIT} bytes") + d = unpack_desc(img[off : off + DESC_LEN]) + errors: list[str] = [] + if d["image_crc"] == 0: + errors.append("image_crc is 0; PX4 will ignore the file") + if d["board_id"] == 0: + errors.append("board_id is 0") + if d["reserved"] != RESERVED: + errors.append("reserved bytes are not 0xFF") + if off % 8: + errors.append(f"descriptor at 0x{off:x} is not 8-byte aligned") + # Must sit entirely inside one 512-byte PX4 read chunk. + chunk = off & ~511 + if off + DESC_LEN > chunk + 512: + errors.append(f"descriptor at 0x{off:x} straddles a 512-byte PX4 read") + + expected = bin_path.parent / uavcan_name(d, version) + if not expected.is_file(): + errors.append(f"missing {expected.name}") + else: + other = expected.read_bytes() + if other != img: + errors.append(f"{expected.name} does not match {bin_path.name}") + + if errors: + print(f"PX4 UAVCAN image check FAILED ({bin_path}):", file=sys.stderr) + for e in errors: + print(f" - {e}", file=sys.stderr) + return 1 + print( + f"OK {expected.name} board_id={d['board_id']} " + f"sw={d['major_version']}.{d['minor_version']} " + f"git={d['git_hash']:08x} off=0x{off:x}" + ) + return 0 + + +def selftest() -> int: + # 600-byte fake image: descriptor at 520 (second 512-byte chunk, first 1 KiB). + img = bytearray(600) + off = 520 + proto = { + "signature": SIGNATURE, + "crc32_block1": 0, + "crc32_block2": 0, + "image_size": 0, + "git_hash": 0, + "major_version": 3, + "minor_version": 0, + "board_id": 71, + "reserved": RESERVED, + } + img[off : off + DESC_LEN] = pack_desc(proto) + d = apply_descriptor(img, off, 0x59EFC137) + assert d["git_hash"] == 0x59EFC137 + assert d["board_id"] == 71 + assert d["image_crc"] != 0 + assert d["image_size"] == 600 + assert uavcan_name(d, "3.0.2") == "71-3.0.2.59efc137.uavcan.bin" + assert firmware_version() == "3.0.2" + assert find_descriptor(bytes(img), PX4_SCAN_LIMIT) == off + # Signature at a non-8-aligned offset must not be found (PX4 uint64 scan). + bad = bytearray(64) + bad[3 : 11] = SIGNATURE + assert find_descriptor(bytes(bad)) < 0 + print("px4_uavcan_image selftest OK") + return 0 + + +def main(argv: list[str] | None = None) -> int: + p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--selftest", action="store_true") + p.add_argument("--git-hash", dest="git_hash", default=None, help="8-hex-digit VCS commit (default: git)") + p.add_argument( + "--version", + dest="version", + default=None, + help="numeric ship version for the .uavcan.bin name (default: MAJOR.MINOR[.PATCH] from Inc/version.h)", + ) + p.add_argument("cmd", nargs="?", choices=("sign", "emit", "check")) + p.add_argument("binfile", nargs="?", type=Path) + p.add_argument("elffile", nargs="?", type=Path) + args = p.parse_args(argv) + + if args.selftest: + return selftest() + if not args.cmd or not args.binfile: + p.error("cmd and binfile required (or --selftest)") + if args.cmd == "sign": + if not args.elffile: + p.error("sign requires the elf file") + git_hash = int(args.git_hash, 16) if args.git_hash else None + return cmd_sign(args.binfile, args.elffile, git_hash) + if args.cmd == "emit": + return cmd_emit(args.binfile, args.version) + return cmd_check(args.binfile, args.version) + + +if __name__ == "__main__": + sys.exit(main())