Skip to content

Commit

Permalink
Merge pull request mciantyre#64 from mitchmindtree/rtic-example
Browse files Browse the repository at this point in the history
Add Peripherals::steal behind `rtic` feature. Add `rtic-led.rs`, `rtic-blink.rs` and `rtic-uart-log.rs` examples.
  • Loading branch information
mciantyre authored Jul 4, 2020
2 parents 2114788 + 5b82a6f commit 8cf06cf
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --verbose --workspace --examples --target thumbv7em-none-eabihf -- -D warnings
args: --verbose --all-features --workspace --examples --target thumbv7em-none-eabihf -- -D warnings
name: Run clippy

precompiled:
Expand All @@ -46,4 +46,4 @@ jobs:
- name: Build runtime support
run: INSTALL_DEPS=0 make libt4boot
- name: Build USB stack
run: INSTALL_DEPS=0 make libt4usb
run: INSTALL_DEPS=0 make libt4usb
33 changes: 33 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,27 @@ path = "teensy4-usb-sys"
optional = true

[dev-dependencies]
cortex-m-rtic = "0.5.3"
embedded-hal = "0.2.4"
heapless = "0.5.5"
log = "0.4.8"
imxrt-uart-log = "0.1.0"
nb = "0.1.2"
panic-halt = "0.2.0"

[[example]]
name = "rtic_led"
path = "examples/rtic_led.rs"
required-features = ["rtic"]
[[example]]
name = "rtic_blink"
path = "examples/rtic_blink.rs"
required-features = ["rtic"]
[[example]]
name = "rtic_uart_log"
path = "examples/rtic_uart_log.rs"
required-features = ["rtic"]

[workspace]
members = [
"cortex-m-rt-patch",
Expand All @@ -59,7 +76,16 @@ default = ["usb-logging", "systick"]
usb-logging = ["systick", "teensy4-usb-sys", "log"]
# Include a definition of the SysTick exception handler. This enables
# a simple delay() spinloop that waits for the timer to elapse.
#
# NOTE: This feature is incompatible with the `rtic` crate as `rtic`
# provides its own `SysTick` definition.
systick = ["embedded-hal"]
# Provides the `Peripherals::steal` constructor required by `rtic`.
#
# NOTE: When using this feature along with the `rtic` crate the
# default features must first be disabled in order to avoid a
# duplicate definition of `SysTick`.
rtic = []

# Don't optimize build dependencies, like proc macros.
# Helps with build times.
Expand All @@ -70,3 +96,10 @@ opt-level = 0
# https://github.com/mciantyre/teensy4-rs#runtime
[patch.crates-io.cortex-m-rt]
path = "cortex-m-rt-patch"

# Patch `imxrt-hal` so that we may access the `hal::Peripherals::steal`
# constructor. This patch should be removed once a new version of
# `imxrt-hal` is published with the new constructor.
[patch.crates-io.imxrt-hal]
git = "https://github.com/imxrt-rs/imxrt-rs"
branch = "master"
87 changes: 87 additions & 0 deletions examples/rtic_blink.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! An adaptation of the `rtic_led.rs` example that demonstrates:
//!
//! 1. how to share late resources and
//! 2. how to use the systick interrupt to cause the LED to blink.
//!
//! Please refer to the [RTIC book](https://rtic.rs) for more information on RTIC.
//!
//! NOTE: This example requires the `rtic` feature to be enabled.
#![no_std]
#![no_main]

use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin};
use panic_halt as _;
use rtic::cyccnt::U32Ext;
use teensy4_bsp as bsp;

// The CYCCNT counts in clock cycles. Using the clock hz should give us a ~1 second period.
const PERIOD: u32 = bsp::hal::ccm::PLL1::ARM_HZ;

