Assembly Programming for the TI-81
----------------------------------

 Writing assembly programs for the TI-81 is somewhat different from
 programming on other TI calculators.  Although the TI-81's hardware
 is extremely similar to its descendants, the software differs in many
 ways from the later calculator OSes.

 This guide provides a brief overview of the TI-81's internals, and
 how to write TI-81 assembly programs using the Unity program loader.
 (This information may, of course, also be helpful for programmers
 interested in using another loader, or writing their own.)

 This is not intended as a guide for learning assembly language; I
 will assume that the reader already has some experience with Z80
 assembly programming.  The TI-81 is not a friendly environment for
 beginners!


Programming Basics
------------------

 The Unity package includes 'asm81', a specialized assembler for
 writing TI-81 programs.  To assemble a program, simply run

    asm81 foo.asm

 Running this command will produce several files as output:

  foo.81: Plain text version of the assembled program, with
    instructions for typing it into the calculator.

  foo.prg: TI-81 binary version of the program, for use with a TI-81
    emulator.  If the program uses any ROM routines, a separate
    version of the file will be created for each ROM version
    (foo-1.6K.prg, foo-1.8K.prg, foo-2.0V.prg.)

  foo.lst: Assembly listing, showing the original source together with
    the assembled bytes and addresses.  If the program uses ROM
    routines, a separate version of the file will be created for each
    ROM version.

 A simple assembly program looks like this:


	.nolist
	.include <ti81.inc>
	.list

	.org tempMatrix

	ld hl,hello_string
	ROM_CALL PutFlagStr
	ROM_CALL GetKey
	ret

hello_string:
	.ascis "Hello!"


 Most programs should begin with the statement ".include <ti81.inc>".
 The file ti81.inc contains definitions of the TI-81 ROM routines, RAM
 areas, flags, font characters, and more (including many more routines
 and RAM areas than are described in this guide.)  See that file for
 more information.

 All programs must include a .org statement, specifying where in
 memory the program is to be loaded.  The TI-81 does not have any
 memory areas set aside for assembly programs' use (for obvious
 reasons), so you must re-use an existing memory area.  A few good
 choices:

  tempMatrix (288 bytes): This area is used for storing intermediate
    matrix results while evaluating an expression.  Unless your
    program calls the parser, this area should be free for assembly
    programs to use.

  cmdShadow (128 bytes): This memory area stores a copy of the home
    screen while other apps are in use.  This area is free to use
    while the home screen is actually being displayed.

  plotSScreen (768 bytes): This memory area stores the current graph,
    so if you put your program or data there, you'll be overwriting
    the graph contents.  If you use this area, you should set the
    graphDraw flag, so that the calculator will redraw the graph if
    necessary.

  scratchMem + 5 (283 bytes): The scratchMem area is used for scratch
    space by various ROM routines (and you can use it as scratch space
    yourself, if you like.)  Unfortunately, all of the text display
    routines (PutC, PutMap, etc.) use the first 5 bytes of this area
    to store a copy of the font bitmap being displayed.  However, the
    remaining 283 bytes may be safe to use if you're not doing any
    complicated math.

 If you want to write a larger program, another option is to overwrite
 any of the BASIC variables you're not using.  For instance, the three
 matrix variables, combined, make up 870 bytes; you could store your
 program there, then clean up afterwards by jumping to the
 ResetMatrices routine.


Hardware Ports
--------------

 The original TI-81 (version 1.x) hardware ports are identical to
 those of the TI-85 and TI-86.  See ti-ports.txt for more information
 about them.  In brief:

  Port 0: Starting address for the display buffer.  You might be able
    to display grayscale by changing this rapidly.

  Port 1: Keypad.  Same as every other Z80 model.

  Port 2: Display contrast.

  Port 3: Interrupt status, ON key, and power control.

  Port 4: Timer and LCD control.

  Port 5: Mapping for memory bank A.  Don't touch this.

  Port 6: Mapping for memory bank B.  The ROM doesn't use this memory
    bank; you can map it either to RAM or to one of the two ROM pages
    if you feel like it.

  Port 7: GPIO.  Doesn't do anything - there's no link port connected.


 The revised TI-81 (version 2.x) hardware ports are (as far as I can
 tell) identical to those of the original TI-82.  See 82-ports.txt for
 more information about them.

  Port 0: Presumably GPIO.

  Port 1: Keypad.

  Port 2: Memory mapping.  Probably works the same way as the TI-82
    and TI-83, but there's really no reason to alter it.

  Port 3: Interrupt status, ON key, and power control.

  Port 4: Timer control and maybe other stuff.

  Port 10h: Control for the T6A04 LCD driver.

  Port 11h: Data for the T6A04 LCD driver.


