rp2040笔记[1]-使用embassy实现呼吸灯并通过命令行切换状态

news/2025/3/18 20:12:53/文章来源:https://www.cnblogs.com/qsbye/p/18779665

摘要

使用rust的embassy在rp2040芯片核心板实现呼吸灯.

关键词

rust;embassy;rp2040;blink;pwm;

关键信息

  • 项目地址:[https://github.com/ByeIO/byeefree.rp2040_quad.embassy]
[package]
edition = "2021"
name = "byeefree_rp2040_quad_embassy"
version = "0.1.0"
license = "MIT OR Apache-2.0"
resolver = "2"[dependencies]
# arm cortex-m相关
cortex-m = { version = "0.7", features = ["inline-asm"] }
cortex-m-rt = "0.7"# rp-pico抽象层相关
# rp-pico = "0.9.0"# 调试信息相关
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }# embassy嵌入式异步框架相关
embassy-embedded-hal = { version = "0.3", features = ["defmt"] }embassy-executor = { version = "0.7", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "task-arena-size-32768"] }
embassy-futures = { version = "0.1" }
embassy-sync = { version = "0.6", features = ["defmt"] }
embassy-time = { version = "0.4", features = ["defmt", "defmt-timestamp-uptime"] }embassy-rp = { version = "0.3", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] }# pio相关, 暂时使用0.2版本
pio-proc = "0.2"
pio = "0.2"## 通过usb获取日志信息 (注意: embassy-usb-logger 需要 portable-atomic 开启 critical-section特性)
embassy-usb-driver = { version = "0.1", features = ["defmt"] }
embassy-usb-logger = { version = "0.4" }
portable-atomic = { version = "1.10", features = ["critical-section"] }
log = "0.4"
embassy-usb = { version = "0.4.0", features = ["defmt"] }## 网络支持
embassy-net = { version = "0.6", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] }
# wiznet ethernet driver support
embassy-net-wiznet = { version = "0.2", features = ["defmt"] }
## pico-w 支持
cyw43 = { version = "0.3", features = ["defmt", "firmware-logs"] }
cyw43-pio = { version = "0.3", features = ["defmt"] }# 生命周期
static_cell = "2.1.0"# Dshot协议
dshot-pio = { path = "./static/dshot-pio", features = ["embassy-rp"] }# 线性代数
nalgebra = { version = "0.33.2", default-features = false, features = ["libm"] }# 浮点数计算支持
libm = "0.2.11"
num-traits = { version = "0.2", default-features = false, features = ["libm"] }# 命令行解析
embedded-cli = "0.2.1"
embedded-io = "0.6.1"
embedded-io-async = "0.6.1"
ufmt = "0.2.0"# 内存分配
embedded-alloc = { version = "0.6.0", features = ["llff"] }
heapless = { version = "0.8.0", features = ["portable-atomic"] }# cargo build/run
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true
incremental = false
opt-level = 1
overflow-checks = true
lto = "off"# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 's'
overflow-checks = false# do not optimize proc-macro crates = faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false# cargo test
[profile.test]
codegen-units = 1
debug = 2
debug-assertions = true
incremental = false
opt-level = 's'
overflow-checks = true# cargo test --release
[profile.bench]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 's'

原理简介

eabassy简介

