From 6196b9f10fdc3d10853980ed18af0baf173b6719 Mon Sep 17 00:00:00 2001
From: Per Lindgren <per.lindgren@ltu.se>
Date: Sun, 7 Mar 2021 21:52:30 +0100
Subject: [PATCH] updated exercises

---
 CHANGELOG.md           |   6 ++
 README.md              |   3 +
 examples/rtic_bare7.rs |   4 +-
 examples/rtic_bare8.rs | 185 ++++++++++++++++++++++++++++++++++
 examples/rtic_bare9.rs | 222 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 418 insertions(+), 2 deletions(-)
 create mode 100644 examples/rtic_bare8.rs
 create mode 100644 examples/rtic_bare9.rs

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58c9738..fc5b8f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## 2021-03-07
+
+- examples/rtic_bare7.rs, using embedded HAL.
+- examples/rtic_bare8.rs, serial communication, bad design.
+- examples/rtic_bare9.rs, serial communication, good design.
+  
 ## 2021-03-05
 
 - examples/rtic_bare6.rs, setup and validate the clock tree.
diff --git a/README.md b/README.md
index 1e6f487..201bf3d 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,9 @@ Bare metal programming:
 - `examples/rtic_bare4.rs`, in this exercise you will encounter a simple bare metal peripheral access API. The API is very unsafe and easy to misuse.
 - `examples/rtic_bare5.rs`, here you will write your own C-like peripheral access API. This API is much safer as you get control over bit-fields in a well defined way, thus less error prone.
 - `examples/rtic_bare6.rs`, in this exercise you learn about clock tree generation and validation.
+- `examples/rtic_bare7.rs`, here you learn more on using embedded HAL abstractions and the use of generics.
+- `examples/rtic_bare8.rs`, in this exercise you will setup serial communication to receive and send data. You will also see that polling may lead to data loss in a bad design.
+- `examples/rtic_bare9.rs`, here you revisit serial communication and implement a good design in RTIC leveraging preemptive scheduling to ensure lossless communication.
 
 ---
 
