
;---------------------------------------------------------------
; Program for displaying battery voltage, current, and amp-hours
; in an ebike application. LCD Module is attached to PORTC, with
; the following connections:
; RC5 - EN of LCD
; RC4 - RS of LCD
; RC3:0 - D7:4 of LCD
; RA0 - Battery Voltage, seen through a 20:1 resistor divider
; RA1 - Positive Battery Current, 0.1 Volt / Amp
; RA2 - Negative Battery Current, -0.111 Volt / Amp
; RA3 - Active Low Reset Button (clears Ah registers)
;
; Assumes an 8MHz crystal
;
; March 15th 2005               Justin Lemire-Elmore
;---------------------------------------------------------------

       list    p=16f684        ; list directive to define processor
       #include        <P16F684.inc>; processor specific variable definitions

       __CONFIG    _BOD_ON & _PWRTE_ON & _WDT_OFF & _HS_OSC & _MCLRE_OFF & _FCMEN_OFF & _IESO_OFF

       cblock 0x20
AcqDel  ; Delay for AD charge holding capacitor
NumH,NumL       ; 16 bit Number to convert to to BCD
TenK,Thou,Hund,Tens,Ones        ; BCD Result
LCDAdd  ; Curser Address of LCD Screen
LCDByte ; Full Byte to transmit to LCD Display
nibToSend       ; Nibble for 4-bit LCD Transmission
hiByte  ; Used by LCD Routine
loByte  ; Used by LCD Routine
d1, d2  ; Delays during LDC output
a2,a1,b2,b1     ; Multiply Arguments
res4,res3,res2,res1     ; 16x16 Multiply Result
Ah4,Ah3,Ah2,Ah1,Ah0     ; Binary format of accumulated amp-hours
Tmp2,Tmp1,Tmp0  ; Temporary registers
Flags   ; Flags for whatever, like positive or negative currents

       endc

#define ADVolts b'10000001'     ; ADCON0 for AN0, Batt Voltage, Vdd
#define ADPAmps b'10000101'     ; ADCON0 for AN1, Current Sense, Vdd
#define ADNAmps b'10001001'     ; ADCON0 for AN2, Negative Current Sense, Vdd
#define LCDEnable       PORTC,5         ; The LCD enable pin
#define LCDRS   PORTC,4         ; The LCD R/S pin
#define ResetPin        PORTA,3 ; Active Low Push-Button for resetting Ah data
#define NegAmpsFlag     Flags,0

       org     0

