I am struggling to wrap my head around generic types and possibly some questions about how best to structure my code.
I have a pretty simple embassy-rs project that consists of the main task and a second task. Below is a simplified version of the code. This PoC is meant to monitor an accelerometer and signal via led/sound depending on weather sufficient movement has been detected.
It seemed prudent to not put all code in main loop so I created a task dedicated to handling the blink_task
function. Eventually I plan on moving all of the accelerometer/filter code into it's own task but I wanted to start with the simpler of the two tasks and got stuck trying to break things out.
My Questions
- Is there a better guideline / rule of thumb for how much peripheral setup should be done and where?
- This pattern seems interesting but doesn't seem wide spread.
- What can I do to adjust the signature for
blink_task
so thepwm
argument can be generic? Right now, the type expression explicitly declares PWM0: (SequencePwm<'static, embassy_nrf::peripherals::PWM0>
) but I'm having one hell of a time trying to figure out the syntax + trait/lifetime constraints needed to make this generic to any peripheral that implementsSequencePwm
. I've tried several different arrangements but always get back some rather verbose/intimidating compiler errors.
One such error:
the trait bound `(dyn embassy_nrf::pwm::Instance + 'static): Peripheral` is not satisfied
the following other types implement trait `Peripheral`:
AnyPin
AnyChannel
AnyStaticChannel
AnyConfigurableChannel
AnyGroup
AnyInput
VddInput
embassy_nrf::peripherals::RTC0
and 93 othersrustcClick for full compiler diagnostic
pwm.rs(27, 31): required by a bound in `SequencePwm`
Which happens when I try this signature:
#[embassy_executor::task]
async fn blink_task(
led_1: AnyPin,
receiver: Receiver<'static, NoopRawMutex, MotionState, 1>,
// Works, explicit:
// mut pwm: SequencePwm<'static, embassy_nrf::peripherals::PWM0>,
// Produces Error(s) depending on syntax variation(s) used:
mut pwm: SequencePwm<'static, embassy_nrf::pwm::Instance>,
) {
// ...
}
Which is a bit confusing to me as I did manage to track the peripheral set up to this macro:
macro_rules! impl_pwm {
($type:ident, $pac_type:ident, $irq:ident) => {
impl crate::pwm::SealedInstance for peripherals::$type {
fn regs() -> &'static pac::pwm0::RegisterBlock {
unsafe { &*pac::$pac_type::ptr() }
}
}
impl crate::pwm::Instance for peripherals::$type {
type Interrupt = crate::interrupt::typelevel::$irq;
}
};
}
Which seems to imply that pwm::Instance
is correct?
PoC Code:
#![no_std]
#![no_main]
use defmt::{info, unwrap};
use embassy_executor::Spawner;
use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin};
use embassy_nrf::pwm::{Prescaler, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer};
use embassy_nrf::{bind_interrupts, peripherals, spim};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::channel::{Channel, Receiver};
use embassy_time::Timer;
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
SPIM0_SPIS0_SPI0 => spim::InterruptHandler<peripherals::SPI0>;
});
enum MotionState {
Still,
Moving,
}
static CHANNEL: StaticCell<Channel<NoopRawMutex, MotionState, 1>> = StaticCell::new();
#[embassy_executor::task]
async fn blink_task(
led_1: AnyPin,
receiver: Receiver<'static, NoopRawMutex, MotionState, 1>,
mut pwm: SequencePwm<'static, embassy_nrf::peripherals::PWM0>,
) {
info!("beep_task: init!");
let mut led_1 = Output::new(led_1, Level::Low, OutputDrive::Standard);
// Set up sequence engine; NOTE: configuration omitted here for brevity
let seq_config = SequenceConfig::default();
// Last step is 0, silent, effectively. This is a cheap way to insert a gap so loops are more distinct with seq_config delay config (omitted here)
let seq_words: [u16; 3] = [800, 100, 0];
let sequencer = SingleSequencer::new(&mut pwm, &seq_words, seq_config);
loop {
match receiver.receive().await {
MotionState::Still => {
led_1.set_high();
sequencer.start(SingleSequenceMode::Times(2)).unwrap();
}
MotionState::Moving => {
led_1.set_low();
sequencer.stop();
}
}
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
info!("main: Creating channel...");
let channel = CHANNEL.init(Channel::new());
info!("main: Configure GPIO...");
let p = embassy_nrf::init(Default::default());
info!("main: Configuring SPI...");
// Omitted for brevity: setting up some GPIO pins, passing them into SPI peripheral and passing the peripheral into SPI driver
// Omitted for brevity: a bunch of register writes to set up the accelerometer / SPI peripheral
// The goal here is to do just enough setup on pwm peripheral before passing it on to the task that will use it
info!("main: setting up PWM...");
let mut pwm_config = embassy_nrf::pwm::Config::default();
pwm_config.prescaler = Prescaler::Div4;
let pwm: SequencePwm<'_, peripherals::PWM0> = SequencePwm::new_1ch(p.PWM0, p.P0_07, pwm_config).unwrap();
// Omitted for brevity: setting up a low pass filter on the SPI accelerometer inputs
info!("main: spawning blink task...");
unwrap!(spawner.spawn(blink_task(p.P0_08.degrade(), channel.receiver(), pwm)));
loop {
// Omitted for brevity: sample the accelerometer and determine if the device is moving or still
// Placeholder for actual logic
let is_currently_moving = false;
if is_currently_moving {
info!("movement!");
channel.send(MotionState::Moving).await;
} else {
info!("still!");
channel.send(MotionState::Still).await;
}
// Wait before we sample again
// TODO: confirm we enter low power mode here
// TODO: see if we can put the accelerometer to sleep?
Timer::after_millis(1000).await;
}
}