Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.sys.apple2.programmer > #1159

Documentation for Multitasking Mouse Draw Demo

Path csiph.com!v102.xanadu-bbs.net!xanadu-bbs.net!news.glorb.com!xmission!news.bbs-scene.org!macgui.com!dog_cow
From D Finnigan <dog_cow@macgui.com>
Newsgroups comp.sys.apple2.programmer
Subject Documentation for Multitasking Mouse Draw Demo
Date Fri, 21 Feb 2014 19:48:46 +0000 (UTC)
Organization Mac GUI
Lines 276
Message-ID <dog_cow-1393012124@macgui.com> (permalink)
References <dog_cow-1393012033@macgui.com>
NNTP-Posting-Host gunshin.wiredns.net
Mime-Version 1.0
Content-Type text/plain; charset=UTF-8
Content-Transfer-Encoding 8bit
X-Trace technocratmedia-w1.news.bbs-scene.org 1393012126 29764 74.119.145.48 (21 Feb 2014 19:48:46 GMT)
X-Complaints-To abuse@bbs-scene.org
NNTP-Posting-Date Fri, 21 Feb 2014 19:48:46 +0000 (UTC)
User-Agent Mac GUI Usenet
In-Reply-To <dog_cow-1393012033@macgui.com>
Xref csiph.com comp.sys.apple2.programmer:1159

Show key headers only | View raw


Documentation for Multitasking Mouse Draw Demo
20 Feb 14

For the benefit of all Apple II users, the source code is hereby surrendered
into the public domain. I claim no copyright on the source code.


=== General Overview ===

This multitasking demo uses an AppleMouse card as a source of interrupts to
power a small multitasking system. More properly, it might instead be called
a multithreaded system. It has been tested to run on an Apple II Plus, a $FF
ROM Apple IIc, and the Apple IIe standard and enhanced.

There are three processes, or threads, depending on what you want to call
them. They all share the same address space, and they all have a portion of
the 6502 hardware stack devoted exclusively to them. Periodically, as the
AppleMouse generates VBL interrupts, one process will be suspended, and
another will be selected to run for some time. It is also possible for a
process to voluntarily give up its CPU time by calling a yield subroutine.

Together, these three processes cooperate to draw on the low-resolution
graphics screen. There are two autonomous processes, controlled entirely by
the Apple. One will plot a series of red blocks, the other, blue blocks. The
third process is controlled by input from the operator. It will plot white
blocks in the position of the mouse cursor when the mouse button is held
down. When any key on the keyboard is struck, all white blocks will be
changed to black.


=== System Initialization ===

The program loads at address $8000. Its first task is to prepare the screen
by exiting from 80-columns firmware and then by setting the top of the text
window in preparation for split graphics/text mode.

The next objective is to locate an AppleMouse card. The search is performed
"by the book," scanning all slots from 7 to 1 and looking for the ID bytes
in the AppleMouse firmware. If found, BASEADDR will contain the low-byte and
high-byte of the AppleMouse, $00C4, for example. MSLOT will contain the slot
number times 16, $40, for example. These two locations are used for calls to
the AppleMouse firmware. Finally, the slot number will be printed on the
text screen.

If no AppleMouse card could be located, the message "NO MOUSE" is printed on
the screen, and the program will jump to the DOS warm start location.

After the AppleMouse card is found, program execution jumps to the CREATEALL
routine. This routine is responsible for creating the three processes. It
starts by clearing the 6502 interrupt disable flag, then setting the flag.
All changes to the interrupt flag are done "by the book," that is, by using
the combination PHP SEI to disable, then PLP to enable once more.

The first byte of STKTAB, the stack table, contains the total number of
active processes. This number is incremented when each process is created,
but it is never used thereafter. The rest of STKTAB holds the current stack
pointer for each process while it is suspended.

The first process starts with its stack pointer at $D0, the second at $B2,
and the third, $94. These initial values are stored in STKTAB using the
Y-register as a process ID offset.

Next, the three processes are created. The Y-register holds the process ID,
the A-register holds the low-byte of the starting address, and the
X-register holds the high-byte of the starting address.

The subroutine NEWPROC actually creates each process. NEWPROC first compares
the intended process ID with MAXP, which holds the maximum process ID
allowed. This comparison is, obviously, a mere formality in this case.

Newly created processes look as if they have just been suspended by a VBL
interrupt. Therefore, NEWPROC will push the program address in the proper
stack location, indexed from STKTAB, and then will push the starting
processor status and register values, all 0, following. Finally, the initial
stack pointer for the process, which has been decremented with these stack
additions, is stored back in its place within STKTAB.


=== Enabling interrupts ===

