Sensors and expansion boards¶
| Source | Part, bus | Driver | Available on |
|---|---|---|---|
| Barometer | LPS22DF (X-NUCLEO-IKS4A1) or LPS22HB (ACwing FC rev A), slave 1 of the LSM6DSV16X sensor hub, 50 Hz, in the same FIFO | imu_lsm6dsv16x.c, model from WHO_AM_I. Optional: if it is missing, the IMU starts without it |
bench and FC |
| ToF height | VL53L1X at 0x29 on the Qwiic bus (ACwing Flow), long mode, 33 ms (30 Hz) | vl53l1x.c (from ST's ULD, BSD-3) |
ACwing Flow |
| Optical flow | PMW3901 behind the STM32C011 bridge at 0x3B, 100 Hz motion burst | flow_bridge.c on the FC; bridge firmware in 01 - FLOW_FW, same register map (checked by its host test). An unprogrammed bridge leaves FLOW at 0 in CAPS |
ACwing Flow |
| Gyroscope, accelerometer | LSM6DSV16X, I²C1 on the bench (SPI on the ACwing FC), FIFO at 960 Hz | imu_lsm6dsv16x.c |
bench and FC |
| Compass | LIS2MDL (bench) / IIS2MDC (FC), slave 0 of the sensor hub, 100 Hz, in the same FIFO | imu_lsm6dsv16x.c |
bench and FC |
The barometer and the compass are read by the LSM6DSV16X sensor hub and land in the same FIFO as the gyroscope and the accelerometer. They share the IMU time base, and the MCU needs no extra bus transaction for them. Sensor bits in CAPS are set only for the parts found at run time.
Expansion bus (Qwiic)¶
flowchart LR
subgraph FC["ACwing FC (or the bench, I²C1 D14/D15)"]
EXT["ExtTask<br/>app/ext_sens.c"] --> I2C["bsp/ext_i2c.c<br/>HAL + bus lock"]
end
I2C -- "0x29" --> TOF["VL53L1X<br/>direct on the bus"]
I2C -- "0x3B" --> BR["STM32C011 bridge"] -- SPI --> PMW["PMW3901"]
BR -. "XSHUT left high-Z" .-> TOF
The ExtTask probes the devices at boot and every 5 s while one is missing, so the Flow board can be plugged in later; a new sensor bit makes the AppTask send CAPS again. It polls them every 10 ms. After 50 consecutive bus errors a device is declared lost and probed again. Every bridge frame goes up, even without new motion, so that the FC knows the sensor is alive on the ground too. The ToF is read by the FC directly, so it works whether the bridge is programmed or not. The bridge firmware is described in ACwing Flow bridge.
Altitude estimate¶
The barometer and the ToF feed the altitude Kalman filter
(filters/altitude_kf/alt_est.c). It runs in the ImuTask: prediction at
240 Hz from the IEKF attitude and the accelerometer, baro update at 50 Hz, ToF
update at 30 Hz. In short:
- without ToF the baro bias cannot be observed, so it is frozen and the height follows the baro;
- with ToF the baro drift is estimated and the height follows the floor; a ToF jump (stepping onto a table) is gated, and the floor is re-anchored after 0.5 s;
- below the ToF minimum range (about 40 mm; landed, the lens is 11 mm above the floor) the reading means "on the ground".
The same logic runs on the models with and without GNSS. Only the mode entered at the end of the takeoff changes: POS_HOLD with GNSS or FLOW, ALT_HOLD otherwise.
Optical flow below its minimum height¶
The flow sensor works from 80 mm
(APP_FLOW_MIN_H). With the drone on the ground the lens is about 11 mm
above the floor, so on the ground, at lift-off and in the last centimetres of
a landing the bridge rejects every sample (no surface features) and the flow
is invalid by design. Below that height the FC:
- marks the flow not usable: no velocity, so NAV
veland the ground speed read 0, andfc_setpoint().pos_hold= 0 (level attitude, no position hold); - judges FLOW_LOST only in the air, above 0.15 m on the ToF, and never during TAKEOFF or LAND;
- in CHECKS, on the ground, checks only that the bridge and the sensor answer (OK, with the quality as value). The quality rule (WARN < 50 %) applies in the air.
The height comes from the ToF. The flow becomes usable again 20 mm above the limit; without a valid ToF reading the flow is not used.
flowchart LR
F["bridge frame<br/>(every 10 ms)"] --> OK{"bridge alive and<br/>PMW3901 OK?"}
OK -- no --> NU["flow not usable<br/>CHECKS: WARN"]
OK -- yes --> T{"ToF valid and<br/>height ≥ 80 mm?<br/>(back on at 100 mm)"}
T -- no --> NU2["not usable: vel = 0,<br/>pos_hold = 0, no FLOW_LOST"]
T -- yes --> V["usable: velocity =<br/>(flow rate − body rate) × height"]
The flow velocity uses about 0.0205 rad per count (42° / 35 px), with the axis
signs APP_FLOW_SIGN_X/Y in app_config.h. They follow the frame-C2
mounting and still have to be confirmed on the first board.