init project
This commit is contained in:
26
embassy-embedded-hal/CHANGELOG.md
Normal file
26
embassy-embedded-hal/CHANGELOG.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Changelog for embassy-embedded-hal
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.3.0 - 2025-01-05
|
||||
|
||||
- The `std` feature has been removed
|
||||
- Updated `embassy-time` to v0.4
|
||||
|
||||
## 0.2.0 - 2024-08-05
|
||||
|
||||
- Add Clone derive to flash Partition in embassy-embedded-hal
|
||||
- Add support for all word sizes to async shared spi
|
||||
- Add Copy and 'static constraint to Word type in SPI structs
|
||||
- Improve flexibility by introducing SPI word size as a generic parameter
|
||||
- Allow changing Spi/I2cDeviceWithConfig's config at runtime
|
||||
- Impl `MultiwriteNorFlash` for `BlockingAsync`
|
||||
|
||||
## 0.1.0 - 2024-01-10
|
||||
|
||||
- First release
|
||||
42
embassy-embedded-hal/Cargo.toml
Normal file
42
embassy-embedded-hal/Cargo.toml
Normal file
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "embassy-embedded-hal"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy."
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
documentation = "https://docs.embassy.dev/embassy-embedded-hal"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/"
|
||||
target = "x86_64-unknown-linux-gnu"
|
||||
|
||||
[features]
|
||||
time = ["dep:embassy-time"]
|
||||
default = ["time"]
|
||||
|
||||
[dependencies]
|
||||
embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal" }
|
||||
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
|
||||
embassy-sync = { version = "0.6.2", path = "../embassy-sync" }
|
||||
embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true }
|
||||
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
|
||||
"unproven",
|
||||
] }
|
||||
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
|
||||
embedded-hal-async = { version = "1.0" }
|
||||
embedded-storage = "0.3.1"
|
||||
embedded-storage-async = { version = "0.4.1" }
|
||||
nb = "1.0.0"
|
||||
|
||||
defmt = { version = "0.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
critical-section = { version = "1.1.1", features = ["std"] }
|
||||
futures-test = "0.3.17"
|
||||
12
embassy-embedded-hal/README.md
Normal file
12
embassy-embedded-hal/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# embassy-embedded-hal
|
||||
|
||||
Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy.
|
||||
|
||||
- Shared SPI and I2C buses, both blocking and async, with a `SetConfig` trait allowing changing bus configuration (e.g. frequency) between devices on the same bus.
|
||||
- Async utilities
|
||||
- Adapters to convert from blocking to (fake) async.
|
||||
- Adapters to insert yields on trait operations.
|
||||
- Flash utilities
|
||||
- Split a flash memory into smaller partitions.
|
||||
- Concatenate flash memories together.
|
||||
- Simulated in-memory flash.
|
||||
149
embassy-embedded-hal/src/adapter/blocking_async.rs
Normal file
149
embassy-embedded-hal/src/adapter/blocking_async.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use embedded_hal_02::blocking;
|
||||
|
||||
/// Wrapper that implements async traits using blocking implementations.
|
||||
///
|
||||
/// This allows driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations.
|
||||
///
|
||||
/// BlockingAsync will implement any async trait that maps to embedded-hal traits implemented for the wrapped driver.
|
||||
///
|
||||
/// Driver users are then free to choose which implementation that is available to them.
|
||||
pub struct BlockingAsync<T> {
|
||||
wrapped: T,
|
||||
}
|
||||
|
||||
impl<T> BlockingAsync<T> {
|
||||
/// Create a new instance of a wrapper for a given peripheral.
|
||||
pub fn new(wrapped: T) -> Self {
|
||||
Self { wrapped }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// I2C implementations
|
||||
//
|
||||
impl<T, E> embedded_hal_1::i2c::ErrorType for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::i2c::Error + 'static,
|
||||
T: blocking::i2c::WriteRead<Error = E> + blocking::i2c::Read<Error = E> + blocking::i2c::Write<Error = E>,
|
||||
{
|
||||
type Error = E;
|
||||
}
|
||||
|
||||
impl<T, E> embedded_hal_async::i2c::I2c for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::i2c::Error + 'static,
|
||||
T: blocking::i2c::WriteRead<Error = E> + blocking::i2c::Read<Error = E> + blocking::i2c::Write<Error = E>,
|
||||
{
|
||||
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(address, read)
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(address, write)
|
||||
}
|
||||
|
||||
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write_read(address, write, read)
|
||||
}
|
||||
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
let _ = address;
|
||||
let _ = operations;
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SPI implementatinos
|
||||
//
|
||||
|
||||
impl<T, E> embedded_hal_async::spi::ErrorType for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::spi::Error,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
type Error = E;
|
||||
}
|
||||
|
||||
impl<T, E> embedded_hal_async::spi::SpiBus<u8> for BlockingAsync<T>
|
||||
where
|
||||
E: embedded_hal_1::spi::Error + 'static,
|
||||
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
|
||||
{
|
||||
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
||||
// Ensure we write the expected bytes
|
||||
for i in 0..core::cmp::min(read.len(), write.len()) {
|
||||
read[i] = write[i].clone();
|
||||
}
|
||||
self.wrapped.transfer(read)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// NOR flash wrapper
|
||||
use embedded_storage::nor_flash::{ErrorType, MultiwriteNorFlash, NorFlash, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{
|
||||
MultiwriteNorFlash as AsyncMultiwriteNorFlash, NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash,
|
||||
};
|
||||
|
||||
impl<T> ErrorType for BlockingAsync<T>
|
||||
where
|
||||
T: ErrorType,
|
||||
{
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T> AsyncNorFlash for BlockingAsync<T>
|
||||
where
|
||||
T: NorFlash,
|
||||
{
|
||||
const WRITE_SIZE: usize = <T as NorFlash>::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = <T as NorFlash>::ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(offset, data)
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.wrapped.erase(from, to)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncReadNorFlash for BlockingAsync<T>
|
||||
where
|
||||
T: ReadNorFlash,
|
||||
{
|
||||
const READ_SIZE: usize = <T as ReadNorFlash>::READ_SIZE;
|
||||
async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(address, data)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.wrapped.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncMultiwriteNorFlash for BlockingAsync<T> where T: MultiwriteNorFlash {}
|
||||
7
embassy-embedded-hal/src/adapter/mod.rs
Normal file
7
embassy-embedded-hal/src/adapter/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
//! Adapters between embedded-hal traits.
|
||||
|
||||
mod blocking_async;
|
||||
mod yielding_async;
|
||||
|
||||
pub use blocking_async::BlockingAsync;
|
||||
pub use yielding_async::YieldingAsync;
|
||||
169
embassy-embedded-hal/src/adapter/yielding_async.rs
Normal file
169
embassy-embedded-hal/src/adapter/yielding_async.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use embassy_futures::yield_now;
|
||||
|
||||
/// Wrapper that yields for each operation to the wrapped instance
|
||||
///
|
||||
/// This can be used in combination with BlockingAsync<T> to enforce yields
|
||||
/// between long running blocking operations.
|
||||
pub struct YieldingAsync<T> {
|
||||
wrapped: T,
|
||||
}
|
||||
|
||||
impl<T> YieldingAsync<T> {
|
||||
/// Create a new instance of a wrapper that yields after each operation.
|
||||
pub fn new(wrapped: T) -> Self {
|
||||
Self { wrapped }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// I2C implementations
|
||||
//
|
||||
impl<T> embedded_hal_1::i2c::ErrorType for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_1::i2c::ErrorType,
|
||||
{
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T> embedded_hal_async::i2c::I2c for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::i2c::I2c,
|
||||
{
|
||||
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(address, read).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(address, write).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write_read(address, write, read).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
|
||||
) -> Result<(), Self::Error> {
|
||||
self.wrapped.transaction(address, operations).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SPI implementations
|
||||
//
|
||||
|
||||
impl<T> embedded_hal_async::spi::ErrorType for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::ErrorType,
|
||||
{
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T, Word: 'static + Copy> embedded_hal_async::spi::SpiBus<Word> for YieldingAsync<T>
|
||||
where
|
||||
T: embedded_hal_async::spi::SpiBus<Word>,
|
||||
{
|
||||
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
self.wrapped.flush().await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(data).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(data).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer(read, write).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||
self.wrapped.transfer_in_place(words).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// NOR flash implementations
|
||||
///
|
||||
impl<T: embedded_storage::nor_flash::ErrorType> embedded_storage::nor_flash::ErrorType for YieldingAsync<T> {
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
impl<T: embedded_storage_async::nor_flash::ReadNorFlash> embedded_storage_async::nor_flash::ReadNorFlash
|
||||
for YieldingAsync<T>
|
||||
{
|
||||
const READ_SIZE: usize = T::READ_SIZE;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.read(offset, bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.wrapped.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: embedded_storage_async::nor_flash::NorFlash> embedded_storage_async::nor_flash::NorFlash for YieldingAsync<T> {
|
||||
const WRITE_SIZE: usize = T::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = T::ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.wrapped.write(offset, bytes).await?;
|
||||
yield_now().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embedded_storage_async::nor_flash::NorFlash;
|
||||
|
||||
use super::*;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_erase() {
|
||||
let flash = MemFlash::<1024, 128, 4>::new(0x00);
|
||||
let mut yielding = YieldingAsync::new(flash);
|
||||
|
||||
yielding.erase(0, 256).await.unwrap();
|
||||
|
||||
let flash = yielding.wrapped;
|
||||
assert_eq!(2, flash.erases.len());
|
||||
assert_eq!((0, 128), flash.erases[0]);
|
||||
assert_eq!((128, 256), flash.erases[1]);
|
||||
}
|
||||
}
|
||||
225
embassy-embedded-hal/src/flash/concat_flash.rs
Normal file
225
embassy-embedded-hal/src/flash/concat_flash.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
/// Convenience helper for concatenating two consecutive flashes into one.
|
||||
/// This is especially useful if used with "flash regions", where one may
|
||||
/// want to concatenate multiple regions into one larger region.
|
||||
pub struct ConcatFlash<First, Second>(First, Second);
|
||||
|
||||
impl<First, Second> ConcatFlash<First, Second> {
|
||||
/// Create a new flash that concatenates two consecutive flashes.
|
||||
pub fn new(first: First, second: Second) -> Self {
|
||||
Self(first, second)
|
||||
}
|
||||
}
|
||||
|
||||
const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize {
|
||||
if first_read_size != second_read_size {
|
||||
panic!("The read size for the concatenated flashes must be the same");
|
||||
}
|
||||
first_read_size
|
||||
}
|
||||
|
||||
const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize {
|
||||
if first_write_size != second_write_size {
|
||||
panic!("The write size for the concatenated flashes must be the same");
|
||||
}
|
||||
first_write_size
|
||||
}
|
||||
|
||||
const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize {
|
||||
let max_erase_size = if first_erase_size > second_erase_size {
|
||||
first_erase_size
|
||||
} else {
|
||||
second_erase_size
|
||||
};
|
||||
if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 {
|
||||
panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size");
|
||||
}
|
||||
max_erase_size
|
||||
}
|
||||
|
||||
impl<First, Second, E> ErrorType for ConcatFlash<First, Second>
|
||||
where
|
||||
First: ErrorType<Error = E>,
|
||||
Second: ErrorType<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
type Error = E;
|
||||
}
|
||||
|
||||
impl<First, Second, E> ReadNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: ReadNorFlash<Error = E>,
|
||||
Second: ReadNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE);
|
||||
|
||||
fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.read(offset, &mut bytes[..len])?;
|
||||
offset += len as u32;
|
||||
bytes = &mut bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.read(offset - self.0.capacity() as u32, bytes)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.0.capacity() + self.1.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<First, Second, E> NorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: NorFlash<Error = E>,
|
||||
Second: NorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE);
|
||||
const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE);
|
||||
|
||||
fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.write(offset, &bytes[..len])?;
|
||||
offset += len as u32;
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.write(offset - self.0.capacity() as u32, bytes)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> {
|
||||
if from < self.0.capacity() as u32 {
|
||||
let to = core::cmp::min(self.0.capacity() as u32, to);
|
||||
self.0.erase(from, to)?;
|
||||
from = self.0.capacity() as u32;
|
||||
}
|
||||
|
||||
if from < to {
|
||||
self.1
|
||||
.erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<First, Second, E> AsyncReadNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: AsyncReadNorFlash<Error = E>,
|
||||
Second: AsyncReadNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE);
|
||||
|
||||
async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.read(offset, &mut bytes[..len]).await?;
|
||||
offset += len as u32;
|
||||
bytes = &mut bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.read(offset - self.0.capacity() as u32, bytes).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.0.capacity() + self.1.capacity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<First, Second, E> AsyncNorFlash for ConcatFlash<First, Second>
|
||||
where
|
||||
First: AsyncNorFlash<Error = E>,
|
||||
Second: AsyncNorFlash<Error = E>,
|
||||
E: NorFlashError,
|
||||
{
|
||||
const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE);
|
||||
const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE);
|
||||
|
||||
async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> {
|
||||
if offset < self.0.capacity() as u32 {
|
||||
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
|
||||
self.0.write(offset, &bytes[..len]).await?;
|
||||
offset += len as u32;
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
|
||||
if !bytes.is_empty() {
|
||||
self.1.write(offset - self.0.capacity() as u32, bytes).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> {
|
||||
if from < self.0.capacity() as u32 {
|
||||
let to = core::cmp::min(self.0.capacity() as u32, to);
|
||||
self.0.erase(from, to).await?;
|
||||
from = self.0.capacity() as u32;
|
||||
}
|
||||
|
||||
if from < to {
|
||||
self.1
|
||||
.erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
|
||||
|
||||
use super::ConcatFlash;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
fn can_write_and_read_across_flashes() {
|
||||
let first = MemFlash::<64, 16, 4>::default();
|
||||
let second = MemFlash::<64, 64, 4>::default();
|
||||
let mut f = ConcatFlash::new(first, second);
|
||||
|
||||
f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap();
|
||||
|
||||
assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]);
|
||||
assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]);
|
||||
|
||||
let mut read_buf = [0; 8];
|
||||
f.read(60, &mut read_buf).unwrap();
|
||||
|
||||
assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_erase_across_flashes() {
|
||||
let first = MemFlash::<128, 16, 4>::new(0x00);
|
||||
let second = MemFlash::<128, 64, 4>::new(0x00);
|
||||
let mut f = ConcatFlash::new(first, second);
|
||||
|
||||
f.erase(64, 192).unwrap();
|
||||
|
||||
assert_eq!(&[0x00; 64], &f.0.mem[0..64]);
|
||||
assert_eq!(&[0xff; 64], &f.0.mem[64..128]);
|
||||
assert_eq!(&[0xff; 64], &f.1.mem[0..64]);
|
||||
assert_eq!(&[0x00; 64], &f.1.mem[64..128]);
|
||||
}
|
||||
}
|
||||
125
embassy-embedded-hal/src/flash/mem_flash.rs
Normal file
125
embassy-embedded-hal/src/flash/mem_flash.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub(crate) struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
|
||||
pub mem: [u8; SIZE],
|
||||
pub writes: Vec<(u32, usize)>,
|
||||
pub erases: Vec<(u32, u32)>,
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> {
|
||||
#[allow(unused)]
|
||||
pub const fn new(fill: u8) -> Self {
|
||||
Self {
|
||||
mem: [fill; SIZE],
|
||||
writes: Vec::new(),
|
||||
erases: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) {
|
||||
let len = bytes.len();
|
||||
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) {
|
||||
self.writes.push((offset, bytes.len()));
|
||||
let offset = offset as usize;
|
||||
assert_eq!(0, bytes.len() % WRITE_SIZE);
|
||||
assert_eq!(0, offset % WRITE_SIZE);
|
||||
assert!(offset + bytes.len() <= SIZE);
|
||||
|
||||
self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) {
|
||||
self.erases.push((from, to));
|
||||
let from = from as usize;
|
||||
let to = to as usize;
|
||||
assert_eq!(0, from % ERASE_SIZE);
|
||||
assert_eq!(0, to % ERASE_SIZE);
|
||||
self.mem[from..to].fill(0xff);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new(0xff)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
type Error = core::convert::Infallible;
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const READ_SIZE: usize = 1;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
|
||||
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
|
||||
{
|
||||
const WRITE_SIZE: usize = WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(offset, bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
self.erase(from, to);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
8
embassy-embedded-hal/src/flash/mod.rs
Normal file
8
embassy-embedded-hal/src/flash/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! Utilities related to flash.
|
||||
|
||||
mod concat_flash;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod mem_flash;
|
||||
pub mod partition;
|
||||
|
||||
pub use concat_flash::ConcatFlash;
|
||||
149
embassy-embedded-hal/src/flash/partition/asynch.rs
Normal file
149
embassy-embedded-hal/src/flash/partition/asynch.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embedded_storage::nor_flash::ErrorType;
|
||||
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// A logical partition of an underlying shared flash
|
||||
///
|
||||
/// A partition holds an offset and a size of the flash,
|
||||
/// and is restricted to operate with that range.
|
||||
/// There is no guarantee that muliple partitions on the same flash
|
||||
/// operate on mutually exclusive ranges - such a separation is up to
|
||||
/// the user to guarantee.
|
||||
pub struct Partition<'a, M: RawMutex, T: NorFlash> {
|
||||
flash: &'a Mutex<M, T>,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, T: NorFlash> Clone for Partition<'a, M, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
flash: self.flash,
|
||||
offset: self.offset,
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> {
|
||||
/// Create a new partition
|
||||
pub const fn new(flash: &'a Mutex<M, 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");
|
||||
}
|
||||
if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 {
|
||||
panic!("Partition size must be a multiple of read, write and erase size");
|
||||
}
|
||||
Self { flash, offset, size }
|
||||
}
|
||||
|
||||
/// Get the partition offset within the flash
|
||||
pub const fn offset(&self) -> u32 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
/// Get the partition size
|
||||
pub const fn size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ErrorType for Partition<'_, M, T> {
|
||||
type Error = Error<T::Error>;
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ReadNorFlash for Partition<'_, M, T> {
|
||||
const READ_SIZE: usize = T::READ_SIZE;
|
||||
|
||||
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash.read(self.offset + offset, bytes).await.map_err(Error::Flash)
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.size as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> NorFlash for Partition<'_, M, T> {
|
||||
const WRITE_SIZE: usize = T::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = T::ERASE_SIZE;
|
||||
|
||||
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash.write(self.offset + offset, bytes).await.map_err(Error::Flash)
|
||||
}
|
||||
|
||||
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
if to > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
let mut flash = self.flash.lock().await;
|
||||
flash
|
||||
.erase(self.offset + from, self.offset + to)
|
||||
.await
|
||||
.map_err(Error::Flash)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
|
||||
use super::*;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_read() {
|
||||
let mut flash = MemFlash::<1024, 128, 4>::default();
|
||||
flash.mem[132..132 + 8].fill(0xAA);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
let mut read_buf = [0; 8];
|
||||
partition.read(4, &mut read_buf).await.unwrap();
|
||||
|
||||
assert!(read_buf.iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_write() {
|
||||
let flash = MemFlash::<1024, 128, 4>::default();
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
let write_buf = [0xAA; 8];
|
||||
partition.write(4, &write_buf).await.unwrap();
|
||||
|
||||
let flash = flash.try_lock().unwrap();
|
||||
assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[futures_test::test]
|
||||
async fn can_erase() {
|
||||
let flash = MemFlash::<1024, 128, 4>::new(0x00);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(flash);
|
||||
let mut partition = Partition::new(&flash, 128, 256);
|
||||
|
||||
partition.erase(0, 128).await.unwrap();
|
||||
|
||||
let flash = flash.try_lock().unwrap();
|
||||
assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none());
|
||||
}
|
||||
}
|
||||
159
embassy-embedded-hal/src/flash/partition/blocking.rs
Normal file
159
embassy-embedded-hal/src/flash/partition/blocking.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use core::cell::RefCell;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// A logical partition of an underlying shared flash
|
||||
///
|
||||
/// A partition holds an offset and a size of the flash,
|
||||
/// and is restricted to operate with that range.
|
||||
/// There is no guarantee that muliple partitions on the same flash
|
||||
/// operate on mutually exclusive ranges - such a separation is up to
|
||||
/// the user to guarantee.
|
||||
pub struct BlockingPartition<'a, M: RawMutex, T: NorFlash> {
|
||||
flash: &'a Mutex<M, RefCell<T>>,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, T: NorFlash> Clone for BlockingPartition<'a, M, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
flash: self.flash,
|
||||
offset: self.offset,
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> {
|
||||
/// Create a new partition
|
||||
pub const fn new(flash: &'a Mutex<M, RefCell<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");
|
||||
}
|
||||
if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 {
|
||||
panic!("Partition size must be a multiple of read, write and erase size");
|
||||
}
|
||||
Self { flash, offset, size }
|
||||
}
|
||||
|
||||
/// Get the partition offset within the flash
|
||||
pub const fn offset(&self) -> u32 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
/// Get the partition size
|
||||
pub const fn size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ErrorType for BlockingPartition<'_, M, T> {
|
||||
type Error = Error<T::Error>;
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> ReadNorFlash for BlockingPartition<'_, M, T> {
|
||||
const READ_SIZE: usize = T::READ_SIZE;
|
||||
|
||||
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
self.flash.lock(|flash| {
|
||||
flash
|
||||
.borrow_mut()
|
||||
.read(self.offset + offset, bytes)
|
||||
.map_err(Error::Flash)
|
||||
})
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.size as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: RawMutex, T: NorFlash> NorFlash for BlockingPartition<'_, M, T> {
|
||||
const WRITE_SIZE: usize = T::WRITE_SIZE;
|
||||
const ERASE_SIZE: usize = T::ERASE_SIZE;
|
||||
|
||||
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
if offset + bytes.len() as u32 > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
self.flash.lock(|flash| {
|
||||
flash
|
||||
.borrow_mut()
|
||||
.write(self.offset + offset, bytes)
|
||||
.map_err(Error::Flash)
|
||||
})
|
||||
}
|
||||
|
||||
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
|
||||
if to > self.size {
|
||||
return Err(Error::OutOfBounds);
|
||||
}
|
||||
|
||||
self.flash.lock(|flash| {
|
||||
flash
|
||||
.borrow_mut()
|
||||
.erase(self.offset + from, self.offset + to)
|
||||
.map_err(Error::Flash)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
|
||||
use super::*;
|
||||
use crate::flash::mem_flash::MemFlash;
|
||||
|
||||
#[test]
|
||||
fn can_read() {
|
||||
let mut flash = MemFlash::<1024, 128, 4>::default();
|
||||
flash.mem[132..132 + 8].fill(0xAA);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
|
||||
let mut partition = BlockingPartition::new(&flash, 128, 256);
|
||||
|
||||
let mut read_buf = [0; 8];
|
||||
partition.read(4, &mut read_buf).unwrap();
|
||||
|
||||
assert!(read_buf.iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_write() {
|
||||
let flash = MemFlash::<1024, 128, 4>::default();
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
|
||||
let mut partition = BlockingPartition::new(&flash, 128, 256);
|
||||
|
||||
let write_buf = [0xAA; 8];
|
||||
partition.write(4, &write_buf).unwrap();
|
||||
|
||||
let flash = flash.into_inner().take();
|
||||
assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_erase() {
|
||||
let flash = MemFlash::<1024, 128, 4>::new(0x00);
|
||||
|
||||
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
|
||||
let mut partition = BlockingPartition::new(&flash, 128, 256);
|
||||
|
||||
partition.erase(0, 128).unwrap();
|
||||
|
||||
let flash = flash.into_inner().take();
|
||||
assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none());
|
||||
}
|
||||
}
|
||||
28
embassy-embedded-hal/src/flash/partition/mod.rs
Normal file
28
embassy-embedded-hal/src/flash/partition/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! Flash Partition utilities
|
||||
|
||||
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
|
||||
|
||||
mod asynch;
|
||||
mod blocking;
|
||||
|
||||
pub use asynch::Partition;
|
||||
pub use blocking::BlockingPartition;
|
||||
|
||||
/// Partition error
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error<T> {
|
||||
/// The requested flash area is outside the partition
|
||||
OutOfBounds,
|
||||
/// Underlying flash error
|
||||
Flash(T),
|
||||
}
|
||||
|
||||
impl<T: NorFlashError> NorFlashError for Error<T> {
|
||||
fn kind(&self) -> NorFlashErrorKind {
|
||||
match self {
|
||||
Error::OutOfBounds => NorFlashErrorKind::OutOfBounds,
|
||||
Error::Flash(f) => f.kind(),
|
||||
}
|
||||
}
|
||||
}
|
||||
39
embassy-embedded-hal/src/lib.rs
Normal file
39
embassy-embedded-hal/src/lib.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
#![no_std]
|
||||
#![allow(async_fn_in_trait)]
|
||||
#![warn(missing_docs)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod adapter;
|
||||
pub mod flash;
|
||||
pub mod shared_bus;
|
||||
|
||||
/// Set the configuration of a peripheral driver.
|
||||
///
|
||||
/// This trait is intended to be implemented by peripheral drivers such as SPI
|
||||
/// and I2C. It allows changing the configuration at runtime.
|
||||
///
|
||||
/// The exact type of the "configuration" is defined by each individual driver, since different
|
||||
/// drivers support different options. Therefore it is defined as an associated type.
|
||||
///
|
||||
/// For example, it is used by [`SpiDeviceWithConfig`](crate::shared_bus::asynch::spi::SpiDeviceWithConfig) and
|
||||
/// [`I2cDeviceWithConfig`](crate::shared_bus::asynch::i2c::I2cDeviceWithConfig) to allow different
|
||||
/// devices on the same bus to use different communication settings.
|
||||
pub trait SetConfig {
|
||||
/// The configuration type used by this driver.
|
||||
type Config;
|
||||
|
||||
/// The error type that can occur if `set_config` fails.
|
||||
type ConfigError;
|
||||
|
||||
/// Set the configuration of the driver.
|
||||
fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError>;
|
||||
}
|
||||
|
||||
/// Get the configuration of a peripheral driver.
|
||||
pub trait GetConfig {
|
||||
/// The configuration type used by this driver.
|
||||
type Config;
|
||||
|
||||
/// Get the configuration of the driver.
|
||||
fn get_config(&self) -> Self::Config;
|
||||
}
|
||||
166
embassy-embedded-hal/src/shared_bus/asynch/i2c.rs
Normal file
166
embassy-embedded-hal/src/shared_bus/asynch/i2c.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
//! Asynchronous shared I2C bus
|
||||
//!
|
||||
//! # Example (nrf52)
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
||||
//! use embassy_sync::mutex::Mutex;
|
||||
//! use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
//!
|
||||
//! static I2C_BUS: StaticCell<Mutex<NoopRawMutex, Twim<TWISPI0>>> = 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);
|
||||
//!
|
||||
//! // Device 1, using embedded-hal-async compatible driver for QMC5883L compass
|
||||
//! let i2c_dev1 = I2cDevice::new(i2c_bus);
|
||||
//! let compass = QMC5883L::new(i2c_dev1).await.unwrap();
|
||||
//!
|
||||
//! // Device 2, using embedded-hal-async compatible driver for Mpu6050 accelerometer
|
||||
//! let i2c_dev2 = I2cDevice::new(i2c_bus);
|
||||
//! let mpu = Mpu6050::new(i2c_dev2);
|
||||
//! ```
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embedded_hal_async::i2c;
|
||||
|
||||
use crate::shared_bus::I2cDeviceError;
|
||||
use crate::SetConfig;
|
||||
|
||||
/// I2C device on a shared bus.
|
||||
pub struct I2cDevice<'a, M: RawMutex, BUS> {
|
||||
bus: &'a Mutex<M, BUS>,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS> I2cDevice<'a, M, BUS> {
|
||||
/// Create a new `I2cDevice`.
|
||||
pub fn new(bus: &'a Mutex<M, BUS>) -> Self {
|
||||
Self { bus }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS> i2c::ErrorType for I2cDevice<'a, M, BUS>
|
||||
where
|
||||
BUS: i2c::ErrorType,
|
||||
{
|
||||
type Error = I2cDeviceError<BUS::Error>;
|
||||
}
|
||||
|
||||
impl<M, BUS> i2c::I2c for I2cDevice<'_, M, BUS>
|
||||
where
|
||||
M: RawMutex + 'static,
|
||||
BUS: i2c::I2c + 'static,
|
||||
{
|
||||
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.read(address, read).await.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.write(address, write).await.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_read(
|
||||
&mut self,
|
||||
address: u8,
|
||||
write: &[u8],
|
||||
read: &mut [u8],
|
||||
) -> Result<(), I2cDeviceError<BUS::Error>> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.write_read(address, write, read)
|
||||
.await
|
||||
.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
address: u8,
|
||||
operations: &mut [embedded_hal_async::i2c::Operation<'_>],
|
||||
) -> Result<(), I2cDeviceError<BUS::Error>> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.transaction(address, operations)
|
||||
.await
|
||||
.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// I2C device on a shared bus, with its own configuration.
|
||||
///
|
||||
/// This is like [`I2cDevice`], with an additional bus configuration that's applied
|
||||
/// to the bus before each use using [`SetConfig`]. This allows different
|
||||
/// devices on the same bus to use different communication settings.
|
||||
pub struct I2cDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig> {
|
||||
bus: &'a Mutex<M, BUS>,
|
||||
config: BUS::Config,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS: SetConfig> I2cDeviceWithConfig<'a, M, BUS> {
|
||||
/// Create a new `I2cDeviceWithConfig`.
|
||||
pub fn new(bus: &'a Mutex<M, BUS>, config: BUS::Config) -> Self {
|
||||
Self { bus, config }
|
||||
}
|
||||
|
||||
/// Change the device's config at runtime
|
||||
pub fn set_config(&mut self, config: BUS::Config) {
|
||||
self.config = config;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M, BUS> i2c::ErrorType for I2cDeviceWithConfig<'a, M, BUS>
|
||||
where
|
||||
BUS: i2c::ErrorType,
|
||||
M: RawMutex,
|
||||
BUS: SetConfig,
|
||||
{
|
||||
type Error = I2cDeviceError<BUS::Error>;
|
||||
}
|
||||
|
||||
impl<M, BUS> i2c::I2c for I2cDeviceWithConfig<'_, M, BUS>
|
||||
where
|
||||
M: RawMutex + 'static,
|
||||
BUS: i2c::I2c + SetConfig + 'static,
|
||||
{
|
||||
async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
|
||||
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
|
||||
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_read(
|
||||
&mut self,
|
||||
address: u8,
|
||||
wr_buffer: &[u8],
|
||||
rd_buffer: &mut [u8],
|
||||
) -> Result<(), I2cDeviceError<BUS::Error>> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
|
||||
bus.write_read(address, wr_buffer, rd_buffer)
|
||||
.await
|
||||
.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transaction(&mut self, address: u8, operations: &mut [i2c::Operation<'_>]) -> Result<(), Self::Error> {
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
|
||||
bus.transaction(address, operations)
|
||||
.await
|
||||
.map_err(I2cDeviceError::I2c)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
3
embassy-embedded-hal/src/shared_bus/asynch/mod.rs
Normal file
3
embassy-embedded-hal/src/shared_bus/asynch/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
//! Asynchronous shared bus implementations for embedded-hal-async
|
||||
pub mod i2c;
|
||||
pub mod spi;
|
||||
212
embassy-embedded-hal/src/shared_bus/asynch/spi.rs
Normal file
212
embassy-embedded-hal/src/shared_bus/asynch/spi.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
//! Asynchronous shared SPI bus
|
||||
//!
|
||||
//! # Example (nrf52)
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use embassy_embedded_hal::shared_bus::spi::SpiDevice;
|
||||
//! use embassy_sync::mutex::Mutex;
|
||||
//! use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
//!
|
||||
//! static SPI_BUS: StaticCell<Mutex<NoopRawMutex, spim::Spim<SPI3>>> = 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);
|
||||
//!
|
||||
//! // Device 1, using embedded-hal-async compatible driver for ST7735 LCD display
|
||||
//! 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);
|
||||
//!
|
||||
//! // Device 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);
|
||||
//! ```
|
||||
|
||||
use embassy_hal_internal::drop::OnDrop;
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
use embedded_hal_1::spi::Operation;
|
||||
use embedded_hal_async::spi;
|
||||
|
||||
use crate::shared_bus::SpiDeviceError;
|
||||
use crate::SetConfig;
|
||||
|
||||
/// SPI device on a shared bus.
|
||||
pub struct SpiDevice<'a, M: RawMutex, BUS, CS> {
|
||||
bus: &'a Mutex<M, BUS>,
|
||||
cs: CS,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> {
|
||||
/// Create a new `SpiDevice`.
|
||||
pub fn new(bus: &'a Mutex<M, BUS>, cs: CS) -> Self {
|
||||
Self { bus, cs }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS>
|
||||
where
|
||||
BUS: spi::ErrorType,
|
||||
CS: OutputPin,
|
||||
{
|
||||
type Error = SpiDeviceError<BUS::Error, CS::Error>;
|
||||
}
|
||||
|
||||
impl<M, BUS, CS, Word> spi::SpiDevice<Word> for SpiDevice<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: spi::SpiBus<Word>,
|
||||
CS: OutputPin,
|
||||
Word: Copy + 'static,
|
||||
{
|
||||
async fn transaction(&mut self, operations: &mut [spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
|
||||
if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
|
||||
return Err(SpiDeviceError::DelayNotSupported);
|
||||
}
|
||||
|
||||
let mut bus = self.bus.lock().await;
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let cs_drop = OnDrop::new(|| {
|
||||
// This drop guard deasserts CS pin if the async operation is cancelled.
|
||||
// Errors are ignored in this drop handler, as there's nothing we can do about them.
|
||||
// If the async operation is completed without cancellation, this handler will not
|
||||
// be run, and the CS pin will be deasserted with proper error handling.
|
||||
let _ = self.cs.set_high();
|
||||
});
|
||||
|
||||
let op_res = 'ops: {
|
||||
for op in operations {
|
||||
let res = match op {
|
||||
Operation::Read(buf) => bus.read(buf).await,
|
||||
Operation::Write(buf) => bus.write(buf).await,
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write).await,
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayNs(_) => unreachable!(),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayNs(ns) => match bus.flush().await {
|
||||
Err(e) => Err(e),
|
||||
Ok(()) => {
|
||||
embassy_time::Timer::after_nanos(*ns as _).await;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
};
|
||||
if let Err(e) = res {
|
||||
break 'ops Err(e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush().await;
|
||||
|
||||
// Now that all the async operations are done, we defuse the CS guard,
|
||||
// and manually set the CS pin low (to better handle the possible errors).
|
||||
cs_drop.defuse();
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
}
|
||||
}
|
||||
|
||||
/// SPI device on a shared bus, with its own configuration.
|
||||
///
|
||||
/// This is like [`SpiDevice`], with an additional bus configuration that's applied
|
||||
/// to the bus before each use using [`SetConfig`]. This allows different
|
||||
/// devices on the same bus to use different communication settings.
|
||||
pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> {
|
||||
bus: &'a Mutex<M, BUS>,
|
||||
cs: CS,
|
||||
config: BUS::Config,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> {
|
||||
/// Create a new `SpiDeviceWithConfig`.
|
||||
pub fn new(bus: &'a Mutex<M, BUS>, cs: CS, config: BUS::Config) -> Self {
|
||||
Self { bus, cs, config }
|
||||
}
|
||||
|
||||
/// Change the device's config at runtime
|
||||
pub fn set_config(&mut self, config: BUS::Config) {
|
||||
self.config = config;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS>
|
||||
where
|
||||
BUS: spi::ErrorType + SetConfig,
|
||||
CS: OutputPin,
|
||||
M: RawMutex,
|
||||
{
|
||||
type Error = SpiDeviceError<BUS::Error, CS::Error>;
|
||||
}
|
||||
|
||||
impl<M, BUS, CS, Word> spi::SpiDevice<Word> for SpiDeviceWithConfig<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: spi::SpiBus<Word> + SetConfig,
|
||||
CS: OutputPin,
|
||||
Word: Copy + 'static,
|
||||
{
|
||||
async fn transaction(&mut self, operations: &mut [spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
|
||||
if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
|
||||
return Err(SpiDeviceError::DelayNotSupported);
|
||||
}
|
||||
|
||||
let mut bus = self.bus.lock().await;
|
||||
bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let cs_drop = OnDrop::new(|| {
|
||||
// Please see comment in SpiDevice for an explanation of this drop handler.
|
||||
let _ = self.cs.set_high();
|
||||
});
|
||||
|
||||
let op_res = 'ops: {
|
||||
for op in operations {
|
||||
let res = match op {
|
||||
Operation::Read(buf) => bus.read(buf).await,
|
||||
Operation::Write(buf) => bus.write(buf).await,
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write).await,
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayNs(_) => unreachable!(),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayNs(ns) => match bus.flush().await {
|
||||
Err(e) => Err(e),
|
||||
Ok(()) => {
|
||||
embassy_time::Timer::after_nanos(*ns as _).await;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
};
|
||||
if let Err(e) = res {
|
||||
break 'ops Err(e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush().await;
|
||||
cs_drop.defuse();
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
}
|
||||
}
|
||||
187
embassy-embedded-hal/src/shared_bus/blocking/i2c.rs
Normal file
187
embassy-embedded-hal/src/shared_bus/blocking/i2c.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
//! Blocking shared I2C bus
|
||||
//!
|
||||
//! # Example (nrf52)
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
|
||||
//!
|
||||
//! static I2C_BUS: StaticCell<NoopMutex<RefCell<Twim<TWISPI0>>>> = StaticCell::new();
|
||||
//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default());
|
||||
//! let i2c_bus = NoopMutex::new(RefCell::new(i2c));
|
||||
//! let i2c_bus = I2C_BUS.init(i2c_bus);
|
||||
//!
|
||||
//! let i2c_dev1 = I2cDevice::new(i2c_bus);
|
||||
//! let mpu = Mpu6050::new(i2c_dev1);
|
||||
//! ```
|
||||
|
||||
use core::cell::RefCell;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embedded_hal_1::i2c::{ErrorType, I2c, Operation};
|
||||
|
||||
use crate::shared_bus::I2cDeviceError;
|
||||
use crate::SetConfig;
|
||||
|
||||
/// I2C device on a shared bus.
|
||||
pub struct I2cDevice<'a, M: RawMutex, BUS> {
|
||||
bus: &'a Mutex<M, RefCell<BUS>>,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS> I2cDevice<'a, M, BUS> {
|
||||
/// Create a new `I2cDevice`.
|
||||
pub fn new(bus: &'a Mutex<M, RefCell<BUS>>) -> Self {
|
||||
Self { bus }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS> ErrorType for I2cDevice<'a, M, BUS>
|
||||
where
|
||||
BUS: ErrorType,
|
||||
{
|
||||
type Error = I2cDeviceError<BUS::Error>;
|
||||
}
|
||||
|
||||
impl<M, BUS> I2c for I2cDevice<'_, M, BUS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: I2c,
|
||||
{
|
||||
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.bus
|
||||
.lock(|bus| bus.borrow_mut().read(address, buffer).map_err(I2cDeviceError::I2c))
|
||||
}
|
||||
|
||||
fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.bus
|
||||
.lock(|bus| bus.borrow_mut().write(address, bytes).map_err(I2cDeviceError::I2c))
|
||||
}
|
||||
|
||||
fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
bus.borrow_mut()
|
||||
.write_read(address, wr_buffer, rd_buffer)
|
||||
.map_err(I2cDeviceError::I2c)
|
||||
})
|
||||
}
|
||||
|
||||
fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
bus.borrow_mut()
|
||||
.transaction(address, operations)
|
||||
.map_err(I2cDeviceError::I2c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Write for I2cDevice<'_, M, BUS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: embedded_hal_02::blocking::i2c::Write<Error = E>,
|
||||
{
|
||||
type Error = I2cDeviceError<E>;
|
||||
|
||||
fn write<'w>(&mut self, addr: u8, bytes: &'w [u8]) -> Result<(), Self::Error> {
|
||||
self.bus
|
||||
.lock(|bus| bus.borrow_mut().write(addr, bytes).map_err(I2cDeviceError::I2c))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Read for I2cDevice<'_, M, BUS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: embedded_hal_02::blocking::i2c::Read<Error = E>,
|
||||
{
|
||||
type Error = I2cDeviceError<E>;
|
||||
|
||||
fn read<'w>(&mut self, addr: u8, bytes: &'w mut [u8]) -> Result<(), Self::Error> {
|
||||
self.bus
|
||||
.lock(|bus| bus.borrow_mut().read(addr, bytes).map_err(I2cDeviceError::I2c))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::WriteRead for I2cDevice<'_, M, BUS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: embedded_hal_02::blocking::i2c::WriteRead<Error = E>,
|
||||
{
|
||||
type Error = I2cDeviceError<E>;
|
||||
|
||||
fn write_read<'w>(&mut self, addr: u8, bytes: &'w [u8], buffer: &'w mut [u8]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
bus.borrow_mut()
|
||||
.write_read(addr, bytes, buffer)
|
||||
.map_err(I2cDeviceError::I2c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// I2C device on a shared bus, with its own configuration.
|
||||
///
|
||||
/// This is like [`I2cDevice`], with an additional bus configuration that's applied
|
||||
/// to the bus before each use using [`SetConfig`]. This allows different
|
||||
/// devices on the same bus to use different communication settings.
|
||||
pub struct I2cDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig> {
|
||||
bus: &'a Mutex<M, RefCell<BUS>>,
|
||||
config: BUS::Config,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS: SetConfig> I2cDeviceWithConfig<'a, M, BUS> {
|
||||
/// Create a new `I2cDeviceWithConfig`.
|
||||
pub fn new(bus: &'a Mutex<M, RefCell<BUS>>, config: BUS::Config) -> Self {
|
||||
Self { bus, config }
|
||||
}
|
||||
|
||||
/// Change the device's config at runtime
|
||||
pub fn set_config(&mut self, config: BUS::Config) {
|
||||
self.config = config;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M, BUS> ErrorType for I2cDeviceWithConfig<'a, M, BUS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: ErrorType + SetConfig,
|
||||
{
|
||||
type Error = I2cDeviceError<BUS::Error>;
|
||||
}
|
||||
|
||||
impl<M, BUS> I2c for I2cDeviceWithConfig<'_, M, BUS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: I2c + SetConfig,
|
||||
{
|
||||
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
|
||||
bus.read(address, buffer).map_err(I2cDeviceError::I2c)
|
||||
})
|
||||
}
|
||||
|
||||
fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
|
||||
bus.write(address, bytes).map_err(I2cDeviceError::I2c)
|
||||
})
|
||||
}
|
||||
|
||||
fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
|
||||
bus.write_read(address, wr_buffer, rd_buffer)
|
||||
.map_err(I2cDeviceError::I2c)
|
||||
})
|
||||
}
|
||||
|
||||
fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> {
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
|
||||
bus.transaction(address, operations).map_err(I2cDeviceError::I2c)
|
||||
})
|
||||
}
|
||||
}
|
||||
3
embassy-embedded-hal/src/shared_bus/blocking/mod.rs
Normal file
3
embassy-embedded-hal/src/shared_bus/blocking/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
//! Blocking shared bus implementations for embedded-hal
|
||||
pub mod i2c;
|
||||
pub mod spi;
|
||||
167
embassy-embedded-hal/src/shared_bus/blocking/spi.rs
Normal file
167
embassy-embedded-hal/src/shared_bus/blocking/spi.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
//! Blocking shared SPI bus
|
||||
//!
|
||||
//! # Example (nrf52)
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
||||
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
|
||||
//!
|
||||
//! static SPI_BUS: StaticCell<NoopMutex<RefCell<Spim<SPI3>>>> = StaticCell::new();
|
||||
//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default());
|
||||
//! let spi_bus = NoopMutex::new(RefCell::new(spi));
|
||||
//! let spi_bus = SPI_BUS.init(spi_bus);
|
||||
//!
|
||||
//! // Device 1, using embedded-hal compatible driver for ST7735 LCD display
|
||||
//! 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(), false, 160, 128);
|
||||
//! ```
|
||||
|
||||
use core::cell::RefCell;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::RawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embedded_hal_1::digital::OutputPin;
|
||||
use embedded_hal_1::spi::{self, Operation, SpiBus};
|
||||
|
||||
use crate::shared_bus::SpiDeviceError;
|
||||
use crate::SetConfig;
|
||||
|
||||
/// SPI device on a shared bus.
|
||||
pub struct SpiDevice<'a, M: RawMutex, BUS, CS> {
|
||||
bus: &'a Mutex<M, RefCell<BUS>>,
|
||||
cs: CS,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> {
|
||||
/// Create a new `SpiDevice`.
|
||||
pub fn new(bus: &'a Mutex<M, RefCell<BUS>>, cs: CS) -> Self {
|
||||
Self { bus, cs }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS>
|
||||
where
|
||||
BUS: spi::ErrorType,
|
||||
CS: OutputPin,
|
||||
{
|
||||
type Error = SpiDeviceError<BUS::Error, CS::Error>;
|
||||
}
|
||||
|
||||
impl<BUS, M, CS, Word> embedded_hal_1::spi::SpiDevice<Word> for SpiDevice<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: SpiBus<Word>,
|
||||
CS: OutputPin,
|
||||
Word: Copy + 'static,
|
||||
{
|
||||
fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
|
||||
if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
|
||||
return Err(SpiDeviceError::DelayNotSupported);
|
||||
}
|
||||
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res = operations.iter_mut().try_for_each(|op| match op {
|
||||
Operation::Read(buf) => bus.read(buf),
|
||||
Operation::Write(buf) => bus.write(buf),
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write),
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayNs(_) => unreachable!(),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayNs(ns) => {
|
||||
embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _));
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush();
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
Ok(op_res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// SPI device on a shared bus, with its own configuration.
|
||||
///
|
||||
/// This is like [`SpiDevice`], with an additional bus configuration that's applied
|
||||
/// to the bus before each use using [`SetConfig`]. This allows different
|
||||
/// devices on the same bus to use different communication settings.
|
||||
pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> {
|
||||
bus: &'a Mutex<M, RefCell<BUS>>,
|
||||
cs: CS,
|
||||
config: BUS::Config,
|
||||
}
|
||||
|
||||
impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> {
|
||||
/// Create a new `SpiDeviceWithConfig`.
|
||||
pub fn new(bus: &'a Mutex<M, RefCell<BUS>>, cs: CS, config: BUS::Config) -> Self {
|
||||
Self { bus, cs, config }
|
||||
}
|
||||
|
||||
/// Change the device's config at runtime
|
||||
pub fn set_config(&mut self, config: BUS::Config) {
|
||||
self.config = config;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: spi::ErrorType + SetConfig,
|
||||
CS: OutputPin,
|
||||
{
|
||||
type Error = SpiDeviceError<BUS::Error, CS::Error>;
|
||||
}
|
||||
|
||||
impl<BUS, M, CS, Word> embedded_hal_1::spi::SpiDevice<Word> for SpiDeviceWithConfig<'_, M, BUS, CS>
|
||||
where
|
||||
M: RawMutex,
|
||||
BUS: SpiBus<Word> + SetConfig,
|
||||
CS: OutputPin,
|
||||
Word: Copy + 'static,
|
||||
{
|
||||
fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
|
||||
if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
|
||||
return Err(SpiDeviceError::DelayNotSupported);
|
||||
}
|
||||
|
||||
self.bus.lock(|bus| {
|
||||
let mut bus = bus.borrow_mut();
|
||||
bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
|
||||
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
|
||||
|
||||
let op_res = operations.iter_mut().try_for_each(|op| match op {
|
||||
Operation::Read(buf) => bus.read(buf),
|
||||
Operation::Write(buf) => bus.write(buf),
|
||||
Operation::Transfer(read, write) => bus.transfer(read, write),
|
||||
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
|
||||
#[cfg(not(feature = "time"))]
|
||||
Operation::DelayNs(_) => unreachable!(),
|
||||
#[cfg(feature = "time")]
|
||||
Operation::DelayNs(ns) => {
|
||||
embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _));
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
// On failure, it's important to still flush and deassert CS.
|
||||
let flush_res = bus.flush();
|
||||
let cs_res = self.cs.set_high();
|
||||
|
||||
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
|
||||
flush_res.map_err(SpiDeviceError::Spi)?;
|
||||
cs_res.map_err(SpiDeviceError::Cs)?;
|
||||
Ok(op_res)
|
||||
})
|
||||
}
|
||||
}
|
||||
59
embassy-embedded-hal/src/shared_bus/mod.rs
Normal file
59
embassy-embedded-hal/src/shared_bus/mod.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
//! Shared bus implementations
|
||||
use core::fmt::Debug;
|
||||
|
||||
use embedded_hal_1::{i2c, spi};
|
||||
|
||||
pub mod asynch;
|
||||
pub mod blocking;
|
||||
|
||||
/// Error returned by I2C device implementations in this crate.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum I2cDeviceError<BUS> {
|
||||
/// An operation on the inner I2C bus failed.
|
||||
I2c(BUS),
|
||||
/// Configuration of the inner I2C bus failed.
|
||||
Config,
|
||||
}
|
||||
|
||||
impl<BUS> i2c::Error for I2cDeviceError<BUS>
|
||||
where
|
||||
BUS: i2c::Error + Debug,
|
||||
{
|
||||
fn kind(&self) -> i2c::ErrorKind {
|
||||
match self {
|
||||
Self::I2c(e) => e.kind(),
|
||||
Self::Config => i2c::ErrorKind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by SPI device implementations in this crate.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum SpiDeviceError<BUS, CS> {
|
||||
/// An operation on the inner SPI bus failed.
|
||||
Spi(BUS),
|
||||
/// Setting the value of the Chip Select (CS) pin failed.
|
||||
Cs(CS),
|
||||
/// Delay operations are not supported when the `time` Cargo feature is not enabled.
|
||||
DelayNotSupported,
|
||||
/// The SPI bus could not be configured.
|
||||
Config,
|
||||
}
|
||||
|
||||
impl<BUS, CS> spi::Error for SpiDeviceError<BUS, CS>
|
||||
where
|
||||
BUS: spi::Error + Debug,
|
||||
CS: Debug,
|
||||
{
|
||||
fn kind(&self) -> spi::ErrorKind {
|
||||
match self {
|
||||
Self::Spi(e) => e.kind(),
|
||||
Self::Cs(_) => spi::ErrorKind::Other,
|
||||
Self::DelayNotSupported => spi::ErrorKind::Other,
|
||||
Self::Config => spi::ErrorKind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user