28#define CORE_PENDING_MS 1000u
31#define CAL_LINK_ABORT_MS 3000u
32#define LIST_HEADROOM 256u
33#define EV_RATE_MS 30000u
34#define VIB_WARN_MG 1500.0f
83static void tx(
const uint8_t *f,
size_t len)
91static uint8_t
s_big[4u + 255u + 2u];
93static uint32_t
bit(uint8_t type) {
return 1u << (type - 1u); }
97static uint8_t
ack_key(uint8_t link, uint8_t op) {
return (uint8_t)(link ==
LINK_USB ? (op | 0x80u) : op); }
99static void ack_link(uint8_t link, uint8_t op, uint8_t
seq, uint8_t res, uint8_t why)
145static void send_event(uint8_t level, uint16_t code, int32_t arg, uint32_t now)
150 for (
unsigned i = 0; i <
sizeof s_rep /
sizeof s_rep[0]; ++i) {
151 if (
s_rep[i].left)
continue;
162 for (
unsigned i = 0; i <
sizeof s_rep /
sizeof s_rep[0]; ++i) {
164 if (!r->
left || (int32_t)(now - r->
next) < 0)
continue;
174 memset(&e, 0,
sizeof e);
182 memset(&
b, 0,
sizeof b);
191 memset(&c, 0,
sizeof c);
202 uint32_t h = 2166136261u;
203 for (
int i = 0; i < 3; ++i)
204 for (
int b = 0;
b < 4; ++
b) { h ^= (
s_cfg.
uid[i] >> (8 *
b)) & 0xFFu; h *= 16777619u; }
205 static const char hex[] =
"0123456789ABCDEF";
206 memcpy(c.
serial,
"ACW-", 4);
207 for (
int i = 0; i < 8; ++i) c.
serial[4 + i] = hex[(h >> (28 - 4 * i)) & 0xFu];
214 const float w = q[0], x = q[1], y = q[2], z = q[3];
215 const float yaw = atan2f(2.0f * (w*z + x*y), 1.0f - 2.0f * (y*y + z*z));
216 float h = -yaw * 57.29578f;
217 while (h < 0.0f) h += 360.0f;
218 while (h >= 360.0f) h -= 360.0f;
225 memset(&f, 0,
sizeof f);
228 memset(&e, 0,
sizeof e);
231 memset(&m, 0,
sizeof m);
255static int16_t
sat16(
float v) {
return (int16_t)(v > 32767.0f ? 32767 : (v < -32768.0f ? -32768 : (int)v)); }
260 memset(&e, 0,
sizeof e);
263 memset(&
n, 0,
sizeof n);
265 n.hdop_x100 = 0xFFFFu;
267 n.alt_msl_cm = (int32_t)(44330.0f * (1.0f - powf((
float)e.
baro_pa / 101325.0f, 0.190295f)) * 100.0f);
272 float vn = 0.0f, ve = 0.0f;
275 vn = cosf(psi) * vx - sinf(psi) * vy;
276 ve = -(sinf(psi) * vx + cosf(psi) * vy);
278 n.vel_ned_cms[0] =
sat16(vn * 100.0f);
279 n.vel_ned_cms[1] =
sat16(ve * 100.0f);
284 n.wind_cms = 0xFFFFu;
291 memset(&f, 0,
sizeof f);
302 memset(&e, 0,
sizeof e);
305 memset(&
s, 0,
sizeof s);
308 s.loop_hz = (uint16_t)(e.
loop_hz + 0.5f);
311 s.vibration_mg = (uint16_t)(e.
vib_mg > 65535.0f ? 65535.0f : e.
vib_mg);
318 if (
s.log_pct == 0xFFu)
s.log_pct = 0u;
322static uint16_t
u16c(
float v) {
return (uint16_t)(v > 65535.0f ? 65535.0f : (v < 0.0f ? 0.0f : v + 0.5f)); }
328 memset(&p, 0,
sizeof p);
335 p.
remain_s = (
b.remain_s < 0.0f) ? 0xFFFFu :
u16c(
b.remain_s > 65534.0f ? 65534.0f :
b.remain_s);
343 memset(&e, 0,
sizeof e);
346 memset(&f, 0,
sizeof f);
349 memset(&in, 0,
sizeof in);
394 static uint8_t f[160];
458 memset(&fl, 0,
sizeof fl);
472 memset(&f, 0,
sizeof f);
477static void local_cmd(uint8_t op, uint8_t
seq,
const uint8_t *p, uint8_t plen, uint32_t now)
550 const uint8_t t = p[0];
576#define RATE(dst, v, lo, hi) do { if ((v) != 0u) dst = (uint8_t)((v) < (lo) ? (lo) : ((v) > (hi) ? (hi) : (v))); } while (0)
607 memcpy(
s_img.
name, p, plen > 16u ? 16u : plen);
655 if (c.
len < 2u)
continue;
656 const uint8_t op = c.
b[0],
seq = c.
b[1];
657 const uint8_t plen = (uint8_t)(c.
len - 2u);
686 if (c.
len < 2u)
continue;
687 const uint8_t op = c.
b[0],
seq = c.
b[1];
688 const uint8_t plen = (uint8_t)(c.
len - 2u);
697 memset(&lc, 0,
sizeof lc);
699 lc.
len = (uint8_t)(plen >
sizeof lc.
p ?
sizeof lc.
p : plen);
700 memcpy(lc.
p, &c.
b[2], lc.
len);
707 memset(&fc, 0,
sizeof fc);
709 fc.
len = (uint8_t)(plen >
sizeof fc.
p ?
sizeof fc.
p : plen);
710 memcpy(fc.
p, &c.
b[2], fc.
len);
733 ack(m->
p[0], m->
p[1], m->
p[2], m->
p[3]);
748static int due(uint32_t now, uint32_t *t, uint32_t period_ms)
750 if ((int32_t)(now - *t) < 0)
return 0;
751 *t = now + period_ms;
764 memset(&
s, 0,
sizeof s);
793static void streams(uint32_t now, uint8_t armed)
918#include "cmsis_os2.h"
932 static const osThreadAttr_t a = { .name =
"app", .priority = osPriorityBelowNormal,
933 .stack_size = 3072 };
934 (void)osThreadNew(
AppTask, NULL, &a);
void ackc_finish(uint8_t op, uint8_t seq, uint8_t result, uint8_t reason)
Completes the entry (or creates it if it was evicted meanwhile).
uint8_t ackc_begin(uint8_t op, uint8_t seq, uint8_t *result, uint8_t *reason)
Looks up (op, seq).
Last (op, seq) -> ACK pairs (HANDOVER_FW.md §5, "Sequence and ACK rules").
uint32_t appl_silence_ms(uint32_t now_ms, uint32_t down_floor_ms)
Silence seen by the failsafe: now - last CMD frame, at least 'down_floor_ms' while the link is report...
int appl_rc_read(appl_rc_t *out, uint32_t *t_ms)
Latest RC values and their arrival time.
int appl_pop(appl_cmd_t *out)
1 if a command was taken
Receive side of the ACwing Pilot link (HANDOVER_FW.md §2 and §6).
static void send_checks(void)
static uint16_t s_sensors
static uint32_t s_t_power
static void send_nav(void)
static uint32_t s_t_ev_mag
static void send_caps(void)
static uint8_t s_frame[FRAME_CAP]
static uint8_t s_hz_power
static void cal_apply(uint8_t type, const cal_set_t *set)
static int16_t sat16(float v)
static void list_start(void)
static uint32_t s_t_ev_vib
static uint8_t s_hz_motors
static int save_image(void)
static int save_image_keep_params(void)
static uint8_t armed_now(void)
static uint32_t s_t_flight
static batt_state_t batt_now(void)
static void send_motors(void)
static void AppTask(void *arg)
static uint8_t s_cal_link
static void send_system(void)
static void usb_commands(uint32_t now)
static uint8_t s_batt_fail
static uint32_t s_t_checks
static uint8_t s_pend_seq
static uint8_t s_hz_system
static void tx(const uint8_t *f, size_t len)
static void events(uint32_t now)
static void cal_batt(float *vbat, float *used, float *scale_i)
static uint16_t sensors_now(void)
static void repeats(uint32_t now)
#define CAL_LINK_ABORT_MS
static void commands(uint32_t now)
static void ack_link(uint8_t link, uint8_t op, uint8_t seq, uint8_t res, uint8_t why)
static float yaw_heading_deg(const float q[4])
static void send_init(void)
static uint32_t s_t_motors
static void ack(uint8_t op, uint8_t seq, uint8_t res, uint8_t why)
static uint32_t s_t_ground
void app_init(const app_task_cfg_t *cfg)
protocol logic, also used by the host test
static void send_param(int idx)
static void send_calib(void)
static uint8_t ack_key(uint8_t link, uint8_t op)
static uint16_t u16c(float v)
static const char * ev_text(uint16_t code)
static void streams(uint32_t now, uint8_t armed)
static void local_cmd(uint8_t op, uint8_t seq, const uint8_t *p, uint8_t plen, uint32_t now)
void app_task_start(const app_task_cfg_t *cfg)
app_init + the RTOS task
static uint8_t other_link(uint8_t link)
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...
static int due(uint32_t now, uint32_t *t, uint32_t period_ms)
static uint8_t s_big[4u+255u+2u]
static uint16_t s_fl_count
static int usb_allowed(uint8_t op)
static void send_event(uint8_t level, uint16_t code, int32_t arg, uint32_t now)
static void list_pace(void)
void app_step(uint32_t now)
static void battery_flight(void)
static uint8_t s_have_ev_vib
static void log_ble_out(void)
static uint32_t s_t_system
static int config_op(uint8_t op)
static uint8_t s_hz_flight
static void send_flight(void)
static uint8_t s_have_ev_mag
#define RATE(dst, v, lo, hi)
static uint32_t bit(uint8_t type)
static void build_checks(pv2_check_t *it, uint8_t *n)
static uint8_t s_have_logmsg
static app_task_cfg_t s_cfg
static void calibration(uint32_t now)
static uint32_t s_t_calib
static void send_power(void)
static log_msg_t s_logmsg
AppTask: ACwing Pilot protocol v2 (HANDOVER_FW.md) on the drone side.
void batt_task_last_flight(batt_flight_t *out)
last flight (any task, seqlock)
int batt_task_post_scales(float sv, float si)
AppTask -> BattTask: new calibration scales; 0 = queued.
BattTask: owns the battery ADC and the battery monitor (battery.c).
void batt_read(batt_state_t *out)
seqlock snapshot for other tasks; keeps *out on a race
uint8_t cal_take_changed(void)
1 if the state/step changed since the last call (send a CALIB frame now)
uint8_t cal_abort(uint8_t *reason)
uint8_t cal_capture(uint32_t now, uint8_t *reason)
void cal_init(const cal_config_t *cfg, cal_set_t *active)
active: the set in effect (RAM), owned by the caller.
uint8_t cal_running(void)
active and not DONE: blocks arming / other starts
uint8_t cal_save_check(uint8_t *reason)
CAL_SAVE validation: returns the ACK; on OK the caller writes the result (cal_result()) to flash and ...
void cal_saved(void)
state SAVED (one frame), then IDLE
size_t cal_frame(uint8_t *out, size_t cap)
CALIB frame for the current calibration; 0 if none.
uint8_t cal_start(uint8_t type, uint8_t option, uint8_t armed, uint32_t now, uint8_t *reason)
uint8_t cal_set_ref(uint8_t type, uint8_t index, float v, uint8_t *reason)
uint8_t cal_take_failed(void)
1 + type once when a calibration failed (EVENT 51), 0 otherwise
uint8_t cal_active(void)
a calibration exists (any state but IDLE)
const cal_set_t * cal_result(void)
uint8_t cal_wants_samples(void)
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'
void cal_set_defaults(cal_set_t *s, const float hi[3])
void cal_set_sensors(uint16_t sensors)
expansion board detected later
void cal_tick(uint32_t now)
void cal_feed(const cal_sample_t *s, uint32_t now)
int cfg_save(cfg_image_t *img)
writes img (seq and crc filled here) to the other page; 0 = ok
void cfg_params_put(cfg_image_t *img, const float v[PARAM_COUNT])
parameters <-> slots (unknown ids ignored, missing ids -> default)
void cfg_params_get(const cfg_image_t *img, float v[PARAM_COUNT])
int cfg_load(cfg_image_t *out)
latest valid image (version 1 converted); returns 0 if found, -1 if none (out left untouched)
Persistent configuration: parameters, drone name, calibrations and one backup per calibration type (H...
uint8_t checks_build(const checks_in_t *in, pv2_check_t *it, uint8_t *ready, uint8_t *battery_fail)
fills items (PV2_CHECKS_MAX), returns the count; *ready = no FAIL; *battery_fail = the battery item i...
Pre-flight checks (CHECKS frame, HANDOVER_FW.md §9).
uint16_t ext_sensors(void)
PV2_S_TOF | PV2_S_FLOW currently detected.
Expansion sensors on the Qwiic bus (ACwing Flow rev A): VL53L1X ToF and the optical-flow bridge.
uint8_t fc_is_core_op(uint8_t op)
ops executed by the core
void fc_set_ground_info(uint8_t ready, uint8_t batt_fail, uint8_t calibrating)
ground information computed by the AppTask (atomic byte stores)
int fc_take_result(fc_res_t *r)
1 if a result was taken
int fc_post(const fc_cmd_t *c)
0 = queued, -1 = full
void fc_set_sensors(uint16_t sensors)
sensors detected at run time (a late expansion board): atomic store
void fc_snapshot(fc_state_t *out)
int fc_take_event(fc_event_t *e)
Flight core: arming, modes, automatic actions, failsafes, motor test (HANDOVER_FW....
#define FC_FS_SHORT_MS
fsAppShort
#define FC_ARM_RC_FRESH_MS
throttle rule applies to RC this recent
#define FC_OP_MOTOR_STOP
stop a running motor test
volatile uint8_t g_fsh_cal_want
calibration samples (ImuTask -> AppTask)
int fsh_apply_post(uint8_t type, const cal_set_t *set)
int fsh_cal_pop(cal_sample_t *s)
void fsh_est_read(fsh_est_t *out)
AppTask: keeps *out on a race.
Data exchanged between the flight context (ImuTask) and the AppTask.
static uint16_t next(uint16_t s)
int log_task_usb_app_take(appl_cmd_t *c)
1 if a command was taken
int log_task_ble_cmd(const log_cmd_t *c)
0 = queued, -1 = full (answer BUSY)
int log_task_ble_take(log_msg_t *m)
1 if a frame was taken
LogTask: owns the log flash, writes the flight log and serves the log download over USB.
uint8_t params_set(uint16_t id, float v, uint8_t *reason)
PARAM_SET rules: returns an ACK result (PV2_ACK_*) and sets *reason.
uint16_t params_visible_count(uint16_t sensors)
const param_def_t g_param_def[PARAM_COUNT]
float param_get(int idx)
lock-free read, any thread
uint8_t params_visible(int idx, uint16_t sensors)
listing: parameters whose sensor is missing are omitted
void params_get_all(float *values)
PARAM_COUNT values.
void params_mark_saved(void)
saved <- RAM
void params_defaults(void)
RAM <- defaults (dirty until saved)
int params_index(uint16_t id)
-1 if unknown
void params_load(const float *values, uint16_t n)
flash image
uint8_t params_dirty(int idx)
RAM != flash.
int params_visible_pos(int idx, uint16_t sensors)
index in PARAM_LIST, -1 if hidden
Drone parameters of protocol v2 (HANDOVER_FW.md §8, ids.ts PARAMS).
size_t pv2_event(uint8_t level, uint16_t code, int32_t arg, const char *text, uint8_t *out, size_t cap)
size_t pv2_ack(uint8_t op, uint8_t seq, uint8_t result, uint8_t reason, uint8_t *out, size_t cap)
float pv2_rd_f32(const uint8_t *p)
size_t pv2_param(uint16_t id, float value, uint8_t flags, uint16_t index, uint16_t count, uint8_t *out, size_t cap)
size_t pv2_motors(const pv2_motor_t *m, uint8_t n, uint8_t *out, size_t cap)
size_t pv2_frame(uint8_t type, const void *payload, uint8_t len, uint8_t *out, size_t cap)
size_t pv2_checks(uint8_t ready, const pv2_check_t *items, uint8_t n, uint8_t *out, size_t cap)
ACwing Pilot app protocol v2: identifiers and frame layouts.
static uint16_t pv2_rd_u16(const uint8_t *p)
little-endian readers for command payloads (unaligned-safe)
@ PV2_R_NOT_OVER_USB
flight command received over the USB link: ground link only
@ PV2_R_OTHER_LINK
configuration change from the link that does not have control
#define PV2_FF_GEOFENCE_ON
#define PV2_OP_PARAM_LIST
#define PV2_OP_PARAM_SAVE
#define PV2_OP_MOTOR_REVERSE
@ PV2_E_BATTERY_EMERGENCY
@ PV2_E_TAKEOFF_ABORTED
firmware extension (WARN): no lift-off; the app shows the text
@ PV2_E_FAILSAFE_APP_LINK
@ PV2_E_BATTERY_MONITOR_LOST
firmware extension (ERROR): BattTask silent, battery unknown
#define PV2_PROTO_VERSION
#define PV2_OP_MOTOR_REMAP
#define PV2_OP_PARAM_DEFAULTS
#define PV2_OP_CAL_RESTORE
#define PV2_OP_CAL_SET_REF
#define PV2_OP_CAL_CAPTURE
#define PV2_OP_LOG_LIST
reserved in HANDOVER §16, USB link
#define PV2_OP_SET_TIME
unix_s u32, ms u16, tz_min i16: wall clock for the log
#define PV2_OP_STREAM_RATES
#define PV2_OP_LOG_READ
id u16, offset u32, len u32
#define PV2_OP_LOG_ERASE
"WIPE" (firmware extension)
#define PV2_FF_ARM_BLOCKED
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
last flight summary (AppTask persists it for the BATTERY calibration)
float used_mah
charge used between ARM and DISARM
uint16_t count
flights finished since boot
float scale_i
current scale it was counted with
uint8_t cells
0 = not known
float vbat
V, pack, filtered (τ 0.1 s)
uint32_t done_mask
bit (type-1): this calibration was done and saved
cal_set_t bak
previous set, per type (CAL_RESTORE)
float batt_used_scale_i
current scale it was counted with
float batt_used_mah
charge used in the last flight
cal_set_t cal
saved calibrations
uint32_t bak_mask
bit (type-1): bak holds a set for it
float batt_crit
params battWarn / battCrit
float gf_max_alt
param gfMaxAlt
uint8_t usb_host
a USB host is connected (item 12, FAIL)
uint8_t flow_ok
bridge alive, PMW3901 OK
uint8_t hold_mode
ALT_HOLD / POS_HOLD: centre stick = hold
uint8_t batt_monitor
battery measured (BattTask runs)
uint16_t sensors
CAPS bits
float gf_max_dist
param gfMaxDist
uint8_t rc_fresh
RC from the app in the last 0.5 s
uint32_t cal_done_mask
bit (type-1): calibration done and saved
uint8_t imu_ok
IMU initialised
uint8_t batt_stale
BattTask silent
uint8_t batt_level
latched level from the flight core
uint8_t est_running
attitude estimate converged and running
uint8_t flow_q
0..100, 0xFF n/a
float batt_cell_v
average cell, sagged and filtered
uint8_t alt_valid
vertical estimate running (baro / ToF)
uint8_t len
len = payload bytes after seq
published state (AppTask builds FLIGHT / MOTORS from it)
int32_t alt_rel_cm
above the take-off point
uint8_t batt_stale
BattTask silent: battery state unknown
uint8_t landed
touched down after a flight (until disarm)
uint8_t batt_level
last level signalled by the BattTask
uint32_t silence_ms
app link silence seen by the failsafe
uint8_t rc_stale
RC older than FC_RC_STALE_MS: neutral sticks in use.
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
a log command received over BLE (op, seq, arguments as written by the app)
an answer frame for BLE: type and payload (LOG_ENTRY, LOG_DATA, ACK)
uint8_t p[sizeof(pv2_log_data_hdr_t)+PV2_LOG_DATA_MAX]
char serial[12]
zero padded
uint8_t hw_rev
ASCII letter.
uint16_t ground_speed_cms
uint16_t home_bearing_cdeg
uint16_t home_dist_m
0xFFFF n/a
uint16_t rth_eta_s
0xFFFF n/a
uint16_t rpm
< 0x09 MOTORS, one entry (after n u8)
uint16_t vbat_mv
< 0x07 POWER
uint16_t remain_s
0xFFFF n/a
uint16_t tlm_ble_hi_free(void)
bytes free in HI
int tlm_ble_take_subscribe(void)
1 once after a client enabled notifications (AppTask: CAPS, INIT, CHECKS)
int tlm_ble_push_hi(const uint8_t *frame, uint16_t len)
HI: AppTask
tlm_ble_state_t tlm_ble_state(void)
void tlm_ble_open_streams(void)
BLE telemetry, transport-independent part (phase 2 + protocol v2).
#define TLM_BLE_RING_SIZE
per queue, power of 2
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
uint32_t usbl_tx_free_src(uint8_t src)
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)