si4703 0.1.0

Platform-agnostic Rust driver for the Si4703 and Si4702 FM radio turners (receivers).
Documentation
use crate::{Error, Si4703};
use embedded_hal::blocking::i2c;

const DEVICE_ADDRESS: u8 = 0x10;

pub struct Register;
impl Register {
    pub const DEVICE_ID: usize = 0x0;
    pub const CHIP_ID: usize = 0x1;
    pub const POWERCFG: usize = 0x2;
    pub const CHANNEL: usize = 0x3;
    pub const SYSCONFIG1: usize = 0x4;
    pub const SYSCONFIG2: usize = 0x5;
    pub const SYSCONFIG3: usize = 0x6;
    pub const TEST1: usize = 0x7;
    pub const STATUSRSSI: usize = 0xA;
    pub const READCHAN: usize = 0xB;
    pub const RDSA: usize = 0xC;
    pub const RDSB: usize = 0xD;
    pub const RDSC: usize = 0xE;
    pub const RDSD: usize = 0xF;
}

pub struct BitFlags;
impl BitFlags {
    pub const DSMUTE: u16 = 1 << 15;
    pub const DMUTE: u16 = 1 << 14;
    pub const MONO: u16 = 1 << 13;
    pub const RDSR: u16 = 1 << 15;
    pub const STC: u16 = 1 << 14;
    pub const SF_BL: u16 = 1 << 13;
    pub const AFCRL: u16 = 1 << 12;
    pub const RDSS: u16 = 1 << 11;
    pub const ST: u16 = 1 << 8;
    pub const DE: u16 = 1 << 11;
    pub const SKMODE: u16 = 1 << 10;
    pub const SEEKUP: u16 = 1 << 9;
    pub const SEEK: u16 = 1 << 8;
    pub const ENABLE: u16 = 1;
    pub const DISABLE: u16 = 1 << 6;
    pub const RDSIEN: u16 = 1 << 15;
    pub const STCIEN: u16 = 1 << 14;
    pub const RDS: u16 = 1 << 12;
    pub const AGCD: u16 = 1 << 10;
    pub const RDSM: u16 = 1 << 11;
    pub const VOLEXT: u16 = 1 << 8;
    pub const XOSCEN: u16 = 1 << 15;
    pub const AHIZEN: u16 = 1 << 14;
    pub const TUNE: u16 = 1 << 15;
    pub const BLERA1: u16 = 1 << 10;
    pub const BLERA0: u16 = 1 << 9;
    pub const BLERB1: u16 = 1 << 15;
    pub const BLERB0: u16 = 1 << 14;
    pub const BLERC1: u16 = 1 << 13;
    pub const BLERC0: u16 = 1 << 12;
    pub const BLERD1: u16 = 1 << 11;
    pub const BLERD0: u16 = 1 << 10;
}

impl<I2C, E, IC> Si4703<I2C, IC>
where
    I2C: i2c::Write<Error = E> + i2c::Read<Error = E>,
{
    pub(crate) fn read_status(&mut self) -> Result<u16, Error<E>> {
        let mut data = [0; 4];
        self.i2c
            .read(DEVICE_ADDRESS, &mut data)
            .map_err(Error::I2C)?;
        Ok(u16::from(data[0]) << 8 | u16::from(data[1]))
    }

    pub(crate) fn read_rds(&mut self) -> Result<[u16; 16], Error<E>> {
        self.read_some_registers_bare_err(6).map_err(Error::I2C)
    }

    pub(crate) fn read_powercfg(&mut self) -> Result<u16, Error<E>> {
        self.read_powercfg_bare_err().map_err(Error::I2C)
    }

    pub(crate) fn read_powercfg_bare_err(&mut self) -> Result<u16, E> {
        let registers = self.read_some_registers_bare_err(9)?;
        Ok(registers[Register::POWERCFG])
    }

    pub(crate) fn read_some_registers_bare_err(&mut self, count: usize) -> Result<[u16; 16], E> {
        const OFFSET: usize = 0xA;
        let mut data = [0; 32];
        self.i2c.read(DEVICE_ADDRESS, &mut data[..count * 2])?;
        Ok(to_registers(data, OFFSET))
    }

    pub(crate) fn read_registers(&mut self) -> Result<[u16; 16], Error<E>> {
        self.read_registers_bare_err().map_err(Error::I2C)
    }

    pub(crate) fn read_registers_bare_err(&mut self) -> Result<[u16; 16], E> {
        const OFFSET: usize = 0xA;
        let mut data = [0; 32];
        self.i2c.read(DEVICE_ADDRESS, &mut data)?;
        let registers = to_registers(data, OFFSET);
        Ok(registers)
    }

    pub(crate) fn write_powercfg(&mut self, value: u16) -> Result<(), Error<E>> {
        self.write_powercfg_bare_err(value).map_err(Error::I2C)
    }

    pub(crate) fn write_powercfg_bare_err(&mut self, value: u16) -> Result<(), E> {
        let data = [(value >> 8) as u8, value as u8];
        self.i2c.write(DEVICE_ADDRESS, &data)
    }

    pub(crate) fn write_registers(&mut self, registers: &[u16]) -> Result<(), Error<E>> {
        self.write_registers_bare_err(registers).map_err(Error::I2C)
    }

    pub(crate) fn write_registers_bare_err(&mut self, registers: &[u16]) -> Result<(), E> {
        const OFFSET: usize = 0x2;
        let data = from_registers(registers, OFFSET);
        self.i2c
            .write(DEVICE_ADDRESS, &data[..((registers.len() - OFFSET) * 2)])
    }
}

fn to_registers(data: [u8; 32], offset: usize) -> [u16; 16] {
    let mut registers = [0; 16];
    for i in 0..registers.len() {
        registers[(i + offset) % registers.len()] =
            u16::from(data[2 * i]) << 8 | u16::from(data[2 * i + 1])
    }
    registers
}

fn from_registers(registers: &[u16], offset: usize) -> [u8; 32] {
    let mut data = [0; 32];
    for i in 0..registers.len() {
        let reg = registers[(i + offset) % registers.len()];
        data[2 * i] = (reg >> 8) as u8;
        data[2 * i + 1] = reg as u8
    }
    data
}

#[cfg(test)]
mod tests {
    extern crate embedded_hal_mock as hal;
    use super::*;
    const DATA: [u8; 32] = [
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x10, 0x11, 0x12, 0x13, 0x14,
        0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
    ];
    const REGS: [u16; 16] = [
        1, 0x203, 0x405, 0x607, 0x809, 0xA0B, 0xC0D, 0xE0F, 0x1011, 0x1213, 0x1415, 0x1617, 0x1819,
        0x1A1B, 0x1C1D, 0x1E1F,
    ];
    #[test]
    fn can_convert_to_registers() {
        let registers = to_registers(DATA, 0xA);
        const SHIFTED_REGS: [u16; 16] = [
            0xC0D, 0xE0F, 0x1011, 0x1213, 0x1415, 0x1617, 0x1819, 0x1A1B, 0x1C1D, 0x1E1F, 1, 0x203,
            0x405, 0x607, 0x809, 0xA0B,
        ];
        assert_eq!(registers, SHIFTED_REGS)
    }

    #[test]
    fn can_convert_from_registers() {
        let data = from_registers(&REGS, 0x2);
        const SHIFTED_DATA: [u8; 32] = [
            4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
            0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0, 1, 2, 3,
        ];
        assert_eq!(data, SHIFTED_DATA)
    }
}