Init    clrf    PORTA   ; clear port I/O
       clrf    PORTC
       movlw   b'00000111'     ; Set RC<4,1:0> to
       movwf   CMCON0  ; digital I/O
       bsf     STATUS,RP0      ; Bank1
       clrf    TRISC   ; All Outputs for LCD module
       movlw   b'00000111'     ; Sets AN0-2 as analog inputs
       movwf   ANSEL
       movlw   b'00100000'     ; AD converter on Tosc/32
       movwf   ADCON1
       bcf     STATUS,RP0      ; Bank0
       movlw   b'00110001'     ; Timer1 on internal clk with 1:8 prescale
       movwf   T1CON
       call    LONGDLY
       call    LONGDLY ; Set Up the LCD Screen
       call    LCDFUN  ; sets the LCD's function to 4 bit, 2 line, 5x7 font
       call    LONGDLY
       call    LCDDISP         ; Turns on the display and cursor
       call    LONGDLY
       call    LCDENT  ; sets auto increment right after write (like a typewriter)
       call    LCDCLR
       call    LONGDLY
       call    LONGDLY
       movlw   "V"     ; Prepare Line1 for heading Data
       call    LCDCHAR
       movlw   "O"
       call    LCDCHAR
       movlw   "L"
       call    LCDCHAR
       movlw   "T"
       call    LCDCHAR
       movlw   "S"
       call    LCDCHAR
       movlw   d'6'
       movwf   LCDAdd
       call    LCDADD
       movlw   "A"
       call    LCDCHAR
       movlw   "M"
       call    LCDCHAR
       movlw   "P"
       call    LCDCHAR
       movlw   "S"
       call    LCDCHAR
       movlw   d'12'
       movwf   LCDAdd
       call    LCDADD
       movlw   "A"
       call    LCDCHAR
       movlw   "-"
       call    LCDCHAR
       movlw   "H"
       call    LCDCHAR
       movlw   "r"
       call    LCDCHAR

       bsf     STATUS,RP0      ; Restore the Ah data to the values
       clrf    EEADR   ; burnt in eeprom on last powerdown
       bsf     EECON1,RD       ; EE Read
       movf    EEDAT,w         ;
       bcf     STATUS,RP0
       movwf   Ah0     ;Restore Ah0
       bsf     STATUS,RP0
       incf    EEADR,f
       bsf     EECON1,RD       ;EE Read
       movf    EEDAT,w
       bcf     STATUS,RP0
       movwf   Ah1     ;Restore Ah1
       bsf     STATUS,RP0
       incf    EEADR,f
       bsf     EECON1,RD       ;EE Read
       movf    EEDAT,w
       bcf     STATUS,RP0
       movwf   Ah2     ;Restore Ah2
       bsf     STATUS,RP0
       incf    EEADR,f
       bsf     EECON1,RD       ;EE Read
       movf    EEDAT,w
       bcf     STATUS,RP0
       movwf   Ah3     ;Restore Ah3
       bsf     STATUS,RP0
       incf    EEADR,f
       bsf     EECON1,RD       ;EE Read
       movf    EEDAT,w
       bcf     STATUS,RP0
       movwf   Ah4     ;Restore Ah4

       goto    Main

;-----------------------------------------------------------
; Main program. Sends voltage, current, and temperature data
; twice a second. Uses TMR1 to set the 0.5 second time base.
; Each line goes "VAT <tab> HHH <tab> HHH <tab> HHH <CR> <LF>
; where each H is a hex value from the conversion
;-----------------------------------------------------------

Main    movlw   h'DD'
       movwf   TMR1L   ; 0.5 Sec = 62500 (500000/8) TMR1 clocks
       movlw   h'B'    ; 65536 -62500 = h'BDC'(+1 = BDD)
       movwf   TMR1H
       bcf     PIR1,TMR1IF     ; Clear the overflow flag
       call    LCDLINE2        ; Set LCD Curser to begining of line2

; -------- Get and Display battery voltage, power down if necessary -----------
       call    ReadVolts       ; Sample the battery pack voltage
       call    CheckPower      ; If volts <12, save Ah to eeprom before loosing pwr
       call    BinToBCD        ; Convert result to binary coded decimal
       movlw   d'48'   ; ASCII for "0"
       subwf   Hund,w  ; If Z set, we have leading zero to suppress
       movlw   " "     ; Space for suppression
       skpz
       movf    Hund,w  ; If not Z, then display thou's digit
       call    LCDCHAR ; Display 10's of voltage or space on LCD
       movf    Tens,w
       call    LCDCHAR ; Display Units of voltage on LCD
       movlw   "."     ;
       call    LCDCHAR ; Display Decimal on LCD
       movf    Ones,w
       call    LCDCHAR ; 1/10th's of voltage on LCD
       movlw   " "     ; Space on LCD Screen
       call    LCDCHAR

; -------- Get and Display battery current -----------
       call    ReadAmps        ; Sample the battery pack current
       call    BinToBCD        ; Convert scaled result to binary coded decimal
       movlw   "-"
       btfss   NegAmpsFlag     ; Place minus sign if amps is negative
       movlw   " "     ; blank space if positive
       call    LCDCHAR ; Display "-"  or " "
       movlw   d'48'   ; ASCII for "0"
       subwf   Thou,w  ; If Z set, we have leading zero to suppress
       movlw   " "     ; Space for suppression
       skpz
       movf    Thou,w
       call    LCDCHAR ; Display 10's of amps or space on LCD
       movf    Hund,w
       call    LCDCHAR ; Display Units of amperage on LCD
       movlw   "."     ;
       call    LCDCHAR ; Display Decimal on LCD
       movf    Tens,w
       call    LCDCHAR ; 1/10th's of amps on LCD
       movlw   " "     ; Space on LCD Screen
       call    LCDCHAR

