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
No results found
Select Git revision
  • master
1 result
Show changes

Commits on Source 54

48 files
+ 6070
1498
Compare changes
  • Side-by-side
  • Inline

Files

+3 −0
Original line number Original line Diff line number Diff line
@@ -18,6 +18,9 @@ rustflags = [
  # LLD (shipped with the Rust toolchain) is used as the default linker
  # LLD (shipped with the Rust toolchain) is used as the default linker
  "-C", "link-arg=-Tlink.x",
  "-C", "link-arg=-Tlink.x",


  # To get inline assembly at link time
  "-C", "linker-plugin-lto",

  # if you run into problems with LLD switch to the GNU linker by commenting out
  # if you run into problems with LLD switch to the GNU linker by commenting out
  # this line
  # this line
  # "-C", "linker=arm-none-eabi-ld",
  # "-C", "linker=arm-none-eabi-ld",
Original line number Original line Diff line number Diff line
@@ -19,7 +19,7 @@
            "runToMain": true,
            "runToMain": true,
            "svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
            "svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
            "configFiles": [
            "configFiles": [
                "interface/stlink-v2-1.cfg",
                "interface/stlink.cfg",
                "target/stm32f4x.cfg"
                "target/stm32f4x.cfg"
            ],
            ],
            "preRestartCommands": [
            "preRestartCommands": [
@@ -41,7 +41,42 @@
                    }
                    }
                ]
                ]
            },
            },
            "executable": "./target/thumbv7em-none-eabi/debug/examples/${fileBasenameNoExtension}",
            "executable": "./target/thumbv7em-none-eabihf/debug/examples/${fileBasenameNoExtension}",
            "cpu": "cortex-m4",
        },
        {
            "type": "cortex-debug",
            "request": "launch",
            "name": "Cortex Debug 48Mhz",
            "servertype": "openocd",
            "cwd": "${workspaceRoot}",
            "preLaunchTask": "cargo build --example",
            "runToMain": true,
            "svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
            "configFiles": [
                "interface/stlink.cfg",
                "target/stm32f4x.cfg"
            ],
            "preRestartCommands": [
                "load",
            ],
            "postLaunchCommands": [
                "monitor arm semihosting enable"
            ],
            "swoConfig": {
                "enabled": true,
                "cpuFrequency": 48000000,
                "swoFrequency": 2000000,
                "source": "probe",
                "decoders": [
                    {
                        "type": "console",
                        "label": "ITM",
                        "port": 0
                    }
                ]
            },
            "executable": "./target/thumbv7em-none-eabihf/debug/examples/${fileBasenameNoExtension}",
            "cpu": "cortex-m4",
            "cpu": "cortex-m4",
        },
        },
        {
        {
@@ -54,7 +89,7 @@
            "runToMain": true,
            "runToMain": true,
            "svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
            "svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
            "configFiles": [
            "configFiles": [
                "interface/stlink-v2-1.cfg",
                "interface/stlink.cfg",
                "target/stm32f4x.cfg"
                "target/stm32f4x.cfg"
            ],
            ],
            "preRestartCommands": [
            "preRestartCommands": [
@@ -76,20 +111,20 @@
                    }
                    }
                ]
                ]
            },
            },
            "executable": "./target/thumbv7em-none-eabi/release/examples/${fileBasenameNoExtension}",
            "executable": "./target/thumbv7em-none-eabihf/release/examples/${fileBasenameNoExtension}",
            "cpu": "cortex-m4",
            "cpu": "cortex-m4",
        },
        },
        {
        {
            "type": "cortex-debug",
            "type": "cortex-debug",
            "request": "launch",
            "request": "launch",
            "name": "Cortex Nightly",
            "name": "Cortex Release 48Mhz",
            "servertype": "openocd",
            "servertype": "openocd",
            "cwd": "${workspaceRoot}",
            "cwd": "${workspaceRoot}",
            "preLaunchTask": "cargo build --example --release --nightly",
            "preLaunchTask": "cargo build --example --release",
            "runToMain": true,
            "runToMain": true,
            "svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
            "svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
            "configFiles": [
            "configFiles": [
                "interface/stlink-v2-1.cfg",
                "interface/stlink.cfg",
                "target/stm32f4x.cfg"
                "target/stm32f4x.cfg"
            ],
            ],
            "preRestartCommands": [
            "preRestartCommands": [
@@ -98,8 +133,21 @@
            "postLaunchCommands": [
            "postLaunchCommands": [
                "monitor arm semihosting enable"
                "monitor arm semihosting enable"
            ],
            ],
            "executable": "./target/thumbv7em-none-eabi/release/examples/${fileBasenameNoExtension}",
            "swoConfig": {
            "cpu": "cortex-m4",
                "enabled": true,
        },
                "cpuFrequency": 48000000,
                "swoFrequency": 2000000,
                "source": "probe",
                "decoders": [
                    {
                        "type": "console",
                        "label": "ITM",
                        "port": 0
                    }
                ]
                ]
            },
            "executable": "./target/thumbv7em-none-eabihf/release/examples/${fileBasenameNoExtension}",
            "cpu": "cortex-m4",
        }
        }
    ]
}.
 No newline at end of file
+15 −15
Original line number Original line Diff line number Diff line
@@ -4,31 +4,31 @@
    "version": "2.0.0",
    "version": "2.0.0",
    "tasks": [
    "tasks": [
        {
        {
            "type": "cargo",
            "label": "cargo build --example",
            "command": "build --example ${fileBasenameNoExtension}",
            "command": "cargo",
            "problemMatcher": [
            "args": [
                "$rustc"
                "build",
                "--example",
                "${fileBasenameNoExtension}"
            ],
            ],
            "group": "build",
            "label": "cargo build --example"
        },
        {
            "type": "cargo",
            "command": "build --example ${fileBasenameNoExtension} --release",
            "problemMatcher": [
            "problemMatcher": [
                "$rustc"
                "$rustc"
            ],
            ],
            "group": "build",
            "group": "build",
            "label": "cargo build --example --release"
        },
        },
        {
        {
            "type": "cargo",
            "label": "cargo build --example --release",
            "command": "build --example ${fileBasenameNoExtension} --release --features nightly",
            "command": "cargo",
            "args": [
                "build",
                "--example",
                "${fileBasenameNoExtension}",
                "--release"
            ],
            "problemMatcher": [
            "problemMatcher": [
                "$rustc"
                "$rustc"
            ],
            ],
            "group": "build",
            "group": "build",
            "label": "cargo build --example --release --nightly"
        },
        }
    ]
    ]
}
}
 No newline at end of file

CHANGELOG.md

0 → 100644
+56 −0
Original line number Original line Diff line number Diff line
# Changelog

## 2021-03-19

- `examples/itm_rtic_hello_48MHz.rs`, example to trace ITM, when processor runs at 48MHz, useful to debug USB applications.
  
- `.vscode/launch.json`, added 48MHz itm tracing profiles. (Now consistenly using `stlink.cfg`.)
  
## 2021-03-18

- `examples/usb-mouse.rs`, a very small example using external hid library.
  
## 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.

## 2021-02-28

- `examples/rtic_bare2.rs`, raw timer access.
- `examples/rtic_bare3.rs`, timing abstractions.
- `examples/rtic_bare4.rs`, a simple bare metal peripheral access API.
- `examples/rtic_bare5.rs`, write your own C-like peripheral access API.

## 2021-02-26

- `examples/bare1.rs`, bare metal 101!
  
## 2021-02-23

- `examples/rtic_blinky.rs`, added instructions to terminal based debugging
  
## 2021-02-22

- `memory.x`, reduced flash size to 128k to match light-weight target
- `Cargo.toml`, updated dependencies to latest `stm32f4xx-hal/pac`

Some experiments (wip):

- `examples/rtt_rtic_i2c.rs`, spi emulation over i2c
- `src/pwm3389e`, driver using emulated spi

## 2021-02-16

- `rtt_rtic_usb_mouse` updated
  Notice, requires release build

## 2021-02-15

- Initial release for the e7020e course 2021
  
 No newline at end of file
+25 −33
Original line number Original line Diff line number Diff line
@@ -6,68 +6,60 @@ name = "app"
version = "0.1.0"
version = "0.1.0"


[dependencies]
[dependencies]
cortex-m = "0.6.4"
cortex-m = { version = "0.7.1", features = ["linker-plugin-lto"] }
# cortex-m = { version = "0.7.1" }
cortex-m-rt = "0.6.13"
cortex-m-rt = "0.6.13"
cortex-m-semihosting = "0.3.7"
cortex-m-semihosting = "0.3.7"
cortex-m-rtic = "0.5.5"
cortex-m-rtic = "0.5.5"
# embedded-hal = { version = "0.2.4", features = ["unproven"] }
embedded-hal = "0.2.4"
embedded-hal = "0.2.4"
usb-device = "0.2.7"
usb-device = "0.2.7"


# Panic handlers, comment all but one to generate doc!
# Panic handlers, comment all but one to generate doc!
panic-halt = "0.2.0"
panic-halt = "0.2.0"


# Uncomment for the panic example.
# Uncomment for the itm panic examples.
#panic-itm = "0.4.2"
panic-itm = "0.4.2"


# Uncomment for the rtt-timing example.
# Uncomment for the rtt-timing examples.
#panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] }
panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] }


# Uncomment for the panic example.
# Uncomment for the semihosting examples.
#panic-semihosting = "0.5.6"
panic-semihosting = "0.5.6"


# Tracing
# Tracing
rtt-target = { version = "0.3.0", features = ["cortex-m"] }
rtt-target = { version = "0.3.1", features = ["cortex-m"] }
nb = "1.0.0"
usbd-hid = "0.5.0"
micromath = "1.1.0"


[dependencies.stm32f4]
[dependencies.stm32f4]
version = "0.12.1"
version = "0.13.0"
features = ["stm32f411", "rt"]
features = ["stm32f411", "rt"]


# Uncomment for the allocator example.
# alloc-cortex-m = "0.4.0"



[dependencies.stm32f4xx-hal]
[dependencies.stm32f4xx-hal]
version = "0.8.3"
version = "0.9.0"
features = ["rt", "stm32f411", "usb_fs"] 
features = ["rt", "stm32f411", "usb_fs"] 
git = "https://github.com/stm32-rs/stm32f4xx-hal"
# Enable to use the latest git version
# gitgit = "https://github.com/stm32-rs/stm32f4xx-hal"
# Enable to use your forked/cloned local repo 
# path = "../stm32f4xx-hal"


# this lets you use `cargo fix`!
# this lets you use `cargo fix`!
[[bin]]
# [[bin]]
name = "app"
# name = "app"
test = false
# # test = false
bench = false
# bench = false


[profile.dev]
[profile.dev]
incremental = false
incremental = false
codegen-units = 1
codegen-units = 1
overflow-checks = false  


[profile.release]
[profile.release]
incremental = false
codegen-units = 1 # better optimizations
codegen-units = 1 # better optimizations
debug = true      # symbols are nice and they don't increase the size on Flash
debug = true      # symbols are nice and they don't increase the size on Flash
lto = true        # better optimizations
lto = true        # better optimizations


# [features]
# nightly = ["cortex-m/inline-asm"]

# # this lets you use `cargo fix`!
# [[bin]]
# name = "app"
# test = false
# bench = false

# [profile.release]
# codegen-units = 1 # better optimizations
# debug = true      # symbols are nice and they don't increase the size on Flash
# lto = true        # better optimizations
+178 −14
Original line number Original line Diff line number Diff line
# App
# RTIC on the STM32F4xx Nucleo board


## Resources
All tooling have been developed and tested under Linux. Any modern Linux distro should work, we usually recommend Arch linux as it provides a great package manager with rolling releases. If you want to run Arch, but don't want to install everything from scratch, you may opt for [Manjaro](https://manjaro.org/) or [Endeavour](https://endeavouros.com/). You will get the best user experience by a native install, but you may run Linux under a VM like virtualbox, or vmware (the player is free). You should install the guest extensions, to get better graphics performance (and perhaps better USB forwarding). Since you will connect your Nucleo using USB, you must make sure that USB port forwarding works (the Nucleo stlink programmer is a USB2 device running in full speed 12MBit).

This repo will be updated with more information throughout the course so please check the `CHANGELOG.md` and recent commits to see what has changed. (You should `pull` the upstream to keep your repository updated.) If you have suggestions to further improvements, please raise an issue and/or create a merge/pull request.

## Rust

