Jaap's Psion II Page

                                 CHAPTER 7

                                 ________
                                 KEYBOARD



The keyboard consists of 36  keys  which  are  polled  on  interrupt  at  a
variable  interval,  initially  set  to  50ms.   The  keys auto-repeat at a
variable rate and key presses are  stored  in  a  16  character  type-ahead
buffer.   The  keyboard look-up table, the polling, the key translation and
the shift status can all be altered through the use of the keyboard vectors
and  variables.  The SHIFT key can be disabled and the keys for CAP and NUM
can be changed.

The keyboard interrupts also control alarm checking and display timing, and
increment a frame counter.

While testing for a key, the datapacks or the machine itself may switch off
and a low battery test is made.



     ______________________
7.1  OPERATING THE KEYBOARD

The keyboard consists of a 6 by 6 matrix of keys, diagrammed below.

    |---------|---------|---------|---------|---------|---------|
    |  clear  |         |   cap   |   num   |         |         |
                             |         |         -         -
    |  [ ON ] |  [MODE] |  [ ^ ]  |  [ v ]  |  [ < ]  |  [ > ]  |
    |---------|---------|---------|---------|---------|---------|
    |    <    |    >    |    (    |    )    |    %    |    /    |
    |  [ A ]  |  [ B ]  |  [ C ]  |  [ D ]  |  [ E ]  |  [ F ]  |
    |---------|---------*******************************---------|
    |    =    |    "    *    7    |    8    |    9    *    *    |
    |  [ G ]  |  [ H ]  *  [ I ]  |  [ J ]  |  [ K ]  *  [ L ]  |
    |---------|---------*---------|---------|---------*---------|
    |    ,    |    $    *    4    |    5    |    6    *    -    |
    |  [ M ]  |  [ N ]  *  [ O ]  |  [ P ]  |  [ Q ]  *  [ R ]  |
    |---------|---------*---------|---------|---------*---------|
    |    ;    |    :    *    1    |    2    |    3    *    +    |
    |  [ S ]  |  [ T ]  *  [ U ]  |  [ V ]  |  [ W ]  *  [ X ]  |
    |---------|---------*---------|---------***********---------|
    |         |         *    0    |    .    *         |         |
    | [SHIFT] |  [DEL]  *  [ Y ]  |  [ Z ]  * [SPACE] |  [EXE]  |
    |---------|---------*********************---------|---------|

The  letter  keys  normally  produce  upper-case  letters  and  return  the
corresponding  ascii value.  By holding down the SHIFT key and pressing one
of the letters you can access the symbols  and  numbers  marked  above  the
keys.

To select lower-case letters, hold down the SHIFT key  and  press  the  CAP
key.  Repeat this to return the keyboard to upper-case mode.

Pressing the SHIFT and NUM keys, puts you permanently in  'shift'  mode  so
that the function of the SHIFT key is now reversed.  The functions of SHIFT
NUM, SHIFT CAP and SHIFT DEL, however,  are  not  affected.   (DEL  deletes
characters to the left and SHIFT DEL deletes characters from the right)

The top row of keys and some on the bottom row  are  special.   These  keys
return the following values:

                KEY             VALUE
                ---             -----
                ON/CLEAR          1
                MODE              2
                |
                ^                 3
                |
                v                 4
                -
                <                 5
                -
                >                 6
                SHIFT DEL         7
                DEL               8
                EXE               13

The SHIFT key, the CAP key and  the  NUM  key  do  not  return  values  but
immediately carry out their function.



     _________________
7.2  KEYBOARD SCANNING

The ON/CLEAR key is polled independently of the others.  The  remaining  35
keys are polled on a 5 by 7 matrix.



       ________________
7.2.1  THE ON/CLEAR KEY

Testing for the ON/CLEAR key is done by reading bit 7 of  port  5  (address
$15).   If the key is pressed, bit 7 will be set, otherwise bit 7 is clear.
Hence, the ON/CLEAR key can be  tested  directly  and  very  quickly.   The
following routine waits for it to be pressed:


TESTKEY:
        LDA     A,POB_PORT5:    ;READ ADDRESS $15
        BPL     TESTKEY         ;BRANCH IF BIT 7 IS CLEAR


In some applications it is necessary to protect against 'key bounce' and it
is  recommended  that a delay of approximately 50ms is used.  The following
routine waits for the ON/CLEAR key to be released and then pauses for 50ms:

DEBOUNCE:
        LDA     A,POB_PORT5:    ;READ ADDRESS $15
        BMI     DEBOUNCE        ;BRANCH IF BIT 7 IS SET
        LDX     #11600          ;FOR DELAY OF 50MS
1$:     DEX
        BNE     1$

Note that if keyboard  interrupts  are  enabled  while  running  the  above
procedure, holding down the ON/CLEAR key will fill up the keyboard buffer.

A system service, KB$BREK, is provided to  test  if  the  ON/CLEAR  key  is
pressed or if an ON/CLEAR key is in the keyboard buffer.



       ______________
7.2.2  THE KEY MATRIX

The key matrix consists of 7 columns, controlled  by  the  SEMI-CUSTOM-CHIP
'COUNTER',  and  5  rows  which can be read as bits 2 to 6 of PORT 5.  (The
ON/CLEAR key uses bit 7).  The layout is as follows:

                                        COUNTER
                                        ~~~~~~~
                K2       K3       K4       K7       K5       K6       K1

                v        v        v        v        v        v        v
                |        |        |        |        |        |        |
                             |        |        -        -
PORT 5          |  MDE   |   ^    |   v    |   <    |   >    |        |
~~~~~~          |  ===   |  ===   |  ===   |  ===   |  ===   |        |
                |  | |   |  | |   |  | |   |  | |   |  | |   |        |
                |  | +---|--|-+---|--|-+---|--|-+---|--|-+---|--------+
             A  |  |  B  |  |  C  |  |  D  |  |  E  |  |  F  |
            === |  | === |  | === |  | === |  | === |  | === |
            | +-+  | | +-+  | | +-+  | | +-+  | | +-+  | | +-+
  6   <-----+---|--|-+---|--|-+---|--|-+---|--|-+---|--+-+   |
             G  |  |  H  |  |  I  |  |  J  |  |  K  |     L  |
            === |  | === |  | === |  | === |  | === |    === |
            | +-+  | | +-+  | | +-+  | | +-+  | | +-+    | +-+
  5   <-----+---|--|-+---|--|-+---|--|-+---|--+-+---|----+   |
             M  |  |  N  |  |  O  |  |  P  |     Q  |     R  |
            === |  | === |  | === |  | === |    === |    === |
            | +-+  | | +-+  | | +-+  | | +-+    | +-+    | +-+
  4   <-----+---|--|-+---|--|-+---|--+-+---|----+---|----+   |
             S  |  |  T  |  |  U  |     V  |     W  |     X  |
            === |  | === |  | === |    === |    === |    === |
            | +-+  | | +-+  | | +-+    | +-+    | +-+    | +-+
  3   <-----+---|--|-+---|--+-+---|----+---|----+---|----+   |
            SHF |  | DEL |     Y  |     Z  |    SPC |    EXE |
            === |  | === |    === |    === |    === |    === |
            | +-+  | | +-+    | +-+    | +-+    | +-+    | +-+
  2   <-----+---|--+-+---|----+---|----+---|----+---|----+