; -------- Compute and Display Total Amp-Hours -----------

       call    CalcAh  ; CalcAh assumes NumH:NumL still contains 1/100th of amp
       call    BinToBCD        ; Convert into decimal format for display
       movlw   d'48'   ; ASCII for "0"
       subwf   Thou,w  ; If Z set, we have leading zero to suppress
       movlw   " "     ; Space for suppression
       skpz
       movf    Thou,w
       call    LCDCHAR ; Displays 10's of A
       movf    Hund,w
       call    LCDCHAR ; Display 1's of Ah
       movlw   "."
       call    LCDCHAR
       movf    Tens,w  ; Display Decimal Pt
       call    LCDCHAR ; Display 1/10th's of an Ah
       movf    Ones,w
       call    LCDCHAR ; Display 1/100th's of an Ah

;  -------- Check if Reset Button is being pressed -----------
       btfss   ResetPin
       call    ResetAh

WaitLoop        clrwdt
       btfss   PIR1,TMR1IF     ; Wait until 0.25s passed
       goto    WaitLoop
       goto    Main

;---------------------------------------------------------
; -Hex2Ascii- Converts the lower 4 bits of the working
; register into the 8 bit ASCII representation, 0..1,A..F
; Result is returned in the working register.
;---------------------------------------------------------

Hex2Ascii       andlw   b'00001111'     ; Mask lower 4 bits
       addwf   PCL,f   ; Jump to appropriate result
       retlw   d'48'   ; ASCII '0'
       retlw   d'49'   ; ASCII '1'
       retlw   d'50'   ;   ''
       retlw   d'51'   ;   ''
       retlw   d'52'   ;   ''
       retlw   d'53'   ;   ''
       retlw   d'54'   ;   ''
       retlw   d'55'   ;   ''
       retlw   d'56'   ;   ''
       retlw   d'57'   ; ASCII '9'
       retlw   d'65'   ; ASCII 'A'
       retlw   d'66'   ;   ''
       retlw   d'67'   ;   ''
       retlw   d'68'   ;   ''
       retlw   d'69'   ;   ''
       retlw   d'70'   ; ASCII 'F'

;---------------------------------------------------------
; -AcqDelay-  Gives a ~60uS delay to allow the sample and hold
; capacitor to fully charge before an AtoD conversion
;---------------------------------------------------------

AcqDelay        movlw   d'40'
       movwf   AcqDel
AcqLoop decfsz  AcqDel,f
       goto    AcqLoop
       return

;---------------------------------------------------------
; -ReadVolts-  Reads the to battery voltage and scales it
; assuming that there is a 1:20 voltage divider and a 0.6V
; diode drop. So full scale = 5V*(20+1)= 105V
;---------------------------------------------------------

ReadVolts       movlw   ADVolts
       movwf   ADCON0
       call    AcqDelay        ; Acquisition delay to charge internal capacitance
       bsf     ADCON0,GO       ; Start AtoD conversion of battery voltage
ADLoop1 btfsc   ADCON0,GO
       goto    ADLoop1
       movf    ADRESH,w        ;
       movwf   a2
       bsf     STATUS,RP0      ;
       movf    ADRESL,w        ; Copy ADResult into a2:a1
       bcf     STATUS,RP0      ; Bank0
       addlw   d'6'    ; Add diode drop, 6 ADC units ~ 0.6V
       movwf   a1
       skpnc
       incf    a2,f    ;

       movlw   0x01
       movwf   b2
       movlw   0x07    ; 0x0107 = 263
       movwf   b1
       call    Multiply        ; 263 * 1023 = 269049
       movf    res3,w  ;
       movwf   NumH    ; 269049 / 256 = 1050 or 105 V
       movf    res2,w
       movwf   NumL

       return

