Skip to main content

rmk_q6_he_ansi/matrix/
calib_store.rs

1use crate::keymap::{COL, ROW};
2use core::mem::size_of;
3use crc::{CRC_16_IBM_3740, Crc, NoTable};
4
5/// Pre-computed buffer length for the HE matrix.
6pub const CALIB_BUF_LEN: usize = total_len(ROW, COL);
7/// CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF).
8const CRC16: Crc<u16, NoTable> = Crc::<u16, NoTable>::new(&CRC_16_IBM_3740);
9/// Byte length of the trailing CRC field.
10const CRC_LEN: usize = size_of::<u16>();
11/// EEPROM word address at which the calibration block begins.
12pub const EEPROM_BASE_ADDR: u16 = 0x0000;
13/// Byte length of a single serialized entry (one u16 full-travel value).
14const ENTRY_LEN: usize = size_of::<u16>();
15/// Byte length of the header: magic + version.
16const HEADER_LEN: usize = size_of::<u32>().saturating_add(size_of::<u8>());
17/// Format version. Must be incremented on any incompatible layout change.
18const VERSION: u8 = 1;
19/// Magic number identifying a valid Q6 HE calibration block.
20const MAGIC: u32 = 0x5136_4845;
21
22/// Per-key full-travel calibration value stored in EEPROM.
23#[derive(Clone, Copy)]
24pub struct CalibEntry {
25    /// Raw ADC reading at full travel (key fully depressed to the PCB).
26    pub full: u16,
27}
28
29/// Copy exactly `N` bytes from `buf[start..end]` into a fixed-size array.
30///
31/// Returns `None` if the range is out of bounds or its length is not `N`.
32/// Callers that need a hard failure (e.g. corrupt EEPROM data) should
33/// treat `None` as a validation error rather than substituting a default,
34/// because zero is a valid output for checksums and serialized values.
35#[inline]
36fn read_array<const N: usize>(buf: &[u8], start: usize, end: usize) -> Option<[u8; N]> {
37    buf.get(start..end)?.try_into().ok()
38}
39
40/// Serialize `entries` (row-major, `ROW × COL`) into `buf`.
41///
42/// Writes the magic number, version byte, all full-travel entries, and a
43/// CRC-16/CCITT checksum over all preceding bytes.
44/// `buf` must be at least [`total_len`]`(ROW, COL)` bytes.
45pub fn serialize<const ROW: usize, const COL: usize>(
46    entries: &[[CalibEntry; COL]; ROW],
47    buf: &mut [u8; CALIB_BUF_LEN],
48) {
49    // Write magic number, 4 bytes little-endian.
50    if let Some(dst) = buf.get_mut(0..size_of::<u32>()) {
51        dst.copy_from_slice(&MAGIC.to_le_bytes());
52    }
53
54    // Write version byte.
55    if let Some(version_byte) = buf.get_mut(size_of::<u32>()) {
56        *version_byte = VERSION;
57    }
58
59    let mut pos = HEADER_LEN;
60    for row in entries {
61        for entry in row {
62            let end = pos.saturating_add(ENTRY_LEN);
63            if let Some(dst) = buf.get_mut(pos..end) {
64                dst.copy_from_slice(&entry.full.to_le_bytes());
65            }
66            pos = end;
67        }
68    }
69
70    // Compute CRC over the header + all entry bytes.
71    let crc_start = pos;
72    let crc_end = crc_start.saturating_add(CRC_LEN);
73    let crc = buf.get(..crc_start).map_or(0, |data| CRC16.checksum(data));
74    if let Some(dst) = buf.get_mut(crc_start..crc_end) {
75        dst.copy_from_slice(&crc.to_le_bytes());
76    }
77}
78
79/// Attempt to deserialize a calibration block from `buf` into `out`.
80///
81/// Validates the magic number, version byte, and CRC-16/CCITT checksum.
82/// Returns `true` and populates `out` on success. Returns `false` without
83/// modifying `out` on any validation failure, including a VERSION mismatch
84/// which rejects data written by an incompatible layout.
85pub fn try_deserialize<const ROW: usize, const COL: usize>(buf: &[u8], out: &mut [[CalibEntry; COL]; ROW]) -> bool {
86    if buf.len() < CALIB_BUF_LEN {
87        return false;
88    }
89
90    // Validate magic number.
91    let magic_end = size_of::<u32>();
92    let Some(magic_bytes) = read_array::<4>(buf, 0, magic_end) else { return false };
93    if u32::from_le_bytes(magic_bytes) != MAGIC {
94        return false;
95    }
96
97    // Validate version byte.
98    let Some(&stored_version) = buf.get(size_of::<u32>()) else { return false };
99    if stored_version != VERSION {
100        return false;
101    }
102
103    // Validate CRC over header + entries.
104    let data_end = HEADER_LEN.saturating_add(ROW.saturating_mul(COL).saturating_mul(ENTRY_LEN));
105    let crc_end = data_end.saturating_add(CRC_LEN);
106    // None must not be silently replaced with 0 (0 is a valid CRC value).
107    let Some(stored_crc_bytes) = read_array::<2>(buf, data_end, crc_end) else { return false };
108    let stored_crc = u16::from_le_bytes(stored_crc_bytes);
109    let computed_crc = buf.get(..data_end).map_or(0, |data| CRC16.checksum(data));
110    if computed_crc != stored_crc {
111        return false;
112    }
113
114    // Deserialize entries.
115    let mut pos = HEADER_LEN;
116    for row in out.iter_mut() {
117        for entry in row.iter_mut() {
118            let end = pos.saturating_add(ENTRY_LEN);
119            let Some(fb) = read_array::<2>(buf, pos, end) else { return false };
120            entry.full = u16::from_le_bytes(fb);
121            pos = end;
122        }
123    }
124    true
125}
126
127/// Compute the total serialized byte length for a `rows × cols` matrix.
128pub const fn total_len(rows: usize, cols: usize) -> usize {
129    HEADER_LEN.saturating_add(rows.saturating_mul(cols).saturating_mul(ENTRY_LEN)).saturating_add(CRC_LEN)
130}