jonb wrote:
Hi everyone!
I was thinking just now... can I implement a preemptive multitasking OS on the Micro UK101? Presumably I'd need a few extra bits (RTC? Better I/O?), but I fancy it might make an interesting challenge. Start with a simple round robin scheduler (I guess this is what you'd call "co-operative multitasking")
A year has gone by so I don't remember for sure but I may have been thinking that you were wanting to run pre-written programs under a multitasking OS. In reviewing the above, I'm not so sure. I'm not familiar with the UK101, so I didn't comment much. A few months later though I found myself working on a multitasking project with a PIC16 microcontroller which of course doesn't have the resources for a multitasking OS but you can do the same thing with nearly any processor so I'll describe it here. I started with this kind of thing:
Code:
MAIN_LOOP:
BEGIN
JSR task_1
JSR task_2
JSR task_3
<etc.>
JSR task_n
AGAIN
On the PIC, this was in ROM, but you could put it in RAM if you like, and lengthen the list as you add tasks. If you do it in RAM, the task at the top of the list should probably be the one that adjusts the list, adding new tasks or deleting ones that are no longer needed and moving the following ones up to fill in the JSR instruction spot (or at least replacing them with NOPs).
Since my processing power was limited and a couple of the tasks needed a bigger share of it than others, I put their JSR lines in the main loop twice each, distributed such that they would never have to wait too long between runs.
The tasks I had were all dependent to some extent on time, or on other things having completed (like a byte being shifted in or out on a hardware serial interface), or on a flag being set to indicate whether the task was active at the time, or something like that; so each one would initially check to see if it even had anything to do. If the answer was no, it would exit right away and it would have taken very few instructions' time. If a task found that it was indeed supposed to be doing something, it might next check to see if what it was waiting for had happened yet, and if not, exit, again without taking much processor time. If it was time to do something,
what that "something" was could depend on where it left off before, so I implemented basically what amounts to a state machine. Rather than having something external schedule the tasks and give each one a certain slice of time, the tasks themselves determined how much time to take, always keeping in mind the fact that processing power was not abundant, and in many cases they gave control right back almost immediately; ie, they were not selfish.
Interrupts were firing at 24,000 per second. Since the task switches were only subroutine calls and returns and there was no context-switching overhead to speak of, the interrupt timing jitter was minimal. Interrupts were never disabled. The interrupts were primarily for timing A/D and D/A (actually PWM) conversions, and a couple of the tasks were doing software anti-aliasing since I was only recording and playing 6K samples per second, for voice-band communications. The ISR also ran an RTC-- not that I cared what time of day it was, but it was for timing button de-bouncing, button-hold time (it has a different function if you hold it for a second or more), squelch-activation times, and other things. It works out fine if you work it like a room full of people all watching the same clock on the wall to time their individual projects. None interfere with any of the others.
So take for example the button debouncing. A task's state says that no button was being pressed, so we're waiting for a key press. If still nothing is being pressed, it exits; but if it finds that now one is being pressed, it increments the state, looks at the time, and adds 30ms to it and records it, then exits. The next time it executes, if the key is not still being pressed, it resets the state and exits; but if it is still being pressed, it compares the current time to the target time it had recorded, and if it has not reached it, it just exits, otherwise considers it a valid keypress, increments the state, does what it's supposed to for that keypress, and adds one second to the current time and records that for another time interval that will mean something else if it is held that long. (There's more, but I won't drag this out.)
Hopefully that gives the idea. Parts of it initially drove me nuts, but gradually I found a way to make it all work and make it clear. This was the first full project I did with my structure macros, and they sure made a difference in making it clear and keeping control of the project.