Optimisation and Shells

by Ciaran McCreesh
Created: 7th December 1999
Last Modified: 15th December 1999

This page will explain another two ports, Port 1 (the keypad port) and Port 2 (the contrast port).

The Keypad

Rather than using the various ROM calls that access the keypad you can read it directly using ports. The port that controls the keypad is port 1. It can be written to and read from, and works as follows:

Writing to port 1 sends a mask to the keypad, enabling you to only read certain keys. Reading from the keypad tells you which group(s) of keys have at least one key pressed down. The values are described in the table below.

Accessing the Keypad through Port 1
Read (Bit Cleared) Write (Binary)
00111111 01011111 01101111 01110111 01111011 01111101 01111110
7 More Alpha x-Var Del      
6 Exit Graph Table Prgm Custom Clear  
5 2nd Log Sin Cos Tan ^  
4 F1 LN EE ( ) ÷  
3 F2 x2 7 8 9 × Up
2 F3 , 4 5 6 - Right
1 F4 STO> 1 2 3 + Left
0 F5   0 . (-) Enter Down

So, for example, to pause the calculator until enter is pressed you could do the following (the bit read in is reset, or 0 if the key has been pressed):

  ld a,%01111101    ;bitmask for Enter key
  out (1),a         ;send it to keypad
KeyLoop:
  in a,(1)          ;get value from keypad
  rrca              ;rotate it right, placing the
                    ;least significant bit in the
                    ;carry flag...
  jr c,KeyLoop      ;...and if there is no carry
                    ;enter has not been pressed

Notice that there are lots of comments for this piece of code. This is because it's optimised for code size, which makes it a bit cryptic at first. A more mundane way of doing the same thing (but taking up one more byte) is:

  ld a,%01111101    ;bitmask for Enter key
  out (1),a         ;send it to keypad
KeyLoop:
  in a,(1)          ;get value from keypad
  and %00000001     ;and it with bit for Enter key
  jr nz,KeyLoop     ;and if the bit is not zero
                    ;enter has not been pressed

The third way of doing it involves the bit instruction. This method differs from the previous two ways in that the value read in is not destroyed. In this case it's not important (and a waste of time) but sometimes it is, for example when testing for multiple keys (often the arrow or F keys). The bit instruction isn't that useful usually - it tests a specific bit of an argument.

  ld a,%01111101    ;bitmask for Enter key
  out (1),a         ;send it to keypad
KeyLoop:
  in a,(1)          ;get value from keypad
  bit 0,a           ;and test bit 0
  jr nz,KeyLoop     ;if it is set enter has not
                    ;been pressed, so loop

In most circumstances I use the second method, but I use the first when it will work (only certain keys are worth doing this for). As you can see, in assembly there are many ways of doing the same thing...

Contrast

The contrast on the ti86 is also controlled from a port - Port 2. Writing a value (between $0 and $1f) to it adjusts the contrast. Unfortunately it is write-only. To get the current value of the contrast you must read memory location $c008 (equate _contrast). Similarly, if you exit a program with the contrast changed you should write the new value to $c008.

Here's a simple program that flickers the screen from being black to clear by setting the contrast to $0 then $1f, then $0 and so on. The halt instruction pauses the calculator until an interrupt occurs - 1/200th of a second.

#include "ti86asm.inc"
.org _asm_exec_ram

  nop
  jp ProgStart

.dw 0,ShellTitle

ShellTitle:
.db "Flicker Screen",0

ProgStart:
  xor a                    ;a = 0
  ld bc,$2002              ;b = 20 (loop counter)
                           ;c = 2 (contrast port)

FlickerLoop:
  out (c),a                ;set contrast to a
  cpl                      ;a = not a
  and $1f                  ;a = a and $1f

  ld d,12                  ;d = 12 (delay loop)
DelayLoop:
  halt                     ;pause for 1/200 sec
  dec d                    ;d = d - 1
  jr nz,DelayLoop          ;if d > 0 goto DelayLoop

  djnz FlickerLoop         ;b = b - 1; if > 0 goto FlickerLoop

  ld a,(_contrast)         ;get real value for contrast
  out (c),a                ;set contrast back to normal
  ret                      ;exit

.end