mirror of https://github.com/eldruin/ds323x-rs
306 lines
11 KiB
Rust
306 lines
11 KiB
Rust
//! Common implementation
|
|
|
|
extern crate embedded_hal as hal;
|
|
use super::super::{ Ds323x, Register, BitFlags, Error };
|
|
use interface::{ ReadData, WriteData };
|
|
|
|
/// Date and time
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct DateTime {
|
|
/// Year [2000-2099]
|
|
pub year : u16,
|
|
/// Month [1-12]
|
|
pub month : u8,
|
|
/// Day [1-31]
|
|
pub day : u8,
|
|
/// Weekday [1-7]
|
|
pub weekday : u8,
|
|
/// Hour in 24h/12h format
|
|
pub hour : Hours,
|
|
/// Minute [0-59]
|
|
pub minute : u8,
|
|
/// Second [0-59]
|
|
pub second : u8,
|
|
}
|
|
|
|
/// Hours in either 12-hour (AM/PM) or 24-hour format
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum Hours {
|
|
/// AM [1-12]
|
|
AM(u8),
|
|
/// PM [1-12]
|
|
PM(u8),
|
|
/// 24H format [0-23]
|
|
H24(u8),
|
|
}
|
|
|
|
impl<DI, IC, E> Ds323x<DI, IC>
|
|
where
|
|
DI: ReadData<Error = E> + WriteData<Error = E>
|
|
{
|
|
/// Read the seconds.
|
|
pub fn get_seconds(&mut self) -> Result<u8, Error<E>> {
|
|
self.read_register_decimal(Register::SECONDS)
|
|
}
|
|
|
|
/// Read the minutes.
|
|
pub fn get_minutes(&mut self) -> Result<u8, Error<E>> {
|
|
self.read_register_decimal(Register::MINUTES)
|
|
}
|
|
|
|
/// Read the hours.
|
|
pub fn get_hours(&mut self) -> Result<Hours, Error<E>> {
|
|
let data = self.iface.read_register(Register::HOURS)?;
|
|
Ok(hours_from_register(data))
|
|
}
|
|
|
|
/// Read the day of the week [1-7].
|
|
pub fn get_weekday(&mut self) -> Result<u8, Error<E>> {
|
|
self.read_register_decimal(Register::DOW)
|
|
}
|
|
|
|
/// Read the day of the month [1-31].
|
|
pub fn get_day(&mut self) -> Result<u8, Error<E>> {
|
|
self.read_register_decimal(Register::DOM)
|
|
}
|
|
|
|
/// Read the month [1-12].
|
|
pub fn get_month(&mut self) -> Result<u8, Error<E>> {
|
|
let data = self.iface.read_register(Register::MONTH)?;
|
|
let value = data & !BitFlags::CENTURY;
|
|
Ok(packed_bcd_to_decimal(value))
|
|
}
|
|
|
|
/// Read the year [2000-2100].
|
|
pub fn get_year(&mut self) -> Result<u16, Error<E>> {
|
|
let mut data = [0; 3];
|
|
data[0] = Register::MONTH;
|
|
self.iface.read_data(&mut data)?;
|
|
Ok(year_from_registers(data[1], data[2]))
|
|
}
|
|
|
|
/// Read the date and time.
|
|
pub fn get_datetime(&mut self) -> Result<DateTime, Error<E>> {
|
|
let mut data = [0; 8];
|
|
self.iface.read_data(&mut data)?;
|
|
Ok(DateTime {
|
|
year: year_from_registers(data[Register::MONTH as usize + 1], data[Register::YEAR as usize + 1]),
|
|
month: packed_bcd_to_decimal(data[Register::MONTH as usize + 1] & !BitFlags::CENTURY),
|
|
day: packed_bcd_to_decimal(data[Register::DOM as usize + 1]),
|
|
weekday: packed_bcd_to_decimal(data[Register::DOW as usize + 1]),
|
|
hour: hours_from_register(data[Register::HOURS as usize + 1]),
|
|
minute: packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]),
|
|
second: packed_bcd_to_decimal(data[Register::SECONDS as usize + 1])
|
|
})
|
|
}
|
|
|
|
fn read_register_decimal(&mut self, register: u8) -> Result<u8, Error<E>> {
|
|
let data = self.iface.read_register(register)?;
|
|
Ok(packed_bcd_to_decimal(data))
|
|
}
|
|
|
|
/// Set the seconds [0-59].
|
|
///
|
|
/// Will return an `Error::InvalidInputData` if the seconds are out of range.
|
|
pub fn set_seconds(&mut self, seconds: u8) -> Result<(), Error<E>> {
|
|
if seconds > 59 {
|
|
return Err(Error::InvalidInputData);
|
|
}
|
|
self.write_register_decimal(Register::SECONDS, seconds)
|
|
}
|
|
|
|
/// Set the minutes [0-59].
|
|
///
|
|
/// Will return an `Error::InvalidInputData` if the minutes are out of range.
|
|
pub fn set_minutes(&mut self, minutes: u8) -> Result<(), Error<E>> {
|
|
if minutes > 59 {
|
|
return Err(Error::InvalidInputData);
|
|
}
|
|
self.write_register_decimal(Register::MINUTES, minutes)
|
|
}
|
|
|
|
/// Set the hours.
|
|
///
|
|
/// Changes the operating mode to 12h/24h depending on the parameter.
|
|
///
|
|
/// Will return an `Error::InvalidInputData` if the hours are out of range.
|
|
pub fn set_hours(&mut self, hours: Hours) -> Result<(), Error<E>> {
|
|
let value = self.get_hours_register_value(&hours)?;
|
|
self.iface.write_register(Register::HOURS, value)
|
|
}
|
|
|
|
fn get_hours_register_value(&mut self, hours: &Hours) -> Result<u8, Error<E>> {
|
|
match *hours {
|
|
Hours::H24(h) if h > 23 => Err(Error::InvalidInputData),
|
|
Hours::H24(h) => Ok(decimal_to_packed_bcd(h)),
|
|
Hours::AM(h) if h < 1 || h > 12 => Err(Error::InvalidInputData),
|
|
Hours::AM(h) => Ok(BitFlags::H24_H12 | decimal_to_packed_bcd(h)),
|
|
Hours::PM(h) if h < 1 || h > 12 => Err(Error::InvalidInputData),
|
|
Hours::PM(h) => Ok(BitFlags::H24_H12 | BitFlags::AM_PM | decimal_to_packed_bcd(h)),
|
|
}
|
|
}
|
|
|
|
/// Set the day of week [1-7].
|
|
///
|
|
/// Will return an `Error::InvalidInputData` if the day is out of range.
|
|
pub fn set_weekday(&mut self, weekday: u8) -> Result<(), Error<E>> {
|
|
if weekday < 1 || weekday > 7 {
|
|
return Err(Error::InvalidInputData);
|
|
}
|
|
self.iface.write_register(Register::DOW, weekday)
|
|
}
|
|
|
|
/// Set the day of month [1-31].
|
|
///
|
|
/// Will return an `Error::InvalidInputData` if the day is out of range.
|
|
pub fn set_day(&mut self, day: u8) -> Result<(), Error<E>> {
|
|
if day < 1 || day > 7 {
|
|
return Err(Error::InvalidInputData);
|
|
}
|
|
self.iface.write_register(Register::DOM, day)
|
|
}
|
|
|
|
/// Set the month [1-12].
|
|
///
|
|
/// Will return an `Error::InvalidInputData` if the month is out of range.
|
|
pub fn set_month(&mut self, month: u8) -> Result<(), Error<E>> {
|
|
if month < 1 || month > 12 {
|
|
return Err(Error::InvalidInputData);
|
|
}
|
|
// keep the century bit
|
|
let data = self.iface.read_register(Register::MONTH)?;
|
|
let value = (data & BitFlags::CENTURY) | decimal_to_packed_bcd(month);
|
|
self.iface.write_register(Register::MONTH, value)
|
|
}
|
|
|
|
/// Set the year [2000-2100].
|
|
///
|
|
/// Will return an `Error::InvalidInputData` if the year is out of range.
|
|
pub fn set_year(&mut self, year: u16) -> Result<(), Error<E>> {
|
|
if year < 2000 || year > 2100 {
|
|
return Err(Error::InvalidInputData);
|
|
}
|
|
let data = self.iface.read_register(Register::MONTH)?;
|
|
let month_bcd = data & !BitFlags::CENTURY;
|
|
if year > 2099 {
|
|
let mut data = [ Register::MONTH,
|
|
BitFlags::CENTURY | month_bcd,
|
|
decimal_to_packed_bcd((year - 2100) as u8) ];
|
|
self.iface.write_data(&mut data)
|
|
}
|
|
else {
|
|
let mut data = [ Register::MONTH, month_bcd,
|
|
decimal_to_packed_bcd((year - 2000) as u8) ];
|
|
self.iface.write_data(&mut data)
|
|
}
|
|
}
|
|
|
|
/// Set the date and time.
|
|
///
|
|
/// Will return an `Error::InvalidInputData` if any of the parameters is out of range.
|
|
pub fn set_datetime(&mut self, datetime: &DateTime) -> Result<(), Error<E>> {
|
|
if datetime.year < 2000 || datetime.year > 2100 ||
|
|
datetime.month < 1 || datetime.month > 12 ||
|
|
datetime.day < 1 || datetime.day > 31 ||
|
|
datetime.weekday < 1 || datetime.weekday > 7 ||
|
|
datetime.minute > 59 ||
|
|
datetime.second > 59 {
|
|
return Err(Error::InvalidInputData);
|
|
}
|
|
let (month, year) = month_year_to_registers(datetime.month, datetime.year);
|
|
let mut payload = [Register::SECONDS,
|
|
decimal_to_packed_bcd(datetime.second),
|
|
decimal_to_packed_bcd(datetime.minute),
|
|
self.get_hours_register_value(&datetime.hour)?,
|
|
decimal_to_packed_bcd(datetime.weekday),
|
|
decimal_to_packed_bcd(datetime.day),
|
|
month, year];
|
|
self.iface.write_data(&mut payload)
|
|
}
|
|
|
|
fn write_register_decimal(&mut self, register: u8, decimal_number: u8) -> Result<(), Error<E>> {
|
|
self.iface.write_register(register, decimal_to_packed_bcd(decimal_number))
|
|
}
|
|
}
|
|
|
|
fn hours_from_register(data: u8) -> Hours {
|
|
if is_24h_format(data) {
|
|
Hours::H24(packed_bcd_to_decimal(data & !BitFlags::H24_H12))
|
|
}
|
|
else if is_am(data) {
|
|
Hours::AM(packed_bcd_to_decimal(data & !(BitFlags::H24_H12 | BitFlags::AM_PM)))
|
|
}
|
|
else {
|
|
Hours::PM(packed_bcd_to_decimal(data & !(BitFlags::H24_H12 | BitFlags::AM_PM)))
|
|
}
|
|
}
|
|
|
|
fn year_from_registers(month: u8, year: u8) -> u16 {
|
|
let century = month & BitFlags::CENTURY;
|
|
let year = packed_bcd_to_decimal(year);
|
|
if century != 0 {
|
|
2100 + (year as u16)
|
|
}
|
|
else {
|
|
2000 + (year as u16)
|
|
}
|
|
}
|
|
|
|
fn month_year_to_registers(month: u8, year: u16) -> (u8, u8) {
|
|
if year > 2099 {
|
|
let month = BitFlags::CENTURY | decimal_to_packed_bcd(month);
|
|
(month, decimal_to_packed_bcd((year - 2100) as u8))
|
|
}
|
|
else {
|
|
(decimal_to_packed_bcd(month), decimal_to_packed_bcd((year - 2000) as u8))
|
|
}
|
|
}
|
|
|
|
fn is_24h_format(hours_data: u8) -> bool {
|
|
hours_data & BitFlags::H24_H12 == 0
|
|
}
|
|
|
|
fn is_am(hours_data: u8) -> bool {
|
|
hours_data & BitFlags::AM_PM == 0
|
|
}
|
|
|
|
// Transforms a decimal number to packed BCD format
|
|
fn decimal_to_packed_bcd(dec: u8) -> u8 {
|
|
((dec / 10) << 4) | (dec % 10)
|
|
}
|
|
|
|
// Transforms a number in packed BCD format to decimal
|
|
fn packed_bcd_to_decimal(bcd: u8) -> u8 {
|
|
(bcd >> 4) * 10 + (bcd & 0xF)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_convert_packed_bcd_to_decimal() {
|
|
assert_eq!(0, packed_bcd_to_decimal(0b0000_0000));
|
|
assert_eq!(1, packed_bcd_to_decimal(0b0000_0001));
|
|
assert_eq!(9, packed_bcd_to_decimal(0b0000_1001));
|
|
assert_eq!(10, packed_bcd_to_decimal(0b0001_0000));
|
|
assert_eq!(11, packed_bcd_to_decimal(0b0001_0001));
|
|
assert_eq!(19, packed_bcd_to_decimal(0b0001_1001));
|
|
assert_eq!(20, packed_bcd_to_decimal(0b0010_0000));
|
|
assert_eq!(21, packed_bcd_to_decimal(0b0010_0001));
|
|
assert_eq!(59, packed_bcd_to_decimal(0b0101_1001));
|
|
}
|
|
|
|
#[test]
|
|
fn can_convert_decimal_to_packed_bcd() {
|
|
assert_eq!(0b0000_0000, decimal_to_packed_bcd( 0));
|
|
assert_eq!(0b0000_0001, decimal_to_packed_bcd( 1));
|
|
assert_eq!(0b0000_1001, decimal_to_packed_bcd( 9));
|
|
assert_eq!(0b0001_0000, decimal_to_packed_bcd(10));
|
|
assert_eq!(0b0001_0001, decimal_to_packed_bcd(11));
|
|
assert_eq!(0b0001_1001, decimal_to_packed_bcd(19));
|
|
assert_eq!(0b0010_0000, decimal_to_packed_bcd(20));
|
|
assert_eq!(0b0010_0001, decimal_to_packed_bcd(21));
|
|
assert_eq!(0b0101_1001, decimal_to_packed_bcd(59));
|
|
}
|
|
} |