diff --git a/.vscode/launch.json b/.vscode/launch.json index ce1e88afb03dd476aff5212c1c052f7826a6b7f3..c361322ac1d36a74642c3dcc579a82217cc642be 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -36,7 +36,7 @@ "request": "launch", "servertype": "openocd", "name": "itm internal (debug)", - "preLaunchTask": "cargo build --examples", + "preLaunchTask": "cargo build --example", "executable": "./target/thumbv7em-none-eabihf/debug/examples/${fileBasenameNoExtension}", "configFiles": [ //"interface/stlink.cfg", @@ -72,7 +72,57 @@ "request": "launch", "servertype": "openocd", "name": "itm fifo (debug)", - "preLaunchTask": "cargo build --examples", + "preLaunchTask": "cargo build --example", + "executable": "./target/thumbv7em-none-eabihf/debug/examples/${fileBasenameNoExtension}", + "configFiles": [ + "interface/stlink.cfg", + // "interface/stlink-v2-1.cfg", // deprecated setup script + "target/stm32f4x.cfg" + ], + "postLaunchCommands": [ + "monitor arm semihosting enable", + "monitor tpiu config internal /tmp/itm.fifo uart off 16000000", + "monitor itm port 0 on" + ], + "runToMain": true, + "cwd": "${workspaceRoot}" + }, + // Launch configuration for `examples` + // - debug + // - semihosting + // - ITM/SWO tracing to file/fifo `/tmp/itm.fifo` + // - run to main + { + "type": "cortex-debug", + "request": "launch", + "servertype": "openocd", + "name": "itm fifo (debug) stm32f4", + "preLaunchTask": "cargo build --example --features stm32f4", + "executable": "./target/thumbv7em-none-eabihf/debug/examples/${fileBasenameNoExtension}", + "configFiles": [ + "interface/stlink.cfg", + // "interface/stlink-v2-1.cfg", // deprecated setup script + "target/stm32f4x.cfg" + ], + "postLaunchCommands": [ + "monitor arm semihosting enable", + "monitor tpiu config internal /tmp/itm.fifo uart off 16000000", + "monitor itm port 0 on" + ], + "runToMain": true, + "cwd": "${workspaceRoot}" + }, + // Launch configuration for `examples` + // - debug + // - semihosting + // - ITM/SWO tracing to file/fifo `/tmp/itm.fifo` + // - run to main + { + "type": "cortex-debug", + "request": "launch", + "servertype": "openocd", + "name": "itm fifo (debug) rtfm", + "preLaunchTask": "cargo build --example --features rtfm", "executable": "./target/thumbv7em-none-eabihf/debug/examples/${fileBasenameNoExtension}", "configFiles": [ //"interface/stlink.cfg", @@ -97,7 +147,7 @@ "request": "launch", "servertype": "openocd", "name": "itm fifo (release)", - "preLaunchTask": "cargo build --examples --release", + "preLaunchTask": "cargo build --example --release", "executable": "./target/thumbv7em-none-eabihf/release/examples/${fileBasenameNoExtension}", "configFiles": [ //"interface/stlink.cfg", @@ -121,8 +171,8 @@ "type": "cortex-debug", "request": "launch", "servertype": "openocd", - "name": "itm fifo 64MHz (release)", - "preLaunchTask": "cargo build --examples --release", + "name": "itm fifo 64MHz (release) stm32f4", + "preLaunchTask": "cargo build --example --release --features stm32f4", "executable": "./target/thumbv7em-none-eabihf/release/examples/${fileBasenameNoExtension}", "configFiles": [ //"interface/stlink.cfg", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 42f4b03ab8cafe8c01c41cd9e4c930c6196c94da..c5dc1cfeb28edfcc0b42b4a4b44f2d993738d597 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,4 +1,7 @@ { + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", "tasks": [ { "type": "shell", @@ -26,8 +29,8 @@ }, { "type": "shell", - "label": "cargo build --examples", - "command": "cargo build --examples", + "label": "cargo build --example", + "command": "cargo build --example ${fileBasenameNoExtension}", "group": { "kind": "build", "isDefault": true @@ -38,8 +41,32 @@ }, { "type": "shell", - "label": "cargo build --examples --release", - "command": "cargo build --examples --release", + "label": "cargo build --example --features stm32f4", + "command": "cargo build --example ${fileBasenameNoExtension} --features stm32f4", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$rustc" + ] + }, + { + "type": "shell", + "label": "cargo build --example --features rtfm", + "command": "cargo build --example ${fileBasenameNoExtension} --features rtfm", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$rustc" + ] + }, + { + "type": "shell", + "label": "cargo build --example --release --features stm32f4", + "command": "cargo build --example ${fileBasenameNoExtension} --release --features stm32f4", "group": { "kind": "build", "isDefault": true diff --git a/Cargo.lock b/Cargo.lock index 99b09adfe0e5a85a3694bb49059f47ebedf89556..65280df958cd7842d88cfaa8b11e88aee5e3cde2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "cortex-m-rt", "cortex-m-rtfm", "cortex-m-semihosting", + "heapless", "nb", "panic-halt", "panic-itm", diff --git a/Cargo.toml b/Cargo.toml index 3a3532ef17d3fc7c78563661fc58d8b80f2c5aeb..c4aa6bc397141c6009cb9a49f74cd4ac001ea1f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,18 +5,19 @@ authors = ["Per Lindgren <per.lindgren@ltu.se>"] description = "Example project (app)" keywords = ["arm", "cortex-m", "rtfm", "e7020e"] license = "MIT OR Apache-2.0" -repository = "https://github.com/korken89/trustflight_firmware" +repository = "https://gitlab.henriktjader.com/pln/e7020e_2020" version = "0.1.0" edition = "2018" [dependencies] panic-halt = "0.2" -panic-semihosting = "0.5" +panic-semihosting = "0.5" # comment out for `cargo doc` +panic-itm = "0.4.1" # comment out for `cargo doc` cortex-m-semihosting = "0.3.5" aligned = "0.3.2" ufmt = "0.1.0" -panic-itm = "0.4.1" nb = "0.1.2" +heapless = "0.5.3" [dependencies.cortex-m] version = "0.6.2" @@ -53,14 +54,33 @@ bench = false name = "device" required-features = ["stm32f4"] +[[example]] +name = "bare6" +required-features = ["stm32f4"] + [[example]] name = "serial" required-features = ["stm32f4xx-hal"] [[example]] -name = "rtfm_itm" +name = "bare7" +required-features = ["stm32f4xx-hal"] + +[[example]] +name = "bare8" +required-features = ["rtfm"] + +[[example]] +name = "bare9" required-features = ["rtfm"] +[[example]] +name = "bare10" +required-features = ["rtfm"] + +[[example]] +name = "rtfm_itm" +required-features = ["rtfm"] [[example]] name = "rtfm_itm_spawn" required-features = ["rtfm"] @@ -85,9 +105,14 @@ required-features = ["rtfm"] name = "rtfm_blinky_msg3" required-features = ["rtfm"] +[[example]] +name = "rtfm_blinky_sw_reset" +required-features = ["rtfm"] + +# for more info see, https://doc.rust-lang.org/rustc/codegen-options/index.html [profile.dev] -opt-level = 1 -codegen-units = 16 +# opt-level = 1 # better optimization (may optimize out symbols) +# codegen-units = 16 debug = true lto = false diff --git a/examples/bare0.rs b/examples/bare0.rs index f54e9509402fbee65ecdd8df8a14ced982d58f2e..f435828bcd5557e847ba8fa44cc1a38f99f41718 100644 --- a/examples/bare0.rs +++ b/examples/bare0.rs @@ -15,12 +15,8 @@ // no standard main, we declare main using [entry] #![no_main] -// Minimal runtime / startup for Cortex-M microcontrollers -//extern crate cortex_m_rt as rt; // Panic handler, for textual output using semihosting -extern crate panic_semihosting; -// Panic handler, infinite loop on panic -// extern crate panic_halt; +use panic_semihosting as _; // import entry point use cortex_m_rt::entry; @@ -65,10 +61,10 @@ fn main() -> ! { // Here we assume you are using `vscode` with `cortex-debug`. // -// 0. Compile/build the example in debug (dev) mode. +// 0. Compile/build and run the example in debug (dev) mode. // -// > cargo build --example bare0 -// (or use the vscode build task) +// > cargo run --example bare0 +// (or use vscode) // // 1. Run the program in the debugger, let the program run for a while and // then press pause. diff --git a/examples/bare1.rs b/examples/bare1.rs index ce46e5ca1167f739d5ab4589ef0b9b241848fcb9..7f8b6429863535501515033e92afb2456b222bea 100644 --- a/examples/bare1.rs +++ b/examples/bare1.rs @@ -3,14 +3,14 @@ //! Inspecting the generated assembly //! //! What it covers -//! - ITM tracing +//! - Rust panic tracing using ITM //! - assembly calls and inline assembly //! - more on arithmetics #![no_main] #![no_std] -extern crate panic_itm; +use panic_itm as _; use cortex_m_rt::entry; @@ -45,18 +45,18 @@ fn main() -> ! { // // You may need/want to install additional components also. // To that end look at the install section in the README.md. -// If you change toolchain, you may need to exit and re-start `vscode`. +// (If you change toolchain, you may need to exit and re-start `vscode`.) // // 1. Build and run the application // -// > cargo build --example bare1 -// (or use the vscode build task) +// > cargo run --example bare1 +// (or use the `itm fifo (debug)` or the `itm internal (debug)` launch configuration.) // -// Make sure you have followed the instructions for fifo `ITM` tracing. -// Debug using the `itm fifo (debug)` launch configuration. +// Make sure you have followed the instructions for fifo `ITM` tracing accordingly. // // When debugging the application it should hit the `bkpt` instruction. // What happens when you continue (second iteration of the loop)? +// (passing 3 breakpoints) // // ** your answer here ** // The program panics "panicked at 'attempt to add with overflow', examples/bare1.rs:23:9 " @@ -64,6 +64,7 @@ fn main() -> ! { // What is the `ITM` output. // // ** your answer here ** +// panicked at 'attempt to add with overflow', examples/bare1.rs:23:9 // // Commit your answer (bare1_1) // @@ -92,7 +93,7 @@ fn main() -> ! { // Rebuild `bare1.rs` in release (optimized mode). // // > cargo build --example bare1 --release -// (or using the vscode build task) +// (or using the vscode) // // Compare the generated assembly for the loop // between the dev (un-optimized) and release (optimized) build. @@ -110,6 +111,11 @@ fn main() -> ! { // // ** your answer here ** // +// Is there now any reference to the panic handler? +// If not, why is that the case? +// +// ** your answer here ** +// // commit your answers (bare1_3) // // Discussion: @@ -132,7 +138,32 @@ fn main() -> ! { // Later we will demonstrate how we can get guarantees of panic free execution. // This is very important to improve reliability. // -// 4. *Optional +// 4. Now comment out the `read_volatile`. +// +// > cargo build --example bare1 --release +// (or using the vscode) +// +// Compare the generated assembly for the loop +// between the dev (un-optimized) and release (optimized) build. +// +// What is the output of: +// > disassemble +// +// ** your answer here ** +// +// How many instructions are in between the two `bkpt` instructions. +// +// ** your answer here ** +// +// Where is the local variable stored? +// What happened, and why is Rust + LLVM allowed to do that? +// +// ** your answer here ** +// +// commit your answers (bare1_4) +// +// +// 5. *Optional // You can pass additional flags to the Rust `rustc` compiler. // // `-Z force-overflow-checks=off` @@ -145,11 +176,11 @@ fn main() -> ! { // // ** your answer here ** // -// commit your answers (bare1_4) +// commit your answers (bare1_5) // // Now restore the `.cargo/config` to its original state. // -// 5. *Optional +// 6. *Optional // There is another way to conveniently use wrapping arithmetics // without passing flags to the compiler. // @@ -165,7 +196,7 @@ fn main() -> ! { // // ** your answer here ** // -// commit your answers (bare1_5) +// commit your answers (bare1_6) // // Final discussion: // diff --git a/examples/bare10.rs b/examples/bare10.rs new file mode 100644 index 0000000000000000000000000000000000000000..ac819564e8eec60cb9188bca1a13786e50bf3429 --- /dev/null +++ b/examples/bare10.rs @@ -0,0 +1,225 @@ +//! The RTFM framework +//! +//! What it covers: +//! - Priority based scheduling +//! - Message passing + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::{asm, iprintln}; + +extern crate stm32f4xx_hal as hal; +use crate::hal::prelude::*; +use crate::hal::serial::{config::Config, Event, Rx, Serial, Tx}; +use hal::stm32::ITM; + +use nb::block; +use rtfm::app; + +// Our error type +#[derive(Debug)] +pub enum Error { + RingBufferOverflow, + UsartSendOverflow, + UsartReceiveOverflow, +} + +#[app(device = hal::stm32, peripherals = true)] +const APP: () = { + struct Resources { + // Late resources + TX: Tx<hal::stm32::USART2>, + RX: Rx<hal::stm32::USART2>, + ITM: ITM, + } + // init runs in an interrupt free section> + #[init] + fn init(cx: init::Context) -> init::LateResources { + let mut core = cx.core; + let device = cx.device; + + let stim = &mut core.ITM.stim[0]; + iprintln!(stim, "bare10"); + + let rcc = device.RCC.constrain(); + + // 16 MHz (default, all clocks) + let clocks = rcc.cfgr.freeze(); + + let gpioa = device.GPIOA.split(); + + let tx = gpioa.pa2.into_alternate_af7(); + let rx = gpioa.pa3.into_alternate_af7(); // try comment out + + let mut serial = Serial::usart2( + device.USART2, + (tx, rx), + Config::default().baudrate(115_200.bps()), + clocks, + ) + .unwrap(); + + // generate interrupt on Rxne + serial.listen(Event::Rxne); + // Separate out the sender and receiver of the serial port + let (tx, rx) = serial.split(); + + // Late resources + init::LateResources { + // Our split serial + TX: tx, + RX: rx, + + // For debugging + ITM: core.ITM, + } + } + + // idle may be interrupted by other interrupt/tasks in the system + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + asm::wfi(); + } + } + + #[task(priority = 1, resources = [ITM])] + fn trace_data(cx: trace_data::Context, byte: u8) { + let stim = &mut cx.resources.ITM.stim[0]; + iprintln!(stim, "data {}", byte); + // for _ in 0..10000 { + // asm::nop(); + // } + } + + #[task(priority = 1, resources = [ITM])] + fn trace_error(cx: trace_error::Context, error: Error) { + let stim = &mut cx.resources.ITM.stim[0]; + iprintln!(stim, "{:?}", error); + } + + #[task(priority = 2, resources = [TX], spawn = [trace_error])] + fn echo(cx: echo::Context, byte: u8) { + let tx = cx.resources.TX; + + if block!(tx.write(byte)).is_err() { + let _ = cx.spawn.trace_error(Error::UsartSendOverflow); + } + } + + #[task(binds = USART2, priority = 3, resources = [RX], spawn = [trace_data, trace_error, echo])] + fn usart2(cx: usart2::Context) { + let rx = cx.resources.RX; + + match rx.read() { + Ok(byte) => { + let _ = cx.spawn.echo(byte); + if cx.spawn.trace_data(byte).is_err() { + let _ = cx.spawn.trace_error(Error::RingBufferOverflow); + } + } + Err(_err) => { + let _ = cx.spawn.trace_error(Error::UsartReceiveOverflow); + } + } + } + + // Set of interrupt vectors, free to use for RTFM tasks + // 1 per priority level suffices + extern "C" { + fn EXTI0(); + fn EXTI1(); + } +}; + +// Optional +// 0. Compile and run the project at 16MHz in release mode +// make sure its running (not paused). +// +// > cargo build --example bare10 --features "rtfm" --release +// (or use the vscode build task) +// +// Connect a terminal program. +// Verify that it works as bare9. +// +// 1. Now, comment out the loop in `trace_data`. +// The loop is just there to simulate some workload... +// +// Try now to send a sequence `abcd` +// +// Did you loose any data (was the data correctly echoed)? +// +// ** your answer here ** +// +// Was the data correctly traced over the ITM? +// +// ** your answer here ** +// +// Why did you loose trace information? +// +// ** your answer here ** +// +// Commit your answers (bare10_1) +// +// 2. Read the RTFM manual (book). +// Figure out a way to accomodate for 4 outstanding messages to the `trace_data` task. +// +// Verify that you can now correctly trace sequences of 4 characters sent. +// +// Can you think of how to determine a safe bound on the message buffer size? +// (Safe meaning, that message would never be lost due to the buffer being full.) +// +// What information would you need? +// +// ** your answer here ** +// +// Commit your answers (bare10_2) +// +// 3. Implement a command line interpreter as a new task. +// It should: +// - have priority 1. +// - take a byte as an argument (passed from the USART2 interrupt). +// - have a local buffer B of 10 characters +// - have sufficient capacity to receive 10 characters sent in a sequence +// - analyse the input buffer B checking the commands +// set <int> <RETURN> // to set blinking frequency +// on <RETURN> // to enable the led blinking +// of <RETURN> // to disable the led blinking +// +// <int> should be decoded to an integer value T, and <RETURN> accept either <CR> or <LF>. +// +// The set value should blink the LED in according the set value in Hertz, +// (so `set 1 <RETURN>` should blink with 1Hz) +// +// Tips: +// Create two tasks, (`on', that turns the led on, and a task `off` that turns the led off). +// `on` calls `off` with a timer offset (check the RTFM manual). +// `off` calls `on` with a timer offset. +// +// The timing offset can implemented as a shared resource T between the command line interpreter and +// the 'on/off ' tasks. From `init` you can give an initial timing offset T, and send an +// initial message to `on` triggering the periodic behavior. +// +// The 'on/off ' tasks can have a high priority 4, and use locking (in the low priority task) +// parsing the input. This way, the led will have a very low jitter. +// +// (You can even use an atomic data structure, which allows for lock free access.) +// +// +// The on/off is easiest implemented by having another shared variable used as a condition +// for the `on` task to set the GPIO. Other solutions could be to stop the sequence (i.e.) +// conditionally call the `off` task instead. Then the command interpreted would +// trigger a new sequence when an "on" command is detected. (Should you allow multiple, overlapping) +// sequences? Why not, could be cool ;) +// The main advantage of the stopping approach is that the system will be truly idle +// if not blinking, and thus more power efficient. +// +// You can reuse the code for setting up and controlling the GPIO/led. +// +// You can come up with various extensions to this application, setting the +// the duty cycle (on/off ratio in %), etc. +// +// Commit your solution (bare10_3) diff --git a/examples/bare2.rs b/examples/bare2.rs new file mode 100644 index 0000000000000000000000000000000000000000..ce6acb20fb9221507961a8586a4ba8211917db1c --- /dev/null +++ b/examples/bare2.rs @@ -0,0 +1,101 @@ +//! bare2.rs +//! +//! Measuring execution time +//! +//! What it covers +//! - Generating documentation +//! - Using core peripherals +//! - Measuring time using the DWT +//! - ITM tracing using `iprintln` +//! - Panic halt +//! + +#![no_main] +#![no_std] + +use panic_halt as _; + +use cortex_m::{iprintln, peripheral::DWT, Peripherals}; +use cortex_m_rt::entry; + +// burns CPU cycles by just looping `i` times +#[inline(never)] +fn wait(i: u32) { + for _ in 0..i { + // no operation (ensured not optimized out) + cortex_m::asm::nop(); + } +} + +#[entry] +fn main() -> ! { + let mut p = Peripherals::take().unwrap(); + let stim = &mut p.ITM.stim[0]; + let mut dwt = p.DWT; + + iprintln!(stim, "bare2"); + + dwt.enable_cycle_counter(); + + // Reading the cycle counter can be done without `owning` access + // the DWT (since it has no side effect). + // + // Look in the docs: + // pub fn enable_cycle_counter(&mut self) + // pub fn get_cycle_count() -> u32 + // + // Notice the difference in the function signature! + + let start = DWT::get_cycle_count(); + wait(1_000_000); + let end = DWT::get_cycle_count(); + + // notice all printing outside of the section to measure! + iprintln!(stim, "Start {:?}", start); + iprintln!(stim, "End {:?}", end); + iprintln!(stim, "Diff {:?}", end - start); + + loop {} +} + +// 0. Setup +// > cargo doc --open +// +// This will document your crate, and open the docs in your browser. +// If it does not auto-open, then copy paste the path in your browser. +// (Notice, it will try to document all dependencies, you may have only one +// one panic handler, so comment out all but one in `Cargo.toml`.) +// +// In the docs, search (`S`) for DWT, and click `cortex_m::peripheral::DWT`. +// Read the API docs. +// +// 1. Build and run the application (debug build). +// Setup ITM tracing (see `bare1.rs`) and `openocd` (if not using vscode). +// +// > cargo run --example bare2 +// (or use the vscode build task) +// +// What is the output in the ITM console? +// +// ** your answer here ** +// +// Rebuild and run in release mode +// +// > cargo build --example bare2 --release +// +// ** your answer here ** +// +// Compute the ratio between debug/release optimized code +// (the speedup). +// +// ** your answer here ** +// +// commit your answers (bare2_1) +// +// 3. *Optional +// Inspect the generated binaries, and try stepping through the code +// for both debug and release binaries. How do they differ? +// +// ** your answer here ** +// +// commit your answers (bare2_2) diff --git a/examples/bare3.rs b/examples/bare3.rs new file mode 100644 index 0000000000000000000000000000000000000000..665b4da687d3ca0285abdf1ae5268b9ba0a4c340 --- /dev/null +++ b/examples/bare3.rs @@ -0,0 +1,148 @@ +//! bare3.rs +//! +//! String types in Rust +//! +//! What it covers: +//! - Types, str, arrays ([u8; usize]), slices (&[u8]) +//! - Iteration, copy +//! - Semihosting (tracing using `hprintln` + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m_rt::entry; +use cortex_m_semihosting::{hprint, hprintln}; + +#[entry] +fn main() -> ! { + hprintln!("bare3").unwrap(); + let s = "ABCD"; + let bs = s.as_bytes(); + + hprintln!("s = {}", s).unwrap(); + hprintln!("bs = {:?}", bs).unwrap(); + + hprintln!("iterate over slice").unwrap(); + for c in bs { + hprint!("{},", c).unwrap(); + } + + hprintln!("iterate iterate using (raw) indexing").unwrap(); + for i in 0..s.len() { + hprintln!("{},", bs[i]).unwrap(); + } + + hprintln!("").unwrap(); + + let a = [65u8; 4]; + // let mut a = [0u8; 4]; + + hprintln!("").unwrap(); + hprintln!("a = {}", core::str::from_utf8(&a).unwrap()).unwrap(); + + loop { + continue; + } +} + +// 0. Build and run the application (debug build). +// +// > cargo run --example bare3 +// (or use the vscode build task) +// +// 1. What is the output in the `openocd` (Adapter Output) console? +// +// ** your answer here ** +// +// What is the type of `s`? +// +// ** your answer here ** +// +// What is the type of `bs`? +// +// ** your answer here ** +// +// What is the type of `c`? +// +// ** your answer here ** +// +// What is the type of `a`? +// +// ** your answer here ** +// +// What is the type of `i`? +// +// ** your answer here ** +// +// Commit your answers (bare3_1) +// +// 2. Make types of `s`, `bs`, `c`, `a`, `i` explicit. +// +// Commit your answers (bare3_2) +// +// 3. Uncomment line `let mut a = [0u8; 4]; +//` +// Run the program, what happens and why? +// +// ** your answer here ** +// +// Commit your answers (bare3_3) +// +// 4. Alter the program so that the data from `bs` is copied byte +// by byte into `a` using a loop and raw indexing. +// +// Test that it works as intended. +// +// Commit your answers (bare3_4) +// +// 5. Look for a way to make this copy done without a loop. +// https://doc.rust-lang.org/std/primitive.slice.html +// +// Implement and test your solution. +// +// Commit your answers (bare3_5) +// +// 6. Optional +// Rust is heavily influenced by functional languages. +// Figure out how you can use an iterator to work over both +// the `a` and `bs` to copy the content of `bs` to `a`. +// +// You may use +// - `iter` (to turn a slice into an iterator) +// - `zip` (to merge two slices into an iterator) +// - a for loop to assign the elements +// +// Commit your solution (bare3_6) +// +// 7. Optional +// Iter using `foreach` and a closure instead of the for loop. +// +// Commit your solution (bare3_7) +// +// 8. Optional* +// Now benchmark your different solutions using the cycle accurate +// DWT based approach (in release mode). +// +// Cycle count for `raw` indexing +// +// ** your answer here ** +// +// Cycle count for the primitive slice approach. +// +// ** your answer here ** +// +// Cycle count for the primitive slice approach. +// +// ** your answer here ** +// +// Cycle count for the zip + for loop approach. +// +// ** your answer here ** +// +// Cycle count for the zip + for_each approach. +// +// What conclusions can you draw, does Rust give you zero-cost abstractions? +// +// ** your answer here ** diff --git a/examples/bare4.rs b/examples/bare4.rs new file mode 100644 index 0000000000000000000000000000000000000000..07ce483f31f97174ac046b5a43a6548af79a212d --- /dev/null +++ b/examples/bare4.rs @@ -0,0 +1,134 @@ +//! bare4.rs +//! +//! Access to Peripherals +//! +//! What it covers: +//! - Raw pointers +//! - Volatile read/write +//! - Busses and clocking +//! - GPIO (a primitive abstraction) + +#![no_std] +#![no_main] + +extern crate panic_halt; + +extern crate cortex_m; +use cortex_m_rt::entry; + +// Peripheral addresses as constants +#[rustfmt::skip] +mod address { + pub const PERIPH_BASE: u32 = 0x40000000; + pub const AHB1PERIPH_BASE: u32 = PERIPH_BASE + 0x00020000; + pub const RCC_BASE: u32 = AHB1PERIPH_BASE + 0x3800; + pub const RCC_AHB1ENR: u32 = RCC_BASE + 0x30; + pub const GBPIA_BASE: u32 = AHB1PERIPH_BASE + 0x0000; + pub const GPIOA_MODER: u32 = GBPIA_BASE + 0x00; + pub const GPIOA_BSRR: u32 = GBPIA_BASE + 0x18; +} + +use address::*; + +// see the Reference Manual RM0368 (www.st.com/resource/en/reference_manual/dm00096844.pdf) +// rcc, chapter 6 +// gpio, chapter 8 + +#[inline(always)] +fn read_u32(addr: u32) -> u32 { + unsafe { core::ptr::read_volatile(addr as *const _) } + //core::ptr::read_volatile(addr as *const _) +} + +#[inline(always)] +fn write_u32(addr: u32, val: u32) { + unsafe { + core::ptr::write_volatile(addr as *mut _, val); + } +} + +fn wait(i: u32) { + for _ in 0..i { + cortex_m::asm::nop(); // no operation (cannot be optimized out) + } +} + +#[entry] +fn main() -> ! { + // power on GPIOA + let r = read_u32(RCC_AHB1ENR); // read + write_u32(RCC_AHB1ENR, r | 1); // set enable + + // configure PA5 as output + let r = read_u32(GPIOA_MODER) & !(0b11 << (5 * 2)); // read and mask + write_u32(GPIOA_MODER, 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 + write_u32(GPIOA_BSRR, 1 << 5); // set bit, output hight (turn on led) + wait(10_000); + + // set PA5 low + write_u32(GPIOA_BSRR, 1 << (5 + 16)); // clear bit, output low (turn off led) + wait(10_000); + } +} + +// 0. Build and run the application (debug build). +// +// > cargo run --example bare4 +// (or use the vscode) +// +// 1. Did you enjoy the blinking? +// +// ** your answer here ** +// +// Now lookup the data-sheets, and read each section referred, +// 6.3.11, 8.4.1, 8.4.7 +// +// Document each low level access *code* by the appropriate section in the +// data sheet. +// +// Commit your answers (bare4_1) +// +// 2. Comment out line 40 and uncomment line 41 (essentially omitting the `unsafe`) +// +// //unsafe { core::ptr::read_volatile(addr as *const _) } +// core::ptr::read_volatile(addr as *const _) +// +// What was the error message and explain why. +// +// ** your answer here ** +// +// Digging a bit deeper, why do you think `read_volatile` is declared `unsafe`. +// (https://doc.rust-lang.org/core/ptr/fn.read_volatile.html, for some food for thought ) +// +// ** your answer here ** +// +// Commit your answers (bare4_2) +// +// 3. Volatile read/writes are explicit *volatile operations* in Rust, while in C they +// are declared at type level (i.e., access to varibles declared volatile amounts to +// volatile reads/and writes). +// +// Both C and Rust (even more) allows code optimization to re-order operations, as long +// as data dependencies are preserved. +// +// Why is it important that ordering of volatile operations are ensured by the compiler? +// +// ** your answer here ** +// +// Give an example in the above code, where reordering might make things go horribly wrong +// (hint, accessing a peripheral not being powered...) +// +// ** your answer here ** +// +// Without the non-reordering property of `write_volatile/read_volatile` could that happen in theory +// (argue from the point of data dependencies). +// +// ** your answer here ** +// +// Commit your answers (bare4_3) diff --git a/examples/bare5.rs b/examples/bare5.rs new file mode 100644 index 0000000000000000000000000000000000000000..138c7ee46881c9fb933de665b8d78a65eb6cc0fc --- /dev/null +++ b/examples/bare5.rs @@ -0,0 +1,253 @@ +//! bare5.rs +//! +//! C Like Peripheral API +//! +//! What it covers: +//! - abstractions in Rust +//! - structs and implementations + +#![no_std] +#![no_main] + +extern crate panic_halt; + +extern crate cortex_m; +use cortex_m_rt::entry; + +// C like API... +mod stm32f40x { + #[allow(dead_code)] + use core::{cell, ptr}; + + #[rustfmt::skip] + mod address { + pub const PERIPH_BASE: u32 = 0x40000000; + pub const AHB1PERIPH_BASE: u32 = PERIPH_BASE + 0x00020000; + pub const RCC_BASE: u32 = AHB1PERIPH_BASE + 0x3800; + pub const GPIOA_BASE: u32 = AHB1PERIPH_BASE + 0x0000; + } + use address::*; + + pub struct VolatileCell<T> { + value: cell::UnsafeCell<T>, + } + + impl<T> VolatileCell<T> { + #[inline(always)] + pub fn read(&self) -> T + where + T: Copy, + { + unsafe { ptr::read_volatile(self.value.get()) } + } + + #[inline(always)] + pub fn write(&self, value: T) + where + T: Copy, + { + unsafe { ptr::write_volatile(self.value.get(), value) } + } + } + + // modify (reads, modifies a field, and writes the volatile cell) + // + // parameters: + // offset (field offset) + // width (field width) + // value (new value that the field should take) + // + // impl VolatileCell<u32> { + // #[inline(always)] + // pub fn modify(&self, offset: u8, width: u8, value: u32) { + // // your code here + // } + // } + + #[repr(C)] + #[allow(non_snake_case)] + #[rustfmt::skip] + pub struct RCC { + pub CR: VolatileCell<u32>, // < RCC clock control register, Address offset: 0x00 + pub PLLCFGR: VolatileCell<u32>, // < RCC PLL configuration register, Address offset: 0x04 + pub CFGR: VolatileCell<u32>, // < RCC clock configuration register, Address offset: 0x08 + pub CIR: VolatileCell<u32>, // < RCC clock interrupt register, Address offset: 0x0C + pub AHB1RSTR: VolatileCell<u32>, // < RCC AHB1 peripheral reset register, Address offset: 0x10 + pub AHB2RSTR: VolatileCell<u32>, // < RCC AHB2 peripheral reset register, Address offset: 0x14 + pub AHB3RSTR: VolatileCell<u32>, // < RCC AHB3 peripheral reset register, Address offset: 0x18 + pub RESERVED0: VolatileCell<u32>, // < Reserved, 0x1C + pub APB1RSTR: VolatileCell<u32>, // < RCC APB1 peripheral reset register, Address offset: 0x20 + pub APB2RSTR: VolatileCell<u32>, // < RCC APB2 peripheral reset register, Address offset: 0x24 + pub RESERVED1: [VolatileCell<u32>; 2], // < Reserved, 0x28-0x2C + pub AHB1ENR: VolatileCell<u32>, // < RCC AHB1 peripheral clock register, Address offset: 0x30 + pub AHB2ENR: VolatileCell<u32>, // < RCC AHB2 peripheral clock register, Address offset: 0x34 + pub AHB3ENR: VolatileCell<u32>, // < RCC AHB3 peripheral clock register, Address offset: 0x38 + pub RESERVED2: VolatileCell<u32>, // < Reserved, 0x3C + pub APB1ENR: VolatileCell<u32>, // < RCC APB1 peripheral clock enable register, Address offset: 0x40 + pub APB2ENR: VolatileCell<u32>, // < RCC APB2 peripheral clock enable register, Address offset: 0x44 + pub RESERVED3: [VolatileCell<u32>; 2], // < Reserved, 0x48-0x4C + pub AHB1LPENR: VolatileCell<u32>, // < RCC AHB1 peripheral clock enable in low power mode register, Address offset: 0x50 + pub AHB2LPENR: VolatileCell<u32>, // < RCC AHB2 peripheral clock enable in low power mode register, Address offset: 0x54 + pub AHB3LPENR: VolatileCell<u32>, // < RCC AHB3 peripheral clock enable in low power mode register, Address offset: 0x58 + pub RESERVED4: VolatileCell<u32>, // < Reserved, 0x5C + pub APB1LPENR: VolatileCell<u32>, // < RCC APB1 peripheral clock enable in low power mode register, Address offset: 0x60 + pub APB2LPENR: VolatileCell<u32>, // < RCC APB2 peripheral clock enable in low power mode register, Address offset: 0x64 + pub RESERVED5: [VolatileCell<u32>; 2], // < Reserved, 0x68-0x6C + pub BDCR: VolatileCell<u32>, // < RCC Backup domain control register, Address offset: 0x70 + pub CSR: VolatileCell<u32>, // < RCC clock control & status register, Address offset: 0x74 + pub RESERVED6: [VolatileCell<u32>; 2], // < Reserved, 0x78-0x7C + pub SSCGR: VolatileCell<u32>, // < RCC spread spectrum clock generation register, Address offset: 0x80 + pub PLLI2SCFGR: VolatileCell<u32>, // < RCC PLLI2S configuration register, Address offset: 0x84 + } + + impl RCC { + pub fn get() -> *mut RCC { + address::RCC_BASE as *mut RCC + } + } + + #[repr(C)] + #[allow(non_snake_case)] + #[rustfmt::skip] + pub struct GPIOA { + pub MODER: VolatileCell<u32>, // < GPIO port mode register, Address offset: 0x00 + pub OTYPER: VolatileCell<u32>, // < GPIO port output type register, Address offset: 0x04 + pub OSPEEDR: VolatileCell<u32>, // < GPIO port output speed register, Address offset: 0x08 + pub PUPDR: VolatileCell<u32>, // < GPIO port pull-up/pull-down register, Address offset: 0x0C + pub IDR: VolatileCell<u32>, // < GPIO port input data register, Address offset: 0x10 + pub ODR: VolatileCell<u32>, // < GPIO port output data register, Address offset: 0x14 + pub BSRRL: VolatileCell<u16>, // < GPIO port bit set/reset low register, Address offset: 0x18 + pub BSRRH: VolatileCell<u16>, // < GPIO port bit set/reset high register, Address offset: 0x1A + pub LCKR: VolatileCell<u32>, // < GPIO port configuration lock register, Address offset: 0x1C + pub AFR: [VolatileCell<u32>;2], // < GPIO alternate function registers, Address offset: 0x20-0x24 + } + + impl GPIOA { + pub fn get() -> *mut GPIOA { + GPIOA_BASE as *mut GPIOA + } + } +} +use stm32f40x::*; + +// see the Reference Manual RM0368 (www.st.com/resource/en/reference_manual/dm00096844.pdf) +// rcc, chapter 6 +// gpio, chapter 8 + +fn wait(i: u32) { + for _ in 0..i { + cortex_m::asm::nop(); // no operation (cannot be optimized out) + } +} + +// simple test of Your `modify` +//fn test() { +// let t:VolatileCell<u32> = unsafe { core::mem::uninitialized() }; +// t.write(0); +// assert!(t.read() == 0); +// t.modify(3, 3, 0b10101); +// // +// // 10101 +// // ..0111000 +// // --------- +// // 000101000 +// assert!(t.read() == 0b101 << 3); +// t.modify(4, 3, 0b10001); +// // 000101000 +// // 111 +// // 001 +// // 000011000 +// assert!(t.read() == 0b011 << 3); + +// if << is used, your code will panic in dev (debug), but not in release mode +// t.modify(32, 3, 1); +//} + +// system startup, can be hidden from the user +#[entry] +fn main() -> ! { + let rcc = unsafe { &mut *RCC::get() }; // get the reference to RCC in memory + let gpioa = unsafe { &mut *GPIOA::get() }; // get the reference to GPIOA in memory + + // test(); // uncomment to run test + idle(rcc, gpioa); + loop { + continue; + } +} + +// user application +fn idle(rcc: &mut RCC, gpioa: &mut GPIOA) { + // power on GPIOA + 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 + + loop { + // set PA5 high + gpioa.BSRRH.write(1 << 5); // set bit, output hight (turn on led) + + // alternatively to set the bit high we can + // read the value, or with PA5 (bit 5) and write back + // gpioa.ODR.write(gpioa.ODR.read() | (1 << 5)); + + wait(10_000); + + // set PA5 low + gpioa.BSRRL.write(1 << 5); // clear bit, output low (turn off led) + + // alternatively to clear the bit we can + // read the value, mask out PA5 (bit 5) and write back + // gpioa.ODR.write(gpioa.ODR.read() & !(1 << 5)); + wait(10_000); + } +} + +// 0. Build and run the application. +// +// > cargo build --example bare5 +// (or use the vscode) +// +// 1. C like API. +// Using C the .h files are used for defining interfaces, like function signatures (prototypes), +// structs and macros (but usually not the functions themselves). +// +// Here is a peripheral abstraction quite similar to what you would find in the .h files +// provided by ST (and other companies). Actually, the file presented here is mostly a +// cut/paste/replace of the stm32f40x.h, just Rustified. +// +// In this case we pass mutable pointers of the peripherals to the `idle` function. +// +// In the loop we access PA5 through bit set/clear operations. +// Comment out those operations and uncomment the the ODR accesses. +// (They should have the same behavior, but is a bit less efficient.) +// +// Run and see that the program behaves the same. +// +// Commit your answers (bare5_1) +// +// 2. Extend the read/write API with a `modify` for u32, taking the +// - address (&mut u32), +// - field offset (in bits, u8), +// - field width (in bits, u8), +// - and value (u32). +// +// Implement and check that running `test` gives you expected behavior. +// +// Change the code into using your new API. +// +// Run and see that the program behaves the same. +// +// Commit your answers (bare5_2) +// +// Discussion: +// As with arithmetic operations, default semantics differ in between +// debug/dev and release builds. E.g., debug << rhs is checked, rhs must be less +// than 32 (for 32 bit datatypes). +// +// Notice, over-shifting (where bits are spilled) is always considered legal, +// its just the shift amount that is checked. +// There are explicit unchecked versions available if so wanted. diff --git a/examples/bare6.rs b/examples/bare6.rs new file mode 100644 index 0000000000000000000000000000000000000000..fe91a1398a516097a29b3c7727ef156b5752fe43 --- /dev/null +++ b/examples/bare6.rs @@ -0,0 +1,212 @@ +//! bare6.rs +//! +//! Clocking +//! +//! What it covers: +//! - using svd2rust generated API +//! - setting the clock via script (again) +//! - routing the clock to a PIN for monitoring by an oscilloscope + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::{iprintln, peripheral::itm::Stim}; +use cortex_m_rt::entry; + +use stm32f4::stm32f401::{self, DWT, GPIOA, GPIOC, RCC}; + +#[entry] +fn main() -> ! { + let p = stm32f401::Peripherals::take().unwrap(); + let mut c = stm32f401::CorePeripherals::take().unwrap(); + + let stim = &mut c.ITM.stim[0]; + iprintln!(stim, "bare6"); + + c.DWT.enable_cycle_counter(); + unsafe { + c.DWT.cyccnt.write(0); + } + let t = DWT::get_cycle_count(); + iprintln!(stim, "{}", t); + + clock_out(&p.RCC, &p.GPIOC); + idle(stim, p.RCC, p.GPIOA); +} + +// user application +fn idle(stim: &mut Stim, rcc: RCC, gpioa: GPIOA) -> ! { + iprintln!(stim, "idle"); + + // power on GPIOA, RM0368 6.3.11 + rcc.ahb1enr.modify(|_, w| w.gpioaen().set_bit()); + + // configure PA5 as output, RM0368 8.4.1 + gpioa.moder.modify(|_, w| w.moder5().bits(1)); + + // at 16 Mhz, 8_000_000 cycles = period 0.5s + // at 64 Mhz, 4*8_000_000 cycles = period 0.5s + // let cycles = 8_000_000; + let cycles = 4 * 8_000_000; + + loop { + iprintln!(stim, "on {}", DWT::get_cycle_count()); + // set PA5 high, RM0368 8.4.7 + gpioa.bsrr.write(|w| w.bs5().set_bit()); + wait_cycles(cycles); + + iprintln!(stim, "off {}", DWT::get_cycle_count()); + // set PA5 low, RM0368 8.4.7 + gpioa.bsrr.write(|w| w.br5().set_bit()); + wait_cycles(cycles); + } +} + +// uses the DWT.CYCNT +// doc: ARM trm_100166_0001_00_en.pdf, chapter 9.2 +// we use the `cortex-m` abstraction, as re-exported by the stm32f40x +fn wait_cycles(nr_cycles: u32) { + let t = DWT::get_cycle_count().wrapping_add(nr_cycles); + while (DWT::get_cycle_count().wrapping_sub(t) as i32) < 0 {} +} + +// see the Reference Manual RM0368 (www.st.com/resource/en/reference_manual/dm00096844.pdf) +// rcc, chapter 6 +// gpio, chapter 8 +fn clock_out(rcc: &RCC, gpioc: &GPIOC) { + // output MCO2 to pin PC9 + + // mco2 : SYSCLK = 0b00 + // mcopre : divide by 4 = 0b110 + rcc.cfgr + .modify(|_, w| unsafe { w.mco2().bits(0b00).mco2pre().bits(0b110) }); + + // power on GPIOC, RM0368 6.3.11 + rcc.ahb1enr.modify(|_, w| w.gpiocen().set_bit()); + + // MCO_2 alternate function AF0, STM32F401xD STM32F401xE data sheet + // table 9 + // AF0, gpioc reset value = AF0 + + // configure PC9 as alternate function 0b10, RM0368 6.2.10 + gpioc.moder.modify(|_, w| w.moder9().bits(0b10)); + + // otyper reset state push/pull, in reset state (don't need to change) + + // ospeedr 0b11 = very high speed + gpioc.ospeedr.modify(|_, w| w.ospeedr9().bits(0b11)); +} + +// Optional assignment +// 0. Compile and run the example, in 16Mhz +// +// > cargo build --example bare6 --features "stm32f4" +// (or use the vscode build task) +// +// The "stm32f4" feature enables the optional dependency "stm32f4", with the "stm32f401" feature set +// +// Cargo.toml: +// +// [dependencies.stm32f4] +// version = "0.9.0" +// features = ["stm32f401", "rt"] +// optional = true +// (this will provide the "stm32f401" interrupt vector through the "rt" feature) +// +// [[example]] +// name = "bare6" +// required-features = ["stm32f4"] +// (this will require/force you to use the stm32f4 feature) +// +// 1. The processor SYSCLK defaults to HCI 16Mhz +// (this is what you get after a `monitor reset halt`). +// +// Confirm that your ITM dump traces the init, idle and led on/off. +// Make sure your TPIU is set to a system clock at 16Mhz +// +// What is the frequency of blinking? +// +// ** your answer here ** +// +// commit your answers (bare6_1) +// +// 2. Now connect an oscilloscope to PC9, which is set to +// output the MCO2. +// +// What is the frequency of MCO2 read by the oscilloscope? +// +// ** your answer here ** +// +// Compute the value of SYSCLK based on the oscilloscope reading +// +// ** your answer here ** +// +// What is the peak to peak reading of the signal? +// +// ** your answer here ** +// +// Make a folder called "pictures" in your git project. +// Make a screen dump or photo of the oscilloscope output. +// Save the the picture as "bare_6_16mhz_high_speed". +// +// Commit your answers (bare6_2) +// +// 3. Now run the example in 64Mz +// You can do that by issuing a `monitor reset init` +// which reprograms SYSCLK to 4*HCI. +// +// +// Confirm that your ITM dump traces the init, idle and led on/off +// (make sure your TPIU is set to a system clock at 64Mhz) +// +// Uncommnet: `let cycles = 4 * 8_000_000; +//` +// What is the frequency of blinking? +// +// ** your answer here ** +// +// Commit your answers (bare6_3) +// +// 4. Repeat experiment 2 +// +// What is the frequency of MCO2 read by the oscilloscope? +// +// ** your answer here ** +// +// Compute the value of SYSCLK based on the oscilloscope reading. +// +// ** your answer here ** +// +// What is the peak to peak reading of the signal? +// +// ** your answer here ** +// +// Make a screen dump or photo of the oscilloscope output. +// Save the the picture as "bare_6_64mhz_high_speed". +// +// Commit your answers (bare6_4) +// +// 5. In the `clock_out` function, the setup of registers is done through +// setting bit-pattens manually, e.g. +// rcc.cfgr +// .modify(|_, w| unsafe { w.mco2().bits(0b00).mco2pre().bits(0b110) }); +// +// However based on the vendor SVD file the svd2rust API provides +// a better abstraction, based on pattern enums and functions. +// +// To view the API you can generate documentation for your crate: +// +// > cargo doc --features "stm32f4" --open +// +// By searching for `mco2` you find the enumerations and functions. +// So here +// `w.mco2().bits{0b00}` is equivalent to +// `w.mco2().sysclk()` and improves readability. +// +// Replace all bit-patterns used by the function name equivalents. +// +// Test that the application still runs as before. +// +// Commit your code (bare6_4) \ No newline at end of file diff --git a/examples/bare7.rs b/examples/bare7.rs new file mode 100644 index 0000000000000000000000000000000000000000..e43cd841e48101b123711a68b11d8762fbd99f56 --- /dev/null +++ b/examples/bare7.rs @@ -0,0 +1,240 @@ +//! bare7.rs +//! +//! Serial echo +//! +//! What it covers: +//! - changing the clock using Rust code +//! - working with the svd2rust API +//! - working with the HAL (Hardware Abstraction Layer) +//! - USART polling (blocking wait) + +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::iprintln; +use cortex_m_rt::entry; +use nb::block; + +extern crate stm32f4xx_hal as hal; +use crate::hal::prelude::*; +use crate::hal::serial::{config::Config, Serial}; + +#[entry] +fn main() -> ! { + let mut c = hal::stm32::CorePeripherals::take().unwrap(); + let stim = &mut c.ITM.stim[0]; + + let p = hal::stm32::Peripherals::take().unwrap(); + + let rcc = p.RCC.constrain(); + + // 16 MHz (default, all clocks) + let clocks = rcc.cfgr.freeze(); + // 84 MHz (with valid config for pclk1 and pclk2) + // let clocks = rcc.cfgr.sysclk(84.mhz()).pclk1(42.mhz()).pclk2(84.mhz()).freeze(); + + let gpioa = p.GPIOA.split(); + + let tx = gpioa.pa2.into_alternate_af7(); + let rx = gpioa.pa3.into_alternate_af7(); // try comment out + + // let rx = gpioa.pa3.into_alternate_af6(); // try uncomment + + let serial = Serial::usart2( + p.USART2, + (tx, rx), + Config::default().baudrate(115_200.bps()), + clocks, + ) + .unwrap(); + iprintln!(stim, "bare7"); + + // Separate out the sender and receiver of the serial port + let (mut tx, mut rx) = serial.split(); + + loop { + match block!(rx.read()) { + Ok(byte) => { + iprintln!(stim, "Ok {:?}", byte); + tx.write(byte).unwrap(); + } + Err(err) => { + iprintln!(stim, "Error {:?}", err); + } + } + } +} + +// Optional assignment +// 0. Background reading: +// STM32F401xD STM32F401xE, section 3.11 +// We have two AMBA High-performance Bus (AHB) +// APB1 low speed bus (max freq 42 MHz) +// APB2 high speed bus (max frex 84 MHz) +// +// RM0368 Section 6.2 +// Some important/useful clock acronyms and their use: +// +// SYSCLK - the clock that drives the `core` +// HCLK - the clock that drives the AMBA bus(es), memory, DMA, trace unit, etc. +// +// Typically we set HCLK = SYSCLK / 1 (no prescale) for our applications +// +// FCLK - Free running clock runing at HCLK +// +// CST - CoreSystemTimer drives the SysTick counter, HCLK/(1 or 8) +// PCLK1 - The clock driving the APB1 (<= 42 MHz) +// Timers on the APB1 bus will be triggered at PCLK1 * 2 +// PCLK2 - The clock driving the APB2 (<= 84 MHz) +// Timers on the APB2 bus will be triggered at PCLK2 +// +// Compliation: +// > cargo build --example bare7 --features "stm32fxx-hal" +// (or use the vscode build task) +// +// +// Cargo.toml: +// +// [dependencies.stm32f4] +// version = "0.5.0" +// features = ["stm32f413", "rt"] +// optional = true +// +// [dependencies.stm32f4xx-hal] +// version = "0.6.0" +// features = ["stm32f401", "rt"] +// optional = true +// +// Notice, stm32f4xx-hal internally enables the dependency to stm32f4, +// so we don't need to explicitly enable it. +// +// The HAL provides a generic abstraction over the whole stm32f4 family. +// +// 1. The rcc.cfgr.x.freeze() sets the clock according to the configuration x given. +// +// rcc.cfgr.freeze(); sets a default configuration. +// sysclk = hclk = pclk1 = pclk2 = 16MHz +// +// What is wrong with the following configurations? +// +// rcc.cfgr.sysclk(64.mhz()).pclk1(64.mhz()).pclk2(64.mhz()).freeze(); +// +// ** your answer here ** +// +// rcc.cfgr.sysclk(84.mhz()).pclk1(42.mhz()).pclk2(64.mhz()).freeze(); +// +// ** your answer here ** +// +// Commit your answers (bare7_1) +// +// Tip: You may use `stm32cubemx` to get a graphical view for experimentation. +// +// 2. Now give the system with a valid clock, sysclk of 84 MHz. +// +// Include the code for outputting the clock to MCO2. +// +// Repeat the experiment bare6_2. +// +// What is the frequency of MCO2 read by the oscilloscope. +// +// ** your answer here ** +// +// Compute the value of SYSCLK based on the oscilloscope reading. +// +// ** your answer here ** +// +// What is the peak to peak reading of the signal. +// +// ** your answer here ** +// +// Make a screen dump or photo of the oscilloscope output. +// Save the the picture as "bare_7_84mhz_high_speed" +// +// Commit your answers (bare7_2) +// +// 3. Now reprogram the PC9 to be "Low Speed", and re-run at 84Mz. +// +// Did the frequency change in comparison to assignment 6? +// +// ** your answer here ** +// +// What is the peak to peak reading of the signal (and why did it change)? +// +// ** your answer here ** +// +// Make a screen dump or photo of the oscilloscope output. +// Save the the picture as "bare_7_84mhz_low_speed". +// +// Commit your answers (bare7_3) +// +// 4. Revisit the `README.md` regarding serial communication. +// 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 +// +// This setting is typically abbreviated as 115200 8N1. +// +// Run the example, make sure your ITM is set to 84MHz. +// +// Send a single character (byte), (set the option No end in moserial). +// Verify that sent bytes are echoed back, and that ITM tracing is working. +// +// If not go back check your ITM setting, clocks etc. +// +// Try sending: "abcd" as a single sequence, don't send the quotation marks, just abcd. +// +// What did you receive, and what was the output of the ITM trace. +// +// ** your answer here ** +// +// commit your answers (bare7_4) +// +// 5. Now, set the CPU to run at 16MHz. +// Repeat the experiment 7.4 (make sure your ITM is set to 16MHz) +// +// Try sending: "abcd" as a single sequence, don't send the quotation marks, just abcd. +// +// What did you receive, and what was the output of the ITM trace. +// +// ** your answer here ** +// +// Explain why the buffer overflows. +// +// ** your answer here ** +// +// commit your answers (bare7_4) +// +// Discussion: +// Common to all MCUs is that they have multiple clocking options. +// Understanding the possibilities and limitations of clocking is fundamental +// to designing both the embedded hardware and software. Tools like +// `stm32cubemx` can be helpful to give you the big picture. +// +// The `stm32f4xx-hal` gives you an abstraction for programming, +// setting up clocks, assigning pins, etc. +// +// The hal overs basic functionality like serial communication. +// Still, in order to fully understand what is going on under the hood you need to +// check the documentation (data sheets, user manuals etc.) +// +// Your crate can be documented by: +// +// > cargo doc --open --features "stm32f4xx-hal" +// +// This will document both your crate and its dependencies besides the `core` library. +// +// You can open the `core` library documentation by +// +// > rustup doc +// +// or just show the path to the doc (to open it manually) +// +// > rustup doc --path diff --git a/examples/bare8.rs b/examples/bare8.rs new file mode 100644 index 0000000000000000000000000000000000000000..c902fe1b834b359b702ee4c5a3f3643c84621850 --- /dev/null +++ b/examples/bare8.rs @@ -0,0 +1,126 @@ +//! bare8.rs +//! +//! The RTFM framework +//! +//! What it covers: +//! - utilizing the RTFM framework for serial communication +//! - singletons (entities with a singe instance) +//! - owned resources +//! - peripheral access in RTFM +//! - polling in `idle` + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m_semihosting::hprintln; +use nb::block; + +extern crate stm32f4xx_hal as hal; +use crate::hal::prelude::*; +use crate::hal::serial::{config::Config, Rx, Serial, Tx}; +use hal::stm32::{ITM, USART2}; + +use rtfm::app; + +#[app(device = hal::stm32, peripherals = true)] +const APP: () = { + struct Resources { + // Late resources + TX: Tx<USART2>, + RX: Rx<USART2>, + } + + // init runs in an interrupt free section + #[init] + fn init(cx: init::Context) -> init::LateResources { + let mut core = cx.core; + let device = cx.device; + + hprintln!("bare8"); + + let rcc = device.RCC.constrain(); + + // 16 MHz (default, all clocks) + let clocks = rcc.cfgr.freeze(); + + let gpioa = device.GPIOA.split(); + + let tx = gpioa.pa2.into_alternate_af7(); + let rx = gpioa.pa3.into_alternate_af7(); + + let serial = Serial::usart2( + device.USART2, + (tx, rx), + Config::default().baudrate(9_600.bps()), + clocks, + ) + .unwrap(); + + // Separate out the sender and receiver of the serial port + let (tx, rx) = serial.split(); + + // Late resources + init::LateResources { + TX: tx, + RX: rx, + } + } + + // idle may be interrupted by other interrupts/tasks in the system + #[idle(resources = [RX, TX])] + fn idle(cx: idle::Context) -> ! { + let rx = cx.resources.RX; + let tx = cx.resources.TX; + + loop { + match block!(rx.read()) { + Ok(byte) => { + hprintln!("Ok {:?}", byte); + tx.write(byte).unwrap(); + } + Err(err) => { + hprintln!("Error {:?}", err); + } + } + } + } +}; + +// Optional assignment +// 0. Compile and run the example. Notice, we use the default 16MHz clock. +// +// > cargo build --example bare8 --features "rtfm" +// (or use the vscode build task) +// +// 1. What is the behavior in comparison to bare7.4 and bare7.5 +// +// ** your answer here ** +// +// Commit your answer (bare8_1) +// +// 2. Add a local variable `received` that counts the number of bytes received. +// Add a local variable `errors` that counts the number of errors. +// +// Adjust the ITM trace to include the additional information. +// +// Commit your development (bare8_2) +// +// 3. The added tracing, how did that effect the performance, +// (are you know loosing more data)? +// +// ** your answer here ** +// +// Commit your answer (bare8_3) +// +// 4. *Optional +// Compile and run the program in release mode. +// If using vscode, look at the `.vscode` folder `task.json` and `launch.json`, +// you may need to add a new "profile" (a bit of copy paste). +// +// How did the optimized build compare to the debug build (performance/lost bytes) +// +// ** your answer here ** +// +// Commit your answer (bare8_4) diff --git a/examples/bare9.rs b/examples/bare9.rs new file mode 100644 index 0000000000000000000000000000000000000000..cf8a2c4ad67689aea6d043a845fd5e05b4e05a9b --- /dev/null +++ b/examples/bare9.rs @@ -0,0 +1,248 @@ +//! bare9.rs +//! +//! Heapless +//! +//! What it covers: +//! - Heapless Ringbuffer +//! - Heapless Producer/Consumer lockfree data access +//! - Interrupt driven I/O +//! + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::{asm, iprintln}; + +extern crate stm32f4xx_hal as hal; +use crate::hal::prelude::*; +use crate::hal::serial::{config::Config, Event, Rx, Serial, Tx}; +use hal::stm32::ITM; + +use heapless::consts::*; +use heapless::spsc::{Consumer, Producer, Queue}; +use nb::block; + +use rtfm::app; + +#[app(device = hal::stm32, peripherals = true)] +const APP: () = { + struct Resources { + // Late resources + TX: Tx<hal::stm32::USART2>, + RX: Rx<hal::stm32::USART2>, + PRODUCER: Producer<'static, u8, U3>, + CONSUMER: Consumer<'static, u8, U3>, + ITM: ITM, + // An initialized resource + #[init(None)] + RB: Option<Queue<u8, U3>>, + } + // init runs in an interrupt free section + #[init(resources = [RB])] + fn init(cx: init::Context) -> init::LateResources { + let mut core = cx.core; + let device = cx.device; + // A ring buffer for our data + *cx.resources.RB = Some(Queue::new()); + + // Split into producer/consumer pair + let (producer, consumer) = cx.resources.RB.as_mut().unwrap().split(); + + let stim = &mut core.ITM.stim[0]; + iprintln!(stim, "bare9"); + + let rcc = device.RCC.constrain(); + + // 16 MHz (default, all clocks) + let clocks = rcc.cfgr.freeze(); + + let gpioa = device.GPIOA.split(); + + let tx = gpioa.pa2.into_alternate_af7(); + let rx = gpioa.pa3.into_alternate_af7(); + + let mut serial = Serial::usart2( + device.USART2, + (tx, rx), + Config::default().baudrate(115_200.bps()), + clocks, + ) + .unwrap(); + + // generate interrupt on Rxne + serial.listen(Event::Rxne); + // Separate out the sender and receiver of the serial port + let (tx, rx) = serial.split(); + + // Late resources + init::LateResources { + // Our split queue + PRODUCER: producer, + CONSUMER: consumer, + + // Our split serial + TX: tx, + RX: rx, + + // For debugging + ITM: core.ITM, + } + } + + // idle may be interrupted by other interrupt/tasks in the system + #[idle(resources = [ITM, CONSUMER])] + fn idle(cx: idle::Context) -> ! { + let stim = &mut cx.resources.ITM.stim[0]; + + loop { + while let Some(byte) = cx.resources.CONSUMER.dequeue() { + iprintln!(stim, "data {}", byte); + } + + iprintln!(stim, "goto sleep"); + asm::wfi(); + + iprintln!(stim, "woken.."); + } + } + + // task run on USART2 interrupt (set to fire for each byte received) + #[task(binds = USART2, resources = [RX, TX, PRODUCER])] + fn usart2(cx: usart2::Context) { + let rx = cx.resources.RX; + let tx = cx.resources.TX; + + // at this point we know there must be a byte to read + match rx.read() { + Ok(byte) => { + tx.write(byte).unwrap(); + + match cx.resources.PRODUCER.enqueue(byte) { + Ok(_) => {} + Err(_) => asm::bkpt(), + } + } + Err(_err) => asm::bkpt(), + } + } +}; + +// Optional +// 0. Compile and run the project at 16MHz in release mode +// make sure its running (not paused). +// +// > cargo build --example bare9 --features "rtfm" --release +// (or use the vscode build task) +// +// 1. Start a terminal program, connect with 15200 8N1 +// +// You should now be able to send data and receive an echo from the MCU +// +// Try sending: "abcd" as a single sequence (set the option No end in moserial), +// don't send the quotation 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 ** +// +// Why does it behave differently than bare7/bare8? +// +// ** your answer here ** +// +// Commit your answers (bare9_1) +// +// 2. Compile and run the project at 16MHz in debug mode. +// +// > cargo build --example bare9 --features "hal rtfm" +// (or use the vscode build task) +// +// Try sending: "abcd" as a single sequence (set the option No end in moserial), +// don't send the quotation 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 ** +// +// Why does it behave differently than in release mode? +// Recall how the execution overhead changed with optimization level. +// +// ** your answer here ** +// +// Commit your answers (bare9_2) +// +// Discussion: +// +// The concurrency model behind RTFM offers +// 1. Race-free resource access +// +// 2. Deadlock-free execution +// +// 3. Shared execution stack (no pre-allocated stack regions) +// +// 4. Bound priority inversion +// +// 5. Theoretical underpinning -> +// + (pen and paper) proofs of soundness +// + schedulability analysis +// + response time analysis +// + stack memory analysis +// + ... leverages on >25 years of research 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 circumstances). +// +// 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 lock/claim entry) +// + 1 cycle OH for releasing a shared resource (on lock/claim exit) +// +// 3. arguably the worlds most memory efficient scheduler * +// + 1 byte stack memory OH for each (nested) lock/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 +// - ... and what's worse OH is typically unbound (no proofs of worst case) +// And additionally threaded models typically imposes +// - potential race conditions (up to the user to verify) +// - potential dead-locks (up to the implementation) +// - potential unbound priority inversion (up to the implementation) +// +// However, 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 infinite by nature and +// actively blocking/yielding awaiting stischedulers +// The scheduler then needs to keep track of all threads and at some point choose +// to dispatch the awaiting thread. So reactivity is bottle-necked to the point +// of scheduling by queue management, context switching and other additional +// book keeping. +// +// In essence, the thread scheduler tries to re-establish the reactivity that +// were there from the beginning (interrupts), a battle that cannot be won... diff --git a/examples/rtfm_blinky_sw_reset.rs b/examples/rtfm_blinky_sw_reset.rs new file mode 100644 index 0000000000000000000000000000000000000000..bf8367568a1420cbb0f02a1e8dbdfc622fdd8aad --- /dev/null +++ b/examples/rtfm_blinky_sw_reset.rs @@ -0,0 +1,73 @@ +#![deny(unsafe_code)] +// #![deny(warnings)] +#![no_main] +#![no_std] + +use cortex_m; +use cortex_m::peripheral::DWT; +use panic_halt as _; +use rtfm::cyccnt::U32Ext; +use stm32f4xx_hal::stm32; + +#[rtfm::app(device = stm32f4xx_hal::stm32, monotonic = rtfm::cyccnt::CYCCNT, peripherals = true)] +const APP: () = { + #[init(schedule = [toggle])] + fn init(cx: init::Context) { + let mut core = cx.core; + let device = cx.device; + + // Initialize (enable) the monotonic timer (CYCCNT) + core.DCB.enable_trace(); + // required on Cortex-M7 devices that software lock the DWT (e.g. STM32F7) + DWT::unlock(); + core.DWT.enable_cycle_counter(); + + // semantically, the monotonic timer is frozen at time "zero" during `init` + // NOTE do *not* call `Instant::now` in this context; it will return a nonsense value + let now = cx.start; // the start time of the system + + // power on GPIOA, RM0368 6.3.11 + device.RCC.ahb1enr.modify(|_, w| w.gpioaen().set_bit()); + // configure PA5 as output, RM0368 8.4.1 + device.GPIOA.moder.modify(|_, w| w.moder5().bits(1)); + + cx.schedule + .toggle(now + 8_000_000.cycles(), true, device.GPIOA, core.SCB, 1) + .ok(); + } + + #[task(schedule= [toggle])] + fn toggle( + cx: toggle::Context, + toggle: bool, + gpioa: stm32::GPIOA, + mut scb: stm32::SCB, + mut state: u8, + ) { + if toggle { + gpioa.bsrr.write(|w| w.bs5().set_bit()); + } else { + gpioa.bsrr.write(|w| w.br5().set_bit()); + } + + state += 1; + if state == 10 { + //state = 0; + // scb.system_reset(); + cortex_m::peripheral::SCB::sys_reset(); + }; + cx.schedule + .toggle( + cx.scheduled + (state as u32 * 400_000).cycles(), + !toggle, + gpioa, + scb, + state, + ) + .ok(); + } + + extern "C" { + fn EXTI0(); + } +};