; oilcd.asm

; This code contains driver functions for getting an OPTREX LCD module
; integrated into the OI 16C74 for the spp project.
; Uses 4-bit protocol due to IO constraints.

; Pin assignments:

;       RA2  Button 1                            IN
;       RA4  Button 2                            IN
;       RA5  Button 3                            IN
;
;       RD1  Optrex RS                           OUT
;       RD2  Optrex R/W                          OUT
;       RD3  Optrex Enable                       OUT
;       RD4  Optrex DB4                          I/O
;       RD5  Optrex DB5                          I/O
;       RD6  Optrex DB6                          I/O
;       RD7  Optrex DB7                          I/O

#define lcd_rs PORTD,1
#define lcd_rw PORTD,2
#define lcd_e  PORTD,3

#include "oimacs.asm"

#define TOI
#ifdef TOI
 #include "oitoi.asm"
#endif

org 0x800       ; page 1

#include "oimsgs.asm"
;-------------------------------------------------------
;-------------------------------------------------------
lcdInit
;       This routine initializes the LCD display
;       as recommended on p84 of the Optrex data book.

        movlw 0x30
        call lcdCmd8nbf
        call lcdwait4ms         ; wait > 4.1 mS

        movlw 0x30
        call lcdCmd8nbf
        call lcdwait4ms         ; wait > 100 uS

        movlw 0x30
        call lcdCmd8nbf
        call lcdwait4ms         ; wait > 100 uS

        movlw 0x20              ; set to 4-bit mode
        call lcdCmd8nbf
        call lcdwait4ms         ; wait > 100 uS

        movlw 0x28              ; 4-bit mode, # lines, 5x7 font. Documentation unclear.
        call lcdCmd
        movlw 0x08              ; display OFF
        call lcdCmd
        movlw 0x0F              ; display ON, csr ON, blink ON
        call lcdCmd
        movlw 0x06              ; entry mode set. csr increment, no shift
        call lcdCmd
        movlw 0x02              ; cursor at HOME
        call lcdCmd
        movlw 0x01              ; clear display
        call lcdCmd

        lcdLine1                ; display spp message
        lcdmsg lcdmsgSPP
        movlw 0x80
        movwf LCDPAGE           ; Main menu w/ message displayed
        movlw 1
        movwf LCDLINESAV

        return          ; from lcdInit
;-------------------------------------------------------
lcdwait4ms
;       delay > 4.1 mS as part of initialization sequence
;       at 20 MHz, that's 20500 instructions.
;       My counting comes to AT LEAST that many.
;       Oscilloscope reading: 14 mS @ 10.24 MHz.

        movlw 0x30              ; 48 x 256 @ 2 inst/cycle = 24576 inst
        movwf LEDLOOP1
lw1
        movlw 0xFF
        movwf LEDLOOP0
lw0
        decfsz LEDLOOP0,F
        goto lw0

        decfsz LEDLOOP1,F
        goto lw1

        return          ; from lcdwait4ms
;-------------------------------------------------------
lcdCmd8nbf
;       This routine sends a 4-bit command to the LCD module
;       using the 8-bit protocol without doing a wait on the busy flag.
;       This is only used during the initialization sequence.
;       On entry, WREG contains the command byte. (e.g. x01 for CLEAR_DISPLAY)

        bcf STATUS,RP0          ; select bank 0
        movwf LCDBYTE           ; save command byte

        bcf lcd_e       ; redundant
        bcf lcd_rs      ; low for an instruction
        bcf lcd_rw      ; low for a write

        movlw 0x0F      ; clear hi data nibble on LCD pins w/o disturbing
        andwf PORTD,F   ; lo nibble value

        movfw LCDBYTE
        andlw 0xF0
        iorwf PORTD,F   ; apply HI nibble of LCDBYTE to LCD pins

        bsf lcd_e       ; leading clock edge for high nibble
        nop             ; pulse width spec is 450nS
        nop
        nop
        nop
        bcf lcd_e       ; trailing edge

        return          ; from lcdCmd8nbf
;-------------------------------------------------------
lcdBF   macro
; converted to a macro to go easy on stack
; This routine polls the LCD Busy flag and returns
; when the current command is complete.
; LCDADDR is updated with the current CG address

        local lbf0
        movlw LEDBIT_LCDBUSY
       ;bcf PCLATH,3    ; paging
       ;call ledSet     ; comment out: go easy on stack
       ;bsf PCLATH,3    ; paging
                        ; poll Busy Flag until command is complete

        bsf STATUS,RP0          ; select bank 1
        movlw 0xF0
        movwf trisd             ; configure data pins 4-7 as inputs

        bcf STATUS,RP0          ; select bank 0
        bcf lcd_rs
        bsf lcd_rw
        bsf LCDBUSY,0           ; LCD is busy until it says otherwise
