From 978398ea6b6a1ee24f007a4e4e64beb75a81daa8 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Mon, 9 Oct 2023 22:33:37 -0600 Subject: [PATCH 01/52] Added additional S2DFM pins and ergonomic API --- src/gpio.rs | 8 +- src/logger.rs | 4 +- src/system.rs | 237 ++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 188 insertions(+), 61 deletions(-) diff --git a/src/gpio.rs b/src/gpio.rs index 74ae32c..0499d05 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -11,7 +11,7 @@ pub use gpio::{ }, gpioc::{ PC0 as Daisy15, PC1 as Daisy20, PC10 as Daisy2, PC11 as Daisy1, PC12 as Daisy6, - PC4 as Daisy21, PC8 as Daisy4, PC9 as Daisy3, + PC2 as Daisy31, PC3 as Daisy32, PC4 as Daisy21, PC8 as Daisy4, PC9 as Daisy3, }, gpiod::{PD11 as Daisy26, PD2 as Daisy5}, gpiog::{PG10 as Daisy7, PG11 as Daisy8, PG9 as Daisy27}, @@ -58,6 +58,8 @@ pub struct GPIO { pub daisy28: Option>, pub daisy29: Option>, pub daisy30: Option>, + pub daisy31: Option>, + pub daisy32: Option>, } impl GPIO { @@ -96,6 +98,8 @@ impl GPIO { daisy28: Option>, daisy29: Option>, daisy30: Option>, + daisy31: Option>, + daisy32: Option>, ) -> GPIO { let led = seed_led.into_push_pull_output(); @@ -132,6 +136,8 @@ impl GPIO { daisy28, daisy29, daisy30, + daisy31, + daisy32, } } } diff --git a/src/logger.rs b/src/logger.rs index 38d752d..90cf392 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -71,10 +71,10 @@ cfg_if::cfg_if! { use cortex_m_log::printer::semihosting; use cortex_m_log::printer::semihosting::Semihosting; use cortex_m_log::modes::InterruptOk; - use cortex_m_semihosting::hio::HStdout; + use cortex_m_semihosting::hio::HostStream; lazy_static! { - static ref LOGGER: Logger> = Logger { + static ref LOGGER: Logger> = Logger { level: LevelFilter::Info, inner: semihosting::InterruptOk::<_>::stdout().expect("Get Semihosting stdout"), }; diff --git a/src/system.rs b/src/system.rs index 7c0184d..bbb4fe1 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,6 +1,7 @@ //! Contains setup for Daisy board hardware. #![allow(dead_code)] +use hal::rcc::CoreClocks; use log::info; use stm32h7xx_hal::{ adc, @@ -8,9 +9,7 @@ use stm32h7xx_hal::{ dma, prelude::*, rcc, stm32, - stm32::TIM2, - time::{Hertz, MegaHertz, MilliSeconds}, - timer::{Event, Timer}, + time::{Hertz, MegaHertz}, }; use crate::{audio::Audio, *}; @@ -41,16 +40,144 @@ const PLL3_R_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); pub struct System { pub gpio: crate::gpio::GPIO, pub audio: audio::Audio, - pub exti: stm32::EXTI, - pub syscfg: stm32::SYSCFG, pub adc1: adc::Adc, pub adc2: adc::Adc, - pub timer2: Timer, pub sdram: &'static mut [f32], pub flash: crate::flash::Flash, + pub delay: Delay, +} + +/// All peripherals and other resources required for the system +pub struct SystemResources<'a> { + pub clocks: &'a CoreClocks, + pub adc1: stm32::ADC1, + pub adc2: stm32::ADC2, + pub adc12_rec: rcc::rec::Adc12, + pub syst: stm32::SYST, + pub mpu: &'a mut stm32::MPU, + pub scb: &'a mut stm32::SCB, + pub dcb: &'a mut stm32::DCB, + pub dwt: &'a mut stm32::DWT, + pub fmc: stm32::FMC, + pub fmc_rec: rcc::rec::Fmc, + pub i2c2: stm32::I2C2, + pub i2c2_rec: rcc::rec::I2c2, + pub cpuid: &'a mut cortex_m::peripheral::CPUID, + pub qspi: stm32::QUADSPI, + pub qspi_rec: rcc::rec::Qspi, + + pub sai1: stm32::SAI1, + pub sai1_rec: rcc::rec::Sai1, + + pub gpioa: stm32::GPIOA, + pub gpioa_rec: rcc::rec::Gpioa, + + pub gpiob: stm32::GPIOB, + pub gpiob_rec: rcc::rec::Gpiob, + + pub gpioc: stm32::GPIOC, + pub gpioc_rec: rcc::rec::Gpioc, + + pub gpiod: stm32::GPIOD, + pub gpiod_rec: rcc::rec::Gpiod, + + pub gpioe: stm32::GPIOE, + pub gpioe_rec: rcc::rec::Gpioe, + + pub gpiof: stm32::GPIOF, + pub gpiof_rec: rcc::rec::Gpiof, + + pub gpiog: stm32::GPIOG, + pub gpiog_rec: rcc::rec::Gpiog, + + pub gpioh: stm32::GPIOH, + pub gpioh_rec: rcc::rec::Gpioh, + + pub gpioi: stm32::GPIOI, + pub gpioi_rec: rcc::rec::Gpioi, + + pub dma1: stm32::DMA1, + pub dma1_rec: rcc::rec::Dma1, +} + +#[macro_export] +macro_rules! system_init { + ($core:ident, $device:ident, $ccdr:ident) => {{ + // let ccdr = libdaisy::system::System::init_clocks($device.PWR, $device.RCC, &$device.SYSCFG); + + let resources = libdaisy::system::SystemResources { + clocks: &$ccdr.clocks, + adc1: $device.ADC1, + adc2: $device.ADC2, + adc12_rec: $ccdr.peripheral.ADC12, + syst: $core.SYST, + mpu: &mut $core.MPU, + scb: &mut $core.SCB, + dcb: &mut $core.DCB, + dwt: &mut $core.DWT, + fmc: $device.FMC, + fmc_rec: $ccdr.peripheral.FMC, + i2c2: $device.I2C2, + i2c2_rec: $ccdr.peripheral.I2C2, + cpuid: &mut $core.CPUID, + qspi: $device.QUADSPI, + qspi_rec: $ccdr.peripheral.QSPI, + sai1: $device.SAI1, + sai1_rec: $ccdr.peripheral.SAI1, + gpioa: $device.GPIOA, + gpioa_rec: $ccdr.peripheral.GPIOA, + gpiob: $device.GPIOB, + gpiob_rec: $ccdr.peripheral.GPIOB, + gpioc: $device.GPIOC, + gpioc_rec: $ccdr.peripheral.GPIOC, + gpiod: $device.GPIOD, + gpiod_rec: $ccdr.peripheral.GPIOD, + gpioe: $device.GPIOE, + gpioe_rec: $ccdr.peripheral.GPIOE, + gpiof: $device.GPIOF, + gpiof_rec: $ccdr.peripheral.GPIOF, + gpiog: $device.GPIOG, + gpiog_rec: $ccdr.peripheral.GPIOG, + gpioh: $device.GPIOH, + gpioh_rec: $ccdr.peripheral.GPIOH, + gpioi: $device.GPIOI, + gpioi_rec: $ccdr.peripheral.GPIOI, + dma1: $device.DMA1, + dma1_rec: $ccdr.peripheral.DMA1, + }; + + libdaisy::system::System::init(resources) + }}; +} + +#[derive(Clone, Copy)] +pub enum Version { + Seed, + Seed1_1, + Seed2DFM, } impl System { + fn detect_version( + s2dfm_pin: hal::gpio::gpiod::PD4, + seed1_1_pin: hal::gpio::gpiod::PD3, + ) -> Version { + let s2dfm_pin = s2dfm_pin.into_pull_up_input(); + let seed1_1_pin = seed1_1_pin.into_pull_up_input(); + + let state = (seed1_1_pin.is_low(), s2dfm_pin.is_low()); + + // Deinitialize the pins after reading + s2dfm_pin.into_analog(); + seed1_1_pin.into_analog(); + + match state { + (true, _) => Version::Seed1_1, + (false, true) => Version::Seed2DFM, + _ => Version::Seed, + } + } + /// Initialize clocks pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { // Power @@ -95,65 +222,50 @@ impl System { } /// Batteries included initialization - pub fn init(mut core: rtic::export::Peripherals, device: stm32::Peripherals) -> System { + pub fn init(resources: SystemResources) -> System { info!("Starting system init"); - let ccdr = Self::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - info!("Setup up DMA RAM in DRAM2..."); crate::mpu::init_dma( - &mut core.MPU, - &mut core.SCB, + resources.mpu, + resources.scb, START_OF_DRAM2 as *mut u32, DMA_MEM_SIZE, ); - // log_clocks(&ccdr); - let mut delay = Delay::new(core.SYST, ccdr.clocks); + let mut delay = Delay::new(resources.syst, *resources.clocks); + // Setup ADCs let (adc1, adc2) = adc::adc12( - device.ADC1, - device.ADC2, + resources.adc1, + resources.adc2, 4.MHz(), &mut delay, - ccdr.peripheral.ADC12, - &ccdr.clocks, - ); - - Self::init_debug(&mut core.DCB, &mut core.DWT); - - // Timers - let mut timer2 = device.TIM2.timer( - MilliSeconds::from_ticks(100).into_rate(), - ccdr.peripheral.TIM2, - &ccdr.clocks, + resources.adc12_rec, + resources.clocks, ); - timer2.listen(Event::TimeOut); - // let mut timer3 = device - // .TIM3 - // .timer(1.ms(), ccdr.peripheral.TIM3, &mut ccdr.clocks); - // timer3.listen(Event::TimeOut); + Self::init_debug(resources.dcb, resources.dwt); info!("Setting up GPIOs..."); - let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); - let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB); - let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC); - let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD); - let gpioe = device.GPIOE.split(ccdr.peripheral.GPIOE); - let gpiof = device.GPIOF.split(ccdr.peripheral.GPIOF); - let gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG); - let gpioh = device.GPIOH.split(ccdr.peripheral.GPIOH); - let gpioi = device.GPIOI.split(ccdr.peripheral.GPIOI); + let gpioa = resources.gpioa.split(resources.gpioa_rec); + let gpiob = resources.gpiob.split(resources.gpiob_rec); + let gpioc = resources.gpioc.split(resources.gpioc_rec); + let gpiod = resources.gpiod.split(resources.gpiod_rec); + let gpioe = resources.gpioe.split(resources.gpioe_rec); + let gpiof = resources.gpiof.split(resources.gpiof_rec); + let gpiog = resources.gpiog.split(resources.gpiog_rec); + let gpioh = resources.gpioh.split(resources.gpioh_rec); + let gpioi = resources.gpioi.split(resources.gpioi_rec); // Configure SDRAM info!("Setting up SDRAM..."); let sdram = crate::sdram::Sdram::new( - device.FMC, - ccdr.peripheral.FMC, - &ccdr.clocks, + resources.fmc, + resources.fmc_rec, + resources.clocks, &mut delay, - &mut core.SCB, - &mut core.MPU, + resources.scb, + resources.mpu, gpiod.pd0, gpiod.pd1, gpiod.pd8, @@ -214,16 +326,18 @@ impl System { ) .into(); - let dma1_streams = dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1); + let dma1_streams = dma::dma::StreamsTuple::new(resources.dma1, resources.dma1_rec); info!("Setup up Audio..."); + let version = Self::detect_version(gpiod.pd4, gpiod.pd3); + let audio = Audio::new( dma1_streams.0, dma1_streams.1, - device.SAI1, - ccdr.peripheral.SAI1, - device.I2C2, - ccdr.peripheral.I2C2, + resources.sai1, + resources.sai1_rec, + resources.i2c2, + resources.i2c2_rec, gpioe.pe2, gpioe.pe3, gpioe.pe4, @@ -231,9 +345,16 @@ impl System { gpioe.pe6, gpioh.ph4, gpiob.pb11, - &ccdr.clocks, + resources.clocks, + version, + &mut delay, ); + let (d31, d32) = match version { + Version::Seed2DFM => (Some(gpioc.pc2), Some(gpioc.pc3)), + _ => (None, None), + }; + // Setup GPIOs let gpio = crate::gpio::GPIO::init( gpioc.pc7, @@ -268,18 +389,20 @@ impl System { Some(gpioa.pa2), Some(gpiob.pb14), Some(gpiob.pb15), + d31, + d32, ); // Setup cache - Self::init_cache(&mut core.SCB, &mut core.CPUID); + Self::init_cache(resources.scb, resources.cpuid); info!("System init done!"); //setup flash let flash = crate::flash::Flash::new( - device.QUADSPI, - ccdr.peripheral.QSPI, - &ccdr.clocks, + resources.qspi, + resources.qspi_rec, + resources.clocks, gpiof.pf6, gpiof.pf7, gpiof.pf8, @@ -291,13 +414,11 @@ impl System { System { gpio, audio, - exti: device.EXTI, - syscfg: device.SYSCFG, adc1, adc2, - timer2, sdram, flash, + delay, } } } From 5633b2009c28aab853fb2b36c6b7fbf7fc2ea52a Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Mon, 9 Oct 2023 23:28:52 -0600 Subject: [PATCH 02/52] Added runtime Seed version detection --- src/audio.rs | 407 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 285 insertions(+), 122 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 9c22dbe..65783c0 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -5,6 +5,7 @@ use log::info; use stm32h7xx_hal::{ dma, gpio::{gpiob, gpioe, gpioh, Analog}, + hal::blocking::delay::DelayMs, i2c::*, pac, rcc, rcc::rec, @@ -56,6 +57,33 @@ type DmaOutputStream = dma::Transfer< dma::DBTransfer, >; +type DmaInputStreamS2dfm = dma::Transfer< + dma::dma::Stream1, + sai::dma::ChannelA, + dma::MemoryToPeripheral, + &'static mut [u32; DMA_BUFFER_SIZE], + dma::DBTransfer, +>; + +type DmaOutputStreamS2dfm = dma::Transfer< + dma::dma::Stream0, + sai::dma::ChannelB, + dma::PeripheralToMemory, + &'static mut [u32; DMA_BUFFER_SIZE], + dma::DBTransfer, +>; + +pub enum AudioStream { + Normal { + input: DmaInputStream, + output: DmaOutputStream, + }, + S2dfm { + input: DmaInputStreamS2dfm, + output: DmaOutputStreamS2dfm, + }, +} + type StereoIteratorHandle = fn(StereoIterator, &mut Output); #[derive(Debug, Copy, Clone, PartialEq)] @@ -109,8 +137,7 @@ pub struct Audio { sai: sai::Sai, input: Input, output: Output, - input_stream: DmaInputStream, - output_stream: DmaOutputStream, + audio_stream: AudioStream, } impl Audio { @@ -136,131 +163,267 @@ impl Audio { i2c_sda: gpiob::PB11, clocks: &rcc::CoreClocks, + board_version: crate::system::Version, + delay: &mut impl DelayMs, ) -> Self { - let rx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut RX_BUFFER }; - let dma_config = dma::dma::DmaConfig::default() - .priority(dma::config::Priority::High) - .memory_increment(true) - .peripheral_increment(false) - .circular_buffer(true) - .fifo_enable(false); - let mut output_stream = dma::Transfer::init( - dma1_stream0, - unsafe { pac::Peripherals::steal().SAI1.dma_ch_a() }, - rx_buffer, - None, - dma_config, - ); - - let tx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut TX_BUFFER }; - let dma_config = dma_config - .transfer_complete_interrupt(true) - .half_transfer_interrupt(true); - let mut input_stream = dma::Transfer::init( - dma1_stream1, - unsafe { pac::Peripherals::steal().SAI1.dma_ch_b() }, - tx_buffer, - None, - dma_config, - ); - - info!("Setup up SAI..."); - let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); - let master_config = I2SChanConfig::new(I2SDir::Rx).set_frame_sync_active_high(false); - let slave_config = I2SChanConfig::new(I2SDir::Tx) - .set_sync_type(I2SSync::Internal) - .set_frame_sync_active_high(false); - - let pins_a = ( - sai_mclk_a.into_alternate(), - sai_sck_a.into_alternate(), - sai_fs_a.into_alternate(), - sai_sd_a.into_alternate(), - Some(sai_sd_b.into_alternate()), - ); - - // Hand off to audio module - let mut sai = sai1_d.i2s_ch_a( - pins_a, - crate::AUDIO_SAMPLE_HZ, - I2SDataSize::BITS_24, - sai1_rec, - clocks, - I2sUsers::new(master_config).add_slave(slave_config), - ); - - info!("Setup up WM8731 Audio Codec..."); - let i2c2_pins = ( - i2c_scl.into_alternate_open_drain(), - i2c_sda.into_alternate_open_drain(), - ); - - let mut i2c = i2c2_d.i2c(i2c2_pins, Hertz::from_raw(100_000), i2c2_p, clocks); - - let codec_i2c_address: u8 = 0x1a; // or 0x1b if CSB is high - - // Go through configuration setup - for (register, value) in REGISTER_CONFIG { - let register: u8 = (*register).into(); - let value: u8 = *value; - let byte1: u8 = ((register << 1) & 0b1111_1110) | ((value >> 7) & 0b0000_0001u8); - let byte2: u8 = value; - let bytes = [byte1, byte2]; - - i2c.write(codec_i2c_address, &bytes).unwrap_or_default(); - - // wait ~10us - asm::delay(5_000); - } - - info!("Start audio stream..."); - input_stream.start(|_sai1_rb| { - sai.enable_dma(SaiChannel::ChannelA); - }); - - output_stream.start(|sai1_rb| { - sai.enable_dma(SaiChannel::ChannelB); - - // wait until sai1's fifo starts to receive data - info!("Sai1 fifo waiting to receive data."); - while sai1_rb.chb().sr.read().flvl().is_empty() {} - info!("Audio started!"); - sai.enable(); - sai.try_send(0, 0).unwrap(); - }); - - let input = Input::new(unsafe { &mut RX_BUFFER }); - let output = Output::new(unsafe { &mut TX_BUFFER }); - - info!( - "Setup up Audio DMA: input: {:?}, output: {:?}", - &input.buffer[0] as *const u32, &output.buffer[0] as *const u32 - ); - - Audio { - sai, - input_stream, - output_stream, - input, - output, + match board_version { + crate::system::Version::Seed | crate::system::Version::Seed1_1 => { + let rx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut RX_BUFFER }; + let dma_config = dma::dma::DmaConfig::default() + .priority(dma::config::Priority::High) + .memory_increment(true) + .peripheral_increment(false) + .circular_buffer(true) + .fifo_enable(false); + let mut output_stream = dma::Transfer::init( + dma1_stream0, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_a() }, + rx_buffer, + None, + dma_config, + ); + + let tx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut TX_BUFFER }; + let dma_config = dma_config + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true); + let mut input_stream = dma::Transfer::init( + dma1_stream1, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_b() }, + tx_buffer, + None, + dma_config, + ); + + info!("Set up SAI..."); + let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); + let master_config = + I2SChanConfig::new(I2SDir::Rx).set_frame_sync_active_high(false); + let slave_config = I2SChanConfig::new(I2SDir::Tx) + .set_sync_type(I2SSync::Internal) + .set_frame_sync_active_high(false); + + let pins_a = ( + sai_mclk_a.into_alternate(), + sai_sck_a.into_alternate(), + sai_fs_a.into_alternate(), + sai_sd_a.into_alternate(), + Some(sai_sd_b.into_alternate()), + ); + + // Hand off to audio module + let mut sai = sai1_d.i2s_ch_a( + pins_a, + crate::AUDIO_SAMPLE_HZ, + I2SDataSize::BITS_24, + sai1_rec, + clocks, + I2sUsers::new(master_config).add_slave(slave_config), + ); + + // Besides this CODEC startup, the code for the Seed and + // Seed 1.1 is the same. + match board_version { + crate::system::Version::Seed => { + info!("Setting up WM8731 Audio Codec..."); + let i2c2_pins = ( + i2c_scl.into_alternate_open_drain(), + i2c_sda.into_alternate_open_drain(), + ); + + let mut i2c = + i2c2_d.i2c(i2c2_pins, Hertz::from_raw(100_000), i2c2_p, clocks); + + let codec_i2c_address: u8 = 0x1a; // or 0x1b if CSB is high + + // Go through configuration setup + for (register, value) in REGISTER_CONFIG { + let register: u8 = (*register).into(); + let value: u8 = *value; + let byte1: u8 = + ((register << 1) & 0b1111_1110) | ((value >> 7) & 0b0000_0001u8); + let byte2: u8 = value; + let bytes = [byte1, byte2]; + + i2c.write(codec_i2c_address, &bytes).unwrap_or_default(); + + delay.delay_ms(1); + } + } + crate::system::Version::Seed1_1 => { + info!("Setting up AK4556 Audio Codec..."); + let mut ak_reset = i2c_sda + .into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::High); + delay.delay_ms(1); + ak_reset.set_low(); + delay.delay_ms(1); + ak_reset.set_high(); + } + crate::system::Version::Seed2DFM => unreachable!(), + } + + info!("Start audio stream..."); + input_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelA); + }); + + output_stream.start(|sai1_rb| { + sai.enable_dma(SaiChannel::ChannelB); + + // wait until sai1's fifo starts to receive data + info!("Sai1 fifo waiting to receive data."); + while sai1_rb.chb().sr.read().flvl().is_empty() {} + info!("Audio started!"); + sai.enable(); + sai.try_send(0, 0).unwrap(); + }); + + let input = Input::new(unsafe { &mut RX_BUFFER }); + let output = Output::new(unsafe { &mut TX_BUFFER }); + + info!( + "Setup up Audio DMA: input: {:?}, output: {:?}", + &input.buffer[0] as *const u32, &output.buffer[0] as *const u32 + ); + + Audio { + sai, + audio_stream: AudioStream::Normal { + input: input_stream, + output: output_stream, + }, + input, + output, + } + } + crate::system::Version::Seed2DFM => { + let rx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut RX_BUFFER }; + let dma_config = dma::dma::DmaConfig::default() + .priority(dma::config::Priority::High) + .memory_increment(true) + .peripheral_increment(false) + .circular_buffer(true) + .fifo_enable(false); + let mut output_stream = dma::Transfer::init( + dma1_stream0, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_b() }, + rx_buffer, + None, + dma_config, + ); + + let tx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut TX_BUFFER }; + let dma_config = dma_config + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true); + let mut input_stream = dma::Transfer::init( + dma1_stream1, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_a() }, + tx_buffer, + None, + dma_config, + ); + + info!("Setup up SAI..."); + let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); + let master_config = + I2SChanConfig::new(I2SDir::Tx).set_frame_sync_active_high(false); + let slave_config = I2SChanConfig::new(I2SDir::Rx) + .set_sync_type(I2SSync::Internal) + .set_frame_sync_active_high(false); + + let pins_a = ( + sai_mclk_a.into_alternate(), + sai_sck_a.into_alternate(), + sai_fs_a.into_alternate(), + sai_sd_a.into_alternate(), + Some(sai_sd_b.into_alternate()), + ); + + // Hand off to audio module + let mut sai = sai1_d.i2s_ch_a( + pins_a, + crate::AUDIO_SAMPLE_HZ, + I2SDataSize::BITS_24, + sai1_rec, + clocks, + I2sUsers::new(master_config).add_slave(slave_config), + ); + + info!("Setting up PCM3060 Audio Codec..."); + i2c_sda.into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::Low); + + info!("Start audio stream..."); + input_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelB); + }); + + output_stream.start(|sai1_rb| { + sai.enable_dma(SaiChannel::ChannelA); + + // wait until sai1's fifo starts to receive data + info!("Sai1 fifo waiting to receive data."); + while sai1_rb.cha().sr.read().flvl().is_empty() {} + info!("Audio started!"); + sai.enable(); + sai.try_send(0, 0).unwrap(); + }); + + let input = Input::new(unsafe { &mut RX_BUFFER }); + let output = Output::new(unsafe { &mut TX_BUFFER }); + + info!( + "Setup up Audio DMA: input: {:?}, output: {:?}", + &input.buffer[0] as *const u32, &output.buffer[0] as *const u32 + ); + + Audio { + sai, + audio_stream: AudioStream::S2dfm { + input: input_stream, + output: output_stream, + }, + input, + output, + } + } } } /// Check interrupts and set indexes for I/O fn read(&mut self) -> bool { // Check interrupt(s) - if self.input_stream.get_half_transfer_flag() { - self.input_stream.clear_half_transfer_interrupt(); - self.input.set_index(0); - self.output.set_index(0); - true - } else if self.input_stream.get_transfer_complete_flag() { - self.input_stream.clear_transfer_complete_interrupt(); - self.input.set_index(MAX_TRANSFER_SIZE); - self.output.set_index(MAX_TRANSFER_SIZE); - true - } else { - false + match &mut self.audio_stream { + AudioStream::Normal { input, .. } => { + if input.get_half_transfer_flag() { + input.clear_half_transfer_interrupt(); + self.input.set_index(0); + self.output.set_index(0); + true + } else if input.get_transfer_complete_flag() { + input.clear_transfer_complete_interrupt(); + self.input.set_index(MAX_TRANSFER_SIZE); + self.output.set_index(MAX_TRANSFER_SIZE); + true + } else { + false + } + } + AudioStream::S2dfm { input, .. } => { + if input.get_half_transfer_flag() { + input.clear_half_transfer_interrupt(); + self.input.set_index(0); + self.output.set_index(0); + true + } else if input.get_transfer_complete_flag() { + input.clear_transfer_complete_interrupt(); + self.input.set_index(MAX_TRANSFER_SIZE); + self.output.set_index(MAX_TRANSFER_SIZE); + true + } else { + false + } + } } } @@ -299,7 +462,7 @@ impl Audio { if self.read() { return Some(StereoIterator::new( &self.input.buffer[self.input.index..MAX_TRANSFER_SIZE], - )) + )); } None } @@ -353,7 +516,7 @@ impl Output { self.buffer[self.index] = S24::from(data.0).into(); self.buffer[self.index + 1] = S24::from(data.1).into(); self.index += 2; - return Ok(()) + return Ok(()); } Err(()) } From 2babfc3345b6b9b446d4c140e4f73d1f513fc284 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Mon, 9 Oct 2023 23:52:38 -0600 Subject: [PATCH 03/52] Removed unused asm use --- src/audio.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio.rs b/src/audio.rs index 65783c0..e8449ee 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,6 +1,6 @@ //! Audio module. Handles audio startup and I/O. //! As well as converting between the S24 input and f32 for processing. -use cortex_m::{asm, prelude::_embedded_hal_blocking_i2c_Write}; +use cortex_m::prelude::_embedded_hal_blocking_i2c_Write; use log::info; use stm32h7xx_hal::{ dma, From c716c67fccad768bb729779a3a191081996c231c Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 7 Dec 2023 10:14:18 -0700 Subject: [PATCH 04/52] Removed examples from CI checks This gives us some time to migrate them to the new API. --- .github/workflows/ci.yml | 2 +- .github/workflows/clippy.yml | 2 +- .github/workflows/nightly.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bafd44..0918412 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,4 +33,4 @@ jobs: with: use-cross: true command: build - args: --verbose --release --examples --target thumbv7em-none-eabihf --features ${{ matrix.logger }} + args: --verbose --release --target thumbv7em-none-eabihf --features ${{ matrix.logger }} diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 73f596c..58e6957 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -19,4 +19,4 @@ jobs: - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --examples --target thumbv7em-none-eabihf + args: --target thumbv7em-none-eabihf diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1d4a4c6..ce7c7c3 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -20,4 +20,4 @@ jobs: with: use-cross: true command: build - args: --verbose --release --examples --target thumbv7em-none-eabihf + args: --verbose --release --target thumbv7em-none-eabihf From 4a6b4330d4954a78dd1a02848e631ff49e76ab43 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 7 Dec 2023 10:20:11 -0700 Subject: [PATCH 05/52] chore(style): rustfmt --- examples/usb_midi.rs | 2 +- src/flash.rs | 2 +- src/hid.rs | 2 +- src/mpu.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/usb_midi.rs b/examples/usb_midi.rs index 7fd65f6..f3a1f58 100644 --- a/examples/usb_midi.rs +++ b/examples/usb_midi.rs @@ -195,7 +195,7 @@ mod app { let led = &mut local.seed_led; if !usb_dev.poll(&mut [midi]) { - return + return; } let mut buffer = [0; 64]; diff --git a/src/flash.rs b/src/flash.rs index ace5517..4c05cf3 100644 --- a/src/flash.rs +++ b/src/flash.rs @@ -83,7 +83,7 @@ impl Flash { fn wait_write(&mut self) -> FlashResult<()> { loop { if self.write_complete()? { - return Ok(()) + return Ok(()); } } } diff --git a/src/hid.rs b/src/hid.rs index b7cff1c..6be4e6d 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -151,7 +151,7 @@ where /// If the switch is being held pub fn is_held(&self) -> bool { if let Some(held_threshold) = self.held_threshold { - return self.falling && self.held_counter >= held_threshold + return self.falling && self.held_counter >= held_threshold; } false } diff --git a/src/mpu.rs b/src/mpu.rs index 66ee759..0ae6135 100644 --- a/src/mpu.rs +++ b/src/mpu.rs @@ -50,7 +50,7 @@ pub fn enable(mpu: &mut cortex_m::peripheral::MPU, scb: &mut cortex_m::periphera fn log2minus1(sz: u32) -> u32 { for x in 5..=31 { if sz == (1 << x) { - return x - 1 + return x - 1; } } panic!("Unknown memory region size!"); From 5b9fed68ddcc7c97436c45931be561e4621bb238 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 7 Dec 2023 10:26:41 -0700 Subject: [PATCH 06/52] chore(ci): removed broken clippy action --- .github/workflows/clippy.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/clippy.yml diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index 58e6957..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,22 +0,0 @@ -on: - push: - branches: [staging, trying, main] - pull_request: - -name: Clippy - -jobs: - clippy_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - components: clippy - toolchain: stable - target: thumbv7em-none-eabihf - override: true - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --target thumbv7em-none-eabihf From 3a39b88eaef79f9ec16be192d10d41ebe51b2399 Mon Sep 17 00:00:00 2001 From: Gergo Huszty Date: Wed, 6 Dec 2023 11:43:55 +0100 Subject: [PATCH 07/52] align blinky example with new system init macro --- Cargo.toml | 2 +- examples/blinky.rs | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 59a030c..64c4a1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ panic-rtt-target = { version = "0.1.2", features = ["cortex-m"], optional = true panic-semihosting = { version = "0.6", optional = true } rtt-target = { version = "0.4.0", optional = true } stm32-fmc = "0.3.0" -stm32h7xx-hal = { version = "0.14.0", features = ["stm32h750v","rt","fmc", "xspi", "sdmmc", "sdmmc-fatfs", "usb_hs"] } +stm32h7xx-hal = { version = "0.15.1", features = ["stm32h750v","rt","fmc", "xspi", "sdmmc", "sdmmc-fatfs", "usb_hs"] } [features] default = [] diff --git a/examples/blinky.rs b/examples/blinky.rs index e180f79..2a2d6d5 100644 --- a/examples/blinky.rs +++ b/examples/blinky.rs @@ -8,8 +8,7 @@ )] mod app { // Includes a panic handler and optional logging facilities - use libdaisy::logger; - use libdaisy::{gpio, system}; + use libdaisy::{gpio, logger, system}; use log::info; use stm32h7xx_hal::{stm32, time::MilliSeconds, timer::Timer}; @@ -25,18 +24,26 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::system_init!(core, device, ccdr); info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(500).into_rate()); + timer2.set_freq(MilliSeconds::from_ticks(500).into_rate()); ( Shared {}, Local { seed_led: system.gpio.led, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) From 30880cfa068023e6ca7982459f87fa04a3c909cf Mon Sep 17 00:00:00 2001 From: Gergo Huszty Date: Wed, 6 Dec 2023 11:40:00 +0100 Subject: [PATCH 08/52] Seed onboard (BOOT) button support and example. --- examples/button.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++++ src/gpio.rs | 7 ++++++- src/system.rs | 1 + 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 examples/button.rs diff --git a/examples/button.rs b/examples/button.rs new file mode 100644 index 0000000..1a8e653 --- /dev/null +++ b/examples/button.rs @@ -0,0 +1,51 @@ +#![deny(warnings)] +#![deny(unsafe_code)] +#![no_std] +#![no_main] + +#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true)] +mod app { + use libdaisy::{gpio, system}; + use stm32h7xx_hal::gpio::{Edge, ExtiPin}; + + #[shared] + struct SharedResources {} + #[local] + struct LocalResources { + button: gpio::SeedButton, + led: gpio::SeedLed, + } + + use panic_halt as _; + + #[init] + fn init(ctx: init::Context) -> (SharedResources, LocalResources, init::Monotonics) { + let mut core = ctx.core; + let mut device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::system_init!(core, device, ccdr); + + // Button + system.gpio.button.make_interrupt_source(&mut device.SYSCFG); + system + .gpio + .button + .trigger_on_edge(&mut device.EXTI, Edge::Rising); + system.gpio.button.enable_interrupt(&mut device.EXTI); + + ( + SharedResources {}, + LocalResources { + button: system.gpio.button, + led: system.gpio.led, + }, + init::Monotonics(), + ) + } + + #[task(binds = EXTI3, local = [button, led])] + fn button_click(ctx: button_click::Context) { + ctx.local.button.clear_interrupt_pending_bit(); + ctx.local.led.toggle(); + } +} diff --git a/src/gpio.rs b/src/gpio.rs index 0499d05..7b33a6b 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -18,15 +18,17 @@ pub use gpio::{ }; use stm32h7xx_hal::{ gpio, - gpio::{gpioc::PC7, Alternate, Analog, Output, PushPull}, + gpio::{gpioc::PC7, gpiog::PG3, Alternate, Analog, Input, Output, PushPull}, }; pub type SeedLed = PC7>; +pub type SeedButton = PG3; /// GPIO struct for holding Daisy GPIO pins #[allow(clippy::upper_case_acronyms)] pub struct GPIO { pub led: SeedLed, + pub button: SeedButton, pub daisy0: Option>, pub daisy1: Option>, pub daisy2: Option>, @@ -67,6 +69,7 @@ impl GPIO { #[allow(clippy::too_many_arguments)] pub fn init( seed_led: gpio::gpioc::PC7, + seed_button: gpio::gpiog::PG3, daisy0: Option>, daisy1: Option>, daisy2: Option>, @@ -102,9 +105,11 @@ impl GPIO { daisy32: Option>, ) -> GPIO { let led = seed_led.into_push_pull_output(); + let button = seed_button.into_floating_input(); Self { led, + button, daisy0, daisy1, daisy2, diff --git a/src/system.rs b/src/system.rs index bbb4fe1..14ff3a4 100644 --- a/src/system.rs +++ b/src/system.rs @@ -358,6 +358,7 @@ impl System { // Setup GPIOs let gpio = crate::gpio::GPIO::init( gpioc.pc7, + gpiog.pg3, Some(gpiob.pb12), Some(gpioc.pc11), Some(gpioc.pc10), From 6212ab71ddb4776ee15b9759bf999432f267cca7 Mon Sep 17 00:00:00 2001 From: Nathan Bradshaw Date: Thu, 22 Aug 2024 18:35:18 -0500 Subject: [PATCH 09/52] feat: use slices for changing block sizes (#6) * feat: update passthru example * update readme * feat: make buffer size generic * feat: failed passthru example build fix: gh action fix: wrong target fix: github action fix: github action fix: github action * fix: delay and flash examples * fix: hid_blinky, knob, sine examples * fix: sdram, switch, toggle, volume * chore: linting fix * wip: use slices feat: use slices instead of generics * fix: update missing slices fix: use slice of audio buffer * feat: base max transfer size on user set block size chore: formatting * fix: examples failing on real device * chore: cleanup comments and linting * chore: remove magic number * fix: branching type change * fix: noise * chore: format document * chore: remove magic numbers dma_block_size --- .github/workflows/build_examples.yml | 48 ++++++++++++++++++ Cargo.toml | 6 +-- README.md | 1 + examples/button.rs | 23 +++++---- examples/delay.rs | 13 +++-- examples/flash.rs | 19 ++++++-- examples/hid_blinky.rs | 21 ++++++-- examples/knob.rs | 20 ++++++-- examples/passthru.rs | 20 ++++++-- examples/sdram.rs | 19 ++++++-- examples/sine.rs | 27 +++++----- examples/switch.rs | 18 +++++-- examples/toggle.rs | 17 +++++-- examples/volume.rs | 21 ++++++-- src/audio.rs | 73 ++++++++++++++++++---------- src/system.rs | 11 +++-- 16 files changed, 267 insertions(+), 90 deletions(-) create mode 100644 .github/workflows/build_examples.yml diff --git a/.github/workflows/build_examples.yml b/.github/workflows/build_examples.yml new file mode 100644 index 0000000..65ea546 --- /dev/null +++ b/.github/workflows/build_examples.yml @@ -0,0 +1,48 @@ +name: Build LibDaisy Examples + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install target + run: rustup target add thumbv7em-none-eabihf + + - name: Cache cargo registry + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + + - name: Build examples + run: | + excluded_examples="sdmmc usb_midi" # List of examples to exclude + for example in $(ls examples/*.rs); do + example_name=$(basename $example .rs) + if [[ ! " $excluded_examples " =~ " $example_name " ]]; then + cargo build --example $example_name + else + echo "Skipping $example_name" + fi + done \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 64c4a1f..5557823 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,11 +23,11 @@ log = "0.4" micromath = "2" panic-halt = "0.2.0" panic-itm = { version = "0.4", optional = true } -panic-rtt-target = { version = "0.1.2", features = ["cortex-m"], optional = true } +panic-rtt-target = { version = "0.1.3", optional = true } panic-semihosting = { version = "0.6", optional = true } -rtt-target = { version = "0.4.0", optional = true } +rtt-target = { version = "0.5.0", optional = true } stm32-fmc = "0.3.0" -stm32h7xx-hal = { version = "0.15.1", features = ["stm32h750v","rt","fmc", "xspi", "sdmmc", "sdmmc-fatfs", "usb_hs"] } +stm32h7xx-hal = { version = "0.16.0", features = ["stm32h750v","rt","fmc", "xspi", "sdmmc", "sdmmc-fatfs", "usb_hs"] } [features] default = [] diff --git a/README.md b/README.md index e98d898..fb45505 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ One of: ```bash cargo embed --features log-rtt --example passthru ``` +**note** You will need to specify the board IE `--chip stm32h750v` for the daisy seed, or create an `Embed.toml` that specifies the chip. ## Build Examples diff --git a/examples/button.rs b/examples/button.rs index 1a8e653..74b9571 100644 --- a/examples/button.rs +++ b/examples/button.rs @@ -5,25 +5,30 @@ #[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true)] mod app { - use libdaisy::{gpio, system}; - use stm32h7xx_hal::gpio::{Edge, ExtiPin}; + use libdaisy::{ + gpio, + hal::gpio::{Edge, ExtiPin}, + logger, system, + }; + use log::info; #[shared] - struct SharedResources {} + struct Shared {} + #[local] - struct LocalResources { + struct Local { button: gpio::SeedButton, led: gpio::SeedLed, } - use panic_halt as _; - #[init] - fn init(ctx: init::Context) -> (SharedResources, LocalResources, init::Monotonics) { + fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { + logger::init(); let mut core = ctx.core; let mut device = ctx.device; let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); let mut system = libdaisy::system_init!(core, device, ccdr); + info!("Startup done!"); // Button system.gpio.button.make_interrupt_source(&mut device.SYSCFG); @@ -34,8 +39,8 @@ mod app { system.gpio.button.enable_interrupt(&mut device.EXTI); ( - SharedResources {}, - LocalResources { + Shared {}, + Local { button: system.gpio.button, led: system.gpio.led, }, diff --git a/examples/delay.rs b/examples/delay.rs index 151c4db..8d82a56 100644 --- a/examples/delay.rs +++ b/examples/delay.rs @@ -7,7 +7,10 @@ peripherals = true, )] mod app { - use libdaisy::{audio, logger, system}; + use libdaisy::{ + audio::{self, AudioBuffer}, + logger, system, + }; use log::info; #[shared] @@ -16,14 +19,18 @@ mod app { #[local] struct Local { audio: audio::Audio, - buffer: audio::AudioBuffer, + buffer: AudioBuffer, sdram: &'static mut [f32], } #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let system = system::System::init(ctx.core, ctx.device); + + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::system_init!(core, device, ccdr); let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; info!("Startup done!"); diff --git a/examples/flash.rs b/examples/flash.rs index 228f6f9..04b1a8b 100644 --- a/examples/flash.rs +++ b/examples/flash.rs @@ -26,12 +26,21 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::system_init!(core, device, ccdr); info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(500).into_rate()); + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); + + timer2.set_freq(MilliSeconds::from_ticks(500).into_rate()); let mut flash = system.flash; @@ -79,7 +88,7 @@ mod app { Shared {}, Local { seed_led: system.gpio.led, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/hid_blinky.rs b/examples/hid_blinky.rs index 0373248..1033b73 100644 --- a/examples/hid_blinky.rs +++ b/examples/hid_blinky.rs @@ -27,11 +27,22 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::system_init!(core, device, ccdr); + info!("Startup done!"); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(1).into_rate()); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); + + timer2.set_freq(MilliSeconds::from_ticks(1).into_rate()); let mut led1 = hid::Led::new(system.gpio.led, false, 1000); led1.set_brightness(0.5); @@ -55,7 +66,7 @@ mod app { led1, adc1, control1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/knob.rs b/examples/knob.rs index 7129f01..e9fe62f 100644 --- a/examples/knob.rs +++ b/examples/knob.rs @@ -11,6 +11,7 @@ mod app { use libdaisy::logger; use libdaisy::{gpio::*, hid, prelude::*, system}; use log::info; + use stm32h7xx_hal::time::MilliSeconds; use stm32h7xx_hal::{adc, stm32, timer::Timer}; #[shared] @@ -27,12 +28,25 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::system_init!(core, device, ccdr); + info!("Startup done!"); + + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); let duty_cycle = 50; let resolution = 20; - system.timer2.set_freq((duty_cycle * resolution).Hz()); + timer2.set_freq((duty_cycle * resolution).Hz()); let daisy28 = system .gpio @@ -62,7 +76,7 @@ mod app { led1, adc1, control1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/passthru.rs b/examples/passthru.rs index a5cec48..acc47c0 100644 --- a/examples/passthru.rs +++ b/examples/passthru.rs @@ -6,7 +6,9 @@ peripherals = true, )] mod app { - use libdaisy::{audio, logger, system}; + const BLOCK_SIZE: usize = 128; + use libdaisy::logger; + use libdaisy::{audio, system}; use log::info; #[shared] @@ -21,10 +23,18 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let system = system::System::init(ctx.core, ctx.device); + + // Latest changes here. This approach allows you to + // access peripherals and resources that were simply + // moved out of the function in the previous implementation. + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::system_init!(core, device, ccdr, BLOCK_SIZE); + let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; - info!("Startup done!"); + info!("Startup done!!"); ( Shared {}, @@ -52,8 +62,8 @@ mod app { let buffer = ctx.local.buffer; if audio.get_stereo(buffer) { - for (left, right) in buffer { - audio.push_stereo((*left, *right)).unwrap(); + for (left, right) in &buffer.as_slice()[..BLOCK_SIZE] { + let _ = audio.push_stereo((*left, *right)); } } else { info!("Error reading data!"); diff --git a/examples/sdram.rs b/examples/sdram.rs index 1f46c7e..3419341 100644 --- a/examples/sdram.rs +++ b/examples/sdram.rs @@ -26,11 +26,20 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::system_init!(core, device, ccdr); + info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + timer2.listen(stm32h7xx_hal::timer::Event::TimeOut); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(500).into_rate()); + timer2.set_freq(MilliSeconds::from_ticks(500).into_rate()); let sdram = system.sdram; @@ -72,7 +81,7 @@ mod app { Shared {}, Local { seed_led: system.gpio.led, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/sine.rs b/examples/sine.rs index e3478a8..3c3a017 100644 --- a/examples/sine.rs +++ b/examples/sine.rs @@ -7,14 +7,17 @@ peripherals = true )] mod app { - use libdaisy::{audio, logger, system}; + use libdaisy::{ + audio::{self, AudioBuffer}, + gpio::SeedLed, + logger, system, + }; use libm; use log::info; - use stm32h7xx_hal::time::MilliSeconds; pub struct AudioRate { pub audio: audio::Audio, - pub buffer: audio::AudioBuffer, + buffer: AudioBuffer, } #[shared] @@ -25,19 +28,18 @@ mod app { ar: AudioRate, phase: f32, pitch: f32, + led: SeedLed, } #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::system_init!(core, device, ccdr); info!("Startup done!"); - - system - .timer2 - .set_freq(MilliSeconds::from_ticks(500).into_rate()); - - let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; // audio ring buffer + let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; ( Shared {}, @@ -48,6 +50,7 @@ mod app { }, phase: 0.0, pitch: 440.0, + led: system.gpio.led, }, init::Monotonics(), ) @@ -61,12 +64,13 @@ mod app { } // Interrupt handler for audio - #[task(binds = DMA1_STR1, local = [ar, phase, pitch], shared = [], priority = 8)] + #[task(binds = DMA1_STR1, local = [ar, phase, pitch, led], shared = [], priority = 8)] fn audio_handler(ctx: audio_handler::Context) { let audio = &mut ctx.local.ar.audio; let buffer = &mut ctx.local.ar.buffer; let phase = ctx.local.phase; let pitch = ctx.local.pitch; + let led = ctx.local.led; audio.get_stereo(buffer); for _ in 0..buffer.len() { @@ -78,6 +82,7 @@ mod app { if *pitch > 10_000.0 { *pitch = 440.0; + led.toggle(); } *pitch += 0.1; diff --git a/examples/switch.rs b/examples/switch.rs index 771cdf3..b9cd938 100644 --- a/examples/switch.rs +++ b/examples/switch.rs @@ -26,7 +26,17 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::system_init!(core, device, ccdr); + info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); let daisy28 = system .gpio @@ -35,9 +45,7 @@ mod app { .expect("Failed to get pin daisy28!") .into_pull_up_input(); - system - .timer2 - .set_freq(MilliSeconds::from_ticks(1).into_rate()); + timer2.set_freq(MilliSeconds::from_ticks(1).into_rate()); // Switch rate is determined by timer freq let mut switch1 = hid::Switch::new(daisy28, hid::SwitchType::PullUp); @@ -49,7 +57,7 @@ mod app { Local { seed_led: system.gpio.led, switch1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/toggle.rs b/examples/toggle.rs index 327e9fa..bd91aba 100644 --- a/examples/toggle.rs +++ b/examples/toggle.rs @@ -10,6 +10,7 @@ mod app { use libdaisy::logger; use libdaisy::{gpio::*, hid, prelude::*, system}; use log::info; + use stm32h7xx_hal::time::MilliSeconds; use stm32h7xx_hal::{stm32, time::Hertz, timer::Timer}; #[shared] @@ -25,7 +26,17 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::system_init!(core, device, ccdr); + info!("Startup done!"); + let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); let daisy28 = system .gpio @@ -34,7 +45,7 @@ mod app { .expect("Failed to get pin daisy28!") .into_pull_up_input(); - system.timer2.set_freq(Hertz::from_raw(100)); + timer2.set_freq(Hertz::from_raw(100)); let switch1 = hid::Switch::new(daisy28, hid::SwitchType::PullUp); @@ -43,7 +54,7 @@ mod app { Local { seed_led: system.gpio.led, switch1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) diff --git a/examples/volume.rs b/examples/volume.rs index f1edccc..f80328b 100644 --- a/examples/volume.rs +++ b/examples/volume.rs @@ -10,9 +10,10 @@ mod app { //use rtic::cyccnt::U32Ext; + use audio::AudioBuffer; use libdaisy::{audio, gpio::*, hid, logger, prelude::*, system, MILICYCLES}; use log::info; - use stm32h7xx_hal::{adc, stm32, timer::Timer}; + use stm32h7xx_hal::{adc, stm32, time::MilliSeconds, timer::Timer}; #[shared] struct Shared { @@ -22,7 +23,7 @@ mod app { #[local] struct Local { audio: audio::Audio, - buffer: audio::AudioBuffer, + buffer: AudioBuffer, adc1: adc::Adc, timer2: Timer, } @@ -30,7 +31,10 @@ mod app { #[init] fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { logger::init(); - let mut system = system::System::init(ctx.core, ctx.device); + let mut core = ctx.core; + let device = ctx.device; + let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::system_init!(core, device, ccdr); let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; info!("Enable adc1"); @@ -49,13 +53,20 @@ mod app { // Transform linear input into logarithmic control1.set_transform(|x| x * x); + let timer2 = stm32h7xx_hal::timer::TimerExt::timer( + device.TIM2, + MilliSeconds::from_ticks(100).into_rate(), + ccdr.peripheral.TIM2, + &ccdr.clocks, + ); + ( Shared { control1 }, Local { audio: system.audio, buffer, adc1, - timer2: system.timer2, + timer2, }, init::Monotonics(), ) @@ -76,7 +87,7 @@ mod app { let audio_handler::LocalResources { audio, buffer } = ctx.local; if audio.get_stereo(buffer) { - for (left, right) in buffer { + for (left, right) in buffer.iter_mut() { ctx.shared.control1.lock(|c| { let volume = c.get_value(); info!("{}", volume); diff --git a/src/audio.rs b/src/audio.rs index e8449ee..4579d79 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -45,7 +45,7 @@ type DmaInputStream = dma::Transfer< dma::dma::Stream1, sai::dma::ChannelB, dma::MemoryToPeripheral, - &'static mut [u32; DMA_BUFFER_SIZE], + &'static mut [u32], dma::DBTransfer, >; @@ -53,7 +53,7 @@ type DmaOutputStream = dma::Transfer< dma::dma::Stream0, sai::dma::ChannelA, dma::PeripheralToMemory, - &'static mut [u32; DMA_BUFFER_SIZE], + &'static mut [u32], dma::DBTransfer, >; @@ -61,7 +61,7 @@ type DmaInputStreamS2dfm = dma::Transfer< dma::dma::Stream1, sai::dma::ChannelA, dma::MemoryToPeripheral, - &'static mut [u32; DMA_BUFFER_SIZE], + &'static mut [u32], dma::DBTransfer, >; @@ -69,7 +69,7 @@ type DmaOutputStreamS2dfm = dma::Transfer< dma::dma::Stream0, sai::dma::ChannelB, dma::PeripheralToMemory, - &'static mut [u32; DMA_BUFFER_SIZE], + &'static mut [u32], dma::DBTransfer, >; @@ -128,7 +128,7 @@ impl From for S24 { impl From for f32 { fn from(x: S24) -> f32 { - ((x.0 ^ S24_SIGN) - S24_SIGN) as f32 * S24_TO_F32_SCALE + ((x.0 << 8) >> 8) as f32 * S24_TO_F32_SCALE } } @@ -138,6 +138,7 @@ pub struct Audio { input: Input, output: Output, audio_stream: AudioStream, + max_transfer_size: usize, } impl Audio { @@ -165,10 +166,14 @@ impl Audio { clocks: &rcc::CoreClocks, board_version: crate::system::Version, delay: &mut impl DelayMs, + + block_size: usize, ) -> Self { match board_version { crate::system::Version::Seed | crate::system::Version::Seed1_1 => { - let rx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut RX_BUFFER }; + let dma_buffer_size = block_size * 2 * 2; + let rx_buffer: &'static mut [u32] = + unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; let dma_config = dma::dma::DmaConfig::default() .priority(dma::config::Priority::High) .memory_increment(true) @@ -183,7 +188,8 @@ impl Audio { dma_config, ); - let tx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut TX_BUFFER }; + let tx_buffer: &'static mut [u32] = + unsafe { &mut TX_BUFFER.as_mut_slice()[..dma_buffer_size] }; let dma_config = dma_config .transfer_complete_interrupt(true) .half_transfer_interrupt(true); @@ -278,8 +284,9 @@ impl Audio { sai.try_send(0, 0).unwrap(); }); - let input = Input::new(unsafe { &mut RX_BUFFER }); - let output = Output::new(unsafe { &mut TX_BUFFER }); + let max_transfer_size = block_size * 2; + let input = Input::new(unsafe { &mut RX_BUFFER }, max_transfer_size); + let output = Output::new(unsafe { &mut TX_BUFFER }, max_transfer_size); info!( "Setup up Audio DMA: input: {:?}, output: {:?}", @@ -294,10 +301,13 @@ impl Audio { }, input, output, + max_transfer_size, } } crate::system::Version::Seed2DFM => { - let rx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut RX_BUFFER }; + let dma_buffer_size = block_size * 2 * 2; + let rx_buffer: &'static mut [u32] = + unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; let dma_config = dma::dma::DmaConfig::default() .priority(dma::config::Priority::High) .memory_increment(true) @@ -312,7 +322,8 @@ impl Audio { dma_config, ); - let tx_buffer: &'static mut [u32; DMA_BUFFER_SIZE] = unsafe { &mut TX_BUFFER }; + let tx_buffer: &'static mut [u32] = + unsafe { &mut TX_BUFFER.as_mut_slice()[..dma_buffer_size] }; let dma_config = dma_config .transfer_complete_interrupt(true) .half_transfer_interrupt(true); @@ -369,8 +380,9 @@ impl Audio { sai.try_send(0, 0).unwrap(); }); - let input = Input::new(unsafe { &mut RX_BUFFER }); - let output = Output::new(unsafe { &mut TX_BUFFER }); + let max_transfer_size = block_size * 2; + let input = Input::new(unsafe { &mut RX_BUFFER }, max_transfer_size); + let output = Output::new(unsafe { &mut TX_BUFFER }, max_transfer_size); info!( "Setup up Audio DMA: input: {:?}, output: {:?}", @@ -385,6 +397,7 @@ impl Audio { }, input, output, + max_transfer_size, } } } @@ -402,8 +415,8 @@ impl Audio { true } else if input.get_transfer_complete_flag() { input.clear_transfer_complete_interrupt(); - self.input.set_index(MAX_TRANSFER_SIZE); - self.output.set_index(MAX_TRANSFER_SIZE); + self.input.set_index(self.max_transfer_size); + self.output.set_index(self.max_transfer_size); true } else { false @@ -417,8 +430,8 @@ impl Audio { true } else if input.get_transfer_complete_flag() { input.clear_transfer_complete_interrupt(); - self.input.set_index(MAX_TRANSFER_SIZE); - self.output.set_index(MAX_TRANSFER_SIZE); + self.input.set_index(self.max_transfer_size); + self.output.set_index(self.max_transfer_size); true } else { false @@ -433,7 +446,7 @@ impl Audio { if self.read() { let mut index = 0; let mut out_index = self.output.index; - while index < MAX_TRANSFER_SIZE { + while index < self.max_transfer_size { self.output.buffer[out_index] = self.input.buffer[index + self.input.index]; self.output.buffer[out_index + 1] = self.input.buffer[index + self.input.index + 1]; index += 2; @@ -446,7 +459,7 @@ impl Audio { pub fn get_stereo(&mut self, buffer: &mut AudioBuffer) -> bool { if self.read() { for (i, (left, right)) in StereoIterator::new( - &self.input.buffer[self.input.index..self.input.index + MAX_TRANSFER_SIZE], + &self.input.buffer[self.input.index..self.input.index + self.max_transfer_size], ) .enumerate() { @@ -461,7 +474,7 @@ impl Audio { fn get_stereo_iter(&mut self) -> Option { if self.read() { return Some(StereoIterator::new( - &self.input.buffer[self.input.index..MAX_TRANSFER_SIZE], + &self.input.buffer[self.input.index..self.input.index + self.max_transfer_size], )); } None @@ -478,12 +491,17 @@ impl Audio { struct Input { index: usize, buffer: &'static DmaBuffer, + max_transfer_size: usize, } impl Input { /// Create a new Input from a DmaBuffer - fn new(buffer: &'static DmaBuffer) -> Self { - Self { index: 0, buffer } + fn new(buffer: &'static DmaBuffer, max_transfer_size: usize) -> Self { + Self { + index: 0, + buffer, + max_transfer_size, + } } fn set_index(&mut self, index: usize) { @@ -499,12 +517,17 @@ impl Input { struct Output { index: usize, buffer: &'static mut DmaBuffer, + max_transfer_size: usize, } impl Output { /// Create a new Input from a DmaBuffer - fn new(buffer: &'static mut DmaBuffer) -> Self { - Self { index: 0, buffer } + fn new(buffer: &'static mut DmaBuffer, max_transfer_size: usize) -> Self { + Self { + index: 0, + buffer, + max_transfer_size, + } } fn set_index(&mut self, index: usize) { @@ -512,7 +535,7 @@ impl Output { } pub fn push(&mut self, data: (f32, f32)) -> Result<(), ()> { - if self.index < (MAX_TRANSFER_SIZE * 2) { + if self.index < (self.max_transfer_size * 2) { self.buffer[self.index] = S24::from(data.0).into(); self.buffer[self.index + 1] = S24::from(data.1).into(); self.index += 2; diff --git a/src/system.rs b/src/system.rs index 14ff3a4..a5e8f47 100644 --- a/src/system.rs +++ b/src/system.rs @@ -98,13 +98,16 @@ pub struct SystemResources<'a> { pub dma1: stm32::DMA1, pub dma1_rec: rcc::rec::Dma1, + + pub block_size: usize, } #[macro_export] macro_rules! system_init { - ($core:ident, $device:ident, $ccdr:ident) => {{ - // let ccdr = libdaisy::system::System::init_clocks($device.PWR, $device.RCC, &$device.SYSCFG); - + ($core:ident, $device:ident, $ccdr:ident) => { + libdaisy::system_init!($core, $device, $ccdr, libdaisy::audio::BLOCK_SIZE_MAX); + }; + ($core:ident, $device:ident, $ccdr:ident, $block_size:expr) => {{ let resources = libdaisy::system::SystemResources { clocks: &$ccdr.clocks, adc1: $device.ADC1, @@ -144,6 +147,7 @@ macro_rules! system_init { gpioi_rec: $ccdr.peripheral.GPIOI, dma1: $device.DMA1, dma1_rec: $ccdr.peripheral.DMA1, + block_size: $block_size, }; libdaisy::system::System::init(resources) @@ -348,6 +352,7 @@ impl System { resources.clocks, version, &mut delay, + resources.block_size, ); let (d31, d32) = match version { From 72e2828d5e18909a564b7a5b9640095e28ff93f1 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 22 Aug 2024 17:48:41 -0600 Subject: [PATCH 10/52] Fixed Seed version codec init --- src/audio.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 4579d79..eb13a0f 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -230,7 +230,7 @@ impl Audio { // Besides this CODEC startup, the code for the Seed and // Seed 1.1 is the same. match board_version { - crate::system::Version::Seed => { + crate::system::Version::Seed1_1 => { info!("Setting up WM8731 Audio Codec..."); let i2c2_pins = ( i2c_scl.into_alternate_open_drain(), @@ -256,7 +256,7 @@ impl Audio { delay.delay_ms(1); } } - crate::system::Version::Seed1_1 => { + crate::system::Version::Seed => { info!("Setting up AK4556 Audio Codec..."); let mut ak_reset = i2c_sda .into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::High); From 58ae5e605fc499cd37aa60c568c5baef224a9669 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Mon, 26 Aug 2024 16:49:48 -0600 Subject: [PATCH 11/52] Exposed internal USB pins for user-space --- src/system.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/system.rs b/src/system.rs index a5e8f47..f5c1452 100644 --- a/src/system.rs +++ b/src/system.rs @@ -6,7 +6,7 @@ use log::info; use stm32h7xx_hal::{ adc, delay::Delay, - dma, + dma, gpio, prelude::*, rcc, stm32, time::{Hertz, MegaHertz}, @@ -44,9 +44,15 @@ pub struct System { pub adc2: adc::Adc, pub sdram: &'static mut [f32], pub flash: crate::flash::Flash, + pub internal_usb: Option, pub delay: Delay, } +pub struct InternalUsbPins { + pub dp: gpio::gpioa::PA12, + pub dm: gpio::gpioa::PA11, +} + /// All peripherals and other resources required for the system pub struct SystemResources<'a> { pub clocks: &'a CoreClocks, @@ -209,7 +215,7 @@ impl System { .freeze(vos, syscfg) } - /// Setup cache + /// Set up cache pub fn init_cache( scb: &mut cortex_m::peripheral::SCB, cpuid: &mut cortex_m::peripheral::CPUID, @@ -228,7 +234,7 @@ impl System { /// Batteries included initialization pub fn init(resources: SystemResources) -> System { info!("Starting system init"); - info!("Setup up DMA RAM in DRAM2..."); + info!("Set up up DMA RAM in DRAM2..."); crate::mpu::init_dma( resources.mpu, resources.scb, @@ -238,7 +244,7 @@ impl System { let mut delay = Delay::new(resources.syst, *resources.clocks); - // Setup ADCs + // Set up ADCs let (adc1, adc2) = adc::adc12( resources.adc1, resources.adc2, @@ -332,7 +338,7 @@ impl System { let dma1_streams = dma::dma::StreamsTuple::new(resources.dma1, resources.dma1_rec); - info!("Setup up Audio..."); + info!("Set up Audio..."); let version = Self::detect_version(gpiod.pd4, gpiod.pd3); let audio = Audio::new( @@ -360,7 +366,7 @@ impl System { _ => (None, None), }; - // Setup GPIOs + // Set up GPIOs let gpio = crate::gpio::GPIO::init( gpioc.pc7, gpiog.pg3, @@ -399,12 +405,12 @@ impl System { d32, ); - // Setup cache + // Set up cache Self::init_cache(resources.scb, resources.cpuid); info!("System init done!"); - //setup flash + // set up flash let flash = crate::flash::Flash::new( resources.qspi, resources.qspi_rec, @@ -417,6 +423,12 @@ impl System { gpiog.pg6, ); + // grab internal usb pins + let internal_usb = InternalUsbPins { + dp: gpioa.pa12, + dm: gpioa.pa11, + }; + System { gpio, audio, @@ -424,6 +436,7 @@ impl System { adc2, sdram, flash, + internal_usb: Some(internal_usb), delay, } } From 8306e00d0790f45260858fea6278050a0757af69 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Mon, 26 Aug 2024 16:50:16 -0600 Subject: [PATCH 12/52] Removed formatting configuration --- .cargo/{config => config.toml} | 0 .rustfmt.toml | 5 ----- 2 files changed, 5 deletions(-) rename .cargo/{config => config.toml} (100%) delete mode 100644 .rustfmt.toml diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index 990acdd..0000000 --- a/.rustfmt.toml +++ /dev/null @@ -1,5 +0,0 @@ -group_imports = "StdExternalCrate" -imports_granularity = "Crate" -reorder_impl_items = true -trailing_semicolon = false -wrap_comments = true From 4140d735eb086eee52178a5077b9ad03da06b79a Mon Sep 17 00:00:00 2001 From: Corvus Date: Wed, 25 Sep 2024 15:03:50 -0600 Subject: [PATCH 13/52] Corrected Rev4 and Rev7 CODEC initialization --- src/audio.rs | 93 ++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index eb13a0f..8468ccf 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -115,14 +115,7 @@ impl From for u32 { impl From for S24 { fn from(x: f32) -> S24 { - let x = if x <= FBIPMIN { - FBIPMIN - } else if x >= FBIPMAX { - FBIPMAX - } else { - x - }; - S24((x * F32_TO_S24_SCALE) as i32) + S24((x.clamp(FBIPMIN, FBIPMAX) * F32_TO_S24_SCALE) as i32) } } @@ -170,7 +163,7 @@ impl Audio { block_size: usize, ) -> Self { match board_version { - crate::system::Version::Seed | crate::system::Version::Seed1_1 => { + crate::system::Version::Seed1_1 => { let dma_buffer_size = block_size * 2 * 2; let rx_buffer: &'static mut [u32] = unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; @@ -227,45 +220,28 @@ impl Audio { I2sUsers::new(master_config).add_slave(slave_config), ); - // Besides this CODEC startup, the code for the Seed and - // Seed 1.1 is the same. - match board_version { - crate::system::Version::Seed1_1 => { - info!("Setting up WM8731 Audio Codec..."); - let i2c2_pins = ( - i2c_scl.into_alternate_open_drain(), - i2c_sda.into_alternate_open_drain(), - ); - - let mut i2c = - i2c2_d.i2c(i2c2_pins, Hertz::from_raw(100_000), i2c2_p, clocks); - - let codec_i2c_address: u8 = 0x1a; // or 0x1b if CSB is high - - // Go through configuration setup - for (register, value) in REGISTER_CONFIG { - let register: u8 = (*register).into(); - let value: u8 = *value; - let byte1: u8 = - ((register << 1) & 0b1111_1110) | ((value >> 7) & 0b0000_0001u8); - let byte2: u8 = value; - let bytes = [byte1, byte2]; - - i2c.write(codec_i2c_address, &bytes).unwrap_or_default(); - - delay.delay_ms(1); - } - } - crate::system::Version::Seed => { - info!("Setting up AK4556 Audio Codec..."); - let mut ak_reset = i2c_sda - .into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::High); - delay.delay_ms(1); - ak_reset.set_low(); - delay.delay_ms(1); - ak_reset.set_high(); - } - crate::system::Version::Seed2DFM => unreachable!(), + info!("Setting up WM8731 Audio Codec..."); + let i2c2_pins = ( + i2c_scl.into_alternate_open_drain(), + i2c_sda.into_alternate_open_drain(), + ); + + let mut i2c = i2c2_d.i2c(i2c2_pins, Hertz::from_raw(100_000), i2c2_p, clocks); + + let codec_i2c_address: u8 = 0x1a; // or 0x1b if CSB is high + + // Go through configuration setup + for (register, value) in REGISTER_CONFIG { + let register: u8 = (*register).into(); + let value: u8 = *value; + let byte1: u8 = + ((register << 1) & 0b1111_1110) | ((value >> 7) & 0b0000_0001u8); + let byte2: u8 = value; + let bytes = [byte1, byte2]; + + i2c.write(codec_i2c_address, &bytes).unwrap_or_default(); + + delay.delay_ms(1); } info!("Start audio stream..."); @@ -304,7 +280,7 @@ impl Audio { max_transfer_size, } } - crate::system::Version::Seed2DFM => { + crate::system::Version::Seed2DFM | crate::system::Version::Seed => { let dma_buffer_size = block_size * 2 * 2; let rx_buffer: &'static mut [u32] = unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; @@ -361,8 +337,23 @@ impl Audio { I2sUsers::new(master_config).add_slave(slave_config), ); - info!("Setting up PCM3060 Audio Codec..."); - i2c_sda.into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::Low); + match board_version { + crate::system::Version::Seed => { + info!("Setting up AK4556/PCM3060 Audio CODEC..."); + let mut ak_reset = i2c_sda + .into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::High); + delay.delay_ms(1); + ak_reset.set_low(); + delay.delay_ms(1); + ak_reset.set_high(); + } + crate::system::Version::Seed2DFM => { + // Set deemphasis low + info!("Setting up PCM3060 Audio CODEC..."); + i2c_sda.into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::Low); + } + _ => unreachable!(), + } info!("Start audio stream..."); input_stream.start(|_sai1_rb| { From 958b4eaf51f31eda5320ebf015df74490b8c4173 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 24 Oct 2024 12:46:34 -0600 Subject: [PATCH 14/52] Provided for_each audio API --- Cargo.toml | 47 ++++++++++++++++-------- examples/passthru.rs | 15 +------- examples/usb_midi.rs | 11 ++---- examples/volume.rs | 26 +++++-------- src/audio.rs | 87 ++++++++++++++++++++++++++++++-------------- src/flash.rs | 10 ++--- src/hid.rs | 9 +---- 7 files changed, 113 insertions(+), 92 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5557823..5cfbbc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ description = "Hardware Abstraction Layer implementation for Daisy boards" keywords = ["cortex-m", "stm32h7xx", "stm32h750", "hal", "daisy"] readme = "README.md" name = "libdaisy" -version = "0.1.0" +version = "0.2.0" license = "MIT" repository = "https://github.com/mtthw-meyer/libdaisy-rust.git" documentation = "https://docs.rs/libdaisy" @@ -14,20 +14,32 @@ exclude = [".gitignore"] [dependencies] cfg-if = "1" cortex-m = "0.7" -cortex-m-log = { version = "0.8", features = ["itm", "semihosting", "log-integration"], optional = true } +cortex-m-log = { version = "0.8", features = [ + "itm", + "semihosting", + "log-integration", +], optional = true } cortex-m-rtic = "1" -cortex-m-semihosting = { version = "0.5", optional = true } +cortex-m-semihosting = { version = "0.5", optional = true } debouncr = "0.2.2" -lazy_static = { version = "1.4.0", features = ["spin_no_std"], optional = true } +lazy_static = { version = "1.4.0", features = ["spin_no_std"], optional = true } log = "0.4" micromath = "2" panic-halt = "0.2.0" -panic-itm = { version = "0.4", optional = true } +panic-itm = { version = "0.4", optional = true } panic-rtt-target = { version = "0.1.3", optional = true } -panic-semihosting = { version = "0.6", optional = true } +panic-semihosting = { version = "0.6", optional = true } rtt-target = { version = "0.5.0", optional = true } stm32-fmc = "0.3.0" -stm32h7xx-hal = { version = "0.16.0", features = ["stm32h750v","rt","fmc", "xspi", "sdmmc", "sdmmc-fatfs", "usb_hs"] } +stm32h7xx-hal = { version = "0.16.0", features = [ + "stm32h750v", + "rt", + "fmc", + "xspi", + "sdmmc", + "sdmmc-fatfs", + "usb_hs", +] } [features] default = [] @@ -35,7 +47,12 @@ default = [] log-itm = ["panic-itm", "lazy_static", "cortex-m-log"] log-none = [] log-rtt = ["rtt-target", "panic-rtt-target"] -log-semihosting = ["panic-semihosting", "lazy_static", "cortex-m-log", "cortex-m-semihosting"] +log-semihosting = [ + "panic-semihosting", + "lazy_static", + "cortex-m-log", + "cortex-m-semihosting", +] # this lets you use `cargo fix`! #[[bin]] @@ -44,18 +61,18 @@ log-semihosting = ["panic-semihosting", "lazy_static", "cortex-m-log", "cortex-m #bench = false [profile.dev] -codegen-units = 1 # better optimizations -debug = true # symbols are nice and they don't increase the size in flash +codegen-units = 1 # better optimizations +debug = true # symbols are nice and they don't increase the size in flash incremental = false -opt-level = "s" # optimize for binary size +opt-level = "s" # optimize for binary size [profile.release] codegen-units = 1 # better optimizations -debug = true # symbols are nice and they don't increase the size in flash -lto = true # better optimizations -opt-level = "s" # optimize for binary size +debug = true # symbols are nice and they don't increase the size in flash +lto = true # better optimizations +opt-level = "s" # optimize for binary size -[dev_dependencies] +[dev-dependencies] embedded-sdmmc = "0.4" libm = "0.2" num_enum = { version = "0.5", default-features = false } diff --git a/examples/passthru.rs b/examples/passthru.rs index acc47c0..b77c16f 100644 --- a/examples/passthru.rs +++ b/examples/passthru.rs @@ -17,7 +17,6 @@ mod app { #[local] struct Local { audio: audio::Audio, - buffer: audio::AudioBuffer, } #[init] @@ -32,15 +31,12 @@ mod app { let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); let system = libdaisy::system_init!(core, device, ccdr, BLOCK_SIZE); - let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; - info!("Startup done!!"); ( Shared {}, Local { audio: system.audio, - buffer, }, init::Monotonics(), ) @@ -56,17 +52,10 @@ mod app { } // Interrupt handler for audio - #[task(binds = DMA1_STR1, local = [audio, buffer], priority = 8)] + #[task(binds = DMA1_STR1, local = [audio], priority = 8)] fn audio_handler(ctx: audio_handler::Context) { let audio = ctx.local.audio; - let buffer = ctx.local.buffer; - if audio.get_stereo(buffer) { - for (left, right) in &buffer.as_slice()[..BLOCK_SIZE] { - let _ = audio.push_stereo((*left, *right)); - } - } else { - info!("Error reading data!"); - } + audio.for_each(|left, right| (left, right)); } } diff --git a/examples/usb_midi.rs b/examples/usb_midi.rs index f3a1f58..ea27997 100644 --- a/examples/usb_midi.rs +++ b/examples/usb_midi.rs @@ -56,14 +56,6 @@ mod app { let _ = ccdr.clocks.hsi48_ck().expect("HSI48 must run"); ccdr.peripheral.kernel_usb_clk_mux(UsbClkSel::Hsi48); - /* - unsafe { - let pwr = &*stm32::PWR::ptr(); - pwr.cr3.modify(|_, w| w.usbregen().set_bit()); - while pwr.cr3.read().usb33rdy().bit_is_clear() {} - } - */ - let mut timer2 = device.TIM2.timer( MilliSeconds::from_ticks(200).into_rate(), ccdr.peripheral.TIM2, @@ -79,6 +71,7 @@ mod app { let gpio = gpio::GPIO::init( gpioc.pc7, + gpiog.pg3, Some(gpiob.pb12), Some(gpioc.pc11), Some(gpioc.pc10), @@ -110,6 +103,8 @@ mod app { Some(gpioa.pa2), Some(gpiob.pb14), Some(gpiob.pb15), + None, + None, ); let (pin_dm, pin_dp) = { (gpioa.pa11.into_alternate(), gpioa.pa12.into_alternate()) }; diff --git a/examples/volume.rs b/examples/volume.rs index f80328b..992c2b5 100644 --- a/examples/volume.rs +++ b/examples/volume.rs @@ -23,7 +23,6 @@ mod app { #[local] struct Local { audio: audio::Audio, - buffer: AudioBuffer, adc1: adc::Adc, timer2: Timer, } @@ -35,7 +34,6 @@ mod app { let device = ctx.device; let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); let mut system = libdaisy::system_init!(core, device, ccdr); - let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; info!("Enable adc1"); let mut adc1 = system.adc1.enable(); @@ -64,7 +62,6 @@ mod app { Shared { control1 }, Local { audio: system.audio, - buffer, adc1, timer2, }, @@ -82,21 +79,16 @@ mod app { } // Interrupt handler for audio - #[task(binds = DMA1_STR1, local = [audio, buffer], shared = [control1], priority = 8)] + #[task(binds = DMA1_STR1, local = [audio], shared = [control1], priority = 8)] fn audio_handler(mut ctx: audio_handler::Context) { - let audio_handler::LocalResources { audio, buffer } = ctx.local; - - if audio.get_stereo(buffer) { - for (left, right) in buffer.iter_mut() { - ctx.shared.control1.lock(|c| { - let volume = c.get_value(); - info!("{}", volume); - *left *= volume; - *right *= volume; - audio.push_stereo((*left, *right)).unwrap(); - }); - } - } + let audio = ctx.local.audio; + + ctx.shared.control1.lock(|c| { + let volume = c.get_value(); + info!("{}", volume); + + audio.for_each(|left, right| (left * volume, right * volume)); + }); } #[task(binds = TIM2, local = [timer2, adc1], shared = [control1])] diff --git a/src/audio.rs b/src/audio.rs index 8468ccf..d151da4 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,5 +1,6 @@ //! Audio module. Handles audio startup and I/O. //! As well as converting between the S24 input and f32 for processing. +use core::convert::Infallible; use cortex_m::prelude::_embedded_hal_blocking_i2c_Write; use log::info; use stm32h7xx_hal::{ @@ -261,8 +262,14 @@ impl Audio { }); let max_transfer_size = block_size * 2; - let input = Input::new(unsafe { &mut RX_BUFFER }, max_transfer_size); - let output = Output::new(unsafe { &mut TX_BUFFER }, max_transfer_size); + let input = Input::new( + unsafe { &*core::ptr::addr_of!(RX_BUFFER) }, + max_transfer_size, + ); + let output = Output::new( + unsafe { &mut *core::ptr::addr_of_mut!(TX_BUFFER) }, + max_transfer_size, + ); info!( "Setup up Audio DMA: input: {:?}, output: {:?}", @@ -360,20 +367,23 @@ impl Audio { sai.enable_dma(SaiChannel::ChannelB); }); - output_stream.start(|sai1_rb| { + // There is no need to wait in this configuration. + output_stream.start(|_sai1_rb| { sai.enable_dma(SaiChannel::ChannelA); - - // wait until sai1's fifo starts to receive data - info!("Sai1 fifo waiting to receive data."); - while sai1_rb.cha().sr.read().flvl().is_empty() {} - info!("Audio started!"); - sai.enable(); - sai.try_send(0, 0).unwrap(); }); + info!("Audio started!"); + sai.enable(); + let max_transfer_size = block_size * 2; - let input = Input::new(unsafe { &mut RX_BUFFER }, max_transfer_size); - let output = Output::new(unsafe { &mut TX_BUFFER }, max_transfer_size); + let input = Input::new( + unsafe { &*core::ptr::addr_of!(RX_BUFFER) }, + max_transfer_size, + ); + let output = Output::new( + unsafe { &mut *core::ptr::addr_of_mut!(TX_BUFFER) }, + max_transfer_size, + ); info!( "Setup up Audio DMA: input: {:?}, output: {:?}", @@ -431,21 +441,6 @@ impl Audio { } } - /// Directly pass received audio to output without any processing. - pub fn passthru(&mut self) { - // Copy data - if self.read() { - let mut index = 0; - let mut out_index = self.output.index; - while index < self.max_transfer_size { - self.output.buffer[out_index] = self.input.buffer[index + self.input.index]; - self.output.buffer[out_index + 1] = self.input.buffer[index + self.input.index + 1]; - index += 2; - out_index += 2; - } - } - } - /// Gets the audio input from the DMA memory and writes it to buffer pub fn get_stereo(&mut self, buffer: &mut AudioBuffer) -> bool { if self.read() { @@ -471,6 +466,44 @@ impl Audio { None } + /// Process audio frame-by-frame. + #[inline] + pub fn for_each(&mut self, mut process: F) + where + F: FnMut(f32, f32) -> (f32, f32), + { + self.try_for_each::<_, Infallible>(|left, right| Ok(process(left, right))) + .unwrap() + } + + /// Process audio frame-by-frame. + /// + /// If the process closure returns an error, + /// it's bubbled up to the callsite of this method. + pub fn try_for_each(&mut self, mut process: F) -> Result<(), E> + where + F: FnMut(f32, f32) -> Result<(f32, f32), E>, + { + if self.read() { + let input = self.input.buffer + [self.input.index..self.input.index + self.max_transfer_size] + .chunks_exact(2); + + let output = self.output.buffer + [self.output.index..self.output.index + self.max_transfer_size] + .chunks_exact_mut(2); + + for (input, output) in input.zip(output) { + let (left, right) = + process(S24(input[0] as i32).into(), S24(input[1] as i32).into())?; + output[0] = S24::from(left).into(); + output[1] = S24::from(right).into(); + } + } + + Ok(()) + } + /// Push data to the DMA buffer for output /// Call this once per sample per call to [get_stereo()](Audio#get_stereo) #[allow(clippy::result_unit_err)] diff --git a/src/flash.rs b/src/flash.rs index 4c05cf3..778377f 100644 --- a/src/flash.rs +++ b/src/flash.rs @@ -195,8 +195,8 @@ impl Flash { /// - Erasing sets all the bits in the given area to `1`. /// - The memory array of the IS25LP064A/032A is organized into uniform 4 /// Kbyte sectors or - /// 32/64 Kbyte uniform blocks (a block consists of eight/sixteen adjacent - /// sectors respectively). + /// 32/64 Kbyte uniform blocks (a block consists of eight/sixteen adjacent + /// sectors respectively). pub fn erase(&mut self, op: FlashErase) -> NBFlashResult<()> { match self.state { FlashState::Erasing(e) => { @@ -266,9 +266,9 @@ impl Flash { /// to a 1. /// - The starting byte can be anywhere within the page (256 byte chunk). /// When the end of the - /// page is reached, the address will wrap around to the beginning of the - /// same page. If the data to be programmed are less than a full page, - /// the data of all other bytes on the same page will remain unchanged. + /// page is reached, the address will wrap around to the beginning of the + /// same page. If the data to be programmed are less than a full page, + /// the data of all other bytes on the same page will remain unchanged. pub fn program(&mut self, address: u32, data: &[u8]) -> NBFlashResult<()> { let prog = |flash: &mut Self, chunk_index: u32| -> NBFlashResult<()> { if let Some(chunk) = data.chunks(32).nth(chunk_index as usize) { diff --git a/src/hid.rs b/src/hid.rs index 6be4e6d..101fe97 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -273,13 +273,8 @@ where /// Set the brightness of the LED from 0.0 to 1.0. pub fn set_brightness(&mut self, value: f32) { - let value = if value > 1.0 { - 1.0 - } else if value < 0.0 { - 0.0 - } else { - value - }; + let value = value.clamp(0., 1.); + match self.invert { // Bias for slower transitions in the low brightness range // TODO configurable? From a9d95bdf5de5d8369ee71e1b3d165fe67daf18a0 Mon Sep 17 00:00:00 2001 From: Corvus Date: Tue, 29 Oct 2024 05:18:13 -0600 Subject: [PATCH 15/52] Added bootloader support (#11) * Added bootloader support code * Removed fancy feature to preserve MSV --- memory.x | 9 +- src/bootloader.rs | 273 ++++++++++++++++++++++++++++++++++++++++++++++ src/flash.rs | 113 ++++++++++++++++++- src/lib.rs | 1 + src/system.rs | 195 ++++++++++++++++++++++++++++++++- 5 files changed, 585 insertions(+), 6 deletions(-) create mode 100644 src/bootloader.rs diff --git a/memory.x b/memory.x index ed7ccb1..f33333d 100644 --- a/memory.x +++ b/memory.x @@ -7,6 +7,7 @@ MEMORY SRAM (RWX) : ORIGIN = 0x24000000, LENGTH = 512K RAM_D2 (RWX) : ORIGIN = 0x30000000, LENGTH = 288K RAM_D3 (RWX) : ORIGIN = 0x38000000, LENGTH = 64K + BACKUP_SRAM (RWX) : ORIGIN = 0x38800000, LENGTH = 4K ITCMRAM (RWX) : ORIGIN = 0x00000000, LENGTH = 64K SDRAM (RWX) : ORIGIN = 0xc0000000, LENGTH = 64M QSPIFLASH (RX): ORIGIN = 0x90000000, LENGTH = 8M @@ -46,5 +47,11 @@ SECTIONS PROVIDE(__sdram_end = _esdram); } > SDRAM - + .boot_info (NOLOAD) : + { + . = ALIGN(4); + *(.boot_info) + *(.boot_info*) + . = ALIGN(4); + } > BACKUP_SRAM } diff --git a/src/bootloader.rs b/src/bootloader.rs new file mode 100644 index 0000000..774be45 --- /dev/null +++ b/src/bootloader.rs @@ -0,0 +1,273 @@ +use core::mem::MaybeUninit; +use cortex_m::prelude::*; +use stm32h7xx_hal::{delay::Delay, pac::SCB}; + +#[link_section = ".boot_info"] +static mut BOOT_INFO: MaybeUninit<[u32; 3]> = MaybeUninit::uninit(); + +unsafe fn get_info() -> [u32; 3] { + let info_ptr = core::ptr::addr_of!(BOOT_INFO); + unsafe { core::ptr::read_volatile(info_ptr).assume_init() } +} + +unsafe fn set_info(info: [u32; 3]) { + let info_ptr = core::ptr::addr_of_mut!(BOOT_INFO); + unsafe { + core::ptr::write_volatile(info_ptr, MaybeUninit::new(info)); + } +} + +// These should probably be defined in the linker, +// but this'll do for now. +const D1_AXIFLASH_BASE: u32 = 0x0800_0000; +const D1_ITCMRAM_BASE: u32 = 0x0000_0000; +const D1_DTCMRAM_BASE: u32 = 0x2000_0000; +pub const D1_AXISRAM_BASE: u32 = 0x2400_0000; +const D2_AXISRAM_BASE: u32 = 0x3000_0000; +const D3_SRAM_BASE: u32 = 0x3800_0000; +const SDRAM_BASE: u32 = 0xC000_0000; +const QSPI_BASE: u32 = 0x9000_0000; + +const INTERNAL_FLASH_SIZE: u32 = 0x20000; +const ITCMRAM_SIZE: u32 = 0x10000; +const DTCMRAM_SIZE: u32 = 0x20000; +pub const SRAM_D1_SIZE: u32 = 0x80000; +const SRAM_D2_SIZE: u32 = 0x48000; +const SRAM_D3_SIZE: u32 = 0x10000; +const SDRAM_SIZE: u32 = 0x4000000; +const QSPI_SIZE: u32 = 0x800000; + +const D1_AXIFLASH_END: u32 = D1_AXIFLASH_BASE + INTERNAL_FLASH_SIZE - 1; +const D1_ITCMRAM_END: u32 = D1_ITCMRAM_BASE + ITCMRAM_SIZE - 1; +const D1_DTCMRAM_END: u32 = D1_DTCMRAM_BASE + DTCMRAM_SIZE - 1; +const D1_AXISRAM_END: u32 = D1_AXISRAM_BASE + SRAM_D1_SIZE - 1; +const D2_AXISRAM_END: u32 = D2_AXISRAM_BASE + SRAM_D2_SIZE - 1; +const D3_SRAM_END: u32 = D3_SRAM_BASE + SRAM_D3_SIZE - 1; +const SDRAM_END: u32 = SDRAM_BASE + SDRAM_SIZE - 1; +const QSPI_END: u32 = QSPI_BASE + QSPI_SIZE - 1; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MemoryRegion { + InternalFlash, + ItcmRam, + DtcmRam, + SramD1, + SramD2, + SramD3, + SdRam, + Qspi, +} + +impl MemoryRegion { + /// Return the memory region containing this address, if any. + pub fn from_address(address: u32) -> Option { + match address { + D1_AXIFLASH_BASE..=D1_AXIFLASH_END => Some(MemoryRegion::InternalFlash), + D1_ITCMRAM_BASE..=D1_ITCMRAM_END => Some(MemoryRegion::ItcmRam), + D1_DTCMRAM_BASE..=D1_DTCMRAM_END => Some(MemoryRegion::DtcmRam), + D1_AXISRAM_BASE..=D1_AXISRAM_END => Some(MemoryRegion::SramD1), + D2_AXISRAM_BASE..=D2_AXISRAM_END => Some(MemoryRegion::SramD2), + D3_SRAM_BASE..=D3_SRAM_END => Some(MemoryRegion::SramD3), + SDRAM_BASE..=SDRAM_END => Some(MemoryRegion::SdRam), + QSPI_BASE..=QSPI_END => Some(MemoryRegion::Qspi), + _ => None, + } + } + + /// Return the memory region occupied by the vector table, + /// which is a proxy for determining the execution location of the + /// current program. + pub fn from_vtable(scb: &SCB) -> Option { + let address = scb.vtor.read(); + + Self::from_address(address) + } +} + +/// Describes how the Daisy bootloader should behave. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DaisyBootType { + /// This is what the bootloader writes when it resets itself. + /// + /// TODO: document better + Jump, + /// Jump immediately to the target application. + SkipTimeout, + /// Wait in the bootloader indefinitely. + InfiniteTimeout, +} + +impl DaisyBootType { + /// Read from backup SRAM to get the boot type, if any. + pub fn from_backup() -> Option { + let info = unsafe { get_info() }; + + let boot_word = info[0]; + + Self::from_bits(boot_word) + } + + fn to_bits(&self) -> u32 { + match self { + Self::Jump => 0xDEADBEEF, + Self::SkipTimeout => 0x5AFEB007, + Self::InfiniteTimeout => 0xB0074EFA, + } + } + + fn from_bits(bits: u32) -> Option { + match bits { + 0xDEADBEEF => Some(Self::Jump), + 0x5AFEB007 => Some(Self::SkipTimeout), + 0xB0074EFA => Some(Self::InfiniteTimeout), + _ => None, + } + } + + /// Write the boot type to backup SRAM. + unsafe fn write(&self) { + let mut info = unsafe { get_info() }; + info[0] = self.to_bits(); + unsafe { + set_info(info); + } + } + + /// Write an invalid bit pattern to backup SRAM, effectively clearing the boot type. + pub unsafe fn clear() { + let mut info = unsafe { get_info() }; + info[0] = 0; + unsafe { + set_info(info); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Version { + /// Any version below v6. + LessThanV6, + /// Exactly v6. + V6, + /// v6.1 or above. + V6_1, +} + +impl Version { + /// Read from backup SRAM to get the version, if any. + pub fn from_backup() -> Option { + let info = unsafe { get_info() }; + let version_word = info[2]; + + Self::from_bits(version_word) + } + + fn from_bits(bits: u32) -> Option { + match bits { + 0 => Some(Self::LessThanV6), + 1 => None, + 2 => Some(Self::V6), + _ => Some(Self::V6_1), + } + } + + fn to_bits(&self) -> u32 { + match self { + Self::LessThanV6 => 0, + Self::V6 => 2, + Self::V6_1 => 3, + } + } + + /// Write the version to backup SRAM. + unsafe fn write(&self) { + let mut info = unsafe { get_info() }; + info[2] = self.to_bits(); + unsafe { + set_info(info); + } + } +} + +/// Get the target application entry point, if present. +/// +/// This will only return `Some` when the boot type is `Jump`. +pub fn application_address() -> Option { + let boot_type = DaisyBootType::from_backup(); + + match boot_type { + Some(DaisyBootType::Jump) => { + let info = unsafe { get_info() }; + Some(info[1]) + } + _ => None, + } +} + +/// Set the target application entry point. +/// +/// This also sets the boot type to `Jump`. +pub unsafe fn set_application_address(address: u32) { + let boot_type = DaisyBootType::Jump; + unsafe { boot_type.write() }; + + let mut info = unsafe { get_info() }; + info[1] = address; + unsafe { + set_info(info); + } +} + +/// Describes which bootloader the device will reset to. +#[derive(Debug)] +pub enum BootType { + /// The built-in ST Micro system bootloader. + Stm(crate::gpio::SeedButton), + /// The daisy bootloader with its associated boot type. + Daisy(DaisyBootType), +} + +/// Reset to the provided bootloader. +/// +/// # Panics +/// +/// Panics if the Daisy bootloader is selected but +/// the program is running from internal flash, meaning +/// no Daisy bootloader can be present. +pub fn reset_to_bootloader(boot_type: BootType, scb: &SCB, delay: &mut Delay) -> ! { + match boot_type { + BootType::Stm(boot_pin) => { + let mut pin = boot_pin.into_push_pull_output(); + pin.set_high(); + + // Allow capacitor to charge. + delay.delay_ms(10u8); + } + BootType::Daisy(daisy_type) => { + let region = MemoryRegion::from_vtable(scb); + + if matches!(region, Some(MemoryRegion::InternalFlash)) { + panic!("Attempted to jump to non-existent Daisy bootloader"); + } + + unsafe { + // write twice just in case + daisy_type.write(); + daisy_type.write(); + } + } + } + + system_reset() +} + +/// Reset the device. +/// +/// This should behave exactly like pressing the RESET button. +pub fn system_reset() -> ! { + // Clear all interrupts. + let ptr = unsafe { &*stm32h7xx_hal::pac::RCC::PTR }; + ptr.cier.reset(); + + SCB::sys_reset() +} diff --git a/src/flash.rs b/src/flash.rs index 778377f..6b4cc38 100644 --- a/src/flash.rs +++ b/src/flash.rs @@ -29,7 +29,7 @@ pub enum FlashErase { #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum FlashState { Idle, - Programming { address: u32, chunk: u32 }, + Programming { address: u32, chunk: usize }, Erasing(FlashErase), } @@ -37,6 +37,8 @@ enum FlashState { pub struct Flash { qspi: stm32h7xx_hal::xspi::Qspi, state: FlashState, + page_buffer: [u8; 256], + page_len: usize, } /* @@ -167,6 +169,8 @@ impl Flash { let mut flash = Flash { qspi, state: FlashState::Idle, + page_buffer: [0u8; 256], + page_len: 0, }; //enable quad @@ -189,6 +193,50 @@ impl Flash { flash } + pub fn begin_erase(&mut self, op: FlashErase) -> NBFlashResult<()> { + match self.state { + FlashState::Idle => { + self.enable_write()?; + self.wait(); + match op { + FlashErase::Chip => self.write_command(0x60), + FlashErase::Sector4K(a) => self.qspi.write_extended( + QspiWord::U8(0xD7), + QspiWord::U24(a as _), + QspiWord::None, + &[], + ), + FlashErase::Block32K(a) => self.qspi.write_extended( + QspiWord::U8(0x52), + QspiWord::U24(a as _), + QspiWord::None, + &[], + ), + FlashErase::Block64K(a) => self.qspi.write_extended( + QspiWord::U8(0xD8), + QspiWord::U24(a as _), + QspiWord::None, + &[], + ), + }?; + self.state = FlashState::Erasing(op); + Ok(()) + } + _ => { + return Err(nbError::WouldBlock); + } + } + } + + pub fn poll_erase(&mut self) -> NBFlashResult<()> { + if self.write_complete()? { + self.state = FlashState::Idle; + Ok(()) + } else { + Err(nbError::WouldBlock) + } + } + /// Erase all or some of the chip. /// /// Remarks: @@ -270,13 +318,13 @@ impl Flash { /// same page. If the data to be programmed are less than a full page, /// the data of all other bytes on the same page will remain unchanged. pub fn program(&mut self, address: u32, data: &[u8]) -> NBFlashResult<()> { - let prog = |flash: &mut Self, chunk_index: u32| -> NBFlashResult<()> { - if let Some(chunk) = data.chunks(32).nth(chunk_index as usize) { + let prog = |flash: &mut Self, chunk_index: usize| -> NBFlashResult<()> { + if let Some(chunk) = data.chunks(32).nth(chunk_index) { flash.enable_write()?; flash.wait(); flash.qspi.write_extended( QspiWord::U8(0x02), - QspiWord::U24(address + chunk_index * 32), + QspiWord::U24(address + chunk_index as u32 * 32), QspiWord::None, chunk, )?; @@ -306,4 +354,61 @@ impl Flash { _ => panic!("invalid state for programming"), } } + + pub fn begin_program(&mut self, address: u32, data: &[u8]) -> NBFlashResult<()> { + for (src, dest) in data.iter().zip(self.page_buffer.iter_mut()) { + *dest = *src; + } + self.page_len = data.len().min(256); + + match self.state { + FlashState::Idle => { + self.enable_write()?; + self.wait(); + self.qspi.write_extended( + QspiWord::U8(0x02), + QspiWord::U24(address), + QspiWord::None, + &self.page_buffer[..self.page_len.min(32)], + )?; + self.state = FlashState::Programming { address, chunk: 1 }; + + Ok(()) + } + _ => Err(nbError::WouldBlock), + } + } + + pub fn poll_program(&mut self) -> NBFlashResult<()> { + match self.state { + FlashState::Programming { address, chunk } => { + if !self.write_complete()? { + return Err(nbError::WouldBlock); + } + + let offset = chunk * 32; + + if offset >= self.page_len { + self.state = FlashState::Idle; + return Ok(()); + } + + self.enable_write()?; + self.wait(); + self.qspi.write_extended( + QspiWord::U8(0x02), + QspiWord::U24(address + offset as u32), + QspiWord::None, + &self.page_buffer[offset..(offset + 32).min(self.page_len)], + )?; + self.state = FlashState::Programming { + address, + chunk: chunk + 1, + }; + + Err(nbError::WouldBlock) + } + _ => Ok(()), + } + } } diff --git a/src/lib.rs b/src/lib.rs index 5c2704c..a441f9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub type FrameTimer = stm32h7xx_hal::timer::Timer; pub use stm32h7xx_hal as hal; pub mod audio; +pub mod bootloader; pub mod flash; pub mod gpio; pub mod hid; diff --git a/src/system.rs b/src/system.rs index f5c1452..fd16068 100644 --- a/src/system.rs +++ b/src/system.rs @@ -48,6 +48,145 @@ pub struct System { pub delay: Delay, } +pub struct MinimalSystem { + pub gpio: crate::gpio::GPIO, + pub flash: crate::flash::Flash, + pub internal_usb: Option, + pub delay: Delay, +} + +pub fn initialize_backup_sram(pwr: &mut stm32::PWR, rcc: &mut stm32::RCC) { + pwr.cr1.modify(|_, w| w.dbp().set_bit()); + pwr.cr2.modify(|_, w| w.bren().set_bit()); + + loop { + if pwr.cr1.read().dbp().bit_is_set() { + break; + } + } + + rcc.ahb4enr.modify(|_, w| w.bkpramen().set_bit()); + // read it back + let _bit = rcc.ahb4enr.read().bkpramen().bit(); +} + +impl MinimalSystem { + /// Initialize clocks + pub fn init_clocks( + mut pwr: stm32::PWR, + mut rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + ) -> rcc::Ccdr { + // Power + initialize_backup_sram(&mut pwr, &mut rcc); + let pwr = pwr.constrain(); + let vos = pwr.vos0(syscfg).freeze(); + + rcc.constrain() + .use_hse(HSE_CLOCK_MHZ.convert()) + .sys_ck(CLOCK_RATE_HZ) + .pclk1(PCLK_HZ) // DMA clock + // PLL1 + .pll1_strategy(rcc::PllConfigStrategy::Iterative) + .pll1_p_ck(PLL1_P_HZ) + .pll1_q_ck(PLL1_Q_HZ) + .pll1_r_ck(PLL1_R_HZ) + // PLL2 + .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input + // .pll2_q_ck(PLL2_Q_HZ) + // .pll2_r_ck(PLL2_R_HZ) + // PLL3 + .pll3_strategy(rcc::PllConfigStrategy::Fractional) + .pll3_p_ck(PLL3_P_HZ) // used for SAI1 + .pll3_q_ck(PLL3_Q_HZ) + .pll3_r_ck(PLL3_R_HZ) + .freeze(vos, syscfg) + } + + pub fn new(resources: SystemResources) -> Self { + let mut delay = Delay::new(resources.syst, *resources.clocks); + + let gpioa = resources.gpioa.split(resources.gpioa_rec); + let gpiob = resources.gpiob.split(resources.gpiob_rec); + let gpioc = resources.gpioc.split(resources.gpioc_rec); + let gpiod = resources.gpiod.split(resources.gpiod_rec); + let gpioe = resources.gpioe.split(resources.gpioe_rec); + let gpiof = resources.gpiof.split(resources.gpiof_rec); + let gpiog = resources.gpiog.split(resources.gpiog_rec); + let gpioh = resources.gpioh.split(resources.gpioh_rec); + let gpioi = resources.gpioi.split(resources.gpioi_rec); + + // Set up GPIOs + let gpio = crate::gpio::GPIO::init( + gpioc.pc7, + gpiog.pg3, + Some(gpiob.pb12), + Some(gpioc.pc11), + Some(gpioc.pc10), + Some(gpioc.pc9), + Some(gpioc.pc8), + Some(gpiod.pd2), + Some(gpioc.pc12), + Some(gpiog.pg10), + Some(gpiog.pg11), + Some(gpiob.pb4), + Some(gpiob.pb5), + Some(gpiob.pb8), + Some(gpiob.pb9), + Some(gpiob.pb6), + Some(gpiob.pb7), + Some(gpioc.pc0), + Some(gpioa.pa3), + Some(gpiob.pb1), + Some(gpioa.pa7), + Some(gpioa.pa6), + Some(gpioc.pc1), + Some(gpioc.pc4), + Some(gpioa.pa5), + Some(gpioa.pa4), + Some(gpioa.pa1), + Some(gpioa.pa0), + Some(gpiod.pd11), + Some(gpiog.pg9), + Some(gpioa.pa2), + Some(gpiob.pb14), + Some(gpiob.pb15), + None, + None, + ); + + System::init_debug(resources.dcb, resources.dwt); + + // set up flash + let flash = crate::flash::Flash::new( + resources.qspi, + resources.qspi_rec, + resources.clocks, + gpiof.pf6, + gpiof.pf7, + gpiof.pf8, + gpiof.pf9, + gpiof.pf10, + gpiog.pg6, + ); + + info!("Minimal system initialization complete."); + + // grab internal usb pins + let internal_usb = InternalUsbPins { + dp: gpioa.pa12, + dm: gpioa.pa11, + }; + + Self { + gpio, + flash, + internal_usb: Some(internal_usb), + delay, + } + } +} + pub struct InternalUsbPins { pub dp: gpio::gpioa::PA12, pub dm: gpio::gpioa::PA11, @@ -160,6 +299,55 @@ macro_rules! system_init { }}; } +#[macro_export] +macro_rules! minimal_init { + ($core:ident, $device:ident, $ccdr:ident) => {{ + let resources = ::libdaisy::system::SystemResources { + clocks: &$ccdr.clocks, + adc1: $device.ADC1, + adc2: $device.ADC2, + adc12_rec: $ccdr.peripheral.ADC12, + syst: $core.SYST, + mpu: &mut $core.MPU, + scb: &mut $core.SCB, + dcb: &mut $core.DCB, + dwt: &mut $core.DWT, + fmc: $device.FMC, + fmc_rec: $ccdr.peripheral.FMC, + i2c2: $device.I2C2, + i2c2_rec: $ccdr.peripheral.I2C2, + cpuid: &mut $core.CPUID, + qspi: $device.QUADSPI, + qspi_rec: $ccdr.peripheral.QSPI, + sai1: $device.SAI1, + sai1_rec: $ccdr.peripheral.SAI1, + gpioa: $device.GPIOA, + gpioa_rec: $ccdr.peripheral.GPIOA, + gpiob: $device.GPIOB, + gpiob_rec: $ccdr.peripheral.GPIOB, + gpioc: $device.GPIOC, + gpioc_rec: $ccdr.peripheral.GPIOC, + gpiod: $device.GPIOD, + gpiod_rec: $ccdr.peripheral.GPIOD, + gpioe: $device.GPIOE, + gpioe_rec: $ccdr.peripheral.GPIOE, + gpiof: $device.GPIOF, + gpiof_rec: $ccdr.peripheral.GPIOF, + gpiog: $device.GPIOG, + gpiog_rec: $ccdr.peripheral.GPIOG, + gpioh: $device.GPIOH, + gpioh_rec: $ccdr.peripheral.GPIOH, + gpioi: $device.GPIOI, + gpioi_rec: $ccdr.peripheral.GPIOI, + dma1: $device.DMA1, + dma1_rec: $ccdr.peripheral.DMA1, + block_size: 0, + }; + + ::libdaisy::system::MinimalSystem::new(resources) + }}; +} + #[derive(Clone, Copy)] pub enum Version { Seed, @@ -189,8 +377,13 @@ impl System { } /// Initialize clocks - pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { + pub fn init_clocks( + mut pwr: stm32::PWR, + mut rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + ) -> rcc::Ccdr { // Power + initialize_backup_sram(&mut pwr, &mut rcc); let pwr = pwr.constrain(); let vos = pwr.vos0(syscfg).freeze(); From f67de58457d6a0f9f4b92baa83fb26dd17b69637 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Wed, 30 Oct 2024 02:32:43 -0600 Subject: [PATCH 16/52] Improved bootloader robustness --- Cargo.toml | 3 +- src/bootloader.rs | 98 +++++++++++++++++++++++++++++++++++------------ src/delay.rs | 53 +++++++++++++++++++++++++ src/lib.rs | 12 ++---- src/system.rs | 5 +-- 5 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 src/delay.rs diff --git a/Cargo.toml b/Cargo.toml index 5cfbbc2..245a911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ cortex-m-log = { version = "0.8", features = [ "semihosting", "log-integration", ], optional = true } -cortex-m-rtic = "1" cortex-m-semihosting = { version = "0.5", optional = true } debouncr = "0.2.2" lazy_static = { version = "1.4.0", features = ["spin_no_std"], optional = true } @@ -40,6 +39,7 @@ stm32h7xx-hal = { version = "0.16.0", features = [ "sdmmc-fatfs", "usb_hs", ] } +embedded-hal = "0.2" [features] default = [] @@ -73,6 +73,7 @@ lto = true # better optimizations opt-level = "s" # optimize for binary size [dev-dependencies] +cortex-m-rtic = "1" embedded-sdmmc = "0.4" libm = "0.2" num_enum = { version = "0.5", default-features = false } diff --git a/src/bootloader.rs b/src/bootloader.rs index 774be45..cc9b4db 100644 --- a/src/bootloader.rs +++ b/src/bootloader.rs @@ -1,16 +1,17 @@ +//! Types and functions for interfacing with the Daisy bootloader. + use core::mem::MaybeUninit; -use cortex_m::prelude::*; -use stm32h7xx_hal::{delay::Delay, pac::SCB}; +use stm32h7xx_hal::pac::SCB; #[link_section = ".boot_info"] -static mut BOOT_INFO: MaybeUninit<[u32; 3]> = MaybeUninit::uninit(); +static mut BOOT_INFO: MaybeUninit<[u32; 4]> = MaybeUninit::uninit(); -unsafe fn get_info() -> [u32; 3] { +unsafe fn get_info() -> [u32; 4] { let info_ptr = core::ptr::addr_of!(BOOT_INFO); unsafe { core::ptr::read_volatile(info_ptr).assume_init() } } -unsafe fn set_info(info: [u32; 3]) { +unsafe fn set_info(info: [u32; 4]) { let info_ptr = core::ptr::addr_of_mut!(BOOT_INFO); unsafe { core::ptr::write_volatile(info_ptr, MaybeUninit::new(info)); @@ -19,23 +20,23 @@ unsafe fn set_info(info: [u32; 3]) { // These should probably be defined in the linker, // but this'll do for now. -const D1_AXIFLASH_BASE: u32 = 0x0800_0000; -const D1_ITCMRAM_BASE: u32 = 0x0000_0000; -const D1_DTCMRAM_BASE: u32 = 0x2000_0000; +pub const D1_AXIFLASH_BASE: u32 = 0x0800_0000; +pub const D1_ITCMRAM_BASE: u32 = 0x0000_0000; +pub const D1_DTCMRAM_BASE: u32 = 0x2000_0000; pub const D1_AXISRAM_BASE: u32 = 0x2400_0000; -const D2_AXISRAM_BASE: u32 = 0x3000_0000; -const D3_SRAM_BASE: u32 = 0x3800_0000; -const SDRAM_BASE: u32 = 0xC000_0000; -const QSPI_BASE: u32 = 0x9000_0000; - -const INTERNAL_FLASH_SIZE: u32 = 0x20000; -const ITCMRAM_SIZE: u32 = 0x10000; -const DTCMRAM_SIZE: u32 = 0x20000; +pub const D2_AXISRAM_BASE: u32 = 0x3000_0000; +pub const D3_SRAM_BASE: u32 = 0x3800_0000; +pub const SDRAM_BASE: u32 = 0xC000_0000; +pub const QSPI_BASE: u32 = 0x9000_0000; + +pub const INTERNAL_FLASH_SIZE: u32 = 0x20000; +pub const ITCMRAM_SIZE: u32 = 0x10000; +pub const DTCMRAM_SIZE: u32 = 0x20000; pub const SRAM_D1_SIZE: u32 = 0x80000; -const SRAM_D2_SIZE: u32 = 0x48000; -const SRAM_D3_SIZE: u32 = 0x10000; -const SDRAM_SIZE: u32 = 0x4000000; -const QSPI_SIZE: u32 = 0x800000; +pub const SRAM_D2_SIZE: u32 = 0x48000; +pub const SRAM_D3_SIZE: u32 = 0x10000; +pub const SDRAM_SIZE: u32 = 0x4000000; +pub const QSPI_SIZE: u32 = 0x800000; const D1_AXIFLASH_END: u32 = D1_AXIFLASH_BASE + INTERNAL_FLASH_SIZE - 1; const D1_ITCMRAM_END: u32 = D1_ITCMRAM_BASE + ITCMRAM_SIZE - 1; @@ -46,6 +47,7 @@ const D3_SRAM_END: u32 = D3_SRAM_BASE + SRAM_D3_SIZE - 1; const SDRAM_END: u32 = SDRAM_BASE + SDRAM_SIZE - 1; const QSPI_END: u32 = QSPI_BASE + QSPI_SIZE - 1; +/// Describes a region in memory. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MemoryRegion { InternalFlash, @@ -87,14 +89,22 @@ impl MemoryRegion { /// Describes how the Daisy bootloader should behave. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DaisyBootType { - /// This is what the bootloader writes when it resets itself. + /// This causes the bootloader to immediately jump to + /// the target application, assuming its address has been loaded in. /// - /// TODO: document better + /// This should not be set by any code outside the bootloader. Jump, /// Jump immediately to the target application. SkipTimeout, /// Wait in the bootloader indefinitely. InfiniteTimeout, + /// The target application encountered an unrecoverable error. + /// + /// This can allow the bootloader to break otherwise infinite + /// boot-reset loops. By default, the bootloader will + /// stop jumping back to the application if this has been + /// set five times. + Panic, } impl DaisyBootType { @@ -112,6 +122,8 @@ impl DaisyBootType { Self::Jump => 0xDEADBEEF, Self::SkipTimeout => 0x5AFEB007, Self::InfiniteTimeout => 0xB0074EFA, + // You gotta be careful what you eat. + Self::Panic => 0x8BADF00D, } } @@ -120,6 +132,7 @@ impl DaisyBootType { 0xDEADBEEF => Some(Self::Jump), 0x5AFEB007 => Some(Self::SkipTimeout), 0xB0074EFA => Some(Self::InfiniteTimeout), + 0x8BADF00D => Some(Self::Panic), _ => None, } } @@ -143,6 +156,7 @@ impl DaisyBootType { } } +/// The bootloader version. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Version { /// Any version below v6. @@ -218,6 +232,23 @@ pub unsafe fn set_application_address(address: u32) { } } +/// A counter for how many times an application has +/// reset itself in a panicked state. +pub fn panic_count() -> u32 { + let info = unsafe { get_info() }; + info[3] +} + +/// Set the panic count. +pub unsafe fn set_panic_count(value: u32) { + let mut info = unsafe { get_info() }; + info[3] = value; + + unsafe { + set_info(info); + } +} + /// Describes which bootloader the device will reset to. #[derive(Debug)] pub enum BootType { @@ -234,17 +265,36 @@ pub enum BootType { /// Panics if the Daisy bootloader is selected but /// the program is running from internal flash, meaning /// no Daisy bootloader can be present. -pub fn reset_to_bootloader(boot_type: BootType, scb: &SCB, delay: &mut Delay) -> ! { +pub fn reset_to_bootloader(boot_type: BootType) -> ! { + // # SAFETY + // + // SCB and CPUID are zero-sized types. They are passed around + // as a means of maintaining correctness. + // + // However, since we're resetting the device here (or panicking), + // requiring that we pass them in can be a little onerous. + // + // TODO: In the event of a panic, invalid configurations may + // be observed. There's no guarantee that the panic handler + // will restore the device to a sound configuration. + // + // This should probably be split out into an unsafe reset function. + let mut scb: SCB = unsafe { core::mem::transmute(()) }; + let mut cpuid = unsafe { core::mem::transmute(()) }; + scb.disable_dcache(&mut cpuid); + match boot_type { BootType::Stm(boot_pin) => { let mut pin = boot_pin.into_push_pull_output(); pin.set_high(); // Allow capacitor to charge. + use crate::delay::DelayMs; + let mut delay = crate::delay::CycleDelay::new(); delay.delay_ms(10u8); } BootType::Daisy(daisy_type) => { - let region = MemoryRegion::from_vtable(scb); + let region = MemoryRegion::from_vtable(&scb); if matches!(region, Some(MemoryRegion::InternalFlash)) { panic!("Attempted to jump to non-existent Daisy bootloader"); diff --git a/src/delay.rs b/src/delay.rs new file mode 100644 index 0000000..5edc4d7 --- /dev/null +++ b/src/delay.rs @@ -0,0 +1,53 @@ +//! Simple, cycle-based delay. + +use cortex_m::asm::delay as delay_cycles; +pub use embedded_hal::blocking::delay::{DelayMs, DelayUs}; + +/// A type that uses CPU cycles as a delay source. +/// +/// [CycleDelay] is guaranteed to block for +/// at least as many cycles as requested, but it +/// makes no claims about accuracy. Servicing +/// interrupts can make the delay extend much longer. +/// +/// For accurate timings, use a hardware timer. +/// +/// Delay methods are provided via `embedded-hal`'s [DelayMs] +/// and [DelayUs] traits. +/// +/// ``` +/// use libdaisy::delay::{CycleDelay, DelayMs}; +/// +/// let mut delay = CycleDelay::new(); +/// // You may need to specify the literal's +/// // type to help Rust resolve the trait. +/// delay.delay_ms(10u8); +/// ``` +pub struct CycleDelay; + +impl CycleDelay { + /// Construct a new [CycleDelay]. + pub fn new() -> Self { + CycleDelay + } +} + +macro_rules! impl_delay { + ($ty:path) => { + impl DelayMs<$ty> for CycleDelay { + fn delay_ms(&mut self, ms: $ty) { + delay_cycles((ms as u32).saturating_mul(crate::MILICYCLES)); + } + } + + impl DelayUs<$ty> for CycleDelay { + fn delay_us(&mut self, us: $ty) { + delay_cycles((us as u32).saturating_mul(crate::MICROCYCLES)); + } + } + }; +} + +impl_delay!(u8); +impl_delay!(u16); +impl_delay!(u32); diff --git a/src/lib.rs b/src/lib.rs index a441f9b..62a8f59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,10 @@ #![no_std] #![allow(dead_code)] -// #[macro_use(singleton)] -// extern crate cortex_m; - -use cortex_m::asm::delay as delay_cycles; use stm32h7xx_hal::time::Hertz; pub const MILLI: u32 = 1_000; +pub const MICRO: u32 = 1_000_000; pub const AUDIO_FRAME_RATE_HZ: u32 = 1_000; pub const AUDIO_BLOCK_SIZE: u16 = 48; pub const AUDIO_SAMPLE_RATE: usize = 48_000; @@ -15,6 +12,7 @@ pub const AUDIO_SAMPLE_HZ: Hertz = Hertz::from_raw(48_000); pub const CLOCK_RATE_HZ: Hertz = Hertz::from_raw(480_000_000_u32); pub const MILICYCLES: u32 = CLOCK_RATE_HZ.raw() / MILLI; +pub const MICROCYCLES: u32 = CLOCK_RATE_HZ.raw() / MICRO; pub type FrameTimer = stm32h7xx_hal::timer::Timer; @@ -22,6 +20,7 @@ pub use stm32h7xx_hal as hal; pub mod audio; pub mod bootloader; +pub mod delay; pub mod flash; pub mod gpio; pub mod hid; @@ -31,8 +30,3 @@ pub mod prelude; pub mod sdmmc; pub mod sdram; pub mod system; - -// Delay for ms, note if interrupts are active delay time will extend -pub fn delay_ms(ms: u32) { - delay_cycles(ms * MILICYCLES); -} diff --git a/src/system.rs b/src/system.rs index fd16068..57f073e 100644 --- a/src/system.rs +++ b/src/system.rs @@ -104,17 +104,14 @@ impl MinimalSystem { } pub fn new(resources: SystemResources) -> Self { - let mut delay = Delay::new(resources.syst, *resources.clocks); + let delay = Delay::new(resources.syst, *resources.clocks); let gpioa = resources.gpioa.split(resources.gpioa_rec); let gpiob = resources.gpiob.split(resources.gpiob_rec); let gpioc = resources.gpioc.split(resources.gpioc_rec); let gpiod = resources.gpiod.split(resources.gpiod_rec); - let gpioe = resources.gpioe.split(resources.gpioe_rec); let gpiof = resources.gpiof.split(resources.gpiof_rec); let gpiog = resources.gpiog.split(resources.gpiog_rec); - let gpioh = resources.gpioh.split(resources.gpioh_rec); - let gpioi = resources.gpioi.split(resources.gpioi_rec); // Set up GPIOs let gpio = crate::gpio::GPIO::init( From 9fd1fd9e42bde273bc685c66716ae39b28d582ce Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Mon, 9 Dec 2024 16:51:36 -0700 Subject: [PATCH 17/52] Started working on multiplexer API --- src/lib.rs | 1 + src/multiplexer.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/multiplexer.rs diff --git a/src/lib.rs b/src/lib.rs index 62a8f59..4767014 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ pub mod gpio; pub mod hid; pub mod logger; pub mod mpu; +pub mod multiplexer; pub mod prelude; pub mod sdmmc; pub mod sdram; diff --git a/src/multiplexer.rs b/src/multiplexer.rs new file mode 100644 index 0000000..4062f82 --- /dev/null +++ b/src/multiplexer.rs @@ -0,0 +1,60 @@ +use embedded_hal::digital::v2::OutputPin; + +pub struct Multiplexer { + analog_pin: A, + digital_pins: D, + channels: [u32; LENGTH], + index: u8, +} + +impl Multiplexer { + pub fn analog_pin(&mut self) -> &mut A { + &mut self.analog_pin + } + + pub fn channels(&self) -> &[u32] { + &self.channels + } + + pub fn update(&mut self, value: u32) { + self.channels[self.index as usize] = value; + } +} + +macro_rules! impl_multi { + ($count:literal, $new:ident, $($ty:ident),*) => { + impl Multiplexer + where + $($ty: OutputPin),* + { + /// Construct a new [Multiplexer]. + /// + /// `digital_pins` must be a tuple of one to three output pins. + pub fn $new(analog_pin: A, digital_pins: ($($ty,)*)) -> Self { + Self { + analog_pin, + digital_pins, + channels: [const { 0 }; $count], + index: 0, + } + } + + /// Tick the multiplexer pins by one. + #[allow(unused_assignments, non_snake_case)] + pub fn tick(&mut self) { + self.index = (self.index + 1) % $count; + let ($($ty,)*) = &mut self.digital_pins; + let mut mask = self.index; + + $( + let _ = $ty.set_state(((mask & 1) > 0).into()); + mask >>= 1; + )* + } + } + }; +} + +impl_multi!(2, new_1, D0); +impl_multi!(4, new_2, D0, D1); +impl_multi!(8, new_3, D0, D1, D2); From 78f9b14170e04bae0509a2c6ba1740521ae257d6 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Tue, 10 Dec 2024 18:39:16 -0700 Subject: [PATCH 18/52] Suppressed mutable static warning --- src/audio.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/audio.rs b/src/audio.rs index d151da4..9715785 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,5 +1,11 @@ //! Audio module. Handles audio startup and I/O. //! As well as converting between the S24 input and f32 for processing. + +// This is a temporary measure for the TX and RX BUFFER +// items. More properly, they should be `UnsafeSyncCell<[0; DMA_BUFFER_SIZE]>`, +// but this type is unstable. +#![allow(static_mut_refs)] + use core::convert::Infallible; use cortex_m::prelude::_embedded_hal_blocking_i2c_Write; use log::info; From a064be3d892319dd3518ff253610038ab5c0c69d Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 12 Dec 2024 17:51:46 -0700 Subject: [PATCH 19/52] Resolved passthrough issue on newer CODECs --- src/audio.rs | 40 ++++++++++++++++++++++++++++++---------- src/bootloader.rs | 28 ++++++++++++++++++++++++---- src/delay.rs | 1 + src/flash.rs | 4 +--- src/mpu.rs | 5 ++--- 5 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 9715785..c1df8ac 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -295,6 +295,7 @@ impl Audio { } crate::system::Version::Seed2DFM | crate::system::Version::Seed => { let dma_buffer_size = block_size * 2 * 2; + let rx_buffer: &'static mut [u32] = unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; let dma_config = dma::dma::DmaConfig::default() @@ -368,19 +369,21 @@ impl Audio { _ => unreachable!(), } - info!("Start audio stream..."); - input_stream.start(|_sai1_rb| { - sai.enable_dma(SaiChannel::ChannelB); - }); - // There is no need to wait in this configuration. output_stream.start(|_sai1_rb| { sai.enable_dma(SaiChannel::ChannelA); }); + // Not sure why exactly, but enabling SAI before the input DMA stream + // will properly order the input channels. info!("Audio started!"); sai.enable(); + info!("Start audio stream..."); + input_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelB); + }); + let max_transfer_size = block_size * 2; let input = Input::new( unsafe { &*core::ptr::addr_of!(RX_BUFFER) }, @@ -429,14 +432,14 @@ impl Audio { false } } - AudioStream::S2dfm { input, .. } => { - if input.get_half_transfer_flag() { - input.clear_half_transfer_interrupt(); + AudioStream::S2dfm { output, .. } => { + if output.get_half_transfer_flag() { + output.clear_half_transfer_interrupt(); self.input.set_index(0); self.output.set_index(0); true - } else if input.get_transfer_complete_flag() { - input.clear_transfer_complete_interrupt(); + } else if output.get_transfer_complete_flag() { + output.clear_transfer_complete_interrupt(); self.input.set_index(self.max_transfer_size); self.output.set_index(self.max_transfer_size); true @@ -510,6 +513,23 @@ impl Audio { Ok(()) } + pub fn passthrough(&mut self) { + if self.read() { + let input = self.input.buffer + [self.input.index..self.input.index + self.max_transfer_size] + .chunks_exact(2); + + let output = self.output.buffer + [self.output.index..self.output.index + self.max_transfer_size] + .chunks_exact_mut(2); + + for (input, output) in input.zip(output) { + output[0] = input[0]; + output[1] = input[1]; + } + } + } + /// Push data to the DMA buffer for output /// Call this once per sample per call to [get_stereo()](Audio#get_stereo) #[allow(clippy::result_unit_err)] diff --git a/src/bootloader.rs b/src/bootloader.rs index cc9b4db..32f0428 100644 --- a/src/bootloader.rs +++ b/src/bootloader.rs @@ -1,7 +1,7 @@ //! Types and functions for interfacing with the Daisy bootloader. use core::mem::MaybeUninit; -use stm32h7xx_hal::pac::SCB; +use stm32h7xx_hal::pac::{CPUID, SCB}; #[link_section = ".boot_info"] static mut BOOT_INFO: MaybeUninit<[u32; 4]> = MaybeUninit::uninit(); @@ -117,7 +117,7 @@ impl DaisyBootType { Self::from_bits(boot_word) } - fn to_bits(&self) -> u32 { + fn to_bits(self) -> u32 { match self { Self::Jump => 0xDEADBEEF, Self::SkipTimeout => 0x5AFEB007, @@ -138,6 +138,11 @@ impl DaisyBootType { } /// Write the boot type to backup SRAM. + /// + /// # Safety + /// + /// Writing this value sets bits in backup SRAM directly, + /// the XOR mutable aliasing is not guaranteed. unsafe fn write(&self) { let mut info = unsafe { get_info() }; info[0] = self.to_bits(); @@ -147,6 +152,11 @@ impl DaisyBootType { } /// Write an invalid bit pattern to backup SRAM, effectively clearing the boot type. + /// + /// # Safety + /// + /// Writing this value sets bits in backup SRAM directly, + /// the XOR mutable aliasing is not guaranteed. pub unsafe fn clear() { let mut info = unsafe { get_info() }; info[0] = 0; @@ -185,7 +195,7 @@ impl Version { } } - fn to_bits(&self) -> u32 { + fn to_bits(self) -> u32 { match self { Self::LessThanV6 => 0, Self::V6 => 2, @@ -221,6 +231,11 @@ pub fn application_address() -> Option { /// Set the target application entry point. /// /// This also sets the boot type to `Jump`. +/// +/// # Safety +/// +/// Writing this value sets bits in backup SRAM directly, +/// the XOR mutable aliasing is not guaranteed. pub unsafe fn set_application_address(address: u32) { let boot_type = DaisyBootType::Jump; unsafe { boot_type.write() }; @@ -240,6 +255,11 @@ pub fn panic_count() -> u32 { } /// Set the panic count. +/// +/// # Safety +/// +/// Writing this value sets bits in backup SRAM directly, +/// the XOR mutable aliasing is not guaranteed. pub unsafe fn set_panic_count(value: u32) { let mut info = unsafe { get_info() }; info[3] = value; @@ -280,7 +300,7 @@ pub fn reset_to_bootloader(boot_type: BootType) -> ! { // // This should probably be split out into an unsafe reset function. let mut scb: SCB = unsafe { core::mem::transmute(()) }; - let mut cpuid = unsafe { core::mem::transmute(()) }; + let mut cpuid = unsafe { core::mem::transmute::<(), CPUID>(()) }; scb.disable_dcache(&mut cpuid); match boot_type { diff --git a/src/delay.rs b/src/delay.rs index 5edc4d7..220d4af 100644 --- a/src/delay.rs +++ b/src/delay.rs @@ -23,6 +23,7 @@ pub use embedded_hal::blocking::delay::{DelayMs, DelayUs}; /// // type to help Rust resolve the trait. /// delay.delay_ms(10u8); /// ``` +#[derive(Default)] pub struct CycleDelay; impl CycleDelay { diff --git a/src/flash.rs b/src/flash.rs index 6b4cc38..a0a4e2e 100644 --- a/src/flash.rs +++ b/src/flash.rs @@ -222,9 +222,7 @@ impl Flash { self.state = FlashState::Erasing(op); Ok(()) } - _ => { - return Err(nbError::WouldBlock); - } + _ => Err(nbError::WouldBlock), } } diff --git a/src/mpu.rs b/src/mpu.rs index 0ae6135..4dd3608 100644 --- a/src/mpu.rs +++ b/src/mpu.rs @@ -1,6 +1,5 @@ -//!Configure MPU +//! Configure MPU -/// /// Based on example from: /// https://github.com/richardeoin/stm32h7-fmc/blob/master/examples/stm32h747i-disco.rs /// @@ -10,7 +9,7 @@ /// /// Function will panic if `size` is not a power of 2. Function /// will panic if `size` is not at least 32 bytes. - +/// /// Refer to ARM®v7-M Architecture Reference Manual ARM DDI 0403 /// Version E.b Section B3.5 use log::info; From 88b358c3f3f452a33dc460aa4705f038e0b14770 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Tue, 17 Dec 2024 12:13:48 -0700 Subject: [PATCH 20/52] Resolved incorrect SAI DMA ordering --- src/audio.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index c1df8ac..6ea93b0 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -137,7 +137,7 @@ pub struct Audio { sai: sai::Sai, input: Input, output: Output, - audio_stream: AudioStream, + pub audio_stream: AudioStream, max_transfer_size: usize, } @@ -303,6 +303,8 @@ impl Audio { .memory_increment(true) .peripheral_increment(false) .circular_buffer(true) + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true) .fifo_enable(false); let mut output_stream = dma::Transfer::init( dma1_stream0, @@ -417,7 +419,9 @@ impl Audio { fn read(&mut self) -> bool { // Check interrupt(s) match &mut self.audio_stream { - AudioStream::Normal { input, .. } => { + AudioStream::Normal { input, output } => { + output.clear_interrupts(); + if input.get_half_transfer_flag() { input.clear_half_transfer_interrupt(); self.input.set_index(0); @@ -432,7 +436,9 @@ impl Audio { false } } - AudioStream::S2dfm { output, .. } => { + AudioStream::S2dfm { output, input } => { + input.clear_interrupts(); + if output.get_half_transfer_flag() { output.clear_half_transfer_interrupt(); self.input.set_index(0); @@ -450,6 +456,21 @@ impl Audio { } } + /// Check interrupts without clearing flags. + pub fn peek_read(&mut self) -> bool { + // Check interrupt(s) + match &mut self.audio_stream { + AudioStream::Normal { input, output } => { + output.clear_interrupts(); + input.get_half_transfer_flag() || input.get_transfer_complete_flag() + } + AudioStream::S2dfm { output, input } => { + input.clear_interrupts(); + output.get_half_transfer_flag() || output.get_transfer_complete_flag() + } + } + } + /// Gets the audio input from the DMA memory and writes it to buffer pub fn get_stereo(&mut self, buffer: &mut AudioBuffer) -> bool { if self.read() { From a2d720ad4ff875f59a0e84fcb47191d17f0c822b Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Tue, 11 Feb 2025 18:19:14 -0700 Subject: [PATCH 21/52] Started work on mpr121 device --- src/devices/mod.rs | 3 + src/devices/mpr121.rs | 222 +++++++++++++++++++++++++++++++ src/{ => devices}/multiplexer.rs | 0 src/devices/shift_register.rs | 0 src/lib.rs | 2 +- 5 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/devices/mod.rs create mode 100644 src/devices/mpr121.rs rename src/{ => devices}/multiplexer.rs (100%) create mode 100644 src/devices/shift_register.rs diff --git a/src/devices/mod.rs b/src/devices/mod.rs new file mode 100644 index 0000000..8b3af90 --- /dev/null +++ b/src/devices/mod.rs @@ -0,0 +1,3 @@ +pub mod mpr121; +pub mod multiplexer; +pub mod shift_register; diff --git a/src/devices/mpr121.rs b/src/devices/mpr121.rs new file mode 100644 index 0000000..f3a7941 --- /dev/null +++ b/src/devices/mpr121.rs @@ -0,0 +1,222 @@ +use cortex_m::prelude::_embedded_hal_blocking_delay_DelayMs; +use stm32h7xx_hal::prelude::{ + _embedded_hal_blocking_i2c_Write as Write, _embedded_hal_blocking_i2c_WriteRead as WriteRead, +}; + +pub struct Mpr121 { + address: u8, + thresholds: [Mpr121Thresholds; 12], + states: TouchStates, + analog_states: [f32; 12], +} + +impl Mpr121 { + pub fn new( + address: u8, + touch_threshold: u8, + release_threshold: u8, + bus: &mut W, + ) -> Result { + let mut mpr = Mpr121 { + address, + thresholds: [Default::default(); 12], + states: Default::default(), + analog_states: [0.0; 12], + }; + + mpr.write(bus, Register::SoftReset, 0x63)?; + crate::delay::CycleDelay::new().delay_ms(1); + + mpr.write(bus, Register::Ecr, 0x0)?; + + mpr.write(bus, Register::Mhdr, 0x01)?; + mpr.write(bus, Register::Nhdr, 0x01)?; + mpr.write(bus, Register::Nclr, 0x0E)?; + mpr.write(bus, Register::Fdlr, 0x00)?; + + mpr.write(bus, Register::Mhdf, 0x01)?; + mpr.write(bus, Register::Nhdf, 0x05)?; + mpr.write(bus, Register::Nclf, 0x01)?; + mpr.write(bus, Register::Fdlf, 0x00)?; + + mpr.write(bus, Register::Nhdt, 0x00)?; + mpr.write(bus, Register::Nclt, 0x00)?; + mpr.write(bus, Register::Fdlt, 0x00)?; + + mpr.write(bus, Register::Debounce, 0)?; + mpr.write(bus, Register::Config1, 0x10)?; // default, 16uA charge current + mpr.write(bus, Register::Config2, 0x20)?; // 0.5uS encoding, 1ms period + + let ecr_settings = 0x80 + 12; // 5 bits for baseline tracking & proximity disabled + X + // amount of electrodes running (12) + mpr.write(bus, Register::Ecr, ecr_settings)?; // start with above ECR setting + + Ok(mpr) + } + + fn write( + &self, + bus: &mut W, + register: Register, + data: u8, + ) -> Result<(), W::Error> { + self.write_raw(bus, register as u8, data) + } + + fn write_raw( + &self, + bus: &mut W, + register: u8, + data: u8, + ) -> Result<(), W::Error> { + // first get the current set value of the MPR121_ECR register + let ecr_reg = Register::Ecr as u8; + let mut ecr_backup = 0; + + bus.write_read( + self.address, + &[Register::Ecr as u8], + core::slice::from_mut(&mut ecr_backup), + )?; + + // MPR121 must be put in Stop Mode to write to most registers + let stop_required = !((register == Register::Ecr) || ((0x73 <= reg) && (reg <= 0x7A))); + + if stop_required { + // clear this register to set stop mode + bus.write(self.address, &[Register::Ecr as u8, 0x00])?; + } + + bus.write(self.address, &[register, data])?; + + if stop_required { + // write back the previous set ECR settings + bus.write(self.address, &[Register::Ecr as u8, ecr_backup])?; + } + + Ok(()) + } + + fn read( + &self, + bus: &mut W, + register: Register, + buffer: &mut [u8], + ) -> Result<(), W::Error> { + self.read_raw(bus, register as u8, buffer) + } + + fn read_raw( + &self, + bus: &mut W, + register: u8, + buffer: &mut [u8], + ) -> Result<(), W::Error> { + bus.write_read(self.address, &[register], buffer) + } + + pub fn write_thresholds(&self, bus: &mut W) -> Result<(), W::Error> { + for (i, Mpr121Thresholds { touch, release }) in self.thresholds.iter().enumerate() { + let touch_register = Register::TouchTh0 as usize + i * 2; + let release_register = Register::ReleaseTh0 as usize + i * 2; + + self.write_raw(bus, touch_register as u8, *touch)?; + self.write_raw(bus, release_register as u8, *release)?; + } + + Ok(()) + } + + pub fn update(&self, bus: &mut W) -> Result<(), W::Error> { + let mut buffer = [0u8; 2]; + self.read(bus, Register::TouchStatusL, &mut buffer)?; + + self.states = TouchStates(u16::from_le_bytes(&buffer)); + let u10_scale = 1.0 / (1 << 10) as f32; + + for i in 0..self.analog_states.len() { + let register = Register::FiltData0L as usize + i * 2; + self.read_raw(bus, register as u8, &mut buffer)?; + + // the data returned is a 10-bit unsigned value + self.analog_states[i] = u16::from_le_bytes(&buffer) as f32 * u10_scale; + } + + Ok(()) + } + + pub fn states(&self) -> TouchStates { + self.states + } + + pub fn analog_state(&self, channel: u8) -> [f32; 12] { + self.analog_states + } +} + +#[derive(Default, Clone, Copy)] +pub struct TouchStates(u16); + +impl TouchStates { + pub fn is_touched(&self, channel: u8) -> bool { + ((self.0 >> channel) & 1) > 0 + } +} + +#[derive(Clone, Copy)] +pub struct Mpr121Thresholds { + pub touch: u8, + pub release: u8, +} + +impl Default for Mpr121Thresholds { + fn default() -> Self { + Mpr121Thresholds { + touch: 12, + release: 6, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +enum Register { + TouchStatusL = 0x00, + TouchStatusH = 0x01, + FiltData0L = 0x04, + FiltData0H = 0x05, + Baseline0 = 0x1E, + Mhdr = 0x2B, + Nhdr = 0x2C, + Nclr = 0x2D, + Fdlr = 0x2E, + Mhdf = 0x2F, + Nhdf = 0x30, + Nclf = 0x31, + Fdlf = 0x32, + Nhdt = 0x33, + Nclt = 0x34, + Fdlt = 0x35, + + TouchTh0 = 0x41, + ReleaseTh0 = 0x42, + Debounce = 0x5B, + Config1 = 0x5C, + Config2 = 0x5D, + ChargeCurr0 = 0x5F, + ChargeTime1 = 0x6C, + Ecr = 0x5E, + AutoConfig0 = 0x7B, + AutoConfig1 = 0x7C, + UpLimit = 0x7D, + LowLimit = 0x7E, + TargetLimit = 0x7F, + + GpioDir = 0x76, + GpioEn = 0x77, + GpioSet = 0x78, + GpioClr = 0x79, + GpioToggle = 0x7A, + + SoftReset = 0x80, +} diff --git a/src/multiplexer.rs b/src/devices/multiplexer.rs similarity index 100% rename from src/multiplexer.rs rename to src/devices/multiplexer.rs diff --git a/src/devices/shift_register.rs b/src/devices/shift_register.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 4767014..1c1556f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,12 +21,12 @@ pub use stm32h7xx_hal as hal; pub mod audio; pub mod bootloader; pub mod delay; +pub mod devices; pub mod flash; pub mod gpio; pub mod hid; pub mod logger; pub mod mpu; -pub mod multiplexer; pub mod prelude; pub mod sdmmc; pub mod sdram; From f0fc9a78fc4c7027aa479992a07a6e24726a27d5 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Wed, 12 Feb 2025 14:45:54 -0700 Subject: [PATCH 22/52] Improved mpr121 driver --- src/devices/mpr121.rs | 65 ++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/devices/mpr121.rs b/src/devices/mpr121.rs index f3a7941..321ac81 100644 --- a/src/devices/mpr121.rs +++ b/src/devices/mpr121.rs @@ -10,22 +10,34 @@ pub struct Mpr121 { analog_states: [f32; 12], } +#[derive(Debug)] +pub enum I2cError { + Write(W), + WriteRead(WR), +} + +pub type WriteReadError = + I2cError<::Error, ::Error>; + impl Mpr121 { pub fn new( address: u8, touch_threshold: u8, release_threshold: u8, bus: &mut W, - ) -> Result { + ) -> Result> { let mut mpr = Mpr121 { address, - thresholds: [Default::default(); 12], + thresholds: [Mpr121Thresholds { + touch: touch_threshold, + release: release_threshold, + }; 12], states: Default::default(), analog_states: [0.0; 12], }; mpr.write(bus, Register::SoftReset, 0x63)?; - crate::delay::CycleDelay::new().delay_ms(1); + crate::delay::CycleDelay::new().delay_ms(1u8); mpr.write(bus, Register::Ecr, 0x0)?; @@ -51,6 +63,8 @@ impl Mpr121 { // amount of electrodes running (12) mpr.write(bus, Register::Ecr, ecr_settings)?; // start with above ECR setting + mpr.write_thresholds(bus)?; + Ok(mpr) } @@ -59,7 +73,7 @@ impl Mpr121 { bus: &mut W, register: Register, data: u8, - ) -> Result<(), W::Error> { + ) -> Result<(), WriteReadError> { self.write_raw(bus, register as u8, data) } @@ -68,7 +82,7 @@ impl Mpr121 { bus: &mut W, register: u8, data: u8, - ) -> Result<(), W::Error> { + ) -> Result<(), WriteReadError> { // first get the current set value of the MPR121_ECR register let ecr_reg = Register::Ecr as u8; let mut ecr_backup = 0; @@ -77,45 +91,54 @@ impl Mpr121 { self.address, &[Register::Ecr as u8], core::slice::from_mut(&mut ecr_backup), - )?; + ) + .map_err(I2cError::WriteRead)?; // MPR121 must be put in Stop Mode to write to most registers - let stop_required = !((register == Register::Ecr) || ((0x73 <= reg) && (reg <= 0x7A))); + let stop_required = + !((register == Register::Ecr as u8) || ((0x73 <= register) && (register <= 0x7A))); if stop_required { // clear this register to set stop mode - bus.write(self.address, &[Register::Ecr as u8, 0x00])?; + bus.write(self.address, &[Register::Ecr as u8, 0x00]) + .map_err(I2cError::Write)?; } - bus.write(self.address, &[register, data])?; + bus.write(self.address, &[register, data]) + .map_err(I2cError::Write)?; if stop_required { // write back the previous set ECR settings - bus.write(self.address, &[Register::Ecr as u8, ecr_backup])?; + bus.write(self.address, &[Register::Ecr as u8, ecr_backup]) + .map_err(I2cError::Write)?; } Ok(()) } - fn read( + fn read( &self, bus: &mut W, register: Register, buffer: &mut [u8], - ) -> Result<(), W::Error> { + ) -> Result<(), WriteReadError> { self.read_raw(bus, register as u8, buffer) } - fn read_raw( + fn read_raw( &self, bus: &mut W, register: u8, buffer: &mut [u8], - ) -> Result<(), W::Error> { + ) -> Result<(), WriteReadError> { bus.write_read(self.address, &[register], buffer) + .map_err(I2cError::WriteRead) } - pub fn write_thresholds(&self, bus: &mut W) -> Result<(), W::Error> { + pub fn write_thresholds( + &self, + bus: &mut W, + ) -> Result<(), WriteReadError> { for (i, Mpr121Thresholds { touch, release }) in self.thresholds.iter().enumerate() { let touch_register = Register::TouchTh0 as usize + i * 2; let release_register = Register::ReleaseTh0 as usize + i * 2; @@ -127,11 +150,11 @@ impl Mpr121 { Ok(()) } - pub fn update(&self, bus: &mut W) -> Result<(), W::Error> { + pub fn update(&mut self, bus: &mut W) -> Result<(), WriteReadError> { let mut buffer = [0u8; 2]; self.read(bus, Register::TouchStatusL, &mut buffer)?; - self.states = TouchStates(u16::from_le_bytes(&buffer)); + self.states = TouchStates(u16::from_le_bytes(buffer)); let u10_scale = 1.0 / (1 << 10) as f32; for i in 0..self.analog_states.len() { @@ -139,7 +162,7 @@ impl Mpr121 { self.read_raw(bus, register as u8, &mut buffer)?; // the data returned is a 10-bit unsigned value - self.analog_states[i] = u16::from_le_bytes(&buffer) as f32 * u10_scale; + self.analog_states[i] = u16::from_le_bytes(buffer) as f32 * u10_scale; } Ok(()) @@ -149,7 +172,7 @@ impl Mpr121 { self.states } - pub fn analog_state(&self, channel: u8) -> [f32; 12] { + pub fn analog_states(&self) -> [f32; 12] { self.analog_states } } @@ -161,6 +184,10 @@ impl TouchStates { pub fn is_touched(&self, channel: u8) -> bool { ((self.0 >> channel) & 1) > 0 } + + pub fn raw(&self) -> u16 { + self.0 + } } #[derive(Clone, Copy)] From e6c59db24748caec7af86f56b8920a61be45bf90 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 13 Feb 2025 20:25:30 -0700 Subject: [PATCH 23/52] Implemented simple shift register driver --- src/delay.rs | 17 ++++- src/devices/encoder.rs | 0 src/devices/mod.rs | 1 + src/devices/mpr121.rs | 12 ++-- src/devices/shift_register.rs | 130 ++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 src/devices/encoder.rs diff --git a/src/delay.rs b/src/delay.rs index 220d4af..c3be752 100644 --- a/src/delay.rs +++ b/src/delay.rs @@ -31,19 +31,32 @@ impl CycleDelay { pub fn new() -> Self { CycleDelay } + + /// Block for `cycles` processor cycles. + pub fn delay_cycles(&mut self, cycles: u32) { + delay_cycles(cycles); + } + + /// Block for approximately `cycles` nanoseconds. + /// + /// This is a rough approximation with a resolution + /// of about four nanoseconds. + pub fn delay_ns(&mut self, ns: u32) { + delay_cycles(ns / 4); + } } macro_rules! impl_delay { ($ty:path) => { impl DelayMs<$ty> for CycleDelay { fn delay_ms(&mut self, ms: $ty) { - delay_cycles((ms as u32).saturating_mul(crate::MILICYCLES)); + self.delay_cycles((ms as u32).saturating_mul(crate::MILICYCLES)); } } impl DelayUs<$ty> for CycleDelay { fn delay_us(&mut self, us: $ty) { - delay_cycles((us as u32).saturating_mul(crate::MICROCYCLES)); + self.delay_cycles((us as u32).saturating_mul(crate::MICROCYCLES)); } } }; diff --git a/src/devices/encoder.rs b/src/devices/encoder.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 8b3af90..70cb30c 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,3 +1,4 @@ +pub mod encoder; pub mod mpr121; pub mod multiplexer; pub mod shift_register; diff --git a/src/devices/mpr121.rs b/src/devices/mpr121.rs index 321ac81..11b2e49 100644 --- a/src/devices/mpr121.rs +++ b/src/devices/mpr121.rs @@ -16,6 +16,7 @@ pub enum I2cError { WriteRead(WR), } +#[expect(type_alias_bounds)] pub type WriteReadError = I2cError<::Error, ::Error>; @@ -26,7 +27,7 @@ impl Mpr121 { release_threshold: u8, bus: &mut W, ) -> Result> { - let mut mpr = Mpr121 { + let mpr = Mpr121 { address, thresholds: [Mpr121Thresholds { touch: touch_threshold, @@ -89,18 +90,17 @@ impl Mpr121 { bus.write_read( self.address, - &[Register::Ecr as u8], + &[ecr_reg], core::slice::from_mut(&mut ecr_backup), ) .map_err(I2cError::WriteRead)?; // MPR121 must be put in Stop Mode to write to most registers - let stop_required = - !((register == Register::Ecr as u8) || ((0x73 <= register) && (register <= 0x7A))); + let stop_required = !((register == ecr_reg) || (0x73..=0x7A).contains(®ister)); if stop_required { // clear this register to set stop mode - bus.write(self.address, &[Register::Ecr as u8, 0x00]) + bus.write(self.address, &[ecr_reg, 0x00]) .map_err(I2cError::Write)?; } @@ -109,7 +109,7 @@ impl Mpr121 { if stop_required { // write back the previous set ECR settings - bus.write(self.address, &[Register::Ecr as u8, ecr_backup]) + bus.write(self.address, &[ecr_reg, ecr_backup]) .map_err(I2cError::Write)?; } diff --git a/src/devices/shift_register.rs b/src/devices/shift_register.rs index e69de29..ab82cff 100644 --- a/src/devices/shift_register.rs +++ b/src/devices/shift_register.rs @@ -0,0 +1,130 @@ +use embedded_hal::digital::v2::{InputPin, OutputPin}; + +/// A trait for reading and updating shift registers. +/// +/// This is implemented for [`ShiftRegister4021`] for up to +/// 8 parallel connections. +pub trait ShiftRegister { + /// Update the shift register object, reading each bit sequentially + /// in a blocking fashion. + fn update(&mut self); + + /// Read `input` from the given `parallel` chain. + /// + /// # Panics + /// + /// Panics if `inptut` or `parallel` are out of range. + fn read_parallel(&self, input: usize, parallel: usize) -> bool; + + /// Read `input` from the first parallel group. + /// + /// # Panics + /// + /// Panics if `inptut` is out of range. + fn read(&self, input: usize) -> bool { + self.read_parallel(input, 0) + } +} + +/// A 4021 shift register meta-struct. +/// +/// This struct permits configurations with both series and parallel +/// configurations. Note that `PARALLEL` will need to match the +/// number of data pins for the update implementation to work. +pub struct ShiftRegister4021 { + clock: C, + latch: L, + data: D, + delay: u32, + bits: [[u8; SERIES]; PARALLEL], +} + +impl + ShiftRegister4021 +{ + /// Construct a new [`ShiftRegister4021`]. + /// + /// `clock` and `latch` are expected to take single pins, + /// while `data` expects a tuple of up to 8 pins. + pub fn new(clock: C, latch: L, data: D) -> Self { + Self { + clock, + latch, + data, + delay: 250, + bits: [[0u8; SERIES]; PARALLEL], + } + } + + /// Set the delay time between clock edges in nanoseconds. + pub fn set_delay(&mut self, delay_ns: u32) { + self.delay = delay_ns; + } + + fn parallel(&self) -> usize { + PARALLEL + } +} + +macro_rules! impl_sr { + ($count:literal, $($ty:ident),*) => { + impl ShiftRegister for ShiftRegister4021 + where + T: OutputPin, + U: OutputPin, + ::Error: core::fmt::Debug, + ::Error: core::fmt::Debug, + $($ty: InputPin,)* + $(<$ty as InputPin>::Error: core::fmt::Debug,)* + { + #[allow(non_snake_case)] + fn update(&mut self) { + let mut delay = crate::delay::CycleDelay::new(); + + self.clock.set_low().unwrap(); + self.latch.set_high().unwrap(); + + delay.delay_ns(self.delay); + + self.latch.set_low().unwrap(); + + let total = self.bits[0].len(); + let ($($ty,)*) = &self.data; + for series in 0..total { + for bit in 0..8 { + self.clock.set_low().unwrap(); + delay.delay_ns(self.delay); + + let new_data: [u8; $count] = [ + $($ty.is_high().unwrap() as u8),* + ]; + + for (parallel, data) in new_data.into_iter().enumerate() { + self.bits[parallel][series] &= !(1 << bit); + self.bits[parallel][series] |= data << bit; + } + + self.clock.set_high().unwrap(); + delay.delay_ns(self.delay); + } + } + } + + fn read_parallel(&self, bit: usize, parallel: usize) -> bool { + let byte_address = bit / 8; + let bit_address = bit % 8; + + ((self.bits[parallel][byte_address] >> bit_address) & 1) > 0 + } + } + }; +} + +impl_sr!(1, A); +impl_sr!(2, A, B); +impl_sr!(3, A, B, C); +impl_sr!(4, A, B, C, D); +impl_sr!(5, A, B, C, D, E); +impl_sr!(6, A, B, C, D, E, F); +impl_sr!(7, A, B, C, D, E, F, G); +impl_sr!(8, A, B, C, D, E, F, G, H); From 9fbb8591ac064b79f506011e776627b48b1897ca Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Mon, 17 Feb 2025 13:50:00 -0700 Subject: [PATCH 24/52] Introduced simple encoder driver --- src/devices/encoder.rs | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/devices/encoder.rs b/src/devices/encoder.rs index e69de29..a557720 100644 --- a/src/devices/encoder.rs +++ b/src/devices/encoder.rs @@ -0,0 +1,103 @@ +use embedded_hal::digital::v2::InputPin; + +/// A quadrature encoder driver. +/// +/// This struct reads the quadrature input via polling. +pub struct Encoder { + a: A, + b: B, + decoder: QuadratureDecoder, +} + +impl Encoder +where + A: InputPin, + B: InputPin, + ::Error: core::fmt::Debug, + ::Error: core::fmt::Debug, +{ + pub const fn new(a: A, b: B) -> Self { + Self { + a, + b, + decoder: QuadratureDecoder::new(), + } + } + + /// Update the encoder. + /// + /// `milliseconds` should be a monotonically increasing count. + /// This allows the debouncing implementation to limit updates + /// to the desired rate. + pub fn update(&mut self, milliseconds: u32) { + self.decoder.update( + self.a.is_high().unwrap(), + self.b.is_high().unwrap(), + milliseconds, + ); + } + + /// Get the magnitude of the last encoder turn, if any. + pub fn increment(&self) -> Option { + self.decoder.increment() + } +} + +pub struct QuadratureDecoder { + last_update: u32, + a: u8, + b: u8, + increment: i32, +} + +impl Default for QuadratureDecoder { + fn default() -> Self { + Self::new() + } +} + +/// A quadrature encoder decoder. +/// +/// This is useful when your quadrature encoder data +/// lines are not connected directly to Daisy pins. +impl QuadratureDecoder { + pub const fn new() -> Self { + Self { + last_update: 0, + a: 0, + b: 0, + increment: 0, + } + } + + /// Update the decoder with the current state of `a` and `b`. + /// + /// `milliseconds` should be a monotonically increasing count. + /// This allows the debouncing implementation to limit updates + /// to the desired rate. + pub fn update(&mut self, a: bool, b: bool, milliseconds: u32) { + self.increment = 0; + + if milliseconds.saturating_sub(self.last_update) >= 1 { + self.last_update = milliseconds; + + self.a = (self.a << 1) | a as u8; + self.b = (self.b << 1) | b as u8; + + if (self.a & 0x03) == 0x02 && (self.b & 0x03) == 0x00 { + self.increment = 1; + } else if (self.b & 0x03) == 0x02 && (self.a & 0x03) == 0x00 { + self.increment = -1; + } + } + } + + /// Get the magnitude of the last encoder turn, if any. + pub fn increment(&self) -> Option { + match self.increment { + 1 => Some(1), + -1 => Some(-1), + _ => None, + } + } +} From 1ab8d814571181c2e03116179ff7fce93eda7534 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Wed, 19 Feb 2025 12:45:05 -0700 Subject: [PATCH 25/52] Improved semihosting configurability --- Cargo.toml | 12 ++++++------ src/logger.rs | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 245a911..be2a12d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,15 +44,15 @@ embedded-hal = "0.2" [features] default = [] -log-itm = ["panic-itm", "lazy_static", "cortex-m-log"] +log-itm = ["dep:panic-itm", "dep:lazy_static", "dep:cortex-m-log"] log-none = [] -log-rtt = ["rtt-target", "panic-rtt-target"] +log-rtt = ["dep:rtt-target", "dep:panic-rtt-target"] log-semihosting = [ - "panic-semihosting", - "lazy_static", - "cortex-m-log", - "cortex-m-semihosting", + "dep:lazy_static", + "dep:cortex-m-log", + "dep:cortex-m-semihosting", ] +panic-semihosting = ["dep:panic-semihosting"] # this lets you use `cargo fix`! #[[bin]] diff --git a/src/logger.rs b/src/logger.rs index 90cf392..eef9896 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -62,6 +62,7 @@ cfg_if::cfg_if! { } } else if #[cfg(any(feature = "log-semihosting"))] { + #[cfg(feature = "panic-semihosting")] use panic_semihosting as _; use lazy_static::lazy_static; From 8470911b5350821866c80b9687459e7e56d6e411 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Tue, 4 Mar 2025 13:57:56 -0700 Subject: [PATCH 26/52] Improved version detection --- src/devices/encoder.rs | 6 +++--- src/devices/shift_register.rs | 2 +- src/system.rs | 22 +++++++++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/devices/encoder.rs b/src/devices/encoder.rs index a557720..2667c26 100644 --- a/src/devices/encoder.rs +++ b/src/devices/encoder.rs @@ -43,6 +43,7 @@ where } } +#[derive(Debug, Clone)] pub struct QuadratureDecoder { last_update: u32, a: u8, @@ -95,9 +96,8 @@ impl QuadratureDecoder { /// Get the magnitude of the last encoder turn, if any. pub fn increment(&self) -> Option { match self.increment { - 1 => Some(1), - -1 => Some(-1), - _ => None, + 0 => None, + i => Some(i), } } } diff --git a/src/devices/shift_register.rs b/src/devices/shift_register.rs index ab82cff..d8f4980 100644 --- a/src/devices/shift_register.rs +++ b/src/devices/shift_register.rs @@ -51,7 +51,7 @@ impl clock, latch, data, - delay: 250, + delay: 1000, bits: [[0u8; SERIES]; PARALLEL], } } diff --git a/src/system.rs b/src/system.rs index 57f073e..0c1f571 100644 --- a/src/system.rs +++ b/src/system.rs @@ -103,7 +103,7 @@ impl MinimalSystem { .freeze(vos, syscfg) } - pub fn new(resources: SystemResources) -> Self { + pub fn new(mut resources: SystemResources) -> Self { let delay = Delay::new(resources.syst, *resources.clocks); let gpioa = resources.gpioa.split(resources.gpioa_rec); @@ -356,20 +356,24 @@ impl System { fn detect_version( s2dfm_pin: hal::gpio::gpiod::PD4, seed1_1_pin: hal::gpio::gpiod::PD3, + _seed3_pin: hal::gpio::gpioh::PH6, ) -> Version { - let s2dfm_pin = s2dfm_pin.into_pull_up_input(); let seed1_1_pin = seed1_1_pin.into_pull_up_input(); + let s2dfm_pin = s2dfm_pin.into_pull_up_input(); - let state = (seed1_1_pin.is_low(), s2dfm_pin.is_low()); + let seed1_1 = seed1_1_pin.is_low(); + let s2dfm = s2dfm_pin.is_low(); // Deinitialize the pins after reading s2dfm_pin.into_analog(); seed1_1_pin.into_analog(); - match state { - (true, _) => Version::Seed1_1, - (false, true) => Version::Seed2DFM, - _ => Version::Seed, + if seed1_1 { + Version::Seed1_1 + } else if s2dfm { + Version::Seed2DFM + } else { + Version::Seed } } @@ -422,7 +426,7 @@ impl System { } /// Batteries included initialization - pub fn init(resources: SystemResources) -> System { + pub fn init(mut resources: SystemResources) -> System { info!("Starting system init"); info!("Set up up DMA RAM in DRAM2..."); crate::mpu::init_dma( @@ -529,7 +533,7 @@ impl System { let dma1_streams = dma::dma::StreamsTuple::new(resources.dma1, resources.dma1_rec); info!("Set up Audio..."); - let version = Self::detect_version(gpiod.pd4, gpiod.pd3); + let version = Self::detect_version(gpiod.pd4, gpiod.pd3, gpioh.ph6); let audio = Audio::new( dma1_streams.0, From 3b6c0f691af9e746949345a5aba605972b4ba863 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 13 Mar 2025 13:09:22 -0600 Subject: [PATCH 27/52] Added special backup region --- memory.x | 11 ++++++++++- src/system.rs | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/memory.x b/memory.x index f33333d..a5439a1 100644 --- a/memory.x +++ b/memory.x @@ -7,7 +7,8 @@ MEMORY SRAM (RWX) : ORIGIN = 0x24000000, LENGTH = 512K RAM_D2 (RWX) : ORIGIN = 0x30000000, LENGTH = 288K RAM_D3 (RWX) : ORIGIN = 0x38000000, LENGTH = 64K - BACKUP_SRAM (RWX) : ORIGIN = 0x38800000, LENGTH = 4K + BACKUP_SRAM (RWX) : ORIGIN = 0x38800000, LENGTH = 2K + BACKUP_SRAM2 (RWX) : ORIGIN = 0x38800800, LENGTH = 2K ITCMRAM (RWX) : ORIGIN = 0x00000000, LENGTH = 64K SDRAM (RWX) : ORIGIN = 0xc0000000, LENGTH = 64M QSPIFLASH (RX): ORIGIN = 0x90000000, LENGTH = 8M @@ -54,4 +55,12 @@ SECTIONS *(.boot_info*) . = ALIGN(4); } > BACKUP_SRAM + + .crash_info (NOLOAD) : + { + . = ALIGN(4); + *(.boot_info) + *(.boot_info*) + . = ALIGN(4); + } > BACKUP_SRAM2 } diff --git a/src/system.rs b/src/system.rs index 0c1f571..4e04435 100644 --- a/src/system.rs +++ b/src/system.rs @@ -103,7 +103,7 @@ impl MinimalSystem { .freeze(vos, syscfg) } - pub fn new(mut resources: SystemResources) -> Self { + pub fn new(resources: SystemResources) -> Self { let delay = Delay::new(resources.syst, *resources.clocks); let gpioa = resources.gpioa.split(resources.gpioa_rec); @@ -426,7 +426,7 @@ impl System { } /// Batteries included initialization - pub fn init(mut resources: SystemResources) -> System { + pub fn init(resources: SystemResources) -> System { info!("Starting system init"); info!("Set up up DMA RAM in DRAM2..."); crate::mpu::init_dma( From bb83fe1af412bede3b00b87ebb36c85c1d0329c3 Mon Sep 17 00:00:00 2001 From: CorvusPrudens Date: Thu, 13 Mar 2025 20:05:06 -0600 Subject: [PATCH 28/52] Added additional boot capabilities --- memory.x | 8 ++++---- src/bootloader.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/memory.x b/memory.x index a5439a1..cb330d6 100644 --- a/memory.x +++ b/memory.x @@ -7,8 +7,8 @@ MEMORY SRAM (RWX) : ORIGIN = 0x24000000, LENGTH = 512K RAM_D2 (RWX) : ORIGIN = 0x30000000, LENGTH = 288K RAM_D3 (RWX) : ORIGIN = 0x38000000, LENGTH = 64K - BACKUP_SRAM (RWX) : ORIGIN = 0x38800000, LENGTH = 2K - BACKUP_SRAM2 (RWX) : ORIGIN = 0x38800800, LENGTH = 2K + BACKUP_SRAM (RWX) : ORIGIN = 0x38800000, LENGTH = 32 + BACKUP_SRAM2 (RWX) : ORIGIN = 0x38800020, LENGTH = 2016 ITCMRAM (RWX) : ORIGIN = 0x00000000, LENGTH = 64K SDRAM (RWX) : ORIGIN = 0xc0000000, LENGTH = 64M QSPIFLASH (RX): ORIGIN = 0x90000000, LENGTH = 8M @@ -59,8 +59,8 @@ SECTIONS .crash_info (NOLOAD) : { . = ALIGN(4); - *(.boot_info) - *(.boot_info*) + *(.crash_info) + *(.crash_info*) . = ALIGN(4); } > BACKUP_SRAM2 } diff --git a/src/bootloader.rs b/src/bootloader.rs index 32f0428..37798f3 100644 --- a/src/bootloader.rs +++ b/src/bootloader.rs @@ -286,6 +286,18 @@ pub enum BootType { /// the program is running from internal flash, meaning /// no Daisy bootloader can be present. pub fn reset_to_bootloader(boot_type: BootType) -> ! { + reset_to_bootloader_with(boot_type, || ()) +} + +/// Reset to the provided bootloader, running the provided +/// closure after data caching is disabled. +/// +/// # Panics +/// +/// Panics if the Daisy bootloader is selected but +/// the program is running from internal flash, meaning +/// no Daisy bootloader can be present. +pub fn reset_to_bootloader_with(boot_type: BootType, f: F) -> ! { // # SAFETY // // SCB and CPUID are zero-sized types. They are passed around @@ -303,6 +315,11 @@ pub fn reset_to_bootloader(boot_type: BootType) -> ! { let mut cpuid = unsafe { core::mem::transmute::<(), CPUID>(()) }; scb.disable_dcache(&mut cpuid); + cortex_m::asm::dsb(); + cortex_m::asm::isb(); + + f(); + match boot_type { BootType::Stm(boot_pin) => { let mut pin = boot_pin.into_push_pull_output(); From 1053e931037113928fbb98da8f5a3abb9aa98e39 Mon Sep 17 00:00:00 2001 From: green-ogre <109698029+void-scape@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:44:46 +0200 Subject: [PATCH 29/52] Add function to expose a block-by-block buffer write --- src/audio.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/audio.rs b/src/audio.rs index 6ea93b0..7eebc88 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -94,7 +94,7 @@ pub enum AudioStream { type StereoIteratorHandle = fn(StereoIterator, &mut Output); #[derive(Debug, Copy, Clone, PartialEq)] -struct S24(i32); +pub struct S24(i32); impl From for S24 { fn from(x: i32) -> S24 { @@ -534,6 +534,45 @@ impl Audio { Ok(()) } + /// Process audio block-by-block. + #[inline] + pub fn for_each_block(&mut self, mut process: F) + where + F: FnMut(&[u32], &mut [S24]), + { + self.try_for_each_block::<_, Infallible>(|input, output| Ok(process(input, output))) + .unwrap() + } + + /// Process audio block-by-block. + /// + /// If the process closure returns an error, + /// it's bubbled up to the callsite of this method. + pub fn try_for_each_block(&mut self, mut process: F) -> Result<(), E> + where + F: FnMut(&[u32], &mut [S24]) -> Result<(), E>, + { + if self.read() { + process( + &self.input.buffer[self.input.index..self.input.index + self.max_transfer_size], + // # Safety + // + // S24 and u32 are both represented with 32 bits. + // + // Anything written to this output should be properly clamped using the S24 + // wrapper. + unsafe { + core::mem::transmute( + &mut self.output.buffer + [self.output.index..self.output.index + self.max_transfer_size], + ) + }, + )?; + } + + Ok(()) + } + pub fn passthrough(&mut self) { if self.read() { let input = self.input.buffer From 41c2e06f1e54aa1f48a40f38a48134014e8a331c Mon Sep 17 00:00:00 2001 From: green-ogre <109698029+void-scape@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:04:37 +0200 Subject: [PATCH 30/52] Renamed functions --- src/audio.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 7eebc88..b0d54a7 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -534,21 +534,21 @@ impl Audio { Ok(()) } - /// Process audio block-by-block. + /// Process audio block. #[inline] - pub fn for_each_block(&mut self, mut process: F) + pub fn process_block(&mut self, mut process: F) where F: FnMut(&[u32], &mut [S24]), { - self.try_for_each_block::<_, Infallible>(|input, output| Ok(process(input, output))) + self.try_process_block::<_, Infallible>(|input, output| Ok(process(input, output))) .unwrap() } - /// Process audio block-by-block. + /// Process audio audio block. /// /// If the process closure returns an error, /// it's bubbled up to the callsite of this method. - pub fn try_for_each_block(&mut self, mut process: F) -> Result<(), E> + pub fn try_process_block(&mut self, mut process: F) -> Result<(), E> where F: FnMut(&[u32], &mut [S24]) -> Result<(), E>, { @@ -562,7 +562,7 @@ impl Audio { // Anything written to this output should be properly clamped using the S24 // wrapper. unsafe { - core::mem::transmute( + core::mem::transmute::<&mut [u32], &mut [S24]>( &mut self.output.buffer [self.output.index..self.output.index + self.max_transfer_size], ) From a9f544e5cee465badee69edd456d1e8c4e46b09c Mon Sep 17 00:00:00 2001 From: green-ogre <109698029+void-scape@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:05:41 +0200 Subject: [PATCH 31/52] Added PatchSmSystem. Renamed System to SeedSystem and friends. --- examples/blinky.rs | 4 +- examples/button.rs | 4 +- examples/delay.rs | 4 +- examples/flash.rs | 4 +- examples/hid_blinky.rs | 4 +- examples/knob.rs | 4 +- examples/passthru.rs | 4 +- examples/sdram.rs | 4 +- examples/sine.rs | 4 +- examples/switch.rs | 4 +- examples/toggle.rs | 4 +- examples/volume.rs | 4 +- src/audio.rs | 10 +- src/gpio.rs | 123 ++++++++- src/system/mod.rs | 173 ++++++++++++ src/system/patch_sm.rs | 443 ++++++++++++++++++++++++++++++ src/{system.rs => system/seed.rs} | 238 ++-------------- 17 files changed, 792 insertions(+), 243 deletions(-) create mode 100644 src/system/mod.rs create mode 100644 src/system/patch_sm.rs rename src/{system.rs => system/seed.rs} (67%) diff --git a/examples/blinky.rs b/examples/blinky.rs index 2a2d6d5..01c866b 100644 --- a/examples/blinky.rs +++ b/examples/blinky.rs @@ -26,8 +26,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( device.TIM2, diff --git a/examples/button.rs b/examples/button.rs index 74b9571..128376b 100644 --- a/examples/button.rs +++ b/examples/button.rs @@ -26,8 +26,8 @@ mod app { logger::init(); let mut core = ctx.core; let mut device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let mut system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_seed_system_init!(core, device, ccdr); info!("Startup done!"); // Button diff --git a/examples/delay.rs b/examples/delay.rs index 8d82a56..bb75a70 100644 --- a/examples/delay.rs +++ b/examples/delay.rs @@ -29,8 +29,8 @@ mod app { let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; info!("Startup done!"); diff --git a/examples/flash.rs b/examples/flash.rs index 04b1a8b..feaef54 100644 --- a/examples/flash.rs +++ b/examples/flash.rs @@ -28,8 +28,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( device.TIM2, diff --git a/examples/hid_blinky.rs b/examples/hid_blinky.rs index 1033b73..8fc2fd3 100644 --- a/examples/hid_blinky.rs +++ b/examples/hid_blinky.rs @@ -29,8 +29,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let mut system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( diff --git a/examples/knob.rs b/examples/knob.rs index e9fe62f..97f6186 100644 --- a/examples/knob.rs +++ b/examples/knob.rs @@ -30,8 +30,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let mut system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( diff --git a/examples/passthru.rs b/examples/passthru.rs index b77c16f..01b98e8 100644 --- a/examples/passthru.rs +++ b/examples/passthru.rs @@ -28,8 +28,8 @@ mod app { // moved out of the function in the previous implementation. let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let system = libdaisy::system_init!(core, device, ccdr, BLOCK_SIZE); + let ccdr = system::SeedSeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_seed_system_init!(core, device, ccdr, BLOCK_SIZE); info!("Startup done!!"); diff --git a/examples/sdram.rs b/examples/sdram.rs index 3419341..4bf3fd6 100644 --- a/examples/sdram.rs +++ b/examples/sdram.rs @@ -28,8 +28,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( device.TIM2, diff --git a/examples/sine.rs b/examples/sine.rs index 3c3a017..13d5871 100644 --- a/examples/sine.rs +++ b/examples/sine.rs @@ -36,8 +36,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); let buffer = [(0.0, 0.0); audio::BLOCK_SIZE_MAX]; diff --git a/examples/switch.rs b/examples/switch.rs index b9cd938..591784d 100644 --- a/examples/switch.rs +++ b/examples/switch.rs @@ -28,8 +28,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let mut system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( device.TIM2, diff --git a/examples/toggle.rs b/examples/toggle.rs index bd91aba..90e2aa7 100644 --- a/examples/toggle.rs +++ b/examples/toggle.rs @@ -28,8 +28,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let mut system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); info!("Startup done!"); let mut timer2 = stm32h7xx_hal::timer::TimerExt::timer( device.TIM2, diff --git a/examples/volume.rs b/examples/volume.rs index 992c2b5..57f2bab 100644 --- a/examples/volume.rs +++ b/examples/volume.rs @@ -32,8 +32,8 @@ mod app { logger::init(); let mut core = ctx.core; let device = ctx.device; - let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG); - let mut system = libdaisy::system_init!(core, device, ccdr); + let ccdr = system::SeedSystem::init_clocks(device.PWR, device.RCC, &device.SYSCFG); + let mut system = libdaisy::seed_system_init!(core, device, ccdr); info!("Enable adc1"); let mut adc1 = system.adc1.enable(); diff --git a/src/audio.rs b/src/audio.rs index b0d54a7..d9fd8c2 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -164,13 +164,13 @@ impl Audio { i2c_sda: gpiob::PB11, clocks: &rcc::CoreClocks, - board_version: crate::system::Version, + board_version: crate::system::seed::Version, delay: &mut impl DelayMs, block_size: usize, ) -> Self { match board_version { - crate::system::Version::Seed1_1 => { + crate::system::seed::Version::Seed1_1 => { let dma_buffer_size = block_size * 2 * 2; let rx_buffer: &'static mut [u32] = unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; @@ -293,7 +293,7 @@ impl Audio { max_transfer_size, } } - crate::system::Version::Seed2DFM | crate::system::Version::Seed => { + crate::system::seed::Version::Seed2DFM | crate::system::seed::Version::Seed => { let dma_buffer_size = block_size * 2 * 2; let rx_buffer: &'static mut [u32] = @@ -354,7 +354,7 @@ impl Audio { ); match board_version { - crate::system::Version::Seed => { + crate::system::seed::Version::Seed => { info!("Setting up AK4556/PCM3060 Audio CODEC..."); let mut ak_reset = i2c_sda .into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::High); @@ -363,7 +363,7 @@ impl Audio { delay.delay_ms(1); ak_reset.set_high(); } - crate::system::Version::Seed2DFM => { + crate::system::seed::Version::Seed2DFM => { // Set deemphasis low info!("Setting up PCM3060 Audio CODEC..."); i2c_sda.into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::Low); diff --git a/src/gpio.rs b/src/gpio.rs index 7b33a6b..08edb99 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -26,7 +26,7 @@ pub type SeedButton = PG3; /// GPIO struct for holding Daisy GPIO pins #[allow(clippy::upper_case_acronyms)] -pub struct GPIO { +pub struct SeedGPIO { pub led: SeedLed, pub button: SeedButton, pub daisy0: Option>, @@ -64,7 +64,7 @@ pub struct GPIO { pub daisy32: Option>, } -impl GPIO { +impl SeedGPIO { /// Initialize the GPIOs #[allow(clippy::too_many_arguments)] pub fn init( @@ -103,7 +103,7 @@ impl GPIO { daisy30: Option>, daisy31: Option>, daisy32: Option>, - ) -> GPIO { + ) -> SeedGPIO { let led = seed_led.into_push_pull_output(); let button = seed_button.into_floating_input(); @@ -146,3 +146,120 @@ impl GPIO { } } } + +pub type PatchSmLed = PC7>; +pub type PatchSmButton = PG3; + +/// GPIO struct for holding Daisy GPIO pins +#[allow(clippy::upper_case_acronyms)] +pub struct PatchSmGPIO { + pub led: Option, + pub button: PatchSmButton, + pub a2: Option>, + pub a3: Option>, + pub a8: Option>, + pub a9: Option>, + pub b5: Option>, + pub b6: Option>, + pub b7: Option>, + pub b8: Option>, + pub b9: Option>, + pub b10: Option>, + pub c1: Option>, + pub c2: Option>, + pub c3: Option>, + pub c4: Option>, + pub c5: Option>, + pub c6: Option>, + pub c7: Option>, + pub c8: Option>, + pub c9: Option>, + pub c10: Option>, + pub d1: Option>>, + pub d2: Option>, + pub d3: Option>, + pub d4: Option>, + pub d5: Option>, + pub d6: Option>, + pub d7: Option>, + pub d8: Option>, + pub d9: Option>, + pub d10: Option>, +} + +impl PatchSmGPIO { + /// Initialize the GPIOs + #[allow(clippy::too_many_arguments)] + pub fn init( + patch_sm_led: gpio::gpioc::PC7, + patch_sm_button: gpio::gpiog::PG3, + a2: Option>, + a3: Option>, + a8: Option>, + a9: Option>, + b5: Option>, + b6: Option>, + b7: Option>, + b8: Option>, + b9: Option>, + b10: Option>, + c1: Option>, + c2: Option>, + c3: Option>, + c4: Option>, + c5: Option>, + c6: Option>, + c7: Option>, + c8: Option>, + c9: Option>, + c10: Option>, + d1: Option>>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + d8: Option>, + d9: Option>, + d10: Option>, + ) -> PatchSmGPIO { + let led = patch_sm_led.into_push_pull_output(); + let button = patch_sm_button.into_floating_input(); + + Self { + led: Some(led), + button, + a2, + a3, + a8, + a9, + b5, + b6, + b7, + b8, + b9, + b10, + c1, + c2, + c3, + c4, + c5, + c6, + c7, + c8, + c9, + c10, + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + } + } +} diff --git a/src/system/mod.rs b/src/system/mod.rs new file mode 100644 index 0000000..9a6516b --- /dev/null +++ b/src/system/mod.rs @@ -0,0 +1,173 @@ +//! Contains setup for Daisy board hardware. +#![allow(dead_code)] + +use hal::rcc::CoreClocks; +use log::info; +use stm32h7xx_hal::{ + gpio, + prelude::*, + rcc, stm32, + time::{Hertz, MegaHertz}, +}; + +use crate::*; + +pub mod patch_sm; +pub mod seed; + +pub use patch_sm::{MinimalPatchSmSystem, PatchSmSystem}; +pub use seed::{MinimalSeedSystem, SeedSystem}; + +const START_OF_DRAM2: u32 = 0x30000000; +const DMA_MEM_SIZE: usize = 32 * 1024; + +const HSE_CLOCK_MHZ: MegaHertz = MegaHertz::from_raw(16); +const HCLK_MHZ: MegaHertz = MegaHertz::from_raw(200); +const HCLK2_MHZ: MegaHertz = MegaHertz::from_raw(200); + +// PCLKx +const PCLK_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 4); +// 49_152_344 +// PLL1 +const PLL1_P_HZ: Hertz = CLOCK_RATE_HZ; +const PLL1_Q_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 18); +const PLL1_R_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 32); +// PLL2 +const PLL2_P_HZ: Hertz = Hertz::from_raw(4_000_000); +const PLL2_Q_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 2); // No divder given, what's the default? +const PLL2_R_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 4); // No divder given, what's the default? + +const PLL3_P_HZ: Hertz = Hertz::from_raw(AUDIO_SAMPLE_HZ.raw() * 257); +const PLL3_Q_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); +const PLL3_R_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); + +fn init_clocks(mut pwr: stm32::PWR, mut rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { + // Power + initialize_backup_sram(&mut pwr, &mut rcc); + let pwr = pwr.constrain(); + let vos = pwr.vos0(syscfg).freeze(); + + rcc.constrain() + .use_hse(HSE_CLOCK_MHZ.convert()) + .sys_ck(CLOCK_RATE_HZ) + .pclk1(PCLK_HZ) // DMA clock + // PLL1 + .pll1_strategy(rcc::PllConfigStrategy::Iterative) + .pll1_p_ck(PLL1_P_HZ) + .pll1_q_ck(PLL1_Q_HZ) + .pll1_r_ck(PLL1_R_HZ) + // PLL2 + .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input + // .pll2_q_ck(PLL2_Q_HZ) + // .pll2_r_ck(PLL2_R_HZ) + // PLL3 + .pll3_strategy(rcc::PllConfigStrategy::Fractional) + .pll3_p_ck(PLL3_P_HZ) // used for SAI1 + .pll3_q_ck(PLL3_Q_HZ) + .pll3_r_ck(PLL3_R_HZ) + .freeze(vos, syscfg) +} + +pub fn initialize_backup_sram(pwr: &mut stm32::PWR, rcc: &mut stm32::RCC) { + pwr.cr1.modify(|_, w| w.dbp().set_bit()); + pwr.cr2.modify(|_, w| w.bren().set_bit()); + + loop { + if pwr.cr1.read().dbp().bit_is_set() { + break; + } + } + + rcc.ahb4enr.modify(|_, w| w.bkpramen().set_bit()); + // read it back + let _bit = rcc.ahb4enr.read().bkpramen().bit(); +} + +pub struct InternalUsbPins { + pub dp: gpio::gpioa::PA12, + pub dm: gpio::gpioa::PA11, +} + +/// All peripherals and other resources required for the system +pub struct SystemResources<'a> { + pub clocks: &'a CoreClocks, + pub adc1: stm32::ADC1, + pub adc2: stm32::ADC2, + pub adc12_rec: rcc::rec::Adc12, + pub dac: stm32::DAC, + pub dac12_rec: rcc::rec::Dac12, + pub syst: stm32::SYST, + pub mpu: &'a mut stm32::MPU, + pub scb: &'a mut stm32::SCB, + pub dcb: &'a mut stm32::DCB, + pub dwt: &'a mut stm32::DWT, + pub fmc: stm32::FMC, + pub fmc_rec: rcc::rec::Fmc, + pub i2c2: stm32::I2C2, + pub i2c2_rec: rcc::rec::I2c2, + pub cpuid: &'a mut cortex_m::peripheral::CPUID, + pub qspi: stm32::QUADSPI, + pub qspi_rec: rcc::rec::Qspi, + + pub sai1: stm32::SAI1, + pub sai1_rec: rcc::rec::Sai1, + + pub gpioa: stm32::GPIOA, + pub gpioa_rec: rcc::rec::Gpioa, + + pub gpiob: stm32::GPIOB, + pub gpiob_rec: rcc::rec::Gpiob, + + pub gpioc: stm32::GPIOC, + pub gpioc_rec: rcc::rec::Gpioc, + + pub gpiod: stm32::GPIOD, + pub gpiod_rec: rcc::rec::Gpiod, + + pub gpioe: stm32::GPIOE, + pub gpioe_rec: rcc::rec::Gpioe, + + pub gpiof: stm32::GPIOF, + pub gpiof_rec: rcc::rec::Gpiof, + + pub gpiog: stm32::GPIOG, + pub gpiog_rec: rcc::rec::Gpiog, + + pub gpioh: stm32::GPIOH, + pub gpioh_rec: rcc::rec::Gpioh, + + pub gpioi: stm32::GPIOI, + pub gpioi_rec: rcc::rec::Gpioi, + + pub dma1: stm32::DMA1, + pub dma1_rec: rcc::rec::Dma1, + + pub block_size: usize, +} + +fn log_clocks(ccdr: &stm32h7xx_hal::rcc::Ccdr) { + info!("Core {}", ccdr.clocks.c_ck()); + info!("hclk {}", ccdr.clocks.hclk()); + info!("pclk1 {}", ccdr.clocks.pclk1()); + info!("pclk2 {}", ccdr.clocks.pclk2()); + info!("pclk3 {}", ccdr.clocks.pclk2()); + info!("pclk4 {}", ccdr.clocks.pclk4()); + info!( + "PLL1\nP: {:?}\nQ: {:?}\nR: {:?}", + ccdr.clocks.pll1_p_ck(), + ccdr.clocks.pll1_q_ck(), + ccdr.clocks.pll1_r_ck() + ); + info!( + "PLL2\nP: {:?}\nQ: {:?}\nR: {:?}", + ccdr.clocks.pll2_p_ck(), + ccdr.clocks.pll2_q_ck(), + ccdr.clocks.pll2_r_ck() + ); + info!( + "PLL3\nP: {:?}\nQ: {:?}\nR: {:?}", + ccdr.clocks.pll3_p_ck(), + ccdr.clocks.pll3_q_ck(), + ccdr.clocks.pll3_r_ck() + ); +} diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs new file mode 100644 index 0000000..3f424e8 --- /dev/null +++ b/src/system/patch_sm.rs @@ -0,0 +1,443 @@ +use log::info; +use stm32h7xx_hal::{adc, dac, delay::Delay, dma, prelude::*, rcc, stm32}; + +use crate::{audio::Audio, *}; + +use super::*; + +pub struct PatchSmSystem { + pub gpio: crate::gpio::PatchSmGPIO, + pub audio: audio::Audio, + pub adc1: adc::Adc, + pub adc2: adc::Adc, + pub c1: Option>, + pub c2: Option>, + pub sdram: &'static mut [f32], + pub flash: crate::flash::Flash, + pub internal_usb: Option, + pub delay: Delay, +} + +pub struct MinimalPatchSmSystem { + pub gpio: crate::gpio::PatchSmGPIO, + pub flash: crate::flash::Flash, + pub internal_usb: Option, + pub delay: Delay, +} + +impl MinimalPatchSmSystem { + /// Initialize clocks + pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg) + } + + pub fn new(resources: SystemResources) -> Self { + let delay = Delay::new(resources.syst, *resources.clocks); + + let gpioa = resources.gpioa.split(resources.gpioa_rec); + let gpiob = resources.gpiob.split(resources.gpiob_rec); + let gpioc = resources.gpioc.split(resources.gpioc_rec); + let gpiod = resources.gpiod.split(resources.gpiod_rec); + let gpiof = resources.gpiof.split(resources.gpiof_rec); + let gpiog = resources.gpiog.split(resources.gpiog_rec); + + // Set up GPIOs + let gpio = crate::gpio::PatchSmGPIO::init( + gpioc.pc7, + gpiog.pg3, + Some(gpioa.pa1), + Some(gpioa.pa0), + Some(gpiob.pb14), + Some(gpiob.pb15), + Some(gpioc.pc14), + Some(gpioc.pc13), + Some(gpiob.pb8), + Some(gpiob.pb9), + Some(gpiog.pg14), + Some(gpiog.pg13), + None, + Some(gpioa.pa7), + Some(gpioa.pa2), + Some(gpioa.pa6), + Some(gpioa.pa3), + Some(gpiob.pb1), + Some(gpioc.pc4), + Some(gpioc.pc0), + Some(gpioc.pc1), + None, + Some(gpiob.pb4), + Some(gpioc.pc11), + Some(gpioc.pc10), + Some(gpioc.pc9), + Some(gpioc.pc8), + Some(gpioc.pc12), + Some(gpiod.pd2), + Some(gpioc.pc2), + Some(gpioc.pc3), + Some(gpiod.pd3), + ); + + PatchSmSystem::init_debug(resources.dcb, resources.dwt); + + // set up flash + let flash = crate::flash::Flash::new( + resources.qspi, + resources.qspi_rec, + resources.clocks, + gpiof.pf6, + gpiof.pf7, + gpiof.pf8, + gpiof.pf9, + gpiof.pf10, + gpiog.pg6, + ); + + info!("Minimal system initialization complete."); + + // grab internal usb pins + let internal_usb = InternalUsbPins { + dp: gpioa.pa12, + dm: gpioa.pa11, + }; + + Self { + gpio, + flash, + internal_usb: Some(internal_usb), + delay, + } + } +} + +#[macro_export] +macro_rules! patch_sm_system_init { + ($core:ident, $device:ident, $ccdr:ident) => { + libdaisy::system_init!($core, $device, $ccdr, libdaisy::audio::BLOCK_SIZE_MAX); + }; + ($core:ident, $device:ident, $ccdr:ident, $block_size:expr) => {{ + let resources = libdaisy::system::SystemResources { + clocks: &$ccdr.clocks, + adc1: $device.ADC1, + adc2: $device.ADC2, + adc12_rec: $ccdr.peripheral.ADC12, + dac: $device.DAC, + dac12_rec: $ccdr.peripheral.DAC12, + syst: $core.SYST, + mpu: &mut $core.MPU, + scb: &mut $core.SCB, + dcb: &mut $core.DCB, + dwt: &mut $core.DWT, + fmc: $device.FMC, + fmc_rec: $ccdr.peripheral.FMC, + i2c2: $device.I2C2, + i2c2_rec: $ccdr.peripheral.I2C2, + cpuid: &mut $core.CPUID, + qspi: $device.QUADSPI, + qspi_rec: $ccdr.peripheral.QSPI, + sai1: $device.SAI1, + sai1_rec: $ccdr.peripheral.SAI1, + gpioa: $device.GPIOA, + gpioa_rec: $ccdr.peripheral.GPIOA, + gpiob: $device.GPIOB, + gpiob_rec: $ccdr.peripheral.GPIOB, + gpioc: $device.GPIOC, + gpioc_rec: $ccdr.peripheral.GPIOC, + gpiod: $device.GPIOD, + gpiod_rec: $ccdr.peripheral.GPIOD, + gpioe: $device.GPIOE, + gpioe_rec: $ccdr.peripheral.GPIOE, + gpiof: $device.GPIOF, + gpiof_rec: $ccdr.peripheral.GPIOF, + gpiog: $device.GPIOG, + gpiog_rec: $ccdr.peripheral.GPIOG, + gpioh: $device.GPIOH, + gpioh_rec: $ccdr.peripheral.GPIOH, + gpioi: $device.GPIOI, + gpioi_rec: $ccdr.peripheral.GPIOI, + dma1: $device.DMA1, + dma1_rec: $ccdr.peripheral.DMA1, + block_size: $block_size, + }; + + libdaisy::system::PatchSmSystem::init(resources) + }}; +} + +#[macro_export] +macro_rules! patch_sm_minimal_init { + ($core:ident, $device:ident, $ccdr:ident) => {{ + let resources = ::libdaisy::system::SystemResources { + clocks: &$ccdr.clocks, + adc1: $device.ADC1, + adc2: $device.ADC2, + adc12_rec: $ccdr.peripheral.ADC12, + dac: $device.DAC, + dac12_rec: $ccdr.peripheral.DAC12, + syst: $core.SYST, + mpu: &mut $core.MPU, + scb: &mut $core.SCB, + dcb: &mut $core.DCB, + dwt: &mut $core.DWT, + fmc: $device.FMC, + fmc_rec: $ccdr.peripheral.FMC, + i2c2: $device.I2C2, + i2c2_rec: $ccdr.peripheral.I2C2, + cpuid: &mut $core.CPUID, + qspi: $device.QUADSPI, + qspi_rec: $ccdr.peripheral.QSPI, + sai1: $device.SAI1, + sai1_rec: $ccdr.peripheral.SAI1, + gpioa: $device.GPIOA, + gpioa_rec: $ccdr.peripheral.GPIOA, + gpiob: $device.GPIOB, + gpiob_rec: $ccdr.peripheral.GPIOB, + gpioc: $device.GPIOC, + gpioc_rec: $ccdr.peripheral.GPIOC, + gpiod: $device.GPIOD, + gpiod_rec: $ccdr.peripheral.GPIOD, + gpioe: $device.GPIOE, + gpioe_rec: $ccdr.peripheral.GPIOE, + gpiof: $device.GPIOF, + gpiof_rec: $ccdr.peripheral.GPIOF, + gpiog: $device.GPIOG, + gpiog_rec: $ccdr.peripheral.GPIOG, + gpioh: $device.GPIOH, + gpioh_rec: $ccdr.peripheral.GPIOH, + gpioi: $device.GPIOI, + gpioi_rec: $ccdr.peripheral.GPIOI, + dma1: $device.DMA1, + dma1_rec: $ccdr.peripheral.DMA1, + block_size: 0, + }; + + ::libdaisy::system::MinimalPatchSmSystem::new(resources) + }}; +} + +impl PatchSmSystem { + /// Initialize clocks + pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg) + } + + /// Set up cache + pub fn init_cache( + scb: &mut cortex_m::peripheral::SCB, + cpuid: &mut cortex_m::peripheral::CPUID, + ) { + scb.enable_icache(); + scb.enable_dcache(cpuid); + } + + /// Enable debug + pub fn init_debug(dcb: &mut cortex_m::peripheral::DCB, dwt: &mut cortex_m::peripheral::DWT) { + dcb.enable_trace(); + cortex_m::peripheral::DWT::unlock(); + dwt.enable_cycle_counter(); + } + + /// Batteries included initialization + pub fn init(resources: SystemResources) -> PatchSmSystem { + info!("Starting system init"); + info!("Set up up DMA RAM in DRAM2..."); + crate::mpu::init_dma( + resources.mpu, + resources.scb, + START_OF_DRAM2 as *mut u32, + DMA_MEM_SIZE, + ); + + let mut delay = Delay::new(resources.syst, *resources.clocks); + + // Set up ADCs + let (adc1, adc2) = adc::adc12( + resources.adc1, + resources.adc2, + 4.MHz(), + &mut delay, + resources.adc12_rec, + resources.clocks, + ); + + Self::init_debug(resources.dcb, resources.dwt); + + info!("Setting up GPIOs..."); + let gpioa = resources.gpioa.split(resources.gpioa_rec); + let gpiob = resources.gpiob.split(resources.gpiob_rec); + let gpioc = resources.gpioc.split(resources.gpioc_rec); + let gpiod = resources.gpiod.split(resources.gpiod_rec); + let gpioe = resources.gpioe.split(resources.gpioe_rec); + let gpiof = resources.gpiof.split(resources.gpiof_rec); + let gpiog = resources.gpiog.split(resources.gpiog_rec); + let gpioh = resources.gpioh.split(resources.gpioh_rec); + let gpioi = resources.gpioi.split(resources.gpioi_rec); + + // Configure SDRAM + info!("Setting up SDRAM..."); + let sdram = crate::sdram::Sdram::new( + resources.fmc, + resources.fmc_rec, + resources.clocks, + &mut delay, + resources.scb, + resources.mpu, + gpiod.pd0, + gpiod.pd1, + gpiod.pd8, + gpiod.pd9, + gpiod.pd10, + gpiod.pd14, + gpiod.pd15, + gpioe.pe0, + gpioe.pe1, + gpioe.pe7, + gpioe.pe8, + gpioe.pe9, + gpioe.pe10, + gpioe.pe11, + gpioe.pe12, + gpioe.pe13, + gpioe.pe14, + gpioe.pe15, + gpiof.pf0, + gpiof.pf1, + gpiof.pf2, + gpiof.pf3, + gpiof.pf4, + gpiof.pf5, + gpiof.pf11, + gpiof.pf12, + gpiof.pf13, + gpiof.pf14, + gpiof.pf15, + gpiog.pg0, + gpiog.pg1, + gpiog.pg2, + gpiog.pg4, + gpiog.pg5, + gpiog.pg8, + gpiog.pg15, + gpioh.ph2, + gpioh.ph3, + gpioh.ph5, + gpioh.ph8, + gpioh.ph9, + gpioh.ph10, + gpioh.ph11, + gpioh.ph12, + gpioh.ph13, + gpioh.ph14, + gpioh.ph15, + gpioi.pi0, + gpioi.pi1, + gpioi.pi2, + gpioi.pi3, + gpioi.pi4, + gpioi.pi5, + gpioi.pi6, + gpioi.pi7, + gpioi.pi9, + gpioi.pi10, + ) + .into(); + + let dma1_streams = dma::dma::StreamsTuple::new(resources.dma1, resources.dma1_rec); + + info!("Set up Audio..."); + let audio = Audio::new( + dma1_streams.0, + dma1_streams.1, + resources.sai1, + resources.sai1_rec, + resources.i2c2, + resources.i2c2_rec, + gpioe.pe2, + gpioe.pe3, + gpioe.pe4, + gpioe.pe5, + gpioe.pe6, + gpioh.ph4, + gpiob.pb11, + resources.clocks, + seed::Version::Seed1_1, + &mut delay, + resources.block_size, + ); + + // Set up GPIOs + let gpio = crate::gpio::PatchSmGPIO::init( + gpioc.pc7, + gpiog.pg3, + Some(gpioa.pa1), + Some(gpioa.pa0), + Some(gpiob.pb14), + Some(gpiob.pb15), + Some(gpioc.pc14), + Some(gpioc.pc13), + Some(gpiob.pb8), + Some(gpiob.pb9), + Some(gpiog.pg14), + Some(gpiog.pg13), + None, + Some(gpioa.pa7), + Some(gpioa.pa2), + Some(gpioa.pa6), + Some(gpioa.pa3), + Some(gpiob.pb1), + Some(gpioc.pc4), + Some(gpioc.pc0), + Some(gpioc.pc1), + None, + Some(gpiob.pb4), + Some(gpioc.pc11), + Some(gpioc.pc10), + Some(gpioc.pc9), + Some(gpioc.pc8), + Some(gpioc.pc12), + Some(gpiod.pd2), + Some(gpioc.pc2), + Some(gpioc.pc3), + Some(gpiod.pd3), + ); + + // Set up cache + Self::init_cache(resources.scb, resources.cpuid); + + info!("Set up DAC..."); + let (c1, c2) = dac::dac(resources.dac, (gpioa.pa4, gpioa.pa5), resources.dac12_rec); + + info!("Patch Submodule system init done!"); + + // set up flash + let flash = crate::flash::Flash::new( + resources.qspi, + resources.qspi_rec, + resources.clocks, + gpiof.pf6, + gpiof.pf7, + gpiof.pf8, + gpiof.pf9, + gpiof.pf10, + gpiog.pg6, + ); + + // grab internal usb pins + let internal_usb = InternalUsbPins { + dp: gpioa.pa12, + dm: gpioa.pa11, + }; + + PatchSmSystem { + gpio, + audio, + adc1, + adc2, + c1: Some(c1), + c2: Some(c2), + sdram, + flash, + internal_usb: Some(internal_usb), + delay, + } + } +} diff --git a/src/system.rs b/src/system/seed.rs similarity index 67% rename from src/system.rs rename to src/system/seed.rs index 4e04435..5637dea 100644 --- a/src/system.rs +++ b/src/system/seed.rs @@ -1,44 +1,12 @@ -//! Contains setup for Daisy board hardware. -#![allow(dead_code)] - -use hal::rcc::CoreClocks; use log::info; -use stm32h7xx_hal::{ - adc, - delay::Delay, - dma, gpio, - prelude::*, - rcc, stm32, - time::{Hertz, MegaHertz}, -}; +use stm32h7xx_hal::{adc, delay::Delay, dma, prelude::*, rcc, stm32}; use crate::{audio::Audio, *}; -const START_OF_DRAM2: u32 = 0x30000000; -const DMA_MEM_SIZE: usize = 32 * 1024; - -const HSE_CLOCK_MHZ: MegaHertz = MegaHertz::from_raw(16); -const HCLK_MHZ: MegaHertz = MegaHertz::from_raw(200); -const HCLK2_MHZ: MegaHertz = MegaHertz::from_raw(200); - -// PCLKx -const PCLK_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 4); -// 49_152_344 -// PLL1 -const PLL1_P_HZ: Hertz = CLOCK_RATE_HZ; -const PLL1_Q_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 18); -const PLL1_R_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 32); -// PLL2 -const PLL2_P_HZ: Hertz = Hertz::from_raw(4_000_000); -const PLL2_Q_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 2); // No divder given, what's the default? -const PLL2_R_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 4); // No divder given, what's the default? - -const PLL3_P_HZ: Hertz = Hertz::from_raw(AUDIO_SAMPLE_HZ.raw() * 257); -const PLL3_Q_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); -const PLL3_R_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); - -pub struct System { - pub gpio: crate::gpio::GPIO, +use super::*; + +pub struct SeedSystem { + pub gpio: crate::gpio::SeedGPIO, pub audio: audio::Audio, pub adc1: adc::Adc, pub adc2: adc::Adc, @@ -48,59 +16,17 @@ pub struct System { pub delay: Delay, } -pub struct MinimalSystem { - pub gpio: crate::gpio::GPIO, +pub struct MinimalSeedSystem { + pub gpio: crate::gpio::SeedGPIO, pub flash: crate::flash::Flash, pub internal_usb: Option, pub delay: Delay, } -pub fn initialize_backup_sram(pwr: &mut stm32::PWR, rcc: &mut stm32::RCC) { - pwr.cr1.modify(|_, w| w.dbp().set_bit()); - pwr.cr2.modify(|_, w| w.bren().set_bit()); - - loop { - if pwr.cr1.read().dbp().bit_is_set() { - break; - } - } - - rcc.ahb4enr.modify(|_, w| w.bkpramen().set_bit()); - // read it back - let _bit = rcc.ahb4enr.read().bkpramen().bit(); -} - -impl MinimalSystem { +impl MinimalSeedSystem { /// Initialize clocks - pub fn init_clocks( - mut pwr: stm32::PWR, - mut rcc: stm32::RCC, - syscfg: &stm32::SYSCFG, - ) -> rcc::Ccdr { - // Power - initialize_backup_sram(&mut pwr, &mut rcc); - let pwr = pwr.constrain(); - let vos = pwr.vos0(syscfg).freeze(); - - rcc.constrain() - .use_hse(HSE_CLOCK_MHZ.convert()) - .sys_ck(CLOCK_RATE_HZ) - .pclk1(PCLK_HZ) // DMA clock - // PLL1 - .pll1_strategy(rcc::PllConfigStrategy::Iterative) - .pll1_p_ck(PLL1_P_HZ) - .pll1_q_ck(PLL1_Q_HZ) - .pll1_r_ck(PLL1_R_HZ) - // PLL2 - .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input - // .pll2_q_ck(PLL2_Q_HZ) - // .pll2_r_ck(PLL2_R_HZ) - // PLL3 - .pll3_strategy(rcc::PllConfigStrategy::Fractional) - .pll3_p_ck(PLL3_P_HZ) // used for SAI1 - .pll3_q_ck(PLL3_Q_HZ) - .pll3_r_ck(PLL3_R_HZ) - .freeze(vos, syscfg) + pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg) } pub fn new(resources: SystemResources) -> Self { @@ -114,7 +40,7 @@ impl MinimalSystem { let gpiog = resources.gpiog.split(resources.gpiog_rec); // Set up GPIOs - let gpio = crate::gpio::GPIO::init( + let gpio = crate::gpio::SeedGPIO::init( gpioc.pc7, gpiog.pg3, Some(gpiob.pb12), @@ -152,7 +78,7 @@ impl MinimalSystem { None, ); - System::init_debug(resources.dcb, resources.dwt); + SeedSystem::init_debug(resources.dcb, resources.dwt); // set up flash let flash = crate::flash::Flash::new( @@ -184,68 +110,8 @@ impl MinimalSystem { } } -pub struct InternalUsbPins { - pub dp: gpio::gpioa::PA12, - pub dm: gpio::gpioa::PA11, -} - -/// All peripherals and other resources required for the system -pub struct SystemResources<'a> { - pub clocks: &'a CoreClocks, - pub adc1: stm32::ADC1, - pub adc2: stm32::ADC2, - pub adc12_rec: rcc::rec::Adc12, - pub syst: stm32::SYST, - pub mpu: &'a mut stm32::MPU, - pub scb: &'a mut stm32::SCB, - pub dcb: &'a mut stm32::DCB, - pub dwt: &'a mut stm32::DWT, - pub fmc: stm32::FMC, - pub fmc_rec: rcc::rec::Fmc, - pub i2c2: stm32::I2C2, - pub i2c2_rec: rcc::rec::I2c2, - pub cpuid: &'a mut cortex_m::peripheral::CPUID, - pub qspi: stm32::QUADSPI, - pub qspi_rec: rcc::rec::Qspi, - - pub sai1: stm32::SAI1, - pub sai1_rec: rcc::rec::Sai1, - - pub gpioa: stm32::GPIOA, - pub gpioa_rec: rcc::rec::Gpioa, - - pub gpiob: stm32::GPIOB, - pub gpiob_rec: rcc::rec::Gpiob, - - pub gpioc: stm32::GPIOC, - pub gpioc_rec: rcc::rec::Gpioc, - - pub gpiod: stm32::GPIOD, - pub gpiod_rec: rcc::rec::Gpiod, - - pub gpioe: stm32::GPIOE, - pub gpioe_rec: rcc::rec::Gpioe, - - pub gpiof: stm32::GPIOF, - pub gpiof_rec: rcc::rec::Gpiof, - - pub gpiog: stm32::GPIOG, - pub gpiog_rec: rcc::rec::Gpiog, - - pub gpioh: stm32::GPIOH, - pub gpioh_rec: rcc::rec::Gpioh, - - pub gpioi: stm32::GPIOI, - pub gpioi_rec: rcc::rec::Gpioi, - - pub dma1: stm32::DMA1, - pub dma1_rec: rcc::rec::Dma1, - - pub block_size: usize, -} - #[macro_export] -macro_rules! system_init { +macro_rules! seed_system_init { ($core:ident, $device:ident, $ccdr:ident) => { libdaisy::system_init!($core, $device, $ccdr, libdaisy::audio::BLOCK_SIZE_MAX); }; @@ -255,6 +121,8 @@ macro_rules! system_init { adc1: $device.ADC1, adc2: $device.ADC2, adc12_rec: $ccdr.peripheral.ADC12, + dac: $device.DAC, + dac12_rec: $ccdr.peripheral.DAC12, syst: $core.SYST, mpu: &mut $core.MPU, scb: &mut $core.SCB, @@ -292,18 +160,20 @@ macro_rules! system_init { block_size: $block_size, }; - libdaisy::system::System::init(resources) + libdaisy::system::SeedSystem::init(resources) }}; } #[macro_export] -macro_rules! minimal_init { +macro_rules! seed_minimal_init { ($core:ident, $device:ident, $ccdr:ident) => {{ let resources = ::libdaisy::system::SystemResources { clocks: &$ccdr.clocks, adc1: $device.ADC1, adc2: $device.ADC2, adc12_rec: $ccdr.peripheral.ADC12, + dac: $device.DAC, + dac12_rec: $ccdr.peripheral.DAC12, syst: $core.SYST, mpu: &mut $core.MPU, scb: &mut $core.SCB, @@ -341,7 +211,7 @@ macro_rules! minimal_init { block_size: 0, }; - ::libdaisy::system::MinimalSystem::new(resources) + ::libdaisy::system::MinimalSeedSystem::new(resources) }}; } @@ -352,7 +222,7 @@ pub enum Version { Seed2DFM, } -impl System { +impl SeedSystem { fn detect_version( s2dfm_pin: hal::gpio::gpiod::PD4, seed1_1_pin: hal::gpio::gpiod::PD3, @@ -378,35 +248,8 @@ impl System { } /// Initialize clocks - pub fn init_clocks( - mut pwr: stm32::PWR, - mut rcc: stm32::RCC, - syscfg: &stm32::SYSCFG, - ) -> rcc::Ccdr { - // Power - initialize_backup_sram(&mut pwr, &mut rcc); - let pwr = pwr.constrain(); - let vos = pwr.vos0(syscfg).freeze(); - - rcc.constrain() - .use_hse(HSE_CLOCK_MHZ.convert()) - .sys_ck(CLOCK_RATE_HZ) - .pclk1(PCLK_HZ) // DMA clock - // PLL1 - .pll1_strategy(rcc::PllConfigStrategy::Iterative) - .pll1_p_ck(PLL1_P_HZ) - .pll1_q_ck(PLL1_Q_HZ) - .pll1_r_ck(PLL1_R_HZ) - // PLL2 - .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input - // .pll2_q_ck(PLL2_Q_HZ) - // .pll2_r_ck(PLL2_R_HZ) - // PLL3 - .pll3_strategy(rcc::PllConfigStrategy::Fractional) - .pll3_p_ck(PLL3_P_HZ) // used for SAI1 - .pll3_q_ck(PLL3_Q_HZ) - .pll3_r_ck(PLL3_R_HZ) - .freeze(vos, syscfg) + pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg) } /// Set up cache @@ -426,7 +269,7 @@ impl System { } /// Batteries included initialization - pub fn init(resources: SystemResources) -> System { + pub fn init(resources: SystemResources) -> SeedSystem { info!("Starting system init"); info!("Set up up DMA RAM in DRAM2..."); crate::mpu::init_dma( @@ -561,7 +404,7 @@ impl System { }; // Set up GPIOs - let gpio = crate::gpio::GPIO::init( + let gpio = crate::gpio::SeedGPIO::init( gpioc.pc7, gpiog.pg3, Some(gpiob.pb12), @@ -602,7 +445,7 @@ impl System { // Set up cache Self::init_cache(resources.scb, resources.cpuid); - info!("System init done!"); + info!("Seed system init done!"); // set up flash let flash = crate::flash::Flash::new( @@ -623,7 +466,7 @@ impl System { dm: gpioa.pa11, }; - System { + SeedSystem { gpio, audio, adc1, @@ -635,30 +478,3 @@ impl System { } } } - -fn log_clocks(ccdr: &stm32h7xx_hal::rcc::Ccdr) { - info!("Core {}", ccdr.clocks.c_ck()); - info!("hclk {}", ccdr.clocks.hclk()); - info!("pclk1 {}", ccdr.clocks.pclk1()); - info!("pclk2 {}", ccdr.clocks.pclk2()); - info!("pclk3 {}", ccdr.clocks.pclk2()); - info!("pclk4 {}", ccdr.clocks.pclk4()); - info!( - "PLL1\nP: {:?}\nQ: {:?}\nR: {:?}", - ccdr.clocks.pll1_p_ck(), - ccdr.clocks.pll1_q_ck(), - ccdr.clocks.pll1_r_ck() - ); - info!( - "PLL2\nP: {:?}\nQ: {:?}\nR: {:?}", - ccdr.clocks.pll2_p_ck(), - ccdr.clocks.pll2_q_ck(), - ccdr.clocks.pll2_r_ck() - ); - info!( - "PLL3\nP: {:?}\nQ: {:?}\nR: {:?}", - ccdr.clocks.pll3_p_ck(), - ccdr.clocks.pll3_q_ck(), - ccdr.clocks.pll3_r_ck() - ); -} From 84261660cacd1fb1457d7ebe8104345969ff81b8 Mon Sep 17 00:00:00 2001 From: green-ogre <109698029+void-scape@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:20:11 +0200 Subject: [PATCH 32/52] Added special audio initialization for the PatchSm --- src/audio.rs | 181 ++++++++++++++++++++++++++++++++++++++++- src/system/mod.rs | 5 ++ src/system/patch_sm.rs | 3 +- src/system/seed.rs | 2 +- 4 files changed, 186 insertions(+), 5 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index d9fd8c2..4547ab4 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -142,9 +142,9 @@ pub struct Audio { } impl Audio { - /// Setup audio handler + /// Setup audio handler for the Seed. #[allow(clippy::too_many_arguments)] - pub fn new( + pub fn seed( dma1_stream0: dma::dma::StreamX, dma1_stream1: dma::dma::StreamX, sai1_d: stm32::SAI1, @@ -415,6 +415,183 @@ impl Audio { } } + /// Initializes audio for the Daisy Patch Submodule. + #[allow(clippy::too_many_arguments)] + pub fn patch_sm( + dma1_stream0: dma::dma::StreamX, + dma1_stream1: dma::dma::StreamX, + sai1_d: stm32::SAI1, + sai1_p: rcc::rec::Sai1, + i2c2_d: stm32::I2C2, + i2c2_p: rcc::rec::I2c2, + + sai_mclk_a: gpioe::PE2, + sai_sd_b: gpioe::PE3, + sai_fs_a: gpioe::PE4, + sai_sck_a: gpioe::PE5, + sai_sd_a: gpioe::PE6, + + i2c_scl: gpiob::PB10, + i2c_sda: gpiob::PB11, + + clocks: &rcc::CoreClocks, + delay: &mut impl DelayMs, + block_size: usize, + ) -> Self { + let dma_buffer_size = block_size * 2 * 2; + let rx_buffer: &'static mut [u32] = + unsafe { &mut RX_BUFFER.as_mut_slice()[..dma_buffer_size] }; + let dma_config = dma::dma::DmaConfig::default() + .priority(dma::config::Priority::High) + .memory_increment(true) + .peripheral_increment(false) + .circular_buffer(true) + .fifo_enable(false); + + let mut output_stream = dma::Transfer::init( + dma1_stream0, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_a() }, + rx_buffer, + None, + dma_config, + ); + + let tx_buffer: &'static mut [u32] = + unsafe { &mut TX_BUFFER.as_mut_slice()[..dma_buffer_size] }; + let dma_config = dma_config + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true); + + let mut input_stream = dma::Transfer::init( + dma1_stream1, + unsafe { pac::Peripherals::steal().SAI1.dma_ch_b() }, + tx_buffer, + None, + dma_config, + ); + + info!("Set up SAI..."); + let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); + let master_config = + sai::I2SChanConfig::new(sai::I2SDir::Rx).set_frame_sync_active_high(false); + let slave_config = sai::I2SChanConfig::new(sai::I2SDir::Tx) + .set_sync_type(sai::I2SSync::Internal) + .set_frame_sync_active_high(false); + + let pins_a = ( + sai_mclk_a.into_alternate(), + sai_sck_a.into_alternate(), + sai_fs_a.into_alternate(), + sai_sd_a.into_alternate(), + Some(sai_sd_b.into_alternate()), + ); + + let mut sai = sai1_d.i2s_ch_a( + pins_a, + crate::AUDIO_SAMPLE_HZ, + sai::I2SDataSize::BITS_24, + sai1_rec, + clocks, + sai::I2sUsers::new(master_config).add_slave(slave_config), + ); + + info!("Setting up PCM3060 Audio Codec..."); + let i2c2_pins = ( + i2c_scl.into_alternate_open_drain(), + i2c_sda.into_alternate_open_drain(), + ); + + let mut i2c = i2c2_d.i2c(i2c2_pins, 100.kHz(), i2c2_p, clocks); + pcm3060_init(&mut i2c, delay); + + const DEV_ADDR: u8 = 0x46; + + const REG_SYS_CTRL: u8 = 0x40; + const REG_DAC_CTRL1: u8 = 0x43; + const REG_ADC_CTRL1: u8 = 0x48; + + const MASK_MRST: u8 = 0x80; + const MASK_SRST: u8 = 0x40; + const MASK_ADC_PSV: u8 = 0x20; + const MASK_DAC_PSV: u8 = 0x10; + const MASK_FMT: u8 = 0x01; + + // handle the error? + fn read_reg(reg_addr: u8, i2c: &mut I2c) -> u8 { + let mut buffer = [0u8; 1]; + i2c.write_read(DEV_ADDR, &[reg_addr], &mut buffer).unwrap(); + buffer[0] + } + + // handle the error? + fn write_reg(reg_addr: u8, val: u8, i2c: &mut I2c) { + i2c.write(DEV_ADDR, &[reg_addr, val]).unwrap(); + } + + // POWER-ON RESET and EXTERNAL RESET Sequence + // Default Format spec for now: I2S 24-bit MSB aligned (FMT1/2[1:0] = 01) + + // Slave address: 0b100011Nx + // N = 0 (GND on hardware) + // x = R/W (not passed to address param) + fn pcm3060_init(i2c: &mut I2c, delay: &mut impl DelayMs) { + // MSRT + let mut sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + sys_ctrl &= !MASK_MRST; + write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; + delay.delay_ms(4); + + // SRST + sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + sys_ctrl &= !MASK_SRST; + write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; + delay.delay_ms(4); + + // ADC/DAC Format set to 24-bit LJ + let mut dac_ctrl = read_reg(REG_DAC_CTRL1, i2c)?; + let mut adc_ctrl = read_reg(REG_ADC_CTRL1, i2c)?; + dac_ctrl |= MASK_FMT; + adc_ctrl |= MASK_FMT; + write_reg(REG_DAC_CTRL1, dac_ctrl, i2c)?; + write_reg(REG_ADC_CTRL1, adc_ctrl, i2c)?; + + // Disable Powersave for ADC/DAC + sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + sys_ctrl &= !(MASK_ADC_PSV | MASK_DAC_PSV); + write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; + } + + info!("Start audio stream..."); + input_stream.start(|_sai1_rb| sai.enable_dma(sai::SaiChannel::ChannelA)); + output_stream.start(|sai1_rb| { + sai.enable_dma(sai::SaiChannel::ChannelB); + while sai1_rb.chb().sr.read().flvl().is_empty() {} + sai.enable(); + sai.try_send(0, 0).unwrap(); + }); + + let max_transfer_size = block_size * 2; + let input = Input::new( + unsafe { &*core::ptr::addr_of!(RX_BUFFER) }, + max_transfer_size, + ); + let output = Output::new( + unsafe { &mut *core::ptr::addr_of_mut!(TX_BUFFER) }, + max_transfer_size, + ); + + Audio { + sai, + audio_stream: AudioStream::Normal { + input: input_stream, + output: output_stream, + }, + input, + output, + max_transfer_size, + } + } + /// Check interrupts and set indexes for I/O fn read(&mut self) -> bool { // Check interrupt(s) diff --git a/src/system/mod.rs b/src/system/mod.rs index 9a6516b..c1c8582 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -18,6 +18,11 @@ pub mod seed; pub use patch_sm::{MinimalPatchSmSystem, PatchSmSystem}; pub use seed::{MinimalSeedSystem, SeedSystem}; +pub enum Board { + Seed(seed::Version), + PatchSm, +} + const START_OF_DRAM2: u32 = 0x30000000; const DMA_MEM_SIZE: usize = 32 * 1024; diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs index 3f424e8..3c343be 100644 --- a/src/system/patch_sm.rs +++ b/src/system/patch_sm.rs @@ -344,7 +344,7 @@ impl PatchSmSystem { let dma1_streams = dma::dma::StreamsTuple::new(resources.dma1, resources.dma1_rec); info!("Set up Audio..."); - let audio = Audio::new( + let audio = Audio::patch_sm( dma1_streams.0, dma1_streams.1, resources.sai1, @@ -359,7 +359,6 @@ impl PatchSmSystem { gpioh.ph4, gpiob.pb11, resources.clocks, - seed::Version::Seed1_1, &mut delay, resources.block_size, ); diff --git a/src/system/seed.rs b/src/system/seed.rs index 5637dea..7b2720c 100644 --- a/src/system/seed.rs +++ b/src/system/seed.rs @@ -378,7 +378,7 @@ impl SeedSystem { info!("Set up Audio..."); let version = Self::detect_version(gpiod.pd4, gpiod.pd3, gpioh.ph6); - let audio = Audio::new( + let audio = Audio::seed( dma1_streams.0, dma1_streams.1, resources.sai1, From 89549aa4690e2950d049f23e38b4bfb321d1a79c Mon Sep 17 00:00:00 2001 From: green-ogre <109698029+void-scape@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:23:24 +0200 Subject: [PATCH 33/52] Fixed compiler errors --- src/audio.rs | 26 ++++++++++++++------------ src/system/patch_sm.rs | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 4547ab4..f3a64d9 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -14,7 +14,9 @@ use stm32h7xx_hal::{ gpio::{gpiob, gpioe, gpioh, Analog}, hal::blocking::delay::DelayMs, i2c::*, - pac, rcc, + pac, + prelude::*, + rcc, rcc::rec, sai, sai::*, @@ -501,7 +503,7 @@ impl Audio { i2c_sda.into_alternate_open_drain(), ); - let mut i2c = i2c2_d.i2c(i2c2_pins, 100.kHz(), i2c2_p, clocks); + let mut i2c = i2c2_d.i2c(i2c2_pins, Hertz::from_raw(100_000), i2c2_p, clocks); pcm3060_init(&mut i2c, delay); const DEV_ADDR: u8 = 0x46; @@ -536,29 +538,29 @@ impl Audio { // x = R/W (not passed to address param) fn pcm3060_init(i2c: &mut I2c, delay: &mut impl DelayMs) { // MSRT - let mut sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + let mut sys_ctrl = read_reg(REG_SYS_CTRL, i2c); sys_ctrl &= !MASK_MRST; - write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; + write_reg(REG_SYS_CTRL, sys_ctrl, i2c); delay.delay_ms(4); // SRST - sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + sys_ctrl = read_reg(REG_SYS_CTRL, i2c); sys_ctrl &= !MASK_SRST; - write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; + write_reg(REG_SYS_CTRL, sys_ctrl, i2c); delay.delay_ms(4); // ADC/DAC Format set to 24-bit LJ - let mut dac_ctrl = read_reg(REG_DAC_CTRL1, i2c)?; - let mut adc_ctrl = read_reg(REG_ADC_CTRL1, i2c)?; + let mut dac_ctrl = read_reg(REG_DAC_CTRL1, i2c); + let mut adc_ctrl = read_reg(REG_ADC_CTRL1, i2c); dac_ctrl |= MASK_FMT; adc_ctrl |= MASK_FMT; - write_reg(REG_DAC_CTRL1, dac_ctrl, i2c)?; - write_reg(REG_ADC_CTRL1, adc_ctrl, i2c)?; + write_reg(REG_DAC_CTRL1, dac_ctrl, i2c); + write_reg(REG_ADC_CTRL1, adc_ctrl, i2c); // Disable Powersave for ADC/DAC - sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; + sys_ctrl = read_reg(REG_SYS_CTRL, i2c); sys_ctrl &= !(MASK_ADC_PSV | MASK_DAC_PSV); - write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; + write_reg(REG_SYS_CTRL, sys_ctrl, i2c); } info!("Start audio stream..."); diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs index 3c343be..d0b00e1 100644 --- a/src/system/patch_sm.rs +++ b/src/system/patch_sm.rs @@ -356,7 +356,7 @@ impl PatchSmSystem { gpioe.pe4, gpioe.pe5, gpioe.pe6, - gpioh.ph4, + gpiob.pb10, gpiob.pb11, resources.clocks, &mut delay, From 86808664cc53b04d67ac49401151c13d25e11990 Mon Sep 17 00:00:00 2001 From: Corvus Date: Mon, 15 Sep 2025 17:25:47 +0100 Subject: [PATCH 34/52] Removed hard-coded sample rates --- src/lib.rs | 4 ---- src/system/mod.rs | 15 ++++++++++----- src/system/patch_sm.rs | 22 ++++++++++++++++------ src/system/seed.rs | 20 +++++++++++++++----- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1c1556f..8ac8edd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,6 @@ use stm32h7xx_hal::time::Hertz; pub const MILLI: u32 = 1_000; pub const MICRO: u32 = 1_000_000; -pub const AUDIO_FRAME_RATE_HZ: u32 = 1_000; -pub const AUDIO_BLOCK_SIZE: u16 = 48; -pub const AUDIO_SAMPLE_RATE: usize = 48_000; -pub const AUDIO_SAMPLE_HZ: Hertz = Hertz::from_raw(48_000); pub const CLOCK_RATE_HZ: Hertz = Hertz::from_raw(480_000_000_u32); pub const MILICYCLES: u32 = CLOCK_RATE_HZ.raw() / MILLI; diff --git a/src/system/mod.rs b/src/system/mod.rs index c1c8582..4c80624 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -42,16 +42,21 @@ const PLL2_P_HZ: Hertz = Hertz::from_raw(4_000_000); const PLL2_Q_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 2); // No divder given, what's the default? const PLL2_R_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 4); // No divder given, what's the default? -const PLL3_P_HZ: Hertz = Hertz::from_raw(AUDIO_SAMPLE_HZ.raw() * 257); -const PLL3_Q_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); -const PLL3_R_HZ: Hertz = Hertz::from_raw(PLL3_P_HZ.raw()); - -fn init_clocks(mut pwr: stm32::PWR, mut rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { +fn init_clocks( + mut pwr: stm32::PWR, + mut rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + audio_sample_rate: Hertz, +) -> rcc::Ccdr { // Power initialize_backup_sram(&mut pwr, &mut rcc); let pwr = pwr.constrain(); let vos = pwr.vos0(syscfg).freeze(); + let PLL3_P_HZ = Hertz::from_raw(audio_sample_rate.raw() * 257); + let PLL3_Q_HZ = Hertz::from_raw(PLL3_P_HZ.raw()); + let PLL3_R_HZ = Hertz::from_raw(PLL3_P_HZ.raw()); + rcc.constrain() .use_hse(HSE_CLOCK_MHZ.convert()) .sys_ck(CLOCK_RATE_HZ) diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs index d0b00e1..59affbb 100644 --- a/src/system/patch_sm.rs +++ b/src/system/patch_sm.rs @@ -1,5 +1,5 @@ use log::info; -use stm32h7xx_hal::{adc, dac, delay::Delay, dma, prelude::*, rcc, stm32}; +use stm32h7xx_hal::{adc, dac, delay::Delay, dma, prelude::*, rcc, stm32, time::Hertz}; use crate::{audio::Audio, *}; @@ -27,8 +27,13 @@ pub struct MinimalPatchSmSystem { impl MinimalPatchSmSystem { /// Initialize clocks - pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { - init_clocks(pwr, rcc, syscfg) + pub fn init_clocks( + pwr: stm32::PWR, + rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + audio_sample_rate: Hertz, + ) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg, audio_sample_rate) } pub fn new(resources: SystemResources) -> Self { @@ -112,7 +117,7 @@ impl MinimalPatchSmSystem { #[macro_export] macro_rules! patch_sm_system_init { ($core:ident, $device:ident, $ccdr:ident) => { - libdaisy::system_init!($core, $device, $ccdr, libdaisy::audio::BLOCK_SIZE_MAX); + libdaisy::system_init!($core, $device, $ccdr, libdaisy::audio::BLOCK_SIZE_MAX,); }; ($core:ident, $device:ident, $ccdr:ident, $block_size:expr) => {{ let resources = libdaisy::system::SystemResources { @@ -216,8 +221,13 @@ macro_rules! patch_sm_minimal_init { impl PatchSmSystem { /// Initialize clocks - pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { - init_clocks(pwr, rcc, syscfg) + pub fn init_clocks( + pwr: stm32::PWR, + rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + audio_sample_rate: Hertz, + ) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg, audio_sample_rate) } /// Set up cache diff --git a/src/system/seed.rs b/src/system/seed.rs index 7b2720c..6cb6d3f 100644 --- a/src/system/seed.rs +++ b/src/system/seed.rs @@ -1,5 +1,5 @@ use log::info; -use stm32h7xx_hal::{adc, delay::Delay, dma, prelude::*, rcc, stm32}; +use stm32h7xx_hal::{adc, delay::Delay, dma, prelude::*, rcc, stm32, time::Hertz}; use crate::{audio::Audio, *}; @@ -25,8 +25,13 @@ pub struct MinimalSeedSystem { impl MinimalSeedSystem { /// Initialize clocks - pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { - init_clocks(pwr, rcc, syscfg) + pub fn init_clocks( + pwr: stm32::PWR, + rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + sample_rate: Hertz, + ) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg, sample_rate) } pub fn new(resources: SystemResources) -> Self { @@ -248,8 +253,13 @@ impl SeedSystem { } /// Initialize clocks - pub fn init_clocks(pwr: stm32::PWR, rcc: stm32::RCC, syscfg: &stm32::SYSCFG) -> rcc::Ccdr { - init_clocks(pwr, rcc, syscfg) + pub fn init_clocks( + pwr: stm32::PWR, + rcc: stm32::RCC, + syscfg: &stm32::SYSCFG, + sample_rate: Hertz, + ) -> rcc::Ccdr { + init_clocks(pwr, rcc, syscfg, sample_rate) } /// Set up cache From 116820bcd9a3d28b051418bedde2c625d3b88152 Mon Sep 17 00:00:00 2001 From: Corvus Date: Mon, 15 Sep 2025 17:57:08 +0100 Subject: [PATCH 35/52] Provided sample rate input --- src/audio.rs | 8 +++++--- src/system/mod.rs | 1 + src/system/patch_sm.rs | 12 ++++++++++-- src/system/seed.rs | 12 ++++++++++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index f3a64d9..263baf7 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -170,6 +170,7 @@ impl Audio { delay: &mut impl DelayMs, block_size: usize, + sample_rate: Hertz, ) -> Self { match board_version { crate::system::seed::Version::Seed1_1 => { @@ -222,7 +223,7 @@ impl Audio { // Hand off to audio module let mut sai = sai1_d.i2s_ch_a( pins_a, - crate::AUDIO_SAMPLE_HZ, + sample_rate, I2SDataSize::BITS_24, sai1_rec, clocks, @@ -348,7 +349,7 @@ impl Audio { // Hand off to audio module let mut sai = sai1_d.i2s_ch_a( pins_a, - crate::AUDIO_SAMPLE_HZ, + sample_rate, I2SDataSize::BITS_24, sai1_rec, clocks, @@ -439,6 +440,7 @@ impl Audio { clocks: &rcc::CoreClocks, delay: &mut impl DelayMs, block_size: usize, + sample_rate: Hertz, ) -> Self { let dma_buffer_size = block_size * 2 * 2; let rx_buffer: &'static mut [u32] = @@ -490,7 +492,7 @@ impl Audio { let mut sai = sai1_d.i2s_ch_a( pins_a, - crate::AUDIO_SAMPLE_HZ, + sample_rate, sai::I2SDataSize::BITS_24, sai1_rec, clocks, diff --git a/src/system/mod.rs b/src/system/mod.rs index 4c80624..e84c415 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -153,6 +153,7 @@ pub struct SystemResources<'a> { pub dma1_rec: rcc::rec::Dma1, pub block_size: usize, + pub sample_rate: Hertz, } fn log_clocks(ccdr: &stm32h7xx_hal::rcc::Ccdr) { diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs index 59affbb..4ff35e0 100644 --- a/src/system/patch_sm.rs +++ b/src/system/patch_sm.rs @@ -117,9 +117,15 @@ impl MinimalPatchSmSystem { #[macro_export] macro_rules! patch_sm_system_init { ($core:ident, $device:ident, $ccdr:ident) => { - libdaisy::system_init!($core, $device, $ccdr, libdaisy::audio::BLOCK_SIZE_MAX,); + libdaisy::system_init!( + $core, + $device, + $ccdr, + libdaisy::audio::BLOCK_SIZE_MAX, + 48_000 + ); }; - ($core:ident, $device:ident, $ccdr:ident, $block_size:expr) => {{ + ($core:ident, $device:ident, $ccdr:ident, $block_size:expr, $sample_rate:expr) => {{ let resources = libdaisy::system::SystemResources { clocks: &$ccdr.clocks, adc1: $device.ADC1, @@ -162,6 +168,7 @@ macro_rules! patch_sm_system_init { dma1: $device.DMA1, dma1_rec: $ccdr.peripheral.DMA1, block_size: $block_size, + sample_rate: $sample_rate, }; libdaisy::system::PatchSmSystem::init(resources) @@ -371,6 +378,7 @@ impl PatchSmSystem { resources.clocks, &mut delay, resources.block_size, + resources.sample_rate, ); // Set up GPIOs diff --git a/src/system/seed.rs b/src/system/seed.rs index 6cb6d3f..f79f390 100644 --- a/src/system/seed.rs +++ b/src/system/seed.rs @@ -118,9 +118,15 @@ impl MinimalSeedSystem { #[macro_export] macro_rules! seed_system_init { ($core:ident, $device:ident, $ccdr:ident) => { - libdaisy::system_init!($core, $device, $ccdr, libdaisy::audio::BLOCK_SIZE_MAX); + libdaisy::system_init!( + $core, + $device, + $ccdr, + libdaisy::audio::BLOCK_SIZE_MAX, + 48_000, + ); }; - ($core:ident, $device:ident, $ccdr:ident, $block_size:expr) => {{ + ($core:ident, $device:ident, $ccdr:ident, $block_size:expr, $sample_rate:expr) => {{ let resources = libdaisy::system::SystemResources { clocks: &$ccdr.clocks, adc1: $device.ADC1, @@ -163,6 +169,7 @@ macro_rules! seed_system_init { dma1: $device.DMA1, dma1_rec: $ccdr.peripheral.DMA1, block_size: $block_size, + sample_rate: $sample_rate, }; libdaisy::system::SeedSystem::init(resources) @@ -406,6 +413,7 @@ impl SeedSystem { version, &mut delay, resources.block_size, + resources.sample_rate, ); let (d31, d32) = match version { From 3c06cc27ff88159af473a8ef22cc92e29635b937 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Mon, 22 Dec 2025 16:15:20 -0800 Subject: [PATCH 36/52] initial version of pca9685 using blocking writes. --- src/devices/led_driver_pca9685.rs | 171 ++++++++++++++++++++++++++++++ src/devices/mod.rs | 1 + 2 files changed, 172 insertions(+) create mode 100644 src/devices/led_driver_pca9685.rs diff --git a/src/devices/led_driver_pca9685.rs b/src/devices/led_driver_pca9685.rs new file mode 100644 index 0000000..49f0f64 --- /dev/null +++ b/src/devices/led_driver_pca9685.rs @@ -0,0 +1,171 @@ +#![allow(dead_code)] + +use crate::hal; +use hal::i2c::I2c; +use hal::prelude::*; +use stm32h7xx_hal::prelude::_embedded_hal_blocking_i2c_Write as Write; + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct LedRegister { + pub on: u16, + pub off: u16, +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct Pca9685TransmitBuffer { + pub register_addr: u8, + pub leds: [LedRegister; 16], +} + +impl Pca9685TransmitBuffer { + pub const SIZE: usize = 1 + 16 * 4; + + pub const fn new() -> Self { + Self { + register_addr: PCA9685_LED0, + leds: [LedRegister { on: 0, off: 0 }; 16], + } + } +} + +impl Default for Pca9685TransmitBuffer { + fn default() -> Self { + Self { + register_addr: PCA9685_LED0, + leds: [LedRegister { on: 0, off: 0 }; 16], + } + } +} + +pub type DmaBuffer = [Pca9685TransmitBuffer; N]; + +pub struct LedDriverPca9685 { + i2c: I2c, + draw_buffer: Option<&'static mut [u8]>, + transmit_buffer: Option<&'static mut [u8]>, + addresses: [u8; NUM_DRIVERS], +} + +// Constants +const PCA9685_I2C_BASE_ADDRESS: u8 = 0b01000000; +const PCA9685_MODE1: u8 = 0x00; +const PCA9685_MODE2: u8 = 0x01; +const PRE_SCALE_MODE: u8 = 0xFE; +const PCA9685_LED0: u8 = 0x06; + +impl LedDriverPca9685 +where + I2c: Write, +{ + pub fn new( + mut i2c: I2c, + addresses: [u8; NUM_DRIVERS], + dma_buffer_a: &'static mut DmaBuffer, + dma_buffer_b: &'static mut DmaBuffer, + ) -> Self { + // Init drivers + for &addr in &addresses { + let address = PCA9685_I2C_BASE_ADDRESS | addr; + let _ = i2c.write(address, &[PCA9685_MODE1, 0x00]); + crate::delay::CycleDelay::new().delay_ms(20u8); + let _ = i2c.write(address, &[PCA9685_MODE1, 0x00]); + crate::delay::CycleDelay::new().delay_ms(20u8); + let _ = i2c.write(address, &[PCA9685_MODE1, 0b00100000]); // Auto increment + crate::delay::CycleDelay::new().delay_ms(20u8); + let _ = i2c.write(address, &[PCA9685_MODE2, 0b000110110]); // OE-high, Push-Pull, etc. + } + + let len = NUM_DRIVERS * Pca9685TransmitBuffer::SIZE; + + // Initialize buffers + for buf in dma_buffer_a.iter_mut() { + buf.register_addr = PCA9685_LED0; + } + for buf in dma_buffer_b.iter_mut() { + buf.register_addr = PCA9685_LED0; + } + + let buffer_a_u8 = + unsafe { core::slice::from_raw_parts_mut(dma_buffer_a.as_mut_ptr() as *mut u8, len) }; + let buffer_b_u8 = + unsafe { core::slice::from_raw_parts_mut(dma_buffer_b.as_mut_ptr() as *mut u8, len) }; + + Self { + i2c, + draw_buffer: Some(buffer_b_u8), + transmit_buffer: Some(buffer_a_u8), + addresses, + } + } + + pub fn set_led(&mut self, led_index: usize, brightness: f32) { + let driver_idx = led_index / 16; + let led_idx_in_driver = led_index % 16; + + if driver_idx >= NUM_DRIVERS { + return; + } + + if let Some(buffer) = &mut self.draw_buffer { + let offset = driver_idx * Pca9685TransmitBuffer::SIZE + 1 + led_idx_in_driver * 4; + + let val = (brightness * 4095.0) as u16; + let on = 0; + let off = if val > 4095 { 4095 } else { val }; + + buffer[offset] = (on & 0xFF) as u8; + buffer[offset + 1] = (on >> 8) as u8; + buffer[offset + 2] = (off & 0xFF) as u8; + buffer[offset + 3] = (off >> 8) as u8; + } + } + + pub fn swap_buffers_and_transmit(&mut self) { + // Swap buffers + let draw = self.draw_buffer.take().unwrap(); + let transmit = self.transmit_buffer.take().unwrap(); + self.draw_buffer = Some(transmit); + self.transmit_buffer = Some(draw); + + // Start transmission + self.transmit(); + } + + fn transmit(&mut self) { + for driver_idx in 0..NUM_DRIVERS { + let address = PCA9685_I2C_BASE_ADDRESS | self.addresses[driver_idx]; + + let buffer = self.transmit_buffer.as_mut().unwrap(); + let start = driver_idx * Pca9685TransmitBuffer::SIZE; + let end = start + Pca9685TransmitBuffer::SIZE; + let bytes = unsafe { + core::mem::transmute::<&mut [u8], &'static mut [u8]>(&mut buffer[start..end]) + }; + + let _ = self.i2c.write(address, bytes); + } + } + + pub fn poll(&mut self) { + // No-op for blocking + } +} + +const GAMMA_TABLE: [u16; 256] = [ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 9, 10, + 11, 12, 13, 15, 16, 17, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36, 38, 40, 43, 45, 48, 50, 53, + 56, 59, 62, 65, 68, 71, 75, 78, 82, 85, 89, 93, 97, 101, 105, 110, 114, 119, 123, 128, 133, + 138, 143, 149, 154, 159, 165, 171, 177, 183, 189, 195, 202, 208, 215, 222, 229, 236, 243, 250, + 258, 266, 273, 281, 290, 298, 306, 315, 324, 332, 341, 351, 360, 369, 379, 389, 399, 409, 419, + 430, 440, 451, 462, 473, 485, 496, 508, 520, 532, 544, 556, 569, 582, 594, 608, 621, 634, 648, + 662, 676, 690, 704, 719, 734, 749, 764, 779, 795, 811, 827, 843, 859, 876, 893, 910, 927, 944, + 962, 980, 998, 1016, 1034, 1053, 1072, 1091, 1110, 1130, 1150, 1170, 1190, 1210, 1231, 1252, + 1273, 1294, 1316, 1338, 1360, 1382, 1404, 1427, 1450, 1473, 1497, 1520, 1544, 1568, 1593, 1617, + 1642, 1667, 1693, 1718, 1744, 1770, 1797, 1823, 1850, 1877, 1905, 1932, 1960, 1988, 2017, 2045, + 2074, 2103, 2133, 2162, 2192, 2223, 2253, 2284, 2315, 2346, 2378, 2410, 2442, 2474, 2507, 2540, + 2573, 2606, 2640, 2674, 2708, 2743, 2778, 2813, 2849, 2884, 2920, 2957, 2993, 3030, 3067, 3105, + 3143, 3181, 3219, 3258, 3297, 3336, 3376, 3416, 3456, 3496, 3537, 3578, 3619, 3661, 3703, 3745, + 3788, 3831, 3874, 3918, 3962, 4006, 4050, 4095, +]; diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 70cb30c..8f58ac2 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,4 +1,5 @@ pub mod encoder; +pub mod led_driver_pca9685; pub mod mpr121; pub mod multiplexer; pub mod shift_register; From f79ca721b2c89407f9a594d8d9a617f6df618f18 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Tue, 23 Dec 2025 09:55:33 -0800 Subject: [PATCH 37/52] fixed MODE2 register weirdness, and did not choose to invert at the device level for now. --- src/devices/led_driver_pca9685.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/devices/led_driver_pca9685.rs b/src/devices/led_driver_pca9685.rs index 49f0f64..14c87a4 100644 --- a/src/devices/led_driver_pca9685.rs +++ b/src/devices/led_driver_pca9685.rs @@ -74,7 +74,8 @@ where crate::delay::CycleDelay::new().delay_ms(20u8); let _ = i2c.write(address, &[PCA9685_MODE1, 0b00100000]); // Auto increment crate::delay::CycleDelay::new().delay_ms(20u8); - let _ = i2c.write(address, &[PCA9685_MODE2, 0b000110110]); // OE-high, Push-Pull, etc. + // There are a few configurations in this register we may want to expose later. + let _ = i2c.write(address, &[PCA9685_MODE2, 0b00000110]); // OE hi-z, odrv=1 } let len = NUM_DRIVERS * Pca9685TransmitBuffer::SIZE; From ea4cfab4b480e2d8eacb7b65b1b330b8abc0d132 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Tue, 23 Dec 2025 12:09:26 -0800 Subject: [PATCH 38/52] removed unnecessary transmute, and removed gamma table --- src/devices/led_driver_pca9685.rs | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/devices/led_driver_pca9685.rs b/src/devices/led_driver_pca9685.rs index 14c87a4..441057e 100644 --- a/src/devices/led_driver_pca9685.rs +++ b/src/devices/led_driver_pca9685.rs @@ -141,10 +141,7 @@ where let buffer = self.transmit_buffer.as_mut().unwrap(); let start = driver_idx * Pca9685TransmitBuffer::SIZE; let end = start + Pca9685TransmitBuffer::SIZE; - let bytes = unsafe { - core::mem::transmute::<&mut [u8], &'static mut [u8]>(&mut buffer[start..end]) - }; - + let bytes = &buffer[start..end]; let _ = self.i2c.write(address, bytes); } } @@ -153,20 +150,3 @@ where // No-op for blocking } } - -const GAMMA_TABLE: [u16; 256] = [ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 9, 10, - 11, 12, 13, 15, 16, 17, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36, 38, 40, 43, 45, 48, 50, 53, - 56, 59, 62, 65, 68, 71, 75, 78, 82, 85, 89, 93, 97, 101, 105, 110, 114, 119, 123, 128, 133, - 138, 143, 149, 154, 159, 165, 171, 177, 183, 189, 195, 202, 208, 215, 222, 229, 236, 243, 250, - 258, 266, 273, 281, 290, 298, 306, 315, 324, 332, 341, 351, 360, 369, 379, 389, 399, 409, 419, - 430, 440, 451, 462, 473, 485, 496, 508, 520, 532, 544, 556, 569, 582, 594, 608, 621, 634, 648, - 662, 676, 690, 704, 719, 734, 749, 764, 779, 795, 811, 827, 843, 859, 876, 893, 910, 927, 944, - 962, 980, 998, 1016, 1034, 1053, 1072, 1091, 1110, 1130, 1150, 1170, 1190, 1210, 1231, 1252, - 1273, 1294, 1316, 1338, 1360, 1382, 1404, 1427, 1450, 1473, 1497, 1520, 1544, 1568, 1593, 1617, - 1642, 1667, 1693, 1718, 1744, 1770, 1797, 1823, 1850, 1877, 1905, 1932, 1960, 1988, 2017, 2045, - 2074, 2103, 2133, 2162, 2192, 2223, 2253, 2284, 2315, 2346, 2378, 2410, 2442, 2474, 2507, 2540, - 2573, 2606, 2640, 2674, 2708, 2743, 2778, 2813, 2849, 2884, 2920, 2957, 2993, 3030, 3067, 3105, - 3143, 3181, 3219, 3258, 3297, 3336, 3376, 3416, 3456, 3496, 3537, 3578, 3619, 3661, 3703, 3745, - 3788, 3831, 3874, 3918, 3962, 4006, 4050, 4095, -]; From d70c8803663da07b23144c3f117fec04943a9855 Mon Sep 17 00:00:00 2001 From: Corvus Date: Mon, 26 Jan 2026 13:47:29 -0700 Subject: [PATCH 39/52] Added new seed version --- src/audio.rs | 7 +++++-- src/system/seed.rs | 8 +++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 263baf7..11aa204 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -296,7 +296,9 @@ impl Audio { max_transfer_size, } } - crate::system::seed::Version::Seed2DFM | crate::system::seed::Version::Seed => { + crate::system::seed::Version::Seed2DFM + | crate::system::seed::Version::Seed + | crate::system::seed::Version::Seed3 => { let dma_buffer_size = block_size * 2 * 2; let rx_buffer: &'static mut [u32] = @@ -366,7 +368,8 @@ impl Audio { delay.delay_ms(1); ak_reset.set_high(); } - crate::system::seed::Version::Seed2DFM => { + crate::system::seed::Version::Seed2DFM + | crate::system::seed::Version::Seed3 => { // Set deemphasis low info!("Setting up PCM3060 Audio CODEC..."); i2c_sda.into_push_pull_output_in_state(stm32h7xx_hal::gpio::PinState::Low); diff --git a/src/system/seed.rs b/src/system/seed.rs index f79f390..44bf52d 100644 --- a/src/system/seed.rs +++ b/src/system/seed.rs @@ -232,28 +232,34 @@ pub enum Version { Seed, Seed1_1, Seed2DFM, + Seed3, } impl SeedSystem { fn detect_version( s2dfm_pin: hal::gpio::gpiod::PD4, seed1_1_pin: hal::gpio::gpiod::PD3, - _seed3_pin: hal::gpio::gpioh::PH6, + seed3_pin: hal::gpio::gpioh::PH6, ) -> Version { let seed1_1_pin = seed1_1_pin.into_pull_up_input(); let s2dfm_pin = s2dfm_pin.into_pull_up_input(); + let seed3_pin = seed3_pin.into_pull_up_input(); let seed1_1 = seed1_1_pin.is_low(); let s2dfm = s2dfm_pin.is_low(); + let seed3 = seed3_pin.is_low(); // Deinitialize the pins after reading s2dfm_pin.into_analog(); seed1_1_pin.into_analog(); + seed3_pin.into_analog(); if seed1_1 { Version::Seed1_1 } else if s2dfm { Version::Seed2DFM + } else if seed3 { + Version::Seed3 } else { Version::Seed } From fef59580494974bdf092c56f9fc4477f61dfe804 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Wed, 28 Jan 2026 12:38:06 -0800 Subject: [PATCH 40/52] initial addition of Pca9555 i/o expander driver. --- src/devices/expander_pca9555.rs | 119 ++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/devices/expander_pca9555.rs diff --git a/src/devices/expander_pca9555.rs b/src/devices/expander_pca9555.rs new file mode 100644 index 0000000..7eafd7f --- /dev/null +++ b/src/devices/expander_pca9555.rs @@ -0,0 +1,119 @@ +use crate::hal; +use hal::i2c::I2c; +use hal::prelude::*; +use stm32h7xx_hal::prelude::{ + _embedded_hal_blocking_i2c_Write as Write, _embedded_hal_blocking_i2c_WriteRead as WriteRead, +}; + +/// Data corresponding to a single PCA9555 I/O Expander +/// +/// In the future we can add: +/// - chained-device support (similar to pca9685) +/// - output configuration +/// - INT pin support for non-polling behavior +/// +/// For now this is a minimal driver for providing access +/// to 16 additional inptus. +pub struct Pca9555 { + address: u8, + states: [bool; 16], +} + +#[derive(Debug)] +pub enum I2cError { + Write(W), + WriteRead(WR), +} + +#[expect(type_alias_bounds)] +pub type WriteReadError = + I2cError<::Error, ::Error>; + +impl Pca9555 { + pub fn new(address: u8, bus: &mut W) -> Result> { + let dev = Pca9555 { + address, + states: Default::default(), + }; + + // Configuration + // Default config appears to be: + // configuration = input + // polarity = not-inverted + // So for our initial implementation.. no config is necessary + + // Fill internal data with current states on hardware. + self.update(); + + // Return + Ok(dev) + } + + pub fn update(&mut self, bus: &mut W) -> Result<(), WriteReadError> { + // Register pairs flip automatically after reads. + // So we can basically just send the `InputPort0` command, + // and read two data bytes back every time we want new data + let mut raw_states = [u8; 2]; + self.read(bus, Register::InputPort0, raw_states)?; + + for i in 0..16 { + let raw_union: u16 = ((raw_states[0] as u16) | ((raw_states as u16) << 8)); + self.states[i] = (raw_union & (1 << i)) != 0; + } + Ok(()) + } + + fn write( + &self, + bus: &mut W, + register: Register, + data: u8, + ) -> Result<(), WriteReadError> { + self.write_raw(bus, register as u8, data) + } + + fn write_raw( + &self, + bus: &mut W, + register: u8, + data: u8, + ) -> Result<(), WriteReadError> { + // This is probably not what we need + bus.write(self.address, &[register, data]) + .map_err(I2cError::Write)?; + + Ok(()) + } + + fn read( + &self, + bus: &mut W, + register: Register, + buffer: &mut [u8], + ) -> Result<(), WriteReadError> { + self.read_raw(bus, register as u8, buffer) + } + + fn read_raw( + &self, + bus: &mut W, + register: u8, + buffer: &mut [u8], + ) -> Result<(), WriteReadError> { + bus.write_read(self.address, &[register], buffer) + .map_err(I2cError::WriteRead) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +enum Register { + InputPort0 = 0x00, + InputPort1 = 0x01, + OutputPort0 = 0x02, + OutputPort1 = 0x03, + PolarityInversion0 = 0x04, + PolarityInversion1 = 0x05, + ConfigurationPort0 = 0x06, + ConfigurationPort1 = 0x07, +} From 3c2be352000f7fb9ea41b8623af19242b913d14e Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Wed, 28 Jan 2026 15:40:12 -0800 Subject: [PATCH 41/52] pca9555 - fix obvious issues/missing bits --- src/devices/expander_pca9555.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/devices/expander_pca9555.rs b/src/devices/expander_pca9555.rs index 7eafd7f..fe2ca5d 100644 --- a/src/devices/expander_pca9555.rs +++ b/src/devices/expander_pca9555.rs @@ -31,8 +31,10 @@ pub type WriteReadError = impl Pca9555 { pub fn new(address: u8, bus: &mut W) -> Result> { + let base_address = 0x20; + let dev_addr = base_address + address; let dev = Pca9555 { - address, + address: dev_addr, states: Default::default(), }; @@ -43,12 +45,13 @@ impl Pca9555 { // So for our initial implementation.. no config is necessary // Fill internal data with current states on hardware. - self.update(); + dev.update(); - // Return Ok(dev) } + /// Polls the device, reading both of it's input registers to fill the + /// internal state data pub fn update(&mut self, bus: &mut W) -> Result<(), WriteReadError> { // Register pairs flip automatically after reads. // So we can basically just send the `InputPort0` command, @@ -63,6 +66,12 @@ impl Pca9555 { Ok(()) } + /// Returns the current state recorded for a particular pin + pub fn get_state(self, index: u16) -> bool { + let idx = index.clamp(0, 16); + self.states[idx] + } + fn write( &self, bus: &mut W, From 61ac43b6a777ab69aaafb331e68891f2b2ab3da5 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Fri, 30 Jan 2026 12:35:28 -0800 Subject: [PATCH 42/52] potentially functional driver after latest changes/cleanup. --- src/devices/expander_pca9555.rs | 89 ++++++++++++--------------------- src/devices/mod.rs | 1 + 2 files changed, 34 insertions(+), 56 deletions(-) diff --git a/src/devices/expander_pca9555.rs b/src/devices/expander_pca9555.rs index fe2ca5d..abed2fc 100644 --- a/src/devices/expander_pca9555.rs +++ b/src/devices/expander_pca9555.rs @@ -2,7 +2,8 @@ use crate::hal; use hal::i2c::I2c; use hal::prelude::*; use stm32h7xx_hal::prelude::{ - _embedded_hal_blocking_i2c_Write as Write, _embedded_hal_blocking_i2c_WriteRead as WriteRead, + _embedded_hal_blocking_i2c_Read as Read, _embedded_hal_blocking_i2c_Write as Write, + _embedded_hal_blocking_i2c_WriteRead as WriteRead, }; /// Data corresponding to a single PCA9555 I/O Expander @@ -14,7 +15,8 @@ use stm32h7xx_hal::prelude::{ /// /// For now this is a minimal driver for providing access /// to 16 additional inptus. -pub struct Pca9555 { +pub struct Pca9555 { + i2c: I2c, address: u8, states: [bool; 16], } @@ -29,11 +31,15 @@ pub enum I2cError { pub type WriteReadError = I2cError<::Error, ::Error>; -impl Pca9555 { - pub fn new(address: u8, bus: &mut W) -> Result> { +impl Pca9555 +where + I2c: Write + WriteRead + Read, +{ + pub fn new(address: u8, mut bus: I2c) -> Self { let base_address = 0x20; - let dev_addr = base_address + address; - let dev = Pca9555 { + let dev_addr = base_address | (address & 0x07); + let mut dev = Pca9555 { + i2c: bus, address: dev_addr, states: Default::default(), }; @@ -44,74 +50,45 @@ impl Pca9555 { // polarity = not-inverted // So for our initial implementation.. no config is necessary + // Any attempts to write here will cause an infinite loop during if device is not + // present and serial callback hasn't been started. So maybe we don't do this here yet. + + // This was here to test why device wasn't sending ACK (but it wasn't on the PCB ...) + // Will remove these comments, and finish driver once I have appropriate HW + // crate::delay::CycleDelay::new().delay_ms(30u8); + // let _ = dev + // .i2c + // .write(dev.address, &[Register::ConfigurationPort0 as u8, 0xff]); + // Fill internal data with current states on hardware. - dev.update(); + // dev.update(); - Ok(dev) + dev } /// Polls the device, reading both of it's input registers to fill the /// internal state data - pub fn update(&mut self, bus: &mut W) -> Result<(), WriteReadError> { + // pub fn update(&mut self) -> Result<(), WriteReadError> { + pub fn update(&mut self) { // Register pairs flip automatically after reads. // So we can basically just send the `InputPort0` command, // and read two data bytes back every time we want new data - let mut raw_states = [u8; 2]; - self.read(bus, Register::InputPort0, raw_states)?; + let mut raw_states = [0u8; 2]; + let _ = self + .i2c + .write_read(self.address, &[Register::InputPort0 as u8], &mut raw_states); + let raw_union: u16 = (raw_states[0] as u16) | ((raw_states[1] as u16) << 8); for i in 0..16 { - let raw_union: u16 = ((raw_states[0] as u16) | ((raw_states as u16) << 8)); self.states[i] = (raw_union & (1 << i)) != 0; } - Ok(()) } /// Returns the current state recorded for a particular pin - pub fn get_state(self, index: u16) -> bool { - let idx = index.clamp(0, 16); + pub fn get_state(&self, index: usize) -> bool { + let idx = index.min(15); self.states[idx] } - - fn write( - &self, - bus: &mut W, - register: Register, - data: u8, - ) -> Result<(), WriteReadError> { - self.write_raw(bus, register as u8, data) - } - - fn write_raw( - &self, - bus: &mut W, - register: u8, - data: u8, - ) -> Result<(), WriteReadError> { - // This is probably not what we need - bus.write(self.address, &[register, data]) - .map_err(I2cError::Write)?; - - Ok(()) - } - - fn read( - &self, - bus: &mut W, - register: Register, - buffer: &mut [u8], - ) -> Result<(), WriteReadError> { - self.read_raw(bus, register as u8, buffer) - } - - fn read_raw( - &self, - bus: &mut W, - register: u8, - buffer: &mut [u8], - ) -> Result<(), WriteReadError> { - bus.write_read(self.address, &[register], buffer) - .map_err(I2cError::WriteRead) - } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 8f58ac2..e71d384 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,4 +1,5 @@ pub mod encoder; +pub mod expander_pca9555; pub mod led_driver_pca9685; pub mod mpr121; pub mod multiplexer; From c50724aac589434e8a3fb41609aa1c27a881e429 Mon Sep 17 00:00:00 2001 From: Corvus Date: Thu, 5 Mar 2026 13:41:21 -0700 Subject: [PATCH 43/52] Fixed initial switch state --- src/hid.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hid.rs b/src/hid.rs index 101fe97..3d7b6f8 100644 --- a/src/hid.rs +++ b/src/hid.rs @@ -47,7 +47,10 @@ where pub fn new(pin: T, switch_type: SwitchType) -> Self { Self { pin, - state: debounce_4(false), + state: debounce_4(match switch_type { + SwitchType::PullUp => true, + SwitchType::PullDown => false, + }), falling: false, rising: false, switch_type, @@ -211,7 +214,7 @@ impl AnalogControl { /// /// ```rust /// if let Ok(data) = adc1.read(control1.get_pin()) { - /// control1.update(data); + /// control1.update(data); /// } /// ``` pub fn update(&mut self, value: u32) { From c6e7544ceea3de546077a05ea0b3e567baa69dc7 Mon Sep 17 00:00:00 2001 From: Corvus Date: Thu, 5 Mar 2026 14:32:12 -0700 Subject: [PATCH 44/52] Made initial state configurable --- src/devices/shift_register.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/devices/shift_register.rs b/src/devices/shift_register.rs index d8f4980..bbc6f52 100644 --- a/src/devices/shift_register.rs +++ b/src/devices/shift_register.rs @@ -46,13 +46,17 @@ impl /// /// `clock` and `latch` are expected to take single pins, /// while `data` expects a tuple of up to 8 pins. - pub fn new(clock: C, latch: L, data: D) -> Self { + pub fn new(clock: C, latch: L, data: D, initial_state: bool) -> Self { + /// We'll use an arithmetic shift to fill the byte + let initial_state = ((initial_state as i8) << 7); + let initial_state = (initial_state >> 7) as u8; + Self { clock, latch, data, delay: 1000, - bits: [[0u8; SERIES]; PARALLEL], + bits: [[initial_state; SERIES]; PARALLEL], } } From bf879b4e532113c7942c386983aada3d46e2d85e Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Mon, 23 Mar 2026 09:01:20 -0700 Subject: [PATCH 45/52] initial fix: blanket i/o channel swap for mismatch in expected SAI/I2S settings --- src/audio.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 11aa204..0708d94 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -206,11 +206,10 @@ impl Audio { info!("Set up SAI..."); let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); - let master_config = - I2SChanConfig::new(I2SDir::Rx).set_frame_sync_active_high(false); + let master_config = I2SChanConfig::new(I2SDir::Rx).set_frame_sync_active_high(true); let slave_config = I2SChanConfig::new(I2SDir::Tx) .set_sync_type(I2SSync::Internal) - .set_frame_sync_active_high(false); + .set_frame_sync_active_high(true); let pins_a = ( sai_mclk_a.into_alternate(), @@ -334,11 +333,10 @@ impl Audio { info!("Setup up SAI..."); let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); - let master_config = - I2SChanConfig::new(I2SDir::Tx).set_frame_sync_active_high(false); + let master_config = I2SChanConfig::new(I2SDir::Tx).set_frame_sync_active_high(true); let slave_config = I2SChanConfig::new(I2SDir::Rx) .set_sync_type(I2SSync::Internal) - .set_frame_sync_active_high(false); + .set_frame_sync_active_high(true); let pins_a = ( sai_mclk_a.into_alternate(), @@ -480,10 +478,10 @@ impl Audio { info!("Set up SAI..."); let sai1_rec = sai1_p.kernel_clk_mux(SAI1SEL_A::Pll3P); let master_config = - sai::I2SChanConfig::new(sai::I2SDir::Rx).set_frame_sync_active_high(false); + sai::I2SChanConfig::new(sai::I2SDir::Rx).set_frame_sync_active_high(true); let slave_config = sai::I2SChanConfig::new(sai::I2SDir::Tx) .set_sync_type(sai::I2SSync::Internal) - .set_frame_sync_active_high(false); + .set_frame_sync_active_high(true); let pins_a = ( sai_mclk_a.into_alternate(), From 78c83f1da74e5923e939a4ddd1e232f6fd221730 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Mon, 23 Mar 2026 10:08:39 -0700 Subject: [PATCH 46/52] secondary fix: resolves secondary channel-swap, and audio input buzz on seed1.1 (rev5) and patch sm --- src/audio.rs | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 0708d94..1ff1f45 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -182,6 +182,8 @@ impl Audio { .memory_increment(true) .peripheral_increment(false) .circular_buffer(true) + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true) .fifo_enable(false); let mut output_stream = dma::Transfer::init( dma1_stream0, @@ -257,16 +259,9 @@ impl Audio { input_stream.start(|_sai1_rb| { sai.enable_dma(SaiChannel::ChannelA); }); - - output_stream.start(|sai1_rb| { + sai.enable(); + output_stream.start(|_sai1_rb| { sai.enable_dma(SaiChannel::ChannelB); - - // wait until sai1's fifo starts to receive data - info!("Sai1 fifo waiting to receive data."); - while sai1_rb.chb().sr.read().flvl().is_empty() {} - info!("Audio started!"); - sai.enable(); - sai.try_send(0, 0).unwrap(); }); let max_transfer_size = block_size * 2; @@ -451,6 +446,8 @@ impl Audio { .memory_increment(true) .peripheral_increment(false) .circular_buffer(true) + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true) .fifo_enable(false); let mut output_stream = dma::Transfer::init( @@ -567,12 +564,12 @@ impl Audio { } info!("Start audio stream..."); - input_stream.start(|_sai1_rb| sai.enable_dma(sai::SaiChannel::ChannelA)); - output_stream.start(|sai1_rb| { - sai.enable_dma(sai::SaiChannel::ChannelB); - while sai1_rb.chb().sr.read().flvl().is_empty() {} - sai.enable(); - sai.try_send(0, 0).unwrap(); + input_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelA); + }); + sai.enable(); + output_stream.start(|_sai1_rb| { + sai.enable_dma(SaiChannel::ChannelB); }); let max_transfer_size = block_size * 2; @@ -602,15 +599,15 @@ impl Audio { // Check interrupt(s) match &mut self.audio_stream { AudioStream::Normal { input, output } => { - output.clear_interrupts(); + input.clear_interrupts(); - if input.get_half_transfer_flag() { - input.clear_half_transfer_interrupt(); + if output.get_half_transfer_flag() { + output.clear_half_transfer_interrupt(); self.input.set_index(0); self.output.set_index(0); true - } else if input.get_transfer_complete_flag() { - input.clear_transfer_complete_interrupt(); + } else if output.get_transfer_complete_flag() { + output.clear_transfer_complete_interrupt(); self.input.set_index(self.max_transfer_size); self.output.set_index(self.max_transfer_size); true @@ -643,8 +640,8 @@ impl Audio { // Check interrupt(s) match &mut self.audio_stream { AudioStream::Normal { input, output } => { - output.clear_interrupts(); - input.get_half_transfer_flag() || input.get_transfer_complete_flag() + input.clear_interrupts(); + output.get_half_transfer_flag() || output.get_transfer_complete_flag() } AudioStream::S2dfm { output, input } => { input.clear_interrupts(); From 1f25e96533be4a2cdaef3c2e7ede2d07c07cfa66 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Thu, 30 Apr 2026 09:27:30 -0700 Subject: [PATCH 47/52] dac initialization methods for the seed, and methods for taking dac pins for patch_sm --- src/system/patch_sm.rs | 8 ++++ src/system/seed.rs | 90 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs index 4ff35e0..9dfc956 100644 --- a/src/system/patch_sm.rs +++ b/src/system/patch_sm.rs @@ -457,4 +457,12 @@ impl PatchSmSystem { delay, } } + + pub fn take_dac_c1(&mut self) -> Option> { + self.c1.take() + } + + pub fn take_dac_c2(&mut self) -> Option> { + self.c2.take() + } } diff --git a/src/system/seed.rs b/src/system/seed.rs index 44bf52d..f148f8b 100644 --- a/src/system/seed.rs +++ b/src/system/seed.rs @@ -1,5 +1,5 @@ use log::info; -use stm32h7xx_hal::{adc, delay::Delay, dma, prelude::*, rcc, stm32, time::Hertz}; +use stm32h7xx_hal::{adc, dac, delay::Delay, dma, prelude::*, rcc, stm32, time::Hertz}; use crate::{audio::Audio, *}; @@ -10,12 +10,25 @@ pub struct SeedSystem { pub audio: audio::Audio, pub adc1: adc::Adc, pub adc2: adc::Adc, + pub c1: Option>, + pub c2: Option>, + dac_state: SeedDacState, pub sdram: &'static mut [f32], pub flash: crate::flash::Flash, pub internal_usb: Option, pub delay: Delay, } +enum SeedDacState { + Uninitialized { + dac: stm32::DAC, + dac12_rec: rcc::rec::Dac12, + }, + Channel1Initialized, + Channel2Initialized, + BothInitialized, +} + pub struct MinimalSeedSystem { pub gpio: crate::gpio::SeedGPIO, pub flash: crate::flash::Flash, @@ -495,10 +508,85 @@ impl SeedSystem { audio, adc1, adc2, + c1: None, + c2: None, + dac_state: SeedDacState::Uninitialized { + dac: resources.dac, + dac12_rec: resources.dac12_rec, + }, sdram, flash, internal_usb: Some(internal_usb), delay, } } + + pub fn init_dac_channels(&mut self) -> Option<()> { + match self.dac_state { + SeedDacState::BothInitialized => return Some(()), + SeedDacState::Channel1Initialized | SeedDacState::Channel2Initialized => { + return None; + } + SeedDacState::Uninitialized { .. } => {} + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.dac_state, SeedDacState::BothInitialized) + else { + return None; + }; + let c1_pin = self.gpio.daisy23.take()?; + let c2_pin = self.gpio.daisy22.take()?; + let (c1, c2) = dac::dac(dac, (c1_pin, c2_pin), dac12_rec); + + self.c1 = Some(c1); + self.c2 = Some(c2); + Some(()) + } + + fn init_dac_c1(&mut self) -> Option<()> { + if self.c1.is_some() { + return Some(()); + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.dac_state, SeedDacState::Channel1Initialized) + else { + return None; + }; + let c1_pin = self.gpio.daisy23.take()?; + let c1 = dac::dac(dac, c1_pin, dac12_rec); + self.c1 = Some(c1); + Some(()) + } + + fn init_dac_c2(&mut self) -> Option<()> { + if self.c2.is_some() { + return Some(()); + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.dac_state, SeedDacState::Channel2Initialized) + else { + return None; + }; + let c2_pin = self.gpio.daisy22.take()?; + let c2 = dac::dac(dac, c2_pin, dac12_rec); + self.c2 = Some(c2); + Some(()) + } + + pub fn take_dac_c1(&mut self) -> Option> { + if self.c1.is_none() { + self.init_dac_c1()?; + } + self.c1.take() + } + + pub fn take_dac_c2(&mut self) -> Option> { + if self.c2.is_none() { + self.init_dac_c2()?; + } + self.c2.take() + } } From 22a4cb825212a799e84801d8e266dfc9182229b0 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Fri, 1 May 2026 08:46:17 -0700 Subject: [PATCH 48/52] refactor DAC management to its own struct to prevent borrow/ownership issues with usage from templates. --- src/system/patch_sm.rs | 36 +++++--- src/system/seed.rs | 183 +++++++++++++++++++++++------------------ 2 files changed, 129 insertions(+), 90 deletions(-) diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs index 9dfc956..775d090 100644 --- a/src/system/patch_sm.rs +++ b/src/system/patch_sm.rs @@ -10,14 +10,35 @@ pub struct PatchSmSystem { pub audio: audio::Audio, pub adc1: adc::Adc, pub adc2: adc::Adc, - pub c1: Option>, - pub c2: Option>, + pub dac: PatchSmDac, pub sdram: &'static mut [f32], pub flash: crate::flash::Flash, pub internal_usb: Option, pub delay: Delay, } +pub struct PatchSmDac { + c1: Option>, + c2: Option>, +} + +impl PatchSmDac { + fn new(c1: dac::C1, c2: dac::C2) -> Self { + Self { + c1: Some(c1), + c2: Some(c2), + } + } + + pub fn take_c1(&mut self) -> Option> { + self.c1.take() + } + + pub fn take_c2(&mut self) -> Option> { + self.c2.take() + } +} + pub struct MinimalPatchSmSystem { pub gpio: crate::gpio::PatchSmGPIO, pub flash: crate::flash::Flash, @@ -449,20 +470,11 @@ impl PatchSmSystem { audio, adc1, adc2, - c1: Some(c1), - c2: Some(c2), + dac: PatchSmDac::new(c1, c2), sdram, flash, internal_usb: Some(internal_usb), delay, } } - - pub fn take_dac_c1(&mut self) -> Option> { - self.c1.take() - } - - pub fn take_dac_c2(&mut self) -> Option> { - self.c2.take() - } } diff --git a/src/system/seed.rs b/src/system/seed.rs index f148f8b..1ccfbf5 100644 --- a/src/system/seed.rs +++ b/src/system/seed.rs @@ -10,15 +10,19 @@ pub struct SeedSystem { pub audio: audio::Audio, pub adc1: adc::Adc, pub adc2: adc::Adc, - pub c1: Option>, - pub c2: Option>, - dac_state: SeedDacState, + pub dac: SeedDac, pub sdram: &'static mut [f32], pub flash: crate::flash::Flash, pub internal_usb: Option, pub delay: Delay, } +pub struct SeedDac { + c1: Option>, + c2: Option>, + state: SeedDacState, +} + enum SeedDacState { Uninitialized { dac: stm32::DAC, @@ -29,6 +33,103 @@ enum SeedDacState { BothInitialized, } +impl SeedDac { + fn new(dac: stm32::DAC, dac12_rec: rcc::rec::Dac12) -> Self { + Self { + c1: None, + c2: None, + state: SeedDacState::Uninitialized { dac, dac12_rec }, + } + } + + pub fn both_initialized(&self) -> bool { + matches!(self.state, SeedDacState::BothInitialized) + } + + pub fn init_channels( + &mut self, + c1_pin: hal::gpio::gpioa::PA4, + c2_pin: hal::gpio::gpioa::PA5, + ) -> Option<()> { + match self.state { + SeedDacState::BothInitialized => return Some(()), + SeedDacState::Channel1Initialized | SeedDacState::Channel2Initialized => { + return None; + } + SeedDacState::Uninitialized { .. } => {} + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.state, SeedDacState::BothInitialized) + else { + return None; + }; + let (c1, c2) = dac::dac(dac, (c1_pin, c2_pin), dac12_rec); + + self.c1 = Some(c1); + self.c2 = Some(c2); + Some(()) + } + + fn init_c1(&mut self, c1_pin: hal::gpio::gpioa::PA4) -> Option<()> { + if self.c1.is_some() { + return Some(()); + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.state, SeedDacState::Channel1Initialized) + else { + return None; + }; + let c1 = dac::dac(dac, c1_pin, dac12_rec); + self.c1 = Some(c1); + Some(()) + } + + fn init_c2(&mut self, c2_pin: hal::gpio::gpioa::PA5) -> Option<()> { + if self.c2.is_some() { + return Some(()); + } + + let SeedDacState::Uninitialized { dac, dac12_rec } = + core::mem::replace(&mut self.state, SeedDacState::Channel2Initialized) + else { + return None; + }; + let c2 = dac::dac(dac, c2_pin, dac12_rec); + self.c2 = Some(c2); + Some(()) + } + + pub fn take_c1_with_pin( + &mut self, + c1_pin: hal::gpio::gpioa::PA4, + ) -> Option> { + if self.c1.is_none() { + self.init_c1(c1_pin)?; + } + self.c1.take() + } + + pub fn take_c2_with_pin( + &mut self, + c2_pin: hal::gpio::gpioa::PA5, + ) -> Option> { + if self.c2.is_none() { + self.init_c2(c2_pin)?; + } + self.c2.take() + } + + pub fn take_c1(&mut self) -> Option> { + self.c1.take() + } + + pub fn take_c2(&mut self) -> Option> { + self.c2.take() + } +} + pub struct MinimalSeedSystem { pub gpio: crate::gpio::SeedGPIO, pub flash: crate::flash::Flash, @@ -508,85 +609,11 @@ impl SeedSystem { audio, adc1, adc2, - c1: None, - c2: None, - dac_state: SeedDacState::Uninitialized { - dac: resources.dac, - dac12_rec: resources.dac12_rec, - }, + dac: SeedDac::new(resources.dac, resources.dac12_rec), sdram, flash, internal_usb: Some(internal_usb), delay, } } - - pub fn init_dac_channels(&mut self) -> Option<()> { - match self.dac_state { - SeedDacState::BothInitialized => return Some(()), - SeedDacState::Channel1Initialized | SeedDacState::Channel2Initialized => { - return None; - } - SeedDacState::Uninitialized { .. } => {} - } - - let SeedDacState::Uninitialized { dac, dac12_rec } = - core::mem::replace(&mut self.dac_state, SeedDacState::BothInitialized) - else { - return None; - }; - let c1_pin = self.gpio.daisy23.take()?; - let c2_pin = self.gpio.daisy22.take()?; - let (c1, c2) = dac::dac(dac, (c1_pin, c2_pin), dac12_rec); - - self.c1 = Some(c1); - self.c2 = Some(c2); - Some(()) - } - - fn init_dac_c1(&mut self) -> Option<()> { - if self.c1.is_some() { - return Some(()); - } - - let SeedDacState::Uninitialized { dac, dac12_rec } = - core::mem::replace(&mut self.dac_state, SeedDacState::Channel1Initialized) - else { - return None; - }; - let c1_pin = self.gpio.daisy23.take()?; - let c1 = dac::dac(dac, c1_pin, dac12_rec); - self.c1 = Some(c1); - Some(()) - } - - fn init_dac_c2(&mut self) -> Option<()> { - if self.c2.is_some() { - return Some(()); - } - - let SeedDacState::Uninitialized { dac, dac12_rec } = - core::mem::replace(&mut self.dac_state, SeedDacState::Channel2Initialized) - else { - return None; - }; - let c2_pin = self.gpio.daisy22.take()?; - let c2 = dac::dac(dac, c2_pin, dac12_rec); - self.c2 = Some(c2); - Some(()) - } - - pub fn take_dac_c1(&mut self) -> Option> { - if self.c1.is_none() { - self.init_dac_c1()?; - } - self.c1.take() - } - - pub fn take_dac_c2(&mut self) -> Option> { - if self.c2.is_none() { - self.init_dac_c2()?; - } - self.c2.take() - } } From 622431c6b595cdba5e68116c57c9566757298070 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Thu, 25 Jun 2026 09:15:30 -0700 Subject: [PATCH 49/52] remaps PatchSM CV input pins to match documented order in datasheet, C++ API, and patch.init frontpanel. --- src/gpio.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/gpio.rs b/src/gpio.rs index 08edb99..5939bc7 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -170,10 +170,12 @@ pub struct PatchSmGPIO { pub c3: Option>, pub c4: Option>, pub c5: Option>, - pub c6: Option>, - pub c7: Option>, - pub c8: Option>, - pub c9: Option>, + // NOTE: C6-C9 are mismatched from the datasheet STM32 pin connections + // This is to match the documented "CVx" numbering that is used everywhere + pub c6: Option>, + pub c7: Option>, + pub c8: Option>, + pub c9: Option>, pub c10: Option>, pub d1: Option>>, pub d2: Option>, @@ -208,10 +210,12 @@ impl PatchSmGPIO { c3: Option>, c4: Option>, c5: Option>, - c6: Option>, - c7: Option>, - c8: Option>, - c9: Option>, + // Physical pin arguments stay in "physical" order to make this obvious, + // and easier to confirm w/ datasheet.. + c6_phys: Option>, + c7_phys: Option>, + c8_phys: Option>, + c9_phys: Option>, c10: Option>, d1: Option>>, d2: Option>, @@ -245,10 +249,11 @@ impl PatchSmGPIO { c3, c4, c5, - c6, - c7, - c8, - c9, + // Remap to match expected CV ordering in datasheet, frontpanels, etc. + c6: c9_phys, + c7: c8_phys, + c8: c6_phys, + c9: c7_phys, c10, d1, d2, From 65e1b46691c5833c49c6d3c0bca720af018cd4b3 Mon Sep 17 00:00:00 2001 From: Nic Date: Mon, 3 Aug 2026 12:01:02 -0600 Subject: [PATCH 50/52] fixed seed_minimal_init macro bug --- src/system/seed.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/system/seed.rs b/src/system/seed.rs index 1ccfbf5..c67c39c 100644 --- a/src/system/seed.rs +++ b/src/system/seed.rs @@ -292,7 +292,7 @@ macro_rules! seed_system_init { #[macro_export] macro_rules! seed_minimal_init { - ($core:ident, $device:ident, $ccdr:ident) => {{ + ($core:ident, $device:ident, $ccdr:ident, $sample_rate:ident) => {{ let resources = ::libdaisy::system::SystemResources { clocks: &$ccdr.clocks, adc1: $device.ADC1, @@ -335,6 +335,7 @@ macro_rules! seed_minimal_init { dma1: $device.DMA1, dma1_rec: $ccdr.peripheral.DMA1, block_size: 0, + sample_rate: $sample_rate, }; ::libdaisy::system::MinimalSeedSystem::new(resources) From a25ca2c2f7bf06e3f6433eef1a8c85d5fdfd3d7e Mon Sep 17 00:00:00 2001 From: Corvus Date: Wed, 30 Sep 2026 13:06:52 -0600 Subject: [PATCH 51/52] Added halting i2c driver --- src/audio.rs | 60 +++++--- src/devices/led_driver_pca9685.rs | 78 +++++++--- src/i2c.rs | 245 ++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 341 insertions(+), 43 deletions(-) create mode 100644 src/i2c.rs diff --git a/src/audio.rs b/src/audio.rs index 1ff1f45..c77f725 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -8,7 +8,8 @@ use core::convert::Infallible; use cortex_m::prelude::_embedded_hal_blocking_i2c_Write; -use log::info; +use embedded_hal::blocking::i2c::WriteRead; +use log::{error, info}; use stm32h7xx_hal::{ dma, gpio::{gpiob, gpioe, gpioh, Analog}, @@ -237,7 +238,12 @@ impl Audio { i2c_sda.into_alternate_open_drain(), ); - let mut i2c = i2c2_d.i2c(i2c2_pins, Hertz::from_raw(100_000), i2c2_p, clocks); + let mut i2c = crate::i2c::I2cTimeout::new(i2c2_d.i2c( + i2c2_pins, + Hertz::from_raw(100_000), + i2c2_p, + clocks, + )); let codec_i2c_address: u8 = 0x1a; // or 0x1b if CSB is high @@ -503,8 +509,15 @@ impl Audio { i2c_sda.into_alternate_open_drain(), ); - let mut i2c = i2c2_d.i2c(i2c2_pins, Hertz::from_raw(100_000), i2c2_p, clocks); - pcm3060_init(&mut i2c, delay); + let mut i2c = crate::i2c::I2cTimeout::new(i2c2_d.i2c( + i2c2_pins, + Hertz::from_raw(100_000), + i2c2_p, + clocks, + )); + if let Err(e) = pcm3060_init(&mut i2c, delay) { + error!("PCM3060 codec did not respond: {e:?}"); + } const DEV_ADDR: u8 = 0x46; @@ -518,16 +531,16 @@ impl Audio { const MASK_DAC_PSV: u8 = 0x10; const MASK_FMT: u8 = 0x01; - // handle the error? - fn read_reg(reg_addr: u8, i2c: &mut I2c) -> u8 { + type CodecI2c = crate::i2c::I2cTimeout; + + fn read_reg(reg_addr: u8, i2c: &mut CodecI2c) -> Result { let mut buffer = [0u8; 1]; - i2c.write_read(DEV_ADDR, &[reg_addr], &mut buffer).unwrap(); - buffer[0] + i2c.write_read(DEV_ADDR, &[reg_addr], &mut buffer)?; + Ok(buffer[0]) } - // handle the error? - fn write_reg(reg_addr: u8, val: u8, i2c: &mut I2c) { - i2c.write(DEV_ADDR, &[reg_addr, val]).unwrap(); + fn write_reg(reg_addr: u8, val: u8, i2c: &mut CodecI2c) -> Result<(), crate::i2c::Error> { + i2c.write(DEV_ADDR, &[reg_addr, val]) } // POWER-ON RESET and EXTERNAL RESET Sequence @@ -536,31 +549,34 @@ impl Audio { // Slave address: 0b100011Nx // N = 0 (GND on hardware) // x = R/W (not passed to address param) - fn pcm3060_init(i2c: &mut I2c, delay: &mut impl DelayMs) { + fn pcm3060_init( + i2c: &mut CodecI2c, + delay: &mut impl DelayMs, + ) -> Result<(), crate::i2c::Error> { // MSRT - let mut sys_ctrl = read_reg(REG_SYS_CTRL, i2c); + let mut sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; sys_ctrl &= !MASK_MRST; - write_reg(REG_SYS_CTRL, sys_ctrl, i2c); + write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; delay.delay_ms(4); // SRST - sys_ctrl = read_reg(REG_SYS_CTRL, i2c); + sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; sys_ctrl &= !MASK_SRST; - write_reg(REG_SYS_CTRL, sys_ctrl, i2c); + write_reg(REG_SYS_CTRL, sys_ctrl, i2c)?; delay.delay_ms(4); // ADC/DAC Format set to 24-bit LJ - let mut dac_ctrl = read_reg(REG_DAC_CTRL1, i2c); - let mut adc_ctrl = read_reg(REG_ADC_CTRL1, i2c); + let mut dac_ctrl = read_reg(REG_DAC_CTRL1, i2c)?; + let mut adc_ctrl = read_reg(REG_ADC_CTRL1, i2c)?; dac_ctrl |= MASK_FMT; adc_ctrl |= MASK_FMT; - write_reg(REG_DAC_CTRL1, dac_ctrl, i2c); - write_reg(REG_ADC_CTRL1, adc_ctrl, i2c); + write_reg(REG_DAC_CTRL1, dac_ctrl, i2c)?; + write_reg(REG_ADC_CTRL1, adc_ctrl, i2c)?; // Disable Powersave for ADC/DAC - sys_ctrl = read_reg(REG_SYS_CTRL, i2c); + sys_ctrl = read_reg(REG_SYS_CTRL, i2c)?; sys_ctrl &= !(MASK_ADC_PSV | MASK_DAC_PSV); - write_reg(REG_SYS_CTRL, sys_ctrl, i2c); + write_reg(REG_SYS_CTRL, sys_ctrl, i2c) } info!("Start audio stream..."); diff --git a/src/devices/led_driver_pca9685.rs b/src/devices/led_driver_pca9685.rs index 441057e..ad00ae5 100644 --- a/src/devices/led_driver_pca9685.rs +++ b/src/devices/led_driver_pca9685.rs @@ -1,9 +1,8 @@ #![allow(dead_code)] -use crate::hal; -use hal::i2c::I2c; -use hal::prelude::*; -use stm32h7xx_hal::prelude::_embedded_hal_blocking_i2c_Write as Write; +use cortex_m::peripheral::DWT; +use embedded_hal::blocking::delay::DelayMs; +use embedded_hal::blocking::i2c::Write; #[repr(C, packed)] #[derive(Clone, Copy, Debug)] @@ -41,11 +40,17 @@ impl Default for Pca9685TransmitBuffer { pub type DmaBuffer = [Pca9685TransmitBuffer; N]; -pub struct LedDriverPca9685 { - i2c: I2c, +const RETRY_MS: u32 = 1000; + +pub struct LedDriverPca9685 { + i2c: I, draw_buffer: Option<&'static mut [u8]>, transmit_buffer: Option<&'static mut [u8]>, addresses: [u8; NUM_DRIVERS], + /// Whether each driver has been initialized and has kept responding. + ready: [bool; NUM_DRIVERS], + /// DWT cycle count after which unready drivers are tried again. + retry_at: u32, } // Constants @@ -55,27 +60,21 @@ const PCA9685_MODE2: u8 = 0x01; const PRE_SCALE_MODE: u8 = 0xFE; const PCA9685_LED0: u8 = 0x06; -impl LedDriverPca9685 +impl LedDriverPca9685 where - I2c: Write, + I: Write, { + /// `i2c` must give up on a dead bus (see [`crate::i2c::I2cTimeout`]); the + /// HAL's own blocking I2C spins forever when no driver is connected. pub fn new( - mut i2c: I2c, + mut i2c: I, addresses: [u8; NUM_DRIVERS], dma_buffer_a: &'static mut DmaBuffer, dma_buffer_b: &'static mut DmaBuffer, ) -> Self { - // Init drivers - for &addr in &addresses { - let address = PCA9685_I2C_BASE_ADDRESS | addr; - let _ = i2c.write(address, &[PCA9685_MODE1, 0x00]); - crate::delay::CycleDelay::new().delay_ms(20u8); - let _ = i2c.write(address, &[PCA9685_MODE1, 0x00]); - crate::delay::CycleDelay::new().delay_ms(20u8); - let _ = i2c.write(address, &[PCA9685_MODE1, 0b00100000]); // Auto increment - crate::delay::CycleDelay::new().delay_ms(20u8); - // There are a few configurations in this register we may want to expose later. - let _ = i2c.write(address, &[PCA9685_MODE2, 0b00000110]); // OE hi-z, odrv=1 + let mut ready = [false; NUM_DRIVERS]; + for (ready, &addr) in ready.iter_mut().zip(&addresses) { + *ready = Self::init_driver(&mut i2c, addr, 20).is_ok(); } let len = NUM_DRIVERS * Pca9685TransmitBuffer::SIZE; @@ -98,9 +97,26 @@ where draw_buffer: Some(buffer_b_u8), transmit_buffer: Some(buffer_a_u8), addresses, + ready, + retry_at: DWT::cycle_count().wrapping_add(RETRY_MS * crate::MILICYCLES), } } + /// Wake one driver and set it up, stopping at the first write it doesn't + /// acknowledge. + fn init_driver(i2c: &mut I, addr: u8, settle_ms: u8) -> Result<(), I::Error> { + let address = PCA9685_I2C_BASE_ADDRESS | addr; + let mut delay = crate::delay::CycleDelay::new(); + i2c.write(address, &[PCA9685_MODE1, 0x00])?; + delay.delay_ms(settle_ms); + i2c.write(address, &[PCA9685_MODE1, 0x00])?; + delay.delay_ms(settle_ms); + i2c.write(address, &[PCA9685_MODE1, 0b00100000])?; // Auto increment + delay.delay_ms(settle_ms); + // There are a few configurations in this register we may want to expose later. + i2c.write(address, &[PCA9685_MODE2, 0b00000110]) // OE hi-z, odrv=1 + } + pub fn set_led(&mut self, led_index: usize, brightness: f32) { let driver_idx = led_index / 16; let led_idx_in_driver = led_index % 16; @@ -135,14 +151,34 @@ where } fn transmit(&mut self) { + let now = DWT::cycle_count(); + // Signed distance, so the counter's wrap is harmless. + let retry = (now.wrapping_sub(self.retry_at) as i32) >= 0; + if retry { + self.retry_at = now.wrapping_add(RETRY_MS * crate::MILICYCLES); + } + for driver_idx in 0..NUM_DRIVERS { + if !self.ready[driver_idx] { + // The oscillator needs 500us after waking; this runs in the + // main loop, so no longer than that. + if !retry + || Self::init_driver(&mut self.i2c, self.addresses[driver_idx], 1).is_err() + { + continue; + } + self.ready[driver_idx] = true; + } + let address = PCA9685_I2C_BASE_ADDRESS | self.addresses[driver_idx]; let buffer = self.transmit_buffer.as_mut().unwrap(); let start = driver_idx * Pca9685TransmitBuffer::SIZE; let end = start + Pca9685TransmitBuffer::SIZE; let bytes = &buffer[start..end]; - let _ = self.i2c.write(address, bytes); + if self.i2c.write(address, bytes).is_err() { + self.ready[driver_idx] = false; + } } } diff --git a/src/i2c.rs b/src/i2c.rs new file mode 100644 index 0000000..015fc8a --- /dev/null +++ b/src/i2c.rs @@ -0,0 +1,245 @@ +//! Blocking I2C with timeouts. +//! +//! The HAL's blocking transactions can spin forever in certain circumstances. +//! This provides the same functionality with hardcoded 2ms timeouts. + +use crate::hal; +use core::ops::Deref; +use cortex_m::peripheral::DWT; +use embedded_hal::blocking::i2c::{Read, Write, WriteRead}; +use hal::i2c::I2c; +use hal::pac::i2c1::{isr, RegisterBlock}; + +/// How long a single step of a transaction may take before giving up. +pub const DEFAULT_TIMEOUT_US: u32 = 2_000; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + /// The addressed device or a data byte was not acknowledged. + NotAcknowledge, + /// A misplaced START or STOP was observed. + Bus, + /// Another master won the bus. + Arbitration, + /// A step of the transaction never completed. The peripheral has been reset. + Timeout, +} + +/// An I2C master whose transactions always return. +pub struct I2cTimeout { + i2c: I2C, + timeout_cycles: u32, +} + +impl I2cTimeout +where + I2C: Deref, +{ + /// Take over a peripheral the HAL has already clocked and timed, e.g. + /// `I2cTimeout::new(device.I2C1.i2c(pins, freq, rec, &clocks))`. + pub fn new(i2c: I2c) -> Self + where + I2c: Free, + { + Self::with_timeout_us(i2c, DEFAULT_TIMEOUT_US) + } + + pub fn with_timeout_us(i2c: I2c, timeout_us: u32) -> Self + where + I2c: Free, + { + Self { + i2c: i2c.free_peripheral(), + timeout_cycles: timeout_us.saturating_mul(crate::MICROCYCLES), + } + } + + fn deadline(&self) -> u32 { + DWT::cycle_count().wrapping_add(self.timeout_cycles) + } + + fn expired(deadline: u32) -> bool { + // Signed distance, so the counter's wrap (~9 s at 480 MHz) is harmless. + (DWT::cycle_count().wrapping_sub(deadline) as i32) >= 0 + } + + /// Clear PE: the state machine and flags reset and SCL/SDA are released. + /// PE must stay low for at least three APB cycles. + fn reset(&mut self) { + self.i2c.cr1.modify(|_, w| w.pe().clear_bit()); + for _ in 0..3 { + let _ = self.i2c.cr1.read(); + } + self.i2c.cr1.modify(|_, w| w.pe().set_bit()); + } + + /// Spin until `done`, an error flag, or the deadline. + fn wait(&mut self, done: impl Fn(&isr::R) -> bool) -> Result<(), Error> { + let deadline = self.deadline(); + loop { + let isr = self.i2c.isr.read(); + + if done(&isr) { + return Ok(()); + } else if isr.berr().is_error() { + self.i2c.icr.write(|w| w.berrcf().set_bit()); + return Err(Error::Bus); + } else if isr.arlo().is_lost() { + self.i2c.icr.write(|w| w.arlocf().set_bit()); + return Err(Error::Arbitration); + } else if isr.nackf().bit_is_set() { + // The hardware sends STOP on its own after a NACK. + self.i2c + .icr + .write(|w| w.stopcf().set_bit().nackcf().set_bit()); + self.flush_txdr(); + return Err(Error::NotAcknowledge); + } else if Self::expired(deadline) { + self.reset(); + return Err(Error::Timeout); + } + } + } + + fn flush_txdr(&mut self) { + if self.i2c.isr.read().txis().bit_is_set() { + self.i2c.txdr.write(|w| w.txdata().bits(0)); + } + if self.i2c.isr.read().txe().is_not_empty() { + self.i2c.isr.write(|w| w.txe().set_bit()); + } + } + + fn start(&mut self, addr: u8, length: usize, read: bool, autoend: bool) -> Result<(), Error> { + // a previous address phase may still be active for up to half a + // bus cycle + let deadline = self.deadline(); + while self.i2c.cr2.read().start().bit_is_set() { + if Self::expired(deadline) { + self.reset(); + return Err(Error::Timeout); + } + } + + self.i2c.cr2.write(|w| { + let w = w + .sadd() + .bits(u16::from(addr) << 1) + .add10() + .clear_bit() + .nbytes() + .bits(length as u8) + .start() + .set_bit() + .autoend() + .bit(autoend); + match read { + true => w.rd_wrn().read(), + false => w.rd_wrn().write(), + } + }); + + Ok(()) + } + + fn send(&mut self, bytes: &[u8]) -> Result<(), Error> { + for byte in bytes { + // START (or the previous byte) was acknowledged. + self.wait(|isr| isr.txis().is_empty())?; + self.i2c.txdr.write(|w| w.txdata().bits(*byte)); + } + Ok(()) + } + + fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + for byte in buffer { + self.wait(|isr| isr.rxne().is_not_empty())?; + *byte = self.i2c.rxdr.read().rxdata().bits(); + } + Ok(()) + } +} + +impl Write for I2cTimeout +where + I2C: Deref, +{ + type Error = Error; + + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + assert!(!bytes.is_empty() && bytes.len() < 256); + + self.start(addr, bytes.len(), false, false)?; + self.send(bytes)?; + self.wait(|isr| isr.tc().is_complete())?; + self.i2c.cr2.write(|w| w.stop().set_bit()); + self.wait(|isr| isr.busy().is_not_busy()) + } +} + +impl WriteRead for I2cTimeout +where + I2C: Deref, +{ + type Error = Error; + + fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + assert!(!bytes.is_empty() && bytes.len() < 256); + assert!(!buffer.is_empty() && buffer.len() < 256); + + self.start(addr, bytes.len(), false, false)?; + self.send(bytes)?; + self.wait(|isr| isr.tc().is_complete())?; + // Repeated START; the previous address phase is over, so no wait. + self.i2c.cr2.write(|w| { + w.sadd() + .bits(u16::from(addr) << 1 | 1) + .add10() + .clear_bit() + .rd_wrn() + .read() + .nbytes() + .bits(buffer.len() as u8) + .start() + .set_bit() + .autoend() + .set_bit() + }); + self.receive(buffer)?; + self.wait(|isr| isr.busy().is_not_busy()) + } +} + +impl Read for I2cTimeout +where + I2C: Deref, +{ + type Error = Error; + + fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + assert!(!buffer.is_empty() && buffer.len() < 256); + + self.start(addr, buffer.len(), true, true)?; + self.receive(buffer)?; + self.wait(|isr| isr.busy().is_not_busy()) + } +} + +/// The HAL's `I2c::free` is per-instance rather than generic. +pub trait Free { + fn free_peripheral(self) -> I2C; +} + +macro_rules! free { + ($($I2CX:ident),+) => { + $( + impl Free for I2c { + fn free_peripheral(self) -> hal::pac::$I2CX { + self.free().0 + } + } + )+ + }; +} + +free!(I2C1, I2C2, I2C3, I2C4); diff --git a/src/lib.rs b/src/lib.rs index 8ac8edd..08156b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod devices; pub mod flash; pub mod gpio; pub mod hid; +pub mod i2c; pub mod logger; pub mod mpu; pub mod prelude; From 59ee4eaef53cf6d56060e281730f814039625e47 Mon Sep 17 00:00:00 2001 From: Nic Date: Tue, 20 Oct 2026 19:00:07 -0600 Subject: [PATCH 52/52] configure pll2r --- src/system/mod.rs | 6 +++--- src/system/patch_sm.rs | 2 +- src/system/seed.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/system/mod.rs b/src/system/mod.rs index e84c415..00bb358 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -38,9 +38,9 @@ const PLL1_P_HZ: Hertz = CLOCK_RATE_HZ; const PLL1_Q_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 18); const PLL1_R_HZ: Hertz = Hertz::from_raw(CLOCK_RATE_HZ.raw() / 32); // PLL2 -const PLL2_P_HZ: Hertz = Hertz::from_raw(4_000_000); +const PLL2_P_HZ: Hertz = Hertz::from_raw(40_000_000); const PLL2_Q_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 2); // No divder given, what's the default? -const PLL2_R_HZ: Hertz = Hertz::from_raw(PLL2_P_HZ.raw() / 4); // No divder given, what's the default? +const PLL2_R_HZ: Hertz = Hertz::from_raw(200_000_000); fn init_clocks( mut pwr: stm32::PWR, @@ -69,7 +69,7 @@ fn init_clocks( // PLL2 .pll2_p_ck(PLL2_P_HZ) // Default adc_ker_ck_input // .pll2_q_ck(PLL2_Q_HZ) - // .pll2_r_ck(PLL2_R_HZ) + .pll2_r_ck(PLL2_R_HZ) // PLL3 .pll3_strategy(rcc::PllConfigStrategy::Fractional) .pll3_p_ck(PLL3_P_HZ) // used for SAI1 diff --git a/src/system/patch_sm.rs b/src/system/patch_sm.rs index 775d090..7223fa3 100644 --- a/src/system/patch_sm.rs +++ b/src/system/patch_sm.rs @@ -291,7 +291,7 @@ impl PatchSmSystem { let (adc1, adc2) = adc::adc12( resources.adc1, resources.adc2, - 4.MHz(), + 2.MHz(), &mut delay, resources.adc12_rec, resources.clocks, diff --git a/src/system/seed.rs b/src/system/seed.rs index c67c39c..89ddb09 100644 --- a/src/system/seed.rs +++ b/src/system/seed.rs @@ -423,7 +423,7 @@ impl SeedSystem { let (adc1, adc2) = adc::adc12( resources.adc1, resources.adc2, - 4.MHz(), + 2.MHz(), &mut delay, resources.adc12_rec, resources.clocks,