;---------------------------------------------------------
; -CheckPower-  Checks the battery voltage to see if the
; system power was removed. If V<15V, this is assumed the
; case. Power supply capacitor must be large enough that
; PIC stays powered up for at least 50 mS, so 50-100uF needed
; Assumes NumH:NumL contain voltage in 1/10th's of a volt.
; If power <15V, data is saved to eeprom and goodby msg
; displayed.
;---------------------------------------------------------
CheckPower      movf    NumH,f
       skpz
       return  ; If NumH>0, then volts > 25.6 and we're good
       movlw   d'150'  ; 15 amps
       subwf   NumL,w
       skpnc
       return  ; If NumL > 150 and NumH =0, we're also good

PowerDown       movf    Ah0,w
       bsf     STATUS,RP0      ; Bank1
       movwf   EEDAT
       clrf    EEADR
       bcf     STATUS,RP0      ; Bank0
       call    EEWrite ; Save Byte0 of Amp-Hours to EEPROM

       movf    Ah1,w
       bsf     STATUS,RP0      ; Bank1
       movwf   EEDAT
       incf    EEADR,f
       bcf     STATUS,RP0      ; Bank0
       call    EEWrite ; Save Byte1 of Amp-Hours to EEPROM

       movf    Ah2,w
       bsf     STATUS,RP0      ; Bank1
       movwf   EEDAT
       incf    EEADR,f
       bcf     STATUS,RP0      ; Bank0
       call    EEWrite ; Save Byte2 of Amp-Hours to EEPROM

       movf    Ah3,w
       bsf     STATUS,RP0      ; Bank1
       movwf   EEDAT
       incf    EEADR,f
       bcf     STATUS,RP0      ; Bank0
       call    EEWrite ; Save Byte3 of Amp-Hours to EEPROM

       movf    Ah4,w
       bsf     STATUS,RP0      ; Bank1
       movwf   EEDAT
       incf    EEADR,f
       bcf     STATUS,RP0      ; Bank0
       call    EEWrite ; Save Byte4 of Amp-Hours to EEPROM

       call    LCDLINE2        ; Set LCD Curser to begining of line2
       movlw   "P"
       call    LCDCHAR
       movlw   "O"
       call    LCDCHAR
       movlw   "W"
       call    LCDCHAR
       movlw   "E"
       call    LCDCHAR
       movlw   "R"
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   "D"
       call    LCDCHAR
       movlw   "O"
       call    LCDCHAR
       movlw   "W"
       call    LCDCHAR
       movlw   "N"
       call    LCDCHAR

KillTime        goto    KillTime        ; Wait in futile loop until Power Supply
                       ; drops to the point the pic can't run.

;---------------------------------------------------------
; -EEWrite-  Burns the data in EEDAT to address EEADR in
; the EEPROM.
;---------------------------------------------------------

EEWrite bsf     STATUS,RP0      ;Bank 1
       bsf     EECON1,WREN ;Enable write
       movlw   0x55    ;Unlock write
       movwf   EECON2  ;
       movlw   0xAA    ;
       movwf   EECON2  ;
       bsf     EECON1,WR       ;Start the write
EELOOP  btfsc   EECON1,WR
       goto    EELOOP  ; Poll until write is complete
       bcf     EECON1,WREN     ; Disable write
       bcf     STATUS,RP0      ; Bank0
       return          ; Burning of eeprom is done

;---------------------------------------------------------
; -ReadAmps-  Reads the to battery current and scales the
; result so that the values NumH:NumL are in 100th's of
; an amp.  Assumes 50 amps = full scale of ADC for positive
; and 45.45 amps = full scale for negative (gains of 10 and
; 11 respectively). Uses the larger of negative or positive
; amps.
;---------------------------------------------------------