diff --git a/examples/rtic_bare7.rs b/examples/rtic_bare7.rs
index cc7327c..0f2dea1 100644
--- a/examples/rtic_bare7.rs
+++ b/examples/rtic_bare7.rs
@@ -231,9 +231,9 @@ fn _toggleable_generic<E>(led: &mut dyn ToggleableOutputPin<Error = E>) {
 //    way to implement it more efficiently. Remember, zero-cost is not without cost
 //    just that it is as good as it possibly gets (you can't make it better by hand).
 //
-//    (You can also force the compiler to deduce the type at compile time, by using
+//    You can also force the compiler to deduce the type at compile time, by using
 //    `impl` instead of `dyn`, if you are sure you don't want the compiler to
-//    "fallback" to dynamic dispatch.)
+//    "fallback" to dynamic dispatch.
 //
 //    You might find Rust to have long compile times. Yes you are right,
 //    and this type of deep analysis done in release mode is part of the story.
diff --git a/examples/rtic_bare8.rs b/examples/rtic_bare8.rs
new file mode 100644
index 0000000..b4b18ee
--- /dev/null
+++ b/examples/rtic_bare8.rs
@@ -0,0 +1,185 @@
+//! bare8.rs
+//!
+//! Serial
+//!
+//! What it covers:
+//! - serial communication
+//! - bad design
+
+#![no_main]
+#![no_std]
+
+use panic_rtt_target as _;
+
+use nb::block;
+
+use stm32f4xx_hal::{
+    gpio::{gpioa::PA, Output, PushPull},
+    prelude::*,
+    serial::{config::Config, Rx, Serial, Tx},
+    stm32::USART2,
+};
+
+use rtic::app;
+use rtt_target::{rprintln, rtt_init_print};
+
+#[app(device = stm32f4xx_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 {
+        rtt_init_print!();
+        rprintln!("init");
+
+        let device = cx.device;
+
+        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 }
+    }
+
+    // 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) => {
+                    rprintln!("Ok {:?}", byte);
+                    tx.write(byte).unwrap();
+                }
+                Err(err) => {
+                    rprintln!("Error {:?}", err);
+                }
+            }
+        }
+    }
+};
+
+// 0. Background
+//
+//    The Nucleo st-link programmer provides a Virtual Com Port (VCP).
+//    It is connected to the PA2(TX)/PA3(RX) pins of the stm32f401/411.
+//    On the host, the VCP is presented under `/dev/ttyACMx`, where
+//    `x` is an enumerated number (ff 0 is busy it will pick 1, etc.)
+//
+// 1. In this example we use RTT.
+//
+//    > cargo run --example rtic_bare8
+//
+//    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.
+//
+//    Send a single character (byte), (set the option `No end` in `moserial`).
+//    Verify that sent bytes are echoed back, and that RTT tracing is working.
+//
+//    Try sending "a", don't send the quotation marks, just a.
+//
+//    What do you receive in `moserial`?
+//
+//    ** your answer here **
+//
+//    What do you receive in the RTT terminal?
+//
+//    ** your answer here **
+//
+//    Try sending: "abcd" as a single sequence, don't send the quotation marks, just abcd.
+//
+//    What did you receive in `moserial`?
+//
+//    ** your answer here **
+//
+//    What do you receive in the RTT terminal?
+//
+//    ** your answer here **
+//
+//    What do you believe to be the problem?
+//
+//    Hint: Look at the code in `idle` what does it do?
+//
+//    ** your answer here **
+//
+//    Experiment a bit, what is the max length sequence you can receive without errors?
+//
+//    ** your answer here **
+//
+//    Commit your answers (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 RTT trace to print the added information inside the loop.
+//
+//    Compile/run reconnect, and verify that it works as intended.
+//
+//    Commit your development (bare8_2)
+//
+// 3. Experiment a bit, what is the max length sequence you can receive without errors?
+//
+//    ** your answer here **
+//
+//    How did the added tracing/instrumentation affect the behavior?
+//
+//    ** your answer here **
+//
+//    Commit your answer (bare8_3)
+//
+// 4. Now try compile and run the same experiment 3 but in --release mode.
+//
+//    > cargo run --example rtic_bare8 --release
+//
+//    Reconnect your `moserial` terminal.
+//
+//    Experiment a bit, what is the max length sequence you can receive without errors?
+//
+//    ** your answer here **
+//
+//    Commit your answer (bare8_4)
+//
+// 5. Discussion
+//
+//    (If you ever used Arduino, you might feel at home with the `loop` and poll design.)
+//
+//    Typically, this is what you can expect from a polling approach, if you
+//    are not very careful what you are doing. This exemplifies a bad design.
+//
+//    Loss of data might be Ok for some applications but this typically NOT what we want.
+//
+//    (With that said, Arduino gets away with some simple examples as their drivers do
+//    internal magic - buffering data etc.)
diff --git a/examples/rtic_bare9.rs b/examples/rtic_bare9.rs
new file mode 100644
index 0000000..a1fc61c
--- /dev/null
+++ b/examples/rtic_bare9.rs
@@ -0,0 +1,222 @@
+//! bare8.rs
+//!
+//! Serial
+//!
+//! What it covers:
+
+#![no_main]
+#![no_std]
+
+use panic_rtt_target as _;
+
+use stm32f4xx_hal::{
+    prelude::*,
+    serial::{config::Config, Event, Rx, Serial, Tx},
+    stm32::USART2,
+};
+
+use rtic::app;
+use rtt_target::{rprintln, rtt_init_print};
+
+#[app(device = stm32f4xx_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 {
+        rtt_init_print!();
+        rprintln!("init");
+
+        let device = cx.device;
+
+        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 { TX: tx, RX: rx }
+    }
+
+    // idle may be interrupted by other interrupts/tasks in the system
+    #[idle()]
+    fn idle(_cx: idle::Context) -> ! {
+        loop {
+            continue;
+        }
+    }
+
+    // capacity sets the size of the input buffer (# outstanding messages)
+    #[task(resources = [TX], priority = 1, capacity = 128)]
+    fn rx(cx: rx::Context, data: u8) {
+        let tx = cx.resources.TX;
+        tx.write(data).unwrap();
+        rprintln!("data {}", data);
+    }
+
+    // Task bound to the USART2 interrupt.
+    #[task(binds = USART2,  priority = 2, resources = [RX], spawn = [rx])]
+    fn usart2(cx: usart2::Context) {
+        let rx = cx.resources.RX;
+        let data = rx.read().unwrap();
+        cx.spawn.rx(data).unwrap();
+    }
+
+    extern "C" {
+        fn EXTI0();
+    }
+};
+
+// 0. Background
+//
+//    As seen in the prior example, you may loose data unless polling frequently enough.
+//    Let's try an interrupt driven approach instead.
+//
+//    In init we just add:
+//
+//    // generate interrupt on Rxne
+//    serial.listen(Event::Rxne);
+//
+//    This causes the USART hardware to generate an interrupt when data is available.
+//
+//    // Task bound to the USART2 interrupt.
+//    #[task(binds = USART2,  priority = 2, resources = [RX], spawn = [rx])]
+//    fn usart2(cx: usart2::Context) {
+//      let rx = cx.resources.RX;
+//      let data = rx.read().unwrap();
+//      cx.spawn.rx(data).unwrap();
+//    }
+//
+//    The `usart2` task will be triggered, and we read one byte from the internal
+//    buffer in the USART2 hardware. (panic if something goes bad)
+//
+//    We send the read byte to the `rx` task (by `cx.spawn.rx(data).unwrap();`)
+//    (We panic if the capacity of the message queue is reached)
+//
+//    // capacity sets the size of the input buffer (# outstanding messages)
+//    #[task(resources = [TX], priority = 1, capacity = 128)]
+//    fn rx(cx: rx::Context, data: u8) {
+//        let tx = cx.resources.TX;
+//        tx.write(data).unwrap();
+//        rprintln!("data {}", data);
+//    }
+//
+//    Here we echo the data back, `tx.write(data).unwrap();` (panic if usart is busy)
+//    We then trace the received data `rprintln!("data {}", data);`
+//
+//    The `priority = 2` gives the `usart2` task the highest priority
+//    (to ensure that we don't miss data).
+//
+//    The `priority = 1` gives the `rx` task a lower priority.
+//    Here we can take our time and process the data.
+//
+//    `idle` runs at priority 0, lowest priority in the system.
+//    Here we can do some background job, when nothing urgent is happening.
+//
+//    This is an example of a good design!
+//
+// 1. In this example we use RTT.
+//
+//    > cargo run --example rtic_bare9
+//
+//    Try breaking it!!!!
+//    Throw any data at it, and see if you could make it panic!
+//
+//    Were you able to crash it?
+//
+//    ** your answer here **
+//
+//    Notice, the input tracing in `moserial` seems broken, and may loose data.
+//    So don't be alarmed if data is missing, its a GUI tool after all.
+//
+//    If you want to sniff the `ttyACM0`, install e.g., `interceptty` and run
+//    > interceptty /dev/ttyACM0
+//
+//    In another terminal, you can do:
+//    > cat examples/rtic_bare9.rs > /dev/ttyACM0
+//
+//    Incoming data will be intercepted/displayed by `interceptty`.
+//    (In the RTT trace you will see that data is indeed arriving to the target.)
+//
+//    Commit your answer (bare9_1)
+//
+// 2. Now, re-implement the received and error counters from previous exercise.
+//
+//    Good design:
+//    - Defer any tracing to lower priority task/tasks
+//      (you may introduce an error task at low priority).
+//
+//    - State variables can be introduced either locally (static mut), or
+//      by using a resource.
+//
+//      If a resource is shared among tasks of different priorities:
+//      The highest priority task will have direct access to the data,
+//      the lower priority task(s) will need to lock the resource first.
+//
+//    Check the RTIC book, https://rtic.rs/0.5/book/en/by-example
+//    regarding resources, software tasks, error handling etc.
+//
+//    Test that your implementation works and traces number of
+//    bytes received and errors encountered.
+//
+//    If implemented correctly, it should be very hard (or impossible)
+//    to get an error.
+//
+//    You can force an error by doing some "stupid delay" (faking workload),
+//    e.g., burning clock cycles using `cortex_m::asm::delay` in the
+//    `rx` task. Still you need to saturate the capacity (128 bytes).
+//
+//    To make errors easier to produce, reduce the capacity.
+//
+//    Once finished, comment your code.
+//
+//    Commit your code (bare9_2)
+//
+// 3. Discussion
+//
+//    Here you have used RTIC to implement a highly efficient and good design.
+//
+//    Tasks in RTIC are run-to-end, with non-blocking access to resources.
+//    (Even `lock` is non-blocking, isn't that sweet?)
+//
+//    Tasks in RTIC are scheduled according to priorities.
+//    (A higher priority task `H` always preempts lower priority task `L` running,
+//    unless `L` holds a resource with higher or equal ceiling as `H`.)
+//
+//    Tasks in RTIC can spawn other tasks.
+//    (`capacity` sets the message queue size.)
+//
+//    By design RTIC guarantees race- and deadlock-free execution.
+//
+//    It also comes with theoretical underpinning for static analysis.
+//    - task response time
+//    - overall schedulability
+//    - stack memory analysis
+//    - etc.
+//
+//    RTIC leverages on the zero-cost abstractions in Rust,
+//    and the implementation offers best in class performance.
-- 
GitLab