use crate::chip::spi::Peer;
use crate::device::DeviceContext;
use crate::time::VirtualNanos;
const OFFSET_X_REG_L_M: usize = 0x05;
const OFFSET_Z_REG_H_M: usize = 0x0A;
const WHO_AM_I: usize = 0x0F;
const CTRL_REG1: usize = 0x20;
const CTRL_REG2: usize = 0x21;
const CTRL_REG3: usize = 0x22;
const CTRL_REG4: usize = 0x23;
const CTRL_REG5: usize = 0x24;
const STATUS_REG: usize = 0x27;
const OUT_X_L: usize = 0x28;
const OUT_Z_H: usize = 0x2D;
const TEMP_OUT_L: usize = 0x2E;
const TEMP_OUT_H: usize = 0x2F;
const INT_CFG: usize = 0x30;
const INT_SRC: usize = 0x31;
const INT_THS_H: usize = 0x33;
const REGISTERS: usize = 0x40;
const DEVICE_ID: u8 = 0x3D;
const CTRL_REG1_DEFAULT: u8 = 0x10;
const CTRL_REG3_DEFAULT: u8 = 0x03;
const INT_CFG_DEFAULT: u8 = 0xE8;
const TEMP_EN: u8 = 0x80;
const OM: u8 = 0x60;
const DO: u8 = 0x1C;
const FAST_ODR: u8 = 0x02;
const FS: u8 = 0x60;
const SOFT_RST: u8 = 0x04;
const LP: u8 = 0x20;
const MD: u8 = 0x03;
const CONTINUOUS: u8 = 0b00;
const SINGLE: u8 = 0b01;
const BLE: u8 = 0x02;
const DATA_AVAILABLE: u8 = 0x0F;
const OVERRUN: u8 = 0xF0;
const READ: u8 = 0x80;
const INCREMENT: u8 = 0x40;
const ADDRESS: u8 = 0x3F;
const RATE_MILLIHERTZ: [u64; 8] = [625, 1_250, 2_500, 5_000, 10_000, 20_000, 40_000, 80_000];
const FAST_RATE_MILLIHERTZ: [u64; 4] = [1_000_000, 560_000, 300_000, 155_000];
const SENSITIVITY: [i64; 4] = [6_842, 3_421, 2_281, 1_711];
const NANOTESLA_PER_GAUSS: i64 = 100_000;
const TEMP_LSB_PER_DEGREE: i64 = 8;
const TEMP_ZERO_CENTI: i32 = 2_500;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Command,
Read(usize, bool),
Write(usize, bool),
}
#[derive(Debug)]
pub struct Lis3mdl {
registers: [u8; REGISTERS],
nanotesla: [i32; 3],
centi_celsius: i32,
epoch_at: VirtualNanos,
epoch_count: u64,
single_until: Option<u64>,
converted: u64,
phase: Phase,
selected: bool,
}
impl Default for Lis3mdl {
fn default() -> Self {
Self::new()
}
}
impl Lis3mdl {
#[must_use]
pub fn new() -> Self {
let mut part = Self {
registers: [0; REGISTERS],
nanotesla: [22_000, 0, -42_000],
centi_celsius: TEMP_ZERO_CENTI,
epoch_at: VirtualNanos(0),
epoch_count: 0,
single_until: None,
converted: 0,
phase: Phase::Command,
selected: false,
};
part.reset(VirtualNanos(0));
part
}
pub fn set_field(&mut self, x: i32, y: i32, z: i32) {
self.nanotesla = [x, y, z];
}
pub fn set_temperature(&mut self, centi_celsius: i32) {
self.centi_celsius = centi_celsius;
}
fn reset(&mut self, now: VirtualNanos) {
self.registers = [0; REGISTERS];
self.registers[WHO_AM_I] = DEVICE_ID;
self.registers[CTRL_REG1] = CTRL_REG1_DEFAULT;
self.registers[CTRL_REG3] = CTRL_REG3_DEFAULT;
self.registers[INT_CFG] = INT_CFG_DEFAULT;
self.single_until = None;
self.rebase(now);
}
fn rate_millihertz(&self) -> u64 {
let control1 = self.registers[CTRL_REG1];
let control3 = self.registers[CTRL_REG3];
if control3 & MD != CONTINUOUS && control3 & MD != SINGLE {
0
} else if control3 & LP != 0 {
RATE_MILLIHERTZ[0]
} else if control1 & FAST_ODR != 0 {
FAST_RATE_MILLIHERTZ[usize::from((control1 & OM) >> 5)]
} else {
RATE_MILLIHERTZ[usize::from((control1 & DO) >> 2)]
}
}
fn due(&self, now: VirtualNanos) -> u64 {
let since = u128::from(now.0.saturating_sub(self.epoch_at.0));
let taken = since * u128::from(self.rate_millihertz()) / 1_000_000_000_000;
let due = self.epoch_count + u64::try_from(taken).expect("a run outlasts no count");
self.single_until.map_or(due, |until| due.min(until))
}
fn rebase(&mut self, now: VirtualNanos) {
self.epoch_count = self.due(now);
self.epoch_at = now;
}
fn catch_up(&mut self, now: VirtualNanos) {
let due = self.due(now);
if due <= self.converted {
return;
}
let unread = self.registers[STATUS_REG] & DATA_AVAILABLE != 0 || due - self.converted > 1;
self.converted = due;
let scale = SENSITIVITY[usize::from((self.registers[CTRL_REG2] & FS) >> 5)];
let high_first = self.registers[CTRL_REG4] & BLE != 0;
for axis in 0..3 {
let offset = i64::from(i16::from_le_bytes([
self.registers[OFFSET_X_REG_L_M + 2 * axis],
self.registers[OFFSET_X_REG_L_M + 2 * axis + 1],
]));
let counts =
round(i64::from(self.nanotesla[axis]) * scale, NANOTESLA_PER_GAUSS) - offset;
let bytes = word(counts, high_first);
self.registers[OUT_X_L + 2 * axis..OUT_X_L + 2 * axis + 2].copy_from_slice(&bytes);
}
let temperature = if self.registers[CTRL_REG1] & TEMP_EN != 0 {
round(
i64::from(self.centi_celsius - TEMP_ZERO_CENTI) * TEMP_LSB_PER_DEGREE,
100,
)
} else {
0
};
self.registers[TEMP_OUT_L..=TEMP_OUT_H].copy_from_slice(&word(temperature, false));
if unread {
self.registers[STATUS_REG] |= OVERRUN;
}
self.registers[STATUS_REG] |= DATA_AVAILABLE;
}
fn mapped(register: usize) -> bool {
matches!(
register,
OFFSET_X_REG_L_M..=OFFSET_Z_REG_H_M | WHO_AM_I | CTRL_REG1..=CTRL_REG5 | STATUS_REG..=INT_THS_H
)
}
fn writable(register: usize) -> bool {
Self::mapped(register) && !matches!(register, WHO_AM_I | STATUS_REG..=TEMP_OUT_H | INT_SRC)
}
fn read_register(&mut self, register: usize) -> u8 {
let value = self.registers[register];
if register == OUT_Z_H {
self.registers[STATUS_REG] = 0;
}
value
}
fn write_register(&mut self, register: usize, byte: u8, now: VirtualNanos) {
if !Self::writable(register) {
return;
}
match register {
CTRL_REG1 => {
self.rebase(now);
self.registers[CTRL_REG1] = byte;
}
CTRL_REG2 if byte & SOFT_RST != 0 => {
for register in OFFSET_X_REG_L_M..=OFFSET_Z_REG_H_M {
self.registers[register] = 0;
}
self.rebase(now);
self.registers[CTRL_REG1] = CTRL_REG1_DEFAULT;
self.registers[CTRL_REG2] = 0;
self.registers[CTRL_REG3] = CTRL_REG3_DEFAULT;
self.registers[CTRL_REG4] = 0;
self.registers[CTRL_REG5] = 0;
self.single_until = None;
}
CTRL_REG3 => {
self.rebase(now);
self.registers[CTRL_REG3] = byte;
self.single_until = (byte & MD == SINGLE).then_some(self.epoch_count + 1);
}
_ => self.registers[register] = byte,
}
}
}
fn round(numerator: i64, denominator: i64) -> i64 {
let half = denominator / 2;
if numerator >= 0 {
(numerator + half) / denominator
} else {
(numerator - half) / denominator
}
}
fn word(counts: i64, high_first: bool) -> [u8; 2] {
let word = i16::try_from(counts.clamp(i64::from(i16::MIN), i64::from(i16::MAX)))
.expect("held to sixteen bits");
if high_first {
word.to_be_bytes()
} else {
word.to_le_bytes()
}
}
impl Peer for Lis3mdl {
fn select(&mut self, context: &DeviceContext<'_>) {
self.selected = true;
self.phase = Phase::Command;
self.catch_up(context.now());
}
fn deselect(&mut self, _context: &DeviceContext<'_>) {
self.selected = false;
self.phase = Phase::Command;
}
fn transfer(&mut self, mosi: u16, context: &DeviceContext<'_>) -> u16 {
if !self.selected {
return 0;
}
let byte = (mosi & 0xFF) as u8;
match self.phase {
Phase::Command => {
let register = usize::from(byte & ADDRESS);
let increment = byte & INCREMENT != 0;
self.phase = if byte & READ != 0 {
Phase::Read(register, increment)
} else {
Phase::Write(register, increment)
};
0
}
Phase::Read(register, increment) => {
let next = if increment {
(register + 1) & 0x3F
} else {
register
};
self.phase = Phase::Read(next, increment);
u16::from(self.read_register(register))
}
Phase::Write(register, increment) => {
self.write_register(register, byte, context.now());
let next = if increment {
(register + 1) & 0x3F
} else {
register
};
self.phase = Phase::Write(next, increment);
0
}
}
}
}