Skip to main content

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}