At this point so far, the three processes have been created, but interrupts
remain off. Program execution now jumps to the VBLON subroutine to enable
AppleMouse VBL interrupts which occur 60 times each second.

VBLON first sends the mode byte $01 to the AppleMouse, which enables the
mouse firmware, but not interrupts. SETMOUSE and INITMOUSE perform the mouse
initialization.

The address of the interrupt handler, IRQ, is stored at $3FE.

Once the interrupt handler is prepared, the programs sends the mouse status
byte of $09, which enables VBL interrupts, to the AppleMouse firmware.

The final initialization task is to store $FF in ACT, which stores the
active process ID, and $13 in CTR, which stores the interrupt tick counter.

Now the AppleMouse is pulling the IRQ line to ground on the 6502, but these
requests are being masked out by the interrupt disable flag. PLP allows
these interrupts to get through, and the fun begins!


=== The interrupt handler ===

After interrupts are enabled for the first time, the system initialization
program jumps into an idle loop, but this loop will probably never be
executed again because the three processes start running.

Execution should jump immediately to the IRQ handler because of the
AppleMouse VBL interrupt. The problem of how to bootstrap the initial
process is that we want it to run first and we do not want the registers
from the system initialization stage to get mixed in. After system
initialization, each process looks like it has just been suspended. The IRQ
handler has no way to tell that the processes are in fact, newly created,
and have never run.

The solution was to make some special case code and set ACT, the active
process ID, to $FF. Process scheduling is round-robin, and the process ID is
merely incremented to get the next one. So $FF rolls over to $00, and the
first process is selected to run.

The IRQ handler starts by first pushing all the registers on the stack. Each
process gets an exclusive portion on the stack, and the stack pointer is
adjusted when selecting the next process to run, so there is no need to
perform any special routine to save the registers. The very first time the
interrupt handler runs, however, the registers pushed on the stack don't
belong to any of the three processes. They are the registers from system
initialization. This is not a problem, because as mentioned in the previous
paragraph, the next process ID will roll over to $0, and the initial,
zeroed-out registers, will be pulled from the appropriate location in the
stack.

After saving registers, SERVEMOUSE is called to ensure that the AppleMouse
caused the interrupt. If so, READMOUSE is called to get the current cursor
coordinates and button status. These values are saved in some zero page
locations, MOUXPOS, MOUYPOS, and MOUSTAT, so they are accessible to running
processes.

The next step is to increment the tick counter, CTR. This counter increments
for each mouse interrupt, and it gives each process a fair share of CPU
time. A process can also choose to give up its CPU time by calling the YIELD
subroutine, which will be described later. When the counter reaches $14
(decimal 20), the process is suspended and the next one is selected to run. 
Because the AppleMouse interrupts the CPU 60 times per second, this means
that context switches occur approximately 3 times per second. The system
initializes CTR to $13, so it will be incremented to $14, thus process 0
will be the very first process to run.

If CTR did not reach $14 after being incremented, the interrupt handler is
done, and it restores the registers. The stack pointer was not changed, so
RTI leads back to the interrupted instruction of the same process.

However, if CTR did reach $14, then the CPU time for the current process is
expired, and it is time to select the next process to run. CTR is reset to
0, then ACT is loaded to the Y-register to examine its contents. If it
contains $FF, then a special branch is made to bootstrap the initial
process, as has already been explained. If not, the stack pointer is then
stored in the STKTAB, indexed on Y, which contains the process ID of the
process which was just interrupted.

The Y-register is incremented to point to the next process ID. If it equals
MAXP, which is 3, it rolls over to 0 again, since there are only three
process IDs. ACT is updated with the new process ID from the Y-register.

The context switch is simple, consisting of just 3 lines that load the stack
pointer for the next process from the STKTAB. The speaker is clicked to give
audible feedback.

With the stack pointer adjusted, the register restore operations at IRQDONE
pull the correct contents from the stack, and RTI sends the CPU operation
back to where the next process had previously been suspended.


=== Giving up CPU time with YIELD ===

So far, the system described has been operating in a preemptive fashion:
each process is allowed to run for a fraction of a second, then it is
forcibly suspended so that the next process can run.

It is also possible for a process to volunteer for preemption, that is, give
up its remaining CPU time so that the next process may run.

A subroutine called YIELD, which may be classified as a system call,
performs this function. A program will perform a JSR to YIELD to give up its
CPU time.

YIELD needs to account for the difference between a JSR and a CPU interrupt.
In the former, the processor status is not pushed on the stack, and the
return address is pushed, but it is one less than the effective address. An
interrupt, on the other hand, does push the processor status, and the return
address is the effective address for resuming the interrupted program.

