libdragon
Loading...
Searching...
No Matches
Files | Data Structures | Functions
Real-Time Clock Subsystem

Real-time clock interface. More...

Files

file  rtc.c
 Real-Time Clock Subsystem.
 
file  rtc.h
 Real-Time Clock Subsystem.
 

Data Structures

struct  rtc_time_t
 Structure for storing RTC time data. More...
 

Functions

bool rtc_init (void)
 High-level convenience helper to initialize the RTC subsystem.
 
void rtc_close (void)
 Close the RTC Subsystem, disabling system hooks.
 
bool rtc_is_writable (void)
 Determine whether the RTC supports writing the time.
 
bool rtc_get (rtc_time_t *rtc_time)
 Read the current date/time from the real-time clock.
 
bool rtc_set (rtc_time_t *rtc_time)
 High-level convenience helper to set the RTC date/time.
 
void rtc_normalize_time (rtc_time_t *rtc_time)
 Calculate sane values for arbitrary time inputs.
 

Detailed Description

Real-time clock interface.

Author
Christopher Bonhage

The Joybus real-time clock is a cartridge peripheral that uses a battery to power a clock that tracks the date, time, and day of the week. The real-time clock keeps running even when the N64 is powered-off. The Joybus RTC is accessed through the serial interface (SI) similar to EEPROM and controllers. The Joybus RTC was only ever available on one official cartridge that was only available in Japan: Dōbutsu no Mori (Animal Forest). Many emulators and flash carts include support for the Animal Forest RTC, which makes it possible to include real-time clock functionality in homebrew! There is also a real-time clock included in the N64DD hardware, which uses a different interface and is not currently supported by libdragon.

To check if the real-time clock is available, call rtc_init. To read the current time from the real-time clock, call rtc_get. Once the RTC subsystem is initialized, you can also use ISO C Time functions to get the current time, for example: time(NULL) will return the number of seconds elapsed since the UNIX epoch (January 1, 1970 at 00:00:00). To check if the real-time clock supports writes, call rtc_is_writable. To write a new time to the real-time clock, call rtc_set.

This subsystem handles decoding and encoding the date/time from its internal format into a struct called rtc_time_t, which contains integer values for year, month, day-of-month, day-of-week, hour, minute, and second.

The Joybus RTC contains 3 "blocks" (or zones) which contain 8 bytes of data: Block 0 contains a half-word control register and opaque calibration data. Block 1 is unused and unsupported. See notes below. Block 2 contains the current date/time as packed binary-coded decimal.

Animal Forest did not use block 1 at all, so most emulators do not bother to implement it. Theoretically, block 1 could be used as 8-bytes of SRAM-backed storage, but this is not supported by libdragon's Real-Time Clock Subsystem. If you need storage, consider using a standard cartridge save type or saving to a Controller Pak.

(As of July 2021) Joybus RTC does not work in combination with any EEPROM save type on EverDrive64 3.0 or X7. To have the best compatibility and player experience, it is not recommended to use the EEPROM + RTC ROM configuration. This is a bug in the EverDrive64 firmware and not a system limitation imposed by the Joybus protocol or Serial Interface.

Unfortunately, since only one game ever used Joybus RTC (and that game was later re-released on the GameCube in English), real-time clock support in emulators and flash carts can be incomplete, inaccurate, or non-existent. Many emulators do not actually implement the Joybus RTC write command and always respond with the host device's current local time. Some emulators and flash carts support writing to RTC but will not persist the date/time after resetting or powering-off. You can run the rtctest example ROM on your preferred emulator or flash cart to what RTC support is available.

The only reliable way to check if writes are actually supported is to write a time to the RTC and read the time back out. Many emulators that do support RTC reads will silently ignore RTC writes. You should detect whether writes are supported using rtc_is_writable so that you can conditionally show the option to change the time if it's supported. If the RTC supports writes, it is safe to call rtc_set to set the date and time.

Due to the inaccurate and inconsistent behavior of RTC reproductions that currently exist, this subsystem trades-off complete accuracy with the actual Animal Forest RTC in favor of broader compatibility with the various quirks and bugs that exist in real-world scenarios like emulators and flash carts.

Some notable examples of RTC support in the ecosystem (as of July 2021):

64drive hw2 fully implements Joybus RTC including writes, but requires delays after setting the time (see JOYBUS_RTC_WRITE_FINISHED_DELAY).

EverDrive64 3.0 and X7 partially support Joybus RTC, with caveats: The RTC must be explicitly enabled in the OS or with a ROM header configuration; RTC will not be detected if the EEPROM save type is used; RTC writes are not supported through the SI, so changing the time must be done in the OS.

