2023-12-07 03:56:09 +00:00
|
|
|
|
//
|
|
|
|
|
// RTC.hpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 06/12/2023.
|
|
|
|
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#ifndef RTC_h
|
|
|
|
|
#define RTC_h
|
|
|
|
|
|
2023-12-07 04:25:34 +00:00
|
|
|
|
#include <ctime>
|
|
|
|
|
|
2023-12-07 03:56:09 +00:00
|
|
|
|
namespace PCCompatible {
|
|
|
|
|
|
|
|
|
|
class RTC {
|
|
|
|
|
public:
|
|
|
|
|
template <int address>
|
|
|
|
|
void write(uint8_t value) {
|
|
|
|
|
switch(address) {
|
|
|
|
|
default: break;
|
|
|
|
|
case 0:
|
|
|
|
|
selected_ = value & 0x7f;
|
|
|
|
|
// NMI not yet supported.
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
2023-12-07 04:25:34 +00:00
|
|
|
|
write_register(value);
|
2023-12-07 03:56:09 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t read() {
|
2023-12-07 04:25:34 +00:00
|
|
|
|
std::time_t now = std::time(NULL);
|
|
|
|
|
std::tm *time_date = std::localtime(&now);
|
|
|
|
|
|
|
|
|
|
switch(selected_) {
|
|
|
|
|
default:
|
2023-12-25 19:35:24 +00:00
|
|
|
|
if(ram_selected()) {
|
|
|
|
|
return ram_[ram_address()];
|
|
|
|
|
}
|
2023-12-07 04:25:34 +00:00
|
|
|
|
return 0xff;
|
|
|
|
|
|
|
|
|
|
case 0x00: return bcd(time_date->tm_sec); // Seconds [0-59]
|
|
|
|
|
case 0x01: return 0; // Seconds alarm
|
|
|
|
|
case 0x02: return bcd(time_date->tm_min); // Minutes [0-59]
|
|
|
|
|
case 0x03: return 0; // Minutes alarm
|
|
|
|
|
case 0x04:
|
|
|
|
|
// Hours [1-12 or 0-23]
|
2023-12-24 22:37:52 +00:00
|
|
|
|
if(is_24hour()) {
|
2023-12-07 04:25:34 +00:00
|
|
|
|
return bcd(time_date->tm_hour);
|
|
|
|
|
}
|
2023-12-07 04:28:16 +00:00
|
|
|
|
return
|
2023-12-24 22:37:52 +00:00
|
|
|
|
((time_date->tm_hour >= 12) ? 0x80 : 0x00) |
|
2023-12-07 04:28:16 +00:00
|
|
|
|
bcd(1 + (time_date->tm_hour + 11)%12);
|
2023-12-07 04:25:34 +00:00
|
|
|
|
break;
|
|
|
|
|
case 0x05: return 0; // Hours alarm
|
|
|
|
|
case 0x06: return bcd(time_date->tm_wday + 1); // Day of the week [Sunday = 1]
|
2023-12-11 03:44:08 +00:00
|
|
|
|
case 0x07: return bcd(time_date->tm_mday); // Date of the Month [1-31]
|
2023-12-07 04:25:34 +00:00
|
|
|
|
case 0x08: return bcd(time_date->tm_mon + 1); // Month [1-12]
|
|
|
|
|
case 0x09: return bcd(time_date->tm_year % 100); // Year [0-99]
|
|
|
|
|
case 0x32: return bcd(19 + time_date->tm_year / 100); // Century
|
|
|
|
|
|
2023-12-25 19:58:01 +00:00
|
|
|
|
case 0x0a: return statusA_ & 0x7f; // Exclude the update-in-progress bit.
|
2023-12-07 04:25:34 +00:00
|
|
|
|
case 0x0b: return statusB_;
|
|
|
|
|
}
|
2023-12-07 03:56:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2023-12-25 19:35:24 +00:00
|
|
|
|
std::size_t selected_;
|
|
|
|
|
std::array<uint8_t, 50> ram_{};
|
2023-12-07 04:25:34 +00:00
|
|
|
|
|
|
|
|
|
uint8_t statusA_ = 0x00;
|
2023-12-24 22:37:52 +00:00
|
|
|
|
uint8_t statusB_ = 0x02;
|
|
|
|
|
|
2023-12-25 19:58:01 +00:00
|
|
|
|
// Status A.
|
|
|
|
|
// b7: update-in-progress.
|
|
|
|
|
// b6–b4: selects condition of the divider chain (?);
|
|
|
|
|
// b3–b0: selects rate of the divider chain.
|
|
|
|
|
|
|
|
|
|
// Status B.
|
|
|
|
|
bool disable_updates() const { return statusB_ & 0x80; }
|
|
|
|
|
bool periodic_interrupt_enabled() const { return statusB_ & 0x40; }
|
|
|
|
|
bool alarm_interrupt_enabled() const { return statusB_ & 0x20; }
|
|
|
|
|
bool update_ended_interrupt_enabled() const { return statusB_ & 0x10; }
|
|
|
|
|
bool square_wave_enabled() const { return statusB_ & 0x08; }
|
|
|
|
|
bool is_decimal() const { return statusB_ & 0x04; }
|
|
|
|
|
bool is_24hour() const { return statusB_ & 0x02; }
|
|
|
|
|
bool daylight_savings_enabled() const { return statusB_ & 0x01; }
|
|
|
|
|
|
|
|
|
|
// Helpers for differentiating RAM accesses from the more meaningful registers.
|
2023-12-25 19:35:24 +00:00
|
|
|
|
bool ram_selected() const { return selected_ >= 0xe && selected_ < 0xe + ram_.size(); }
|
|
|
|
|
std::size_t ram_address() const { return selected_ - 0xe; }
|
|
|
|
|
|
2023-12-25 19:58:01 +00:00
|
|
|
|
/// Converts @c input to BCD if BCD mode is enabled; otherwise returns @c input unaltered.
|
2023-12-07 04:25:34 +00:00
|
|
|
|
template <typename IntT>
|
|
|
|
|
uint8_t bcd(IntT input) {
|
|
|
|
|
// If calendar is in binary format, don't convert.
|
2023-12-24 22:37:52 +00:00
|
|
|
|
if(is_decimal()) {
|
2023-12-07 04:25:34 +00:00
|
|
|
|
return uint8_t(input);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert a one or two digit number to BCD.
|
|
|
|
|
return uint8_t(
|
|
|
|
|
(input % 10) +
|
|
|
|
|
((input / 10) * 16)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-25 19:58:01 +00:00
|
|
|
|
/// Writes @c value to the register @c selected_ .
|
2023-12-07 04:25:34 +00:00
|
|
|
|
void write_register(uint8_t value) {
|
|
|
|
|
switch(selected_) {
|
2023-12-25 19:35:24 +00:00
|
|
|
|
default:
|
|
|
|
|
if(ram_selected()) {
|
|
|
|
|
ram_[ram_address()] = value;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-12-07 04:25:34 +00:00
|
|
|
|
case 0x0a: statusA_ = value; break;
|
2023-12-24 22:37:52 +00:00
|
|
|
|
case 0x0b: statusB_ = value; break;
|
2023-12-07 04:25:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-07 03:56:09 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* RTC_h */
|