YIELD begins by pushing the processor status to the stack, like an interrupt
request does. It then disables interrupts, and proceeds to push the
remaining registers to the stack. The next step is to fix the return address
for the RTI. It needs to be incremented. The stack pointer is transferred to
the X-register, which is then incremented by 5 to point to the low-byte of
the return address. It is incremented, and if necessary, so is the
high-byte. Program execution jumps to RESETCTR to reset the tick counter to
0, then the next program is selected to run, as usual.


=== The processes ===

As already mentioned, there are three processes. Two are completely
controlled by the CPU, and the third is controlled by the user. Thus the two
may be thought of as background daemons with the latter being the foreground
process.

Process 0 performs the setup work for a low-resolution drawing program using
the mouse. It disables interrupts, then it sets the X and Y clamps to be
40x40. It then calls HOMEMOUSE to ensure that the cursor is within these
boundaries. Interrupts are briefly enabled while the MIX and GR
soft-switches are referenced to change the screen to low-resolution graphics
with text.

Most all of the Monitor subroutines are not reentrant because they store
some values in zero-page locations. This means that only one process can
call a Monitor routine at a time, and in order to enforce this policy, the
process must disable interrupts for each Monitor call with PHP SEI, then
enable with PLP afterward.

After the screen is switched to graphics mode, CLRTOP is called to clear the
top 40 rows to black, leaving just the text screen visible, which should
still show the AppleMouse slot number.

The main event loop of process 0 follows. First it will check the keyboard
for any key press. If there is one, the program will jump to :CLRIT to clear
all white blocks. If no key was pressed, then the mouse button status is
examined to see if the button is pressed. If no button was pressed, then the
loop goes back to examine the keyboard again.

If the mouse button is down, a white block is plotted at the cursor
coordinates which are stored in MOUXPOS and MOUYPOS. These are zero-page
locations which were updated by the IRQ handler.

If a key was pressed, the program will loop over the entire 40x40 screen in
search of white blocks with the SCRN Monitor subroutine. If any are found,
they are plotted over with black using the PLOT subroutine. Afterward, the
message CLEAR is printed on the text screen. Interrupts are only disabled
for the SCRN and PLOT subroutine calls.

Processes 1 and 2 operate alike with only one meaningful difference in
respect to multitasking. Both will plot a sequence of blocks on the screen,
starting from the top or bottom row and working toward the opposite side. To
distinguish the two, process 1 only plots red blocks, and process 2 only
plots blue blocks.

The meaningful difference is that process 1 only calls YIELD after it has
plotted an entire row of blocks, whereas process 2 calls YIELD after each
plot.

Due to the use of YIELD in these two processes, the majority of CPU time is
devoted to process 0, and thus the user operating his drawing program does
not perceive any lag or delay.


=== Conclusion and improvements ===

This program demonstrates that it is possible to use interrupt-enabled,
multitasking programs in a meaningful way on the 8-bit Apple II. Defensive
programming practice, exercised by calling YIELD, means that process 0 is
given highest priority. Therefore, the user experience does not suffer when
there are two background processes also using CPU time.

Some improvements of this system would be to allow variable process
priorities by making a CTRTAB, similar to the STKTAB. This would also be
indexed on the process ID in the Y-register, and it would contain a maximum
tick count to be reached before the context switch occurs. This would be in
substitute of the current system, which gives all processes up to $14 ticks
on CTR before a context switch is forced.

Another improvement would be to take advantage of the bank-switching scheme
of the Apple IIe with a RamWorks-style AUX memory card. Each process would
be granted its own stack, which would eliminate the need to subdivide the
single 6502 stack as the program currently does.

Back to comp.sys.apple2.programmer | Previous | Next — Previous in thread | Next in thread | Find similar | Unroll thread


Thread

Multitasking Mouse Draw Demo D Finnigan <dog_cow@macgui.com> - 2014-02-21 19:47 +0000
  Documentation for Multitasking Mouse Draw Demo D Finnigan <dog_cow@macgui.com> - 2014-02-21 19:48 +0000
  Source Code for Multitasking Mouse Draw Demo D Finnigan <dog_cow@macgui.com> - 2014-02-21 19:51 +0000
  Source Code for Multitasking Mouse Draw Demo 2 D Finnigan <dog_cow@macgui.com> - 2014-02-21 19:55 +0000
  Re: Multitasking Mouse Draw Demo D Finnigan <dog_cow@macgui.com> - 2014-02-21 19:56 +0000
  Re: Multitasking Mouse Draw Demo Egan Ford <datajerk@gmail.com> - 2014-02-22 09:47 -0700
    Re: Multitasking Mouse Draw Demo D Finnigan <dog_cow@macgui.com> - 2014-02-22 17:36 +0000

csiph-web