任务之间共享外设
通常,多个任务需要访问相同的资源(引脚、通信接口等)。Embassy 在 embassy-sync crate 中提供了许多不同的同步原语。
以下示例展示了两个任务同时使用 Raspberry Pi Pico 板载 LED 的不同方法。
使用互斥锁 (Mutex) 共享
使用互斥是共享外设最简单的方法。
提示:运行此示例所需的依赖项可以在 这里 找到。
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::gpio;
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Ticker};
use gpio::{AnyPin, Level, Output};
use {defmt_rtt as _, panic_probe as _};
type LedType = Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>>;
static LED: LedType = Mutex::new(None);
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
// set the content of the global LED reference to the real LED pin
let led = Output::new(AnyPin::from(p.PIN_25), Level::High);
// inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the
// Mutex is released
{
*(LED.lock().await) = Some(led);
}
let dt = 100 * 1_000_000;
let k = 1.003;
unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos(dt))));
unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos((dt as f64 * k) as u64))));
}
// A pool size of 2 means you can spawn two instances of this task.
#[embassy_executor::task(pool_size = 2)]
async fn toggle_led(led: &'static LedType, delay: Duration) {
let mut ticker = Ticker::every(delay);
loop {
{
let mut led_unlocked = led.lock().await;
if let Some(pin_ref) = led_unlocked.as_mut() {
pin_ref.toggle();
}
}
ticker.next().await;
}
}
定义 LedType
的结构有助于访问资源。
为什么这么复杂
解开这些层层结构,可以深入了解为什么每个层都是必要的。
Mutex<RawMutexType, T>
互斥锁的存在是为了防止当一个任务首先获得资源并开始修改它时,所有其他想要写入的任务都必须等待(如果没有任何任务锁定互斥锁,led.lock().await
将立即返回;如果互斥锁在其他地方被访问,则会阻塞)。
Option<T>
LED
变量需要在 main 任务之外定义,因为任务接受的引用需要是 'static
的。但是,如果它在 main 任务之外,则无法初始化以指向任何引脚,因为引脚本身尚未初始化。因此,它被设置为 None
。
Output<AnyPin>
指示引脚将设置为输出 (Output
)。AnyPin
本可以是 embassy_rp::peripherals::PIN_25
,但此选项使 toggle_led
函数更通用。
使用通道 (Channel) 共享
通道是另一种确保对资源进行独占访问的方法。在访问可以在稍后的时间点发生的情况下,使用通道非常棒,它允许你将操作排队并执行其他操作。
提示:运行此示例所需的依赖项可以在 这里 找到。
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::gpio;
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::channel::{Channel, Sender};
use embassy_time::{Duration, Ticker};
use gpio::{AnyPin, Level, Output};
use {defmt_rtt as _, panic_probe as _};
enum LedState {
Toggle,
}
static CHANNEL: Channel<ThreadModeRawMutex, LedState, 64> = Channel::new();
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High);
let dt = 100 * 1_000_000;
let k = 1.003;
unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt))));
unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos((dt as f64 * k) as u64))));
loop {
match CHANNEL.receive().await {
LedState::Toggle => led.toggle(),
}
}
}
// A pool size of 2 means you can spawn two instances of this task.
#[embassy_executor::task(pool_size = 2)]
async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) {
let mut ticker = Ticker::every(delay);
loop {
control.send(LedState::Toggle).await;
ticker.next().await;
}
}
此示例用通道替换了互斥锁,并使用另一个任务(主循环)来驱动 LED。这种方法的优点是只有一个任务引用外围设备,从而分离了关注点。但是,使用互斥锁的开销较低,如果需要确保在继续执行任务中的其他工作之前完成操作,则可能是必要的。
此处 可以找到一个展示更多共享方法的示例。
在多个设备之间共享 I2C 或 SPI 总线
此处 可以找到一个关于如何处理多个设备共享公共 I2C 或 SPI 总线的示例。