To control the COUNTER lines, there are two significant addresses:

     1.  SCA_COUNTERRESET (address $300) - sets all lines to zero.

     2.  SCA_COUNTERCLOCK (address $340) - increments  COUNTER  address  by
         one.


To carry out these functions,  simply  read  or  write  to  the  respective
address.   The following will set the contents of the counter to $3F, i.e. K1
to K6 high and K7 low:

        TST     SCA_COUNTERRESET        ;SET COUNTER TO ZERO
        LDA     A,#$3F
1$:     TST     SCA_COUNTERCLOCK        ;INCREMENT COUNTER
        DEC     A
        BNE     1$

When a key is pressed, a connection is made from one of K1 to K7 to one  of
the  PORT  5 lines.  The lines on PORT 5 are pulled high, so by setting one
of K1 to K7 low, a specific key press can be detected.  For example, if  K7
only is low, the 'D' key can be detected by bit 6 of PORT 5 going low.

Polling the entire keyboard involves setting each of K1 to K7 low in  turn,
reading  PORT  5,  and  decoding  the  key.   By  setting  all  7 lines low
simultaneously, a quick check for any  key  can  be  made.   The  following
routine will test for any key press:

        TST     SCA_COUNTERRESET        ;SET K1 TO K7 LOW
        LDA     A,POB_PORT5:            ;READ PORT 5
        BMI     CLRKEY                  ;BRANCH IF ON/CLEAR KEY IS PRESSED
        AND     A,#$7C                  ;IGNORE BITS 7,1 AND 0
        CMP     A,#$7C                  ;CHECK IF ANY BIT IS LOW
        BNE     AKEY                    ;BRANCH IF KEY PRESS DETECTED

The method of keyboard scanning is explained in the next section.



       ____________
7.2.3  KEY SCANNING

     The keyboard is scanned as fast as possible using the routine  pointed
to  by  the  ram  vector  BTA_POLL.   For maximum efficiency, the following
method is used:

     1.  Check if ON/CLEAR key is pressed and exit if it is.

     2.  Check quickly if any other key is pressed and exit if not.

     3.  Determine which key is pressed and check if the SHIFT key is  also
         pressed.


Steps 1 and 2 are described in sections 7.2.1 and 7.2.2.  Step  3  involves
setting  each of the SEMI-CUSTOM-CHIP COUNTER lines (K1 to K7) low in turn.
This is done most efficiently by setting K7 low first then K6 down to K1:


K7      K6      K5      K4      K3      K2      K1      COUNTER VALUE

0       1       1       1       1       1       1            $3F
1       0       1       1       1       1       1            $5F
1       1       0       1       1       1       1            $6F
1       1       1       0       1       1       1            $77
1       1       1       1       0       1       1            $7B
1       1       1       1       1       0       1            $7D
1       1       1       1       1       1       0            $7E

The following code will set the values $3F,$5F...  etc on the COUNTER:

        TST     SCA_COUNTERRESET        ;READ ADDRESS $300 TO ZERO COUNTER
        LDA     B,#$40
        PSH     B
        DEC     B                       ;INITIAL VALUE $3F
        BRA     CLOCKB
NEXTCOL:
        PSH     B
CLOCKB:
        TST     SCA_COUNTERCLOCK        ;READ ADDRESS $340 TO INC COUNTER
        DEC     B                       ;DO IT B TIMES
        BNE     CLOCKB
        BSR     READPORT5               ;READ PORT 5 HERE AND DECODE KEY
        PUL     B
        LSR     B
        BNE     NEXTCOL                 ;REPEAT FOR COLUMNS K7 to K1

The SHIFT key is in column K2 so scanning of the  keyboard  must  continue,
even after one key has been found, to check if SHIFT is also pressed.

This method results in the following approximate keyboard scan times:

        KEYBOARD STATE                  TIME TAKEN TO SCAN KEYBOARD
        ==============                  ===========================
        NO KEY PRESSED                            0.3ms
        ON/CLEAR KEY PRESSED                      0.2ms
        ANY OTHER KEY PRESSED                     1.9ms

Note that the time taken for the entire keyboard interrupt may be increased
by one or more of the following:

     1.  The variable key click, 1ms by default

     2.  The 'buffer-full' beep which lasts 10ms.

     3.  Alarm checking and/or ringing which may last up to 2  mins.   (see
         section 19.3)




     ___________________
7.3  KEYBOARD INTERRUPTS

     The keyboard scanning routine is called at regular intervals  from  an
interrupt  generated by the processors's TIMER 1 counter.  This is a 16 bit
FREE RUNNING COUNTER (FRC) (address $09,$0A) incremented by  the  processor
clock.

The interrupt is generated when the value in the FRC matches a value set up
in  the TIMER 1 OUTPUT COMPARE REGISTER 1 (address $0B,$0C) and is directed
to the address contained in the rom  at  address  $FFF4.   The  ram  vector
BTA_OCI is then used to jump to the interrupt service routine, i.e.:

        LDX     BTA_OCI
        JMP     0,X

The code in the interrupt service routine handles the following:

     1.  Polling the keyboard using a routine at BTA_POLL

     2.  Translating any key found using a routine at BTA_TRAN

     3.  Producing a key 'click'

     4.  Checking for alarms

     5.  Incrementing a frame-counter

     6.  Decrementing a display-timer




       ____________
7.3.1  INITIALISING

     Keyboard interrupts are fully initialised  on  a  cold  start  of  the
machine  only,  using  the  system  service  KB$INIT.   When each interrupt
occurs, the first task is to set up the FREE  RUNNING  COUNTER  and  OUTPUT
COMPARE REGISTER for the next interrupt, i.e.:

        LDA     A,POB_TCSR1:    ;MUST BE READ SO THAT A SUBSEQUENT WRITE TO
        CLR     A               ; - OCR1 WILL CLEAR THE OUTPUT COMPARE FLAG
        CLR     B               ; - SEE ACCOMPANYING 6301 BOOK
        STD     POW_FRC:        ;RESET FREE RUNNING COUNTER TO ZERO
        LDD     KBW_TDEL:       ;GET VALUE FOR KEYBOARD INTERRUPT RATE
        STD     POW_OCR1:       ;SET OUTPUT COMPARE REGISTER


When the machine switches off, the value in TIMER CONTROL STATUS REGISTER 1
and  the state of the INTERRUPT MASK are saved so that on a warm start they
can be restored.  Also, the keyboard buffer is  flushed  on  a  warm  start
using system service KB$FLSH.



       _______
7.3.2  POLLING

     The routine to poll the keyboard is  called  through  the  ram  vector
BTA_POLL.   The function of this routine is to scan the keyboard and return
the key pressed in the A register and to set  any  flags  required  by  the
translating routine at ram vector BTA_TRAN.  The following code can be used
to poll the keyboard and will return the key pressed in the A register as a
value between 0 and 36 (0 means no key):

