     SET/RESET AND DIRECT VIDEO CONTROL FOR MODEL 4 BASIC
             by Bob Grommes -- B & G Microsystems
       1733 Eastern S.E., Grand Rapids, Michigan  49507
                      CompuServe 74126.72

     It has always been something of a challenge to me to
achieve absolute control over the hardware using lowly old
BASIC.  Without the documentation to create some kind of
interface with the DOS, this had been relatively difficult on
earlier machines like the Model I and III until IJG and others
provided the documentation that Tandy wouldn't -- such as
MICROSOFT BASIC DECODED and BASIC FASTER AND BETTER.  About the
time the Model 4 was introduced, Tandy's attitude of paranoid
secrecy began to soften, and even the "official" Tandy coverage
of the Model 4 hardware and DOS (I refer the the MODEL 4
TECHNICAL REFERENCE MANUAL) was superb in comparison to the
past.  I've since collected a wealth of other tidbits from my
investigations and the work of others, but am amazed how little
has been published from the perspective of the Model I/III
programmer trying to shift his mental gears to work on the
Model 4.  If you're like me, you've developed some favorite
techniques you'd like to use on the 4, but just haven't the
time or resources (or perhaps the heart) to start over from
scratch and get those techniques working on the Model 4.  Well,
here's a fairly satisfying solution to this lack.  Hang in here
with me and when we're all done, you'll have a simple method of
calling machine language routines from Model 4 BASIC that will
give you much more power, flexibility and sophistication in
designing displays than you have had until now.
     We're going to concern ourselves here with taming the
Model 4 video display.  While some of you have been happily
PEEKing and POKEing your Model I/III video display, pointing
strings to it and so forth, you find this quite impossible with
the Model 4, because the video RAM is not "memory mapped".  A
fair amount of writing has been done about the Model 4 video,
including a fairly decent article in 80-MICRO a few months
back, so I won't go into great detail here.  To make a long
story short, there is a 3K RAM in the Model 4 that has the
keyboard and the video display "mapped" to it.  In order to
read or write to this RAM, we send certain values out port 84H.
The hardware then "bank switches" this RAM so that it can be
addressed in high RAM starting at F400H and extending all the
way to FFFFH, the top of physical memory.  The video RAM
actually goes from F800H through FF7FH (1920 bytes).  The area
from F400H through F7FFH is your keyboard RAM, and the area
ABOVE video RAM (FF80H through FFFFH) is somewhat mysterious.
Under TRSDOS 6.2, it is said to hold the keyboard type-ahead
buffer and "other system buffers".  Under previous versions,
nothing I know of was said about this area but I believe it was
unused by TRSDOS.  A very few applications stashed data and
short machine language routines here and won't work under
TRSDOS 6.2 for this reason.
     Anyway, while this 3K RAM is switched in, the previous
contents of the high memory it shadows are "lost" until the
video/keyboard RAM is swapped back out.  Since most users have
some kind of high memory drivers or filters active in this
area, keyboard and video access routines must be very careful
or they will clobber code that the system needs to function
when the video/keyboard RAM is switched in.  A little
reflection will tell you that the DOS has quite a task keeping
all the various system and user requests from colliding with
the need to almost constantly scan keyboard and video.  Also,
our ability to directly access this RAM from BASIC is really
quite restricted and inflexible because there are so many
things we just can't to at the same time as video RAM is
available.  Any kind of I/O to or from system devices or the
disks will cause a conflict.  Any BASIC function which happens
to call DOS routines may cause trouble.  And because BASIC
wasn't written with our kind of shenanigans in mind, IT may
undo our configuration at any time in the course of its normal
housekeeping.
     Still, for certain purposes, a direct video access method
from BASIC could be useful -- for example, the old trick of
POKEing a value in the very last screen position to avoid
scrolling.  So before we go to a discussion of how to get
TRSDOS 6 to handle video access FOR us, we'll demonstrate it
the HARD way first.  At the same time, we'll demonstrate the
logic behind SET and RESET.  These functions work too slow when
written in BASIC, but it's easier to understand the logic
behind them if we work through them that way and then explain
the assembly language version.
     DGRAPH/BAS is actually two demonstration programs in one.
The lines from 500 on are a self-contained program; to use it
you have to load DGRAPH/BAS and RUN 500.  But for now let's
look at the code prior to line 500, which demonstrates direct
video access from BASIC.
     The purpose of this module is to draw a border around the
