Skip to content

Instantly share code, notes, and snippets.

@justacec
Created April 12, 2020 17:45
Show Gist options
  • Save justacec/b219135667c8c57ebacfedffa3b67f27 to your computer and use it in GitHub Desktop.
Save justacec/b219135667c8c57ebacfedffa3b67f27 to your computer and use it in GitHub Desktop.
[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 }
#![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