diff --git a/Cargo.lock b/Cargo.lock index bc8866ca69a7cf7b1d323bcb35c950eda27f72c9..c87e0125df2c7164cf8a89e0843babc049345169 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,6 +270,7 @@ dependencies = [ "rf4463", "ringbuffer", "stm32f4xx-hal", + "systick-monotonic", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 91a85a69c1857ef4b220040e2d055a3b58a9e233..567f884876b60bf51488e4e84a8bb8883a5c19ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,5 @@ panic-semihosting = "0.6" rf4463 = { path = "../rf4463" } cortex-m-rtic = { version = "1.1.4" } ringbuffer = { version = "0.13.0", default_features = false } +systick-monotonic = "1.0.1" #panic-reset = "0.1.1" diff --git a/src/main.rs b/src/main.rs index 9354cafb09bdeb7d9e81b9bfa33ca6d1ffb4c4a8..d87ee617c10314de8f468d2596832e1c8265dfd2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,26 +8,38 @@ extern crate panic_semihosting; extern crate stm32f4xx_hal as hal; mod packet; +mod spi_data; -#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true)] +#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [USART6])] mod app { - use hal::block; + use hal::dma; use hal::gpio::{self, Speed}; - use hal::pac::{SPI1, TIM5, USART1}; + use hal::pac::{self, SPI1, TIM5}; use hal::prelude::*; - use hal::serial::{Config, Event, Serial}; + use hal::spi; use hal::spi::{Mode, Phase, Polarity, Spi}; use hal::timer::Delay; use rf4463::config::RADIO_CONFIG_500_2; use rf4463::Rf4463; use ringbuffer::{ConstGenericRingBuffer, RingBuffer, RingBufferRead, RingBufferWrite}; + use systick_monotonic::*; use crate::packet::Packet; use crate::packet::PACKET_LEN; + use crate::spi_data::{self, SpiData}; // in # packets const BUFFER_LEN: usize = 50; + // in bytes + // explicitly make this smaller than a packet so that we don't buffer things in the tx DMA for a while + const PI_RX_BUFFER_LEN: usize = 256; + + // in bytes + const PI_TX_BUFFER_LEN: usize = spi_data::LEN; + + const SYS_CLK: u32 = 100_000_000; + const MODE: Mode = Mode { polarity: Polarity::IdleLow, phase: Phase::CaptureOnFirstTransition, @@ -41,21 +53,31 @@ mod app { Delay<TIM5, 1000000>, >; + type RxTransfer = dma::Transfer< + dma::StreamX<pac::DMA1, 3>, + 0, + spi::Rx<pac::SPI2>, + dma::PeripheralToMemory, + &'static mut [u8; PI_RX_BUFFER_LEN], + >; + + type TxTransfer = dma::Transfer< + dma::StreamX<pac::DMA1, 4>, + 0, + spi::Tx<pac::SPI2>, + dma::MemoryToPeripheral, + &'static mut [u8; PI_TX_BUFFER_LEN], + >; + #[derive(Debug)] enum SlaveCmd { - BufferStatus, SendPacket, - GetTemp, - Sync, } impl SlaveCmd { pub fn from_u8(i: u8) -> Option<Self> { match i { - 0x00 => Some(Self::BufferStatus), 0x01 => Some(Self::SendPacket), - 0x02 => Some(Self::GetTemp), - 0x03 => Some(Self::Sync), _ => None, } } @@ -70,17 +92,25 @@ mod app { #[shared] struct Shared { radio: Radio, - radio_temp: f32, tx_buf: ConstGenericRingBuffer<Packet, BUFFER_LEN>, + + pi_tx: TxTransfer, + other_tx_buf: Option<&'static mut [u8; PI_TX_BUFFER_LEN]>, + spi_data: SpiData, } #[local] struct Local { radio_irq: gpio::Pin<'B', 2, gpio::Input>, - usart: Serial<USART1>, state: SlaveState, + + pi_rx: RxTransfer, + other_rx_buf: Option<&'static mut [u8; PI_RX_BUFFER_LEN]>, } + #[monotonic(binds = SysTick, default = true)] + type Tonic = Systick<1000>; + #[init] fn init(mut ctx: init::Context) -> (Shared, Local, init::Monotonics) { let rcc = ctx.device.RCC.constrain(); @@ -92,12 +122,18 @@ mod app { let clocks = rcc .cfgr .use_hse(25.MHz()) - .sysclk(100.MHz()) + .sysclk(SYS_CLK.Hz()) .pclk1(48.MHz()) .pclk2(48.MHz()) .freeze(); let mut sys_cfg = ctx.device.SYSCFG.constrain(); + // setup our fake reset pin (temporary) + let mut reset_pin = gpiob.pb12.into_pull_up_input(); + reset_pin.make_interrupt_source(&mut sys_cfg); + reset_pin.enable_interrupt(&mut ctx.device.EXTI); + reset_pin.trigger_on_edge(&mut ctx.device.EXTI, gpio::Edge::Rising); + // setup 4463 spi let mosi = gpioa.pa7.into_alternate().speed(Speed::VeryHigh); let miso = gpioa.pa6.into_alternate().speed(Speed::VeryHigh); @@ -116,38 +152,93 @@ mod app { radio_irq.enable_interrupt(&mut ctx.device.EXTI); radio_irq.trigger_on_edge(&mut ctx.device.EXTI, gpio::Edge::Falling); - // setup Pi UART - let tx = gpioa.pa9; - let rx = gpioa.pa10; + // setup Pi SPI + let pi_mosi = gpiob.pb15.internal_resistor(gpio::Pull::Up); + let pi_miso = gpiob.pb14.internal_resistor(gpio::Pull::Down); + let pi_sclk = gpiob.pb13.internal_resistor(gpio::Pull::Down); + + // TODO nss? + + let mut pi_spi = ctx + .device + .SPI2 + .spi_slave((pi_sclk, pi_miso, pi_mosi, None), MODE); + pi_spi.set_internal_nss(false); + + let (tx, rx) = pi_spi.use_dma().txrx(); + + let streams = dma::StreamsTuple::new(ctx.device.DMA1); + + let spi_data = SpiData::new(0.0, true); + let tx_buffer = cortex_m::singleton!(: [u8; PI_TX_BUFFER_LEN] = spi_data.into()).unwrap(); + let other_tx_buf = + Some(cortex_m::singleton!(: [u8; PI_TX_BUFFER_LEN] = spi_data.into()).unwrap()); + + let mut pi_tx = dma::Transfer::init_memory_to_peripheral( + streams.4, + tx, + tx_buffer, + None, + dma::config::DmaConfig::default() + .memory_increment(true) + .fifo_enable(true) + .fifo_error_interrupt(true), + ); + + // Enable circular mode + // safety: probably? + unsafe { + let dma1 = &*pac::DMA1::ptr(); + dma1.st[4].cr.modify(|_, w| w.circ().set_bit()); + } + + pi_tx.start(|_tx| {}); - let mut usart = Serial::new( - ctx.device.USART1, - (tx, rx), - Config::default().baudrate(921_600.bps()), - &clocks, - ) - .unwrap(); + let rx_buffer = + cortex_m::singleton!(: [u8; PI_RX_BUFFER_LEN] = [0u8; PI_RX_BUFFER_LEN]).unwrap(); + let other_rx_buf = + Some(cortex_m::singleton!(: [u8; PI_RX_BUFFER_LEN] = [0u8; PI_RX_BUFFER_LEN]).unwrap()); + + let mut pi_rx = dma::Transfer::init_peripheral_to_memory( + streams.3, + rx, + rx_buffer, + None, + dma::config::DmaConfig::default() + .memory_increment(true) + .fifo_enable(true) + .fifo_error_interrupt(true) + .transfer_complete_interrupt(true), + ); + + pi_rx.start(|_rx| {}); - usart.listen(Event::Rxne); + // start our radio temp loop + get_radio_temp::spawn_after(1u64.secs()).unwrap(); ( Shared { radio, - radio_temp: 0.0, tx_buf: ConstGenericRingBuffer::new(), + + pi_tx, + other_tx_buf, + + spi_data, }, Local { radio_irq, - usart, state: SlaveState::Idle, + + pi_rx, + other_rx_buf, }, - init::Monotonics(), + init::Monotonics(Systick::new(ctx.core.SYST, SYS_CLK)), ) } - #[idle(shared=[radio, tx_buf, radio_temp])] + #[idle(shared=[radio, tx_buf, pi_tx, other_tx_buf, spi_data])] fn idle(mut ctx: idle::Context) -> ! { - let mut i = 0; let mut iterations_since_last_packet = 0; loop { if let Some(pkt) = ctx.shared.tx_buf.lock(|buf| buf.dequeue()) { @@ -176,61 +267,116 @@ mod app { } } + // update our SPI bus to say if we have free space or not + let free = ctx.shared.tx_buf.lock(|b| b.len() < b.capacity() - 2); + let mut new_spi_data = ctx.shared.spi_data.lock(|sd| *sd); + new_spi_data.set_free(free); + + set_spi_tx( + &mut ctx.shared.pi_tx, + &mut ctx.shared.other_tx_buf, + &mut ctx.shared.spi_data, + new_spi_data, + ); + iterations_since_last_packet = 0; } else { iterations_since_last_packet += 1; } + } + } - i += 1; + #[task(shared = [radio, spi_data, pi_tx, other_tx_buf])] + fn get_radio_temp(mut ctx: get_radio_temp::Context) { + // get temp every second - // get temp every 500 packets, or whenever when idle - if i >= 500 { - i = 0; + if let Ok(new_temp) = ctx.shared.radio.lock(|r| r.get_temp()) { + let mut new_spi_data = ctx.shared.spi_data.lock(|sd| *sd); + new_spi_data.set_temp(new_temp); - if let Ok(new_temp) = ctx.shared.radio.lock(|r| r.get_temp()) { - ctx.shared.radio_temp.lock(|cur_temp| *cur_temp = new_temp); - } - } + set_spi_tx( + &mut ctx.shared.pi_tx, + &mut ctx.shared.other_tx_buf, + &mut ctx.shared.spi_data, + new_spi_data, + ); } + + get_radio_temp::spawn_after(1u64.secs()).unwrap(); + } + + #[task(binds = DMA1_STREAM3, priority = 2, local = [state, pi_rx, other_rx_buf], shared = [tx_buf, pi_tx, other_tx_buf, spi_data])] + fn pi_spi_recv(mut ctx: pi_spi_recv::Context) { + let rx = ctx.local.pi_rx; + + let (old_buf, _) = rx + .next_transfer(ctx.local.other_rx_buf.take().unwrap()) + .unwrap(); + + for b in old_buf.iter() { + handle_incoming_byte( + *b, + ctx.local.state, + &mut ctx.shared.tx_buf, + &mut ctx.shared.pi_tx, + &mut ctx.shared.other_tx_buf, + &mut ctx.shared.spi_data, + ); + } + + *ctx.local.other_rx_buf = Some(old_buf); + + rx.clear_transfer_complete_interrupt(); + rx.clear_fifo_error_interrupt(); } - #[task(binds = USART1, priority = 2, local = [state, usart], shared = [tx_buf, radio_temp])] - fn usart1(mut ctx: usart1::Context) { - let state = ctx.local.state; - let usart = ctx.local.usart; - let mut buf = ctx.shared.tx_buf; + #[task(binds = DMA1_STREAM4, priority = 2, shared = [pi_tx])] + fn pi_spi_tx(mut ctx: pi_spi_tx::Context) { + ctx.shared.pi_tx.lock(|tx| tx.clear_fifo_error_interrupt()); + } + + #[task(binds = EXTI2, shared = [radio], local = [radio_irq])] + fn radio_irq(mut ctx: radio_irq::Context) { + ctx.shared.radio.lock(|r| r.interrupt()).unwrap(); + + let irq = ctx.local.radio_irq; + if irq.is_high() { + irq.clear_interrupt_pending_bit(); + } + } - let x = block!(usart.read()).unwrap(); + // temporary. Eventually we'll want the pi to be connected directly to the reset pin + #[task(binds = EXTI15_10, priority = 5)] + fn reset(_: reset::Context) { + cortex_m::peripheral::SCB::sys_reset(); + } + fn handle_incoming_byte< + T: rtic::Mutex<T = ConstGenericRingBuffer<Packet, BUFFER_LEN>>, + A: rtic::Mutex<T = TxTransfer>, + B: rtic::Mutex<T = Option<&'static mut [u8; PI_TX_BUFFER_LEN]>>, + C: rtic::Mutex<T = SpiData>, + >( + x: u8, + state: &mut SlaveState, + buf: &mut T, + tx: &mut A, + other_tx: &mut B, + spi_data: &mut C, + ) { match state { SlaveState::Idle => { if let Some(cmd) = SlaveCmd::from_u8(x) { match cmd { - SlaveCmd::BufferStatus => { - if buf.lock(|b| b.is_full()) { - block!(usart.write(0x01)).unwrap(); // not enough space for a new packet - } else { - block!(usart.write(0x02)).unwrap(); // safe to send a packet - } - } - SlaveCmd::SendPacket => { - if buf.lock(|b| b.len() >= b.capacity() - 1) { - block!(usart.write(0x01)).unwrap(); // not enough space for a new packet - } else { - block!(usart.write(0x02)).unwrap(); // safe to send a packet - } + let free = buf.lock(|b| b.len() < b.capacity() - 2); - *state = SlaveState::RecvPacket(Packet::default()); - } + let mut new_spi_data = spi_data.lock(|sd| *sd); + new_spi_data.set_free(free); - SlaveCmd::GetTemp => { - let temp = ctx.shared.radio_temp.lock(|x| *x); - usart.bwrite_all(&temp.to_le_bytes()).unwrap(); - } + set_spi_tx(tx, other_tx, spi_data, new_spi_data); - SlaveCmd::Sync => { - usart.bwrite_all(&[0x03]).unwrap(); + *state = SlaveState::RecvPacket(Packet::default()); } } } @@ -247,13 +393,32 @@ mod app { }; } - #[task(binds = EXTI2, shared = [radio], local = [radio_irq])] - fn radio_irq(mut ctx: radio_irq::Context) { - ctx.shared.radio.lock(|r| r.interrupt()).unwrap(); - - let irq = ctx.local.radio_irq; - if irq.is_high() { - irq.clear_interrupt_pending_bit(); + fn set_spi_tx< + A: rtic::Mutex<T = TxTransfer>, + B: rtic::Mutex<T = Option<&'static mut [u8; PI_TX_BUFFER_LEN]>>, + C: rtic::Mutex<T = SpiData>, + >( + tx: &mut A, + other_tx: &mut B, + spi_data: &mut C, + new_spi_data: SpiData, + ) { + let bytes: [u8; spi_data::LEN] = new_spi_data.into(); + + // if the new data is equal to the old data, do nothing + if spi_data.lock(|sd| <[u8; spi_data::LEN]>::from(*sd)) == bytes { + return; } + + spi_data.lock(|sd| *sd = new_spi_data); + + other_tx.lock(|t| t.as_mut().unwrap().clone_from_slice(&bytes)); + + other_tx.lock(|t| { + let (tx, _) = + tx.lock(|tx_transfer| tx_transfer.next_transfer(t.take().unwrap()).unwrap()); + + t.replace(tx); + }); } } diff --git a/src/spi_data.rs b/src/spi_data.rs new file mode 100644 index 0000000000000000000000000000000000000000..4c2655bb20ca054c77d346a86d1b971492741a20 --- /dev/null +++ b/src/spi_data.rs @@ -0,0 +1,43 @@ +// Represents the data that we repeatedly put on the SPI bus +pub const LEN: usize = 7; + +#[derive(Copy, Clone, Debug)] +pub struct SpiData { + temp: f32, + free_space: bool, +} + +impl SpiData { + pub fn new(temp: f32, free_space: bool) -> Self { + Self { temp, free_space } + } + + pub fn set_temp(&mut self, temp: f32) { + self.temp = temp + } + + pub fn set_free(&mut self, free_space: bool) { + self.free_space = free_space; + } + + pub fn free_space(&self) -> bool { + self.free_space + } +} + +// There's probably a better way to do this +// if the temp happens to have some 0xE5 or 0xAA/0x55 in there, it might be possible to cause problems? +// could just use 7 bits per byte and have the extra bit cleared in the data, and set in the pre/post-amble +impl From<SpiData> for [u8; LEN] { + fn from(data: SpiData) -> Self { + let free = if data.free_space { 0xAA } else { 0x55 }; + + let temp = data.temp.to_le_bytes(); + + let mut out = [0xE5, free, 0, 0, 0, 0, 0xE5]; + + out[2..6].copy_from_slice(&temp); + + out + } +}