lbf0
        bsf lcd_e       ; leading clock edge for low nibble
        nop             ;
        nop             ;
        nop             ;
        btfss PORTD,7   ; check BF (Busy Flag)
        clrf LCDBUSY
        movfw PORTD
        andlw 0xF0
        movwf LCDADDR   ; save hi nibble of CG address
        bcf lcd_e

        bsf lcd_e       ; low nibble
        nop             ;
        nop             ;
        nop             ;
        swapf PORTD,W
        andlw 0x0F
        iorwf LCDADDR,F ; save lo nibble of CG address
        bcf lcd_e

        btfsc LCDBUSY,0
        goto lbf0

        bcf lcd_rw
        bsf STATUS,RP0          ; select bank 1
        clrf trisd              ; Restore data pins to outputs
        bcf STATUS,RP0          ; select bank 0

       ;bcf PCLATH,3    ; paging
       ;call ledClear   ; comment out: go easy on stack
       ;bsf PCLATH,3    ; paging

        endm            ; from lcdBF
;-------------------------------------------------------
lcdCmd
;       This routine sends a command to the LCD module.
;       On entry, WREG contains the command byte. (e.g. x01 for CLEAR_DISPLAY)

        bcf STATUS,RP0          ; select bank 0
        movwf LCDBYTE           ; save command byte

        bcf lcd_rs
        bcf lcd_rw

        movlw 0x0F      ; clear hi data nibble on LCD pins w/o disturbing
        andwf PORTD,F   ; lo nibble value

        movfw LCDBYTE
        andlw 0xF0
        iorwf PORTD,F   ; apply HI nibble of LCDBYTE to LCD pins

        bsf lcd_e       ; leading clock edge for high nibble
        nop             ; pulse width spec is 450nS
        nop
        nop
        bcf lcd_e       ; trailing edge

        movlw 0x0F      ; clear hi data nibble on LCD pins w/o disturbing
        andwf PORTD,F   ; lo nibble value

        swapf LCDBYTE,W
        andlw 0xF0
        iorwf PORTD,F   ; apply LO nibble of LCDBYTE to LCD pins

        bsf lcd_e       ; leading clock edge for low nibble
        nop             ; pulse width spec is 450nS
        nop
        nop
        bcf lcd_e       ; trailing edge

       ;call lcdBF      ; wait for command completion
        lcdBF           ; wait for command completion (macro version)

        return          ; from lcdCmd
;-------------------------------------------------------
lcdWrite
;       This routine writes a data byte to the LCD module.
;       On entry, WREG contains the data byte.

        bcf STATUS,RP0          ; select bank 0
        movwf LCDBYTE           ; save data byte

        bsf lcd_rs
        bcf lcd_rw

        movlw 0x0F      ; clear hi data nibble on LCD pins w/o disturbing
        andwf PORTD,F   ; lo nibble value

        movfw LCDBYTE
        andlw 0xF0
        iorwf PORTD,F   ; apply HI nibble of LCDBYTE to LCD pins

        bsf lcd_e       ; leading clock edge for high nibble
        nop             ; pulse width spec is 450nS
        nop
        nop
        bcf lcd_e       ; trailing edge

        movlw 0x0F      ; clear hi data nibble on LCD pins w/o disturbing
        andwf PORTD,F   ; lo nibble value

        swapf LCDBYTE,W
        andlw 0xF0
        iorwf PORTD,F   ; apply LO nibble of LCDBYTE to LCD pins

        bsf lcd_e       ; leading clock edge for low nibble
        nop             ; pulse width spec is 450nS
        nop
        nop
        bcf lcd_e       ; trailing edge

       ;call lcdBF      ; wait for command completion
        lcdBF           ; wait for command completion (macro version)

        return          ; from lcdWrite
;-------------------------------------------------------
lcdNewline
;       this routine advances to next line.
;       Uses the variable LCDLINE rather than getting address from LCD module

        btfss LCDLINE,2
        goto lnlnot4
        lcdLine1        ; it's on line 4. Go to line 1
        return
lnlnot4
        btfsc LCDLINE,0
        goto lnlnot2
        lcdLine3        ; it's on line 2. Go to line 3
        return
