diff --git a/examples/tiva-c-launchpad/src/main.rs b/examples/tiva-c-launchpad/src/main.rs index 60539ed..cfe6b69 100644 --- a/examples/tiva-c-launchpad/src/main.rs +++ b/examples/tiva-c-launchpad/src/main.rs @@ -5,6 +5,9 @@ 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::*}; #[entry] @@ -20,6 +23,17 @@ fn main() -> ! { let mut porta = p.GPIO_PORTA.split(&sc.power_control); + let mut eeprom = Eeprom::new(p.EEPROM, &sc.power_control); + + match eeprom_test_all(&mut eeprom) { + Ok(_) => { + // Huzzah! + } + Err(code) => { + panic!("Error detected while testing EEPROM: {}", code); + } + } + // Activate UART let mut uart = hal::serial::Serial::uart0( p.UART0, @@ -43,3 +57,74 @@ 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/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 new file mode 100644 index 0000000..2ea8b3e --- /dev/null +++ b/tm4c-hal/src/eeprom.rs @@ -0,0 +1,142 @@ +//! 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 { + /// 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, +} + +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 +#[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 + } + + /// 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; + + /// 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 + /// given EepromAddress + fn address_to_word_index(&self, block: &EepromAddress) -> Result; +} + +/// Erase functions of the EEPROM +pub trait Erase { + /// 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>; + + /// Erase (zero out) a block + fn erase_block(&mut self, block: usize) -> 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 c74bfb2..511cf2e 100644 --- a/tm4c-hal/src/lib.rs +++ b/tm4c-hal/src/lib.rs @@ -6,6 +6,7 @@ pub mod bb; pub mod delay; +pub mod eeprom; pub mod gpio; pub mod i2c; pub mod serial; diff --git a/tm4c123x-hal/src/eeprom.rs b/tm4c123x-hal/src/eeprom.rs new file mode 100644 index 0000000..af45154 --- /dev/null +++ b/tm4c123x-hal/src/eeprom.rs @@ -0,0 +1,400 @@ +//! Code for the EEProm module. + +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}; + +// 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 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 }; + + // 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. + 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"); + } + + 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 + 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"); + } + + 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 + } + + /// Set the block register + fn set_block(&self, block: usize) -> Result<(), EepromError> { + if self.is_busy() { + return Err(EepromError::Busy); + } + + if block < EEPROM_NUM_BLOCKS { + unsafe { + self.eeprom.eeblock.write(|w| w.bits(block as u32)); + } + + // Changing blocks requires a small delay, see Section 8.2.4.1 Timing Considerations + delay(4); + + self.wait(); + + Ok(()) + } else { + Err(EepromError::BlockOutOfBounds) + } + } + + /// Set the offset register + fn set_offset(&self, offset: usize) -> Result<(), EepromError> { + if self.is_busy() { + return Err(EepromError::Busy); + } + + 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) -> Result<(), EepromError> { + self.wait(); + 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. 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) => { + return start_word_address * BYTES_PER_WORD + length_bytes + < EEPROM_END_ADDRESS_BYTES; + } + Err(_) => { + return false; + } + } + } + + /// 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 { + 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_index_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_index(&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); + } + + // Check if the address is valid and if the data will fit + if !self.is_access_valid(address, data.len()) { + return Err(EepromError::WriteWouldOverflow); + } + + self.set_block_and_offset(address)?; + + let chunk_iter = data.chunks_exact(4); + let leftover_bytes = chunk_iter.remainder(); + let mut address_copy = *address; + + 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.increment_offset(&mut address_copy)?; + } + + // 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 + .eerdwr + .write(|w| w.bits(u32::from_le_bytes(buffer))); + } + } + + self.wait(); + + Ok(()) + } +} + +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 !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)?; + + let mut byte_offset = 0; + + for _i in 0..num_words { + self.wait(); + + 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; + byte_offset += 1; + } + } + + if leftover_bytes != 0 { + self.wait(); + + 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]; + byte_offset += 1; + } + } + + self.wait(); + + Ok(()) + } +} + +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.is_access_valid(address, length_bytes) { + return Err(EepromError::WriteWouldOverflow); + } + + 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(()) + } + + 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(()) + } +} diff --git a/tm4c123x-hal/src/lib.rs b/tm4c123x-hal/src/lib.rs index 89bed4e..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;