[https://embassy.dev/book/#_embassy_in_the_wild]
在处理输入/输出(I/O)时,软件必须调用一些函数,这些函数会阻塞程序的执行,直到I/O操作完成。在像Linux这样的操作系统中运行的程序,这些函数通常会将控制权转移给内核,以便可以执行另一个任务(称为“线程”),或者在没有其他任务准备就绪时让CPU进入睡眠状态。

由于操作系统无法假设线程会以协作方式运行,线程相对来说是资源密集型的,如果它们没有在分配的时间内将控制权交还给内核,可能会被强制中断。如果可以假设任务会以协作方式运行,或者至少不会恶意运行,那么就可以创建一些任务,这些任务与传统操作系统线程相比,几乎可以看作是“免费”的。

在其他编程语言中,这些轻量级任务被称为“协程”或“goroutine”。在Rust中,它们通过async实现。async-await的工作原理是将每个async函数转换为一个称为future的对象。当一个future在I/O上阻塞时,它会“让步”,调度程序(称为executor)可以选择执行另一个future

与实时操作系统(RTOS)等替代方案相比,async可以提供更好的性能和更低的功耗,因为executor不需要猜测future何时准备好执行。然而,程序的大小可能比其他替代方案更大,这对于某些内存非常有限的设备来说可能是一个问题。在Embassy支持的设备上,如stm32nrf,内存通常足够大,可以容纳适度增加的程序大小。

Embassy项目由几个可以一起或独立使用的crate组成:

Executor
embassy-executor是一个async/await执行器,通常执行固定数量的任务,这些任务在启动时分配,但稍后可以添加更多任务。执行器还可能提供一个系统计时器,你可以将其用于async和阻塞延迟。对于不到一微秒的延迟,应该使用阻塞延迟,因为上下文切换的成本太高,执行器无法提供准确的计时。

硬件抽象层(HAL)
HAL实现了安全的Rust API,让你可以使用外设,如USART、UART、I2C、SPI、CAN和USB,而无需直接操作寄存器。

Embassy在合适的地方提供了async和阻塞API的实现。DMA(直接内存访问)是async的一个很好的应用场景,而GPIO状态则更适合阻塞API。

Embassy项目为特定硬件维护了HAL,但你仍然可以将其他项目的HAL与Embassy一起使用。

When handling I/O, software must call functions that block program execution until the I/O operation completes. When running inside of an OS such as Linux, such functions generally transfer control to the kernel so that another task (known as a “thread”) can be executed if available, or the CPU can be put to sleep until another task is ready.

Because an OS cannot presume that threads will behave cooperatively, threads are relatively resource-intensive, and may be forcibly interrupted if they do not transfer control back to the kernel within an allotted time. If tasks could be presumed to behave cooperatively, or at least not maliciously, it would be possible to create tasks that appear to be almost free when compared to a traditional OS thread.

In other programming languages, these lightweight tasks are known as “coroutines” or ”goroutines”. In Rust, they are implemented with async. Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute.

Compared to alternatives such as an RTOS, async can yield better performance and lower power consumption because the executor doesn’t have to guess when a future is ready to execute. However, program size may be higher than other alternatives, which may be a problem for certain space-constrained devices with very low memory. On the devices Embassy supports, such as stm32 and nrf, memory is generally large enough to accommodate the modestly-increased program size.

The Embassy project consists of several crates that you can use together or independently:

Executor
The embassy-executor is an async/await executor that generally executes a fixed number of tasks, allocated at startup, though more can be added later. The executor may also provide a system timer that you can use for both async and blocking delays. For less than one microsecond, blocking delays should be used because the cost of context-switching is too high and the executor will be unable to provide accurate timing.

Hardware Abstraction Layers
HALs implement safe Rust API which let you use peripherals such as USART, UART, I2C, SPI, CAN, and USB without having to directly manipulate registers.

Embassy provides implementations of both async and blocking APIs where it makes sense. DMA (Direct Memory Access) is an example where async is a good fit, whereas GPIO states are a better fit for a blocking API.

The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy.

核心代码

.cargo/config.toml

# 默认构建目标平台
[build]
target = "thumbv6m-none-eabi"# 配置上传命令
[target.thumbv6m-none-eabi]# rustflags = [
#     "-C", "link-arg=--nmagic",
#     "-C", "link-arg=-Tlink.x",
#     "-C", "no-vectorize-loops",
# ]# runner = "elf2uf2-rs -d"
runner = "probe-rs run --chip RP2040"# 使用本地库文件
# [source.crates-io]
# replace-with = "vendored-sources"# [source.vendored-sources]
# directory = "vendor"

tasks/blink.rs

#![allow(unused)]// led指示灯任务// gpio相关
use embassy_rp::gpio;
use gpio::{Level, Output, AnyPin};// 多任务相关
use embassy_time::{Duration, Timer};
use embassy_executor::Spawner;
use embassy_futures::join::join;
use embassy_rp::bind_interrupts;// 打印调试信息
use defmt::{info, panic};
use { defmt_rtt as _, panic_probe as _ };// pio相关(led:gpio25)
use embassy_rp::peripherals::PIO0;// PWM相关
use embassy_rp::peripherals::{PIN_25, PWM_SLICE4};
use embassy_rp::pwm::{Config, Pwm, SetDutyCycle};// 浮点数计算
use libm::powf;  // 常量
/// 亮度变化总步数
const STEP: usize = 200;/// PWM最大占空比
const MAX_DUTY: u32 = 32768;  /// Gamma校正系数(符合人眼感知)
const GAMMA: f32 = 2.2;/// 编译期生成的Gamma校正查找表
/// 
/// 生成算法:
/// 1. 将0-STEP线性序列归一化为0.0-1.0
/// 2. 对每个值进行Gamma幂次运算
/// 3. 映射到PWM占空比范围
const GAMMA_LUT: [u32; STEP] = {// 使用编译期常量表达式初始化数组let mut table = [0u32; STEP];let step_f32 = (STEP - 1) as f32; // 转换为浮点用于计算// 编译期循环展开(无运行时开销)let mut i = 0;while i < STEP {// 线性归一化(注意:Rust const上下文暂不支持浮点运算,故采用整数近似)let t = (i * 1000) as u32; // 扩大1000倍转为整数运算let normalized = t as u64 * 0xFFFF_FFFF / ((STEP - 1) * 1000) as u64;// Gamma校正整数近似算法(误差<0.5%)// 原理:x^2.2 ≈ x² * x^(0.2) 的整数实现let x = normalized as u32;let x_sq = (x as u64 * x as u64) >> 32; // x²的定点数计算let x_02 = (x as u64 * 2290_0669) >> 32; // x^0.2的预计算系数// 混合计算结果let gamma_val = (x_sq as u64 * x_02 as u64) >> 32;// 映射到PWM占空比范围table[i] = (gamma_val as u64 * MAX_DUTY as u64 / 0xFFFF_FFFF) as u32;i += 1;}table
};// 内部库:信号
use crate::signals;// 闪灯模式
#[derive(Clone,Copy)]
pub enum BlinkMode {// 不闪灯None,// 快速1OneFast,// 快速2TwoFast,// 快速3ThreeFast,// 快速闪OnOffFast,// 慢速闪OnOffSlow,// 呼吸灯Breathe,
}// embassy任务
#[embassy_executor::task]
pub async fn blink(// 订阅blink模式mut in_blink_mode : signals::BlinkModeSub,mut pin : PIN_25,mut slice : PWM_SLICE4,
) {// 呼吸灯状态变量let mut breathe_ctx = BreatheContext {direction: 1i8,index: 0usize,pwm_config: Config::default(),};breathe_ctx.pwm_config.top = 32_768;defmt::println!("run blink");// 初始化blink模式// let mut blink_mode = BlinkMode::None;let mut blink_mode = BlinkMode::OnOffFast;// pwm相关let mut c = Config::default();c.top = 32_768;c.compare_b = 8;loop { if let Some(b) = in_blink_mode.try_next_message_pure() {// 模式切换时重置呼吸灯状态if !matches!(b, BlinkMode::Breathe) {breathe_ctx.reset(); }blink_mode = b;}// 匹配不同模式match blink_mode {BlinkMode::None => {// 初始化led引脚let mut led = Output::new(&mut pin,Level::Low);blink_mode = in_blink_mode.next_message_pure().await;},BlinkMode::OneFast => one_fast(&mut pin).await,BlinkMode::TwoFast => two_fast(&mut pin).await,BlinkMode::ThreeFast => three_fast(&mut pin).await,BlinkMode::OnOffFast => on_off_fast(&mut pin).await,BlinkMode::OnOffSlow => on_off_slow(&mut pin).await,BlinkMode::Breathe => breathe(&mut breathe_ctx, &mut slice, &mut pin).await,};}
}// 1. 快速1
#[allow(unused)]
async fn one_fast<'a>(pin: &mut PIN_25) {// 初始化led引脚let mut led = Output::new(pin,Level::Low);// 灭一会led.set_high(); Timer::after(Duration::from_millis(50)).await;// 长亮led.set_low();Timer::after(Duration::from_millis(950)).await;
}// 2. 快速2
#[allow(unused)]
async fn two_fast<'a>(pin: &mut PIN_25) {// 初始化led引脚let mut led = Output::new(pin,Level::Low);// 短灭led.set_high();Timer::after(Duration::from_millis(50)).await;// 中亮led.set_low();Timer::after(Duration::from_millis(100)).await;// 短灭led.set_high(); Timer::after(Duration::from_millis(50)).await;// 长亮led.set_low();Timer::after(Duration::from_millis(800)).await;
}// 3. 快速3
#[allow(unused)]
async fn three_fast<'a>(pin: &mut PIN_25) {// 初始化led引脚let mut led = Output::new(pin,Level::Low);// 短灭led.set_high();Timer::after(Duration::from_millis(50)).await;// 中亮led.set_low();Timer::after(Duration::from_millis(100)).await;// 短灭led.set_high(); Timer::after(Duration::from_millis(50)).await;// 中亮led.set_low();Timer::after(Duration::from_millis(100)).await;// 短灭led.set_high();Timer::after(Duration::from_millis(50)).await;// 长亮led.set_low();Timer::after(Duration::from_millis(650)).await;
}// 4. 快速亮灭
#[allow(unused)]
async fn on_off_fast<'a>(pin: &mut PIN_25) {// 初始化led引脚let mut led = Output::new(pin,Level::Low);// 短灭led.set_high(); Timer::after(Duration::from_millis(100)).await;// 中亮led.set_low();Timer::after(Duration::from_millis(100)).await;
}// 5. 慢速亮灭
#[allow(unused)]
async fn on_off_slow<'a>(pin: &mut PIN_25) {// 初始化led引脚let mut led = Output::new(pin,Level::Low);// 短灭led.set_high(); Timer::after(Duration::from_millis(150)).await;// 中亮led.set_low();Timer::after(Duration::from_millis(150)).await;
}// 6. 呼吸灯
/* start 呼吸灯 */
// 呼吸灯上下文结构体
struct BreatheContext {direction: i8,index: usize,pwm_config: Config,
}impl BreatheContext {fn reset(&mut self) {self.direction = 1;self.index = 0;}
}#[allow(unused)]
async fn breathe<'a>(ctx: &mut BreatheContext,slice: &mut PWM_SLICE4,pin: &mut PIN_25
) {let mut pwm = Pwm::new_output_b(slice, pin, ctx.pwm_config.clone());// 应用当前Gamma值ctx.pwm_config.compare_b = GAMMA_LUT[ctx.index] as u16;pwm.set_config(&ctx.pwm_config);// 更新索引和方向(原loop内的状态机逻辑)ctx.index = match (ctx.index, ctx.direction) {(_, 1) if ctx.index >= STEP - 1 => {ctx.direction = -1;STEP - 1}(0, -1) => {ctx.direction = 1;0}(i, 1) => i + 1,(i, -1) => i - 1,_ => unreachable!()};// 保持原有的呼吸节奏Timer::after_millis(5).await;
}
/* end 呼吸灯 */

