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
Loading items

Target

Select target project
  • Frappe/e7020e_2021
  • rognda-6/e7020e_2021
  • Klomega/e7020e_2021
  • pln/e7020e_2021
  • CarlOsterberg/e7020e_2021
  • jonjac-6/e7020e_2021
  • deux-babiri-futari/e7020e_2021
  • samgra-7/e7020e_2021
  • JosefUtbult/e7020e_2021
  • edwkll-7/e7020e_2021
10 results
Select Git revision
Loading items
Show changes
Commits on Source (16)
Showing
with 1464 additions and 927 deletions
......@@ -19,7 +19,7 @@
"runToMain": true,
"svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
"configFiles": [
"interface/stlink-v2-1.cfg",
"interface/stlink.cfg",
"target/stm32f4x.cfg"
],
"preRestartCommands": [
......@@ -44,6 +44,41 @@
"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",
},
{
"type": "cortex-debug",
"request": "launch",
......@@ -54,7 +89,7 @@
"runToMain": true,
"svdFile": "${workspaceRoot}/.vscode/STM32F401.svd",
"configFiles": [
"interface/stlink-v2-1.cfg",
"interface/stlink.cfg",
"target/stm32f4x.cfg"
],
"preRestartCommands": [
......@@ -78,6 +113,41 @@
},
"executable": "./target/thumbv7em-none-eabihf/release/examples/${fileBasenameNoExtension}",
"cpu": "cortex-m4",
},
{
"type": "cortex-debug",
"request": "launch",
"name": "Cortex Release 48Mhz",
"servertype": "openocd",
"cwd": "${workspaceRoot}",
"preLaunchTask": "cargo build --example --release",
"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/release/examples/${fileBasenameNoExtension}",
"cpu": "cortex-m4",
}
]
}.
\ No newline at end of file
# 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.
- `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!
- `examples/bare1.rs`, bare metal 101!
## 2021-02-23
- examples/rtic_blinky.rs, added instructions to terminal based debugging
- `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
- `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
- `examples/rtt_rtic_i2c.rs`, spi emulation over i2c
- `src/pwm3389e`, driver using emulated spi
## 2021-02-16
- rtt_rtic_usb_mouse updated
- `rtt_rtic_usb_mouse` updated
Notice, requires release build
## 2021-02-15
......
......@@ -7,9 +7,11 @@ version = "0.1.0"
[dependencies]
cortex-m = { version = "0.7.1", features = ["linker-plugin-lto"] }
# cortex-m = { version = "0.7.1" }
cortex-m-rt = "0.6.13"
cortex-m-semihosting = "0.3.7"
cortex-m-rtic = "0.5.5"
# embedded-hal = { version = "0.2.4", features = ["unproven"] }
embedded-hal = "0.2.4"
usb-device = "0.2.7"
......@@ -17,7 +19,7 @@ usb-device = "0.2.7"
panic-halt = "0.2.0"
# Uncomment for the itm panic examples.
#panic-itm = "0.4.2"
panic-itm = "0.4.2"
# Uncomment for the rtt-timing examples.
panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] }
......@@ -26,18 +28,18 @@ panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] }
panic-semihosting = "0.5.6"
# 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]
version = "0.13.0"
features = ["stm32f411", "rt"]
# Uncomment for the allocator example.
# alloc-cortex-m = "0.4.0"
[dependencies.stm32f4xx-hal]
version = "0.8.3"
version = "0.9.0"
features = ["rt", "stm32f411", "usb_fs"]
# Enable to use the latest git version
# gitgit = "https://github.com/stm32-rs/stm32f4xx-hal"
......@@ -45,28 +47,19 @@ features = ["rt", "stm32f411", "usb_fs"]
# path = "../stm32f4xx-hal"
# this lets you use `cargo fix`!
[[bin]]
name = "app"
test = false
bench = false
# [[bin]]
# name = "app"
# # test = false
# bench = false
[profile.dev]
incremental = false
codegen-units = 1
overflow-checks = false
[profile.release]
incremental = false
codegen-units = 1 # better optimizations
debug = true # symbols are nice and they don't increase the size on Flash
lto = true # better optimizations
# [features]
# nightly = ["cortex-m/inline-asm"]
# # this lets you use `cargo fix`!
# [[bin]]
# name = "app"
# test = false
# bench = false
......@@ -80,10 +80,14 @@ Using `vscode` just press F5 to launch and debug the program in the currently ac
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_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.
---
......@@ -193,9 +197,9 @@ D+ used for re-enumeration. You don't need to connect the V+ from the USB cable,
- If this does not work you can try to erase the flash memory (the program running on the STM32F401/F11).
``` shell
> st-util erase
st-flash 1.6.1
2021-01-11T16:02:14 INFO common.c: F4xx (Dynamic Efficency): 96 KiB SRAM, 512 KiB flash in at least 16 KiB pages.
> 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.......
```
......
......@@ -9,6 +9,10 @@ use 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 {
......
// 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);
}
}
};
This diff is collapsed.
......@@ -9,6 +9,7 @@
#![no_main]
#![no_std]
use cortex_m_semihosting::hprintln;
use panic_semihosting as _;
use stm32f4;
......@@ -18,6 +19,7 @@ const APP: () = {
#[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();
......
......@@ -64,6 +64,20 @@ const APP: () = {
// .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,
......@@ -126,6 +140,95 @@ fn clock_out(rcc: &RCC, gpioc: &GPIOC) {
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
......@@ -144,7 +247,7 @@ fn clock_out(rcc: &RCC, gpioc: &GPIOC) {
//
// ** your answer here **
//
// commit your answers (bare6_1)
// Commit your answers (bare6_1)
//
// 2. Now connect an oscilloscope to PC9, which is set to
// output the MCO2.
......@@ -170,8 +273,6 @@ fn clock_out(rcc: &RCC, gpioc: &GPIOC) {
//
// ** your answer here **
//
// Commit your answers (bare6_3)
//
// Now change the constant `OFFSET` so you get the same blinking frequency as in 1.
// Test and validate that you got the desired behavior.
//
......@@ -192,7 +293,7 @@ fn clock_out(rcc: &RCC, gpioc: &GPIOC) {
// ** your answer here **
//
// Make a screen dump or photo of the oscilloscope output.
// Save the the picture as "bare_6_64mhz_high_speed".
// Save the the picture as "bare_6_48mhz_high_speed".
//
// Commit your answers (bare6_4)
//
......@@ -213,13 +314,58 @@ fn clock_out(rcc: &RCC, gpioc: &GPIOC) {
// `w.mco2().bits{0b00}` is equivalent to
// `w.mco2().sysclk()` and improves readability.
//
// Replace all bit-patterns used by the function name equivalents.
// 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. Discussion
// 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.
......@@ -231,7 +377,7 @@ fn clock_out(rcc: &RCC, gpioc: &GPIOC) {
// by leveraging the abstractions provided by the PAC.
//
// As mentioned before the PACs are machine generated by `svd2rust`
// from vendor provided System View Desciptions (SVDs).
// from vendor provided System View Descriptions (SVDs).
//
// The PACs provide low level peripheral access abstractions, while
// the HALs provide higher level abstractions and functionality.
//! rtic_bare7.rs
//!
//! Clocking
//! 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)]
......@@ -17,6 +27,7 @@ const APP: () = {
struct Resources {
// late resources
GPIOA: stm32::GPIOA,
// led: PA5<Output<PushPull>>,
}
#[init(schedule = [toggle])]
fn init(cx: init::Context) -> init::LateResources {
......@@ -76,23 +87,38 @@ const APP: () = {
}
};
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
//
// Now look at the documentation for `embedded_hal::digital::v2::OutputPin`.
// 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.
//
// 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.
// 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`
// (and implements the `OutputPin` trait in embedded-hal).
// - 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.
//
......@@ -101,29 +127,115 @@ const APP: () = {
//
// Commit your code (bare7_1)
//
// 2. Optional
// 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.
//
// Use the `toggle` function instead to further simply your code.
// 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.
//
// Notice:
// The `ToggleableOutputPin` abstraction requires `embedded-hal`
// to compiled with the `unproven` feature.
// 5. More details:
//
// The `embedded-hal` traits is mostly used to write drivers
// that is hardware agnostic (and thus cross platform).
// Looking closer at the implementation:
// `led: &mut dyn OutputPin<Error = E>`
//
// However:
// In our case we can use `toggle` directly as implemented by the `stm32f4xx-hal`.
// You may ask what kind of mumbo jumbo is at play here.
//
// Confirm that your implementation correctly toggles the LED.
// 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.
//
// Which one do you prefer and why (what problem does it solve)?
// 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
//
// ** your answer here **
// 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.
//
// Commit your answer (bare7_2)
// 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).
//
// 3. Discussion
// 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.
//
// In this exercise you have learned more on navigating the generated documentation
// and to use abstractions to simplify and generalize your code.
// 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.
//! 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.)
//! 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.
//! 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
......@@ -6,14 +6,17 @@
#![no_main]
#![no_std]
use core::f32::consts::PI;
// 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 stm32f4xx_hal::{bb, dma, gpio::Speed, prelude::*, pwm, stm32};
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"));
......@@ -153,19 +156,23 @@ const APP: () = {
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(*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() > 300.cycles() {
if cx.scheduled.elapsed() > 500.cycles() {
panic!("task overrun");
}
}
......
......@@ -47,7 +47,7 @@ const APP: () = {
// 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
// At this point it has been constrained into SysConf and used to set clocks
let rcc = unsafe { &(*stm32::RCC::ptr()) };
rcc.apb2enr.modify(|_, w| w.tim1en().set_bit());
......
//! examples/rtt_timing.rs
//! cargo run --examples rtt-timing
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
......
......@@ -6,7 +6,6 @@
#![no_std]
use cortex_m::{asm::delay, delay};
// use panic_halt as _;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
......@@ -21,15 +20,36 @@ use stm32f4xx_hal::{
stm32::I2C1,
};
use rtic::cyccnt::{Instant, U32Ext as _};
use app::{
pmw3389e::{self, Register},
DwtDelay,
};
#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)]
const OFFSET: u32 = 8_000_000 / 10;
const LOG_OFFSET: u32 = 8_000_000;
#[rtic::app(device = stm32f4xx_hal::stm32, monotonic = rtic::cyccnt::CYCCNT, peripherals = true)]
const APP: () = {
#[init]
fn init(cx: init::Context) {
struct Resources {
pmw3389: pmw3389e::Pmw3389e<
SC18IS602::SH18IS602<
I2c<
I2C1,
(
PB8<AlternateOD<stm32f4xx_hal::gpio::AF4>>,
PB9<AlternateOD<stm32f4xx_hal::gpio::AF4>>,
),
>,
>,
SC18IS602::Error,
>,
#[init((0, 0))]
pos: (i64, i64),
}
#[init(schedule = [poll, log])]
fn init(cx: init::Context) -> init::LateResources {
rtt_init_print!();
rprintln!("init");
let dp = cx.device;
......@@ -41,6 +61,7 @@ const APP: () = {
// Initialize (enable) the monotonic timer (CYCCNT)
cp.DCB.enable_trace();
cp.DWT.enable_cycle_counter();
// Set up I2C.
let gpiob = dp.GPIOB.split();
......@@ -52,54 +73,17 @@ const APP: () = {
use embedded_hal::spi::MODE_3;
use SC18IS602::{Order, Speed, SH18IS602};
let mut spi_emu =
SH18IS602::new(i2c, 0, Order::MsbFirst, MODE_3, Speed::Speed1843kHz, true);
let spi_emu = SH18IS602::new(i2c, 0, Order::MsbFirst, MODE_3, Speed::Speed1843kHz, true);
rprintln!("spi_emu initialized");
// reset SPI transfer
spi_emu.set_low().ok();
cortex_m::asm::delay(1_000_000);
spi_emu.set_high().ok();
cortex_m::asm::delay(1_000_000);
rprintln!("set to gpio management");
// try split transaction
rprintln!("try split transaction");
// the write part
spi_emu.set_low().unwrap();
let mut req = [0x00];
spi_emu.transfer(&mut req).unwrap();
rprintln!("id request {:02x?}", req);
cortex_m::asm::delay(1_000);
// the read part
let mut req = [00];
spi_emu.transfer(&mut req).unwrap();
rprintln!("id resp {:02x?}", req);
spi_emu.set_high().unwrap();
rprintln!("try split transaction");
// the write part
spi_emu.set_low().unwrap();
let mut req = [0x01];
spi_emu.transfer(&mut req).unwrap();
rprintln!("version request {:02x?}", req);
cortex_m::asm::delay(1_000);
// the read part
let mut req = [00];
spi_emu.transfer(&mut req).unwrap();
rprintln!("version resp {:02x?}", req);
spi_emu.set_high().unwrap();
let delay = DwtDelay::new(&mut cp.DWT, clocks);
let pmw3389 = pmw3389e::Pmw3389e::new(spi_emu, delay).unwrap();
rprintln!("success");
cx.schedule.poll(cx.start + OFFSET.cycles()).unwrap();
cx.schedule.log(cx.start + LOG_OFFSET.cycles()).unwrap();
init::LateResources { pmw3389 }
}
#[idle]
......@@ -109,6 +93,52 @@ const APP: () = {
continue;
}
}
#[task(priority = 2, resources = [pmw3389, pos], schedule = [poll])]
fn poll(cx: poll::Context) {
// rprintln!("poll @ {:?}", Instant::now());
// static mut X: i64 = 0;
// static mut Y: i64 = 0;
cx.schedule.poll(cx.scheduled + OFFSET.cycles()).unwrap();
let pmw3389 = cx.resources.pmw3389;
pmw3389.write_register(Register::Motion, 0x01).unwrap();
let motion = pmw3389.read_register(Register::Motion).unwrap();
let xl = pmw3389.read_register(Register::DeltaXL).unwrap();
let xh = pmw3389.read_register(Register::DeltaXH).unwrap();
let yl = pmw3389.read_register(Register::DeltaYL).unwrap();
let yh = pmw3389.read_register(Register::DeltaYH).unwrap();
let x = (xl as u16 + (xh as u16) << 8) as i16;
let y = (yl as u16 + (yh as u16) << 8) as i16;
cx.resources.pos.0 += x as i64;
cx.resources.pos.1 += y as i64;
// let surface = motion & 0x08;
// let motion_detect = motion & 0x80;
// rprintln!(
// "motion {}, surface {}, (x, y) {:?}",
// motion_detect,
// surface,
// (x, y),
// );
}
#[task(priority = 1, resources = [pos], schedule = [log])]
fn log(mut cx: log::Context) {
cx.schedule.log(cx.scheduled + LOG_OFFSET.cycles()).unwrap();
cx.resources.pos.lock(|pos| rprintln!("(x, y) {:?}", pos));
}
extern "C" {
fn EXTI0();
fn EXTI1();
}
};
// SC18IS602
......@@ -222,8 +252,12 @@ mod SC18IS602 {
| (mode.phase as u8) << 2
| speed as u8;
rprintln!("configuring i2c->spi bridge");
device.i2c.write(addr, &mut [SpiConfigure.id(), cfg]).ok();
rprintln!("spi bridge configured");
if gpio {
device.set_ss0_gpio();
} else {
......@@ -258,8 +292,6 @@ mod SC18IS602 {
}
}
// impl<I2C> Default for SH18IS602<I2C> where I2C: i2c::Write + i2c::Read {}
impl<I2C> Transfer<u8> for SH18IS602<I2C>
where
I2C: i2c::Write + i2c::Read,
......@@ -290,12 +322,10 @@ mod SC18IS602 {
// A short delay is needed
// For improved performance use write if result is not needed
cortex_m::asm::delay(1000);
cortex_m::asm::delay(1_000);
self.i2c.read(self.addr, words).map_err(|_| panic!()).ok();
// rprintln!("transfer_read {:02x?}", words);
Ok(words)
}
}
......@@ -310,12 +340,13 @@ mod SC18IS602 {
if !self.gpio {
Err(Error::NotConfigured)
} else {
rprintln!("set low");
// rprintln!("set low");
// cortex_m::asm::delay(10_000);
self.i2c
.write(self.addr, &[Function::GpioWrite.id(), 0x0])
.map_err(|_| panic!())
.ok();
cortex_m::asm::delay(100_000);
// cortex_m::asm::delay(20_000);
Ok(())
}
}
......@@ -324,7 +355,8 @@ mod SC18IS602 {
if !self.gpio {
Err(Error::NotConfigured)
} else {
rprintln!("set_high");
// rprintln!("set_high");
// cortex_m::asm::delay(10_000);
self.i2c
.write(self.addr, &[Function::GpioWrite.id(), 0x1])
.map_err(|_| panic!())
......
......@@ -324,6 +324,7 @@ fn usb_poll<B: bus::UsbBus>(
usb_dev: &mut UsbDevice<'static, B>,
hid: &mut HIDClass<'static, B>,
) {
//
if !usb_dev.poll(&mut [hid]) {
return;
}
......
// > cargo run usb-mouse
// or
// > cargo run usb-mouse --release
#![no_main]
#![no_std]
use stm32f4xx_hal::{
gpio::{gpioc::PC13, Input, PullUp},
otg_fs::{UsbBus, UsbBusType, USB},
prelude::*,
rcc::Clocks,
};
use usb_device::{bus::UsbBusAllocator, prelude::*};
use usbd_hid::{
descriptor::{generator_prelude::*, MouseReport},
hid_class::HIDClass,
};
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)]
const APP: () = {
struct Resources {
btn: PC13<Input<PullUp>>,
hid: HIDClass<'static, UsbBusType>,
usb_dev: UsbDevice<'static, UsbBus<USB>>,
}
#[init]
fn init(ctx: init::Context) -> init::LateResources {
static mut EP_MEMORY: [u32; 1024] = [0; 1024];
static mut USB_BUS: Option<UsbBusAllocator<UsbBusType>> = None;
rtt_init_print!();
rprintln!("init");
// Set up the system clock.
let rcc = ctx.device.RCC.constrain();
let clocks: Clocks = rcc.cfgr.sysclk(48.mhz()).require_pll48clk().freeze();
let hclk = clocks.hclk();
let gpioc = ctx.device.GPIOC.split();
let btn = gpioc.pc13.into_pull_up_input();
let gpioa = ctx.device.GPIOA.split();
let usb = USB {
usb_global: ctx.device.OTG_FS_GLOBAL,
usb_device: ctx.device.OTG_FS_DEVICE,
usb_pwrclk: ctx.device.OTG_FS_PWRCLK,
pin_dm: gpioa.pa11.into_alternate_af10(),
pin_dp: gpioa.pa12.into_alternate_af10(),
hclk,
};
USB_BUS.replace(UsbBus::new(usb, EP_MEMORY));
let hid = HIDClass::new(USB_BUS.as_ref().unwrap(), MouseReport::desc(), 1);
let usb_dev = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0xc410, 0x0000))
.manufacturer("E70011E")
.product("Mouse")
.serial_number("1.0")
.device_class(0)
.build();
init::LateResources { btn, hid, usb_dev }
}
#[task(binds=OTG_FS, resources = [btn, hid, usb_dev])]
fn on_usb(ctx: on_usb::Context) {
static mut COUNTER: u16 = 0;
static mut FIRST: bool = true;
if *FIRST {
rprintln!("on_usb");
*FIRST = false;
}
// destruct the context
let (btn, usb_dev, hid) = (ctx.resources.btn, ctx.resources.usb_dev, ctx.resources.hid);
let report = MouseReport {
x: match *COUNTER {
// reached after 100ms
100 => {
rprintln!("10");
10
}
// reached after 199ms
199 => {
rprintln!("-10");
-10
}
_ => 0,
},
y: 0,
buttons: btn.is_low().unwrap().into(), // (into takes a bool into an integer)
wheel: 0,
};
// wraps around after 200ms
*COUNTER = (*COUNTER + 1) % 200;
// push the report
hid.push_input(&report).ok();
// update the usb device state
if usb_dev.poll(&mut [hid]) {
return;
}
}
#[idle]
fn idle(_cx: idle::Context) -> ! {
rprintln!("idle");
loop {
cortex_m::asm::nop();
}
}
};
......@@ -24,7 +24,7 @@ monitor arm semihosting enable
# # send captured ITM to the file itm.fifo
# # (the microcontroller SWO pin must be connected to the programmer SWO pin)
# # 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)
# # 8000000 must match the core clock frequency
......