use crate::chip::Timeline;
use crate::chip::i2c::Peer;
use crate::snapshot::store;
pub const ADDRESS: u8 = 0x0C;
const WIA: usize = 0x00;
const INFO: usize = 0x01;
const ST1: usize = 0x02;
const HXL: usize = 0x03;
const HZH: usize = 0x08;
const ST2: usize = 0x09;
const CNTL1: usize = 0x0A;
const CNTL2: usize = 0x0B;
const ASAX: usize = 0x10;
const ASAZ: usize = 0x12;
const REGISTERS: usize = 0x13;
const DEVICE_ID: u8 = 0x48;
const DRDY: u8 = 0x01;
const BITM: u8 = 0x10;
const HOFL: u8 = 0x08;
const MODE: u8 = 0x0F;
const POWER_DOWN: u8 = 0b0000;
const FUSE_ROM: u8 = 0b1111;
const BIT: u8 = 0x10;
const SRST: u8 = 0x01;
const FULL_SCALE: i32 = 32760;
const NANOTESLA_PER_COUNT_16: i32 = 150;
const NANOTESLA_PER_COUNT_14: i32 = 600;
pub const ADJUSTMENT: [u8; 3] = [0xB0, 0xAC, 0xB4];
const INFORMATION: u8 = 0x09;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Phase {
Idle,
Register,
Data,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Ak8963 {
registers: [u8; REGISTERS],
reading: bool,
phase: Phase,
pointer: usize,
nanotesla: [i32; 3],
}
impl Default for Ak8963 {
fn default() -> Self {
Self::new()
}
}
impl Ak8963 {
#[must_use]
pub fn new() -> Self {
let mut die = Self {
registers: [0; REGISTERS],
reading: false,
phase: Phase::Idle,
pointer: 0,
nanotesla: [22_000, 0, -42_000],
};
die.initialize();
die
}
pub fn set_field(&mut self, x: i32, y: i32, z: i32) {
self.nanotesla = [x, y, z];
}
fn initialize(&mut self) {
self.registers = [0; REGISTERS];
self.registers[WIA] = DEVICE_ID;
self.registers[INFO] = INFORMATION;
self.registers[ASAX..=ASAZ].copy_from_slice(&ADJUSTMENT);
self.reading = false;
}
fn mode(&self) -> u8 {
self.registers[CNTL1] & MODE
}
fn measure(&mut self) {
let sixteen = self.registers[CNTL1] & BIT != 0;
let per_count = if sixteen {
NANOTESLA_PER_COUNT_16
} else {
NANOTESLA_PER_COUNT_14
};
let mut overflow = false;
for (axis, adjustment) in ADJUSTMENT.iter().enumerate() {
let adjusted = i64::from(self.nanotesla[axis]) * 256
/ (i64::from(*adjustment) + 128)
/ i64::from(per_count);
let word = adjusted.clamp(-i64::from(FULL_SCALE), i64::from(FULL_SCALE));
overflow |= word != adjusted;
let bytes = i16::try_from(word)
.expect("held to the range")
.to_le_bytes();
self.registers[HXL + 2 * axis] = bytes[0];
self.registers[HXL + 2 * axis + 1] = bytes[1];
}
self.registers[ST2] = if sixteen { BITM } else { 0 } | if overflow { HOFL } else { 0 };
self.registers[ST1] = DRDY;
}
fn read_register(&mut self, register: usize) -> u8 {
let value = self.registers.get(register).copied().unwrap_or(0);
match register {
HXL..=HZH => {
self.reading = true;
self.registers[ST1] &= !DRDY;
}
ST2 => {
self.reading = false;
self.registers[ST1] &= !DRDY;
}
ASAX..=ASAZ if self.mode() != FUSE_ROM => return 0,
_ => {}
}
value
}
fn write_register(&mut self, register: usize, byte: u8) {
match register {
CNTL1 => {
self.registers[CNTL1] = byte & (BIT | MODE);
for status in ST1..=ST2 {
self.registers[status] = 0;
}
self.reading = false;
}
CNTL2 => {
if byte & SRST != 0 {
self.initialize();
}
}
0x0C..=0x0F => self.registers[register] = byte,
_ => {}
}
}
}
store!(Ak8963);
impl Peer for Ak8963 {
fn start(&mut self, address: u8, read: bool, _context: &mut Timeline<'_, '_>) -> bool {
if address != ADDRESS {
self.phase = Phase::Idle;
return false;
}
if read {
if !self.reading && self.mode() != POWER_DOWN && self.mode() != FUSE_ROM {
self.measure();
}
self.phase = Phase::Data;
} else {
self.phase = Phase::Register;
}
true
}
fn write(&mut self, byte: u8, _context: &mut Timeline<'_, '_>) -> bool {
match self.phase {
Phase::Register => {
self.pointer = usize::from(byte);
self.phase = Phase::Data;
}
Phase::Data => {
self.write_register(self.pointer, byte);
self.pointer += 1;
}
Phase::Idle => return false,
}
true
}
fn read(&mut self, _last: bool, _context: &mut Timeline<'_, '_>) -> u8 {
match self.phase {
Phase::Data => {
let register = self.pointer;
self.pointer += 1;
self.read_register(register)
}
Phase::Register | Phase::Idle => 0,
}
}
fn stop(&mut self, _context: &mut Timeline<'_, '_>) {
self.phase = Phase::Idle;
}
}