openaliro
Aliro reader: UWB/CCC core and ESP32-S3/C5/C6 port
Loading...
Searching...
No Matches
uwb_cirdiag.c File Reference

— CIA RX-diagnostics latch + [ALAB] emitter (channel-impulse Stage 0/1). More...

#include "uwb_cirdiag.h"
#include <stdint.h>
#include <string.h>
#include <deca_device_api.h>
#include "woz_log.h"
#include "woz_port.h"
Include dependency graph for uwb_cirdiag.c:

Data Structures

struct  cirdiag_rec
 Channel impulse response diagnostic record: timestamp (microseconds), sample count, base index, and tap array (I/Q magnitude or signed values) for post-processing. More...
 

Macros

#define CIRDIAG_SYS_CFG   0x10UL
 SYS_CFG + STS-packet-config (CP_SPC): the mode of THIS reception (0=SP0..3=SP3).
 
#define CIRDIAG_CIR_WIN   64u
 Windowed-CIR dump width, in complex taps, centred on the first-path index.
 
#define CIRDIAG_CIR_EVERY   4u
 Take a window on one Final in every N.
 
#define CIRDIAG_FP_FRAC_BITS   6u
 ipatovFpIndex is Q10.6 (6 fractional bits); the integer sample index is the high bits.
 
#define CIRDIAG_CLK_CTRL   0x110004UL
 CLK_CTRL_ID.
 
#define CIRDIAG_PROBE_TAPS   8u
 Taps per probe pass.
 
#define CIRDIAG_RING_RECS   16u
 Deferred-dump ring.
 

Functions

static uint16_t cirdiag_window_base (void)
 Absolute Ipatov sample index of tap 0 for a window centred on the latched first path, clamped into the valid accumulator span [0, DWT_CIR_LEN_IP_PRF64 - WIN].
 
static void cirdiag_drain (void)
 Emit every buffered window as ev=uwb.cir lines (oldest first), then empty the ring.
 
uint32_t uwb_cirdiag_ring_count (void)
 Return the count of CIR windows currently in the ring buffer (0 to CIRDIAG_RING_RECS).
 
void uwb_cirdiag_set_enabled (bool on)
 Enable or disable CIA diagnostic capture globally.
 
bool uwb_cirdiag_enabled (void)
 Return true if CIA diagnostic capture is enabled.
 
void uwb_cirdiag_dump_set_enabled (bool on)
 Arm or disarm CIR window capture.
 
bool uwb_cirdiag_dump_enabled (void)
 Return true if CIR window dump is armed (enabled and CIA logging armed).
 
bool uwb_cirdiag_capture (uint32_t status, uint16_t datalength, bool deadline_pending)
 Capture one reception's CIR diagnostic snapshot: arm CIA logging on first RX, then latch the status, frame length, STS quality/status, and (if window dump is enabled and the radio is idle) a fixed-size Ipatov-centred CIR window.
 
bool uwb_cirdiag_window_due (void)
 Return true on every CIRDIAG_CIR_EVERY-th call if capture is enabled, CIA logging is armed, and window dump is armed; used to throttle window reads during streaming.
 
void uwb_cirdiag_probe (void)
 Diagnostic: read and print the CIR at four sample offsets (three distinct addresses plus one repeat) to verify addressing and detect non-determinism.
 
void uwb_cirdiag_flush (void)
 Emit the pending CIR snapshot: write the summary line ([ALAB] ev=uwb.diag) with Ipatov and STS peak/power/quality fields, and either defer the window to the ring buffer (if window dump is enabled) or skip it.
 

Variables

static volatile bool g_on
 Runtime arm state (console-toggled; OFF at boot).
 
static volatile bool g_dump
 Windowed-CIR dump arm (independent of g_on; OFF at boot).
 
static bool g_cia_armed
 Chip-side CIA diagnostic logging enabled (lazily, on the RX path).
 
static dwt_rxdiag_t g_diag
 Latched snapshot of the most recent reception (latest wins).
 
static uint32_t g_cir [CIRDIAG_CIR_WIN]
 Windowed-CIR snapshot: DWT_CIR_READ_MID packs each tap as two int16 (real, imag) in one word, so g_cir doubles as an int16[2*WIN] pair array.
 
static volatile uint32_t g_seq
 Seqlock around the snapshot: odd while the RX path writes; the flush copies only between two equal even reads.
 
static uint32_t g_win_tick
 Decimation tick: the shims call this once per Final, so it advances per ranging block.
 

Detailed Description

— CIA RX-diagnostics latch + [ALAB] emitter (channel-impulse Stage 0/1).

Split the work across the two contexts the ALAB contract demands: the RX callback only latches registers into a snapshot (uwb_cirdiag_capture, plain stores + one SPI read), and a task-side uwb_cirdiag_flush formats/prints the line. On the nRF the flush runs on the sysworkq (uwb_rxdiag.c submits it); on the ESP32 the pinned ISR-service task calls it after its IRQ drain loop, so capture and flush are sequential there. A seqlock covers the one real race (nRF: a new capture preempting a flush mid-copy): torn snapshots are dropped, the next reception re-latches.