lnlnot2
        btfss LCDLINE,1
        goto lnlnot3
        lcdLine4        ; it's on line 3. Go to line 4
        return
lnlnot3
        lcdLine2        ; must be on line 1. Go to line 2
        return
;-------------------------------------------------------
readLCDButtons
; This routine reads the LCD buttons and returns their status in LCDBUTTONS0,
; in which bits 2,4,5 are set if button 1,2,or 3 were pressed.
; Debouncing and wait-for-release is handled.

        bcf STATUS,RP0          ; PORTA in bank 0
        movfw PORTA
        andlw 0x34              ; Mask out button bits 2,4,5. 0 = pressed
        xorlw 0x34              ; Invert button bits. 1 = pressed
        movwf LCDBUTTONS0
        movf LCDBUTTONS0,1
        btfsc STATUS,Z
        goto rlbdone            ; no buttons pressed. return

                                ; a button was pressed.
rlbloop                         ; Poll for release before doing anything.

        bcf PCLATH,3    ; paging
        call visibleDelay       ; let switches bounce (~.5mS each)
        bsf PCLATH,3    ; paging
        bcf PCLATH,3    ; paging
        call visibleDelay
        bsf PCLATH,3    ; paging
        bcf PCLATH,3    ; paging
        call visibleDelay
        bsf PCLATH,3    ; paging

        movfw PORTA
        andlw 0x34
        xorlw 0x34              ; all 3 pressed --> WREG=0x34
        movwf LCDBUTTONS1

        movlw 0                 ; if button 1 is being held down, flash led0
        bcf PCLATH,3    ; paging
        btfsc LCDBUTTONS1,2     ; (button 1 is on bit 2)
        call ledSet
        bsf PCLATH,3    ; paging

        movlw 1                 ; if button 2 is being held down, flash led1
        bcf PCLATH,3    ; paging
        btfsc LCDBUTTONS1,4     ; (button 2 is on bit 4)
        call ledSet
        bsf PCLATH,3    ; paging

        movlw 2                 ; if button 3 is being held down, flash led2
        bcf PCLATH,3    ; paging
        btfsc LCDBUTTONS1,5     ; (button 3 is on bit 5)
        call ledSet
        bsf PCLATH,3    ; paging

        movf LCDBUTTONS1,1      ; keep flashing bits until buttons are released
        btfss STATUS,Z
        goto rlbloop

        bcf PCLATH,3    ; paging
        call ledClear           ; buttons finally released
        bsf PCLATH,3    ; paging
rlbdone
        return          ; from readLCDButtons
;-------------------------------------------------------
pollLCDButtons
;       This routine polls the 3 buttons assigned to the OPTREX

        call readLCDButtons     ; handles debouncing, etc. sets LCDBUTTONS0
        movf LCDBUTTONS0,1
        btfss STATUS,Z
        goto plbbut1
        goto plbdone

plbbut1
        btfss LCDBUTTONS0,2
        goto plbbut2
                        ; button 1 was pressed. Invoke callback
        call lcdBut1CB
        goto plbdone
plbbut2
        btfss LCDBUTTONS0,4
        goto plbbut3
                        ; button 2 was pressed. Invoke callback
        call lcdBut2CB
        goto plbdone
plbbut3
        btfss LCDBUTTONS0,5
        goto plbbut4
                        ; button 3 was pressed. Invoke callback
        call lcdBut3CB
        goto plbdone

plbbut4

plbdone
        return          ; from pollLCDButtons
;-------------------------------------------------------
lcdShowHex
;       This routine takes the value in WREG and displays its 2-char
;       HEX value on the LCD module

        movwf LCDTMP

        movlw HIGH lcdmsgHEX
        movwf PCLATH

        swapf LCDTMP,W  ; move hi nibble in low
        andlw 0x0F
        call lcdmsgHEX  ; look up char equivalent
        call lcdWrite   ; display it

        movfw LCDTMP
        andlw 0x0F      ; keep lo nibble this time
        call lcdmsgHEX
        call lcdWrite   ; display it

        return          ; from lcdShowHex
;-------------------------------------------------------
lcdShowSigned
;       This routine takes the value in WREG and displays a 3-char
;       HEX value on the LCD module in the form ' AB' or '-AB'

        movwf LCDTMP

        btfsc LCDTMP,7
        goto lssneg
        goto lsspos
