diff --git a/examples/bare5.rs b/examples/bare5.rs index f3e63082833b990f4779a864fb5a4bb4646ea904..8d5b12289f0f3d46886ab5822808c360f9b7e5e3 100644 --- a/examples/bare5.rs +++ b/examples/bare5.rs @@ -128,24 +128,25 @@ fn main() { // user application fn idle(rcc: &mut RCC, gpioa: &mut GPIOA) { + let rcc_copy = &rcc; // power on GPIOA - let r = (*rcc).AHB1ENR.read(); // read - (*rcc).AHB1ENR.write(r | 1 << (0)); // set enable + let r = rcc.AHB1ENR.read(); // read + rcc.AHB1ENR.write(r | 1 << (0)); // set enable // configure PA5 as output - let r = (*gpioa).MODER.read() & !(0b11 << (5 * 2)); // read and mask - (*gpioa).MODER.write(r | 0b01 << (5 * 2)); // set output mode + let r = gpioa.MODER.read() & !(0b11 << (5 * 2)); // read and mask + gpioa.MODER.write(r | 0b01 << (5 * 2)); // set output mode // and alter the data output through the BSRR register // this is more efficient as the read register is not needed. loop { // set PA5 high - (*gpioa).BSRRH.write(1 << 5); // set bit, output hight (turn on led) + gpioa.BSRRH.write(1 << 5); // set bit, output hight (turn on led) wait(10_000); // set PA5 low - (*gpioa).BSRRL.write(1 << 5); // clear bit, output low (turn off led) + gpioa.BSRRL.write(1 << 5); // clear bit, output low (turn off led) wait(10_000); } } @@ -157,8 +158,22 @@ fn idle(rcc: &mut RCC, gpioa: &mut GPIOA) { // provided by ST (and other companies). Actually, the file presesnted here is mostly a // cut/paste/replace of the stm32f40x.h, just Rustified. // -// Here all peripheral access is unsafe, (as we are dereferencing raw pointers). In the code -// we have fairly large unsafe blocks. Your task here is to +// I this case we pass mutable pointer to the `idle`. +// +// Rewrite the accesses in the loop to use the data registerf directly (and make a read/modify/write). +// +// Run and see that the program behaves the same. +// +// commit your answers (bare5_1) +// +// 2. +// Extend the read/write API with a modify, taking the address, field offsbet, field width, and value. +// +// Change the code into using your new API. +// +// Run and see that the program behaves the same. +// +// commit your answers (bare5_2) // As we are not using interrupts, we just register a dummy catch all handler #[link_section = ".vector_table.interrupts"] diff --git a/examples/bare7.rs b/examples/bare7.rs new file mode 100644 index 0000000000000000000000000000000000000000..101a29ea0ed6b89a27a91b01969f43cced223e28 --- /dev/null +++ b/examples/bare7.rs @@ -0,0 +1,119 @@ +//! Serial interface loopback +#![deny(unsafe_code)] +//#![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +extern crate cortex_m_rtfm as rtfm; +extern crate f4; +extern crate heapless; + +#[macro_use(block)] +extern crate nb; + +#[macro_use] +extern crate cortex_m_debug; + +use f4::prelude::*; +use f4::Serial; +use f4::time::Hertz; +use heapless::Vec; +use rtfm::app; + +// CONFIGURATION +const BAUD_RATE: Hertz = Hertz(115_200); + +// RTFM FRAMEWORK +app! { + device: f4::stm32f40x, +} + +// Init executes with interrupts disabled +// Hence its safe to access all peripherals (no race-conditions) +// +// In this case init will never return +// This is not the typical use-case as we will see later +fn init(p: init::Peripherals) { + ipln!("init"); + let serial = Serial(p.USART2); + + serial.init(BAUD_RATE.invert(), None, p.GPIOA, p.RCC); + + let mut vector: Vec<u8, [u8; 4]> = Vec::new(); + loop { + match block!(serial.read()) { + Ok(byte) => { + let _ = vector.push(byte); + ipln!("Ok {:?}", vector); + let _ = serial.write(byte); + } + Err(err) => { + ipln!("Error {:?}", err); + p.USART2.dr.read(); // clear the error by reading the data register + } + } + } +} + +// We will never reach `idle` since we burn the CPU arduino style :) +fn idle() -> ! { + // Sleep + loop { + rtfm::wfi(); + } +} + +// 1. compile and run the project at 16MHz +// make sure its running (not paused) +// start a terminal program, e.g., `moserial` +// connect to the port +// +// Device /dev/ttyACM0 +// Baude Rate 115200 +// Data Bits 8 +// Stop Bits 1 +// Parity None +// Handshake None +// +// (this is also known in short as 15200 8N1) +// +// you should now be able to send data and recive an echo from the MCU +// +// try sending: "abcd" as a single sequence (set the option No end in moserial) +// (don't send the quation marks, just abcd) +// +// what did you receive, and what was the output of the ITM trace +// ** your answer here ** +// +// now try sending 'a', 'b', 'c', 'd' character by character +// (just send the characters not the single quotes and commas) +// what did you receive, and what was the output of the ITM trace +// ** your answer here ** +// +// why did the transmission fail? (hint, think about timing...) +// ** your answer here ** +// +// commit your answers (bare7_1) +// +// 2. now stress the buffer lengt sending a sequence +// 'a', 'b', 'c', 'd', 'e' character by character +// what did you receive, and what was the output of the ITM trace +// ** your answer here ** +// +// if done correctly you see an evedince of Rust's memory safety +// the buffer will be saturated (all elements occupied) +// but no buffer owerwrite will occur (outside the buffer) +// +// your job now is to check the API of `heapless` +// https://docs.rs/heapless/0.2.1/heapless/ +// +// and catch the case we are trying to write to a full buffer/vector +// and write a suiteble error message +// +// commit your answers (bare7_2) +// +// !!!!! NOTICE !!!!! +// here we are not solving the underlying problem +// we are just mitigating the effects +// in bare8 we will se how to use interrupts for reliable +// high-speed communictation diff --git a/examples/bare8.rs b/examples/bare8.rs new file mode 100644 index 0000000000000000000000000000000000000000..e3befba16e6a0ebb27341d111854b6392bdb6013 --- /dev/null +++ b/examples/bare8.rs @@ -0,0 +1,211 @@ +//! Serial interface loopback +#![deny(unsafe_code)] +#![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +extern crate cortex_m_rtfm as rtfm; +extern crate f4; +extern crate heapless; + +#[macro_use] +extern crate cortex_m_debug; + +use f4::prelude::*; +use f4::Serial; +use f4::time::Hertz; +use heapless::Vec; +use rtfm::{app, Resource, Threshold}; + +// CONFIGURATION +const BAUD_RATE: Hertz = Hertz(115_200); + +// RTFM FRAMEWORK +app! { + device: f4::stm32f40x, + + resources: { + static VECTOR: Vec<u8, [u8; 4]> = Vec::new(); + }, + + tasks: { + USART2: { + path: rx, + priority: 2, + resources: [VECTOR, USART2], + }, + EXTI1: { + path: trace, + priority: 1, + resources: [VECTOR], + } + }, +} + +// `rx` task trigger on arrival of a USART2 interrupt +fn rx(t: &mut Threshold, r: USART2::Resources) { + let serial = Serial(&**r.USART2); + + // we don't need to block waiting for data to arrive + // (as we were triggered) by the data arrival (or error) + match serial.read() { + Ok(byte) => { + // received byte correct + r.VECTOR.claim_mut(t, |vector, _| { + // critical section for the shared vector + let _ = vector.push(byte); + // here you could put your error handling for vector full + }); + let _ = serial.write(byte); + } + Err(err) => { + // some transmission error + ipln!("Error {:?}", err); + r.USART2.dr.read(); // clear the error by reading the data register + } + } + + // trigger the `trace` task + rtfm::set_pending(f4::stm32f40x::Interrupt::EXTI1); +} + +// `trace` task triggered by the hight priority `rx` task +// a low priority task for the background processing (like tracing) +fn trace(t: &mut Threshold, r: EXTI1::Resources) { + let mut b = [0; 4]; // local buffer + let mut l = 0; // length of the received vector + + r.VECTOR.claim(t, |vector, _| { + // critical section for the shared vector + // here the task `rx` will be blocked from executing + l = vector.len(); + b[..l].copy_from_slice(&***vector); // efficent copy vector to the local buffer + }); + // since we do the actual tracing (relatively slow) + // OUTSIDE the claim (critical section), there will be no + // additional blocking of `rx` + ipln!("Vec {:?}", &b[..l]); +} + +// Here we see the typical use of init INITIALIZING the system +fn init(p: init::Peripherals, _r: init::Resources) { + ipln!("init"); + let serial = Serial(p.USART2); + + serial.init(BAUD_RATE.invert(), None, p.GPIOA, p.RCC); + // in effect telling the USART2 to trigger the `rx` task/interrupt + serial.listen(f4::serial::Event::Rxne); +} + +// We will spend all time sleeping (unless we have work to do) +// reactive programming in RTFM ftw!!! +fn idle() -> ! { + // Sleep + loop { + rtfm::wfi(); + } +} + +// 1. compile and run the project at 16MHz +// make sure its running (not paused) +// start a terminal program, e.g., `moserial` +// connect to the port +// +// Device /dev/ttyACM0 +// Baude Rate 115200 +// Data Bits 8 +// Stop Bits 1 +// Parity None +// Handshake None +// +// (this is also known in short as 15200 8N1) +// +// you should now be able to send data and recive an echo from the MCU +// +// try sending: "abcd" as a single sequence (set the option No end in moserial) +// (don't send the quation marks, just abcd) +// +// what did you receive, and what was the output of the ITM trace +// ** your answer here ** +// +// did you experience any over-run errors? +// ** your answer here ** +// +// what is the key problem and its solution (try to follow the commented code) +// ** your answer here ** +// +// commit your answers (bare8_1) +// +// 2. now catch the case when we are trying to write to a full vector/buffer +// and write a suiteble error message +// +// commit your answers (bare8_2) +// +// as a side note.... +// +// The concurrency model behind RTFM offers +// 1. Race-free resource access +// +// 2. Deadlock-free exection +// +// 3. Shared execution stack (no pre-allocated stack regions) +// +// 4. Bound priority inversion +// +// 5. Theoretical underpinning -> +// + proofs of soundness +// + schedulability analysis +// + response time analysis +// + stack memory analysis +// + ... leverages on 25 years of reseach in the real-time community +// based on the seminal work of Baker in the early 1990s +// (known as the Stack Resource Policy, SRP) +// +// Our implementation in Rust offers +// 1. compile check and analysis of tasks and resources +// + the API implementation together with the Rust compiler will ensure that +// both RTFM (SRP) soundness and the Rust memory model invariants +// are upheld (under all circumpstances). +// +// 2. arguably the worlds fastest real time scheduler * +// + task invocation 0-cycle OH on top of HW interrupt handling +// + 2 cycle OH for locking a shared resource (on claim entry) +// + 1 cycle OH for releasineg a shared resoure (on claim exit) +// +// 3. arguably the worlds most memory efficient scheduler * +// + 1 byte stack memory OH for each (nested) claim +// (no additional book-keeping during run-time) +// +// * applies to static task/resource models with single core +// pre-emptive, static priority scheduling +// +// in comparison "real-time" schedulers for threaded models like FreeRTOS +// - CPU and memory OH magnitudes larger (100s of cycles/kilobytes of memory) +// - ... and what's worse OH is typically unbound (no proofs of worst case) +// - potential race conditions (up to the user to verify) +// - potential dead-locks (up to the implementation) +// - potential unbound priority inversion (up to the implementation) +// +// Rust RTFM (currently) target ONLY STATIC SYSTEMS, there is no notion +// of dynamically creating new executions contexts/threads +// so a direct comparison is not completely fair. +// +// On the other hand, embedded applications are typically static by nature +// so a STATIC model is to that end better suitable. +// +// RTFM is reactive by nature, a task execute to end, triggered +// by an internal or external event, (where an interrupt is an external event +// from the environment, like a HW peripheral such as the USART2). +// +// Threads on the other hand are concurrent and infinte by nature and +// actively blocking/yeilding awaiting stimuli. Hence reactivity needs to be CODED. +// This leads to an anomaly, the underlying HW is reactive (interrupts), +// requiring an interrupt handler, that creates a signal to the scheduler. +// +// The scheduler then needs to keep track of all threads and at some point choose +// to dispatch the awaiting thread. So reactivity is bottlenecked to the point +// of scheduling by que management, context switching and other additional +// book keeping. +// +// In essence, the thread scheduler tries to re-establish the reactivity that +// were there (interrupts), a battle that cannot be won... diff --git a/examples/bare9.rs b/examples/bare9.rs new file mode 100644 index 0000000000000000000000000000000000000000..90a4ad80253814c695f6079a789233db068062bd --- /dev/null +++ b/examples/bare9.rs @@ -0,0 +1,32 @@ +// now you are on your own..... +// +// look at the examples in the f4 crate +// https://github.com/jsjolund/f4 +// +// try out the examples/pwm-control.rs +// +// connect an oschillocope to the pwm output +// check that you can control the pwm over the serial +// +// +// your assingment is to improve the user interface +// to take commands +// +// on // turn on the pwm output +// off // turn off the pwm output +// set p // set the duty cycle to `p` % +// +// and optionally +// freq f // set pww frequency to `f` hz +// ... // your own commands +// +// I would suggest using the bare8 as a starting point +// and adding code from the pwm-control example +// you may look if `parse` can be of use to you here... +// make the command parser work first, and then add the pwm code. +// +// +// commit your solution (bare9) +// +// https://play.rust-lang.org/, +// offers a good way to prototype and share code snippets