[SC64][SW] Added support for 400 leap years for the RTC

This commit is contained in:
Mateusz Faderewski 2024-06-14 21:47:10 +02:00
parent 903efe5353
commit a571fe16f5
6 changed files with 282 additions and 167 deletions

View File

@ -329,17 +329,17 @@ _This command does not require arguments or data._
#### `response` (time)
| offset | type | description |
| ------ | ------- | ----------------------------------------- |
| ------ | ------- | --------------------------------------- |
| `0` | uint8_t | Weekday (1 - 7), 1 represents Monday |
| `1` | uint8_t | Hours (0 - 23) |
| `2` | uint8_t | Minutes (0 - 59) |
| `3` | uint8_t | Seconds (0 - 59) |
| `4` | uint8_t | Century (0 - 255), 0 represents year 1900 |
| `4` | uint8_t | Century (0 - 7), 0 represents year 1900 |
| `5` | uint8_t | Year (0 - 99) |
| `6` | uint8_t | Month (1 - 12) |
| `7` | uint8_t | Day (1 - 31) |
Date/time values use the [BCD](https://en.wikipedia.org/wiki/Binary-coded_decimal) format, except for the century field.
Date/time values use the [BCD](https://en.wikipedia.org/wiki/Binary-coded_decimal) format.
---
@ -357,15 +357,15 @@ Date/time values use the [BCD](https://en.wikipedia.org/wiki/Binary-coded_decima
#### `arg1` (time_1)
| bits | description |
| --------- | ----------------------------------------- |
| `[31:24]` | Century (0 - 255), 0 represents year 1900 |
| --------- | --------------------------------------- |
| `[31:24]` | Century (0 - 7), 0 represents year 1900 |
| `[23:16]` | Year (0 - 99) |
| `[15:8]` | Month (1 - 12) |
| `[7:0]` | Day (1 - 31) |
_This command does not send response data._
Date/time values use the [BCD](https://en.wikipedia.org/wiki/Binary-coded_decimal) format, except for the century field.
Date/time values use the [BCD](https://en.wikipedia.org/wiki/Binary-coded_decimal) format.
---

View File

@ -435,14 +435,14 @@ save_type_t cfg_get_save_type (void) {
}
void cfg_get_time (uint32_t *args) {
rtc_time_t t;
rtc_real_time_t t;
rtc_get_time(&t);
args[0] = ((t.weekday << 24) | (t.hour << 16) | (t.minute << 8) | t.second);
args[1] = ((t.century << 24) | (t.year << 16) | (t.month << 8) | t.day);
}
void cfg_set_time (uint32_t *args) {
rtc_time_t t;
rtc_real_time_t t;
t.second = (args[0] & 0xFF);
t.minute = ((args[0] >> 8) & 0xFF);
t.hour = ((args[0] >> 16) & 0xFF);

View File

@ -71,7 +71,7 @@ typedef struct {
struct process {
enum state state;
rtc_time_t time;
rtc_real_time_t time;
bool disk_spinning;
bool cmd_response_delayed;
bool bm_running;

View File

@ -14,14 +14,12 @@
#define RTC_ADDRESS_RTCDATE (0x04)
#define RTC_ADDRESS_RTCMTH (0x05)
#define RTC_ADDRESS_RTCYEAR (0x06)
#define RTC_ADDRESS_CONTROL (0x07)
#define RTC_ADDRESS_OSCTRIM (0x08)
#define RTC_ADDRESS_SRAM_MAGIC (0x20)
#define RTC_ADDRESS_SRAM_REGION (0x24)
#define RTC_ADDRESS_SRAM_CENTURY (0x25)
#define RTC_ADDRESS_SRAM_VERSION (0x28)
#define RTC_ADDRESS_SRAM_SETTINGS (0x2C)
#define RTC_ADDRESS_SRAM_CENTURY (0x40)
#define RTC_ADDRESS_SRAM_LAST_YEAR (0x41)
#define RTC_RTCSEC_ST (1 << 7)
@ -32,8 +30,28 @@
#define RTC_TIME_REFRESH_PERIOD_MS (500)
#define RTC_FROM_BCD(x) ((((((x) >> 4) & 0xF) % 10) * 10) + (((x) & 0xF) % 10))
#define RTC_TO_BCD(x) (((((x) / 10) % 10) << 4) | ((x) % 10))
static rtc_time_t rtc_time = {
typedef struct {
struct {
uint8_t second;
uint8_t minute;
uint8_t hour;
uint8_t weekday;
uint8_t day;
uint8_t month;
uint8_t year;
} time;
struct {
uint8_t value;
uint8_t last_year;
} century;
} rtc_raw_time_t;
static rtc_real_time_t rtc_time = {
.second = 0x00,
.minute = 0x00,
.hour = 0x12,
@ -43,25 +61,10 @@ static rtc_time_t rtc_time = {
.year = 0x24,
.century = 0x01,
};
static bool rtc_time_pending = false;
static uint8_t rtc_region = 0xFF;
static bool rtc_region_pending = false;
static rtc_settings_t rtc_settings = {
.led_enabled = true,
};
static bool rtc_settings_pending = false;
static const uint8_t rtc_regs_bit_mask[7] = {
0b01111111,
0b01111111,
0b00111111,
0b00000111,
0b00111111,
0b00011111,
0b11111111
};
static bool rtc_read (uint8_t address, uint8_t *data, uint8_t length) {
@ -89,86 +92,209 @@ static bool rtc_write (uint8_t address, uint8_t *data, uint8_t length) {
return false;
}
static void rtc_sanitize_time (uint8_t *regs) {
for (int i = 0; i < 7; i++) {
regs[i] &= rtc_regs_bit_mask[i];
}
}
static void rtc_osc_stop (void) {
uint8_t tmp = 0x00;
uint8_t tmp;
rtc_write(RTC_ADDRESS_RTCSEC, &tmp, 1);
rtc_read(RTC_ADDRESS_RTCSEC, &tmp, sizeof(tmp));
tmp &= ~(RTC_RTCSEC_ST);
rtc_write(RTC_ADDRESS_RTCSEC, &tmp, sizeof(tmp));
while ((!rtc_read(RTC_ADDRESS_RTCWKDAY, &tmp, 1)) && (tmp & RTC_RTCWKDAY_OSCRUN));
}
static void rtc_sanitize_raw_time (rtc_raw_time_t *raw) {
raw->time.second &= 0b01111111;
raw->time.minute &= 0b01111111;
raw->time.hour &= 0b00111111;
raw->time.weekday &= 0b00000111;
raw->time.day &= 0b00111111;
raw->time.month &= 0b00011111;
if (raw->time.weekday == 0) {
raw->time.weekday = 7;
}
raw->century.value &= 0b00000111;
}
static int8_t rtc_calculate_raw_day_offset (uint8_t century, uint8_t year, uint8_t month, uint8_t day, bool adjust_forward) {
int8_t raw_day_offset = ((century + 2) / 4);
if (((century % 4) == 1) && ((year > 0x00) || (month > 0x02) || ((month == 0x02) && (day >= (0x29 - raw_day_offset))))) {
raw_day_offset += 1;
}
return adjust_forward ? raw_day_offset : (-raw_day_offset);
}
static uint8_t rtc_get_days_in_month (uint8_t century, uint8_t year, uint8_t month) {
const uint8_t DAYS_IN_MONTH[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
if (month < 1 || month > 12) {
month = (((month - 1) % 12) + 1);
}
uint8_t days = DAYS_IN_MONTH[month - 1];
if (month == 2) {
if (((century % 4) == 1) || ((year > 0) && ((year % 4) == 0))) {
days += 1;
}
}
return days;
}
static void rtc_adjust_date (uint8_t *century, uint8_t *year, uint8_t *month, uint8_t *day, int8_t day_offset) {
if (day_offset == 0) {
return;
}
int8_t dec_century = RTC_FROM_BCD(*century);
int8_t dec_year = RTC_FROM_BCD(*year);
int8_t dec_month = RTC_FROM_BCD(*month);
int8_t dec_day = RTC_FROM_BCD(*day);
int8_t century_offset = 0;
int8_t year_offset = 0;
int8_t month_offset = 0;
uint8_t current_month_days = rtc_get_days_in_month(dec_century, dec_year, dec_month);
dec_day += day_offset;
if (dec_day < 1) {
month_offset = -1;
} else if (dec_day > current_month_days) {
dec_day %= current_month_days;
month_offset = 1;
}
dec_month += month_offset;
if (dec_month < 1) {
dec_month = 12;
year_offset = -1;
} else if (dec_month > 12) {
dec_month = 1;
year_offset = 1;
}
*month = RTC_TO_BCD(dec_month);
dec_year += year_offset;
if (dec_year < 0) {
dec_year = 99;
century_offset = -1;
} else if (dec_year > 99) {
dec_year = 0;
century_offset = 1;
}
*year = RTC_TO_BCD(dec_year);
dec_century += century_offset;
if (dec_century < 0) {
dec_century = 7;
} else if (dec_century > 7) {
dec_century = 0;
}
*century = RTC_TO_BCD(dec_century);
if (dec_day <= 0) {
dec_day = rtc_get_days_in_month(dec_century, dec_year, dec_month) + dec_day;
}
*day = RTC_TO_BCD(dec_day);
}
static void rtc_raw_to_real (rtc_raw_time_t *raw, rtc_real_time_t *real) {
real->century = raw->century.value;
real->year = raw->time.year;
real->month = raw->time.month;
real->day = raw->time.day;
real->weekday = raw->time.weekday;
real->hour = raw->time.hour;
real->minute = raw->time.minute;
real->second = raw->time.second;
int8_t day_offset = rtc_calculate_raw_day_offset(real->century, real->year, real->month, real->day, false);
rtc_adjust_date(&real->century, &real->year, &real->month, &real->day, day_offset);
}
static void rtc_real_to_raw (rtc_raw_time_t *raw, rtc_real_time_t *real) {
raw->century.value = real->century;
raw->time.year = real->year;
raw->time.month = real->month;
raw->time.day = real->day;
raw->time.weekday = real->weekday;
raw->time.hour = real->hour;
raw->time.minute = real->minute;
raw->time.second = real->second;
int8_t day_offset = rtc_calculate_raw_day_offset(raw->century.value, raw->time.year, raw->time.month, raw->time.day, true);
rtc_adjust_date(&raw->century.value, &raw->time.year, &raw->time.month, &raw->time.day, day_offset);
}
static void rtc_read_time (void) {
uint8_t regs[7];
uint8_t last_year;
rtc_raw_time_t raw;
bool update_raw_century = false;
if (rtc_read(RTC_ADDRESS_RTCSEC, regs, 7)) {
return;
}
if (rtc_read(RTC_ADDRESS_SRAM_CENTURY, &rtc_time.century, 1)) {
return;
}
if (rtc_read(RTC_ADDRESS_SRAM_LAST_YEAR, &last_year, 1)) {
if (rtc_read(RTC_ADDRESS_RTCSEC, (uint8_t *) (&raw.time), sizeof(raw.time))) {
return;
}
rtc_sanitize_time(regs);
rtc_time.second = regs[0];
rtc_time.minute = regs[1];
rtc_time.hour = regs[2];
rtc_time.weekday = regs[3];
rtc_time.day = regs[4];
rtc_time.month = regs[5];
rtc_time.year = regs[6];
if (rtc_time.year < last_year) {
rtc_time.century += 1;
rtc_write(RTC_ADDRESS_SRAM_CENTURY, &rtc_time.century, 1);
if (rtc_read(RTC_ADDRESS_SRAM_CENTURY, (uint8_t *) (&raw.century), sizeof(raw.century))) {
return;
}
if (rtc_time.year != last_year) {
rtc_write(RTC_ADDRESS_SRAM_LAST_YEAR, &rtc_time.year, 1);
rtc_sanitize_raw_time(&raw);
if (raw.time.year < raw.century.last_year) {
raw.century.value += 1;
update_raw_century = true;
}
if (raw.time.year != raw.century.last_year) {
raw.century.last_year = raw.time.year;
update_raw_century = true;
}
if (update_raw_century) {
rtc_write(RTC_ADDRESS_SRAM_CENTURY, (uint8_t *) (&raw.century), sizeof(raw.century));
}
rtc_raw_to_real(&raw, &rtc_time);
}
static void rtc_write_time (void) {
uint8_t regs[7];
rtc_raw_time_t raw;
uint8_t raw_regs[7];
rtc_real_to_raw(&raw, &rtc_time);
rtc_sanitize_raw_time(&raw);
raw_regs[0] = (raw.time.second | RTC_RTCSEC_ST);
raw_regs[1] = raw.time.minute;
raw_regs[2] = raw.time.hour;
raw_regs[3] = (raw.time.weekday | RTC_RTCWKDAY_VBATEN);
raw_regs[4] = raw.time.day;
raw_regs[5] = raw.time.month;
raw_regs[6] = raw.time.year;
raw.century.last_year = raw.time.year;
rtc_osc_stop();
regs[0] = rtc_time.second;
regs[1] = rtc_time.minute;
regs[2] = rtc_time.hour;
regs[3] = rtc_time.weekday;
regs[4] = rtc_time.day;
regs[5] = rtc_time.month;
regs[6] = rtc_time.year;
rtc_sanitize_time(regs);
regs[0] |= RTC_RTCSEC_ST;
regs[3] |= RTC_RTCWKDAY_VBATEN;
rtc_write(RTC_ADDRESS_SRAM_CENTURY, &rtc_time.century, 1);
rtc_write(RTC_ADDRESS_SRAM_LAST_YEAR, &rtc_time.year, 1);
rtc_write(RTC_ADDRESS_RTCMIN, &regs[1], 6);
rtc_write(RTC_ADDRESS_RTCSEC, &regs[0], 1);
rtc_read_time();
rtc_write(RTC_ADDRESS_SRAM_CENTURY, (uint8_t *) (&raw.century), sizeof(raw.century));
rtc_write(RTC_ADDRESS_RTCMIN, &raw_regs[1], sizeof(raw_regs) - sizeof(raw_regs[0]));
rtc_write(RTC_ADDRESS_RTCSEC, &raw_regs[0], sizeof(raw_regs[0]));
}
static void rtc_read_region (void) {
rtc_read(RTC_ADDRESS_SRAM_REGION, &rtc_region, 1);
rtc_read(RTC_ADDRESS_SRAM_REGION, &rtc_region, sizeof(rtc_region));
}
static void rtc_write_region (void) {
rtc_write(RTC_ADDRESS_SRAM_REGION, &rtc_region, 1);
rtc_write(RTC_ADDRESS_SRAM_REGION, &rtc_region, sizeof(rtc_region));
}
static void rtc_read_settings (void) {
@ -179,8 +305,41 @@ static void rtc_write_settings (void) {
rtc_write(RTC_ADDRESS_SRAM_SETTINGS, (uint8_t *) (&rtc_settings), sizeof(rtc_settings));
}
static void rtc_read_joybus_time (void) {
uint32_t time[2];
void rtc_get_time (rtc_time_t *time) {
time[0] = fpga_reg_get(REG_RTC_TIME_0);
time[1] = fpga_reg_get(REG_RTC_TIME_1);
rtc_time.weekday = ((time[0] >> 24) & 0xFF) + 1;
rtc_time.hour = ((time[0] >> 16) & 0xFF);
rtc_time.minute = ((time[0] >> 8) & 0xFF);
rtc_time.second = ((time[0] >> 0) & 0xFF);
rtc_time.century = ((time[1] >> 24) & 0xFF);
rtc_time.year = ((time[1] >> 16) & 0xFF);
rtc_time.month = ((time[1] >> 8) & 0xFF);
rtc_time.day = ((time[1] >> 0) & 0xFF);
}
static void rtc_write_joybus_time (void) {
uint32_t time[2] = {(
((rtc_time.weekday - 1) << 24) |
(rtc_time.hour << 16) |
(rtc_time.minute << 8) |
(rtc_time.second << 0)
), (
(rtc_time.century << 24) |
(rtc_time.year << 16) |
(rtc_time.month << 8) |
(rtc_time.day << 0)
)};
fpga_reg_set(REG_RTC_TIME_0, time[0]);
fpga_reg_set(REG_RTC_TIME_1, time[1]);
}
void rtc_get_time (rtc_real_time_t *time) {
time->second = rtc_time.second;
time->minute = rtc_time.minute;
time->hour = rtc_time.hour;
@ -191,7 +350,7 @@ void rtc_get_time (rtc_time_t *time) {
time->century = rtc_time.century;
}
void rtc_set_time (rtc_time_t *time) {
void rtc_set_time (rtc_real_time_t *time) {
rtc_time.second = time->second;
rtc_time.minute = time->minute;
rtc_time.hour = time->hour;
@ -200,7 +359,8 @@ void rtc_set_time (rtc_time_t *time) {
rtc_time.month = time->month;
rtc_time.year = time->year;
rtc_time.century = time->century;
rtc_time_pending = true;
rtc_write_time();
rtc_read_time();
}
@ -210,7 +370,7 @@ uint8_t rtc_get_region (void) {
void rtc_set_region (uint8_t region) {
rtc_region = region;
rtc_region_pending = true;
rtc_write_region();
}
@ -219,23 +379,25 @@ rtc_settings_t *rtc_get_settings (void) {
}
void rtc_save_settings (void) {
rtc_settings_pending = true;
rtc_write_settings();
}
void rtc_init (void) {
bool uninitialized = false;
const char *magic = "SC64";
uint8_t buffer[4];
const char *magic = "RTC1";
uint8_t buffer[sizeof(magic)];
uint8_t osc_trim = 0;
uint32_t settings_version;
for (int i = 0; i < 4; i++) {
for (int i = 0; i < sizeof(buffer) / sizeof(buffer[0]); i++) {
buffer[i] = 0;
}
rtc_read(RTC_ADDRESS_SRAM_MAGIC, buffer, 4);
rtc_read(RTC_ADDRESS_SRAM_MAGIC, buffer, sizeof(buffer));
rtc_read(RTC_ADDRESS_SRAM_VERSION, (uint8_t *) (&settings_version), sizeof(settings_version));
for (int i = 0; i < 4; i++) {
for (int i = 0; i < sizeof(magic); i++) {
if (buffer[i] != magic[i]) {
uninitialized = true;
break;
@ -243,19 +405,16 @@ void rtc_init (void) {
}
if (uninitialized) {
buffer[0] = 0;
rtc_write(RTC_ADDRESS_SRAM_MAGIC, (uint8_t *) (magic), 4);
rtc_write(RTC_ADDRESS_OSCTRIM, buffer, 1);
rtc_write(RTC_ADDRESS_OSCTRIM, &osc_trim, sizeof(osc_trim));
rtc_write_time();
rtc_write_region();
rtc_write(RTC_ADDRESS_SRAM_MAGIC, (uint8_t *) (magic), sizeof(magic));
}
rtc_read(RTC_ADDRESS_SRAM_VERSION, (uint8_t *) (&settings_version), 4);
if (uninitialized || (settings_version != RTC_SETTINGS_VERSION)) {
settings_version = RTC_SETTINGS_VERSION;
rtc_write(RTC_ADDRESS_SRAM_VERSION, (uint8_t *) (&settings_version), 4);
rtc_write_settings();
rtc_write(RTC_ADDRESS_SRAM_VERSION, (uint8_t *) (&settings_version), sizeof(settings_version));
}
rtc_read_time();
@ -270,62 +429,16 @@ void rtc_process (void) {
uint32_t scr = fpga_reg_get(REG_RTC_SCR);
if ((scr & RTC_SCR_PENDING) && ((scr & RTC_SCR_MAGIC_MASK) == RTC_SCR_MAGIC)) {
uint32_t data[2];
data[0] = fpga_reg_get(REG_RTC_TIME_0);
data[1] = fpga_reg_get(REG_RTC_TIME_1);
rtc_time.weekday = ((data[0] >> 24) & 0xFF) + 1;
rtc_time.hour = ((data[0] >> 16) & 0xFF);
rtc_time.minute = ((data[0] >> 8) & 0xFF);
rtc_time.second = ((data[0] >> 0) & 0xFF);
rtc_time.century = ((data[1] >> 24) & 0xFF);
rtc_time.year = ((data[1] >> 16) & 0xFF);
rtc_time.month = ((data[1] >> 8) & 0xFF);
rtc_time.day = ((data[1] >> 0) & 0xFF);
rtc_time_pending = true;
fpga_reg_set(REG_RTC_TIME_0, data[0]);
fpga_reg_set(REG_RTC_TIME_1, data[1]);
fpga_reg_set(REG_RTC_SCR, RTC_SCR_DONE);
}
if (rtc_time_pending) {
rtc_time_pending = false;
rtc_read_joybus_time();
rtc_write_time();
}
if (rtc_region_pending) {
rtc_region_pending = false;
rtc_write_region();
}
if (rtc_settings_pending) {
rtc_settings_pending = false;
rtc_write_settings();
rtc_read_time();
rtc_write_joybus_time();
fpga_reg_set(REG_RTC_SCR, RTC_SCR_DONE);
}
if (timer_countdown_elapsed(TIMER_ID_RTC)) {
timer_countdown_start(TIMER_ID_RTC, RTC_TIME_REFRESH_PERIOD_MS);
rtc_read_time();
uint32_t data[2];
data[0] = (
((rtc_time.weekday - 1) << 24) |
(rtc_time.hour << 16) |
(rtc_time.minute << 8) |
(rtc_time.second << 0)
);
data[1] = (
(rtc_time.century << 24) |
(rtc_time.year << 16) |
(rtc_time.month << 8) |
(rtc_time.day << 0)
);
fpga_reg_set(REG_RTC_TIME_0, data[0]);
fpga_reg_set(REG_RTC_TIME_1, data[1]);
rtc_write_joybus_time();
}
}

View File

@ -15,15 +15,15 @@ typedef struct {
uint8_t month;
uint8_t year;
uint8_t century;
} rtc_time_t;
} rtc_real_time_t;
typedef struct {
bool led_enabled;
} rtc_settings_t;
void rtc_get_time (rtc_time_t *time);
void rtc_set_time (rtc_time_t *time);
void rtc_get_time (rtc_real_time_t *time);
void rtc_set_time (rtc_real_time_t *time);
uint8_t rtc_get_region (void);
void rtc_set_region (uint8_t region);

View File

@ -15,12 +15,14 @@ pub fn convert_to_datetime(data: &[u8; 8]) -> Result<NaiveDateTime, Error> {
let hour = u8_from_bcd(data[1]);
let day = u8_from_bcd(data[7]);
let month = u8_from_bcd(data[6]);
let year = 1900u32 + (data[4] as u32 * 100) + u8_from_bcd(data[5]) as u32;
let year = 1900u32 + (u8_from_bcd(data[4]) as u32 * 100) + u8_from_bcd(data[5]) as u32;
NaiveDateTime::parse_from_str(
&format!("{year:4}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}"),
"%Y-%m-%dT%H:%M:%S",
)
.map_err(|_| Error::new("Couldn't convert from bytes to NaiveDateTime"))
.map_err(|e| {
Error::new(format!("Couldn't convert time from bytes to NaiveDateTime: {e}").as_str())
})
}
pub fn convert_from_datetime(datetime: NaiveDateTime) -> [u32; 2] {
@ -31,7 +33,7 @@ pub fn convert_from_datetime(datetime: NaiveDateTime) -> [u32; 2] {
let day = bcd_from_u8(datetime.day() as u8);
let month = bcd_from_u8(datetime.month() as u8);
let year = bcd_from_u8((datetime.year() % 100) as u8);
let century = ((datetime.year() - 1900) / 100) as u8;
let century = bcd_from_u8(((datetime.year() - 1900) / 100) as u8);
[
u32::from_be_bytes([weekday, hour, minute, second]),
u32::from_be_bytes([century, year, month, day]),