outer edge of the Model 4 display, beginning in the upper left
hand corner, and then erase it, using pixel SET and RESET.
Program logic actually begins at line 100, where the first
thing we have to do is keep BASIC from using addresses above
F3FFH, where video/keyboard RAM will soon reside.  The CLEAR
statement is equivalent to entering BASIC with the command
BASIC (M=X'F3FF').  In your applications, you could have
machine-language routines or data stored in the area F400H to
HIGH$, as long as you won't use them during the direct video
access.
     Next, we do what I feel BASIC should do by default, and
consider all variables an integer unless otherwise specifically
defined.  This is assumed by all our variable passing
techniques later on, so be forewarned.  Next, in an effort to
squeeze every bit of speed we can out of this program, we
DIMension all the variables we will use in the order we want
them in the variable lookup table. Finally, we set the variable
VIDEO to the starting RAM address of video memory.  Then the
GOSUB 10 instruction will define several user functions. Ignore
these for the moment, we'll get back to them shortly.
     Now in lines 110 - 130 we do the actual bank switching.
The idea is: (1) disable interrupts.  This will get rid of
TRSDOS' constant access of the video to blink the cursor, and
will also stop any interrupt driven tasks currently active in
high memory so we won't clobber them.  (2) Determine the
current status of port 84H, tweak a couple of bits, and output
that value to switch in video RAM.
     Unlike the Model I, which has CMD functions to
enable/disable interrupts, we need a short machine-language
routine (Wait! Come back!) to do the job.  This is a huge, two-
byte routine, so we store it in an integer variable in line
110.  The assembler mnemonics for this routine is simply DI
followed by RET.  The VARPTR of DI.Z80 will serve as the entry
address to this routine.  Although unused in this demo, EI.Z80
is loaded with the instructions to re-enable interrupts (EI
followed by RET).  Naturally you have to switch video memory
back out and re-enable interrupts once you're done fooling
around in video RAM, but using a normal video output
instruction (such as PRINT) or an END statement will do this
anyway, so you usually don't have to explicitly bother with the
chore.
     In line 130, we take a look at the byte in memory location
78H.  In all releases of TRSDOS 6 to date (including 6.2.0)
this location has contained the last value sent to port 84H.
There is no absolute guarantee that this location won't move in
future releases of the DOS; it is part of the system flags
table which is supposed to be located with a machine-language
call to the @FLAGS routine.  (Officially, it's the "O" flag,
usually labelled OPREG$).  This is the one big thing they
didn't tell you in the 80 Micro article; if you want to insure
your program will run under any future release of TRSDOS 6, you
have to at least call a machine-language routine to pinpoint
the location of this flag.  Anyway, in this case we'll risk
future portability for the sake of illustration.  We take the
value at 78H, and send that value out port 84H after first
adjusting the proper bits which will tell the hardware to bring
in the video RAM.  At this point we have temporarily lost any
previous contents of memory above F3FFH, and this area is now
addressable as keyboard and video.  Note that we didn't
actually change the value at 78H; if we were to send that value
back out port 84H we would de-select the keyboard video RAM.
     Now we have to draw our border around the edge of the
display.  How do we turn on the proper pixels?  First, we have
to poke graphics blanks (128 decimal) around the edges of the
display.  For those who haven't gotten into the theory behind
direct pixel addressing, it goes like this:  Assuming we have a
video location with a value in the range 128 through 191
decimal in it, we can turn any of the six pixels at that
position on or off by setting or resetting the appropriate bit.
If we number the pixels like so:
                             0   1
                             2   3
                             4   5
. . . then setting bit 0 would turn on pixel 0, setting bit 4
would turn on pixel 4, and so on.  In lines 132 and 134, we are
accomplishing 2 things:  we are making sure that no non-
graphics characters are at the screen edge, and that all pixels
are off.
     The balance of this first demo through the END statement
in line 220 draws and then erases the border using user-defined
functions which mimic Model III SET and RESET statements as
closely as possible.  Again, although unused in this demo, the
function POINT has been defined to describe how it would work.
Let's take a closer look at the functions.
     The VIDRAM function calculates the memory address of a
given screen location, given the screen row and column.  SET,
RESET and POINT then use this function to PEEK at the proper
byte on the video display and modify it.  With SET and RESET we
must then poke the changed byte back into the same location.
     We pass the X and Y coordinates of the pixel we want to