Stage 1 adds an independently-armed windowed-CIR dump: when armed, capture also reads a fixed window of Ipatov complex taps centred on the first-path index into the snapshot. The taps are NOT printed on the RX/flush path — a full window is ~64 serial lines per reception, enough blocking UART to overrun the ranging slot and stall a live walk-up. Instead flush appends each window to a small RAM ring (the last CIRDIAG_RING_RECS receptions), and the taps are drained to ev=uwb.cir lines only when the dump is disarmed (uwb_cirdiag_dump_set_enabled (false)) — that runs in console/task context after the walk-up, so the unlock is unaffected while capturing. Deferring the printing was necessary but not sufficient: the window READ is itself too long to sit inside a live ranging block, where the responder still owes a POLL or Final reception. The shims pass that down as deadline_pending and the window is taken only on the Final. Nor was that sufficient: the accumulator cannot be read at all while the receiver is up, and the shim re-arms an SP0 listen the moment the Final is serviced, so the read has to happen BEFORE that (the shims gate it on ccc_shim_rx_awaiting_final). Doing it on every block then cost every range, so uwb_cirdiag_window_due decimates it to one Final in CIRDIAG_CIR_EVERY.

Macro Definition Documentation

◆ CIRDIAG_CIR_EVERY

#define CIRDIAG_CIR_EVERY   4u

Take a window on one Final in every N.

The read is the most expensive thing this unit does — the driver walks the accumulator in CHUNK_CIR_NB_SAMP-sample chunks, three SPI transactions each — and doing it on every block cost every range of the walk-up (bench run 5: 16/16 windows came back as real CIR, and not one block produced a distance). Sampling every fourth block still fills the ring across an approach while leaving three blocks in four untouched.

◆ CIRDIAG_CIR_WIN

#define CIRDIAG_CIR_WIN   64u

Windowed-CIR dump width, in complex taps, centred on the first-path index.

64 is enough to carry the leading edge + early multipath that separates inside/outside a door, while staying cheap on serial (~64 lines) and RAM (256 B in DWT_CIR_READ_MID: 1 word/tap).

◆ CIRDIAG_CLK_CTRL

#define CIRDIAG_CLK_CTRL   0x110004UL

CLK_CTRL_ID.

dwt_readcir ORs the ACC_MCLK_EN|ACC_CLK_EN bits in here on every call and never clears them; the probe reads it back so a failed force shows up as data, not inference.

◆ CIRDIAG_PROBE_TAPS

#define CIRDIAG_PROBE_TAPS   8u

Taps per probe pass.

Small enough that three passes at different offsets stay cheap; the point is comparing them, not the width.

◆ CIRDIAG_RING_RECS

#define CIRDIAG_RING_RECS   16u

Deferred-dump ring.

flush() appends each armed reception's window here (a cheap memcpy, no UART) instead of printing it on the ranging path; the taps are emitted only on disarm (cirdiag_drain), off that path, so a live walk-up still unlocks while capturing. Overwrites oldest, so it holds the last RECS receptions — the near-door end of an approach (~272 B/record). Single-producer (flush) / single-consumer (drain-after-disarm): the drain runs only after g_dump is cleared, so in the intended "walk up, then dump off" workflow no live flush races it.

◆ CIRDIAG_SYS_CFG

#define CIRDIAG_SYS_CFG   0x10UL

SYS_CFG + STS-packet-config (CP_SPC): the mode of THIS reception (0=SP0..3=SP3).

Same trick as uwb_rxdiag.c's RXDIAG_CP_SPC, duplicated to keep this unit freestanding.

Function Documentation

◆ cirdiag_drain()

static void cirdiag_drain ( void  )
static

Emit every buffered window as ev=uwb.cir lines (oldest first), then empty the ring.

Called on dump disarm, off the ranging path. Blocking: up to RECS*WIN serial lines.

◆ uwb_cirdiag_capture()

bool uwb_cirdiag_capture ( uint32_t  status,
uint16_t  datalength,
bool  deadline_pending 
)

Capture one reception's CIR diagnostic snapshot: arm CIA logging on first RX, then latch the status, frame length, STS quality/status, and (if window dump is enabled and the radio is idle) a fixed-size Ipatov-centred CIR window.

Returns true if capture succeeded; false on first RX or if already pending. Seqlock-protected; safe to call from RX callback.

◆ uwb_cirdiag_dump_set_enabled()

void uwb_cirdiag_dump_set_enabled ( bool  on)

Arm or disarm CIR window capture.

When arming, also arms the summary diagnostics. When disarming, drains all buffered windows to the console via ev=uwb.cir lines and clears the ring.

◆ uwb_cirdiag_flush()

void uwb_cirdiag_flush ( void  )

Emit the pending CIR snapshot: write the summary line ([ALAB] ev=uwb.diag) with Ipatov and STS peak/power/quality fields, and either defer the window to the ring buffer (if window dump is enabled) or skip it.

Retry up to 3 times if the seqlock detects concurrent capture. Idempotent.

◆ uwb_cirdiag_probe()

void uwb_cirdiag_probe ( void  )

Diagnostic: read and print the CIR at four sample offsets (three distinct addresses plus one repeat) to verify addressing and detect non-determinism.

Requires CIA logging armed (one reception taken). Outputs one pass in MID mode (int16 real/imag) and one in FULL mode (raw 24-bit) at the base offset.

Variable Documentation

◆ g_cir

uint32_t g_cir[CIRDIAG_CIR_WIN]
static

Windowed-CIR snapshot: DWT_CIR_READ_MID packs each tap as two int16 (real, imag) in one word, so g_cir doubles as an int16[2*WIN] pair array.

g_cir_base is the absolute Ipatov sample index of tap 0; g_cir_have gates emission (false if dump disarmed or the read failed).