Microbit change output dynamically

Hi community!

I've been working on a Microbit v2 project that is a metronome - device that emits sounds periodically. Sound is emitted using PWM and it can be directed to build-in speaker pin (P0_01) or a free GPIO pin (example: P0_00) that I use with a jack plug.

I tried to implement a dynamic switching of the output with the button and encountered some problems related to the typestate design of the HAL lib and ownership. Here's simplified version of the code:


// Speaker is protected with mutex, because its frequency is changed in the interrupts
static SPEAKER: Mutex<RefCell<Option<pwm::Pwm<pac::PWM0>>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
    if let Some(mut board) = Board::take() {
        let button_a = board.buttons.button_a.into_pullup_input();
        let button_b = board.buttons.button_b.into_pullup_input();

        let mut jack_pin = board.pins.p0_02.into_push_pull_output(gpio::Level::Low);
        let mut speaker_pin = board.speaker_pin.into_push_pull_output(gpio::Level::Low);

        let mut out_pin: Option<Pin<Output<PushPull>>> = Some(jack_pin.degrade());
        let speaker = pwm::Pwm::new(board.PWM0);
        speaker
            .set_output_pin(pwm::Channel::C0, speaker_pin.degrade())
            .set_prescaler(pwm::Prescaler::Div16)
            .set_period(Hertz(440u32))
            .set_counter_mode(pwm::CounterMode::UpAndDown)
            .set_max_duty(32767)
            .enable();

        speaker
            .set_seq_refresh(pwm::Seq::Seq0, 0)
            .set_seq_end_delay(pwm::Seq::Seq0, 0);

        cortex_m::interrupt::free(move |cs| {
            *SPEAKER.borrow(cs).borrow_mut() = Some(speaker);

            unsafe {
                pac::NVIC::unmask(pac::Interrupt::RTC0);
            }
            pac::NVIC::unpend(pac::Interrupt::RTC0);
        });

        loop {
            // When both buttons are pressed, switch sound output
            if let (Ok(true), Ok(true)) = (button_a.is_low(), button_b.is_low()) {
                cortex_m::interrupt::free(move |cs| {
                    if let Some(speaker) = SPEAKER.borrow(cs).borrow_mut().as_ref() {
                        if let Some(new_pin) = out_pin {
                            if let Some(old_pin) = speaker.swap_output_pin(pwm::Channel::C0, new_pin.into()) {
                                out_pin = Some(old_pin)
                            }
                        }
                    }
                });
            }
            // Rest of the code handling metronome functions
        }
    }
}

This code has multiple issues, but the one I'm unable to solve is the move of the pin value.

error[E0382]: use of moved value: `out_pin`
   --> src/main.rs:102:43
    |
72  |         let mut out_pin: Option<Pin<Output<PushPull>>> = Some(jack_pin.degrade());
    |             ----------- move occurs because `out_pin` has type `Option<microbit::nrf52833_hal::gpio::Pin<Output<PushPull>>>`, which does not implement the `Copy` trait
...
102 |                 cortex_m::interrupt::free(move |cs| {
    |                                           ^^^^^^^^^
    |                                           |
    |                                           value moved into closure here, in previous iteration of loop
    |                                           value used here after move
103 |                     if let Some(speaker) = SPEAKER.borrow(cs).borrow_mut().as_ref() {
104 |                         if let Some(new_pin) = out_pin {
    |                                                ------- use occurs due to use in closure

I'm aware that the pin is moved into the closure, but I don't have better idea how swap the pin in the speaker. The speaker must be in interrupt-free block closure, because it can be accessed any time, due to interrupt. I also tried wrapping the pin in a mutex, but this doesn't help much.

What's the correct way of dealing with dynamic switch of output pin in PWM? Is there any API that could help me?

Full code is in GitHub - sireliah/metronome: Pet metronome project designed to work on nRF52 chip for the reference.

Have you tried making your closures non-move? It is the cause of out_pin being moved in the previous iteration, and I don't see a reason it is needed.

Hi, yeah, I tried, however the value is still moved:

error[E0382]: use of moved value: `out_pin`
   --> src/main.rs:103:43
    |
72  |         let mut out_pin: Option<Pin<Output<PushPull>>> = Some(jack_pin.degrade());
    |             ----------- move occurs because `out_pin` has type `Option<microbit::nrf52833_hal::gpio::Pin<Output<PushPull>>>`, which does not implement the `Copy` trait
...
103 |                 cortex_m::interrupt::free(|cs| {
    |                                           ^^^^
    |                                           |
    |                                           value moved into closure here, in previous iteration of loop
    |                                           value used here after move
104 |                     if let Some(speaker) = SPEAKER.borrow(cs).borrow_mut().as_mut() {
105 |                         if let Some(new_pin) = out_pin {
    |                                                ------- use occurs due to use in closure

error: aborting due to 2 previous errors; 3 warnings emitted

I think the issue is related to the loop, rather than closure.

I fixed the problem with degraded pin in the mutex:

static OUT_PIN: Mutex<RefCell<Option<Pin<Output<PushPull>>>>> = Mutex::new(RefCell::new(None));
// (...)
*OUT_PIN.borrow(cs).borrow_mut() = Some(jack_pin.degrade());
// (...)

loop {
    if let (Ok(true), Ok(true)) = (button_a.is_low(), button_b.is_low()) {
        cortex_m::interrupt::free(|cs| {
            if let Some(speaker) = SPEAKER.borrow(cs).borrow_mut().as_mut() {
                let inner_pin = OUT_PIN.borrow(cs).borrow_mut().take();
                if let Some(new_pin) = inner_pin {
                    if let Some(old_pin) = speaker.swap_output_pin(pwm::Channel::C0, new_pin) {
                        OUT_PIN.borrow(cs).borrow_mut().replace(old_pin);
                    }
                }
            }
        });
    }
    // (...)

As far as I understand, degraded pin is a generic Pin struct that can be converted to a concrete pin, like P0_00 that I use for jack cable. I put the pin to a Option and protected with a mutex. It looks terrible, but it does the job and it doesn't use any unsafe code.