news 2026/9/24 13:37:16

embassy-nxp 0.1.0 变更深度解析:LPC55 阻塞 SPI、异步 USART 与 nxp-pac 代码生成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
embassy-nxp 0.1.0 变更深度解析:LPC55 阻塞 SPI、异步 USART 与 nxp-pac 代码生成
  • 嵌入式
  • 物联网
  • 异步编程

【免费下载链接】embassy

Modern embedded framework, using Rust and async.

项目地址:https://gitcode.com/gh_mirrors/em/embassy
点击查看免费下载

导读

本文以 embassy-nxp/CHANGELOG.md 为骨架,逐条解读 embassy-nxp 首个带 changelog 的版本(0.1.0)中针对 NXP LPC55 系列芯片落地的一系列 HAL 能力:阻塞版 SPI 驱动、基于 DMA 的异步 USART、简单 PWM 输出、以及底层从lpc55-pac迁移到统一的nxp-pac元数据代码生成机制。通过结合 embassy-nxp/src 下的真实实现,你将掌握 LPC55S69 等芯片上 Flexcomm 外设(USART/SPI)的驱动结构、DMA 异步收发的底层调用链,以及 embassy 系 HAL 如何借助nxp-pac元数据在构建期自动生成外设与引脚代码。

一、版本与变更背景

embassy-nxp 是 Embassy 异步嵌入式框架中面向 NXP 芯片的 HAL crate,当前版本为0.1.0(见 embassy-nxp/Cargo.toml),许可证为 MIT OR Apache-2.0,publish = false,属于仓库内的工作区 crate。它通过package.metadata.embassy定义了针对thumbv8m.main-none-eabihf(LPC55 系列)与thumbv7em-none-eabihf(i.MX RT 系列)的构建矩阵:

[package.metadata.embassy] build = [ {target = "thumbv8m.main-none-eabihf", features = ["defmt", "lpc55-core0"]}, {target = "thumbv8m.main-none-eabihf", features = ["defmt", "lpc55s16"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mimxrt1011", "rt", "time-driver-pit"]}, {target = "thumbv7em-none-eabihf", features = ["defmt", "mimxrt1062", "rt", "time-driver-pit"]}, ]

从 changelog 的条目来看,这一版本的核心工作集中在 LPC55 平台:补齐阻塞 SPI、引入 DMA 与异步 USART、落地简单 PWM,并把整个 PAC 依赖切换为统一的nxp-pac。以下逐条展开。

二、LPC55:阻塞版 SPI 驱动

变更条目LPC55: blocking version of SPI

在 embassy-nxp/src/spi.rs 中,SPI 模块通过#[cfg_attr(lpc55, path = "./spi/lpc55.rs")]按芯片选择实现文件,LPC55 的实际实现位于 embassy-nxp/src/spi/lpc55.rs。

2.1 配置结构

驱动提供Config结构体,包含频率、相位、极性、数据位序四项:

pub struct Config { pub frequency: u32, // 时钟频率,默认 1_000_000(1 MHz) pub phase: Phase, // 默认 CaptureOnFirstTransition pub polarity: Polarity, // 默认 IdleLow pub data_format: DataFormat, // MsbFirst / LsbFirst,默认 MsbFirst }

phasepolarity直接复用embedded_hal_02::spiPhasePolarity枚举,DataFormat则由 HAL 自行定义,最终映射到 SPI 寄存器的LSBF位(spi::vals::Lsbf::Reverse/Standard)。

2.2 阻塞 API 集合

阻塞模式通过Spi<'d, Blocking>提供四组核心方法:

  • blocking_write:只写,逐字节等待txnotfull,写入fifowr寄存器后flush
  • blocking_read:只读,写入哑数据(txdata = 0)同时读取fiford
  • blocking_transfer:半双工读写,以read.len().max(write.len())为循环上界;
  • blocking_transfer_in_place:原地读写。

所有方法都会在写入 FIFO 前轮询txnotfull,并在返回前检查txerr/rxerr,出错时返回Error::Overrun。此外还实现了embedded_hal_02::blocking::spi::Transfer<u8>Write<u8>trait,保证与既有 embedded-hal 生态驱动兼容。

构造函数方面提供三种模式:

  • new_blocking:全双工(SCK + MOSI + MISO);
  • new_blocking_txonly:仅发送(SCK + MOSI);
  • new_blocking_rxonly:仅接收(SCK + MISO)。

若 MOSI 与 MISO 均未提供,构造函数会返回ConstructorError::NoTransferringPinsError;频率超出可配置范围时返回IncompatibleFrequencyError

2.3 底层时钟链:整数分频 + 分数分频两级"雕琢"

SPI 的时钟配置是这段代码中比较有代表性的实现。LPC55 的 Flexcomm 接口函数时钟公式为:

FCLK = (FCCLKSEL 选择的时钟) / (1 + MULT / DIV) 最终频率 = FCLK / (DIVVAL + 1)

源码(embassy-nxp/src/spi/lpc55.rs 的configure_clock)固定选择 96 MHz 作为源时钟(FcclkselSel::Enum0x3),先计算整数分频div_val = (96_000_000 / frequency).min(0xFFFF),再计算分数分频mult_val = ((raw_clock * 256 / frequency) - 256).min(255),其中DIV固定写0xFF(即分母 256)。注释明确给出了理论可达范围:最低约 732 Hz(96 MHz / 131_072)。这种"先整数分频、再分数微调"的两级设计,是为了让任意目标频率都能被较高精度的MULT/DIV分数发生器"雕琢"出来。

2.4 Flexcomm 外设选择与引脚绑定

LPC55 的 Flexcomm 外设是"多面手",同一 IP 可通过PERSEL位选择工作为 USART、SPI 或 I2C。configure_flexcomm完成三步:

  1. 使能SYSCON中 IOCON 与对应 Flexcomm 实例的 AHB 时钟;
  2. 通过presetctrl1对外设做一次"断言复位再释放";
  3. pselid.persel置为Spiset_lock(true)——锁定位一旦置位,直到整板复位前都无法再更改外设模式。

引脚绑定采用"实例 trait + 引脚 trait"的双重约束:Spi<'d, Blocking>的构造函数要求sck: impl SckPin<T>mosi: impl MosiPin<T>miso: impl MisoPin<T>。这些 trait 由impl_spi_sck_pin!/impl_spi_mosi_pin!/impl_spi_miso_pin!三个宏在芯片级代码中批量实现,每个引脚会带出对应的PioFunc(IOCON 复用功能编号)。

三、Codegen:基于 nxp-pac 元数据的外设代码生成

变更条目Codegen using 'nxp-pac' metadata

这是本次版本最底层的变化:不再为每个芯片手写外设枚举,而是由构建脚本从nxp-pacMETADATA元数据中自动生成。

3.1 构建脚本整体流程

embassy-nxp/build.rs 的main函数执行以下步骤:

  1. 通过环境变量CARGO_FEATURE_*检测启用了哪个芯片 feature(mimxrt1011/mimxrt1062/lpc55s16/lpc55-core0),未启用或启用多个都会 panic;
  2. 定义两组 cfg 别名:
cfg_aliases! { rt1xxx: { any(feature = "mimxrt1011", feature = "mimxrt1062") }, } cfg_aliases! { lpc55: { any(feature = "lpc55s16", feature = "lpc55-core0") }, }
  1. 调用generate_code生成外设单例(peripheral singleton)与引脚实现,输出到OUT_DIR/_generated.rs

3.2 单例生成策略

singletons函数遍历metadata::METADATA.peripherals,按名字前缀分流:

  • 跳过GPIO*DMA*(它们需要在第二轮处理);
  • GPIO{n}:为每个引脚生成PIO{n}_{pin}单例,并根据引脚号是否大于 15 启用gpio{n}_hicfg;
  • DMA{n}:为每个DMA{n}_CH{channel}生成通道单例;
  • SCT{n}:只取名字以OUT开头的信号,生成SCT{n}_OUT{m}输出单例。

最终这些单例在 embassy-nxp/src/chips/lpc55.rs 通过include!(concat!(env!("OUT_DIR"), "/_generated.rs"))汇入,并由 embassy-nxp/src/lib.rs 的pub use chip::{Peripherals, interrupt, peripherals}对外暴露。

对于 i.MX RT 系列(_rt1xxx),generate_iomuxc还会从元数据的pins中筛选带有iomuxc.mux定义的引脚,生成iomuxc_pad/iomuxc_mux查询函数,供 embassy-nxp/src/iomuxc.rs 使用。这意味着引脚复用配置也全部由元数据驱动,消除了手写 match 表。

3.3 对开发者的影响

  • 新增芯片时只需要在nxp-pac中补充元数据,并给Cargo.toml增加一个芯片 feature,无需手写大量重复的引脚实现;
  • 外设单例命名(如peripherals::USART0peripherals::PIO1_5)全部与 PAC 元数据保持一致,降低了出错概率;
  • 代价是构建依赖nxp-pac(git 依赖,固定 rev 为98b09d2eae1f073804d6ded639c8dab583f614b0)与proc-macro2quote,构建期更长。

四、LPC55:简单 PWM

变更条目LPC55: PWM simple

PWM 实现位于 embassy-nxp/src/pwm/lpc55.rs,底层使用 LPC55 的 SCTimer/PWM(SCT0)统一计数器。

4.1 配置模型与周期公式

pub struct Config { pub invert: bool, // 是否反相输出 pub phase_correct: bool, // 相位校正模式,开启后输出频率减半 pub enable: bool, // 是否启动输出 pub divider: u8, // SYSCON 时钟分频,实际除以 divider + 1 pub prescale_factor: u8, // SCT 预分频,实际除以 prescale_factor + 1 pub compare: u32, // 比较值,输出电平翻转点 pub top: u32, // 计数上限,决定周期 }

文档注释给出了输出周期计算公式:

周期(时钟周期数)= (top + 1) * (phase_correct ? 1 : 2) * divider * prescale_factor

默认 SCT 时钟为 96 MHz。Config::new(compare, top)提供便捷构造函数,默认divider = 255prescale_factor = 255

4.2 实现要点

  • 共享计数器:SCT0 是统一计数器,TOP_VALUEAtomicU32静态保存。源码通过assert!(config.compare <= config.top)强制比较值不超过计数上限,否则计数器永远达不到匹配事件;TOP_VALUE一旦在第一个实例初始化时写入,之后再次修改会直接panic!("The top value cannot be changed after the initialization.")。这与注释中"counter is shared"的设计一致——周期由第一个通道决定,后续通道只能改占空比。
  • 事件与输出映射match_(0)保存top值,match_(output_number + 1)保存compare值;ev(0)ev(output_number + 1)两个事件配合out_set/out_clr决定输出拉高/拉低的时机。invert = true时二者互换。
  • 生命周期Pwm实现Drop,用REF_COUNT(AtomicU8)跟踪活跃实例,最后一个实例销毁时重置TOP_VALUE,允许后续重新配置周期。
  • 初始化钩子Pwm::reset()init阶段(见 embassy-nxp/src/lib.rs 的pwm::Pwm::reset())通过presetctrl1.sct_rst断言/释放复位,保证计数器从确定状态开始。

4.3 使用方式

let mut pwm = Pwm::new_output( p.SCT0_OUT0, // SCT 输出通道单例 p.PIO1_5, // 输出引脚 Config::new(compare, top), // 直接指定 compare 与 top ); // 运行时改占空比 pwm.set_config(&Config { compare: new_val, ..config }); let counter = pwm.counter(); // 读取当前计数值

五、LPC55:USART 的 ALT 定义迁移与内部宏清理

变更条目

  • LPC55: Move ALT definitions for USART to TX/RX pin impls.
  • LPC55: Remove internal match_iocon macro

这两条属于内部重构,但影响了公共 API 的形态。在 embassy-nxp/src/usart/lpc55.rs 中可以看到,每个impl_usart_txd_pin!/impl_usart_rxd_pin!宏展开即为"引脚 + USART 实例 + IOCON 复用功能号"的三元绑定:

impl_usart_txd_pin!(PIO0_29, USART0, Func4); impl_usart_rxd_pin!(PIO0_0, USART0, Func3);

即把"该引脚作为某 USART 的 TX/RX 时应该配置成哪个 ALT 功能"直接收进引脚实现里,取代了原先集中式match_iocon宏根据 (pin, peripheral) 查表的分发逻辑。对用户而言,编译器在Usart::new(usart, tx_pin, rx_pin, ...)时通过impl TxPin<T>/impl RxPin<T>trait 约束直接校验引脚与 USART 实例是否匹配,错误在编译期暴露,而不是运行期查表失败。

六、LPC55:DMA 控制器与异步 USART

变更条目LPC55: DMA Controller and asynchronous version of USART

这是本版本功能量最大的条目:DMA 驱动落地,并为 USART 提供异步收发能力。DMA 模块入口在 embassy-nxp/src/dma.rs,实际实现位于 embassy-nxp/src/dma/lpc55.rs;异步 USART 与 DMA 的配合逻辑集中在 embassy-nxp/src/usart/lpc55.rs。

6.1 模式系统与中断绑定

USART 驱动使用Mode泛型区分阻塞/异步两种模式(embassy-nxp/src/lib.rs):

pub trait Mode: SealedMode {} pub struct Blocking; // 阻塞模式 pub struct Async; // 异步模式

Usart<'d, M: Mode>内部持有UsartTx<'d, M>UsartRx<'d, M>两个半部,可调用split()/split_ref()拆分给不同任务使用。

异步模式依赖中断绑定。lib.rs导出的bind_interrupts!宏将 IRQ 与InterruptHandler关联:

bind_interrupts!( struct Irqs { FLEXCOMM0 => usart::InterruptHandler<peripherals::USART0>; } );

InterruptHandler::on_interrupt的实现值得注意:当收到 RX 错误中断时,它清中断标志,而是置位dma_state.rx_err(AtomicBool)并唤醒rx_err_waker(AtomicWaker)。注释解释了原因:"清标志会让 DMA 传输继续,可能在我们检查传输期间发生的错误之前就发出完成信号"——因此必须让 DMA 先停下来,再统一判定错误类型。

6.2 异步 TX:DMA 写

pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { let ch = self.tx_dma.as_mut().unwrap().reborrow(); let transfer = unsafe { self.info.usart_reg.fifocfg().modify(|w| w.set_dmatx(true)); crate::dma::write(ch, buffer, self.info.usart_reg.fifowr().as_ptr() as *mut _) }; transfer.await; Ok(()) }

流程是:开启dmatx位让 DMA 按 FIFO 水位节拍搬运数据 → 构造 DMA 传输 future →await等待完成。源码特意将 future 绑定到变量transfer上再 await,注释指出若不这样做,"数据寄存器指针会跨 await 被持有,使 future 变为非 Send"。

6.3 异步 RX:FIFO 预读 + DMA + 错误竞争

read的实现比 TX 复杂得多,核心思想是"错误字节也会进 FIFO":

  1. 先清错误标志,并同步读取至多 16 字节(FIFO 深度)——drain_fifo逐个检查rxerr(Overrun)、parityerrframerrrxnoisedeltarxbrk
  2. 若 FIFO 预读已满足请求长度,直接返回;
  3. 否则使能错误中断(framerren/parityerren/rxnoiseen/rxerr)与dmarx,发起 DMA 读;
  4. embassy_futures::select::select同时等待"传输完成"与"错误唤醒"两个 future;
  5. 若 DMA 先完成,仍要通过rx_err.swap(false)检查最后一字节是否携带错误(Either::First分支的注释:错误可能发生在最后一个字节上);
  6. 判定具体错误类型:按framerrintparityerrintrxnoiseint→ FIFOrxerr的顺序检查中断状态寄存器。

6.4 阻塞与异步的 API 对照

能力阻塞(Blocking异步(Async
发送blocking_write/blocking_flushwrite().await(DMA)
接收blocking_read(轮询 FIFO)read().await(FIFO 预读 + DMA)
状态tx_busy()同上
构造new_blocking(无需 DMA 通道)new(需要 IRQ 绑定 + TX/RX DMA 通道)

UsartRx::new_inner中有一处debug_assert_eq!(has_irq, rx_dma.is_some()),即"有中断处理则必须有 DMA 通道",保证错误中断路径与 DMA 路径配对出现。异步构造的UsartRxnew_inner中会先unpend()enable()对应 NVIC 中断。

七、从 lpc55-pac 迁移到 nxp-pac

变更条目Moved NXP LPC55S69 from 'lpc55-pac' to 'nxp-pac'

这是版本中最具方向性的架构决策:LPC55S69 的 PAC 依赖从独立的lpc55-pac统一迁移到nxp-pac。从 embassy-nxp/Cargo.toml 可以看到:

  • nxp-pac同时是普通依赖(可选,rev = "98b09d2eae1f073804d6ded639c8dab583f614b0")与构建依赖default-features = false, features = ["metadata"]);
  • 芯片选择 feature 直接透传 PAC 的芯片 feature:
lpc55-core0 = ["nxp-pac/lpc55s69_cm33_core0", "_lpc55"] lpc55s16 = ["nxp-pac/lpc55s16", "_lpc55"] mimxrt1011 = ["nxp-pac/mimxrt1011", "_rt1xxx", "dep:imxrt-rt"] mimxrt1062 = ["nxp-pac/mimxrt1062", "_rt1xxx", "dep:imxrt-rt"]
  • PAC 通过unstable-pacfeature 在embassy_nxp::pac重导出;不启用时仅pub(crate)可见。Cargo.toml注释解释了为何这个重导出永远不稳定:embassy-nxp 的 semver-minor(非破坏性)发布可能对 PAC 做 major-bump(破坏性)升级,官方建议需要固定 PAC 版本的用户直接依赖固定版本的 PAC,且"没有计划让这一 feature 稳定"。

统一到nxp-pac后,SPI/USART 的寄存器访问都变成crate::pac::spi::Spicrate::pac::usart::Usartcrate::pac::flexcomm::Flexcommcrate::pac::sct0等统一命名空间;配合第三节的元数据 codegen,驱动代码不再感知具体芯片的 PAC crate 差异,同一套驱动代码可同时覆盖 LPC55 与 i.MX RT 两条产品线。

八、版本演进脉络小结

将各条目按依赖关系串联,可以还原 0.1.0 的开发顺序与设计意图:

  1. 基础设施先行:从lpc55-pac迁移到nxp-pac,并引入基于METADATA的构建期 codegen,为多芯片支持打底;
  2. 外设驱动按"阻塞 → 异步"梯度补齐:先有阻塞版 SPI(Spi<'d, Blocking>+ embedded-hal 0.2 trait 实现),随后 DMA 控制器就绪,USART 才获得异步收发能力;
  3. 重构改善可维护性:把 USART 的 ALT 定义下沉到 TX/RX 引脚 impl,移除内部match_iocon宏,让引脚-外设匹配关系在类型层面表达;
  4. PWM 提供基础输出能力:基于 SCT0 统一计数器,先做"简单"版本(单一周期、多通道占空比)。

对于想深入代码的读者,推荐按以下路径阅读:

  • 入口与初始化:embassy-nxp/src/lib.rs(init()bind_interrupts!Mode类型系统);
  • 阻塞 SPI 完整实现:embassy-nxp/src/spi/lpc55.rs;
  • 异步 USART + DMA 竞争逻辑:embassy-nxp/src/usart/lpc55.rs;
  • 构建期 codegen:embassy-nxp/build.rs 与 embassy-nxp/src/chips/lpc55.rs;
  • PWM 实现:embassy-nxp/src/pwm/lpc55.rs。

需要注意的是,本 crate 仍处于 0.1.0 早期阶段,unstable-pac明确不稳定,SPI 的 DMA 版本在configure_spi中仍以注释"// DMA is going to be disabled until the async version is implemented"标记为未完成,LPC55 USART 的 CTS/RTS/SCK 引脚 trait 也带有TODO(wt): This needs to be wired up的未接线注释——这些都可以视为下一步版本的功能预告,引用该版本能力时应以这些实际代码状态为准。

  • 嵌入式
  • 物联网
  • 异步编程

【免费下载链接】embassy

Modern embedded framework, using Rust and async.

项目地址:https://gitcode.com/gh_mirrors/em/embassy
点击查看免费下载
上一篇:在 Angular 中集成 Tailwind CSS 与 daisyUI:让模板摆脱工具类噪声的组件化方案
下一篇:nvm-desktop高级技巧:自定义配置与镜像源设置指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 13:36:05

烘焙后城市场景满是黑斑?用6步检查 Lightmap UV 与光照接缝

城市场景完成光照烘焙后&#xff0c;如果出现整面发黑、局部脏斑、模块接缝发亮&#xff0c;先不要急着提高灯光强度。更常见的原因是 Lightmap UV 重叠、UV 岛间距不足、光照贴图分辨率与对象尺寸不匹配&#xff0c;以及薄面、法线或模块边界存在问题。 本文用一个最小场景演…

作者头像 李华
网站建设 2026/9/24 13:35:49

Ceph 三大存储接口之 RGW 对象存储深度梳理

Ceph 对象存储基于 Ceph RADOS Gateway&#xff08;RGW&#xff09;&#xff0c;Ceph 实现兼容 S3、Swift 协议的对象存储&#xff0c;无需修改底层 RADOS 集群&#xff0c;对外提供 HTTP/HTTPS 对象访问能力。一、什么是 Ceph 对象网关 RGW RGW&#xff08;RADOS Gateway&…

作者头像 李华
网站建设 2026/9/24 13:35:17

十大排序算法之快速排序

一.概念&#xff1a;通过一趟排序将要排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据都比另外一部分的所有数据都要小&#xff0c;然后再按此方法对这两部分数据分别进行快速排序&#xff0c;整个排序过程可以递归进行&#xff0c;以此达到整个数据变成有序序列…

作者头像 李华
网站建设 2026/9/24 13:34:18

【DvAdmin】写个脚本更新云端前后端服务

在现代软件开发中,前后端代码更新和部署是常见的操作。然而,传统的手动更新过程繁琐且耗时,极大地影响了开发效率。通过Python脚本实现自动化,能够简化这些操作,显著提升工作效率和项目部署的便捷性。 本文将展示如何利用Python中的paramiko库编写自动化脚本,帮助开发者…

作者头像 李华
网站建设 2026/9/24 13:34:00

【Dv2Admin】自定义审批流程数据验证和用户交互

在当今的数字化时代,企业的高效运作和用户友好性在很大程度上依赖于管理系统的设计与实现。其中,自定义审批流程作为管理系统中的核心功能,其精确、灵活且易用的实现变得尤为重要。 本文将探讨如何通过Django和Vue.js框架的结合,构建一个高效、直观的审批流程管理系统。通…

作者头像 李华
网站建设 2026/9/24 13:33:42

【Dv2Admin】dvadmin用户功能基础models解析

在现代软件开发中,数据模型的设计和管理直接决定了系统的可扩展性、性能和维护成本。尤其在基于Django-Vue-Admin框架的项目中,数据模型的合理设计更是确保系统稳定高效运行的关键。本文将重点介绍项目中核心数据模型的实现,包括Users、Post、Role、Dept、Menu、MenuButton、…

作者头像 李华