#[rtic::app(device = teensy4_bsp, monotonic = rtic::cyccnt::CYCCNT, peripherals = true)]
const APP: () = {
struct Resources {
led: bsp::LED,
}

#[init(schedule = [blink])]
fn init(mut cx: init::Context) -> init::LateResources {
init_delay();

// Set the clock mode to 'RUN'
//
// i.MX RT (106x) processors will not wake on SYSTICK. When we enter
// WFI or WFE, we'll enter WAIT mode by default. This will disable
// SYSTICK. So, if you're waiting for SYSTICK to wake you up, it won't
// happen. By setting ourselves to 'RUN' low-power mode, SYSTICK will
// still wake us up.
//
// See the CCM_CLPCR register for more information. The HAL docs also note
// this aspect of the processor.
cx.device.ccm.set_mode(bsp::hal::ccm::ClockMode::Run);

// Initialise the monotonic CYCCNT timer.
cx.core.DWT.enable_cycle_counter();

// Ensure the ARM clock is configured for the default speed seeing as we use this speed to
// determine a 1 second `PERIOD`.
cx.device.ccm.pll1.set_arm_clock(
bsp::hal::ccm::PLL1::ARM_HZ,
&mut cx.device.ccm.handle,
&mut cx.device.dcdc,
);

// Schedule the first blink.
cx.schedule.blink(cx.start + PERIOD.cycles()).unwrap();

let mut led = bsp::configure_led(&mut cx.device.gpr, cx.device.pins.p13);
led.set_high().unwrap();

init::LateResources { led }
}

#[task(resources = [led], schedule = [blink])]
fn blink(cx: blink::Context) {
cx.resources.led.toggle().unwrap();
// Schedule the following blink.
cx.schedule.blink(cx.scheduled + PERIOD.cycles()).unwrap();
}

// RTIC requires that unused interrupts are declared in an extern block when
// using software tasks; these free interrupts will be used to dispatch the
// software tasks.
extern "C" {
fn LPUART8();
}
};

// If we reach WFI on teensy 4.0 too quickly it seems to halt. Here we wait a short while in `init`
// to avoid this issue. The issue only appears to occur when rebooting the device (via the button),
// however there appears to be no issue when power cycling the device.
//
// TODO: Investigate exactly why this appears to be necessary.
fn init_delay() {
for _ in 0..10_000_000 {
core::sync::atomic::spin_loop_hint();
}
}
38 changes: 38 additions & 0 deletions examples/rtic_led.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Demonstrates how to use `teensy4_bsp` alongside `rtic`.
//!
//! NOTE: This example requires the `rtic` feature to be enabled.
//!
//! `rtic` stands for "Real-Time Interrupt-driven Concurrency". It is a convenient concurrency
//! framework for building real-time systems. If you are unfamiliar with `rtic`, I recommend
//! reading the online book: https://rtic.rs
//!
//! Success criteria: the LED turns on!
#![no_std]
#![no_main]

extern crate panic_halt;

use embedded_hal::digital::v2::OutputPin;
use teensy4_bsp as bsp;

#[rtic::app(device = teensy4_bsp, peripherals = true)]
const APP: () = {
#[init]
fn init(cx: init::Context) {
// Cortex-M peripherals
let _core: cortex_m::Peripherals = cx.core;

// Device-specific peripherals
let mut device: bsp::Peripherals = cx.device;

let mut led = bsp::configure_led(&mut device.gpr, device.pins.p13);
led.set_high().unwrap();
}
#[idle]
fn idle(_: idle::Context) -> ! {
loop {
core::sync::atomic::spin_loop_hint();
}
}
};
153 changes: 153 additions & 0 deletions examples/rtic_uart_log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//! An adaptation of the `rtic_blink.rs` example that demonstrates logging via Teensy 4 UART.
//!
//! This example requires:
//!
//! - The `rtic` feature to be enabled.
//! - a serial to USB converter (tested with CP2102). The converter should be connected to pins 14
//! and 15. Pin 14 is teensy's TX and pin 15 is teensy's RX pin.
//!
//! Success criteria:
//! - The on-board LED should blink once per second.
//! - On each blink, we receive a message from the teensy via the serial console (e.g. `screen`).
//! - When writing serial data from the console, the teensy should log when each call to the
//! interrupt hardware task occurs and prints the characters received as a utf8 string on each
//! blink.
#![no_std]
#![no_main]

use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin};
use embedded_hal::serial::Read;
use heapless::consts::U256;
use panic_halt as _;
use rtic::cyccnt::U32Ext;
use teensy4_bsp as bsp;

const PERIOD: u32 = bsp::hal::ccm::PLL1::ARM_HZ;
const BAUD: u32 = 115_200;
const TX_FIFO_SIZE: u8 = 4;

// Type aliases for the Queue we want to use.
type Ty = u8;
type Cap = U256;
type Queue = heapless::spsc::Queue<Ty, Cap>;
type Producer = heapless::spsc::Producer<'static, Ty, Cap>;
type Consumer = heapless::spsc::Consumer<'static, Ty, Cap>;

// The UART receiver.
type UartRx = bsp::hal::uart::Rx<bsp::hal::iomuxc::uart::module::_2>;