work with to these functions, so their first task is to convert
X and Y to the appropriate row and column to pass to the VIDRAM
function.  Second, we must determine which bit WITHIN that byte
we are going to deal with.  At this point I'm probably going to
loose you if you don't get a pencil and paper and try a few
examples of what I'm about to discuss to convince yourself it
works.  It's easier to SEE how it works than to understand the
theory behind it.  Once you SEE it, the theory isn't so bad.
     We can determine the video character row by dividing Y by
3 and ignoring any remainder (each video character position is
three pixels high).  Similarly, we can calculate the video
column by dividing X by 2 and ignoring any remainder, because
each video character is two pixels wide.  Fortunately, since
Model 4 BASIC supports integer division (note the backslashes
we used instead of the normal forward slash) it's easier and
much faster than using FIX, as we would have had to under Model
III BASIC.
     Now that we have the proper video byte, how do we find the
correct bit within that byte?  Well, just pretend that the byte
we are dealing with is a tiny video display, two columns wide
by three rows deep.  Remember those remainders we just threw
away?  Well, we shouldn't have, because they are our pixel row
and column.  Again, Model 4 BASIC to the rescue.  The MOD
(short for modulus) operation gives us the remainder of any
integer division, so we don't have to develop a remainder
function.  So Y MOD 3 yields the pixel row (0-2) and X MOD 2
yields the pixel column (0-1).  Just to be difficult (and speed
things up a bit), I didn't actually use X MOD 2 to arrive at
the pixel column; I used the alternate method of ANDing X with
1.  ANDing any number with 1 will yield 1 if the number is odd,
0 if it's even.  Since the only two possible outcomes are 0 or
1, this method works for the pixel column and is faster since
it avoids a software-intensive division operation.
     Finally, we just take the pixel row, multiply it by two
and add the pixel column to it.  This yields a number 0 through
5, which happens to be the bit we are looking for.  Think of
this operation as a mini-VIDRAM function for our imaginary
mini-video display.
     Now the only dirty work left is to set, reset or test the
bit we have just calculated.  We do this with our logical
operators.  The method is borrowed from Rosenfelder's book,
BASIC FASTER AND BETTER which, if you don't have a copy of it,
you should.  (Lousy English, but you get the point).  To make a
long story short:

To set any bit in a byte: New.byte = Old.byte OR 2 to the power
        of bit
To reset any bit in a byte: New.byte = Old.byte AND NOT 2 to
        the power of bit
To test any bit in a byte: Value = Old.byte AND 2 to the power
        of bit

So, believe it or not, we went through all that just to address
ONE pixel!  Now run the 1st demo and see how long it takes to
address ALL the pixels at the outer screen edge TWICE!  Hmmm
. . . clearly not anywhere near as fast as Model III built-in
pixel functions, since it takes about 40 seconds to draw and
un-draw our border.  Looks like we need machine-language speed
on this one.
     Well, if we're going to all that trouble, we might as well
