chitselb wrote:
I have a need, for a game I'm writing on top of PETTIL, to animate several things on the display such that they appear to occur simultaneously. There's an easy enough way to intercept the Jiffy clock IRQ (every 1/60th of a second) and do whatever I want to do. The approach I'm considering is to have the IRQ run through a (sorted) "timeout" list (contains 3-byte expiration times corresponding to some future jiffy clock value at $8d-$8f), and when the jiffy clock is greater than the timeout value, a corresponding execution token gets added to the "triggered" list (a FIFO queue that contains only execution tokens). While each individual event is unlikely to clobber the next interrupt (by taking longer than a jiffy), several events triggered simultaneously just might.
This is exactly what one of the parts of an article is about that I will soon be posting on my website (although the article will not be Forth-specific), about simple methods to effectively get multitasking without a multitasking OS. [
Edit, 5/15/14: It's up, at
http://wilsonminesco.com/multitask/index.html.] Here's a (very cropped) piece of it:
You could have a hardware timer that has a comparator that watches for a match and causes an interrupt; but what I've always done is to use the software real-time clock (which runs on interrupts from a 65c22 VIA's T1), adding a portion of code to do the comparison and act if there's a match. It adds very little execution time to the real-time clock ISR, for two reasons:
- The alarms are sorted by chronological order, so the RTC ISR only has to watch for the first one in line. There is no need to check all the pending alarms for a match.
- When checking for a match, the first byte (the low byte) will usually fail the test, so you jump out of the routine quickly without wasting much time.
Your alarm-management software can of course keep, install, delete, and sort (by due times)
many alarms, and any given alarm's routine might also set an alarm for the next time it's supposed to run. It might be to collect new data every second, every minute, every fifteen minutes, or whatever. What I have set up has 0.010 second (10ms) resolution, meaning I could conceivably have as many as 100 alarms per second, up to something being scheduled out 2^32 centiseconds which is over 16 months out.
If there's a risk of having multiple alarms with the exact same time due time, a few possible ways to deal with it are:
- Have the real-time clock ISR check for more possibly due alarms after executing the first one.
- Have the real-time clock ISR watch for the next alarm being slightly past due, not just an exact match.
- For less runtime overhead, it might be better to have the alarm installation routine see if there's already an alarm with the same due time, and if so, increment the next one by a tick, or even more if there's any risk that a single tick won't give the last previous alarm time to finish executing first.
If there's no alarm pending, the RTC NMI ISR will usually take 8 [assembly-language] instructions, including the RTI. If there
is at least one alarm pending, it will usually take three more. Comparison is done on the low byte first and high byte last to save time, since the high bytes are more likely to be the same. Where my set goes over 16 months, if my alarms are typically for the same day, the high byte will usually match, requiring more byte comparisons in order to see that we're not there yet; so that's not the place to start the comparison.
The alarm array might look something like:
Code:
ALM_LIST: DFS 8*6 ; DFS ("DeFine Storage") in the C32 assembler makes a
ALM_cs_32: EQU ALM_LIST ; variable space of that many bytes. Here we're leaving
ALM_XEQ_ADR: EQU ALM_LIST+4 ; room for an 8-alarm queue, each having 4 bytes for the
; cs-32-matching time and 2 for the alarm-execute address.
; ALM_cs_32 and ALM_XEQ_ADR are for the first one in line.
In my 6502 Forth system, the alarms essentially activate a Forth ISR, which, as shown in the
article on that subject, really has no overhead. As you might imagine, there are many ways to carry out alarm methods. You might have routines to:
- set an alarm with an absolute time
- another to set an alarm with a time relative to right now
- sort alarms by chronological order (which would be used by the two above) and leave the next-due one at the head of the queue
- delete an alarm, using its execute address to find the right one, and close up the gap in the alarm list
- delete the alarm at the head of the queue (used especially by that alarm when it executes)
- find out how long before the next alarm is due
- do conversions from H:MM:SS to centiseconds (or milliseconds, or whatever you use) and back
How complex you want to get is up to you of course. These are merely suggestions, based on what I have running in Forth (not assembly).
Certain things in your tasks may be
inappropriate to time with alarms though. For example, if you have a key-scanning routine and it sees that you've pressed a key, and, for debouncing, it waits to see if it finds it pressed for 50ms in a row before submitting it to the other routines as a valid keypress, you don't set an alarm for 50ms later. The task needs to be run many times in that amount of time, making sure the key remains pressed for 50ms in a row, and it should re-start the count every time it sees the key up and then down again. There is no need to do this constant re-checking on any particular schedule though. There's little difference between getting to it every 100 microseconds versus every 2 milliseconds, while taking care of other tasks in between. That's one of countless senarios that are better taken care of with the cyclic executive method below. <truncated>