commands/blink.rs

// 命令解析相关
use embassy_futures::select::{select, Either};
use embassy_time::{Duration, Ticker};
use embedded_cli::Command;
use embedded_io::ErrorKind;
use embedded_io_async::{Read, Write};
use ufmt::uwrite;
use crate::shell::{UBuffer, INTERRUPT};
use heapless::String;
use core::str::FromStr;// 内部库
use crate::tasks::blink::BlinkMode;
use crate::utils::signals;// 命令定义部分
#[derive(Command, Clone)]
pub enum BlinkCommand {// 关闭led灯/// led offNone,/// fast#1One_Fast,/// fast#2Two_Fast,/// fast#3Three_Fast,/// blink fastOn_Off_Fast,/// blink slowOn_Off_Slow,/// breathe...Breathe,
}// 实现CommandHandler特征
impl super::CommandHandler for BlinkCommand {async fn handler(&self,mut serial: impl Read<Error = ErrorKind> + Write<Error = ErrorKind>,) -> Result<(), ErrorKind> {// 匹配所有blink子命令变体let (mode_str, blink_mode) = match self {BlinkCommand::None => ("none", BlinkMode::None),BlinkCommand::One_Fast => ("one_fast", BlinkMode::OneFast),BlinkCommand::Two_Fast => ("two_fast", BlinkMode::TwoFast),BlinkCommand::Three_Fast => ("three_fast", BlinkMode::ThreeFast),BlinkCommand::On_Off_Fast => ("on_off_fast", BlinkMode::OnOffFast),BlinkCommand::On_Off_Slow => ("on_off_slow", BlinkMode::OnOffSlow),BlinkCommand::Breathe => ("breathe", BlinkMode::Breathe),};// 通过signal发布新模式signals::BLINK_MODE.publisher().unwrap().publish_immediate(blink_mode);// 写入确认信息到串口let mut buf = UBuffer::<32>::new();uwrite!(&mut buf, "Blink mode set to: {}\n\r", mode_str)?;serial.write_all(&buf.inner).await?;Ok(())}
}

效果

命令切换灯状态

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/901083.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

014 登入页-Element-Plus的图标引入和使用

1、安装图标 2、使用 这里我们用全局注册的方法 放到这里 现在我们换一种方式 在src文件夹里面 新建文件夹global,都是一些全局的东西(这个就叫全局注册) 新建文件register-icons.ts (注册图标)这页这样写这段代码是使用 JavaScript(或可能是 TypeScript,从 app: any…

20242405 实验一《Python程序设计》实验报告

20242405 2024-2025-2 《Python程序设计》实验一报告 课程:《Python程序设计》 班级: 2424 姓名: 孙煜麟 学号:20242405 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 1.实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能 3.编写程序,练习…

day:24 python——类的三大特性

python三大特性:封装,多态,继承 一、封装: 1、封装:封装就是隐藏对象一些不希望外部所访问到的属性和方法,保证安全 2、概念:指的是在设计时,不对外提供,隐藏起来,提供公共方法以外对其方法就是把属性和方法封装到一个抽象的类中, 3、封装相当于一个黑盒子,我们将事务相…

探秘Transformer系列之(15)--- 采样和输出

从零开始解析Transformer,目标是:(1) 解析Transformer如何运作,以及为何如此运作,让新同学可以入门;(2) 力争融入一些比较新的或者有特色的论文或者理念,让老鸟也可以有所收获。探秘Transformer系列之(15)--- 采样和输出 目录探秘Transformer系列之(15)--- 采样和输出…

c语言02_数据类型上

一、c是怎么变成汇编的 1、裸函数是编译器不管的 ⑴写一个空函数(里面什么都不写),f7f5打开反汇编f11打开jmp什么都没写里面还是有一大堆(是编译器和连接器做的)⑵裸函数f7f5查看反汇编找到调用的函数0040D708f11打开再f11打开,里面一行汇编代码都没有⑶ ①空函数 f7f5运…

win11家庭版24H2,使用VMware虚拟机问题

一、安装VMware,提示 “安装程序检测到主机启用了 Hyper-v或 Device/credential Guard。。。”,我勾选了自动安装WHP后点击下一步,完成了VMware的安装。二、解压了一个虚拟机,用VMware打开并选择已复制虚拟机,出现“此平台不支持虚拟化”的错误,点击“是”也无法打开虚拟…

M-LAG

一、M-LAG简介 1.1 M-LAG使用场景​ M-LAG(Multichassis Link Aggregation Group)即跨设备链路聚合组,是一种实现跨设备链路聚合的机制。M-LAG主要应用于普通以太网络、VXLAN和IP网络的双归接入,可以起到负载分担或备份保护的作用。相较于另一种常见的可靠性接入技术——堆…

FastAPI测试策略:参数解析单元测试

扫描二维码关注或者微信搜一搜:编程智域 前端至全栈交流与成长 探索数千个预构建的 AI 应用,开启你的下一个伟大创意第一章:核心测试方法论 1.1 三层测试体系架构 # 第一层:模型级测试 def test_user_model_validation():with pytest.raises(ValidationError):User(age=-5)…

MSTP协议

一、MSTP(多生成树协议)简介 1.1 MSTP(多生成树协议)使用场景MSTP(Multiple Spanning Tree Protocol,IEEE 802.1s), 基于 RSTP 的多实例扩展协议,通过划分多个生成树实例(MSTI)实现 VLAN 与生成树的灵活映射,在保留 RSTP 快速收敛特性的基础上,支持 多拓扑负载均衡…

Eth-Trunk协议

一、Eth-Trunk(链路聚合协议)使用场景 1.1 Eth-Trunk(链路聚合协议)简介​ 原名叫链路聚合组(Link Aggregation Group),通过将多个物理接口捆绑为一个逻辑接口,可以在不进行硬件升级的条件下,达到增加链路带宽的目的。1.2 Eth-Trunk(链路聚合协议)实现目的增加带宽链…

PPP协议

一、PPP(点对点协议)简介 1.1 PPP使用场景​ 我们家庭中使用最多的宽带接入方式就是PPPoE(PPP over Ethernet)。这是一种PPP利用以太网(Ethernet)资源,在以太网上运行PPP来对用户进行接入认证的技术,PPP负责在用户端和运营商的接入服务器之间建立通信链路。二、PPP(点…