Ported basic things over from STM32f1043 version

This commit is contained in:
Sebastian 2023-07-02 00:30:17 +02:00
commit f9dcb07de5
8 changed files with 643 additions and 0 deletions

21
.cargo/config.toml Normal file
View File

@ -0,0 +1,21 @@
[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 STM32F401CCU6"
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 = "thumbv7em-none-eabihf"
[alias]
rb = "run --bin"
rrb = "run --release --bin"
[env]
DEFMT_LOG = "debug"

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

82
Cargo.toml Normal file
View File

@ -0,0 +1,82 @@
[package]
authors = ["Sebastian <sebastian@sebastians-site.de>"]
name = "fartrx"
edition = "2018"
version = "0.1.0"
[dependencies]
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7.2"
cortex-m-rtic = "1.1.3"
defmt = "0.3.2"
defmt-rtt = "0.4"
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
stm32f4xx-hal = { version = "0.16.2", features = ["stm32f401"] }
embedded-hal = {version = "0.2.3"}
nb = "1.0.0"
arrayvec = {version = "0.7.0", default-features = false}
systick-monotonic = "1.0.0"
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
num = {version = "0.4", default-features = false}
[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`" }

11
memory.x Normal file
View File

@ -0,0 +1,11 @@
MEMORY
{
/* NOTE K = KiBi = 1024 bytes */
FLASH : ORIGIN = 0x08000000, LENGTH = 256K
RAM : ORIGIN = 0x20000000, LENGTH = 64K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

98
src/filters.rs Normal file
View File

@ -0,0 +1,98 @@
use num::complex::Complex;
pub struct FirFilter<const LEN: usize> {
state: [Complex<f32>; LEN],
coeffs: [Complex<f32>; LEN],
pos: usize,
}
impl<const LEN: usize> FirFilter<LEN> {
pub fn new(coeffs: [Complex<f32>; LEN]) -> FirFilter<LEN> {
FirFilter {
state: [Complex::new(0.0, 0.0); LEN],
coeffs: coeffs,
pos: 0,
}
}
pub fn compute(&mut self, sample: Complex<f32>) -> Complex<f32> {
self.state[self.pos] = sample;
self.pos = (self.pos + 1) % LEN;
let mut result = Complex::<f32>::new(0.0, 0.0);
for i in 0..LEN {
result += self.state[(self.pos + i) % LEN] * self.coeffs[i];
}
result
}
}
pub fn usb_firfilter() -> FirFilter<63> {
FirFilter::new([
Complex::new(-7.76328407e-03, 0.00000000e+00),
Complex::new(-1.76283137e-04, -4.92678363e-04),
Complex::new(-6.25577634e-03, 5.13398296e-03),
Complex::new(-6.39279452e-03, -3.83169357e-03),
Complex::new(-5.03930054e-04, 2.53342746e-03),
Complex::new(-1.03144816e-02, 1.53000882e-03),
Complex::new(-2.84091140e-03, -5.31497140e-03),
Complex::new(-4.30880421e-03, 4.75403284e-03),
Complex::new(-1.12464745e-02, -4.65844227e-03),
Complex::new(1.60156679e-04, -3.26006409e-03),
Complex::new(-1.03362126e-02, 3.13545581e-03),
Complex::new(-7.69974139e-03, -1.03818994e-02),
Complex::new(-5.76821480e-04, 8.63274351e-04),
Complex::new(-1.50337277e-02, -3.76575276e-03),
Complex::new(-1.19695644e-03, -1.21529027e-02),
Complex::new(-6.42978800e-03, 3.04106324e-03),
Complex::new(-1.42967043e-02, -1.42967043e-02),
Complex::new(4.02680910e-03, -8.51397251e-03),
Complex::new(-1.52764230e-02, -1.50459634e-03),
Complex::new(-5.98961717e-03, -2.39118921e-02),
Complex::new(2.95727524e-03, -1.97598814e-03),
Complex::new(-2.14271696e-02, -1.58914720e-02),
Complex::new(8.26910313e-03, -2.72595798e-02),
Complex::new(-7.54577227e-03, 3.70700021e-04),
Complex::new(-1.67955192e-02, -4.05479701e-02),
Complex::new(2.33021328e-02, -2.11198221e-02),
Complex::new(-2.74472271e-02, -1.46708486e-02),
Complex::new(1.15826806e-02, -7.80840900e-02),
Complex::new(3.26800996e-02, -6.50047599e-03),
Complex::new(-6.05385232e-02, -1.01002424e-01),
Complex::new(1.84598172e-01, -2.24933523e-01),
Complex::new(3.45823071e-01, 1.23737473e-01),
Complex::new(-7.14505905e-17, 2.90983805e-01),
Complex::new(-1.10872171e-01, 3.96706970e-02),
Complex::new(2.11382003e-02, 2.57569716e-02),
Complex::new(-4.05824891e-02, 6.77077926e-02),
Complex::new(-3.05240813e-02, -6.07161727e-03),
Complex::new(4.61451895e-03, 3.11085599e-02),
Complex::new(-3.87064718e-02, 2.06890402e-02),
Complex::new(-5.59779124e-03, -5.07354224e-03),
Complex::new(-1.09011912e-02, 2.63178036e-02),
Complex::new(-2.66448692e-02, -1.30897849e-03),
Complex::new(1.03245107e-03, 3.40353504e-03),
Complex::new(-1.97995805e-02, 1.46843697e-02),
Complex::new(-1.27633405e-02, -8.52819147e-03),
Complex::new(-2.28844145e-03, 9.13597039e-03),
Complex::new(-2.01212351e-02, 1.98176868e-03),
Complex::new(-3.04106324e-03, -6.42978800e-03),
Complex::new(-8.63497967e-03, 8.63497967e-03),
Complex::new(-1.40101969e-02, -6.62632965e-03),
Complex::new(1.01766417e-04, -1.03325177e-03),
Complex::new(-1.25381879e-02, 3.14065256e-03),
Complex::new(-6.00088826e-03, -8.98096395e-03),
Complex::new(-1.94435998e-03, 2.62166594e-03),
Complex::new(-1.16489269e-02, -3.53366333e-03),
Complex::new(-3.14824186e-04, -6.40839353e-03),
Complex::new(5.56783637e-03, 2.30627334e-03),
Complex::new(-7.00257479e-03, -7.72615067e-03),
Complex::new(1.21764617e-03, -2.27805575e-03),
Complex::new(-7.37249766e-03, -1.09360672e-03),
Complex::new(-1.57881619e-03, -7.93724499e-03),
Complex::new(-4.48820552e-04, 2.69012686e-04),
Complex::new(-6.00109974e-03, -4.92497528e-03),
])
}

193
src/main.rs Normal file
View File

@ -0,0 +1,193 @@
#![no_main]
#![no_std]
use defmt_rtt as _; // global logger
use panic_probe as _;
use stm32f4xx_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 filters;
mod si5153;
#[app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [SPI3])]
mod app {
use num::Complex;
use stm32f4xx_hal::{
adc::{self, config::AdcConfig},
gpio::{self, gpioa, gpioc, Analog, Output, PushPull},
i2c::{self, I2c},
pac::{ADC1, I2C1, TIM2, TIM4},
prelude::*,
timer::{
self, Channel, Channel1, Channel3, ChannelBuilder, CounterHz, Event, PwmHz,
},
};
use systick_monotonic::Systick;
use num_traits::float::Float;
use crate::filters;
use crate::si5153;
type AudioPwm = PwmHz<TIM4, ChannelBuilder<TIM4, 2>>;
#[monotonic(binds = SysTick, default = true)]
type MonoTimer = Systick<1_000>;
#[shared]
struct Shared {}
#[local]
struct Local {
pll: si5153::Si5153<I2c<I2C1>>,
i2c: I2c<I2C1>,
board_led: gpioc::PC13<Output<PushPull>>,
rx_en: gpioa::PA7<Output<PushPull>>,
adc1: adc::Adc<ADC1>,
mic_in: gpio::Pin<'A', 4, Analog>,
i_in: gpio::Pin<'A', 1, Analog>,
q_in: gpio::Pin<'A', 0, Analog>,
phase: f32,
i_offset: f32,
q_offset: f32,
audio_pwm: AudioPwm,
timer: CounterHz<TIM2>,
usb_filter: filters::FirFilter<63>,
}
#[init]
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
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();
defmt::info!("Clock Setup done");
let mono = Systick::new(cx.core.SYST, clocks.sysclk().to_Hz());
// Acquire the GPIOC peripheral
let gpioa = cx.device.GPIOA.split();
let gpiob = cx.device.GPIOB.split();
let gpioc = cx.device.GPIOC.split();
let board_led = gpioc.pc13.into_push_pull_output();
let scl = gpiob.pb6.into_alternate_open_drain();
let sda = gpiob.pb7.into_alternate_open_drain();
let mut i2c = i2c::I2c::new(
cx.device.I2C1,
(scl, sda),
i2c::Mode::Standard {
frequency: 400.kHz(),
},
&clocks,
);
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);
pll.set_ms_source(&mut i2c, si5153::Multisynth::MS1, si5153::PLL::A);
pll.set_ms_source(&mut i2c, si5153::Multisynth::MS2, si5153::PLL::B);
pll.set_ms_freq(&mut i2c, si5153::Multisynth::MS0, 8_000_000);
pll.set_ms_phase(&mut i2c, si5153::Multisynth::MS0, 0);
pll.enable_ms_output(&mut i2c, si5153::Multisynth::MS0);
pll.set_ms_freq(&mut i2c, si5153::Multisynth::MS1, 8_000_000);
pll.set_ms_phase(&mut i2c, si5153::Multisynth::MS1, 100);
pll.enable_ms_output(&mut i2c, si5153::Multisynth::MS1);
let adc1 = adc::Adc::adc1(cx.device.ADC1, true, AdcConfig::default());
let mic_in = gpioa.pa4.into_analog();
let i_in = gpioa.pa1.into_analog();
let q_in = gpioa.pa0.into_analog();
let audio_out = Channel3::new(gpiob.pb8);
let mut audio_pwm = cx.device.TIM4.pwm_hz(audio_out, 192.kHz(), &clocks);
audio_pwm.enable(Channel::C3);
audio_pwm.set_duty(Channel::C3, 0u16);
let mut rx_en = gpioa.pa7.into_push_pull_output();
rx_en.set_high();
let bias_pin = Channel1::new(gpioa.pa6);
let _bias_pwm = cx.device.TIM3.pwm_hz(bias_pin, 64.kHz(), &clocks);
let mut timer = timer::Timer2::new(cx.device.TIM2, &clocks).counter_hz();
timer.start(6400.Hz()).unwrap();
// Generate an interrupt when the timer expires
timer.listen(Event::Update);
(
Shared {},
Local {
i2c,
pll,
board_led,
rx_en,
adc1,
mic_in,
i_in,
q_in,
phase: 0.0,
i_offset: 2048.0,
q_offset: 2048.0,
audio_pwm,
timer,
usb_filter: filters::usb_firfilter(),
},
init::Monotonics(mono),
)
}
#[task(binds=TIM2, local=[timer, pll, i2c, adc1, mic_in, i_in, q_in, audio_pwm, phase, i_offset, q_offset, board_led, usb_filter])]
fn transmit(ctx: transmit::Context) {
ctx.local.board_led.toggle();
let adc = ctx.local.adc1;
let i_in = ctx.local.i_in;
let q_in = ctx.local.q_in;
let i_raw: u16 = adc.read(&mut *q_in).unwrap();
let q_raw: u16 = adc.read(&mut *i_in).unwrap();
*ctx.local.i_offset = 0.95 * *ctx.local.i_offset + 0.05 * (i_raw as f32);
*ctx.local.q_offset = 0.95 * *ctx.local.q_offset + 0.05 * (q_raw as f32);
let i_sample = (i_raw as f32) - *ctx.local.i_offset;
let q_sample = (q_raw as f32) - *ctx.local.q_offset;
let sample = Complex::new(i_sample as f32 / 4096.0, q_sample as f32 / 4096.0);
let filtered = ctx.local.usb_filter.compute(sample) * 2.0;
let max_duty = if ctx.local.audio_pwm.get_max_duty() != 0 {
ctx.local.audio_pwm.get_max_duty() as f32
} else {
2.0.powi(16)
};
let output = filtered.re * max_duty;
ctx.local.audio_pwm.set_duty(Channel::C3, output as u16);
ctx.local.timer.clear_interrupt(Event::Update);
}
}

199
src/si5153.rs Normal file
View File

@ -0,0 +1,199 @@
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<I2C> {
// Marker that makes sure we always get the same I2C
i2c: PhantomData<I2C>,
pll_freqs: [u32; 2],
outputs: u8,
ms_srcs: [PLL; 3],
}
impl<I2C, E> Si5153<I2C>
where
I2C: i2c::Write<Error = E>,
{
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
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.
}
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(), &params)
}
self.write_byte_reg(i2c, PLL_RESET, 0xA0); // Reset both PLLs
}
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(), &params)
}
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);
}
}

37
ssb_filter.py Normal file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
def main():
fs = 6400.0
coeffs = signal.firls(63, (0, 1150, 1200, fs/2), (1, 1, 0, 0), fs=fs)
freq_space = np.linspace(-fs/2 / (fs/2)*np.pi, fs/2 / (fs/2)*np.pi, 512)
freqs, response = signal.freqz(coeffs, worN=freq_space)
response = 10 * np.log(abs(response))
plt.plot(fs/2*freqs/(np.pi), response)
plt.grid()
plt.show()
f0 = (1200 + 50) / fs
complex_coeffs = []
for n in range(0, len(coeffs)):
complex_coeffs += [coeffs[n] * np.exp(1j * 2 * np.pi * f0 * n)]
complex_coeffs = np.array(complex_coeffs)
print(complex_coeffs)
freq_space = np.linspace(-fs/2 / (fs/2)*np.pi, fs/2 / (fs/2)*np.pi, 512)
freqs, response = signal.freqz(complex_coeffs, worN=freq_space)
response = 10 * np.log(abs(response))
plt.plot(fs/2*freqs/(np.pi), response)
plt.grid()
plt.show()
if __name__ == '__main__':
main()