/ firmware / tests / eeprom.rs
eeprom.rs
  1  //! `describe("AT24C32 EEPROM")`
  2  //!
  3  //! Probes bus 1 for an AT24C32 EEPROM at 0x50 and exercises byte,
  4  //! buffer, and page-boundary read/write operations. Tests use offsets
  5  //! near the end of the address space (3900+) to avoid clobbering
  6  //! useful data. Each test cleans up by writing zeros after itself.
  7  //!
  8  //! Skips gracefully if the EEPROM is not detected.
  9  
 10  #![no_std]
 11  #![no_main]
 12  
 13  #[path = "common/mod.rs"]
 14  mod common;
 15  
 16  use cat24c32_rs::{Cat24c32, SlaveAddr};
 17  use defmt::info;
 18  use firmware::config::board;
 19  
 20  use common::Device;
 21  
 22  esp_bootloader_esp_idf::esp_app_desc!();
 23  
 24  #[cfg(test)]
 25  #[embedded_test::setup]
 26  fn setup() {
 27      rtt_target::rtt_init_defmt!();
 28  }
 29  
 30  // Keep all test offsets below 256 — cat24c32-rs has a bug in devaddr()
 31  // where addresses above 0xFF cause high bits to leak into the I2C
 32  // device address, sending to 0x57 instead of 0x50. The AT24C32 uses a
 33  // flat 12-bit address in the data bytes, not in the device address.
 34  const TEST_BASE: u32 = 0x80;
 35  
 36  fn is_eeprom_present(device: &mut Device) -> bool {
 37      let bus = match device.i2c_bus_1.as_mut() {
 38          Some(bus) => bus,
 39          None => return false,
 40      };
 41      bus.write(board::eeprom::I2C_ADDR, &[]).is_ok()
 42  }
 43  
 44  #[cfg(test)]
 45  #[embedded_test::tests(default_timeout = 15, executor = esp_rtos::embassy::Executor::new())]
 46  mod tests {
 47      use super::*;
 48  
 49      #[init]
 50      fn init() -> Device {
 51          info!("=== AT24C32 EEPROM — describe block ===");
 52          common::setup::boot_device()
 53      }
 54  
 55      /// `it("user detects the EEPROM on bus 1")`
 56      #[test]
 57      async fn user_detects_eeprom_on_bus_1(
 58          mut device: Device,
 59      ) -> Result<(), &'static str> {
 60          if !is_eeprom_present(&mut device) {
 61              info!("EEPROM not present at 0x50 — skipping");
 62              return Ok(());
 63          }
 64  
 65          info!(
 66              "AT24C32 detected at 0x{=u8:02x} on i2c.1, capacity={=u16} bytes",
 67              board::eeprom::I2C_ADDR,
 68              board::eeprom::TOTAL_SIZE
 69          );
 70          Ok(())
 71      }
 72  
 73      /// `it("user writes and reads a byte")`
 74      #[test]
 75      async fn user_writes_and_reads_a_byte(
 76          mut device: Device,
 77      ) -> Result<(), &'static str> {
 78          if !is_eeprom_present(&mut device) {
 79              info!("EEPROM not present — skipping");
 80              return Ok(());
 81          }
 82  
 83          let bus = device.i2c_bus_1.take().ok_or("i2c bus 1 consumed")?;
 84          let mut eeprom = Cat24c32::new(bus, SlaveAddr::Default);
 85  
 86          eeprom.write_byte(TEST_BASE, 0xAB).map_err(|_| "write failed")?;
 87          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
 88  
 89          let readback = eeprom.read_byte(TEST_BASE).map_err(|_| "read failed")?;
 90          defmt::assert_eq!(readback, 0xAB, "byte mismatch");
 91  
 92          eeprom.write_byte(TEST_BASE, 0x00).map_err(|_| "cleanup write failed")?;
 93          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
 94  
 95          info!("byte roundtrip verified");
 96          Ok(())
 97      }
 98  
 99      /// `it("user writes and reads a buffer")`
