61extern I2C_HandleTypeDef
hi2c1;
62extern UART_HandleTypeDef
huart1;
66#error "In CubeMX, assign the label IMU_INT1 to pin PA15 (GPIO_EXTI15)"
69#define FLAG_IMU_IRQ 0x0001u
75#define APP_IWDG_RELOAD 500u
76#define FC_SERVICE_MS 4u
80static osMessageQueueId_t
qTlm;
126#define IMU_STALE_MS 50u
131 CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
133 DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
138 if (pin != IMU_INT1_Pin)
return;
167 const float am = sqrtf(e->acc_mean[0]*e->acc_mean[0] + e->acc_mean[1]*e->acc_mean[1] +
168 e->acc_mean[2]*e->acc_mean[2]);
169 const float d = am - (e->f.nom_acc > 0.5f ? e->f.nom_acc : 1.0f);
176 for (
int i = 0; i < 3; ++i) {
183 memcpy(c.
q, q,
sizeof q);
194 if ((
n++ % dec) == 0)
201static void on_init(
const est_t *e,
void *user)
214 DBGMCU->APB1FZR1 |= DBGMCU_APB1FZR1_DBG_IWDG_STOP;
220 while (IWDG->SR != 0u && ++t < 100000u) { }
229 for (
size_t j = 0; j <
n; ++j) {
231 switch (ev[j].type) {
236 if (fabsf(v[0]) > 15.9f || fabsf(v[1]) > 15.9f || fabsf(v[2]) > 15.9f) {
239 for (
int i = 0; i < 3; ++i) {
295static void take_ext(uint32_t now,
float cos_tilt,
const float omega[3])
306 uint32_t q = (uint32_t)x.
squal * 100u / 120u;
308 s_flow_q = (uint8_t)(q > 100u ? 100u : q);
314 if (x.
dt_s <= 1e-3f)
continue;
319 const float k = 0.0205f / x.
dt_s;
323 float v[2] = { (fx - omega[1]) * h, (fy + omega[0]) * h };
337 static uint32_t last, lastWin, winN;
338 static uint32_t lastIrq;
339 if ((now - last) < 20u)
return;
343 if ((now - lastWin) >= 1000u) {
344 const uint32_t dtw = now - lastWin;
346 s_loop_hz = (float)(
s_est.win_count - winN) * 1000.0f / (float)dtw;
347 s_cpu_pct = (float)
s_cpu_cyc * 100.0f / ((
float)SystemCoreClock * (float)dtw * 1e-3f);
353 memset(&o, 0,
sizeof o);
360 const float *P =
s_est.f.P;
364 o.
att_unc_deg =
s_est.running ? sqrtf(m > 0.0f ? m : 0.0f) * 57.29578f : 180.0f;
393 static uint32_t last50, last10;
395 if ((now - last50) >= 20u) {
400 if ((now - last10) >= 100u) {
403 memset(&f, 0,
sizeof f);
406 const float *P =
s_est.f.P;
418 const uint32_t now = osKernelGetTickCount();
426 const float ct = 1.0f - 2.0f * (q[1]*q[1] + q[2]*q[2]);
443 static uint8_t wasArmed;
472 HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
489 memcpy(ec.
mag_map, M,
sizeof M);
514 const float cyc2s = 1.0f / (float)SystemCoreClock;
515 const uint32_t winCyc = 2u * SystemCoreClock;
517 uint32_t refCyc = 0, refN = 0, lastCyc = 0;
518 uint8_t haveRef = 0, haveEst = 0;
531 uint32_t wait_ms = 5u;
532 uint32_t lastSvc = osKernelGetTickCount();
536 const uint32_t fl = osThreadFlagsWait(
FLAG_IMU_IRQ, osFlagsWaitAny, wait_ms);
537 const int by_timeout = (fl & osFlagsError) != 0u;
542 if (words == 0) { wait_ms = 5u;
continue; }
563 const uint8_t fresh = (cyc != lastCyc);
565 if (haveRef) refN += ps.
gyro;
568 static uint32_t prevEdge;
570 const float per = (float)(uint32_t)(cyc - prevEdge) * cyc2s;
571 if (prevEdge && per < 1.5f * expP)
575 haveRef = 1; refCyc = cyc; refN = 0;
576 }
else if ((uint32_t)(cyc - refCyc) >= winCyc && refN > 0u) {
577 const float meas = (float)(uint32_t)(cyc - refCyc) * cyc2s / (
float)refN;
578 if (meas > 0.8f * dtNom && meas < 1.25f * dtNom) {
579 dtEst = haveEst ? dtEst + 0.2f * (meas - dtEst) : meas;
583 refCyc = cyc; refN = 0;
586 const float dt = dtEst;
588 const uint32_t c0 = DWT->CYCCNT;
600static void tx_frame(
const uint8_t *f, uint16_t len)
603#if APP_USB_CDC && APP_USB_TLM
610 const uint16_t
seq = (uint16_t)(f[4] | ((uint16_t)f[5] << 8));
636 st.calib_count = (uint16_t)
s_est.cal.n;
644 if (len)
tx_frame(frame, (uint16_t)len);
655 uint32_t nextStatus = osKernelGetTickCount();
658 const uint32_t now = osKernelGetTickCount();
659 if ((int32_t)(now - nextStatus) >= 0) {
663 if ((now / 100u) % 5u == 0u) HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
672 if (osMessageQueueGet(
qTlm, &msg, NULL, 10) != osOK)
continue;
684 if (!
n ||
n >
cap)
return 0;
694#if APP_LOG_FLASH == 1
700#elif APP_LOG_FLASH == 2
711 memset(&e, 0,
sizeof e);
742 static const osThreadAttr_t aI = { .name =
"imu", .priority = osPriorityRealtime,
743 .stack_size = 3072 };
744 static const osThreadAttr_t aT = { .name =
"tlm", .priority = osPriorityBelowNormal,
745 .stack_size = 1024 };
750 memset(&ac, 0,
sizeof ac);
754 ac.
uid[0] = HAL_GetUIDw0(); ac.
uid[1] = HAL_GetUIDw1(); ac.
uid[2] = HAL_GetUIDw2();
765 memset(&bc, 0,
sizeof bc);
777 const uint8_t usb = 0u;
780 memset(&lc, 0,
sizeof lc);
void alt_est_baro(alt_est_t *e, float pa)
void alt_est_tof(alt_est_t *e, float range, uint8_t ok, float ct)
range_m: raw ToF range; status_ok: range status valid; cos_tilt: R33
void alt_est_default_cfg(alt_est_cfg_t *c)
void alt_est_init(alt_est_t *e, const alt_est_cfg_t *c)
float alt_est_accel_up(const float q[4], const float f[3], float nom_g)
vertical acceleration (m/s^2, + up, gravity removed) from the attitude and the specific force in body...
void alt_est_predict(alt_est_t *e, float a, float dt)
Vertical estimator: height, climb rate, baro bias, accelerometer bias.
static float alt_est_vz(const alt_est_t *e)
static float alt_est_h(const alt_est_t *e)
Phase-1 configuration: P-NUCLEO-WB55 (NUCLEO-WB55RG) + X-NUCLEO-IKS4A1 in Mode 3 (LSM6DSV16X is the s...
#define APP_LOG_RAM_KB
RAM log on the bench: ~14 s at ~3.4 kB/s
#define APP_MAG_FAST
LIS2MDL 100 Hz, hub 120 Hz: halves the age of mag data.
#define APP_HW_REV
CAPS hwRev: 'N' = NUCLEO-WB55 + IKS4A1 bench, 'A' = ACwing FC rev A.
#define MAG_HARD_IRON
uT, body frame
#define APP_TLM_TX_TIMEOUT_MS
max wait for the UART DMA before dropping
#define APP_INIT_EVERY_N
repeat INIT ~once per second (2 frames per window)
#define APP_XL_LPF2_ENABLE
#define APP_TLM_DECIM_REPLAY
#define APP_BATT_SENS_V_A
ACwing ESC 1S: TSC213 + 2 mOhm = 0.1 V/A
#define APP_TLM_DECIMATION
1 = every acc update (240 Hz), ~18.5 kB/s
#define APP_GY_LPF1_BW
gyro anti-alias filter: to be tuned
#define APP_IMU_WATERMARK
FIFO words: 2 gyro + 2 acc -> IRQ at 960 Hz.
#define APP_STEPS_PER_ACC
-> 240 Hz acc update + attitude loop
#define APP_BATT_CURRENT
a current sensor is wired (CAPS CURRENT)
#define APP_STEP_HZ
propagation + rate loop
void app_exti(uint16_t pin)
static imu_parse_stats_t s_psTot
static volatile uint32_t s_wordsMax
static osThreadId_t s_imuTask
static void take_calibrations(void)
void app_i2c_error(I2C_HandleTypeDef *h)
static volatile uint32_t s_procCycMax
static void tx_frame(const uint8_t *f, uint16_t len)
UART_HandleTypeDef huart1
static cal_set_t s_calset
static volatile uint32_t s_procCycLast
static imu_event_t s_ev[IMU_FIFO_MAX_WORDS]
static volatile uint32_t s_tlmDrop
static volatile float g_level_trim_deg[2]
static uint16_t log_sensors(void)
static void correct_events(imu_event_t *ev, size_t n)
static volatile uint32_t s_irqCyc
static volatile uint32_t s_fifoOvr
static void on_init(const est_t *e, void *user)
static volatile int8_t s_imuInit
static void TelemetryTask(void *arg)
static void push_frame(size_t len)
volatile uint16_t g_initLen
uint8_t g_initFrame[TLM_FRAME_MAX]
static size_t get_init(uint8_t *dst, size_t cap)
static volatile uint32_t s_drainReads
void app_i2c_rx_cplt(I2C_HandleTypeDef *h)
static volatile float s_odrHz
static uint16_t s_imu_sensors
static void log_records(uint32_t now, const float q[4], const fc_vert_t *v, const fc_setpoint_t *sp)
static void ImuTask(void *arg)
static uint16_t s_acc_clip
static volatile uint32_t s_lastGyroMs
static uint32_t s_cpu_cyc
static void motor_test_hook(uint8_t m, float duty)
static uint8_t s_fifo[IMU_FIFO_MAX_WORDS *IMU_FIFO_WORD_BYTES]
static volatile uint32_t s_busErr
static void iwdg_start(void)
static void send_status(uint8_t *frame, size_t cap)
static volatile uint32_t s_irqCount
static void flight_service(uint32_t *lastSvc)
static void publish_est(uint32_t now)
void app_uart_tx_cplt(UART_HandleTypeDef *h)
static volatile uint8_t s_est_restart
static uint32_t s_flow_ms
static void take_ext(uint32_t now, float cos_tilt, const float omega[3])
static const log_flash_t * log_flash(void)
static osMessageQueueId_t qTlm
static void on_state(const est_t *e, void *user)
static uint8_t s_flow_usable
static void dwt_init(void)
Integration into main.c (CubeMX, CMSIS-RTOS2):
void app_task_start(const app_task_cfg_t *cfg)
app_init + the RTOS task
const cal_set_t * app_task_boot(const float mag_hi_default[3])
Before the scheduler starts: loads the flash image (parameters, name, calibrations) and posts the sav...
AppTask: ACwing Pilot protocol v2 (HANDOVER_FW.md) on the drone side.
int batt_adc_read(float *vbat_pin, float *cur_pin)
0 = a new sample: pin voltages in volts
int batt_adc_init(void)
0 = ok
ADC1 binding of the BattTask: VBAT and current pins, VREFINT.
void batt_task_start(const batt_task_cfg_t *cfg)
BattTask: owns the battery ADC and the battery monitor (battery.c).
void cal_copy_type(cal_set_t *d, const cal_set_t *s, uint8_t type)
copies the part of 'src' that belongs to 'type' into 'dst'
Control hook points, already called by the estimation task at the right rates.
void app_est_config(est_config_t *ec)
Fills every field from app_config.h, including APP_COMPARE_ORIGINAL.
The production configuration of the attitude estimator, built from app_config.h in one place,...
void est_init(est_t *e, const est_config_t *cfg, est_cb_t on_state, est_cb_t on_init, void *user)
void est_process(est_t *e, const imu_event_t *ev, size_t n, float dt)
dt_sample: IMU sample period measured by the MCU (s)
Multi-rate estimation pipeline, WITHOUT any HAL or RTOS dependency.
int ext_take(ext_sample_t *s)
flight context: 1 if a sample was taken
uint16_t ext_sensors(void)
PV2_S_TOF | PV2_S_FLOW currently detected.
void ext_sens_start(void)
creates the task (after imu_i2c_bus_init)
Expansion sensors on the Qwiic bus (ACwing Flow rev A): VL53L1X ToF and the optical-flow bridge.
void fc_service(uint32_t now, uint8_t est_running, const fc_vert_t *v)
v: NULL = no vertical estimate (in air only via g_fc_dbg_in_air)
void fc_setpoint(fc_setpoint_t *out)
last setpoints, same context
void fc_init(const fc_config_t *cfg)
void fc_snapshot(fc_state_t *out)
Flight core: arming, modes, automatic actions, failsafes, motor test (HANDOVER_FW....
int fsh_cal_push(const cal_sample_t *s)
int fsh_apply_take(fsh_apply_t *a)
volatile uint8_t g_fsh_cal_want
calibration samples (ImuTask -> AppTask)
void fsh_est_write(const fsh_est_t *s)
ImuTask.
void fsh_est_read(fsh_est_t *out)
AppTask: keeps *out on a race.
Data exchanged between the flight context (ImuTask) and the AppTask.
void iekf_get_quat(const iekf_t *f, float q[4])
void imu_i2c_make_bus(sensor_bus_t *bus, I2C_HandleTypeDef *hi2c, uint8_t addr7)
void imu_i2c_error_isr(I2C_HandleTypeDef *hi2c)
void imu_i2c_rx_cplt_isr(I2C_HandleTypeDef *hi2c)
To be called from HAL_I2C_MemRxCpltCallback / HAL_I2C_ErrorCallback.
int32_t imu_i2c_read_dma(uint8_t reg, uint8_t *buf, uint16_t len, uint32_t timeout_ms)
Starts the DMA read and blocks the calling task until completion.
void imu_i2c_bus_init(I2C_HandleTypeDef *hi2c)
Creates the bus lock: call once before starting the tasks that use the bus.
STM32 HAL (STM32WB55) binding of the sensor_bus_t interface + DMA burst read of the FIFO with task no...
size_t imu_fifo_parse(imu_t *imu, const uint8_t *raw, uint16_t words, imu_event_t *ev, size_t cap, imu_parse_stats_t *st)
Decodes 'words' words read in a burst from IMU_FIFO_REG.
int32_t imu_init(imu_t *imu, const sensor_bus_t *bus, const imu_config_t *cfg)
int32_t imu_fifo_level(imu_t *imu, uint16_t *words)
Number of 7-byte words in the FIFO.
IMU library: LSM6DSV16X (gyro + acc) with the LIS2MDL and the barometer (LPS22DF on the X-NUCLEO-IKS4...
#define IMU_FIFO_WORD_BYTES
#define IMU_I2C_ADDR_7BIT
SA0 = 1 (default IKS4A1: 0xD6/0xD7)
#define IMU_FIFO_MAX_WORDS
burst buffer: 336 bytes
void logf_vert(uint32_t t, const fc_vert_t *v, const fc_setpoint_t *sp, uint16_t tof_mm, uint32_t baro_pa)
void logf_est(uint32_t t, const float b[3], float unc, float vib, float hz, float jit, float cpu, float mi)
void logf_att(uint32_t t, const float q[4], const float w[3], const float a[3])
void logf_set_clock(uint32_t(*now_ms)(void))
clock for the event records (ms)
void logf_event(const fc_event_t *e)
fc_config_t.on_event
void logf_flight(uint32_t t, const fc_state_t *s)
Producers' side of the flight log: build a record from the values the task already has and queue it (...
void log_flash_ram_init(log_flash_t *f, uint8_t *buf, uint32_t size)
buf: size bytes (rounded down to whole sectors)
log_flash_t on a RAM buffer with NOR semantics (erase sets 0xFF, program can only clear bits).
uint8_t log_q_active(void)
Record queues from the producing tasks to the LogTask.
uint8_t log_task_pct(void)
flash used %, 0xFF = no log flash (SYSTEM.log); any task
void log_task_start(const log_task_cfg_t *cfg)
LogTask: owns the log flash, writes the flight log and serves the log download over USB.
void motors_arm(uint8_t armed)
disarmed: duty forced to 0
void motors_test(uint8_t motor, float duty)
MOTOR_TEST (props off, disarmed): motor 1..4 at duty, the others off; motor 0 = all off.
PWM for 4 brushed coreless motors (8520, 1S) - PREPARED, unused in phase 1.
#define MOTORS_ENABLE
1 only with the power stage connected
ACwing Pilot app protocol v2: identifiers and frame layouts.
Single-precision SO(3) Lie-group utilities (port of expSO3.m / skewSym.m + firmware helpers).
uint8_t tof_valid
last ToF sample used
uint8_t valid
an altitude source is initialised
uint8_t tof_ground
below the minimum range: on the floor
float tof_h
last height above the floor (m)
uint16_t board_sensors
CAPS bits of the board itself (CURRENT, FLASH_LOG)
char hw_rev
CAPS hwRev: 'N' Nucleo bench, 'A' ACwing FC rev A.
uint32_t uid[3]
MCU unique id -> serial "ACW-xxxxxxxx"
uint8_t(* log_pct)(void)
flash log used %, 0xFF = no log; may be NULL
size_t(* get_init)(uint8_t *dst, size_t cap)
INIT frame of the estimator (copy; 0 if not ready yet)
uint8_t batt_monitor
a BattTask runs: POWER, CHECKS item 1, BATTERY calibration
float sens_v_per_a
current sensor gain, V/A
uint8_t has_current
a current sensor is wired
float scale_i
current multiplier (1 = nominal sensor)
float scale_v
×10 divider ratio (110 = ×11)
int(* adc_read)(float *vbat_pin, float *cur_pin)
ADC binding: pin voltages of VBAT and CUR; 0 = sample ready.
batt_cfg_t batt
sensor gains, scales at boot
float q[4]
attitude, body -> world NWU [w x y z]
uint8_t running
estimator running
float mag[3]
uT, body (axis map applied, no hard/soft iron)
float mag_hard_iron[3]
uT, body frame
float mag_map[9]
LIS2MDL sensor -> body (LSM6DSV16X)
float dt_s
flow: time covered by dx/dy, 0 = status only
int16_t dy
flow counts (0 without new motion)
uint8_t kind
EXT_TOF / EXT_FLOW.
uint8_t status
ToF range status (0 valid) / flow STATUS byte.
uint16_t sensors
CAPS bits detected at boot
setpoints for the attitude / rate loops (phase 3) and the motor mixer
published state (AppTask builds FLIGHT / MOTORS from it)
vertical state from the estimator (flight-control context)
uint8_t valid
an altitude source is running (baro and/or ToF)
float vz
m, m/s, + up (alt_est)
float cos_tilt
R33 of the attitude
uint8_t tof_ground
ToF used / ToF below its minimum range
float tof_h
m above the floor
uint8_t flow_q
0..100, 0xFF n/a
calibration to apply (AppTask -> ImuTask)
uint8_t alt_valid
vertical / navigation
uint8_t flow_ok
bridge alive, PMW3901 OK
uint16_t tof_mm
raw, 0xFFFF n/a
uint8_t flow_q
0..100, 0xFF n/a
uint16_t sensors
IMU / MAG_FC / BARO found by the IMU init.
float vib_mg
RMS of |a| - 1 g over ~1 s
float vel_body[2]
m/s from flow x height (x fwd, y left); 0 when not usable
float att_unc_deg
1 sigma, largest axis
uint16_t acc_clip
samples near full scale since arm
uint8_t flow_usable
above the PMW3901 minimum height (80 mm)
float q[4]
body -> world NWU
imu_odr_t odr
same ODR for gyro and acc
flash backend; addresses in bytes from the start of the log area
const log_flash_t * flash
NULL: no log flash on this board
void(* usb_pump)(void)
board binding: move queued USB TX bytes to the endpoint; may be NULL
uint16_t(* sensors)(void)
CAPS bits for LR_INFO; may be NULL
uint8_t bytes[TLM_FRAME_MAX]
size_t tlm_encode(uint8_t type, const void *payload, uint8_t len, uint8_t *out, size_t cap)
Writes the complete frame into out; returns its length, 0 if cap is too small.
Telemetry packet format (shared with 02 - Tools/00 - Matlab/Telemetry/parseTelemetry....
#define TLM_TYPE_STATUS
10 Hz diagnostics, ALWAYS sent (also during calibration)
#define TLM_TYPE_REPLAY
trace of the filter calls + resulting state (parity)
int tlm_ble_push(const uint8_t *frame, uint16_t len)
Queue a whole frame or nothing.
int tlm_ble_want_state(uint16_t seq)
1 if a STATE frame with this sequence number must go over BLE (decimation)
uint16_t tlm_ble_mtu(void)
uint32_t tlm_ble_dropped(void)
tlm_ble_state_t tlm_ble_state(void)
uint8_t tlm_ble_error(void)
int tlm_ble_take_init_request(void)
1 once after the legacy [0x01] command: send INIT now (TelemetryTask)
BLE telemetry, transport-independent part (phase 2 + protocol v2).
size_t tlm_build_init(const est_t *e, float fs_state_hz, uint8_t *frame, size_t cap)
size_t tlm_build_replay(const est_t *e, uint8_t *frame, size_t cap)
size_t tlm_build_state(const est_t *e, uint8_t *frame, size_t cap)
Builds telemetry frames from the pipeline state.
int tlm_uart_send(const uint8_t *buf, uint16_t len, uint32_t timeout_ms)
Non-blocking beyond timeout_ms: if the previous DMA has not finished, the frame is dropped.
uint32_t tlm_uart_dropped(void)
int tlm_uart_init(UART_HandleTypeDef *huart)
void tlm_uart_tx_cplt_isr(UART_HandleTypeDef *huart)
To be called from HAL_UART_TxCpltCallback() (see 03 - Docs/firmware/build.md)
USB transport: UART -> ST-LINK Virtual COM Port (USART1 on the NUCLEO-WB55).
int usb_cdc_init(void)
0 = started
USB CDC (virtual COM port) binding of the USB link (telemetry/usb_link.h).
uint8_t usbl_open(uint32_t now)
a program has the port open: host and (DTR or a command in the last USBL_OPEN_MS)
int usbl_put_frame(uint8_t src, const uint8_t *f, uint32_t n)
a frame already encoded; 0 = queued whole
uint8_t usbl_host(void)
a USB host has configured the device (cable to a PC): ARM is refused
Byte-stream link over USB CDC: protocol v2 frames in both directions (A5 5A | TYPE | LEN | PAYLOAD | ...
static USBD_StatusTypeDef st(HAL_StatusTypeDef h)