;$PAGINATE
;$title(Arnold 5 test)
;$subtitle(Support subroutines)
;$copyright(Copyright (c) 1989, 1990, Amstrad plc.)
;$pagewidth=131

;
; These routines are the general "firmware" support routines that do the
; mundane things like writing characters to the screen or printer etc.
;

;        PUBLIC  ScreenSetMode
;        PUBLIC  ScreenResetPalette
;        PUBLIC  SetCursorPos
;        PUBLIC  ScreenPrintChar
;        PUBLIC  PrintAHex
;        PUBLIC  LPrintAHex
;        PUBLIC  HighlightLine
;        PUBLIC  PrinterPrintChar
;        PUBLIC  PrintStringHL
;        PUBLIC  WriteAYChip
;        PUBLIC  ProgramNote
;        PUBLIC  KeyboardRead
;        PUBLIC  KeyboardReadNoDelay
;        PUBLIC  DelayASeconds
;        PUBLIC  UnlockArn5
;        PUBLIC  RelockArn5
;        PUBLIC  OfferReTest
;
;
;        EXTERN  ?KeyboardBuffer                 ;in TESTVARS
;        EXTERN  ?KeysHeldDownBuffer             ;in TESTVARS
;        EXTERN  ?PrintCharXORMask               ;in TESTVARS
;        EXTERN  ?ScreenMode                     ;in TESTVARS
;        EXTERN  .CRTCInitialValues              ;in MESSAGES
;        EXTERN  .Initial16ColorPalette          ;in MESSAGES
;        EXTERN  .InitProcessorMessPt1           ;in MESSAGES
;        EXTERN  .MatrixDefinitions              ;in MESSAGES
;        EXTERN  .HexDigits                      ;in MESSAGES
;        EXTERN  .Arnold5Key                     ;in MESSAGES
;        EXTERN  .RetestMess                     ;in MESSAGES
;
;
;        include "equates.inc"
;
;        DEFSEG  TestCode, CLASS=CODE
;
;        SEG     TestCode

;=============
ScreenSetMode:
;=============
;
; This takes a value in A to decide which of the three "normal" screen modes
; the screen should be set into. The video RAM is also cleared at this time.
;
; As the God awful ULA is write only and the register controlling the screen
; mode also contains the enables for the upper and lower ROMs, I have to keep
; a register copy of the current setting in BC' - when the ULA mode is changed
; the value in C' is masked with the passed mode number and is then set to the
; new value.
;
; Next the fixed initialisation values are output to the 6845
;
; Finally, the video memory (at 0C000h) is cleared to 0
;
        and     003h                    ;mode must be 0, 1 or 2
        ld      (QUESTMARK_ScreenMode),a
        exx                             ;get access to BC'
        ld      b,a                     ;store A somewhere safe
        ld      a,c                     ;need C' in A to do bit bashing
        and     0FCh                    ;clear bottom two mode bits
        or      b                       ;retrieve the desired from B'
        ld      c,a                     ;put it where it should be
        ld      b,07Fh                  ;it's always 7F ! (ULA I/O address)
        out     (c),c                   ;switch video mode
        exx                             ;hide BC' again

        ld      hl,.CRTCInitialValues   ;in MESSAGES (fixed data)
        ld      bc,0BC0Fh               ;BCXX is 6845 addr reg. 0F is top reg.
_SupSet6845loop:
        out     (c),c                   ;select register C
        ld      a,(hl)                  ;get value for that reg. from data
        inc     hl                      ;step on pointer for next time round
        inc     b                       ;BDXX is write data reg.
        out     (c),a                   ;write that data into selected reg.
        dec     b                       ;B points back to BCXX again
        dec     c                       ;going backwards thru registers
        jp      p,_SupSet6845loop       ;if not -ve register then do another

        ld      hl,0C000h               ;base of default screen memory
        ld      c,0
_SupClearScreen:
        ld      (hl),c
        inc     hl
        ld      a,h
        or      l
        jr      nz,_SupClearScreen

		UTIL_SetCursorPos(0);
		UTIL_PrintString(1,.InitProcMessPt1)

        ret

;==================
ScreenResetPalette:
;==================
;
; This routine sets the ULA palette to 16 well known colors (as on a real
; Arnold). The border and PEN 0 (background) are Blue while PEN 1 (used for
; all text) is Bright Yellow. The other 15 pens are set the same as Arnold
; except that 14 and 15 don't flash - they are fixed at Sky Blue and Orange.
;
        ld      hl,.Initial16ColorPalette
        ld      a,(hl)
        ld      c,10h                   ;PR4 set means set border
        ld      b,07Fh
        out     (c),c                   ;select border register
        and     01Fh                  ;all colors are 0..32
        or      01000000b             ;top bits=010 means palette value
        out     (c),a                   ;set border color

        inc     hl                      ;now point at list of 16 pens
        ld      d,15                    ;a counter to count them down