Memory Layout
-------------

 The TI-81's memory layout is very different from those of later
 calculators.  Instead of a large "user memory" area where variables
 are dynamically allocated, most variables (even the matrices) are
 assigned to fixed addresses in RAM.  (Most of these addresses can be
 found in ti81.inc.)  The exceptions are programs, equations, and the
 {x} and {y} lists.

 The equations are dynamically allocated within the equMem area.  They
 are stored in order, starting with Y1 and ending with Y3t.  So, for
 instance, (y3Start) is both the starting address of Y3, and the
 ending address (plus 1) of Y2.  In normal operation, (y1Start) always
 equals equMem, but you can set it to some other value if you like.

 Programs are dynamically allocated in the progMem area.  Like
 equations, programs are stored in numerical order (prgm0 first,
 followed by prgm1 through prgm9, then prgmA through prgmTheta.)  In
 normal operation, (prgm0Start) would always equal progMem, but if
 Unity is installed, a small amount of memory is reserved between
 progMem and (prgm0Start).

 The statistics data lists, {x} and {y}, are stored in reverse order,
 at the end of the program memory area.  So {y}(1) will always be
 stored at progMemEnd - 7, {x}(1) at progMemEnd - 15, {y}(2) at
 progMemEnd - 23, and so forth.  To obtain the address of the last
 item in the list (which is also the end of the free memory area),
 call the system routine GetStatCount followed by GetStatXPtr.

 As with other TI calculators, the TI-81 has a "system flags" area,
 which can be addressed using the IY register.  (As with other
 calculators, the IY register is always expected to point to this
 area, and changing it may cause problems.)

 The following diagram summarizes the layout of variables in RAM:


   E000 ================================ Start of RAM
         Video memory
   E300 --------------------------------
         System flags, variables, etc.
	 (statically allocated.)
   F347 -------------------------------- progMem
         Unity program loader
      * -------------------------------- (prgm0Start)
         prgm0
      * -------------------------------- (prgm1Start)
         prgm1
      * -------------------------------- (prgm2Start)
         ...
      * -------------------------------- (prgmThetaStart)
         prgmTheta
      * -------------------------------- (prgmThetaEnd)

         Free memory

      * -------------------------------- progMemEnd - 16*N + 1
         {x}(N)
	 {y}(N)
	 ...
	 {x}(2)
	 {y}(2)
	 {x}(1)
	 {y}(1)
   FCA7 -------------------------------- progMemEnd + 1
         More static storage
   FCC7 -------------------------------- equMem
         Y1
      * -------------------------------- (y2Start)
         Y2
      * -------------------------------- (y3Start)
         ...
      * -------------------------------- (x3tStart)
         X3t
      * -------------------------------- (y3tStart)
         Y3t
      * -------------------------------- (y3tEnd)

         Free memory

   FE2F -------------------------------- equMemEnd + 1
         Hardware stack
  10000 ================================ End of RAM


LCD Management
--------------

 The two hardware versions of the TI-81 manage the LCD in very
 different ways.  The original TI-81 hardware, like the TI-85 and
 TI-86, refreshes the LCD directly from RAM, so whatever is stored in
 the videoMem area will show up immediately on the screen.

 The revised TI-81 hardware, like the TI-82 series, uses a T6A04 LCD
 driver, which is separate from the CPU.  To update the screen
 contents, you need to call the LCD_Copy routine.  This routine will
 be called as part of the GetKey routine, and will also be called
 periodically while the run indicator is enabled.

 Thus, if your program is interactive but doesn't use GetKey (e.g.,
 you use GetCSC in a loop), you should either enable the run indicator
 (call RunIndicOn or set the indicRun flag) or call LCD_Copy
 periodically.

 ti81.inc defines the macro UPDATE_LCD, which either does nothing or
 calls LCD_Copy depending on the hardware version.