lssneg
        sublw 0         ; wreg = 0 - wreg
        movwf LCDTMP
        movlw '-'
        call lcdWrite
        goto lsssigndone
lsspos
        movlw ' '
        call lcdWrite
lsssigndone
                        ; rather than call lcdShowHex, duplicate it's code
                        ; this way, we go easy on the stack depth
        movlw HIGH lcdmsgHEX
        movwf PCLATH

        swapf LCDTMP,W  ; move hi nibble in low
        andlw 0x0F
        call lcdmsgHEX  ; look up char equivalent
        call lcdWrite   ; display it

        movfw LCDTMP
        andlw 0x0F      ; keep lo nibble this time
        call lcdmsgHEX
        call lcdWrite   ; display it

        return          ; from lcdShowSigned
;-------------------------------------------------------
lcdShowPots
;       This routine displays all the POT values on the LCD module

        bcf PCLATH,3    ; paging
        call readPotVals
        bsf PCLATH,3    ; paging

        lcdClear

        lcdLine1        ; line 1 will hold POTVALs 0-5
        movlw POTVAL0
        movwf FSR       ; point to POTVAL0
        movlw 6
        movwf TMP
lsp0
        movfw INDF
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsp0

        lcdLine2        ; line 2 will hold POTVALs 6-11
        movlw POTVAL6
        movwf FSR       ; point to POTVAL6
        movlw 6
        movwf TMP
lsp1
        movfw INDF
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsp1

        lcdLine3        ; line 3 will hold POTVALs 12-17
        movlw POTVAL12
        movwf FSR       ; point to POTVAL12
        movlw 6
        movwf TMP
lsp2
        movfw INDF
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsp2

        lcdLine4        ; line 4 will hold POTVALs 18-23
        movlw POTVAL18
        movwf FSR       ; point to POTVAL18
        movlw 6
        movwf TMP
lsp3
        movfw INDF
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsp3

        return          ; from lcdShowPots
;-------------------------------------------------------
lcdShowThresh
;       This routine displays the threshold stim levels on the LCD module

        bcf PCLATH,3    ; paging
        call getMapValues       ; get 60 map values from Master
                                ; into a BANK1 buffer at UNION
        bsf PCLATH,3    ; paging
        lcdClear

        movlw UNION
        movwf FSR               ; point at MAPT0  -->> in BANK1 <<--

        lcdLine1        ; line 1 will hold MAPT0-1
        lcdmsg lcdmsgThresh
        movlw 2
        movwf TMP
lst0
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lst0

        lcdLine2        ; line 2 will hold MAPT2-7
        movlw 6
        movwf TMP
lst1
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lst1

        lcdLine3        ; line 3 will hold MAPT8-13
        movlw 6
        movwf TMP
lst2
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lst2

        lcdLine4        ; line 4 will hold MAPT14-19
        movlw 6
        movwf TMP
lst3
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lst3

        return          ; from lcdShowThresh
;-------------------------------------------------------
lcdShowComfort
;       This routine displays the comfort stim levels on the LCD module

        bcf PCLATH,3    ; paging
        call getMapValues       ; get values from Master
        bsf PCLATH,3    ; paging

        lcdClear

        movlw UNION
        addlw .20
        movwf FSR               ; point at MAPC0  -->> in BANK1 <<--



        lcdLine1        ; line 1 will hold MAPC0-1
        lcdmsg lcdmsgComf
        movlw 2
        movwf TMP
lsc0
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsc0

        lcdLine2        ; line 2 will hold MAPC2-7
        movlw 6
        movwf TMP
lsc1
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsc1

        lcdLine3        ; line 3 will hold MAPC8-13
        movlw 6
        movwf TMP
lsc2
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsc2

        lcdLine4        ; line 4 will hold MAPC14-19
        movlw 6
        movwf TMP
lsc3
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsc3

        return          ; from lcdShowComfort
;-------------------------------------------------------
lcdShowRange
;       This routine displays the stim level ranges on the LCD module

        bcf PCLATH,3    ; paging
        call getMapValues       ; get values from Master
        bsf PCLATH,3    ; paging

        lcdClear

        movlw UNION
        addlw .40
        movwf FSR               ; point at MAPR0  -->> in BANK1 <<--

       ;lcdLine4
       ;lcdmsg lcdmsgNotImpl

        lcdLine1        ; line 1 will hold MAPR0-1
        lcdmsg lcdmsgRange
        movlw 2
        movwf TMP
lsr0
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsr0

        lcdLine2        ; line 2 will hold MAPR2-7
        movlw 6
        movwf TMP