ReadAmps        nop             ; Not sure if current will be positive or negative

ReadPosAmps     movlw   ADPAmps ; Check for positive current first
       movwf   ADCON0
       call    AcqDelay        ; Acquisition delay to charge internal capacitance
       bsf     ADCON0,GO       ; Start AtoD conversion of battery current
ADLoop2 btfsc   ADCON0,GO
       goto    ADLoop2
       movf    ADRESH,w        ;
       movwf   a2      ; Argument for Multiply Routine
       bsf     STATUS,RP0      ; Bank1
       movf    ADRESL,w        ; Copy the lower 8 bits of conversion
       bcf     STATUS,RP0      ; Bank0
       movwf   a1      ; Lower Arg of Multiply Routine

       movf    a2,f
       skpz            ; If high byte >0, amps is clearly positive
       goto    PosAmps

ReadNegAmps     movlw   ADNAmps ; Measure the negative current,
       movwf   ADCON0
       call    AcqDelay        ; Acquisition delay to charge internal capacitance
       bsf     ADCON0,GO       ; Start AtoD temperature conversion
ADLoop3 btfsc   ADCON0,GO
       goto    ADLoop3

       movf    ADRESH,w        ;
       skpz            ; If high byte >0, no need to compare low byte
       goto    NegCont
       bsf     STATUS,RP0      ; Bank1
       movf    ADRESL,w        ; Copy the lower 8 bits of conversion
       bcf     STATUS,RP0      ; Bank0
       subwf   a1,w    ; = lower byte of [PosAmps - Neg Amps]
       skpnc           ;
       goto    PosAmps

NegCont movf    ADRESH,w
       movwf   a2      ; ADRESH is already in 'w'
       bsf     STATUS,RP0      ; Bank1
       movf    ADRESL,w        ; Copy the lower 8 bits of conversion
       bcf     STATUS,RP0      ; Bank0
       movwf   a1      ; Move to lower arg of multiply routine

NegAmps movlw   0x04
       movwf   b2
       movlw   0xA7    ; 0x04a7 = 1191
       movwf   b1
       call    Multiply        ; 1191 * 1023 (full scale ADC) = 1218393
       movf    res3,w  ; Move only the middle two bytes, ignorning
       movwf   NumH    ; lower byte equivalent to divide by 256
       movf    res2,w
       movwf   NumL    ; 1218393/256 = 4759 which will display as -47.5 amps
       bsf     NegAmpsFlag     ; Indicate negative currents flag
       return

PosAmps movlw   0x04
       movwf   b2
       movlw   0xE4    ; 0x04E4 = 1252
       movwf   b1
       call    Multiply        ; 1252 * 1023 (full scale ADC) = 1280796
       movf    res3,w  ; Move only the middle two bytes, ignorning
       movwf   NumH    ; lower byte equivalent to divide by 256
       movf    res2,w
       movwf   NumL    ; 1280796/256 = 5003 which will display as 50.0 amps
       bcf     NegAmpsFlag     ; Clear flag to indicate + currents
       return

;---------------------------------------------------------
; -CalcAh- Adds the value in NumH:NumL directly to the amp-
; hour counter Ah4:Ah0. Assumes that NumH:L is in 1/100th
; of an amp, ie. 1 amp = 100, sampling is 4 times a second, so
; 1 Ah will accumulate to (3600*4*100) = 1440000 = 15F900
; Scales results and places them in NumH:NumL such that
; the value in NumH:NumL is in 1/100th's of an amp-hour
;---------------------------------------------------------

CalcAh  btfss   NegAmpsFlag
       goto    AddAmps
       goto    SubAmps

