Created
April 12, 2020 17:45
-
-
Save justacec/b219135667c8c57ebacfedffa3b67f27 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "stm32f407_play" | |
version = "0.1.0" | |
authors = ["Justace Clutter <justacec@gmail.com>"] | |
edition = "2018" | |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
[dependencies] | |
panic-abort = "0.3" | |
panic-semihosting = "0.5" | |
cortex-m-semihosting = "0.3" | |
cortex-m = "0.6" | |
cortex-m-rt = "0.6" | |
cortex-m-rtfm = "0.5" | |
rtfm-core = "0.3" | |
embedded-hal = "0.2.3" | |
stm32f4xx-hal = {version = "0.7", features = ["stm32f407", "rt"]} | |
stm32f4 = "0.10" | |
bbqueue = "0.4" | |
num-derive = {version = "0.3", default-features = false } | |
num-traits = {version = "0.2", default-features = false } | |
num = {version = "0.2", default-features = false } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![no_std] | |
#![no_main] | |
extern crate cortex_m_rt as rt; | |
extern crate stm32f4; | |
//extern crate panic_abort; | |
extern crate panic_semihosting; | |
use cortex_m::asm; | |
use rtfm::app; | |
use rtfm::cyccnt::{U32Ext}; | |
use stm32f4xx_hal::{ | |
prelude::*, | |
stm32, | |
gpio::gpioa::{PA6}, | |
gpio::gpiob::{PB11}, | |
gpio::gpioe::{PE4}, | |
gpio::{ExtiPin, Edge, Output, PushPull, Input, PullUp}, | |
timer::{Timer, Event}, | |
spi::{Phase, Polarity}, | |
}; | |
use bbqueue::{BBBuffer, GrantR, GrantW, ConstBBBuffer, Consumer, Producer, consts::*}; | |
use num_derive::FromPrimitive; | |
// Define the possible SPI states | |
#[derive(PartialEq)] | |
pub enum SPIState { | |
WaitForCommand, | |
WaitForResponse | |
} | |
#[derive(FromPrimitive)] | |
pub enum Command { | |
None = 0x00, | |
Echo = 0x01, | |
} | |
static rx_buffer: BBBuffer<U64> = BBBuffer( ConstBBBuffer::new() ); | |
static tx_buffer: BBBuffer<U64> = BBBuffer( ConstBBBuffer::new() ); | |
#[app(device = stm32f4xx_hal::stm32, peripherals = true, monotonic = rtfm::cyccnt::CYCCNT)] | |
const APP: () = { | |
struct Resources { | |
spi: stm32::SPI2, | |
#[init(SPIState::WaitForCommand)] | |
spi_state: SPIState, | |
spi_busy: PB11<Output<PushPull>>, | |
dma: stm32::DMA1, | |
led: PA6<Output<PushPull>>, | |
#[init(false)] | |
led_state: bool, | |
button: PE4<Input<PullUp>>, | |
timer: Timer<stm32::TIM2>, | |
rx_buffer_producer: Producer<'static, U64>, | |
rx_buffer_consumer: Consumer<'static, U64>, | |
tx_buffer_producer: Producer<'static, U64>, | |
tx_buffer_consumer: Consumer<'static, U64>, | |
receive_read_grant: Option<GrantR<'static, U64>>, | |
receive_write_grant: Option<GrantW<'static, U64>>, | |
transmit_read_grant: Option<GrantR<'static, U64>>, | |
transmit_write_grant: Option<GrantW<'static, U64>> | |
} | |
// #[init(schedule=[flash_it])] | |
#[init()] | |
fn init(cx: init::Context) -> init::LateResources { | |
// Cortex-M peripherals | |
let mut _core: cortex_m::Peripherals = cx.core; | |
// Device specific peripherals | |
let mut _device: stm32::Peripherals = cx.device; | |
_device.RCC.apb2enr.write(|w| w.syscfgen().enabled()); | |
// Specify and fix the clock speeds | |
let rcc = _device.RCC.constrain(); | |
let _clocks = rcc.cfgr | |
.use_hse(8.mhz()) | |
.sysclk(168.mhz()) | |
// .pclk1(32.mhz()) | |
.freeze(); | |
// Initialize (enable) the monotonic timer (CYCCNT) | |
_core.DCB.enable_trace(); | |
_core.DWT.enable_cycle_counter(); | |
let gpioa = _device.GPIOA.split(); | |
let gpiob = _device.GPIOB.split(); | |
let mut led = gpioa.pa6.into_push_pull_output(); | |
led.set_high().unwrap(); | |
let gpioe = _device.GPIOE.split(); | |
let mut button = gpioe.pe4.into_pull_up_input(); | |
button.make_interrupt_source(&mut _device.SYSCFG); | |
button.enable_interrupt(&mut _device.EXTI); | |
button.trigger_on_edge(&mut _device.EXTI, Edge::FALLING); | |
// Estblish a debounce timer | |
let timer = Timer::tim2(_device.TIM2, ((1.0/(50.0 * 1.0e-3)) as u32).hz(), _clocks); | |
cortex_m::peripheral::NVIC::mask(stm32::Interrupt::TIM2); | |
unsafe{ | |
cortex_m::peripheral::NVIC::unmask(stm32::Interrupt::EXTI4); | |
_core.NVIC.set_priority(stm32::Interrupt::EXTI4, 3); | |
}; | |
cortex_m::peripheral::NVIC::unpend(stm32::Interrupt::TIM2); | |
cortex_m::peripheral::NVIC::unpend(stm32::Interrupt::EXTI4); | |
// Manually create a slave SPI device | |
// Manually configure the SPI2 deivce to be a slave | |
let _ack = gpiob.pb11.into_push_pull_output(); | |
let mut _ss = gpiob.pb12.into_pull_up_input(); | |
let _sck = gpiob.pb13.into_alternate_af5(); | |
let _miso = gpiob.pb14.into_alternate_af5(); | |
let _mosi = gpiob.pb15.into_alternate_af5(); | |
// Turn on the SPI device clock | |
let rcc_bus_pointer: *const stm32f4::stm32f407::rcc::RegisterBlock = stm32f4xx_hal::stm32::RCC::ptr(); | |
unsafe{ | |
(*rcc_bus_pointer).apb1enr.modify(|_, w| w.spi2en().set_bit()); | |
(*rcc_bus_pointer).apb1rstr.modify(|_, w| w.spi2rst().set_bit()); | |
(*rcc_bus_pointer).apb1rstr.modify(|_, w| w.spi2rst().clear_bit()); | |
} | |
// Configure the SPI device registers | |
_device.SPI2.cr1.write(|w| w | |
// 8-bit frames | |
.dff().clear_bit() | |
.cpol().bit(Polarity::IdleLow == Polarity::IdleHigh) | |
.cpha().bit(Phase::CaptureOnFirstTransition == Phase::CaptureOnSecondTransition) | |
// MSB transmitted first | |
.lsbfirst().clear_bit() | |
// Use hardware SS line | |
.ssm().clear_bit() | |
// Set as slave mode | |
.mstr().clear_bit() | |
// Enable the peripheral | |
.spe().set_bit() | |
); | |
// Disable the TI mode | |
_device.SPI2.cr2.write(|w| w | |
.frf().clear_bit() | |
); | |
// Enable the receive interrupt | |
_device.SPI2.cr2.write(|w| w | |
.rxneie().set_bit() | |
); | |
// Enable the SS line to trigger SPI data transmission stops | |
_ss.make_interrupt_source(&mut _device.SYSCFG); | |
_ss.enable_interrupt(&mut _device.EXTI); | |
_ss.trigger_on_edge(&mut _device.EXTI, Edge::RISING); | |
// Split out the BBQueue Buffers | |
let (mut rx_prod, rx_cons) = rx_buffer.try_split().unwrap(); | |
let (tx_prod, tx_cons) = tx_buffer.try_split().unwrap(); | |
// Execute the initial BBQueue grant | |
let mut rx_grant = rx_prod.grant_exact(32).unwrap(); | |
// Enable the DMA (Proceedure outlined on page 322 of the RM0090 from ST) | |
// Turn on the DMA clock | |
let rcc_bus_pointer: *const stm32f4::stm32f407::rcc::RegisterBlock = stm32f4xx_hal::stm32::RCC::ptr(); | |
unsafe{ | |
(*rcc_bus_pointer).ahb1enr.modify(|_, w| w.dma1en().set_bit()); | |
(*rcc_bus_pointer).ahb1rstr.modify(|_, w| w.dma1rst().set_bit()); | |
(*rcc_bus_pointer).ahb1rstr.modify(|_, w| w.dma1rst().clear_bit()); | |
} | |
// Receive Stream | |
if _device.DMA1.st[3].cr.read().en() == true { | |
_device.DMA1.st[3].cr.write(|w| w.en().set_bit()); | |
// Should wait here, and double check that the bit went to zero | |
// after a read, but this is the startup process and I am pretty | |
// sure there are no stale operations going on here to watch out | |
// for. | |
} | |
unsafe { | |
_device.DMA1.st[3].par.write(|w| w.bits(0x4000_3800 + 0x0c)); // Set the peripheral address (SPI2 DR) | |
_device.DMA1.st[3].m0ar.write(|w| w.bits(rx_grant.buf().as_ptr() as u32)); // Set the memmory address | |
_device.DMA1.st[3].ndtr.write(|w| w.bits(32)); // Set number of bytes to read to 32 | |
_device.DMA1.st[3].cr.write(|w| w.chsel().bits(0)); // Use Channel 0 | |
_device.DMA1.st[3].cr.write(|w| w.pfctrl().clear_bit()); // Explicitly disable peripheral flow control | |
_device.DMA1.st[3].cr.write(|w| w.pl().bits(0x10)); // Set the priority to 2 (TX will have 3) | |
_device.DMA1.st[3].fcr.write(|w| w.dmdis().clear_bit()); // Ensure the FIFO is disabled | |
_device.DMA1.st[3].cr.write(|w| w.msize().bits(8)); // Set memory chunk size | |
_device.DMA1.st[3].cr.write(|w| w.psize().bits(8)); // Set peripheral chunk size | |
_device.DMA1.st[3].cr.write(|w| w.minc().set_bit()); // The memory location should shift after each byte | |
_device.DMA1.st[3].cr.write(|w| w.pinc().clear_bit()); // The peripheral location should shift after each byte | |
_device.DMA1.st[3].cr.write(|w| w.dir().bits(0x00)); // Set the direction to "Peripheral to Memory" | |
} | |
// let a = 1 + _device.DMA1.st[3]; | |
// Transmit Stream | |
if _device.DMA1.st[4].cr.read().en() == true { | |
_device.DMA1.st[4].cr.write(|w| w.en().set_bit()); | |
// Should wait here, and double check that the bit went to zero | |
// after a read, but this is the startup process and I am pretty | |
// sure there are no stale operations going on here to watch out | |
// for. | |
} | |
unsafe { | |
_device.DMA1.st[4].par.write(|w| w.bits(0x4000_3800 + 0x0c)); // Set the peripheral address (SPI2 DR) | |
_device.DMA1.st[4].ndtr.write(|w| w.bits(32)); // Set number of bytes to read to 32 | |
_device.DMA1.st[4].cr.write(|w| w.chsel().bits(0)); // Use Channel 0 | |
_device.DMA1.st[4].cr.write(|w| w.pfctrl().clear_bit()); // Explicitly disable peripheral flow control | |
_device.DMA1.st[4].cr.write(|w| w.pl().bits(0x11)); // Set the priority to 3 | |
_device.DMA1.st[4].fcr.write(|w| w.dmdis().clear_bit()); // Ensure the FIFO is disabled | |
_device.DMA1.st[4].cr.write(|w| w.msize().bits(8)); // Set memory chunk size | |
_device.DMA1.st[4].cr.write(|w| w.psize().bits(8)); // Set peripheral chunk size | |
_device.DMA1.st[4].cr.write(|w| w.minc().set_bit()); // The memory location should shift after each byte | |
_device.DMA1.st[4].cr.write(|w| w.pinc().clear_bit()); // The peripheral location should shift after each byte | |
_device.DMA1.st[4].cr.write(|w| w.dir().bits(0x01)); // Set the direction to "Memory to Peripheral" | |
} | |
// Enable the receive DMA channels (transmit will only be enabled when data is ready to go out) | |
_device.DMA1.st[3].cr.write(|w| w.en().set_bit()); | |
// _device.SPI2.cr2.write(|w| w.rxdmaen().set_bit()); | |
// _device.SPI2.cr2.write(|w| w.txdmaen().set_bit()); | |
init::LateResources { | |
spi: _device.SPI2, | |
spi_busy: _ack, | |
dma: _device.DMA1, | |
led: led, | |
button: button, | |
timer: timer, | |
rx_buffer_producer: rx_prod, | |
rx_buffer_consumer: rx_cons, | |
tx_buffer_producer: tx_prod, | |
tx_buffer_consumer: tx_cons, | |
receive_read_grant: None, | |
receive_write_grant: Some(rx_grant), | |
transmit_read_grant: None, | |
transmit_write_grant: None | |
} | |
} | |
#[idle()] | |
fn idle(_cx: idle::Context) -> ! { | |
loop { | |
asm::nop(); | |
} | |
} | |
#[task(binds=EXTI4, resources=[led_state, led, button, timer])] | |
fn exti4_ISR(cx: exti4_ISR::Context) { | |
cortex_m::peripheral::NVIC::mask(stm32::Interrupt::EXTI4); | |
cx.resources.timer.start(((1.0/(50.0 * 1.0e-3)) as u32).hz()); | |
cx.resources.timer.listen(Event::TimeOut); | |
cx.resources.button.clear_interrupt_pending_bit(); | |
unsafe { stm32::NVIC::unmask(stm32::Interrupt::TIM2) }; | |
match cx.resources.led_state { | |
true => { | |
cx.resources.led.set_low().unwrap(); | |
*cx.resources.led_state = false; | |
}, | |
false => { | |
cx.resources.led.set_high().unwrap(); | |
*cx.resources.led_state = true; | |
} | |
} | |
} | |
// This function is used for the switch debounceing proceedure | |
#[task(binds=TIM2, resources=[timer])] | |
fn tim2_ISR(cx: tim2_ISR::Context) { | |
cortex_m::peripheral::NVIC::mask(stm32::Interrupt::TIM2); | |
unsafe { cortex_m::peripheral::NVIC::unmask(stm32::Interrupt::EXTI4) }; | |
cx.resources.timer.clear_interrupt(Event::TimeOut); | |
} | |
#[task(resources = [dma, spi_state, spi_busy, tx_buffer_producer, rx_buffer_consumer, tx_buffer_consumer, transmit_read_grant, transmit_write_grant])] | |
fn process_command(cx: process_command::Context) { | |
// Set the SPI busy line to tell the master we are thinking... | |
cx.resources.spi_busy.set_high(); | |
// Extract the data from the incomming stream | |
// This needs to be updated to support the potential that the data was wrapped. | |
// Essentailly, it is a read and copy from until the read grant returns a None. | |
// I am not sure that this needs to me implemented here as I use the grant_exact | |
// in the producer. | |
let mut incomming_grant = cx.resources.rx_buffer_consumer.read().unwrap(); | |
let command = num::FromPrimitive::from_u8(incomming_grant[0]).unwrap_or(Command::None); | |
let data = &incomming_grant[1..incomming_grant.len()]; | |
// Process the command | |
match command { | |
Command::None => {}, | |
Command::Echo => { | |
// Request a grant for the transmit queue the same size as the incomming data | |
*cx.resources.transmit_write_grant = Some(cx.resources.tx_buffer_producer.grant_exact(data.len()).unwrap()); | |
// Copy the data into the buffer | |
let tx_buf = cx.resources.transmit_write_grant.unwrap().buf(); | |
tx_buf.copy_from_slice(data); | |
} | |
} | |
// Set the return buffer into the DMA register | |
// Release the incomming receive grant (this frees of that space in the BBQueue) | |
incomming_grant.release(incomming_grant.len()); | |
// Set the spi_state to return data | |
match *cx.resources.transmit_write_grant { | |
Some(write_grant) => { | |
// This is the case that there is data to return | |
// Set the spi_mode to the correct state | |
*cx.resources.spi_state = SPIState::WaitForResponse; | |
// Commit the write | |
cx.resources.transmit_write_grant.unwrap().commit(data.len()); | |
*cx.resources.transmit_write_grant = None; | |
// Request a read grant from the tranmission consumer | |
*cx.resources.transmit_read_grant = Some(cx.resources.tx_buffer_consumer.read().unwrap()); | |
// Set the DMA pointer | |
cx.resources.dma.st[3].m0ar.write(|w| w.bits(cx.resources.transmit_read_grant.unwrap().buf().as_ptr() as u32)); | |
// Enable the DMA | |
cx.resources.dma.st[3].cr.write(|w| w.en().set_bit()); | |
}, | |
None => { | |
// Enable the DMA | |
cx.resources.dma.st[4].cr.write(|w| w.en().set_bit()); | |
} | |
} | |
// Set the SPI busy line to low to tell the master we are ready to send him some data (If there is data he is expecting) | |
cx.resources.spi_busy.set_high(); | |
} | |
// This function is called when the SPI master stops talking to the slave and sets the NSS line high | |
// This function will then call the process command function | |
// #[task(binds=EXTI15_10, spawn = [process_spi_command], resources = [spi_state, spi, tx_buffer, dma_tx_buffer, rx_buffer, dma_rx_buffer, led, dma_channels])] | |
#[task(binds=EXTI15_10, spawn=[process_command], resources = [dma, spi, spi_state, rx_buffer_producer, receive_write_grant, transmit_read_grant])] | |
fn spi_complete(cx: spi_complete::Context) { | |
// hprintln!("Counter: {:?}", cx.resources.enc_a.get_count()); | |
let exti_pointer: *const stm32f4::stm32f407::exti::RegisterBlock = stm32f4xx_hal::stm32::EXTI::ptr(); | |
unsafe { | |
(*exti_pointer).pr.modify(|_,w| w.pr12().set_bit()); | |
} | |
// Stop both of the DMA channels | |
// I would really like to find a way to store the stream instead of the full DMA device. (hardcoding has always been bad) | |
cx.resources.dma.st[3].cr.write(|w| w.en().clear_bit()); | |
cx.resources.dma.st[4].cr.write(|w| w.en().clear_bit()); | |
match cx.resources.spi_state { | |
SPIState::WaitForCommand => { | |
// Commit the data in the receiver buffer | |
let grant = cx.resources.receive_write_grant.unwrap(); | |
grant.commit(32 - cx.resources.dma.st[3].ndtr.read().bits() as usize); | |
*cx.resources.receive_write_grant = None; | |
// Spawn the process command function | |
cx.spawn.process_command().unwrap(); | |
}, | |
SPIState::WaitForResponse => { | |
// At this point the return data been transmitted and we need to clean up | |
// and get ready for the next outgoing transmission | |
// Clear the SPI output buffer | |
cx.resources.spi.dr.write(|w| unsafe { w.bits(0 as u32) } ); | |
// Release the tx_buffer_consumer grant | |
// cx.resources.transmit_read_grant.unwrap().release(); | |
// Set the state back to waiting on command | |
*cx.resources.spi_state = SPIState::WaitForCommand; | |
} | |
} | |
// Call the process command function | |
/* | |
cx.resources.dma_channels.4.stop(); | |
cx.resources.dma_channels.5.stop(); | |
match cx.resources.spi_state { | |
SPIState::WaitForCommand => { | |
// Copy the contents of the dma input buffer to the rx buffer | |
cx.resources.rx_buffer.clear(); | |
for i in 0..(32 - cx.resources.dma_channels.4.get_ndtr()) { | |
cx.resources.rx_buffer.push(cx.resources.dma_rx_buffer[i as usize]).unwrap(); | |
} | |
cx.spawn.process_spi_command().unwrap(); | |
}, | |
SPIState::WaitForResponse => { | |
// Clear the SPI output buffer | |
cx.resources.spi.dr.write(|w| unsafe { w.bits(0 as u32) } ); | |
// Clear the response buffer | |
cx.resources.tx_buffer.clear(); | |
for i in 0..32 { | |
cx.resources.dma_tx_buffer[i] = 0; | |
} | |
*cx.resources.spi_state = SPIState::WaitForCommand; | |
} | |
} | |
cx.resources.dma_channels.4.set_transfer_length(32); | |
cx.resources.dma_channels.4.start(); | |
*/ | |
} | |
/* | |
#[task(schedule = [flash_it], resources = [led_state, led])] | |
fn flash_it(cx: flash_it::Context) { | |
// cx.schedule.flash_it(cx.scheduled + 84_000_000.cycles()).unwrap(); | |
} | |
*/ | |
extern "C" { | |
fn USART2(); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment