From c950e583aadfc32dd2a83d2daef67706b8d80637 Mon Sep 17 00:00:00 2001 From: Austin McElroy Date: Tue, 16 May 2023 14:40:10 -0500 Subject: [PATCH 1/7] Added EEPROM with test cases for the TM4C123 --- examples/tiva-c-launchpad/src/main.rs | 41 +++++ tm4c-hal/src/eeprom.rs | 94 +++++++++++ tm4c-hal/src/lib.rs | 1 + tm4c123x-hal/src/eeprom.rs | 231 ++++++++++++++++++++++++++ tm4c123x-hal/src/lib.rs | 1 + 5 files changed, 368 insertions(+) create mode 100644 tm4c-hal/src/eeprom.rs create mode 100644 tm4c123x-hal/src/eeprom.rs diff --git a/examples/tiva-c-launchpad/src/main.rs b/examples/tiva-c-launchpad/src/main.rs index 60539ed..70072bb 100644 --- a/examples/tiva-c-launchpad/src/main.rs +++ b/examples/tiva-c-launchpad/src/main.rs @@ -6,6 +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; #[entry] fn main() -> ! { @@ -20,6 +21,46 @@ fn main() -> ! { let mut porta = p.GPIO_PORTA.split(&sc.power_control); + let 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) { + Ok(_) => { + assert!(true, "Unaligned word read / write should NOT be ok"); + } + 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"); + } + } + // Activate UART let mut uart = hal::serial::Serial::uart0( p.UART0, diff --git a/tm4c-hal/src/eeprom.rs b/tm4c-hal/src/eeprom.rs new file mode 100644 index 0000000..68e0f45 --- /dev/null +++ b/tm4c-hal/src/eeprom.rs @@ -0,0 +1,94 @@ +//! Code for the EEProm module. + +/// Possible errors for the Flash memory module +#[derive(Debug, PartialEq)] +pub enum EepromError{ + /// Eeprom is not finished + Busy, + /// Address is out of bounds + AddressOutOfBounds, + /// Block is out of bounds + BlockOutOfBounds, + /// 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 +} + +/// Struct used to pack the block and offset +#[derive(Clone, Copy)] +pub struct EepromAddress { + /// Eeprom block + block: usize, + /// Eeprom offset in a block + offset: usize, +} + +impl EepromAddress { + /// Creates a new EepromAddres with configured block and offset + pub fn new(block: usize, offset: usize) -> Self { + EepromAddress { block, offset } + } + + /// Returns the block + pub fn block(&self) -> usize { + self.block + } + + /// Returns the offset + pub fn offset(&self) -> usize { + self.offset + } +} + +/// 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 ; + + /// 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; + + /// Gives the starting address of a block + fn address_to_word_offset(&self, block: &EepromAddress) -> Result; +} + +/// Erase functions of the EEPROM +pub trait Erase { + /// Erase a block + fn erase(&self, block: EepromAddress) -> Result<(), EepromError>; + + /// Mass erase the EEPROM + fn mass_erase(&self) -> nb::Result<(), EepromError>; +} + +/// Check if the Eeprom is busy +pub trait Busy { + /// Check the EEDONE register, true if busy + fn is_busy(&self) -> bool; + + /// Blocks until the EEPROM is not busy + fn wait(&self); +} + +/// Write data to the EEPROM +pub trait Write { + /// Write data to a flash address + fn write(&mut self, address: &EepromAddress, data: &[u8]) -> Result<(), EepromError>; +} + +/// Read data from the EEPROM +pub trait Read { + /// Eeprom Address to start reading data from + fn read(&mut self, address: &EepromAddress, bytes_to_read: usize, buffer: &mut [u8]) -> Result<(), EepromError>; +} + + + + + diff --git a/tm4c-hal/src/lib.rs b/tm4c-hal/src/lib.rs index 790e1a5..3cc10e7 100644 --- a/tm4c-hal/src/lib.rs +++ b/tm4c-hal/src/lib.rs @@ -11,6 +11,7 @@ pub mod i2c; pub mod serial; pub mod sysctl; pub mod time; +pub mod eeprom; ///! An internal macro to implement the GPIO functionality for each port #[macro_export] diff --git a/tm4c123x-hal/src/eeprom.rs b/tm4c123x-hal/src/eeprom.rs new file mode 100644 index 0000000..b55ed24 --- /dev/null +++ b/tm4c123x-hal/src/eeprom.rs @@ -0,0 +1,231 @@ +//! Code for the EEProm module. + +use core::{convert::TryInto}; + +use tm4c123x::{EEPROM}; +use crate::sysctl::{self}; +pub use tm4c_hal::eeprom::{Blocks, Busy, Write, Read, EepromError, EepromAddress}; + +// Number of EEPROM block on the TM4C123 +const EEPROM_BLOCK_SIZE : usize = 16; + +// Number of EEPROM blocks on the TM4C123 +const EEPROM_NUM_BLOCKS : usize = 32; + +// Total number of bytes in the EEPROM on the TM4C123 +const EEPROM_END_ADDRESS_BYTES : usize = 2048; + +// Total number of words in the EEPROM on the TM4C123 +const EEPROM_END_ADDRESS_WORDS : usize = 512; + +// Size of the EEPROM word in bytes +const BYTES_PER_WORD : usize = 4; + +/// Eeprom struct +pub struct Eeprom { + /// Eeprom registers + eeprom: EEPROM, +} + +impl Eeprom { + /// Configures a new EEPROM with a start / end address defined by the + /// user. + pub fn new(eeprom: EEPROM, _pc: &sysctl::PowerControl) -> Self { + + let final_eeprom = Eeprom { eeprom }; + + sysctl::control_power( + _pc, + sysctl::Domain::Eeprom, + tm4c_hal::sysctl::RunMode::Run, + tm4c_hal::sysctl::PowerState::On); + sysctl::reset(_pc, sysctl::Domain::Eeprom); + + final_eeprom.wait(); + + final_eeprom + } + + /// Set the block register + fn set_block(&self, block: usize) { + unsafe { + self.eeprom.eeblock.write(|w| { + w.bits(block as u32) + }); + + self.wait(); + } + } + + /// Set the offset register + fn set_offset(&self, offset: usize) { + unsafe { + self.eeprom.eeoffset.write(|w| { + w.bits(offset as u32) + }); + + self.wait(); + } + } + + /// Set the block and offset registers + fn set_block_and_offset(&self, address: &EepromAddress) { + self.set_block(address.block()); + self.set_offset(address.offset()); + } + + /// Checks if read / writing a certain number of bytes from an address is + /// valid. + fn validate_byte_array_bounds(&self, address: &EepromAddress, length_bytes: usize) -> bool { + // Check if the initial address is valid, then check byte length + match self.address_to_word_offset(&address) { + Ok(start_word_address) => { + return start_word_address*BYTES_PER_WORD + length_bytes < EEPROM_END_ADDRESS_BYTES; + } + Err(_) => { + return false; + } + } + } +} + +impl Busy for Eeprom { + fn is_busy(&self) -> bool { + self.eeprom.eedone.read().working().bit_is_set() + } + + fn wait(&self) { + while self.is_busy() == true {} + } +} + +impl Blocks for Eeprom { + fn block_size(&self) -> Result { + Ok(EEPROM_BLOCK_SIZE) + } + + fn word_offset_to_address(&self, word_address: usize) -> Result { + if word_address > EEPROM_END_ADDRESS_WORDS { + return Err(EepromError::AddressOutOfBounds); + }else{ + let block = word_address / EEPROM_BLOCK_SIZE; + let offset = word_address - (block * EEPROM_BLOCK_SIZE); + Ok(EepromAddress::new(block, offset)) + } + } + + fn address_to_word_offset(&self, block: &EepromAddress) -> Result { + if block.block() > EEPROM_NUM_BLOCKS || block.offset() > EEPROM_BLOCK_SIZE { + return Err(EepromError::BlockOutOfBounds); + }else{ + return Ok(block.block() * EEPROM_BLOCK_SIZE + block.offset()); + } + } +} + +impl Write for Eeprom { + fn write(&mut self, address: &EepromAddress, data: &[u8]) -> Result<(), EepromError> { + if self.is_busy() { + return Err(EepromError::Busy); + } + + if address.offset() % BYTES_PER_WORD != 0 { + return Err(EepromError::OffsetShouldBeWordAligned); + } + + // Check if the address is valid and if the data will fit + if self.validate_byte_array_bounds(address, data.len()) { + self.set_block_and_offset(address); + + let chunk_iter = data.chunks_exact(4); + let leftover_bytes = chunk_iter.remainder(); + + // Write the easy part using the auto increment register + for chunk in chunk_iter { + let tmp = u32::from_le_bytes(chunk.try_into().unwrap()); + + self.wait(); + + unsafe { + self.eeprom.eerdwrinc.write(|w| { + w.bits(tmp) + }); + } + } + + // Buffer the leftover bytes, if any, and write + if leftover_bytes.len() != 0 { + let mut buffer = [0 as u8; 4]; + for (i, byte) in leftover_bytes.iter().enumerate() { + buffer[i] = *byte; + } + + self.wait(); + + unsafe { + self.eeprom.eerdwrinc.write(|w| { + w.bits(u32::from_le_bytes(buffer)) + }); + } + } + + self.wait(); + + Ok(()) + }else{ + Err(EepromError::WriteWouldOverflow) + } + } +} + +impl Read for Eeprom { + fn read(&mut self, address: &EepromAddress, bytes_to_read: usize, buffer: &mut [u8]) -> Result<(), EepromError> { + if self.is_busy() { + return Err(EepromError::Busy); + } + + if bytes_to_read > buffer.len() { + return Err(EepromError::ReadBufferTooSmall); + } + + if address.offset() % BYTES_PER_WORD != 0 { + return Err(EepromError::OffsetShouldBeWordAligned); + } + + if self.validate_byte_array_bounds(&address, bytes_to_read) { + let num_words = bytes_to_read / BYTES_PER_WORD; + let leftover_bytes = bytes_to_read % BYTES_PER_WORD; + + self.set_block_and_offset(&address); + + let mut byte_offset = 0; + + for _i in 0..num_words { + self.wait(); + + let word_as_bytes = self.eeprom.eerdwrinc.read().bits().to_le_bytes(); + + for byte in word_as_bytes { + buffer[byte_offset ] = byte; + byte_offset += 1; + } + } + + if leftover_bytes != 0 { + self.wait(); + + let word_as_bytes = self.eeprom.eerdwrinc.read().bits().to_le_bytes(); + + for index in 0..leftover_bytes { + buffer[byte_offset] = word_as_bytes[index]; + byte_offset += 1; + } + } + + Ok(()) + }else{ + Err(EepromError::ReadWouldOverflow) + } + + } +} diff --git a/tm4c123x-hal/src/lib.rs b/tm4c123x-hal/src/lib.rs index 12137ba..09c4d9f 100644 --- a/tm4c123x-hal/src/lib.rs +++ b/tm4c123x-hal/src/lib.rs @@ -41,3 +41,4 @@ pub mod serial; pub mod spi; pub mod sysctl; pub mod timer; +pub mod eeprom; From 442035d213988bb052968e8ec0ada0586ae1a1b5 Mon Sep 17 00:00:00 2001 From: Austin McElroy Date: Wed, 17 May 2023 12:04:29 -0500 Subject: [PATCH 2/7] EEPROM bug fixes - 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. --- examples/tiva-c-launchpad/src/main.rs | 95 +++++++---- tm4c-hal/src/eeprom.rs | 68 ++++++-- tm4c123x-hal/src/eeprom.rs | 219 ++++++++++++++++++++++---- 3 files changed, 303 insertions(+), 79 deletions(-) diff --git a/examples/tiva-c-launchpad/src/main.rs b/examples/tiva-c-launchpad/src/main.rs index 70072bb..843097e 100644 --- a/examples/tiva-c-launchpad/src/main.rs +++ b/examples/tiva-c-launchpad/src/main.rs @@ -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() -> ! { @@ -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); } } @@ -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(()) +} diff --git a/tm4c-hal/src/eeprom.rs b/tm4c-hal/src/eeprom.rs index 68e0f45..0a86ff2 100644 --- a/tm4c-hal/src/eeprom.rs +++ b/tm4c-hal/src/eeprom.rs @@ -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 @@ -43,6 +70,22 @@ 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 @@ -50,21 +93,22 @@ pub trait Blocks { /// Returns the blocksize for read / write to the flash fn block_size(&self) -> Result ; - /// 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; + /// 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; - /// Gives the starting address of a block - fn address_to_word_offset(&self, block: &EepromAddress) -> Result; + /// Gives the the word index (0 to EEPROM_END_ADDRESS_WORDS) for a + /// given EepromAddress + fn address_to_word_index(&self, block: &EepromAddress) -> Result; } /// 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 diff --git a/tm4c123x-hal/src/eeprom.rs b/tm4c123x-hal/src/eeprom.rs index b55ed24..a6ae34b 100644 --- a/tm4c123x-hal/src/eeprom.rs +++ b/tm4c123x-hal/src/eeprom.rs @@ -4,7 +4,7 @@ use core::{convert::TryInto}; use tm4c123x::{EEPROM}; use crate::sysctl::{self}; -pub use tm4c_hal::eeprom::{Blocks, Busy, Write, Read, EepromError, EepromAddress}; +pub use tm4c_hal::eeprom::{Blocks, Busy, Write, Read, EepromError, EepromAddress, Erase}; // Number of EEPROM block on the TM4C123 const EEPROM_BLOCK_SIZE : usize = 16; @@ -34,51 +34,117 @@ impl Eeprom { let final_eeprom = Eeprom { eeprom }; + // See Section 8.2.4.2 EEPROM Initialization and Configuration + // in the datasheet: + + // 0. Power on the EEPROM peripheral sysctl::control_power( _pc, sysctl::Domain::Eeprom, tm4c_hal::sysctl::RunMode::Run, tm4c_hal::sysctl::PowerState::On); + + // 1. The datasheet calls for at least a 6 cycle delay before polling + // the working register. Need to make sure the loop isn't optimized + // out. + final_eeprom.delay(20); + + // 2. Poll busy + final_eeprom.wait(); + + // 3. Read PRETRY and ERETRY + if final_eeprom.eeprom.eesupp.read().eretry().bit_is_set() { + panic!("Eeprom ERETRY bit set, please investigate or stop using the EEPROM peripheral"); + } + + if final_eeprom.eeprom.eesupp.read().pretry().bit_is_set() { + panic!("Eeprom PRETRY bit set, please investigate or stop using the EEPROM peripheral"); + } + + // 4. Software reset sysctl::reset(_pc, sysctl::Domain::Eeprom); + // 5. Another delay + final_eeprom.delay(20); + + // 6. Poll busy final_eeprom.wait(); + // 7. Recheck PRETRY and ERETRY + if final_eeprom.eeprom.eesupp.read().eretry().bit_is_set() { + panic!("Eeprom ERETRY bit set, please investigate or stop using the EEPROM peripheral"); + } + + if final_eeprom.eeprom.eesupp.read().pretry().bit_is_set() { + panic!("Eeprom PRETRY bit set, please investigate or stop using the EEPROM peripheral"); + } + + // 8. All done final_eeprom } + /// The EERPOM has multiple instances where small delays are needed. This + /// function tries introduce unoptimizable delays + pub fn delay(&self, est_clk_cycles: usize) { + let mut unoptimizable_delay: u32 = 0; + + for _ in 0..est_clk_cycles { + unoptimizable_delay += 1; + unsafe { + core::ptr::read_volatile(&unoptimizable_delay); + } + } + } + /// Set the block register - fn set_block(&self, block: usize) { - unsafe { - self.eeprom.eeblock.write(|w| { - w.bits(block as u32) - }); + fn set_block(&self, block: usize) -> Result<(), EepromError> { + self.wait(); + + if block < EEPROM_NUM_BLOCKS { + unsafe { + self.eeprom.eeblock.write(|w| { + w.bits(block as u32) + }); + } - self.wait(); + // Changing blocks requires a small delay, see Section 8.2.4.1 Timing Considerations + self.delay(4); + + Ok(()) + }else{ + Err(EepromError::BlockOutOfBounds) } } /// Set the offset register - fn set_offset(&self, offset: usize) { - unsafe { - self.eeprom.eeoffset.write(|w| { - w.bits(offset as u32) - }); + fn set_offset(&self, offset: usize) -> Result<(), EepromError> { + self.wait(); + + if offset < EEPROM_BLOCK_SIZE { + unsafe { + self.eeprom.eeoffset.write(|w| { + w.bits(offset as u32) + }); + } - self.wait(); + Ok(()) + }else{ + Err(EepromError::OffsetOutOfBounds) } } /// Set the block and offset registers - fn set_block_and_offset(&self, address: &EepromAddress) { - self.set_block(address.block()); - self.set_offset(address.offset()); + fn set_block_and_offset(&self, address: &EepromAddress) -> Result<(), EepromError> { + self.set_block(address.block())?; + self.set_offset(address.offset())?; + Ok(()) } /// Checks if read / writing a certain number of bytes from an address is /// valid. fn validate_byte_array_bounds(&self, address: &EepromAddress, length_bytes: usize) -> bool { // Check if the initial address is valid, then check byte length - match self.address_to_word_offset(&address) { + match self.address_to_word_index(&address) { Ok(start_word_address) => { return start_word_address*BYTES_PER_WORD + length_bytes < EEPROM_END_ADDRESS_BYTES; } @@ -87,6 +153,19 @@ impl Eeprom { } } } + + /// Increments the block and offset by 1 word. Will wrap both the offset and + /// block to 0 if an increment would cause either to exceed their bounds. + /// + /// For example: + /// * Block 0, Offset, 1 -> Block 0, Offset 2 + /// * Block 0, Offset, 15 -> Block 1, Offset 0 + /// * Block 31, Offset, 15 -> Block 0, Offset 0 + fn increment_offset(&mut self, starting_address: &mut EepromAddress) -> Result<(), EepromError> { + starting_address.increment(EEPROM_BLOCK_SIZE, EEPROM_NUM_BLOCKS); + self.set_block_and_offset(&starting_address)?; + Ok(()) + } } impl Busy for Eeprom { @@ -104,7 +183,7 @@ impl Blocks for Eeprom { Ok(EEPROM_BLOCK_SIZE) } - fn word_offset_to_address(&self, word_address: usize) -> Result { + fn word_index_to_address(&self, word_address: usize) -> Result { if word_address > EEPROM_END_ADDRESS_WORDS { return Err(EepromError::AddressOutOfBounds); }else{ @@ -114,7 +193,7 @@ impl Blocks for Eeprom { } } - fn address_to_word_offset(&self, block: &EepromAddress) -> Result { + fn address_to_word_index(&self, block: &EepromAddress) -> Result { if block.block() > EEPROM_NUM_BLOCKS || block.offset() > EEPROM_BLOCK_SIZE { return Err(EepromError::BlockOutOfBounds); }else{ @@ -129,16 +208,13 @@ impl Write for Eeprom { return Err(EepromError::Busy); } - if address.offset() % BYTES_PER_WORD != 0 { - return Err(EepromError::OffsetShouldBeWordAligned); - } - // Check if the address is valid and if the data will fit if self.validate_byte_array_bounds(address, data.len()) { - self.set_block_and_offset(address); + self.set_block_and_offset(address)?; let chunk_iter = data.chunks_exact(4); let leftover_bytes = chunk_iter.remainder(); + let mut address_copy = *address; // Write the easy part using the auto increment register for chunk in chunk_iter { @@ -147,10 +223,12 @@ impl Write for Eeprom { self.wait(); unsafe { - self.eeprom.eerdwrinc.write(|w| { + self.eeprom.eerdwr.write(|w| { w.bits(tmp) }); } + + self.increment_offset(&mut address_copy)?; } // Buffer the leftover bytes, if any, and write @@ -163,7 +241,7 @@ impl Write for Eeprom { self.wait(); unsafe { - self.eeprom.eerdwrinc.write(|w| { + self.eeprom.eerdwr.write(|w| { w.bits(u32::from_le_bytes(buffer)) }); } @@ -187,23 +265,22 @@ impl Read for Eeprom { if bytes_to_read > buffer.len() { return Err(EepromError::ReadBufferTooSmall); } - - if address.offset() % BYTES_PER_WORD != 0 { - return Err(EepromError::OffsetShouldBeWordAligned); - } if self.validate_byte_array_bounds(&address, bytes_to_read) { let num_words = bytes_to_read / BYTES_PER_WORD; let leftover_bytes = bytes_to_read % BYTES_PER_WORD; + let mut address_copy = *address; - self.set_block_and_offset(&address); + self.set_block_and_offset(&address)?; let mut byte_offset = 0; for _i in 0..num_words { self.wait(); - let word_as_bytes = self.eeprom.eerdwrinc.read().bits().to_le_bytes(); + let word_as_bytes = self.eeprom.eerdwr.read().bits().to_le_bytes(); + + self.increment_offset(&mut address_copy)?; for byte in word_as_bytes { buffer[byte_offset ] = byte; @@ -214,7 +291,9 @@ impl Read for Eeprom { if leftover_bytes != 0 { self.wait(); - let word_as_bytes = self.eeprom.eerdwrinc.read().bits().to_le_bytes(); + let word_as_bytes = self.eeprom.eerdwr.read().bits().to_le_bytes(); + + self.increment_offset(&mut address_copy)?; for index in 0..leftover_bytes { buffer[byte_offset] = word_as_bytes[index]; @@ -222,6 +301,8 @@ impl Read for Eeprom { } } + self.wait(); + Ok(()) }else{ Err(EepromError::ReadWouldOverflow) @@ -229,3 +310,75 @@ impl Read for Eeprom { } } + +impl Erase for Eeprom { + fn erase(&mut self, address: &EepromAddress, length_bytes: usize) -> Result<(), EepromError> { + if self.is_busy() { + return Err(EepromError::Busy); + } + + if self.validate_byte_array_bounds(address, length_bytes) { + let num_words = length_bytes / BYTES_PER_WORD; + let leftover_bytes = length_bytes % BYTES_PER_WORD; + let mut address_copy = *address; + + self.set_block_and_offset(&address)?; + + let zero = 0 as u32; + for _i in 0..num_words { + self.wait(); + + unsafe { + self.eeprom.eerdwr.write(|w| { + w.bits(zero) + }); + } + + self.increment_offset(&mut address_copy)?; + } + + // Special case here, need to read-modify-write + if leftover_bytes != 0 { + self.wait(); + + let mut word = self.eeprom.eerdwr.read().bits().to_le_bytes(); + + for i in 0..leftover_bytes { + word[i] = 0; + } + + unsafe { + self.eeprom.eerdwr.write(|w| { + w.bits(u32::from_le_bytes(word)) + }); + } + } + + self.wait(); + + Ok(()) + }else{ + return Err(EepromError::WriteWouldOverflow); + } + + + } + + fn erase_block(&mut self, block: usize) -> Result<(), EepromError> { + if self.is_busy() { + return Err(EepromError::Busy); + } + + self.set_block(block)?; + + let mut address = EepromAddress::new(block, 0); + + let zeros = [0 as u8; EEPROM_BLOCK_SIZE*BYTES_PER_WORD]; + + self.write(&mut address, &zeros)?; + + self.wait(); + + Ok(()) + } +} \ No newline at end of file From 93f245774ce4c2a09d3d2430657506df0e21d81a Mon Sep 17 00:00:00 2001 From: Austin McElroy Date: Wed, 17 May 2023 12:19:13 -0500 Subject: [PATCH 3/7] Comments and more robust checking if the EEPROM is busy --- tm4c123x-hal/src/eeprom.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tm4c123x-hal/src/eeprom.rs b/tm4c123x-hal/src/eeprom.rs index a6ae34b..3e7438e 100644 --- a/tm4c123x-hal/src/eeprom.rs +++ b/tm4c123x-hal/src/eeprom.rs @@ -28,8 +28,7 @@ pub struct Eeprom { } impl Eeprom { - /// Configures a new EEPROM with a start / end address defined by the - /// user. + /// Configures a new EEPROM struct using the datasheet section 8.2.4.2 pub fn new(eeprom: EEPROM, _pc: &sysctl::PowerControl) -> Self { let final_eeprom = Eeprom { eeprom }; @@ -98,7 +97,9 @@ impl Eeprom { /// Set the block register fn set_block(&self, block: usize) -> Result<(), EepromError> { - self.wait(); + if self.is_busy() { + return Err(EepromError::Busy); + } if block < EEPROM_NUM_BLOCKS { unsafe { @@ -110,6 +111,8 @@ impl Eeprom { // Changing blocks requires a small delay, see Section 8.2.4.1 Timing Considerations self.delay(4); + self.wait(); + Ok(()) }else{ Err(EepromError::BlockOutOfBounds) @@ -118,7 +121,9 @@ impl Eeprom { /// Set the offset register fn set_offset(&self, offset: usize) -> Result<(), EepromError> { - self.wait(); + if self.is_busy() { + return Err(EepromError::Busy); + } if offset < EEPROM_BLOCK_SIZE { unsafe { @@ -127,6 +132,8 @@ impl Eeprom { }); } + self.wait(); + Ok(()) }else{ Err(EepromError::OffsetOutOfBounds) @@ -135,6 +142,10 @@ impl Eeprom { /// Set the block and offset registers fn set_block_and_offset(&self, address: &EepromAddress) -> Result<(), EepromError> { + if self.is_busy() { + return Err(EepromError::Busy); + } + self.set_block(address.block())?; self.set_offset(address.offset())?; Ok(()) @@ -161,7 +172,7 @@ impl Eeprom { /// * Block 0, Offset, 1 -> Block 0, Offset 2 /// * Block 0, Offset, 15 -> Block 1, Offset 0 /// * Block 31, Offset, 15 -> Block 0, Offset 0 - fn increment_offset(&mut self, starting_address: &mut EepromAddress) -> Result<(), EepromError> { + fn increment_offset(&mut self, starting_address: &mut EepromAddress) -> Result<(), EepromError> { starting_address.increment(EEPROM_BLOCK_SIZE, EEPROM_NUM_BLOCKS); self.set_block_and_offset(&starting_address)?; Ok(()) From f8db917a2a6c89265c6231f052db9e7a3b1c5560 Mon Sep 17 00:00:00 2001 From: Austin McElroy Date: Wed, 17 May 2023 12:22:28 -0500 Subject: [PATCH 4/7] Replaced busy check with wait in fn set_block_and_offset() --- tm4c123x-hal/src/eeprom.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tm4c123x-hal/src/eeprom.rs b/tm4c123x-hal/src/eeprom.rs index 3e7438e..bf67b46 100644 --- a/tm4c123x-hal/src/eeprom.rs +++ b/tm4c123x-hal/src/eeprom.rs @@ -142,10 +142,7 @@ impl Eeprom { /// Set the block and offset registers fn set_block_and_offset(&self, address: &EepromAddress) -> Result<(), EepromError> { - if self.is_busy() { - return Err(EepromError::Busy); - } - + self.wait(); self.set_block(address.block())?; self.set_offset(address.offset())?; Ok(()) From 8d392beee510eda7c7e223949fd8953472ff6e65 Mon Sep 17 00:00:00 2001 From: Austin McElroy Date: Fri, 19 May 2023 07:45:43 -0500 Subject: [PATCH 5/7] Formatting Changes - Updated Changlog, added EEPROM section - Linter / format fixes --- examples/tiva-c-launchpad/src/main.rs | 29 +++++-- tm4c-hal/README.md | 2 + tm4c-hal/src/eeprom.rs | 42 +++++----- tm4c-hal/src/lib.rs | 2 +- tm4c123x-hal/src/eeprom.rs | 114 +++++++++++++------------- tm4c123x-hal/src/lib.rs | 2 +- 6 files changed, 106 insertions(+), 85 deletions(-) diff --git a/examples/tiva-c-launchpad/src/main.rs b/examples/tiva-c-launchpad/src/main.rs index 843097e..cfe6b69 100644 --- a/examples/tiva-c-launchpad/src/main.rs +++ b/examples/tiva-c-launchpad/src/main.rs @@ -5,8 +5,10 @@ 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::eeprom::{ + Blocks, Eeprom, EepromAddress, EepromError, Erase, Read, Write as EepromWrite, +}; use tm4c123x_hal::{self as hal, prelude::*}; -use tm4c123x_hal::eeprom::{Eeprom, Read, Write as EepromWrite, EepromAddress, EepromError, Erase, Blocks}; #[entry] fn main() -> ! { @@ -56,7 +58,12 @@ fn main() -> ! { } } -pub fn eeprom_test_write_read(eeprom: &mut Eeprom, address: &EepromAddress, data_to_write: &[u8], read_buffer: &mut [u8]) -> Result<(), EepromError> { +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)?; @@ -73,11 +80,18 @@ pub fn eeprom_test_all(eeprom: &mut Eeprom) -> Result<(), EepromError> { // 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"); + 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"); + 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]; @@ -104,9 +118,12 @@ pub fn eeprom_test_all(eeprom: &mut Eeprom) -> Result<(), EepromError> { 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") + assert_eq!( + buffer[i], test_array_2[i], + "Buffer[4..9] should match test_array_2" + ) } - } + } } Ok(()) diff --git a/tm4c-hal/README.md b/tm4c-hal/README.md index 123f110..f75378d 100644 --- a/tm4c-hal/README.md +++ b/tm4c-hal/README.md @@ -7,6 +7,8 @@ depending on your processor. ## Changelog +* Basic EEPROM Read, Write, Erase added + ### Unreleased Changes ([Source](https://github.com/rust-embedded-community/tm4c-hal/tree/master/tm4c-hal) [Diff](https://github.com/rust-embedded-community/tm4c-hal/compare/tm4c-hal-0.4.1...master)) * Implement use of sealed traits by downstream crates (i.e. `tm4c123x-hal` and `tm4c129x-hal`) diff --git a/tm4c-hal/src/eeprom.rs b/tm4c-hal/src/eeprom.rs index 0a86ff2..2ea8b3e 100644 --- a/tm4c-hal/src/eeprom.rs +++ b/tm4c-hal/src/eeprom.rs @@ -1,17 +1,17 @@ //! 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. -//! +//! 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. +//! register. /// Possible errors for the Flash memory module #[derive(Debug, PartialEq)] @@ -39,8 +39,12 @@ impl core::fmt::Display for EepromError { 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::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"), } } @@ -71,7 +75,7 @@ impl EepromAddress { self.offset } - /// Increments the offset by one, if that would cause an overflow, increment the block. If + /// 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 { @@ -91,13 +95,13 @@ impl EepromAddress { /// 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 ; + fn block_size(&self) -> Result; /// 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; - /// Gives the the word index (0 to EEPROM_END_ADDRESS_WORDS) for a + /// Gives the the word index (0 to EEPROM_END_ADDRESS_WORDS) for a /// given EepromAddress fn address_to_word_index(&self, block: &EepromAddress) -> Result; } @@ -115,7 +119,7 @@ pub trait Erase { pub trait Busy { /// Check the EEDONE register, true if busy fn is_busy(&self) -> bool; - + /// Blocks until the EEPROM is not busy fn wait(&self); } @@ -129,10 +133,10 @@ pub trait Write { /// Read data from the EEPROM pub trait Read { /// Eeprom Address to start reading data from - fn read(&mut self, address: &EepromAddress, bytes_to_read: usize, buffer: &mut [u8]) -> Result<(), EepromError>; + fn read( + &mut self, + address: &EepromAddress, + bytes_to_read: usize, + buffer: &mut [u8], + ) -> Result<(), EepromError>; } - - - - - diff --git a/tm4c-hal/src/lib.rs b/tm4c-hal/src/lib.rs index 4da7d19..511cf2e 100644 --- a/tm4c-hal/src/lib.rs +++ b/tm4c-hal/src/lib.rs @@ -6,12 +6,12 @@ pub mod bb; pub mod delay; +pub mod eeprom; pub mod gpio; pub mod i2c; pub mod serial; pub mod sysctl; pub mod time; -pub mod eeprom; ///! An internal macro to implement the GPIO functionality for each port #[macro_export] diff --git a/tm4c123x-hal/src/eeprom.rs b/tm4c123x-hal/src/eeprom.rs index bf67b46..54c6905 100644 --- a/tm4c123x-hal/src/eeprom.rs +++ b/tm4c123x-hal/src/eeprom.rs @@ -1,25 +1,25 @@ //! Code for the EEProm module. -use core::{convert::TryInto}; +use core::convert::TryInto; -use tm4c123x::{EEPROM}; use crate::sysctl::{self}; -pub use tm4c_hal::eeprom::{Blocks, Busy, Write, Read, EepromError, EepromAddress, Erase}; +use tm4c123x::EEPROM; +pub use tm4c_hal::eeprom::{Blocks, Busy, EepromAddress, EepromError, Erase, Read, Write}; // Number of EEPROM block on the TM4C123 -const EEPROM_BLOCK_SIZE : usize = 16; +const EEPROM_BLOCK_SIZE: usize = 16; // Number of EEPROM blocks on the TM4C123 -const EEPROM_NUM_BLOCKS : usize = 32; +const EEPROM_NUM_BLOCKS: usize = 32; // Total number of bytes in the EEPROM on the TM4C123 -const EEPROM_END_ADDRESS_BYTES : usize = 2048; +const EEPROM_END_ADDRESS_BYTES: usize = 2048; // Total number of words in the EEPROM on the TM4C123 -const EEPROM_END_ADDRESS_WORDS : usize = 512; +const EEPROM_END_ADDRESS_WORDS: usize = 512; // Size of the EEPROM word in bytes -const BYTES_PER_WORD : usize = 4; +const BYTES_PER_WORD: usize = 4; /// Eeprom struct pub struct Eeprom { @@ -30,18 +30,18 @@ pub struct Eeprom { impl Eeprom { /// Configures a new EEPROM struct using the datasheet section 8.2.4.2 pub fn new(eeprom: EEPROM, _pc: &sysctl::PowerControl) -> Self { - let final_eeprom = Eeprom { eeprom }; - + // See Section 8.2.4.2 EEPROM Initialization and Configuration // in the datasheet: - // 0. Power on the EEPROM peripheral + // 0. Power on the EEPROM peripheral sysctl::control_power( - _pc, - sysctl::Domain::Eeprom, + _pc, + sysctl::Domain::Eeprom, tm4c_hal::sysctl::RunMode::Run, - tm4c_hal::sysctl::PowerState::On); + tm4c_hal::sysctl::PowerState::On, + ); // 1. The datasheet calls for at least a 6 cycle delay before polling // the working register. Need to make sure the loop isn't optimized @@ -103,9 +103,7 @@ impl Eeprom { if block < EEPROM_NUM_BLOCKS { unsafe { - self.eeprom.eeblock.write(|w| { - w.bits(block as u32) - }); + self.eeprom.eeblock.write(|w| w.bits(block as u32)); } // Changing blocks requires a small delay, see Section 8.2.4.1 Timing Considerations @@ -114,7 +112,7 @@ impl Eeprom { self.wait(); Ok(()) - }else{ + } else { Err(EepromError::BlockOutOfBounds) } } @@ -127,15 +125,13 @@ impl Eeprom { if offset < EEPROM_BLOCK_SIZE { unsafe { - self.eeprom.eeoffset.write(|w| { - w.bits(offset as u32) - }); + self.eeprom.eeoffset.write(|w| w.bits(offset as u32)); } self.wait(); Ok(()) - }else{ + } else { Err(EepromError::OffsetOutOfBounds) } } @@ -154,7 +150,8 @@ impl Eeprom { // Check if the initial address is valid, then check byte length match self.address_to_word_index(&address) { Ok(start_word_address) => { - return start_word_address*BYTES_PER_WORD + length_bytes < EEPROM_END_ADDRESS_BYTES; + return start_word_address * BYTES_PER_WORD + length_bytes + < EEPROM_END_ADDRESS_BYTES; } Err(_) => { return false; @@ -164,12 +161,15 @@ impl Eeprom { /// Increments the block and offset by 1 word. Will wrap both the offset and /// block to 0 if an increment would cause either to exceed their bounds. - /// + /// /// For example: - /// * Block 0, Offset, 1 -> Block 0, Offset 2 + /// * Block 0, Offset, 1 -> Block 0, Offset 2 /// * Block 0, Offset, 15 -> Block 1, Offset 0 /// * Block 31, Offset, 15 -> Block 0, Offset 0 - fn increment_offset(&mut self, starting_address: &mut EepromAddress) -> Result<(), EepromError> { + fn increment_offset( + &mut self, + starting_address: &mut EepromAddress, + ) -> Result<(), EepromError> { starting_address.increment(EEPROM_BLOCK_SIZE, EEPROM_NUM_BLOCKS); self.set_block_and_offset(&starting_address)?; Ok(()) @@ -187,14 +187,14 @@ impl Busy for Eeprom { } impl Blocks for Eeprom { - fn block_size(&self) -> Result { + fn block_size(&self) -> Result { Ok(EEPROM_BLOCK_SIZE) } fn word_index_to_address(&self, word_address: usize) -> Result { if word_address > EEPROM_END_ADDRESS_WORDS { return Err(EepromError::AddressOutOfBounds); - }else{ + } else { let block = word_address / EEPROM_BLOCK_SIZE; let offset = word_address - (block * EEPROM_BLOCK_SIZE); Ok(EepromAddress::new(block, offset)) @@ -204,7 +204,7 @@ impl Blocks for Eeprom { fn address_to_word_index(&self, block: &EepromAddress) -> Result { if block.block() > EEPROM_NUM_BLOCKS || block.offset() > EEPROM_BLOCK_SIZE { return Err(EepromError::BlockOutOfBounds); - }else{ + } else { return Ok(block.block() * EEPROM_BLOCK_SIZE + block.offset()); } } @@ -219,7 +219,7 @@ impl Write for Eeprom { // Check if the address is valid and if the data will fit if self.validate_byte_array_bounds(address, data.len()) { self.set_block_and_offset(address)?; - + let chunk_iter = data.chunks_exact(4); let leftover_bytes = chunk_iter.remainder(); let mut address_copy = *address; @@ -227,13 +227,11 @@ impl Write for Eeprom { // Write the easy part using the auto increment register for chunk in chunk_iter { let tmp = u32::from_le_bytes(chunk.try_into().unwrap()); - + self.wait(); - + unsafe { - self.eeprom.eerdwr.write(|w| { - w.bits(tmp) - }); + self.eeprom.eerdwr.write(|w| w.bits(tmp)); } self.increment_offset(&mut address_copy)?; @@ -249,23 +247,28 @@ impl Write for Eeprom { self.wait(); unsafe { - self.eeprom.eerdwr.write(|w| { - w.bits(u32::from_le_bytes(buffer)) - }); + self.eeprom + .eerdwr + .write(|w| w.bits(u32::from_le_bytes(buffer))); } } self.wait(); Ok(()) - }else{ + } else { Err(EepromError::WriteWouldOverflow) } } } impl Read for Eeprom { - fn read(&mut self, address: &EepromAddress, bytes_to_read: usize, buffer: &mut [u8]) -> Result<(), EepromError> { + fn read( + &mut self, + address: &EepromAddress, + bytes_to_read: usize, + buffer: &mut [u8], + ) -> Result<(), EepromError> { if self.is_busy() { return Err(EepromError::Busy); } @@ -273,7 +276,7 @@ impl Read for Eeprom { if bytes_to_read > buffer.len() { return Err(EepromError::ReadBufferTooSmall); } - + if self.validate_byte_array_bounds(&address, bytes_to_read) { let num_words = bytes_to_read / BYTES_PER_WORD; let leftover_bytes = bytes_to_read % BYTES_PER_WORD; @@ -290,8 +293,8 @@ impl Read for Eeprom { self.increment_offset(&mut address_copy)?; - for byte in word_as_bytes { - buffer[byte_offset ] = byte; + for byte in word_as_bytes { + buffer[byte_offset] = byte; byte_offset += 1; } } @@ -303,7 +306,7 @@ impl Read for Eeprom { self.increment_offset(&mut address_copy)?; - for index in 0..leftover_bytes { + for index in 0..leftover_bytes { buffer[byte_offset] = word_as_bytes[index]; byte_offset += 1; } @@ -312,10 +315,9 @@ impl Read for Eeprom { self.wait(); Ok(()) - }else{ + } else { Err(EepromError::ReadWouldOverflow) } - } } @@ -324,7 +326,7 @@ impl Erase for Eeprom { if self.is_busy() { return Err(EepromError::Busy); } - + if self.validate_byte_array_bounds(address, length_bytes) { let num_words = length_bytes / BYTES_PER_WORD; let leftover_bytes = length_bytes % BYTES_PER_WORD; @@ -337,9 +339,7 @@ impl Erase for Eeprom { self.wait(); unsafe { - self.eeprom.eerdwr.write(|w| { - w.bits(zero) - }); + self.eeprom.eerdwr.write(|w| w.bits(zero)); } self.increment_offset(&mut address_copy)?; @@ -356,20 +356,18 @@ impl Erase for Eeprom { } unsafe { - self.eeprom.eerdwr.write(|w| { - w.bits(u32::from_le_bytes(word)) - }); + self.eeprom + .eerdwr + .write(|w| w.bits(u32::from_le_bytes(word))); } } self.wait(); Ok(()) - }else{ + } else { return Err(EepromError::WriteWouldOverflow); } - - } fn erase_block(&mut self, block: usize) -> Result<(), EepromError> { @@ -381,7 +379,7 @@ impl Erase for Eeprom { let mut address = EepromAddress::new(block, 0); - let zeros = [0 as u8; EEPROM_BLOCK_SIZE*BYTES_PER_WORD]; + let zeros = [0 as u8; EEPROM_BLOCK_SIZE * BYTES_PER_WORD]; self.write(&mut address, &zeros)?; @@ -389,4 +387,4 @@ impl Erase for Eeprom { Ok(()) } -} \ No newline at end of file +} diff --git a/tm4c123x-hal/src/lib.rs b/tm4c123x-hal/src/lib.rs index 04e63ea..394212b 100644 --- a/tm4c123x-hal/src/lib.rs +++ b/tm4c123x-hal/src/lib.rs @@ -40,6 +40,7 @@ mod sealed { impl Sealed for () {} } +pub mod eeprom; pub mod gpio; pub mod hib; pub mod i2c; @@ -49,4 +50,3 @@ pub mod serial; pub mod spi; pub mod sysctl; pub mod timer; -pub mod eeprom; From 58697ea50c2e904a5e8b23c7e96f3a1d676ef656 Mon Sep 17 00:00:00 2001 From: Austin McElroy Date: Fri, 19 May 2023 14:41:24 -0500 Subject: [PATCH 6/7] Updates from collaborators - Renamed validate_byte_array_bounds to is_access_valid for improved readability - Removed custom delay function, using the cortex_m::asm::delay function now. - Renamed _pc to pc since it was being used in Eeprom::new() - Simplified branching for functions that invoke the is_access_valid(...) function. - Comment improvement --- tm4c123x-hal/src/eeprom.rs | 212 ++++++++++++++++++------------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/tm4c123x-hal/src/eeprom.rs b/tm4c123x-hal/src/eeprom.rs index 54c6905..b485418 100644 --- a/tm4c123x-hal/src/eeprom.rs +++ b/tm4c123x-hal/src/eeprom.rs @@ -3,6 +3,7 @@ use core::convert::TryInto; use crate::sysctl::{self}; +use cortex_m::asm::delay; use tm4c123x::EEPROM; pub use tm4c_hal::eeprom::{Blocks, Busy, EepromAddress, EepromError, Erase, Read, Write}; @@ -29,7 +30,7 @@ pub struct Eeprom { impl Eeprom { /// Configures a new EEPROM struct using the datasheet section 8.2.4.2 - pub fn new(eeprom: EEPROM, _pc: &sysctl::PowerControl) -> Self { + pub fn new(eeprom: EEPROM, pc: &sysctl::PowerControl) -> Self { let final_eeprom = Eeprom { eeprom }; // See Section 8.2.4.2 EEPROM Initialization and Configuration @@ -37,7 +38,7 @@ impl Eeprom { // 0. Power on the EEPROM peripheral sysctl::control_power( - _pc, + pc, sysctl::Domain::Eeprom, tm4c_hal::sysctl::RunMode::Run, tm4c_hal::sysctl::PowerState::On, @@ -46,12 +47,18 @@ impl Eeprom { // 1. The datasheet calls for at least a 6 cycle delay before polling // the working register. Need to make sure the loop isn't optimized // out. - final_eeprom.delay(20); + delay(20); // 2. Poll busy final_eeprom.wait(); // 3. Read PRETRY and ERETRY + // Note: If either bit is set, the data sheet indicates this is a pretty severe + // error with the EEPROM and the EEPROM shouldn't be used, which is why + // a panic!() is chosen over an error. There could be issues with the + // chip, core voltage, or EEPROM; regardless, it probably should be + // investigated further. + // See section 8.2.4.2 if final_eeprom.eeprom.eesupp.read().eretry().bit_is_set() { panic!("Eeprom ERETRY bit set, please investigate or stop using the EEPROM peripheral"); } @@ -61,15 +68,21 @@ impl Eeprom { } // 4. Software reset - sysctl::reset(_pc, sysctl::Domain::Eeprom); + sysctl::reset(pc, sysctl::Domain::Eeprom); // 5. Another delay - final_eeprom.delay(20); + delay(20); // 6. Poll busy final_eeprom.wait(); // 7. Recheck PRETRY and ERETRY + // Note: If either bit is set, the data sheet indicates this is a pretty severe + // error with the EEPROM and the EEPROM shouldn't be used, which is why + // a panic!() is chosen over an error. There could be issues with the + // chip, core voltage, or EEPROM; regardless, it probably should be + // investigated further. + // See section 8.2.4.2 if final_eeprom.eeprom.eesupp.read().eretry().bit_is_set() { panic!("Eeprom ERETRY bit set, please investigate or stop using the EEPROM peripheral"); } @@ -82,19 +95,6 @@ impl Eeprom { final_eeprom } - /// The EERPOM has multiple instances where small delays are needed. This - /// function tries introduce unoptimizable delays - pub fn delay(&self, est_clk_cycles: usize) { - let mut unoptimizable_delay: u32 = 0; - - for _ in 0..est_clk_cycles { - unoptimizable_delay += 1; - unsafe { - core::ptr::read_volatile(&unoptimizable_delay); - } - } - } - /// Set the block register fn set_block(&self, block: usize) -> Result<(), EepromError> { if self.is_busy() { @@ -107,7 +107,7 @@ impl Eeprom { } // Changing blocks requires a small delay, see Section 8.2.4.1 Timing Considerations - self.delay(4); + delay(4); self.wait(); @@ -145,8 +145,9 @@ impl Eeprom { } /// Checks if read / writing a certain number of bytes from an address is - /// valid. - fn validate_byte_array_bounds(&self, address: &EepromAddress, length_bytes: usize) -> bool { + /// valid. Returns true if EEPROM access is valid, false if there + /// are any issues (overflow or invalid address). + fn is_access_valid(&self, address: &EepromAddress, length_bytes: usize) -> bool { // Check if the initial address is valid, then check byte length match self.address_to_word_index(&address) { Ok(start_word_address) => { @@ -217,48 +218,47 @@ impl Write for Eeprom { } // Check if the address is valid and if the data will fit - if self.validate_byte_array_bounds(address, data.len()) { - self.set_block_and_offset(address)?; + if !self.is_access_valid(address, data.len()) { + return Err(EepromError::WriteWouldOverflow); + } - let chunk_iter = data.chunks_exact(4); - let leftover_bytes = chunk_iter.remainder(); - let mut address_copy = *address; + self.set_block_and_offset(address)?; - // Write the easy part using the auto increment register - for chunk in chunk_iter { - let tmp = u32::from_le_bytes(chunk.try_into().unwrap()); + let chunk_iter = data.chunks_exact(4); + let leftover_bytes = chunk_iter.remainder(); + let mut address_copy = *address; - self.wait(); + for chunk in chunk_iter { + let tmp = u32::from_le_bytes(chunk.try_into().unwrap()); - unsafe { - self.eeprom.eerdwr.write(|w| w.bits(tmp)); - } + self.wait(); - self.increment_offset(&mut address_copy)?; + unsafe { + self.eeprom.eerdwr.write(|w| w.bits(tmp)); } - // Buffer the leftover bytes, if any, and write - if leftover_bytes.len() != 0 { - let mut buffer = [0 as u8; 4]; - for (i, byte) in leftover_bytes.iter().enumerate() { - buffer[i] = *byte; - } - - self.wait(); + self.increment_offset(&mut address_copy)?; + } - unsafe { - self.eeprom - .eerdwr - .write(|w| w.bits(u32::from_le_bytes(buffer))); - } + // Buffer the leftover bytes, if any, and write + if leftover_bytes.len() != 0 { + let mut buffer = [0 as u8; 4]; + for (i, byte) in leftover_bytes.iter().enumerate() { + buffer[i] = *byte; } self.wait(); - Ok(()) - } else { - Err(EepromError::WriteWouldOverflow) + unsafe { + self.eeprom + .eerdwr + .write(|w| w.bits(u32::from_le_bytes(buffer))); + } } + + self.wait(); + + Ok(()) } } @@ -277,47 +277,47 @@ impl Read for Eeprom { return Err(EepromError::ReadBufferTooSmall); } - if self.validate_byte_array_bounds(&address, bytes_to_read) { - let num_words = bytes_to_read / BYTES_PER_WORD; - let leftover_bytes = bytes_to_read % BYTES_PER_WORD; - let mut address_copy = *address; + if !self.is_access_valid(&address, bytes_to_read) { + return Err(EepromError::ReadWouldOverflow); + } + + let num_words = bytes_to_read / BYTES_PER_WORD; + let leftover_bytes = bytes_to_read % BYTES_PER_WORD; + let mut address_copy = *address; - self.set_block_and_offset(&address)?; + self.set_block_and_offset(&address)?; - let mut byte_offset = 0; + let mut byte_offset = 0; - for _i in 0..num_words { - self.wait(); + for _i in 0..num_words { + self.wait(); - let word_as_bytes = self.eeprom.eerdwr.read().bits().to_le_bytes(); + let word_as_bytes = self.eeprom.eerdwr.read().bits().to_le_bytes(); - self.increment_offset(&mut address_copy)?; + self.increment_offset(&mut address_copy)?; - for byte in word_as_bytes { - buffer[byte_offset] = byte; - byte_offset += 1; - } + for byte in word_as_bytes { + buffer[byte_offset] = byte; + byte_offset += 1; } + } - if leftover_bytes != 0 { - self.wait(); + if leftover_bytes != 0 { + self.wait(); - let word_as_bytes = self.eeprom.eerdwr.read().bits().to_le_bytes(); + let word_as_bytes = self.eeprom.eerdwr.read().bits().to_le_bytes(); - self.increment_offset(&mut address_copy)?; + self.increment_offset(&mut address_copy)?; - for index in 0..leftover_bytes { - buffer[byte_offset] = word_as_bytes[index]; - byte_offset += 1; - } + for index in 0..leftover_bytes { + buffer[byte_offset] = word_as_bytes[index]; + byte_offset += 1; } + } - self.wait(); + self.wait(); - Ok(()) - } else { - Err(EepromError::ReadWouldOverflow) - } + Ok(()) } } @@ -327,47 +327,47 @@ impl Erase for Eeprom { return Err(EepromError::Busy); } - if self.validate_byte_array_bounds(address, length_bytes) { - let num_words = length_bytes / BYTES_PER_WORD; - let leftover_bytes = length_bytes % BYTES_PER_WORD; - let mut address_copy = *address; + if !self.is_access_valid(address, length_bytes) { + return Err(EepromError::WriteWouldOverflow); + } - self.set_block_and_offset(&address)?; + let num_words = length_bytes / BYTES_PER_WORD; + let leftover_bytes = length_bytes % BYTES_PER_WORD; + let mut address_copy = *address; - let zero = 0 as u32; - for _i in 0..num_words { - self.wait(); + self.set_block_and_offset(&address)?; - unsafe { - self.eeprom.eerdwr.write(|w| w.bits(zero)); - } + let zero = 0 as u32; + for _i in 0..num_words { + self.wait(); - self.increment_offset(&mut address_copy)?; + unsafe { + self.eeprom.eerdwr.write(|w| w.bits(zero)); } - // Special case here, need to read-modify-write - if leftover_bytes != 0 { - self.wait(); + self.increment_offset(&mut address_copy)?; + } + + // Special case here, need to read-modify-write + if leftover_bytes != 0 { + self.wait(); - let mut word = self.eeprom.eerdwr.read().bits().to_le_bytes(); + let mut word = self.eeprom.eerdwr.read().bits().to_le_bytes(); - for i in 0..leftover_bytes { - word[i] = 0; - } + for i in 0..leftover_bytes { + word[i] = 0; + } - unsafe { - self.eeprom - .eerdwr - .write(|w| w.bits(u32::from_le_bytes(word))); - } + unsafe { + self.eeprom + .eerdwr + .write(|w| w.bits(u32::from_le_bytes(word))); } + } - self.wait(); + self.wait(); - Ok(()) - } else { - return Err(EepromError::WriteWouldOverflow); - } + Ok(()) } fn erase_block(&mut self, block: usize) -> Result<(), EepromError> { From a2b9d11e58d8a2bf2518fc010c6efaa2267aad96 Mon Sep 17 00:00:00 2001 From: Austin McElroy Date: Fri, 19 May 2023 16:16:53 -0500 Subject: [PATCH 7/7] Improved comments for Eeprom::new() --- tm4c123x-hal/src/eeprom.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tm4c123x-hal/src/eeprom.rs b/tm4c123x-hal/src/eeprom.rs index b485418..af45154 100644 --- a/tm4c123x-hal/src/eeprom.rs +++ b/tm4c123x-hal/src/eeprom.rs @@ -29,7 +29,17 @@ pub struct Eeprom { } impl Eeprom { - /// Configures a new EEPROM struct using the datasheet section 8.2.4.2 + /// Configures a new EEPROM struct using the datasheet section 8.2.4.2. Note: + /// this function may panic if PRETRY and ERETRY in the EESUPP register + /// are set during the initialization. Section 8.2.4.2 explains further: + /// + /// If the PRETRY or ERETRY bits are set in the EESUPP register, the EEPROM + /// was unable to recover its state. If power is stable when this occurs, + /// this indicates a fatal error and is likely an indication that the EEPROM + /// memory has exceeded its specified lifetime write/erase specification. + /// If the supply voltage is unstable when this return code is observed, + /// retrying the operation once the voltage is stabilized may clear the + /// error. pub fn new(eeprom: EEPROM, pc: &sysctl::PowerControl) -> Self { let final_eeprom = Eeprom { eeprom };