Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

impl RtcNvram

Let's implement the RtcNvram trait to provide access to the DS1307's non-volatile memory.

#![allow(unused)]
fn main() {
impl<I2C> RtcNvram for Ds1307<I2C>
where
    I2C: embedded_hal::i2c::I2c,
{
    fn read_nvram(&mut self, offset: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
        if buffer.is_empty() {
            return Ok(());
        }

        self.validate_nvram_bounds(offset, buffer.len())?;

        let nvram_addr = NVRAM_START + offset;
        self.read_bytes_at_address(nvram_addr, buffer)?;

        Ok(())
    }

    fn write_nvram(&mut self, offset: u8, data: &[u8]) -> Result<(), Self::Error> {
        if data.is_empty() {
            return Ok(());
        }

        self.validate_nvram_bounds(offset, data.len())?;

        let mut buffer = [0u8; MAX_NVRAM_WRITE];
        buffer[0] = NVRAM_START + offset;
        buffer[1..data.len() + 1].copy_from_slice(data);

        self.write_raw_bytes(&buffer[..data.len() + 1])?;

        Ok(())
    }

    fn nvram_size(&self) -> u16 {
        NVRAM_SIZE as u16
    }
}
}

Method to get NVRAM Size

In the nvram_size implementation, we just return the NVRAM_SIZE constant that we declared in the registers module.

Reading data from NVRAM

In the read_nvram method, we first check if the buffer is empty and return early if there's no space to store the read data.

We then do the bounds check to ensure the offset is within the NVRAM memory layout and that offset + buffer length stays within the memory boundaries.

We calculate the actual NVRAM address by adding the NVRAM starting address with the given offset. Finally we use the read_bytes_at_address method to read the data in one burst operation.

Writing data to NVRAM

For the write_nvram method, we follow a similar approach but prepare the data for a burst write operation.

We create a buffer with the starting address followed by the actual data to write, then use write_raw_bytes to send everything in one I2C transaction.

The full code for the nvram module (nvram.rs)