AddAmps movf    NumL,w  ; Add the results in Num to Ah
       addwf   Ah0,f   ; Ah0 = Ah0 +NumL
       movf    NumH,w
       btfsc   STATUS,C
       incfsz  NumH,w
       addwf   Ah1,f   ; Ah1 = Ah1 +NumH + C
       movlw   d'0'
       btfsc   STATUS,C
       movlw   d'1'
       addwf   Ah2,f
       movlw   d'0'
       btfsc   STATUS,C
       movlw   d'1'
       addwf   Ah3,f
       movlw   d'0'
       btfsc   STATUS,C
       movlw   d'1'
       addwf   Ah4,f
       goto    ScaleAh

SubAmps movf    NumL,w  ; Subtracts NumH:L from Ah4:0
       subwf   Ah0,f
       movf    NumH,w
       btfss   STATUS,C
       incfsz  NumH,w
       subwf   Ah1,f   ; Ah1:0 = Ah1:0 - NumH:L with valid carry
       movlw   d'0'
       btfss   STATUS,C
       movlw   d'1'
       subwf   Ah2,f
       movlw   d'0'
       btfss   STATUS,C
       movlw   d'1'
       subwf   Ah3,f
       movlw   d'0'
       btfss   STATUS,C
       movlw   d'1'
       subwf   Ah4,f   ; Ah4:0 = Ah4:0 - NumH:L with valid carry

       btfsc   STATUS,C
       goto    ScaleAh
       clrf    Ah4     ; If we're here, it means we're in
       clrf    Ah3     ; negative amp-hour territory. Just
       clrf    Ah2     ; reset it to 0 amp-hours in this generation
       clrf    Ah1     ; of code. Later should upgrade to display
       clrf    Ah0     ; negative amp-hours
       goto    ScaleAh

ScaleAh movf    Ah3,w   ; Need to roll Ah3:1 data
       movwf   Tmp0    ; for extracting optimum 16 bits
       movf    Ah2,w   ; of the accumulated amp-hours
       movwf   a2      ; to use in the multiply routine
       movf    Ah1,w
       movwf   a1

       movlw   d'4'    ; Roll Ah3:1 data 4 times for /16
       movwf   Tmp1    ; Tmp1 as loop counter
RollLoop        rrf     Tmp0,f  ; After this division, a1:a0 contains
       rrf     a2,f    ; the amp-hours such that 1Ah corresponds to
       rrf     a1,f    ; 1440000/256/16 = 351.5625
       decfsz  Tmp1,f
       goto    RollLoop

       movlw   0x48    ; 0x48D1 = d'18641'
       movwf   b2
       movlw   0xD1
       movwf   b1
       call    Multiply        ; 18641 * 351.5625 = 6553476 in Res3:Res0
       movf    res4,w
       movwf   NumH    ; Using upper 2 bytes like dividing by 2^16
       movf    res3,w  ; 6553476 / 2^16 = 100, which displays as 1.00 Ah
       movwf   NumL

       return

;---------------------------------------------------------
; -ResetAh-  Checks that the user is indeed holding the
; reset button. If so, Ah data in RAM is reset to zero
;---------------------------------------------------------

ResetAh movlw   d'15'
       movwf   Tmp2
ResetLoop       call    LONGDLY
       decfsz  Tmp2
       goto    ResetLoop
       btfsc   ResetPin        ; If user not holding button after
       return          ; this duration, don't reset
       clrf    Ah0
       clrf    Ah1
       clrf    Ah2
       clrf    Ah3
       clrf    Ah4

       call    LCDLINE2        ; Set LCD Curser to begining of line2
       movlw   " "     ; Then indicate data has been reset
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   "R"
       call    LCDCHAR
       movlw   "E"
       call    LCDCHAR
       movlw   "S"
       call    LCDCHAR
       movlw   "E"
       call    LCDCHAR
       movlw   "T"
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR
       movlw   " "
       call    LCDCHAR

ResetLoop2      btfss   ResetPin        ; Hold "RESET" on screen until button released
       goto    ResetLoop2

       return

;---------------------------------------------------------
; BinToBCD.  Code copied from sample by John Payson.
; http://www.dattalo.com/technical/software/pic/bcd.txt
; Enter with 16-bit binary number in NumH:NumL.
; Exits with BCD ASCII equivalent in TenK:Thou:Hund:Tens:Ones.
;---------------------------------------------------------

