From 67366409dcd3ec1bd7f3e408921b24e520079d02 Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Wed, 11 Sep 2024 23:40:39 +0530 Subject: [PATCH 1/9] Remove dependency on alloc with new API exposing `Driver` --- .github/workflows/ci.yml | 18 ++-- CHANGELOG.md | 27 +++++- Cargo.toml | 20 +++-- LICENSE | 28 +++--- README.md | 85 +++++++++++++++++- build.sh | 8 ++ examples/basic.rs | 36 -------- rust-toolchain.toml | 2 + src/bus.rs | 68 +++++++------- src/driver.rs | 186 ++++++++++++++++++++++++++++++++++++++ src/error.rs | 126 +++++++++----------------- src/lib.rs | 187 ++++----------------------------------- src/models.rs | 18 ++-- src/rtc.rs | 143 ++++++++++++++++++++++++++++++ 14 files changed, 584 insertions(+), 368 deletions(-) create mode 100755 build.sh delete mode 100644 examples/basic.rs create mode 100644 rust-toolchain.toml create mode 100644 src/driver.rs create mode 100644 src/rtc.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 447d4ab..de0a4ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,6 @@ jobs: toolchain: ${{ matrix.toolchain }} target: ${{ matrix.target }} - run: cargo build --target ${{ matrix.target }} - - run: cargo build --target ${{ matrix.target }} --features linux_embedded_hal - if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} test: name: Unit Tests @@ -38,7 +36,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - run: cargo test --all-features + - run: cargo test clippy: name: Clippy @@ -48,7 +46,7 @@ jobs: - uses: dtolnay/rust-toolchain@nightly with: components: clippy - - run: cargo clippy --all-features --all-targets -- --deny warnings + - run: cargo clippy -- -Dclippy::all -Dclippy::pedantic --deny warnings format: name: Format @@ -67,7 +65,16 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - run: cargo +nightly rustdoc --all-features + - run: cargo +nightly rustdoc + + examples: + name: Examples + runs-on: ubuntu-latest + env: {"RUSTFLAGS": "-D warnings"} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo build --examples crates_io_publish: name: Publish (crates.io) @@ -77,6 +84,7 @@ jobs: - test - clippy - format + - examples - doc runs-on: ubuntu-latest timeout-minutes: 25 diff --git a/CHANGELOG.md b/CHANGELOG.md index fe97218..b69dd28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.4.4] - 2024-03-01 +## [4.0.0] - XX Sept 2024 + +### Changed +- __Breaking Change__: Rewamped blocking I2C with `embedded-hal` v0.2; Testing on hardware pending. + +## [3.0.0] - 15 Sept 2024 - Yanked + +### Changed +- __Breaking Change__: Underlying driver is protected from the public API. + +## [2.0.0] - 14 Sept 2024 - Yanked + +### Changed +- __Breaking Change__: `RTClock::new()` accepts a shared reference to the shared bus; this allows communicating with multiple rtc chips at different addresses. + +## [1.0.0] - 14 Sept 2024 - Yanked + +### Changed +- __Breaking Change__: Removed alloc as a default requirement +- __Breaking Change__: Brand new public API via `RTCClock`, refer to docs for details. +- Tested (compilation/clippy) with Embassy and `defmt`. +- TODO: Testing on actual hardware. + +## [0.4.4] - 1 March 2024 ### Changed - __Breaking Change__: Fully revised module organisation - __Breaking Change__: Revised error API - Renamed `Rv8803<_>::from_i2c0` to `Rv8803<_>::from_i2c`. -## [0.1.0] - 2022-01-01 +## [0.1.0] - 1 January 2022 - Initial release diff --git a/Cargo.toml b/Cargo.toml index 179b9f1..90e91a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,30 @@ [package] name = "rv8803" -version = "0.4.4" -authors = ["Michael de Silva "] +version = "4.0.0" +authors = ["Michael de Silva "] edition = "2021" repository = "https://github.com/bsodmike/rv8803-rs" license = "MIT" -description = "RV8803 driver with support for I2C" +description = "RTC clock driver for the rv8803 chip via I2C" readme = "README.md" keywords = ["i2c", "driver", "embedded-hal-driver", "rv8803"] categories = ["embedded", "hardware-support", "no-std"] [features] -default = ["alloc"] -alloc = [] -async = ["embedded-hal-async"] -linux_embedded_hal = ["linux-embedded-hal"] +default = ["defmt"] +linux_blocking = ["linux-embedded-hal", "log",] [dependencies] -log = "0.4" +chrono = { version = "0.4.38", default-features = false } +defmt = { version = "0.3", optional = true } +heapless = { version = "0.8", default-features = false } embedded-hal = { package = "embedded-hal", version = "^1.0" } embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"] } -serde = { version = "1", features = ["derive"], default-features = false, optional = true } embedded-hal-async = { package = "embedded-hal-async", version = "^1.0", optional = true } linux-embedded-hal = { version = "^0.3", optional = true } +log = { version = "0.4", optional = true } +serde = { version = "1", features = ["derive"], default-features = false, optional = true } +shared-bus = { version = "0.3.1" } [dev-dependencies] i2cdev = "0.5.1" diff --git a/LICENSE b/LICENSE index 8d6c43f..da1433f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License +The MIT License (MIT) -Copyright (c) 2024-present Michael de Silva (https://desilva.io) +Copyright (c) Michael de Silva (https://desilva.io/about) +Email: michael@cyberdynea.io / michael@crabtronics.net -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 4916a98..47e52bc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,91 @@ # rv8803 +[![Crates.io Version](https://img.shields.io/crates/v/rv8803)](https://crates.io/crates/rv8803) +[![Released API docs](https://img.shields.io/docsrs/rv8803)](https://docs.rs/rv8803/) + +## API usage + +Here's an example with `embassy_stm32`; you will need to configure the I2C peripheral according to the exact chip used. This example uses the `stm32wl55cc-cm4` metapac info. + +```rust +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.ls = LsConfig::default_lse(); + config.rcc.hse = Some(Hse { + freq: Hertz(32_000_000), + mode: HseMode::Bypass, + prescaler: HsePrescaler::DIV1, + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL6, + divp: None, + divq: Some(PllQDiv::DIV2), // PLL1_Q clock (32 / 2 * 6 / 2), used for RNG + divr: Some(PllRDiv::DIV2), // sysclk 48Mhz clock (32 / 2 * 6 / 2) + }); + } + let p = embassy_stm32::init_primary(config, &SHARED_DATA); + info!("Hello World!"); + let i2c1_periph = p.I2C1; + + // External RTC with the rv8803 + let i2c = I2c::new( + i2c1_periph, + p.PA9, // scl + p.PA10, // sda + Irqs, + p.DMA1_CH2, //tx_dma + p.DMA1_CH1, + Hertz(100_000), + Default::default(), + ); + + let device_address: u8 = 0x32; + let bus = shared_bus::BusManagerCortexM::new(i2c); + + let mut rtc = rv8803::rtc::RTClock::< + embassy_stm32::i2c::I2c<'_, embassy_stm32::mode::Async>, + embassy_stm32::i2c::Error, + cortex_m::interrupt::Mutex>>, + >::new(&bus, &device_address); + + info!("Starting loop()..."); + loop { + Timer::after_secs(1).await; + + let mut buf = [0u8, 8]; + if let Ok(_succeeded) = rtc.update_time(&mut buf) { + defmt::debug!("Updated time: {}", buf); + } + } +} + +``` + +Refer to the [docs](https://docs.rs/rv8803/latest/rv8803/) for details. + +### WARNING! + +The [latest release](https://crates.io/crates/rv8803) simply stabilises a new public API, however, this still needs testing on actual hardware and is pending. + +Should you wish to contribute towards this effort, kindly do so by opening issues/PRs. Thanks! + +### Building + +This runs a `release` build, runs tests and generates docs. + +```shell script +./build.sh +``` + ## Minimum supported Rust version -The Minimum Supported Rust Version (MSRV) at the moment is rustc **1.78.0-nightly** (c475e2303 2024-02-28). +This project is tested against rust `nightly`. ## License diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..941d9d8 --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -ex + +cargo build --release +cargo test +cargo clippy -- -Dclippy::all -Dclippy::pedantic +cargo doc diff --git a/examples/basic.rs b/examples/basic.rs deleted file mode 100644 index 3c34cfc..0000000 --- a/examples/basic.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! This example reads the chip ID from a RV8803. -#[cfg(feature = "linux_embedded_hal")] -use linux_embedded_hal::I2cdev; -#[allow(unused_imports)] -use rv8803::{ - error::CrateError, - models::{Rv8803, TIME_ARRAY_LENGTH}, -}; - -#[cfg(feature = "linux_embedded_hal")] -fn main() -> Result<(), CrateError> { - let dev = I2cdev::new("/dev/i2c-1"); - let i2c = dev.map_err(CrateError::new)?; - - let mut rtc: Rv8803<_> = - Rv8803::from_i2c(i2c, rv8803::bus::Address::Default).expect("Failed to initialize RV8803"); - - std::thread::sleep(std::time::Duration::from_millis(2)); - - let mut time = [0_u8; TIME_ARRAY_LENGTH]; - - // Fetch time from RTC. - let update = rtc - .update_time(&mut time) - .expect("Fetched latest time from RTC"); - if !update { - log::warn!("RTC: Failed reading latest time"); - } - - Ok(()) -} - -#[cfg(not(feature = "linux_embedded_hal"))] -fn main() -> Result<(), CrateError> { - Ok(()) -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/src/bus.rs b/src/bus.rs index f69aa9d..097427c 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,18 +1,33 @@ -use super::Register; +use super::models::Register; +#[allow(unused_imports)] +use crate::error::DriverTransferError; use core::marker::PhantomData; -/// Bus trait (named [`BusTrait`]). +/// Trait for [`Bus`] +#[allow(clippy::module_name_repetitions)] pub trait BusTrait { /// Bus error. type Error; - /// Read from the RV8803 + /// Read from the `rv8803` chip. + /// + /// # Errors + /// + /// Will return [`BusTrait::Error`] if the read attempt fails. fn read_register(&mut self, register: Register) -> Result; - /// Write to the RV8803 + /// Write to the `rv8803` chip. + /// + /// # Errors + /// + /// Will return [`BusTrait::Error`] if the write attempt fails. fn write_register(&mut self, register: Register, value: u8) -> Result<(), Self::Error>; /// Read multiple registers + /// + /// # Errors + /// + /// Will return [`BusTrait::Error`] if the read attempt fails. fn read_multiple_registers( &mut self, addr: u8, @@ -21,41 +36,27 @@ pub trait BusTrait { ) -> Result; /// Write to register by register address + /// + /// # Errors + /// + /// Will return [`BusTrait::Error`] if the write attempt fails. fn write_register_by_addr(&mut self, reg_addr: u8, value: u8) -> Result<(), Self::Error>; /// Read register by register address + /// + /// # Errors + /// + /// Will return [`BusTrait::Error`] if the read attempt fails. fn read_register_by_addr(&mut self, reg_addr: u8) -> Result; } -/// I2C device address. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -#[repr(u8)] -pub enum Address { - /// Default device address - Default = 0x32, -} - -impl Address { - /// Value of the address variant - pub fn value(&self) -> u8 { - *self as u8 - } -} - -impl From
for u8 { - fn from(value: Address) -> Self { - match value { - Address::Default => Address::Default.value(), - } - } -} - -/// Holds an instance of an i2c bus, where the bus implements the `embedded-hal` traits [`embedded_hal_0_2::blocking::i2c::WriteRead`] and [`embedded_hal_0_2::blocking::i2c::Write`] +/// Struct type to hold an I2C peripheral #[derive(Debug)] +#[allow(clippy::struct_field_names)] pub struct Bus<'a, I2C> { address: u8, bus: I2C, - _p: PhantomData<&'a I2C>, + _i2c: PhantomData<&'a I2C>, } impl<'a, I2C, E> Bus<'a, I2C> @@ -64,13 +65,12 @@ where + embedded_hal_0_2::blocking::i2c::Write, Bus<'a, I2C>: BusTrait, { - /// Creates a new `BusTrait` from a I2C peripheral, and an I2C - /// device address. - pub fn new(bus: I2C, address: Address) -> Self { + /// Creates a new [`Bus`] from an I2C peripheral. + pub fn new(bus: I2C, address: &u8) -> Self { Self { bus, - address: address as u8, - _p: PhantomData, + address: *address, + _i2c: PhantomData, } } } diff --git a/src/driver.rs b/src/driver.rs new file mode 100644 index 0000000..6ad325d --- /dev/null +++ b/src/driver.rs @@ -0,0 +1,186 @@ +#[allow(unused_imports)] +use crate::bus::{self, Bus, BusTrait}; +use crate::error::DriverTransferError; +use crate::models::{Register, TIME_ARRAY_LENGTH}; + +/// Driver driver. +#[derive(Debug)] +pub struct Driver { + /// Holds the bus. + pub bus: B, +} + +#[allow(dead_code)] +impl<'a, I2C, E> Driver> +where + I2C: embedded_hal_0_2::blocking::i2c::WriteRead + + embedded_hal_0_2::blocking::i2c::Write, + Bus<'a, I2C>: bus::BusTrait, + DriverTransferError: From, +{ + /// Creates a new `Driver` driver from a I2C peripheral, and an I2C + /// device address. + pub fn new(i2c: I2C, address: u8) -> Self { + let bus = crate::bus::Bus::new(i2c, &address); + + Self { bus } + } + + /// Set time on the Driver module + /// + /// # Errors + /// + /// If the year specified is always > 2000, hence `u16` casting to `u8` + /// is OK, as long as the year is < 2100. When the year > 2255 this will + /// return [`core::num::TryFromIntError`]. + /// + /// Read/write errors during communication with the `Driver` chip will also return an error. + #[allow(clippy::too_many_arguments)] + pub fn set_time( + &mut self, + sec: u8, + min: u8, + hour: u8, + weekday: u8, + date: u8, + month: u8, + year: u16, + ) -> Result> { + match u8::try_from(year - 2000) { + Ok(year) => { + self.bus + .write_register(Register::Seconds, dec_to_bcd(sec))?; + self.bus + .write_register(Register::Minutes, dec_to_bcd(min))?; + self.bus.write_register(Register::Hours, dec_to_bcd(hour))?; + self.bus.write_register(Register::Date, dec_to_bcd(date))?; + self.bus + .write_register(Register::Month, dec_to_bcd(month))?; + self.bus.write_register(Register::Year, dec_to_bcd(year))?; + self.bus.write_register(Register::Weekday, weekday)?; + + // Set RESET bit to 0 after setting time to make sure seconds don't get stuck. + self.write_bit( + Register::Control.address(), + Register::ControlReset.address(), + false, + )?; + + #[cfg(feature = "defmt")] + defmt::debug!("Driver::set_time: updated RTC clock"); + + Ok(true) + } + Err(_err) => Err(DriverTransferError::Transfer), + } + } + + /// Fetch time from the RTC clock and store it in the buffer `dest`. + pub fn update_time(&mut self, dest: &mut [u8]) -> Result { + if !(self.bus.read_multiple_registers( + Register::Hundredths.address(), + dest, + TIME_ARRAY_LENGTH, + )?) { + #[cfg(feature = "defmt")] + defmt::warn!("update_time: attempt read - fail 1"); + return Ok(false); // Something went wrong + } + + // If hundredths are at 99 or seconds are at 59, read again to make sure we didn't accidentally skip a second/minute + if bcd_to_dec(dest[0]) == 99 || bcd_to_dec(dest[1]) == 59 { + let mut temp_time = [0_u8; TIME_ARRAY_LENGTH]; + + #[cfg(feature = "defmt")] + defmt::debug!("update_time: if hundredths are at 99 or seconds are at 59, read again to make sure we didn't accidentally skip a second/minute / Hundreths: {} / Seconds: {}", bcd_to_dec(dest[0]),bcd_to_dec(dest[1])); + + if !(self.bus.read_multiple_registers( + Register::Hundredths.address(), + &mut temp_time, + TIME_ARRAY_LENGTH, + )?) { + #[cfg(feature = "defmt")] + defmt::warn!("update_time: attempt read - fail 2"); + return Ok(false); // Something went wrong + }; + + // If the reading for hundredths has rolled over, then our new data is correct, otherwise, we can leave the old data. + if bcd_to_dec(dest[0]) > bcd_to_dec(temp_time[0]) { + #[cfg(feature = "defmt")] + defmt::debug!("update_time: the reading for hundredths has rolled over, then our new data is correct. / Hundreths: {} / temp_time[0]: {}", + bcd_to_dec(dest[0]), + bcd_to_dec(temp_time[0])); + + for (i, el) in temp_time.iter().enumerate() { + dest[i] = *el; + } + } + } + + // byte order: https://github.com/sparkfun/SparkFun_RV-8803_Arduino_Library/blob/main/src/SparkFun_RV8803.h#L129-L138 + let mut buf = [0_u8; 8]; + for (i, el) in dest.iter().enumerate() { + // Note: Weekday does not undergo BCD to Decimal conversion. + if i == 4 { + buf[i] = *el; + } else { + #[cfg(feature = "defmt")] + defmt::info!("Raw: {} / BCD to Dec: {}", *el, bcd_to_dec(*el)); + buf[i] = bcd_to_dec(*el); + } + } + + dest.copy_from_slice(&buf[..dest.len()]); + + Ok(true) + } + + /// Write a single bit to the specified register + pub fn write_bit(&mut self, reg_addr: u8, bit_addr: u8, bit_to_write: bool) -> Result { + let mut value = 0; + if let Ok(reg_value) = self.bus.read_register_by_addr(reg_addr) { + value = reg_value; + } + + value &= !(1 << bit_addr); + value |= u8::from(bit_to_write) << bit_addr; + + self.bus.write_register_by_addr(reg_addr, value)?; + + Ok(true) + } + + /// Read seconds from the RTC clock + pub fn read_seconds(&mut self) -> Result { + let secs = self.bus.read_register(Register::Seconds)?; + + Ok(bcd_to_dec(secs)) + } + + /// Read the year from the RTC clock + pub fn read_year(&mut self) -> Result { + let year = self.bus.read_register(Register::Year)?; + + Ok(bcd_to_dec(year)) + } + + /// Set the year and update the RTC clock + pub fn set_year(&mut self, year: u16) -> Result + where + E: core::convert::From, + { + let year = dec_to_bcd(u8::try_from(year - 2000)?); + + self.bus.write_register(Register::Year, year)?; + + self.read_year() + } +} + +fn bcd_to_dec(value: u8) -> u8 { + ((value / 0x10) * 10) + (value % 0x10) +} + +fn dec_to_bcd(value: u8) -> u8 { + ((value / 10) * 0x10) + (value % 10) +} diff --git a/src/error.rs b/src/error.rs index d3980a5..d02ee73 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,101 +1,59 @@ -/// All possible errors in this crate -use alloc::boxed::Box; -use core::{ - error::{self}, - fmt::Display, -}; - -/// Type for all crate errors -pub type CrateError = Error; -/// Boxed error type -pub type BoxError = Box; - -#[allow(dead_code)] -#[derive(Debug)] -/// Error struct -pub struct Error { - inner: Box, -} - -#[allow(dead_code)] -#[derive(Debug)] -/// Error kind record struct holding both [`Kind`] and an [`Option`]< [`BoxError`]> -struct ErrorKind { - kind: Kind, - cause: Option, -} - -/// Error kind enum +/// Error type #[derive(Debug)] -pub enum Kind { - /// Default crate error. - InternalError, -} - -impl Error { - /// Create an instance of [`Error`] specifying a [`Kind`] but without a cause. - fn with_kind(kind: Kind) -> Self { - Self { - inner: Box::new(ErrorKind { kind, cause: None }), - } - } - - /// Create a new instance of [`Error`] with cause of type [`BoxError`] - pub fn with>(mut self, cause: C) -> Error { - self.inner.cause = Some(cause.into()); - self - } - - /// Create a new instance of [`Error`] of type [`Kind::InternalError`] with cause of type [`BoxError`] - pub fn new>(cause: E) -> Self { - Self::default().with(cause) - } -} - -impl core::default::Default for Error { - /// Create a new instance of [`Error`] of type [`Kind::InternalError`] - fn default() -> Self { - Self::with_kind(Kind::InternalError) - } -} - -#[cfg(feature = "linux_embedded_hal")] -impl FnOnce<(linux_embedded_hal::i2cdev::linux::LinuxI2CError,)> for Error { - type Output = Error; - - extern "rust-call" fn call_once( - self, - args: (linux_embedded_hal::i2cdev::linux::LinuxI2CError,), - ) -> Self::Output { - Error::new(args.0) - } -} +pub struct Error(dyn core::error::Error + Send + Sync); -impl Display for CrateError { +impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - if let Some(ref cause) = self.inner.cause { - write!(f, "CrateError: {}", cause) - } else { - f.write_str("CrateError: Unknown error") - } + write!(f, "Error: {}", &self.0) } } -impl error::Error for CrateError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - self.inner - .cause - .as_ref() - .map(|cause| &**cause as &(dyn error::Error + 'static)) +impl core::error::Error for Error { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + self.0.source() } fn description(&self) -> &str { "description() is deprecated; use Display" } - fn cause(&self) -> Option<&dyn error::Error> { + fn cause(&self) -> Option<&dyn core::error::Error> { self.source() } fn provide<'a>(&'a self, _request: &mut core::error::Request<'a>) {} } + +/// Driver transfer error. + +// NOTE: The error type in `embassy_stm32::i2c::Error` does not impl any traits (refer to https://docs.embassy.dev/embassy-stm32/git/stm32wl55cc-cm4/i2c/enum.Error.html), and since this is a driver, there is no context as to what specific driver is being used with this HAL this lib is depending on. +// Hence a generic type is used on `DriverTransferError` to aid as a workaround. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(clippy::module_name_repetitions)] +pub enum DriverTransferError { + /// Bus error occurred. e.g. A START or a STOP condition is detected and is not + /// located after a multiple of 9 SCL clock pulses. + Bus, + /// The arbitration was lost, e.g. electrical problems with the clock signal. + ArbitrationLoss, + /// A bus operation was not acknowledged, e.g. due to the addressed device not + /// being available on the bus or the device not being ready to process requests + /// at the moment. + NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource), + /// The peripheral receive buffer was overrun. + Overrun, + /// A different error occurred. The original error may contain more information. + Other, + #[allow(missing_docs)] + _Phant(core::marker::PhantomData), + /// Error during I2C Transfer + Transfer, +} + +// This allows erasing the originating error, i.e `DriverTransferError: From`. +impl From for DriverTransferError { + fn from(_value: E) -> Self { + self::DriverTransferError::Transfer + } +} diff --git a/src/lib.rs b/src/lib.rs index 3d7bb22..8cdf08d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,184 +1,31 @@ -//! RV8803 driver with support for I2C. +//! RTC clock driver for the `rv8803` chip over I2C. +//! +//! Latest implementation supports `blocking` transfer, either directly via an owned I2C peripheral or using a [`shared_bus`]. #![no_std] -#![cfg_attr(feature = "async", allow(incomplete_features))] -#![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))] #![forbid(unsafe_code)] #![warn(missing_docs)] -#![feature(error_in_core)] +#![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))] #![feature(error_generic_member_access)] -#![feature(fn_traits)] -#![feature(unboxed_closures)] +#![feature(trivial_bounds)] -use crate::bus::{Bus, BusTrait}; pub use embedded_hal_0_2; -use log::{debug, warn}; -use models::{Register, Rv8803, TIME_ARRAY_LENGTH}; -#[cfg(feature = "alloc")] -extern crate alloc; - -/// RV8803 I2C bus implementation with embedded-hal version 0.2 +/// An I2C bus, allowing communications over an I2C peripheral. pub mod bus; -/// Error handler for this crate -pub mod error; -/// Models -pub mod models; - -#[allow(dead_code)] -impl<'a, I2C, E> Rv8803> -where - I2C: embedded_hal_0_2::blocking::i2c::WriteRead - + embedded_hal_0_2::blocking::i2c::Write, - Bus<'a, I2C>: bus::BusTrait, -{ - /// Create a new RV8803 from a [`bus::Bus`]. - pub fn new(bus: Bus<'a, I2C>) -> Result { - Ok(Self { bus }) - } - - /// Creates a new `Rv8803` driver from a I2C peripheral, and an I2C - /// device address. - pub fn from_i2c(i2c: I2C, address: crate::bus::Address) -> Result { - let bus = crate::bus::Bus::new(i2c, address); - - Self::new(bus) - } - - /// Set time on the RV8803 module - #[allow(clippy::too_many_arguments)] - pub fn set_time( - &mut self, - sec: u8, - min: u8, - hour: u8, - weekday: u8, - date: u8, - month: u8, - year: u16, - ) -> Result { - self.bus - .write_register(Register::Seconds, dec_to_bcd(sec))?; - self.bus - .write_register(Register::Minutes, dec_to_bcd(min))?; - self.bus.write_register(Register::Hours, dec_to_bcd(hour))?; - self.bus.write_register(Register::Date, dec_to_bcd(date))?; - self.bus - .write_register(Register::Month, dec_to_bcd(month))?; - self.bus - .write_register(Register::Year, dec_to_bcd((year - 2000) as u8))?; - self.bus.write_register(Register::Weekday, weekday)?; - - //Set RESET bit to 0 after setting time to make sure seconds don't get stuck. - self.write_bit( - Register::Control.address(), - Register::ControlReset.address(), - false, - )?; - - debug!("rv8803::set_time: updated RTC clock"); - - Ok(true) - } - - /// Fetch time from the RTC clock and store it in the buffer `dest`. - pub fn update_time(&mut self, dest: &mut [u8]) -> Result { - if !(self.bus.read_multiple_registers( - Register::Hundredths.address(), - dest, - TIME_ARRAY_LENGTH, - )?) { - warn!("update_time: attempt read - fail 1"); - return Ok(false); // Something went wrong - } - - // If hundredths are at 99 or seconds are at 59, read again to make sure we didn't accidentally skip a second/minute - if bcd_to_dec(dest[0]) == 99 || bcd_to_dec(dest[1]) == 59 { - let mut temp_time = [0_u8; TIME_ARRAY_LENGTH]; - - debug!("update_time: if hundredths are at 99 or seconds are at 59, read again to make sure we didn't accidentally skip a second/minute / Hundreths: {} / Seconds: {}", bcd_to_dec(dest[0]),bcd_to_dec(dest[1])); - - if !(self.bus.read_multiple_registers( - Register::Hundredths.address(), - &mut temp_time, - TIME_ARRAY_LENGTH, - )?) { - warn!("update_time: attempt read - fail 2"); - return Ok(false); // Something went wrong - }; - - // If the reading for hundredths has rolled over, then our new data is correct, otherwise, we can leave the old data. - if bcd_to_dec(dest[0]) > bcd_to_dec(temp_time[0]) { - debug!("update_time: the reading for hundredths has rolled over, then our new data is correct. / Hundreths: {} / temp_time[0]: {}", - bcd_to_dec(dest[0]), - bcd_to_dec(temp_time[0])); - for (i, el) in temp_time.iter().enumerate() { - dest[i] = *el - } - } - } +/// Underlying driver. +pub(crate) mod driver; - // byte order: https://github.com/sparkfun/SparkFun_RV-8803_Arduino_Library/blob/main/src/SparkFun_RV8803.h#L129-L138 - let mut buf = [0_u8; 8]; - for (i, el) in dest.iter().enumerate() { - // Note: Weekday does not undergo BCD to Decimal conversion. - if i != 4 { - // println!("Raw: {} / BCD to Dec: {}", *el, bcd_to_dec(*el)); - buf[i] = bcd_to_dec(*el) - } else { - buf[i] = *el - } - } +pub(crate) mod error; - dest.copy_from_slice(&buf[..dest.len()]); - - Ok(true) - } - - /// Write a single bit to the specified register - pub fn write_bit(&mut self, reg_addr: u8, bit_addr: u8, bit_to_write: bool) -> Result { - let mut value = 0; - if let Ok(reg_value) = self.bus.read_register_by_addr(reg_addr) { - value = reg_value - } - - value &= !(1 << bit_addr); - value |= u8::from(bit_to_write) << bit_addr; - - self.bus.write_register_by_addr(reg_addr, value)?; - - Ok(true) - } - - /// Read seconds from the RTC clock - pub fn read_seconds(&mut self) -> Result { - let secs = self.bus.read_register(Register::Seconds)?; - - Ok(bcd_to_dec(secs)) - } - - /// Read the year from the RTC clock - pub fn read_year(&mut self) -> Result { - let year = self.bus.read_register(Register::Year)?; - - Ok(bcd_to_dec(year)) - } - - /// Set the year and update the RTC clock - pub fn set_year(&mut self, year: u16) -> Result { - let years_since_2000 = (year - 2000) as u8; - let year = dec_to_bcd(years_since_2000); - - self.bus.write_register(Register::Year, year)?; - - self.read_year() - } -} +/// Models +pub(crate) mod models; -fn bcd_to_dec(value: u8) -> u8 { - ((value / 0x10) * 10) + (value % 0x10) -} +/// Driver for the `rv8803` rtc chip. +pub mod rtc; -fn dec_to_bcd(value: u8) -> u8 { - ((value / 10) * 0x10) + (value % 10) +/// Re-exports +pub mod prelude { + pub use crate::bus::Bus; + pub use crate::error::{DriverTransferError, Error}; } diff --git a/src/models.rs b/src/models.rs index 289e0cb..cdac1d7 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,7 +1,9 @@ -/// All the models +/// Length of the time array constant +pub const TIME_ARRAY_LENGTH: usize = 8; /// Mapping of all the registers used to operate the RTC module #[derive(Clone, Copy)] +#[allow(clippy::doc_markdown)] pub enum Register { /// Hundreths Hundredths = 0x10, @@ -27,17 +29,7 @@ pub enum Register { impl Register { /// Read address value, returns as [`u8`] - pub fn address(&self) -> u8 { - *self as u8 + pub fn address(self) -> u8 { + self as u8 } } - -/// Length of the time array constant -pub const TIME_ARRAY_LENGTH: usize = 8; - -/// RV8803 driver. -#[derive(Debug)] -pub struct Rv8803 { - /// Holds the bus. - pub bus: B, -} diff --git a/src/rtc.rs b/src/rtc.rs new file mode 100644 index 0000000..b041a16 --- /dev/null +++ b/src/rtc.rs @@ -0,0 +1,143 @@ +#[allow(unused_imports)] +use crate::{driver::Driver, error::DriverTransferError}; +use chrono::{DateTime, Utc}; +use core::marker::PhantomData; +#[cfg(feature = "defmt")] +#[allow(unused_imports)] +use defmt::error; +#[allow(unused_imports)] +use heapless::String; +use shared_bus::BusManager; + +/// The [`RTClock`] type is the interface for communicating with the `rv8803` rtc clock chip via a shared bus over I2C. +#[allow(dead_code)] +pub struct RTClock<'a, I2C, I2cErr, M> { + datetime: Option>, + phantom: PhantomData<&'a I2C>, + bus_err: PhantomData<&'a I2cErr>, + bus: &'a BusManager, + device_address: u8, +} + +#[allow(dead_code)] +impl<'a, I2C, I2cErr, SharedBusMutex> RTClock<'a, I2C, I2cErr, SharedBusMutex> +where + I2C: embedded_hal_0_2::blocking::i2c::Write + + embedded_hal_0_2::blocking::i2c::WriteRead, + SharedBusMutex: shared_bus::BusMutex, + ::Bus: embedded_hal_0_2::blocking::i2c::Write + + embedded_hal_0_2::blocking::i2c::WriteRead, + I2cErr: defmt::Format, + DriverTransferError: From, +{ + /// Creates a new [`RTClock`]. + pub fn new(bus: &'a BusManager, address: &u8) -> Self { + Self { + datetime: None, + bus, + phantom: PhantomData, + bus_err: PhantomData, + device_address: *address, + } + } + + /// Set time on the Driver module + /// + /// # Errors + /// + /// Read/write errors during communication with the `rv8803` chip will return an error. + #[allow(clippy::too_many_arguments)] + pub fn set_time( + &mut self, + sec: u8, + min: u8, + hour: u8, + weekday: u8, + date: u8, + month: u8, + year: u16, + ) -> Result> { + let proxy = self.bus.acquire_i2c(); + let mut driver = Driver::new(proxy, self.device_address); + + match driver.set_time(sec, min, hour, weekday, date, month, year) { + Ok(val) => Ok(val), + Err(err) => Err(err), + } + } + + /// Fetch time from the RTC clock and store it in the buffer `dest`. + /// + /// # Errors + /// + /// Read/write errors during communication with the `rv8803` chip will return an error. + pub fn update_time(&mut self, dest: &mut [u8]) -> Result> { + let proxy = self.bus.acquire_i2c(); + let mut driver: Driver>> = + Driver::new(proxy, self.device_address); + + Ok(driver.update_time(dest)?) + } +} + +/// The [`RTClockDirect`] type is the interface for communicating with the `rv8803` rtc clock chip directly over I2C. +#[allow(dead_code)] +pub struct RTClockDirect<'a, I2C, I2cErr> { + datetime: Option>, + periph: I2C, + bus_err: PhantomData<&'a I2cErr>, + device_address: u8, +} + +#[allow(dead_code)] +impl<'a, I2C, I2cErr> RTClockDirect<'a, I2C, I2cErr> +where + I2C: embedded_hal_0_2::blocking::i2c::Write + + embedded_hal_0_2::blocking::i2c::WriteRead, + DriverTransferError: From, +{ + /// Creates a new [`RTClockDirect`]. + pub fn new(periph: I2C, address: &u8) -> Self { + Self { + datetime: None, + periph, + bus_err: PhantomData, + device_address: *address, + } + } + + /// Set time on the Driver module + /// + /// # Errors + /// + /// Read/write errors during communication with the `rv8803` chip will return an error. + #[allow(clippy::too_many_arguments)] + pub fn set_time( + self, + sec: u8, + min: u8, + hour: u8, + weekday: u8, + date: u8, + month: u8, + year: u16, + ) -> Result> { + let mut driver = Driver::new(self.periph, self.device_address); + + match driver.set_time(sec, min, hour, weekday, date, month, year) { + Ok(val) => Ok(val), + Err(err) => Err(err), + } + } + + /// Fetch time from the RTC clock and store it in the buffer `dest`. + /// + /// # Errors + /// + /// Read/write errors during communication with the `rv8803` chip will return an error. + pub fn update_time(self, dest: &mut [u8]) -> Result> { + let mut driver = Driver::new(self.periph, self.device_address); + + Ok(driver.update_time(dest)?) + } +} From a1195bd3e9b4ecd6733af89982fd2648880de508 Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Thu, 26 Sep 2024 20:58:12 +0530 Subject: [PATCH 2/9] New public API (v4.0.0) - Needs complete testing --- CHANGELOG.md | 6 +- Cargo.toml | 18 +-- src/bus.rs | 120 -------------------- src/driver.rs | 186 ------------------------------ src/error.rs | 34 ++---- src/formatter.rs | 53 +++++++++ src/lib.rs | 30 +++-- src/models.rs | 264 +++++++++++++++++++++++++++++++++++++------ src/rtc.rs | 238 ++++++++++++++++++++------------------ src/rtc/address.rs | 53 +++++++++ src/rtc/now.rs | 109 ++++++++++++++++++ src/rtc/reg.rs | 110 ++++++++++++++++++ src/rtc/registers.rs | 149 ++++++++++++++++++++++++ src/rtc/update.rs | 71 ++++++++++++ 14 files changed, 933 insertions(+), 508 deletions(-) delete mode 100644 src/bus.rs delete mode 100644 src/driver.rs create mode 100644 src/formatter.rs create mode 100644 src/rtc/address.rs create mode 100644 src/rtc/now.rs create mode 100644 src/rtc/reg.rs create mode 100644 src/rtc/registers.rs create mode 100644 src/rtc/update.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b69dd28..5aeda8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [4.0.0] - XX Sept 2024 ### Changed -- __Breaking Change__: Rewamped blocking I2C with `embedded-hal` v0.2; Testing on hardware pending. +- __Breaking Change__: New public API based on `embedded-hal-async`. ## [3.0.0] - 15 Sept 2024 - Yanked @@ -29,14 +29,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Tested (compilation/clippy) with Embassy and `defmt`. - TODO: Testing on actual hardware. -## [0.4.4] - 1 March 2024 +## [0.4.4] - 1 March 2024 - Yanked ### Changed - __Breaking Change__: Fully revised module organisation - __Breaking Change__: Revised error API - Renamed `Rv8803<_>::from_i2c0` to `Rv8803<_>::from_i2c`. -## [0.1.0] - 1 January 2022 +## [0.1.0] - 1 January 2022 - Yanked - Initial release diff --git a/Cargo.toml b/Cargo.toml index 90e91a4..e6784d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,25 +9,15 @@ description = "RTC clock driver for the rv8803 chip via I2C" readme = "README.md" keywords = ["i2c", "driver", "embedded-hal-driver", "rv8803"] categories = ["embedded", "hardware-support", "no-std"] +publish = false [features] -default = ["defmt"] -linux_blocking = ["linux-embedded-hal", "log",] +default = [] [dependencies] -chrono = { version = "0.4.38", default-features = false } -defmt = { version = "0.3", optional = true } -heapless = { version = "0.8", default-features = false } +defmt = { version = "^0.3" } embedded-hal = { package = "embedded-hal", version = "^1.0" } -embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"] } -embedded-hal-async = { package = "embedded-hal-async", version = "^1.0", optional = true } -linux-embedded-hal = { version = "^0.3", optional = true } -log = { version = "0.4", optional = true } -serde = { version = "1", features = ["derive"], default-features = false, optional = true } -shared-bus = { version = "0.3.1" } - -[dev-dependencies] -i2cdev = "0.5.1" +embedded-hal-async = "1.0.0" [package.metadata.docs.rs] all-features = true diff --git a/src/bus.rs b/src/bus.rs deleted file mode 100644 index 097427c..0000000 --- a/src/bus.rs +++ /dev/null @@ -1,120 +0,0 @@ -use super::models::Register; -#[allow(unused_imports)] -use crate::error::DriverTransferError; -use core::marker::PhantomData; - -/// Trait for [`Bus`] -#[allow(clippy::module_name_repetitions)] -pub trait BusTrait { - /// Bus error. - type Error; - - /// Read from the `rv8803` chip. - /// - /// # Errors - /// - /// Will return [`BusTrait::Error`] if the read attempt fails. - fn read_register(&mut self, register: Register) -> Result; - - /// Write to the `rv8803` chip. - /// - /// # Errors - /// - /// Will return [`BusTrait::Error`] if the write attempt fails. - fn write_register(&mut self, register: Register, value: u8) -> Result<(), Self::Error>; - - /// Read multiple registers - /// - /// # Errors - /// - /// Will return [`BusTrait::Error`] if the read attempt fails. - fn read_multiple_registers( - &mut self, - addr: u8, - dest: &mut [u8], - len: usize, - ) -> Result; - - /// Write to register by register address - /// - /// # Errors - /// - /// Will return [`BusTrait::Error`] if the write attempt fails. - fn write_register_by_addr(&mut self, reg_addr: u8, value: u8) -> Result<(), Self::Error>; - - /// Read register by register address - /// - /// # Errors - /// - /// Will return [`BusTrait::Error`] if the read attempt fails. - fn read_register_by_addr(&mut self, reg_addr: u8) -> Result; -} - -/// Struct type to hold an I2C peripheral -#[derive(Debug)] -#[allow(clippy::struct_field_names)] -pub struct Bus<'a, I2C> { - address: u8, - bus: I2C, - _i2c: PhantomData<&'a I2C>, -} - -impl<'a, I2C, E> Bus<'a, I2C> -where - I2C: embedded_hal_0_2::blocking::i2c::WriteRead - + embedded_hal_0_2::blocking::i2c::Write, - Bus<'a, I2C>: BusTrait, -{ - /// Creates a new [`Bus`] from an I2C peripheral. - pub fn new(bus: I2C, address: &u8) -> Self { - Self { - bus, - address: *address, - _i2c: PhantomData, - } - } -} - -impl BusTrait for Bus<'_, I2C> -where - I2C: embedded_hal_0_2::blocking::i2c::WriteRead - + embedded_hal_0_2::blocking::i2c::Write, -{ - type Error = E; - - fn read_register(&mut self, register: Register) -> Result { - let mut data = [0]; - self.bus - .write_read(self.address, &[register.address()], &mut data)?; - Ok(u8::from_le_bytes(data)) - } - - fn write_register(&mut self, register: Register, byte: u8) -> Result<(), E> { - self.bus.write(self.address, &[register.address(), byte])?; - - Ok(()) - } - - fn read_multiple_registers( - &mut self, - addr: u8, - dest: &mut [u8], - _len: usize, - ) -> Result { - self.bus.write_read(self.address, &[addr], dest)?; - - Ok(true) - } - - fn write_register_by_addr(&mut self, reg_addr: u8, byte: u8) -> Result<(), Self::Error> { - self.bus.write(self.address, &[reg_addr, byte])?; - - Ok(()) - } - - fn read_register_by_addr(&mut self, reg_addr: u8) -> Result { - let mut data = [0]; - self.bus.write_read(self.address, &[reg_addr], &mut data)?; - Ok(u8::from_le_bytes(data)) - } -} diff --git a/src/driver.rs b/src/driver.rs deleted file mode 100644 index 6ad325d..0000000 --- a/src/driver.rs +++ /dev/null @@ -1,186 +0,0 @@ -#[allow(unused_imports)] -use crate::bus::{self, Bus, BusTrait}; -use crate::error::DriverTransferError; -use crate::models::{Register, TIME_ARRAY_LENGTH}; - -/// Driver driver. -#[derive(Debug)] -pub struct Driver { - /// Holds the bus. - pub bus: B, -} - -#[allow(dead_code)] -impl<'a, I2C, E> Driver> -where - I2C: embedded_hal_0_2::blocking::i2c::WriteRead - + embedded_hal_0_2::blocking::i2c::Write, - Bus<'a, I2C>: bus::BusTrait, - DriverTransferError: From, -{ - /// Creates a new `Driver` driver from a I2C peripheral, and an I2C - /// device address. - pub fn new(i2c: I2C, address: u8) -> Self { - let bus = crate::bus::Bus::new(i2c, &address); - - Self { bus } - } - - /// Set time on the Driver module - /// - /// # Errors - /// - /// If the year specified is always > 2000, hence `u16` casting to `u8` - /// is OK, as long as the year is < 2100. When the year > 2255 this will - /// return [`core::num::TryFromIntError`]. - /// - /// Read/write errors during communication with the `Driver` chip will also return an error. - #[allow(clippy::too_many_arguments)] - pub fn set_time( - &mut self, - sec: u8, - min: u8, - hour: u8, - weekday: u8, - date: u8, - month: u8, - year: u16, - ) -> Result> { - match u8::try_from(year - 2000) { - Ok(year) => { - self.bus - .write_register(Register::Seconds, dec_to_bcd(sec))?; - self.bus - .write_register(Register::Minutes, dec_to_bcd(min))?; - self.bus.write_register(Register::Hours, dec_to_bcd(hour))?; - self.bus.write_register(Register::Date, dec_to_bcd(date))?; - self.bus - .write_register(Register::Month, dec_to_bcd(month))?; - self.bus.write_register(Register::Year, dec_to_bcd(year))?; - self.bus.write_register(Register::Weekday, weekday)?; - - // Set RESET bit to 0 after setting time to make sure seconds don't get stuck. - self.write_bit( - Register::Control.address(), - Register::ControlReset.address(), - false, - )?; - - #[cfg(feature = "defmt")] - defmt::debug!("Driver::set_time: updated RTC clock"); - - Ok(true) - } - Err(_err) => Err(DriverTransferError::Transfer), - } - } - - /// Fetch time from the RTC clock and store it in the buffer `dest`. - pub fn update_time(&mut self, dest: &mut [u8]) -> Result { - if !(self.bus.read_multiple_registers( - Register::Hundredths.address(), - dest, - TIME_ARRAY_LENGTH, - )?) { - #[cfg(feature = "defmt")] - defmt::warn!("update_time: attempt read - fail 1"); - return Ok(false); // Something went wrong - } - - // If hundredths are at 99 or seconds are at 59, read again to make sure we didn't accidentally skip a second/minute - if bcd_to_dec(dest[0]) == 99 || bcd_to_dec(dest[1]) == 59 { - let mut temp_time = [0_u8; TIME_ARRAY_LENGTH]; - - #[cfg(feature = "defmt")] - defmt::debug!("update_time: if hundredths are at 99 or seconds are at 59, read again to make sure we didn't accidentally skip a second/minute / Hundreths: {} / Seconds: {}", bcd_to_dec(dest[0]),bcd_to_dec(dest[1])); - - if !(self.bus.read_multiple_registers( - Register::Hundredths.address(), - &mut temp_time, - TIME_ARRAY_LENGTH, - )?) { - #[cfg(feature = "defmt")] - defmt::warn!("update_time: attempt read - fail 2"); - return Ok(false); // Something went wrong - }; - - // If the reading for hundredths has rolled over, then our new data is correct, otherwise, we can leave the old data. - if bcd_to_dec(dest[0]) > bcd_to_dec(temp_time[0]) { - #[cfg(feature = "defmt")] - defmt::debug!("update_time: the reading for hundredths has rolled over, then our new data is correct. / Hundreths: {} / temp_time[0]: {}", - bcd_to_dec(dest[0]), - bcd_to_dec(temp_time[0])); - - for (i, el) in temp_time.iter().enumerate() { - dest[i] = *el; - } - } - } - - // byte order: https://github.com/sparkfun/SparkFun_RV-8803_Arduino_Library/blob/main/src/SparkFun_RV8803.h#L129-L138 - let mut buf = [0_u8; 8]; - for (i, el) in dest.iter().enumerate() { - // Note: Weekday does not undergo BCD to Decimal conversion. - if i == 4 { - buf[i] = *el; - } else { - #[cfg(feature = "defmt")] - defmt::info!("Raw: {} / BCD to Dec: {}", *el, bcd_to_dec(*el)); - buf[i] = bcd_to_dec(*el); - } - } - - dest.copy_from_slice(&buf[..dest.len()]); - - Ok(true) - } - - /// Write a single bit to the specified register - pub fn write_bit(&mut self, reg_addr: u8, bit_addr: u8, bit_to_write: bool) -> Result { - let mut value = 0; - if let Ok(reg_value) = self.bus.read_register_by_addr(reg_addr) { - value = reg_value; - } - - value &= !(1 << bit_addr); - value |= u8::from(bit_to_write) << bit_addr; - - self.bus.write_register_by_addr(reg_addr, value)?; - - Ok(true) - } - - /// Read seconds from the RTC clock - pub fn read_seconds(&mut self) -> Result { - let secs = self.bus.read_register(Register::Seconds)?; - - Ok(bcd_to_dec(secs)) - } - - /// Read the year from the RTC clock - pub fn read_year(&mut self) -> Result { - let year = self.bus.read_register(Register::Year)?; - - Ok(bcd_to_dec(year)) - } - - /// Set the year and update the RTC clock - pub fn set_year(&mut self, year: u16) -> Result - where - E: core::convert::From, - { - let year = dec_to_bcd(u8::try_from(year - 2000)?); - - self.bus.write_register(Register::Year, year)?; - - self.read_year() - } -} - -fn bcd_to_dec(value: u8) -> u8 { - ((value / 0x10) * 10) + (value % 0x10) -} - -fn dec_to_bcd(value: u8) -> u8 { - ((value / 10) * 0x10) + (value % 10) -} diff --git a/src/error.rs b/src/error.rs index d02ee73..906acdb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,36 +24,16 @@ impl core::error::Error for Error { fn provide<'a>(&'a self, _request: &mut core::error::Request<'a>) {} } -/// Driver transfer error. - -// NOTE: The error type in `embassy_stm32::i2c::Error` does not impl any traits (refer to https://docs.embassy.dev/embassy-stm32/git/stm32wl55cc-cm4/i2c/enum.Error.html), and since this is a driver, there is no context as to what specific driver is being used with this HAL this lib is depending on. -// Hence a generic type is used on `DriverTransferError` to aid as a workaround. +/// Driver error. #[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[allow(clippy::module_name_repetitions)] -pub enum DriverTransferError { - /// Bus error occurred. e.g. A START or a STOP condition is detected and is not - /// located after a multiple of 9 SCL clock pulses. - Bus, - /// The arbitration was lost, e.g. electrical problems with the clock signal. - ArbitrationLoss, - /// A bus operation was not acknowledged, e.g. due to the addressed device not - /// being available on the bus or the device not being ready to process requests - /// at the moment. - NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource), - /// The peripheral receive buffer was overrun. - Overrun, - /// A different error occurred. The original error may contain more information. - Other, - #[allow(missing_docs)] - _Phant(core::marker::PhantomData), - /// Error during I2C Transfer - Transfer, +pub enum DriverError { + /// I2C bus error + I2c(E), } -// This allows erasing the originating error, i.e `DriverTransferError: From`. -impl From for DriverTransferError { - fn from(_value: E) -> Self { - self::DriverTransferError::Transfer +impl From for DriverError { + fn from(other: E) -> Self { + self::DriverError::I2c(other) } } diff --git a/src/formatter.rs b/src/formatter.rs new file mode 100644 index 0000000..2bafbdd --- /dev/null +++ b/src/formatter.rs @@ -0,0 +1,53 @@ +/// Format bytes using a formatter. +use core::fmt::{self}; +use core::str; + +pub struct ByteMutWriter<'a> { + buf: &'a mut [u8], + cursor: usize, +} + +impl<'a> ByteMutWriter<'a> { + pub fn new(buf: &'a mut [u8]) -> Self { + ByteMutWriter { buf, cursor: 0 } + } + + pub fn as_str(&self) -> &str { + str::from_utf8(&self.buf[0..self.cursor]).unwrap() + } + + #[inline] + pub fn capacity(&self) -> usize { + self.buf.len() + } + + pub fn clear(&mut self) { + self.cursor = 0; + } + + pub fn len(&self) -> usize { + self.cursor + } + + pub fn empty(&self) -> bool { + self.cursor == 0 + } + + pub fn full(&self) -> bool { + self.capacity() == self.cursor + } +} + +impl fmt::Write for ByteMutWriter<'_> { + fn write_str(&mut self, s: &str) -> fmt::Result { + let cap = self.capacity(); + for (i, &b) in self.buf[self.cursor..cap] + .iter_mut() + .zip(s.as_bytes().iter()) + { + *i = b; + } + self.cursor = usize::min(cap, self.cursor + s.as_bytes().len()); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8cdf08d..b26fd25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,31 +1,29 @@ //! RTC clock driver for the `rv8803` chip over I2C. -//! -//! Latest implementation supports `blocking` transfer, either directly via an owned I2C peripheral or using a [`shared_bus`]. #![no_std] #![forbid(unsafe_code)] #![warn(missing_docs)] +#![warn(dead_code)] +// #![deny(unused_imports)] #![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))] #![feature(error_generic_member_access)] #![feature(trivial_bounds)] -pub use embedded_hal_0_2; - -/// An I2C bus, allowing communications over an I2C peripheral. -pub mod bus; - -/// Underlying driver. -pub(crate) mod driver; +pub use crate::models::ClockData; +pub use crate::rtc::Driver; +pub use crate::rtc::DriverAsync; pub(crate) mod error; - -/// Models +#[allow(dead_code)] +pub(crate) mod formatter; pub(crate) mod models; - -/// Driver for the `rv8803` rtc chip. -pub mod rtc; +pub(crate) mod rtc; /// Re-exports pub mod prelude { - pub use crate::bus::Bus; - pub use crate::error::{DriverTransferError, Error}; + pub use crate::error::DriverError; + pub use crate::models::{Month, Weekday}; + pub use crate::rtc::address::SlaveAddress; + pub use crate::rtc::now; + pub use crate::rtc::update::{self, ClockUpdater}; + pub use crate::rtc::AddressingMode; } diff --git a/src/models.rs b/src/models.rs index cdac1d7..942d634 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,35 +1,233 @@ -/// Length of the time array constant -pub const TIME_ARRAY_LENGTH: usize = 8; - -/// Mapping of all the registers used to operate the RTC module -#[derive(Clone, Copy)] -#[allow(clippy::doc_markdown)] -pub enum Register { - /// Hundreths - Hundredths = 0x10, - /// Seconds - Seconds = 0x11, - /// Minutes - Minutes = 0x12, - /// Hours - Hours = 0x13, - /// Weekday - Weekday = 0x14, - /// Date - Date = 0x15, - /// Month - Month = 0x16, - /// Year - Year = 0x17, - /// ControlReset - ControlReset = 0, - /// Control - Control = 0x1F, -} - -impl Register { - /// Read address value, returns as [`u8`] - pub fn address(self) -> u8 { - self as u8 +/// Holds the clock data. +#[derive(Debug, Copy, Clone, Default)] +pub struct ClockData { + /// Hundreths. + pub hundreths: u8, + /// Seconds. + pub seconds: u8, + /// Minutes. + pub minutes: u8, + /// Hours. + pub hours: u8, + /// Weekday. + pub weekday: u8, + /// Date. + pub date: u8, + /// Month. + pub month: u8, + /// Year. + pub year: u8, +} + +impl ClockData { + /// Hundreths. + #[must_use] + pub fn hundreths(&self) -> u8 { + self.hundreths + } + + /// Seconds. + #[must_use] + pub fn seconds(&self) -> u8 { + self.seconds + } + + /// Minutes. + #[must_use] + pub fn minutes(&self) -> u8 { + self.minutes + } + + /// Hours. + #[must_use] + pub fn hours(&self) -> u8 { + self.hours + } + + /// Weekday. + #[must_use] + pub fn weekday(&self) -> u8 { + self.weekday + } + + /// Day. The rtc module refers to this as "date". + #[must_use] + pub fn day(&self) -> u8 { + self.date + } + + /// Month. + #[must_use] + pub fn month(&self) -> u8 { + self.month + } + + /// Year. + #[must_use] + pub fn year(&self) -> u8 { + self.year + } + + /// Set the date and time. Hundreths is set to 0. + pub fn set(&mut self, value: (u8, u8, u8, Weekday, u8, Month, u8)) { + let (hours, minutes, seconds, weekday, day, month, year) = value; + + self.hundreths = 0; + self.hours = hours; + self.minutes = minutes; + self.seconds = seconds; + self.weekday = weekday as u8; + self.date = day; + self.month = month as u8; + self.year = year; + } +} + +/// Enumerated type values for the weekday register. +#[allow(dead_code)] +pub enum Weekday { + /// Sunday + Sunday = 1, + /// Monday + Monday = 2, + /// Tuesday + Tuesday = 4, + /// Wednesday + Wednesday = 8, + /// Thursday + Thursday = 16, + /// Friday + Friday = 32, + /// Saturday + Saturday = 64, +} + +/// FIXME this causes [defmt] to get stuck. Why? +// impl From for Weekday { +// fn from(value: u8) -> Self { +// Self::into(value.into()) +// } +// } +#[allow(dead_code)] +#[allow(clippy::match_same_arms)] +impl Weekday { + /// Get variant from a provided value. + #[must_use] + pub fn from(val: u8) -> Self { + match val { + 1 => Self::Sunday, + 2 => Self::Monday, + 4 => Self::Tuesday, + 8 => Self::Wednesday, + 16 => Self::Thursday, + 32 => Self::Friday, + 64 => Self::Saturday, + _ => Self::Sunday, + } + } +} + +impl defmt::Format for Weekday { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!( + fmt, + "{}", + match self { + Self::Sunday => "Sunday", + Self::Monday => "Monday", + Self::Tuesday => "Tuesday", + Self::Wednesday => "Wednesday", + Self::Thursday => "Thursday", + Self::Friday => "Friday", + Self::Saturday => "Saturday", + } + ); + } +} + +/// Enumerated type values for the month register. +#[allow(dead_code)] +pub enum Month { + /// January + January = 1, + /// February + February = 2, + /// March + March = 3, + /// April + April = 4, + /// May + May = 5, + /// June + June = 6, + /// July + July = 7, + /// August + August = 8, + /// September + September = 9, + /// October + October = 10, + /// November + November = 11, + /// December + December = 12, +} + +#[allow(dead_code)] +#[allow(clippy::match_same_arms)] +impl Month { + /// Get variant from a provided value. + #[must_use] + pub fn from(val: u8) -> Self { + match val { + 1 => Self::January, + 2 => Self::February, + 3 => Self::March, + 4 => Self::April, + 5 => Self::May, + 6 => Self::June, + 7 => Self::July, + 8 => Self::August, + 9 => Self::September, + 10 => Self::October, + 11 => Self::November, + 12 => Self::December, + _ => Self::January, + } + } +} + +impl defmt::Format for Month { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!( + fmt, + "{}", + match self { + Self::January => "January", + Self::February => "February", + Self::March => "March", + Self::April => "April", + Self::May => "May", + Self::June => "June", + Self::July => "July", + Self::August => "August", + Self::September => "September", + Self::October => "October", + Self::November => "November", + Self::December => "December", + } + ); + } +} + +#[allow(dead_code)] +pub mod misc { + pub fn bcd_to_dec(value: u8) -> u8 { + ((value / 0x10) * 10) + (value % 0x10) + } + + pub fn dec_to_bcd(value: u8) -> u8 { + ((value / 10) * 0x10) + (value % 10) } } diff --git a/src/rtc.rs b/src/rtc.rs index b041a16..908b7f2 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -1,143 +1,163 @@ -#[allow(unused_imports)] -use crate::{driver::Driver, error::DriverTransferError}; -use chrono::{DateTime, Utc}; +use crate::error::DriverError; +use crate::models::ClockData; +use crate::rtc::{address::SlaveAddress, registers as ClockRegisters}; use core::marker::PhantomData; -#[cfg(feature = "defmt")] -#[allow(unused_imports)] -use defmt::error; -#[allow(unused_imports)] -use heapless::String; -use shared_bus::BusManager; - -/// The [`RTClock`] type is the interface for communicating with the `rv8803` rtc clock chip via a shared bus over I2C. -#[allow(dead_code)] -pub struct RTClock<'a, I2C, I2cErr, M> { - datetime: Option>, - phantom: PhantomData<&'a I2C>, - bus_err: PhantomData<&'a I2cErr>, - bus: &'a BusManager, - device_address: u8, +use embedded_hal::i2c::{I2c, SevenBitAddress}; + +pub mod address; +pub mod reg; +// pub mod reg_year; +pub mod registers; + +/// Used to fetch latest readings. +pub mod now; +/// Used to update the rtc clock. +pub mod update; + +/// Trait to specify addressing mode. +pub trait AddressingMode { + /// The addressing mode. + type Mode; } -#[allow(dead_code)] -impl<'a, I2C, I2cErr, SharedBusMutex> RTClock<'a, I2C, I2cErr, SharedBusMutex> +impl AddressingMode for SevenBitAddress { + type Mode = SevenBitAddress; +} + +/// Driver for the `rv8803` rtc chip. +/// +/// # Registers +/// +/// Refer Page 15: +pub struct Driver { + addr: u8, + i2c: I2C, + _addr_mode: core::marker::PhantomData, + _mode_type: core::marker::PhantomData, +} + +impl Driver where - I2C: embedded_hal_0_2::blocking::i2c::Write - + embedded_hal_0_2::blocking::i2c::WriteRead, - SharedBusMutex: shared_bus::BusMutex, - ::Bus: embedded_hal_0_2::blocking::i2c::Write - + embedded_hal_0_2::blocking::i2c::WriteRead, - I2cErr: defmt::Format, - DriverTransferError: From, + I2C: I2c, + I2C::Error: Into>, + A: AddressingMode + embedded_hal::i2c::AddressMode, { - /// Creates a new [`RTClock`]. - pub fn new(bus: &'a BusManager, address: &u8) -> Self { - Self { - datetime: None, - bus, - phantom: PhantomData, - bus_err: PhantomData, - device_address: *address, + /// Creates a new driver from an I2C peripheral. + pub fn new(i2c: I2C) -> Self { + Driver { + addr: SlaveAddress::Default.into(), + i2c, + _addr_mode: PhantomData, + _mode_type: PhantomData, } } - /// Set time on the Driver module + /// Change I2C address + pub fn set_address(&mut self, addr: SlaveAddress) -> u8 { + self.addr = addr.into(); + self.addr + } + + /// release resources + pub fn free(self) -> I2C { + self.i2c + } + + #[allow(dead_code)] + fn read_register(&mut self, mut reg: T) -> Result> + where + T: reg::Read, + I2C: I2c, + I2C::Error: Into>, + { + reg.read_from_device(&mut self.i2c, self.addr)?; + Ok(reg) + } + + #[allow(dead_code)] + fn write_register(&mut self, reg: R) -> Result<(), DriverError> { + reg.write_to_device::(&mut self.i2c, self.addr)?; + Ok(()) + } + + /// Fetch the latest reading from the rtc module. /// /// # Errors /// - /// Read/write errors during communication with the `rv8803` chip will return an error. - #[allow(clippy::too_many_arguments)] - pub fn set_time( - &mut self, - sec: u8, - min: u8, - hour: u8, - weekday: u8, - date: u8, - month: u8, - year: u16, - ) -> Result> { - let proxy = self.bus.acquire_i2c(); - let mut driver = Driver::new(proxy, self.device_address); - - match driver.set_time(sec, min, hour, weekday, date, month, year) { - Ok(val) => Ok(val), - Err(err) => Err(err), - } + /// Returns a [`DriverError`] + pub fn now(&mut self, mut clock_data: T) -> Result> + where + T: crate::rtc::now::Read, + I2C: I2c, + I2C::Error: Into>, + { + let mut data = crate::prelude::now::new(); + + // Associated instance on T, not to be confused with the value data above. + clock_data.now(&mut self.i2c, self.addr, &mut data)?; + + Ok(data) } - /// Fetch time from the RTC clock and store it in the buffer `dest`. + /// Update the rtc module. /// /// # Errors /// - /// Read/write errors during communication with the `rv8803` chip will return an error. - pub fn update_time(&mut self, dest: &mut [u8]) -> Result> { - let proxy = self.bus.acquire_i2c(); - let mut driver: Driver>> = - Driver::new(proxy, self.device_address); + /// Returns a [`DriverError`] + pub fn update( + &mut self, + mut clock_data: T, + data: &Option, + ) -> Result> + where + T: crate::rtc::update::Read, + I2C: I2c, + I2C::Error: Into>, + { + let mut cu = ClockRegisters::new(self.addr); - Ok(driver.update_time(dest)?) + if let Some(d) = data { + clock_data.set_datetime(&mut self.i2c, self.addr, &mut cu, d)?; + } + Ok(clock_data) } } -/// The [`RTClockDirect`] type is the interface for communicating with the `rv8803` rtc clock chip directly over I2C. +/// Async Driver for the `rv8803` rtc chip. +/// *WARNING*: This is in progress, and will be completed in a future release. #[allow(dead_code)] -pub struct RTClockDirect<'a, I2C, I2cErr> { - datetime: Option>, - periph: I2C, - bus_err: PhantomData<&'a I2cErr>, - device_address: u8, +pub struct DriverAsync { + addr: u8, + i2c: I2C, + _addr_mode: core::marker::PhantomData, + _mode_type: core::marker::PhantomData, } -#[allow(dead_code)] -impl<'a, I2C, I2cErr> RTClockDirect<'a, I2C, I2cErr> +impl DriverAsync where - I2C: embedded_hal_0_2::blocking::i2c::Write - + embedded_hal_0_2::blocking::i2c::WriteRead, - DriverTransferError: From, + I2C: embedded_hal_async::i2c::I2c, + I2C::Error: Into>, + A: AddressingMode + embedded_hal_async::i2c::AddressMode, { - /// Creates a new [`RTClockDirect`]. - pub fn new(periph: I2C, address: &u8) -> Self { - Self { - datetime: None, - periph, - bus_err: PhantomData, - device_address: *address, - } - } - - /// Set time on the Driver module - /// - /// # Errors - /// - /// Read/write errors during communication with the `rv8803` chip will return an error. - #[allow(clippy::too_many_arguments)] - pub fn set_time( - self, - sec: u8, - min: u8, - hour: u8, - weekday: u8, - date: u8, - month: u8, - year: u16, - ) -> Result> { - let mut driver = Driver::new(self.periph, self.device_address); - - match driver.set_time(sec, min, hour, weekday, date, month, year) { - Ok(val) => Ok(val), - Err(err) => Err(err), + /// Creates a new driver from an I2C peripheral. + #[allow(dead_code)] + pub fn new(i2c: I2C) -> Self { + DriverAsync { + addr: SlaveAddress::Default.into(), + i2c, + _addr_mode: PhantomData, + _mode_type: PhantomData, } } - /// Fetch time from the RTC clock and store it in the buffer `dest`. + /// Fetch the year value. /// /// # Errors /// - /// Read/write errors during communication with the `rv8803` chip will return an error. - pub fn update_time(self, dest: &mut [u8]) -> Result> { - let mut driver = Driver::new(self.periph, self.device_address); + /// Returns a [`DriverError`] + pub async fn get_year(&mut self, buf: u8) -> Result<(), DriverError> { + self.i2c.write_read(self.addr, &[0x06], &mut [buf]).await?; - Ok(driver.update_time(dest)?) + Ok(()) } } diff --git a/src/rtc/address.rs b/src/rtc/address.rs new file mode 100644 index 0000000..f5715b9 --- /dev/null +++ b/src/rtc/address.rs @@ -0,0 +1,53 @@ +//! Device Address + +const DEFAULT_ADDRESS: u8 = 0x32; + +/// I2C device address +#[derive(Debug, Clone, Copy)] +#[allow(clippy::module_name_repetitions)] +pub enum SlaveAddress { + /// Default slave address + Default, + /// Alternative slave address + Alternative(u8), +} + +impl SlaveAddress { + /// Creates a [`SlaveAddress`] at the provided [`SlaveAddress::Alternative`] address. + #[must_use] + pub fn at_address(addr: u8) -> Self { + Self::Alternative(addr) + } +} + +impl From for u8 { + fn from(slave_address: SlaveAddress) -> Self { + match slave_address { + SlaveAddress::Default => DEFAULT_ADDRESS, + SlaveAddress::Alternative(address) => address, + } + } +} + +impl PartialEq for SlaveAddress { + fn eq(&self, other: &Self) -> bool { + let (lhs, rhs): (u8, u8) = ((*self).into(), (*other).into()); + lhs == rhs + } +} + +#[cfg(test)] +mod tests { + use super::SlaveAddress; + + #[test] + fn satisfies_partialeq() { + let default_address = SlaveAddress::Default; + let alt_address = SlaveAddress::Alternative(0x32); + + assert_eq!(default_address, alt_address); + + let alt_address = SlaveAddress::at_address(0x032); + assert_eq!(default_address, alt_address); + } +} diff --git a/src/rtc/now.rs b/src/rtc/now.rs new file mode 100644 index 0000000..0b1c316 --- /dev/null +++ b/src/rtc/now.rs @@ -0,0 +1,109 @@ +use crate::formatter::ByteMutWriter; +use crate::models::ClockData; +use crate::{error::DriverError, models::Month, models::Weekday}; +use core::fmt::{Debug, Write}; +use embedded_hal::i2c::{I2c, SevenBitAddress}; + +/// Creates a [`ClockData`]. +#[must_use] +pub fn new() -> ClockData { + ClockData { + ..Default::default() + } +} + +/// Trait to read from I2C periph +pub trait Read: Debug + Copy + Clone { + /// Fetch the latest date and time. + /// + /// # Errors + /// + /// Returns a [`DriverError`] + fn now( + &mut self, + i2c: &mut I2C, + addr: u8, + data: &mut ClockData, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>; +} + +impl Read for ClockData { + fn now( + &mut self, + i2c: &mut I2C, + addr: u8, + data: &mut ClockData, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>, + { + use crate::models::misc::bcd_to_dec; + + let mut cregs = super::registers::new(addr); + + let latest = ClockData { + hundreths: bcd_to_dec( + cregs.read_register(i2c, super::registers::Register::Hundredths)?, + ), + seconds: bcd_to_dec(cregs.read_register(i2c, super::registers::Register::Seconds)?), + minutes: bcd_to_dec(cregs.read_register(i2c, super::registers::Register::Minutes)?), + hours: bcd_to_dec(cregs.read_register(i2c, super::registers::Register::Hours)?), + date: bcd_to_dec(cregs.read_register(i2c, super::registers::Register::Date)?), + month: bcd_to_dec(cregs.read_register(i2c, super::registers::Register::Month)?), + year: bcd_to_dec(cregs.read_register(i2c, super::registers::Register::Year)?), + + // Read directly as a byte. + weekday: cregs.read_register(i2c, super::registers::Register::Weekday)?, + }; + + *data = latest; + + Ok(()) + } +} + +fn left_pad<'a>(buf: &'a mut ByteMutWriter<'_>, value: u8) -> &'a str { + buf.clear(); + write!(buf, "{}{}", if value < 10 { "0" } else { "" }, value).unwrap(); + + buf.as_str() +} + +impl defmt::Format for ClockData { + fn format(&self, fmt: defmt::Formatter) { + let mut buf = [0u8; 2]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let hours = left_pad(&mut buf, self.hours); + + let mut buf = [0u8; 2]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let minutes = left_pad(&mut buf, self.minutes); + + let mut buf = [0u8; 2]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let seconds = left_pad(&mut buf, self.seconds); + + let mut buf = [0u8; 2]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let day = left_pad(&mut buf, self.date); + + let month = Month::from(self.month); + let weekday = Weekday::from(self.weekday); + + defmt::write!( + fmt, + "{}:{}:{}, {}, {} {} {}", + hours, + minutes, + seconds, + weekday, + day, + month, + self.year(), + ); + } +} diff --git a/src/rtc/reg.rs b/src/rtc/reg.rs new file mode 100644 index 0000000..8d12411 --- /dev/null +++ b/src/rtc/reg.rs @@ -0,0 +1,110 @@ +use crate::error::DriverError; +use core::fmt::Debug; +use embedded_hal::i2c::{I2c, SevenBitAddress}; + +use super::AddressingMode; + +#[derive(Debug, Copy, Clone)] +pub struct Register { + /// points to a specific register in the sensor + ptr: u8, + /// register contents, either 1 or 2 bytes + buf: [u8; 2], + /// actual register size in bytes, either 1 or 2 + len: u8, +} + +impl Register { + #[allow(dead_code)] + #[allow(clippy::manual_assert)] + pub fn new(ptr: u8, len: u8) -> Self { + if len > 2 { + panic!("length > 2") + } + + let buf = [0u8, 0]; + Register { ptr, buf, len } + } + + pub fn get_buf(&self) -> &[u8] { + &self.buf[0..self.len as usize] + } + + pub fn set_buf(&mut self, val: [u8; 2]) { + self.buf = val; + } + + pub fn get_ptr(self) -> u8 { + self.ptr + } + + pub fn get_len(self) -> u8 { + self.len + } +} + +/// trait for a register that can be read from an i2c device +pub trait Read: Debug + Copy + Clone { + fn read_from_device( + &mut self, + i2c: &mut I2C, + addr: u8, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>; +} + +impl Read for Register { + fn read_from_device( + &mut self, + i2c: &mut I2C, + addr: u8, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>, + { + let mut buf = [0u8; 2]; + i2c.write_read( + addr, + &[self.get_ptr()], + &mut buf[0..self.get_len() as usize], + )?; + self.set_buf(buf); + Ok(()) + } +} + +/// trait for a register that can be written to an i2c device +pub trait Write: Read { + fn write_to_device( + &self, + i2c: &mut I2C, + addr: u8, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>, + A: AddressingMode + embedded_hal::i2c::AddressMode; +} + +impl Write for Register { + fn write_to_device( + &self, + i2c: &mut I2C, + addr: u8, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>, + A: AddressingMode + embedded_hal::i2c::AddressMode, + { + // reg ptr + 1 or 2 bytes + let mut buf = [self.get_ptr(); 3]; + for (i, item) in self.get_buf().iter().enumerate() { + buf[i + 1] = *item; + } + Ok(i2c.write(addr, &buf[0..self.get_len() as usize])?) + } +} diff --git a/src/rtc/registers.rs b/src/rtc/registers.rs new file mode 100644 index 0000000..e1bea35 --- /dev/null +++ b/src/rtc/registers.rs @@ -0,0 +1,149 @@ +use crate::error::DriverError; +use core::fmt::Debug; +use embedded_hal::i2c::{I2c, SevenBitAddress}; + +/// Mapping of all the registers used to operate the RTC module +#[derive(Clone, Copy)] +#[allow(clippy::doc_markdown)] +pub enum Register { + /// RAM + Ram = 0x07, + /// Minutes Alarm + MinutesAlarm = 0x08, + /// HoursAlarm + HoursAlarm = 0x09, + /// Hundreths + Hundredths = 0x10, + /// Seconds + Seconds = 0x11, + /// Minutes + Minutes = 0x12, + /// Hours + Hours = 0x13, + /// Weekday + Weekday = 0x14, + /// Date + Date = 0x15, + /// Month + Month = 0x16, + /// Year + Year = 0x17, + /// ControlReset + ControlReset = 0, + /// Extension Register + Extension = 0x1D, + /// Flag Register + Flag = 0x1E, + /// Control Register + Control = 0x1F, + /// Offset + Offset = 0x2C, + /// Event Control + Event = 0x2F, +} + +impl Register { + /// Read address value, returns as [`u8`] + pub fn address(self) -> u8 { + self as u8 + } +} + +#[derive(Debug, Copy, Clone, Default)] +#[allow(clippy::module_name_repetitions)] +pub struct ClockRegisters { + device_address: u8, +} + +pub fn new(address: u8) -> ClockRegisters { + ClockRegisters { + device_address: address, + } +} + +impl ClockRegisters { + /// Write a single bit to the specified register + pub fn write_bit( + &mut self, + i2c: &mut I2C, + reg_addr: u8, + bit_addr: u8, + bit_to_write: bool, + ) -> Result> + where + I2C: I2c, + I2C::Error: Into>, + { + let mut value = 0; + + if let Ok(reg_value) = self.read_register_by_addr(i2c, reg_addr) { + value = reg_value; + } + + value &= !(1 << bit_addr); + value |= u8::from(bit_to_write) << bit_addr; + + self.write_register_by_addr(i2c, reg_addr, value)?; + + Ok(true) + } + + pub fn read_register( + &mut self, + i2c: &mut I2C, + register: Register, + ) -> Result> + where + I2C: I2c, + I2C::Error: Into>, + { + let mut data = [0]; + i2c.write_read(self.device_address, &[register.address()], &mut data)?; + // debug!("data: {:b}", data); + + Ok(u8::from_le_bytes(data)) + } + + pub fn write_register( + &mut self, + i2c: &mut I2C, + register: Register, + byte: u8, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>, + { + i2c.write(self.device_address, &[register.address(), byte])?; + Ok(()) + } + + pub fn write_register_by_addr( + &mut self, + i2c: &mut I2C, + reg_addr: u8, + byte: u8, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>, + { + i2c.write(self.device_address, &[reg_addr, byte])?; + + Ok(()) + } + + pub fn read_register_by_addr( + &mut self, + i2c: &mut I2C, + reg_addr: u8, + ) -> Result> + where + I2C: I2c, + I2C::Error: Into>, + { + let mut data = [0]; + i2c.write_read(self.device_address, &[reg_addr], &mut data)?; + Ok(u8::from_le_bytes(data)) + } +} diff --git a/src/rtc/update.rs b/src/rtc/update.rs new file mode 100644 index 0000000..0fdb5a2 --- /dev/null +++ b/src/rtc/update.rs @@ -0,0 +1,71 @@ +use crate::{error::DriverError, rtc::registers::Register, ClockData}; +use core::fmt::Debug; +use embedded_hal::i2c::{I2c, SevenBitAddress}; + +use super::registers::ClockRegisters; + +/// Clock updater. +#[derive(Debug, Copy, Clone, Default)] +pub struct ClockUpdater; + +/// Creates a [`ClockUpdater`]. +#[must_use] +pub fn new() -> ClockUpdater { + ClockUpdater {} +} + +/// Trait to read from I2C periph +pub trait Read: Debug + Copy + Clone { + /// Set the date and time. + /// + /// # Errors + /// + /// Returns a [`DriverError`] + fn set_datetime( + &mut self, + i2c: &mut I2C, + addr: u8, + cu: &mut ClockRegisters, + data: &ClockData, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>; +} + +impl Read for ClockUpdater { + fn set_datetime( + &mut self, + i2c: &mut I2C, + _addr: u8, + cu: &mut ClockRegisters, + data: &ClockData, + ) -> Result<(), DriverError> + where + I2C: I2c, + I2C::Error: Into>, + { + use crate::models::misc::dec_to_bcd; + + // Stored as BCD values. + cu.write_register(i2c, Register::Hours, dec_to_bcd(data.hours()))?; + cu.write_register(i2c, Register::Minutes, dec_to_bcd(data.minutes()))?; + cu.write_register(i2c, Register::Seconds, dec_to_bcd(data.seconds()))?; + cu.write_register(i2c, Register::Date, dec_to_bcd(data.day()))?; + cu.write_register(i2c, Register::Month, dec_to_bcd(data.month()))?; + cu.write_register(i2c, Register::Year, dec_to_bcd(data.year()))?; + + // Single bit value only + cu.write_register(i2c, Register::Weekday, data.weekday())?; + + // Set RESET bit to 0 in Control register to prevent seconds getting stuck. + cu.write_bit( + i2c, + Register::Control.address(), + Register::ControlReset.address(), + false, + )?; + + Ok(()) + } +} From a8dbd81866ace412dd81485258b9f4dbff4c7b1b Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Sun, 29 Sep 2024 08:07:42 +0530 Subject: [PATCH 3/9] Update Github CI --- .github/FUNDING.yml | 1 + .github/dependabot.yml | 15 ++++++++-- .github/workflows/ci.yml | 64 ++++++++++++++++++++++++++++++++++++---- Cargo.toml | 3 +- build.sh | 2 +- deny.toml | 20 +++++++++++++ src/models.rs | 14 ++++----- src/rtc/now.rs | 2 +- src/rtc/registers.rs | 2 +- 9 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 deny.toml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..363db3f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: bsodmike \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d69e053..16c27e0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,9 +3,18 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" - + interval: "weekly" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + groups: + npm-dependencies: + patterns: ["*"] - package-ecosystem: "cargo" directory: "/" schedule: - interval: "daily" + interval: "weekly" + groups: + cargo-dependencies: + patterns: ["*"] \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de0a4ad..8ac3ba2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,14 @@ +name: CI + on: push: branches: - master - tags: - - 'v*' pull_request: - -name: CI + branches: + - master + schedule: + - cron: "0 0 * * 0" jobs: build: @@ -46,7 +48,7 @@ jobs: - uses: dtolnay/rust-toolchain@nightly with: components: clippy - - run: cargo clippy -- -Dclippy::all -Dclippy::pedantic --deny warnings + - run: cargo clippy -- -D warnings format: name: Format @@ -58,6 +60,58 @@ jobs: components: rustfmt - run: cargo +nightly fmt -- --check + deny: + name: Deny + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Run cargo-deny + uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check all + + links: + name: Links + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Run lychee + uses: lycheeverse/lychee-action@v1 + with: + args: -v *.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + typos: + name: Typos + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Check typos + uses: crate-ci/typos@master + + msrv: + name: MSRV + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Install cargo-binstall + uses: taiki-e/install-action@cargo-binstall + + - name: Install cargo-msrv + run: cargo binstall -y --force cargo-msrv + + - name: Run cargo-msrv + run: cargo msrv --output-format json verify | tail -n 1 | jq --exit-status '.success' + doc: name: doc runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index e6784d8..5ada4f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,14 @@ name = "rv8803" version = "4.0.0" authors = ["Michael de Silva "] -edition = "2021" repository = "https://github.com/bsodmike/rv8803-rs" license = "MIT" description = "RTC clock driver for the rv8803 chip via I2C" readme = "README.md" keywords = ["i2c", "driver", "embedded-hal-driver", "rv8803"] categories = ["embedded", "hardware-support", "no-std"] +edition = "2021" +rust-version = "1.83.0" publish = false [features] diff --git a/build.sh b/build.sh index 941d9d8..0cdbe76 100755 --- a/build.sh +++ b/build.sh @@ -4,5 +4,5 @@ set -ex cargo build --release cargo test -cargo clippy -- -Dclippy::all -Dclippy::pedantic +cargo clippy -- -D warnings cargo doc diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..dd2b15c --- /dev/null +++ b/deny.toml @@ -0,0 +1,20 @@ +# configuration for https://github.com/EmbarkStudios/cargo-deny + +[licenses] +confidence-threshold = 0.8 +allow = [ + "MIT", + "Apache-2.0", + "Unicode-DFS-2016", + "BSD-2-Clause", + "BSL-1.0", + "Zlib", +] + +[sources] +unknown-registry = "deny" +unknown-git = "warn" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] + +[advisories] +ignore = ["RUSTSEC-2023-0040", "RUSTSEC-2023-0059", "RUSTSEC-2021-0145"] \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index 942d634..39841ed 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,8 +1,8 @@ /// Holds the clock data. #[derive(Debug, Copy, Clone, Default)] pub struct ClockData { - /// Hundreths. - pub hundreths: u8, + /// Hundredths. + pub hundredths: u8, /// Seconds. pub seconds: u8, /// Minutes. @@ -20,10 +20,10 @@ pub struct ClockData { } impl ClockData { - /// Hundreths. + /// Hundredths. #[must_use] - pub fn hundreths(&self) -> u8 { - self.hundreths + pub fn hundredths(&self) -> u8 { + self.hundredths } /// Seconds. @@ -68,11 +68,11 @@ impl ClockData { self.year } - /// Set the date and time. Hundreths is set to 0. + /// Set the date and time. Hundredths is set to 0. pub fn set(&mut self, value: (u8, u8, u8, Weekday, u8, Month, u8)) { let (hours, minutes, seconds, weekday, day, month, year) = value; - self.hundreths = 0; + self.hundredths = 0; self.hours = hours; self.minutes = minutes; self.seconds = seconds; diff --git a/src/rtc/now.rs b/src/rtc/now.rs index 0b1c316..0707f9f 100644 --- a/src/rtc/now.rs +++ b/src/rtc/now.rs @@ -46,7 +46,7 @@ impl Read for ClockData { let mut cregs = super::registers::new(addr); let latest = ClockData { - hundreths: bcd_to_dec( + hundredths: bcd_to_dec( cregs.read_register(i2c, super::registers::Register::Hundredths)?, ), seconds: bcd_to_dec(cregs.read_register(i2c, super::registers::Register::Seconds)?), diff --git a/src/rtc/registers.rs b/src/rtc/registers.rs index e1bea35..bc0d4e6 100644 --- a/src/rtc/registers.rs +++ b/src/rtc/registers.rs @@ -12,7 +12,7 @@ pub enum Register { MinutesAlarm = 0x08, /// HoursAlarm HoursAlarm = 0x09, - /// Hundreths + /// Hundredths Hundredths = 0x10, /// Seconds Seconds = 0x11, From da8f15c91182eafa3ee01bed5da1cc76997ef03d Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Sun, 29 Sep 2024 08:24:01 +0530 Subject: [PATCH 4/9] Assert rust version and switch to stable channel --- .github/workflows/ci.yml | 2 + Cargo.toml | 2 +- deny.toml | 2 +- rust-toolchain.toml | 2 +- src/error.rs | 2 - src/formatter.rs | 11 ++-- src/lib.rs | 7 +-- src/models.rs | 2 +- src/rtc.rs | 31 ++--------- src/rtc/reg.rs | 110 --------------------------------------- 10 files changed, 18 insertions(+), 153 deletions(-) delete mode 100644 src/rtc/reg.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ac3ba2..ed14c91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: - "thumbv6m-none-eabi" - "thumbv7em-none-eabi" toolchain: + - "stable" - "nightly" steps: - uses: actions/checkout@v4 @@ -29,6 +30,7 @@ jobs: with: toolchain: ${{ matrix.toolchain }} target: ${{ matrix.target }} + - run: rustup target add ${{ matrix.target }} - run: cargo build --target ${{ matrix.target }} test: diff --git a/Cargo.toml b/Cargo.toml index 5ada4f8..37a1abd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ readme = "README.md" keywords = ["i2c", "driver", "embedded-hal-driver", "rv8803"] categories = ["embedded", "hardware-support", "no-std"] edition = "2021" -rust-version = "1.83.0" +rust-version = "1.81.0" publish = false [features] diff --git a/deny.toml b/deny.toml index dd2b15c..5a3efb1 100644 --- a/deny.toml +++ b/deny.toml @@ -17,4 +17,4 @@ unknown-git = "warn" allow-registry = ["https://github.com/rust-lang/crates.io-index"] [advisories] -ignore = ["RUSTSEC-2023-0040", "RUSTSEC-2023-0059", "RUSTSEC-2021-0145"] \ No newline at end of file +ignore = ["RUSTSEC-2023-0040", "RUSTSEC-2023-0059", "RUSTSEC-2021-0145", "RUSTSEC-2024-0370"] \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 271800c..31578d3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly" \ No newline at end of file +channel = "stable" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 906acdb..9c20c79 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,8 +20,6 @@ impl core::error::Error for Error { fn cause(&self) -> Option<&dyn core::error::Error> { self.source() } - - fn provide<'a>(&'a self, _request: &mut core::error::Request<'a>) {} } /// Driver error. diff --git a/src/formatter.rs b/src/formatter.rs index 2bafbdd..835fea9 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -13,7 +13,7 @@ impl<'a> ByteMutWriter<'a> { } pub fn as_str(&self) -> &str { - str::from_utf8(&self.buf[0..self.cursor]).unwrap() + str::from_utf8(&self.buf[0..self.cursor]).expect("Unable to create &str from buf") } #[inline] @@ -41,12 +41,13 @@ impl<'a> ByteMutWriter<'a> { impl fmt::Write for ByteMutWriter<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { let cap = self.capacity(); - for (i, &b) in self.buf[self.cursor..cap] + self.buf[self.cursor..cap] .iter_mut() .zip(s.as_bytes().iter()) - { - *i = b; - } + .for_each(|(i, &b)| { + *i = b; + }); + self.cursor = usize::min(cap, self.cursor + s.as_bytes().len()); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index b26fd25..94586ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,9 @@ //! RTC clock driver for the `rv8803` chip over I2C. #![no_std] #![forbid(unsafe_code)] -#![warn(missing_docs)] -#![warn(dead_code)] -// #![deny(unused_imports)] +#![warn(missing_docs, dead_code, clippy::unwrap_used)] #![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))] -#![feature(error_generic_member_access)] -#![feature(trivial_bounds)] +// #![deny(unused_imports)] pub use crate::models::ClockData; pub use crate::rtc::Driver; diff --git a/src/models.rs b/src/models.rs index 39841ed..976fcb7 100644 --- a/src/models.rs +++ b/src/models.rs @@ -102,7 +102,7 @@ pub enum Weekday { Saturday = 64, } -/// FIXME this causes [defmt] to get stuck. Why? +// FIXME this causes [defmt] to get stuck. Why? // impl From for Weekday { // fn from(value: u8) -> Self { // Self::into(value.into()) diff --git a/src/rtc.rs b/src/rtc.rs index 908b7f2..fa6110b 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -5,8 +5,6 @@ use core::marker::PhantomData; use embedded_hal::i2c::{I2c, SevenBitAddress}; pub mod address; -pub mod reg; -// pub mod reg_year; pub mod registers; /// Used to fetch latest readings. @@ -29,14 +27,13 @@ impl AddressingMode for SevenBitAddress { /// # Registers /// /// Refer Page 15: -pub struct Driver { +pub struct Driver { addr: u8, i2c: I2C, _addr_mode: core::marker::PhantomData, - _mode_type: core::marker::PhantomData, } -impl Driver +impl Driver where I2C: I2c, I2C::Error: Into>, @@ -48,7 +45,6 @@ where addr: SlaveAddress::Default.into(), i2c, _addr_mode: PhantomData, - _mode_type: PhantomData, } } @@ -63,23 +59,6 @@ where self.i2c } - #[allow(dead_code)] - fn read_register(&mut self, mut reg: T) -> Result> - where - T: reg::Read, - I2C: I2c, - I2C::Error: Into>, - { - reg.read_from_device(&mut self.i2c, self.addr)?; - Ok(reg) - } - - #[allow(dead_code)] - fn write_register(&mut self, reg: R) -> Result<(), DriverError> { - reg.write_to_device::(&mut self.i2c, self.addr)?; - Ok(()) - } - /// Fetch the latest reading from the rtc module. /// /// # Errors @@ -126,14 +105,13 @@ where /// Async Driver for the `rv8803` rtc chip. /// *WARNING*: This is in progress, and will be completed in a future release. #[allow(dead_code)] -pub struct DriverAsync { +pub struct DriverAsync { addr: u8, i2c: I2C, _addr_mode: core::marker::PhantomData, - _mode_type: core::marker::PhantomData, } -impl DriverAsync +impl DriverAsync where I2C: embedded_hal_async::i2c::I2c, I2C::Error: Into>, @@ -146,7 +124,6 @@ where addr: SlaveAddress::Default.into(), i2c, _addr_mode: PhantomData, - _mode_type: PhantomData, } } diff --git a/src/rtc/reg.rs b/src/rtc/reg.rs deleted file mode 100644 index 8d12411..0000000 --- a/src/rtc/reg.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::error::DriverError; -use core::fmt::Debug; -use embedded_hal::i2c::{I2c, SevenBitAddress}; - -use super::AddressingMode; - -#[derive(Debug, Copy, Clone)] -pub struct Register { - /// points to a specific register in the sensor - ptr: u8, - /// register contents, either 1 or 2 bytes - buf: [u8; 2], - /// actual register size in bytes, either 1 or 2 - len: u8, -} - -impl Register { - #[allow(dead_code)] - #[allow(clippy::manual_assert)] - pub fn new(ptr: u8, len: u8) -> Self { - if len > 2 { - panic!("length > 2") - } - - let buf = [0u8, 0]; - Register { ptr, buf, len } - } - - pub fn get_buf(&self) -> &[u8] { - &self.buf[0..self.len as usize] - } - - pub fn set_buf(&mut self, val: [u8; 2]) { - self.buf = val; - } - - pub fn get_ptr(self) -> u8 { - self.ptr - } - - pub fn get_len(self) -> u8 { - self.len - } -} - -/// trait for a register that can be read from an i2c device -pub trait Read: Debug + Copy + Clone { - fn read_from_device( - &mut self, - i2c: &mut I2C, - addr: u8, - ) -> Result<(), DriverError> - where - I2C: I2c, - I2C::Error: Into>; -} - -impl Read for Register { - fn read_from_device( - &mut self, - i2c: &mut I2C, - addr: u8, - ) -> Result<(), DriverError> - where - I2C: I2c, - I2C::Error: Into>, - { - let mut buf = [0u8; 2]; - i2c.write_read( - addr, - &[self.get_ptr()], - &mut buf[0..self.get_len() as usize], - )?; - self.set_buf(buf); - Ok(()) - } -} - -/// trait for a register that can be written to an i2c device -pub trait Write: Read { - fn write_to_device( - &self, - i2c: &mut I2C, - addr: u8, - ) -> Result<(), DriverError> - where - I2C: I2c, - I2C::Error: Into>, - A: AddressingMode + embedded_hal::i2c::AddressMode; -} - -impl Write for Register { - fn write_to_device( - &self, - i2c: &mut I2C, - addr: u8, - ) -> Result<(), DriverError> - where - I2C: I2c, - I2C::Error: Into>, - A: AddressingMode + embedded_hal::i2c::AddressMode, - { - // reg ptr + 1 or 2 bytes - let mut buf = [self.get_ptr(); 3]; - for (i, item) in self.get_buf().iter().enumerate() { - buf[i + 1] = *item; - } - Ok(i2c.write(addr, &buf[0..self.get_len() as usize])?) - } -} From 9ab497d9d50e53f467cb312fcc23c59b4f621f27 Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Sun, 29 Sep 2024 16:09:33 +0530 Subject: [PATCH 5/9] Consolidate usage around a single ClockData type. --- src/lib.rs | 2 -- src/models.rs | 8 ++++++++ src/rtc.rs | 22 ++++++++++++---------- src/rtc/now.rs | 12 ++---------- src/rtc/update.rs | 17 +++-------------- 5 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 94586ba..c68ad30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,5 @@ pub mod prelude { pub use crate::error::DriverError; pub use crate::models::{Month, Weekday}; pub use crate::rtc::address::SlaveAddress; - pub use crate::rtc::now; - pub use crate::rtc::update::{self, ClockUpdater}; pub use crate::rtc::AddressingMode; } diff --git a/src/models.rs b/src/models.rs index 976fcb7..0c5541d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -20,6 +20,14 @@ pub struct ClockData { } impl ClockData { + /// Creates a [`ClockData`]. + #[must_use] + pub fn new() -> ClockData { + ClockData { + ..Default::default() + } + } + /// Hundredths. #[must_use] pub fn hundredths(&self) -> u8 { diff --git a/src/rtc.rs b/src/rtc.rs index fa6110b..279427b 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -64,16 +64,19 @@ where /// # Errors /// /// Returns a [`DriverError`] - pub fn now(&mut self, mut clock_data: T) -> Result> + pub fn now( + &mut self, + mut rtc_chip: impl crate::rtc::now::Readable, + ) -> Result> where - T: crate::rtc::now::Read, + // T: crate::rtc::now::Read, I2C: I2c, I2C::Error: Into>, { - let mut data = crate::prelude::now::new(); + let mut data = crate::models::ClockData::new(); // Associated instance on T, not to be confused with the value data above. - clock_data.now(&mut self.i2c, self.addr, &mut data)?; + rtc_chip.now(&mut self.i2c, self.addr, &mut data)?; Ok(data) } @@ -83,22 +86,21 @@ where /// # Errors /// /// Returns a [`DriverError`] - pub fn update( + pub fn update( &mut self, - mut clock_data: T, + mut rtc_chip: impl crate::rtc::update::Updatable, data: &Option, - ) -> Result> + ) -> Result> where - T: crate::rtc::update::Read, I2C: I2c, I2C::Error: Into>, { let mut cu = ClockRegisters::new(self.addr); if let Some(d) = data { - clock_data.set_datetime(&mut self.i2c, self.addr, &mut cu, d)?; + rtc_chip.set_datetime(&mut self.i2c, self.addr, &mut cu, d)?; } - Ok(clock_data) + Ok(rtc_chip) } } diff --git a/src/rtc/now.rs b/src/rtc/now.rs index 0707f9f..01ec4f2 100644 --- a/src/rtc/now.rs +++ b/src/rtc/now.rs @@ -4,16 +4,8 @@ use crate::{error::DriverError, models::Month, models::Weekday}; use core::fmt::{Debug, Write}; use embedded_hal::i2c::{I2c, SevenBitAddress}; -/// Creates a [`ClockData`]. -#[must_use] -pub fn new() -> ClockData { - ClockData { - ..Default::default() - } -} - /// Trait to read from I2C periph -pub trait Read: Debug + Copy + Clone { +pub trait Readable: Debug + Copy + Clone { /// Fetch the latest date and time. /// /// # Errors @@ -30,7 +22,7 @@ pub trait Read: Debug + Copy + Clone { I2C::Error: Into>; } -impl Read for ClockData { +impl Readable for ClockData { fn now( &mut self, i2c: &mut I2C, diff --git a/src/rtc/update.rs b/src/rtc/update.rs index 0fdb5a2..fb6d633 100644 --- a/src/rtc/update.rs +++ b/src/rtc/update.rs @@ -4,18 +4,7 @@ use embedded_hal::i2c::{I2c, SevenBitAddress}; use super::registers::ClockRegisters; -/// Clock updater. -#[derive(Debug, Copy, Clone, Default)] -pub struct ClockUpdater; - -/// Creates a [`ClockUpdater`]. -#[must_use] -pub fn new() -> ClockUpdater { - ClockUpdater {} -} - -/// Trait to read from I2C periph -pub trait Read: Debug + Copy + Clone { +pub trait Updatable: Debug + Copy + Clone { /// Set the date and time. /// /// # Errors @@ -25,7 +14,7 @@ pub trait Read: Debug + Copy + Clone { &mut self, i2c: &mut I2C, addr: u8, - cu: &mut ClockRegisters, + cr: &mut ClockRegisters, data: &ClockData, ) -> Result<(), DriverError> where @@ -33,7 +22,7 @@ pub trait Read: Debug + Copy + Clone { I2C::Error: Into>; } -impl Read for ClockUpdater { +impl Updatable for ClockData { fn set_datetime( &mut self, i2c: &mut I2C, From 3a0b582076d68281714604d3b32f2fa2e6ffd422 Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Mon, 30 Sep 2024 17:42:05 +0530 Subject: [PATCH 6/9] Allow decorating the ClockData with century --- src/lib.rs | 2 +- src/models.rs | 27 +++++++++++++++++++++++++++ src/rtc/now.rs | 21 +++++++++++++++++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c68ad30..2207c58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ pub(crate) mod rtc; /// Re-exports pub mod prelude { pub use crate::error::DriverError; - pub use crate::models::{Month, Weekday}; + pub use crate::models::{Month, Weekday, Year}; pub use crate::rtc::address::SlaveAddress; pub use crate::rtc::AddressingMode; } diff --git a/src/models.rs b/src/models.rs index 0c5541d..dceaaf2 100644 --- a/src/models.rs +++ b/src/models.rs @@ -17,6 +17,8 @@ pub struct ClockData { pub month: u8, /// Year. pub year: u8, + /// Current century + pub century: Year, } impl ClockData { @@ -89,6 +91,11 @@ impl ClockData { self.month = month as u8; self.year = year; } + + /// Set the century. This is mainly used for presentational purposes. + pub fn set_century(&mut self, value: Year) { + self.century = value + } } /// Enumerated type values for the weekday register. @@ -229,6 +236,26 @@ impl defmt::Format for Month { } } +/// Enumerated type values for the year. +#[derive(Debug, Copy, Clone)] +#[allow(dead_code)] +pub enum Year { + TwentiethCentury(u8), + TwentyFirstCentury(u8), +} + +impl Default for Year { + fn default() -> Self { + Self::TwentyFirstCentury(20) + } +} + +// impl Year { +// pub from() -> Self{ + +// } +// } + #[allow(dead_code)] pub mod misc { pub fn bcd_to_dec(value: u8) -> u8 { diff --git a/src/rtc/now.rs b/src/rtc/now.rs index 01ec4f2..ac62323 100644 --- a/src/rtc/now.rs +++ b/src/rtc/now.rs @@ -1,5 +1,5 @@ use crate::formatter::ByteMutWriter; -use crate::models::ClockData; +use crate::models::{ClockData, Year}; use crate::{error::DriverError, models::Month, models::Weekday}; use core::fmt::{Debug, Write}; use embedded_hal::i2c::{I2c, SevenBitAddress}; @@ -50,6 +50,8 @@ impl Readable for ClockData { // Read directly as a byte. weekday: cregs.read_register(i2c, super::registers::Register::Weekday)?, + + ..Default::default() }; *data = latest; @@ -65,6 +67,17 @@ fn left_pad<'a>(buf: &'a mut ByteMutWriter<'_>, value: u8) -> &'a str { buf.as_str() } +fn pad_year<'a>(buf: &'a mut ByteMutWriter<'_>, value: u8, century: Year) -> &'a str { + buf.clear(); + + match century { + Year::TwentiethCentury(_) => write!(buf, "19{}", value).unwrap(), + Year::TwentyFirstCentury(_) => write!(buf, "20{}", value).unwrap(), + } + + buf.as_str() +} + impl defmt::Format for ClockData { fn format(&self, fmt: defmt::Formatter) { let mut buf = [0u8; 2]; @@ -86,6 +99,10 @@ impl defmt::Format for ClockData { let month = Month::from(self.month); let weekday = Weekday::from(self.weekday); + let mut buf = [0u8; 4]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let year = pad_year(&mut buf, self.year, self.century); + defmt::write!( fmt, "{}:{}:{}, {}, {} {} {}", @@ -95,7 +112,7 @@ impl defmt::Format for ClockData { weekday, day, month, - self.year(), + year, ); } } From 7c2b3a6c9359bcc37154d77e76858a7398f4d448 Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Mon, 30 Sep 2024 18:55:54 +0530 Subject: [PATCH 7/9] Use a loggable newtype to decorate Logging information for Clock Data --- src/lib.rs | 6 ++- src/log.rs | 34 +++++++++++++++++ src/models.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++------ src/rtc/now.rs | 66 ++------------------------------- 4 files changed, 129 insertions(+), 76 deletions(-) create mode 100644 src/log.rs diff --git a/src/lib.rs b/src/lib.rs index 2207c58..cd60570 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! RTC clock driver for the `rv8803` chip over I2C. #![no_std] #![forbid(unsafe_code)] -#![warn(missing_docs, dead_code, clippy::unwrap_used)] +#![deny(missing_docs, dead_code, clippy::unwrap_used)] #![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))] // #![deny(unused_imports)] @@ -12,13 +12,15 @@ pub use crate::rtc::DriverAsync; pub(crate) mod error; #[allow(dead_code)] pub(crate) mod formatter; +pub(crate) mod log; pub(crate) mod models; pub(crate) mod rtc; /// Re-exports pub mod prelude { pub use crate::error::DriverError; - pub use crate::models::{Month, Weekday, Year}; + pub use crate::log::LoggableClockData; + pub use crate::models::{CurrentYear, Month, Weekday, Year}; pub use crate::rtc::address::SlaveAddress; pub use crate::rtc::AddressingMode; } diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..a0bccff --- /dev/null +++ b/src/log.rs @@ -0,0 +1,34 @@ +use crate::{models::Year, ClockData}; + +/// Loggable newtype for [`ClockData`] +#[derive(Debug, Copy, Clone, Default)] +pub struct LoggableClockData { + data: ClockData, + /// Current century + century: Year, +} + +impl LoggableClockData { + /// Creates a [`LoggableClockData`] + pub fn new(data: ClockData) -> Self { + Self { + data, + century: Year::default(), + } + } + + /// Set the century. This is mainly used for presentational purposes. + pub fn set_century(&mut self, value: Year) { + self.century = value + } + + /// Get the clock data. + pub fn data(&self) -> ClockData { + self.data + } + + /// Get the century. + pub fn century(&self) -> Year { + self.century + } +} diff --git a/src/models.rs b/src/models.rs index dceaaf2..87ba28c 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,3 +1,6 @@ +use crate::{formatter::ByteMutWriter, log::LoggableClockData}; +use core::fmt::{Debug, Write}; + /// Holds the clock data. #[derive(Debug, Copy, Clone, Default)] pub struct ClockData { @@ -17,8 +20,6 @@ pub struct ClockData { pub month: u8, /// Year. pub year: u8, - /// Current century - pub century: Year, } impl ClockData { @@ -79,7 +80,7 @@ impl ClockData { } /// Set the date and time. Hundredths is set to 0. - pub fn set(&mut self, value: (u8, u8, u8, Weekday, u8, Month, u8)) { + pub fn set(&mut self, value: (u8, u8, u8, Weekday, u8, Month, CurrentYear)) { let (hours, minutes, seconds, weekday, day, month, year) = value; self.hundredths = 0; @@ -89,12 +90,66 @@ impl ClockData { self.weekday = weekday as u8; self.date = day; self.month = month as u8; - self.year = year; + self.year = year.0; } +} + +/// Creates a tuple to hold the current year. +pub struct CurrentYear(u8); + +impl CurrentYear { + /// Provides the current year as a [`u8`]. + pub fn new(value: u16) -> Self { + if value < 1970 { + panic!("Year must be greater than 1970"); + } - /// Set the century. This is mainly used for presentational purposes. - pub fn set_century(&mut self, value: Year) { - self.century = value + if value < 2000 { + Self((value - 1900) as u8) + } else { + Self((value - 2000) as u8) + } + } +} + +impl defmt::Format for LoggableClockData { + fn format(&self, fmt: defmt::Formatter) { + let data = self.data(); + + let mut buf = [0u8; 2]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let hours = left_pad(&mut buf, data.hours); + + let mut buf = [0u8; 2]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let minutes = left_pad(&mut buf, data.minutes); + + let mut buf = [0u8; 2]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let seconds = left_pad(&mut buf, data.seconds); + + let mut buf = [0u8; 2]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let day = left_pad(&mut buf, data.date); + + let month = Month::from(data.month); + let weekday = Weekday::from(data.weekday); + + let mut buf = [0u8; 4]; + let mut buf = ByteMutWriter::new(&mut buf[..]); + let year = pad_year(&mut buf, data.year, self.century()); + + defmt::write!( + fmt, + "{}:{}:{}, {}, {} {} {}", + hours, + minutes, + seconds, + weekday, + day, + month, + year, + ); } } @@ -240,7 +295,9 @@ impl defmt::Format for Month { #[derive(Debug, Copy, Clone)] #[allow(dead_code)] pub enum Year { + /// 20th Century Fox. Definitely, better times. TwentiethCentury(u8), + /// Reality. TwentyFirstCentury(u8), } @@ -250,11 +307,31 @@ impl Default for Year { } } -// impl Year { -// pub from() -> Self{ +fn left_pad<'a>(buf: &'a mut ByteMutWriter<'_>, value: u8) -> &'a str { + buf.clear(); + write!(buf, "{}{}", common_padding(value), value).unwrap(); -// } -// } + buf.as_str() +} + +fn pad_year<'a>(buf: &'a mut ByteMutWriter<'_>, value: u8, century: Year) -> &'a str { + buf.clear(); + + match century { + Year::TwentiethCentury(_) => write!(buf, "19{}{}", common_padding(value), value).unwrap(), + Year::TwentyFirstCentury(_) => write!(buf, "20{}{}", common_padding(value), value).unwrap(), + } + + buf.as_str() +} + +fn common_padding<'a>(value: u8) -> &'a str { + if value < 10 { + "0" + } else { + "" + } +} #[allow(dead_code)] pub mod misc { diff --git a/src/rtc/now.rs b/src/rtc/now.rs index ac62323..c6a50f2 100644 --- a/src/rtc/now.rs +++ b/src/rtc/now.rs @@ -1,7 +1,6 @@ -use crate::formatter::ByteMutWriter; -use crate::models::{ClockData, Year}; -use crate::{error::DriverError, models::Month, models::Weekday}; -use core::fmt::{Debug, Write}; +use crate::error::DriverError; +use crate::models::ClockData; +use core::fmt::Debug; use embedded_hal::i2c::{I2c, SevenBitAddress}; /// Trait to read from I2C periph @@ -50,8 +49,6 @@ impl Readable for ClockData { // Read directly as a byte. weekday: cregs.read_register(i2c, super::registers::Register::Weekday)?, - - ..Default::default() }; *data = latest; @@ -59,60 +56,3 @@ impl Readable for ClockData { Ok(()) } } - -fn left_pad<'a>(buf: &'a mut ByteMutWriter<'_>, value: u8) -> &'a str { - buf.clear(); - write!(buf, "{}{}", if value < 10 { "0" } else { "" }, value).unwrap(); - - buf.as_str() -} - -fn pad_year<'a>(buf: &'a mut ByteMutWriter<'_>, value: u8, century: Year) -> &'a str { - buf.clear(); - - match century { - Year::TwentiethCentury(_) => write!(buf, "19{}", value).unwrap(), - Year::TwentyFirstCentury(_) => write!(buf, "20{}", value).unwrap(), - } - - buf.as_str() -} - -impl defmt::Format for ClockData { - fn format(&self, fmt: defmt::Formatter) { - let mut buf = [0u8; 2]; - let mut buf = ByteMutWriter::new(&mut buf[..]); - let hours = left_pad(&mut buf, self.hours); - - let mut buf = [0u8; 2]; - let mut buf = ByteMutWriter::new(&mut buf[..]); - let minutes = left_pad(&mut buf, self.minutes); - - let mut buf = [0u8; 2]; - let mut buf = ByteMutWriter::new(&mut buf[..]); - let seconds = left_pad(&mut buf, self.seconds); - - let mut buf = [0u8; 2]; - let mut buf = ByteMutWriter::new(&mut buf[..]); - let day = left_pad(&mut buf, self.date); - - let month = Month::from(self.month); - let weekday = Weekday::from(self.weekday); - - let mut buf = [0u8; 4]; - let mut buf = ByteMutWriter::new(&mut buf[..]); - let year = pad_year(&mut buf, self.year, self.century); - - defmt::write!( - fmt, - "{}:{}:{}, {}, {} {} {}", - hours, - minutes, - seconds, - weekday, - day, - month, - year, - ); - } -} From 350cffaf67dd77979026696ac1625aa7e001ab66 Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Sat, 5 Oct 2024 21:18:17 +0530 Subject: [PATCH 8/9] Add `DateTimeBuilder` - `DateTimeBuilder::new()` defaults to the UNIX epoch. --- src/lib.rs | 2 +- src/models.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++- src/rtc/update.rs | 2 +- 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cd60570..280e15b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ pub(crate) mod rtc; pub mod prelude { pub use crate::error::DriverError; pub use crate::log::LoggableClockData; - pub use crate::models::{CurrentYear, Month, Weekday, Year}; + pub use crate::models::{CurrentYear, DateTimeBuilder, Month, Weekday, Year}; pub use crate::rtc::address::SlaveAddress; pub use crate::rtc::AddressingMode; } diff --git a/src/models.rs b/src/models.rs index 87ba28c..04072fc 100644 --- a/src/models.rs +++ b/src/models.rs @@ -63,7 +63,7 @@ impl ClockData { /// Day. The rtc module refers to this as "date". #[must_use] - pub fn day(&self) -> u8 { + pub fn date(&self) -> u8 { self.date } @@ -80,7 +80,11 @@ impl ClockData { } /// Set the date and time. Hundredths is set to 0. - pub fn set(&mut self, value: (u8, u8, u8, Weekday, u8, Month, CurrentYear)) { + pub fn set(&mut self, value: &ClockData) { + *self = *value; + } + + fn _set(&mut self, value: (u8, u8, u8, Weekday, u8, Month, CurrentYear)) { let (hours, minutes, seconds, weekday, day, month, year) = value; self.hundredths = 0; @@ -95,6 +99,7 @@ impl ClockData { } /// Creates a tuple to hold the current year. +#[derive(Debug, Default)] pub struct CurrentYear(u8); impl CurrentYear { @@ -154,8 +159,10 @@ impl defmt::Format for LoggableClockData { } /// Enumerated type values for the weekday register. +#[derive(Debug, Default)] #[allow(dead_code)] pub enum Weekday { + #[default] /// Sunday Sunday = 1, /// Monday @@ -217,7 +224,9 @@ impl defmt::Format for Weekday { /// Enumerated type values for the month register. #[allow(dead_code)] +#[derive(Debug, Default)] pub enum Month { + #[default] /// January January = 1, /// February @@ -307,6 +316,100 @@ impl Default for Year { } } +/// Creates a [`DateTimeBuilder`] to set the time. +#[derive(Debug, Default)] +pub struct DateTimeBuilder { + hours: u8, + minutes: u8, + seconds: u8, + date: u8, + weekday: Weekday, + month: Month, + year: CurrentYear, +} + +impl DateTimeBuilder { + /// Creates a new [`DateTimeBuilder`], defaulting to the UNIX epoch. + #[must_use] + pub fn new() -> Self { + Self { + hours: 0, + minutes: 0, + seconds: 0, + date: 1, + weekday: Weekday::Thursday, + month: Month::January, + year: CurrentYear::new(1970), + } + } + + /// Set the hours. + #[must_use] + pub fn hours(mut self, value: u8) -> Self { + self.hours = value; + self + } + + /// Set the minutes. + #[must_use] + pub fn minutes(mut self, value: u8) -> Self { + self.minutes = value; + self + } + + /// Set the seconds. + #[must_use] + pub fn seconds(mut self, value: u8) -> Self { + self.seconds = value; + self + } + + /// Set the date. + #[must_use] + pub fn date(mut self, value: u8) -> Self { + self.date = value; + self + } + + /// Set the weekday. + #[must_use] + pub fn weekday(mut self, value: Weekday) -> Self { + self.weekday = value; + self + } + + /// Set the month. + #[must_use] + pub fn month(mut self, value: Month) -> Self { + self.month = value; + self + } + + /// Set the year. + #[must_use] + pub fn year(mut self, value: CurrentYear) -> Self { + self.year = value; + self + } + + /// Build the time. + #[must_use] + pub fn build(self) -> ClockData { + let mut data = ClockData::new(); + data._set(( + self.hours, + self.minutes, + self.seconds, + self.weekday, + self.date, + self.month, + self.year, + )); + + data + } +} + fn left_pad<'a>(buf: &'a mut ByteMutWriter<'_>, value: u8) -> &'a str { buf.clear(); write!(buf, "{}{}", common_padding(value), value).unwrap(); diff --git a/src/rtc/update.rs b/src/rtc/update.rs index fb6d633..8f03aae 100644 --- a/src/rtc/update.rs +++ b/src/rtc/update.rs @@ -40,7 +40,7 @@ impl Updatable for ClockData { cu.write_register(i2c, Register::Hours, dec_to_bcd(data.hours()))?; cu.write_register(i2c, Register::Minutes, dec_to_bcd(data.minutes()))?; cu.write_register(i2c, Register::Seconds, dec_to_bcd(data.seconds()))?; - cu.write_register(i2c, Register::Date, dec_to_bcd(data.day()))?; + cu.write_register(i2c, Register::Date, dec_to_bcd(data.date()))?; cu.write_register(i2c, Register::Month, dec_to_bcd(data.month()))?; cu.write_register(i2c, Register::Year, dec_to_bcd(data.year()))?; From b0b16a6ae353d2687a0bc4c5ea8925e1167fff3a Mon Sep 17 00:00:00 2001 From: Michael de Silva Date: Sun, 6 Oct 2024 12:59:48 +0530 Subject: [PATCH 9/9] Update CHANGELOG & licensing for v4.0.0 release --- CHANGELOG.md | 8 +- Cargo.toml | 3 +- LICENSE-APACHE | 179 +++++++++++++++++++++++++++++++++++++++++ LICENSE => LICENSE-MIT | 0 4 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 LICENSE-APACHE rename LICENSE => LICENSE-MIT (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aeda8b..5fb4d13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [4.0.0] - XX Sept 2024 +## [4.0.0] - 06 October 2024 ### Changed -- __Breaking Change__: New public API based on `embedded-hal-async`. +- __Breaking Change__: New public API. + +This release has been tested on: +- Raspberry Pi Pico (RP2040) +- RAKwireless RAK3172 (STM32wle5cc) ## [3.0.0] - 15 Sept 2024 - Yanked diff --git a/Cargo.toml b/Cargo.toml index 37a1abd..a4c5c8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,14 +3,13 @@ name = "rv8803" version = "4.0.0" authors = ["Michael de Silva "] repository = "https://github.com/bsodmike/rv8803-rs" -license = "MIT" +license = "MIT OR Apache-2.0" description = "RTC clock driver for the rv8803 chip via I2C" readme = "README.md" keywords = ["i2c", "driver", "embedded-hal-driver", "rv8803"] categories = ["embedded", "hardware-support", "no-std"] edition = "2021" rust-version = "1.81.0" -publish = false [features] default = [] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..d3361fd --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,179 @@ +Copyright (c) Michael de Silva (https://desilva.io/about) +Email: michael@cyberdynea.io / michael@crabtronics.net + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/LICENSE b/LICENSE-MIT similarity index 100% rename from LICENSE rename to LICENSE-MIT