Arlet wrote:
Quote:
It may not be practical to write ISRs in Forth though, because Forth is maybe an order of magnitude slower than assembly-language, and ISRs need to be fast --- it may be necessary to write ISRs in assembly-language because of the speed issue --- ISRs tend to be short and simple though, so this isn't a lot of extra work for the programmer.
The idea was that you would have a choice between a fast interrupt that can happen at any time, and a slow one that you could write in Forth. Not all ISRs need to be fast, and as long as the fast ones can interrupt the slow one (by using more than one priority), there's no problem with missing important events while processing a slow IRQ.
I wasn't planning on allowing ISRs to be interrupted. I was expecting ISRs to be pretty short and quick. The AIRQ interrupts do have priorities, but that only affects which one is serviced if more than one are pending. The I-flag is set during an ISR, so when the ISR ends and the I-flag gets cleared, there may be more than one pending interrupt that was triggered while the I-flag was set and hasn't been serviced yet.
Note that an ISR can force a task-switch to a particular task. So, if you have some lengthy code that you want written in Forth, this would not be an ISR, but would be a task. You would have a short ISR that does some time-critical access of I/O and then it forces a task-switch to the task that is waiting on this data.
Anyway, I have a new document with the EXA instruction added. It is attached. I also have a section discussing the multi-tasking OS:
Code:
Section 5.) the multi-tasking OS
The OS does preemptive task switches. A task-switch may occur at any time, including in the middle of a primitive.
There is a heartbeat timer that does a task-switch periodically (typically every 10 milliseconds).
It is also possible for a task to pause, waiting to obtain a semaphore, in which case it does a task-switch.
Tasks aren't guaranteed to execute on an exact schedule.
The time between executions of a particular task will vary a lot depending upon these factors:
1.) The number of tasks is the major factor.
2.) If any tasks are pausing, they don't get their full allotment of time.
3.) An ISR may change the order of the tasks being executed, to move a particular task to the front.
If you need code to execute on an exact schedule, this should be an ISR tied to a timer interrupt.
An ISR is different from a task because it can't be interrupted by anything (the I-flag is set).
An ISR should be pretty short and quick so it doesn't prevent other ISRs from losing data.
An ISR can be written in either assembly-language or Forth.
Forth is about an order of magnitude slower than assembly-language though, so assembly-language is generally preferred.
Each ISR is quite short though, so the application programmer won't be writing a lot of assembly-language.
Most of the work will be in tasks, and these are written in Forth. Tasks are not time-critical.
The ISRs and tasks communicate through circular buffers. The task only has to be fast enough that the buffer doesn't overflow.
The buffer will typically be 256 elements because this is easy to implement on the 65VM02 using Y as the index.
The buffers don't have associated semaphores.
A task can be reading or writing to a buffer and get interrupted by an ISR that is also reading or writing to that buffer.
Semaphores are primarily needed when a task accesses I/O directly, and more than one task may access the same I/O.
A typical example would be an LCD display. A task has to hold this for itself until it is done, or the messages will get jumbled.
The EXA instruction was provided to support obtaining a semaphore without the test and set being interrupted by a task switch.
This is the code for obtaining and releasing a semaphore (1 means held and 0 means free):
OBTAIN: ( semadr -- )
LDY toslo,X
LDA toshi,X
STA ptr+1
obtain_begin
LDA #1
EXA (ptr),Y ; hold the semaphore (by setting it to 1)
BEQ obtain_done ; the semaphore was free, so we have obtained it for our task
JSR task-switch ; let the other tasks execute --- maybe whichever task is holding the semaphore will free it
BRA obain_begin
obtain_done:
INX
NEXT
RELEASE: ( semadr -- ) ; assumes that OBTAIN has already been done and we are holding the semaphore
LDY toslo,X
LDA toshi,X
STA ptr+1
LDA #0
STA (ptr),Y ; release the semaphore (by setting it to 0)
NEXT
In OBTAIN we have an EXA that sets the semaphore to 1 and tests the old value for 0. EXA can't be interrupted.
If we didn't have EXA and used LDA and STA instead, a task-switch could occur between loading the semaphore and storing the 1.
If the semaphore was 0, then our OBTAIN is going to assume that it can obtain the semaphore.
Another task may call OBTAIN for the same semaphore while it is still 0 and will assume that it can also obtain the semaphore.
The result is that both tasks think that they have obtained the semaphore.
We also have a READ-BUFFER primitive that a task uses to read data from a buffer that is being filled by an ISR (such as a UART).
If there is no data in the buffer, then READ-BUFFER will do a task-switch to allow other tasks to execute.
READ-BUFFER will also set a flag to indicate that the task is waiting on a buffer, and should not be executed by the heartbeat timer.
The ISR for the heartbeat timer will do a task-switch, but will not execute any task that is waiting on a buffer.
If all the tasks are waiting on buffers, heartbeat timer ISR will shut down the system.
This means that the heartbeat timer is shut down so it doesn't cause any more interrupts, and a WAI is done.
The system will restart when an interrupt occurs. That ISR will execute, and the heartbeat timer will be restarted.
It is possible that this ISR puts some data in a buffer when it executes (data that was input from an external source).
When the heartbeat timer does a task-switch it will find the task that is waiting on a buffer that now has data (from the ISR earlier).
If all the tasks are waiting on buffers that are still empty though, the heartbeat timer ISR will shut down the system again.
The heartbeat timer is typically 10 milliseconds (100 times per second).
If the heartbeat is too slow, a fast interrupt may cause a buffer to overflow before the task comes around to empty the buffer.
If the heartbeat is too fast, the processor spends too much time doing task-switches and not enough time in the tasks.
The tasks are stored in a circular linked-list.
If an ISR is filling a buffer, and notices that the buffer is over half full, it can adjust the task-list so that its task will be next.
It would also force an task-switch so its task will execute immediately.
This allows the heartbeat timer to be slow (100 hz.) and yet have a task execute at a high frequency while inputting data quickly.
It is important to be able to handle a burst of data that comes in fast, while still having a slow heartbeat.