284 lines
8.9 KiB
Rust
284 lines
8.9 KiB
Rust
#![no_main]
|
|
#![no_std]
|
|
#![feature(type_alias_impl_trait)]
|
|
use defmt_brtt 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 filters;
|
|
mod si5153;
|
|
|
|
#[app(device = stm32f1xx_hal::pac, peripherals = true, dispatchers = [SPI3])]
|
|
mod app {
|
|
|
|
use cortex_m::{asm, singleton};
|
|
use num::Complex;
|
|
use stm32f1xx_hal::{
|
|
adc,
|
|
dma::{self, CircBuffer},
|
|
gpio::{
|
|
self, gpioa, gpiob, gpioc, Alternate, Analog, Floating, Input, OpenDrain, Output,
|
|
PushPull,
|
|
},
|
|
i2c,
|
|
i2c::BlockingI2c,
|
|
pac::adc1::cr2::EXTSEL_A,
|
|
pac::{ADC1, I2C1, TIM2, TIM4},
|
|
prelude::*,
|
|
serial::{self, Config, Serial},
|
|
stm32,
|
|
timer::{self, Channel, CounterHz, Event, Tim3NoRemap, Tim4NoRemap},
|
|
};
|
|
|
|
use arrayvec::ArrayString;
|
|
use microfft::complex::cfft_128;
|
|
use num_traits::{float::Float, Pow};
|
|
|
|
use crate::filters;
|
|
use crate::si5153;
|
|
|
|
type AppI2C1 = BlockingI2c<
|
|
I2C1,
|
|
(
|
|
gpiob::PB6<Alternate<OpenDrain>>,
|
|
gpiob::PB7<Alternate<OpenDrain>>,
|
|
),
|
|
>;
|
|
|
|
type AudioPwm =
|
|
timer::PwmHz<TIM4, Tim4NoRemap, timer::Ch<2>, gpio::Pin<'B', 8, Alternate<gpio::PushPull>>>;
|
|
|
|
pub struct AdcPins(gpio::PA0<Analog>, gpio::PA1<Analog>);
|
|
impl adc::SetChannels<AdcPins> for adc::Adc<ADC1> {
|
|
fn set_samples(&mut self) {
|
|
self.set_channel_sample_time(0, adc::SampleTime::T_239);
|
|
self.set_channel_sample_time(1, adc::SampleTime::T_239);
|
|
}
|
|
fn set_sequence(&mut self) {
|
|
self.set_regular_sequence(&[0, 1]);
|
|
// Optionally we can set continuous scan mode
|
|
self.set_continuous_mode(false);
|
|
}
|
|
}
|
|
|
|
#[shared]
|
|
struct Shared {}
|
|
|
|
#[local]
|
|
struct Local {
|
|
pll: si5153::Si5153<AppI2C1>,
|
|
i2c: AppI2C1,
|
|
board_led: gpioc::PC13<Output<PushPull>>,
|
|
rx_en: gpioa::PA7<Output<PushPull>>,
|
|
iq_buffer: dma::CircBuffer<
|
|
[u16; 256],
|
|
dma::RxDma<adc::AdcPayload<ADC1, AdcPins, adc::Scan>, dma::dma1::C1>,
|
|
>,
|
|
i_offset: f32,
|
|
q_offset: f32,
|
|
audio_pwm: AudioPwm,
|
|
usb_filter: filters::FirFilter<63>,
|
|
}
|
|
|
|
#[init]
|
|
fn init(cx: init::Context) -> (Shared, Local) {
|
|
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 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);
|
|
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);
|
|
|
|
defmt::info!("Si5153 Setup done");
|
|
|
|
let dma1_channels = cx.device.DMA1.split();
|
|
let dma1_ch1 = dma1_channels.1;
|
|
// Setup ADC
|
|
let mut adc1 = adc::Adc::adc1(cx.device.ADC1, clocks);
|
|
adc1.set_external_trigger(EXTSEL_A::Tim1cc1);
|
|
|
|
let pa8 = gpioa.pa8.into_alternate_push_pull(&mut gpioa.crh);
|
|
let mut sample_pwm = cx.device.TIM1.pwm_hz::<timer::Tim1NoRemap, _, _>(
|
|
pa8,
|
|
&mut afio.mapr,
|
|
8.kHz(),
|
|
&clocks,
|
|
);
|
|
let max_duty = sample_pwm.get_max_duty();
|
|
sample_pwm.set_duty(Channel::C1, max_duty / 2);
|
|
sample_pwm.enable(Channel::C1);
|
|
|
|
let i_in = gpioa.pa1.into_analog(&mut gpioa.crl);
|
|
let q_in = gpioa.pa0.into_analog(&mut gpioa.crl);
|
|
|
|
let adc_dma = adc1.with_scan_dma(AdcPins(q_in, i_in), dma1_ch1);
|
|
|
|
let buf = singleton!(: [[u16; 256]; 2] = [[0; 256]; 2]).unwrap();
|
|
let iq_buffer = adc_dma.circ_read(buf);
|
|
|
|
//let mic_in = gpioa.pa4.into_analog(&mut gpioa.crl);
|
|
|
|
let audio_out = gpiob.pb8.into_alternate_push_pull(&mut gpiob.crh);
|
|
let mut audio_pwm =
|
|
cx.device
|
|
.TIM4
|
|
.pwm_hz::<Tim4NoRemap, _, _>(audio_out, &mut afio.mapr, 8.kHz(), &clocks);
|
|
audio_pwm.enable(Channel::C3);
|
|
audio_pwm.set_duty(Channel::C3, 0u16);
|
|
|
|
let mut rx_en = gpioa.pa7.into_push_pull_output(&mut gpioa.crl);
|
|
rx_en.set_high();
|
|
|
|
/*
|
|
let mut bias_pin = gpioa.pa6.into_alternate_push_pull(&mut gpioa.crl);
|
|
let mut bias_pwm =
|
|
cx.device
|
|
.TIM3
|
|
.pwm_hz::<Tim3NoRemap, _, _>(bias_pin, &mut afio.mapr, 64.kHz(), &clocks);*/
|
|
|
|
receiver_task::spawn().ok();
|
|
|
|
(
|
|
Shared {},
|
|
Local {
|
|
i2c,
|
|
pll,
|
|
board_led,
|
|
rx_en,
|
|
iq_buffer,
|
|
i_offset: 2048.0,
|
|
q_offset: 2048.0,
|
|
audio_pwm,
|
|
usb_filter: filters::usb_firfilter(),
|
|
},
|
|
)
|
|
}
|
|
|
|
#[task(local=[board_led, iq_buffer, board_led, usb_filter])]
|
|
async fn receiver_task(ctx: receiver_task::Context) {
|
|
defmt::info!("Start receiver_task!");
|
|
|
|
let mut i_offset = 0.0;
|
|
let mut q_offset = 0.0;
|
|
|
|
let mut expected_half = dma::Half::First;
|
|
|
|
loop {
|
|
while ctx.local.iq_buffer.readable_half().unwrap() != expected_half {}
|
|
ctx.local.board_led.set_low();
|
|
|
|
let samples = ctx
|
|
.local
|
|
.iq_buffer
|
|
.peek(|half, _| {
|
|
let mut samples = [Complex::<f32>::default(); 128];
|
|
for idx in 0..half.len() / 2 {
|
|
let q_raw = half[idx * 2];
|
|
let i_raw = half[idx * 2 + 1];
|
|
|
|
i_offset = 0.95 * i_offset + 0.05 * (i_raw as f32);
|
|
q_offset = 0.95 * q_offset + 0.05 * (q_raw as f32);
|
|
|
|
let i_sample = (i_raw as f32) - i_offset;
|
|
let q_sample = (q_raw as f32) - q_offset;
|
|
|
|
samples[idx] =
|
|
Complex::new(i_sample as f32 / 4096.0, q_sample as f32 / 4096.0);
|
|
}
|
|
samples
|
|
})
|
|
.unwrap();
|
|
|
|
let mut fft_input = [Complex::<f32>::default(); 128];
|
|
for idx in 0..samples.len() / 2 {
|
|
let _filtered = ctx.local.usb_filter.compute(samples[idx]) * 2.0;
|
|
fft_input[idx] = samples[idx];
|
|
}
|
|
|
|
let spectrum = cfft_128(&mut fft_input);
|
|
|
|
let mut max_idx: usize = 0;
|
|
let mut max_mag = 0.0;
|
|
for idx in 0..spectrum.len() {
|
|
let mag_cur = ((spectrum[idx].re.pow(2) + spectrum[idx].im.pow(2)) as f32).sqrt();
|
|
|
|
if mag_cur > max_mag {
|
|
max_idx = idx;
|
|
max_mag = mag_cur;
|
|
}
|
|
}
|
|
defmt::debug!(
|
|
"Max at {}kHz: {}",
|
|
max_idx as f32 * (8.0 / 128.0) - 4.0,
|
|
max_mag
|
|
);
|
|
|
|
ctx.local.board_led.set_high();
|
|
|
|
expected_half = if expected_half == dma::Half::First {
|
|
defmt::info!("Switching to second half.");
|
|
dma::Half::Second
|
|
} else {
|
|
defmt::info!("Switching to first half.");
|
|
dma::Half::First
|
|
}
|
|
}
|
|
}
|
|
}
|