We assume Rust to be installed using [rustup](https://www.rust-lang.org/tools/install).

Additionally you need to install the `thumbv7em-none-eabi` target.

```shell
> rustup target add thumbv7em-none-eabi 
```

You also need [cargo-binutils](https://github.com/rust-embedded/cargo-binutils), for inspecting the generated binaries. You install Rust applications through `cargo`

```shell
> cargo install cargo-binutils
```

There are a number of other useful [cargo subcommands](https://github.com/rust-lang/cargo/wiki/Third-party-cargo-subcommands), notably `cargo-bloat` (that gives you info on the size of different sections of the generated binary), `cargo-tree` (that list your dependency tree), etc.

## For RTT tracing

We assume the following tools are in place:

- [probe-run](https://crates.io/crates/probe-run)

## For programming and low level `gdb` based debugging

Linux tooling:

- `stlink`, this package will install programming utilities like `st-flash` (useful if you need to recover a bricked target by erasing the flash), and setup `udev` rules, allowing you to access the USB device without `sudo`. Install may require you to login/logout to have new `udev` rules applied.
- `openocd`, this tool allows the host to connect to the (stlink) programmer.
- `arm-none-eabi-gdb`, or `gdb-multiarch` (dependent on Linux distro). This tool allows you to program (flash) and debug your target.

## Editor

You may use any editor of choice. `vscode` supports Rust using the  `rust-analyzer` plugin. You may also want to install the `Cortex Debug` plugin. In the `.vscode` folder, there are a number of configuration files (`launch.json` for target debugging, `tasks.json` for building, etc.).

## Useful Resources


- Nucleo 64
- Nucleo 64
  - [UM1724 - stm32 Nucleo-64](https://www.st.com/resource/en/user_manual/dm00105823-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf).
  - [UM1724 - stm32 Nucleo-64](https://www.st.com/resource/en/user_manual/dm00105823-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf).
  - [Nucleo 64 Schematics](https://www.st.com/resource/en/schematic_pack/nucleo_64pins_sch.zip) (The file MB1136.pdf is the schematics in pdf.)
  - [Nucleo 64 Schematics](https://www.st.com/resource/en/schematic_pack/nucleo_64pins_sch.zip) (The file MB1136.pdf is the schematics in pdf.)
  - [stm32f4xx_hal](https://docs.rs/stm32f4xx-hal/0.8.3/stm32f4xx_hal/) documentation of the HAL API, and [git repository](https://github.com/stm32-rs/stm32f4xx-hal).
  - [stm32f4xx_hal](https://docs.rs/stm32f4xx-hal/0.8.3/stm32f4xx_hal/) documentation of the HAL API, and [git repository](https://github.com/stm32-rs/stm32f4xx-hal).



- STM32F01/FO11
- STM32F01/FO11
  - [RM0383 - F411 Reference Manual](https://www.st.com/resource/zh/reference_manual/dm00119316-stm32f411xce-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) 
  - [RM0383 - F411 Reference Manual](https://www.st.com/resource/zh/reference_manual/dm00119316-stm32f411xce-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) 
  - [RM0368 - F401 Reference Manual](https://www.st.com/resource/en/reference_manual/dm00096844-stm32f401xbc-and-stm32f401xde-advanced-armbased-32bit-mcus-stmicroelectronics.pdf)
  - [RM0368 - F401 Reference Manual](https://www.st.com/resource/en/reference_manual/dm00096844-stm32f401xbc-and-stm32f401xde-advanced-armbased-32bit-mcus-stmicroelectronics.pdf)
@@ -20,25 +59,150 @@
- General Embedded
- General Embedded
  - [Introduction to SPI](https://www.analog.com/en/analog-dialogue/articles/introduction-to-spi-interface.html#), a short introduction to the SPI interface.
  - [Introduction to SPI](https://www.analog.com/en/analog-dialogue/articles/introduction-to-spi-interface.html#), a short introduction to the SPI interface.


---

## Examples

### VSCODE based debug and trace

Some simple bare metal examples for you to try out before starting to run your own code:
Using `vscode` just press F5 to launch and debug the program in the currently active vscode window.

- `rtic_hello.rs`, this example uses semihosting to print the output terminal. Open the `OUTPUT` pane, and select `Adapter Output` (which is the openocd console).
- `itm_rtic_hello.rs`, this examples uses the ITM trace to print to an output trace channel. Open the `OUTPUT` pane, and select `SWO:ITM[port:0, type:console]`.
- `rtic_panic.rs`, this example shows how to trace panic messages (in this case over semihosting).  Open the `OUTPUT` pane, and select `Adapter Output` (which is the openocd console).
- `rtic_crash.rs`, this example shows how to trace a HardFault (an error raised by the ARM processor).
  
---

### Exercises


Bare metal programming:


- `examples/rtic_bare1.rs`, in this exercise you learn about debugging, inspecting the generated assembly code, inline assembly, and about checked vs. unchecked (wrapping) arithmetics. Provides essential skills and understanding of low level (bare metal) programming.
- `examples/rtic_bare2.rs`, in this exercise you learn how to measure execution time using raw timer access.
- `examples/rtic_bare3.rs`, here you learn more about RTIC timing semantics and timing abstractions.
- `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.


---
---


## Connections
### Console based debug and trace

- `rtt_rtic_hello.rs`, this example uses the RTT framework for tracing.


- [USB Cable]
  ```shell
  > cargo run --example rtt_rtic_hello
  ```

---

## Nucleo Connections

---


| Signl | Color | Pin  |
Some of the examples need external connection to the Nucleo to work.
| ----- | ----- | ---- |
| V+    | Red   | ---- |
| D-    | White | PA11 |
| D+    | Green | PA12 |
| Gnd   | Black | ---- |


D+ used for re-enumeration
---

### USB example

| Signal | Color | Pin  | Nucleo    |
| ------ | ----- | ---- | --------- |
| V+     | Red   |      |           |
| D-     | White | PA11 | CN10 - 14 |
| D+     | Green | PA12 | CN10 - 12 |
| Gnd    | Black |      | CN10 - 9  |

D+ used for re-enumeration. You don't need to connect the V+ from the USB cable, as the NUCLEO is self powered.

---

### PWM example

| Signal | Pin | Nucleo  |
| ------ | --- | ------- |
| PWM1   | PA8 | CN9 - 8 |
| PWM2   | PA9 | CN5 - 1 |

---

### I2C example

| Signal   | Pin | Nucleo |
| -------- | --- | ------ |
| I2C1_SDA | PB9 | CN10-5 |
| I2C1_SCL | PB8 | CN10-3 |
| +3.3v    |     | CN7-16 |
| GND      |     | Gnd    |


## Debug interface
## Debug interface


- Serial Wire debugging uses pins PA13 and PA14.
- Serial Wire debugging uses pins PA13 and PA14. So refrain from using those unless absolutely necessary.

---


## Troubleshooting

---


### Fail to connect or program (flash) your target

- Make sure you have the latest version of the [stlink](https://www.st.com/en/development-tools/stsw-link007.html) firmware (2.37.27 or later).

- Check that your stlink nucleo programmer is found by the host.

  ```shell
  > lsusb
  ...
  Bus 003 Device 013: ID 0483:374b STMicroelectronics ST-LINK/V2.1
  ...
  ```

  If not check your USB cable. Notice, you need a USB data cable (not a USB charging cable).
  If the problem is still there, there might be a USB issue with the host (or VM if you run Linux under a VM that is).

- If you get a connection error similar to the below:

  ```shell
  > openocd -f openocd.cfg
  Open On-Chip Debugger 0.10.0+dev-01157-gd6541a811-dirty (2020-03-28-18:34)
  Licensed under GNU GPL v2
  For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
  Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
  Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
  Info : Listening on port 6666 for tcl connections
  Info : Listening on port 4444 for telnet connections
  Info : clock speed 2000 kHz
  Info : STLINK V2J37M26 (API v2) VID:PID 0483:374B
  Info : Target voltage: 3.243627
  Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
  Info : Listening on port 3333 for gdb connections
  Error: jtag status contains invalid mode value - communication failure
  Polling target stm32f4x.cpu failed, trying to reexamine
  Examination failed, GDB will be halted. Polling again in 100ms
  Info : Previous state query failed, trying to reconnect
  Error: jtag status contains invalid mode value - communication failure
  Polling target stm32f4x.cpu failed, trying to reexamine 
  ```

  - First thing to try is holding the reset button while connecting.

  - If this does not work you can try to erase the flash memory (the program running on the STM32F401/F11).

    ``` shell
    > st-flash erase
    st-flash 1.7.0
    2021-06-23T14:07:35 INFO common.c: F4xx (Dynamic Efficency): 96 KiB SRAM, 512 KiB flash in at least 16 KiB pages.
    Mass erasing.......
    ```

  - If this still does not work you can connect `Boot0` to `VDD` (found on CN7 pins 7, and 5 respectively). Unplug/replug the Nucleo and try to erase the flash as above.
  
  - If this still does not work, the Nucleo might actually been damaged, or that the problem is the usb-cable or host   machine related.
+26 −3
Original line number Original line Diff line number Diff line
@@ -8,12 +8,15 @@
//! updating `memory.x` ensures a rebuild of the application with the
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
//! new memory settings.


use core::f64::consts::PI;
use std::env;
use std::env;
use std::fs::File;
use std::fs::File;
use std::io::Write;
use std::{
use std::path::PathBuf;
    io::{Result, Write},
    path::{Path, PathBuf},
};


fn main() {
fn main() -> Result<()> {
    // Put `memory.x` in our output directory and ensure it's
    // Put `memory.x` in our output directory and ensure it's
    // on the linker search path.
    // on the linker search path.
    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
@@ -28,4 +31,24 @@ fn main() {
    // here, we ensure the build script is only re-run when
    // here, we ensure the build script is only re-run when
    // `memory.x` is changed.
    // `memory.x` is changed.
    println!("cargo:rerun-if-changed=memory.x");
    println!("cargo:rerun-if-changed=memory.x");

    // generate a sine table
    println!("cargo:rerun-if-changed=build.rs");
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("sin_abs_const.rs");
    let mut f = File::create(&dest_path).unwrap();

    const SINE_BUF_SIZE: usize = 65536;
    write!(f, "const SINE_BUF_SIZE: usize = {};\n", SINE_BUF_SIZE)?;
    write!(f, "const SINE_BUF: [u8; SINE_BUF_SIZE] = [")?;

    for i in 0..SINE_BUF_SIZE {
        let s = ((i as f64) * 2.0 * PI / SINE_BUF_SIZE as f64).sin();
        let v = (128.0 + 128.0 * s) as u8;

        write!(f, " {},", v)?;
    }
    write!(f, "];\n")?;

    Ok(())
}
}

examples/allocator.rs

deleted100644 → 0
+0 −56
Original line number Original line Diff line number Diff line
//! How to use the heap and a dynamic memory allocator
//!
//! This example depends on the alloc-cortex-m crate so you'll have to add it to your Cargo.toml:
//!
//! ``` text
//! # or edit the Cargo.toml file manually
//! $ cargo add alloc-cortex-m
//! ```
//!
//! ---

#![feature(alloc_error_handler)]
#![no_main]
#![no_std]

extern crate alloc;
use panic_halt as _;

use self::alloc::vec;
use core::alloc::Layout;

use alloc_cortex_m::CortexMHeap;
use cortex_m::asm;
use cortex_m_rt::entry;
use cortex_m_semihosting::{hprintln, debug};

// this is the allocator the application will use
#[global_allocator]
static ALLOCATOR: CortexMHeap = CortexMHeap::empty();

const HEAP_SIZE: usize = 1024; // in bytes

#[entry]
fn main() -> ! {
    // Initialize the allocator BEFORE you use it
    unsafe { ALLOCATOR.init(cortex_m_rt::heap_start() as usize, HEAP_SIZE) }

    // Growable array allocated on the heap
    let xs = vec![0, 1, 2];

    hprintln!("{:?}", xs).unwrap();

    // exit QEMU
    // NOTE do not run this on hardware; it can corrupt OpenOCD state
    debug::exit(debug::EXIT_SUCCESS);

    loop {}
}

// define what happens in an Out Of Memory (OOM) condition
#[alloc_error_handler]
fn alloc_error(_layout: Layout) -> ! {
    asm::bkpt();

    loop {}
}

examples/crash.rs

deleted100644 → 0
+0 −96
Original line number Original line Diff line number Diff line
//! Debugging a crash (exception)
//!
//! Most crash conditions trigger a hard fault exception, whose handler is defined via
//! `exception!(HardFault, ..)`. The `HardFault` handler has access to the exception frame, a
//! snapshot of the CPU registers at the moment of the exception.
//!
//! This program crashes and the `HardFault` handler prints to the console the contents of the
//! `ExceptionFrame` and then triggers a breakpoint. From that breakpoint one can see the backtrace
//! that led to the exception.
//!
//! ``` text
//! (gdb) continue
//! Program received signal SIGTRAP, Trace/breakpoint trap.
//! __bkpt () at asm/bkpt.s:3
//! 3         bkpt
//!
//! (gdb) backtrace
//! #0  __bkpt () at asm/bkpt.s:3
//! #1  0x080030b4 in cortex_m::asm::bkpt () at $$/cortex-m-0.5.0/src/asm.rs:19
//! #2  rust_begin_unwind (args=..., file=..., line=99, col=5) at $$/panic-semihosting-0.2.0/src/lib.rs:87
//! #3  0x08001d06 in core::panicking::panic_fmt () at libcore/panicking.rs:71
//! #4  0x080004a6 in crash::hard_fault (ef=0x20004fa0) at examples/crash.rs:99
//! #5  0x08000548 in UserHardFault (ef=0x20004fa0) at <exception macros>:10
//! #6  0x0800093a in HardFault () at asm.s:5
//! Backtrace stopped: previous frame identical to this frame (corrupt stack?)
//! ```
//!
//! In the console output one will find the state of the Program Counter (PC) register at the time
//! of the exception.
//!
//! ``` text
//! panicked at 'HardFault at ExceptionFrame {
//!     r0: 0x2fffffff,
//!     r1: 0x2fffffff,
//!     r2: 0x080051d4,
//!     r3: 0x080051d4,
//!     r12: 0x20000000,
//!     lr: 0x08000435,
//!     pc: 0x08000ab6,
//!     xpsr: 0x61000000
//! }', examples/crash.rs:106:5
//! ```
//!
//! This register contains the address of the instruction that caused the exception. In GDB one can
//! disassemble the program around this address to observe the instruction that caused the
//! exception.
//!
//! ``` text
//! (gdb) disassemble/m 0x08000ab6
//! Dump of assembler code for function core::ptr::read_volatile:
//! 451     pub unsafe fn read_volatile<T>(src: *const T) -> T {
//!    0x08000aae <+0>:     sub     sp, #16
//!    0x08000ab0 <+2>:     mov     r1, r0
//!    0x08000ab2 <+4>:     str     r0, [sp, #8]
//!
//! 452         intrinsics::volatile_load(src)
//!    0x08000ab4 <+6>:     ldr     r0, [sp, #8]
//! -> 0x08000ab6 <+8>:     ldr     r0, [r0, #0]
//!    0x08000ab8 <+10>:    str     r0, [sp, #12]
//!    0x08000aba <+12>:    ldr     r0, [sp, #12]
//!    0x08000abc <+14>:    str     r1, [sp, #4]
//!    0x08000abe <+16>:    str     r0, [sp, #0]
//!    0x08000ac0 <+18>:    b.n     0x8000ac2 <core::ptr::read_volatile+20>
//!
//! 453     }
//!    0x08000ac2 <+20>:    ldr     r0, [sp, #0]
//!    0x08000ac4 <+22>:    add     sp, #16
//!    0x08000ac6 <+24>:    bx      lr
//!
//! End of assembler dump.
//! ```
//!
//! `ldr r0, [r0, #0]` caused the exception. This instruction tried to load (read) a 32-bit word
//! from the address stored in the register `r0`. Looking again at the contents of `ExceptionFrame`
//! we see that the `r0` contained the address `0x2FFF_FFFF` when this instruction was executed.
//!
//! ---

#![no_main]
#![no_std]

use panic_halt as _;

use core::ptr;

use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    unsafe {
        // read an address outside of the RAM region; this causes a HardFault exception
        ptr::read_volatile(0x2FFF_FFFF as *const u32);
    }

    loop {}
}

examples/device.rs

deleted100644 → 0
+0 −62
Original line number Original line Diff line number Diff line
//! Using a device crate
//!
//! Crates generated using [`svd2rust`] are referred to as device crates. These crates provide an
//! API to access the peripherals of a device.
//!
//! [`svd2rust`]: https://crates.io/crates/svd2rust
//!
//! This example depends on the [`stm32f3`] crate so you'll have to
//! uncomment it in your Cargo.toml.
//!
//! [`stm32f3`]: https://crates.io/crates/stm32f3
//!
//! ```
//! $ edit Cargo.toml && tail $_
//! [dependencies.stm32f3]
//! features = ["stm32f303", "rt"]
//! version = "0.7.1"
//! ```
//!
//! You also need to set the build target to thumbv7em-none-eabihf,
//! typically by editing `.cargo/config` and uncommenting the relevant target line.
//!
//! ---

#![no_main]
#![no_std]

#[allow(unused_extern_crates)]
use panic_halt as _;

use cortex_m::peripheral::syst::SystClkSource;
use cortex_m_rt::entry;
use cortex_m_semihosting::hprint;
use stm32f3::stm32f303::{interrupt, Interrupt, NVIC};

#[entry]
fn main() -> ! {
    let p = cortex_m::Peripherals::take().unwrap();

    let mut syst = p.SYST;
    let mut nvic = p.NVIC;

    nvic.enable(Interrupt::EXTI0);

    // configure the system timer to wrap around every second
    syst.set_clock_source(SystClkSource::Core);
    syst.set_reload(8_000_000); // 1s
    syst.enable_counter();

    loop {
        // busy wait until the timer wraps around
        while !syst.has_wrapped() {}

        // trigger the `EXTI0` interrupt
        NVIC::pend(Interrupt::EXTI0);
    }
}

#[interrupt]
fn EXTI0() {
    hprint!(".").unwrap();
}

examples/exception.rs

deleted100644 → 0
+0 −37
Original line number Original line Diff line number Diff line
//! Overriding an exception handler
//!
//! You can override an exception handler using the [`#[exception]`][1] attribute.
//!
//! [1]: https://rust-embedded.github.io/cortex-m-rt/0.6.1/cortex_m_rt_macros/fn.exception.html
//!
//! ---

#![deny(unsafe_code)]
#![no_main]
#![no_std]

use panic_halt as _;

use cortex_m::peripheral::syst::SystClkSource;
use cortex_m::Peripherals;
use cortex_m_rt::{entry, exception};
use cortex_m_semihosting::hprint;

#[entry]
fn main() -> ! {
    let p = Peripherals::take().unwrap();
    let mut syst = p.SYST;

    // configures the system timer to trigger a SysTick exception every second
    syst.set_clock_source(SystClkSource::Core);
    syst.set_reload(8_000_000); // period = 1s
    syst.enable_counter();
    syst.enable_interrupt();

    loop {}
}

#[exception]
fn SysTick() {
    hprint!(".").unwrap();
}

examples/hello.rs

deleted100644 → 0
+0 −16
Original line number Original line Diff line number Diff line
//! Prints "Hello, world!" on the host console using semihosting

#![no_main]
#![no_std]

use panic_halt as _;

use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hprintln};

#[entry]
fn main() -> ! {
    hprintln!("Hello, world!!").unwrap();

    loop {}
}

examples/itm.rs

deleted100644 → 0
+0 −34
Original line number Original line Diff line number Diff line
//! Sends "Hello, world!" through the ITM port 0
//!
//! ITM is much faster than semihosting. Like 4 orders of magnitude or so.
//!
//! **NOTE** Cortex-M0 chips don't support ITM.
//!
//! You'll have to connect the microcontroller's SWO pin to the SWD interface. Note that some
//! development boards don't provide this option.
//!
//! You'll need [`itmdump`] to receive the message on the host plus you'll need to uncomment two
//! `monitor` commands in the `.gdbinit` file.
//!
//! [`itmdump`]: https://docs.rs/itm/0.2.1/itm/
//!
//! ---

#![no_main]
#![no_std]

use panic_halt as _;

use cortex_m::{iprintln, Peripherals};
use cortex_m_rt::entry;
use stm32f4 as _; // to get interrupt vectors

#[entry]
fn main() -> ! {
    let mut p = Peripherals::take().unwrap();
    let stim = &mut p.ITM.stim[0];

    iprintln!(stim, "Hello, world!");

    loop {}
}
+22 −0
Original line number Original line Diff line number Diff line
#![no_main]
#![no_std]

use cortex_m::iprintln;
use panic_halt as _;
use stm32f4;

#[rtic::app(device = stm32f4)]
const APP: () = {
    #[init]
    fn init(cx: init::Context) {
        // Set up the system clock.
        let rcc = ctx.device.RCC.constrain();
        let _clocks = rcc.cfgr.sysclk(48.mhz()).require_pll48clk().freeze();

        let mut p = cx.core;
        let stim = &mut p.ITM.stim[0];
        for a in 0..=10 {
            iprintln!(stim, "RTIC Hello, world!! {}", a);
        }
    }
};
+27 −0
Original line number Original line Diff line number Diff line
// itm_rtic_hello_48Mhz
//
// Use the vscode 48Mhz launch profiles

#![no_main]
#![no_std]

use cortex_m::iprintln;
use panic_halt as _;

use stm32f4xx_hal::prelude::*;

#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)]
const APP: () = {
    #[init]
    fn init(ctx: init::Context) {
        // Set up the system clock.
        let rcc = ctx.device.RCC.constrain();
        let _clocks = rcc.cfgr.sysclk(48.mhz()).require_pll48clk().freeze();

        let mut p = ctx.core;
        let stim = &mut p.ITM.stim[0];
        for a in 0..=10 {
            iprintln!(stim, "RTIC Hello, world!! {}", a);
        }
    }
};
+84 −802

File changed.

Preview size limit exceeded, changes collapsed.

examples/rtic_bare1.rs

0 → 100644
+391 −0
Original line number Original line Diff line number Diff line
//! bare1.rs
//!
//! Inspecting the generated assembly
//!
//! What it covers
//! - Rust panic on arithmetics
//! - assembly calls and inline assembly

#![no_main]
#![no_std]

use cortex_m_semihosting::hprintln;
use panic_semihosting as _;
use stm32f4;

#[rtic::app(device = stm32f4)]
const APP: () = {
    #[init]
    #[inline(never)] // avoid inlining of this function/task
    #[no_mangle] // to strip hash from symbols (easier to read)
    fn init(_cx: init::Context) {
        hprintln!("hello");
        let mut x = core::u32::MAX - 1;
        loop {
            // cortex_m::asm::bkpt();
            x += 1;
            // cortex_m::asm::bkpt();

            // prevent optimization by read-volatile (unsafe)
            unsafe {
                core::ptr::read_volatile(&x);
            }
        }
    }
};

// 0. Setup
//    Make sure that your repository is updated (pull from upstream).
//
// 1. Build in debug mode and run the application in vscode (Cortex Debug)
//
//    Continue until you hit a breakpoint.
//
//    Now select OUTPUT and Adapter Output.
//
//    You should have encountered a Rust panic.
//
//    Paste the error message:
//
//    ** your answer here **
//
//    Explain in your own words why the code panic:ed.
//
//    ** your answer here **
//
//    Commit your answer (bare1_1)
//
// 2. Inspecting what caused the panic.
//
//    Under CALL STACK you find the calls done to reach the panic:
//
//    You can get the same information directly from GDB
//
//    Select the DEBUG CONSOLE and enter the command
//
//    > backtrace
//
//    Paste the backtrace:
//
//    ** your answer here
//
//    Explain in your own words the chain of calls.
//
//    ** your answer here
//
//    Commit your answer (bare1_2)
//
// 3. Now let's try to break it down to see what caused the panic.
//
//    Put a breakpoint at line 24 (x += 1;)
//    (Click to the left of the line marker, you get a red dot.)
//
//    Restart the debug session, and continue until you hit the breakpoint.
//
//    What is the value of `x`?
//
//    ** your answer here **
//
//    Explain in your own words where this value comes from.
//
//    ** your answer here **
//
//    Now continue the program, since you are in a loop
//    the program will halt again at line 24.
//
//    What is the value of `x`?
//
//    Explain in your own words why `x` now has this value.
//
//    ** your answer here **
//
//    Now continue again.
//
//    At this point your code should panic.
//
//    You can navigate the CALL STACK.
//    Click on rtic_bare::init@0x08.. (24)
//
//    The line leading up to the panic should now be highlighted.
//    So you can locate the precise line which caused the error.
//
//    Explain in your own words why a panic makes sense at this point.
//
//    ** your answer here **
//
//    Commit your answer (bare1_3)
//
// 4. Now lets have a look at the generated assembly.
//
//    First restart the debug session and continue to the first halt (line 24).
//
//    Select DEBUG CONSOLE and give the command
//
//    > disassemble
//
//    The current PC (program counter is marked with an arrow)
//    => 0x08000f18 <+20>:	ldr	r0, [sp, #0]
//
//    Explain in your own words what this assembly line does.
//
//    ** your answer here **
//
//    In Cortex Registers (left) you can see the content of `r0`
//
//    What value do you observe?
//
//    ** your answer here **
//
//    You can also get the register info from GDB directly.
//
//    > register info
//
//    Many GDB commands have short names try `i r`.
//
//    So now, time to move on, one assembly instruction at a time.
//
//    > stepi
//    > disassemble
//
//    Now you should get
//    => 0x08000f1a <+22>:	adds	r0, #1
//
//    Explain in your own words what is happening here.
//
//    ** your answer here **
//
//    We move to the next assembly instruction:
//
//    > si
//    > i r
//
//    What is the reported value for `r0`
//
//    ** your answer here **
//
//    So far so good.
//
//    We can now continue to the next breakpoint.
//
//    > continue
//    (or in short >c, or press the play button, or press F5, many options here ...)
//    > disassemble
//    (or in short >disass)
//
//    You should now be back at the top of the loop:
//
//    => 0x08000f18 <+20>:	ldr	r0, [sp, #0]
//
//    and the value of `r0` should be -1 (or 0xffffffff in hexadecimal)
//
//    Now we can step an instruction again.
//
//    > si
//    => 0x08000f1a <+22>:	adds	r0, #1
//
//    So far so good, and another one.
//
//    > si
//    => 0x08000f1c <+24>:	bcs.n	0x8000f28 <rtic_bare::init+36>
//
//    lookup the arm instruction set: https://developer.arm.com/documentation/ddi0210/c/Introduction/Instruction-set-summary/Thumb-instruction-summary
//
//    What does BCS do?
//
//    ** your answer here **
//
//    Now let's see what happens.
//
//    > si
//    => 0x08000f28 <+36>:	movw	r0, #6128	; 0x17f0
//       0x08000f2c <+40>:	movw	r2, #6112	; 0x17e0
//       0x08000f30 <+44>:	movt	r0, #2048	; 0x800
//       0x08000f34 <+48>:	movt	r2, #2048	; 0x800
//       0x08000f38 <+52>:	movs	r1, #28
//       0x08000f3a <+54>:	bl	0x8000346 <_ZN4core9panicking5panic17h6c8437680724f6d0E>
//
//    Explain in your own words where we are heading.
//
//    ** your answer here **
//
//    To validate that your answer, let's let the program continue
//
//    > c
//
//    Look in the OUTPUT/Adapter Output console again.
//
//    Explain in your own words what the code
//    0x08000f28 ..  0x08000f38 achieves
//
//    Hint 1, look at the error message?
//    Hint 2, look at the call stack.
//    Hint 3, the code is generated by the Rust compiler to produce the error message.
//            there is no "magic" here, just a compiler generating code...
//
//    ** your answer here **
//
//    Commit your answer (bare1_4)
//
// 5. Now we can remove the break point (click the `Remove All Breakpoints`),
//    and instead uncomment the two breakpoint instructions (on lines 23 and 25).
//
//    Close the debug session and press F5 again to re-compile and launch the app.
//
//    Continue until you hit the firs breakpoint.
//
//    The disassembly should look like this:
//
//
//       0x08000f18 <+20>:	bl	0x800103e <lib::__bkpt>
//    => 0x08000f1c <+24>:	ldr	r0, [sp, #0]
//       0x08000f1e <+26>:	adds	r0, #1
//       0x08000f20 <+28>:	bcs.n	0x8000f30 <rtic_bare::init+44>
//       0x08000f22 <+30>:	str	r0, [sp, #0]
//       0x08000f24 <+32>:	bl	0x800103e <lib::__bkpt>
//       0x08000f28 <+36>:	mov	r0, r4
//       0x08000f2a <+38>:	bl	0x8000fde <_ZN4core3ptr13read_volatile17hea5ef1c780562e1fE>
//
//    In stable Rust we cannot currently write inline assembly, thus we do a "workaround"
//    and call a function that that contains the assembly instruction.
//
//    In this code:
//       0x08000f18 <+20>:	bl	0x800103e <lib::__bkpt>
//    and
//       0x08000f24 <+32>:	bl	0x800103e <lib::__bkpt>
//
//    In cases, this is not good enough (if we want exact cycle by cycle control).
//    We can overcome this by letting the linker inline the code.
//
//    Let's try this, build and run the code in release mode (Cortex Release).
//    Continue until you hit the first assembly breakpoint.
//
//    The disassembly now should look like this:
//
//    => 0x0800024c <+12>:	bkpt	0x0000
//       0x0800024e <+14>:	adds	r0, #1
//       0x08000250 <+16>:	str	r0, [sp, #4]
//       0x08000252 <+18>:	bkpt	0x0000
//       0x08000254 <+20>:	ldr	r0, [sp, #4]
//       0x08000256 <+22>:	b.n	0x800024c <rtic_bare::init+12>
//
//    Now let's compare the two assembly snippets.
//    We now see that the breakpoints have been inlined (offsets +12, +18).
//
//    But something else also happened here!
//
//    Do you see any way this code may end up in a panic?
//
//    ** your answer here **
//
//    So clearly, the "semantics" (meaning) of the program has changed.
//    This is on purpose, Rust adopts "unchecked" (wrapping) additions (and subtractions)
//    by default in release mode (to improve performance).
//
//    The downside, is that programs change meaning. If you intend the operation
//    to be wrapping you can explicitly express that in the code.
//
//    Change the x += 1 to x = x.wrapping_add(1).
//
//    And recompile/run/the code in Debug mode
//
//    Paste the generated assembly:
//
//    ** your answer here **
//
//    Can this code generate a panic?
//
//    ** 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_5)
//
//    Discussion:
//    In release (optimized) mode the addition is unchecked,
//    so there is a semantic difference here in between
//    the dev and release modes. This is motivated by:
//    1) efficiency, unchecked/wrapping is faster
//    2) convenience, it would be inconvenient to explicitly use
//    wrapping arithmetics, and wrapping is what the programmer
//    typically would expect in any case. So the check
//    in dev/debug mode is just there for some extra safety
//    if your intention is NON-wrapping arithmetics.
//
//    The debug build should have additional code that checks if the addition
//    wraps (and in such case call panic). In the case of the optimized
//    build there should be no reference to the panic handler in the generated
//    binary. Recovering from a panic is in general very hard. Typically
//    the best we can do is to stop and report the error (and maybe restart).
//
//    Later we will demonstrate how we can get guarantees of panic free execution.
//    This is very important to improve reliability.
//
// 6. Now comment out the `read_volatile`.
//
//    Rebuild and run the code in Release mode.
//
//    Dump the generated assembly.
//
//    ** your answer here **
//
//    Where is the local variable stored?
//    What happened, and why is Rust + LLVM allowed to optimize out your code?
//
//    ** your answer here **
//
//    Commit your answers (bare1_6)
//
//
// 7. *Optional
//    You can pass additional flags to the Rust `rustc` compiler.
//
//    `-Z force-overflow-checks=off`
//
//    Under this flag, code is never generated for overflow checking even in
//    non optimized (debug/dev) builds.
//    You can enable this flag in the `.cargo/config` file.
//
//    What is now the disassembly of the loop (in debug/dev mode):
//
//    ** your answer here **
//
//    commit your answers (bare1_7)
//
//    Now restore the `.cargo/config` to its original state.
//
// 8. *Optional
//    There is another way to conveniently use wrapping arithmetics
//    without passing flags to the compiler.
//
//    https://doc.rust-lang.org/std/num/struct.Wrapping.html
//
//    Rewrite the code using this approach.
//
//    What is now the disassembly of the code in dev mode?
//
//    ** your answer here **
//
//    What is now the disassembly of the code in release mode?
//
//    ** your answer here **
//
//    commit your answers (bare1_8)
//
//    Final discussion:
//
//    Embedded code typically is performance sensitive, hence
//    it is important to understand how code is generated
//    to achieve efficient implementations.
//
//    Moreover, arithmetics are key to processing of data,
//    so its important that we are in control over the
//    computations. E.g. computing checksums, hashes, cryptos etc.
//    all require precise control over wrapping vs. overflow behavior.
//
//    If you write a library depending on wrapping arithmetics
//    do NOT rely on a compiler flag. (The end user might compile
//    it without this flag enabled, and thus get erroneous results.)
//

examples/rtic_bare2.rs

0 → 100644
+134 −0
Original line number Original line Diff line number Diff line
//! rtic_bare2.rs
//!
//! Measuring execution time
//!
//! What it covers
//! - Generating documentation
//! - Using core peripherals
//! - Measuring time using the DWT

#![no_main]
#![no_std]

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 42).
//
//    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 **
//
//    Commit your answers (bare2_3)

examples/rtic_bare3.rs

0 → 100644
+129 −0
Original line number Original line Diff line number Diff line
//! rtic_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.

examples/rtic_bare4.rs

0 → 100644
+133 −0
Original line number Original line Diff line number Diff line
//! rtic_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)

examples/rtic_bare5.rs

0 → 100644
+246 −0
Original line number Original line Diff line number Diff line
//! rtic_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

        // test_modify();

        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);
        }
    }
};

// 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 the loop we access PA5 through bit set/clear operations.
//    Comment out those operations and uncomment the ODR based 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 `modify` API.
//
//    Run and see that the program behaves the same.
//
//    Discussion:
//    As with arithmetic operations, default semantics differ in between
//    debug/dev and release builds.
//    In 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.
//
//    We are now approaching a more "safe" to use API.
//    What if we could automatically generate that from Vendors specifications (SVD files)?
//    Wouldn't that be great?
//
//    ** your answer here **
//
//    Commit your answers (bare5_2)

examples/rtic_bare6.rs

0 → 100644
+383 −0
Original line number Original line Diff line number Diff line
//! rtic_bare6.rs
//!
//! Clocking
//!
//! What it covers:
//! - using svd2rust generated API
//! - using the stm32f4xx-hal to set clocks
//! - routing the clock to a PIN for monitoring by an oscilloscope

#![no_main]
#![no_std]

use panic_rtt_target as _;
use rtic::cyccnt::{Instant, U32Ext as _};
use rtt_target::{rprintln, rtt_init_print};
use stm32f4xx_hal::{
    prelude::*,
    stm32::{self, GPIOC, RCC},
};

const OFFSET: u32 = 8_000_000;

#[rtic::app(device = stm32f4xx_hal::stm32, monotonic = rtic::cyccnt::CYCCNT, peripherals = true)]
const APP: () = {
    struct Resources {
        // late resources
        GPIOA: stm32::GPIOA,
    }
    #[init(schedule = [toggle])]
    fn init(cx: init::Context) -> init::LateResources {
        rtt_init_print!();
        rprintln!("init");

        let mut core = cx.core;
        let device = cx.device;

        // Initialize (enable) the monotonic timer (CYCCNT)
        core.DCB.enable_trace();
        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

        // Schedule `toggle` to run 8e6 cycles (clock cycles) in the future
        cx.schedule.toggle(now + OFFSET.cycles()).unwrap();

        // setup LED
        // 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));

        clock_out(&device.RCC, &device.GPIOC);

        let rcc = device.RCC.constrain();

        let _clocks = rcc.cfgr.freeze();

        // Set up the system clock. 48 MHz?
        // let _clocks = rcc
        //     .cfgr
        //     .sysclk(48.mhz())
        //     .pclk1(24.mhz())
        //     .freeze();

        // let _clocks = rcc
        //     .cfgr
        //     .sysclk(64.mhz())
        //     .pclk1(64.mhz())
        //     .pclk2(64.mhz())
        //     .freeze();
        //
        // let _clocks = rcc
        //     .cfgr
        //     .sysclk(84.mhz())
        //     .pclk1(42.mhz())
        //     .pclk2(64.mhz())
        //     .freeze();

        // pass on late resources
        init::LateResources {
            GPIOA: device.GPIOA,
        }
    }

    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        rprintln!("idle");
        loop {
            continue;
        }
    }

    #[task(resources = [GPIOA], schedule = [toggle])]
    fn toggle(cx: toggle::Context) {
        static mut TOGGLE: bool = false;
        rprintln!("toggle  @ {:?}", Instant::now());

        if *TOGGLE {
            cx.resources.GPIOA.bsrr.write(|w| w.bs5().set_bit());
        } else {
            cx.resources.GPIOA.bsrr.write(|w| w.br5().set_bit());
        }

        *TOGGLE = !*TOGGLE;
        cx.schedule.toggle(cx.scheduled + OFFSET.cycles()).unwrap();
    }

    extern "C" {
        fn EXTI0();
    }
};

// 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));
}

// 0. Background reading:
//
//    Clock trees:
//    STM32F401xD STM32F401xE, section 3.11
//    We have two AMBA High-performance Buses (APBs)
//    APB1 low speed bus (max freq 42 MHz)
//    APB2 high speed bus (max freq 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 pre-scale) for our applications
//
//    FCLK   - Free running clock running 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
//
//    Configuration:
//
//    The `Cargo.toml` file defines your dependencies.
//
//    [dependencies.stm32f4]
//    version = "0.13.0"
//    features = ["stm32f411", "rt"]
//
//    [dependencies.stm32f4xx-hal]
//    version = "0.8.3"
//    features = ["rt", "stm32f411", "usb_fs"]
//
//    The stm32f411 and f401 is essentially the same chip, the f411 is guaranteed
//    up to 100MHz, but we can "overclock" the f401 to 100MHz if needed.
//
//    The `features = ["stm32f411", "rt"]` selects the target MCU, and
//    "rt" enables functionality for exception handling etc.
//
//    The HAL provides a generic abstraction over the whole stm32f4 family.
//
//    In our configuration we enable "stm32f411" with the "rt" feature
//    and the "usb_fs" (for USB OnTheGo support).
//
//    The HAL re-exports the selected "stm32f411" under the `stm32` path.
//
//    Initialization:
//
//    In the code, we first setup the DWT/CYCCNT for the Monotonic timer,
//    and schedule a task to be run after `OFFSET` number of clock cycles.
//
//    The `device.RCC.constrain()`, gives a default setting for the MCU RCC
//    (Reset and Clock Control) peripheral.
//    `rcc.cfgr.x.freeze()`, freezes the current (default) config.
//
//    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 **
//
//    Start `stm32cubemx` and select or create a project targeting stm32f401.
//    Go to the graphical clock configuration view.
//
//    Try to setup the clock according to:
//
//    `rcc.cfgr.sysclk(64.mhz()).pclk1(64.mhz()).pclk2(64.mhz()).freeze()`;
//
//    What happens?
//
//    ** your answer here **
//
//    Try to setup the clock according to:
//
//    What happens?
//
//    `rcc.cfgr.sysclk(84.mhz()).pclk1(42.mhz()).pclk2(64.mhz()).freeze();`
//
//    ** your answer here **
//
//    Commit your answers (bare6_0)
//
// 1. In this example you will use RTT.
//
//    > cargo run --example rtic_bare6
//
//    Confirm that your RTT traces the init, idle and led on/off.
//
//    What is the (default) MCU (SYSCLK) frequency?
//
//    ** your answer here **
//
//    What is the (default) DWT CYCCNT frequency?
//
//    ** your answer here **
//
//    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.
//
//    Compute the value of SYSCLK based on the oscilloscope reading
//
//    ** your answer here **
//
//    What is the peak to peak (voltage) 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 48Mz, by commenting out line 56, and un-commenting
//    lines 58-63.
//`
//    What is the frequency of blinking?
//
//    ** your answer here **
//
//    Now change the constant `OFFSET` so you get the same blinking frequency as in 1.
//    Test and validate that you got the desired behavior.
//
//    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_48mhz_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 --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 in `clock_out` by the function name equivalents.
//    (alternatively, use the enum values.)
//
//    Test that the application still runs as before.
//
//    Commit your code (bare6_5)
//
// 6. Now reprogram the PC9 to be "Low Speed", and re-run at 48Mz.
//
//    Did the frequency change in comparison to assignment 5?
//
//    ** 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_6_48mhz_low_speed".
//
//    Commit your answers (bare6_6)
//
// 7. Try setting the clocks according to:
//
//    `rcc.cfgr.sysclk(64.mhz()).pclk1(64.mhz()).pclk2(64.mhz()).freeze()`;
//
//    Does the code compile?
//
//    ** your answer here **
//
//    What happens at run-time?
//
//    ** your answer here **
//
//    Try setting the clocks according to:
//
//    `rcc.cfgr.sysclk(84.mhz()).pclk1(42.mhz()).pclk2(64.mhz()).freeze();`
//
//    Does the code compile?
//
//    ** your answer here **
//
//    What happens at run-time?
//
//    ** your answer here **
//
//    Is that a correct?
//
//    Optional: If you find it incorrect, file an issue to `stm32f4xx-hal` describing the problem.
//    (Remember always check already open issues, and add to existing if related.)
//
// 7. Discussion
//
//    In this exercise, you have learned to use the stm32f4xx-hal
//    to set the clock speed of your MCU.
//
//    You have also learned how you can monitor/validate MCU clock(s) on pin(s)
//    connected to an oscilloscope.
//
//    You have also learned how you can improve readability of your code
//    by leveraging the abstractions provided by the PAC.
//
//    As mentioned before the PACs are machine generated by `svd2rust`
//    from vendor provided System View Descriptions (SVDs).
//
//    The PACs provide low level peripheral access abstractions, while
//    the HALs provide higher level abstractions and functionality.

examples/rtic_bare7.rs

0 → 100644
+241 −0
Original line number Original line Diff line number Diff line
//! rtic_bare7.rs
//!
//! HAL OutputPin abstractions
//!
//! What it covers:
//! - using embedded hal, and the OutputPin abstraction

#![no_main]
#![no_std]

use panic_rtt_target as _;
use rtic::cyccnt::{Instant, U32Ext as _};
use rtt_target::{rprintln, rtt_init_print};
use stm32f4xx_hal::stm32;

use stm32f4xx_hal::{
    gpio::{gpioa::PA5, Output, PushPull},
    prelude::*,
};

use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin};

const OFFSET: u32 = 8_000_000;

#[rtic::app(device = stm32f4xx_hal::stm32, monotonic = rtic::cyccnt::CYCCNT, peripherals = true)]
const APP: () = {
    struct Resources {
        // late resources
        GPIOA: stm32::GPIOA,
        // led: PA5<Output<PushPull>>,
    }
    #[init(schedule = [toggle])]
    fn init(cx: init::Context) -> init::LateResources {
        rtt_init_print!();
        rprintln!("init");

        let mut core = cx.core;
        let device = cx.device;

        // Initialize (enable) the monotonic timer (CYCCNT)
        core.DCB.enable_trace();
        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

        // Schedule `toggle` to run 8e6 cycles (clock cycles) in the future
        cx.schedule.toggle(now + OFFSET.cycles()).unwrap();

        // 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));

        // pass on late resources
        init::LateResources {
            GPIOA: device.GPIOA,
        }
    }

    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        rprintln!("idle");
        loop {
            continue;
        }
    }

    #[task(resources = [GPIOA], schedule = [toggle])]
    fn toggle(cx: toggle::Context) {
        static mut TOGGLE: bool = false;
        rprintln!("toggle  @ {:?}", Instant::now());

        if *TOGGLE {
            cx.resources.GPIOA.bsrr.write(|w| w.bs5().set_bit());
        } else {
            cx.resources.GPIOA.bsrr.write(|w| w.br5().set_bit());
        }

        *TOGGLE = !*TOGGLE;
        cx.schedule.toggle(cx.scheduled + OFFSET.cycles()).unwrap();
    }

    extern "C" {
        fn EXTI0();
    }
};

fn _toggle_generic<E>(led: &mut dyn OutputPin<Error = E>, toggle: &mut bool) {
    if *toggle {
        led.set_high().ok();
    } else {
        led.set_low().ok();
    }

    *toggle = !*toggle;
}

fn _toggleable_generic<E>(led: &mut dyn ToggleableOutputPin<Error = E>) {
    led.toggle().ok();
}

// 1. In this example you will use RTT.
//
//    > cargo run --example rtic_bare7
//
//    Look in the generated documentation for `set_high`/`set_low`.
//    (You created documentation for your dependencies in previous exercise
//    so you can just search (press `S`) for `OutputPin`).
//    You will find that these methods are implemented for `Output` pins.
//
//    Now change your code to use these functions instead of the low-level GPIO API.
//
//    HINTS:
//    - A GPIOx peripheral can be `split` into individual PINs Px0..Px15).
//    - A Pxy, can be turned into an `Output` by `into_push_pull_output`.
//    - You may optionally set other pin properties as well (such as `speed`).
//    - An `Output` pin provides `set_low`/`set_high`
//    - Instead of passing `GPIO` resource to the `toggle` task pass the
//      `led: PA5<Output<PushPull>>` resource instead.
//
//    Comment your code to explain the steps taken.
//
//    Confirm that your implementation correctly toggles the LED as in
//    previous exercise.
//
//    Commit your code (bare7_1)
//
// 2. Further generalizations:
//
//    Now look at the documentation for `embedded_hal::digital::v2::OutputPin`.
//
//    You see that the OutputPin trait defines `set_low`/`set_high` functions.
//    Your task is to alter the code to use the `set_low`/`set_high` API.
//
//    The function `_toggle_generic` is generic to any object that
//    implements the `OutputPin<Error = E>` trait.
//
//    Digging deeper we find the type parameter `E`, which in this case
//    is left generic (unbound).
//
//    It will be instantiated with a concrete type argument when called.
//
//    Our `PA5<Output<PushPull>>` implements `OutputPin` trait, thus
//    we can pass the `led` resource to `_toggle_generic`.
//
//    The error type is given by the stm32f4xx-hal implementation:
//    where `core::convert::Infallible` is used to indicate
//    there are no errors to be expected (hence infallible).
//
//    Additionally, `_toggle_generic` takes a mutable reference
//    `toggle: &mut bool`, so you need to pass your `TOGGLE` variable.
//
//    As you see, `TOGGLE` holds the "state", switching between
//    `true` and `false` (to make your led blink).
//
//    Change your code into using the `_toggle_generic` function.
//    (You may rename it to `toggle_generic` if wished.)
//
//    Confirm that your implementation correctly toggles the LED as in
//    previous exercise.
//
//    Commit your code (bare7_2)
//
// 3. What about the state?
//
//    In your code `TOGGLE` holds the "state". However, the underlying
//    hardware ALSO holds the state (if the corresponding bit is set/cleared).
//
//    What if we can leverage that, and guess what we can!!!!
//
//    Look at the documentation for `embedded_hal::digital::v2::ToggleableOutputPin`,
//    and the implementation of:
//
//    fn _toggleable_generic(led: &mut dyn ToggleableOutputPin<Error = Infallible>) {
//      led.toggle().ok();
//    }
//
//    The latter does not take any state variable, instead it directly `toggle()`
//    the `ToggleableOutputPin`.
//
//    Now alter your code to leverage on the `_toggleable_generic` function.
//    (You should be able to remove the `TOGGLE` state variable altogether.)
//
//    Confirm that your implementation correctly toggles the LED as in
//    previous exercise.
//
//    Commit your code (bare7_3)
//
// 4. Discussion:
//
//    In this exercise you have gone from a very hardware specific implementation,
//    to leveraging abstractions (batteries included).
//
//    Your final code amounts to "configuration" rather than "coding".
//
//    This reduces the risk of errors (as you let the libraries do the heavy lifting).
//
//    This also improves code-re use. E.g., if you were to do something less
//    trivial then merely toggling you can do that in a generic manner,
//    breaking out functionality into "components" re-usable in other applications.
//
//    Of course the example is trivial, you don't gain much here, but the principle
//    is the same behind drivers for USART communication, USB, PMW3389 etc.
//
// 5. More details:
//
//    Looking closer at the implementation:
//    `led: &mut dyn OutputPin<Error = E>`
//
//    You may ask what kind of mumbo jumbo is at play here.
//
//    This is the way to express that we expect a mutable reference to a trait object
//    that implements the `OutputPin`. Since we will change the underlying object
//    (in this case an GPIOA pin 5) the reference needs to be mutable.
//
//    Trait objects are further explained in the Rust book.
//    The `dyn` keyword indicates dynamic dispatch (through a VTABLE).
//    https://doc.rust-lang.org/std/keyword.dyn.html
//
//    Notice: the Rust compiler (rustc + LLVM) is really smart. In many cases
//    it can analyse the call chain, and conclude the exact trait object type at hand.
//    In such cases the dynamic dispatch is turned into a static dispatch
//    and the VTABLE is gone, and we have a zero-cost abstraction.
//
//    If the trait object is stored for e.g., in an array along with other
//    trait objects (of different concrete type), there is usually no telling
//    the concrete type of each element, and we will have dynamic dispatch.
//    Arguably, this is also a zero-cost abstraction, as there is no (obvious)
//    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
//    `impl` instead of `dyn`, if you are sure you don't want the compiler to
//    "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.
//    On the other hand, the aggressive optimization allows us to code
//    in a generic high level fashion and still have excellent performing binaries.

examples/rtic_bare8.rs

0 → 100644
+185 −0
Original line number Original line Diff line number Diff line
//! 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.)

examples/rtic_bare9.rs

0 → 100644
+222 −0
Original line number Original line Diff line number Diff line
//! 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.
Original line number Original line Diff line number Diff line
// run with gdb, either from terminal or in vscode
//
// from terminal:
// start openocd in separate terminal
// > openocd -f openocd.cfg
//
// start gdb
// > arm-none-eabi-gdb target/thumbv7em-none-eabihf/debug/examples/rtic_blinky -x openocd.gdb

#![deny(unsafe_code)]
#![deny(unsafe_code)]
#![deny(warnings)]
#![deny(warnings)]
#![no_main]
#![no_main]
@@ -5,7 +14,7 @@


use cortex_m::peripheral::DWT;
use cortex_m::peripheral::DWT;
use cortex_m_semihosting::hprintln;
use cortex_m_semihosting::hprintln;
use panic_rtt_target as _;
use panic_semihosting as _;
use rtic::cyccnt::{Instant, U32Ext as _};
use rtic::cyccnt::{Instant, U32Ext as _};
use stm32f4xx_hal::stm32;
use stm32f4xx_hal::stm32;


examples/rtic_crash.rs

0 → 100644
+33 −0
Original line number Original line Diff line number Diff line
#![no_main]
#![no_std]

use core::ptr;
use panic_halt as _;
use stm32f4;

#[rtic::app(device = stm32f4)]
const APP: () = {
    #[init]
    fn init(_cx: init::Context) {
        unsafe {
            // read an address outside of the RAM region; this causes a HardFault exception
            ptr::read_volatile(0x2FFF_FFFF as *const u32);
        }
    }
};

// Here you can inspect the call stack (found to the left in vscode).
//
// The default implementation for `HardFault` exception is just an infinite loop.
// Press the pause symbol to halt the processor:
//
// The upmost item in CALL STACK, is the current frame:
// (the infinite loop loop)
//
// The bottom most item is the start of the program (the generated main).
//
// In between, you can see the calls made
// main->init->read_volatile->HardFault->compiler_fence
//
// Click on init, and you will see that line 14 in this application caused the
// erroneous read operation.

examples/rtic_hello.rs

0 → 100644
+16 −0
Original line number Original line Diff line number Diff line
#![no_main]
#![no_std]

use cortex_m_semihosting::hprintln;
use panic_halt as _;
use stm32f4;

#[rtic::app(device = stm32f4)]
const APP: () = {
    #[init]
    fn init(_cx: init::Context) {
        for a in 0..11 {
            hprintln!("RTIC Says Hello, to all students!! {}", a).unwrap();
        }
    }
};
Original line number Original line Diff line number Diff line
//! Changing the panicking behavior
//!
//! The easiest way to change the panicking behavior is to use a different [panic handler crate][0].
//!
//! [0]: https://crates.io/keywords/panic-impl

#![no_main]
#![no_main]
#![no_std]
#![no_std]


use cortex_m_semihosting::hprintln;

// Pick one of these panic handlers:
// Pick one of these panic handlers:


// `panic!` halts execution; the panic message is ignored
// `panic!` halts execution; the panic message is ignored
// use panic_halt as _;
// use panic_halt as _;


// Reports panic messages to the host stderr using semihosting
// Reports panic messages to the host stderr using semihosting
// NOTE to use this you need to uncomment the `panic-semihosting` dependency in Cargo.toml
use panic_semihosting as _;
// use panic_semihosting as _;


// Logs panic messages using the ITM (Instrumentation Trace Macrocell)
// Logs panic messages using the ITM (Instrumentation Trace Macrocell)
// NOTE to use this you need to uncomment the `panic-itm` dependency in Cargo.toml
// use panic_itm as _;
use panic_itm as _;


use cortex_m_rt::entry;
use stm32f4;
use stm32f4 as _;


#[entry]
#[rtic::app(device = stm32f4)]
fn main() -> ! {
const APP: () = {
    panic!("Oops")
    #[init]
    fn init(_cx: init::Context) {
        for i in 0..10 {
            hprintln!("RTIC Says Hello, world {}!!", 100 / (5 - i)).ok();
        }
    }
    }
};
+315 −0
Original line number Original line Diff line number Diff line
//! examples/rtt-pwm-sine.rs
//! cargo run --examples rtt-pwm-sine --release

// #![deny(unsafe_code)]
// #![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m::{asm, peripheral::DWT};

use panic_rtt_target as _;
use rtic::cyccnt::{Instant, U32Ext as _};
use rtt_target::{rprint, rprintln, rtt_init_print};

use core::f32::consts::PI;
use micromath::F32Ext;

use stm32f4xx_hal::{
    bb,
    dwt::Dwt,
    gpio::Speed,
    gpio::{
        gpiob::{PB10, PB4},
        gpioc::{PC2, PC3},
        Alternate, Output, PushPull,
    },
    prelude::*,
    pwm,
    rcc::Clocks,
    spi::Spi,
    stm32,
};

use embedded_hal::spi::MODE_3;
use panic_rtt_target as _;

use app::{
    pmw3389::{self, Register},
    DwtDelay,
};

type PMW3389T = pmw3389::Pmw3389<
    Spi<
        stm32f4xx_hal::stm32::SPI2,
        (
            PB10<Alternate<stm32f4xx_hal::gpio::AF5>>,
            PC2<Alternate<stm32f4xx_hal::gpio::AF5>>,
            PC3<Alternate<stm32f4xx_hal::gpio::AF5>>,
        ),
    >,
    PB4<Output<PushPull>>,
>;

include!(concat!(env!("OUT_DIR"), "/sin_abs_const.rs"));

#[rtic::app(device = stm32f4xx_hal::stm32,  monotonic = rtic::cyccnt::CYCCNT, peripherals = true)]
const APP: () = {
    struct Resources {
        // late resources
        TIM1: stm32::TIM1,
        pmw3389: PMW3389T,
    }
    #[init(schedule = [pwm_out, poll])]
    fn init(mut cx: init::Context) -> init::LateResources {
        rtt_init_print!();
        rprintln!("init");

        let device = cx.device;
        let mut core = cx.core;

        // Initialize (enable) the monotonic timer (CYCCNT)
        core.DCB.enable_trace();
        core.DWT.enable_cycle_counter();

        let rcc = device.RCC.constrain();
        // Set up the system clock. 96 MHz?
        let clocks = rcc.cfgr.sysclk(96.mhz()).pclk1(24.mhz()).freeze();

        // Configure SPI
        // spi2
        // sck    - pb10, (yellow)
        // miso   - pc2, (red)
        // mosi   - pc3, (orange)
        // ncs    - pb4, (long yellow)
        // motion - (brown)
        //
        // +5, (white)
        // gnd, (black)

        cortex_m::asm::dsb(); // chip errata
        let gpiob = device.GPIOB.split();
        cortex_m::asm::dsb(); // chip errata
        let gpioc = device.GPIOC.split();
        cortex_m::asm::dsb(); // chip errata

        let sck = gpiob.pb10.into_alternate_af5().set_speed(Speed::VeryHigh);
        let miso = gpioc.pc2.into_alternate_af5().set_speed(Speed::High);
        let mosi = gpioc.pc3.into_alternate_af5().set_speed(Speed::High);
        let cs = gpiob.pb4.into_push_pull_output().set_speed(Speed::High);

        let spi = Spi::spi2(
            device.SPI2,
            (sck, miso, mosi),
            MODE_3,
            stm32f4xx_hal::time::KiloHertz(2000).into(),
            clocks,
        );

        let delay = DwtDelay::new(&mut core.DWT, clocks);
        let mut pmw3389 = pmw3389::Pmw3389::new(spi, cs, delay).unwrap();

        // // set in burst mode
        // pmw3389.write_register(Register::MotionBurst, 0x00).unwrap();

        // setup TIM1 as PWM (for implementing an 8 bit DA)
        let gpioa = device.GPIOA.split();
        cortex_m::asm::dsb(); // chip errata

        // we set the pins to VeryHigh to get the sharpest waveform possible
        // (rise and fall times should have similar characteristics)
        let _channels = (
            gpioa.pa8.into_alternate_af1().set_speed(Speed::VeryHigh),
            gpioa.pa9.into_alternate_af1().set_speed(Speed::VeryHigh),
        );

        // Setup PWM RAW
        let tim1 = device.TIM1;
        // Here we need unsafe as we are "stealing" the RCC peripheral
        // At this point it has been contrained into SysConf and used to set clocks
        let rcc = unsafe { &(*stm32::RCC::ptr()) };

        rcc.apb2enr.modify(|_, w| w.tim1en().set_bit());
        rcc.apb2rstr.modify(|_, w| w.tim1rst().set_bit());
        rcc.apb2rstr.modify(|_, w| w.tim1rst().clear_bit());

        // Setup chanel 1 and 2 as pwm_mode1
        tim1.ccmr1_output()
            .modify(|_, w| w.oc1pe().set_bit().oc1m().pwm_mode1());

        tim1.ccmr1_output()
            .modify(|_, w| w.oc2pe().set_bit().oc2m().pwm_mode1());

        // The reference manual is a bit ambiguous about when enabling this bit is really
        // necessary, but since we MUST enable the pre-load for the output channels then we
        // might as well enable for the auto-reload too
        tim1.cr1.modify(|_, w| w.arpe().set_bit());

        let clk = clocks.pclk2().0 * if clocks.ppre2() == 1 { 1 } else { 2 };
        // check the actual clock setup
        rprintln!("clk {}", clk);

        // we want maximum performance, thus we set the prescaler to 0
        let pre = 0;
        rprintln!("pre {}", pre);
        tim1.psc.write(|w| w.psc().bits(pre));

        // we want 8 bits of resolution
        // so our ARR = 2^8 - 1 = 256 - 1 = 255
        let arr = 255;
        rprintln!("arr {}", arr);
        tim1.arr.write(|w| unsafe { w.bits(arr) });

        //  Trigger update event to load the registers
        tim1.cr1.modify(|_, w| w.urs().set_bit());
        tim1.egr.write(|w| w.ug().set_bit());
        tim1.cr1.modify(|_, w| w.urs().clear_bit());

        // Set main output enable of all Output Compare (OC) registers
        tim1.bdtr.modify(|_, w| w.moe().set_bit());

        // Set output enable for channels 1 and 2
        tim1.ccer.write(|w| w.cc1e().set_bit().cc2e().set_bit());

        // Setup the timer
        tim1.cr1.write(|w| {
            w.cms()
                .bits(0b00) // edge aligned mode
                .dir() // counter used as up-counter
                .clear_bit()
                .opm() // one pulse mode
                .clear_bit()
                .cen() // enable counter
                .set_bit()
        });

        // Set main output enable of all Output Compare (OC) registers
        tim1.bdtr.modify(|_, w| w.moe().set_bit());

        // Set duty cycle of Channels
        tim1.ccr1.write(|w| unsafe { w.ccr().bits(128) });
        tim1.ccr2.write(|w| unsafe { w.ccr().bits(128) });

        // Set preload for the CCx
        tim1.cr2.write(|w| w.ccpc().set_bit());

        // Enable update events
        tim1.dier.write(|w| w.uie().enabled());
        tim1.sr.modify(|_, w| w.uif().clear());

        // just to check that we actually get update events
        while tim1.sr.read().uif().is_clear() {
            rprint!("-");
        }
        rprintln!("here");
        tim1.sr.modify(|_, w| w.uif().clear());

        cx.schedule.pwm_out(cx.start + PWM_CYCLES.cycles()).ok();
        cx.schedule.poll(cx.start + POLL_CYCLES.cycles()).ok();
        // pass on late resources
        init::LateResources {
            TIM1: tim1,
            pmw3389,
        }
    }

    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        rprintln!("idle");
        // panic!("panic");
        loop {
            asm::nop()
        }
    }

    #[task(priority = 1)]
    fn trace(_cx: trace::Context, pos_x: i64, pos_y: i64) {
        static mut OLD_POS_X: i64 = 0;
        static mut OLD_POS_Y: i64 = 0;
        rprintln!(
            "@{:?}, pos_x {:010}, diff {:010}, pos_y {:010}, diff {:010} ",
            Instant::now(),
            pos_x,
            pos_x.wrapping_sub(*OLD_POS_X),
            pos_y,
            pos_y.wrapping_sub(*OLD_POS_Y),
        );
        *OLD_POS_X = pos_x;
        *OLD_POS_Y = pos_y;
    }

    #[task(priority = 2, resources = [pmw3389], schedule = [poll], spawn = [trace])]
    fn poll(cx: poll::Context) {
        static mut COUNTER: u32 = 0;
        static mut POS_X: i64 = 0;
        static mut POS_Y: i64 = 0;

        // run each ms
        *COUNTER += 1;
        if *COUNTER == 1000 {
            // run each s
            cx.spawn.trace(*POS_X, *POS_Y).unwrap();
            *COUNTER = 0;
        }

        let pmw3389 = cx.resources.pmw3389;

        //write 0x01 to Motion register and read from it to freeze the motion values and make them available
        pmw3389.write_register(Register::Motion, 0x01).unwrap();
        let _ = pmw3389.read_register(Register::Motion).unwrap();

        let xl: u8 = pmw3389.read_register(Register::DeltaXL).unwrap();
        let xh: u8 = pmw3389.read_register(Register::DeltaXH).unwrap();
        let yl = pmw3389.read_register(Register::DeltaYL).unwrap();
        let yh = pmw3389.read_register(Register::DeltaYH).unwrap();

        // let (x, _y) = cx.resources.pmw3389.read_status().unwrap();
        *POS_X += (((xl as u16) | ((xh as u16) << 8)) as i16) as i64;
        *POS_Y += (((yl as u16) | ((yh as u16) << 8)) as i16) as i64;

        // task should run each second N ms (96_000 cycles at 96MHz)
        cx.schedule
            .poll(cx.scheduled + POLL_CYCLES.cycles())
            .unwrap();
    }

    #[task(priority = 3, resources = [TIM1], schedule = [pwm_out])]
    fn pwm_out(cx: pwm_out::Context) {
        static mut INDEX: u16 = 0;
        static mut LEFT: u16 = 0;
        static mut RIGHT: u16 = 0;
        // static mut FLOAT: u16 = 0;

        let tim1 = cx.resources.TIM1;

        tim1.ccr1.write(|w| unsafe { w.ccr().bits(*LEFT) });
        tim1.ccr2.write(|w| unsafe { w.ccr().bits(*RIGHT) });
        // tim1.ccr2.write(|w| unsafe { w.ccr().bits(*FLOAT) });

        cx.schedule
            .pwm_out(cx.scheduled + PWM_CYCLES.cycles())
            .unwrap();

        *INDEX = (*INDEX).wrapping_add(1_000);
        // let f: f32 = (*INDEX as f32 * 2.0 * PI) / 65536.0;
        // *FLOAT = (128.0 + f.sin() * 128.0) as u16;

        *LEFT = SINE_BUF[*INDEX as usize] as u16;
        *RIGHT = SINE_BUF[*INDEX as usize] as u16;

        // if cx.scheduled.elapsed() > 1500.cycles() {
        //     panic!("task overrun");
        // }
    }

    extern "C" {
        fn EXTI0();
        fn EXTI1();
        fn EXTI2();
    }
};

// We aim for a sampling rate of 48kHz, assuming that the input filter of the
// sound card used to sample the generated signal has an appropriate input filter
const PWM_CYCLES: u32 = 2000; // 96_000_000 / 2_000 = 48_000 Hz
const POLL_CYCLES: u32 = 96_000; // 96_000_000 / 96_000 = 1_000 Hz
+202 −0
Original line number Original line Diff line number Diff line
//! examples/rtt-pwm-dma.rs
//! cargo run --examples rtt-pwm-dma

// #![deny(unsafe_code)]
// #![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m::{asm, peripheral::DWT};
use panic_halt as _;
use rtt_target::{rprint, rprintln, rtt_init_print};
use stm32f4xx_hal::{bb, dma, gpio::Speed, prelude::*, pwm, stm32};

#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)]
const APP: () = {
    #[init]
    fn init(mut cx: init::Context) {
        rtt_init_print!();
        rprintln!("init");
        let dp = cx.device;

        // Initialize (enable) the monotonic timer (CYCCNT)
        cx.core.DCB.enable_trace();
        cx.core.DWT.enable_cycle_counter();

        let rcc = dp.RCC.constrain();
        // Set up the system clock. 48 MHz?
        let clocks = rcc
            .cfgr
            // .use_hse(8.mhz())
            .sysclk(48.mhz())
            .pclk1(24.mhz())
            .freeze();

        let gpioa = dp.GPIOA.split();
        // we set the pins to VeryHigh to get the sharpest waveform possible
        // (rise and fall times should have similar characteristics)
        let channels = (
            gpioa.pa8.into_alternate_af1().set_speed(Speed::VeryHigh),
            gpioa.pa9.into_alternate_af1().set_speed(Speed::VeryHigh),
        );

        // Setup PWM RAW
        let tim1 = dp.TIM1;
        // Here we need unsafe as we are "stealing" the RCC peripheral
        // At this point it has been contrained into SysConf and used to set clocks
        let rcc = unsafe { &(*stm32::RCC::ptr()) };

        rcc.apb2enr.modify(|_, w| w.tim1en().set_bit());
        rcc.apb2rstr.modify(|_, w| w.tim1rst().set_bit());
        rcc.apb2rstr.modify(|_, w| w.tim1rst().clear_bit());

        // Setup chanel 1 and 2 as pwm_mode1
        tim1.ccmr1_output()
            .modify(|_, w| w.oc1pe().set_bit().oc1m().pwm_mode1());

        tim1.ccmr1_output()
            .modify(|_, w| w.oc2pe().set_bit().oc2m().pwm_mode1());

        // The reference manual is a bit ambiguous about when enabling this bit is really
        // necessary, but since we MUST enable the preload for the output channels then we
        // might as well enable for the auto-reload too
        tim1.cr1.modify(|_, w| w.arpe().set_bit());

        let clk = clocks.pclk2().0 * if clocks.ppre2() == 1 { 1 } else { 2 };
        // check that its actually 48_000_000
        rprintln!("clk {}", clk);

        // we want maximum performance, thus we set the prescaler to 0
        let pre = 0;
        rprintln!("pre {}", pre);
        tim1.psc.write(|w| w.psc().bits(pre));

        // we want 8 bits of resolution
        // so our ARR = 2^8 - 1 = 256 - 1 = 255
        let arr = 255;
        rprintln!("arr {}", arr);
        tim1.arr.write(|w| unsafe { w.bits(arr) });

        //  Trigger update event to load the registers
        tim1.cr1.modify(|_, w| w.urs().set_bit());
        tim1.egr.write(|w| w.ug().set_bit());
        tim1.cr1.modify(|_, w| w.urs().clear_bit());

        // Set main output enable of all Output Compare (OC) registers
        tim1.bdtr.modify(|_, w| w.moe().set_bit());

        // Set output enable for channels 1 and 2
        // Channel 1 (bit  0)
        unsafe { bb::set(&tim1.ccer, 0) }
        // Channel 4 (bit  0)
        unsafe { bb::set(&tim1.ccer, 4) }

        // Setup the timer
        tim1.cr1.write(|w| {
            w.cms()
                .bits(0b00) // edge aligned mode
                .dir() // counter used as upcounter
                .clear_bit()
                .opm() // one pulse mode
                .clear_bit()
                .cen() // enable counter
                .set_bit()
        });

        // Setup TIMER2
        let tim2 = dp.TIM2;
        // Here we need unsafe as we are "stealing" the RCC peripheral
        // At this point it has been contrained into SysConf and used to set clocks
        let rcc = unsafe { &(*stm32::RCC::ptr()) };

        rcc.apb1enr.modify(|_, w| w.tim2en().set_bit());
        rcc.apb1rstr.modify(|_, w| w.tim2rst().set_bit());
        rcc.apb1rstr.modify(|_, w| w.tim2rst().clear_bit());

        // auto-reload, preload
        tim1.cr1.modify(|_, w| w.arpe().set_bit());

        let clk = clocks.pclk2().0 * if clocks.ppre2() == 1 { 1 } else { 2 };
        // check that its actually 48_000_000
        rprintln!("clk {}", clk);

        // assume we have a sine at
        let pre = 0;
        rprintln!("pre {}", pre);
        tim2.psc.write(|w| w.psc().bits(pre));

        // we want 8 bits of resolution
        // so our ARR = 2^8 - 1 = 256 - 1 = 255
        let arr = 255;
        rprintln!("arr {}", arr);
        tim1.arr.write(|w| unsafe { w.bits(arr) });

        //  Trigger update event to load the registers
        tim1.cr1.modify(|_, w| w.urs().set_bit());
        tim1.egr.write(|w| w.ug().set_bit());
        tim1.cr1.modify(|_, w| w.urs().clear_bit());

        // Set main output enable of all Output Compare (OC) registers
        tim1.bdtr.modify(|_, w| w.moe().set_bit());

        // Set output enable for channels 1 and 2
        // Channel 1 (bit  0)
        unsafe { bb::set(&tim1.ccer, 0) }
        // Channel 4 (bit  0)
        unsafe { bb::set(&tim1.ccer, 4) }

        // Setup the timer
        tim1.cr1.write(|w| {
            w.cms()
                .bits(0b00) // edge aligned mode
                .dir() // counter used as upcounter
                .clear_bit()
                .opm() // one pulse mode
                .clear_bit()
                .cen() // enable counter
                .set_bit()
        });

        // Set duty cycle of Channels
        tim1.ccr1.write(|w| unsafe { w.ccr().bits(128) });
        tim1.ccr2.write(|w| unsafe { w.ccr().bits(128) });

        // Set preload for the CCx
        tim1.cr2.write(|w| w.ccpc().set_bit());

        tim1.dier.write(|w| w.uie().enabled());
        tim1.sr.modify(|_, w| w.uif().clear());

        while tim1.sr.read().uif().is_clear() {
            rprint!("-");
        }
        rprintln!("here");
        tim1.sr.modify(|_, w| w.uif().clear());

        let mut dma_buff = [0u8; 256];
        for i in 0..256 {
            dma_buff[i] = i as u8;
        }

        loop {
            for i in 0..256 {
                // wait until next update event
                while tim1.sr.read().uif().is_clear() {}
                tim1.sr.modify(|_, w| w.uif().clear());

                tim1.ccr1
                    .write(|w| unsafe { w.ccr().bits(dma_buff[i] as u16) });
                tim1.ccr2
                    .write(|w| unsafe { w.ccr().bits(dma_buff[i] as u16) });
            }
        }
    }

    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        rprintln!("idle");
        loop {
            continue;
        }
    }
};
+149 −0
Original line number Original line Diff line number Diff line
//! examples/rtt-pwm-saw.rs
//! cargo run --examples rtt-pwm-saw

// #![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

// use cortex_m::{asm, peripheral::DWT};
use panic_halt as _;
use rtt_target::{rprint, rprintln, rtt_init_print};
use stm32f4xx_hal::{bb, gpio::Speed, prelude::*, stm32};

#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)]
const APP: () = {
    #[init]
    fn init(mut cx: init::Context) {
        rtt_init_print!();
        rprintln!("init");
        let dp = cx.device;

        // Initialize (enable) the monotonic timer (CYCCNT)
        cx.core.DCB.enable_trace();
        cx.core.DWT.enable_cycle_counter();

        let rcc = dp.RCC.constrain();
        // Set up the system clock. 48 MHz?
        let clocks = rcc
            .cfgr
            // .use_hse(8.mhz())
            .sysclk(48.mhz())
            .pclk1(24.mhz())
            .freeze();

        let gpioa = dp.GPIOA.split();
        // we set the pins to VeryHigh to get the sharpest waveform possible
        // (rise and fall times should have similar characteristics)
        let _channels = (
            gpioa.pa8.into_alternate_af1().set_speed(Speed::VeryHigh),
            gpioa.pa9.into_alternate_af1().set_speed(Speed::VeryHigh),
        );

        // Setup PWM RAW
        let tim1 = dp.TIM1;
        // Here we need unsafe as we are "stealing" the RCC peripheral
        // (At this point it has been constrained into SysConf and used to set clocks.)
        let rcc = unsafe { &(*stm32::RCC::ptr()) };

        // unsafe {
        //     bb::set(&rcc.apb2enr, 0u8);
        //     bb::set(&rcc.apb2rstr, 0u8);
        //     bb::clear(&rcc.apb2rstr, 0u8);
        // }
        //
        // For some reason bb:: hangs target in release mode,
        // so I implemented it using modify instead
        rcc.apb2enr.modify(|_, w| w.tim1en().set_bit());
        rcc.apb2rstr.modify(|_, w| w.tim1rst().set_bit());
        rcc.apb2rstr.modify(|_, w| w.tim1rst().clear_bit());

        // Setup chanel 1 and 2 as pwm_mode1
        tim1.ccmr1_output()
            .modify(|_, w| w.oc1pe().set_bit().oc1m().pwm_mode1());

        tim1.ccmr1_output()
            .modify(|_, w| w.oc2pe().set_bit().oc2m().pwm_mode1());

        // The reference manual is a bit ambiguous about when enabling this bit is really
        // necessary, but since we MUST enable the preload for the output channels then we
        // might as well enable for the auto-reload too
        tim1.cr1.modify(|_, w| w.arpe().set_bit());

        let clk = clocks.pclk2().0 * if clocks.ppre2() == 1 { 1 } else { 2 };
        // check that its actually 48_000_000
        rprintln!("clk {}", clk);

        // we want maximum performance, thus we set the prescaler to 0
        let pre = 0;
        rprintln!("pre {}", pre);
        tim1.psc.write(|w| w.psc().bits(pre));

        // we want 8 bits of resolution
        // so our ARR = 2^8 - 1 = 256 - 1 = 255
        let arr = 255;
        rprintln!("arr {}", arr);
        tim1.arr.write(|w| unsafe { w.bits(arr) });

        //  Trigger update event to load the registers
        tim1.cr1.modify(|_, w| w.urs().set_bit());
        tim1.egr.write(|w| w.ug().set_bit());
        tim1.cr1.modify(|_, w| w.urs().clear_bit());

        // Set main output enable of all Output Compare (OC) registers
        tim1.bdtr.modify(|_, w| w.moe().set_bit());

        // Set output enable for channels 1 and 2
        // Channel 1 (bit  0)
        unsafe { bb::set(&tim1.ccer, 0) }
        // Channel 4 (bit  0)
        unsafe { bb::set(&tim1.ccer, 4) }

        // Setup the timer
        tim1.cr1.write(|w| {
            w.cms()
                .bits(0b00) // edge aligned mode
                .dir() // counter used as upcounter
                .clear_bit()
                .opm() // one pulse mode
                .clear_bit()
                .cen() // enable counter
                .set_bit()
        });

        // Set duty cycle of Channels
        tim1.ccr1.write(|w| unsafe { w.ccr().bits(128) });
        tim1.ccr2.write(|w| unsafe { w.ccr().bits(128) });

        // Set preload for the CCx
        tim1.cr2.write(|w| w.ccpc().set_bit());

        tim1.dier.write(|w| w.uie().enabled());
        tim1.sr.modify(|_, w| w.uif().clear());

        while tim1.sr.read().uif().is_clear() {
            rprint!("-");
        }
        rprintln!("here");
        tim1.sr.modify(|_, w| w.uif().clear());

        loop {
            for i in 0..256 {
                // wait until next update event
                while tim1.sr.read().uif().is_clear() {}
                tim1.sr.modify(|_, w| w.uif().clear());

                tim1.ccr1.write(|w| unsafe { w.ccr().bits(i) });
                tim1.ccr2.write(|w| unsafe { w.ccr().bits(i) });
            }
        }
    }

    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        rprintln!("idle");
        loop {
            continue;
        }
    }
};
+187 −0
Original line number Original line Diff line number Diff line
//! examples/rtt-pwm-sine.rs
//! cargo run --examples rtt-pwm-sine --release

// #![deny(unsafe_code)]
// #![deny(warnings)]
#![no_main]
#![no_std]

// use core::f32::consts::PI;
use cortex_m::{asm, peripheral::DWT};
// use panic_halt as _;
use panic_rtt_target as _;
use rtic::cyccnt::{Instant, U32Ext as _};
use rtt_target::{rprint, rprintln, rtt_init_print};

use core::f32::consts::PI;
use micromath::F32Ext;

use stm32f4xx_hal::{bb, gpio::Speed, prelude::*, pwm, stm32};

include!(concat!(env!("OUT_DIR"), "/sin_abs_const.rs"));

#[rtic::app(device = stm32f4xx_hal::stm32,  monotonic = rtic::cyccnt::CYCCNT, peripherals = true)]
const APP: () = {
    struct Resources {
        // late resources
        TIM1: stm32::TIM1,
    }
    #[init(schedule = [pwm_out])]
    fn init(mut cx: init::Context) -> init::LateResources {
        rtt_init_print!();
        rprintln!("init");
        let dp = cx.device;

        // Initialize (enable) the monotonic timer (CYCCNT)
        cx.core.DCB.enable_trace();
        cx.core.DWT.enable_cycle_counter();

        let rcc = dp.RCC.constrain();
        // Set up the system clock. 48 MHz?
        let clocks = rcc
            .cfgr
            // .use_hse(8.mhz())
            // .sysclk(48.mhz())
            .sysclk(96.mhz())
            .pclk1(24.mhz())
            .freeze();

        let gpioa = dp.GPIOA.split();
        // we set the pins to VeryHigh to get the sharpest waveform possible
        // (rise and fall times should have similar characteristics)
        let _channels = (
            gpioa.pa8.into_alternate_af1().set_speed(Speed::VeryHigh),
            gpioa.pa9.into_alternate_af1().set_speed(Speed::VeryHigh),
        );

        // Setup PWM RAW
        let tim1 = dp.TIM1;
        // Here we need unsafe as we are "stealing" the RCC peripheral
        // At this point it has been contrained into SysConf and used to set clocks
        let rcc = unsafe { &(*stm32::RCC::ptr()) };

        rcc.apb2enr.modify(|_, w| w.tim1en().set_bit());
        rcc.apb2rstr.modify(|_, w| w.tim1rst().set_bit());
        rcc.apb2rstr.modify(|_, w| w.tim1rst().clear_bit());

        // Setup chanel 1 and 2 as pwm_mode1
        tim1.ccmr1_output()
            .modify(|_, w| w.oc1pe().set_bit().oc1m().pwm_mode1());

        tim1.ccmr1_output()
            .modify(|_, w| w.oc2pe().set_bit().oc2m().pwm_mode1());

        // The reference manual is a bit ambiguous about when enabling this bit is really
        // necessary, but since we MUST enable the pre-load for the output channels then we
        // might as well enable for the auto-reload too
        tim1.cr1.modify(|_, w| w.arpe().set_bit());

        let clk = clocks.pclk2().0 * if clocks.ppre2() == 1 { 1 } else { 2 };
        // check that its actually 48_000_000
        rprintln!("clk {}", clk);

        // we want maximum performance, thus we set the prescaler to 0
        let pre = 0;
        rprintln!("pre {}", pre);
        tim1.psc.write(|w| w.psc().bits(pre));

        // we want 8 bits of resolution
        // so our ARR = 2^8 - 1 = 256 - 1 = 255
        let arr = 255;
        rprintln!("arr {}", arr);
        tim1.arr.write(|w| unsafe { w.bits(arr) });

        //  Trigger update event to load the registers
        tim1.cr1.modify(|_, w| w.urs().set_bit());
        tim1.egr.write(|w| w.ug().set_bit());
        tim1.cr1.modify(|_, w| w.urs().clear_bit());

        // Set main output enable of all Output Compare (OC) registers
        tim1.bdtr.modify(|_, w| w.moe().set_bit());

        // Set output enable for channels 1 and 2
        tim1.ccer.write(|w| w.cc1e().set_bit().cc2e().set_bit());

        // Setup the timer
        tim1.cr1.write(|w| {
            w.cms()
                .bits(0b00) // edge aligned mode
                .dir() // counter used as up-counter
                .clear_bit()
                .opm() // one pulse mode
                .clear_bit()
                .cen() // enable counter
                .set_bit()
        });

        // Set main output enable of all Output Compare (OC) registers
        tim1.bdtr.modify(|_, w| w.moe().set_bit());

        // Set duty cycle of Channels
        tim1.ccr1.write(|w| unsafe { w.ccr().bits(128) });
        tim1.ccr2.write(|w| unsafe { w.ccr().bits(128) });

        // Set preload for the CCx
        tim1.cr2.write(|w| w.ccpc().set_bit());

        // Enable update events
        tim1.dier.write(|w| w.uie().enabled());
        tim1.sr.modify(|_, w| w.uif().clear());

        // Set divider to 4, (48_000_000/256)/4
        // tim1.rcr.modify(|_, w| unsafe { w.rep().bits(4) });

        while tim1.sr.read().uif().is_clear() {
            rprint!("-");
        }
        rprintln!("here");
        tim1.sr.modify(|_, w| w.uif().clear());

        // pass on late resources
        cx.schedule.pwm_out(cx.start + PERIOD.cycles()).ok();
        init::LateResources { TIM1: tim1 }
    }

    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        rprintln!("idle");
        // panic!("panic");
        loop {
            continue;
        }
    }

    #[task(resources = [TIM1], schedule = [pwm_out])]
    fn pwm_out(cx: pwm_out::Context) {
        static mut INDEX: u16 = 0;
        static mut LEFT: u16 = 0;
        static mut RIGHT: u16 = 0;
        static mut FLOAT: u16 = 0;

        let tim1 = cx.resources.TIM1;

        tim1.ccr1.write(|w| unsafe { w.ccr().bits(*LEFT) });
        // tim1.ccr2.write(|w| unsafe { w.ccr().bits(*RIGHT) });
        tim1.ccr2.write(|w| unsafe { w.ccr().bits(*FLOAT) });

        *INDEX = (*INDEX).wrapping_add(10_000);
        let f: f32 = (*INDEX as f32 * 2.0 * PI) / 65536.0;
        *FLOAT = (128.0 + f.sin() * 128.0) as u16;
        cx.schedule.pwm_out(cx.scheduled + PERIOD.cycles()).ok();

        *LEFT = SINE_BUF[*INDEX as usize] as u16;
        *RIGHT = SINE_BUF[*INDEX as usize] as u16;

        if cx.scheduled.elapsed() > 500.cycles() {
            panic!("task overrun");
        }
    }

    extern "C" {
        fn EXTI0();
    }
};

// We aim for a sampling rate of 48kHz, assuming that the input filter of the
// sound card used to sample the generated signal has an appropriate input filter
const PERIOD: u32 = 2000; // 96_000_000 / 48_000;
+173 −0

File added.

Preview size limit exceeded, changes collapsed.

Original line number Original line Diff line number Diff line
//! examples/rtt_timing.rs
//! cargo run --examples rtt-pwm
//! cargo run --examples rtt-pwm


#![deny(unsafe_code)]
#![deny(unsafe_code)]
// #![deny(warnings)]
#![deny(warnings)]
#![no_main]
#![no_main]
#![no_std]
#![no_std]


use cortex_m::{asm, peripheral::DWT};
use panic_halt as _;
use panic_halt as _;
use rtt_target::{rprintln, rtt_init_print};
use rtt_target::{rprintln, rtt_init_print};
use stm32f4xx_hal::{dma, gpio::Speed, prelude::*, pwm, stm32};
use stm32f4xx_hal::{gpio::Speed, prelude::*, pwm};


#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)]
#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)]
const APP: () = {
const APP: () = {
    #[init]
    #[init]
    fn init(mut cx: init::Context) {
    fn init(cx: init::Context) {
        rtt_init_print!();
        rtt_init_print!();
        rprintln!("init");
        rprintln!("init");
        let dp = cx.device;
        let dp = cx.device;


        // Initialize (enable) the monotonic timer (CYCCNT)
        cx.core.DCB.enable_trace();
        cx.core.DWT.enable_cycle_counter();

        // Set up the system clock. 16 MHz?
        // Set up the system clock. 16 MHz?
        let rcc = dp.RCC.constrain();
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.freeze();
        let clocks = rcc.cfgr.freeze();


        let gpioa = dp.GPIOA.split();
        let gpioa = dp.GPIOA.split();
        let channels = (
        let channels = (
            gpioa.pa8.into_alternate_af1().set_speed(Speed::Low),
            gpioa.pa8.into_alternate_af1().set_speed(Speed::High),
            gpioa.pa9.into_alternate_af1(),
            gpioa.pa9.into_alternate_af1().set_speed(Speed::High),
        );
        );


        let pwm = pwm::tim1(dp.TIM1, channels, clocks, 1u32.khz());
        let pwm = pwm::tim1(dp.TIM1, channels, clocks, 1u32.khz());
@@ -39,7 +33,7 @@ const APP: () = {
        rprintln!("max_duty {}", max_duty);
        rprintln!("max_duty {}", max_duty);
        ch1.set_duty(max_duty / 2);
        ch1.set_duty(max_duty / 2);
        ch1.enable();
        ch1.enable();
        ch2.set_duty((max_duty * 1) / 2);
        ch2.set_duty((max_duty * 1) / 3);
        ch2.enable();
        ch2.enable();
    }
    }


Original line number Original line Diff line number Diff line
//! examples/rtt_timing.rs
//! examples/rtt_timing.rs
//! cargo run --examples rtt-timing
//! cargo run --examples rtt-timing


#![deny(unsafe_code)]
#![deny(warnings)]
#![deny(warnings)]
#![no_main]
#![no_main]
#![no_std]
#![no_std]
+27 −0
Original line number Original line Diff line number Diff line
// cargo run --example rtt_rtic_hello

#![no_main]
#![no_std]

use panic_halt as _;
use rtt_target::{rprintln, rtt_init_print};
use stm32f4;

#[rtic::app(device = stm32f4)]
const APP: () = {
    #[init]
    fn init(_cx: init::Context) {
        rtt_init_print!();
        for i in 0..11 {
            rprintln!("RTIC Says Hello, world {}!!", i);
        }
    }

    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        rprintln!("lets get lazy");
        loop {
            continue;
        }
    }
};
+368 −0

File added.

Preview size limit exceeded, changes collapsed.

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Original line Diff line number Diff line
@@ -3,7 +3,7 @@ MEMORY
  /* NOTE 1 K = 1 KiBi = 1024 bytes */
  /* NOTE 1 K = 1 KiBi = 1024 bytes */
  /* TODO Adjust these memory regions to match your device memory layout */
  /* TODO Adjust these memory regions to match your device memory layout */
  /* These values correspond to the STM32F411 */
  /* These values correspond to the STM32F411 */
  FLASH : ORIGIN = 0x08000000, LENGTH = 256K
  FLASH : ORIGIN = 0x08000000, LENGTH = 128K
  RAM : ORIGIN = 0x20000000, LENGTH = 64K
  RAM : ORIGIN = 0x20000000, LENGTH = 64K
}
}


+1 −1
Original line number Original line Diff line number Diff line
@@ -24,7 +24,7 @@ monitor arm semihosting enable
# # send captured ITM to the file itm.fifo
# # send captured ITM to the file itm.fifo
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
# # 8000000 must match the core clock frequency
# # 8000000 must match the core clock frequency
monitor tpiu config internal itm.txt uart off 16000000
monitor tpiu config internal itm.txt uart off 48000000


# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
# # OR: make the microcontroller SWO pin output compatible with UART (8N1)
# # 8000000 must match the core clock frequency
# # 8000000 must match the core clock frequency

src/lib.rs

0 → 100644
+38 −0

File added.

Preview size limit exceeded, changes collapsed.

src/main.rs

deleted100644 → 0
+0 −20

File deleted.

Preview size limit exceeded, changes collapsed.

src/pmw3389.rs

0 → 100644
+611 −0

File added.

Preview size limit exceeded, changes collapsed.

src/pmw3389e.rs

0 → 100644
+672 −0

File added.

Preview size limit exceeded, changes collapsed.