Display Routines
----------------

 The routines ClrScrn, PutC, PutMap, DispHL, NewLine, HomeUp,
 EraseEOL, and EraseEOW work as they do on the other Z80 calculators.

 The TI-81 has a peculiar, non-standard character set.  The normal,
 "printable" characters range from 01h (space) to 7Fh (cross).  The
 lowercase letters 'j', 'k', and 'z' are not included (since these
 letters are not used in any system messages or tokens.)  In addition,
 the "small font" used for displaying values on the graph screen is in
 fact a subset of the large font (for instance, the small digit '2' is
 the same as the superscript '2' used for squaring.)  The "small" set
 is very limited: only the characters =, -, ., 0 to 9, E, theta, R, T,
 X, and Y are available.

 For displaying normal, large-font strings, you can use PutFlagStr.
 This routine is similar to PutS, but instead of marking the end of
 the string with a zero byte, the end of the string is marked by
 setting bit 7 of the last character.  In asm81, you can define such a
 string using the ASCIS directive.  For example:

	ld hl,my_string
	ROM_CALL PutFlagStr

	...

my_string:
	.ascis "Blah blah"

 (The last line is equivalent to

	.db "Blah bla", 'h' | $80

 but more readable.  Note that asm81 is also translating ASCII
 characters to the TI-81 character set.)

 For displaying floating-point numbers, the FormReal and FormEReal
 routines are similar to their TI-83 counterparts, but produce a
 "flag"-marked string as output rather than a zero-terminated string.

 The small-font display routines are somewhat tricky to use; you must
 pass the address of the location in the screen buffer where you want
 the character to be displayed.  The position of the character within
 that byte is determined by the textLeftSide flag.  For instance, to
 display a character at row 5, column 16:

	set textLeftSide,(iy + textFlags)
	ld hl,videoMem + 12*5 + 2
	ld a,ScapX
	ROM_CALL PutSmallChar

 The PutSmallFlagStr routine can be used to display numbers (e.g.,
 strings produced by FormReal or FormEReal) in small characters:

	set textLeftSide,(iy + textFlags)
	ld hl,videoMem + 12*5 + 2
	ld de,my_string
	ROM_CALL PutSmallFlagStr

	...

my_string:
	.ascis "3.14159"

 (PutSmallFlagStr can also be abused to display stuff other than
 numbers, but this is clearly not the intention.)


