如何在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
相关产品推荐
相关产品推荐

