Skip to content

Commit

Permalink
EEPROM bug fixes
Browse files Browse the repository at this point in the history
- Added example / test cases to main.rs in tiva-c-launchpad
- Cleaned up EepromError enum
- Added impl Display to EepromError
- Added increment() method to EepromAddress, used to increment
the block and offset.
- Improved documentation and function names
- Improved the Eeprom peripheral initialization to match the datasheet
- Added a delay function to introduce clock cycle delays as called for in
the datasheet. See code comments for datasheet sections that point
out the delays.
- Added erase and erase_block impl.
- Much improved test code.
  • Loading branch information
amcelroy committed May 17, 2023
1 parent ffe90f4 commit 442035d
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 79 deletions.
95 changes: 61 additions & 34 deletions examples/tiva-c-launchpad/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch
use core::fmt::Write;
use cortex_m_rt::entry;
use tm4c123x_hal::{self as hal, prelude::*};
use tm4c123x_hal::eeprom::Eeprom;
use tm4c123x_hal::eeprom::{Eeprom, Read, Write as EepromWrite, EepromAddress, EepromError, Erase, Blocks};

#[entry]
fn main() -> ! {
Expand All @@ -21,43 +21,14 @@ fn main() -> ! {

let mut porta = p.GPIO_PORTA.split(&sc.power_control);

let eeprom = Eeprom::new(p.EEPROM, &sc.power_control);
let mut eeprom = Eeprom::new(p.EEPROM, &sc.power_control);

let mut eeprom_buffer = [0 as u8; 64]; // 64 byte read buffer

let address = eeprom.word_offset_to_address(52).unwrap();
assert_eq!(address.block(), 3, "Word 50 should be in block 3, offset 4");
assert_eq!(address.offset(), 4, "Word 50 should be in block 3, offset 4");

let word_index = eeprom.address_to_word_offset(&address).unwrap();
assert_eq!(word_index, 52, "Word index for block 3, offset 4 should be 52");

// Simplest case, middle of a block, no straddle
let test_array_1: [u8; 4] = [1, 2, 3, 4];
test_write_read(eeprom, &address, &test_array_1, &mut buffer);

// Test boundry conditions for access that straddles a block
let test_array_2: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let address_straddle_block = EepromAddress::new(0, 12);
test_write_read(eeprom, &address_straddle_block, &test_array_2, &mut buffer);

// Test case for unaligned EEPROM access.
let unaligned_read_write_address = EepromAddress::new(0, 13);
match eeprom.write(&unaligned_read_write_address, &test_array_2) {
match eeprom_test_all(&mut eeprom) {
Ok(_) => {
assert!(true, "Unaligned word read / write should NOT be ok");
// Huzzah!
}
Err(code) => {
assert_eq!(code, EepromError::OffsetShouldBeWordAligned, "This write test should fail due to alignment issues");
}
}

match eeprom.read(&unaligned_read_write_address, 4, &mut buffer) {
Ok(_) => {
assert!(true, "Unaligned word read / write should NOT be ok");
}
Err(code) => {
assert_eq!(code, EepromError::OffsetShouldBeWordAligned, "This read test should fail due to alignment issues");
panic!("Error detected while testing EEPROM: {}", code);
}
}

Expand All @@ -84,3 +55,59 @@ fn main() -> ! {
counter = counter.wrapping_add(1);
}
}

pub fn eeprom_test_write_read(eeprom: &mut Eeprom, address: &EepromAddress, data_to_write: &[u8], read_buffer: &mut [u8]) -> Result<(), EepromError> {
eeprom.write(address, &data_to_write)?;
eeprom.read(address, data_to_write.len(), read_buffer)?;

for (i, byte) in data_to_write.iter().enumerate() {
assert_eq!(*byte, read_buffer[i], "Read data differs from written data");
}

Ok(())
}

pub fn eeprom_test_all(eeprom: &mut Eeprom) -> Result<(), EepromError> {
let mut buffer = [0 as u8; 64]; // 64 byte read buffer

// Sanity check for simple mapping from word offset to an EepromAddress
let mut address = eeprom.word_index_to_address(52).unwrap();
assert_eq!(address.block(), 3, "Word 52 should be in block 3, offset 4");
assert_eq!(address.offset(), 4, "Word 52 should be in block 3, offset 4");

// Sanity check for EepromAddress to word offset
let word_index = eeprom.address_to_word_index(&address).unwrap();
assert_eq!(word_index, 52, "Word index for block 3, offset 4 should be 52");

// Simplest case, middle of a block, no straddle
let test_array_1: [u8; 4] = [1, 2, 3, 4];
eeprom_test_write_read(eeprom, &mut address, &test_array_1, &mut buffer)?;

// Test boundry conditions for access that straddles a block
let test_array_2: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let address_straddle_block = EepromAddress::new(0, 15);
eeprom_test_write_read(eeprom, &address_straddle_block, &test_array_2, &mut buffer)?;

eeprom.erase(&address_straddle_block, test_array_2.len())?;
eeprom.read(&address_straddle_block, test_array_2.len(), &mut buffer)?;
for i in 0..test_array_2.len() {
assert_eq!(buffer[i], 0, "Buffer should be all 0's")
}

// Test the block erase using the straddle address and data
eeprom.write(&address_straddle_block, &test_array_2)?;
eeprom.erase_block(0)?;
eeprom.read(&address_straddle_block, test_array_2.len(), &mut buffer)?;
for i in 0..test_array_2.len() {
match i {
0..=3 => {
assert_eq!(buffer[i], 0, "Buffer[0..3] should be all 0's");
}
_ => {
assert_eq!(buffer[i], test_array_2[i], "Buffer[4..9] should match test_array_2")
}
}
}

Ok(())
}
68 changes: 56 additions & 12 deletions tm4c-hal/src/eeprom.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,49 @@
//! Code for the EEProm module.
//!
//! Tested on a TM4C123 Tiva C Series Launchpad
//!
//! Note: This code manually increments the EEBLOCK and EEOFFSET registers
//! after each read and write instead of using the EERDWRINC register. The
//! debugger was giving inconsistent register results for the EEOFFSET register
//! after using EERDWRINC. Also, the EERDWRINC does not increment the block in
//! the case of a wrap of the offset, so it seems less useful for data that
//! spans blocks.
//!
//! This flexibility comes at the cost of efficiency, as the
//! datasheet calls for at least 4 cycles of delay after setting the EEBLOCK
//! register.
/// Possible errors for the Flash memory module
#[derive(Debug, PartialEq)]
pub enum EepromError{
pub enum EepromError {
/// Eeprom is not finished
Busy,
/// Address is out of bounds
AddressOutOfBounds,
/// Block is out of bounds
BlockOutOfBounds,
/// Offset is out of bounds
OffsetOutOfBounds,
/// Indicates that writing data would exceed the EEPROM memory space
WriteWouldOverflow,
/// Indicates that reading data would exceed the EEPROM memory space
ReadWouldOverflow,
/// Requesting to read more data than the provided buffer can hold
ReadBufferTooSmall,
/// Access to EEPROM needs to be word aligned
OffsetShouldBeWordAligned
}

impl core::fmt::Display for EepromError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
EepromError::Busy => write!(f, "Eeprom is busy"),
EepromError::AddressOutOfBounds => write!(f, "Address is out of bounds"),
EepromError::BlockOutOfBounds => write!(f, "Block is out of bounds"),
EepromError::OffsetOutOfBounds => write!(f, "Offset is out of bounds"),
EepromError::WriteWouldOverflow => write!(f, "Writing this data would overflow the EEPROM"),
EepromError::ReadWouldOverflow => write!(f, "Reading this data would overflow the EEPROM"),
EepromError::ReadBufferTooSmall => write!(f, "Allocated buffer too small for reading"),
}
}
}

