diff --git a/Cargo.toml b/Cargo.toml index 115a336..9b77209 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,9 @@ [package] name = "si5351" -version = "0.1.0" -categories = ["embedded", "hardware-support", "no-std"] +version = "0.1.1" +categories = ["embedded-hal-driver", "ham-radio", "clock", "pll" ] authors = ["Ilya Epifanov "] [dependencies] -embedded-hal = "0.1.0" - -[dependencies.cast] -default-features = false -version = "0.2.2" +embedded-hal = "0.1" +bitflags = "1.0" diff --git a/Xargo.toml b/Xargo.toml new file mode 100644 index 0000000..f0c1e62 --- /dev/null +++ b/Xargo.toml @@ -0,0 +1,7 @@ +[dependencies.core] +stage = 0 + +[dependencies.compiler_builtins] +git = "https://github.com/rust-lang-nursery/compiler-builtins" +features = ["mem"] +stage = 1 diff --git a/src/lib.rs b/src/lib.rs index f7f551b..180a9bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,140 +3,555 @@ #![feature(unsize)] #![no_std] -extern crate cast; +#[macro_use] +extern crate bitflags; extern crate embedded_hal as hal; -use cast::{f32, u32}; use core::mem; use hal::blocking::i2c::{Write, WriteRead}; -mod si5351; - -/// Si5351 driver -pub struct Si5351 { - i2c: I2C, - address: u8, - xtal_freq: u32, +#[derive(Debug)] +pub enum Error { + CommunicationError, + InvalidParameter, } -impl Si5351 +#[derive(Debug, Copy, Clone)] +pub enum CrystalLoad { + _6, + _8, + _10, +} + +#[derive(Debug, Copy, Clone)] +pub enum PLL { + A, + B, +} + +#[derive(Debug, Copy, Clone)] +pub enum FeedbackMultisynth { + MSNA, + MSNB, +} + +#[derive(Debug, Copy, Clone)] +pub enum Multisynth { + MS0, + MS1, + MS2, + MS3, + MS4, + MS5, +} + +#[derive(Debug, Copy, Clone)] +pub enum SimpleMultisynth { + MS6, + MS7, +} + +#[derive(Debug, Copy, Clone)] +pub enum ClockOutput { + Clk0 = 0, + Clk1, + Clk2, + Clk3, + Clk4, + Clk5, + Clk6, + Clk7, +} + +#[derive(Debug, Copy, Clone)] +pub enum OutputDivider { + Div1 = 0, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +const ADDRESS: u8 = 0b0110_0000; + +impl PLL { + pub fn multisynth(&self) -> FeedbackMultisynth { + match *self { + PLL::A => FeedbackMultisynth::MSNA, + PLL::B => FeedbackMultisynth::MSNB, + } + } +} + +trait FractionalMultisynth { + fn base_addr(&self) -> u8; + fn ix(&self) -> u8; +} + +impl FractionalMultisynth for FeedbackMultisynth { + fn base_addr(&self) -> u8 { + match *self { + FeedbackMultisynth::MSNA => 26, + FeedbackMultisynth::MSNB => 34, + } + } + fn ix(&self) -> u8 { + match *self { + FeedbackMultisynth::MSNA => 6, + FeedbackMultisynth::MSNB => 7, + } + } +} + +impl FractionalMultisynth for Multisynth { + fn base_addr(&self) -> u8 { + match *self { + Multisynth::MS0 => 42, + Multisynth::MS1 => 50, + Multisynth::MS2 => 58, + Multisynth::MS3 => 66, + Multisynth::MS4 => 74, + Multisynth::MS5 => 82, + } + } + fn ix(&self) -> u8 { + match *self { + Multisynth::MS0 => 0, + Multisynth::MS1 => 1, + Multisynth::MS2 => 2, + Multisynth::MS3 => 3, + Multisynth::MS4 => 4, + Multisynth::MS5 => 5, + } + } +} + +impl SimpleMultisynth { + pub fn base_addr(&self) -> u8 { + match *self { + SimpleMultisynth::MS6 => 90, + SimpleMultisynth::MS7 => 91, + } + } +} + +#[derive(Debug, Copy, Clone)] +enum Register { + DeviceStatus = 0, + OutputEnable = 3, + Clk0 = 16, + Clk1 = 17, + Clk2 = 18, + Clk3 = 19, + Clk4 = 20, + Clk5 = 21, + Clk6 = 22, + Clk7 = 23, + PLLReset = 177, + CrystalLoad = 183, +} + +impl Register { + pub fn addr(&self) -> u8 { + *self as u8 + } +} + +bitflags! { + pub struct DeviceStatusBits: u8 { + const SYS_INIT = 0b1000_0000; + const LOL_B = 0b0100_0000; + const LOL_A = 0b0010_0000; + const LOS = 0b0001_0000; + } +} + +bitflags! { + struct CrystalLoadBits: u8 { + const RESERVED = 0b00_010010; + const CL_MASK = 0b11_000000; + const CL_6 = 0b01_000000; + const CL_8 = 0b10_000000; + const CL_10 = 0b11_000000; + } +} + +bitflags! { + struct ClockControlBits: u8 { + const CLK_PDN = 0b1000_0000; + const MS_INT = 0b0100_0000; + const MS_SRC = 0b0010_0000; + const CLK_INV = 0b0001_0000; + const CLK_SRC_MASK = 0b0000_1100; + const CLK_SRC_XTAL = 0b0000_0000; + const CLK_SRC_CLKIN = 0b0000_0100; + const CLK_SRC_MS_ALT = 0b0000_1000; + const CLK_SRC_MS = 0b0000_1100; + const CLK_DRV_MASK = 0b0000_0011; + const CLK_DRV_2 = 0b0000_0000; + const CLK_DRV_4 = 0b0000_0001; + const CLK_DRV_6 = 0b0000_0010; + const CLK_DRV_8 = 0b0000_0011; + } +} + +bitflags! { + struct PLLResetBits: u8 { + const PLLB_RST = 0b1000_0000; + const PLLA_RST = 0b0010_0000; + } +} + +impl ClockOutput { + fn register(self) -> Register { + match self { + ClockOutput::Clk0 => Register::Clk0, + ClockOutput::Clk1 => Register::Clk1, + ClockOutput::Clk2 => Register::Clk2, + ClockOutput::Clk3 => Register::Clk3, + ClockOutput::Clk4 => Register::Clk4, + ClockOutput::Clk5 => Register::Clk5, + ClockOutput::Clk6 => Register::Clk6, + ClockOutput::Clk7 => Register::Clk7, + } + } + + fn ix(&self) -> u8 { + *self as u8 + } +} + +impl OutputDivider { + fn bits(&self) -> u8 { + *self as u8 + } + + fn min_divider(desired_divider: u16) -> Result { + match 16 - (desired_divider.max(1) - 1).leading_zeros() { + 0 => Ok(OutputDivider::Div1), + 1 => Ok(OutputDivider::Div2), + 2 => Ok(OutputDivider::Div4), + 3 => Ok(OutputDivider::Div8), + 4 => Ok(OutputDivider::Div16), + 5 => Ok(OutputDivider::Div32), + 6 => Ok(OutputDivider::Div64), + 7 => Ok(OutputDivider::Div128), + _ => Err(Error::InvalidParameter) + } + } + + fn denominator_u8(&self) -> u8 { + match *self { + OutputDivider::Div1 => 1, + OutputDivider::Div2 => 2, + OutputDivider::Div4 => 4, + OutputDivider::Div8 => 8, + OutputDivider::Div16 => 16, + OutputDivider::Div32 => 32, + OutputDivider::Div64 => 64, + OutputDivider::Div128 => 128, + } + } +} + +fn i2c_error(_: E) -> Error { + Error::CommunicationError +} + +/// Si5351 driver +pub struct Si5351Device<'a, I2C: 'a> { + i2c: &'a mut I2C, + address: u8, + xtal_freq: u32, + clk_enabled_mask: u8, + ms_int_mode_mask: u8, + ms_src_mask: u8, +} + +pub trait Si5351<'a> { + fn init_adafruit_module(&mut self) -> Result<(), Error>; + fn init(&mut self, xtal_load: CrystalLoad) -> Result<(), Error>; + fn read_device_status(&mut self) -> Result; + + fn find_int_dividers_for_max_pll_freq(&self, max_pll_freq: u32, freq: u32) -> Result<(u16, OutputDivider), Error>; + fn find_pll_coeffs_for_dividers(&self, total_div: u32, denom: u32, freq: u32) -> Result<(u8, u32), Error>; + + fn set_frequency(&mut self, pll: PLL, clk: ClockOutput, freq: u32) -> Result<(), Error>; + fn set_clock_enabled(&mut self, clk: ClockOutput, enabled: bool); + + fn flush_output_enabled(&mut self) -> Result<(), Error>; + fn flush_clock_control(&mut self, clk: ClockOutput) -> Result<(), Error>; + + fn setup_pll_int(&mut self, pll: PLL, mult: u8) -> Result<(), Error>; + fn setup_pll(&mut self, pll: PLL, mult: u8, num: u32, denom: u32) -> Result<(), Error>; + fn setup_multisynth_int(&mut self, ms: Multisynth, mult: u16, r_div: OutputDivider) -> Result<(), Error>; + fn setup_multisynth(&mut self, ms: Multisynth, div: u16, num: u32, denom: u32, r_div: OutputDivider) -> Result<(), Error>; + fn select_clock_pll(&mut self, clocl: ClockOutput, pll: PLL); +} + +impl<'a, I2C, E> Si5351Device<'a, I2C> where I2C: WriteRead + Write, { /// Creates a new driver from a I2C peripheral - pub fn new(i2c: I2C, address_bit: bool, xtal_freq: u32) -> Result { - let si5351 = Si5351 { + pub fn new(i2c: &'a mut I2C, address_bit: bool, xtal_freq: u32) -> Self { + let si5351 = Si5351Device { i2c, - address: si5351::ADDRESS | if address_bit { 1 } else { 0 }, + address: ADDRESS | if address_bit { 1 } else { 0 }, xtal_freq, + clk_enabled_mask: 0, + ms_int_mode_mask: 0, + ms_src_mask: 0, }; - Ok(si5351) + si5351 } - pub fn set_frequency(&mut self, freq: u32) -> Result<(), E> { - let pll_freq: u32; - let l: u32; - let f: f32; - let mult: u8; - let num: u32; - let denom: u32; - let divider: u32; - - divider = match 900000000 / freq { - d if d % 2 == 0 => d, - d => d - 1 - }; - - pll_freq = divider * freq; - - mult = (pll_freq / self.xtal_freq) as u8; - l = pll_freq % self.xtal_freq; - f = (l as f32) * (1048575 as f32) / (self.xtal_freq as f32); - num = f as u32; - denom = 1048575; - - self.setup_pll(si5351::PLL::A, mult, num, denom)?; - self.setup_multisynth(si5351::Multisynth::MS0, divider, 0)?; - self.reset_pll(si5351::PLL::A)?; - self.enable_clock(si5351::ClockOutput::CLK0)?; - - Ok(()) + pub fn new_adafruit_module(i2c: &'a mut I2C) -> Self { + Si5351Device::new(i2c, false, 25_000_000) } - fn setup_pll(&mut self, pll: si5351::PLL, mult: u8, num: u32, denom: u32) -> Result<(), E> { - let p1: u32; - let p2: u32; - let p3: u32; - let ratio = (128f32 * (f32(num) / f32(denom))) as u32; + fn write_ms_config(&mut self, ms: MS, int: u16, frac_num: u32, frac_denom: u32, r_div: OutputDivider) -> Result<(), Error> { + if frac_denom == 0 { + return Err(Error::InvalidParameter); + } + if frac_num > 0xfffff { + return Err(Error::InvalidParameter); + } + if frac_denom > 0xfffff { + return Err(Error::InvalidParameter); + } - p1 = u32(128u32 * mult as u32 + ratio - 512); - p2 = u32(128 * num - denom * ratio); - p3 = denom; - - self.write_pll_register(pll, 0, ((p3 & 0x0000FF00) >> 8) as u8)?; - self.write_pll_register(pll, 1, p3 as u8)?; - self.write_pll_register(pll, 2, ((p1 & 0x00030000) >> 16) as u8)?; - self.write_pll_register(pll, 3, ((p1 & 0x0000FF00) >> 8) as u8)?; - self.write_pll_register(pll, 4, p1 as u8)?; - self.write_pll_register(pll, 5, (((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16)) as u8)?; - self.write_pll_register(pll, 6, ((p2 & 0x0000FF00) >> 8) as u8)?; - self.write_pll_register(pll, 7, p2 as u8)?; - - Ok(()) - } - - fn setup_multisynth(&mut self, synth: si5351::Multisynth, divider: u32, r_div: u8) -> Result<(), E> { let p1: u32; let p2: u32; let p3: u32; - p1 = 128 * divider - 512; - p2 = 0; - p3 = 1; + if frac_num == 0 { + p1 = 128 * int as u32 - 512; + p2 = 0; + p3 = 1; + } else { + let ratio = (128u64 * (frac_num as u64) / (frac_denom as u64)) as u32; - self.write_synth_register(synth, 0, ((p3 & 0x0000FF00) >> 8) as u8)?; - self.write_synth_register(synth, 1, p3 as u8)?; - self.write_synth_register(synth, 2, ((p1 & 0x00030000) >> 16) as u8 | r_div)?; - self.write_synth_register(synth, 3, ((p1 & 0x0000FF00) >> 8) as u8)?; - self.write_synth_register(synth, 4, p1 as u8)?; - self.write_synth_register(synth, 5, (((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16)) as u8)?; - self.write_synth_register(synth, 6, ((p2 & 0x0000FF00) >> 8) as u8)?; - self.write_synth_register(synth, 7, p2 as u8)?; + p1 = 128 * int as u32 + ratio - 512; + p2 = 128 * frac_num - frac_denom * ratio; + p3 = frac_denom; + } + + self.write_synth_registers(ms, [ + ((p3 & 0x0000FF00) >> 8) as u8, + p3 as u8, + ((p1 & 0x00030000) >> 16) as u8 | r_div.bits(), + ((p1 & 0x0000FF00) >> 8) as u8, + p1 as u8, + (((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16)) as u8, + ((p2 & 0x0000FF00) >> 8) as u8, + p2 as u8, + ])?; + + if frac_num == 0 { + self.ms_int_mode_mask |= ms.ix(); + } else { + self.ms_int_mode_mask &= !ms.ix(); + } Ok(()) } - fn reset_pll(&mut self, pll: si5351::PLL) -> Result<(), E> { - self.write_register(si5351::Register::PLL_RESET, - match pll { - si5351::PLL::A => 0b0010_0000, - si5351::PLL::B => 0b1000_0000 - })?; + fn reset_pll(&mut self, pll: PLL) -> Result<(), Error> { + self.write_register(Register::PLLReset, match pll { + PLL::A => PLLResetBits::PLLA_RST.bits(), + PLL::B => PLLResetBits::PLLB_RST.bits(), + })?; Ok(()) } - fn enable_clock(&mut self, clock: si5351::ClockOutput) -> Result<(), E> { - self.write_register(clock.register(), 0x4f) - } - - #[allow(unused)] - fn read_register(&mut self, reg: si5351::Register) -> Result { + fn read_register(&mut self, reg: Register) -> Result { let mut buffer: [u8; 1] = unsafe { mem::uninitialized() }; - self.i2c.write_read(self.address, &[reg.addr()], &mut buffer)?; + self.i2c.write_read(self.address, &[reg.addr()], &mut buffer).map_err(i2c_error)?; Ok(buffer[0]) } - fn write_register(&mut self, reg: si5351::Register, byte: u8) -> Result<(), E> { - self.i2c.write(self.address, &[reg.addr(), byte]) + fn write_register(&mut self, reg: Register, byte: u8) -> Result<(), Error> { + self.i2c.write(self.address, &[reg.addr(), byte]).map_err(i2c_error) } - fn write_pll_register(&mut self, pll: si5351::PLL, reg: u8, byte: u8) -> Result<(), E> { - self.i2c.write(self.address, &[pll.base_addr() + reg, byte]) - } - - fn write_synth_register(&mut self, pll: si5351::Multisynth, reg: u8, byte: u8) -> Result<(), E> { - self.i2c.write(self.address, &[pll.base_addr() + reg, byte]) + fn write_synth_registers(&mut self, ms: MS, params: [u8; 8]) -> Result<(), Error> { + self.i2c.write(self.address, &[ms.base_addr(), + params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7] + ]).map_err(i2c_error) + } +} + +impl<'a, I2C, E> Si5351<'a> for Si5351Device<'a, I2C> where + I2C: WriteRead + Write +{ + fn init_adafruit_module(&mut self) -> Result<(), Error> { + self.init(CrystalLoad::_10) + } + + fn init(&mut self, xtal_load: CrystalLoad) -> Result<(), Error> { + loop { + let device_status = self.read_device_status()?; + if !device_status.contains(DeviceStatusBits::SYS_INIT) { + break; + } + } + + self.flush_output_enabled()?; + const CLK_REGS: [Register; 8] = [Register::Clk0, Register::Clk1, + Register::Clk2, Register::Clk3, Register::Clk4, + Register::Clk5, Register::Clk6, Register::Clk7]; + for ® in CLK_REGS.iter() { + self.write_register(reg, ClockControlBits::CLK_PDN.bits())?; + } + + self.write_register(Register::CrystalLoad, + (CrystalLoadBits::RESERVED | match xtal_load { + CrystalLoad::_6 => CrystalLoadBits::CL_6, + CrystalLoad::_8 => CrystalLoadBits::CL_8, + CrystalLoad::_10 => CrystalLoadBits::CL_10, + }).bits())?; + + Ok(()) + } + + fn read_device_status(&mut self) -> Result { + Ok(DeviceStatusBits::from_bits_truncate(self.read_register(Register::DeviceStatus)?)) + } + + fn find_int_dividers_for_max_pll_freq(&self, max_pll_freq: u32, freq: u32) -> Result<(u16, OutputDivider), Error> { + let total_divider = (max_pll_freq / freq) as u16; + + let r_div = OutputDivider::min_divider(total_divider / 900)?; + + let ms_div = (total_divider / (2 * r_div.denominator_u8() as u16) * 2).max(6); + if ms_div > 1800 { + return Err(Error::InvalidParameter); + } + + Ok((ms_div, r_div)) + } + + fn find_pll_coeffs_for_dividers(&self, total_div: u32, denom: u32, freq: u32) -> Result<(u8, u32), Error> { + if denom == 0 || denom > 0xfffff { + return Err(Error::InvalidParameter); + } + + let pll_freq = freq * total_div; + + let mult = (pll_freq / self.xtal_freq) as u8; + let f = ((pll_freq % self.xtal_freq) as u64 * denom as u64 / self.xtal_freq as u64) as u32; + + Ok((mult, f)) + } + + fn set_frequency(&mut self, pll: PLL, clk: ClockOutput, freq: u32) -> Result<(), Error> { + let denom: u32 = 1048575; + + let (ms_divider, r_div) = self.find_int_dividers_for_max_pll_freq(900_000_000, freq)?; + let total_div = ms_divider as u32 * r_div.denominator_u8() as u32; + let (mult, num) = self.find_pll_coeffs_for_dividers(total_div, denom, freq)?; + + let ms = match clk { + ClockOutput::Clk0 => Multisynth::MS0, + ClockOutput::Clk1 => Multisynth::MS1, + ClockOutput::Clk2 => Multisynth::MS2, + ClockOutput::Clk3 => Multisynth::MS3, + ClockOutput::Clk4 => Multisynth::MS4, + ClockOutput::Clk5 => Multisynth::MS5, + _ => return Err(Error::InvalidParameter), + }; + + self.setup_pll(pll, mult, num, denom)?; + self.setup_multisynth_int(ms, ms_divider, r_div)?; + self.select_clock_pll(clk, pll); + self.set_clock_enabled(clk, true); + self.flush_clock_control(clk)?; + self.reset_pll(pll)?; + self.flush_output_enabled()?; + + Ok(()) + } + + fn set_clock_enabled(&mut self, clk: ClockOutput, enabled: bool) { + let bit = 1u8 << clk.ix(); + if enabled { + self.clk_enabled_mask |= bit; + } else { + self.clk_enabled_mask &= !bit; + } + } + + fn flush_output_enabled(&mut self) -> Result<(), Error> { + let mask = self.clk_enabled_mask; + self.write_register(Register::OutputEnable, !mask) + } + + fn flush_clock_control(&mut self, clk: ClockOutput) -> Result<(), Error> { + let bit = 1u8 << clk.ix(); + let clk_control_pdn = if self.clk_enabled_mask & bit != 0 { + ClockControlBits::empty() + } else { + ClockControlBits::CLK_PDN + }; + + let ms_int_mode = if self.ms_int_mode_mask & bit == 0 { + ClockControlBits::empty() + } else { + ClockControlBits::MS_INT + }; + + let ms_src = if self.ms_src_mask & bit == 0 { + ClockControlBits::empty() + } else { + ClockControlBits::MS_SRC + }; + + let base = ClockControlBits::CLK_SRC_MS | ClockControlBits::CLK_DRV_8; + + self.write_register(clk.register(), (clk_control_pdn | ms_int_mode | ms_src | base).bits()) + } + + fn setup_pll_int(&mut self, pll: PLL, mult: u8) -> Result<(), Error> { + self.setup_pll(pll, mult, 0, 1) + } + + fn setup_pll(&mut self, pll: PLL, mult: u8, num: u32, denom: u32) -> Result<(), Error> { + if mult < 15 || mult > 90 { + return Err(Error::InvalidParameter); + } + + self.write_ms_config(pll.multisynth(), mult.into(), num, denom, OutputDivider::Div1)?; + + if mult % 2 == 0 && num == 0 {} else {} + + Ok(()) + } + + fn setup_multisynth_int(&mut self, ms: Multisynth, mult: u16, r_div: OutputDivider) -> Result<(), Error> { + self.setup_multisynth(ms, mult, 0, 1, r_div) + } + + fn setup_multisynth(&mut self, ms: Multisynth, div: u16, num: u32, denom: u32, r_div: OutputDivider) -> Result<(), Error> { + if div < 6 || div > 1800 { + return Err(Error::InvalidParameter); + } + + self.write_ms_config(ms, div, num, denom, r_div)?; + + Ok(()) + } + + fn select_clock_pll(&mut self, clock: ClockOutput, pll: PLL) { + let bit = 1u8 << clock.ix(); + match pll { + PLL::A => self.ms_src_mask &= !bit, + PLL::B => self.ms_src_mask |= bit, + } } } diff --git a/src/si5351.rs b/src/si5351.rs deleted file mode 100644 index 42a0fc2..0000000 --- a/src/si5351.rs +++ /dev/null @@ -1,65 +0,0 @@ -pub const ADDRESS: u8 = 0b1100_0000; - -#[allow(dead_code)] -#[allow(non_camel_case_types)] -#[derive(Copy, Clone)] -pub enum PLL { - A = 26, - B = 34 -} - -impl PLL { - pub fn base_addr(&self) -> u8 { - *self as u8 - } -} - -#[allow(dead_code)] -#[allow(non_camel_case_types)] -#[derive(Copy, Clone)] -pub enum Multisynth { - MS0 = 42, - MS1 = 50, - MS2 = 58 -} - -impl Multisynth { - pub fn base_addr(&self) -> u8 { - *self as u8 - } -} - -#[allow(dead_code)] -#[allow(non_camel_case_types)] -#[derive(Copy, Clone)] -pub enum Register { - PLL_RESET = 177, - CLK0 = 16, - CLK1 = 17, - CLK2 = 18 -} - -impl Register { - pub fn addr(&self) -> u8 { - *self as u8 - } -} - -#[allow(dead_code)] -#[allow(non_camel_case_types)] -#[derive(Copy, Clone)] -pub enum ClockOutput { - CLK0 = 16, - CLK1 = 17, - CLK2 = 18 -} - -impl ClockOutput { - pub fn register(&self) -> Register { - match self { - &ClockOutput::CLK0 => Register::CLK0, - &ClockOutput::CLK1 => Register::CLK1, - &ClockOutput::CLK2 => Register::CLK2 - } - } -}