100      #[test]
101      async fn user_writes_and_reads_a_buffer(
102          mut device: Device,
103      ) -> Result<(), &'static str> {
104          if !is_eeprom_present(&mut device) {
105              info!("EEPROM not present — skipping");
106              return Ok(());
107          }
108  
109          let bus = device.i2c_bus_1.take().ok_or("i2c bus 1 consumed")?;
110          let mut eeprom = Cat24c32::new(bus, SlaveAddr::Default);
111  
112          let write_buf: [u8; 16] = [
113              0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
114              0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
115          ];
116          // Align to page start so write_page doesn't reject for crossing boundary
117          let addr = TEST_BASE & !31;
118  
119          eeprom.write_page(addr, &write_buf).map_err(|_| "page write failed")?;
120          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
121  
122          let mut read_buf = [0u8; 16];
123          eeprom.read_data(addr, &mut read_buf).map_err(|_| "read failed")?;
124  
125          defmt::assert_eq!(read_buf, write_buf, "buffer mismatch");
126  
127          let zeros = [0u8; 16];
128          eeprom.write_page(addr, &zeros).map_err(|_| "cleanup write failed")?;
129          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
130  
131          info!("buffer roundtrip verified");
132          Ok(())
133      }
134  
135      /// `it("user reads the last byte of EEPROM")`
136      #[test]
137      async fn user_reads_the_last_byte(
138          mut device: Device,
139      ) -> Result<(), &'static str> {
140          if !is_eeprom_present(&mut device) {
141              info!("EEPROM not present — skipping");
142              return Ok(());
143          }
144  
145          let bus = device.i2c_bus_1.take().ok_or("i2c bus 1 consumed")?;
146          let mut eeprom = Cat24c32::new(bus, SlaveAddr::Default);
147  
148          // Use 0xFF (255) instead of TOTAL_SIZE-1 (4095) due to devaddr bug
149          let last: u32 = 0xFF;
150  
151          eeprom.write_byte(last, 0x77).map_err(|_| "write last byte failed")?;
152          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
153  
154          let readback = eeprom.read_byte(last).map_err(|_| "read last byte failed")?;
155          defmt::assert_eq!(readback, 0x77, "last byte mismatch");
156  
157          eeprom.write_byte(last, 0x00).map_err(|_| "cleanup write failed")?;
158          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
159  
160          info!("last byte roundtrip verified");
161          Ok(())
162      }
163  
164      /// `it("user writes a buffer crossing a page boundary")`
165      #[test]
166      async fn user_writes_buffer_crossing_page_boundary(
167          mut device: Device,
168      ) -> Result<(), &'static str> {
169          if !is_eeprom_present(&mut device) {
170              info!("EEPROM not present — skipping");
171              return Ok(());
172          }
173  
174          let bus = device.i2c_bus_1.take().ok_or("i2c bus 1 consumed")?;
175          let mut eeprom = Cat24c32::new(bus, SlaveAddr::Default);
176  
177          let page_size = board::eeprom::PAGE_SIZE as u32;
178          let addr = page_size - 4;
179          let write_buf: [u8; 8] = [0xA0, 0xA1, 0xA2, 0xA3, 0xB0, 0xB1, 0xB2, 0xB3];
180  
181          // Write first half (within page)
182          eeprom.write_page(addr, &write_buf[..4]).map_err(|_| "write first half failed")?;
183          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
184  
185          // Write second half (next page)
186          eeprom.write_page(addr + 4, &write_buf[4..]).map_err(|_| "write second half failed")?;
187          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
188  
189          let mut read_buf = [0u8; 8];
190          eeprom.read_data(addr, &mut read_buf).map_err(|_| "read failed")?;
191  
192          defmt::assert_eq!(read_buf, write_buf, "cross-page buffer mismatch");
193  
194          let zeros = [0u8; 4];
195          eeprom.write_page(addr, &zeros).map_err(|_| "cleanup first half failed")?;
196          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
197          eeprom.write_page(addr + 4, &zeros).map_err(|_| "cleanup second half failed")?;
198          embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
199  
200          info!("page boundary buffer verified");
201          Ok(())
202      }
203  }