lsr1
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsr1

        lcdLine3        ; line 3 will hold MAPR8-13
        movlw 6
        movwf TMP
lsr2
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsr2

        lcdLine4        ; line 4 will hold MAPR14-19
        movlw 6
        movwf TMP
lsr3
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsr3

        return          ; from lcdShowRange
;-------------------------------------------------------
lcdShowMCT
;       This routine displays the MCthreshold stim levels on the LCD module

        bcf PCLATH,3    ; paging
        call getMCValues        ; get 60 MC values from Master
                                ; into a BANK1 buffer at UNION
        bsf PCLATH,3    ; paging
        lcdClear

        movlw UNION
        movwf FSR               ; point at MC_T0  -->> in BANK1 <<--

        lcdLine1        ; line 1 will hold MC_T0-1
        lcdmsg lcdmsgMCT
        movlw 2
        movwf TMP
lsmct0
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsmct0

        lcdLine2        ; line 2 will hold MC_T2-7
        movlw 6
        movwf TMP
lsmct1
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsmct1

        lcdLine3        ; line 3 will hold MC_T8-13
        movlw 6
        movwf TMP
lsmct2
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsmct2

        lcdLine4        ; line 4 will hold MC_T14-19
        movlw 6
        movwf TMP
lsmct3
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsmct3

        return          ; from lcdShowMCT
;-------------------------------------------------------
lcdShowMCC
;       This routine displays the MCcomfort stim levels on the LCD module

        bcf PCLATH,3    ; paging
        call getMCValues       ; get values from Master
        bsf PCLATH,3    ; paging

        lcdClear

        movlw UNION
        addlw .20
        movwf FSR               ; point at MC_C0  -->> in BANK1 <<--



        lcdLine1        ; line 1 will hold MC_C0-1
        lcdmsg lcdmsgMCC
        movlw 2
        movwf TMP
lsmcc0
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsmcc0

        lcdLine2        ; line 2 will hold MC_C2-7
        movlw 6
        movwf TMP
lsmcc1
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsmcc1

        lcdLine3        ; line 3 will hold MC_C8-13
        movlw 6
        movwf TMP
lsmcc2
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsmcc2

        lcdLine4        ; line 4 will hold MC_C14-19
        movlw 6
        movwf TMP
lsmcc3
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        movlw ' '
        call lcdWrite
        incf FSR,F
        decfsz TMP,F
        goto lsmcc3

        return          ; from lcdShowMCC
;-------------------------------------------------------
lcdShowFrame
;       This routine displays the FRAME data on the LCD module

        lcdClear

        movlw UNION
        addlw .00
        movwf FSR               ; point at FRAME0LO -->> in BANK1 <<--

        lcdLine1        ; line 1 will hold FRAME message
        lcdmsg lcdmsgFrame


        lcdLine2        ; line 2 will hold FRAME[0-5]

        movlw ' '
        call lcdWrite

        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex ; FRAME0LO

        movlw ' '
        call lcdWrite

        incf FSR,F      ; FRAME0HI
        incf FSR,F      ; FRAME1LO
        incf FSR,F      ; points to FRAME1HI
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        movwf TMP       ; TMP=FRAME1HI
        movlw ' '       ; display D8
        btfsc TMP,0
        movlw '1'
        call lcdWrite
        decf FSR,F      ; point back to FRAME1LO
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        incf FSR,F      ; FRAME1HI
        incf FSR,F      ; points to FRAME2LO

        movlw 4         ; display FRAME[2-5]LO
        movwf TMP
lsf1
        movlw ' '
        call lcdWrite
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        incf FSR,F      ; point to FRAMExHI
        incf FSR,F      ; point to FRAMEx+1LO
        decfsz TMP,F
        goto lsf1

        lcdLine3        ; line 3 will hold FRAME[6-10]

        incf FSR,F      ; point to FRAME6HI
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        movwf TMP       ; TMP=FRAME6HI
        movlw ' '       ; display D8
        btfsc TMP,0
        movlw '1'
        call lcdWrite
        decf FSR,F      ; point back to FRAME6LO
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        incf FSR,F
        incf FSR,F      ; points to FRAME7LO

        movlw ' '
        call lcdWrite

        movlw 4         ; display FRAME[7-10]LO
        movwf TMP
