Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions machine/cortex-m/src/native/can.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub struct Frame {
pub data: [u8; 8],
pub len: u8,
pub is_extended: bool,
/// 64-bit-extended bxCAN SOF hardware timestamp (1 tick = 1 CAN
/// bit-time, captured at the SOF sample point). RX only; 0 on Tx.
pub hw_timestamp_rx: u64,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
Expand Down Expand Up @@ -92,7 +95,8 @@ fn frame_to_c(frame: &Frame) -> bindings::can_frame_t {
data: frame.data,
len: frame.len,
is_extended: frame.is_extended as u8,
reserved: 0,
// Tx: unused (no Tx mailbox sets TGT).
hw_timestamp_rx: 0,
}
}

Expand All @@ -102,6 +106,7 @@ fn frame_from_c(c: &bindings::can_frame_t) -> Frame {
data: c.data,
len: c.len,
is_extended: c.is_extended != 0,
hw_timestamp_rx: c.hw_timestamp_rx,
}
}

Expand Down Expand Up @@ -143,7 +148,7 @@ pub fn receive(dev: &Device, out: &mut Frame) -> Result<bool> {
data: [0u8; 8],
len: 0,
is_extended: 0,
reserved: 0,
hw_timestamp_rx: 0,
};
let rc = unsafe { bindings::can_receive(dev.0.index, &mut cframe) };
match rc {
Expand Down
2 changes: 2 additions & 0 deletions machine/cortex-m/src/stub/can.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub struct Frame {
pub data: [u8; 8],
pub len: u8,
pub is_extended: bool,
/// Mirrors `native::can::Frame`; always 0 on the stub (no timer).
pub hw_timestamp_rx: u64,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
Expand Down
28 changes: 27 additions & 1 deletion machine/cortex-m/st/stm32l4/interface/can.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ static uint32_t s_rx_hw_ovr_fifo1[CAN_SLOT_COUNT];
static uint32_t s_rx_peak_fmp[CAN_SLOT_COUNT];
static uint32_t s_rx_get_fails[CAN_SLOT_COUNT];

/* Wrap-extension state for the 16-bit bxCAN SOF timestamp: previous
* raw read + accumulated high bits. ISR-only; reset in can_init. */
static uint16_t s_ts_last[CAN_SLOT_COUNT];
static uint64_t s_ts_hi[CAN_SLOT_COUNT];

static struct {
can_irq_handler_fn fn;
void *ctx;
Expand Down Expand Up @@ -192,6 +197,10 @@ int can_init(const can_bus_cfg_t *cfg) {
rx->tail = 0;
rx->count = 0;

/* Reset the per-slot HW-timestamp wrap tracker (see drain_fifo). */
s_ts_last[cfg->index] = 0;
s_ts_hi[cfg->index] = 0;

s_irqn[cfg->index].rx0_irqn = cfg->rx0_irqn;
s_irqn[cfg->index].rx1_irqn = cfg->rx1_irqn;

Expand All @@ -216,7 +225,9 @@ int can_init(const can_bus_cfg_t *cfg) {
h->Init.TimeSeg2 = ts2;
h->Init.AutoBusOff = ENABLE;
h->Init.AutoRetransmission = ENABLE;
h->Init.TimeTriggeredMode = DISABLE;
/* TTCM=1: latch the bxCAN bit-time counter into RDTxR.TIME at each
* RX SOF. No Tx mailbox sets TGT, so Tx framing is unaffected. */
h->Init.TimeTriggeredMode = ENABLE;
h->Init.AutoWakeUp = DISABLE;
/* RFLM=1: keep the oldest 3 frames on overflow; FOVR counts drops. */
h->Init.ReceiveFifoLocked = ENABLE;
Expand Down Expand Up @@ -345,6 +356,7 @@ int can_receive(uint8_t slot, can_frame_t *out) {
out->id = src->id;
out->len = src->len;
out->is_extended = src->is_extended;
out->hw_timestamp_rx = src->hw_timestamp_rx;
memcpy(out->data, (const void *)src->data, sizeof(out->data));

rx->head = (rx->head + 1u) % CAN_RX_BUF_SIZE;
Expand Down Expand Up @@ -411,6 +423,19 @@ static void drain_fifo(CAN_HandleTypeDef *hcan, uint8_t slot_idx,
s_rx_frames_fifo1[slot_idx]++;
}

/* Extend the HW SOF timestamp (hdr.Timestamp, 1 tick = 1 CAN
* bit-time) to 64 bits. Frames arrive here in order, so raw <
* previous means one 16-bit wrap. Done before the overflow drop
* so dropped frames still advance the tracker. Caveat: a >65.5 ms
* gap with no RX frame hides a wrap — fine, sync traffic is
* periodic and far faster than that. */
Comment thread
xarantolus marked this conversation as resolved.
Outdated
uint16_t ts_raw = (uint16_t)hdr.Timestamp;
if (ts_raw < s_ts_last[slot_idx]) {
s_ts_hi[slot_idx] += 0x10000ull;
}
s_ts_last[slot_idx] = ts_raw;
uint64_t ts_ext = s_ts_hi[slot_idx] | (uint64_t)ts_raw;

if (rx->count >= CAN_RX_BUF_SIZE) {
s_rx_drops[slot_idx]++;
continue;
Expand All @@ -420,6 +445,7 @@ static void drain_fifo(CAN_HandleTypeDef *hcan, uint8_t slot_idx,
slot->id = (hdr.IDE == CAN_ID_EXT) ? hdr.ExtId : hdr.StdId;
slot->len = (hdr.DLC > 8) ? 8 : (uint8_t)hdr.DLC;
slot->is_extended = (hdr.IDE == CAN_ID_EXT);
slot->hw_timestamp_rx = ts_ext;
memcpy((void *)slot->data, data, sizeof(data));

rx->tail = (rx->tail + 1u) % CAN_RX_BUF_SIZE;
Expand Down
4 changes: 3 additions & 1 deletion machine/cortex-m/st/stm32l4/interface/export.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ typedef struct
uint8_t data[8];
uint8_t len;
uint8_t is_extended;
uint16_t reserved;
/* RX only: 64-bit-extended bxCAN SOF timestamp (1 tick = 1 CAN
* bit-time), filled by drain_fifo. 0 on the Tx path. */
uint64_t hw_timestamp_rx;
} can_frame_t;

typedef struct
Expand Down
64 changes: 64 additions & 0 deletions src/drivers/can.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,67 @@ impl Device {
pub fn init() {
let _ = LazyLock::force(&BUSES);
}

/// Host mirror of the SOF-timestamp wrap-extension in C `drain_fifo()`
/// (`machine/cortex-m/st/stm32l4/interface/can.c`) — keep in sync.
/// Value units: CAN bit-times since boot; wall-time conversion is the
/// consumer's job (divide by bitrate).
Comment thread
xarantolus marked this conversation as resolved.
Outdated
#[cfg(test)]
mod hw_ts_extend_spec {
/// One extension step. `last`/`hi`: persisted per-slot state;
/// `raw`: new `TIME[15:0]`. Returns `(new_last, new_hi, extended)`.
fn extend(last: u16, hi: u64, raw: u16) -> (u16, u64, u64) {
let hi = if raw < last { hi + 0x1_0000 } else { hi };
(raw, hi, hi | raw as u64)
}

/// Walk a sequence of raw readings from zeroed state, as the ISR
/// does, and collect the extended values.
fn run(raws: &[u16]) -> Vec<u64> {
let mut last = 0u16;
let mut hi = 0u64;
let mut out = Vec::new();
for &r in raws {
let (l, h, ext) = extend(last, hi, r);
last = l;
hi = h;
out.push(ext);
}
out
}

#[test]
fn monotonic_within_one_epoch_passes_through() {
assert_eq!(run(&[0, 1, 100, 5_000, 65_535]), [0, 1, 100, 5_000, 65_535]);
}

#[test]
fn single_wrap_carries_into_high_word() {
assert_eq!(run(&[65_500, 30]), [65_500, 0x1_0000 + 30]);
}

#[test]
fn many_consecutive_wraps_accumulate() {
// Each step is below the previous => one wrap per step.
let v = run(&[60_000, 10, 5, 4, 3]);
assert_eq!(
v,
[60_000, 0x1_0000 + 10, 0x2_0000 + 5, 0x3_0000 + 4, 0x4_0000 + 3]
);
// strictly monotonic across wraps
assert!(v.windows(2).all(|w| w[1] > w[0]));
}

#[test]
fn equal_reading_is_not_treated_as_wrap() {
// rule is `<`, not `<=`: equality must not bump hi
assert_eq!(run(&[1234, 1234]), [1234, 1234]);
}

#[test]
fn idle_gap_longer_than_one_epoch_undercounts_is_known_limitation() {
// Known caveat: a >65.5 ms RX gap hides full wraps (the `<`
// rule sees only one). Fine — sync traffic is far faster.
assert_eq!(run(&[100, 90]), [100, 0x1_0000 + 90]);
}
}
Loading