pub struct AnalogHallMatrix<'peripherals, ADC, D, IRQ, IM, const ROW: usize, const COL: usize>where
ADC: Instance<Regs = Adc> + BasicInstance,
D: RxDma<ADC>,
IRQ: Binding<D::Interrupt, InterruptHandler<D>> + Copy + 'peripherals,
IM: MasterMode,
<<ADC as BasicInstance>::Regs as BasicAdcRegs>::SampleTime: Clone,{
adc_part: AdcPart<'peripherals, ADC, D, ROW>,
auto_calib: [[AutoCalib; COL]; ROW],
calib: [[KeyCalib; COL]; ROW],
cfg: HallCfg,
cols: Hc164Cols<'peripherals>,
eeprom: Ft24c64<'peripherals, IM>,
irq: IRQ,
state: [[KeyState; COL]; ROW],
}Expand description
Hall-effect analog matrix scanner with EEPROM-backed per-key calibration and continuous auto-calibration.
On first boot (or after EEPROM corruption) the firmware performs a guided two-phase calibration:
- Zero-travel pass - all keys fully released; the firmware averages
HallCfg::calib_passesreads per key. Backlight signals amber. - Full-travel pass - user presses every key to the bottom within
HallCfg::full_calib_duration; each key must be held forcrate::matrix::analog_matrix::types::CALIB_HOLD_DURATION_MSbefore it is accepted and its LED turns green.
After all keys are accepted a
crate::matrix::analog_matrix::types::CALIB_SETTLE_AFTER_ALL_DONE
window continues sampling to capture the true bottom-out ADC. Validated
entries are written to the FT24C64 EEPROM and verified by read-back. On all
subsequent boots only full-travel data is loaded from EEPROM; zero-travel
is re-measured fresh to compensate for temperature drift.
During normal operation the auto-calibrator silently refines both zero and full-travel values on every press/release cycle, keeping the scanner accurate as the sensor drifts over time without requiring user interaction.
Fields§
§adc_part: AdcPart<'peripherals, ADC, D, ROW>ADC peripherals and channels grouped for split-borrow compatibility.
auto_calib: [[AutoCalib; COL]; ROW]Per-key auto-calibration state used to refine
self::AnalogHallMatrix::calib during normal operation.
calib: [[KeyCalib; COL]; ROW]Per-key calibration data applied during the scan loop.
cfg: HallCfgSensing and scanning configuration.
cols: Hc164Cols<'peripherals>Column driver used to select the active column via the HC164.
eeprom: Ft24c64<'peripherals, IM>EEPROM driver for loading and persisting calibration data.
irq: IRQDMA interrupt binding reused for every ADC sequence read.
state: [[KeyState; COL]; ROW]Dynamic per-key runtime state for the scan loop.
Implementations§
Source§impl<'peripherals, ADC, D, IRQ, IM, const ROW: usize, const COL: usize> AnalogHallMatrix<'peripherals, ADC, D, IRQ, IM, ROW, COL>where
ADC: Instance<Regs = Adc> + BasicInstance,
D: RxDma<ADC>,
IRQ: Binding<D::Interrupt, InterruptHandler<D>> + Copy + 'peripherals,
IM: MasterMode,
<<ADC as BasicInstance>::Regs as BasicAdcRegs>::SampleTime: Clone,
impl<'peripherals, ADC, D, IRQ, IM, const ROW: usize, const COL: usize> AnalogHallMatrix<'peripherals, ADC, D, IRQ, IM, ROW, COL>where
ADC: Instance<Regs = Adc> + BasicInstance,
D: RxDma<ADC>,
IRQ: Binding<D::Interrupt, InterruptHandler<D>> + Copy + 'peripherals,
IM: MasterMode,
<<ADC as BasicInstance>::Regs as BasicAdcRegs>::SampleTime: Clone,
Sourcepub(super) const fn default_entries() -> [[CalibEntry; COL]; ROW]
pub(super) const fn default_entries() -> [[CalibEntry; COL]; ROW]
Build the default calibration entries used when EEPROM is blank or invalid, giving a reasonable approximation of travel until real calibration runs.
Sourcepub(super) async fn calibrate_zero_raw(&mut self) -> [[u16; COL]; ROW]
pub(super) async fn calibrate_zero_raw(&mut self) -> [[u16; COL]; ROW]
Average cfg.calib_passes full-matrix scans to establish per-key
zero-travel (resting) ADC values.
All keys must be fully released during this pass. Returns a
ROW × COL array of raw ADC averages, each reduced by
ZERO_TRAVEL_DEAD_ZONE so that the resting position sits cleanly
below the measured average, preventing ADC noise from producing
spurious non-zero travel readings. Does not modify self.calib.
Sourcepub(super) async fn sample_full_raw(
&mut self,
duration: Duration,
zero_raw: &[[u16; COL]; ROW],
) -> [[u16; COL]; ROW]
pub(super) async fn sample_full_raw( &mut self, duration: Duration, zero_raw: &[[u16; COL]; ROW], ) -> [[u16; COL]; ROW]
Sample the matrix for duration (or until all real keys are accepted),
recording the minimum ADC reading seen per key.
Lower ADC = more magnet travel, so the minimum reading over the window
is the deepest press seen. A key is accepted only after it has stayed
continuously below CALIB_PRESS_THRESHOLD for
CALIB_HOLD_DURATION_MS; releasing and re-pressing resets the timer.
The LED turns green only at acceptance, not at first crossing.
After all keys are accepted a CALIB_SETTLE_AFTER_ALL_DONE
continuation window keeps updating min_raw so the stored value
reflects the true bottom-out ADC, not merely the acceptance instant.
Sourcepub(super) async fn run_first_boot_calib(
&mut self,
eeprom_buf: &mut [u8; 259],
entries: &mut [[CalibEntry; COL]; ROW],
)
pub(super) async fn run_first_boot_calib( &mut self, eeprom_buf: &mut [u8; 259], entries: &mut [[CalibEntry; COL]; ROW], )
Run the guided first-boot two-phase calibration, persist the result to
EEPROM, and apply it to self.calib.
Backlight signals during the process:
- Amber - zero-travel pass, all keys must be released.
- Blue → green per key - full-travel press window.
- Green blink ×3 - all keys accepted; keys may be released.
- Green for 2 s - calibration stored successfully.
- Amber - EEPROM write-back verification failed; keyboard will re-calibrate on the next boot.
Keys not pressed during the full-travel window fall back to
zero − DEFAULT_FULL_RANGE so the keyboard remains functional.
Source§impl<'peripherals, ADC, D, IRQ, IM, const ROW: usize, const COL: usize> AnalogHallMatrix<'peripherals, ADC, D, IRQ, IM, ROW, COL>where
ADC: Instance<Regs = Adc> + BasicInstance,
D: RxDma<ADC>,
IRQ: Binding<D::Interrupt, InterruptHandler<D>> + Copy + 'peripherals,
IM: MasterMode,
<<ADC as BasicInstance>::Regs as BasicAdcRegs>::SampleTime: Clone,
impl<'peripherals, ADC, D, IRQ, IM, const ROW: usize, const COL: usize> AnalogHallMatrix<'peripherals, ADC, D, IRQ, IM, ROW, COL>where
ADC: Instance<Regs = Adc> + BasicInstance,
D: RxDma<ADC>,
IRQ: Binding<D::Interrupt, InterruptHandler<D>> + Copy + 'peripherals,
IM: MasterMode,
<<ADC as BasicInstance>::Regs as BasicAdcRegs>::SampleTime: Clone,
Sourcepub(super) async fn scan_for_next_change(
cols: &mut Hc164Cols<'peripherals>,
state: &mut [[KeyState; COL]; ROW],
calib: &mut [[KeyCalib; COL]; ROW],
auto_calib: &mut [[AutoCalib; COL]; ROW],
seq: &mut ConfiguredSequence<'_, Adc>,
buf: &mut [u16; ROW],
cfg: HallCfg,
) -> Option<KeyboardEvent>
pub(super) async fn scan_for_next_change( cols: &mut Hc164Cols<'peripherals>, state: &mut [[KeyState; COL]; ROW], calib: &mut [[KeyCalib; COL]; ROW], auto_calib: &mut [[AutoCalib; COL]; ROW], seq: &mut ConfiguredSequence<'_, Adc>, buf: &mut [u16; ROW], cfg: HallCfg, ) -> Option<KeyboardEvent>
Scan the matrix once, returning the first key state change found.
The [ConfiguredSequence] is programmed once per invocation and
reused across all columns. Both the column settle delay and the DMA
transfer are fully async. For each reading that passes the noise gate
the auto-calibrator is updated before the travel and rapid-trigger
logic runs, so any calibration refinement takes effect within the same
scan pass.
Source§impl<'peripherals, ADC, D, IRQ, IM, const ROW: usize, const COL: usize> AnalogHallMatrix<'peripherals, ADC, D, IRQ, IM, ROW, COL>where
ADC: Instance<Regs = Adc> + BasicInstance,
D: RxDma<ADC>,
IRQ: Binding<D::Interrupt, InterruptHandler<D>> + Copy + 'peripherals,
IM: MasterMode,
<<ADC as BasicInstance>::Regs as BasicAdcRegs>::SampleTime: Clone,
impl<'peripherals, ADC, D, IRQ, IM, const ROW: usize, const COL: usize> AnalogHallMatrix<'peripherals, ADC, D, IRQ, IM, ROW, COL>where
ADC: Instance<Regs = Adc> + BasicInstance,
D: RxDma<ADC>,
IRQ: Binding<D::Interrupt, InterruptHandler<D>> + Copy + 'peripherals,
IM: MasterMode,
<<ADC as BasicInstance>::Regs as BasicAdcRegs>::SampleTime: Clone,
Sourcepub fn new(
adc_part: AdcPart<'peripherals, ADC, D, ROW>,
irq: IRQ,
cols: Hc164Cols<'peripherals>,
cfg: HallCfg,
eeprom: Ft24c64<'peripherals, IM>,
) -> Self
pub fn new( adc_part: AdcPart<'peripherals, ADC, D, ROW>, irq: IRQ, cols: Hc164Cols<'peripherals>, cfg: HallCfg, eeprom: Ft24c64<'peripherals, IM>, ) -> Self
Create a new matrix scanner.
Calibration is deferred to [Runnable::run], which loads from EEPROM
on subsequent boots or runs a full first-boot calibration pass.
Sourcefn apply_calib(
&mut self,
entries: &[[CalibEntry; COL]; ROW],
zero_raw: &[[u16; COL]; ROW],
)
fn apply_calib( &mut self, entries: &[[CalibEntry; COL]; ROW], zero_raw: &[[u16; COL]; ROW], )
Apply a row-major array of CalibEntry values to
self.calib, pairing each stored full-travel reading with the
corresponding live zero-travel reading from zero_raw.