lsf2
        movlw ' '
        call lcdWrite
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        call lcdShowHex
        incf FSR,F      ; point to FRAMExHI
        incf FSR,F      ; point to FRAMEx+1LO
        decfsz TMP,F
        goto lsf2

        lcdLine4        ; line 4 will hold frame summary

        movlw UNION
        addlw .03
        movwf FSR               ; point at FRAME1HI -->> in BANK1 <<--
        bsf STATUS,RP0
        movfw INDF              ; FRAME1HI is in wreg
        bcf STATUS,RP0
        movwf Wreg
        btfsc Wreg,0
        goto lsfdum
        decf FSR,F              ; point to FRAME1LO
        bsf STATUS,RP0
        movfw INDF              ; FRAME1LO is in wreg
        bcf STATUS,RP0
        movwf Wreg
        btfsc Wreg,7
        goto lsfdum
                        ; the frame is not a dummy. Generate a line saying
                        ; 'Electrode %d Stim %d'
        lcdmsg lcdmsgElecStim
        lcdLine4
        incf FSR,F
                        ; figure out which electrode is selected
        incf FSR,F              ; point to FRAME2LO
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        movwf TMP               ; FRAME2LO is in TMP

        movlw 0xFF
        movwf PAIRNO                    ; 0xFF will increment to 0
        movlw MAXPAIRWIDTHPLUS          ; Max pulse2 width + PAIRWIDTHDELTA/2
lsf3
        incf PAIRNO,F
        movfw TMP
        addlw PAIRWIDTHDELTAOI          ; ---OI is positive version of P-W-D-
        movwf TMP
        sublw MAXPAIRWIDTHPLUS          ; W = MAXPAIRWIDTHPLUS - W
        btfsc STATUS,C                  ; C will be clear if result is neg
        goto lsf3
                                ; electrode # is in PAIRNO

        movlw 0xD4      ; command/address of start of line 4
        addlw .10       ; column .11
        call lcdCmd     ; go to line 4 column 11
        movfw PAIRNO
        call lcdShowHex ; display electrode #

                        ; calculate the STIM level
        movfw FSR
        addlw .8
        movwf FSR       ; points to FRAME6LO in bank 1
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        movwf FRAME6LO

        movfw FSR
        addlw .4
        movwf FSR       ; points to FRAME8LO in bank 1
        bsf STATUS,RP0
        movfw INDF
        bcf STATUS,RP0
        movwf FRAME8LO

        clrf ADDER
        movlw MINSTIMSTRENGTH
        subwf FRAME6LO,W        ; w = FRAME6LO - MINSTIMSTRENGTH
        btfss STATUS,C          ; C is 0 if neg
        goto lsf4               ; short pulse. No adder
                        ; FRAME6LO is extended. calc adder value
        movwf ADDER
        rrf ADDER,F     ; divide by 4 for scaling
        rrf ADDER,F
        bcf ADDER,7
        bcf ADDER,6
lsf4
        movlw 0xD4      ; command/address of start of line 4
        addlw .18       ; column .19
        call lcdCmd     ; go to line 4 column 19
        movfw ADDER
        subwf FRAME8LO,W        ; w = FRAME8LO - ADDER = STIM level
        call lcdShowHex         ; show STIM level on lcd


        goto lsfdone
lsfdum                  ; the frame is a dummy
        lcdmsg lcdmsgDummy

lsfdone
        bsf LCDPAGE,7   ; LCD display is in data mode

        return          ; from lcdShowFrame
;-------------------------------------------------------
;       The LCD menu system works as follows:
;       The LCD screen in considered to be displaying 1 of 2 kinds of
;       information: a menu or a page of data.
;       The variable LCDPAGE contains the current context of the screen.
;       When LCDPAGE[7] is set, data is displayed. Otherwise, it's a menu.
;       LCDPAGE[0-6] are the menu ID. (when bit 7 is set, it's the ID of
;       the menu from which the data page was requested.)
;       The buttons are assigned as:
;         1=page: display next (or prev) menu
;         2=line: move to next menu item
;         3=pick: select menu item
;       In data mode, any button returns you to the previous menu.
;-------------------------------------------------------
lcdDisplayMenu
;       This routine displays the menu specified in LCDPAGE

        bcf LCDPAGE,7
        lcdClear
        lcdLine1
                        ; Branch to the right code fragment for this menu.
                        ; start w/ highest numbered menu

        movlw .2        ; is it menu 2 ?
        subwf LCDPAGE,W
        btfss STATUS,Z
        goto lcddm_not2
        lcdmsg lcdmsgMenu2
        goto lcddm_end
