You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在RP2040的Rust Embassy环境下正确跨核/线程共享外设/GPIO

在embassy-rp的RP2040多核项目中支持任意引脚参数或跨核外设访问的方案

方案一:使用泛型参数实现任意引脚支持

embassy-rp的引脚类型是静态区分的(如PIN_17),但可以通过泛型约束让任务函数接受任意符合要求的引脚。修改core1_task的定义,用泛型替代具体引脚类型:

#[embassy_executor::task]
async fn core1_task(
    led_r_pin: impl embassy_rp::Peripheral<P = impl embassy_rp::gpio::Pin> + 'static,
    led_g_pin: impl embassy_rp::Peripheral<P = impl embassy_rp::gpio::Pin> + 'static,
    led_b_pin: impl embassy_rp::Peripheral<P = impl embassy_rp::gpio::Pin> + 'static,
) {
    let mut led_r = embassy_rp::gpio::Output::new(led_r_pin, embassy_rp::gpio::Level::High);
    let mut led_g = embassy_rp::gpio::Output::new(led_g_pin, embassy_rp::gpio::Level::High);
    let mut led_b = embassy_rp::gpio::Output::new(led_b_pin, embassy_rp::gpio::Level::High);

    // 原LED循环逻辑不变
    loop {
        info!("all off!");
        led_b.set_high();
        led_r.set_high();
        led_g.set_high();
        embassy_rp::time::Timer::after(embassy_rp::time::Duration::from_secs(1)).await;

        info!("blue on!");
        led_b.set_low();
        led_r.set_high();
        led_g.set_high();
        embassy_rp::time::Timer::after(embassy_rp::time::Duration::from_secs(1)).await;
        // ... 其余颜色循环
    }
}

修改后任务可接受任意可配置为输出的GPIO引脚,编译器会自动推断传入的具体引脚类型,无需手动指定PIN_xx。

方案二:使用AnyPin进行类型擦除

如果需要统一参数类型(比如动态选择引脚),可以将具体引脚转换为AnyPin类型,再传递给任务:

1. 在main中转换引脚

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_rp::init(Default::default());
    // 将具体引脚转换为AnyPin的PeripheralRef
    let led_r_pin = embassy_rp::Peripheral::into_ref(p.PIN_17).into_any_pin();
    let led_g_pin = embassy_rp::Peripheral::into_ref(p.PIN_16).into_any_pin();
    let led_b_pin = embassy_rp::Peripheral::into_ref(p.PIN_25).into_any_pin();

    // 启动Core1任务的逻辑不变
    embassy_rp::multicore::spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
        let executor1 = EXECUTOR1.init(embassy_executor::Executor::new());
        executor1.run(|spawner| {
            spawner.spawn(core1_task(led_r_pin, led_g_pin, led_b_pin)).unwrap();
        });
    });

    // ... USB CDC初始化逻辑
}

2. 修改任务参数类型

#[embassy_executor::task]
async fn core1_task(
    led_r_pin: embassy_rp::PeripheralRef<'static, embassy_rp::gpio::AnyPin>,
    led_g_pin: embassy_rp::PeripheralRef<'static, embassy_rp::gpio::AnyPin>,
    led_b_pin: embassy_rp::PeripheralRef<'static, embassy_rp::gpio::AnyPin>,
) {
    let mut led_r = embassy_rp::gpio::Output::new(led_r_pin, embassy_rp::gpio::Level::High);
    let mut led_g = embassy_rp::gpio::Output::new(led_g_pin, embassy_rp::gpio::Level::High);
    let mut led_b = embassy_rp::gpio::Output::new(led_b_pin, embassy_rp::gpio::Level::High);

    // 原LED循环逻辑不变
}

注意:AnyPin是类型擦除后的引脚,使用时需确保该引脚确实支持输出模式,否则会在运行时panic(静态转换时编译器会做基本检查)。

方案三:通过消息传递实现跨核外设控制

如果希望避免跨核直接传递外设所有权,可以采用消息传递的方式:Core1发送控制命令,Core0负责实际操作LED引脚。这种方式更符合嵌入式并发安全规范,也能避免类型匹配问题:

1. 定义命令类型和通信通道

use embassy_sync::channel::{Channel, Sender, Receiver};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;

// 定义LED控制命令
#[derive(Debug, Clone, Copy)]
enum LedCommand {
    AllOff,
    RedOn,
    GreenOn,
    BlueOn,
}

// 静态存储通信通道(容量为4)
static LED_CHANNEL: embassy_sync::StaticCell<Channel<NoopRawMutex, LedCommand, 4>> = embassy_sync::StaticCell::new();

2. 修改Core1任务为发送命令

#[embassy_executor::task]
async fn core1_task(tx: Sender<'static, LedCommand>) {
    loop {
        tx.send(LedCommand::AllOff).await;
        embassy_rp::time::Timer::after(embassy_rp::time::Duration::from_secs(1)).await;

        tx.send(LedCommand::BlueOn).await;
        embassy_rp::time::Timer::after(embassy_rp::time::Duration::from_secs(1)).await;

        tx.send(LedCommand::RedOn).await;
        embassy_rp::time::Timer::after(embassy_rp::time::Duration::from_secs(1)).await;

        tx.send(LedCommand::GreenOn).await;
        embassy_rp::time::Timer::after(embassy_rp::time::Duration::from_secs(1)).await;
    }
}

3. 在Core0中处理命令并控制LED

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_rp::init(Default::default());
    // 初始化通信通道并拆分发送/接收端
    let (tx, rx) = LED_CHANNEL.init(Channel::new()).split();

    // 启动Core1任务,传递发送端
    embassy_rp::multicore::spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
        let executor1 = EXECUTOR1.init(embassy_executor::Executor::new());
        executor1.run(|spawner| {
            spawner.spawn(core1_task(tx)).unwrap();
        });
    });

    // 初始化LED引脚(Core0持有所有权)
    let mut led_r = embassy_rp::gpio::Output::new(p.PIN_17, embassy_rp::gpio::Level::High);
    let mut led_g = embassy_rp::gpio::Output::new(p.PIN_16, embassy_rp::gpio::Level::High);
    let mut led_b = embassy_rp::gpio::Output::new(p.PIN_25, embassy_rp::gpio::Level::High);

    // 循环接收Core1的命令并执行
    loop {
        match rx.recv().await {
            LedCommand::AllOff => {
                led_r.set_high();
                led_g.set_high();
                led_b.set_high();
                info!("all off!");
            }
            LedCommand::RedOn => {
                led_r.set_low();
                led_g.set_high();
                led_b.set_high();
                info!("red on!");
            }
            LedCommand::GreenOn => {
                led_r.set_high();
                led_g.set_low();
                led_b.set_high();
                info!("green on!");
            }
            LedCommand::BlueOn => {
                led_r.set_high();
                led_g.set_high();
                led_b.set_low();
                info!("blue on!");
            }
        }
    }

    // ... USB CDC初始化逻辑
}

方案对比

  • 泛型方案:保留静态类型安全,无额外开销,是最推荐的方式。
  • AnyPin方案:适合需要统一参数类型的场景,但会丢失部分静态类型检查。
  • 消息传递方案:最安全的跨核交互方式,避免外设并发访问问题,适合复杂场景。

内容的提问来源于stack exchange,提问作者sgiessmann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 12:18:09