391 lines
12 KiB
Rust
391 lines
12 KiB
Rust
#![no_main]
|
|
#![no_std]
|
|
#![feature(type_alias_impl_trait)]
|
|
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 core::mem;
|
|
|
|
use embedded_hal::blocking::spi::transfer;
|
|
use num::Complex;
|
|
use stm32f4xx_hal::{
|
|
adc::{
|
|
self,
|
|
config::{
|
|
AdcConfig, Continuous, Dma, ExternalTrigger, SampleTime, Scan, Sequence,
|
|
TriggerMode,
|
|
},
|
|
Adc,
|
|
},
|
|
dma::{
|
|
self, config::DmaConfig, PeripheralToMemory, Stream0, Stream7, StreamsTuple, Transfer,
|
|
},
|
|
gpio::{self, gpioa, gpioc, Analog, Output, PushPull},
|
|
i2c::{self, I2c},
|
|
pac::{ADC1, DMA1, DMA2, I2C1, SPI1, TIM2, TIM4},
|
|
prelude::*,
|
|
spi::{self, Spi},
|
|
timer::{
|
|
self, Channel, Channel1, Channel3, ChannelBuilder, CounterHz, Event, PwmHz, CCR, CCR3,
|
|
},
|
|
};
|
|
|
|
use cortex_m::{asm, singleton};
|
|
|
|
use microfft::complex::cfft_128;
|
|
use num_traits::{float::Float, Pow};
|
|
|
|
use crate::filters;
|
|
use crate::si5153;
|
|
|
|
use embedded_graphics::pixelcolor::Rgb565;
|
|
use embedded_graphics::prelude::*;
|
|
use st7735_lcd::{Orientation, ST7735};
|
|
|
|
type AudioPwm = PwmHz<TIM4, ChannelBuilder<TIM4, 2>>;
|
|
|
|
#[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>>,
|
|
//mic_in: gpio::Pin<'A', 4, Analog>,
|
|
i_in: gpio::Pin<'A', 1, Analog>,
|
|
q_in: gpio::Pin<'A', 0, Analog>,
|
|
i_offset: f32,
|
|
q_offset: f32,
|
|
audio_pwm: AudioPwm,
|
|
usb_filter: filters::FirFilter<63>,
|
|
adc_transfer:
|
|
Transfer<Stream0<DMA2>, 0, Adc<ADC1>, PeripheralToMemory, &'static mut [u16; 256]>,
|
|
iq_buffer: Option<&'static mut [u16; 256]>,
|
|
|
|
pwm_transfer: Transfer<
|
|
Stream7<DMA1>,
|
|
2,
|
|
CCR<stm32f4xx_hal::pac::TIM4, 2>,
|
|
stm32f4xx_hal::dma::MemoryToPeripheral,
|
|
&'static mut [u16; 256],
|
|
>,
|
|
audio_buffer: Option<&'static mut [u16; 256]>,
|
|
phase: f32,
|
|
audio_max_duty: u16,
|
|
|
|
disp_led: gpioa::PA10<Output<PushPull>>,
|
|
disp_cs: gpioa::PA15<Output<PushPull>>,
|
|
disp: ST7735<Spi<SPI1>, gpio::Pin<'A', 12, Output>, gpio::Pin<'A', 11, Output>>,
|
|
col_pos: u16,
|
|
max_mag: f32,
|
|
}
|
|
|
|
#[init]
|
|
fn init(cx: init::Context) -> (Shared, Local) {
|
|
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(25.MHz())
|
|
.require_pll48clk()
|
|
.sysclk(84.MHz())
|
|
.hclk(84.MHz())
|
|
.pclk1(42.MHz())
|
|
.pclk2(84.MHz())
|
|
.freeze();
|
|
|
|
defmt::info!("Clock Setup done");
|
|
|
|
// 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);
|
|
|
|
defmt::info!("PLL chip setup done");
|
|
|
|
let mut disp_led = gpioa.pa10.into_push_pull_output();
|
|
disp_led.set_high();
|
|
let mut disp_cs = gpioa.pa15.into_push_pull_output();
|
|
disp_cs.set_low();
|
|
|
|
let disp_rst = gpioa.pa11.into_push_pull_output();
|
|
let disp_dc = gpioa.pa12.into_push_pull_output();
|
|
|
|
let disp_sck = gpiob.pb3.into_alternate();
|
|
let disp_mosi = gpiob.pb5.into_alternate();
|
|
let spi1 = Spi::new(
|
|
cx.device.SPI1,
|
|
(disp_sck, spi::NoMiso::new(), disp_mosi),
|
|
spi::Mode {
|
|
polarity: spi::Polarity::IdleLow,
|
|
phase: spi::Phase::CaptureOnFirstTransition,
|
|
},
|
|
16.MHz(),
|
|
&clocks,
|
|
);
|
|
|
|
let mut disp = ST7735::new(spi1, disp_dc, disp_rst, true, false, 160, 128);
|
|
|
|
let mut delay = cx.core.SYST.delay(&clocks);
|
|
|
|
disp.init(&mut delay).unwrap();
|
|
disp.set_orientation(&Orientation::Landscape).unwrap();
|
|
disp.clear(Rgb565::BLUE).unwrap();
|
|
|
|
defmt::info!("Display setup done");
|
|
|
|
let i_in = gpioa.pa1.into_analog();
|
|
let q_in = gpioa.pa0.into_analog();
|
|
|
|
let adc_config = AdcConfig::default()
|
|
.dma(Dma::Continuous)
|
|
.external_trigger(TriggerMode::RisingEdge, ExternalTrigger::Tim_1_cc_1)
|
|
.scan(Scan::Enabled);
|
|
let mut adc1 = adc::Adc::adc1(cx.device.ADC1, true, adc_config);
|
|
adc1.configure_channel(&i_in, Sequence::One, SampleTime::Cycles_480);
|
|
adc1.configure_channel(&q_in, Sequence::Two, SampleTime::Cycles_480);
|
|
|
|
let pa8 = Channel1::new(gpioa.pa8);
|
|
let mut sample_pwm = cx.device.TIM1.pwm_hz(pa8, 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 mic_in = gpioa.pa4.into_analog()
|
|
|
|
defmt::info!("ADC Setup done");
|
|
|
|
let iq_buff1 = singleton!(: [u16; 256] = [0; 256]).unwrap();
|
|
let iq_buff2 = singleton!(: [u16; 256] = [0; 256]).unwrap();
|
|
|
|
let dma2 = StreamsTuple::new(cx.device.DMA2);
|
|
|
|
let config = DmaConfig::default()
|
|
.transfer_complete_interrupt(true)
|
|
.memory_increment(true)
|
|
.double_buffer(false);
|
|
|
|
let mut adc_transfer =
|
|
Transfer::init_peripheral_to_memory(dma2.0, adc1, iq_buff1, None, config);
|
|
|
|
adc_transfer.start(|_| {});
|
|
|
|
defmt::info!("ADC DMA Setup done");
|
|
|
|
let ccr3_tim4: CCR3<TIM4> = unsafe { mem::transmute_copy(&cx.device.TIM4) };
|
|
|
|
let audio_out = Channel3::new(gpiob.pb8);
|
|
let mut audio_pwm = cx.device.TIM4.pwm_hz(audio_out, 8.kHz(), &clocks);
|
|
audio_pwm.enable(Channel::C3);
|
|
audio_pwm.set_duty(Channel::C3, audio_pwm.get_max_duty() / 2);
|
|
let audio_max_duty = audio_pwm.get_max_duty();
|
|
|
|
defmt::info!("Max duty: {}", audio_pwm.get_max_duty());
|
|
|
|
unsafe {
|
|
(*TIM4::ptr()).dier.modify(|_, w| {
|
|
w.tde().set_bit(); // enable DMA trigger
|
|
w.cc3de().set_bit(); // dma on Update
|
|
w
|
|
});
|
|
};
|
|
|
|
let audio_buff1 = singleton!(: [u16; 256] = [100; 256]).unwrap();
|
|
let audio_buff2 = singleton!(: [u16; 256] = [100; 256]).unwrap();
|
|
|
|
let dma1 = StreamsTuple::new(cx.device.DMA1);
|
|
|
|
let config = DmaConfig::default()
|
|
.transfer_complete_interrupt(true)
|
|
.memory_increment(true)
|
|
.double_buffer(false);
|
|
|
|
let mut pwm_transfer =
|
|
Transfer::init_memory_to_peripheral(dma1.7, ccr3_tim4, audio_buff1, None, config);
|
|
|
|
pwm_transfer.start(|_| {});
|
|
|
|
defmt::info!("PWM DMA Setup done");
|
|
|
|
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);
|
|
|
|
(
|
|
Shared {},
|
|
Local {
|
|
i2c,
|
|
pll,
|
|
board_led,
|
|
rx_en,
|
|
//mic_in,
|
|
i_in,
|
|
q_in,
|
|
i_offset: 2048.0,
|
|
q_offset: 2048.0,
|
|
audio_pwm,
|
|
usb_filter: filters::usb_firfilter(),
|
|
adc_transfer,
|
|
iq_buffer: Some(iq_buff2),
|
|
|
|
pwm_transfer,
|
|
audio_buffer: Some(audio_buff2),
|
|
phase: 0.0,
|
|
audio_max_duty,
|
|
|
|
disp_led,
|
|
disp_cs,
|
|
disp,
|
|
col_pos: 0,
|
|
max_mag: 0.0,
|
|
},
|
|
)
|
|
}
|
|
|
|
#[task(priority = 0, local = [disp, col_pos, max_mag])]
|
|
async fn update_display(cx: update_display::Context, col: [Complex<f32>; 128]) {
|
|
for samp in col {
|
|
let mag = (samp.re.pow(2) + samp.im.pow(2)) / 5.0 as f32;
|
|
*cx.local.max_mag = if mag > *cx.local.max_mag {
|
|
mag
|
|
} else {
|
|
*cx.local.max_mag
|
|
};
|
|
}
|
|
|
|
let gradient = colorous::TURBO;
|
|
|
|
for y in 0..128 {
|
|
let norm_intens = (col[y].re.pow(2) + col[y].im.pow(2)) / *cx.local.max_mag;
|
|
let color = gradient.eval_continuous(norm_intens as f64);
|
|
|
|
Pixel(
|
|
Point::new(*cx.local.col_pos as i32, y as i32),
|
|
Rgb565::new(color.r >> 3, color.g >> 2, color.b >> 3),
|
|
)
|
|
.draw(cx.local.disp)
|
|
.unwrap();
|
|
}
|
|
|
|
*cx.local.col_pos = (*cx.local.col_pos + 1) % 160;
|
|
*cx.local.max_mag *= 0.999;
|
|
|
|
defmt::info!("Position is {}", cx.local.col_pos);
|
|
}
|
|
|
|
#[task(binds = DMA2_STREAM0, local = [adc_transfer, iq_buffer, board_led, i_offset, q_offset, usb_filter])]
|
|
fn dma2_stream0(cx: dma2_stream0::Context) {
|
|
let (buffer, _) = cx
|
|
.local
|
|
.adc_transfer
|
|
.next_transfer(cx.local.iq_buffer.take().unwrap())
|
|
.unwrap();
|
|
|
|
defmt::info!("ADC transfer complete");
|
|
cx.local.board_led.toggle();
|
|
|
|
let mut samples = [Complex::<f32>::default(); 128];
|
|
for idx in 0..buffer.len() / 2 {
|
|
let q_raw = buffer[idx * 2];
|
|
let i_raw = buffer[idx * 2 + 1];
|
|
|
|
*cx.local.i_offset = 0.95 * *cx.local.i_offset + 0.05 * (i_raw as f32);
|
|
*cx.local.q_offset = 0.95 * *cx.local.q_offset + 0.05 * (q_raw as f32);
|
|
|
|
let i_sample = (i_raw as f32) - *cx.local.i_offset;
|
|
let q_sample = (q_raw as f32) - *cx.local.q_offset;
|
|
|
|
samples[idx] = Complex::new(q_sample as f32 / 4096.0, i_sample as f32 / 4096.0);
|
|
}
|
|
|
|
let mut fft_input = [Complex::<f32>::default(); 128];
|
|
|
|
for idx in 0..samples.len() / 2 {
|
|
let filtered = cx.local.usb_filter.compute(samples[idx]);
|
|
fft_input[idx] = filtered;
|
|
}
|
|
|
|
let spectrum = cfft_128(&mut fft_input);
|
|
|
|
update_display::spawn(spectrum.clone()).unwrap();
|
|
|
|
*cx.local.iq_buffer = Some(buffer);
|
|
}
|
|
|
|
#[task(binds = DMA1_STREAM7, local = [pwm_transfer, audio_buffer, phase, audio_max_duty])]
|
|
fn dma1_stream7(cx: dma1_stream7::Context) {
|
|
let (mut buffer, _) = cx
|
|
.local
|
|
.pwm_transfer
|
|
.next_transfer(cx.local.audio_buffer.take().unwrap())
|
|
.unwrap();
|
|
|
|
defmt::info!("PWM transfer complete");
|
|
|
|
let phase_inc = core::f32::consts::PI * 2.0 / 8000.0 * 440.0;
|
|
|
|
for i in 0..buffer.len() {
|
|
*cx.local.phase += phase_inc;
|
|
if *cx.local.phase > 2.0 * core::f32::consts::PI {
|
|
*cx.local.phase -= 2.0 * core::f32::consts::PI;
|
|
}
|
|
|
|
buffer[i] =
|
|
((cx.local.phase.sin() + 1.0) * (*cx.local.audio_max_duty as f32) / 4.0) as u16;
|
|
}
|
|
|
|
*cx.local.audio_buffer = Some(buffer);
|
|
}
|
|
}
|