#[rtic::app(device = teensy4_bsp, monotonic = rtic::cyccnt::CYCCNT, peripherals = true)]
const APP: () = {
struct Resources {
led: bsp::LED,
u_rx: UartRx,
q_tx: Producer,
q_rx: Consumer,
}

#[init(schedule = [blink])]
fn init(mut cx: init::Context) -> init::LateResources {
init_delay();

// Setup the clock for rtic scheduling.
cx.device.ccm.set_mode(bsp::hal::ccm::ClockMode::Run);
cx.core.DWT.enable_cycle_counter();
cx.device.ccm.pll1.set_arm_clock(
bsp::hal::ccm::PLL1::ARM_HZ,
&mut cx.device.ccm.handle,
&mut cx.device.dcdc,
);

// UART setup.
let uarts = cx.device.uart.clock(
&mut cx.device.ccm.handle,
bsp::hal::ccm::uart::ClockSelect::OSC,
bsp::hal::ccm::uart::PrescalarSelect::DIVIDE_1,
);
let mut uart = uarts
.uart2
.init(cx.device.pins.p14.alt2(), cx.device.pins.p15.alt2(), BAUD)
.unwrap();
uart.set_tx_fifo(core::num::NonZeroU8::new(TX_FIFO_SIZE));
uart.set_rx_fifo(true);
uart.set_receiver_interrupt(Some(0));
let (u_tx, u_rx) = uart.split();
imxrt_uart_log::blocking::init(u_tx, Default::default()).unwrap();

// The queue used for buffering bytes.
static mut Q: Queue = heapless::spsc::Queue(heapless::i::Queue::new());
let (q_tx, q_rx) = unsafe { Q.split() };

// LED setup.
let mut led = bsp::configure_led(&mut cx.device.gpr, cx.device.pins.p13);
led.set_high().unwrap();

// Schedule the first blink.
cx.schedule.blink(cx.start + PERIOD.cycles()).unwrap();

init::LateResources {
led,
u_rx,
q_tx,
q_rx,
}
}

#[task(resources = [led, q_rx], schedule = [blink])]
fn blink(cx: blink::Context) {
// Log via UART.
static mut TIMES: u32 = 0;
*TIMES += 1;
log::info!(
"`blink` called {} time{}",
*TIMES,
if *TIMES > 1 { "s" } else { "" }
);

// Log all bytes that have been read via UART as a utf8 str.
if cx.resources.q_rx.ready() {
let mut buffer = [0u8; 256];
for elem in buffer.iter_mut() {
*elem = match cx.resources.q_rx.dequeue() {
None => break,
Some(b) => b,
};
}
let s = core::str::from_utf8(&buffer).unwrap();
log::info!("read: {}", s);
}

// Toggle the LED.
cx.resources.led.toggle().unwrap();

// Schedule the following blink.
cx.schedule.blink(cx.scheduled + PERIOD.cycles()).unwrap();
}

#[task(binds = LPUART2, resources = [u_rx, q_tx])]
fn lpuart2(cx: lpuart2::Context) {
log::info!("LPUART2 interrupt task called!");
while let Ok(b) = cx.resources.u_rx.read() {
cx.resources.q_tx.enqueue(b).ok();
}
}

// RTIC requires that unused interrupts are declared in an extern block when
// using software tasks; these free interrupts will be used to dispatch the
// software tasks.
extern "C" {
fn LPUART8();
}
};

// If we reach WFI on teensy 4.0 too quickly it seems to halt. Here we wait a short while in `init`
// to avoid this issue. The issue only appears to occur when rebooting the device (via the button),
// however there appears to be no issue when power cycling the device.
//
// TODO: Investigate exactly why this appears to be necessary.
fn init_delay() {
for _ in 0..10_000_000 {
core::sync::atomic::spin_loop_hint();
}
}
15 changes: 15 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ pub mod usb;
pub use systick::SysTick;

pub use hal::ral::interrupt;
// `rtic` expects these in the root.
#[cfg(feature = "rtic")]
pub use hal::ral::{interrupt as Interrupt, NVIC_PRIO_BITS};

pub use cortex_m_rt as rt;
pub use imxrt_hal as hal;
Expand Down Expand Up @@ -258,6 +261,18 @@ impl Peripherals {
Some(Peripherals::new(p))
}

#[cfg(feature = "rtic")]
/// Steal all of the HAL's peripherals.
///
/// # Safety
///
/// NOTE: This constructor is only intended for use with the `rtic` crate. This is **not** an
/// alternative to the `take` constructor. The `take` constructor sets the system timer
/// interrupt while this constructor does not seeing as `rtic` will take care of this for us.
pub unsafe fn steal() -> Self {
Self::new(hal::Peripherals::steal())
}

fn set_systick(systick: &mut cortex_m::peripheral::SYST) {
systick.disable_counter();
systick.set_clock_source(cortex_m::peripheral::syst::SystClkSource::External);
Expand Down

0 comments on commit 8cf06cf

Please sign in to comment.