BinToBCD                        ; Takes number in NumH:NumL
                               ; Returns decimal in
                               ; TenK:Thou:Hund:Tens:Ones
       swapf   NumH,w
       andlw   0x0F             ;*** PERSONALLY, I'D REPLACE THESE 2
       addlw   0xF0             ;*** LINES WITH "IORLW 11110000B" -AW
       movwf   Thou
       addwf   Thou,f
       addlw   0xE2
       movwf   Hund
       addlw   0x32
       movwf   Ones

       movf    NumH,w
       andlw   0x0F
       addwf   Hund,f
       addwf   Hund,f
       addwf   Ones,f
       addlw   0xE9
       movwf   Tens
       addwf   Tens,f
       addwf   Tens,f

       swapf   NumL,w
       andlw   0x0F
       addwf   Tens,f
       addwf   Ones,f

       rlf     Tens,f
       rlf     Ones,f
       comf    Ones,f
       rlf     Ones,f

       movf    NumL,w
       andlw   0x0F
       addwf   Ones,f
       rlf     Thou,f

       movlw   0x07
       movwf   TenK

                       ; At this point, the original number is
                       ; equal to TenK*10000+Thou*1000+Hund*100+Tens*10+Ones
               ; if those entities are regarded as two's compliment
               ; binary.  To be precise, all of them are negative
               ; except TenK.  Now the number needs to be normal-
               ; ized, but this can all be done with simple byte
               ; arithmetic.

       movlw   0x0A    ; Ten
Lb1:
       addwf   Ones,f
       decf    Tens,f
       btfss   3,0
        goto   Lb1
Lb2:
       addwf   Tens,f
       decf    Hund,f
       btfss   3,0
        goto   Lb2
Lb3:
       addwf   Hund,f
       decf    Thou,f
       btfss   3,0
        goto   Lb3
Lb4:
       addwf   Thou,f
       decf    TenK,f
       btfss   3,0
        goto   Lb4
; --  Convert BCD Result into ASCII format -----
       movf    TenK,w
       call    Hex2Ascii
       movwf   TenK
       movf    Thou,w
       call    Hex2Ascii
       movwf   Thou
       movf    Hund,w
       call    Hex2Ascii
       movwf   Hund
       movf    Tens,w
       call    Hex2Ascii
       movwf   Tens
       movf    Ones,w
       call    Hex2Ascii
       movwf   Ones

       return

;**********************************************************************
; -Multiply- Computes the unsigned product of a2:a1 with b2:b1 leaving
; the result in res4:res3:res2:res1
; Pulled from PICLIST webiste
; http://www.piclist.com/techref/microchip/math/mul/16x16umalin.htm
;**********************************************************************

Multiply        clrf    res4
       clrf    res3
       clrf    res2
       movlw   0x80
       movwf   res1

nextbit
       rrf     a2,f
       rrf     a1,f

       btfss   STATUS,C
       goto    nobit_l
       movf    b1,w
       addwf   res2,f

       movf    b2, w
       btfsc   STATUS,C
       incfsz  b2, w
       addwf   res3, f
       btfsc   STATUS,C
       incf    res4, f
       bcf     STATUS,C

nobit_l
       btfss   a1, 7
       goto    nobit_h
       movf    b1,w
       addwf   res3,f
       movf    b2, w
       btfsc   STATUS,C
       incfsz  b2, w
       addwf   res4, f

nobit_h
       rrf     res4,f
       rrf     res3,f
       rrf     res2,f
       rrf     res1,f

       btfss   STATUS,C
       goto    nextbit
       return

;**********************************************************************
; LCD Routine, modified and optimized from code found on the NET
; Useful functions to call include LCDCHAR to output a character stored
; in LCDByte to the module
; LCDADD sets the curser to the position in LCDAdd
;**********************************************************************

