Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
1 result

Target

Select target project
  • pln/e7020e_2020
  • 97gushan/e7020e_2020
  • markhakansson/e7020e_2020
  • Grumme2/e7020e_2020
  • Hammarkvast/e7020e_2020
5 results
Select Git revision
  • master
1 result
Show changes
Commits on Source (20)
......@@ -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",
......
{
// 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
......
......@@ -5,21 +5,22 @@ 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]
verison = "0.6.2"
version = "0.6.2"
# features = ["inline-asm"] # <- currently requires nightly compiler
[dependencies.cortex-m-rt]
......@@ -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
......
......@@ -557,7 +557,7 @@ $ kill -9 7825
There can be a number of reasons ITM tracing fails.
- The `openocd.gdb` script enables ITM tracing assuming the `/tmp/itm.log` and `itmdump` has been correctly setup before `gdb` is launched (and the script run). So the first thing is to check that you follow the sequence suggested above.
- The `openocd.gdb` script (or `launch.json` if in vscode using external ITM logging) enables ITM tracing assuming that the `/tmp/itm.fifo` and `itmdump` has been correctly setup before `gdb` is launched. So the first thing is to check that you follow the sequence suggested above.
- `openocd.gdb`sets enables ITM tracing by:
......@@ -593,7 +593,7 @@ This invokes the `init` event, which sets the core clock to 64MHz. If you intend
monitor tpiu config internal /tmp/itm.fifo uart off 64000000 2000000
```
If you on the other hand want to use `monitor reset init` but not having the core clock set to 64MHz, you can use a custom `.cfg` (instead of the one shipped with `openocd`). The original `/usr/share/openocd/scripts/target/stm32f0x.cfg` looks like this:
If you on the other hand want to use `monitor reset init` but not having the core clock set to 64MHz, you can use a custom `.cfg` (instead of the one shipped with `openocd`). The original `/usr/share/openocd/scripts/target/stm32f4x.cfg` looks like this:
``` txt
...
......@@ -808,6 +808,16 @@ We see some similarities to the `openocd.gdb` file, we don't need to explicitly
---
### Rust-Analyzer
In order to properly track symbols from the `core` and/or `std` libraries, you need to add the Rust source. This can be done by:
``` shell
> rustup component add rust-src
````
---
## GDB Advanced Usage
There are numerous ways to automate `gdb`. Scripts can be run by the `gdb` command `source` (`so` for short). Scripting common tasks like setting breakpoints, dumping some memory region etc. can be really helpful.
......
......@@ -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;
......@@ -50,10 +46,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.
......
......@@ -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 **
//
......@@ -91,7 +91,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.
......@@ -109,6 +109,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:
......@@ -131,7 +136,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`
......@@ -144,11 +174,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.
//
......@@ -164,7 +194,7 @@ fn main() -> ! {
//
// ** your answer here **
//
// commit your answers (bare1_5)
// commit your answers (bare1_6)
//
// Final discussion:
//
......
//! 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)
//! 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)
//! 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 **
//! 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)
//! 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.
//! 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
//! 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
//! 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::iprintln;
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>,
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, "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(115_200.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,
ITM: core.ITM,
}
}
// idle may be interrupted by other interrupts/tasks in the system
#[idle(resources = [RX, TX, ITM])]
fn idle(cx: idle::Context) -> ! {
let rx = cx.resources.RX;
let tx = cx.resources.TX;
let stim = &mut cx.resources.ITM.stim[0];
loop {
match block!(rx.read()) {
Ok(byte) => {
iprintln!(stim, "Ok {:?}", byte);
tx.write(byte).unwrap();
}
Err(err) => {
iprintln!(stim, "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)
//! 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...
#![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();
}
};