The realtime clock (RTC) was introduced in this log. There was mention of instructions to get the time in a more convenient format. That code was written about a year ago, but has only just been integrated as various loose ends are being tied up.
The RTC counters are reset on power up or cold boot. The clock then keeps track of time with an accuracy of around 14ppm. The first application was to add an UPTIME command to the monitor. This displays the time since power up (cold boot) in the format DDD HH:MM:SS
Clock Division
The hardware abstraction layer (HAL) divides down the 8.25MHz CPU clock by a total of 182,474,244,096,000. This is done via the following steps:
- Osc: 33.000MHz (+/- 100ppm)
- CPU (÷4) 8.25MHz
- HAL (÷43) 191.86kHz
- Line (÷5 or ÷4) 38.372kHz or 47.965kHz (depending on video mode)
- Block (÷4 or ÷5) 9.593kHz
- Frame (÷128 or ÷160) 74.9455Hz or 59.9564Hz (depending on video mode)
- Tick (÷5 or ÷4) 14.9891Hz
- TIME0 (÷90*) count to 6.00436 secs (*adjust TIME0 by -1 every 15th TIME1 count)
- TIME1 (÷120) count to 719.990 secs or 11.9998 mins
- TIME2 (÷120) count to 1439.98 mins, 23.9997 hrs, or 0.999986 days
- TIME3 (÷256) count to 256 days, 36.57 weeks, 8.4 months, or 0.7 years
Real-Time Clock
The realtime clock consists of the four bytes: TIME0-TIME3. As shown above, these don't count typical increments like seconds, minutes, hours etc. They are optimized for 7-bit arithmetic and the ability to update everything within one additional virtual-machine cycle. These counters are updated at the following rates:
- TIME0 - 15 ticks per second
- TIME1 - 10 ticks per minute
- TIME2 - 5 ticks per hour
- TIME3 - 1 tick per day
One other thing to note. The counts for TIME0-TIME2 are negative and count up to zero. This is done to simplify the overflow condition and just test the sign bit. The final count TIME3 is the only positive count and represents the number of complete days the machine has been on.
Calculating Time
A general formula can be used to calculate the actual time unit:
- UNIT[n] = (TIME[n+1] % 10) * max + TIME[n] / ticks
Where max is the highest value of that unit held by the counter and ticks are the ticks per unit. This leads to the following formulas for the standard time units:
- SECONDS = (TIME1 % 10) * 6 + TIME0 / 15
- MINUTES = (TIME2 % 10) * 12 + TIME1 / 10
- HOURS = TIME2 / 5
- DAYS = TIME3
However, these formulas assume the counts are positive numbers. In this case they are not and this type of math will not work.
Modular Arithmetic
There are two approaches to dealing with the negative counts: convert the numbers to their positive versions, or use the negative versions and then convert the result. An important observation here is the fact the results will be sexagesimal. This allows the use of modular or clock arithmetic and favors the latter method.
The counts are considered to be positive 8-bit integers and adjusted with a final offset to wrap to the correct sexagesimal number using a MOD60 operation:
- SECONDS = MOD60 (13 + (|TIME1| % 10) * 6 + |TIME0 - 1| / 15)
- MINUTES = MOD60 (35 + (|TIME2| % 10) * 12 + |TIME1 - 6| / 10)
- HOURS = MOD60 (|TIME2 + 120| / 5)
- DAYS = TIME3
This may look more complex, but it can be efficiently coded to fit in a single 2-cycle extended instruction (utilizing a jump page in the 0xED range of instructions). The MOD60 unary function is also used to convert the result to binary-coded decimal (BCD), or more specifically, binary-coded sexagesimal.
Sexagesimal Adjust Accumulator
The reason for converting the result to BCD is the typical use case where the time displayed as a timestamp. The realtime clock is reset to zero on cold boot and the current time can be calculated by adding the time the machine started to the current uptime tracked by the realtime clock.
This requires the addition of two BCD numbers and is possible with the use of the Decimal Adjust Accumulator (DAA) instruction. This will adjust the result back to BCD after the addition, along with setting the carry from the 100's column.
The same technique can be used to add times with a Sexagesimal Adjust Accumulator (SAA) instruction. This is similar to DAA except the carry is generated when the most significant digit is greater than 5 rather than 9. The result is mod-60 rather than mod-100.
Kernel Timers
One additional use of the realtime clock is to addition of timers to the kernel. These can be programmed to trigger at some point in the future based on a number of ticks. The concept is fairly simple: Each time the kernel main loop runs it will check to see if TIME0 has changed. If it has then the kernel will check the timers.
Each timer consists of a count and an address to call. Each time the kernel detects a tick it will decrement the counters and check to see if any have reached zero. When a timer reaches zero the kernel will delete the timer and execute the code specified by the address. The timer code can add the timer back again for a repetitive event.
Timers are added to a heap and the kernel will scan the entire heap to find expired timers. The timer is deleted by replacing the expired timer with the timer removed from the top of the heap. When the expired timer is at the top of the heap then there are no more timers.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.