├── .gitignore ├── examples └── linux.rs ├── release.toml ├── tests ├── construction.rs ├── ds3234.rs ├── status.rs ├── ds3232_4.rs ├── configuration.rs ├── common │ └── mod.rs ├── datetime.rs └── alarms.rs ├── src ├── ds3231.rs ├── ds3232.rs ├── ds323x │ ├── mod.rs │ ├── status.rs │ ├── configuration.rs │ ├── datetime.rs │ └── alarms.rs ├── ds3234.rs ├── interface.rs └── lib.rs ├── LICENSE-MIT ├── Cargo.toml ├── .github └── workflows │ └── build.yml ├── CHANGELOG.md ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | /target/ 4 | Cargo.lock 5 | .gdb_history 6 | 7 | -------------------------------------------------------------------------------- /examples/linux.rs: -------------------------------------------------------------------------------- 1 | use ds323x::{DateTimeAccess, Ds323x, NaiveDate, Rtcc}; 2 | use linux_embedded_hal::I2cdev; 3 | 4 | fn main() { 5 | let dev = I2cdev::new("/dev/i2c-1").unwrap(); 6 | let mut rtc = Ds323x::new_ds3231(dev); 7 | let datetime = NaiveDate::from_ymd_opt(2020, 5, 1) 8 | .unwrap() 9 | .and_hms_opt(19, 59, 58) 10 | .unwrap(); 11 | rtc.set_datetime(&datetime).unwrap(); 12 | // do something else... 13 | let time = rtc.time().unwrap(); 14 | println!("Time: {}", time); 15 | 16 | let _dev = rtc.destroy_ds3231(); 17 | } 18 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, 3 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 4 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, 5 | {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, 6 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/eldruin/{{crate_name}}-rs/compare/{{tag_name}}...HEAD", exactly=1}, 7 | ] -------------------------------------------------------------------------------- /tests/construction.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use self::common::{ 3 | destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, 4 | }; 5 | 6 | macro_rules! construction_test { 7 | ($name:ident, $create:ident, $destroy:ident) => { 8 | #[test] 9 | fn $name() { 10 | let dev = $create(&[]); 11 | $destroy(dev); 12 | } 13 | }; 14 | } 15 | 16 | construction_test!(can_create_ds3231, new_ds3231, destroy_ds3231); 17 | construction_test!(can_create_ds3232, new_ds3232, destroy_ds3232); 18 | construction_test!(can_create_ds3234, new_ds3234, destroy_ds3234); 19 | -------------------------------------------------------------------------------- /src/ds3231.rs: -------------------------------------------------------------------------------- 1 | //! Functions exclusive of DS3231 2 | 3 | use crate::{ic, interface::I2cInterface, BitFlags, Ds323x, CONTROL_POR_VALUE}; 4 | use core::marker::PhantomData; 5 | use embedded_hal::i2c; 6 | 7 | impl Ds323x, ic::DS3231> 8 | where 9 | I2C: i2c::I2c, 10 | { 11 | /// Create a new instance of the DS3231 device. 12 | pub fn new_ds3231(i2c: I2C) -> Self { 13 | const STATUS_POR_VALUE: u8 = BitFlags::OSC_STOP | BitFlags::EN32KHZ; 14 | Ds323x { 15 | iface: I2cInterface { i2c }, 16 | control: CONTROL_POR_VALUE, 17 | status: STATUS_POR_VALUE, 18 | _ic: PhantomData, 19 | } 20 | } 21 | 22 | /// Destroy driver instance, return I²C bus instance. 23 | pub fn destroy_ds3231(self) -> I2C { 24 | self.iface.i2c 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/ds3234.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal_mock::eh1::spi::Transaction as SpiTrans; 2 | 3 | #[allow(unused)] 4 | mod common; 5 | use self::common::{destroy_ds3234, new_ds3234, BitFlags, Register}; 6 | 7 | call_test!( 8 | can_en_temp_conv_bat, 9 | enable_temperature_conversions_on_battery, 10 | new_ds3234, 11 | destroy_ds3234, 12 | [ 13 | SpiTrans::transaction_start(), 14 | SpiTrans::write_vec(vec![Register::TEMP_CONV + 0x80, 0]), 15 | SpiTrans::transaction_end() 16 | ] 17 | ); 18 | 19 | call_test!( 20 | can_dis_temp_conv_bat, 21 | disable_temperature_conversions_on_battery, 22 | new_ds3234, 23 | destroy_ds3234, 24 | [ 25 | SpiTrans::transaction_start(), 26 | SpiTrans::write_vec(vec![Register::TEMP_CONV + 0x80, BitFlags::TEMP_CONV_BAT]), 27 | SpiTrans::transaction_end(), 28 | ] 29 | ); 30 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018-2025 Diego Barrios Romero 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ds323x" 3 | version = "0.7.0" 4 | authors = ["Diego Barrios Romero "] 5 | repository = "https://github.com/eldruin/ds323x-rs" 6 | license = "MIT OR Apache-2.0" 7 | description = "Platform-agnostic Rust driver for the DS3231, DS3232 and DS3234 extremely accurate real-time clocks (RTC)." 8 | readme = "README.md" 9 | keywords = ["rtc", "clock", "real-time", "driver", "embedded-hal-driver"] 10 | categories = ["embedded", "hardware-support", "no-std"] 11 | homepage = "https://github.com/eldruin/ds323x-rs" 12 | documentation = "https://docs.rs/ds323x" 13 | include = [ 14 | "/**/*.rs", 15 | "/Cargo.toml", 16 | "/README.md", 17 | "/CHANGELOG.md", 18 | "/LICENSE-MIT", 19 | "/LICENSE-APACHE", 20 | ] 21 | edition = "2018" 22 | 23 | [features] 24 | defmt = ["dep:defmt", "rtcc/defmt"] 25 | 26 | [dependencies] 27 | embedded-hal = "1.0.0" 28 | rtcc = "0.4" 29 | defmt = { version = "1.0.1", optional = true } 30 | 31 | [dev-dependencies] 32 | embedded-hal-mock = { version = "0.11.1", features = ["eh1"] } 33 | embedded-hal-bus = "0.2" 34 | linux-embedded-hal = "0.4.0" 35 | 36 | [profile.release] 37 | lto = true 38 | -------------------------------------------------------------------------------- /tests/status.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal_mock::eh1::{i2c::Transaction as I2cTrans, spi::Transaction as SpiTrans}; 2 | mod common; 3 | use self::common::{ 4 | destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, 5 | BitFlags as BF, Register, DEVICE_ADDRESS as DEV_ADDR, 6 | }; 7 | 8 | get_param_test!(running, running, CONTROL, true, 0); 9 | get_param_test!(is_not_running, running, CONTROL, false, BF::EOSC); 10 | 11 | get_param_test!(busy, busy, STATUS, true, 0xFF); 12 | get_param_test!(is_not_busy, busy, STATUS, false, !BF::BUSY); 13 | 14 | get_param_test!(stopped, has_been_stopped, STATUS, true, 0xFF); 15 | get_param_test!(not_stopped, has_been_stopped, STATUS, false, !BF::OSC_STOP); 16 | 17 | get_param_test!(alarm1_matched, has_alarm1_matched, STATUS, true, 0xFF); 18 | get_param_test!( 19 | alarm1_not_matched, 20 | has_alarm1_matched, 21 | STATUS, 22 | false, 23 | !BF::ALARM1F 24 | ); 25 | 26 | get_param_test!(alarm2_matched, has_alarm2_matched, STATUS, true, 0xFF); 27 | get_param_test!( 28 | alarm2_not_matched, 29 | has_alarm2_matched, 30 | STATUS, 31 | false, 32 | !BF::ALARM2F 33 | ); 34 | 35 | get_param_read_array_test!(temp_0, temperature, 0.0, TEMP_MSB, [0, 0], [0, 0]); 36 | get_param_read_array_test!( 37 | temp_min, 38 | temperature, 39 | -128.0, 40 | TEMP_MSB, 41 | [0b1000_0000, 0], 42 | [0, 0] 43 | ); 44 | get_param_read_array_test!( 45 | temp_max, 46 | temperature, 47 | 127.75, 48 | TEMP_MSB, 49 | [0b0111_1111, 0b1100_0000], 50 | [0, 0] 51 | ); 52 | -------------------------------------------------------------------------------- /src/ds3232.rs: -------------------------------------------------------------------------------- 1 | //! Functions exclusive of DS3232 2 | 3 | use crate::{ 4 | ic, interface::I2cInterface, BitFlags, Ds323x, Error, TempConvRate, CONTROL_POR_VALUE, 5 | }; 6 | use core::marker::PhantomData; 7 | use embedded_hal::i2c; 8 | 9 | impl Ds323x, ic::DS3232> 10 | where 11 | I2C: i2c::I2c, 12 | { 13 | /// Create a new instance of the DS3232 device. 14 | pub fn new_ds3232(i2c: I2C) -> Self { 15 | const STATUS_POR_VALUE: u8 = BitFlags::OSC_STOP | BitFlags::BB32KHZ | BitFlags::EN32KHZ; 16 | Ds323x { 17 | iface: I2cInterface { i2c }, 18 | control: CONTROL_POR_VALUE, 19 | status: STATUS_POR_VALUE, 20 | _ic: PhantomData, 21 | } 22 | } 23 | 24 | /// Destroy driver instance, return I²C bus instance. 25 | pub fn destroy_ds3232(self) -> I2C { 26 | self.iface.i2c 27 | } 28 | 29 | /// Enable the 32kHz output when battery-powered. (enabled per default) 30 | /// 31 | /// Additionally, the 32kHz output needs to be enabled. See 32 | /// [`enable_32khz_output()`](#method.enable_32khz_output). 33 | /// 34 | /// Note: This is only available for DS3232 and DS3234 devices. 35 | pub fn enable_32khz_output_on_battery(&mut self) -> Result<(), Error> { 36 | let status = self.status | BitFlags::BB32KHZ; 37 | self.write_status_without_clearing_alarm(status) 38 | } 39 | 40 | /// Disable the 32kHz output when battery-powered. 41 | /// 42 | /// The 32kHz output will still generate a wave when not battery-powered if 43 | /// it enabled. See [`enable_32khz_output()`](#method.enable_32khz_output). 44 | /// 45 | /// Note: This is only available for DS3232 and DS3234 devices. 46 | pub fn disable_32khz_output_on_battery(&mut self) -> Result<(), Error> { 47 | let status = self.status & !BitFlags::BB32KHZ; 48 | self.write_status_without_clearing_alarm(status) 49 | } 50 | 51 | /// Set the temperature conversion rate. 52 | /// 53 | /// Set how often the temperature is measured and applies compensation to 54 | /// the oscillator. This can be used to reduce power consumption but sudden 55 | /// temperature changes will not be compensated for. 56 | /// 57 | /// Note: This is only available for DS3232 and DS3234 devices. 58 | pub fn set_temperature_conversion_rate(&mut self, rate: TempConvRate) -> Result<(), Error> { 59 | let status = match rate { 60 | TempConvRate::_64s => self.status & !BitFlags::CRATE1 & !BitFlags::CRATE0, 61 | TempConvRate::_128s => self.status & !BitFlags::CRATE1 | BitFlags::CRATE0, 62 | TempConvRate::_256s => self.status | BitFlags::CRATE1 & !BitFlags::CRATE0, 63 | TempConvRate::_512s => self.status | BitFlags::CRATE1 | BitFlags::CRATE0, 64 | }; 65 | self.write_status_without_clearing_alarm(status) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ds323x/mod.rs: -------------------------------------------------------------------------------- 1 | mod alarms; 2 | mod configuration; 3 | mod status; 4 | pub use self::alarms::{ 5 | Alarm1Matching, Alarm2Matching, DayAlarm1, DayAlarm2, WeekdayAlarm1, WeekdayAlarm2, 6 | }; 7 | mod datetime; 8 | use crate::{BitFlags, Error, Hours, NaiveTime, Timelike}; 9 | 10 | // Transforms a decimal number to packed BCD format 11 | fn decimal_to_packed_bcd(dec: u8) -> u8 { 12 | ((dec / 10) << 4) | (dec % 10) 13 | } 14 | 15 | // Transforms a number in packed BCD format to decimal 16 | fn packed_bcd_to_decimal(bcd: u8) -> u8 { 17 | (bcd >> 4) * 10 + (bcd & 0xF) 18 | } 19 | 20 | fn hours_to_register(hours: Hours) -> Result> { 21 | match hours { 22 | Hours::H24(h) if h > 23 => Err(Error::InvalidInputData), 23 | Hours::H24(h) => Ok(decimal_to_packed_bcd(h)), 24 | Hours::AM(h) if !(1..=12).contains(&h) => Err(Error::InvalidInputData), 25 | Hours::AM(h) => Ok(BitFlags::H24_H12 | decimal_to_packed_bcd(h)), 26 | Hours::PM(h) if !(1..=12).contains(&h) => Err(Error::InvalidInputData), 27 | Hours::PM(h) => Ok(BitFlags::H24_H12 | BitFlags::AM_PM | decimal_to_packed_bcd(h)), 28 | } 29 | } 30 | 31 | fn some_or_invalid_error(data: Option) -> Result> { 32 | if let Some(data) = data { 33 | Ok(data) 34 | } else { 35 | Err(Error::InvalidDeviceState) 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | 43 | #[test] 44 | fn if_some_then_get_inner() { 45 | match some_or_invalid_error::(Some(1)) { 46 | Ok(1) => (), 47 | _ => panic!(), 48 | } 49 | } 50 | 51 | #[test] 52 | fn if_none_then_error() { 53 | match some_or_invalid_error::(None) { 54 | Err(Error::InvalidDeviceState) => (), 55 | _ => panic!(), 56 | } 57 | } 58 | 59 | #[test] 60 | fn can_convert_packed_bcd_to_decimal() { 61 | assert_eq!(0, packed_bcd_to_decimal(0b0000_0000)); 62 | assert_eq!(1, packed_bcd_to_decimal(0b0000_0001)); 63 | assert_eq!(9, packed_bcd_to_decimal(0b0000_1001)); 64 | assert_eq!(10, packed_bcd_to_decimal(0b0001_0000)); 65 | assert_eq!(11, packed_bcd_to_decimal(0b0001_0001)); 66 | assert_eq!(19, packed_bcd_to_decimal(0b0001_1001)); 67 | assert_eq!(20, packed_bcd_to_decimal(0b0010_0000)); 68 | assert_eq!(21, packed_bcd_to_decimal(0b0010_0001)); 69 | assert_eq!(59, packed_bcd_to_decimal(0b0101_1001)); 70 | } 71 | 72 | #[test] 73 | fn can_convert_decimal_to_packed_bcd() { 74 | assert_eq!(0b0000_0000, decimal_to_packed_bcd(0)); 75 | assert_eq!(0b0000_0001, decimal_to_packed_bcd(1)); 76 | assert_eq!(0b0000_1001, decimal_to_packed_bcd(9)); 77 | assert_eq!(0b0001_0000, decimal_to_packed_bcd(10)); 78 | assert_eq!(0b0001_0001, decimal_to_packed_bcd(11)); 79 | assert_eq!(0b0001_1001, decimal_to_packed_bcd(19)); 80 | assert_eq!(0b0010_0000, decimal_to_packed_bcd(20)); 81 | assert_eq!(0b0010_0001, decimal_to_packed_bcd(21)); 82 | assert_eq!(0b0101_1001, decimal_to_packed_bcd(59)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | 4 | env: 5 | RUSTFLAGS: '--deny warnings' 6 | 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | rust: [stable, 1.75.0] 14 | TARGET: 15 | - x86_64-unknown-linux-gnu 16 | - x86_64-unknown-linux-musl 17 | - arm-unknown-linux-gnueabi # Raspberry Pi 1 18 | - armv7-unknown-linux-gnueabihf # Raspberry Pi 2, 3, etc 19 | # Bare metal 20 | - thumbv6m-none-eabi 21 | - thumbv7em-none-eabi 22 | - thumbv7em-none-eabihf 23 | - thumbv7m-none-eabi 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: dtolnay/rust-toolchain@master 28 | with: 29 | toolchain: ${{ matrix.rust }} 30 | targets: ${{ matrix.TARGET }} 31 | 32 | - name: Checkout CI scripts 33 | uses: actions/checkout@v4 34 | with: 35 | repository: 'eldruin/rust-driver-ci-scripts' 36 | ref: 'master' 37 | path: 'ci' 38 | 39 | - run: ./ci/patch-no-std.sh 40 | if: ${{ ! contains(matrix.TARGET, 'x86_64') }} 41 | 42 | - run: cargo build --target=${{ matrix.TARGET }} 43 | 44 | checks: 45 | name: Checks 46 | runs-on: ubuntu-latest 47 | 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: dtolnay/rust-toolchain@stable 51 | with: 52 | targets: x86_64-unknown-linux-gnu 53 | components: rustfmt 54 | 55 | - run: cargo doc 56 | - run: cargo fmt --all -- --check 57 | 58 | clippy: 59 | name: Clippy 60 | runs-on: ubuntu-latest 61 | 62 | steps: 63 | - uses: actions/checkout@v4 64 | - uses: dtolnay/rust-toolchain@master 65 | with: 66 | toolchain: 1.83.0 67 | targets: x86_64-unknown-linux-gnu 68 | components: clippy 69 | 70 | - run: cargo clippy --all-targets 71 | 72 | test: 73 | name: Tests 74 | runs-on: ubuntu-latest 75 | strategy: 76 | matrix: 77 | rust: [stable] 78 | TARGET: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl] 79 | 80 | steps: 81 | - uses: actions/checkout@v4 82 | - uses: dtolnay/rust-toolchain@master 83 | with: 84 | toolchain: ${{ matrix.rust }} 85 | targets: ${{ matrix.TARGET }} 86 | 87 | - name: Test 88 | run: cargo test --target=${{ matrix.TARGET }} 89 | 90 | - name: Build examples 91 | run: cargo build --target=${{ matrix.TARGET }} --examples 92 | 93 | coverage: 94 | name: Coverage 95 | runs-on: ubuntu-latest 96 | steps: 97 | - uses: actions/checkout@v4 98 | - uses: dtolnay/rust-toolchain@stable 99 | 100 | - name: Install cargo-llvm-cov 101 | uses: taiki-e/install-action@cargo-llvm-cov 102 | 103 | - name: Generate code coverage 104 | run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info 105 | 106 | - name: upload to Coveralls 107 | uses: coverallsapp/github-action@master 108 | with: 109 | github-token: ${{ secrets.GITHUB_TOKEN }} 110 | path-to-lcov: './lcov.info' -------------------------------------------------------------------------------- /src/ds3234.rs: -------------------------------------------------------------------------------- 1 | //! Functions exclusive of DS3234 2 | use crate::interface::{SpiInterface, WriteData}; 3 | use crate::{ic, BitFlags, Ds323x, Error, Register, TempConvRate, CONTROL_POR_VALUE}; 4 | use core::marker::PhantomData; 5 | use embedded_hal::spi; 6 | 7 | impl Ds323x, ic::DS3234> 8 | where 9 | SPI: spi::SpiDevice, 10 | { 11 | /// Create a new instance. 12 | pub fn new_ds3234(spi: SPI) -> Self { 13 | const STATUS_POR_VALUE: u8 = BitFlags::OSC_STOP | BitFlags::BB32KHZ | BitFlags::EN32KHZ; 14 | Ds323x { 15 | iface: SpiInterface { spi }, 16 | control: CONTROL_POR_VALUE, 17 | status: STATUS_POR_VALUE, 18 | _ic: PhantomData, 19 | } 20 | } 21 | 22 | /// Destroy driver instance, return SPI bus instance and CS output pin. 23 | pub fn destroy_ds3234(self) -> SPI { 24 | self.iface.spi 25 | } 26 | 27 | /// Enable the 32kHz output when battery-powered. (enabled per default) 28 | /// 29 | /// Additionally, the 32kHz output needs to be enabled. See 30 | /// [`enable_32khz_output()`](#method.enable_32khz_output). 31 | /// 32 | /// Note: This is only available for DS3232 and DS3234 devices. 33 | pub fn enable_32khz_output_on_battery(&mut self) -> Result<(), Error> { 34 | let status = self.status | BitFlags::BB32KHZ; 35 | self.write_status_without_clearing_alarm(status) 36 | } 37 | 38 | /// Disable the 32kHz output when battery-powered. 39 | /// 40 | /// The 32kHz output will still generate a wave when not battery-powered if 41 | /// it enabled. See [`enable_32khz_output()`](#method.enable_32khz_output). 42 | /// 43 | /// Note: This is only available for DS3232 and DS3234 devices. 44 | pub fn disable_32khz_output_on_battery(&mut self) -> Result<(), Error> { 45 | let status = self.status & !BitFlags::BB32KHZ; 46 | self.write_status_without_clearing_alarm(status) 47 | } 48 | 49 | /// Set the temperature conversion rate. 50 | /// 51 | /// Set how often the temperature is measured and applies compensation to 52 | /// the oscillator. This can be used to reduce power consumption but sudden 53 | /// temperature changes will not be compensated for. 54 | /// 55 | /// Note: This is only available for DS3232 and DS3234 devices. 56 | pub fn set_temperature_conversion_rate(&mut self, rate: TempConvRate) -> Result<(), Error> { 57 | let status = match rate { 58 | TempConvRate::_64s => self.status & !BitFlags::CRATE1 & !BitFlags::CRATE0, 59 | TempConvRate::_128s => self.status & !BitFlags::CRATE1 | BitFlags::CRATE0, 60 | TempConvRate::_256s => self.status | BitFlags::CRATE1 & !BitFlags::CRATE0, 61 | TempConvRate::_512s => self.status | BitFlags::CRATE1 | BitFlags::CRATE0, 62 | }; 63 | self.write_status_without_clearing_alarm(status) 64 | } 65 | 66 | /// Enable the temperature conversions when battery-powered. (enabled per default) 67 | /// 68 | /// Note: This is only available for DS3234 devices. 69 | pub fn enable_temperature_conversions_on_battery(&mut self) -> Result<(), Error> { 70 | self.iface.write_register(Register::TEMP_CONV, 0) 71 | } 72 | 73 | /// Disable the temperature conversions when battery-powered. 74 | /// 75 | /// Note: This is only available for DS3234 devices. 76 | pub fn disable_temperature_conversions_on_battery(&mut self) -> Result<(), Error> { 77 | self.iface 78 | .write_register(Register::TEMP_CONV, BitFlags::TEMP_CONV_BAT) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/interface.rs: -------------------------------------------------------------------------------- 1 | //! I2C/SPI interfaces 2 | 3 | use crate::{private, Error, DEVICE_ADDRESS}; 4 | use embedded_hal::{i2c, spi}; 5 | 6 | /// I2C interface 7 | #[derive(Debug, Default)] 8 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 9 | pub struct I2cInterface { 10 | pub(crate) i2c: I2C, 11 | } 12 | 13 | /// SPI interface 14 | #[derive(Debug, Default)] 15 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 16 | pub struct SpiInterface { 17 | pub(crate) spi: SPI, 18 | } 19 | 20 | /// Write data 21 | pub trait WriteData: private::Sealed { 22 | /// Error type 23 | type Error; 24 | /// Write to an u8 register 25 | fn write_register(&mut self, register: u8, data: u8) -> Result<(), Self::Error>; 26 | /// Write data. The first element corresponds to the starting address. 27 | fn write_data(&mut self, payload: &mut [u8]) -> Result<(), Self::Error>; 28 | } 29 | 30 | impl WriteData for I2cInterface 31 | where 32 | I2C: i2c::I2c, 33 | { 34 | type Error = Error; 35 | fn write_register(&mut self, register: u8, data: u8) -> Result<(), Self::Error> { 36 | let payload: [u8; 2] = [register, data]; 37 | self.i2c 38 | .write(DEVICE_ADDRESS, &payload) 39 | .map_err(Error::Comm) 40 | } 41 | 42 | fn write_data(&mut self, payload: &mut [u8]) -> Result<(), Self::Error> { 43 | self.i2c.write(DEVICE_ADDRESS, payload).map_err(Error::Comm) 44 | } 45 | } 46 | 47 | impl WriteData for SpiInterface 48 | where 49 | SPI: spi::SpiDevice, 50 | { 51 | type Error = Error; 52 | fn write_register(&mut self, register: u8, data: u8) -> Result<(), Self::Error> { 53 | let payload: [u8; 2] = [register + 0x80, data]; 54 | self.spi.write(&payload).map_err(Error::Comm) 55 | } 56 | 57 | fn write_data(&mut self, payload: &mut [u8]) -> Result<(), Self::Error> { 58 | payload[0] += 0x80; 59 | self.spi.write(payload).map_err(Error::Comm) 60 | } 61 | } 62 | 63 | /// Read data 64 | pub trait ReadData: private::Sealed { 65 | /// Error type 66 | type Error; 67 | /// Read an u8 register 68 | fn read_register(&mut self, register: u8) -> Result; 69 | /// Read some data. The first element corresponds to the starting address. 70 | fn read_data(&mut self, payload: &mut [u8]) -> Result<(), Self::Error>; 71 | } 72 | 73 | impl ReadData for I2cInterface 74 | where 75 | I2C: i2c::I2c, 76 | { 77 | type Error = Error; 78 | fn read_register(&mut self, register: u8) -> Result { 79 | let mut data = [0]; 80 | self.i2c 81 | .write_read(DEVICE_ADDRESS, &[register], &mut data) 82 | .map_err(Error::Comm) 83 | .and(Ok(data[0])) 84 | } 85 | 86 | fn read_data(&mut self, payload: &mut [u8]) -> Result<(), Self::Error> { 87 | let len = payload.len(); 88 | self.i2c 89 | .write_read(DEVICE_ADDRESS, &[payload[0]], &mut payload[1..len]) 90 | .map_err(Error::Comm) 91 | } 92 | } 93 | 94 | impl ReadData for SpiInterface 95 | where 96 | SPI: spi::SpiDevice, 97 | { 98 | type Error = Error; 99 | fn read_register(&mut self, register: u8) -> Result { 100 | let mut data = [register, 0]; 101 | let result = self.spi.transfer_in_place(&mut data).map_err(Error::Comm); 102 | result.and(Ok(data[1])) 103 | } 104 | 105 | fn read_data(&mut self, payload: &mut [u8]) -> Result<(), Self::Error> { 106 | self.spi.transfer_in_place(payload).map_err(Error::Comm) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/ds3232_4.rs: -------------------------------------------------------------------------------- 1 | use ds323x::TempConvRate; 2 | use embedded_hal_mock::eh1::{i2c::Transaction as I2cTrans, spi::Transaction as SpiTrans}; 3 | 4 | #[allow(unused)] 5 | mod common; 6 | use self::common::{ 7 | destroy_ds3232, destroy_ds3234, new_ds3232, new_ds3234, BitFlags as BF, Register, 8 | DEVICE_ADDRESS as DEV_ADDR, DS323X_POR_STATUS, 9 | }; 10 | 11 | macro_rules! call_method_status_test { 12 | ($name:ident, $method:ident, $value:expr) => { 13 | mod $name { 14 | use super::*; 15 | call_test!( 16 | can_call_ds3232, 17 | $method, 18 | new_ds3232, 19 | destroy_ds3232, 20 | [I2cTrans::write(DEV_ADDR, vec![Register::STATUS, $value])] 21 | ); 22 | call_test!( 23 | can_call_ds3234, 24 | $method, 25 | new_ds3234, 26 | destroy_ds3234, 27 | [ 28 | SpiTrans::transaction_start(), 29 | SpiTrans::write_vec(vec![Register::STATUS + 0x80, $value]), 30 | SpiTrans::transaction_end(), 31 | ] 32 | ); 33 | } 34 | }; 35 | } 36 | 37 | #[macro_export] 38 | macro_rules! _set_param_test_2_4 { 39 | ($name:ident, $method:ident, $value:expr, $i2c_transactions:expr, $spi_transactions:expr) => { 40 | mod $name { 41 | use super::*; 42 | set_test!( 43 | can_set_ds3232, 44 | $method, 45 | new_ds3232, 46 | destroy_ds3232, 47 | $value, 48 | $i2c_transactions 49 | ); 50 | set_test!( 51 | can_set_ds3234, 52 | $method, 53 | new_ds3234, 54 | destroy_ds3234, 55 | $value, 56 | $spi_transactions 57 | ); 58 | } 59 | }; 60 | } 61 | 62 | #[macro_export] 63 | macro_rules! set_param_test_2_4 { 64 | ($name:ident, $method:ident, $register:ident, $value:expr, $binary_value:expr) => { 65 | _set_param_test_2_4!( 66 | $name, 67 | $method, 68 | $value, 69 | [I2cTrans::write( 70 | DEV_ADDR, 71 | vec![Register::$register, $binary_value] 72 | )], 73 | [ 74 | SpiTrans::transaction_start(), 75 | SpiTrans::write_vec(vec![Register::$register + 0x80, $binary_value]), 76 | SpiTrans::transaction_end(), 77 | ] 78 | ); 79 | }; 80 | } 81 | 82 | const DEFAULT_WRITE_STATUS: u8 = DS323X_POR_STATUS | BF::ALARM2F | BF::ALARM1F; 83 | 84 | call_method_status_test!( 85 | can_en_32khz_bat, 86 | enable_32khz_output_on_battery, 87 | DEFAULT_WRITE_STATUS | BF::BB32KHZ 88 | ); 89 | call_method_status_test!( 90 | can_dis_32khz_bat, 91 | disable_32khz_output_on_battery, 92 | DEFAULT_WRITE_STATUS & !BF::BB32KHZ 93 | ); 94 | 95 | set_param_test_2_4!( 96 | can_set_cr_64s, 97 | set_temperature_conversion_rate, 98 | STATUS, 99 | TempConvRate::_64s, 100 | DEFAULT_WRITE_STATUS & !BF::CRATE1 & !BF::CRATE0 101 | ); 102 | set_param_test_2_4!( 103 | can_set_cr_128s, 104 | set_temperature_conversion_rate, 105 | STATUS, 106 | TempConvRate::_128s, 107 | DEFAULT_WRITE_STATUS & !BF::CRATE1 | BF::CRATE0 108 | ); 109 | set_param_test_2_4!( 110 | can_set_cr_256s, 111 | set_temperature_conversion_rate, 112 | STATUS, 113 | TempConvRate::_256s, 114 | DEFAULT_WRITE_STATUS | BF::CRATE1 & !BF::CRATE0 115 | ); 116 | set_param_test_2_4!( 117 | can_set_cr_512s, 118 | set_temperature_conversion_rate, 119 | STATUS, 120 | TempConvRate::_512s, 121 | DEFAULT_WRITE_STATUS | BF::CRATE1 | BF::CRATE0 122 | ); 123 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | 9 | ## [Unreleased] - ReleaseDate 10 | 11 | ## [0.7.0] - 2025-10-11 12 | 13 | ### Added 14 | 15 | - Added `defmt` support behind a feature flag. 16 | 17 | ### Changed 18 | 19 | - Updated `rtcc` to version 0.4.0, which has `defmt` support. 20 | 21 | ## [0.6.0] - 2025-01-02 22 | 23 | ### Changed 24 | - [breaking-change] Removed `Error::Pin` variant. 25 | - [breaking-change] Update to `embedded-hal` 1.0.0. 26 | - Raised MSRV to version 1.75.0 27 | 28 | ## [0.5.1] - 2023-07-17 29 | 30 | ### Fixed 31 | - Fixed `set_day` method. See: [PR #9](https://github.com/eldruin/ds323x-rs/pull/9) 32 | 33 | ### Changed 34 | - Raised MSRV to version 1.60.0 35 | 36 | ## [0.5.0] - 2022-02-21 37 | 38 | ### Changed 39 | 40 | - [breaking-change] Update `rtcc` to version 0.3. 41 | - [breaking-change] Remove `get_` from all public method names to comply with the Rust API guidelines. 42 | - Raise MSRV to version 1.35.0 43 | 44 | ## [0.4.0] - 2021-05-22 45 | 46 | ### Changed 47 | - [breaking-change] Return `Error::InvalidDeviceState` if it was not possible to read the 48 | date and/or time from the device because the state of the device corresponds to 49 | an invalid date and/or time. 50 | 51 | ## [0.3.2] - 2021-02-22 52 | 53 | ### Fixed 54 | - Day bounds on the `set_day()` method. Thanks to @jamesmunns. See: 55 | [PR #5](https://github.com/eldruin/ds323x-rs/pull/5) 56 | 57 | ## [0.3.1] - 2020-07-10 58 | 59 | ### Added 60 | - Added methods to set alarms 1 and 2 with a `chrono::NaiveTime`: `set_alarm1_hms()` 61 | and `set_alarm2_hm()`. 62 | 63 | ### Changed 64 | - Changed alarm setting methods to automatically correct invalid values to irrelevant 65 | input parameters due to the selected matching strategy. 66 | 67 | ## [0.3.0] - 2020-05-02 68 | 69 | ### Changed 70 | - [breaking-change] Renamed `is_busy()` and `is_running()` methods `busy()` and `running()` 71 | due to Rust naming conventions. 72 | - Implement trait from [`rtcc`] crate. 73 | - Changed `get_datetime()` and `set_datetime()` parameter from `DateTime` 74 | to `chrono::NaiveDateTime`. 75 | 76 | ### Added 77 | - Methods to set and get date and time using `chrono::NaiveDate` and `chrono::NaiveTime`: 78 | - `get_time()` 79 | - `set_time()` 80 | - `get_date()` 81 | - `set_date()` 82 | - [`chrono`] (through [`rtcc`]) dependency. 83 | 84 | ### Removed 85 | - `DateTime` data structure was replaced by `chrono::NaiveDateTime`. 86 | 87 | ## [0.2.0] - 2018-11-16 88 | 89 | ### Added 90 | - Support for configuration of alarms 1 and 2. 91 | 92 | ### Changed 93 | - [breaking-change] `clear_has_been_stopped_flag()` always sets the value of the status register. 94 | 95 | ## 0.1.0 - 2018-10-31 96 | 97 | This is the initial release to crates.io. All changes will be documented in 98 | this CHANGELOG. 99 | 100 | [`chrono`]: https://crates.io/crates/chrono 101 | [`rtcc`]: https://crates.io/crates/rtcc 102 | 103 | 104 | [Unreleased]: https://github.com/eldruin/ds323x-rs/compare/v0.7.0...HEAD 105 | [0.7.0]: https://github.com/eldruin/ds323x-rs/compare/v0.6.0...v0.7.0 106 | [0.6.0]: https://github.com/eldruin/ds323x-rs/compare/v0.5.1...v0.6.0 107 | [0.5.1]: https://github.com/eldruin/ds323x-rs/compare/v0.5.0...v0.5.1 108 | [0.5.0]: https://github.com/eldruin/ds323x-rs/compare/v0.4.0...v0.5.0 109 | [0.4.0]: https://github.com/eldruin/ds323x-rs/compare/v0.3.2...v0.4.0 110 | [0.3.2]: https://github.com/eldruin/ds323x-rs/compare/v0.3.1...v0.3.2 111 | [0.3.1]: https://github.com/eldruin/ds323x-rs/compare/v0.3.0...v0.3.1 112 | [0.3.0]: https://github.com/eldruin/ds323x-rs/compare/v0.2.0...v0.3.0 113 | [0.2.0]: https://github.com/eldruin/ds323x-rs/compare/v0.1.0...v0.2.0 114 | -------------------------------------------------------------------------------- /src/ds323x/status.rs: -------------------------------------------------------------------------------- 1 | //! Device status 2 | 3 | use crate::{ 4 | interface::{ReadData, WriteData}, 5 | BitFlags, Ds323x, Error, Register, 6 | }; 7 | 8 | impl Ds323x 9 | where 10 | DI: ReadData> + WriteData>, 11 | { 12 | /// Read whether the oscillator is running 13 | pub fn running(&mut self) -> Result> { 14 | let control = self.iface.read_register(Register::CONTROL)?; 15 | Ok((control & BitFlags::EOSC) == 0) 16 | } 17 | 18 | /// Read the busy status 19 | pub fn busy(&mut self) -> Result> { 20 | let status = self.iface.read_register(Register::STATUS)?; 21 | Ok((status & BitFlags::BUSY) != 0) 22 | } 23 | 24 | /// Read whether the oscillator is stopped or has been stopped at 25 | /// some point. 26 | /// 27 | /// This allows a better assessment of the validity of the timekeeping data. 28 | /// 29 | /// Once this is true, it will stay as such until cleared with 30 | /// [`clear_has_been_stopped_flag()`](#method.clear_has_been_stopped_flag) 31 | pub fn has_been_stopped(&mut self) -> Result> { 32 | let status = self.iface.read_register(Register::STATUS)?; 33 | Ok((status & BitFlags::OSC_STOP) != 0) 34 | } 35 | 36 | /// Clear flag signalling whether the oscillator is stopped or has been 37 | /// stopped at some point. 38 | /// 39 | /// See also: [`has_been_stopped()`](#method.has_been_stopped) 40 | pub fn clear_has_been_stopped_flag(&mut self) -> Result<(), Error> { 41 | let status = self.status & !BitFlags::OSC_STOP; 42 | self.write_status_without_clearing_alarm(status) 43 | } 44 | 45 | /// Read whether the Alarm1 has matched at some point. 46 | /// 47 | /// Once this is true, it will stay as such until cleared with 48 | /// [`clear_alarm1_matched_flag()`](#method.clear_alarm1_matched_flag) 49 | pub fn has_alarm1_matched(&mut self) -> Result> { 50 | let status = self.iface.read_register(Register::STATUS)?; 51 | Ok((status & BitFlags::ALARM1F) != 0) 52 | } 53 | 54 | /// Clear flag signalling whether the Alarm1 has matched at some point. 55 | /// 56 | /// See also: [`has_alarm1_matched()`](#method.has_alarm1_matched) 57 | pub fn clear_alarm1_matched_flag(&mut self) -> Result<(), Error> { 58 | let status = self.status | BitFlags::ALARM2F; 59 | self.iface.write_register(Register::STATUS, status) 60 | } 61 | 62 | /// Read whether the Alarm2 has matched at some point. 63 | /// 64 | /// Once this is true, it will stay as such until cleared with 65 | /// [`clear_alarm2_matched_flag()`](#method.clear_alarm2_matched_flag) 66 | pub fn has_alarm2_matched(&mut self) -> Result> { 67 | let status = self.iface.read_register(Register::STATUS)?; 68 | Ok((status & BitFlags::ALARM2F) != 0) 69 | } 70 | 71 | /// Clear flag signalling whether the Alarm2 has matched at some point. 72 | /// 73 | /// See also: [`has_alarm2_matched()`](#method.has_alarm2_matched) 74 | pub fn clear_alarm2_matched_flag(&mut self) -> Result<(), Error> { 75 | let status = self.status | BitFlags::ALARM1F; 76 | self.iface.write_register(Register::STATUS, status) 77 | } 78 | 79 | /// Read the temperature. 80 | /// 81 | /// Note: It is possible to manually force a temperature conversion with 82 | /// [`convert_temperature()`](#method.convert_temperature) 83 | pub fn temperature(&mut self) -> Result> { 84 | let mut data = [Register::TEMP_MSB, 0, 0]; 85 | self.iface.read_data(&mut data)?; 86 | let is_negative = (data[1] & 0b1000_0000) != 0; 87 | let temp = (u16::from(data[1]) << 2) | u16::from(data[2] >> 6); 88 | if is_negative { 89 | let temp_sign_extended = temp | 0b1111_1100_0000_0000; 90 | Ok(f32::from(temp_sign_extended as i16) * 0.25) 91 | } else { 92 | Ok(f32::from(temp) * 0.25) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/ds323x/configuration.rs: -------------------------------------------------------------------------------- 1 | //! Device configuration 2 | 3 | use crate::{ 4 | interface::{ReadData, WriteData}, 5 | BitFlags, Ds323x, Error, Register, SqWFreq, 6 | }; 7 | 8 | impl Ds323x 9 | where 10 | DI: ReadData> + WriteData>, 11 | { 12 | /// Enable the oscillator (set the clock running) (default). 13 | pub fn enable(&mut self) -> Result<(), Error> { 14 | let control = self.control; 15 | self.write_control(control & !BitFlags::EOSC) 16 | } 17 | 18 | /// Disable the oscillator (stops the clock). 19 | pub fn disable(&mut self) -> Result<(), Error> { 20 | let control = self.control; 21 | self.write_control(control | BitFlags::EOSC) 22 | } 23 | 24 | /// Force a temperature conversion and time compensation with TXCO algorithm. 25 | /// 26 | /// The *busy* status should be checked before doing this. See [`busy()`](#method.busy) 27 | pub fn convert_temperature(&mut self) -> Result<(), Error> { 28 | let control = self.iface.read_register(Register::CONTROL)?; 29 | // do not overwrite if a conversion is in progress 30 | if (control & BitFlags::TEMP_CONV) == 0 { 31 | self.iface 32 | .write_register(Register::CONTROL, control | BitFlags::TEMP_CONV)?; 33 | } 34 | Ok(()) 35 | } 36 | 37 | /// Enable the 32kHz output. (enabled per default) 38 | pub fn enable_32khz_output(&mut self) -> Result<(), Error> { 39 | let status = self.status | BitFlags::EN32KHZ; 40 | self.write_status_without_clearing_alarm(status) 41 | } 42 | 43 | /// Disable the 32kHz output. 44 | pub fn disable_32khz_output(&mut self) -> Result<(), Error> { 45 | let status = self.status & !BitFlags::EN32KHZ; 46 | self.write_status_without_clearing_alarm(status) 47 | } 48 | 49 | /// Set the aging offset. 50 | pub fn set_aging_offset(&mut self, offset: i8) -> Result<(), Error> { 51 | self.iface 52 | .write_register(Register::AGING_OFFSET, offset as u8) 53 | } 54 | 55 | /// Read the aging offset. 56 | pub fn aging_offset(&mut self) -> Result> { 57 | let offset = self.iface.read_register(Register::AGING_OFFSET)?; 58 | Ok(offset as i8) 59 | } 60 | 61 | /// Set the interrupt/square-wave output to be used as interrupt output. 62 | pub fn use_int_sqw_output_as_interrupt(&mut self) -> Result<(), Error> { 63 | let control = self.control; 64 | self.write_control(control | BitFlags::INTCN) 65 | } 66 | 67 | /// Set the interrupt/square-wave output to be used as square-wave output. (default) 68 | pub fn use_int_sqw_output_as_square_wave(&mut self) -> Result<(), Error> { 69 | let control = self.control; 70 | self.write_control(control & !BitFlags::INTCN) 71 | } 72 | 73 | /// Enable battery-backed square wave generation. 74 | pub fn enable_square_wave(&mut self) -> Result<(), Error> { 75 | let control = self.control; 76 | self.write_control(control | BitFlags::BBSQW) 77 | } 78 | 79 | /// Disable battery-backed square wave generation. 80 | pub fn disable_square_wave(&mut self) -> Result<(), Error> { 81 | let control = self.control; 82 | self.write_control(control & !BitFlags::BBSQW) 83 | } 84 | 85 | /// Set the square-wave output frequency. 86 | pub fn set_square_wave_frequency(&mut self, freq: SqWFreq) -> Result<(), Error> { 87 | let new_control = match freq { 88 | SqWFreq::_1Hz => self.control & !BitFlags::RS2 & !BitFlags::RS1, 89 | SqWFreq::_1_024Hz => self.control & !BitFlags::RS2 | BitFlags::RS1, 90 | SqWFreq::_4_096Hz => self.control | BitFlags::RS2 & !BitFlags::RS1, 91 | SqWFreq::_8_192Hz => self.control | BitFlags::RS2 | BitFlags::RS1, 92 | }; 93 | self.write_control(new_control) 94 | } 95 | 96 | /// Enable Alarm1 interrupts. 97 | pub fn enable_alarm1_interrupts(&mut self) -> Result<(), Error> { 98 | let control = self.control; 99 | self.write_control(control | BitFlags::ALARM1_INT_EN) 100 | } 101 | 102 | /// Disable Alarm1 interrupts. 103 | pub fn disable_alarm1_interrupts(&mut self) -> Result<(), Error> { 104 | let control = self.control; 105 | self.write_control(control & !BitFlags::ALARM1_INT_EN) 106 | } 107 | 108 | /// Enable Alarm2 interrupts. 109 | pub fn enable_alarm2_interrupts(&mut self) -> Result<(), Error> { 110 | let control = self.control; 111 | self.write_control(control | BitFlags::ALARM2_INT_EN) 112 | } 113 | 114 | /// Disable Alarm2 interrupts. 115 | pub fn disable_alarm2_interrupts(&mut self) -> Result<(), Error> { 116 | let control = self.control; 117 | self.write_control(control & !BitFlags::ALARM2_INT_EN) 118 | } 119 | 120 | fn write_control(&mut self, control: u8) -> Result<(), Error> { 121 | self.iface.write_register(Register::CONTROL, control)?; 122 | self.control = control; 123 | Ok(()) 124 | } 125 | 126 | pub(crate) fn write_status_without_clearing_alarm( 127 | &mut self, 128 | status: u8, 129 | ) -> Result<(), Error> { 130 | // avoid clearing alarm flags 131 | let new_status = status | BitFlags::ALARM2F | BitFlags::ALARM1F; 132 | self.iface.write_register(Register::STATUS, new_status)?; 133 | self.status = status; 134 | Ok(()) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust DS3231, DS3232 and DS3234 Extremely Accurate Real-Time Clock Driver 2 | 3 | [![crates.io](https://img.shields.io/crates/v/ds323x.svg)](https://crates.io/crates/ds323x) 4 | [![Docs](https://docs.rs/ds323x/badge.svg)](https://docs.rs/ds323x) 5 | ![MSRV](https://img.shields.io/badge/rustc-1.75+-blue.svg) 6 | [![Build Status](https://github.com/eldruin/ds323x-rs/workflows/Build/badge.svg)](https://github.com/eldruin/ds323x-rs/actions?query=workflow%3ABuild) 7 | [![Coverage Status](https://coveralls.io/repos/eldruin/ds323x-rs/badge.svg?branch=master)](https://coveralls.io/r/eldruin/ds323x-rs?branch=master) 8 | 9 | This is a platform agnostic Rust driver for the DS3231, DS3232 and DS3234 10 | extremely accurate real-time clocks, based on the [`embedded-hal`] traits. 11 | 12 | [`embedded-hal`]: https://github.com/rust-embedded/embedded-hal 13 | 14 | This driver allows you to: 15 | - Read and set date and time in 12-hour and 24-hour format. See: `datetime`. 16 | - Read and set date and time individual elements. For example, see: `year`. 17 | - Enable and disable the real-time clock. See: `enable`. 18 | - Read the busy status. See `busy`. 19 | - Read whether the oscillator is or has been stopped. See `has_been_stopped`. 20 | - Clear the has-been-stopped flag. See `clear_has_been_stopped_flag`. 21 | - Set and read the aging offset. See `set_aging_offset`. 22 | - Select the function of the INT/SQW output pin. See `use_int_sqw_output_as_interrupt`. 23 | - Alarms: 24 | - Set alarms 1 and 2 with several matching policies. See `set_alarm1_day`. 25 | - Set alarms 1 and 2 for a time. See `set_alarm1_hms`. 26 | - Read whether alarms 1 or 2 have matched. See `has_alarm1_matched`. 27 | - Clear flag indicating that alarms 1 or 2 have matched. See `clear_alarm1_matched_flag`. 28 | - Enable and disable alarms 1 and 2 interrupt generation. See `enable_alarm1_interrupts`. 29 | - Wave generation: 30 | - Enable and disable the square-wave generation. See `enable_square_wave`. 31 | - Select the square-wave frequency. See `set_square_wave_frequency`. 32 | - Enable and disable the 32kHz output. See `enable_32khz_output`. 33 | - Enable and disable the 32kHz output when battery powered. See `enable_32khz_output_on_battery`. 34 | - Temperature conversion: 35 | - Read the temperature. See `temperature`. 36 | - Force a temperature conversion and time compensation. See `convert_temperature`. 37 | - Set the temperature conversion rate. See `set_temperature_conversion_rate`. 38 | - Enable and disable the temperature conversions when battery-powered. See `enable_temperature_conversions_on_battery`. 39 | 40 | ## The devices 41 | 42 | This driver is compatible with the DS3231 and DS3232 I2C devices and the 43 | DS3234 SPI device. 44 | 45 | These devices are low-cost temperature-compensated crystal oscillator (TCXO) 46 | with a very accurate, temperature-compensated, integrated real-time clock 47 | (RTC) including 236/256 bytes of battery-backed SRAM, depending on the model. 48 | 49 | ### DS3231 and DS3232 details 50 | 51 | The devices incorporate a battery input, and maintain accurate timekeeping 52 | when main power to the devices is interrupted. The integration of the 53 | crystal resonator enhances the long-term accuracy of the devices as well as 54 | reduces the piece-part count in a manufacturing line. 55 | The devices are available in commercial and industrial temperature ranges, 56 | and are offered in a 16-pin, 300-mil SO package. 57 | 58 | The RTC maintains seconds, minutes, hours, day, date, month, and year 59 | information. The date at the end of the month is automatically adjusted for 60 | months with fewer than 31 days, including corrections for leap year. The 61 | clock operates in either the 24-hour or 12-hour format with an AM/PM 62 | indicator. Two programmable time-of-day alarms and a programmable 63 | square-wave output are provided. Address and data are transferred serially 64 | through an I2C bidirectional bus. 65 | 66 | A precision temperature-compensated voltage reference and comparator 67 | circuit monitors the status of VCC to detect power failures, to provide a 68 | reset output, and to automatically switch to the backup supply when 69 | necessary. Additionally, the RST pin is monitored as a pushbutton 70 | input for generating a μP reset. 71 | 72 | ### DS3234 details 73 | 74 | The DS3234 incorporates a precision, temperature-compensated voltage 75 | reference and comparator circuit to monitor VCC. When VCC drops below the 76 | power-fail voltage (VPF), the device asserts the RST output and also 77 | disables read and write access to the part when VCC drops below both VPF 78 | and VBAT. The RST pin is monitored as a pushbutton input for generating a 79 | μP reset. The device switches to the backup supply input and maintains 80 | accurate timekeeping when main power to the device is interrupted. 81 | The integration of the crystal resonator enhances the long-term accuracy of 82 | the device as well as reduces the piece-part count in a manufacturing line. 83 | The DS3234 is available in commercial and industrial temperature ranges, 84 | and is offered in an industry-standard 300-mil, 20-pin SO package. 85 | 86 | The DS3234 also integrates 256 bytes of battery-backed SRAM. In the event 87 | of main power loss, the contents of the memory are maintained by the power 88 | source connected to the V BAT pin. The RTC maintains seconds, minutes, 89 | hours, day, date, month, and year information. The date at the end of the 90 | month is automatically adjusted for months with fewer than 31 days, 91 | including corrections for leap year. The clock operates in either the 92 | 24-hour or 12-hour format with AM/PM indicator. Two programmable 93 | time-of-day alarms and a programmable square-wave output are provided. 94 | Address and data are transferred serially by an SPI bidirectional bus. 95 | 96 | Datasheets: 97 | - [DS3231](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) 98 | - [DS3232](https://datasheets.maximintegrated.com/en/ds/DS3232.pdf) 99 | - [DS3234](https://datasheets.maximintegrated.com/en/ds/DS3234.pdf) 100 | 101 | ## Usage 102 | 103 | To use this driver, import this crate and an `embedded_hal` implementation, 104 | then instantiate the appropriate device. 105 | In the following example an instance of the device DS3231 will be created. 106 | Other devices can be created with similar methods like: 107 | `Ds323x::new_ds3234(...)`. 108 | 109 | Please find additional examples using hardware in this repository: [driver-examples] 110 | 111 | [driver-examples]: https://github.com/eldruin/driver-examples 112 | 113 | ```rust 114 | use ds323x::{DateTimeAccess, Ds323x, NaiveDate, Rtcc}; 115 | use linux_embedded_hal::I2cdev; 116 | 117 | fn main() { 118 | let dev = I2cdev::new("/dev/i2c-1").unwrap(); 119 | let mut rtc = Ds323x::new_ds3231(dev); 120 | let datetime = NaiveDate::from_ymd_opt(2020, 5, 1) 121 | .unwrap() 122 | .and_hms_opt(19, 59, 58) 123 | .unwrap(); 124 | rtc.set_datetime(&datetime).unwrap(); 125 | // do something else... 126 | let time = rtc.time().unwrap(); 127 | println!("Time: {}", time); 128 | 129 | let _dev = rtc.destroy_ds3231(); 130 | } 131 | ``` 132 | 133 | ## Support 134 | 135 | For questions, issues, feature requests like compatibility with other devices and other 136 | changes, please file an 137 | [issue in the github project](https://github.com/eldruin/ds323x-rs/issues). 138 | 139 | ## Minimum Supported Rust Version (MSRV) 140 | 141 | This crate is guaranteed to compile on stable Rust 1.75 and up. It *might* 142 | compile with older versions but that may change in any new patch release. 143 | 144 | ## License 145 | 146 | Licensed under either of 147 | 148 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 149 | http://www.apache.org/licenses/LICENSE-2.0) 150 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 151 | http://opensource.org/licenses/MIT) 152 | 153 | at your option. 154 | 155 | ### Contributing 156 | 157 | Unless you explicitly state otherwise, any contribution intentionally submitted 158 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 159 | be dual licensed as above, without any additional terms or conditions. 160 | -------------------------------------------------------------------------------- /tests/configuration.rs: -------------------------------------------------------------------------------- 1 | use ds323x::SqWFreq; 2 | use embedded_hal_mock::eh1::{i2c::Transaction as I2cTrans, spi::Transaction as SpiTrans}; 3 | 4 | mod common; 5 | use self::common::{ 6 | destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, 7 | BitFlags as BF, Register, CONTROL_POR_VALUE, DEVICE_ADDRESS as DEV_ADDR, DS3231_POR_STATUS, 8 | DS323X_POR_STATUS, 9 | }; 10 | 11 | macro_rules! call_triple_test { 12 | ($name:ident, $method:ident, $i2c_transactions:expr, $spi_transactions:expr) => { 13 | mod $name { 14 | use super::*; 15 | call_test!( 16 | can_call_ds3231, 17 | $method, 18 | new_ds3231, 19 | destroy_ds3231, 20 | $i2c_transactions 21 | ); 22 | call_test!( 23 | can_call_ds3232, 24 | $method, 25 | new_ds3232, 26 | destroy_ds3232, 27 | $i2c_transactions 28 | ); 29 | call_test!( 30 | can_call_ds3234, 31 | $method, 32 | new_ds3234, 33 | destroy_ds3234, 34 | $spi_transactions 35 | ); 36 | } 37 | }; 38 | } 39 | 40 | macro_rules! call_method_test { 41 | ($name:ident, $method:ident, $register:ident, $value_enabled:expr) => { 42 | call_triple_test!( 43 | $name, 44 | $method, 45 | [I2cTrans::write( 46 | DEV_ADDR, 47 | vec![Register::$register, $value_enabled] 48 | )], 49 | [ 50 | SpiTrans::transaction_start(), 51 | SpiTrans::write_vec(vec![Register::$register + 0x80, $value_enabled]), 52 | SpiTrans::transaction_end(), 53 | ] 54 | ); 55 | }; 56 | } 57 | 58 | macro_rules! call_method_status_test { 59 | ($name:ident, $method:ident, $value_ds3231:expr, $value_ds323x:expr) => { 60 | mod $name { 61 | use super::*; 62 | call_test!( 63 | can_call_ds3231, 64 | $method, 65 | new_ds3231, 66 | destroy_ds3231, 67 | [I2cTrans::write( 68 | DEV_ADDR, 69 | vec![Register::STATUS, $value_ds3231] 70 | )] 71 | ); 72 | call_test!( 73 | can_call_ds3232, 74 | $method, 75 | new_ds3232, 76 | destroy_ds3232, 77 | [I2cTrans::write( 78 | DEV_ADDR, 79 | vec![Register::STATUS, $value_ds323x] 80 | )] 81 | ); 82 | call_test!( 83 | can_call_ds3234, 84 | $method, 85 | new_ds3234, 86 | destroy_ds3234, 87 | [ 88 | SpiTrans::transaction_start(), 89 | SpiTrans::write_vec(vec![Register::STATUS + 0x80, $value_ds323x]), 90 | SpiTrans::transaction_end(), 91 | ] 92 | ); 93 | } 94 | }; 95 | } 96 | 97 | macro_rules! change_if_necessary_test { 98 | ($name:ident, $method:ident, $register:ident, $value_enabled:expr, $value_disabled:expr) => { 99 | mod $name { 100 | use super::*; 101 | call_triple_test!( 102 | do_nothing_if_not_necessary, 103 | $method, 104 | [I2cTrans::write_read( 105 | DEV_ADDR, 106 | vec![Register::$register], 107 | vec![$value_enabled] 108 | )], 109 | [ 110 | SpiTrans::transaction_start(), 111 | SpiTrans::transfer_in_place( 112 | vec![Register::$register, 0], 113 | vec![Register::$register, $value_enabled] 114 | ), 115 | SpiTrans::transaction_end(), 116 | ] 117 | ); 118 | 119 | call_triple_test!( 120 | change, 121 | $method, 122 | [ 123 | I2cTrans::write_read( 124 | DEV_ADDR, 125 | vec![Register::$register], 126 | vec![$value_disabled] 127 | ), 128 | I2cTrans::write(DEV_ADDR, vec![Register::$register, $value_enabled]) 129 | ], 130 | [ 131 | SpiTrans::transaction_start(), 132 | SpiTrans::transfer_in_place( 133 | vec![Register::$register, 0], 134 | vec![Register::$register, $value_disabled] 135 | ), 136 | SpiTrans::transaction_end(), 137 | SpiTrans::transaction_start(), 138 | SpiTrans::write_vec(vec![Register::$register + 0x80, $value_enabled]), 139 | SpiTrans::transaction_end(), 140 | ] 141 | ); 142 | } 143 | }; 144 | } 145 | 146 | call_method_test!(enable, enable, CONTROL, CONTROL_POR_VALUE & !BF::EOSC); 147 | call_method_test!(disable, disable, CONTROL, CONTROL_POR_VALUE | BF::EOSC); 148 | call_method_status_test!( 149 | en_32khz_out, 150 | enable_32khz_output, 151 | DS3231_POR_STATUS | BF::EN32KHZ | BF::ALARM2F | BF::ALARM1F, 152 | DS323X_POR_STATUS | BF::EN32KHZ | BF::ALARM2F | BF::ALARM1F 153 | ); 154 | call_method_status_test!( 155 | dis_32khz_out, 156 | disable_32khz_output, 157 | DS3231_POR_STATUS & !BF::EN32KHZ | BF::ALARM2F | BF::ALARM1F, 158 | DS323X_POR_STATUS & !BF::EN32KHZ | BF::ALARM2F | BF::ALARM1F 159 | ); 160 | 161 | call_method_status_test!( 162 | clear_alarm1_matched, 163 | clear_alarm1_matched_flag, 164 | DS3231_POR_STATUS | BF::ALARM2F, 165 | DS323X_POR_STATUS | BF::ALARM2F 166 | ); 167 | 168 | call_method_status_test!( 169 | clear_alarm2_matched, 170 | clear_alarm2_matched_flag, 171 | DS3231_POR_STATUS | BF::ALARM1F, 172 | DS323X_POR_STATUS | BF::ALARM1F 173 | ); 174 | 175 | call_method_status_test!( 176 | clr_stop, 177 | clear_has_been_stopped_flag, 178 | DS3231_POR_STATUS & !BF::OSC_STOP | BF::ALARM2F | BF::ALARM1F, 179 | DS323X_POR_STATUS & !BF::OSC_STOP | BF::ALARM2F | BF::ALARM1F 180 | ); 181 | 182 | change_if_necessary_test!( 183 | conv_temp, 184 | convert_temperature, 185 | CONTROL, 186 | CONTROL_POR_VALUE | BF::TEMP_CONV, 187 | CONTROL_POR_VALUE & !BF::TEMP_CONV 188 | ); 189 | 190 | call_method_test!( 191 | en_al1_int, 192 | enable_alarm1_interrupts, 193 | CONTROL, 194 | CONTROL_POR_VALUE | BF::ALARM1_INT_EN 195 | ); 196 | call_method_test!( 197 | dis_al1_int, 198 | disable_alarm1_interrupts, 199 | CONTROL, 200 | CONTROL_POR_VALUE & !BF::ALARM1_INT_EN 201 | ); 202 | 203 | call_method_test!( 204 | en_al2_int, 205 | enable_alarm2_interrupts, 206 | CONTROL, 207 | CONTROL_POR_VALUE | BF::ALARM2_INT_EN 208 | ); 209 | call_method_test!( 210 | dis_al2_int, 211 | disable_alarm2_interrupts, 212 | CONTROL, 213 | CONTROL_POR_VALUE & !BF::ALARM2_INT_EN 214 | ); 215 | 216 | set_param_test!( 217 | set_aging_offset_min, 218 | set_aging_offset, 219 | AGING_OFFSET, 220 | -128, 221 | 0b1000_0000 222 | ); 223 | set_param_test!( 224 | set_aging_offset_max, 225 | set_aging_offset, 226 | AGING_OFFSET, 227 | 127, 228 | 127 229 | ); 230 | 231 | get_param_test!( 232 | get_aging_offset_min, 233 | aging_offset, 234 | AGING_OFFSET, 235 | -128, 236 | 0b1000_0000 237 | ); 238 | get_param_test!(get_aging_offset_max, aging_offset, AGING_OFFSET, 127, 127); 239 | 240 | call_method_test!( 241 | int_sqw_out_int, 242 | use_int_sqw_output_as_interrupt, 243 | CONTROL, 244 | CONTROL_POR_VALUE | BF::INTCN 245 | ); 246 | call_method_test!( 247 | int_sqw_out_sqw, 248 | use_int_sqw_output_as_square_wave, 249 | CONTROL, 250 | CONTROL_POR_VALUE & !BF::INTCN 251 | ); 252 | 253 | call_method_test!( 254 | enable_sqw, 255 | enable_square_wave, 256 | CONTROL, 257 | CONTROL_POR_VALUE | BF::BBSQW 258 | ); 259 | call_method_test!( 260 | disable_sqw, 261 | disable_square_wave, 262 | CONTROL, 263 | CONTROL_POR_VALUE & !BF::BBSQW 264 | ); 265 | 266 | set_param_test!( 267 | set_sqw_freq_1, 268 | set_square_wave_frequency, 269 | CONTROL, 270 | SqWFreq::_1Hz, 271 | CONTROL_POR_VALUE & !BF::RS2 & !BF::RS1 272 | ); 273 | set_param_test!( 274 | set_sqw_freq_1_024, 275 | set_square_wave_frequency, 276 | CONTROL, 277 | SqWFreq::_1_024Hz, 278 | CONTROL_POR_VALUE & !BF::RS2 | BF::RS1 279 | ); 280 | set_param_test!( 281 | set_sqw_freq_4_096, 282 | set_square_wave_frequency, 283 | CONTROL, 284 | SqWFreq::_4_096Hz, 285 | CONTROL_POR_VALUE | BF::RS2 & !BF::RS1 286 | ); 287 | set_param_test!( 288 | set_sqw_freq_8_192, 289 | set_square_wave_frequency, 290 | CONTROL, 291 | SqWFreq::_8_192Hz, 292 | CONTROL_POR_VALUE | BF::RS2 | BF::RS1 293 | ); 294 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use ds323x::{ic, interface, Ds323x}; 2 | use embedded_hal_mock::eh1::{ 3 | i2c::{Mock as I2cMock, Transaction as I2cTrans}, 4 | spi::{Mock as SpiMock, Transaction as SpiTrans}, 5 | }; 6 | 7 | #[allow(unused)] 8 | pub const DEVICE_ADDRESS: u8 = 0b110_1000; 9 | #[allow(unused)] 10 | pub const CONTROL_POR_VALUE: u8 = 0b0001_1100; 11 | #[allow(unused)] 12 | pub const DS3231_POR_STATUS: u8 = BitFlags::OSC_STOP | BitFlags::EN32KHZ; 13 | #[allow(unused)] 14 | pub const DS323X_POR_STATUS: u8 = BitFlags::OSC_STOP | BitFlags::BB32KHZ | BitFlags::EN32KHZ; 15 | 16 | pub struct Register; 17 | 18 | #[allow(unused)] 19 | impl Register { 20 | pub const SECONDS: u8 = 0x00; 21 | pub const MINUTES: u8 = 0x01; 22 | pub const HOURS: u8 = 0x02; 23 | pub const DOW: u8 = 0x03; 24 | pub const DOM: u8 = 0x04; 25 | pub const MONTH: u8 = 0x05; 26 | pub const ALARM1_SECONDS: u8 = 0x07; 27 | pub const ALARM2_MINUTES: u8 = 0x0B; 28 | pub const CONTROL: u8 = 0x0E; 29 | pub const STATUS: u8 = 0x0F; 30 | pub const AGING_OFFSET: u8 = 0x10; 31 | pub const TEMP_MSB: u8 = 0x11; 32 | pub const TEMP_CONV: u8 = 0x13; 33 | } 34 | 35 | pub struct BitFlags; 36 | 37 | #[allow(unused)] 38 | impl BitFlags { 39 | pub const EOSC: u8 = 0b1000_0000; 40 | pub const BBSQW: u8 = 0b0100_0000; 41 | pub const TEMP_CONV: u8 = 0b0010_0000; 42 | pub const RS2: u8 = 0b0001_0000; 43 | pub const RS1: u8 = 0b0000_1000; 44 | pub const INTCN: u8 = 0b0000_0100; 45 | pub const ALARM2_INT_EN: u8 = 0b0000_0010; 46 | pub const ALARM1_INT_EN: u8 = 0b0000_0001; 47 | pub const OSC_STOP: u8 = 0b1000_0000; 48 | pub const BB32KHZ: u8 = 0b0100_0000; 49 | pub const CRATE1: u8 = 0b0010_0000; 50 | pub const CRATE0: u8 = 0b0001_0000; 51 | pub const EN32KHZ: u8 = 0b0000_1000; 52 | pub const BUSY: u8 = 0b0000_0100; 53 | pub const ALARM2F: u8 = 0b0000_0010; 54 | pub const ALARM1F: u8 = 0b0000_0001; 55 | pub const TEMP_CONV_BAT: u8 = 0b0000_0001; 56 | pub const ALARM_MATCH: u8 = 0b1000_0000; 57 | pub const WEEKDAY: u8 = 0b0100_0000; 58 | } 59 | 60 | pub fn new_ds3231( 61 | transactions: &[I2cTrans], 62 | ) -> Ds323x, ic::DS3231> { 63 | Ds323x::new_ds3231(I2cMock::new(transactions)) 64 | } 65 | 66 | pub fn new_ds3232( 67 | transactions: &[I2cTrans], 68 | ) -> Ds323x, ic::DS3232> { 69 | Ds323x::new_ds3232(I2cMock::new(transactions)) 70 | } 71 | 72 | pub fn new_ds3234( 73 | transactions: &[SpiTrans], 74 | ) -> Ds323x>, ic::DS3234> { 75 | Ds323x::new_ds3234(SpiMock::new(transactions)) 76 | } 77 | 78 | pub fn destroy_ds3231(dev: Ds323x, ic::DS3231>) { 79 | dev.destroy_ds3231().done(); 80 | } 81 | 82 | pub fn destroy_ds3232(dev: Ds323x, ic::DS3232>) { 83 | dev.destroy_ds3232().done(); 84 | } 85 | 86 | pub fn destroy_ds3234(dev: Ds323x>, ic::DS3234>) { 87 | dev.destroy_ds3234().done(); 88 | } 89 | 90 | #[macro_export] 91 | macro_rules! get_test { 92 | ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $expected:expr, $transactions:expr) => { 93 | #[test] 94 | fn $name() { 95 | let trans = $transactions; 96 | let mut dev = $create_method(&trans); 97 | assert_eq!($expected, dev.$method().unwrap()); 98 | $destroy_method(dev); 99 | } 100 | }; 101 | } 102 | 103 | #[macro_export] 104 | macro_rules! set_test { 105 | ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $value:expr, $transactions:expr) => { 106 | #[test] 107 | fn $name() { 108 | let trans = $transactions; 109 | let mut dev = $create_method(&trans); 110 | dev.$method($value).unwrap(); 111 | $destroy_method(dev); 112 | } 113 | }; 114 | } 115 | 116 | #[macro_export] 117 | macro_rules! assert_invalid_input_data { 118 | ($result:expr) => { 119 | match $result { 120 | Err(Error::InvalidInputData) => (), 121 | _ => panic!("InvalidInputData error not returned."), 122 | } 123 | }; 124 | } 125 | 126 | #[macro_export] 127 | macro_rules! set_invalid_test { 128 | ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $value:expr) => { 129 | #[test] 130 | fn $name() { 131 | let mut dev = $create_method(&[]); 132 | assert_invalid_input_data!(dev.$method($value)); 133 | $destroy_method(dev); 134 | } 135 | }; 136 | } 137 | 138 | #[macro_export] 139 | macro_rules! call_test { 140 | ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $transactions:expr) => { 141 | #[test] 142 | fn $name() { 143 | let trans = $transactions; 144 | let mut dev = $create_method(&trans); 145 | dev.$method().unwrap(); 146 | $destroy_method(dev); 147 | } 148 | }; 149 | } 150 | 151 | #[macro_export] 152 | macro_rules! _get_param_test { 153 | ($name:ident, $method:ident, $value:expr, $i2c_transactions:expr, $spi_transactions:expr) => { 154 | mod $name { 155 | use super::*; 156 | get_test!( 157 | can_get_ds3231, 158 | $method, 159 | new_ds3231, 160 | destroy_ds3231, 161 | $value, 162 | $i2c_transactions 163 | ); 164 | get_test!( 165 | can_get_ds3232, 166 | $method, 167 | new_ds3232, 168 | destroy_ds3232, 169 | $value, 170 | $i2c_transactions 171 | ); 172 | get_test!( 173 | can_get_ds3234, 174 | $method, 175 | new_ds3234, 176 | destroy_ds3234, 177 | $value, 178 | $spi_transactions 179 | ); 180 | } 181 | }; 182 | } 183 | 184 | #[macro_export] 185 | macro_rules! get_param_test { 186 | ($name:ident, $method:ident, $register:ident, $value:expr, $binary_value:expr) => { 187 | _get_param_test!( 188 | $name, 189 | $method, 190 | $value, 191 | [I2cTrans::write_read( 192 | DEV_ADDR, 193 | vec![Register::$register], 194 | vec![$binary_value] 195 | )], 196 | [ 197 | SpiTrans::transaction_start(), 198 | SpiTrans::transfer_in_place( 199 | vec![Register::$register, 0], 200 | vec![Register::$register, $binary_value] 201 | ), 202 | SpiTrans::transaction_end(), 203 | ] 204 | ); 205 | }; 206 | } 207 | 208 | #[macro_export] 209 | macro_rules! transactions_i2c_read { 210 | ($register1:ident, [ $( $read_bin:expr ),+ ], [ $( $read_bin2:expr ),* ]) => { 211 | [ I2cTrans::write_read(DEV_ADDR, vec![Register::$register1], vec![$( $read_bin ),*]) ] 212 | } 213 | } 214 | 215 | #[macro_export] 216 | macro_rules! transactions_spi_read { 217 | ($register1:ident, [ $( $read_bin:expr ),+ ], [ $( $read_bin2:expr ),+ ]) => { 218 | [SpiTrans::transaction_start(), 219 | SpiTrans::transfer_in_place(vec![Register::$register1, $( $read_bin2 ),*], vec![Register::$register1, $( $read_bin ),*]), 220 | SpiTrans::transaction_end() 221 | ] 222 | } 223 | } 224 | 225 | #[macro_export] 226 | macro_rules! get_param_read_array_test { 227 | ($name:ident, $method:ident, $value:expr, $register1:ident, [ $( $read_bin:expr ),+ ], [ $( $read_bin2:expr ),+ ]) => { 228 | _get_param_test!($name, $method, $value, 229 | transactions_i2c_read!($register1, [ $( $read_bin ),* ], [ ]), 230 | transactions_spi_read!($register1, [ $( $read_bin ),* ], [ $( $read_bin2 ),* ]) ); 231 | }; 232 | } 233 | 234 | #[macro_export] 235 | macro_rules! _set_param_test { 236 | ($name:ident, $method:ident, $value:expr, $i2c_transactions:expr, $spi_transactions:expr) => { 237 | mod $name { 238 | use super::*; 239 | set_test!( 240 | can_set_ds3231, 241 | $method, 242 | new_ds3231, 243 | destroy_ds3231, 244 | $value, 245 | $i2c_transactions 246 | ); 247 | set_test!( 248 | can_set_ds3232, 249 | $method, 250 | new_ds3232, 251 | destroy_ds3232, 252 | $value, 253 | $i2c_transactions 254 | ); 255 | set_test!( 256 | can_set_ds3234, 257 | $method, 258 | new_ds3234, 259 | destroy_ds3234, 260 | $value, 261 | $spi_transactions 262 | ); 263 | } 264 | }; 265 | } 266 | 267 | #[macro_export] 268 | macro_rules! set_param_test { 269 | ($name:ident, $method:ident, $register:ident, $value:expr, $binary_value:expr) => { 270 | _set_param_test!( 271 | $name, 272 | $method, 273 | $value, 274 | [I2cTrans::write( 275 | DEV_ADDR, 276 | vec![Register::$register, $binary_value] 277 | )], 278 | [ 279 | SpiTrans::transaction_start(), 280 | SpiTrans::write_vec(vec![Register::$register + 0x80, $binary_value]), 281 | SpiTrans::transaction_end(), 282 | ] 283 | ); 284 | }; 285 | } 286 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/ds323x/datetime.rs: -------------------------------------------------------------------------------- 1 | //! Common implementation 2 | 3 | use super::{ 4 | decimal_to_packed_bcd, hours_to_register, packed_bcd_to_decimal, some_or_invalid_error, 5 | }; 6 | use crate::{ 7 | interface::{ReadData, WriteData}, 8 | BitFlags, DateTimeAccess, Datelike, Ds323x, Error, Hours, NaiveDate, NaiveDateTime, NaiveTime, 9 | Register, Rtcc, Timelike, 10 | }; 11 | 12 | impl DateTimeAccess for Ds323x 13 | where 14 | DI: ReadData> + WriteData>, 15 | { 16 | type Error = Error; 17 | 18 | fn datetime(&mut self) -> Result { 19 | let mut data = [0; 8]; 20 | self.iface.read_data(&mut data)?; 21 | 22 | let year = year_from_registers( 23 | data[Register::MONTH as usize + 1], 24 | data[Register::YEAR as usize + 1], 25 | ); 26 | let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1] & !BitFlags::CENTURY); 27 | let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1]); 28 | let hour = hours_from_register(data[Register::HOURS as usize + 1]); 29 | let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); 30 | let second = packed_bcd_to_decimal(data[Register::SECONDS as usize + 1]); 31 | 32 | let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()); 33 | let date = some_or_invalid_error(date)?; 34 | let datetime = date.and_hms_opt(get_h24(hour).into(), minute.into(), second.into()); 35 | some_or_invalid_error(datetime) 36 | } 37 | 38 | fn set_datetime(&mut self, datetime: &NaiveDateTime) -> Result<(), Self::Error> { 39 | if datetime.year() < 2000 || datetime.year() > 2100 { 40 | return Err(Error::InvalidInputData); 41 | } 42 | let (month, year) = month_year_to_registers(datetime.month() as u8, datetime.year() as u16); 43 | let mut payload = [ 44 | Register::SECONDS, 45 | decimal_to_packed_bcd(datetime.second() as u8), 46 | decimal_to_packed_bcd(datetime.minute() as u8), 47 | hours_to_register(Hours::H24(datetime.hour() as u8))?, 48 | datetime.weekday().number_from_sunday() as u8, 49 | decimal_to_packed_bcd(datetime.day() as u8), 50 | month, 51 | year, 52 | ]; 53 | self.iface.write_data(&mut payload) 54 | } 55 | } 56 | 57 | impl Rtcc for Ds323x 58 | where 59 | DI: ReadData> + WriteData>, 60 | { 61 | fn seconds(&mut self) -> Result { 62 | self.read_register_decimal(Register::SECONDS) 63 | } 64 | 65 | fn minutes(&mut self) -> Result { 66 | self.read_register_decimal(Register::MINUTES) 67 | } 68 | 69 | fn hours(&mut self) -> Result { 70 | let data = self.iface.read_register(Register::HOURS)?; 71 | Ok(hours_from_register(data)) 72 | } 73 | 74 | fn time(&mut self) -> Result { 75 | let mut data = [0; 4]; 76 | self.iface.read_data(&mut data)?; 77 | let hour = hours_from_register(data[Register::HOURS as usize + 1]); 78 | let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); 79 | let second = packed_bcd_to_decimal(data[Register::SECONDS as usize + 1]); 80 | 81 | let time = NaiveTime::from_hms_opt(get_h24(hour).into(), minute.into(), second.into()); 82 | some_or_invalid_error(time) 83 | } 84 | 85 | fn weekday(&mut self) -> Result { 86 | self.read_register_decimal(Register::DOW) 87 | } 88 | 89 | fn day(&mut self) -> Result { 90 | self.read_register_decimal(Register::DOM) 91 | } 92 | 93 | fn month(&mut self) -> Result { 94 | let data = self.iface.read_register(Register::MONTH)?; 95 | let value = data & !BitFlags::CENTURY; 96 | Ok(packed_bcd_to_decimal(value)) 97 | } 98 | 99 | fn year(&mut self) -> Result { 100 | let mut data = [0; 3]; 101 | data[0] = Register::MONTH; 102 | self.iface.read_data(&mut data)?; 103 | Ok(year_from_registers(data[1], data[2])) 104 | } 105 | 106 | fn date(&mut self) -> Result { 107 | let mut data = [0; 4]; 108 | data[0] = Register::DOM; 109 | self.iface.read_data(&mut data)?; 110 | 111 | let offset = Register::DOM as usize; 112 | let year = year_from_registers( 113 | data[Register::MONTH as usize + 1 - offset], 114 | data[Register::YEAR as usize + 1 - offset], 115 | ); 116 | let month = 117 | packed_bcd_to_decimal(data[Register::MONTH as usize + 1 - offset] & !BitFlags::CENTURY); 118 | let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1 - offset]); 119 | let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()); 120 | some_or_invalid_error(date) 121 | } 122 | 123 | fn set_seconds(&mut self, seconds: u8) -> Result<(), Self::Error> { 124 | if seconds > 59 { 125 | return Err(Error::InvalidInputData); 126 | } 127 | self.write_register_decimal(Register::SECONDS, seconds) 128 | } 129 | 130 | fn set_minutes(&mut self, minutes: u8) -> Result<(), Self::Error> { 131 | if minutes > 59 { 132 | return Err(Error::InvalidInputData); 133 | } 134 | self.write_register_decimal(Register::MINUTES, minutes) 135 | } 136 | 137 | fn set_hours(&mut self, hours: Hours) -> Result<(), Self::Error> { 138 | let value = hours_to_register(hours)?; 139 | self.iface.write_register(Register::HOURS, value) 140 | } 141 | 142 | fn set_time(&mut self, time: &NaiveTime) -> Result<(), Self::Error> { 143 | let mut payload = [ 144 | Register::SECONDS, 145 | decimal_to_packed_bcd(time.second() as u8), 146 | decimal_to_packed_bcd(time.minute() as u8), 147 | hours_to_register(Hours::H24(time.hour() as u8))?, 148 | ]; 149 | self.iface.write_data(&mut payload) 150 | } 151 | 152 | fn set_weekday(&mut self, weekday: u8) -> Result<(), Self::Error> { 153 | if !(1..=7).contains(&weekday) { 154 | return Err(Error::InvalidInputData); 155 | } 156 | self.iface.write_register(Register::DOW, weekday) 157 | } 158 | 159 | fn set_day(&mut self, day: u8) -> Result<(), Self::Error> { 160 | if !(1..=31).contains(&day) { 161 | return Err(Error::InvalidInputData); 162 | } 163 | self.write_register_decimal(Register::DOM, day) 164 | } 165 | 166 | fn set_month(&mut self, month: u8) -> Result<(), Self::Error> { 167 | if !(1..=12).contains(&month) { 168 | return Err(Error::InvalidInputData); 169 | } 170 | // keep the century bit 171 | let data = self.iface.read_register(Register::MONTH)?; 172 | let value = (data & BitFlags::CENTURY) | decimal_to_packed_bcd(month); 173 | self.iface.write_register(Register::MONTH, value) 174 | } 175 | 176 | fn set_year(&mut self, year: u16) -> Result<(), Self::Error> { 177 | if !(2000..=2100).contains(&year) { 178 | return Err(Error::InvalidInputData); 179 | } 180 | let data = self.iface.read_register(Register::MONTH)?; 181 | let month_bcd = data & !BitFlags::CENTURY; 182 | if year > 2099 { 183 | let mut data = [ 184 | Register::MONTH, 185 | BitFlags::CENTURY | month_bcd, 186 | decimal_to_packed_bcd((year - 2100) as u8), 187 | ]; 188 | self.iface.write_data(&mut data) 189 | } else { 190 | let mut data = [ 191 | Register::MONTH, 192 | month_bcd, 193 | decimal_to_packed_bcd((year - 2000) as u8), 194 | ]; 195 | self.iface.write_data(&mut data) 196 | } 197 | } 198 | 199 | fn set_date(&mut self, date: &rtcc::NaiveDate) -> Result<(), Self::Error> { 200 | if date.year() < 2000 || date.year() > 2100 { 201 | return Err(Error::InvalidInputData); 202 | } 203 | let (month, year) = month_year_to_registers(date.month() as u8, date.year() as u16); 204 | let mut payload = [ 205 | Register::DOW, 206 | date.weekday().number_from_sunday() as u8, 207 | decimal_to_packed_bcd(date.day() as u8), 208 | month, 209 | year, 210 | ]; 211 | self.iface.write_data(&mut payload) 212 | } 213 | } 214 | 215 | impl Ds323x 216 | where 217 | DI: ReadData> + WriteData>, 218 | { 219 | fn read_register_decimal(&mut self, register: u8) -> Result> { 220 | let data = self.iface.read_register(register)?; 221 | Ok(packed_bcd_to_decimal(data)) 222 | } 223 | 224 | fn write_register_decimal(&mut self, register: u8, decimal_number: u8) -> Result<(), Error> { 225 | self.iface 226 | .write_register(register, decimal_to_packed_bcd(decimal_number)) 227 | } 228 | } 229 | 230 | fn hours_from_register(data: u8) -> Hours { 231 | if is_24h_format(data) { 232 | Hours::H24(packed_bcd_to_decimal(data & !BitFlags::H24_H12)) 233 | } else if is_am(data) { 234 | Hours::AM(packed_bcd_to_decimal( 235 | data & !(BitFlags::H24_H12 | BitFlags::AM_PM), 236 | )) 237 | } else { 238 | Hours::PM(packed_bcd_to_decimal( 239 | data & !(BitFlags::H24_H12 | BitFlags::AM_PM), 240 | )) 241 | } 242 | } 243 | 244 | fn year_from_registers(month: u8, year: u8) -> u16 { 245 | let century = month & BitFlags::CENTURY; 246 | let year = packed_bcd_to_decimal(year); 247 | if century != 0 { 248 | 2100 + u16::from(year) 249 | } else { 250 | 2000 + u16::from(year) 251 | } 252 | } 253 | 254 | fn month_year_to_registers(month: u8, year: u16) -> (u8, u8) { 255 | if year > 2099 { 256 | let month = BitFlags::CENTURY | decimal_to_packed_bcd(month); 257 | (month, decimal_to_packed_bcd((year - 2100) as u8)) 258 | } else { 259 | ( 260 | decimal_to_packed_bcd(month), 261 | decimal_to_packed_bcd((year - 2000) as u8), 262 | ) 263 | } 264 | } 265 | 266 | fn is_24h_format(hours_data: u8) -> bool { 267 | hours_data & BitFlags::H24_H12 == 0 268 | } 269 | 270 | fn is_am(hours_data: u8) -> bool { 271 | hours_data & BitFlags::AM_PM == 0 272 | } 273 | 274 | fn get_h24(hour: Hours) -> u8 { 275 | match hour { 276 | Hours::H24(h) => h, 277 | Hours::AM(h) => h, 278 | Hours::PM(h) => h + 12, 279 | } 280 | } 281 | 282 | #[cfg(test)] 283 | mod tests { 284 | use super::*; 285 | 286 | #[test] 287 | fn can_convert_to_h24() { 288 | assert_eq!(0, get_h24(Hours::H24(0))); 289 | assert_eq!(0, get_h24(Hours::AM(0))); 290 | assert_eq!(12, get_h24(Hours::PM(0))); 291 | 292 | assert_eq!(1, get_h24(Hours::H24(1))); 293 | assert_eq!(1, get_h24(Hours::AM(1))); 294 | assert_eq!(13, get_h24(Hours::PM(1))); 295 | 296 | assert_eq!(23, get_h24(Hours::H24(23))); 297 | assert_eq!(12, get_h24(Hours::AM(12))); 298 | assert_eq!(23, get_h24(Hours::PM(11))); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/ds323x/alarms.rs: -------------------------------------------------------------------------------- 1 | //! Alarm support 2 | 3 | use super::{decimal_to_packed_bcd, hours_to_register}; 4 | use crate::{ 5 | ds323x::{NaiveTime, Timelike}, 6 | interface::{ReadData, WriteData}, 7 | BitFlags, Ds323x, Error, Hours, Register, 8 | }; 9 | 10 | /// Parameters for setting Alarm1 on a day of the month 11 | /// 12 | /// Depending on the matching strategy, some fields may not be relevant. In this 13 | /// case, invalid values are ignored and the minimum valid values are used instead to 14 | /// configure the alarm: 15 | /// - Second, minute and hour: 0 16 | /// - Day: 1 17 | #[derive(Debug, Clone, Copy, PartialEq)] 18 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 19 | pub struct DayAlarm1 { 20 | /// Day of the month [1-31] 21 | pub day: u8, 22 | /// Hour 23 | pub hour: Hours, 24 | /// Minute [0-59] 25 | pub minute: u8, 26 | /// Second [0-59] 27 | pub second: u8, 28 | } 29 | 30 | /// Parameters for setting Alarm1 on a weekday 31 | /// 32 | /// Depending on the matching strategy, some fields may not be relevant. In this 33 | /// case, invalid values are ignored and the minimum valid values are used instead to 34 | /// configure the alarm: 35 | /// - Second, minute and hour: 0 36 | /// - Weekday: 1 37 | #[derive(Debug, Clone, Copy, PartialEq)] 38 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 39 | pub struct WeekdayAlarm1 { 40 | /// Weekday [1-7] 41 | pub weekday: u8, 42 | /// Hour 43 | pub hour: Hours, 44 | /// Minute [0-59] 45 | pub minute: u8, 46 | /// Second [0-59] 47 | pub second: u8, 48 | } 49 | 50 | /// Alarm1 trigger rate 51 | #[derive(Debug, Clone, Copy, PartialEq)] 52 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 53 | pub enum Alarm1Matching { 54 | /// Alarm once per second. 55 | OncePerSecond, 56 | /// Alarm when seconds match. 57 | SecondsMatch, 58 | /// Alarm when minutes and seconds match. 59 | MinutesAndSecondsMatch, 60 | /// Alarm when hours, minutes and seconds match. 61 | HoursMinutesAndSecondsMatch, 62 | /// Alarm when date/weekday, hours, minutes and seconds match. 63 | AllMatch, 64 | } 65 | 66 | /// Parameters for setting Alarm2 on a day of the month 67 | /// 68 | /// Depending on the matching strategy, some fields may not be relevant. In this 69 | /// case, invalid values are ignored and the minimum valid values are used instead to 70 | /// configure the alarm: 71 | /// - Minute and hour: 0 72 | /// - Day: 1 73 | #[derive(Debug, Clone, Copy, PartialEq)] 74 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 75 | pub struct DayAlarm2 { 76 | /// Day of month [1-31] 77 | pub day: u8, 78 | /// Hour 79 | pub hour: Hours, 80 | /// Minute [0-59] 81 | pub minute: u8, 82 | } 83 | 84 | /// Parameters for setting Alarm2 on a weekday 85 | /// 86 | /// Depending on the matching strategy, some fields may not be relevant. In this 87 | /// case, invalid values are ignored and the minimum valid values are used instead to 88 | /// configure the alarm: 89 | /// - Minute and hour: 0 90 | /// - Weekday: 1 91 | #[derive(Debug, Clone, Copy, PartialEq)] 92 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 93 | pub struct WeekdayAlarm2 { 94 | /// Weekday [1-7] 95 | pub weekday: u8, 96 | /// Hour 97 | pub hour: Hours, 98 | /// Minute [0-59] 99 | pub minute: u8, 100 | } 101 | 102 | /// Alarm2 trigger rate 103 | #[derive(Debug, Clone, Copy, PartialEq)] 104 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 105 | pub enum Alarm2Matching { 106 | /// Alarm once per minute. (00 seconds of every minute) 107 | OncePerMinute, 108 | /// Alarm when minutes match. 109 | MinutesMatch, 110 | /// Alarm when hours and minutes match. 111 | HoursAndMinutesMatch, 112 | /// Alarm when date/weekday, hours and minutes match. 113 | AllMatch, 114 | } 115 | 116 | fn get_matching_mask_alarm1(matching: Alarm1Matching) -> [u8; 4] { 117 | const AM: u8 = BitFlags::ALARM_MATCH; 118 | match matching { 119 | Alarm1Matching::OncePerSecond => [AM, AM, AM, AM], 120 | Alarm1Matching::SecondsMatch => [0, AM, AM, AM], 121 | Alarm1Matching::MinutesAndSecondsMatch => [0, 0, AM, AM], 122 | Alarm1Matching::HoursMinutesAndSecondsMatch => [0, 0, 0, AM], 123 | Alarm1Matching::AllMatch => [0, 0, 0, 0], 124 | } 125 | } 126 | 127 | fn get_matching_mask_alarm2(matching: Alarm2Matching) -> [u8; 3] { 128 | const AM: u8 = BitFlags::ALARM_MATCH; 129 | match matching { 130 | Alarm2Matching::OncePerMinute => [AM, AM, AM], 131 | Alarm2Matching::MinutesMatch => [0, AM, AM], 132 | Alarm2Matching::HoursAndMinutesMatch => [0, 0, AM], 133 | Alarm2Matching::AllMatch => [0, 0, 0], 134 | } 135 | } 136 | 137 | /// Test if hour value is valid 138 | fn is_hour_valid(hours: Hours) -> bool { 139 | match hours { 140 | Hours::H24(h) if h > 23 => true, 141 | Hours::AM(h) if !(1..=12).contains(&h) => true, 142 | Hours::PM(h) if !(1..=12).contains(&h) => true, 143 | _ => false, 144 | } 145 | } 146 | 147 | /// Amend invalid hour values 148 | fn amend_hour(hours: Hours) -> Hours { 149 | match hours { 150 | Hours::H24(h) if h > 23 => Hours::H24(0), 151 | Hours::H24(h) => Hours::H24(h), 152 | Hours::AM(h) if !(1..=12).contains(&h) => Hours::AM(1), 153 | Hours::AM(h) => Hours::AM(h), 154 | Hours::PM(h) if !(1..=12).contains(&h) => Hours::PM(1), 155 | Hours::PM(h) => Hours::PM(h), 156 | } 157 | } 158 | 159 | impl Ds323x 160 | where 161 | DI: ReadData> + WriteData>, 162 | { 163 | /// Set Alarm1 for day of the month. 164 | /// 165 | /// Will return an `Error::InvalidInputData` if any of the used parameters 166 | /// (depending on the matching startegy) is out of range. Any unused 167 | /// parameter is set to the corresponding minimum valid value: 168 | /// - Second, minute, hour: 0 169 | /// - Day: 1 170 | pub fn set_alarm1_day( 171 | &mut self, 172 | when: DayAlarm1, 173 | matching: Alarm1Matching, 174 | ) -> Result<(), Error> { 175 | let day_invalid = when.day < 1 || when.day > 31; 176 | let hour_invalid = is_hour_valid(when.hour); 177 | let minute_invalid = when.minute > 59; 178 | let second_invalid = when.second > 59; 179 | 180 | let day = if day_invalid { 1 } else { when.day }; 181 | let hour = amend_hour(when.hour); 182 | let minute = if minute_invalid { 0 } else { when.minute }; 183 | 184 | if (matching == Alarm1Matching::AllMatch && (day_invalid || hour_invalid)) 185 | || (hour_invalid && matching == Alarm1Matching::HoursMinutesAndSecondsMatch) 186 | || ((matching != Alarm1Matching::SecondsMatch 187 | && matching != Alarm1Matching::OncePerSecond) 188 | && minute_invalid) 189 | || second_invalid 190 | { 191 | return Err(Error::InvalidInputData); 192 | } 193 | 194 | let match_mask = get_matching_mask_alarm1(matching); 195 | let mut data = [ 196 | Register::ALARM1_SECONDS, 197 | decimal_to_packed_bcd(when.second) | match_mask[0], 198 | decimal_to_packed_bcd(minute) | match_mask[1], 199 | hours_to_register(hour)? | match_mask[2], 200 | decimal_to_packed_bcd(day) | match_mask[3], 201 | ]; 202 | self.iface.write_data(&mut data) 203 | } 204 | 205 | /// Set Alarm1 for a time (fires when hours, minutes and seconds match). 206 | /// 207 | /// Will return an `Error::InvalidInputData` if any of the parameters is out of range. 208 | /// The day is not used by this matching strategy and is set to 1. 209 | pub fn set_alarm1_hms(&mut self, when: NaiveTime) -> Result<(), Error> { 210 | let alarm = DayAlarm1 { 211 | day: 1, 212 | hour: Hours::H24(when.hour() as u8), 213 | minute: when.minute() as u8, 214 | second: when.second() as u8, 215 | }; 216 | self.set_alarm1_day(alarm, Alarm1Matching::HoursMinutesAndSecondsMatch) 217 | } 218 | 219 | /// Set Alarm1 for weekday. 220 | /// 221 | /// Will return an `Error::InvalidInputData` if any of the used parameters 222 | /// (depending on the matching startegy) is out of range. Any unused 223 | /// parameter is set to the corresponding minimum valid value: 224 | /// - Second, minute, hour: 0 225 | /// - Weekday: 1 226 | pub fn set_alarm1_weekday( 227 | &mut self, 228 | when: WeekdayAlarm1, 229 | matching: Alarm1Matching, 230 | ) -> Result<(), Error> { 231 | let weekday_invalid = when.weekday < 1 || when.weekday > 7; 232 | let hour_invalid = is_hour_valid(when.hour); 233 | let minute_invalid = when.minute > 59; 234 | let second_invalid = when.second > 59; 235 | 236 | let weekday = if weekday_invalid { 1 } else { when.weekday }; 237 | let hour = amend_hour(when.hour); 238 | let minute = if minute_invalid { 0 } else { when.minute }; 239 | let second = if second_invalid { 0 } else { when.second }; 240 | 241 | if ((hour_invalid || weekday_invalid) && matching == Alarm1Matching::AllMatch) 242 | || (hour_invalid && matching == Alarm1Matching::HoursMinutesAndSecondsMatch) 243 | || (minute_invalid 244 | && (matching != Alarm1Matching::OncePerSecond 245 | && matching != Alarm1Matching::SecondsMatch)) 246 | || (second_invalid && matching != Alarm1Matching::OncePerSecond) 247 | { 248 | return Err(Error::InvalidInputData); 249 | } 250 | let match_mask = get_matching_mask_alarm1(matching); 251 | let mut data = [ 252 | Register::ALARM1_SECONDS, 253 | decimal_to_packed_bcd(second) | match_mask[0], 254 | decimal_to_packed_bcd(minute) | match_mask[1], 255 | hours_to_register(hour)? | match_mask[2], 256 | decimal_to_packed_bcd(weekday) | match_mask[3] | BitFlags::WEEKDAY, 257 | ]; 258 | self.iface.write_data(&mut data) 259 | } 260 | 261 | /// Set Alarm2 for date (day of month). 262 | /// 263 | /// Will return an `Error::InvalidInputData` if any of the used parameters 264 | /// (depending on the matching startegy) is out of range. Any unused 265 | /// parameter is set to the corresponding minimum valid value: 266 | /// - Minute, hour: 0 267 | /// - Day: 1 268 | pub fn set_alarm2_day( 269 | &mut self, 270 | when: DayAlarm2, 271 | matching: Alarm2Matching, 272 | ) -> Result<(), Error> { 273 | let day_invalid = when.day < 1 || when.day > 31; 274 | let hour_invalid = is_hour_valid(when.hour); 275 | let minute_invalid = when.minute > 59; 276 | 277 | let day = if day_invalid { 1 } else { when.day }; 278 | let hour = amend_hour(when.hour); 279 | let minute = if minute_invalid { 0 } else { when.minute }; 280 | 281 | if ((day_invalid || hour_invalid) && matching == Alarm2Matching::AllMatch) 282 | || (hour_invalid && matching == Alarm2Matching::HoursAndMinutesMatch) 283 | || (matching != Alarm2Matching::OncePerMinute && minute_invalid) 284 | { 285 | return Err(Error::InvalidInputData); 286 | } 287 | 288 | let match_mask = get_matching_mask_alarm2(matching); 289 | let mut data = [ 290 | Register::ALARM2_MINUTES, 291 | decimal_to_packed_bcd(minute) | match_mask[0], 292 | hours_to_register(hour)? | match_mask[1], 293 | decimal_to_packed_bcd(day) | match_mask[2], 294 | ]; 295 | self.iface.write_data(&mut data) 296 | } 297 | 298 | /// Set Alarm2 for a time (fires when hours and minutes match). 299 | /// 300 | /// Will return an `Error::InvalidInputData` if any of the parameters is out of range. 301 | /// The day is not used by this matching strategy and is set to 1. 302 | pub fn set_alarm2_hm(&mut self, when: NaiveTime) -> Result<(), Error> { 303 | let alarm = DayAlarm2 { 304 | day: 1, 305 | hour: Hours::H24(when.hour() as u8), 306 | minute: when.minute() as u8, 307 | }; 308 | self.set_alarm2_day(alarm, Alarm2Matching::HoursAndMinutesMatch) 309 | } 310 | 311 | /// Set Alarm2 for weekday. 312 | /// 313 | /// Will return an `Error::InvalidInputData` if any of the used parameters 314 | /// (depending on the matching startegy) is out of range. Any unused 315 | /// parameter is set to the corresponding minimum valid value: 316 | /// - Minute, hour: 0 317 | /// - Weekday: 1 318 | pub fn set_alarm2_weekday( 319 | &mut self, 320 | when: WeekdayAlarm2, 321 | matching: Alarm2Matching, 322 | ) -> Result<(), Error> { 323 | let weekday_invalid = when.weekday < 1 || when.weekday > 7; 324 | let hour_invalid = is_hour_valid(when.hour); 325 | let minute_invalid = when.minute > 59; 326 | 327 | let weekday = if weekday_invalid { 1 } else { when.weekday }; 328 | let hour = amend_hour(when.hour); 329 | let minute = if minute_invalid { 0 } else { when.minute }; 330 | 331 | if (matching == Alarm2Matching::AllMatch && (weekday_invalid || hour_invalid)) 332 | || (matching == Alarm2Matching::HoursAndMinutesMatch && hour_invalid) 333 | || (minute_invalid && matching != Alarm2Matching::OncePerMinute) 334 | { 335 | return Err(Error::InvalidInputData); 336 | } 337 | let match_mask = get_matching_mask_alarm2(matching); 338 | let mut data = [ 339 | Register::ALARM2_MINUTES, 340 | decimal_to_packed_bcd(minute) | match_mask[0], 341 | hours_to_register(hour)? | match_mask[1], 342 | decimal_to_packed_bcd(weekday) | match_mask[2] | BitFlags::WEEKDAY, 343 | ]; 344 | self.iface.write_data(&mut data) 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /tests/datetime.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal_mock::eh1::{i2c::Transaction as I2cTrans, spi::Transaction as SpiTrans}; 2 | use rtcc::NaiveDateTime; 3 | mod common; 4 | use self::common::{ 5 | destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, Register, 6 | DEVICE_ADDRESS as DEV_ADDR, 7 | }; 8 | #[allow(unused)] // Rust 1.31.0 is confused due to the macros 9 | use ds323x::Rtcc; 10 | use ds323x::{DateTimeAccess, Error, Hours, NaiveDate, NaiveTime}; 11 | 12 | fn new_datetime(y: i32, mo: u32, d: u32, h: u32, min: u32, s: u32) -> NaiveDateTime { 13 | NaiveDate::from_ymd_opt(y, mo, d) 14 | .unwrap() 15 | .and_hms_opt(h, min, s) 16 | .unwrap() 17 | } 18 | 19 | fn new_date(y: i32, mo: u32, d: u32) -> NaiveDate { 20 | NaiveDate::from_ymd_opt(y, mo, d).unwrap() 21 | } 22 | 23 | macro_rules! read_set_param_write_two_test { 24 | ($name:ident, $method:ident, $value:expr, $register:ident, $binary_value1_read:expr, $bin1:expr, $bin2:expr) => { 25 | _set_param_test!( 26 | $name, 27 | $method, 28 | $value, 29 | [ 30 | I2cTrans::write_read( 31 | DEV_ADDR, 32 | vec![Register::$register], 33 | vec![$binary_value1_read] 34 | ), 35 | I2cTrans::write(DEV_ADDR, vec![Register::$register, $bin1, $bin2]) 36 | ], 37 | [ 38 | SpiTrans::transaction_start(), 39 | SpiTrans::transfer_in_place( 40 | vec![Register::$register, 0], 41 | vec![Register::$register, $binary_value1_read] 42 | ), 43 | SpiTrans::transaction_end(), 44 | SpiTrans::transaction_start(), 45 | SpiTrans::write_vec(vec![Register::$register + 0x80, $bin1, $bin2]), 46 | SpiTrans::transaction_end(), 47 | ] 48 | ); 49 | }; 50 | } 51 | 52 | macro_rules! read_set_param_test { 53 | ($name:ident, $method:ident, $register:ident, $value:expr, $binary_value_read:expr, $binary_value_write:expr) => { 54 | _set_param_test!( 55 | $name, 56 | $method, 57 | $value, 58 | [ 59 | I2cTrans::write_read( 60 | DEV_ADDR, 61 | vec![Register::$register], 62 | vec![$binary_value_read] 63 | ), 64 | I2cTrans::write(DEV_ADDR, vec![Register::$register, $binary_value_write]) 65 | ], 66 | [ 67 | SpiTrans::transaction_start(), 68 | SpiTrans::transfer_in_place( 69 | vec![Register::$register, 0], 70 | vec![Register::$register, $binary_value_read] 71 | ), 72 | SpiTrans::transaction_end(), 73 | SpiTrans::transaction_start(), 74 | SpiTrans::write_vec(vec![Register::$register + 0x80, $binary_value_write]), 75 | SpiTrans::transaction_end(), 76 | ] 77 | ); 78 | }; 79 | } 80 | 81 | macro_rules! set_invalid_param_test { 82 | ($name:ident, $method:ident, $value:expr) => { 83 | mod $name { 84 | use super::*; 85 | set_invalid_test!( 86 | cannot_set_invalid_ds3231, 87 | $method, 88 | new_ds3231, 89 | destroy_ds3231, 90 | $value 91 | ); 92 | set_invalid_test!( 93 | cannot_set_invalid_ds3232, 94 | $method, 95 | new_ds3232, 96 | destroy_ds3232, 97 | $value 98 | ); 99 | set_invalid_test!( 100 | cannot_set_invalid_ds3234, 101 | $method, 102 | new_ds3234, 103 | destroy_ds3234, 104 | $value 105 | ); 106 | } 107 | }; 108 | } 109 | 110 | macro_rules! set_invalid_param_range_test { 111 | ($name:ident, $method:ident, $too_small_value:expr, $too_big_value:expr) => { 112 | mod $name { 113 | use super::*; 114 | set_invalid_param_test!(too_small, $method, $too_small_value); 115 | set_invalid_param_test!(too_big, $method, $too_big_value); 116 | } 117 | }; 118 | } 119 | 120 | macro_rules! for_all { 121 | ($name:ident) => { 122 | mod $name { 123 | use super::*; 124 | $name!(for_ds3231, new_ds3231, destroy_ds3231); 125 | $name!(for_ds3232, new_ds3232, destroy_ds3232); 126 | $name!(for_ds3234, new_ds3234, destroy_ds3234); 127 | } 128 | }; 129 | } 130 | 131 | // TODO set/get date 132 | // TODO set/get time 133 | 134 | mod seconds { 135 | use super::*; 136 | get_param_test!(get, seconds, SECONDS, 1, 1); 137 | set_param_test!(set, set_seconds, SECONDS, 1, 1); 138 | set_invalid_param_test!(invalid, set_seconds, 60); 139 | } 140 | 141 | mod minutes { 142 | use super::*; 143 | get_param_test!(get, minutes, MINUTES, 1, 1); 144 | set_param_test!(set, set_minutes, MINUTES, 1, 1); 145 | set_invalid_param_test!(invalid, set_minutes, 60); 146 | } 147 | 148 | mod hours_24h { 149 | use super::*; 150 | get_param_test!(get, hours, HOURS, Hours::H24(21), 0b0010_0001); 151 | set_param_test!(set, set_hours, HOURS, Hours::H24(21), 0b0010_0001); 152 | set_invalid_param_test!(invalid, set_hours, Hours::H24(24)); 153 | } 154 | 155 | mod hours_12h_am { 156 | use super::*; 157 | get_param_test!(get, hours, HOURS, Hours::AM(12), 0b0101_0010); 158 | set_param_test!(set, set_hours, HOURS, Hours::AM(12), 0b0101_0010); 159 | set_invalid_param_range_test!(invalid, set_hours, Hours::AM(0), Hours::AM(13)); 160 | } 161 | 162 | mod hours_12h_pm { 163 | use super::*; 164 | get_param_test!(get, hours, HOURS, Hours::PM(12), 0b0111_0010); 165 | set_param_test!(set, set_hours, HOURS, Hours::PM(12), 0b0111_0010); 166 | set_invalid_param_range_test!(invalid, set_hours, Hours::PM(0), Hours::PM(13)); 167 | } 168 | 169 | mod weekday { 170 | use super::*; 171 | get_param_test!(get, weekday, DOW, 1, 1); 172 | set_param_test!(set, set_weekday, DOW, 1, 1); 173 | set_invalid_param_range_test!(invalid, set_weekday, 0, 8); 174 | } 175 | 176 | mod day { 177 | use super::*; 178 | get_param_test!(get, day, DOM, 1, 1); 179 | set_param_test!(set, set_day, DOM, 1, 1); 180 | set_invalid_param_range_test!(invalid, set_day, 0, 32); 181 | } 182 | 183 | mod month { 184 | use super::*; 185 | get_param_test!(get, month, MONTH, 1, 1); 186 | read_set_param_test!(set, set_month, MONTH, 12, 0b0000_0010, 0b0001_0010); 187 | set_invalid_param_range_test!(invalid, set_month, 0, 13); 188 | 189 | mod keeps_century { 190 | use super::*; 191 | get_param_test!(get, month, MONTH, 12, 0b1001_0010); 192 | read_set_param_test!(set, set_month, MONTH, 12, 0b1000_0010, 0b1001_0010); 193 | } 194 | } 195 | 196 | mod year { 197 | use super::*; 198 | get_param_read_array_test!(century0_get, year, 2099, MONTH, [0, 0b1001_1001], [0, 0]); 199 | read_set_param_write_two_test!( 200 | century0_set, 201 | set_year, 202 | 2099, 203 | MONTH, 204 | 0b1001_0010, 205 | 0b0001_0010, 206 | 0b1001_1001 207 | ); 208 | 209 | get_param_read_array_test!(century1_get, year, 2100, MONTH, [0b1000_0000, 0], [0, 0]); 210 | read_set_param_write_two_test!( 211 | century1_set, 212 | set_year, 213 | 2100, 214 | MONTH, 215 | 0b0001_0010, 216 | 0b1001_0010, 217 | 0 218 | ); 219 | 220 | set_invalid_param_range_test!(invalid, set_year, 1999, 2101); 221 | } 222 | 223 | macro_rules! invalid_dt_test { 224 | ($name:ident, $create_method:ident, $destroy_method:ident) => { 225 | mod $name { 226 | use super::*; 227 | #[test] 228 | fn datetime_too_small() { 229 | let dt = new_datetime(1999, 1, 2, 3, 4, 5); 230 | let mut dev = $create_method(&[]); 231 | assert_invalid_input_data!(dev.set_datetime(&dt)); 232 | $destroy_method(dev); 233 | } 234 | #[test] 235 | fn datetime_too_big() { 236 | let dt = new_datetime(2101, 1, 2, 3, 4, 5); 237 | let mut dev = $create_method(&[]); 238 | assert_invalid_input_data!(dev.set_datetime(&dt)); 239 | $destroy_method(dev); 240 | } 241 | #[test] 242 | fn date_too_small() { 243 | let d = new_date(1999, 1, 2); 244 | let mut dev = $create_method(&[]); 245 | assert_invalid_input_data!(dev.set_date(&d)); 246 | $destroy_method(dev); 247 | } 248 | #[test] 249 | fn date_too_big() { 250 | let d = new_date(2101, 1, 2); 251 | let mut dev = $create_method(&[]); 252 | assert_invalid_input_data!(dev.set_date(&d)); 253 | $destroy_method(dev); 254 | } 255 | } 256 | }; 257 | } 258 | 259 | macro_rules! transactions_i2c_write { 260 | ($register:ident, [ $( $exp_bin:expr ),+ ]) => { 261 | [ I2cTrans::write(DEV_ADDR, vec![Register::$register, $( $exp_bin ),*]) ] 262 | }; 263 | } 264 | 265 | macro_rules! transactions_spi_write { 266 | ($register:ident, [ $( $exp_bin:expr ),+ ]) => { 267 | [ 268 | SpiTrans::transaction_start(), 269 | SpiTrans::write_vec(vec![Register::$register + 0x80, $( $exp_bin ),*]), 270 | SpiTrans::transaction_end() 271 | ] 272 | }; 273 | } 274 | 275 | macro_rules! dt_test { 276 | ($name:ident, $create_method:ident, $destroy_method:ident, 277 | $mac_trans_read:ident, $mac_trans_write:ident) => { 278 | mod $name { 279 | use super::*; 280 | #[test] 281 | fn get_datetime() { 282 | let dt = new_datetime(2018, 8, 13, 23, 59, 58); 283 | let mut dev = $create_method(&$mac_trans_read!( 284 | SECONDS, 285 | [ 286 | 0b0101_1000, 287 | 0b0101_1001, 288 | 0b0010_0011, 289 | 0b0000_0010, 290 | 0b0001_0011, 291 | 0b0000_1000, 292 | 0b0001_1000 293 | ], 294 | [0, 0, 0, 0, 0, 0, 0] 295 | )); 296 | assert_eq!(dt, dev.datetime().unwrap()); 297 | $destroy_method(dev); 298 | } 299 | 300 | #[test] 301 | fn set_datetime() { 302 | let dt = new_datetime(2018, 8, 13, 23, 59, 58); 303 | let mut dev = $create_method(&$mac_trans_write!( 304 | SECONDS, 305 | [ 306 | 0b0101_1000, 307 | 0b0101_1001, 308 | 0b0010_0011, 309 | 0b0000_0010, 310 | 0b0001_0011, 311 | 0b0000_1000, 312 | 0b0001_1000 313 | ] 314 | )); 315 | dev.set_datetime(&dt).unwrap(); 316 | $destroy_method(dev); 317 | } 318 | 319 | #[test] 320 | fn get_date() { 321 | let d = new_date(2018, 8, 13); 322 | let mut dev = $create_method(&$mac_trans_read!( 323 | DOM, 324 | [0b0001_0011, 0b0000_1000, 0b0001_1000], 325 | [0, 0, 0] 326 | )); 327 | assert_eq!(d, dev.date().unwrap()); 328 | $destroy_method(dev); 329 | } 330 | 331 | #[test] 332 | fn set_date() { 333 | let d = new_date(2018, 8, 13); 334 | let mut dev = $create_method(&$mac_trans_write!( 335 | DOW, 336 | [0b0000_0010, 0b0001_0011, 0b0000_1000, 0b0001_1000] 337 | )); 338 | dev.set_date(&d).unwrap(); 339 | $destroy_method(dev); 340 | } 341 | 342 | #[test] 343 | fn set_date_century() { 344 | let d = new_date(2100, 8, 13); 345 | let mut dev = $create_method(&$mac_trans_write!( 346 | DOW, 347 | [0b0000_0110, 0b0001_0011, 0b1000_1000, 0] 348 | )); 349 | dev.set_date(&d).unwrap(); 350 | $destroy_method(dev); 351 | } 352 | 353 | #[test] 354 | fn get_time() { 355 | let t = NaiveTime::from_hms_opt(23, 59, 58).unwrap(); 356 | let mut dev = $create_method(&$mac_trans_read!( 357 | SECONDS, 358 | [0b0101_1000, 0b0101_1001, 0b0010_0011], 359 | [0, 0, 0] 360 | )); 361 | assert_eq!(t, dev.time().unwrap()); 362 | $destroy_method(dev); 363 | } 364 | 365 | #[test] 366 | fn set_time() { 367 | let t = NaiveTime::from_hms_opt(23, 59, 58).unwrap(); 368 | let mut dev = $create_method(&$mac_trans_write!( 369 | SECONDS, 370 | [0b0101_1000, 0b0101_1001, 0b0010_0011] 371 | )); 372 | dev.set_time(&t).unwrap(); 373 | $destroy_method(dev); 374 | } 375 | } 376 | }; 377 | } 378 | 379 | mod datetime { 380 | use super::*; 381 | 382 | dt_test!( 383 | for_ds3231, 384 | new_ds3231, 385 | destroy_ds3231, 386 | transactions_i2c_read, 387 | transactions_i2c_write 388 | ); 389 | dt_test!( 390 | for_ds3232, 391 | new_ds3232, 392 | destroy_ds3232, 393 | transactions_i2c_read, 394 | transactions_i2c_write 395 | ); 396 | dt_test!( 397 | for_ds3234, 398 | new_ds3234, 399 | destroy_ds3234, 400 | transactions_spi_read, 401 | transactions_spi_write 402 | ); 403 | 404 | for_all!(invalid_dt_test); 405 | } 406 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a platform agnostic Rust driver for the DS3231, DS3232 and DS3234 2 | //! extremely accurate real-time clocks, based on the [`embedded-hal`] traits. 3 | //! 4 | //! [`embedded-hal`]: https://github.com/rust-embedded/embedded-hal 5 | //! 6 | //! This driver allows you to: 7 | //! - Read and set date and time in 12-hour and 24-hour format. See: [`datetime`]. 8 | //! - Read and set date and time individual elements. For example, see: [`year`]. 9 | //! - Enable and disable the real-time clock. See: [`enable`]. 10 | //! - Read the busy status. See [`busy`]. 11 | //! - Read whether the oscillator is or has been stopped. See [`has_been_stopped`]. 12 | //! - Clear the has-been-stopped flag. See [`clear_has_been_stopped_flag`]. 13 | //! - Set and read the aging offset. See [`set_aging_offset`]. 14 | //! - Select the function of the INT/SQW output pin. See [`use_int_sqw_output_as_interrupt`]. 15 | //! - Alarms: 16 | //! - Set alarms 1 and 2 with several matching policies. See [`set_alarm1_day`]. 17 | //! - Set alarms 1 and 2 for a time. See [`set_alarm1_hms`]. 18 | //! - Read whether alarms 1 or 2 have matched. See [`has_alarm1_matched`]. 19 | //! - Clear flag indicating that alarms 1 or 2 have matched. See [`clear_alarm1_matched_flag`]. 20 | //! - Enable and disable alarms 1 and 2 interrupt generation. See [`enable_alarm1_interrupts`]. 21 | //! - Wave generation: 22 | //! - Enable and disable the square-wave generation. See [`enable_square_wave`]. 23 | //! - Select the square-wave frequency. See [`set_square_wave_frequency`]. 24 | //! - Enable and disable the 32kHz output. See [`enable_32khz_output`]. 25 | //! - Enable and disable the 32kHz output when battery powered. See [`enable_32khz_output_on_battery`]. 26 | //! - Temperature conversion: 27 | //! - Read the temperature. See [`temperature`]. 28 | //! - Force a temperature conversion and time compensation. See [`convert_temperature`]. 29 | //! - Set the temperature conversion rate. See [`set_temperature_conversion_rate`]. 30 | //! - Enable and disable the temperature conversions when battery-powered. See [`enable_temperature_conversions_on_battery`]. 31 | //! 32 | //! [`datetime`]: Ds323x::datetime 33 | //! [`year`]: Ds323x::year 34 | //! [`enable`]: Ds323x::enable 35 | //! [`temperature`]: Ds323x::temperature 36 | //! [`convert_temperature`]: Ds323x::convert_temperature 37 | //! [`busy`]: Ds323x::busy 38 | //! [`has_been_stopped`]: Ds323x::has_been_stopped 39 | //! [`clear_has_been_stopped_flag`]: Ds323x::clear_has_been_stopped_flag 40 | //! [`set_aging_offset`]: Ds323x::set_aging_offset 41 | //! [`enable_32khz_output`]: Ds323x::enable_32khz_output 42 | //! [`use_int_sqw_output_as_interrupt`]: Ds323x::use_int_sqw_output_as_interrupt 43 | //! [`enable_square_wave`]: Ds323x::enable_square_wave 44 | //! [`set_square_wave_frequency`]: Ds323x::set_square_wave_frequency 45 | //! [`set_alarm1_day`]: Ds323x::set_alarm1_day 46 | //! [`set_alarm1_hms`]: Ds323x::set_alarm1_hms 47 | //! [`has_alarm1_matched`]: Ds323x::has_alarm1_matched 48 | //! [`clear_alarm1_matched_flag`]: Ds323x::clear_alarm1_matched_flag 49 | //! [`enable_alarm1_interrupts`]: Ds323x::enable_alarm1_interrupts 50 | //! [`enable_32khz_output_on_battery`]: Ds323x::enable_32khz_output_on_battery 51 | //! [`set_temperature_conversion_rate`]: Ds323x::set_temperature_conversion_rate 52 | //! [`enable_temperature_conversions_on_battery`]: Ds323x::enable_temperature_conversions_on_battery 53 | //! 54 | //! ## The devices 55 | //! 56 | //! This driver is compatible with the DS3231 and DS3232 I2C devices and the 57 | //! DS3234 SPI device. 58 | //! 59 | //! These devices are low-cost temperature-compensated crystal oscillator (TCXO) 60 | //! with a very accurate, temperature-compensated, integrated real-time clock 61 | //! (RTC) including 236/256 bytes of battery-backed SRAM, depending on the model. 62 | //! 63 | //! ### DS3231 and DS3232 details 64 | //! 65 | //! The devices incorporate a battery input, and maintain accurate timekeeping 66 | //! when main power to the devices is interrupted. The integration of the 67 | //! crystal resonator enhances the long-term accuracy of the devices as well as 68 | //! reduces the piece-part count in a manufacturing line. 69 | //! The devices are available in commercial and industrial temperature ranges, 70 | //! and are offered in a 16-pin, 300-mil SO package. 71 | //! 72 | //! The RTC maintains seconds, minutes, hours, day, date, month, and year 73 | //! information. The date at the end of the month is automatically adjusted for 74 | //! months with fewer than 31 days, including corrections for leap year. The 75 | //! clock operates in either the 24-hour or 12-hour format with an AM/PM 76 | //! indicator. Two programmable time-of-day alarms and a programmable 77 | //! square-wave output are provided. Address and data are transferred serially 78 | //! through an I2C bidirectional bus. 79 | //! 80 | //! A precision temperature-compensated voltage reference and comparator 81 | //! circuit monitors the status of VCC to detect power failures, to provide a 82 | //! reset output, and to automatically switch to the backup supply when 83 | //! necessary. Additionally, the RST pin is monitored as a pushbutton 84 | //! input for generating a μP reset. 85 | //! 86 | //! ### DS3234 details 87 | //! 88 | //! The DS3234 incorporates a precision, temperature-compensated voltage 89 | //! reference and comparator circuit to monitor VCC. When VCC drops below the 90 | //! power-fail voltage (VPF), the device asserts the RST output and also 91 | //! disables read and write access to the part when VCC drops below both VPF 92 | //! and VBAT. The RST pin is monitored as a pushbutton input for generating a 93 | //! μP reset. The device switches to the backup supply input and maintains 94 | //! accurate timekeeping when main power to the device is interrupted. 95 | //! The integration of the crystal resonator enhances the long-term accuracy of 96 | //! the device as well as reduces the piece-part count in a manufacturing line. 97 | //! The DS3234 is available in commercial and industrial temperature ranges, 98 | //! and is offered in an industry-standard 300-mil, 20-pin SO package. 99 | //! 100 | //! The DS3234 also integrates 256 bytes of battery-backed SRAM. In the event 101 | //! of main power loss, the contents of the memory are maintained by the power 102 | //! source connected to the V BAT pin. The RTC maintains seconds, minutes, 103 | //! hours, day, date, month, and year information. The date at the end of the 104 | //! month is automatically adjusted for months with fewer than 31 days, 105 | //! including corrections for leap year. The clock operates in either the 106 | //! 24-hour or 12-hour format with AM/PM indicator. Two programmable 107 | //! time-of-day alarms and a programmable square-wave output are provided. 108 | //! Address and data are transferred serially by an SPI bidirectional bus. 109 | //! 110 | //! Datasheets: 111 | //! - [DS3231](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) 112 | //! - [DS3232](https://datasheets.maximintegrated.com/en/ds/DS3232.pdf) 113 | //! - [DS3234](https://datasheets.maximintegrated.com/en/ds/DS3234.pdf) 114 | //! 115 | //! ## Usage examples (see also examples folder) 116 | //! 117 | //! To use this driver, import this crate and an `embedded_hal` implementation, 118 | //! then instantiate the appropriate device. 119 | //! In the following 3 examples an instance of the devices DS3231, DS3232 and 120 | //! DS3234 will be created as an example. The rest of examples will use the 121 | //! DS3231 as an example, except when using features specific to another IC, 122 | //! for example, RAM access which is not available in the DS3231 device. 123 | //! 124 | //! Please find additional examples using hardware in this repository: [driver-examples] 125 | //! 126 | //! [driver-examples]: https://github.com/eldruin/driver-examples 127 | //! 128 | //! ### Create a driver instance for the DS3231 129 | //! 130 | //! ```no_run 131 | //! use ds323x::Ds323x; 132 | //! use linux_embedded_hal::I2cdev; 133 | //! 134 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 135 | //! let rtc = Ds323x::new_ds3231(dev); 136 | //! // do something... 137 | //! 138 | //! // get the I2C device back 139 | //! let dev = rtc.destroy_ds3231(); 140 | //! ``` 141 | //! 142 | //! ### Create a driver instance for the DS3232 143 | //! 144 | //! ```no_run 145 | //! use ds323x::Ds323x; 146 | //! use linux_embedded_hal::I2cdev; 147 | //! 148 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 149 | //! let rtc = Ds323x::new_ds3232(dev); 150 | //! // do something... 151 | //! 152 | //! // get the I2C device back 153 | //! let dev = rtc.destroy_ds3232(); 154 | //! ``` 155 | //! 156 | //! ### Create a driver instance for the DS3234 157 | //! 158 | //! ```no_run 159 | //! use ds323x::Ds323x; 160 | //! use embedded_hal_bus::spi::ExclusiveDevice; 161 | //! use linux_embedded_hal::{Delay, SpidevBus, SysfsPin}; 162 | //! 163 | //! let spi = SpidevBus::open("/dev/spidev0.0").unwrap(); 164 | //! let chip_select = SysfsPin::new(25); 165 | //! let dev = ExclusiveDevice::new(spi, chip_select, Delay).unwrap(); 166 | //! let rtc = Ds323x::new_ds3234(dev); 167 | //! // do something... 168 | //! 169 | //! // get the SPI device back 170 | //! let dev = rtc.destroy_ds3234(); 171 | //! ``` 172 | //! 173 | //! ### Set the current date and time at once 174 | //! 175 | //! ```no_run 176 | //! use ds323x::{Ds323x, NaiveDate, DateTimeAccess}; 177 | //! use linux_embedded_hal::I2cdev; 178 | //! 179 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 180 | //! let mut rtc = Ds323x::new_ds3231(dev); 181 | //! let datetime = NaiveDate::from_ymd(2020, 5, 1).and_hms(19, 59, 58); 182 | //! rtc.set_datetime(&datetime).unwrap(); 183 | //! ``` 184 | //! 185 | //! ### Get the current date and time at once 186 | //! 187 | //! ```no_run 188 | //! use ds323x::{Ds323x, DateTimeAccess}; 189 | //! use linux_embedded_hal::I2cdev; 190 | //! 191 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 192 | //! let mut rtc = Ds323x::new_ds3231(dev); 193 | //! let dt = rtc.datetime().unwrap(); 194 | //! println!("{}", dt); 195 | //! // This will print something like: 2020-05-01 19:59:58 196 | //! ``` 197 | //! 198 | //! ### Get the year 199 | //! 200 | //! Similar methods exist for month, day, weekday, hours, minutes and seconds. 201 | //! 202 | //! ```no_run 203 | //! use ds323x::{Ds323x, Rtcc}; 204 | //! use linux_embedded_hal::I2cdev; 205 | //! 206 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 207 | //! let mut rtc = Ds323x::new_ds3231(dev); 208 | //! let year = rtc.year().unwrap(); 209 | //! println!("Year: {}", year); 210 | //! ``` 211 | //! 212 | //! ### Set the year 213 | //! 214 | //! Similar methods exist for month, day, weekday, hours, minutes and seconds. 215 | //! 216 | //! ```no_run 217 | //! use ds323x::{Ds323x, Rtcc}; 218 | //! use linux_embedded_hal::I2cdev; 219 | //! 220 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 221 | //! let mut rtc = Ds323x::new_ds3231(dev); 222 | //! rtc.set_year(2018).unwrap(); 223 | //! ``` 224 | //! 225 | //! ### Enable/disable the device 226 | //! 227 | //! ```no_run 228 | //! use ds323x::Ds323x; 229 | //! use linux_embedded_hal::I2cdev; 230 | //! 231 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 232 | //! let mut rtc = Ds323x::new_ds3231(dev); 233 | //! rtc.disable().unwrap(); // stops the clock 234 | //! let running = rtc.running().unwrap(); 235 | //! println!("Is running: {}", running); // will print false 236 | //! rtc.enable().unwrap(); // set clock to run 237 | //! println!("Is running: {}", running); // will print true 238 | //! ``` 239 | //! 240 | //! ### Read the temperature 241 | //! 242 | //! ```no_run 243 | //! use ds323x::Ds323x; 244 | //! use linux_embedded_hal::I2cdev; 245 | //! 246 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 247 | //! let mut rtc = Ds323x::new_ds3231(dev); 248 | //! let temperature = rtc.temperature().unwrap(); 249 | //! ``` 250 | //! 251 | //! ### Read busy status 252 | //! 253 | //! ```no_run 254 | //! use ds323x::Ds323x; 255 | //! use linux_embedded_hal::I2cdev; 256 | //! 257 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 258 | //! let mut rtc = Ds323x::new_ds3231(dev); 259 | //! let busy = rtc.busy().unwrap(); 260 | //! ``` 261 | //! 262 | //! ### Enable the square-wave output with a frequency of 4.096Hz 263 | //! 264 | //! ```no_run 265 | //! use ds323x::{Ds323x, SqWFreq}; 266 | //! use linux_embedded_hal::I2cdev; 267 | //! 268 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 269 | //! let mut rtc = Ds323x::new_ds3231(dev); 270 | //! rtc.set_square_wave_frequency(SqWFreq::_4_096Hz).unwrap(); 271 | //! // The same output pin can be used for interrupts or as square-wave output 272 | //! rtc.use_int_sqw_output_as_square_wave().unwrap(); 273 | //! rtc.enable_square_wave().unwrap(); 274 | //! ``` 275 | //! 276 | //! ### Enable the 32kHz output except when on battery power 277 | //! 278 | //! Additionally enabling the output depending on the power source is only 279 | //! available for the devices DS3232 and DS3234. 280 | //! 281 | //! ```no_run 282 | //! use ds323x::{Ds323x, SqWFreq}; 283 | //! use linux_embedded_hal::I2cdev; 284 | //! 285 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 286 | //! let mut rtc = Ds323x::new_ds3232(dev); 287 | //! rtc.disable_32khz_output_on_battery().unwrap(); // only available for DS3232 and DS3234 288 | //! rtc.enable_32khz_output().unwrap(); 289 | //! ``` 290 | //! 291 | //! ### Set the aging offset 292 | //! 293 | //! ```no_run 294 | //! use ds323x::Ds323x; 295 | //! use linux_embedded_hal::I2cdev; 296 | //! 297 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 298 | //! let mut rtc = Ds323x::new_ds3231(dev); 299 | //! rtc.set_aging_offset(-15).unwrap(); 300 | //! ``` 301 | //! 302 | //! ### Set the temperature conversion rate to once every 128 seconds 303 | //! 304 | //! This is only available for the devices DS3232 and DS3234. 305 | //! 306 | //! ```no_run 307 | //! use ds323x::{Ds323x, TempConvRate}; 308 | //! use linux_embedded_hal::I2cdev; 309 | //! 310 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 311 | //! let mut rtc = Ds323x::new_ds3232(dev); 312 | //! rtc.set_temperature_conversion_rate(TempConvRate::_128s).unwrap(); 313 | //! ``` 314 | //! 315 | //! ### Set the Alarm1 to each week on a week day at a specific time 316 | //! 317 | //! ```no_run 318 | //! use ds323x::{Ds323x, Hours, WeekdayAlarm1, Alarm1Matching}; 319 | //! use linux_embedded_hal::I2cdev; 320 | //! 321 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 322 | //! let mut rtc = Ds323x::new_ds3231(dev); 323 | //! let alarm1 = WeekdayAlarm1 { 324 | //! weekday: 1, 325 | //! hour: Hours::H24(7), 326 | //! minute: 2, 327 | //! second: 15 328 | //! }; 329 | //! rtc.set_alarm1_weekday(alarm1, Alarm1Matching::AllMatch).unwrap(); 330 | //! ``` 331 | //! 332 | //! ### Set the Alarm2 to each day at the same time and enable interrupts on output 333 | //! 334 | //! The INT/SQW output pin will be set to 1 when it the alarm matches. 335 | //! 336 | //! ```no_run 337 | //! use ds323x::{Ds323x, Hours, DayAlarm2, Alarm2Matching}; 338 | //! use linux_embedded_hal::I2cdev; 339 | //! 340 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 341 | //! let mut rtc = Ds323x::new_ds3231(dev); 342 | //! let alarm2 = DayAlarm2 { 343 | //! day: 1, // does not matter given the chosen matching 344 | //! hour: Hours::AM(11), 345 | //! minute: 2 346 | //! }; 347 | //! rtc.set_alarm2_day(alarm2, Alarm2Matching::HoursAndMinutesMatch).unwrap(); 348 | //! rtc.use_int_sqw_output_as_interrupt().unwrap(); 349 | //! rtc.enable_alarm2_interrupts().unwrap(); 350 | //! ``` 351 | //! 352 | //! ### Set the Alarm1 to a specific time 353 | //! 354 | //! ```no_run 355 | //! use ds323x::{Ds323x, Hours, NaiveTime}; 356 | //! use linux_embedded_hal::I2cdev; 357 | //! 358 | //! let dev = I2cdev::new("/dev/i2c-1").unwrap(); 359 | //! let mut rtc = Ds323x::new_ds3231(dev); 360 | //! let time = NaiveTime::from_hms(19, 59, 58); 361 | //! rtc.set_alarm1_hms(time).unwrap(); 362 | //! ``` 363 | 364 | #![deny(unsafe_code, missing_docs)] 365 | #![no_std] 366 | 367 | use core::marker::PhantomData; 368 | use embedded_hal::spi::{Mode, MODE_1, MODE_3}; 369 | pub use rtcc::{ 370 | DateTimeAccess, Datelike, Hours, NaiveDate, NaiveDateTime, NaiveTime, Rtcc, Timelike, 371 | }; 372 | 373 | /// SPI mode 1 (CPOL = 0, CPHA = 1) 374 | pub const SPI_MODE_1: Mode = MODE_1; 375 | /// SPI mode 3 (CPOL = 1, CPHA = 1) 376 | pub const SPI_MODE_3: Mode = MODE_3; 377 | 378 | /// All possible errors in this crate 379 | #[derive(Debug)] 380 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 381 | pub enum Error { 382 | /// I²C/SPI bus error 383 | Comm(E), 384 | /// Invalid input data provided 385 | InvalidInputData, 386 | /// Internal device state is invalid. 387 | /// 388 | /// It was not possible to read a valid date and/or time. 389 | /// The device is probably missing initialization. 390 | InvalidDeviceState, 391 | } 392 | 393 | /// Square-wave output frequency 394 | #[derive(Debug, Clone, Copy, PartialEq)] 395 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 396 | pub enum SqWFreq { 397 | /// 1 Hz (default) 398 | _1Hz, 399 | /// 1.024 Hz 400 | _1_024Hz, 401 | /// 4.096 Hz 402 | _4_096Hz, 403 | /// 8.192 Hz 404 | _8_192Hz, 405 | } 406 | 407 | /// Temperature conversion rate 408 | /// 409 | /// This is only available on the DS3232 and DS3234 devices. 410 | #[derive(Debug, Clone, Copy, PartialEq)] 411 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 412 | pub enum TempConvRate { 413 | /// Once every 64 seconds (default) 414 | _64s, 415 | /// Once every 128 seconds 416 | _128s, 417 | /// Once every 256 seconds 418 | _256s, 419 | /// Once every 512 seconds 420 | _512s, 421 | } 422 | 423 | struct Register; 424 | 425 | impl Register { 426 | const SECONDS: u8 = 0x00; 427 | const MINUTES: u8 = 0x01; 428 | const HOURS: u8 = 0x02; 429 | const DOW: u8 = 0x03; 430 | const DOM: u8 = 0x04; 431 | const MONTH: u8 = 0x05; 432 | const YEAR: u8 = 0x06; 433 | const ALARM1_SECONDS: u8 = 0x07; 434 | const ALARM2_MINUTES: u8 = 0x0B; 435 | const CONTROL: u8 = 0x0E; 436 | const STATUS: u8 = 0x0F; 437 | const AGING_OFFSET: u8 = 0x10; 438 | const TEMP_MSB: u8 = 0x11; 439 | const TEMP_CONV: u8 = 0x13; 440 | } 441 | 442 | struct BitFlags; 443 | 444 | impl BitFlags { 445 | const H24_H12: u8 = 0b0100_0000; 446 | const AM_PM: u8 = 0b0010_0000; 447 | const CENTURY: u8 = 0b1000_0000; 448 | const EOSC: u8 = 0b1000_0000; 449 | const BBSQW: u8 = 0b0100_0000; 450 | const TEMP_CONV: u8 = 0b0010_0000; 451 | const RS2: u8 = 0b0001_0000; 452 | const RS1: u8 = 0b0000_1000; 453 | const INTCN: u8 = 0b0000_0100; 454 | const ALARM2_INT_EN: u8 = 0b0000_0010; 455 | const ALARM1_INT_EN: u8 = 0b0000_0001; 456 | const OSC_STOP: u8 = 0b1000_0000; 457 | const BB32KHZ: u8 = 0b0100_0000; 458 | const CRATE1: u8 = 0b0010_0000; 459 | const CRATE0: u8 = 0b0001_0000; 460 | const EN32KHZ: u8 = 0b0000_1000; 461 | const BUSY: u8 = 0b0000_0100; 462 | const ALARM2F: u8 = 0b0000_0010; 463 | const ALARM1F: u8 = 0b0000_0001; 464 | const TEMP_CONV_BAT: u8 = 0b0000_0001; 465 | const ALARM_MATCH: u8 = 0b1000_0000; 466 | const WEEKDAY: u8 = 0b0100_0000; 467 | } 468 | 469 | const DEVICE_ADDRESS: u8 = 0b110_1000; 470 | const CONTROL_POR_VALUE: u8 = 0b0001_1100; 471 | 472 | /// IC markers 473 | pub mod ic { 474 | /// DS3231 IC marker 475 | pub struct DS3231; 476 | /// DS3232 IC marker 477 | pub struct DS3232; 478 | /// DS3234 IC marker 479 | pub struct DS3234; 480 | } 481 | 482 | /// DS3231, DS3232 and DS3234 RTC driver 483 | #[derive(Debug, Default)] 484 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 485 | pub struct Ds323x { 486 | iface: DI, 487 | control: u8, 488 | status: u8, 489 | _ic: PhantomData, 490 | } 491 | 492 | mod ds323x; 493 | pub mod interface; 494 | pub use crate::ds323x::{ 495 | Alarm1Matching, Alarm2Matching, DayAlarm1, DayAlarm2, WeekdayAlarm1, WeekdayAlarm2, 496 | }; 497 | mod ds3231; 498 | mod ds3232; 499 | mod ds3234; 500 | 501 | mod private { 502 | use super::{ic, interface}; 503 | pub trait Sealed {} 504 | 505 | impl Sealed for interface::SpiInterface {} 506 | impl Sealed for interface::I2cInterface {} 507 | 508 | impl Sealed for ic::DS3231 {} 509 | impl Sealed for ic::DS3232 {} 510 | impl Sealed for ic::DS3234 {} 511 | } 512 | -------------------------------------------------------------------------------- /tests/alarms.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal_mock::eh1::{i2c::Transaction as I2cTrans, spi::Transaction as SpiTrans}; 2 | mod common; 3 | use self::common::{ 4 | destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, 5 | BitFlags as BF, Register, DEVICE_ADDRESS as DEV_ADDR, 6 | }; 7 | use ds323x::{ 8 | Alarm1Matching as A1M, Alarm2Matching as A2M, DayAlarm1, DayAlarm2, Error, Hours, NaiveTime, 9 | WeekdayAlarm1, WeekdayAlarm2, 10 | }; 11 | 12 | #[macro_export] 13 | macro_rules! _set_invalid_alarm_test { 14 | ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $( $value:expr ),+) => { 15 | #[test] 16 | fn $name() { 17 | let mut dev = $create_method(&[]); 18 | assert_invalid_input_data!(dev.$method($($value),*)); 19 | $destroy_method(dev); 20 | } 21 | }; 22 | } 23 | 24 | macro_rules! set_invalid_alarm_test { 25 | ($name:ident, $method:ident, $( $value:expr ),+) => { 26 | mod $name { 27 | use super::*; 28 | _set_invalid_alarm_test!( 29 | cannot_set_invalid_ds3231, 30 | $method, 31 | new_ds3231, 32 | destroy_ds3231, 33 | $($value),* 34 | ); 35 | _set_invalid_alarm_test!( 36 | cannot_set_invalid_ds3232, 37 | $method, 38 | new_ds3232, 39 | destroy_ds3232, 40 | $($value),* 41 | ); 42 | _set_invalid_alarm_test!( 43 | cannot_set_invalid_ds3234, 44 | $method, 45 | new_ds3234, 46 | destroy_ds3234, 47 | $($value),* 48 | ); 49 | } 50 | }; 51 | } 52 | 53 | mod alarm1 { 54 | use super::*; 55 | set_invalid_alarm_test!( 56 | day_invalid_s, 57 | set_alarm1_day, 58 | DayAlarm1 { 59 | day: 1, 60 | hour: Hours::H24(1), 61 | minute: 1, 62 | second: 60 63 | }, 64 | A1M::AllMatch 65 | ); 66 | set_invalid_alarm_test!( 67 | day_invalid_min, 68 | set_alarm1_day, 69 | DayAlarm1 { 70 | day: 1, 71 | hour: Hours::H24(1), 72 | minute: 60, 73 | second: 1 74 | }, 75 | A1M::AllMatch 76 | ); 77 | set_invalid_alarm_test!( 78 | day_invalid_second, 79 | set_alarm1_day, 80 | DayAlarm1 { 81 | day: 1, 82 | hour: Hours::H24(1), 83 | minute: 59, 84 | second: 60 85 | }, 86 | A1M::SecondsMatch 87 | ); 88 | set_invalid_alarm_test!( 89 | day_invalid_minute, 90 | set_alarm1_day, 91 | DayAlarm1 { 92 | day: 1, 93 | hour: Hours::H24(1), 94 | minute: 60, 95 | second: 10 96 | }, 97 | A1M::MinutesAndSecondsMatch 98 | ); 99 | set_invalid_alarm_test!( 100 | day_invalid_h, 101 | set_alarm1_day, 102 | DayAlarm1 { 103 | day: 1, 104 | hour: Hours::H24(24), 105 | minute: 1, 106 | second: 1 107 | }, 108 | A1M::AllMatch 109 | ); 110 | set_invalid_alarm_test!( 111 | day_invalid_h_hmasm, 112 | set_alarm1_day, 113 | DayAlarm1 { 114 | day: 1, 115 | hour: Hours::H24(24), 116 | minute: 1, 117 | second: 1 118 | }, 119 | A1M::HoursMinutesAndSecondsMatch 120 | ); 121 | set_invalid_alarm_test!( 122 | day_invalid_am1, 123 | set_alarm1_day, 124 | DayAlarm1 { 125 | day: 1, 126 | hour: Hours::AM(0), 127 | minute: 1, 128 | second: 1 129 | }, 130 | A1M::AllMatch 131 | ); 132 | set_invalid_alarm_test!( 133 | day_invalid_am2, 134 | set_alarm1_day, 135 | DayAlarm1 { 136 | day: 1, 137 | hour: Hours::AM(13), 138 | minute: 1, 139 | second: 1 140 | }, 141 | A1M::AllMatch 142 | ); 143 | set_invalid_alarm_test!( 144 | day_invalid_pm1, 145 | set_alarm1_day, 146 | DayAlarm1 { 147 | day: 1, 148 | hour: Hours::PM(0), 149 | minute: 1, 150 | second: 1 151 | }, 152 | A1M::AllMatch 153 | ); 154 | set_invalid_alarm_test!( 155 | day_invalid_pm2, 156 | set_alarm1_day, 157 | DayAlarm1 { 158 | day: 1, 159 | hour: Hours::PM(13), 160 | minute: 1, 161 | second: 1 162 | }, 163 | A1M::AllMatch 164 | ); 165 | set_invalid_alarm_test!( 166 | day_invalid_d1, 167 | set_alarm1_day, 168 | DayAlarm1 { 169 | day: 0, 170 | hour: Hours::H24(1), 171 | minute: 1, 172 | second: 1 173 | }, 174 | A1M::AllMatch 175 | ); 176 | set_invalid_alarm_test!( 177 | day_invalid_d2, 178 | set_alarm1_day, 179 | DayAlarm1 { 180 | day: 32, 181 | hour: Hours::H24(1), 182 | minute: 1, 183 | second: 1 184 | }, 185 | A1M::AllMatch 186 | ); 187 | 188 | set_invalid_alarm_test!( 189 | wd_invalid_s, 190 | set_alarm1_weekday, 191 | WeekdayAlarm1 { 192 | weekday: 1, 193 | hour: Hours::H24(1), 194 | minute: 1, 195 | second: 60 196 | }, 197 | A1M::AllMatch 198 | ); 199 | set_invalid_alarm_test!( 200 | wd_invalid_min, 201 | set_alarm1_weekday, 202 | WeekdayAlarm1 { 203 | weekday: 1, 204 | hour: Hours::H24(1), 205 | minute: 60, 206 | second: 1 207 | }, 208 | A1M::AllMatch 209 | ); 210 | set_invalid_alarm_test!( 211 | wd_invalid_h, 212 | set_alarm1_weekday, 213 | WeekdayAlarm1 { 214 | weekday: 1, 215 | hour: Hours::H24(24), 216 | minute: 1, 217 | second: 1 218 | }, 219 | A1M::AllMatch 220 | ); 221 | set_invalid_alarm_test!( 222 | wd_invalid_h_hmasm, 223 | set_alarm1_weekday, 224 | WeekdayAlarm1 { 225 | weekday: 1, 226 | hour: Hours::H24(24), 227 | minute: 1, 228 | second: 1 229 | }, 230 | A1M::HoursMinutesAndSecondsMatch 231 | ); 232 | 233 | set_invalid_alarm_test!( 234 | wd_invalid_am1, 235 | set_alarm1_weekday, 236 | WeekdayAlarm1 { 237 | weekday: 1, 238 | hour: Hours::AM(0), 239 | minute: 1, 240 | second: 1 241 | }, 242 | A1M::AllMatch 243 | ); 244 | set_invalid_alarm_test!( 245 | wd_invalid_am2, 246 | set_alarm1_weekday, 247 | WeekdayAlarm1 { 248 | weekday: 1, 249 | hour: Hours::AM(13), 250 | minute: 1, 251 | second: 1 252 | }, 253 | A1M::AllMatch 254 | ); 255 | set_invalid_alarm_test!( 256 | wd_invalid_pm1, 257 | set_alarm1_weekday, 258 | WeekdayAlarm1 { 259 | weekday: 1, 260 | hour: Hours::PM(0), 261 | minute: 1, 262 | second: 1 263 | }, 264 | A1M::AllMatch 265 | ); 266 | set_invalid_alarm_test!( 267 | wd_invalid_pm2, 268 | set_alarm1_weekday, 269 | WeekdayAlarm1 { 270 | weekday: 1, 271 | hour: Hours::PM(13), 272 | minute: 1, 273 | second: 1 274 | }, 275 | A1M::AllMatch 276 | ); 277 | set_invalid_alarm_test!( 278 | wd_invalid_d1, 279 | set_alarm1_weekday, 280 | WeekdayAlarm1 { 281 | weekday: 0, 282 | hour: Hours::H24(1), 283 | minute: 1, 284 | second: 1 285 | }, 286 | A1M::AllMatch 287 | ); 288 | set_invalid_alarm_test!( 289 | wd_invalid_d2, 290 | set_alarm1_weekday, 291 | WeekdayAlarm1 { 292 | weekday: 32, 293 | hour: Hours::H24(1), 294 | minute: 1, 295 | second: 1 296 | }, 297 | A1M::AllMatch 298 | ); 299 | set_invalid_alarm_test!( 300 | wd_invalid_sec_sm, 301 | set_alarm1_weekday, 302 | WeekdayAlarm1 { 303 | weekday: 1, 304 | hour: Hours::H24(1), 305 | minute: 1, 306 | second: 60 307 | }, 308 | A1M::SecondsMatch 309 | ); 310 | set_invalid_alarm_test!( 311 | wd_invalid_min_masm, 312 | set_alarm1_weekday, 313 | WeekdayAlarm1 { 314 | weekday: 1, 315 | hour: Hours::H24(1), 316 | minute: 60, 317 | second: 1 318 | }, 319 | A1M::MinutesAndSecondsMatch 320 | ); 321 | } 322 | 323 | mod alarm2 { 324 | use super::*; 325 | set_invalid_alarm_test!( 326 | day_invalid_min_mm, 327 | set_alarm2_day, 328 | DayAlarm2 { 329 | day: 1, 330 | hour: Hours::H24(1), 331 | minute: 60 332 | }, 333 | A2M::MinutesMatch 334 | ); 335 | set_invalid_alarm_test!( 336 | day_invalid_min, 337 | set_alarm2_day, 338 | DayAlarm2 { 339 | day: 1, 340 | hour: Hours::H24(1), 341 | minute: 60 342 | }, 343 | A2M::AllMatch 344 | ); 345 | set_invalid_alarm_test!( 346 | day_invalid_h, 347 | set_alarm2_day, 348 | DayAlarm2 { 349 | day: 1, 350 | hour: Hours::H24(24), 351 | minute: 1 352 | }, 353 | A2M::AllMatch 354 | ); 355 | set_invalid_alarm_test!( 356 | day_invalid_h_hamm, 357 | set_alarm2_day, 358 | DayAlarm2 { 359 | day: 1, 360 | hour: Hours::H24(24), 361 | minute: 1 362 | }, 363 | A2M::HoursAndMinutesMatch 364 | ); 365 | set_invalid_alarm_test!( 366 | day_invalid_am1, 367 | set_alarm2_day, 368 | DayAlarm2 { 369 | day: 1, 370 | hour: Hours::AM(0), 371 | minute: 1 372 | }, 373 | A2M::AllMatch 374 | ); 375 | set_invalid_alarm_test!( 376 | day_invalid_am2, 377 | set_alarm2_day, 378 | DayAlarm2 { 379 | day: 1, 380 | hour: Hours::AM(13), 381 | minute: 1 382 | }, 383 | A2M::AllMatch 384 | ); 385 | set_invalid_alarm_test!( 386 | day_invalid_pm1, 387 | set_alarm2_day, 388 | DayAlarm2 { 389 | day: 1, 390 | hour: Hours::PM(0), 391 | minute: 1 392 | }, 393 | A2M::AllMatch 394 | ); 395 | set_invalid_alarm_test!( 396 | day_invalid_pm2, 397 | set_alarm2_day, 398 | DayAlarm2 { 399 | day: 1, 400 | hour: Hours::PM(13), 401 | minute: 1 402 | }, 403 | A2M::AllMatch 404 | ); 405 | set_invalid_alarm_test!( 406 | day_invalid_d1, 407 | set_alarm2_day, 408 | DayAlarm2 { 409 | day: 0, 410 | hour: Hours::H24(1), 411 | minute: 1 412 | }, 413 | A2M::AllMatch 414 | ); 415 | set_invalid_alarm_test!( 416 | day_invalid_d2, 417 | set_alarm2_day, 418 | DayAlarm2 { 419 | day: 32, 420 | hour: Hours::H24(1), 421 | minute: 1 422 | }, 423 | A2M::AllMatch 424 | ); 425 | 426 | set_invalid_alarm_test!( 427 | wd_invalid_min_mm, 428 | set_alarm2_weekday, 429 | WeekdayAlarm2 { 430 | weekday: 1, 431 | hour: Hours::H24(1), 432 | minute: 60 433 | }, 434 | A2M::MinutesMatch 435 | ); 436 | set_invalid_alarm_test!( 437 | wd_invalid_min, 438 | set_alarm2_weekday, 439 | WeekdayAlarm2 { 440 | weekday: 1, 441 | hour: Hours::H24(1), 442 | minute: 60 443 | }, 444 | A2M::AllMatch 445 | ); 446 | set_invalid_alarm_test!( 447 | wd_invalid_h, 448 | set_alarm2_weekday, 449 | WeekdayAlarm2 { 450 | weekday: 1, 451 | hour: Hours::H24(24), 452 | minute: 1 453 | }, 454 | A2M::AllMatch 455 | ); 456 | set_invalid_alarm_test!( 457 | wd_invalid_h_hmm, 458 | set_alarm2_weekday, 459 | WeekdayAlarm2 { 460 | weekday: 1, 461 | hour: Hours::H24(24), 462 | minute: 1 463 | }, 464 | A2M::HoursAndMinutesMatch 465 | ); 466 | set_invalid_alarm_test!( 467 | wd_invalid_am1, 468 | set_alarm2_weekday, 469 | WeekdayAlarm2 { 470 | weekday: 1, 471 | hour: Hours::AM(0), 472 | minute: 1 473 | }, 474 | A2M::AllMatch 475 | ); 476 | set_invalid_alarm_test!( 477 | wd_invalid_am2, 478 | set_alarm2_weekday, 479 | WeekdayAlarm2 { 480 | weekday: 1, 481 | hour: Hours::AM(13), 482 | minute: 1 483 | }, 484 | A2M::AllMatch 485 | ); 486 | set_invalid_alarm_test!( 487 | wd_invalid_pm1, 488 | set_alarm2_weekday, 489 | WeekdayAlarm2 { 490 | weekday: 1, 491 | hour: Hours::PM(0), 492 | minute: 1 493 | }, 494 | A2M::AllMatch 495 | ); 496 | set_invalid_alarm_test!( 497 | wd_invalid_pm2, 498 | set_alarm2_weekday, 499 | WeekdayAlarm2 { 500 | weekday: 1, 501 | hour: Hours::PM(13), 502 | minute: 1 503 | }, 504 | A2M::AllMatch 505 | ); 506 | set_invalid_alarm_test!( 507 | wd_invalid_d1, 508 | set_alarm2_weekday, 509 | WeekdayAlarm2 { 510 | weekday: 0, 511 | hour: Hours::H24(1), 512 | minute: 1 513 | }, 514 | A2M::AllMatch 515 | ); 516 | set_invalid_alarm_test!( 517 | wd_invalid_d2, 518 | set_alarm2_weekday, 519 | WeekdayAlarm2 { 520 | weekday: 32, 521 | hour: Hours::H24(1), 522 | minute: 1 523 | }, 524 | A2M::AllMatch 525 | ); 526 | set_invalid_alarm_test!( 527 | wd_invalid_minute, 528 | set_alarm2_weekday, 529 | WeekdayAlarm2 { 530 | weekday: 1, 531 | hour: Hours::H24(1), 532 | minute: 60 533 | }, 534 | A2M::HoursAndMinutesMatch 535 | ); 536 | } 537 | 538 | macro_rules! _set_values_test { 539 | ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $transactions:expr, $( $value:expr ),+) => { 540 | #[test] 541 | fn $name() { 542 | let trans = $transactions; 543 | let mut dev = $create_method(&trans); 544 | dev.$method($($value),*).unwrap(); 545 | $destroy_method(dev); 546 | } 547 | }; 548 | } 549 | 550 | macro_rules! set_values_test { 551 | ($name:ident, $method:ident, $i2c_transactions:expr, $spi_transactions:expr, $( $value:expr ),+) => { 552 | mod $name { 553 | use super::*; 554 | _set_values_test!( 555 | can_set_ds3231, 556 | $method, 557 | new_ds3231, 558 | destroy_ds3231, 559 | $i2c_transactions, 560 | $($value),* 561 | ); 562 | _set_values_test!( 563 | can_set_ds3232, 564 | $method, 565 | new_ds3232, 566 | destroy_ds3232, 567 | $i2c_transactions, 568 | $($value),* 569 | ); 570 | _set_values_test!( 571 | can_set_ds3234, 572 | $method, 573 | new_ds3234, 574 | destroy_ds3234, 575 | $spi_transactions, 576 | $($value),* 577 | ); 578 | } 579 | }; 580 | } 581 | 582 | macro_rules! set_alarm_test { 583 | ($name:ident, $method:ident, $register:ident, [ $( $registers:expr ),+ ], $( $value:expr ),+) => { 584 | set_values_test!($name, $method, 585 | [ I2cTrans::write(DEV_ADDR, vec![Register::$register, $( $registers ),*]) ], 586 | [ SpiTrans::transaction_start(), SpiTrans::write_vec(vec![Register::$register + 0x80, $( $registers ),*]), SpiTrans::transaction_end() ], 587 | $($value),* 588 | ); 589 | }; 590 | } 591 | 592 | const AM: u8 = BF::ALARM_MATCH; 593 | 594 | mod alarm1_day { 595 | use super::*; 596 | set_alarm_test!( 597 | h24, 598 | set_alarm1_day, 599 | ALARM1_SECONDS, 600 | [4, 3, 2, 1], 601 | DayAlarm1 { 602 | day: 1, 603 | hour: Hours::H24(2), 604 | minute: 3, 605 | second: 4 606 | }, 607 | A1M::AllMatch 608 | ); 609 | set_alarm_test!( 610 | am, 611 | set_alarm1_day, 612 | ALARM1_SECONDS, 613 | [4, 3, 0b0100_0010, 1], 614 | DayAlarm1 { 615 | day: 1, 616 | hour: Hours::AM(2), 617 | minute: 3, 618 | second: 4 619 | }, 620 | A1M::AllMatch 621 | ); 622 | set_alarm_test!( 623 | pm, 624 | set_alarm1_day, 625 | ALARM1_SECONDS, 626 | [4, 3, 0b0110_0010, 1], 627 | DayAlarm1 { 628 | day: 1, 629 | hour: Hours::PM(2), 630 | minute: 3, 631 | second: 4 632 | }, 633 | A1M::AllMatch 634 | ); 635 | set_alarm_test!( 636 | match_hms_naivetime, 637 | set_alarm1_hms, 638 | ALARM1_SECONDS, 639 | [4, 3, 2, AM | 1], 640 | NaiveTime::from_hms_opt(2, 3, 4).unwrap() 641 | ); 642 | set_alarm_test!( 643 | match_hms, 644 | set_alarm1_day, 645 | ALARM1_SECONDS, 646 | [4, 3, 2, AM | 1], 647 | DayAlarm1 { 648 | day: 1, 649 | hour: Hours::H24(2), 650 | minute: 3, 651 | second: 4 652 | }, 653 | A1M::HoursMinutesAndSecondsMatch 654 | ); 655 | set_alarm_test!( 656 | match_hms_ignore_incorrect_day, 657 | set_alarm1_day, 658 | ALARM1_SECONDS, 659 | [4, 3, 2, AM | 1], 660 | DayAlarm1 { 661 | day: 0, 662 | hour: Hours::H24(2), 663 | minute: 3, 664 | second: 4 665 | }, 666 | A1M::HoursMinutesAndSecondsMatch 667 | ); 668 | set_alarm_test!( 669 | match_ms_ignore_invalid_hour, 670 | set_alarm1_day, 671 | ALARM1_SECONDS, 672 | [4, 3, AM, AM | 1], 673 | DayAlarm1 { 674 | day: 1, 675 | hour: Hours::H24(24), 676 | minute: 3, 677 | second: 4 678 | }, 679 | A1M::MinutesAndSecondsMatch 680 | ); 681 | set_alarm_test!( 682 | match_ms, 683 | set_alarm1_day, 684 | ALARM1_SECONDS, 685 | [4, 3, AM | 2, AM | 1], 686 | DayAlarm1 { 687 | day: 1, 688 | hour: Hours::H24(2), 689 | minute: 3, 690 | second: 4 691 | }, 692 | A1M::MinutesAndSecondsMatch 693 | ); 694 | set_alarm_test!( 695 | match_ms_ignore_incorrect_day, 696 | set_alarm1_day, 697 | ALARM1_SECONDS, 698 | [4, 3, AM | 2, AM | 1], 699 | DayAlarm1 { 700 | day: 0, 701 | hour: Hours::H24(2), 702 | minute: 3, 703 | second: 4 704 | }, 705 | A1M::MinutesAndSecondsMatch 706 | ); 707 | set_alarm_test!( 708 | match_s, 709 | set_alarm1_day, 710 | ALARM1_SECONDS, 711 | [4, AM | 3, AM | 2, AM | 1], 712 | DayAlarm1 { 713 | day: 1, 714 | hour: Hours::H24(2), 715 | minute: 3, 716 | second: 4 717 | }, 718 | A1M::SecondsMatch 719 | ); 720 | set_alarm_test!( 721 | match_s_ignore_incorrect_min, 722 | set_alarm1_day, 723 | ALARM1_SECONDS, 724 | [4, AM, AM | 2, AM | 1], 725 | DayAlarm1 { 726 | day: 1, 727 | hour: Hours::H24(2), 728 | minute: 60, 729 | second: 4 730 | }, 731 | A1M::SecondsMatch 732 | ); 733 | set_alarm_test!( 734 | match_ops, 735 | set_alarm1_day, 736 | ALARM1_SECONDS, 737 | [AM | 4, AM | 3, AM | 2, AM | 1], 738 | DayAlarm1 { 739 | day: 1, 740 | hour: Hours::H24(2), 741 | minute: 3, 742 | second: 4 743 | }, 744 | A1M::OncePerSecond 745 | ); 746 | } 747 | 748 | mod alarm1_weekday { 749 | use super::*; 750 | set_alarm_test!( 751 | h24, 752 | set_alarm1_weekday, 753 | ALARM1_SECONDS, 754 | [4, 3, 2, BF::WEEKDAY | 1], 755 | WeekdayAlarm1 { 756 | weekday: 1, 757 | hour: Hours::H24(2), 758 | minute: 3, 759 | second: 4 760 | }, 761 | A1M::AllMatch 762 | ); 763 | set_alarm_test!( 764 | am, 765 | set_alarm1_weekday, 766 | ALARM1_SECONDS, 767 | [4, 3, 0b0100_0010, BF::WEEKDAY | 1], 768 | WeekdayAlarm1 { 769 | weekday: 1, 770 | hour: Hours::AM(2), 771 | minute: 3, 772 | second: 4 773 | }, 774 | A1M::AllMatch 775 | ); 776 | set_alarm_test!( 777 | pm, 778 | set_alarm1_weekday, 779 | ALARM1_SECONDS, 780 | [4, 3, 0b0110_0010, BF::WEEKDAY | 1], 781 | WeekdayAlarm1 { 782 | weekday: 1, 783 | hour: Hours::PM(2), 784 | minute: 3, 785 | second: 4 786 | }, 787 | A1M::AllMatch 788 | ); 789 | set_alarm_test!( 790 | match_hms_ignore_incorrect_wd, 791 | set_alarm1_weekday, 792 | ALARM1_SECONDS, 793 | [4, 3, 2, AM | BF::WEEKDAY | 1], 794 | WeekdayAlarm1 { 795 | weekday: 0, 796 | hour: Hours::H24(2), 797 | minute: 3, 798 | second: 4 799 | }, 800 | A1M::HoursMinutesAndSecondsMatch 801 | ); 802 | set_alarm_test!( 803 | match_hms, 804 | set_alarm1_weekday, 805 | ALARM1_SECONDS, 806 | [4, 3, 2, AM | BF::WEEKDAY | 1], 807 | WeekdayAlarm1 { 808 | weekday: 1, 809 | hour: Hours::H24(2), 810 | minute: 3, 811 | second: 4 812 | }, 813 | A1M::HoursMinutesAndSecondsMatch 814 | ); 815 | set_alarm_test!( 816 | match_ms, 817 | set_alarm1_weekday, 818 | ALARM1_SECONDS, 819 | [4, 3, AM | 2, AM | BF::WEEKDAY | 1], 820 | WeekdayAlarm1 { 821 | weekday: 1, 822 | hour: Hours::H24(2), 823 | minute: 3, 824 | second: 4 825 | }, 826 | A1M::MinutesAndSecondsMatch 827 | ); 828 | set_alarm_test!( 829 | match_s, 830 | set_alarm1_weekday, 831 | ALARM1_SECONDS, 832 | [4, AM | 3, AM | 2, AM | BF::WEEKDAY | 1], 833 | WeekdayAlarm1 { 834 | weekday: 1, 835 | hour: Hours::H24(2), 836 | minute: 3, 837 | second: 4 838 | }, 839 | A1M::SecondsMatch 840 | ); 841 | set_alarm_test!( 842 | match_s_ignore_incorrect_min, 843 | set_alarm1_weekday, 844 | ALARM1_SECONDS, 845 | [4, AM, AM | 2, AM | BF::WEEKDAY | 1], 846 | WeekdayAlarm1 { 847 | weekday: 1, 848 | hour: Hours::H24(2), 849 | minute: 60, 850 | second: 4 851 | }, 852 | A1M::SecondsMatch 853 | ); 854 | set_alarm_test!( 855 | match_ops, 856 | set_alarm1_weekday, 857 | ALARM1_SECONDS, 858 | [AM | 4, AM | 3, AM | 2, AM | BF::WEEKDAY | 1], 859 | WeekdayAlarm1 { 860 | weekday: 1, 861 | hour: Hours::H24(2), 862 | minute: 3, 863 | second: 4 864 | }, 865 | A1M::OncePerSecond 866 | ); 867 | set_alarm_test!( 868 | match_ops_ignore_incorrect_sec, 869 | set_alarm1_weekday, 870 | ALARM1_SECONDS, 871 | [AM, AM | 3, AM | 2, AM | BF::WEEKDAY | 1], 872 | WeekdayAlarm1 { 873 | weekday: 1, 874 | hour: Hours::H24(2), 875 | minute: 3, 876 | second: 60 877 | }, 878 | A1M::OncePerSecond 879 | ); 880 | } 881 | 882 | mod alarm2_day { 883 | use super::*; 884 | set_alarm_test!( 885 | h24, 886 | set_alarm2_day, 887 | ALARM2_MINUTES, 888 | [3, 2, 1], 889 | DayAlarm2 { 890 | day: 1, 891 | hour: Hours::H24(2), 892 | minute: 3 893 | }, 894 | A2M::AllMatch 895 | ); 896 | set_alarm_test!( 897 | am, 898 | set_alarm2_day, 899 | ALARM2_MINUTES, 900 | [3, 0b0100_0010, 1], 901 | DayAlarm2 { 902 | day: 1, 903 | hour: Hours::AM(2), 904 | minute: 3 905 | }, 906 | A2M::AllMatch 907 | ); 908 | set_alarm_test!( 909 | pm, 910 | set_alarm2_day, 911 | ALARM2_MINUTES, 912 | [3, 0b0110_0010, 1], 913 | DayAlarm2 { 914 | day: 1, 915 | hour: Hours::PM(2), 916 | minute: 3 917 | }, 918 | A2M::AllMatch 919 | ); 920 | set_alarm_test!( 921 | match_hm_naivetime, 922 | set_alarm2_hm, 923 | ALARM2_MINUTES, 924 | [3, 2, AM | 1], 925 | NaiveTime::from_hms_opt(2, 3, 0).unwrap() 926 | ); 927 | set_alarm_test!( 928 | match_hm, 929 | set_alarm2_day, 930 | ALARM2_MINUTES, 931 | [3, 2, AM | 1], 932 | DayAlarm2 { 933 | day: 1, 934 | hour: Hours::H24(2), 935 | minute: 3 936 | }, 937 | A2M::HoursAndMinutesMatch 938 | ); 939 | set_alarm_test!( 940 | match_hm_ignore_incorrect_day, 941 | set_alarm2_day, 942 | ALARM2_MINUTES, 943 | [3, 2, AM | 1], 944 | DayAlarm2 { 945 | day: 0, 946 | hour: Hours::H24(2), 947 | minute: 3 948 | }, 949 | A2M::HoursAndMinutesMatch 950 | ); 951 | set_alarm_test!( 952 | match_m, 953 | set_alarm2_day, 954 | ALARM2_MINUTES, 955 | [3, AM | 2, AM | 1], 956 | DayAlarm2 { 957 | day: 1, 958 | hour: Hours::H24(2), 959 | minute: 3 960 | }, 961 | A2M::MinutesMatch 962 | ); 963 | set_alarm_test!( 964 | match_m_ignore_invalid_h, 965 | set_alarm2_day, 966 | ALARM2_MINUTES, 967 | [3, AM, AM | 1], 968 | DayAlarm2 { 969 | day: 1, 970 | hour: Hours::H24(25), 971 | minute: 3 972 | }, 973 | A2M::MinutesMatch 974 | ); 975 | set_alarm_test!( 976 | match_opm, 977 | set_alarm2_day, 978 | ALARM2_MINUTES, 979 | [AM | 3, AM | 2, AM | 1], 980 | DayAlarm2 { 981 | day: 1, 982 | hour: Hours::H24(2), 983 | minute: 3 984 | }, 985 | A2M::OncePerMinute 986 | ); 987 | set_alarm_test!( 988 | match_opm_ignore_incorrect_min, 989 | set_alarm2_day, 990 | ALARM2_MINUTES, 991 | [AM, AM | 2, AM | 1], 992 | DayAlarm2 { 993 | day: 1, 994 | hour: Hours::H24(2), 995 | minute: 60 996 | }, 997 | A2M::OncePerMinute 998 | ); 999 | } 1000 | 1001 | mod alarm2_weekday { 1002 | use super::*; 1003 | set_alarm_test!( 1004 | h24, 1005 | set_alarm2_weekday, 1006 | ALARM2_MINUTES, 1007 | [3, 2, BF::WEEKDAY | 1], 1008 | WeekdayAlarm2 { 1009 | weekday: 1, 1010 | hour: Hours::H24(2), 1011 | minute: 3 1012 | }, 1013 | A2M::AllMatch 1014 | ); 1015 | set_alarm_test!( 1016 | am, 1017 | set_alarm2_weekday, 1018 | ALARM2_MINUTES, 1019 | [3, 0b0100_0010, BF::WEEKDAY | 1], 1020 | WeekdayAlarm2 { 1021 | weekday: 1, 1022 | hour: Hours::AM(2), 1023 | minute: 3 1024 | }, 1025 | A2M::AllMatch 1026 | ); 1027 | set_alarm_test!( 1028 | pm, 1029 | set_alarm2_weekday, 1030 | ALARM2_MINUTES, 1031 | [3, 0b0110_0010, BF::WEEKDAY | 1], 1032 | WeekdayAlarm2 { 1033 | weekday: 1, 1034 | hour: Hours::PM(2), 1035 | minute: 3 1036 | }, 1037 | A2M::AllMatch 1038 | ); 1039 | set_alarm_test!( 1040 | match_hm, 1041 | set_alarm2_weekday, 1042 | ALARM2_MINUTES, 1043 | [3, 2, AM | BF::WEEKDAY | 1], 1044 | WeekdayAlarm2 { 1045 | weekday: 1, 1046 | hour: Hours::H24(2), 1047 | minute: 3 1048 | }, 1049 | A2M::HoursAndMinutesMatch 1050 | ); 1051 | set_alarm_test!( 1052 | match_hm_ignore_incorrect_wd, 1053 | set_alarm2_weekday, 1054 | ALARM2_MINUTES, 1055 | [3, 2, AM | BF::WEEKDAY | 1], 1056 | WeekdayAlarm2 { 1057 | weekday: 0, 1058 | hour: Hours::H24(2), 1059 | minute: 3 1060 | }, 1061 | A2M::HoursAndMinutesMatch 1062 | ); 1063 | set_alarm_test!( 1064 | match_m, 1065 | set_alarm2_weekday, 1066 | ALARM2_MINUTES, 1067 | [3, AM | 2, AM | BF::WEEKDAY | 1], 1068 | WeekdayAlarm2 { 1069 | weekday: 1, 1070 | hour: Hours::H24(2), 1071 | minute: 3 1072 | }, 1073 | A2M::MinutesMatch 1074 | ); 1075 | set_alarm_test!( 1076 | match_m_ignore_invalid_hour, 1077 | set_alarm2_weekday, 1078 | ALARM2_MINUTES, 1079 | [3, AM, AM | BF::WEEKDAY | 1], 1080 | WeekdayAlarm2 { 1081 | weekday: 1, 1082 | hour: Hours::H24(24), 1083 | minute: 3 1084 | }, 1085 | A2M::MinutesMatch 1086 | ); 1087 | set_alarm_test!( 1088 | match_opm, 1089 | set_alarm2_weekday, 1090 | ALARM2_MINUTES, 1091 | [AM | 3, AM | 2, AM | BF::WEEKDAY | 1], 1092 | WeekdayAlarm2 { 1093 | weekday: 1, 1094 | hour: Hours::H24(2), 1095 | minute: 3 1096 | }, 1097 | A2M::OncePerMinute 1098 | ); 1099 | set_alarm_test!( 1100 | match_opm_ignore_incorrect_min, 1101 | set_alarm2_weekday, 1102 | ALARM2_MINUTES, 1103 | [AM, AM | 2, AM | BF::WEEKDAY | 1], 1104 | WeekdayAlarm2 { 1105 | weekday: 1, 1106 | hour: Hours::H24(2), 1107 | minute: 60 1108 | }, 1109 | A2M::OncePerMinute 1110 | ); 1111 | } 1112 | --------------------------------------------------------------------------------