Commit a3ce7984 authored by Per Lindgren's avatar Per Lindgren
Browse files

added exercises

parent b793907e
# Changelog
## 2021-02-28
- examples/bare2.rs, raw timer access.
- examples/bare3.rs, timing abstractions.
- examples/bare4.rs, a simple bare metal peripheral access API.
- examples/bare5.rs, write your own C-like peripheral access API.
## 2021-02-26
- examples/bare1.rs, bare metal 101!
......
//! bare2.rs
//!
//! Measuring execution time
//!
//! What it covers
//! - Generating documentation
//! - Using core peripherals
//! - Measuring time using the DWT
#![no_main]
#![no_std]
// use panic_halt as _;
// use cortex_m::{iprintln, peripheral::DWT, Peripherals};
// use cortex_m_rt::entry;
use cortex_m::peripheral::DWT;
use cortex_m_semihosting::hprintln;
use panic_semihosting as _;
use stm32f4;
#[rtic::app(device = stm32f4)]
const APP: () = {
#[init]
fn init(mut cx: init::Context) {
cx.core.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!
hprintln!("Start {:?}", start).ok();
hprintln!("End {:?}", end).ok();
hprintln!("Diff {:?}", end.wrapping_sub(start)).ok();
// wait(100);
}
};
// burns CPU cycles by just looping `i` times
#[inline(never)]
#[no_mangle]
fn wait(i: u32) {
for _ in 0..i {
// no operation (ensured not optimized out)
cortex_m::asm::nop();
}
}
// 0. Setup
//
// > cargo doc --open
//
// `cargo.doc` will document your crate, and open the docs in your browser.
// If it does not auto-open, then copy paste the path shown in your browser.
//
// Notice, it will try to document all dependencies, you may have only one
// one panic handler, so temporarily 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 in vscode using (Cortex Debug).
//
// What is the output in the Adapter Output console?
// (Notice, it will take a while we loop one million times at only 16 MHz.)
//
// ** your answer here **
//
// Rebuild and run in (Cortex Release).
//
// ** your answer here **
//
// Compute the ratio between debug/release optimized code
// (the speedup).
//
// ** your answer here **
//
// commit your answers (bare2_1)
//
// 2. As seen there is a HUGE difference in between Debug and Release builds.
// In Debug builds, the compiler preserves all abstractions, so there will
// be a lot of calls and pointer indirections.
//
// In Release builds, the compiler strives to "smash" all abstractions into straight
// line code.
//
// This is what Rust "zero-cost abstractions" means, not zero execution time but rather,
// "as good as it possibly gets" (you pay no extra cost for using abstractions at run-time).
//
// In Release builds, the compiler is able to "specialize" the implementation
// of each function.
//
// Let us look in detail at the `wait` function:
// Place a breakpoint at line 54 (wait). Restart the (Cortex Release) session and
// look at the generated code.
//
// > disass
//
// Dump generated assembly for the "wait" function.
//
// ** your answer here **
//
// Under the ARM calling convention, r0.. is used as arguments.
// However in this case, we se that r0 is set by the assembly instructions,
// before the loop is entered.
//
// Lookup the two instructions `movw` and `movt` to figure out what happens here.
//
// Answer in your own words, how they assign r0 to 1000000.
//
// ** your answer here **
//
// Commit your answers (bare2_2)
//
// 3. Now add a second call to `wait` (line 47).
//
// Recompile and run until the breakpoint.
//
// Dump the generated assembly for the "wait" function.
//
// ** your answer here **
//
// Answer in your own words, why you believe the generated code differs?
//
// ** your answer here **
//
//
//! bare3.rs
//!
//! Measuring execution time
//!
//! What it covers
//! - Reading Rust documentation
//! - Timing abstractions and semantics
//! - Understanding Rust abstractions
#![no_main]
#![no_std]
use cortex_m_semihosting::hprintln;
use panic_semihosting as _;
use rtic::cyccnt::Instant;
use stm32f4;
#[rtic::app(device = stm32f4)]
const APP: () = {
#[init]
fn init(mut cx: init::Context) {
cx.core.DWT.enable_cycle_counter();
let start = Instant::now();
wait(1_000_000);
let end = Instant::now();
// notice all printing outside of the section to measure!
hprintln!("Start {:?}", start).ok();
hprintln!("End {:?}", end).ok();
// hprintln!("Diff {:?}", (end - start) ).ok();
}
};
// burns CPU cycles by just looping `i` times
#[inline(never)]
#[no_mangle]
fn wait(i: u32) {
for _ in 0..i {
// no operation (ensured not optimized out)
cortex_m::asm::nop();
}
}
// 0. Setup
//
// > cargo doc --open
//
// In the docs, search (`S`) for `Monotonic` and read the API docs.
// Also search for `Instant`, and `Duration`.
//
// Together these provide timing semantics.
//
// - `Monotonic` is a "trait" for a timer implementation.
// - `Instant` is a point in time.
// - `Duration` is a range in time.
//
// By default RTIC uses the `Systic` and the `DWT` cycle counter
// to provide a `Monotonic` timer.
//
// 1. Build and run the application in vscode using (Cortex Release).
//
// What is the output in the Adapter Output console?
//
// ** your answer here **
//
// As you see line 31 is commented out (we never print the difference).
//
// Now uncomment line 31, and try to run the program. You will see
// that it fails to compile right as `Duration` does not implement `Debug`
// (needed for formatting the printout.)
//
// This is on purpose as `Duration` is abstract (opaque). You need to
// turn it into a concrete value. Look at the documentation, to find out
// a way to turn it into clock cycles (which are printable).
//
// What is now the output in the Adapter Output console?
//
// ** your answer here **
//
// Commit your answers (bare3_1)
//
// 2. Look at the `Instant` documentation.
//
// Alter the code so that you use `duration_since`, instead of manual subtraction.
//
// What is now the output in the Adapter Output console?
//
// ** your answer here **
//
// Commit your answers (bare3_2)
//
// 3. Look at the `Instant` documentation.
// Now alter the code so that it uses `elapsed` instead.
//
// What is now the output in the Adapter Output console?
//
// ** your answer here **
//
// Commit your answers (bare3_3)
//
// 4. Discussion.
//
// If you did implement the above exercises correctly you should get exactly the same
// result (in clock cycles) for all cases as you got in the bare2 exercise.
// (If not, go back and revise your code.)
//
// What this shows, is that we can step away from pure hardware accesses
// and deal with time in a more convenient and "abstract" fashion.
//
// `Instant` and `Duration` are associated with semantics (meaning).
// `Monotonic` is associated the implementation.
//
// This is an example of separation of concerns!
//
// If you implement your application based on Instant and Duration, your code
// will be "portable" across all platforms (that implement Monotonic).
//
// The implementation of Monotonic is done only once for each platform, thus
// bugs related to low level timer access will occur only at one place,
// not scattered across thousands of manually written applications.
//
// However, as you have already seen, the current time abstraction (API) is
// is rather "thin" (provided just a bare minimum functionality).
//
// We are working to further generalize timing semantics, by building
// on a richer abstraction `https://docs.rs/embedded-time/0.10.1/embedded_time/`.
//
// Support for embedded time is projected for next RTIC release.
//! 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 cortex_m;
extern crate panic_halt;
use stm32f4;
// 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)
}
}
#[rtic::app(device = stm32f4)]
const APP: () = {
#[init]
fn init(_cx: init::Context) {
// 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 (Cortex Debug).
//
// 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 38 and uncomment line 39 (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 variables 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 cortex_m;
extern crate panic_semihosting;
// 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> {
pub 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_modify() {
let t: VolatileCell<u32> = VolatileCell {
value: core::cell::UnsafeCell::new(0),
};
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);
//
// add more tests here if you like
}
#[rtic::app(device = stm32f4)]
const APP: () = {
#[init]
fn init(_cx: init::Context) {
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
// 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