Skip to content

Instantly share code, notes, and snippets.

@justacec
Last active April 29, 2020 21:21
Show Gist options
  • Save justacec/3e54d4d7d7f3a3da860a5b40bc02a77e to your computer and use it in GitHub Desktop.
Save justacec/3e54d4d7d7f3a3da860a5b40bc02a77e to your computer and use it in GitHub Desktop.
Use of the BBQueue Crate with the RTFM Framework
[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"
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"}
[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)
#![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();
}
};
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