LCDADD ; makes LCDAdd the current LCD Address
       bcf     LCDRS
       movf    LCDAdd,w
       movwf   LCDByte
       bsf     LCDByte, 7
       call    LCDBYTE
       bsf     LCDRS
       return

LCDENT ; Entry Mode set - currently set to move cursor to right after each write
       bcf     LCDRS
       movlw   b'00000110'
       movwf   LCDByte
       call    LCDBYTE
       bsf     LCDRS
       return

LCDDISP ; turns on display and cursor
       bcf     LCDRS
       movlw   b'00001110'
       movwf   LCDByte
       call    LCDBYTE
       bsf     LCDRS
       return

LCDFUN ; sets up the LCD function byte, for 4 bit control, 2 lines, standard font
       movlw   d'2'
       movwf   nibToSend
       bcf     LCDRS ; we're sending a command, so R/S must be lo
       call    SENDNIB ; due to 4 bit operation, we have to resend the first nibble
       call    SHORTDLY
       movlw   b'00101000'
       movwf   LCDByte
       call    LCDBYTE
       bsf     LCDRS
       return

LCDCLR ; clears the entire display
               clrf    LCDAdd
       movlw   d'1'
       movwf   LCDByte
       bcf     LCDRS ; 'cause we are doing a command, set the R/S line lo
       call    LCDBYTE ; writes LCDByte to the LCD
       call    LONGDLY ; Clearing the LCD takes ages, so a larger delay is needed
       bsf     LCDRS ; set the R/S line, ready for characters
       return

LCDCHAR
      ;Updates curser address it seems to do word wrapping.
       movwf   LCDByte
       movf    LCDAdd,w
       call    LCDBYTE

       incf    LCDAdd,f
; check if we have hit the end of the top line
       movlw   d'16'
       subwf   LCDAdd,w
       skpnz
       goto    LCDLINE2 ; must go to new line
       return

LCDLINE2        movlw   b'01000000'
       movwf   LCDAdd
       call    LCDADD
       return

LCDBYTE; sends a byte to the LCD, in 4bit fashion
       ; responsible for breaking up the byte to send into high and low nibbles
       ; and dumps them to LCD
       ;NOT responsible for the status of the LCDRS line, nor the major delays
       ; IN: LCDByte  - destructive OUT: hiByte, loByte

       movlw   b'00001111'
       andwf   LCDByte,w       ; Mask out upper for bits to generate
       movwf   loByte  ; lower nibble
       swapf   LCDByte,w
       andlw   b'00001111'     ; Same for high nibble
       movwf   hiByte

       movf    hiByte,w        ; Transmit both the Hi and Low Nibbles
       movwf   nibToSend
       call    SENDNIB
       movf    loByte,w
       movwf   nibToSend
       call    SENDNIB
       call    SHORTDLY ; blocks until most instructions are done
       return

SENDNIB; responsible for sending nibble commands to the LCD.
        ;IN: nibToSend - non-distructive

       movf    PORTC,w
       andlw   b'110000'       ; Preserve RS amd E lines
       iorwf   nibToSend,w     ;
       movwf   PORTC       ; Send data out to PORTC
       nop
       nop
       bsf     LCDEnable; pulse the LCD Enable high
       nop
       nop
       nop
       nop
       bcf     LCDEnable ; lower LCD Enable
       nop
       nop
       RETURN

SHORTDLY ; 217 cycles
       movlw   0x48
       movwf   d1
SHORTDLY_0
       decfsz  d1, f
       goto    SHORTDLY_0

                       ;4 cycles (including call)
       return

LONGDLY ; 92153 cycles
       movlw   0xFE
       movwf   d1
       movlw   0x48
       movwf   d2
LONGDLY_0
       decfsz  d1, f
       goto    $+2
       decfsz  d2, f
       goto    LONGDLY_0       ;3 cycles
       goto    $+1
       nop     ;4 cycles (including call)
       return

       end