UltraPIF fully implements an emulated Joybus RTC that can be accessed even when the cartridge does not include the real-time clock circuitry.

Special thanks to marshallh and jago85 for their hard work and research reverse-engineering and documenting the inner-workings of the Joybus RTC.


Data Structure Documentation

◆ rtc_time_t

struct rtc_time_t

Structure for storing RTC time data.

Data Fields
uint16_t year Year. [1900-20XX].
uint8_t month Month. [0-11].
uint8_t day Day of month. [1-31].
uint8_t hour Hours. [0-23].
uint8_t min Minutes. [0-59].
uint8_t sec Seconds. [0-59].
uint8_t week_day Day of week. [0-6] (Sun-Sat)

Function Documentation

◆ rtc_init()

bool rtc_init ( void  )

High-level convenience helper to initialize the RTC subsystem.

The RTC Subsystem depends on the libdragon Timer Subsystem, so make sure to call timer_init before calling rtc_init!

Some flash carts require the RTC to be explicitly enabled before loading the ROM file. Some emulators and flash carts do not support RTC at all.

This function will detect if the RTC is available and if so, will prepare the RTC so that the current time can be read from it.

This operation may take up to 50 milliseconds to complete.

This will also hook the RTC into the newlib gettimeofday function, so you will be able to use the ISO C time functions if RTC is available.

Returns
whether the RTC is present and supported by the RTC Subsystem.

◆ rtc_close()

void rtc_close ( void  )

Close the RTC Subsystem, disabling system hooks.

Unhooks the RTC from the newlib gettimeofday function. This will cause subsequent calls to gettimeofday to error with ENOSYS.

The only reason you should ever need to call this is if you need to stop the Timer Subsystem, which the RTC Subsystem depends on. If you do need to do this, make sure to call rtc_close BEFORE timer_close and then call rtc_init again after you call timer_init to restart the Timer Subsystem!

◆ rtc_is_writable()

bool rtc_is_writable ( void  )

Determine whether the RTC supports writing the time.

Some emulators and flash carts do not support writing to the RTC, so this function makes an attempt to detect silent write failures and will return false if it is unable to change the time on the RTC.

This function is useful if your program wants to conditionally offer the ability to set the time based on hardware/emulator support.

Unfortunately this operation may introduce a slight drift in the clock, but it is the only way to determine if the RTC supports the write command.

This operation will take approximately 1 second to complete.

Returns
whether RTC writes appear to be supported

◆ rtc_get()

bool rtc_get ( rtc_time_t rtc_time)

Read the current date/time from the real-time clock.

If the RTC is not detected or supported, this function will not modify the destination rtc_time parameter.

Your code should call this once per frame to update the rtc_time_t data structure. The RTC Subsystem maintains a cache of the most-recent RTC time that was read and will only perform an actual RTC read command if the cache is invalidated. The destination rtc_time parameter will be updated regardless of the cache validity.

Cache will invalidate every RTC_GET_CACHE_INVALIDATE_TICKS. Calling rtc_set will also invalidate the cache.

If an actual RTC read command is needed, this function can take a few milliseconds to complete.

Parameters
[out]rtc_timeDestination pointer for the RTC time data structure
Returns
whether the rtc_time destination pointer data was modified

◆ rtc_set()

bool rtc_set ( rtc_time_t rtc_time)

High-level convenience helper to set the RTC date/time.

Prepares the RTC for writing, sets the new time, and resumes the clock.

This function will take approximately 570 milliseconds to complete.

Unfortunately, the best way to ensure that writes to the RTC have actually finished is by waiting for a fixed duration. Emulators may not accurately reflect this, but this delay is necessary on real hardware.

Parameters
[in]rtc_timeSource pointer for the RTC time data structure
Returns
false if the RTC does not support being set

◆ rtc_normalize_time()

void rtc_normalize_time ( rtc_time_t rtc_time)

Calculate sane values for arbitrary time inputs.

If your time inputs are already sane, nothing should change. This function will clamp date/time values within the expected ranges, including the correct day-of-month based on year/month. It will also recalculate the day-of-week based on the clamped year/month/day.

This is useful to call while the player is adjusting the time after each input to ensure that the date being set always makes sense before they actually confirm and commit the updated date/time. The rtctest example demonstrates a user-interface for setting the time with live validation.

Internally, RTC cannot represent dates before 1990-01-01, although some RTC implementations (like UltraPIF) only support dates after 2000-01-01.

For highest compatibility, it is not recommended to set the date past 2038-01-19 03:14:07 UTC, which is the UNIX timestamp Epochalypse.

Special thanks to networkfusion for providing the algorithm to calculate day-of-week from an arbitrary date.

Parameters
[in,out]rtc_timePointer to the RTC time data structure