Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

## [0.6.0] - 2022-07-25
### Added
- Add support for polling during write operations
- Add functions `enable_polling` and `disable_polling`

## [0.5.0] - 2022-01-20
### Added
- Add support for STM M24C01 and M24C02.
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ resolver = "2"
embedded-hal = "0.2"
embedded-storage = "0.2.0"
nb = "1.0.0"
embedded-timeout-macros = "0.3"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
embedded-timeout-macros = "0.3"


[dev-dependencies]
linux-embedded-hal = "0.3"
Expand Down
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ This driver allows you to:
- Read the current memory address (please read notes). See: `read_current_address()`.
- Write a byte to a memory address. See: `write_byte()`.
- Write a byte array (up to a memory page) to a memory address. See: `write_page()`.
- Enable polling to improve performance if the eeprom supports it(active by default). See: `enable_polling()`.
- Disable polling if you don't want to block the bus. See: `disable_polling()`.

Can be used at least with the devices listed below.

Expand Down Expand Up @@ -102,6 +104,44 @@ fn main() {
}
```

By default polling will be used to wait for the eeprom during a write operation( if the eeprom supports it). This will block the bus. If this is not suitable for your usecase, you can disable it:

```rust
use eeprom24x::{Eeprom24x, SlaveAddr};
use embedded_hal::blocking::delay::DelayMs;
use linux_embedded_hal::{Delay, I2cdev};

