Skip to content

Commit

Permalink
Added EEPROM with test cases for the TM4C123
Browse files Browse the repository at this point in the history
  • Loading branch information
amcelroy committed May 16, 2023
1 parent 14d76a6 commit c950e58
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 0 deletions.
41 changes: 41 additions & 0 deletions examples/tiva-c-launchpad/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() -> ! {
Expand All @@ -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,
Expand Down
94 changes: 94 additions & 0 deletions tm4c-hal/src/eeprom.rs
Original file line number Diff line number Diff line change
@@ -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<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>;

/// Gives the starting address of a block
fn address_to_word_offset(&self, block: &EepromAddress) -> Result<usize, EepromError>;
}

/// 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>;
}





1 change: 1 addition & 0 deletions tm4c-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
231 changes: 231 additions & 0 deletions tm4c123x-hal/src/eeprom.rs
Original file line number Diff line number Diff line change
@@ -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<usize, EepromError> {
Ok(EEPROM_BLOCK_SIZE)
}

fn word_offset_to_address(&self, word_address: usize) -> Result<EepromAddress, EepromError> {
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<usize, EepromError> {
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)
}

}
}
1 change: 1 addition & 0 deletions tm4c123x-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ pub mod serial;
pub mod spi;
pub mod sysctl;
pub mod timer;
pub mod eeprom;

0 comments on commit c950e58

Please sign in to comment.