rmk_q6_he_ansi/eeprom.rs
1use Pull::Up;
2use embassy_stm32::{
3 gpio::{Flex, Pull, Speed},
4 i2c::{Error, Error::Overrun, I2c, mode::MasterMode},
5 mode::Async,
6};
7use embassy_time::{Duration, Timer};
8use embedded_hal_async::i2c::Operation;
9
10/// 7-bit I²C device address (A0 = A1 = A2 = GND).
11const DEVICE_ADDR: u8 = 0x51;
12/// Total size of the FT24C64 in bytes (64 Kbit).
13const EEPROM_SIZE: usize = 8192;
14/// Page write size in bytes per the FT24C64 datasheet.
15const PAGE_SIZE: usize = 32;
16
17/// Driver for the FT24C64 64-Kbit (8 K × 8) I²C EEPROM.
18pub struct Ft24c64<'peripherals, IM: MasterMode> {
19 /// I²C peripheral used for all device communication.
20 i2c: I2c<'peripherals, Async, IM>,
21 /// Write-protect pin managed as input pull-up when idle, output-low when
22 /// writing.
23 wp: Flex<'peripherals>,
24}
25
26impl<'peripherals, IM: MasterMode> Ft24c64<'peripherals, IM> {
27 /// Create a new driver.
28 pub fn new(i2c: I2c<'peripherals, Async, IM>, mut wp: Flex<'peripherals>) -> Self {
29 // Input pull-up: write-protect asserted via pull resistor.
30 wp.set_as_input(Up);
31 Self { i2c, wp }
32 }
33
34 /// Poll the device with a zero-length write until it ACKs, indicating
35 /// the internal write cycle has completed. Retries up to `max_attempts`
36 /// times with a short yield between each attempt.
37 ///
38 /// Returns `Ok(())` as soon as the device acknowledges. Returns
39 /// [`Error`] if the device does not become ready within `max_attempts`.
40 async fn poll_until_ready(&mut self, max_attempts: u8) -> Result<(), Error> {
41 let mut attempts = 0_u8;
42 loop {
43 let result = self.i2c.write(DEVICE_ADDR, &[]).await;
44 if result.is_ok() {
45 return Ok(());
46 }
47 attempts = attempts.saturating_add(1);
48 if attempts >= max_attempts {
49 return result;
50 }
51 Timer::after(Duration::from_micros(200)).await;
52 }
53 }
54
55 /// Read `buf.len()` bytes starting at 16-bit word address `addr`.
56 pub async fn read(&mut self, addr: u16, buf: &mut [u8]) -> Result<(), Error> {
57 self.i2c.write_read(DEVICE_ADDR, &addr.to_be_bytes(), buf).await
58 }
59
60 /// Write `data` starting at 16-bit word address `start_addr`.
61 pub async fn write(&mut self, start_addr: u16, data: &[u8]) -> Result<(), Error> {
62 // Switch to output-low: write-protect deasserted.
63 self.wp.set_low();
64 self.wp.set_as_output(Speed::Low);
65
66 let mut offset = 0_usize;
67 let mut result = Ok(());
68
69 while offset < data.len() {
70 // Convert byte offset to an u16 address, saturating on overflow so
71 // we never silently wrap into a wrong EEPROM location.
72 let addr = start_addr.saturating_add(u16::try_from(offset).unwrap_or(u16::MAX));
73
74 // Bytes remaining before the next 32-byte page boundary.
75 let page_offset = usize::from(addr).rem_euclid(PAGE_SIZE);
76 let page_remaining = PAGE_SIZE.saturating_sub(page_offset);
77
78 // Bytes to write on this page, the smaller of the remaining page
79 // space and the remaining data.
80 let chunk_len = page_remaining.min(data.len().saturating_sub(offset));
81 let addr_bytes = addr.to_be_bytes();
82 let chunk = &data[offset..offset.saturating_add(chunk_len)];
83 result =
84 self.i2c.transaction(DEVICE_ADDR, &mut [Operation::Write(&addr_bytes), Operation::Write(chunk)]).await;
85 if result.is_err() {
86 break;
87 }
88
89 result = self.poll_until_ready(30).await;
90 if result.is_err() {
91 break;
92 }
93
94 offset = offset.saturating_add(chunk_len);
95 }
96
97 // Restore to input pull-up: write-protect re-asserted.
98 self.wp.set_as_input(Up);
99 result
100 }
101
102 /// Erase the entire EEPROM by writing `0xFF` to all 8 192 bytes.
103 ///
104 /// Pages are written sequentially from address `0x0000` to `0x1FFF` (256
105 /// pages × 32 bytes). After each page write, the driver waits for the
106 /// device to become ready again using the polling-based readiness check
107 /// before continuing with the next page.
108 ///
109 /// Call this once on first boot before starting a calibration run to
110 /// guarantee no stale data survives in any region of the device.
111 ///
112 /// # Errors
113 ///
114 /// Returns the first [`Error`] encountered. Any pages written before the
115 /// failure are not rolled back.
116 pub async fn zero_out(&mut self) -> Result<(), Error> {
117 let blank = [0xFF_u8; PAGE_SIZE];
118 let mut offset = 0_usize;
119 while offset < EEPROM_SIZE {
120 let addr = match u16::try_from(offset) {
121 Ok(a) => a,
122 Err(_) => return Err(Overrun),
123 };
124 let result = self.write(addr, &blank).await;
125 if let Err(e) = result {
126 return Err(e);
127 }
128 offset = offset.saturating_add(PAGE_SIZE);
129 }
130 Ok(())
131 }
132}