KB_POLL:
        TST     SCA_COUNTERRESET        ;SET COUNTER TO ZERO
        AIM     #<$FF-KY_SHFT>,KBB_STAT:        ;NO SHIFT KEY YET
        LDA     B,POB_PORT5:            ;TEST FOR ON/CLEAR KEY
        BPL     NOTCLR                  ;BRANCH IF NOT PRESSED
        LDA     A,#36                   ;36 REPRESENTS THE ON/CLEAR KEY
        RTS

NOTCLR:
        CLR     A                       ;NO KEY YET
        STA     A,KBB_KNUM:             ;KEY NUMBER BECOMES 0
        PSH     A                       ;PSH 0
        BSR     LOOKKEY                 ;ANY KEY IS PRESSED? SETS Z IF NOT
        PUL     A                       ;DOES NOT AFFECT Z FLAG
        BEQ     ENDKYBD                 ;BRANCH IF NO KEY FOUND IN LOOKKEY
        LDA     B,#$40
        PSH     B
        DEC     B
        BRA     CLOCKB

NEXTCOL:
        PSH     B
CLOCKB:
        PSH     A
1$:     TST     SCA_COUNTERCLOCK        ;READ ADDRESS $340 TO INC COUNTER
        DEC     B                       ;DO IT B TIMES
        BNE     1$
        BSR     LOOKKEY                 ;SETS B TO ROW NUMBER, IF KEY FOUND
        PUL     A                       ;IN THIS COLUMN
        BEQ     NOPSH                   ;BRANCH IF NO KEY IN THIS COLUMN
        ADD     B,KBB_KNUM:             ;GOT A KEY BUT MUST CONTINUE,
        TBA                             ;TO CHECK IF SHIFT IS ALSO PRESSED
NOPSH:  LDA     B,KBB_KNUM:
        ADD     B,#5
        STA     B,KBB_KNUM:             ;POINT TO NEXT COLUMN
        PUL     B
        LSR     B                       ;SELECT NEXT COLUMN
        BNE     NEXTCOL
ENDKYBD:
        RTS



LOOKKEY:
                ;SETS B TO ROW NUMBER (1-5) OF KEY IN CURRENT COLUMN
                ;B WILL BE ZERO IF NO KEY IN THIS COLUMN (OR JUST SHIFT)
                ;SETS KY_SHFT FLAG IF SHIFT IS PRESSED
                ;SETS Z ON B
                ;PRESERVES X

        LDA     B,#5            ;CHECK 5 ROWS
        LDA     A,POB_PORT5:    ;READ PORT 5
        ASL     A               ;IGNORE BIT 7
NEXTROW:ASL     A               ;ROTATE KEY INTO CARRY, CLR IF KEY PRESSED
        BCC     GOTKEY          ;BRANCH IF KEY PRESS DETECTED
        DEC     B
        BNE     NEXTROW         ;CHECK NEXT ROW
        RTS                     ;RETURN WITH B=0 IF NO KEY FOUND

GOTKEY:                         ;B IS POSITION IN ROW
        TST     KBB_SHFK        ;TEST SHIFT ENABLE FLAG
        BNE     11$             ;EXIT IF SHIFT DISABLED, B IS NOT ZERO
        PSH     A
        LDA     A,KBB_KNUM:     ;GET CURRENT KEY NUMBER
        CMP     A,#25           ;IS IT 6TH COLUMN, CONTAINING SHIFT KEY?
        PUL     A
        BNE     11$             ;EXIT IF NOT, B IS NOT ZERO
        DEC     B               ;COMPARE B WITH ROW 1 (ROW WITH SHIFT KEY)
        BEQ     10$             ;BRANCH IF THIS IS THE SHIFT KEY (B IS 0)

        PSH     B               ;ELSE CHECK IF SHIFT ALSO PRESSED
1$:     ASL     A
        DEC     B
        BNE     1$
        PUL     B
        INC     B               ;RESTORE B TO REAL KEY PRESSED
        BCS     11$             ;BRANCH IF C SET (FROM ASL A)

10$:    OIM     #KY_SHFT,KBB_STAT:      ;SET SHIFT-PRESSED FLAG
        TST     B                       ;SET Z FLAG ON B
11$:    RTS

The auto-repeat of the  keys  is  accomplished  using  KBB_PREV,  KBB_DLAY,
KBB_REPT and KBB_CNTR in the following manner:

        LDX     BTA_POLL                ;POLL KEYBOARD (SEE ABOVE)
        JSR     0,X                     ;RETURN KEY PRESS IN A
        CMP     A,KBB_PREV:             ;IS SAME KEY STILL PRESSED ?
        BEQ     1$                      ;BRANCH IF YES
        STA     A,KBB_PREV:             ;SAVE NEW KEY
        LDA     B,KBB_DLAY:             ;DELAY BEFORE AUTO-REPEATING BEGINS
        BRA     2$

1$:     LDA     B,KBB_REPT:             ;DELAY BETWEEN KEYS DURING REPEAT
        TST     KBB_CNTR                ;TEST DELAY COUNTER
        BEQ     2$                      ;BRANCH IF TIME TO RETURN KEY
        LDA     B,KBB_CNTR:
        DEC     B                       ;TO DECREMENT DELAY COUNTER
        CLR     A                       ;NO KEY UNTIL COUNTER IS ZERO

2$:     STA     B,KBB_CNTR:             ;SET DELAY COUNTER
        LDX     BTA_TRAN
        JSR     0,X                     ;TRANSLATE KEY IN A REGISTER

        TIM     #KY_CPNM,KBB_STAT:      ;IS IT CAP OR NUM.
        BNE     DOCLICK                 ;IF SO, JUST EMIT KEY CLICK

        TST     A                       ;TEST IF KEY PRESSED
        BEQ     END                     ;END OF INTERRUPT IF NOT

                ;INSERT KEY INTO BUFFER - SEE SECTION 7.3.4.
DOCLICK:
                ;EMIT KEY CLICK         - SEE SECTION 7.3.5.

END:    RTI                             ;RETURN FROM INTERRUPT




       ___________
7.3.3  TRANSLATING

     The ram vector BTA_TRAN points to the routine used  to  translate  the
key  returned  from BTA_POLL.  The function of this routine is to translate
the key number passed in the A register into an ascii character and  return
it in the A register.

The following code can be used to translate a key number (0 to 36) into the
ascii  key  it  represents  on the standard Organiser.  It uses KBB_STAT to
decide whether 'shifted' characters, or lower-case characters are  returned
and  also  to  refresh the cursor in the correct state (block or line).  If
the key pressed is SHIFT CAP or SHIFT NUM, no key is returned (i.e.   the  A
register is 0) but the KY_CPNM flag is set.

KB_TRAN:
        LDA     B,KBB_STAT:             ;GET KEYBOARD STATUS FLAGS
        AND     B,#<$FF-KY_CPNM>        ;CLEAR CAP/NUM FLAG
        BPL     3$                      ;BRANCH IF NOT SHIFT, NOT CAP OR NUM
        CMP     A,KBB_CAPK              ;IS IT THE CAP KEY ?
        BNE     1$                      ;BRANCH IF NOT
        EOR     B,#KY_CAPS              ;TOGGLE CAPS FLAG
        BRA     2$
