commit 507bd0dfef6d7a2255f517fbdec9128103c85744 Author: Sebastian Date: Tue Dec 13 12:19:49 2022 +0100 Initial commit diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..dae491b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,19 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# TODO(2) replace `$CHIP` with your chip's name (see `probe-run --list-chips` output) +runner = "probe-run --chip STM32F103C8" +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + "-C", "link-arg=--nmagic", +] + +[build] +target = "thumbv7m-none-eabi" # Cortex-M3 + + +[alias] +rb = "run --bin" +rrb = "run --release --bin" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b2d2674 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,80 @@ +[package] +authors = ["LongHairedHacker "] +name = "wspr-beacon" +edition = "2018" +version = "0.1.0" + +[dependencies] +cortex-m = "0.7.6" +cortex-m-rt = "0.7.2" +cortex-m-rtic = "1.1.3" +defmt = "0.3.2" +defmt-rtt = "0.3.2" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } +stm32f1xx-hal = { version = "0.9.0", features = ["stm32f103", "rt"] } +embedded-hal = {version = "0.2.3"} +nb = "1.0.0" +arrayvec = {version = "0.7.0", default-features = false} +systick-monotonic = "1.0.0" + + +[features] +# set logging levels here +default = [ + "defmt-default", + # "dependency-a/defmt-trace", +] + +# do NOT modify these features +defmt-default = [] +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 'z' # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# uncomment this to switch from the crates.io version of defmt to its git version +# check app-template's README for instructions +# [patch.crates-io] +# defmt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# defmt-rtt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# defmt-test = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# panic-probe = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..89459b6 --- /dev/null +++ b/memory.x @@ -0,0 +1,6 @@ +/* Fake bluepill using STM32F103C8T6 */ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..69200f3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,148 @@ +#![no_main] +#![no_std] +use defmt_rtt as _; // global logger + +use panic_probe as _; +use stm32f1xx_hal as _; + +// 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] +fn panic() -> ! { + cortex_m::asm::udf() +} + +use rtic::app; + +mod si5153; + +#[app(device = stm32f1xx_hal::pac, peripherals = true, dispatchers = [TIM2])] +mod app { + + use core::num::dec2flt::float; + + use stm32f1xx_hal::{ + adc, + gpio::{ + self, gpioa, gpiob, gpioc, Alternate, Analog, Floating, Input, OpenDrain, Output, + PushPull, CRL, + }, + i2c, + i2c::BlockingI2c, + pac::{ADC1, I2C1}, + prelude::*, + serial::{self, Config, Serial}, + stm32, + timer::{self, Event}, + }; + + use systick_monotonic::Systick; + + use arrayvec::ArrayString; + + use crate::si5153; + + type AppI2C1 = BlockingI2c< + I2C1, + ( + gpiob::PB6>, + gpiob::PB7>, + ), + >; + + #[monotonic(binds = SysTick, default = true)] + type MonoTimer = Systick<1_000_000>; + + #[shared] + struct Shared {} + + #[local] + struct Local { + pll: si5153::Si5153, + i2c: AppI2C1, + board_led: gpioc::PC13>, + adc1: adc::Adc, + mic_in: gpio::Pin, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let mut flash = cx.device.FLASH.constrain(); + let rcc = cx.device.RCC.constrain(); + + // Freeze the configuration of all the clocks in the system and store the frozen frequencies in + // `clocks` + let clocks = rcc + .cfgr + .use_hse(8.MHz()) + .sysclk(72.MHz()) + .pclk1(36.MHz()) + .freeze(&mut flash.acr); + + defmt::info!("Clock Setup done"); + + let mono = Systick::new(cx.core.SYST, clocks.sysclk().to_Hz()); + + let mut afio = cx.device.AFIO.constrain(); + + // Acquire the GPIOC peripheral + let mut gpioa = cx.device.GPIOA.split(); + let mut gpiob = cx.device.GPIOB.split(); + let mut gpioc = cx.device.GPIOC.split(); + + let board_led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); + + let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl); + let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl); + let mut i2c = i2c::BlockingI2c::i2c1( + cx.device.I2C1, + (scl, sda), + &mut afio.mapr, + i2c::Mode::Standard { + frequency: 400.kHz(), + }, + clocks, + 5, + 1, + 5, + 5, + ); + + let mut pll = si5153::Si5153::new(&i2c); + pll.init(&mut i2c, 25000000, 800000000, 800000000); + pll.set_ms_source(&mut i2c, si5153::Multisynth::MS0, si5153::PLL::A); + + let adc1 = adc::Adc::adc1(cx.device.ADC1, clocks); + let mic_in = gpioa.pa4.into_analog(&mut gpioa.crl); + + let mut pwm = + cx.device + .TIM2 + .pwm_hz::(pins, &mut afio.mapr, 4800.Hz(), &clocks); + + transmit::spawn().unwrap(); + + ( + Shared {}, + Local { + i2c, + pll, + board_led, + adc1, + mic_in, + }, + init::Monotonics(mono), + ) + } + + #[task(local=[pll, i2c, adc1, mic_in])] + fn transmit(mut ctx: transmit::Context) { + let mut adc = ctx.local.adc1; + let mut mic_in = ctx.local.mic_in; + + let data: u16 = adc.read(&mut *mic_in).unwrap(); + let sample = (data as f32 / u16::MAX as f32); + + transmit::spawn_after(208.micros().into()).unwrap(); + } +} diff --git a/src/si5153.rs b/src/si5153.rs new file mode 100644 index 0000000..b077da2 --- /dev/null +++ b/src/si5153.rs @@ -0,0 +1,175 @@ +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]; + +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]; + } +} + +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, + 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, + 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.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.write_byte_reg(i2c, PLL_RESET, 0xA0); // Reset both PLLs + + 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 + } + + for pll in [PLL::A, PLL::B].iter() { + let fdiv = self.pll_freqs[*pll as usize] / freq_xtal; + let rm = self.pll_freqs[*pll as usize] % freq_xtal; + + //TODO: Find better way to determine c and b + let c = 0x0FFFFF; + let a = fdiv; + let b = ((rm as u64) * (c as u64) / (freq_xtal as u64)) as u32; + + 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) + } + } + + 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 fdiv = self.pll_freqs[pll as usize] / freq; + let rm = self.pll_freqs[pll as usize] % freq; + + //TODO: Find better way to determine c and b + let c: u32 = 0x0FFFFF; + let a: u32 = fdiv; + let b: u32 = ((rm as u64) * (c as u64) / (freq as u64)) as u32; + + 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) + } + + 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); + } +}