/// Struct used to pack the block and offset
Expand All @@ -43,28 +70,45 @@ impl EepromAddress {
pub fn offset(&self) -> usize {
self.offset
}

/// Increments the offset by one, if that would cause an overflow, increment the block. If
/// both the block and offset wrap, the output for the new block and offset
/// will both be 0.
pub fn increment(&mut self, offset_size: usize, block_size: usize) -> &mut Self {
self.offset += 1;
if self.offset >= offset_size {
self.offset = 0;
self.block += 1;
if self.block >= block_size {
self.block = 0;
}
}

self
}
}

/// Series of traits to make access blocks easier
pub trait Blocks {
/// Returns the blocksize for read / write to the flash
fn block_size(&self) -> Result<usize, EepromError> ;

/// Returns the block for a given address. This should always be
/// a power of 2 and rounded down to the nearest block.
fn word_offset_to_address(&self, address: usize) -> Result<EepromAddress, EepromError>;
/// Returns the EepromAddress for a given index. Valid indexes are 0 to
/// EEPROM_END_ADDRESS_WORDS.
fn word_index_to_address(&self, index: usize) -> Result<EepromAddress, EepromError>;

/// Gives the starting address of a block
fn address_to_word_offset(&self, block: &EepromAddress) -> Result<usize, EepromError>;
/// Gives the the word index (0 to EEPROM_END_ADDRESS_WORDS) for a
/// given EepromAddress
fn address_to_word_index(&self, block: &EepromAddress) -> Result<usize, EepromError>;
}

/// Erase functions of the EEPROM
pub trait Erase {
/// Erase a block
fn erase(&self, block: EepromAddress) -> Result<(), EepromError>;
/// Erase (zero out) data starting at an address spanning a length of bytes (not words!)
fn erase(&mut self, address: &EepromAddress, length_bytes: usize) -> Result<(), EepromError>;

/// Mass erase the EEPROM
fn mass_erase(&self) -> nb::Result<(), EepromError>;
/// Erase (zero out) a block
fn erase_block(&mut self, block: usize) -> Result<(), EepromError>;
}

/// Check if the Eeprom is busy
Expand Down
Loading

0 comments on commit 442035d

Please sign in to comment.