cover all the other things we can do to video from Model III
BASIC that we can't directly do from Model 4 BASIC.  The
assembler code for VDCTL/OBJ which accompanies this article
gives us complete control over the Model 4 video, with SET,
RESET, POINT, video PEEK and POKE, scroll-protect, and the
facilities to copy back and forth between video and RAM (or
BASIC string variables).   The second BASIC demo (line 500 to
the end) loads VDCTL/OBJ and calls two of its several routines
to do the same job as the first demo in about one tenth the
time.  In fact it works out about perfect, by my benchmark
. . . about twice as fast as Model III SET and RESET, which is
just about how fast it needs to be, since we have almost twice
as many pixels to deal with.  Let's bypass the assembler code
for a bit and concentrate on the BASIC interface.  If you don't
know (or don't care to know) Z-80 assembly language, just
concentrate on the methods for making practical use of VDCTL.
Hopefully those of you interested in assembly language --
beginners and old-timers alike -- will learn a few tricks and
get more comfortable "talking" with TRSDOS 6.
     Taking a look at line 500, the entry point for our second
demo, you will notice we are reserving memory above F3FFH
again.  This is not because we have to protect video memory
(TRSDOS will take care of that matter for us this time) but
only because we decided our VDCTL machine code will reside in
memory starting at F400H.  We could have put it anywhere within
reason, but I selected this location because it's low enough to
stay below the system HIGH$ pointer (unless you have a lot of
drivers or filters in high memory) and still high enough to
keep from stealing too much memory from BASIC.
     In line 510 we load VDCTL/OBJ and define several
variables.  These all represent the entry points to the various
routines supported by VDCTL.  Here's one good thing about long
variable names -- it's immediately apparent what the purpose of
each routine is.
     Line 520 clears the screen and turns the cursor off, and
the balance of the program gets down to business and draws,
then erases, our border -- ah, THAT'S more like it!!
     All the VDCTL routines use the BASIC keyword CALL for
access, rather than USR.  Why CALL?  It's more flexible and
self-documenting.  It's also easier to understand and use.
About the only advantage I can see of USR over CALL is that USR
can be used in expressions -- for example, IF NOT USR7 THEN
. . . or even DEF FNX=Y*USR7(Z).  USR, however, has several
limitations:  you can't directly pass more than one variable to
the subroutine, or receive more than one value back from it.
You can't directly pass string variables to or from a routine
called by USR.  You are limited to 10 simultaneously defined
USR functions in a program.  Another factor is that, at least
in Microsoft's thinking, USR seems to be on the way "out".  The
Microsoft BASIC manual that comes with MS-DOS, for example,
says that USR is provided "mainly for compatibility with older
programs" and that CALL "is the recommended method" for
interfacing with machine-language routines.  Thus, if you use
USR today, it may be obsolete tomorrow.  Nuff said.
     CALL allows us to pass any number of variables of any type
(including strings) to and from our machine language subroutine
in a very simple and flexible manner.  The format is CALL
ROUTINE(variable list . . . ) where ROUTINE is an integer
variable that points to the entry address of the desired
routine and "variable list" is one or more variables to be
passed to the subroutine and/or receive values back from it.
(If you don't provide a variable list, no values are passed).
There are some limitations:  arguments to CALL  MUST be
variables, not constants.  Also, they must be DECLARED
variables -- that is, when you use the command CALL SET(X,Y), X
and Y must have been previously assigned a value somewhere in
the program.  BASIC normally assumes a value of zero in such
cases, but not with CALL -- you'll get an Illegal Function Call
error.
     Below are the syntax and operation of each of the
functions in VDCTL -- the entry points for each are as defined
in line 510 of the demo program.  A few points to keep in mind:
Except for VIDTORAM, all values passed TO  VDCTL are character
values (0 to 255 decimal).  VDCTL ignores the Most Significant
Byte of these variables and thus "sees" them as modulo 256.
Except for VLINE, absolutely no error checking is done for
invalid values passed (for example, a SET or RESET outside the
boundaries of the display).  In turn, the TRSDOS routines used
by VDCTL do minimal error checking also.  It's your
responsibility to pass VALID values to VDCTL!  VLINE checks for
a target string that's too short but doesn't check for an
invalid video display line.
     SET -- Turns on a pixel at a specified location on the
video display.  The call format is CALL SET(X,Y) where X is the
X coordinate (0 thru 159) and Y is the Y coordinate (0 thru
71).  If there is a non-graphics character at that particular
position on the display, it will be be treated as if it had
been a graphics "all off" character (128 decimal).
     RESET -- The inverse of SET; turns off a pixel at a
specified location.  Same call format as SET.  If there is a
non-graphics character at that particular position on the
display, it will be replaced with a graphics "all off"
character (128 decimal).
     POINT -- Test a pixel at a specified location on the video
display.  The call format is CALL POINT(X,Y,PIXEL) where X is
the X coordinate, Y is the Y coordinate, and PIXEL is a
variable to hold the results of the test.  On entry, X may be 0
thru 159, Y may be 0 thru 71, and the contents of PIXEL is
unimportant.  On exit, X and Y are unchanged and PIXEL will be
0 if the tested pixel was off.  If the tested pixel was on, the
variable PIXEL will be non-zero.
     VPEEK -- Read the value at a given position on the video
display.  This is like a PEEK to video ram on the Model III.
The format is CALL VPEEK(RW,CL,CHAR) where RW is the video row
(0-23) and CL is the video column (0-79).  The value at that
position will be returned in CHAR.
     VPOKE -- The inverse of VPEEK, works like a POKE to video
RAM on the Model III.  Format:  CALL VPOKE(RW,CL,CHAR) where RW
is the video row, CL is the video column and CHAR is the value
to place there.  Note that this function completely bypasses
the video driver so it does not move the cursor and all values
less than 32 and greater than 191 placed on the display with
VPOKE will result in the corresponding special character being
displayed.
     CURSOR -- Change the cursor character and store the old
cursor character.  You can change the cursor character with the
statement SYSTEM "SYSTEM (BLINK=x)", but the CURSOR call is
faster (no disk access involved; it's instantaneous) and you
can also save the current cursor character for later recall.
For example, suppose your program wants to use a large cursor
such as 191, but when your program is finished, you want to
restore the cursor character to whatever it was when the
program was executed.  You could just ASSUME it was 95 (the
default cursor under TRSDOS 6.2) but it might have been 176
(the default cursor under earlier TRSDOS 6 releases) or the
user might have changed it to some other character of his own
preference.  Our calling format for this one is CALL
CURSOR(NEW.CURSOR,OLD.CURSOR) where NEW.CURSOR is the new
cursor character, and OLD.CURSOR is a variable to hold the old
character.  NEW.CURSOR must, of course, be a value of 0 to 255;
as with VPOKE, all values result in a character of some kind.
On entry, the contents of OLD.CURSOR are unimportant and on
exit, OLD.CURSOR will have the cursor character just replaced.
Later, a call such as CALL CURSOR(OLD.CURSOR,NEW.CURSOR) will
reverse the action of the first call.  IMPORTANT NOTE:  TRSDOS
Version 6.0.0 doesn't return the old cursor value, so using
this call under that particular release of the DOS will return
garbage values for OLD.CURSOR.  All releases from 6.0.1 on
return the old cursor value correctly.  (Quick aside:  Two
undocumented features of Model 4 BASIC:  You can legally use a
period in a variable name, and you can substitute square
brackets for parentheses when dealing with array variables).
     PROTECT -- Scroll protect a specified number of lines at
the top of the video display.  Format:  CALL PROTECT(LINES)
where LINES is the number of lines you want to protect.  This
value must be in the range 0 thru 7 (larger numbers are treated
as modulo 8).  This call does not alter the appearance of the
display in any way, but subsequent scrolling will not have any
effect on the top number of lines you specified.  CLS will
still clear the entire screen and you can still PRINT normally
to positions in the scroll-protected area; however, the ONLY
way to remove the scroll protect is to CALL PROTECT(LINES) with
LINES=0, or reboot the system.  By the way, the 7 lines is a
limitation of TRSDOS, not VDCTL.  Break for an editorial:  why
this seemingly arbitrary limitation?  Why not let you protect
any number of lines?  Why not allow top AND bottom scroll
protects?  For that matter, why not specify the four corners of
your actual display area so you could create a window -- even
the primitive Apple II allows you to do that!!
     VLINE -- Copy a line of the video display into a BASIC
string variable. Format:  CALL VLINE(VL$,LN) where VL$ is a
string which has been pre-defined to a length of AT LEAST 80
(actual contents irrelevant) and LN is the line of the video
display you want stored in the string.  VLINE returns without
doing anything if the string is less than 80 bytes long.  If
you have some reason to do so, you can reverse the direction of
the copy by issuing the command POKE VLINE+17,0.  Once you do
this poke, your 80 byte string VL$ will be placed on line LN of
the display.  Of course for most purposes you can do the same
thing by just using the PRINT @ statement.  To restore VLINE to
its normal function, POKE VLINE+17,1.  IMPORTANT NOTE: This
function works ONLY under TRSDOS 6.2!
     RAMTOVID -- Copy a 2K area of RAM to the video display.
With some skillful manipulation, you can create a buffer area
in high memory (say, in a string array), construct your desired
screen there, then instantaneously copy that area to the
display.  The format is CALL RAMTOVID(BUFFER) where BUFFER is
the starting address of the RAM area to be copied to the
display.  BUFFER must be greater than 23FFH and less than
EC00H.
      The RAMTOVID function can be reversed with a POKE
RAMTOVID+7,6.  (To restore normal operations, POKE
RAMTOVID+7,5). You must be careful with this one.  You have to
properly reserve a RAM buffer that will hold the video display.
The buffer must begin BETWEEN 23FFH and EC01H.  For the
purposes of interfacing with BASIC, you might as well locate it
right under your machine language routines and reserve the
needed memory with the CLEAR statement.  That's another reason
I chose F400H as the starting address for VDCTL.  The BASIC
statement CLEAR, &HEBFF would allow you to have your video
display buffer from EC00H through F3FFH.  This is exactly 2048
bytes.  If you had issued the above mentioned CLEAR statement
at the start of your program, and BUFFER=&HEC00, you would
instantly have a copy of the video display just below VDCTL in
memory.
     The method used in the paragraph above will work with any
release of TRSDOS 6.  There are some wrinkles, though.
Releases PRIOR to 6.2.0 copied 2048 bytes into the buffer, so
you would get the 1920 bytes of video and the 128 "mystery
bytes" immediately following video.  Version 6.2.0, however,
copies only the actual 1920 bytes of video RAM.  This means you
only need reserve 1920 bytes, instead of 2048; HOWEVER, the
revised Technical Reference Manual STILL gives EC00H as the
highest possible starting address for a RAM video buffer.  Was
this an oversight in documentation, or a bug in the DOS itself?
     After some testing, I found that, at least under release
6.2.0, you can have your buffer start as high as EC80H, which
is exactly 1920 bytes below F400H.  In fact, no error trapping
is done if you specify a higher address, so you CAN do strange,
dangerous and mostly useless things to your program if you
aren't careful.
     Anyway, for the benefit of those who want to customize
(excuse me, hack) on the assembly language source code, I'm
going to mention a few points regarding the DOS interface.  Due
to the space we've already taken, I'm not going into great
detail; you should have a copy of the MODEL 4 TECHNICAL
REFERENCE MANUAL if you seriously want to write this kind of
code, anyway.
     All of the routines in VDCTL use just one TRSDOS 6
supervisor call (SVC).  The name of it is @VDCTL (ViDeo
ConTroL) and it's probably the most multi faceted SVC in the
entire DOS.  Actually, this SVC has several functions we
haven't used, but they are easily available from BASIC
(positioning the cursor and determining the current cursor
position).
     The following should be kept in mind about all our calls
to @VDCTL:  None of the @VDCTL functions used in our code
changes the current cursor location; AF and B are destroyed by
all the functions of @VDCTL; and all calls to @VDCTL are done
with 15 decimal loaded into the A register (to select @VDCTL,
which is SVC #15) and the desired @VDCTL function number (1
thru 9) in the B register.  All calls to @VDCTL which deal with
a particular row and column location on the display are entered
with H=desired row and L=desired column.
     Since neither TRSDOS nor BASIC provide any facilities for
direct pixel addressing on the Model 4, I had to create a
routine which I call CALCADDR to handle the number-crunching
involved.  SET, RESET and POINT all call it as the first order
of business.  This routine gets the X and Y coordinates passed
by BASIC's CALL verb and translates them into the appropriate
video row and column, handily left in HL, just where @VDCTL
needs them to be.  It also leaves the number of the bit we have
to address at that location (0 thru 5) in the C register.  SET,
RESET and POINT then take the appropriate action on the proper
pixel by performing the correct logical operation on the byte
at video row, column.  The method of using a lookup table for
this operation, as well as most of the math in CALCADDR, is
adapted from a routine devised by Barden for the Model III in
MORE TRS-80 ASSEMBLY LANGUAGE PROGRAMMING, page 201.
     How does BASIC pass variables with the CALL verb?  Upon
entry to our assembly code, HL points to the VARPTR of the
first BASIC variable; DE points to the VARPTR of the second
variable and BC points to the VARPTR of the third variable.  If
there are more than three variables, BC will point to the start
of a block of memory containing the 3rd through Nth VARPTRs,
one after another.  To understand how to read or alter these
variables, you have to understand the structure of BASIC
VARPTRs, which is beyond the scope of this article. However,
studying the source to VDCTL/OBJ should give you an idea or two
of how to go about it.
     As a final note, notice that we gave the last RET
instruction in the code a label, DOSENTRY, and used that label
in the END statement.  If the user attempts to execute the
object code module from TRSDOS Ready, all that happens is that
the program is loaded, control transferred to DOSENTRY, and the
RET instruction takes him right back to TRSDOS Ready.
     Your comments and/or enhancements to this code are
appreciated.  Have fun!
.
...................................
.                                 .
.    Downloaded in 1990 from:     .
.                                 .
.      ********************       .
.      *     8/N/1 #1     *       .
.      *   904/377-1200   *       .
.      * Gainesville, FL  *       .
.      * Guy Omer - SYSOP *       .
.      ********************       .
.                                 .
. Software support for the TRS-80 .
...................................
