I2C Communication
The DS3231 chip also use the same fixed I2C device address of “0x68”. We’ll declare this as a constant at the top of our module:
#![allow(unused)]
fn main() {
/// DS3231 I2C device address (fixed)
pub const I2C_ADDR: u8 = 0x68;
}
The fullcode for the Ds3231 module (ds3231.rs)
The remaining functions for reading and writing registers, and setting and clearing register bits are identical to the DS1307.
#![allow(unused)]
fn main() {
//! DS3231 Real-Time Clock Driver
use embedded_hal::i2c::I2c;
use crate::{error::Error, registers::Register};
/// DS3231 I2C device address (fixed)
pub const I2C_ADDR: u8 = 0x68;
/// Default base century for year calculations (2000-2099).
/// Since the DS3231's century bit meaning is ambiguous, we assume
/// years 00-99 represent the 21st century by default.
pub const DEFAULT_BASE_CENTURY: u8 = 20;
/// DS3231 Real-Time Clock driver
pub struct Ds3231<I2C> {
i2c: I2C,
pub(crate) base_century: u8,
}
impl<I2C: embedded_hal::i2c::I2c> rtc_hal::error::ErrorType for Ds3231<I2C> {
type Error = crate::error::Error<I2C::Error>;
}
impl<I2C, E> Ds3231<I2C>
where
I2C: I2c<Error = E>,
E: core::fmt::Debug,
{
/// Create a new DS3231 driver instance
///
/// # Parameters
/// * `i2c` - I2C peripheral that implements the embedded-hal I2c trait
///
/// # Returns
/// New DS3231 driver instance
pub fn new(i2c: I2C) -> Self {
Self {
i2c,
base_century: DEFAULT_BASE_CENTURY,
}
}
/// Sets the base century for year calculations.
///
/// The DS3231 stores years as 00-99 in BCD format. This base century
/// determines how those 2-digit years are interpreted as full 4-digit years.
///
/// # Arguments
///
/// * `base_century` - The century to use (e.g., 20 for 2000-2099, 21 for 2100-2199)
///
/// # Returns
///
/// Returns `Err(Error::InvalidBaseCentury)` if base_century is less than 19.
///
/// # Examples
///
/// ```
/// // Years 00-99 will be interpreted as 2000-2099
/// let rtc = Ds3231::new(i2c).with_base_century(20)?;
///
/// // Years 00-99 will be interpreted as 2100-2199
/// let rtc = Ds3231::new(i2c).with_base_century(21)?;
/// ```
pub fn set_base_century(&mut self, base_century: u8) -> Result<(), Error<E>> {
if base_century < 19 {
return Err(Error::InvalidBaseCentury);
}
self.base_century = base_century;
Ok(())
}
/// Returns the underlying I2C bus instance, consuming the driver.
///
/// This allows the user to reuse the I2C bus for other purposes
/// after the driver is no longer needed.
///
/// However, if you are using [`embedded-hal-bus`](https://crates.io/crates/embedded-hal-bus),
/// you typically do not need `release_i2c`.
/// In that case the crate takes care of the sharing
pub fn release_i2c(self) -> I2C {
self.i2c
}
/// Write a single byte to a DS3231 register
pub(crate) fn write_register(&mut self, register: Register, value: u8) -> Result<(), Error<E>> {
self.i2c.write(I2C_ADDR, &[register.addr(), value])?;
Ok(())
}
/// Read a single byte from a DS3231 register
pub(crate) fn read_register(&mut self, register: Register) -> Result<u8, Error<E>> {
let mut data = [0u8; 1];
self.i2c
.write_read(I2C_ADDR, &[register.addr()], &mut data)?;
Ok(data[0])
}
/// Read multiple bytes from DS3231 starting at a register
pub(crate) fn read_register_bytes(
&mut self,
register: Register,
buffer: &mut [u8],
) -> Result<(), Error<E>> {
self.i2c.write_read(I2C_ADDR, &[register.addr()], buffer)?;
Ok(())
}
// Read multiple bytes from DS3231 starting at a raw address
// pub(crate) fn read_bytes_at_address(
// &mut self,
// register_addr: u8,
// buffer: &mut [u8],
// ) -> Result<(), Error<E>> {
// self.i2c.write_read(I2C_ADDR, &[register_addr], buffer)?;
// Ok(())
// }
/// Write raw bytes directly to DS3231 via I2C (register address must be first byte)
pub(crate) fn write_raw_bytes(&mut self, data: &[u8]) -> Result<(), Error<E>> {
self.i2c.write(I2C_ADDR, data)?;
Ok(())
}
/// Read-modify-write operation for setting bits
///
/// Performs a read-modify-write operation to set the bits specified by the mask
/// while preserving all other bits in the register. Only performs a write if
/// the register value would actually change, optimizing I2C bus usage.
///
/// # Parameters
/// - `register`: The DS3231 register to modify
/// - `mask`: Bit mask where `1` bits will be set, `0` bits will be ignored
///
/// # Example
/// ```ignore
/// // Set bits 2 and 4 in the control register
/// self.set_register_bits(Register::Control, 0b0001_0100)?;
/// ```
///
/// # I2C Operations
/// - 1 read + 1 write (if change needed)
/// - 1 read only (if no change needed)
pub(crate) fn set_register_bits(
&mut self,
register: Register,
mask: u8,
) -> Result<(), Error<E>> {
let current = self.read_register(register)?;
let new_value = current | mask;
if new_value != current {
self.write_register(register, new_value)
} else {
Ok(())
}
}
/// Read-modify-write operation for clearing bits
///
/// Performs a read-modify-write operation to clear the bits specified by the mask
/// while preserving all other bits in the register. Only performs a write if
/// the register value would actually change, optimizing I2C bus usage.
///
/// # Parameters
/// - `register`: The DS3231 register to modify
/// - `mask`: Bit mask where `1` bits will be cleared, `0` bits will be ignored
///
/// # Example
/// ```ignore
/// // Clear the Clock Halt bit (bit 7) in seconds register
/// self.clear_register_bits(Register::Seconds, 0b1000_0000)?;
/// ```
///
/// # I2C Operations
/// - 1 read + 1 write (if change needed)
/// - 1 read only (if no change needed)
pub(crate) fn clear_register_bits(
&mut self,
register: Register,
mask: u8,
) -> Result<(), Error<E>> {
let current = self.read_register(register)?;
let new_value = current & !mask;
if new_value != current {
self.write_register(register, new_value)
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
use crate::registers::Register;
use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
const DS3231_ADDR: u8 = 0x68;
#[test]
fn test_new() {
let i2c_mock = I2cMock::new(&[]);
let ds3231 = Ds3231::new(i2c_mock);
assert_eq!(ds3231.base_century, DEFAULT_BASE_CENTURY);
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_set_base_century_valid() {
let i2c_mock = I2cMock::new(&[]);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_base_century(21);
assert!(result.is_ok());
assert_eq!(ds3231.base_century, 21);
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_set_base_century_minimum_valid() {
let i2c_mock = I2cMock::new(&[]);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_base_century(19);
assert!(result.is_ok());
assert_eq!(ds3231.base_century, 19);
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_set_base_century_invalid() {
let i2c_mock = I2cMock::new(&[]);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_base_century(18);
assert!(matches!(result, Err(Error::InvalidBaseCentury)));
assert_eq!(ds3231.base_century, DEFAULT_BASE_CENTURY);
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_write_register() {
let expectations = vec![I2cTransaction::write(
DS3231_ADDR,
vec![Register::Control.addr(), 0x42],
)];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.write_register(Register::Control, 0x42);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_write_register_error() {
let expectations = vec![
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0x42])
.with_error(embedded_hal::i2c::ErrorKind::Other),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.write_register(Register::Control, 0x42);
assert!(result.is_err());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_read_register() {
let expectations = vec![I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0x55],
)];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.read_register(Register::Control);
assert_eq!(result.unwrap(), 0x55);
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_read_register_error() {
let expectations = vec![
I2cTransaction::write_read(DS3231_ADDR, vec![Register::Control.addr()], vec![0x00])
.with_error(embedded_hal::i2c::ErrorKind::Other),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.read_register(Register::Control);
assert!(result.is_err());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_read_register_bytes() {
let expectations = vec![I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Seconds.addr()],
vec![0x11, 0x22, 0x33],
)];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let mut buffer = [0u8; 3];
let result = ds3231.read_register_bytes(Register::Seconds, &mut buffer);
assert!(result.is_ok());
assert_eq!(buffer, [0x11, 0x22, 0x33]);
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_read_register_bytes_error() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Seconds.addr()],
vec![0x00, 0x00],
)
.with_error(embedded_hal::i2c::ErrorKind::Other),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let mut buffer = [0u8; 2];
let result = ds3231.read_register_bytes(Register::Seconds, &mut buffer);
assert!(result.is_err());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_write_raw_bytes() {
let expectations = vec![I2cTransaction::write(DS3231_ADDR, vec![0x0E, 0x1C, 0x00])];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.write_raw_bytes(&[0x0E, 0x1C, 0x00]);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_write_raw_bytes_error() {
let expectations = vec![
I2cTransaction::write(DS3231_ADDR, vec![0x0E, 0x1C])
.with_error(embedded_hal::i2c::ErrorKind::Other),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.write_raw_bytes(&[0x0E, 0x1C]);
assert!(result.is_err());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_set_register_bits_change_needed() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b0000_1000],
),
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0b0001_1000]),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_set_register_bits_no_change_needed() {
let expectations = vec![I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b0001_1000],
)];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_set_register_bits_multiple_bits() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b0000_0000],
),
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0b1010_0101]),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_register_bits(Register::Control, 0b1010_0101);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_set_register_bits_read_error() {
let expectations = vec![
I2cTransaction::write_read(DS3231_ADDR, vec![Register::Control.addr()], vec![0x00])
.with_error(embedded_hal::i2c::ErrorKind::Other),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_err());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_set_register_bits_write_error() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b0000_0000],
),
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0b0001_0000])
.with_error(embedded_hal::i2c::ErrorKind::Other),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_err());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_clear_register_bits_change_needed() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b1111_1111],
),
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0b1110_1111]),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.clear_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_clear_register_bits_no_change_needed() {
let expectations = vec![I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b1110_1111],
)];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.clear_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_clear_register_bits_multiple_bits() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b1111_1111],
),
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0b0101_1010]),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.clear_register_bits(Register::Control, 0b1010_0101);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_clear_register_bits_read_error() {
let expectations = vec![
I2cTransaction::write_read(DS3231_ADDR, vec![Register::Control.addr()], vec![0x00])
.with_error(embedded_hal::i2c::ErrorKind::Other),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.clear_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_err());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_clear_register_bits_write_error() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b1111_1111],
),
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0b1110_1111])
.with_error(embedded_hal::i2c::ErrorKind::Other),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.clear_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_err());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_constants() {
assert_eq!(I2C_ADDR, 0x68);
assert_eq!(DEFAULT_BASE_CENTURY, 20);
}
#[test]
fn test_set_register_bits_preserves_other_bits() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b1000_0010],
),
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0b1001_0010]),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.set_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
#[test]
fn test_clear_register_bits_preserves_other_bits() {
let expectations = vec![
I2cTransaction::write_read(
DS3231_ADDR,
vec![Register::Control.addr()],
vec![0b1001_0010],
),
I2cTransaction::write(DS3231_ADDR, vec![Register::Control.addr(), 0b1000_0010]),
];
let i2c_mock = I2cMock::new(&expectations);
let mut ds3231 = Ds3231::new(i2c_mock);
let result = ds3231.clear_register_bits(Register::Control, 0b0001_0000);
assert!(result.is_ok());
let mut i2c_mock = ds3231.release_i2c();
i2c_mock.done();
}
}
}