#![allow(unused)]
fn main() {
//! DS1307 NVRAM Support
//!
//! This module provides an implementation of the [`RtcNvram`] trait for the
//! [`Ds1307`] real-time clock (RTC).

pub use rtc_hal::nvram::RtcNvram;

use crate::{
    Ds1307,
    registers::{MAX_NVRAM_WRITE, NVRAM_SIZE, NVRAM_START},
};

impl<I2C> RtcNvram for Ds1307<I2C>
where
    I2C: embedded_hal::i2c::I2c,
{
    /// Read data from DS1307 NVRAM.
    ///
    /// - `offset`: starting NVRAM address (0..55)
    /// - `buffer`: output buffer to store the read data
    ///
    /// Performs a sequential read starting at `NVRAM_START + offset`.
    fn read_nvram(&mut self, offset: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
        if buffer.is_empty() {
            return Ok(());
        }

        self.validate_nvram_bounds(offset, buffer.len())?;

        let nvram_addr = NVRAM_START + offset;
        self.read_bytes_at_address(nvram_addr, buffer)?;

        Ok(())
    }

    /// Write data into DS1307 NVRAM.
    ///
    /// - `offset`: starting NVRAM address (0..55)
    /// - `data`: slice containing data to write
    ///
    /// Uses either single-byte write or burst write depending on length.
    fn write_nvram(&mut self, offset: u8, data: &[u8]) -> Result<(), Self::Error> {
        if data.is_empty() {
            return Ok(());
        }

        self.validate_nvram_bounds(offset, data.len())?;

        // Burst write
        let mut buffer = [0u8; MAX_NVRAM_WRITE];
        buffer[0] = NVRAM_START + offset;
        buffer[1..data.len() + 1].copy_from_slice(data);

        self.write_raw_bytes(&buffer[..data.len() + 1])?;

        Ok(())
    }

    /// Return the size of DS1307 NVRAM in bytes (56).
    fn nvram_size(&self) -> u16 {
        NVRAM_SIZE as u16
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Ds1307;
    use crate::error::Error;
    use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
    use rtc_hal::nvram::RtcNvram;

    const DS1307_ADDR: u8 = 0x68;
    const NVRAM_START: u8 = 0x08;
    const NVRAM_SIZE: u8 = 56;

    #[test]
    fn test_nvram_size() {
        let i2c_mock = I2cMock::new(&[]);
        let ds1307 = Ds1307::new(i2c_mock);

        assert_eq!(ds1307.nvram_size(), NVRAM_SIZE as u16);

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_validate_nvram_bounds_valid() {
        let i2c_mock = I2cMock::new(&[]);
        let ds1307 = Ds1307::new(i2c_mock);

        // Test valid cases
        assert!(ds1307.validate_nvram_bounds(0, 1).is_ok());
        assert!(ds1307.validate_nvram_bounds(0, 56).is_ok());
        assert!(ds1307.validate_nvram_bounds(55, 1).is_ok());
        assert!(ds1307.validate_nvram_bounds(10, 46).is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_validate_nvram_bounds_invalid_offset() {
        let i2c_mock = I2cMock::new(&[]);
        let ds1307 = Ds1307::new(i2c_mock);

        // Test invalid offset
        assert!(matches!(
            ds1307.validate_nvram_bounds(56, 1),
            Err(Error::NvramOutOfBounds)
        ));
        assert!(matches!(
            ds1307.validate_nvram_bounds(100, 1),
            Err(Error::NvramOutOfBounds)
        ));

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_validate_nvram_bounds_invalid_length() {
        let i2c_mock = I2cMock::new(&[]);
        let ds1307 = Ds1307::new(i2c_mock);

        // Test length that goes beyond NVRAM
        assert!(matches!(
            ds1307.validate_nvram_bounds(0, 57),
            Err(Error::NvramOutOfBounds)
        ));
        assert!(matches!(
            ds1307.validate_nvram_bounds(55, 2),
            Err(Error::NvramOutOfBounds)
        ));
        assert!(matches!(
            ds1307.validate_nvram_bounds(10, 50),
            Err(Error::NvramOutOfBounds)
        ));

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_single_byte() {
        let expectations = vec![I2cTransaction::write_read(
            DS1307_ADDR,
            vec![NVRAM_START + 10], // Read from NVRAM offset 10
            vec![0xAB],
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; 1];
        let result = ds1307.read_nvram(10, &mut buffer);
        assert!(result.is_ok());
        assert_eq!(buffer[0], 0xAB);

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_multiple_bytes() {
        let expectations = vec![I2cTransaction::write_read(
            DS1307_ADDR,
            vec![NVRAM_START + 5], // Read from NVRAM offset 5
            vec![0x01, 0x02, 0x03, 0x04, 0x05],
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; 5];
        let result = ds1307.read_nvram(5, &mut buffer);
        assert!(result.is_ok());
        assert_eq!(buffer, [0x01, 0x02, 0x03, 0x04, 0x05]);

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_from_start() {
        let expectations = vec![I2cTransaction::write_read(
            DS1307_ADDR,
            vec![NVRAM_START], // Read from beginning of NVRAM
            vec![0xFF, 0xEE],
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; 2];
        let result = ds1307.read_nvram(0, &mut buffer);
        assert!(result.is_ok());
        assert_eq!(buffer, [0xFF, 0xEE]);

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_from_end() {
        let expectations = vec![I2cTransaction::write_read(
            DS1307_ADDR,
            vec![NVRAM_START + 55], // Read from last NVRAM byte
            vec![0x42],
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; 1];
        let result = ds1307.read_nvram(55, &mut buffer);
        assert!(result.is_ok());
        assert_eq!(buffer[0], 0x42);

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_full_size() {
        let expected_data = vec![NVRAM_START];
        let mut response_data = Vec::new();
        for i in 0..NVRAM_SIZE {
            response_data.push(i);
        }

        let expectations = vec![I2cTransaction::write_read(
            DS1307_ADDR,
            expected_data,
            response_data.clone(),
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; NVRAM_SIZE as usize];
        let result = ds1307.read_nvram(0, &mut buffer);
        assert!(result.is_ok());
        assert_eq!(buffer.to_vec(), response_data);

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_empty_buffer() {
        let i2c_mock = I2cMock::new(&[]);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [];
        let result = ds1307.read_nvram(0, &mut buffer);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_out_of_bounds_offset() {
        let i2c_mock = I2cMock::new(&[]);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; 1];
        let result = ds1307.read_nvram(56, &mut buffer);
        assert!(matches!(result, Err(Error::NvramOutOfBounds)));

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_out_of_bounds_length() {
        let i2c_mock = I2cMock::new(&[]);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; 2];
        let result = ds1307.read_nvram(55, &mut buffer);
        assert!(matches!(result, Err(Error::NvramOutOfBounds)));

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_read_nvram_i2c_error() {
        let expectations = vec![
            I2cTransaction::write_read(DS1307_ADDR, vec![NVRAM_START], vec![0x00])
                .with_error(embedded_hal::i2c::ErrorKind::Other),
        ];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; 1];
        let result = ds1307.read_nvram(0, &mut buffer);
        assert!(result.is_err());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_single_byte() {
        let expectations = vec![I2cTransaction::write(
            DS1307_ADDR,
            vec![NVRAM_START + 10, 0xCD], // Write to NVRAM offset 10
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [0xCD];
        let result = ds1307.write_nvram(10, &data);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_multiple_bytes() {
        let expectations = vec![I2cTransaction::write(
            DS1307_ADDR,
            vec![NVRAM_START + 5, 0x10, 0x20, 0x30, 0x40], // Write to NVRAM offset 5
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [0x10, 0x20, 0x30, 0x40];
        let result = ds1307.write_nvram(5, &data);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_to_start() {
        let expectations = vec![I2cTransaction::write(
            DS1307_ADDR,
            vec![NVRAM_START, 0xAA, 0xBB], // Write to beginning of NVRAM
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [0xAA, 0xBB];
        let result = ds1307.write_nvram(0, &data);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_to_end() {
        let expectations = vec![I2cTransaction::write(
            DS1307_ADDR,
            vec![NVRAM_START + 55, 0x99], // Write to last NVRAM byte
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [0x99];
        let result = ds1307.write_nvram(55, &data);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_full_size() {
        let mut expected_data = vec![NVRAM_START];
        let write_data: Vec<u8> = (0..NVRAM_SIZE).collect();
        expected_data.extend_from_slice(&write_data);

        let expectations = vec![I2cTransaction::write(DS1307_ADDR, expected_data)];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let result = ds1307.write_nvram(0, &write_data);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_empty_data() {
        let i2c_mock = I2cMock::new(&[]);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [];
        let result = ds1307.write_nvram(0, &data);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_out_of_bounds_offset() {
        let i2c_mock = I2cMock::new(&[]);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [0x42];
        let result = ds1307.write_nvram(56, &data);
        assert!(matches!(result, Err(Error::NvramOutOfBounds)));

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_out_of_bounds_length() {
        let i2c_mock = I2cMock::new(&[]);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [0x42, 0x43];
        let result = ds1307.write_nvram(55, &data);
        assert!(matches!(result, Err(Error::NvramOutOfBounds)));

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_i2c_error() {
        let expectations = vec![
            I2cTransaction::write(DS1307_ADDR, vec![NVRAM_START, 0x42])
                .with_error(embedded_hal::i2c::ErrorKind::Other),
        ];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [0x42];
        let result = ds1307.write_nvram(0, &data);
        assert!(result.is_err());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_constants() {
        assert_eq!(NVRAM_START, 0x08);
        assert_eq!(NVRAM_SIZE, 56);
        assert_eq!(MAX_NVRAM_WRITE, 57);
    }

    #[test]
    fn test_nvram_boundary_conditions() {
        // Test reading/writing exactly at boundaries
        let expectations = vec![I2cTransaction::write_read(
            DS1307_ADDR,
            vec![NVRAM_START + 20],
            vec![
                0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
                0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
                0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
            ],
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        // Read from offset 20 to the end (36 bytes)
        let mut buffer = [0u8; 36];
        let result = ds1307.read_nvram(20, &mut buffer);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_write_nvram_burst_write_format() {
        // Test that the burst write format is correct
        let expectations = vec![I2cTransaction::write(
            DS1307_ADDR,
            vec![NVRAM_START + 15, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77],
        )];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let data = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77];
        let result = ds1307.write_nvram(15, &data);
        assert!(result.is_ok());

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }

    #[test]
    fn test_nvram_address_calculation() {
        // Test that NVRAM addresses are calculated correctly
        let expectations = vec![
            I2cTransaction::write_read(DS1307_ADDR, vec![0x08], vec![0x01]), // offset 0
            I2cTransaction::write_read(DS1307_ADDR, vec![0x10], vec![0x02]), // offset 8
            I2cTransaction::write_read(DS1307_ADDR, vec![0x20], vec![0x03]), // offset 24
            I2cTransaction::write_read(DS1307_ADDR, vec![0x3F], vec![0x04]), // offset 55
        ];

        let i2c_mock = I2cMock::new(&expectations);
        let mut ds1307 = Ds1307::new(i2c_mock);

        let mut buffer = [0u8; 1];

        // Test offset 0 -> address 0x08
        assert!(ds1307.read_nvram(0, &mut buffer).is_ok());
        assert_eq!(buffer[0], 0x01);

        // Test offset 8 -> address 0x10
        assert!(ds1307.read_nvram(8, &mut buffer).is_ok());
        assert_eq!(buffer[0], 0x02);

        // Test offset 24 -> address 0x20
        assert!(ds1307.read_nvram(24, &mut buffer).is_ok());
        assert_eq!(buffer[0], 0x03);

        // Test offset 55 -> address 0x3F
        assert!(ds1307.read_nvram(55, &mut buffer).is_ok());
        assert_eq!(buffer[0], 0x04);

        let mut i2c_mock = ds1307.release_i2c();
        i2c_mock.done();
    }
}
}