Skip to main content

pcf8563_dd/
lib.rs

1#![cfg_attr(not(any(test, feature = "std")), no_std)]
2//! # PCF8563/BM8563 Real-Time Clock Driver
3//!
4//! This crate provides a bisync-based driver for the PCF8563 and BM8563 real-time clock ICs,
5//! built upon the `device-driver` crate for robust, declarative register definitions via a
6//! YAML manifest. It supports both asynchronous (`async`) and blocking operation through a
7//! unified API, using the [`bisync`](https://docs.rs/bisync) crate for seamless compatibility
8//! with both `embedded-hal` and `embedded-hal-async` traits.
9//!
10//! ## Features
11//!
12//! *   **Declarative Register Map:** Full device configuration defined in `device.yaml`.
13//! *   **Unified Async/Blocking Support:** Write your code once and use it in both async and blocking contexts via bisync.
14//! *   **Type-Safe API:** High-level functions for reading/setting date and time
15//!     and a generated low-level API (`ll`) for direct register access.
16//! *   **Full RTC Functionality:** Date/time, alarms, timer, and clock output control.
17//! *   **Optional `rtcc` Traits (blocking):** Enable the `rtcc` feature to implement
18//!     [`rtcc::DateTimeAccess`](https://docs.rs/rtcc/latest/rtcc/trait.DateTimeAccess.html)
19//!     and [`rtcc::Rtcc`](https://docs.rs/rtcc/latest/rtcc/trait.Rtcc.html) on the blocking driver.
20//! *   **`defmt` and `log` Integration:** Optional support for logging and debugging.
21//!
22//! ## Getting Started
23//!
24//! To use the driver, instantiate `Pcf8563` (blocking) or `Pcf8563Async` (async) with your I2C bus implementation:
25//!
26//! ```rust,no_run
27//! # use embedded_hal::i2c::I2c;
28//! # use pcf8563_dd::Pcf8563;
29//! let i2c_bus = todo!();
30//! let mut rtc = Pcf8563::new(i2c_bus);
31//!
32//! let datetime = rtc.get_datetime()?;
33//! # Ok::<(), pcf8563_dd::RtcError<std::io::Error>>(())
34//! ```
35//!
36//! For async environments, use `Pcf8563Async` (re-exported from the `asynchronous` module):
37//!
38//! ```rust,no_run
39//! # use embedded_hal_async::i2c::I2c;
40//! # use pcf8563_dd::Pcf8563Async;
41//! let i2c_bus = todo!();
42//! let mut rtc = Pcf8563Async::new(i2c_bus);
43//!
44//! let datetime = rtc.get_datetime().await?;
45//! # Ok::<(), pcf8563_dd::RtcError<std::io::Error>>(())
46//! ```
47//!
48//! For a detailed register map, please refer to the `device.yaml` file in the
49//! [repository](https://github.com/okhsunrog/pcf8563-dd).
50//!
51//! ## Supported Devices
52//!
53//! - **PCF8563** - NXP real-time clock (original)
54//! - **BM8563** - Compatible clone found in M5Stack devices
55
56#[macro_use]
57pub(crate) mod fmt;
58
59use thiserror::Error;
60
61device_driver::create_device!(device_name: Pcf8563LowLevel, manifest: "device.yaml");
62
63/// PCF8563/BM8563 I2C address (7-bit)
64pub const PCF8563_I2C_ADDR: u8 = 0x51;
65
66#[derive(Debug, Error)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68pub enum RtcError<I2cErr> {
69    #[error("I2C error")]
70    I2c(I2cErr),
71    #[error("Invalid input data")]
72    InvalidInputData,
73}
74
75/// Date and time structure
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77#[cfg_attr(feature = "defmt", derive(defmt::Format))]
78pub struct DateTime {
79    /// Year (0-99, represents 2000-2099 or 1900-1999 based on century flag)
80    pub year: u8,
81    /// Month (1-12)
82    pub month: u8,
83    /// Day of month (1-31)
84    pub day: u8,
85    /// Weekday (0-6, typically 0=Sunday)
86    pub weekday: u8,
87    /// Hours (0-23)
88    pub hours: u8,
89    /// Minutes (0-59)
90    pub minutes: u8,
91    /// Seconds (0-59)
92    pub seconds: u8,
93}
94
95/// Time-only structure (for clock applications without calendar)
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
97#[cfg_attr(feature = "defmt", derive(defmt::Format))]
98pub struct Time {
99    /// Hours (0-23)
100    pub hours: u8,
101    /// Minutes (0-59)
102    pub minutes: u8,
103    /// Seconds (0-59)
104    pub seconds: u8,
105}
106
107/// Alarm configuration
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
109#[cfg_attr(feature = "defmt", derive(defmt::Format))]
110pub struct Alarm {
111    /// Minute alarm (0-59), None if disabled
112    pub minute: Option<u8>,
113    /// Hour alarm (0-23), None if disabled
114    pub hour: Option<u8>,
115    /// Day alarm (1-31), None if disabled
116    pub day: Option<u8>,
117    /// Weekday alarm (0-6), None if disabled
118    pub weekday: Option<u8>,
119}
120
121pub struct Pcf8563Interface<I2CBus> {
122    i2c_bus: I2CBus,
123}
124
125impl<I2CBus> Pcf8563Interface<I2CBus> {
126    pub fn new(i2c_bus: I2CBus) -> Self {
127        Self { i2c_bus }
128    }
129}
130
131#[path = "."]
132mod asynchronous {
133    use bisync::asynchronous::*;
134    use device_driver::AsyncRegisterInterface as RegisterInterface;
135    use embedded_hal_async::i2c::I2c;
136    mod driver;
137    pub use driver::*;
138}
139pub use asynchronous::Pcf8563 as Pcf8563Async;
140
141#[path = "."]
142mod blocking {
143    use bisync::synchronous::*;
144    use device_driver::RegisterInterface;
145    use embedded_hal::i2c::I2c;
146    #[allow(clippy::duplicate_mod)]
147    mod driver;
148    pub use driver::*;
149}
150pub use blocking::Pcf8563;
151
152/// Convert BCD to decimal
153#[inline]
154pub(crate) fn bcd_to_dec(bcd: u8) -> u8 {
155    (bcd & 0x0F) + ((bcd >> 4) * 10)
156}
157
158/// Convert decimal to BCD
159#[inline]
160pub(crate) fn dec_to_bcd(dec: u8) -> u8 {
161    ((dec / 10) << 4) | (dec % 10)
162}