Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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 on lines +429 to +431
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 on lines +201 to +202
#[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