1$:     CMP     A,KBB_NUMK              ;IS IT THE NUM KEY ?
        BNE     3$                      ;BRANCH IF NOT
        EOR     B,#KY_NUMB              ;TOGGLE NUM FLAG
2$:     ORA     B,#KY_CPNM              ;SET CAP/NUM FLAG
        CLR     A                       ;RETURN NO KEY

3$:     PSH     A                       ;SAVE KEY PRESSED
        OS      KB$STAT                 ;SET KEYBOARD STATE
                                        ;PRESERVES B
        TBA                             ;SET A TO KBB_STAT
        PUL     B                       ;B IS KEY PRESSED
        TST     B
        BEQ     9$                      ;BRANCH IF NO KEY PRESSED
        PSH     B
        LDX     BTA_TABL                ;ADDRESS OF KEYBOARD LOOK-UP TABLE
        DEX                             ;SO KEY 1 IS FIRST KEY IN TABLE
        LDA     B,KBB_STAT:             ;GET KEYBOARD STATUS
        ASL     B                       ;EXCLUSIVE OR KY_SHFT WITH KY_NUMB
        BVC     4$                      ;BRANCH IF NOT 'SHIFT' MODE
        LDA     B, 36
        ABX                             ;SKIP TO 'SHIFTED' LOOK-UP TABLE
4$:     PUL     B
        ABX                             ;INDEX INTO TABLE
        LDA     B,0,X                   ;GET ASCII KEY FROM TABLE
        LSR     A                       ;TEST CAPS FLAG
        BCS     5$                      ;IF SET LEAVE AS LOWER CASE
        CMP     B,#^a/a/
        BCS     5$
        CMP     B,#^a/z/
        BHI     5$
        ADD     B,#^a/A/-^a/a/          ;CONVERT B TO UPPER CASE
5$:
        CMP     B,#K_DEL                ;IS IT DELETE KEY
        BNE     9$                      ;BRANCH IF NOT
        ASL     A                       ;TEST SHFT FLAG
        BPL     9$                      ;BRANCH IF NOT SHIFT
        DEC     B                       ;SHIFT DEL IS ALWAYS DEL RIGHT
9$:     TBA                             ;RETURN TRANSLATED KEY IN A
        RTS


The keyboard table pointer  KBA_TABL  points  to  the  following  table  by
default:

KBT_TABL:
        .ASCII  /zvpjd/
        .BYTE   K_EXE
        .ASCII  /xrlf/
        .ASCII  / wqke/
        .ASCII  /yuoic/
        .BYTE   K_DEL
        .ASCII  /tnhb/
        .ASCII  /?smga/         ;(?=SHIFT - NOT RETURNED EVER !)
        .BYTE   K_MODE
        .BYTE   K_UP,K_DOWN
        .BYTE   K_LEFT,K_RGHT
        .BYTE   K_AC            ;ON/CLEAR KEY

        ;SHIFTED CHARACTERS
        ;==================

        .ASCII  /.258)/
        .BYTE   K_EXE
                   $
        .ASCII  $+-$
        .ASCII  $/$
        .ASCII  / 369%/
        .ASCII  /0147(/
        .BYTE   K_DEL
        .ASCII  /:$">/
        .ASCII  /?;,=</         ;(?=SHIFT - NOT RETURNED EVER)
        .BYTE   K_MODE
        .BYTE   K_UP,K_DOWN
        .BYTE   K_LEFT,K_RGHT
        .BYTE   K_AC            ;ON/CLEAR KEY


This vector can be changed to point to a new  translation  table,  but  the
table  must  contain  72  characters unless the translation routine is also
altered.



       _________
7.3.4  BUFFERING

When a key press is detected, the ascii character for that key is placed in
a 16 byte wrap-around buffer, KBT_BUFF.  An offset into the buffer KBB_BACK
and a count of the number of characters in the buffer KBB_NKYS are used  to
implement  the  buffering.  If the buffer is already full, the character is
not stored,  but  a  'buffer-full'  beep  is  emitted  lasting  10ms.   The
following code can be used to buffer the character in the A register:

        LDA     B,KBB_NKYS:             ;GET NUMBER OF KEYS IN BUFFER
        LDX     #10                     ;10MS FOR 'BUFFER-FULL' BEEP
        CMP     B,#16                   ;IS BUFFER FULL ?
        BEQ     DOBUZ                   ;BRANCH IF YES
        LDX     #KBT_BUFF               ;ADDRESS OF KEYBOARD BUFFER
        ADD     B,KBB_BACK:             ;OFFSET INTO BUFFER
        AND     B,#$0F                  ;WRAP AROUND
        ABX
        STA     A,0,X                   ;STORE ASCII KEY IN BUFFER
        INC     KBB_NKYS                ;INCREMENT NUMBER OF KEYS
DOCLICK:
        LDA     B,KBB_CLIK              ;LENGTH OF KEY CLICK
        BEQ     END                     ;NO CLICK IF ZERO
        DEC     B                       ;LENGTH 1 WILL GIVE < 1MS CLICK
        CLR     A
        XGDX                            ;DURATION IN X
DOBUZ:  LDD     TMW_TCNT                ;SIZE OF SWITCH OFF TIME OUT
        STD     TMW_TOUT:               ;RESET TIMEOUT COUNT IF KEY PRESSED
        LDD     #56                     ;A GOOD NOTE
        OS      BZ$TONE                 ;BEEP FOR X MS
END:


The keyboard buffer can be cleared out using  system  service  KB$FLSH  and
there  is  a  facility for inserting a key into a 1 byte buffer KBB_WAIT by
using system service KB$UGET.  KBB_WAIT is always tested for a  key  before
looking in the main keyboard buffer KBT_BUFF.

Keys can also be inserted into KBT_BUFF, but keyboard  interrupts  must  be
prevented  while  this  is  done.   The  following code will insert the key
contained in the A register:

PUTA:
        SEI                             ;STOP INTERRUPTS OCCURRING IN HERE
        LDA     B,KBB_NKYS:             ;GET NUMBER OF KEYS IN BUFFER
        CMP     B,#16                   ;IS BUFFER FULL ?
        BEQ     NOPUT                   ;BRANCH IF YES
        LDX     #KBT_BUFF               ;ADDRESS OF KEYBOARD BUFFER
        ADD     B,KBB_BACK:             ;OFFSET INTO BUFFER
        AND     B,#$0F                  ;WRAP AROUND
        ABX
        STA     A,0,X                   ;STORE ASCII KEY IN BUFFER
        INC     KBB_NKYS                ;INCREMENT NUMBER OF KEYS
NOPUT:  CLI                             ;RESTORE INTERRUPTS




       _________
7.3.5  KEY CLICK

     The key click is produced in  the  keyboard  interrupt  by  using  the
routine for system service BZ$TONE, using the following code:


KEYCLICK:
        LDA     B,KBB_CLIK              ;LENGTH OF KEY CLICK
        BEQ     END                     ;NO CLICK IF ZERO
        DEC     B                       ;LENGTH 1 WILL GIVE < 1MS CLICK
        CLR     A
        XGDX                            ;DURATION IN X
        LDD     #56                     ;A GOOD NOTE
        JSR     BZ_TONE                 ;BEEP FOR X MS
END:

Note:  BZ_TONE is JSR'ed to and not called as an operating system  service.
Hence,  intercepting operating system calls to BZ$TONE (by re-vectoring SWI
- see section 5.5.3) will not affect the key click.  JSR'ing or JMP'ing  to
operating  system services can only be done within the operating system and
not by external routines.

The length of the key click, KBB_CLIK, can be changed.  Zero  will  disable
the  click  and any value up to $FF will specify the length of the click in
milliseconds.

Warning:  BZ_TONE toggles the  alarm  line  to  produce  the  click  (using
addresses  SCA_ALARMHIGH  and  SCA_ALARMLOW)  but  if  SOE_B is high, it is
possible that 21v will appear on any devices present, see chapter  9.   For
example  if  an  eprom device is selected, a byte may be blown on the pack.
Since the  click  occurs  on  interrupt,  care  must  be  taken  to  ensure
interrupts are disabled whenever SOE_B is set high.



       ______________
7.3.6  ALARM CHECKING

Alarms and diary alarms are checked for only when the flag AMB_DOIT is  set
(e.g.  by the NMI on a minute boundary):

        TST     AMB_DOIT                ;TEST DOIT FLAG
        BEQ     1$                      ;BRANCH IF CLEAR

        ;ALARM CHECKING CODE GOES HERE

        CLR     AMB_DOIT                ;CLEAR DOIT FLAG
1$:

     The method of alarm checking is described in chapter 19.  AMB_DOIT  is
cleared  when  alarm  checking  is finished to prevent it being done on the
next interrupt.



       _____________
7.3.7  FRAME-COUNTER

     A 16 bit frame-counter, TMW_FRAM (address $20CB,$20CC), is incremented
by one by the keyboard interrupt and wraps around every 64k:

        LDX     TMW_FRAM
        INX
        STX     TMW_FRAM

It can be used as a random number or for timing.  The rate that the counter
runs at is the keyboard interrupt rate and so can be calculated as follows:

        TIME BETWEEN INCREMENTS (IN SECS) = ( KBW_TDEL + 35 ) / 921600

        KBW_TDEL IS $B3DD BY DEFAULT, GIVING:

        TIME = ( $B3DD + 35 ) / 921600

             = 0.05 SECS

KBW_TDEL can  be  changed  to  adjust  the  timing  but  will  also  affect
everything else controlled by the keyboard interrupts, e.g. key repeat rate.

Note that the rate of  the  frame-counter  is  not  100%  accurate  because
interrupts are disabled while running some parts of the operating system.



       _______ ______
7.3.8  DISPLAY TIMING

     The rate of horizontal and vertical scrolling on the display is  timed
using  DPW_REDY (address $006D,$006E).  This variable is decremented by one
by each keyboard interrupt until it reaches zero, i.e.:

        LDX     DPW_REDY:
        BEQ     NODEX
        DEX
        STX     DPW_REDY:
NODEX:




     ________________
7.4  TESTING FOR KEYS

     Keys are tested for by looking in the keyboard buffer.  While  testing
for a key, if no key is found, the packs may be switched off, a low battery
test is made, and the machine itself may switch off.



       __________________
7.4.1  KEYTEST AND KEYGET

     The system service KB$TEST will return the  first  key  found  in  the
keyboard  buffer (zero if no key) but will not remove it.  KB$GETK uses the
same routine as KB$TEST but will wait for a key  to  be  pressed  and  then
remove it from the buffer.

If the function of key testing is to be changed, both KB$TEST  and  KB$GETK
must be re-vectored by intercepting system calls through SWI, (KB$GETK does
not call KB$TEST through the SWI).  For example, KB$TEST may be  re-written
to carry out a different task when low battery is detected.



       _______________
7.4.2  PACK SWITCH OFF

     If KB$TEST finds no keys in the keyboard buffer,  the  pack-switch-off
flag  KBB_PKOF is tested.  If it is non-zero, the packs are switched off by
calling the system service PK$PKOF (see chapter 9).



       ________________
7.4.3  LOW BATTERY TEST

     If KB$TEST finds no keys in the keyboard buffer, a low battery test is
made.   Bit  0 of PORT 5 is set when the battery supply voltage drops below
approximately 5.2v.  If this is the case, the message BATTERY  TOO  LOW  is
displayed  and  the  machine  will  switch  off  after 4 seconds.  When the
machine is revived, the program will continue in KB$TEST where it left off.



       __________________
7.4.4  MACHINE SWITCH OFF

     If KB$TEST finds no keys in the keyboard buffer,  the  auto-switch-off
flag,  TMB_SWOF,  is  tested.   If  it  is  non-zero, the time remaining in
seconds before switch off occurs, TMW_TOUT,  is  tested.   If  TMW_TOUT  is
zero,  the  machine  is switched off by calling system service BT$SWOF (see
chapter 9).  When the machine is revived,  the  program  will  continue  in
KB$TEST where it left off.



     ______________________________
7.5  KEYBOARD VECTORS AND VARIABLES

     The following vectors and variables allow the function of the keyboard
to  be  customised  very  easily.   They  can  all  be  read and written to
directly, except for KBB_STAT, which should only be written to using system
service KB$STAT.



       ________
7.5.1  KBB_STAT

     KBB_STAT stores the following flags:

        FLAG            BIT OF KBB_STAT         DESCRIPTION
        ====            ===============         ===========
        KY_SHFT               7                 SET IF SHIFT KEY PRESSED
        KY_NUMB               6                 SET IF NUMERIC LOCK
        KY_CPNM               1                 SET IF CAP OR NUM KEY
        KY_CAPS               0                 SET IF LOWER CASE LOCK

KBB_STAT can be read directly, but system service KB$STAT should be used to
write to it (see section 7.6.7).



       ________
7.5.2  BTA_POLL

     This vector points to the routine which polls the keyboard.   It  must
return  the number of the key pressed in the A register (e.g.  0 to 36 - see
section 7.3.2) and can set flags to be used by BTA_TRAN (e.g.   shift,  cap,
num flags in KBB_STAT).



       ________
7.5.3  BTA_TRAN

     This vector points to the routine  which  translates  the  key  number
supplied  from  BTA_POLL  in  the  A  register  into the ascii character it
represents, returned in the A register.  The routine will use  flags  which
have  been  set by BTA_POLL, to decide how to translate the character.  For
example, if the SHIFT flag is set, a 'shifted' set of  characters  will  be
returned,  or  if  a CAP flag is set, the characters will be returned as
lower case.  To decode the character, a table of characters pointed  to  by
BTA_TABL is used.



       ________
7.5.4  BTA_TABL

     This is the vector which points to  a  table  of  characters  used  to
translate a key press into an ascii character.  For an example, see section
7.3.3.  The vector can be changed to point to a new set of  characters  and
should  contain  72  characters  (36  'shifted')  unless,  of  course,  the
translate routine has been changed or SHIFT has been disabled.



       ________
7.5.5  KBB_SHFK

     This flag is used to disable the SHIFT function.  It is tested only in
the  keyboard  poll  routine  at  BTA_POLL.   If the flag is set, the SHIFT
function is disabled and the SHIFT key will act as a normal key.  Hence, it
will return the 26th character of the lookup table which is currently a "?"
character (see section 7.3.2)



       ________
7.5.6  KBB_CAPK

     This byte contains the number of the key (1 to 36) required to be  the
CAP  key.   It  is used only by the keyboard translate routine at BTA_TRAN.
By default it is set to 32 (the up-arrow key).   To  disable  the  CAP  key
altogether, a number greater than 36 should be stored in KBB_CAPK.



       ________
7.5.7  KBB_NUMK

     This byte contains the number of the key required to be the  NUM  key.
It works in exactly the same way as KBB_CAPK.



       ________
7.5.8  KBW_TDEL

     This word controls the rate of keyboard interrupts.  When an interrupt
occurs  the  value  in  KBW_TDEL  is  stored  in the TIMER 1 OUTPUT COMPARE
REGISTER 1, and the FREE RUNNING COUNTER is set to zero, so that  the  next
interrupt will occur after KBW_TDEL clock cycles.

Hence, the time between interrupts = ( KBW_TDEL + 35 ) / 921600 secs

There is an overhead of 35 cycles for each interrupt.   The  default  value
for  KBW_TDEL  is  $B3DD, giving a time interval of 0.05 secs.  Note that a
value of zero in KBW_TDEL, will cause the machine to lock up.



       ________
7.5.9  KBB_DLAY

     This byte stores the delay before auto-repeat of the  keys  begins  in
terms  of  the  number of keyboard interrupts.  The default value is 14, so
with interrupts running at 20 times per second, the delay is 0.7 secs.



        ________
7.5.10  KBB_REPT

     This byte stores the delay between keys when auto-repeating  in  terms
of keyboard interrupts.  The default value is 0 which is the fastest value.
A value of 1 will repeat at half normal speed, 2 at a  third  normal  speed
etc.



        ________
7.5.11  KBB_CLIK

     This byte stores the length of the keyboard click in ms.  A  value  of
zero will turn off the key click altogether.  The default value is 1 giving
the shortest possible click.



        ________
7.5.12  KBB_PKOF

     This flag controls whether the packs are switched off by KB$TEST.   If
it  is  non-zero,  which  it  is by default, the packs will be switched off
whenever KB$TEST is called and there is are no keys in the keyboard buffer.



     _______________
7.6  SYSTEM SERVICES

     This section  describes  the  operating  system  calls  available  for
keyboard handling.



7.6.1  KB$INIT

VECTOR NUMBER:          073
INPUT PARAMETERS:       None.
OUTPUT VALUES:          None.

DESCRIPTION

    Initialises keyboard interrupts.  The following tasks are  carried  out
    by this routine:

    1.  The keyboard buffer is flushed of any characters.

    2.  The keyboard interrupt rate KBW_TDEL is set to $B3DD.

    3.  The initial delay and key repeat rate KBB_DLAY, KBB_REPT are set to
        14 and 0 respectively.

    4.  The keyboard status flags, in KBB_STAT are set to  0,  i.e.  KY_SHFT,
        KY_CPNM, KY_CAPS and KY_NUMB are cleared.

    5.  The length of the key click, KBB_CLIK, is set to 1.

    6.  The interrupts are enabled:  FREE RUNNING  COUNTER  is  set  to  0,
        TIMER 1 OUTPUT COMPARE REGISTER 1 is set to $B3DD, bit 3 of TIMER 1
        CONTROL STATUS REGISTER 1 is set, to enable the interrupt, and  the
        I mask bit of the condition code register is cleared.


EXAMPLE:

        OS      KB$INIT         ;INITIALISE KEYBOARD

ERRORS:                 None.



7.6.2  KB$TEST

VECTOR NUMBER:          075
INPUT PARAMETERS:       None.
OUTPUT VALUES:          B register - ascii value of next key in buffer
                                     (0 if no key).
REGISTERS PRESERVED:    A,X

DESCRIPTION

    Looks in keyboard buffer for a key but does  not  remove  it.   If  the
    buffer  is  not  empty,  the  ascii value of the first key found in the
    buffer is returned in the B register.  The  unget  buffer  KBB_WAIT  is
    tested  for a key before looking in KBT_BUFF.  If KBB_WAIT is empty and
    there is a key in KBT_BUFF, it is transferred to KBB_WAIT.

    If no keys are found, the following will occur:

    1.  If KBB_PKOF is non-zero, the pack  will  switch  off  using  system
        service PK$PKOF.

    2.  If TMB_SWOF is non-zero and TMW_TOUT  is  zero,  the  machine  will
        switch off using system service BT$SWOF.

    3.  If low battery is detected, BATTERY TOO LOW will be displayed for 4
        secs before the machine switches off.


    If the I mask (bit 4)  of  the  condition  code  register  is  set  (i.e.
    interrupts  are  disabled)  when  KB$TEST  is  called, it will poll the
    keyboard itself allowing the operating system to  run  with  interrupts
    disabled.   Every function of the keyboard interrupt routine is carried
    out except alarm checking.  Any keys found  are  put  in  the  keyboard
    buffer,  the  frame-counter  is  incremented  and  the display timer is
    decremented etc.  There  is  a  50ms  fixed  delay  after  polling  the
    keyboard so that if KB$TEST is called in a loop, the keyboard is polled
    approximately every 50ms.  After polling the keyboard  itself,  KB$TEST
    looks in the keyboard buffer as usual.

EXAMPLE:

        OS      KB$TEST         ;LOOK IN KEYBOARD BUFFER
        TST     B               ;TEST IF KEY
        BEQ     NOKEY           ;BRANCH IF NO KEY FOUND

        ;HERE IF KEY FOUND

NOKEY:

ERRORS:                 None.



7.6.3  KB$GETK

VECTOR NUMBER:          072
INPUT PARAMETERS:       None.
OUTPUT VALUES:          B register - ascii value of key taken from buffer.
REGISTERS PRESERVED:    A,X

DESCRIPTION

    Waits for a key press and returns it in the B register.   This  routine
    uses  the  same  code as KB$TEST.  If there are no keys in the keyboard
    buffer, a SLP instruction is executed before next testing for a key, in
    order  to  save  power.  When a key is detected, it is removed from the
    buffer simply by clearing KBB_WAIT.

    The auto-switch-off timeout counter TMW_TOUT is reset to the  value  in
    TMW_TCNT  at  the start of this routine, so the machine will not switch
    off in this routine for TMW_TCNT seconds.

EXAMPLE:

        OS      KB$GETK                 ;WAIT FOR KEY PRESS
        CMP     B,#K_EXE
        BEQ     EXEKEY                  ;BRANCH IF EXECUTE KEY

        ;HERE IF ANY OTHER KEY

EXEKEY:

ERRORS:                 None.



7.6.4  KB$BREK

VECTOR NUMBER:          070
INPUT PARAMETERS:       None.
OUTPUT VALUES:          Carry flag is set if ON/CLEAR key is pressed.

DESCRIPTION

    Tests if ON/CLEAR key is pressed.  The key is tested  for  directly  by
    reading  PORT  5 and then searched for in the keyboard buffer KBT_BUFF.
    If ON/CLEAR is detected, KB$BREK waits for  it  to  be  released,  then
    flushes  the  keyboard  buffer  and  returns the carry flag set.  If no
    ON/CLEAR key is found, the carry flag is cleared.

EXAMPLE:

        OS      KB$BREK         ;TEST FOR ON/CLEAR
        BCC     NOCLR           ;BRANCH IF ON/CLEAR NOT PRESSED

        ;HERE IF ON/CLEAR KEY WAS PRESSED (AND RELEASED)

NOCLR:

ERRORS:                 None.



7.6.5  KB$FLSH

VECTOR NUMBER:          071
INPUT PARAMETERS:       None.
OUTPUT VALUES:          None.

DESCRIPTION

    Flushes keyboard of any characters  in  type-ahead  buffer.   KBB_BACK,
    KBB_NKYS, KBB_PREV and KBB_WAIT are all set to zero.

EXAMPLE:
        OS      KB$FLSH         ;FLUSH KEYBOARD BUFFER

ERRORS:                 None.



7.6.6  KB$UGET

VECTOR NUMBER:          076
INPUT PARAMETERS:       B register - key to be placed in buffer.
OUTPUT VALUES:          None.
REGISTERS PRESERVED:    A,B,X

DESCRIPTION

    Puts the key supplied in the  B  register  into  the  keyboard  'unget'
    buffer,  KBB_WAIT.   The  buffer can only hold 1 key.  If the buffer is
    already full, the key is not placed in the buffer.

EXAMPLE:

        LDA     B,#K_AC         ;ON/CLEAR KEY
        OS      KB$UGET         ;INSERT INTO BUFFER

ERRORS:                 None.



7.6.7  KB$STAT

VECTOR NUMBER:          074
INPUT PARAMETERS:       B register - keyboard state byte.
OUTPUT VALUES:          None.
REGISTERS PRESERVED:    B

DESCRIPTION

    Sets the state of the keyboard.  The B register is stored into keyboard
    status byte KBB_STAT.

                IF BIT 0 OF B REG IS SET THEN LOWER CASE
                IF BIT 0 OF B REG IS CLEAR THEN UPPER CASE
                IF BIT 6 OF B REG IS SET THEN NUMERIC LOCK
                IF BIT 6 OF B REG IS CLEAR THEN NOT IN NUMERIC LOCK

    Note that some other bits of KBB_STAT are used as flags and should  not
    be  affected.   Hence  KBB_STAT  should be read and bits 0 and 6 either
    cleared or set before calling KB$STAT, see example below.

    The state of the cursor (block  or  line)  is  determined  by  the  new
    keyboard  status  and stored in DPB_CUST.  A line cursor is for 'shift'
    mode,  otherwise  it's  a  block  cursor.   The  new  cursor  type   is
    immediately refreshed on the display.

EXAMPLE:

        LDA     B,KBB_STAT:     ;GET CURRENT KEYBOARD STATUS
        ORA     B,#KY_NUMB      ;NUMERIC LOCK. LINE CURSOR.
        OS      KB$STAT         ;SET KEYBOARD STATUS


ERRORS:                 None.




     _______
7.7  EXAMPLE


       ____________________________
7.7.1  FULL ASCII SET FROM KEYBOARD

This example enables the whole ascii character set to be accessed from  the
keyboard.   The LEFT-ARROW and RIGHT-ARROW keys become two more SHIFT keys.
Holding down LEFT-ARROW and pressing the P key,  for  example  will  return
ascii character 16 which, if displayed on the Organiser, is translated as a
bell character.  For all the characters returned in each 'shift' mode,  see
the table at the end of the example.

This example is taken from the code used in the RS232  device  in  terminal
mode, to enable the whole ascii set to be typed from the keyboard.

There are two routines provided below:  KB_NEW and KB_OLD.  KB_NEW must  be
called  to set up the new keyboard and KB_OLD must be called to restore the
normal keyboard.


KB_NEW:                         ;INSTALL NEW KEYBOARD
        TPA
        PSH     A
        SEI
        BSR     GETP
        XGDX
        OS      UT$CPYB         ;COPY OLD VECTORS TO SAVEVCT
        BSR     GETP
        LDX     #NEWVCT
        OS      UT$CPYB         ;COPY NEW VECTORS IN
        PUL     A
        TAP
        RTS

KB_OLD:                         ;RESTORE OLD KEYBOARD
        TPA
        PSH     A
        SEI
        BSR     GETP
        OS      UT$CPYB         ;RESTORE OLD VECTORS
        PUL     A
        TAP
        RTS

NEWVCT:
        .WORD   NEWPOLL,NEWTRAN,NEWTABL         ;ADDRESSES OF NEW ROUTINES
SAVEVCT:
        .WORD   0,0,0           ;6 BYTES TO SAVE OLD VECTORS IN.

GETP:   LDD     #6
        STD     UTW_S0:
        LDD     #BTA_POLL
        LDX     #SAVEVCT
        RTS


NEWPOLL:

;TURBO CHARGED KEYBOARD POLL ROUTINE
;CHECKS FOR A/C FIRST
;CHECKS FOR ANY KEY QUICKLY, THEN FINDS WHICH KEY WITH DOUBLE COUNTING
;A := KEY PRESSED (0-36, 0=NOKEY)
;KTABPOSN POINTS TO LAST CHAR IN EACH COLUMN IN TURN
;PRESERVES X

        TST     SCA_COUNTERRESET                        ;CLEAR COUNTER
        AIM     #<$FF-<KY_SHFT!KY_SHFT2!KY_SHFT3>>,KBB_STAT:
        CLR     A                                       ;NO KEY YET
        LDA     B,POB_PORT5:
        BPL     NOAC
        LDA     A,#36
RTS5:   RTS

NOAC:   STA     A,KBB_KNUM:     ;TABPOSN:=0
        PSH     A
        BSR     LOOKKEY         ;ZERO IF NO KEY FOUND
        PUL     A
        BEQ     ENDKYBD         ;BRANCH IF NO KEY FOUND
        LDA     B,#$40
        PSH     B
        DEC     B
        BRA     PP

GOCOL:  PSH     B
PP:     PSH     A
1$:     TST     SCA_COUNTERCLOCK        ;CLOCK COUNTER B TIMES
        DEC     B
        BNE     1$
        BSR     LOOKKEY         ;B:= OFFSET IN COL (IF KEY FOUND)
        PUL     A
        BEQ     NOPSH           ;BRANCH IF NO KEY IN THIS COL
        ADD     B,KBB_KNUM:
        TBA                     ;GOT KEY BUT MUST CHECK IF SHIFT
NOPSH:  LDA     B,KBB_KNUM:
        ADD     B,#5
        STA     B,KBB_KNUM:     ;POINT TO NEXT COL
        PUL     B
        LSR     B               ;SELECT NEXT COL
        BNE     GOCOL
ENDKYBD:
        RTS



LOOKKEY:

;B:=KEY OFFSET IN COL (1-5)
;B:=0 IF NOKEY IN THIS COL (OR JUST SHIFT)
;SETS KY_SHFT FLAG IF SHIFT PRESSED
;SETS KY_SHFT2 IF <LEFT-ARROW> PRESSED
;SETS KY_SHFT3 IF <RIGHT-ARROW> PRESSED
;SETS Z ON B
;PRESERVES X

        LDA     B,#5
        LDA     A,POB_PORT5:
        ASL     A
NEXTROW:
        ASL     A               ;ROTATE KEY INTO CARRY
        BCC     GOTKEY          ;CARRY CLEAR IF KEY PRESSED
        DEC     B
        BNE     NEXTROW
        RTS

GOTKEY:                         ;B IS POSITION IN COL
        TST     KBB_SHFK        ;CLR IF SHIFT ENABLED
        BNE     13$             ;B IS NOT ZERO
        PSH     A
        LDA     A,KBB_KNUM:
        CMP     A,#25           ;IS IT COL 6 (COL CONTAINING SHIFT) ?
        BNE     11$             ;B IS NOT ZERO
        PUL     A
        DEC     B
        BEQ     10$             ;BRANCH IF B WAS 1 IE SHIFT

        PSH     B               ;ELSE CHECK IF SHIFT PRESSED AS WELL
1$:     ASL     A
        DEC     B
        BNE     1$
        PUL     B
        INC     B               ;RESTORE B
        BCS     13$             ;BRANCH IF C SET FROM ASL A

10$:    OIM     #KY_SHFT,KBB_STAT:
        TST     B
        RTS

11$:    CMP     A,#30           ;COL CONTAINING <LEFT>, <RIGHT> ARROWS ?
        PUL     A
        BNE     13$             ;B IS NON-ZERO
        CMP     B,#4            ;LEFT ?
        BEQ     12$
        CMP     B,#5            ;RIGHT ?
        BNE     13$
        OIM     #KY_SHFT3,KBB_STAT:
        CLR     B               ;SET Z
        RTS
12$:
        OIM     #KY_SHFT2,KBB_STAT:
        CLR     B               ;SET Z
13$:    RTS


NEWTRAN:

;TRANSLATE KEY NUMBER IN A (0-36) INTO ASCII KEY USING KBB_STAT
;SETS DPB_CUST APPROPRIATELY + REFRESHES CURSOR STATE
;SETS Z=1 IF A=0 (NOKEY)
;SETS KY_CPNM IF CAP OR NUM

        AIM     #<$FF-KY_CPNM>,KBB_STAT:
        TAB
        TST     KBB_STAT
        BPL     2$                      ;BRANCH IF CAN'T BE CAP OR NUM
        CMP     A,KBB_CAPK              ; - CAPS KEY
        BNE     1$
        EIM     #KY_CAPS,KBB_STAT:
        OIM     #KY_CPNM,KBB_STAT:
        CLR     B
1$:     CMP     A,KBB_NUMK              ; - NUM KEY
        BNE     2$
        EIM     #KY_NUMB,KBB_STAT:
        OIM     #KY_CPNM,KBB_STAT:
        CLR     B
2$:     BSR     KCUST                   ;FIX CURSOR STATE FOR KBB_STAT
                                        ;RETURNS X POINTING TO APPROPRIATE
                                        ;KEYBOARD TABLE !
                                        ;DISPLAYS CURSOR TYPE. PRESERVES B
        TST     B
        BEQ     9$
        ABX
        LDA     B,0,X                   ;GET ASCII KEY FROM TABLE
        LDA     A,KBB_STAT:
        LSR     A
        BCS     4$                      ;IF SET LEAVE AS LOWER CASE
        CMP     B,#^A/A/
        BCS     21$
        CMP     B,#^A/Z/
        BHI     21$
        ADD     B,#^A/a/-^A/A/          ;CONVERT TO UPPER CASE
21$:
4$:     CMP     B,#K_DEL
        BNE     9$
        LDA     A,KBB_STAT:
        BPL     9$
        LDA     B,#$7F          ;DEL
9$:     TBA
        RTS

KCUST:
        LDX     BTA_TABL                ;ADDRESS OF KEYBOARD TABLE
        DEX
        LDA     A,KBB_STAT:
        ASL     A
        ASL     A
        BMI     DO2
        ASL     A
        BMI     DO3
        LDA     A,KBB_STAT:
        ASL     A
        BVS     DOSHFT

        AIM     #<$FF-CURSOR_LINE>,DPB_CUST:    ;BLOCK CURSOR
        BRA     DP_CTYP                 ;REFRESH CURSOR TYPE ON LCD

DO3:    BSR     ADD36
DO2:    BSR     ADD36
DOSHFT: OIM     #CURSOR_LINE,DPB_CUST:  ;LINE CURSOR FOR SHIFTED CHARS
        BSR     ADD36

DP_CTYP:
        LDA     A,#$0C
        TIM     #CURS_ON,DPB_CUST:
        BEQ     1$                      ;BRANCH IF CURSOR OFF
        ORA     A,#$02
        TIM     #CURSOR_LINE,DPB_CUST:
        BNE     1$                      ;BRANCH IF CURSOR IN LINE FORM
        ORA     A,#$01
1$:     TST     SCA_LCDCONTROL
        BMI     1$
        STA     A,SCA_LCDCONTROL
        RTS

ADD36:
        PSH     B
        LDA     B,#36
        ABX
        PUL     B
        RTS


NEWTABL:
        .ASCII  /ZVPJD/
        .BYTE   K_EXE
        .ASCII  /XRLF/
        .ASCII  / WQKE/
        .ASCII  /YUOIC/
        .BYTE   K_DEL
        .ASCII  /TNHB/
        .BYTE   0
        .ASCII  /SMGA/
        .BYTE   $81
        .BYTE   $82,$83
        .BYTE   K_LEFT,K_RGHT
        .BYTE   $80

SHFT1:
        .ASCII  /.258)/
        .BYTE   K_EXE
                   $
        .ASCII  $+-$
        .ASCII  $/$
        .ASCII  / 369%/
        .ASCII  /0147(/
        .BYTE   K_DEL
        .ASCII  /:$">/
        .BYTE   0
        .ASCII  /;,=</
        .BYTE   $81
        .BYTE   $82,$83         ;UP, DOWN
        .BYTE   K_LEFT,K_RGHT
        .BYTE   $80             ;A/C KEY
SHFT2:
        .BYTE   26,22,16,10,4
        .BYTE   13,24,18,12,6
        .BYTE   32,23,17,11,5
        .BYTE   25,21,15,9,3
        .BYTE   K_DEL,20,14,8,2
        .BYTE   0,19,13,7,1
        .BYTE   $81
        .BYTE   $82,$83         ;UP, DOWN
        .BYTE   K_LEFT,K_RGHT
        .BYTE   $80             ;A/C KEY

SHFT3:
        .ASCII  /?/
        .BYTE   127,95,63,30

        .BYTE   K_EXE
        .ASCII  /?/
        .BYTE   123,91,33

        .ASCII  / ?/
        .BYTE   96,64,31

        .ASCII  /?/
        .BYTE   126,94,39,29

        .BYTE   K_DEL
        .BYTE   125,93,38,28

        .BYTE   0,124,92,35,27
        .BYTE   $81
        .BYTE   $82,$83         ;UP, DOWN
        .BYTE   K_LEFT,K_RGHT
        .BYTE   $80             ;A/C KEY