embassy-embedded-hal 实用工具指南在 Embassy 中复用 embedded-hal 与 embedded-storage 生态【免费下载链接】embassyModern embedded framework, using Rust and async.项目地址: https://gitcode.com/gh_mirrors/em/embassy导读embassy-embedded-hal是 Embassy 异步嵌入式框架中连接硬件抽象层生态的关键粘合层它提供了一套工具让embedded-hal阻塞式与embedded-hal-async异步式的驱动模型可以相互复用并在 Embassy 的异步运行时之上实现 SPI/I2C 总线共享、总线配置动态切换、NOR Flash 分区/拼接与内存模拟。读完本文你将掌握如何在一条物理 SPI/I2C 总线上挂接多个不同速率的设备、如何把已有的阻塞式驱动包装成异步接口、以及如何用Partition、ConcatFlash、MemFlash组织和管理 Flash 存储布局。所有结论均以当前仓库 embassy-embedded-hal 的源码与示例为依据。模块全景三个子模块与两个核心 trait该 crate 的核心内容在 embassy-embedded-hal/src/lib.rs 中组织为三个公开模块pub mod adapter; pub mod flash; pub mod shared_bus;同时lib.rs还定义了全 crate 通用的两个配置 trait是整个共享总线机制的地基SetConfig允许在运行时更改外设驱动的配置如 SPI 频率、极性、相位I2C 速率等。配置的具体类型由每个驱动自行定义因此以关联类型type Config呈现set_config失败时返回type ConfigError。它在SpiDeviceWithConfig、I2cDeviceWithConfig中被用来在每次通信前将总线切换到当前设备所需的工作参数。GetConfig配套的查询接口返回当前驱动配置。pub trait SetConfig { type Config; type ConfigError; fn set_config(mut self, config: Self::Config) - Result(), Self::ConfigError; }从 Cargo.toml 可以看到该 crate 的依赖取向同时依赖embedded-hal 0.2unprovenfeature、embedded-hal 1.0、embedded-hal-async 1.0、embedded-storage 0.3与embedded-storage-async 0.4因此它可以同时充当新旧两代 embedded-hal 生态的桥。crate 是#![no_std]的可直接用于裸机目标并提供两个可选 Cargo featuredefmt为错误类型派生defmt::Format日志格式化time启用DelayNs操作SPI 事务中插入纳秒级延时与 Flash 擦除等待需要依赖embassy-time。共享 SPI / I2C 总线让多个设备挂同一条总线设计思想Mutex 包裹总线 每设备 CS 引脚共享总线的思路非常直接把底层 SPI/I2C 总线实例放进embassy_sync::Mutex异步版或embassy_sync::blocking_mutex::Mutex阻塞版然后为每个设备创建一个持有该锁引用的设备视图。每次事务开始时获取互斥锁保证同一时刻只有一个设备在使用总线事务结束后释放锁从而让多个驱动任务安全地复用同一物理总线。异步实现位于 shared_bus/asynch阻塞实现位于 shared_bus/blocking两套 API 形态一一对应。异步共享 SPISpiDevice 与 SpiDeviceWithConfig以 shared_bus/asynch/spi.rs 文档中的 nRF52 示例为例核心步骤是use embassy_embedded_hal::shared_bus::spi::SpiDevice; // 实际路径为 shared_bus::asynch::spi::SpiDevice use embassy_sync::mutex::Mutex; use embassy_sync::blocking_mutex::raw::NoopRawMutex; static SPI_BUS: StaticCellMutexNoopRawMutex, spim::SpimSPI3 StaticCell::new(); let mut config spim::Config::default(); config.frequency spim::Frequency::M32; let spi spim::Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, config); let spi_bus Mutex::new(spi); let spi_bus SPI_BUS.init(spi_bus); // 设备 1使用兼容 embedded-hal-async 的 ST7735 LCD 驱动 let cs_pin1 Output::new(p.P0_24, Level::Low, OutputDrive::Standard); let spi_dev1 SpiDevice::new(spi_bus, cs_pin1); let display1 ST7735::new(spi_dev1, dc1, rst1, Default::default(), 160, 128); // 设备 2 let cs_pin2 Output::new(p.P0_24, Level::Low, OutputDrive::Standard); let spi_dev2 SpiDevice::new(spi_bus, cs_pin2); let display2 ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128);这里SpiDevicea, M, BUS, CS通过spi::SpiDeviceWord实现提供transaction方法底层流程如下见 asynch/spi.rsbus.lock().await获取总线互斥锁cs.set_low()拉低片选逐条执行Operation::Read / Write / Transfer / TransferInPlace若启用timefeatureOperation::DelayNs会先bus.flush().await再embassy_time::Timer::after_nanos延时未启用则直接返回SpiDeviceError::DelayNotSupported无论成功与否都执行bus.flush()并cs.set_high()释放片选。关键工程细节——取消安全cancel safety源码中用embassy_hal_internal::drop::OnDrop注册了一个 Drop 守卫见 asynch/spi.rs一旦事务被异步取消future 被 drop守卫会自动把 CS 引脚拉高避免总线被取消的事务卡住导致后续设备无法通信正常完成时则defuse()取消守卫后手动管理 CS 的错误处理。这一取消安全修复记录在 CHANGELOG.md 的 0.4.0 条目中SpiDevicecancel safety: always set CS pin to high on drop。若总线上不同设备需要不同的通信参数就使用SpiDeviceWithConfig它的new(bus, cs, config)额外保存一份BUS::Config每次事务前先执行bus.set_config(self.config)对应SpiDeviceError::Config错误再拉低 CS 开始通信见 asynch/spi.rs。它还提供set_config(mut self, config)允许在运行时动态更换设备配置。仓库中的 shared_bus 示例RP2040 平台展示了完整的实战写法先构造type SpiBus MutexNoopRawMutex, Spistatic, mode::Async用StaticCell把总线初始化进静态存储然后把同一个static SpiBus分别spawn到两个任务每个任务里各自SpiDevice::new(spi_bus, cs)创建自己的设备视图总线共享完全透明。异步共享 I2CI2cDevice 与 I2cDeviceWithConfigI2C 没有片选引脚设备寻址由地址字段完成因此I2cDevicea, M, BUS只持总线锁引用构造时无需 CS见 asynch/i2c.rs并且Clone后可以直接复制给多个任务使用。官方文档示例use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_sync::mutex::Mutex; use embassy_sync::blocking_mutex::raw::NoopRawMutex; static I2C_BUS: StaticCellMutexNoopRawMutex, TwimTWISPI0 StaticCell::new(); let config twim::Config::default(); let i2c Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config); let i2c_bus Mutex::new(i2c); let i2c_bus I2C_BUS.init(i2c_bus); // 设备 1QMC5883L 罗盘 let i2c_dev1 I2cDevice::new(i2c_bus); let compass QMC5883L::new(i2c_dev1).await.unwrap(); // 设备 2MPU6050 加速度计 let i2c_dev2 I2cDevice::new(i2c_bus); let mpu Mpu6050::new(i2c_dev2);I2cDevice实现embedded_hal_async::i2c::I2c的read / write / write_read / transaction四个方法每个方法内部都是锁总线 → 转发调用 → 映射错误见 asynch/i2c.rs。I2cDeviceWithConfig则在锁内先set_config再通信见 asynch/i2c.rs并且同样可Clone要求Config: Clone。异步共享 I2C 的实战参考同样在 shared_bus 示例 中两个任务各持有I2cDevice::new(i2c_bus)以不同从机地址访问总线。阻塞版共享总线Blocking 场景同样可用对于不使用异步运行时、或驱动库只实现了embedded-hal 1.0阻塞 trait 的场景可以改用 shared_bus/blocking/spi.rs 与 shared_bus/blocking/i2c.rs。两者的区别在于总线类型包装为MutexM, RefCellBUS因为阻塞共享需要运行时借用检查SPI 的DelayNs用embassy_time::block_for(Duration::from_nanos(ns))实现同样需要timefeatureI2cDevice除了embedded_hal_1::i2c::I2c还额外实现了embedded_hal_02::blocking::i2c::{Write, Read, WriteRead}见 blocking/i2c.rs因此老一代0.2阻塞驱动也能直接使用共享总线。一个非常典型的实战案例是 spi_display 示例同一块 RP2040 的 SPI1 总线同时驱动 ST7789 显示64 MHz和触摸控制器200 kHz两块外设对 SPI 极性和相位的要求一致但频率相差 320 倍示例正是用SpiDeviceWithConfig::new(spi_bus, cs_pin, display_config / touch_config)为每个设备绑定各自的spi::Config每次事务前自动切换频率完美体现了SetConfig的价值。错误类型设计共享总线设备统一使用 shared_bus/mod.rs 中定义的两个错误枚举I2cDeviceErrorBUSI2c(BUS)总线通信失败与Config总线配置失败SpiDeviceErrorBUS, CS#[non_exhaustive]Spi(BUS)、Cs(CS)片选引脚操作失败、DelayNotSupported未启用timefeature 时遇到DelayNs、Config配置失败。两者都实现了embedded_hal_1的i2c::Error/spi::Errorkind()映射、fmt::Display与core::error::Errorsource()提供底层错误链并支持defmt::Format启用defmtfeature 时方便在嵌入式日志中打印。异步适配器让阻塞式驱动跑在异步生态里BlockingAsync阻塞实现 → 伪异步实现embedded-hal生态有大量成熟的阻塞式外设驱动而embedded-hal-async是它们的新接口形态。BlockingAsyncT通过包装一个实现embedded_hal_1阻塞 trait 的对象为其补上对应的 async trait 实现见 adapter/blocking_async.rs。其文档明确说明驱动编写者可以只依赖 async trait同时仍兼容阻塞式外设实现驱动使用者则自由选择可用的实现形态。具体覆盖范围blocking_async.rs被包装对象的 trait得到的 async 实现embedded_hal_1::i2c::I2cembedded_hal_async::i2c::I2cread/write/write_read/transactionembedded_hal_1::spi::SpiBusu8embedded_hal_async::spi::SpiBusu8flush/write/read/transfer/transfer_in_placeembedded_storage::nor_flash::NorFlash / ReadNorFlash / MultiwriteNorFlashembedded_storage_async::nor_flash对应的异步版本并透传WRITE_SIZE / ERASE_SIZE / READ_SIZE常量任意实现SetConfig的驱动透传SetConfig关联类型与错误原样转发注意BlockingAsync的 SPI 仅实现SpiBusu8字节宽度。它在语义上只是把阻塞调用塞进 async 函数体内部没有任何真正异步的操作因此文档称之为伪异步fake async——被包装的阻塞操作在异步任务里执行期间仍会占住执行器线程这也是它需要与下面的YieldingAsync搭配使用的原因。仓库实例i2c_blocking_async 示例 用I2c::new_blocking创建 STM32 阻塞式 I2C再BlockingAsync::new(i2c)包装后直接.await调用write_read读取传感器 WHOAMI 寄存器。boot 应用示例如 boot/application/nrf/src/bin/a.rs同样用BlockingAsync把 Flash 适配给异步固件升级流程使用。YieldingAsync在长操作之间主动让出执行权YieldingAsyncT在每次包装对象的异步操作完成后追加一次yield_now().await见 adapter/yielding_async.rs使执行器有机会调度其他任务。其文档推荐与BlockingAsync组合使用YieldingAsync::new(BlockingAsync::new(dev))从而在长阻塞操作之间强制 yield避免单任务长期霸占 CPU。它对 I2C/SPI/SetConfig的实现模式统一为await 转发 → yield而对 Flash 的erase实现更精细会把一次大范围擦除按ERASE_SIZE逐块切分每擦除一块就 yield 一次async fn erase(mut self, from: u32, to: u32) - Result(), Self::Error { // Yield between each actual erase for from in (from..to).step_by(T::ERASE_SIZE) { let to core::cmp::min(from T::ERASE_SIZE as u32, to); self.wrapped.erase(from, to).await?; yield_now().await; } Ok(()) }模块内的单元测试yielding_async.rs直接验证了这一行为对一个MemFlash::1024, 128, 4执行erase(0, 256)断言底层产生了两次独立的(0,128)与(128,256)擦除记录——源码与测试相互印证了逐块擦除并让出的实现细节。Flash 实用工具分区、拼接与内存模拟Partition把一块 Flash 切成逻辑分区Partition异步版与BlockingPartition阻塞版允许在一块底层共享 Flash 上划分出带偏移量和长度的逻辑分区并把访问限制在该范围内见 flash/partition/mod.rs。构造函数是const fn且会在编译期常量求值阶段检查对齐约束pub const fn new(flash: a MutexM, T, offset: u32, size: u32) - Self { if offset % T::READ_SIZE as u32 ! 0 || offset % T::WRITE_SIZE as u32 ! 0 || offset % T::ERASE_SIZE as u32 ! 0 { panic!(Partition offset must be a multiple of read, write and erase size); } ... }即分区的 offset 与 size 必须同时是底层 Flash 读、写、擦除粒度的整数倍否则直接panic!。读写越界时返回Error::OutOfBounds底层 Flash 错误包装为Error::Flash(T)ErrorT本身实现了NorFlashError把OutOfBounds映射为NorFlashErrorKind::OutOfBounds见 partition/mod.rs。对外提供offset()/size()访问器且实现Clone0.2.0 起。READ_SIZE / WRITE_SIZE / ERASE_SIZE常量与底层 Flash 保持一致。源码注释同时提醒一个容易被忽视的点分区本身不保证多个分区之间的范围互斥互斥需要使用者自行保证。异步版测试partition/asynch.rs演示了在MemFlash::1024, 128, 4上开 128 偏移、256 大小的分区后read(4, ...)能正确读到 Flash 物理地址 132 处的内容、write/erase的地址都会自动叠加分区偏移。典型用法是把分区偏移与固件布局表对齐例如把 1 MiB 的片内 Flash 划分为 bootloader、应用、配置数据三个互不重叠的分区各分区以Partition形式分发给不同模块独立访问。ConcatFlash把两块 Flash 拼接成一块ConcatFlashFirst, Second把两块物理连续的 Flash 拼接为一块逻辑 Flash见 concat_flash.rs特别适用于Flash 区域场景——例如某些芯片把 Flash 划分为多个 region需要把它们合并成一个大容量区域来统一管理。约束同样在const fn中检查违反即 panicREAD_SIZE与WRITE_SIZE必须相同ERASE_SIZE取两者较大者且该较大者必须能被两者整除get_max_erase_size即最大擦除粒度必须同时是两个擦除粒度的公倍数。read / write在跨第一块 Flash 边界时自动把缓冲区切成两段分别处理先填满 First 剩余空间再转入 Seconderase同理按边界拆分。capacity()返回两块容量之和。阻塞与异步两个版本实现完全对称且都提供MultiwriteNorFlash转发。测试concat_flash.rs验证了跨边界写入write(60, [0x11..0x88])后前 4 字节落在 First 末尾、后 4 字节落在 Second 开头以及跨边界擦除的正确性。MemFlash内存模拟 Flash测试与开发利器MemFlashSIZE, ERASE_SIZE, WRITE_SIZE用一段 RAM 数组模拟 NOR Flash 的读写/擦除语义见 mem_flash.rs三个 const 泛型分别对应容量、擦除粒度、写粒度new(fill)用指定字节填充初始内容Default默认填充0xff模拟出厂状态write强制要求偏移与长度均为WRITE_SIZE的整数倍且不越界assert_eq!erase要求from/to对齐ERASE_SIZE且把区间填充为0xff——与真实 NOR Flash 的行为一致Error Infallible永不失败方便测试同时实现ReadNorFlash/NorFlash阻塞与AsyncReadNorFlash/AsyncNorFlash异步两套 traitREAD_SIZE固定为 1内部记录writes: Vec(u32, usize)与erases: Vec(u32, u32)调用日志当前为pub(crate)仅供 crate 内部测试断言使用。虽然它主要面向本 crate 的单元测试例如 partition/asynch.rs 与 yielding_async.rs 的测试均以它作为受测 Flash但它同样是开发调试的利器在没有真实 Flash 芯片、或需要在 PC 上复现 Flash 逻辑时可以用它替换真实 Flash 快速验证分区、拼接、擦写策略的正确性再无缝切换到真实存储。组合使用一条完整的实战链路把上面的工具串联起来可以构建一条完整的嵌入式存储/通信链路总线共享用MutexSpiDeviceWithConfig让显示、触摸、传感器共享一条 SPI/I2C 总线各设备独立配置频率/模式生态复用把第三方阻塞式驱动用BlockingAsync包成 async 接口必要时外层再套YieldingAsync保证长操作让出执行权存储管理用Partition把真实 Flash 按固件布局切成独立分区用ConcatFlash合并芯片的多个 Flash region用MemFlash在开发阶段模拟存储行为进行验证运行时动态配置通过SetConfigtrait 随时调整某设备的总线参数无需重新初始化硬件。这一组合模式在仓库中均有实例佐证总线共享见 rp/shared_bus.rs 与 rp/spi_display.rsBlockingAsync见 stm32l4/i2c_blocking_async.rs 与 boot/application 下的各平台示例。版本与依赖速查根据 CHANGELOG.md 与 Cargo.toml当前版本为0.6.02026-03-10 发布异步共享 I2C 总线自该版本起实现Clone并升级embassy-sync至0.8.00.4.0起 SPI 设备获得取消安全保证CS 在事务取消时自动释放0.3.0移除了stdfeature并升级embassy-time至 0.40.2.0引入 SPI 字宽泛型、运行时可改配置等能力依赖链embassy-hal-internal、embassy-futures、embassy-sync、可选embassy-time以及embedded-hal 0.2/1.0、embedded-hal-async、embedded-storage、embedded-storage-async生态 crate。在Cargo.toml中添加embassy-embedded-hal 0.6如需defmt或time则开启对应 feature即可开始使用crate 本身不依赖具体芯片 HAL与embassy-nrf、embassy-rp、embassy-stm32等任一平台 HAL 配合均可工作。【免费下载链接】embassyModern embedded framework, using Rust and async.项目地址: https://gitcode.com/gh_mirrors/em/embassy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
PHP_Timer 计时工具类源码解析:从 PHPUnit 中独立出来的 PHP 计时组件实战指南 示例工程数据库教程后端 【免费下载链接】sql-server-samples Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge 项目地址: https://gitcode.com/gh_mirrors… · 2026/9/24 15:45:42
离线翻译一步接入:Argos Translate 三语言最短路径 离线翻译一步接入:Argos Translate 三语言最短路径 【免费下载链接】argos-translate Open-source offline translation library written in Python 项目地址: https://gitcode.com/GitHub_Trending/ar/argos-translate
地铁里信号断了,翻译请求发… · 2026/9/24 15:45:42
ESP32 WebAssembly 小应用权限模型:Manifest、API 检查与资源配额 摘要
在 ESP32 上运行可独立安装的 WebAssembly 小应用时,只把 display、touch、storage 写进 Manifest,并不能自动形成安全边界。权限声明必须由应用包签名保护,并在安装校验、WASM 加载、事件分发和每一次系统 API 调用中真正执行。
本文介… · 2026/9/24 15:45:42
F´ 框架中的 Svc::BufferManager:基于固定分箱(Bin)池的内存缓冲区管理器组件深度解析 F 框架中的 Svc::BufferManager:基于固定分箱(Bin)池的内存缓冲区管理器组件深度解析 【免费下载链接】fprime F - A flight software and embedded systems framework 项目地址: https://gitcode.com/gh_mirrors/fp/fprime
导读
Svc… · 2026/9/24 16:17:48
Prisma 数据建模完全指南:用 GraphQL SDL 编写 Data Model 并生成数据库 Schema 后端数据库GraphQL 【免费下载链接】prisma1 💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated] 项目地址: https://gitcode.com/gh_mirrors/pr/prisma1 点击查看 免费下载 导读
Prisma 使用 Graph… · 2026/9/24 16:17:36
【Dify】AI角色形象自动生成与语言切换应用方法 多角色AI形象的自动生成与中日文智能切换,为智能对话、教育、内容创作等领域带来高度灵活的交互体验。
工作流以简单直观的流程,将素材加载、角色生成、语义处理和输出渲染有机整合,支持多样化角色设定与多语言环境自如切换。节点设计高效,功能聚焦,便于初学者快速掌握并… · 2026/9/24 16:17:30
那些被我们忽略的,窗帘带来的生活仪式感 提到生活仪式感,很多人第一反应是鲜花、香薰、精致餐具,或者定期外出旅行。但仪式感不一定需要花费高昂的成本,也不必刻意制造盛大场面。很多时候,仪式感藏在家里面那些重复、微小的日常动作里。拉开窗帘,等待阳光涌入… · 2026/9/24 16:17:30
【Dify】智能体工作流应用与操作 多工具AI智能体已成为自动化任务处理与智能对话发展的关键方向。高效配置和合理调用各类工具节点,是实现多场景智能响应的基础。
本文聚焦Dify智能体工作流的核心原理、工具节点构成与实际应用案例。内容涵盖对话智能体的功能配置、主要模型说明、节点分工与具体场景的操作实… · 2026/9/24 16:17:30
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44