Input Routines
--------------

 The routines GetCSC and GetKey should be familiar to TI-83-series
 programmers.  GetCSC checks if any key has been pressed since the
 last call to GetCSC (the actual scanning is done by the system
 interrupt routine, which calls the KbdScan routine.)  GetKey waits
 for a key to be pressed, and handles the 2nd and Alpha keys as
 modifiers.

 (If you use GetKey, the user can press 2nd+Off to turn the calculator
 off, which aborts the running program.  Unlike many other calculators
 and shells, this is perfectly safe on the TI-81, provided that your
 program doesn't itself leave memory in an unstable state.)

 The ReadKeyGroup routine (known as KEY_READ on the TI-82, and similar
 to MirageOS's directin) can be used to scan the keypad manually.


Floating-Point Math
-------------------

 Like later calculators, the TI-81 has both a "standard-precision"
 (64-bit) and an "extended-precision" (80-bit) floating-point format.
 Unlike later calculators, the difference is not trivial.  Most math
 routines require extended-precision numbers as inputs, and produce
 extended-precision numbers as outputs.  Most variables, however, are
 stored in standard-precision form.

 The "standard-precision" format is 8 bytes, containing 13 decimal
 digits:

  EE MM MM MM MM MM MM MT

 The "extended-precision" format is 10 bytes, containing 16 decimal
 digits:

  EE MM MM MM MM MM MM MM MM 0T

 In these formats, EE is the biased exponent (same as on the TI-82/83
 series), MMMM... are the mantissa digits (BCD, big endian), and T is
 a set of flags (where 8 = negative, 4 = matrix value, 2 = value used
 in graphing, 1 = unknown.)

 The routines OP1StdToExt, OP2StdToExt, and OP1ExtToStd can be used to
 convert numbers between the two formats.  The routines MovFPToOP1,
 MovFPToOP2, and MovFPFrOP1 will both convert and copy a number, so
 they can be helpful for copying values to and from standard-precision
 variables.

 The TI-81 does not use "variable names" in the sense that they are
 used by later calculators, but it is possible for an OP register to
 store a "matrix reference."  This is the format used for inputs and
 outputs to the matrix math routines, as well as by certain other
 routines such as StoAns and RclAns.  A matrix reference has the
 following form:

  -- -- CC RR LL HH -- 04

 where RR is the number of rows, CC the number of columns, and HHLL is
 the address of the matrix data.  The other byte values are ignored.


Custom Interrupt Routines
-------------------------

 Unity allows programs to install custom interrupt handler routines,
 which you can use for a variety of purposes (key hooks, cursors or
 other status indicators, timers, grayscale emulation, whatever you
 can think of.)

 Custom interrupt routines are responsible for uninstalling themselves
 when necessary (whether because the program exited normally, an error
 occurred, or the user aborted the program using Y= + GRAPH + ON.)
 The easiest and safest way to do so is by using the macros defined in
 ti81.inc.

 As an example, the following code displays a continuously-increasing
 counter in the top right corner of the screen.  Note that we save and
 restore (curRow), as well as OP1 and scratchMem (since the latter two
 are destroyed by the DispHL routine.)

      DEFINE_CUSTOM_INT Counter
	ld hl,(curRow)
	push hl
	ld de,counter_temp
	ROM_CALL Mov10FrOP1
	ld hl,scratchMem
	ROM_CALL Mov5B
	ld hl,11 * 256
	ld (curRow),hl
	ld hl,(counter_value)
	inc hl
	ld (counter_value),hl
	ROM_CALL DispHL
	UPDATE_LCD
	ld hl,counter_temp
	ROM_CALL Mov10ToOP1
	ld de,scratchMem
	ROM_CALL Mov5B
	pop hl
	ld (curRow),hl
	ret

 To enable this routine, simply write:

	CUSTOM_INT_START Counter

 To disable it:

	CUSTOM_INT_STOP

 If you want a routine to only run once, you can use
 DEFINE_CUSTOM_INT_NO_REPEAT instead of DEFINE_CUSTOM_INT.

 A few important notes about custom interrupt routines:

 - Custom interrupt routines are called before the system interrupt
   routine runs (which has both advantages and drawbacks.)

 - Obviously, the "emergency abort" sequence (Y= + GRAPH + ON) cannot
   be used to abort an interrupt routine, so make your routines as
   simple as possible.

 - Custom interrupt routines MUST preserve the IX register.  You may,
   however, destroy the AF, BC, DE, HL, AF', BC', DE', and HL'
   registers.

 - Custom interrupt routines must not enable interrupts (since that
   could lead to massive stack overflows) and therefore must not call
   any system routines (such as GetCSC) that enable interrupts.

 - If your interrupt routine takes a long time to complete, you will
   find that you are missing some timer events (this will, for
   instance, cause the cursor to blink more slowly.)  This is probably
   harmless, apart from taking up a lot of CPU time and draining the
   batteries faster.

 - If for whatever reason you need to enable interrupts, or you want
   to allow for emergency aborts inside your routine, one option is
   for your routine to use DEFINE_CUSTOM_INT_NO_REPEAT, then manually
   re-enable itself when it's finished:

      DEFINE_CUSTOM_INT_NO_REPEAT BigRoutine
	ei
	<do stuff>
	di
	CUSTOM_INT_CONTINUE
	ret

 - If you need to do something *after* the system interrupt routine
   runs, you can do the following:

      DEFINE_CUSTOM_INT_NO_REPEAT PostRoutine
	rst 38h
	<do stuff - note that interrupts are enabled here>
	CUSTOM_INT_CONTINUE
	pop hl
	jp _PopHLDEBCAFRet

   Be very careful, however - this sort of thing can easily get you
   into an extremely nasty infinite loop.

 It's also possible to create a persistent interrupt routine that will
 stick around after your program exits, but in order to do that,
 you'll need to allocate a permanent memory area (most likely
 somewhere in program memory) to use for the purpose.  This is rather
 complicated to do correctly, so I will leave it as an exercise for
 the interested reader.