lcddm_not2              ; not menu 2

        movlw .1        ; is it menu 1 ?
        subwf LCDPAGE,W
        btfss STATUS,Z
        goto lcddm_not1
        lcdmsg lcdmsgMenu1
        goto lcddm_end
lcddm_not1              ; not menu 1

        clrf LCDPAGE    ; Force it to 0 and display Main Menu
        lcdmsg lcdmsgMenu0
        goto lcddm_end

lcddm_end
        lcdLine1        ; leave cursor on line 1

        return          ; from lcdDisplayMenu
;-------------------------------------------------------
lcdRestoreLine
;       This routine moves the cursor to the line in LCDLINESAV,
;       where LCDLINE was saved before a menu pick
                        ; if LCDLINE was saved, restore it
        btfss LCDLINESAV,2
        goto lcdrl_notl4
        lcdLine4        ; it was on line 4. restore it
        goto lcdrl_res
lcdrl_notl4
        btfsc LCDLINESAV,0
        goto lcdrl_notl2
        lcdLine2        ; it was on line 2. restore it
        goto lcdrl_res
lcdrl_notl2
        btfss LCDLINESAV,1
        goto lcdrl_notl3
        lcdLine3        ; it was on line 3. restore it
        goto lcdrl_res
lcdrl_notl3
        btfss LCDLINESAV,0
        goto lcdrl_notl1
        lcdLine1        ; it was on line 1. restore it
        goto lcdrl_res
lcdrl_notl1

lcdrl_res
        return          ; from lcdRestoreLine
;-------------------------------------------------------
lcdBut1CB
;       This routine is called when button 1 is pressed

        btfss LCDPAGE,7
        goto lcdb1cbm
                        ; LCD is in data mode. Restore menu
        call lcdDisplayMenu
        call lcdRestoreLine
        return

lcdb1cbm                ; LCD is in menu mode. Perform PAGE function

                ; twix also want to be able to go to previous menu
        incf LCDPAGE,F          ; advance to next menu
        call lcdDisplayMenu

        return          ; from lcdBut1CB
;-------------------------------------------------------
lcdBut2CB
;       This routine is called when button 2 is pressed

        btfss LCDPAGE,7
        goto lcdb2cbm
                        ; LCD is in data mode. Restore menu
        call lcdDisplayMenu
        call lcdRestoreLine
        return

lcdb2cbm                ; LCD is in menu mode. Perform LINE function

        movlw .1        ; is it on line 1 ?
        subwf LCDLINE,W
        btfss STATUS,Z
        goto lcdb2cb_not1
        lcdLine2        ; advance to line 2
        goto lcdb2cb_end
lcdb2cb_not1            ; not on line 1
        movlw .2        ; is it on line 2 ?
        subwf LCDLINE,W
        btfss STATUS,Z
        goto lcdb2cb_not2
        lcdLine3        ; advance to line 3
        goto lcdb2cb_end
lcdb2cb_not2            ; not on line 2
        movlw .3        ; is it on line 3 ?
        subwf LCDLINE,W
        btfss STATUS,Z
        goto lcdb2cb_not3
        lcdLine4        ; advance to line 4
        goto lcdb2cb_end
lcdb2cb_not3            ; not on line 3
                        ; must be on line 4
        lcdLine1        ; wrap to line 1
        goto lcdb2cb_end

lcdb2cb_end
        return          ; from lcdBut2CB
;-------------------------------------------------------
lcdBut3CB
;       This routine is called when button 3 is pressed

        btfss LCDPAGE,7
        goto lcdb3cbm
                        ; LCD is in data mode. Restore menu
        call lcdDisplayMenu
        call lcdRestoreLine
        return



lcdb3cbm                ; LCD is in menu mode. Perform PICK function

        movfw LCDLINE   ; save line # so we can restore it
        movwf LCDLINESAV

                ; First, figure out which menu we're in
        movlw .2
        subwf LCDPAGE,W
        btfss STATUS,Z
        goto lcdb3cb_notm2
                                ; it's menu2
lcdb3cb_m2l4
        btfss LCDLINE,2
        goto lcdb3cb_m2notl4
#define REIDONLY
#ifdef REIDONLY
                                ; menu2 line4: Reset Map
        bcf PCLATH,3            ; paging
        call toiMyOwnAdders
        bsf PCLATH,3            ; paging
        lcdClear                ; show a completion message
        lcdLine4
        lcdmsg lcdmsgDone
        bsf LCDPAGE,7