fn main() {
let dev = I2cdev::new("/dev/i2c-1").unwrap();
let address = SlaveAddr::default();
let mut eeprom = Eeprom24x::new_24x256(dev, address);
let memory_address = 0x1234;
let data = 0xAB;

// Polling active
eeprom.write_byte(memory_address, data).unwrap();

// Polling disabled
eeprom.disable_polling();
eeprom.write_byte(memory_address, data).unwrap();

// Polling reenabled
eeprom.enable_polling();
eeprom.write_byte(memory_address, data).unwrap();

Delay.delay_ms(5u16);

let read_data = eeprom.read_byte(memory_address).unwrap();

println!(
"Read memory address: {}, retrieved content: {}",
memory_address, &read_data
);

let _dev = eeprom.destroy(); // Get the I2C device back
}
```

## Support

For questions, issues, feature requests, and other changes, please file an
Expand Down
70 changes: 45 additions & 25 deletions src/eeprom24x.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{addr_size, page_size, private, Eeprom24x, Error, SlaveAddr};
use crate::{
addr_size, page_size, private, Eeprom24x, Error,
PollingSupport::{self, NoPolling, Polling},
SlaveAddr,
};
use core::marker::PhantomData;
use embedded_hal::blocking::i2c::{Write, WriteRead};

Expand Down Expand Up @@ -124,60 +128,76 @@ where
i2c,
address,
address_bits: 4,
polling: NoPolling,
polling_active: false,
_ps: PhantomData,
_as: PhantomData,
}
}
}

macro_rules! impl_create {
( $dev:expr, $part:expr, $address_bits:expr, $create:ident ) => {
( $dev:expr, $part:expr, $address_bits:expr, $create:ident, $HPS:ident ) => {
impl_create! {
@gen [$create, $address_bits,
concat!("Create a new instance of a ", $dev, " device (e.g. ", $part, ")")]
concat!("Create a new instance of a ", $dev, " device (e.g. ", $part, ")"), $HPS]
}
};

(@gen [$create:ident, $address_bits:expr, $doc:expr] ) => {
(@gen [$create:ident, $address_bits:expr, $doc:expr, $HPS:ident] ) => {
#[doc = $doc]
pub fn $create(i2c: I2C, address: SlaveAddr) -> Self {
Self::new(i2c, address, $address_bits)
Self::new(i2c, address, $address_bits, $HPS)
}
};
}

// This macro could be simplified once https://github.com/rust-lang/rust/issues/42863 is fixed.
macro_rules! impl_for_page_size {
( $AS:ident, $addr_bytes:expr, $PS:ident, $page_size:expr,
$( [ $dev:expr, $part:expr, $address_bits:expr, $create:ident ] ),* ) => {
$( [ $dev:expr, $part:expr, $address_bits:expr, $create:ident, $HPS:ident ] ),* ) => {
impl_for_page_size!{
@gen [$AS, $addr_bytes, $PS, $page_size,
concat!("Specialization for devices with a page size of ", stringify!($page_size), " bytes."),
concat!("Create generic instance for devices with a page size of ", stringify!($page_size), " bytes."),
$( [ $dev, $part, $address_bits, $create ] ),* ]
$( [ $dev, $part, $address_bits, $create, $HPS ] ),* ]
}
};

(@gen [$AS:ident, $addr_bytes:expr, $PS:ident, $page_size:expr, $doc_impl:expr, $doc_new:expr,
$( [ $dev:expr, $part:expr, $address_bits:expr, $create:ident ] ),* ] ) => {
$( [ $dev:expr, $part:expr, $address_bits:expr, $create:ident, $HPS:ident] ),* ] ) => {
#[doc = $doc_impl]
impl<I2C, E> Eeprom24x<I2C, page_size::$PS, addr_size::$AS>
where
I2C: Write<Error = E>
I2C: Write<Error = E>,
{
$(
impl_create!($dev, $part, $address_bits, $create);
impl_create!($dev, $part, $address_bits, $create, $HPS);
)*

#[doc = $doc_new]
fn new(i2c: I2C, address: SlaveAddr, address_bits: u8) -> Self {
fn new(i2c: I2C, address: SlaveAddr, address_bits: u8, polling_support: PollingSupport) -> Self {
if let Polling = polling_support {
Eeprom24x {
i2c,
address,
address_bits,
polling: polling_support,
polling_active: true,
_ps: PhantomData,
_as: PhantomData,
}
}else{
Eeprom24x {
i2c,
address,
address_bits,
polling: polling_support,
polling_active: false,
_ps: PhantomData,
_as: PhantomData,
}
}
}
}

Expand Down Expand Up @@ -257,48 +277,48 @@ impl_for_page_size!(
1,
B8,
8,
["24x01", "AT24C01", 7, new_24x01],
["24x02", "AT24C02", 8, new_24x02]
["24x01", "AT24C01", 7, new_24x01, NoPolling],
["24x02", "AT24C02", 8, new_24x02, NoPolling]
);
impl_for_page_size!(
OneByte,
1,
B16,
16,
["24x04", "AT24C04", 9, new_24x04],
["24x08", "AT24C08", 10, new_24x08],
["24x16", "AT24C16", 11, new_24x16],
["M24C01", "M24C01", 7, new_m24x01],
["M24C02", "M24C02", 8, new_m24x02]
["24x04", "AT24C04", 9, new_24x04, NoPolling],
["24x08", "AT24C08", 10, new_24x08, NoPolling],
["24x16", "AT24C16", 11, new_24x16, NoPolling],
["M24C01", "M24C01", 7, new_m24x01, Polling],
["M24C02", "M24C02", 8, new_m24x02, Polling]
);
impl_for_page_size!(
TwoBytes,
2,
B32,
32,
["24x32", "AT24C32", 12, new_24x32],
["24x64", "AT24C64", 13, new_24x64]
["24x32", "AT24C32", 12, new_24x32, NoPolling],
["24x64", "AT24C64", 13, new_24x64, NoPolling]
);
impl_for_page_size!(
TwoBytes,
2,
B64,
64,
["24x128", "AT24C128", 14, new_24x128],
["24x256", "AT24C256", 15, new_24x256]
["24x128", "AT24C128", 14, new_24x128, NoPolling],
["24x256", "AT24C256", 15, new_24x256, NoPolling]
);
impl_for_page_size!(
TwoBytes,
2,
B128,
128,
["24x512", "AT24C512", 16, new_24x512]
["24x512", "AT24C512", 16, new_24x512, NoPolling]
);
impl_for_page_size!(
TwoBytes,
2,
B256,
256,
["24xM01", "AT24CM01", 17, new_24xm01],
["24xM02", "AT24CM02", 18, new_24xm02]
["24xM01", "AT24CM01", 17, new_24xm01, NoPolling],
["24xM02", "AT24CM02", 18, new_24xm02, NoPolling]
);
13 changes: 13 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,15 @@ pub mod page_size {
pub struct B256(());
}

/// Eeprom supports polling
#[derive(Debug)]
enum PollingSupport {
/// has polling support
Polling,
/// has no polling support
NoPolling,
}

/// EEPROM24X driver
#[derive(Debug)]
pub struct Eeprom24x<I2C, PS, AS> {
Expand All @@ -226,6 +235,10 @@ pub struct Eeprom24x<I2C, PS, AS> {
address: SlaveAddr,
/// Number or bits used for memory addressing.
address_bits: u8,
/// Has polling Support
polling: PollingSupport,
/// Polling is active
polling_active: bool,
/// Page size marker type.
_ps: PhantomData<PS>,
/// Address size marker type.
Expand Down
55 changes: 46 additions & 9 deletions src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ where
// Furthermore, we also have to wait for the countdown before reading the eeprom again.
// Basically, we have to wait before any I2C access and ensure that the countdown is
// running again afterwards.
count_down.start(Duration::from_millis(0));
count_down.start(Duration::from_millis(5));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for this change?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess @eldruin is right and the initial countdown should be zero. The rationale is already given in the comment above.

Storage { eeprom, count_down }
}
}
Expand All @@ -36,6 +36,48 @@ impl<I2C, PS, AS, CD> Storage<I2C, PS, AS, CD> {
pub fn destroy(self) -> (I2C, CD) {
(self.eeprom.destroy(), self.count_down)
}

/// disables polling for the eeprom
pub fn disable_polling(&mut self) {
self.eeprom.polling_active = false;
}

/// enables polling for the current eeprom, if the eeprom supports it
pub fn enable_polling(&mut self) {
if let crate::PollingSupport::Polling = self.eeprom.polling {
self.eeprom.polling_active = true;
};
}

/// returns whether polling is currently active or not
pub fn get_polling_status(self) -> bool {
self.eeprom.polling_active
}
}

impl<I2C, E, PS, AS, CD> Storage<I2C, PS, AS, CD>
where
I2C: Write<Error = E> + WriteRead<Error = E>,
AS: MultiSizeAddr,
CD: CountDown<Time = Duration>,
{
fn wait(&mut self) -> Result<(), Error<E>> {
loop {
match self.count_down.wait() {
Err(nb::Error::WouldBlock) => {
if self.eeprom.polling_active {
match self.eeprom.read_byte(0) {
Ok(_) => break Ok(()), // done
Err(Error::I2C(_)) => {} // not ready, repeat
Err(e) => break Err(e),
}
}
}
Ok(_) => break Ok(()),
Err(_) => {} // timer never fails, we can ignore this case
}
}
}
}

impl<I2C, E, PS, AS, CD> embedded_storage::ReadStorage for Storage<I2C, PS, AS, CD>
Expand All @@ -47,7 +89,7 @@ where
type Error = Error<E>;

fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
let _ = nb::block!(self.count_down.wait()); // CountDown::wait() never fails
self.wait()?;
self.count_down.start(Duration::from_millis(0));
self.eeprom.read_data(offset, bytes)
}
Expand All @@ -70,19 +112,14 @@ where
}
let page_size = self.eeprom.page_size();
while !bytes.is_empty() {
let _ = nb::block!(self.count_down.wait()); // CountDown::wait() never fails
self.wait()?;

let this_page_offset = offset as usize % page_size;
let this_page_remaining = page_size - this_page_offset;
let chunk_size = min(bytes.len(), this_page_remaining);
self.eeprom.page_write(offset, &bytes[..chunk_size])?;
offset += chunk_size as u32;
bytes = &bytes[chunk_size..];
// TODO At least ST's eeproms allow polling, i.e. trying the next i2c access which will
// just be NACKed as long as the device is still busy. This could potentially speed up
// the write process.
// TODO Currently outdated comment:
// A (theoretically needless) delay after the last page write ensures that the user can
// call Storage::write() again immediately.
self.count_down.start(Duration::from_millis(5));
}
Ok(())
Expand Down
23 changes: 23 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,26 @@ macro_rules! for_all_writestorage_ics_with_capacity {
}
};
}

#[macro_export]
macro_rules! for_all_ics_with_polling {
($name:ident) => {
mod $name {
use super::*;
$name!(for_24x01, new_24x01, false);
$name!(for_m24x01, new_m24x01, true);
$name!(for_24x02, new_24x02, false);
$name!(for_m24x02, new_m24x02, true);
$name!(for_24x04, new_24x04, false);
$name!(for_24x08, new_24x08, false);
$name!(for_24x16, new_24x16, false);
$name!(for_24x32, new_24x32, false);
$name!(for_24x64, new_24x64, false);
$name!(for_24x128, new_24x128, false);
$name!(for_24x256, new_24x256, false);
$name!(for_24x512, new_24x512, false);
$name!(for_24xm01, new_24xm01, false);
$name!(for_24xm02, new_24xm02, false);
}
};
}
Loading