_SupResetPalLoop:
        ld      a,(hl)                  ;get next color
        inc     hl                      ;step on thru list for next time
        ld      c,d                     ;use loop counter as register number
        out     (c),c                   ;select register
        and     01Fh                    ;make sure value (from (hl)) is in range
        or      01000000b               ;top=010 to set palette
        out     (c),a                   ;write that palette register
        dec     d                       ;backwards thru the colors !
        jp      p,_SupResetPalLoop      ;if not -ve yet then go round for more

        ret

;============
SetCursorPos:
;============
;
; Takes new value for row and column in D and E respectively. These are defined
; in characters (so row is always 0..24). Co-ordinates are 0 based with 0,0
; identifying the character in the top left.
;
        ld      a,d                     ;new row value to A
        exx                             ;get access to DE'
        ld      d,a                     ;so D' = new row value
        exx
        ld      a,e
        exx
        ld      e,a                     ;so E' = new column value
        exx
        ret

;===============
ScreenPrintChar:
;===============
;
; On entry to this routine A contains the number of the character to print
; (32..127). The definitions of these chars have been stolen from Arnold. Chars
; 0..31 and 128..255 do not exist so 32 is subtracted from the character number
; then it is multiplied by 8 to index into the matrix table. At this point HL
; points to the base of the character definition. Register B holds the screen
; mode that the character should be printed in. DE' hold the current cursor
; position.
;
; late change: "ScreenMode" not B holds the display mode
;
; If B=2 (MODE 2 - the "easy" one). Then the usual start of character address
; routine is performed:
;       start = 0C000h + (D' * 80d) + E'
; The character is then printed by moving (HL) to start, (HL+1) to start+800h,
; (HL+2) to start+1000h, (HL+3) to start+1800h... (HL+7) to start+3800h
;
; If B=1 (MODE 1) then the start address is still calculated as above. Then
; FOR I=0 TO 7
;  (start+(I*800h))   = ((HL) AND 0F0h)
;  (start+(I*800h)+1) = ((HL) AND 00Fh) < 4
; NEXT I
;
; If B=0 (MODE 0) then the start address is still calculated as above. Then
; FOR I= 0 TO 7
;  (start+(I*800h))   = ((HL) AND 0C0h)
;  (start+(I*800h)+1) = ((HL) AND 030h) < 2
;  (start+(I*800h)+2) = ((HL) AND 00Ch) < 4
;  (start+(I*800h)+3) = ((HL) AND 003h) < 6
; NEXT I
;
        cp      0Dh                     ;is character a carriage return QUESTMARK_
        jr      nz,_SupPrintNotCR
        exx
        ld      e,0                     ;set column back to left hand side
        exx
        ret

_SupPrintNotCR:
        cp      0Ah                     ;is character a line feed QUESTMARK_
        jr      nz,_SupPrintNotLF
        exx
        inc     d                       ;step row on by one
        exx
        ret


_SupPrintNotLF:
        cp      08                      ;is it a backspace QUESTMARK_
        jr      nz,_SupPrintNotBS
        exx
        ld      a,e
        dec     a
        jp      p,_SupBSOK              ;can't go -ve
        ld      a,0
_SupBSOK:
        ld      e,a
        exx
        ret

_SupPrintNotBS:
        cp      09                      ;is it a TAB character
        jr      nz,_SupPrintableChar
        exx
        ld      a,e
        add     a,8                       ;if yes then step cursor on 8
        and     0F8h                    ;and clear bottom 3 bits to make
        ld      e,a                     ;an 8 char boundary.
        exx
        ret

_SupPrintableChar:
        sub     32                      ;characters start at 32
        ld      l,a
        ld      h,0                     ;use hl for 16 bit maths
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;hl = hl * 8
        ld      de,.MatrixDefinitions
        add     hl,de                   ;index into table of matrices
        ld      a,(QUESTMARK_ScreenMode)
        cp      0
        jp      z,_SupPrintMode0
        cp      1
        jp      z,_SupPrintMode1
_SupPrintMode2:
        exx                             ;access DE' (row,column)
        ld      a,d
        exx
        ld      d,a                     ;D=D' = row
        exx
        ld      a,e
        exx
        ld      e,a                     ;E=E' so DE=row,column
        ld      b,h                     ;store matrix source addr for a mo'
        ld      c,l
        ld      hl,0C000h               ;screen base address
        ld      a,d                     ;save row somewhere safe (in A)
        ld      d,0                     ;so DE = just column
        add     hl,de                   ;HL = 0C000h + column
        ex      de,hl                   ;save that result somewhere safe (DE)
        ld      h,0
        ld      l,a                     ;so HL=row number
        add     hl,hl
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;so HL=row*10h
        ex      de,hl                   ;so HL=0C000h+col and DE=row*10h
        add     hl,de                   ;so HL=0C000h+col+(row*10h) and DE=row*10h
        ex      de,hl                   ;so HL=row*10h and DE=0C000h+col+(row*10h)
        add     hl,hl                   ;so HL=20h*row
        add     hl,hl                   ;so HL=40h*row
        add     hl,de                   ;so HL=0C000h+col+(row*10h)+(40h*row)
                                        ;     =0C000h + row * 80d + col
        ex      de,hl                   ;so DE=all that (address in screen)
        ld      h,b
        ld      l,c                     ;so HL= source address in matrix
        ld      c,8                     ;8 screen lines
_SupMode2NextLine:
        push    bc
        ld      c,(hl)                  ;get byte of matrix
        ld      a,(QUESTMARK_PrintCharXORMask)
        xor     c
        pop     bc
        ld      (de),a                  ;print it on screen!!
        inc     hl                      ;step on to next byte of matrix
        ld      a,d
        add     a,8                       ;step destination down a screen line
        ld      d,a
        dec     c
        jr      nz,_SupMode2NextLine
        jp      _SupPrintStepCursor

_SupPrintMode1:
        exx                             ;access DE' (row,column)
        ld      a,d
        exx
        ld      d,a                     ;D=D' = row
        exx
        ld      a,e
        exx
        ld      e,a                     ;E=E' so DE=row,column
        ld      b,h                     ;store matrix source addr for a mo'
        ld      c,l
        ld      hl,0C000h               ;screen base address
        ld      a,d                     ;save row somewhere safe (in A)
        ld      d,0                     ;so DE = just column
        add     hl,de                   ;HL = 0C000h + column
        add     hl,de                   ;HL = 0C000h + (column*2) (2 bytes/char)
        ex      de,hl                   ;save that result somewhere safe (DE)
        ld      h,0
        ld      l,a                     ;so HL=row number
        add     hl,hl
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;so HL=row*10h
        ex      de,hl                   ;so HL=0C000h+col and DE=row*10h
        add     hl,de                   ;so HL=0C000h+col+(row*10h) and DE=row*10h
        ex      de,hl                   ;so HL=row*10h and DE=0C000h+col+(row*10h)
        add     hl,hl                   ;so HL=20h*row
        add     hl,hl                   ;so HL=40h*row
        add     hl,de                   ;so HL=0C000h+col+(row*10h)+(40h*row)
                                        ;     =0C000h + row * 80d + col
        ex      de,hl                   ;so DE=all that (address in screen)
        ld      h,b
        ld      l,c                     ;so HL= source address in matrix
        ld      c,8                     ;8 screen lines
_SupMode1NextLine:
;        ld      a,(hl)                  ;get matrix entry
        push    bc
        ld      c,(hl)                  ;get byte of matrix
        ld      a,(QUESTMARK_PrintCharXORMask)
        xor     c
        pop     bc
        and     0F0h                    ;just want top two bits
        ld      (de),a                  ;store first of 4 bytes
        inc     de                      ;point at next screen byte

;        ld      a,(hl)                  ;get same matrix entry
        push    bc
        ld      c,(hl)                  ;get byte of matrix
        ld      a,(QUESTMARK_PrintCharXORMask)
        xor     c
        pop     bc
        and     00Fh                    ;get 0000XXXX
        sla     a
        sla     a
        sla     a
        sla     a                       ;make XXXX0000
        ld      (de),a

        dec     de                      ;now make de point at first byte again

        inc     hl                      ;step on to next byte of matrix
        ld      a,d
        add     a,8                       ;step destination down a screen line
        ld      d,a
        dec     c
        jr      nz,_SupMode1NextLine
        jp      _SupPrintStepCursor

_SupPrintMode0:
        exx                             ;access DE' (row,column)
        ld      a,d
        exx
        ld      d,a                     ;D=D' = row
        exx
        ld      a,e
        exx
        ld      e,a                     ;E=E' so DE=row,column
        ld      b,h                     ;store matrix source addr for a mo'
        ld      c,l
        ld      hl,0C000h               ;screen base address
        ld      a,d                     ;save row somewhere safe (in A)
        ld      d,0                     ;so DE = just column
        add     hl,de                   ;HL = 0C000h + column
        add     hl,de                   ;HL = 0C000h + column*2
        add     hl,de                   ;HL = 0C000h + column*3
        add     hl,de                   ;HL = 0C000h + column*4 (4 bytes/char)
        ex      de,hl                   ;save that result somewhere safe (DE)
        ld      h,0
        ld      l,a                     ;so HL=row number
        add     hl,hl
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;so HL=row*10h
        ex      de,hl                   ;so HL=0C000h+col and DE=row*10h
        add     hl,de                   ;so HL=0C000h+col+(row*10h) and DE=row*10h
        ex      de,hl                   ;so HL=row*10h and DE=0C000h+col+(row*10h)
        add     hl,hl                   ;so HL=20h*row
        add     hl,hl                   ;so HL=40h*row
        add     hl,de                   ;so HL=0C000h+col+(row*10h)+(40h*row)
                                        ;     =0C000h + row * 80d + col
        ex      de,hl                   ;so DE=all that (address in screen)
        ld      h,b
        ld      l,c                     ;so HL= source address in matrix
        ld      c,8                     ;8 screen lines
_SupMode0NextLine:
        ld      a,(hl)                  ;get matrix entry
        and     0C0h                    ;just want top two bits (XX000000)
        ld      (de),a                  ;store first of 4 bytes
        inc     de                      ;point at next screen byte

        ld      a,(hl)                  ;get same matrix entry
        and     030h                    ;get  00XX0000
        sla     a
        sla     a                       ;make XX000000
        ld      (de),a
        inc     de                      ;point at next screen byte

        ld      a,(hl)                  ;get same matrix entry
        and     00Ch                    ;get  0000XX00
        sla     a
        sla     a
        sla     a
        sla     a                       ;make XX000000
        ld      (de),a
        inc     de                      ;point at next screen byte

        ld      a,(hl)                  ;get same matrix entry
        and     003h                    ;get  000000XX
        sla     a
        sla     a
        sla     a
        sla     a
        sla     a
        sla     a                       ;make XX000000
        ld      (de),a

        dec     de                      ;now make de point at first byte again
        dec     de
        dec     de

        inc     hl                      ;step on to next byte of matrix
        ld      a,d
        add     a,8                       ;step destination down a screen line
        ld      d,a
        dec     c
        jr      nz,_SupMode0NextLine

_SupPrintStepCursor:
        exx                             ;as we have printed on the scren we
                                        ;should step the cursor on one char
        inc     e
        exx

        ret


;=========
PrintAHex:
;=========
;
; This is just the old classic that prints the contents of the A register
; as 2 hex digits. I'm gonna make a big assumption and use the stack.!
;
        push    af
        push    de
        push    hl
        push    bc

        push    af                      ;save lower nybble
        and     0F0h
        srl     a
        srl     a
        srl     a
        srl     a                       ;move upper to lower nybble
        call    _SupPrintANybble
        pop     af

        and     00Fh
        call    _SupPrintANybble

        pop     hl
        pop     de
        pop     bc
        pop     af
        ret

_SupPrintANybble:
        ld      hl,.HexDigits           ;string "0123456789ABCDEF"
        ld      e,a
        ld      d,0
        add     hl,de                   ;index into that string
        ld      a,(hl)                  ;pick up character to print
        jp      ScreenPrintChar
        
;==========
LPrintAHex:
;==========
;
; This is just the old classic that prints the contents of the A register
; as 2 hex digits. I'm gonna make a big assumption and use the stack.!
;
;
        push    af
        push    de
        push    hl
        push    bc

        push    af                      ;save lower nybble
        and     0F0h
        srl     a
        srl     a
        srl     a
        srl     a                       ;move upper to lower nybble
        call    _SupLPrintANybble
        pop     af

        and     00Fh
        call    _SupLPrintANybble

        pop     hl
        pop     de
        pop     bc
        pop     af
        ret

_SupLPrintANybble:
        ld      hl,.HexDigits           ;string "0123456789ABCDEF"
        ld      e,a
        ld      d,0
        add     hl,de                   ;index into that string
        ld      a,(hl)                  ;pick up character to print
        jp      PrinterPrintChar
        


;=============
HighlightLine:
;=============
;
; Uses the value in D which is a character line number and then goes along the
; 80 bytes of each of the 8 scan lines that that refers to and does an XOR on
; them with 0FFH to invert them (probably best to only use it in MODE 2!).
;
; A second call referring to the same line will obviously remove the highlight
;
        exx                             ;BC' has current ROM state etc
        ld      b,07Fh
        ld      a,c
        or      8                       ;set the upper ROM off bit
        out     (c),a                   ;so the upper ROM is now off
        exx

        push    de
        ld      h,0
        ld      l,d                     ;use HL for 16 bit sums
        add     hl,hl
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;make HL = char row * 10h
        ld      b,h
        ld      c,l                     ;take a copy of row*10h in BC
        add     hl,hl
        add     hl,hl                   ;so HL = row * 40h
        add     hl,bc                   ;now HL = row * 50h (i.e. * 80)
        ld      bc,0C000h               ;screen base address
        add     hl,bc                   ;HL = 0C000h + row * 80h
        ld      b,8                     ;gonna XOR 8 scan lines worth
_SupHighLineLoop:
        ld      c,80                    ;count of bytes per line
_SupHighByteLoop:
        ld      a,(hl)
        xor     0FFh                    ;flip the bits
        ld      (hl),a
        inc     hl                      ;step on a byte in this line
        dec     c
        jr      nz,_SupHighByteLoop
        ld      de,800h - 80
        add     hl,de                   ;step down a screen line
        dec     b
        jr      nz,_SupHighLineLoop
        pop     de

        exx
        out     (c),c                   ;set ROM state back as it was
        exx
        ret

;================
PrinterPrintChar:
;================
;
; Takes the value in A (0..255) and outputs it to the Centronics port.
; First a check is made on bit 0 of L' which is a flag bit that says whether
; a printer exists. Initially this says it does but if this routine timesout
; while waiting to see the BUSY signal go away then the bit is cleared and
; subsequent attempts to print via this routine are ignored (if the user
; wants he can set L':0 to retry.
;
; If the routine cannot print the character it returns C. It sets Z if the
; reason is that L':0=0 and NZ if the BUSY test timed out.
;
        ld      l,a                     ;save char to print somewhere safe
        exx
        bit     0,l                     ;check printer present bit in L'
        exx
        jr      nz,_SupGotPrinter
_SupNoPrinter:
;
; Z is set when we get here
;
        scf                             ;indicate no printing happened
        ret

_SupGotPrinter:
        ld      de,03200h               ;the timeout period (as per Arnold)
_SupWaitNoBUSY:
        ld      bc,0F500h               ;8255 port B (bit 6 = BUSY)
        in      a,(c)                   ;read the state of BUSY
        bit     6,a
        jr      z,_SupNotBUSY
        dec     de
        ld      a,d
        or      e
        jr      nz,_SupWaitNoBUSY
        exx
        res     0,l                     ;L':0=0 means printer not there
        exx

        xor     a                       
        inc     a                       ;make NZ
        scf                             ;indicate no printing happened
        ret

_SupNotBUSY:
;
; get here when BUSY went away within the timeout period. Next we must get
; the current setting of 6845 register 12. We then set or reset bit 3
; depending on whether the top bit of the char to print is set or not.
;
        ld      bc,0BC0Ch               ;gonna access 6845 register 12
        out     (c),c                   ;make reg. selection
        ld      b,0BFh                  ;addr. of 6845 input data reg.
        in      a,(c)                   ;get current state of reg 12

        bit     7,l                     ;is top bit of char to print set QUESTMARK_
        jr      z,_SupNoPrintTop
;
; Top bit is set so we must set bit 3 in 6845 register 12 which controls D7
;
        set     3,a                     ;set the printer D7 signal high
        jr      _SupPrintTopSet

_SupNoPrintTop:
;
; Top bit is clear so we must reset bit 3 in 6845 register 12.
;
        res     3,a

_SupPrintTopSet:
        ld      b,0BDh                  ;addr of 6845 output data reg.
        out     (c),a                   ;write value of reg 12 back again

;
; Now that the D7 magic has been performed we can do the bog standard
; Centronics print routine which consists of outputting D6..D0 to EFxx
; then setting bit 7 in EFxx (to STROBE the char) and finally resetting it.
; i.e.
;            STROBE
;              |D6....D0
;              ||     |
;       EFxx = 0XXXXXXX
;       EFxx = 1XXXXXXX
;       EFxx = 0XXXXXXX
;
        ld      a,l                     ;get char to print
        and     07Fh                    ;mask out top bit (clear STROBE)
        ld      b,0EFh                  ;I/O addr of Centronics is EFxx
;        di
        out     (c),a                   ;set data byte into latch
        or      080h                    ;set top bit (STROBE active)
        out     (c),a                   ;print the character
        and     07Fh                    ;clear STROBE bit again
        out     (c),a                   ;make port idle again.
;        ei
        xor     a                       ;clear carry to indicate success
        ret


;=============
PrintStringHL:
;=============
;
; This is the routine that is called to print a sting of characters pointed
; to by HL. The string is terminated with a "$" character.
;
; The B register indicates the destination of the output. B:0 set means output
; to the screen while B:1 set means output to the printer. The two may be used
; together.
;
; On exit HL points at the terminating "$".
;
        ld      a,h
        exx
        ld      h,a
        exx

        ld      a,l
        exx
        ld      b,a                     ;copy of HL in HB'
        exx

        ld      a,(hl)
        cp      '$'                     ;check for terminating "$"
        ret     z

        bit     0,b                     ;B:0=1 => print to screen
        jr      z,_SupPrintHLTryPrinter

        ex      af,af'
        ld      a,b                     ;store B in A'
        ex      af,af'
        call    ScreenPrintChar         ;print the char on screen at (D',E')
        ex      af,af'
        ld      b,a                     ;recover stored B
        ex      af,af'

        exx
        ld      a,h
        exx
        ld      h,a

        exx
        ld      a,b
        exx
        ld      l,a                     ;get HL back from HB'
        ld      a,(hl)                  ;get back char to print

_SupPrintHLTryPrinter
        bit     1,b                     ;B:1=1 => print to printer
        jr      z,_SupPrintHLNoPrinter

        ex      af,af'
        ld      a,b                     ;store B in A'
        ex      af,af'
        call    PrinterPrintChar
        ex      af,af'
        ld      b,a                     ;recover stored B
        ex      af,af'

_SupPrintHLNoPrinter:
        exx
        ld      a,h
        exx
        ld      h,a

        exx
        ld      a,b
        exx
        ld      l,a                     ;get HL back from HB'

        inc     hl                      ;step along the string
        jr      PrintStringHL

;===========
WriteAYChip:
;===========
;
; This routine will write the value in the E register to the AY register
; identified by the value in D.
;
        ld      bc,0F782h               ;port F7xx=8255 control. 82=AoBiCo
        out     (c),c                   ;make A Output, B Input, C Output
        ld      a,d                     ;get required AY reg. number
        ld      b,0F4h                  ;8255 port A (AY data lines)
        out     (c),a                   ;send selected AY reg. num.

        ld      b,0F6h                  ;8255 port C (AY control, key row, etc)
        in      a,(c)                   ;get state of port C
        or      0C0h                    ;set top 2 bits (AY: BDIR, BC1 = 11)
        out     (c),a                   ;latch the AY reg num into AY chip
        and     03Fh                    ;reset BDIR, BC1 (inactive)
        out     (c),a                   ;AY chip now has selected reg D
        ld      c,a                     ;rember state of BDIR, BC1 + key row etc

        ld      a,e                     ;the value to write to reg D
        ld      b,0F4h                  ;8255 port A (AY data lines)
        out     (c),a                   ;the data byte is now availble to AY
        ld      a,c                     ;get BDIR BC1 etc.
        or      080h                    ;make BDIR BC1 = 10 (write data)
        ld      b,0F6h                  ;8255 port C controls BDIR etc.
        out     (c),a                   ;BDIR BC1 = 10 (write it!)
        out     (c),c                   ;BDIR BC1 = 00 (back to inactive)
; YOIK !
        ret

;===========
ProgramNote:
;===========
;
; This routine is aimed at taking the misery out of programming the AY chip.
; It takes the following parameters:
;
; HL = period of note to be played
; B = which channels the tone period should be set into (B:0=A, B:1=B, B:2=C)
;
; The value in A is used to look up a tone period and this value is programmed
; into the AY register pairs 0/1, 2/3 and 4/5 according to which bits are set
; in B.
;
; The AY noise period register (6) will not be set. (This should be done before
; or after calling the routine if required).
;
; The enables register (7) will not be set. The caller should call this routine
; as many times as it takes to load up the AY chip and then set the channel
; (and perhaps noise) bits in R7 to start the channel(s) playing.
;
; The 3 amplitude control registers (8,9,10) will be set to 00Fh (max volume
; with no envelopes used). These could be updated after the routine is called
; if required (for example to fade out a note).
;
; Registers 11, 12 and 13 will not be touched as envelopes are not being used.
;
;
        bit     0,b                     ;program channel A QUESTMARK_
        jr      z,_SupNoSetAYA
        push    bc
        ld      d,0                     ;AY reg 0 is channel A tone (fine)
        ld      e,l
        call    WriteAYChip             ;set channel A fine tone
        ld      d,1                     ;AY reg 1 is channel A tone (coarse)
        ld      e,h
        call    WriteAYChip             ;set channel A coarse tone
        pop     bc

_SupNoSetAYA:
        bit     1,b                     ;program channel B QUESTMARK_
        jr      z,_SupNoSetAYB
        push    bc
        ld      d,2                     ;AY reg 2 is channel B tone (fine)
        ld      e,l
        call    WriteAYChip             ;set channel B fine tone
        ld      d,3                     ;AY reg 3 is channel B tone (coarse)
        ld      e,h
        call    WriteAYChip             ;set channel B coarse tone
        pop     bc

_SupNoSetAYB:
        bit     2,b                     ;program channel C QUESTMARK_
        jr      z,_SupNoSetAYC
        push    bc
        ld      d,4                     ;AY reg 4 is channel C tone (fine)
        ld      e,l
        call    WriteAYChip             ;set channel C fine tone
        ld      d,5                     ;AY reg 5 is channel C tone (coarse)
        ld      e,h
        call    WriteAYChip             ;set channel C coarse tone
        pop     bc

_SupNoSetAYC:
        ld      d,8                     ;channel A amplitude
        ld      e,00Fh                  ;max volume with no envelope
        call    WriteAYChip
        ld      d,9                     ;channel B amplitude
        ld      e,00Fh                  ;max volume with no envelope
        call    WriteAYChip
        ld      d,10                    ;channel C amplitude
        ld      e,00Fh                  ;max volume with no envelope
        call    WriteAYChip
        ret

;===================
KeyboardReadNoDelay:
;===================
;
; Normally I do a bit of a delay at the end of Keyboard Read to debounce but
; when running time critical code (e.g. PALET27) I want to get back as fast as
; possible.
;
        ld      a,0
        jr      _SupKeyContinueRead

;============
KeyboardRead:
;============
;
; This simple routine just outputs the values 0..9 to the 4 keyboard row
; select lines in 8255 port C. As each row in turn is made active the AY
; register 14 (8 bit I/O) is read to check the state of the column lines
; If a column is seen to be high (bit set) while a particular row is
; selected then the key at the intersection of that row and column must be
; being pushed at that time. The keynumber = (row * 8) + column
;
        ld      a,1
_SupKeyContinueRead:

        push    af                      ;save the delay y/n flag
        ld      hl,QUESTMARK_KeyboardBuffer

        ld      bc,0F782h
        out     (c),c                   ;make 8255 port A=output
        ld      bc,0F40Eh
        out     (c),c                   ;selecting AY register 14
        ld      b,0F6h                  ;8255 port C has BDIR/BC1 bits
        in      a,(c)                   ;read current state
        or      0C0h                    ;set BDIR BC1 = 11 (select addr)
        out     (c),a                   ;latch 14 into AY addr select reg
        and     03Fh                    ;reset top two bits (BDIR,BC1 = 00)
        out     (c),a                   ;make AY inactive again
        ld      bc,0F792h
        out     (c),c                   ;make 8255 port A=input

        ld      d,9                     ;testing 10 rows (9, 8, ... 0)
_SupKeyRowLoop:
        ld      b,0F6h
        in      a,(c)                   ;get state of 8255 port C
        and     0F0h                    ;clear bottom 4 row select bits
        or      d                       ;replace them with value in D
        out     (c),a                   ;SELECT A ROW
        and     03Fh
        or      040h                    ;make BDIR BC1 = 01 (read from AY)

        ld      b,0F6h                  ;port C
        out     (c),a                   ;read from AY reg 14 to port A

        ld      b,0F4h                  ;8255 port A now has column values
        in      a,(c)                   ;READ THE COLUMNS
        ld      (hl),a                  ;store byte in buffer
        inc     hl
        dec     d                       ;next keyboard row
        jp      p,_SupKeyRowLoop        ;keep doing rows till we go thru 0
;
; now need to check buffer and return HL pointing to a list of key numbers
; being held down with A containing the first in the list.
;
        ld      hl,QUESTMARK_KeyboardBuffer
        ld      de,QUESTMARK_keysHeldDownBuffer
        ld      a,0FFh
        ld      (de),a                  ;start by assuming no key pressed
        ld      b,10                    ;10 bytes (one for each row)
        ld      c,0                     ;count of keys found
_SupKeyProcessBitsLoop:
        ld      a,(hl)
        inc     hl
        cp      0FFh
        jr      z,_SupNoKeyOnThisRow
        call    _SupKeyIsDown           ;convert
        ld      (de),a                  ;store in vector of key numbers
        inc     c                       ;count of keys found
        inc     de
_SupNoKeyOnThisRow:
        djnz    _SupKeyProcessBitsLoop
        ld      hl,QUESTMARK_KeysHeldDownBuffer

        pop     af                      ;get delay flag (0/1)
        or      a
        jr      z,_SupKeyNoDelay

        push    bc
        ld      c,030h
_SupKeyDeBounceOuter:
        ld      b,0
_SupKeyDeBounceInner:
        djnz    _SupKeyDeBounceInner
        dec     c
        jr      nz,_SupKeyDeBounceOuter
        pop     bc

_SupKeyNoDelay:
        ld      a,(hl)                  ;first key found or 0FFh
        ret

_SupKeyIsDown:
;
; A column value has been read which is not 0FFh so at least 1 bit must be 0
; We now scan across the byte looking for the 0 bit (rotating into carry)
; When found the bit count is added to 8 times the current row (in D) to give
; a keynumber (0..79)
;
        push    de
        ld      e,8                     ;bit count
_SupKeyDownBitLoop:
        sla     a                       ;shift top bit into carry (0 to bot)
        jr      nc,_SupKeyBitFound
        dec     e
        jr      _SupKeyDownBitLoop      ;no conditional - it must be found

_SupKeyBitFound:
        ld      a,b
        dec     a                       ;cos rows are 0..9 not 1..10
        add     a,a
        add     a,a           
        add     a,a                     ;A = row * 8
        add     a,e                     ;A = row * 8 + column = keynumber
        dec     a                       ;cos E is 1..8 rather than 0..7
        pop     de
        ret

;=============
DelayASeconds:
;=============
;
; Delay for number of seconds given in A
;
; In one second there are 4,000,000 T-States
;
        push    af
        push    bc
        push    hl
        ld      h,0
        ld      l,a
        add     hl,hl
        add     hl,hl                   ;times A by 4 cos emulator told me
DelayMainLoop:
        ld      c,0F0h
DelayOuterLoop:
        ld      b,0
DelayInnerLoop:
        djnz    DelayInnerLoop
        dec     c
        jr      nz,DelayOuterLoop
        dec     hl
        ld      a,h
        or      l
        jr      nz,DelayMainLoop
        pop     hl
        pop     bc
        pop     af
        ret


;==========
UnlockArn5:
;==========
;
; Before an Arnold 5 feature can be accessed we must unlock the ASIC. The
; following does just that. The method is to send a fixed sequence of data
; to I/O addr BCxx (6845 address register).
;
; Next the new features I/O page is switched to appear at 04000h.
;
; The routine then makes a simple check to see if the new features have come
; alive by writing the upper nybble of the green register of sprites 0..15
; (addresses = 6423h, 6425h, 6427h, ... 643Fh) with non-zero values - reading
; them back and checking they are zeroised.
;
        ld      hl,.Arnold5Key
        ld      d,18
        ld      bc,0BC00h               ;6845 address register
_SupUnlockLoop:
        ld      a,(hl)                  ;a byte of the key
        out     (c),a                   ;output the sequence to 6845 addr reg
        inc     hl                      ;step along key
        dec     d
        jr      nz,_SupUnlockLoop

        ld      b,07Fh                  ;the ULA
        ld      c,10111000b             ;make ULA registers appear at 4000h
        out     (c),c

        ld      b,15                    ;there are 15 sprites
        ld      hl,06423h               ;address of 1st green reg to test
_SupUnlockGreenLoop:
        ld      a,l                     ;will go 23,25,...3F (always non-zero)
        and     00Fh                  ;only want lower nybble
        add     a,a
        add     a,a
        add     a,a
        add     a,a                     ;move to higher nybble
        ld      e,a                     ;save it for a sec'
        ld      a,(hl)                  ;get current green
        or      e                       ;add in the new top nybble
        ld      (hl),a                  ;try to set the new green
        ld      a,(hl)                  ;when read back D7..D4 should = 0
        inc     hl
        inc     hl
        and     0F0h                    ;clear low bits
        jr      nz,_SupUnlockFail
        djnz    _SupUnlockGreenLoop
        xor     a                       ;make NC
        ret

_SupUnlockFail:
        scf
        ret

;==========
RelockArn5:
;==========
;
; After testing/using an Arnold 5 feature I will re-lock it just to be on
; the safe side. This involves sending the first 15 bytes of the 16 byte key
; but then sending an out of sequence 16th byte
;
; We assume the register page is on at 4000h. So after re-locking we test
; locations 6401, 6403 and expect that we will read what we write. If not
; then perhaps the new features are stuck on.
;
        ld      b,07Fh                  ;ULA
        ld      c,10100000b             ;register page off
        out     (c),c

        ld      hl,.Arnold5Key
        ld      d,17
        ld      bc,0BC00h               ;6845 address register
_SupRelockLoop:
        ld      a,(hl)
        out     (c),a
        inc     hl
        dec     d
        jr      nz,_SupRelockLoop

        ld      a,0A5h                  ;it actually expects 0FFh
        out     (c),a

        ld      b,15                    ;there are 32 green entries
        ld      hl,06423h               ;address of 1st green reg to test
_SupRelockGreenLoop:
        ld      a,l                     ;will go 1,3,...3F (always non-zero)
        and     00Fh                  ;only want lower nybble
        add     a,a
        add     a,a
        add     a,a
        add     a,a                     ;move to higher nybble
        ld      e,a                     ;save it for a sec'
        ld      a,(hl)                  ;get current green
        or      e                       ;add in the new top nybble
        ld      (hl),a                  ;try to set the new green
        ld      a,(hl)                  ;when read back D7..D4 should = 0
        inc     hl
        inc     hl
        and     0F0h                    ;clear low bits
        jr      z,_SupRelockFail        ;we expected upper to be non-zero
        djnz    _SupRelockGreenLoop
        xor     a                       ;make NC
        ret

_SupRelockFail:
        scf
        ret

;===========
OfferRetest:
;===========
;
; This is called at the end of some tests. It just clears the screen and puts
; up a message saying "Press ^ to re run the test,
;                      any other key to continue"
; If the user presses ^ then we RET C else we RET NC
;
        ld      a,1
        call    ScreenSetMode

        ld      de,00A00h
        call    SetCursorPos

        ld      hl,.RetestMess
        ld      b,1
        call    PrintStringHL

        ld      a,1
        call    DelayASeconds           ;keyboard de-bounce

        ld      b,100
RetestWaitKey:
        push    bc
        call    KeyboardRead
        pop     bc
        cp      0FFh
        jr      nz,RetestGotKey
        djnz    RetestWaitKey

RetestGotKey:
        cp      72
        jr      z,DoRetest
        cp      48
        jr      z,DoRetest
        cp      0
        jr      z,DoRetest

        ld      a,1
        call    ScreenSetMode
        xor     a                       ;make NC cos no retest
        ret

DoRetest:
        ld      a,1
        call    ScreenSetMode
        scf                             ;make C for a retest
        ret

.CRTCInitialValues
                .DB    000h            ;register 15 = cursor low
                .DB    0C0h            ;register 14 = cursor high
                .DB    000h            ;register 13 = start addr high
                .DB    030h            ;register 12 = start addr low
                .DB    000h            ;register 11 = cursor end raster
                .DB    000h            ;register 10 = cursor start raster
                .DB    007h            ;register 9  = max raster addr
                .DB    000h            ;register 8  = interlace & skew
                .DB    01Eh            ;register 7  = vert sync pos
                .DB    019h            ;register 6  = vertical displayed
                .DB    000h            ;register 5  = vert total adjust
                .DB    026h            ;register 4  = vertical total
                .DB    00Eh            ;register 3  = vsync/hsync widths
                .DB    02Eh            ;register 2  = horiz sync pos
                .DB    028h            ;register 1  = horiz displayed
                .DB    03Fh            ;register 0  = horiz total

.Initial16ColorPalette
                .DB    22              ;border = Green
                .DB    14              ;pen 15 = Orange (normally flashing)
                .DB    23              ;pen 14 = Sky Blue (normally flashing)
                .DB    25              ;pen 13 = Pastel Green
                .DB    18              ;pen 12 = Bright Green
                .DB    7               ;pen 11 = Pink
                .DB    31              ;pen 10 = Pastel Blue
                .DB    30              ;pen  9 = Yellow
                .DB    6               ;pen  8 = Cyan
                .DB    13              ;pen  7 = Bright Magenta
                .DB    21              ;pen  6 = Bright Blue
                .DB    20              ;pen  5 = Black
                .DB    11              ;pen  4 = Bright White
                .DB    12              ;pen  3 = Bright Red
                .DB    19              ;pen  2 = Bright Cyan
                .DB    10              ;pen  1 = Bright Yellow (text)
                .DB    4               ;pen  0 = Blue  (background)