#else
        lcdClear                ; menu1 line4: Dummy
        lcdLine4
        lcdmsg lcdDummym1l4
        bsf LCDPAGE,7
#endif
        goto lcdb3cb_end
lcdb3cb_m2notl4
        btfsc LCDLINE,0
        goto lcdb3cb_m2notl2
                                ; menu2 line2: Apply Threshold Adders
        bcf PCLATH,3            ; paging
        call toiAppThreshOv
        bsf PCLATH,3            ; paging
        lcdClear                ; show a completion message
        lcdLine2
        lcdmsg lcdmsgDone
        bsf LCDPAGE,7
        goto lcdb3cb_end
lcdb3cb_m2notl2
        btfsc LCDLINE,1
        goto lcdb3cb_m2notl1
                                ; menu2 line1: Threshold OI
        bcf PCLATH,3            ; paging
        call thresholdOI
        bsf PCLATH,3            ; paging

        call lcdDisplayMenu     ; non-standard return-to-menu
        bcf LCDPAGE,7           ;  "     "       "    "   "

        lcdLine2                ; on exit from OI, make 'apply adders'
        movfw LCDLINE           ; (on line 2) the current menu choice.
        movwf LCDLINESAV

        goto lcdb3cb_end
lcdb3cb_m2notl1
                                ; menu2 line3: Disable threshold adders
        bcf PCLATH,3            ; paging
        call toiDisThreshOv
        bsf PCLATH,3            ; paging
        lcdClear                ; show a completion message
        lcdLine3
        lcdmsg lcdmsgDone
        bsf LCDPAGE,7
        goto lcdb3cb_end

lcdb3cb_notm2
        movlw .1
        subwf LCDPAGE,W
        btfss STATUS,Z
        goto lcdb3cb_notm1
                                ; it's menu1
lcdb3cb_m1l4
        btfss LCDLINE,2
        goto lcdb3cb_m1notl4
                                ; menu1 line4: Reset Map
        bcf PCLATH,3            ; paging
        call toiResetMap
        bsf PCLATH,3            ; paging
        lcdClear                ; show a completion message
        lcdLine4
        lcdmsg lcdmsgDone
        bsf LCDPAGE,7
        goto lcdb3cb_end
lcdb3cb_m1notl4
        btfsc LCDLINE,0
        goto lcdb3cb_m1notl2
                                ; menu1 line1: Show MC_Comforts
        call lcdShowMCC
        bsf LCDPAGE,7
        goto lcdb3cb_end
lcdb3cb_m1notl2
        btfsc LCDLINE,1
        goto lcdb3cb_m1notl1
                                ; menu1 line1: Show MC_Thresholds
        call lcdShowMCT
        bsf LCDPAGE,7
        goto lcdb3cb_end
lcdb3cb_m1notl1
                                ; menu1 line3: Show Frame
        bcf PCLATH,3    ; paging
        call requestFrame
        bsf PCLATH,3    ; paging
       ;bsf LCDPAGE,7           ; no data is actually shown until master
        goto lcdb3cb_end        ; gets back to us with it.
lcdb3cb_notm1

                                ; it's menu0
lcdb3cb_m0l4
        btfss LCDLINE,2
        goto lcdb3cb_m0notl4
                                ; menu0 line4: Show POT values
        call lcdShowPots
        bsf LCDPAGE,7
        goto lcdb3cb_end
lcdb3cb_m0notl4
        btfsc LCDLINE,0
        goto lcdb3cb_m0notl2
                                ; menu0 line2: Show Comforts
        call lcdShowComfort
        bsf LCDPAGE,7
        goto lcdb3cb_end
lcdb3cb_m0notl2
        btfsc LCDLINE,1
        goto lcdb3cb_m0notl1
                                ; menu0 line1: Show Thresholds
        call lcdShowThresh
        bsf LCDPAGE,7
        goto lcdb3cb_end
lcdb3cb_m0notl1
                                ; menu0 line3: Show Ranges
        call lcdShowRange
        bsf LCDPAGE,7
        goto lcdb3cb_end

lcdb3cb_end
        return          ; from lcdBut3CB
;-------------------------------------------------------
p0lcdmsgAdders
;       this routine provides a way for a page 0 routine to put a message
;       on the lcd. I had problems because it tried to invoke a page 1
;       macro from page 0. This is a kluge.

        lcdmsg lcdmsgAdders

        return          ; from p0lcdmsgAdders
;-------------------------------------------------------
