Last active
April 29, 2020 21:21
-
-
Save justacec/3e54d4d7d7f3a3da860a5b40bc02a77e to your computer and use it in GitHub Desktop.
Use of the BBQueue Crate with the RTFM Framework
This file contains hidden or 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 <[email protected]>"] | |
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" | |
num-derive = {version = "0.3", default-features = false } | |
num-traits = {version = "0.2", default-features = false } | |
num = {version = "0.2", default-features = false } | |
bbqueue = {git="https://github.com/jamesmunns/bbqueue.git", branch = "sync-grants"} | |
#bbqueue = {path = "/users/justaceclutter/Source/bbqueue"} |
This file contains hidden or 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
[target.thumbv7m-none-eabi] | |
# uncomment this to make `cargo run` execute programs on QEMU | |
# runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" | |
[target.'cfg(all(target_arch = "arm", target_os = "none"))'] | |
# uncomment ONE of these three option to make `cargo run` start a GDB session | |
# which option to pick depends on your system | |
# runner = "arm-none-eabi-gdb -q -x openocd.gdb" | |
# runner = "gdb-multiarch -q -x openocd.gdb" | |
# runner = "gdb -q -x openocd.gdb" | |
rustflags = [ | |
# LLD (shipped with the Rust toolchain) is used as the default linker | |
"-C", "link-arg=-Tlink.x", | |
# if you run into problems with LLD switch to the GNU linker by commenting out | |
# this line | |
# "-C", "linker=arm-none-eabi-ld", | |
# if you need to link to pre-compiled C libraries provided by a C toolchain | |
# use GCC as the linker by commenting out both lines above and then | |
# uncommenting the three lines below | |
# "-C", "linker=arm-none-eabi-gcc", | |
# "-C", "link-arg=-Wl,-Tlink.x", | |
# "-C", "link-arg=-nostartfiles", | |
] | |
[build] | |
# Pick ONE of these compilation targets | |
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ | |
# | |
# target = "thumbv7m-none-eabi" # Cortex-M3 | |
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) | |
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) |
This file contains hidden or 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().unwrap(); | |
// 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 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()]; | |
let data_length = data.len(); | |
// Process the command | |
match command { | |
Command::None => {}, | |
Command::Echo => { | |
// Request a grant for the transmit queue the same size as the incomming data | |
let mut twg = cx.resources.tx_buffer_producer.grant_exact(data.len()).unwrap(); | |
// Copy the data into the buffer | |
let tx_buf = twg.buf(); | |
tx_buf.copy_from_slice(data); | |
// Store the grant in the resources strruct for later use | |
*cx.resources.transmit_write_grant = Some(twg); | |
} | |
} | |
// Set the return buffer into the DMA register | |
// 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.take().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 | |
unsafe{ cx.resources.dma.st[3].m0ar.write(|w| w.bits(cx.resources.transmit_read_grant.as_ref().unwrap().buf().as_ptr() as u32)) }; | |
// Set the number of bytes to go out | |
unsafe{ cx.resources.dma.st[3].ndtr.write(|w| w.bits(_write_grant.buf().len() as u32)) }; | |
// Enable the outgoing DMA | |
cx.resources.dma.st[3].cr.write(|w| w.en().set_bit()); | |
}, | |
None => { | |
// Enable the incomming DMA | |
cx.resources.dma.st[4].cr.write(|w| w.en().set_bit()); | |
} | |
} | |
// Release the incomming receive grant (this frees of that space in the BBQueue) | |
incomming_grant.release(data_length); | |
// 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_low().unwrap(); | |
} | |
// 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.as_ref().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; | |
} | |
} | |
} | |
extern "C" { | |
fn USART2(); | |
} | |
}; |
This file contains hidden or 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
MEMORY | |
{ | |
/* NOTE 1 K = 1 KiBi = 1024 bytes */ | |
FLASH : ORIGIN = 0x08000000, LENGTH = 512K | |
RAM : ORIGIN = 0x20000000, LENGTH = 128K | |
} | |
/* This is where the call stack will be allocated. */ | |
/* The stack is of the full descending type. */ | |
/* You may want to use this variable to locate the call stack and static | |
variables in different memory regions. Below is shown the default value */ | |
/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ | |
/* You can use this symbol to customize the location of the .text section */ | |
/* If omitted the .text section will be placed right after the .vector_table | |
section */ | |
/* This is required only on microcontrollers that store some configuration right | |
after the vector table */ | |
/* _stext = ORIGIN(FLASH) + 0x400; */ | |
/* Example of putting non-initialized variables into custom RAM locations. */ | |
/* This assumes you have defined a region RAM2 above, and in the Rust | |
sources added the attribute `#[link_section = ".ram2bss"]` to the data | |
you want to place there. */ | |
/* Note that the section will not be zero-initialized by the runtime! */ | |
/* SECTIONS { | |
.ram2bss (NOLOAD) : ALIGN(4) { | |
*(.ram2bss); | |
. = ALIGN(4); | |
} > RAM2 | |
} INSERT AFTER .bss; | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment