diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 87c70b3..5508bac 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -24,4 +24,5 @@ usb-device = "0.2.8" usbd-serial = "0.1.1" cheapsdo-protocol = { path = "../protocol" } postcard = {version = "1.0.8", features = ["use-defmt"]} -heapless = {version = "0.8.0", features = ["defmt-03"]} \ No newline at end of file +heapless = {version = "0.8.0", features = ["defmt-03"]} +num = {version = "0.4.1", default-features = false, features = ["libm"]} diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 7c9ad61..6ba8ba9 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -6,6 +6,8 @@ use defmt_rtt as _; // global logger use panic_probe as _; use stm32f1xx_hal as _; +mod si5153; + // same panicking *behavior* as `panic-probe` but doesn't print a panic message // this prevents the panic message being printed *twice* when `defmt::panic` is invoked #[defmt::panic_handler] @@ -23,7 +25,7 @@ mod app { use stm32f1xx_hal::{ gpio::{self, gpioa, gpioc, Alternate, Output, PushPull}, - pac, + i2c, pac, pac::{RCC, TIM2, TIM3, TIM4}, prelude::*, rcc::Enable, @@ -39,6 +41,8 @@ mod app { use cheapsdo_protocol::{DeviceMessage, HostMessage, StatusMessage}; + use crate::si5153; + const USB_BUFFER_SIZE: usize = 64; #[local] @@ -198,6 +202,33 @@ mod app { .device_class(usbd_serial::USB_CLASS_CDC) .build(); + let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh); + let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh); + let mut i2c1 = i2c::BlockingI2c::i2c1( + cx.device.I2C1, + (scl, sda), + &mut afio.mapr, + i2c::Mode::Fast { + frequency: 400_000.Hz(), + duty_cycle: i2c::DutyCycle::Ratio2to1, + }, + clocks, + 1000, + 10, + 1000, + 1000, + ); + + defmt::info!("I2C Setup done"); + + let mut si_pll = si5153::Si5153::new(&i2c1); + si_pll.init(&mut i2c1, 10_000_000, 800_000_000, 800_000_000); + si_pll.set_ms_source(&mut i2c1, si5153::Multisynth::MS0, si5153::PLL::A); + defmt::info!("si5153 Setup done"); + + si_pll.set_ms_freq(&mut i2c1, si5153::Multisynth::MS0, 100_000_000); + si_pll.enable_ms_output(&mut i2c1, si5153::Multisynth::MS0); + update_pwm::spawn().unwrap(); ( diff --git a/firmware/src/si5153.rs b/firmware/src/si5153.rs new file mode 100644 index 0000000..757513b --- /dev/null +++ b/firmware/src/si5153.rs @@ -0,0 +1,223 @@ +use core::marker::PhantomData; +use embedded_hal::blocking::i2c; + +const I2C_ADDR: u8 = 96; + +const CLK_ENABLE_CONTROL: u8 = 3; +//const PLLX_SRC: u8 = 15; +const PLL_RESET: u8 = 177; +const XTAL_LOAD_CAP: u8 = 183; + +#[derive(PartialEq, Copy, Clone)] +pub enum PLL { + A, + B, +} + +const PLL_BASE_ADDR: [u8; 2] = [26, 34]; + +impl PLL { + fn base_address(&self) -> u8 { + return PLL_BASE_ADDR[*self as usize]; + } +} + +#[derive(Copy, Clone)] +pub enum Multisynth { + MS0, + MS1, + MS2, +} + +const MS_BASE_ADDR: [u8; 3] = [42, 50, 58]; +const MS_CTRL_ADDR: [u8; 3] = [16, 17, 18]; +const CLK_PHOFF_ADDR: [u8; 3] = [165, 166, 167]; + +impl Multisynth { + fn base_address(&self) -> u8 { + return MS_BASE_ADDR[*self as usize]; + } + + fn ctrl_address(&self) -> u8 { + return MS_CTRL_ADDR[*self as usize]; + } + + fn phoff_address(&self) -> u8 { + defmt::debug!("Adress: {}", CLK_PHOFF_ADDR[*self as usize]); + return CLK_PHOFF_ADDR[*self as usize]; + } +} + +pub struct PllParams { + pub p1: u32, + pub p2: u32, + pub p3: u32, +} + +pub struct Si5153 { + // Marker that makes sure we always get the same I2C + i2c: PhantomData, + freq_xtal: u32, + pll_freqs: [u32; 2], + outputs: u8, + ms_srcs: [PLL; 3], +} + +impl Si5153 +where + I2C: i2c::Write, +{ + pub fn new(_i2c: &I2C) -> Self { + Si5153 { + i2c: PhantomData, + freq_xtal: 0, + pll_freqs: [0, 0], + outputs: 0, + ms_srcs: [PLL::A, PLL::A, PLL::A], + } + } + + pub fn init(&mut self, i2c: &mut I2C, freq_xtal: u32, freq_a: u32, freq_b: u32) { + self.freq_xtal = freq_xtal; + + self.pll_freqs[PLL::A as usize] = freq_a; + self.pll_freqs[PLL::B as usize] = freq_b; + + self.outputs = 0xFF; + self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs); // Disable all outputs + self.write_byte_reg(i2c, XTAL_LOAD_CAP, 0xD2); //crystal load capacitor = 10pF + + self.set_pll_freq(i2c, PLL::A, freq_a); + self.set_pll_freq(i2c, PLL::B, freq_b); + + for ms in [Multisynth::MS0, Multisynth::MS1, Multisynth::MS2].iter() { + self.ms_srcs[*ms as usize] = PLL::A; + self.write_byte_reg(i2c, ms.ctrl_address(), 0x0F); // MSi as Source, PLLA to MSi, 8 mA output + self.write_byte_reg(i2c, ms.phoff_address(), 0); // Phase offset to 0. + } + + self.write_byte_reg(i2c, PLL_RESET, 0xA0); // Reset both PLLs + } + + pub fn set_pll_freq(&mut self, i2c: &mut I2C, pll: PLL, freq: u32) { + // Divider is a + (b/c) + + let (a, b, c) = as_fraction(freq, self.freq_xtal); + + let params = PllParams { + p1: 128 * a + (128 * b / c) - 512, + p2: 128 * b - c * (128 * b / c), + p3: c, + }; + + self.write_params(i2c, pll.base_address(), ¶ms); + self.pll_freqs[pll as usize] = freq; + } + + pub fn enable_ms_output(&mut self, i2c: &mut I2C, synth: Multisynth) { + self.outputs &= !(1 << (synth as u8)); + self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs); + } + + pub fn disable_ms_output(&mut self, i2c: &mut I2C, synth: Multisynth) { + self.outputs |= 1 << (synth as u8); + self.write_byte_reg(i2c, CLK_ENABLE_CONTROL, self.outputs); + } + + pub fn set_ms_source(&mut self, i2c: &mut I2C, synth: Multisynth, pll: PLL) { + let value: u8 = if pll == PLL::A { + self.ms_srcs[synth as usize] = PLL::A; + 0x0F // MS as Source, PLLA to MS, 8 mA output + } else { + self.ms_srcs[synth as usize] = PLL::B; + 0x2F // MS as Source, PLLB to MS, 8 mA output + }; + + self.write_byte_reg(i2c, synth.ctrl_address(), value); + } + + pub fn set_ms_freq(&mut self, i2c: &mut I2C, synth: Multisynth, freq: u32) { + let pll = self.ms_srcs[synth as usize]; + let pll_freq = self.pll_freqs[pll as usize]; + + let (a, b, c) = as_fraction(pll_freq, freq); + + let params = PllParams { + p1: 128 * a + (128 * b / c) - 512, + p2: 128 * b - c * (128 * b / c), + p3: c, + }; + self.write_params(i2c, synth.base_address(), ¶ms) + } + + pub fn set_ms_phase(&mut self, i2c: &mut I2C, synth: Multisynth, phase: u8) { + self.write_byte_reg(i2c, synth.phoff_address(), phase); + + match self.ms_srcs[synth as usize] { + PLL::A => { + self.write_byte_reg(i2c, PLL_RESET, 1 << 5); + } + PLL::B => { + self.write_byte_reg(i2c, PLL_RESET, 1 << 7); + } + } + } + + pub fn pll_reset(&mut self, i2c: &mut I2C) { + self.write_byte_reg(i2c, PLL_RESET, 0xA0); + } + + fn write_byte_reg(&self, i2c: &mut I2C, reg_addr: u8, data: u8) { + let res = i2c.write(I2C_ADDR, &[reg_addr, data]); + if res.is_err() { + panic!("i2c write failed. regAdder: {}", reg_addr) + } + } + + fn write_params(&self, i2c: &mut I2C, base: u8, params: &PllParams) { + let data: [u8; 9] = [ + base, + ((params.p3 & 0x00FF00) >> 8) as u8, + (params.p3 & 0x0000FF) as u8, + ((params.p1 & 0x030000) >> 16) as u8, + ((params.p1 & 0x00FF00) >> 8) as u8, + (params.p1 & 0x0000FF) as u8, + (((params.p3 & 0x0F0000) >> 12) | ((params.p2 & 0x0F0000) >> 16)) as u8, + ((params.p2 & 0x00FF00) >> 8) as u8, + (params.p2 & 0x0000FF) as u8, + ]; + + let res = i2c.write(I2C_ADDR, &data); + if res.is_err() { + panic!("i2c write failed. regAdder: {}", base) + } + } + + pub fn write_synth_params(&self, i2c: &mut I2C, synth: Multisynth, params: &PllParams) { + self.write_params(i2c, synth.base_address(), params); + } +} + +// Turns a fraction x/y into a + b/c with minimal c. +// If c is larger than 0x0FFFFF, c will be limited to that value and +// a best effort approximation for b will be computed +fn as_fraction(x: u32, y: u32) -> (u32, u32, u32) { + let gcd = num::integer::gcd(x, y); + + let num = x / gcd; + let denom = y / gcd; + + let a = num / denom; + + if denom < 0x0FFFFF { + let rm = num % denom; + let c = denom; + let b = rm; + (a, b, c) + } else { + let rm = x % y; + let c = 0x0FFFFF; + let b = ((rm as u64) * (c as u64) / (y as u64)) as u32; + (a, b, c) + } +}