Batterimonitor

A PIC based battery monitor

Bild

This is the source code for the firmware.

;**********************************************************************
;                                                                     *
;    Filename:      Counter2.asm                                      *
;    Date:          2009-04-09                                        *
;    File Version:  0.4                                               *
;                                                                     *
;    Purpose:		Creates a battery-monitoring device for lead-     *   
;					acid batteries                                    *
;                                                                     *
;    Author:		Anders Gustafsson (c) 2008-2010                   *
;                   All Rights Reserved                               *
;    Copyright: 	You are free to use this code for your own        *
;					personal use. You may not use this work for       *
;					commercial purposes.                              *
;                                                                     *
;    Feedback:		I appreciate comments and suggestions. Please     *
;   				send all feedback to battmeter@dalton.ax          *
;                                                                     *
;   				As published in the July-August 2010 issue of     *
;                   Elektor:                                          *
;    http://www.elektor.com/magazines/2010/july-047-august/sailor-s-battery-meter.1392301.lynkx *
;**********************************************************************
;                                                                     *
;    History                                                          *
;    0.1                                                              *
;    0.2	Elektor version                                           *
;    0.3	Added support for "stop discharge" (RB5)                  *
;    0.4	Added Danish as language, plus defines to select          *
;           20A or 200A fullscale. Numerous bugfixes                  *
;           Defines for voltage to support 48V systems                *
;           Manual input of offset will now set "calibrated" flag     *
;           Limts the charge so that we do not display more than      *
;           the set capacity.                                         *
;           Resets the bq2018 as well when you do "set full"          *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Files required: P16F690.INC                                      *
;                                                                     *
;    Notes: Disable case sensitivity when compiling                   *
;                                                                     *
;    IMPORTANT!                                                       *
;    In the original design, RB5 was unused and thus configured       *
;    as input and grounded. The Elektor article and PCB layout        *
;    reflects this. The current code uses RB5 as an output to         *
;    trigger a relay that cuts off the load after a controlled        *
;    discharge cycle. Either disable this feature or cut the traces   *
;    that connects RB5 to ground.                                     *
;**********************************************************************
;                                                                     *
;    Pins:                                                            *
;    RA0 - Pulses in, HDQ from bq2018                                 *
;	 RA1 - Up key                                                     *
;	 RA2 - Wake up from bq2018   (not yet implemented)                *
;	 RA3 - Reset (MCLR)                                               *
;	 RA5 - Down key                                                   *
;    RB4 - Volts  (AN10)                                              *
;    RB5 - Signal out for load testing. Goes low at 10.5V             *
;	 RB6 - Left key                                                   *
;	 RB7 - NMEA out (TX)                                              *
;    RC0 - LCD E                                                      *
;    RC2 - LCD Backlight                                              *
;    RC1 - LCD RS                                                     *
;	 RC3 - Right key                                                  *
;    RC4 - LCD D4                                                     *
;      ..                                                             *
;    RC7 - LCD D7                                                     *
;	 RW connected to ground                                           *
;                                                                     *
;**********************************************************************
;                                                                     *
;   Notes:                                                            *
;   Uses intosc at 8 Mhz.                                             *
;                                                                     *
;	NMEA:                                                             *
;	http://www.kh-gps.de/nmea.faq,                                    *
;	http://www.piclist.com/tecHREF/microchip/16F/628/UARTtest.htm     *
;   http://www.newmarpower.com/pdf/Manual-DCV.pdf,                    *
;	http://www.cruzpro.com/rp30mani.pdf                               *
;	http://vancouver-webpages.com/peter/nmeafaq.txt                   *
;                                                                     *
;   For calibration. Hold down key up whilst powering up with no      *
;   current through shunt.                                            *
;   A full calibration cycle takes one hour. The routine shows a      *
;   decrementing counter during calibration and then the calculated   *
;   offset in hex                                                     *
;   Alternatively, short the shunt and leave the system running for   *
;   1 hour or more, then read CCR, DCR, CTC, DTC, offset is then      *
;   (CCR-DCR)/((CTC+DTC)/4096)                                        *
;
;	For capacity testing, charge the battery to full and set counter  *
;	to full, Then connect a load through a relay energised by RB5     *
;	When the voltage reaches 10.5, then the PIC will pull RB5 low,    *
;	thus releasing the load. Read the display and calculate the       *
; 	Battery capacity as the difference between the full value and the *
;	read value.                                                       *
;**********************************************************************

	radix	dec
    list      p=16F690           ; list directive to define processor
    #include         ; processor specific variable definitions

    errorlevel  -302              ; suppress message 302 from list file

;
; NOTE! _MCLRE_ON for production, _MCLRE_OFF for test in MPLAB
;
; '__CONFIG' directive is used to embed configuration word within .asm file.
; The lables following the directive are located in the respective .inc file.
; See data sheet for additional information on configuration word settings.

#ifdef __DEBUG
	__config (_INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF)
#else
	__config (_INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_ON & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF)
#endif
;
; Debugging defines
;
;	#define NO_KEYBOARD 1				; If we are running without a keyboard

;
; Language selection. Uncomment desired language
;
	;#define	ENG
	#define DEN
	;#define SWE

	#ifdef ENG
		#define DECIMAL_POINT '.'
	#endif
	#ifdef SWE
		#define DECIMAL_POINT ','
	#endif
	#ifdef DEN
		#define DECIMAL_POINT ','
	#endif

	;define CAPACITY_TEST			; If RB5 is to go low at 10.5V
	#define	SCALE_200AH	1			; Fullscale is 200Ah, 800 counts = 10Ah
	;#define	SCALE_20AH	1			; Fullscale is 20Ah, 800 counts = 1Ah
	;#define VOLTS12				; System is 12V
	#define VOLTS48					; System is 48V

	#ifdef VOLTS48
	#define VOLTSDIV	.7			; Divisor for volts. Should be 2 for 12V where fullscale is 20.48V
	#endif							; set to 7 for 48V systems where fullscale is 71,68V
	#define	RSENSE		0.01		; Sense resistor value in ohms
	#ifdef VOLTS12
	#define VOLTSDIV	.2
	#endif

	#define	GVFC		22.2		; VFC gain factor in Hz/V

	; The define below does not work so do the maths manually for now...
	;#define	AFACTOR		((4096 * 1000)/(GVFC * RSENSE * 3600))	; Multiply Charge/DeltaT by this to get mA
	#ifdef SCALE_20AH
	#define	AFACTOR .5125
	#define AMP_DIGITS 2			; Amps shown as 99.999
	#endif
	#ifdef SCALE_200AH
	#define	AFACTOR .51251
	#define AMP_DIGITS 3			; Amps shown as 999.99
	#endif
	;#define		NMEA					; Define this for "NMEA" output. In the end, this code would not fit in 4K
	;#define		IDEBUG					; ...additionally define this together with NMEA to get debug data output instead

	#define		POLLTIME	.30			; How often (in s) to poll bq2018 and update average amps. No less than 15s! (because this kicks off the hour counter also)
	#define		READTIME	.6			; How often (in hours) to read and zero bq2018

    #define     LCD_E       PORTC, 0	; How the LCD is connected
    #define     LCD_RS      PORTC, 1
    #define     LCD_PORT    PORTC
	#define		BACKLIGHT	PORTC, 2	; Port that controls the backlight

	#define		UPKEY		porta,1		; Keyboard ports
	#define		DNKEY		porta,5
	#define		LTKEY		portb,6
	#define		RTKEY		portc,3

	#define		UP			1			; Keyboard returncodes
	#define		DN			2
	#define		LT			4
	#define		RT			8

	#define		TICK		0.26214		; Unit of timing.

	#define		IDLEWAIT	0xff		; Time (number of ticks) that current has to be below IDLEAMPS before we evaluate charge
	#define		IDLEAMPS	0x63		; We assume idle is when current is below this (0x63=100mA)
	#define		DISPDELAY 	.4
	#define		MINCOUNT	(POLLTIME* .229)/60		; Timer1 kicks off every 0,26214s, multiply that by 229 and you get 60,03s
	#define		READCOUNT	(READTIME *.60 * .60 )/ POLLTIME			; Incremented by the routine that runs every polltime seconds, , 123456
	;#define		READCOUNT	.4 ; for testing, so that you do not have to wait 6 hours...
	#define		CALIBHOUR	0x35ff		; Suitable time to wait for calibration to be done

	;
	; Register defines for the bq2018
	;
	#define		BQ_OFFSET	0x73
	#define		BQ_TEMP		0x74
	#define		BQ_MODE		0x75
	#define		BQ_CTC		0x76		; Charge time low, high is 0x77
	#define		BQ_DTC		0x78		; Discharge time low, high is 0x79
	#define		BQ_SDC		0x7a		; Self-discharge low, high is 0x7b
	#define		BQ_CCR		0x7c		; Charge count low, high is 0x7d
	#define		BQ_DCR		0x7e		; Discharge count low, high is 0x7f
	#define		BQ_WRITE	0x80		; Add 0x80 to register to indicate write

;***** VARIABLE DEFINITIONS
;
group1		UDATA	0x20	; Note! Some work could be done here to conserve variables. Currently do some routines
							; blagged from various sources use their own temp variables, so they could be conbined into fewer.

cnt			res		1		; Used by bcd2a and b2bcd
pto			res		1
pti			res		1
temp		res		1
temp2		res		1

char_hi		res		1		; Used by prthex
char_lo		res		1

sign		res		1		; Sign bit for Ah display (+/-)
bcd			res		10		; Holds output ascii string from b2bcd
prtflag		res		1		; Flag is set by timer so that we get an display update

voltshi		res		1		; Measured volts from ADC
voltslo		res		1
idlevoltshi	res		1		; Measured volts when idle, ie when current has been very low for a certain time. See below
idlevoltslo	res		1

idlecount	res		1		; Counter used to track idle volts
amps0		res		1		; Calculated amps from bq2018
amps1		res		1
amps2		res		1
amps3		res		1

templo		res		1		; Measured temp from bq2018, resolution is 10 degrees C

dspmode		res		1		; Display mode, ie what we show on the display currently
mincounter	res		1		; Counter used to kick off a routine every minute
readcounter_lo	res		1	; 16 bit counter used to kick off a routine that reads and zeros the bq2018 every 6 hours
readcounter_hi	res		1	; 
;
; Registers used by 32-bit math
;
REGA0		res		1		;lsb
REGA1		res		1
REGA2		res 	1
REGA3		res 	1		;msb

REGB0		res 	1		;lsb
REGB1		res 	1
REGB2		res 	1
REGB3		res 	1		;msb

REGC0		res 	1		;lsb
REGC1		res 	1
REGC2		res 	1
REGC3		res 	1		;msb

REGD0		res 	1		;lsb
REGD1		res 	1
REGD2		res 	1
REGD3		res 	1		;msb

MTEMP		res 	1
MCOUNT		res		1

charge		res		4		; 32-bit charge counter. This holds the accumulated battery charge

; These registers are read from the bq2018
;
dcr_lo		res		1		; Discharge count register, 12.5uVh increments
dcr_hi		res		1
ccr_lo		res		1		; Charge count register, 12.5uVh increments
ccr_hi		res		1
dtc_lo		res		1		; Discharge time counter, 1 count/0.8789s
dtc_hi		res		1
ctc_lo		res		1		; Charge time counter, 1 count/0.8789s
ctc_hi		res		1
;
offset		res		1		; Calibrated offset error is based on one hour's worth of error. Subtract this from total 1/h
;
; These registers hold the previous read values so that we can calculate average current
;
dcr0_lo		res		1		; Discharge count register, 12.5uVh increments
dcr0_hi		res		1
ccr0_lo		res		1		; Charge count register, 12.5uVh increments
ccr0_hi		res		1
dtc0_lo		res		1		; Discharge time counter, 1 count/0.8789s
dtc0_hi		res		1
ctc0_lo		res		1		; Charge time counter, 1 count/0.8789s
ctc0_hi		res		1
rd_err		res		1		; Read error count
;
; example of using Shared Uninitialized Data Section
;
INT_VAR     UDATA_SHR    
w_temp      RES     1       ; variable used for context saving 
status_temp RES     1       ; variable used for context saving
pclath_temp RES     1       ; variable used for context saving
fsr_temp	res		1
strlen		res		1		; Length of string to be printed by printstr

;
LCD_VARS	UDATA_SHR		; Variables used by LCD routines
LCD_TMP1	RES 	1
LCD_TMP2	RES 	1
;
; HDQ comms variables - begin
;
timeout 	res		1	 	;timeout flag, 0=no timeout
hserdat 	res		1		;high serial data register
hserbit 	res		1		;high serial bit counter
wstack 		res		1		;temp store for w register
;
hcmd 		res		1		;host command
;
btris 		res		1		;mirror trisb (stores value put in trisa, used to wiggle hdq-bit)
;
; HDQ comms variables - end

;**********************************************************************************************************
; MACROS
;**********************************************************************************************************
; Macro that sends a text to the LCD. First two bytes are row and column
;
lcd_text    macro   text_label
        banksel eeadr               ; change to right bank...
        movlw   high text_label     ; address bits 8-15
        movwf   eeadrh
        movlw   low text_label      ; address bits 0-7
        movwf   eeadr
;
; Address registers set up, fcall the routine
;
        fcall    lcd_send_text
        banksel porta               ; Leave routine in bank 0.
;
        endm
;
; Macro that sends a text to the LCD. No positioning
;
lcd_text2    macro   text_label
        banksel eeadr               ; change to right bank...
        movlw   high text_label     ; address bits 8-15
        movwf   eeadrh
        movlw   low text_label      ; address bits 0-7
        movwf   eeadr
;
; Address registers set up, fcall the routine
;
        fcall    lcd_send_text2
        banksel porta               ; Leave routine in bank 0.
;
        endm
;**********************************************************************************************************
; Helper macro for addressing EE
;
ee_address    macro   text_label
        banksel eeadr               ; Change to correct bank...
        movlw   high text_label     ; label address bits 8-15 
        movwf   eeadrh
        movlw   low text_label      ; label address 0-7 
        movwf   eeadr
		banksel	0					; Return in bank 0
;
; EEadr and EEadrh now hold the address of our label

        endm	
;**********************************************************************************************************
; Helper macro for 32-bit numbers. Puts byte number 'byte' of a 32-bit literal 'var'
; into register 'reg'
;
movbyte	macro   reg,var,byte
		movlw   (var >>(D'08' * byte) & H'FF')
		movwf	reg
        endm

;**********************************************************************************************************
; the fcall macro - do a far call and set PCLATH correctly upon return
; by Roger Froud of Amytech Ltd.
fcall	macro subroutine_name
	local here
	lcall subroutine_name
	pagesel here
here:
	endm
;**********************************************************************************************************
RESET_VECTOR    CODE    0x0000       ; processor reset vector
	goto    start                ; go to beginning of program

INT_VECTOR      CODE    0x0004       ; interrupt vector location
;**********************************************************************************************************
; ISR
;**********************************************************************************************************
INTERRUPT
; Keep the actual ISR as short as possible.
;
; Arrive here on any interrupt - first save the world...
; 
	movwf   w_temp 			; save off current W register contents
	swapf	STATUS,w		; move status register into W register
	movwf   status_temp		; save off contents of STATUS register
	movf    PCLATH,w		; move pclath register into W register
	movwf   pclath_temp		; save off contents of PCLATH register
	movf    FSR,w          	; move fsr register into W register
	movwf   fsr_temp       	; save off contents of PCLATH register

; Actual isr code, determine cause of interrupt. Most likely first

;
; This is the handler for other interrupts, ie timer1
;
other_int
	banksel	PIR1
	btfss   PIR1,TMR1IF     ; If Timer 1 overflowed and caused the interrupt, handle it.
	goto	other_int1		; No timer overflow

	bsf		prtflag,1		; Yes, set flag to indicate to main loop that we need an update

other_int1

	banksel	PIR1
	BCF 	PIR1,TMR1IF 	; Clear flag and continue.
;
; Catch-all for interrupt exit. Restore the world as we know it.
;
exit_interrupt
	movf    fsr_temp,w		; retrieve copy of FSR register
	movwf   fsr				; restore pre-isr FSR register contents
	movf    pclath_temp,w	; retrieve copy of PCLATH register
	movwf   PCLATH			; restore pre-isr PCLATH register contents
	swapf   status_temp,w	; retrieve copy of STATUS register
	movwf   STATUS			; restore pre-isr STATUS register contents
	swapf   w_temp,f
	swapf   w_temp,w 		; restore pre-isr W register contents
	retfie 					; return from interrupt
;**********************************************************************************************************
; END ISR
;**********************************************************************************************************
MAIN_PROG       CODE

start
;
; Main program start. Init all ports, timers etc
;
;
	banksel osccon
	movf	osccon,w
	iorlw	b'01110000'			; Set clock to 8MHz
	movwf	osccon
;
	banksel ansel               ; Turn off all analogue
	clrf    ansel
	clrf    anselh
	bsf		anselh,ANS10		; ANS10 is volts

	CLRW 						; SET UP PORT
	banksel	porta
	MOVWF 	PORTA

	bsf		portb,5				; Set capacity test out high

	movlw	b'00100011'			; 0,1,5 are inputs
	banksel	trisa

	movwf	trisa				; Set trisa and mirror the data to btris, the register
	movwf	btris				; that is used to wiggle the HDQ pin

	banksel	hserbit
	MOVLW 	08H 				; LOAD BIT COUNTER
	MOVWF 	HSERBIT 			; WITH 8 FOR 8 BIT

	banksel	trisb
	clrf    trisb
	;bsf		trisb,5				; B5/AN11
	bsf		trisb,4				; B4/AN10
; ----------------------------------------------------------------------------------------------------------
; NMEA - init USART to 4800 baud
;
#ifdef NMEA
	fcall	nmea_init

;	fcall	nmea_head
;	fcall	nmea_test
;	fcall 	nmea_checksum
#endif
; ------------------------------------ ----------------------------------------------------------------------
	banksel	trisc
	clrf    trisc
	;bsf		trisc,2
	bsf		trisc,3				; RC3 - Right key
;
	banksel intcon
	clrf    intcon

	banksel	option_reg
	bcf		option_reg,7		; Weak pullups are enabled on individual pins

	banksel	wpua
	clrf	wpua				; Disable all weak pullups
	bsf		wpua,wpua0			; Enable weak pull-up on A0
	bsf		wpua,wpua1			; Enable weak pull-up on A1
	bsf		wpua,wpua5			; Enable weak pull-up on A5

	banksel	wpub
	clrf	wpub				; Disable all weak pullups
	bsf		wpub,wpub6			; Enable weak pull-up on B6
;
; Init A/D converter
;
	banksel	adcon1
	movlw	b'01110000'			; FRC Clock
	movwf	adcon1
	banksel	adcon0
	movlw	b'10101001'			; Right justify, use Vdd as Vref, read AN10
	movwf	adcon0
	fcall	delay_5ms

; OK. Basic stuff done, proceed to init the LCD
;

	fcall	lcd_init_hd44780    ; Init the LCD
	bcf		BACKLIGHT			; Turn off backlight
;
; Print greeting text on the LCD
;
	lcd_text    lbl_text1
	lcd_text    lbl_text2
	lcd_text    lbl_text3

	fcall    delay_1s			; Wait 2 s before proceeding
	fcall    delay_1s

	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd

	movlw	'A'
	fcall	lcd_send_data
	
	banksel	prtflag
	bcf		prtflag,1			; Clear the flag that triggers display update. This is later set by TMR1
	banksel	idlecount
	movlw	IDLEWAIT			
	movwf	idlecount
	movlw	MINCOUNT	
	banksel	mincounter		
	movwf	mincounter
;
	movbyte	readcounter_lo,READCOUNT,0	
	movbyte	readcounter_hi,READCOUNT,1

	clrf	dcr0_lo				; Clear out the saved values
	clrf	dcr0_hi	
	clrf	ccr0_lo	
	clrf	ccr0_hi	
	clrf	dtc0_lo	
	clrf	dtc0_hi	
	clrf	ctc0_lo	
	clrf	ctc0_hi

								; Debug data
	clrf	dcr_lo				; Clear out values
	clrf	dcr_hi	
	clrf	ccr_lo	
	clrf	ccr_hi	
	clrf	dtc_lo	
	clrf	dtc_hi	
	clrf	ctc_lo	
	clrf	ctc_hi

	clrf	rd_err
;
; Debug/test data
;
;	movlw	0x47					
;	movwf	ctc_lo
;	movlw	0x24					
;	movwf	ctc0_lo
;	movlw	0x19
;	movwf	ccr_lo
;	movlw	0x1
;	movwf	ccr_hi				; 8000/60/2 = 100mV in 30 s
;	movlw	0x8e
;	movwf	ccr0_lo
	;movlw	.33
	;movwf	dcr_lo				; 8000/60/2 = 50mV in 30 s
	;movlw	b'01100000'					; Temp 10-20
	;movwf	templo
	;movlw	0xfe					; Offset
	;movwf	offset
	;movlw	.6					; displaymode6
	;movwf	dspmode
	
; ***********************************************************************************
; START OF CODE to initialize Timer 1

; Set up Timer 1 to generate interrupts approx every 262.14ms. (8MHz/4/8/65536)
; This is used as a system timekeeper to generate polls every 30s and reads every 6 hours
;
	banksel	tmr1l
	CLRF    TMR1L            	; Clear Timer1 register
	CLRF	TMR1H

	banksel	intcon
	bsf     INTCON,PEIE     	; Enable peripheral interrupts
	banksel	pie1
	CLRF    PIE1            	; Mask all peripheral interrupts except
	bsf     PIE1,TMR1IE     	; the timer 1 interrupts.

	banksel	pir1
	CLRF    PIR1            	; Clear peripheral interrupts Flags
	movlw   B'00110000'     	; Set  Prescale = 8

	movwf   T1CON
	BSF     T1CON,TMR1ON    	; Timer1 starts to increment
;***********************************************************************************
;END OF CODE to initialize Timer 1
;***********************************************************************************

    bsf		INTCON, GIE          ; enable interrupts

	fcall	reset_counter

	banksel	dspmode
	clrf	dspmode				; Initial display mode is zero

	CLRWDT
	MOVLW 	06H					; 00000110 = Prescaler assigned to timer 0, rate = 1/128
	OPTION

;	goto	ourloop
;	lgoto	test_charge			; Test remove 12345
;
; OK. Check if we want calibration. Ie UPKEY pressed when powering up
;

	movlw	'B'
	fcall	lcd_send_data

	banksel porta
	btfsc	UPKEY				; Up key pressed?
	goto	nocalibrate
	lgoto	calibrate			; Yes, run calibration 
								; no, continue

nocalibrate
;	lgoto	do_cmd4
;

	movlw	'C'
	fcall	lcd_send_data

;
; Reset 2018 on startup, normally. Reset is done through the menu
;
	fcall	send_break

	movlw	'D'
	fcall	lcd_send_data

	fcall	reset_bq2018

;
;;	MOVLW 	BQ_OFFSET			; Offset register read
;;	MOVWF 	HSERDAT
;;	fcall	SNDA_IT
;;	fcall	hs_serva			; Read data
;;	movf	hserdat,w
;;	movwf	offset
;
;	MOVLW 	BQ_TEMP				; Temperature register read
;	MOVWF 	HSERDAT
;	fcall 	SNDA_IT
;	fcall	hs_serva			; Read data
;	movf	hserdat,w
;	movwf	templo

; Check if calibrated and if we are, then read offset from eeprom
;
	clrf	offset				; Zero offset

	ee_address	flags
	fcall	read_flash_ee		; read flags from eeprom into w
	movwf	rega0
	btfss	rega0,0				; Check calibrated bit
	goto	ourloop				; Not calibrated, leave offset at zero

	ee_address	stored_offset	; Valid calibration, read offset from eeprom
	fcall	read_flash_ee		; 
	movwf	offset				; and put in offset register

	movlw	'E'
	fcall	lcd_send_data

;**********************************************************************************************************
; MAIN LOOP
;**********************************************************************************************************
;
; All init done, enter loop that never ends
;
ourloop
;--------------- Test loop -----------------
;loop2
;	bcf		portc,0				; Wiggle scope trigger
;	bsf		portc,0				; 
;
;	fcall	send_break
;	banksel	hserbit
;	MOVLW 	08H 			;LOAD BIT COUNTER
;	MOVWF 	HSERBIT 		;WITH 8 FOR 8 BIT
;	MOVLW 	74H				; read TMP/CLR register
;	MOVWF 	HSERDAT
;	CALL 	SNDA_IT
;	fcall	hs_serva
;	goto loop2

;	fcall	send_break
;
;	movlw	dcr_lo				; Set up address to receive data
;	movwf	fsr					; in fsr
;	movlw	BQ_DCR				; Set up address of discharge low register
;	fcall	read_16				; fcall routine to read it
;
;	movlw	ccr_lo				; Set up address to receive data
;	movwf	fsr					; in fsr
;	movlw	BQ_CCR				; Set up address of charge low register
;	fcall	read_16				; fcall routine to read it
;
;	movlw	dtc_lo				; Set up address to receive data
;	movwf	fsr					; in fsr
;	movlw	BQ_DTC				; Set up address of discharge time low register
;	fcall	read_16				; fcall routine to read it
;
;	movlw	ctc_lo				; Set up address to receive data
;	movwf	fsr					; in fsr
;	movlw	BQ_CTC				; Set up address of discharge low register
;	fcall	read_16				; fcall routine to read it
;goto loop2
;--------------- Test loop -----------------
;
; Check our "keyboard". We have four buttons:  Up, Down, left, right
; left and right steps through the top level displays.
;
	;lgoto	maintenance
	;fcall	poll
check_key

	;movlw	'F'
	;fcall	lcd_send_data

	fcall	getkey				; Check for key
	btfss	status,C
	goto	check_key1			; No key, skip to loop check
	movwf	regd0				; We have a key, save it

	btfss	regd0,2				; LT ie 4?
	goto	check_key_not_left

	decf	dspmode,1			; Decrement displaymode
	btfss	dspmode,7			; Rolled over?
	goto	check_key1	 		; no
	movlw	.7					; yes, put back to 7
	movwf	dspmode
	goto	check_key1

check_key_not_left
	btfss	regd0,3				; RT ie 8?
	goto	check_key_not_right
	incf	dspmode,1			; Increment the display mode (0..7)
	btfss	dspmode,3			; reached end?
	goto	check_key_not_right	; no
	clrf	dspmode				; yes, back to zero
;	goto	check_key1

check_key_not_right

check_key1
	;movlw	'G'
	;fcall	lcd_send_data
	banksel	prtflag
	btfss	prtflag,1			; Do we need to print/display? Flag is set by Timer1 interrupt.
	goto    ourloop            	; no, loop forever
								; Yes, print
	bcf		prtflag,1			; and clear print flag

	;movlw	'H'
	;fcall	lcd_send_data
;
; Things that happen on every TICK
;
; Check events that should happen every minute and every hour
;
check_minute
	banksel	mincounter
	decfsz	mincounter,f		; Has one minute elapsed?
	goto	check_minute1		; no, not yet
	movlw	MINCOUNT			; yes, prime counter		
	movwf	mincounter			; and jump to routine

	;movlw	'I'
	;fcall	lcd_send_data

	; fcall goes here!
	fcall	poll
poll_rtn

	;movlw	'J'
	;fcall	lcd_send_data
								; Has six hours elapsed?
	movf	readcounter_lo,f	; 16-bit decrement
	skpnz
	decf	readcounter_hi,f
	decf	readcounter_lo,f

	btfss	readcounter_hi,7	; Wrapped around?
	goto	check_hour1			; no, not yet

	;movlw	'K'
	;fcall	lcd_send_data
								; yes, prime counter
	movbyte	readcounter_lo,READCOUNT,0	
	movbyte	readcounter_hi,READCOUNT,1		
								; and jump to routine
	; 
	lgoto	read
check_minute1
check_hour1

;
; Data aquisition stuff
;
;
; Read AD10 to get voltage
;
read_voltage				; Store old voltage to calculate difference
	banksel	voltslo			; Old voltage -> REGB
	movf	voltslo,w
	movwf	regb0
	movf	voltshi,w
	movwf	regb1
	clrf	regb2
	clrf	regb3

	banksel	adcon0			; OK. Read it
	movlw	b'10101001'		; Right justify, Vdd Vref, an10
	movwf	adcon0
	fcall	delay_5us
	fcall	delay_5us

	bsf		adcon0,go
	btfsc	adcon0,go
	goto	$-1
	banksel	adresh			; Note that adresh/adresl are in different banks!
	movf	adresh,w
	banksel voltshi
	movwf	voltshi
	movwf	rega1
	banksel	adresl
	movf	adresl,w
	banksel voltslo
	movwf	voltslo
	movwf	rega0			; New voltage -> REGA
	clrf	rega2
	clrf	rega3

	fcall	subtract		; Calculate change in voltage REGA-REGB -> REGA
	fcall	absa			; absolute value of change is in REGA

	movf	rega1,1			; If msb is above zero then skip
	btfss	STATUS,z
	goto	not_samevolts

	; Snippet:	if (A > CONST)
	; Inputs:	rega0
	movf	rega0, W		; Check value of lsb
	sublw	5				; W = 5-A. 5 is 0.1V
	btfsc	STATUS, C		; Check for borrow..
	goto	v_false			; C=1, A<=CONST, false cond'n

	; Place result TRUE code here.

	goto	not_samevolts

v_false:					; Delta volts is below what we consider idle
	movf	idlecount,1		; Idle count zero?
	btfsc	STATUS,z
	goto	xyzzy
	decf	idlecount,1		; Decrement idle counter.
	goto	xyzzy


not_samevolts				; Voltage has changed
	movlw	IDLEWAIT		; We are not idle, prime idle counter so that we start counting down again
	movwf	idlecount

xyzzy
	bcf		status,c		; Clear carry
	banksel voltslo

	fcall	delay_5us		; Give ADC time to breathe

#ifdef CAPACITY_TEST
;
; Check for end voltage (10.5) in a capacity test, 10.5V = 525
;
; Snippet:	16-bit if(A <= B), A = Volts, B = 10.5 (525, 0x20D)
; Inputs:	A_high, A_low, B_high, B_low
; Strategy:	Do a 16-bit B-A and watch for borrow off 16th bit.
;
; Note that 10.5 is hardcoded, but it is OK. It is a constant like pi, e or c.

	movf	voltslo, W
	sublw	0x0d			; W = B-A (low)

	movlw	0x00			; Clear W
	btfss	STATUS, C		; Check for carry on low bytes.
	movlw	0x01			; C=0, A_low < B_low, needed to borrow (sub 1 from high)
							; C=1, B_low > A_low, do not sub 1 from high byte

	addwf	voltshi, W		; Adding 1 to B is like subtracting 1 from A (if W=1)
	btfsc	STATUS, C		; If A_high == FF, can borrow and not realize
	goto	false			; false cond'n
	sublw	0x02			; W = B-A (high) - borrow if applicable

	btfss	STATUS, C		; Check whole 16-bit addition for borrow 
	goto	false			; C=0, result needs borrow from 17th bit.
							; C=1, result true

	; Place result TRUE code here.
	bcf		portb,5			; Voltage at or below 10.5, pull pin down to disconnect load
	goto 	end_if
	
false:

	; Place result FALSE code here.

end_if:								; end of snippet
#endif
;
;
; Calculate average current, Note that the error will be substantial for small
; currents!
; First calculate delta t

;
	movf	ctc_lo,W		; Get ctc into REGA
	movwf	rega0
	movf	ctc_hi,W
	movwf	rega1
	clrf	rega2
	clrf	rega3

	movf	ctc0_lo,w		; Get ctc0 into REGB
	movwf	regb0
	movf	ctc0_hi,w
	movwf	regb1
	clrf	regb2		
	clrf	regb3

	fcall	subtract		; Subtract REGA-REGB-> REGA

	movf	dtc_lo,w		; Get dtc into REGB
	movwf	regb0
	movf	dtc_hi,w
	movwf	regb1
	clrf	regb2		
	clrf	regb3

	fcall	add				; Add REGA+REGB-> REGA

	movf	dtc0_lo,w		; Get dtc0 into REGB
	movwf	regb0
	movf	dtc0_hi,w
	movwf	regb1
	clrf	regb2		
	clrf	regb3

	fcall	subtract		; Delta t now in REGA, save it in regd

	movf	rega0,w	
	movwf	regd0
	movf	rega1,w
	movwf	regd1
	movf	rega2,w
	movwf	regd2
	movf	rega3,w
	movwf	regd3
;
; Then calculate the change in charge
;
	movf	ccr_lo,W		; Get ccr into REGA
	movwf	rega0
	movf	ccr_hi,W
	movwf	rega1
	clrf	rega2
	clrf	rega3

	movf	ccr0_lo,w		; Get ccr0 into REGB
	movwf	regb0
	movf	ccr0_hi,w
	movwf	regb1
	clrf	regb2		
	clrf	regb3

	fcall	subtract		; Subtract REGA-REGB-> REGA

	movf	dcr_lo,w		; Get dcr into REGB
	movwf	regb0
	movf	dcr_hi,w
	movwf	regb1
	clrf	regb2		
	clrf	regb3

	fcall	subtract		; Subtract REGA-REGB-> REGA

	movf	dcr0_lo,w		; Get dcr0 into REGB
	movwf	regb0
	movf	dcr0_hi,w
	movwf	regb1
	clrf	regb2		
	clrf	regb3

	fcall	add				; Change in charge now in rega

	;
	; Multiply to get real amps goes here, multiply to give milliamps
	; Actual factor is above
	;

	movbyte	regb0,AFACTOR,0	
	movbyte	regb1,AFACTOR,1
	movbyte	regb2,AFACTOR,2
	movbyte	regb3,AFACTOR,3

	fcall	multiply

	movf	regd0,w			; Get delta t into regb
	movwf	regb0
	movf	regd1,w
	movwf	regb1
	movf	regd2,w
	movwf	regb2
	movf	regd3,w
	movwf	regb3

	fcall	divide			; rega / regb -> rega

	movf	rega0,w			; Store calculated amps
	movwf	amps0
	movf	rega1,w
	movwf	amps1
	movf	rega2,w
	movwf	amps2
	movf	rega3,w
	movwf	amps3

; On a lead-acid battery, the idle voltage corresponds roughly to charge (especially if you adjust for temperature)
; the trick is that you need to measure when the battery has been idle for a while. Ideally hours, but
; for now we assume that if voltage has been within 0.1V for 60s and current is small, then take a sample.
;
;
; Are we idle? Idle is defined as a current less than 100ma, ie amps1..3 = 0, amps0 = 0x63 or below
;

	banksel	amps0

	movf	amps0,w			; Get amps into rega
	movwf	rega0
	movf	amps1,w
	movwf	rega1
	movf	amps2,w
	movwf	rega2
	movf	amps3,w
	movwf	rega3

	fcall	absa			; Make absolute

	movf	rega3,1			; Amps hi zero?
	btfss	status,z
	goto	notidle			; Zero flag not set, current not idle

	movf	rega2,1			; Amps hi zero?
	btfss	status,z
	goto	notidle			; Zero flag not set, current not idle

	movf	rega1,1			; Amps hi zero?
	btfss	status,z
	goto	notidle			; Zero flag not set, current not idle

	movlw	IDLEAMPS		; Our definition of idle (0x63 = 100mA)
	subwf	rega0,0			; subtract and store in w
	btfsc	status,c		; if w<=ampslo, then carry will be set
	goto	notidle
;
; Current draw is less than what we define as "idle", check idle counter, if zero, then store idle volts
;
	banksel idlecount
	movf	idlecount,1		; Idle count zero?
	btfss	STATUS,z
	goto	notidle
	
	banksel voltslo
	movf	voltslo,W		; Low current and voltage has been the same for xx loops, grab lower part of voltage
	movwf	idlevoltslo		; and store as idle volts value
	movf	voltshi,W		; Same for high part
	movwf	idlevoltshi		; 

notidle

done_amps

;
; Read bq2018 to get temperature. It is held in bits7..5 in register 74 (tmp/clr)
;
; Temp 	Value (hex) SDR Count Rate
;	<0° 	0h 		× 1/8
;	0–10° 	1h 		× 1/4
;	10–20° 	2h 		× 1/2
;	20–30° 	3h 		1 count/hr.
;	30–40° 	4h 		× 2
;	40–50° 	5h 		× 4
;	50–60° 	6h 		× 8
;	>60° 	7h 		× 16
;
read_temperature
;
;DISPLAY_STUFF CODE
;-----------------------------------------------------------------------------------------------------
; Done aquiring data, display it according to selected display mode:
; 0 - 999.999Ah 99%, 9,99V +99,99A
; 1 - 999.999999Ah, 9,99V +99,99A
; 2 - 99,99C
; 3 -

								; Page-safe jumptable from AN556. Checks for carry when adding offset and
								; adjusts PCLATH accordingly
	banksel	dspmode
	movf	dspmode,w			; Get display mode in W
	andlw	.7					; Make sure it is within bounds (0..7)
	movwf	rega0				; save in temp register

	movlw	LOW dsptable		; Get low address of table
	addwf	rega0,F				; add offset
	movlw	HIGH dsptable		; Get high 5 bits of address
	btfsc	status,c			; Did we cross a page?
	addlw	1					; Yes, increment high address
	movwf	pclath				; Load high address in latch
	movf	rega0,w
	
	movwf	pcl					; Load computed offset into program counter
dsptable
	goto	displaymode0 		; Jump table to display mode handlers
	goto	displaymode1
	goto	displaymode2
	goto	displaymode3
	goto	displaymode4
	goto	displaymode5
	goto	displaymode6
	goto	displaymode7
;----------------------------------------------------------------------------------------------------------------------------
; Display mode 0
;
; 999.999Ah 99%
; 9,99V +99,99A 

displaymode0
	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd

	banksel	charge

	movf	charge,W			; Get charge into REGA
	movwf	rega0
	movf	charge+1,W
	movwf	rega1
	movf	charge+2,W
	movwf	rega2
	movf	charge+3,W
	movwf	rega3

	movf	ccr_lo,w			; Get charge count into REGB
	movwf	regb0
	movf	ccr_hi,w
	movwf	regb1
	clrf	regb2				; ccr is a 16-bit value so zero the upper 16 bita
	clrf	regb3

	fcall	add					; Add up REGA+REGB-> REGA

	movf	dcr_lo,w			; Get discharge count into REGB
	movwf	regb0
	movf	dcr_hi,w
	movwf	regb1
	clrf	regb2				; dcr is a 16-bit value so zero the upper 16 bita
	clrf	regb3

	fcall	subtract			; REGA - REGB -> REGA

								; 100mV/1h = 8000 counts. 100mV / 0.01 ohm is 10A
								; 100mV/1h = 8000 counts. 100mV / 0.001 ohm is 100A

#ifdef SCALE_20AH
								; For 20Ah max scale
	movlw	.8					; adjust: 8000 = 10Ah, so divide by 8, then multiply by 10 when presenting
	movwf	regb0
	clrf	regb1
	clrf	regb2
	clrf	regb3

	fcall	divide
#endif

#ifdef SCALE_200AH					; For 200Ah max scale
	movf	rega0,w				; Save value REGA -> REGD
	movwf	regd0
	movf	rega1,w
	movwf	regd1
	movf	rega2,w
	movwf	regd2
	movf	rega3,w
	movwf	regd3
;
	movlw	.4					; Divide by 4 to get one quarter
	movwf	regb0				; REGA = REGA /4
	clrf	regb1
	clrf	regb2
	clrf	regb3

	fcall	divide

	movf	regd0,w				; Grab value
	movwf	regb0
	movf	regd1,w
	movwf	regb1
	movf	regd2,w
	movwf	regb2
	movf	regd3,w
	movwf	regb3

	fcall	add					; REGA = REGA + REGB (REGA = REGA * 1.25)
#endif

	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii


	movlw   b'00000010'     	; Display/curs home
	fcall   lcd_send_cmd

	movf	sign,W
	fcall	lcd_send_data

	movlw	.3					;3d bytes of ascii, ie 3 digits "999"
	movwf	strlen
	movlw	bcd+5				;based at this location
	fcall	printstr
;
	movlw	DECIMAL_POINT		;','
	fcall	lcd_send_data
;
	movlw	.2					;2d bytes of ascii, ie 2 digits ".00Ah"
	movwf	strlen
	movlw	bcd+8				;based at this location
	fcall	printstr
;
	movlw	'A'
	fcall	lcd_send_data
	movlw	'h'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	fcall    delay_5ms

;
; determine approx battery left from idle voltage and display
;
	fcall	find_charge
								; Find_charge returns charge in w as two nibbles, print them.
	banksel	temp
	movwf	temp

	andlw	0xf0				;Mask lsb to leave msb
	movwf	temp2
	bcf		status,c			; Clear carry
	rrf		temp2,1				; Rotate to get msb
	rrf		temp2,1
	rrf		temp2,1
	rrf		temp2,0
	addlw	0x30				; '0'
	fcall	lcd_send_data
	movf	temp,w
	andlw	0x0f				;Mask msb to leave lsb
	addlw	0x30				; '0'
	fcall	lcd_send_data
	movlw	'%'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data	
;
; Convert & print voltage
;
display_voltage
	banksel		voltslo

	movf	voltslo,W			; Get volts

	movwf	rega0
	movf	voltshi,W
	movwf	rega1

	clrf	rega2
	clrf	rega3

	movlw	VOLTSDIV
	movwf	regb0				; 1024 is 20.48V so multiply by two (Or you could rotate, but we have the multiply routine anyway..)
	clrf	regb1
	clrf	regb2				
	clrf	regb3

	fcall	multiply

	fcall	b2bcd				; convert to 32-bit binary to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall   lcd_send_cmd
;
; Convert and print "1500" as "15.0V"
;
	movlw	.2					;2d bytes of ascii
	movwf	strlen
	movlw	bcd+6				;based at this location
	fcall	printstr
;
	movlw	DECIMAL_POINT		;','
	fcall	lcd_send_data
;
	movlw	.2					;2d bytes of ascii
	movwf	strlen
	movlw	bcd+8				;based at this location
	fcall	printstr
;
;
	movlw	'V'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
;
; Convert & print current
;
display_amps
	banksel	amps0

	movf	amps0,W				; Get amps
	movwf	rega0
	movf	amps1,W
	movwf	rega1
	movf	amps2,W
	movwf	rega2
	movf	amps3,W
	movwf	rega3

	fcall	b2bcd				; convert to 32-bit binary to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

;
; Convert and print "2048" as "+20.48A"
;

	movf	sign,w
	fcall	lcd_send_data
								; Buffer data is 0123456789 -> 0123456.789A
	movlw	AMP_DIGITS			;2d bytes of ascii for 20A, 3d bytes of ASCII for 200A (.2)
	movwf	strlen
	movlw	bcd+(6-AMP_DIGITS+1)	;based at this location (5 for xx.xxx, 4 for xxx.xx)
	fcall	printstr
;
	movlw	DECIMAL_POINT		;	','
	fcall	lcd_send_data
;
	movlw	5-AMP_DIGITS		;3d bytes of ascii for 20A, 2d bytes for 200A (.3)
	movwf	strlen
	movlw	bcd+7				;based at this location
	fcall	printstr
;
	movlw	'A'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
;;
;; Debug stuff to show display mode
;;
;	banksel	dspmode
;	movf	dspmode,W
;	addlw	'0'
;	fcall	lcd_send_data

	lgoto 	ourloop

;----------------------------------------------------------------------------------------------------------------------------
; Display mode 1 - Show temperature and the temperature/clear register
;
; 99C (xx)
; 
displaymode1
display_temperature
	banksel	templo

	movf	templo,W			; Get temperature/clear into w. MS 3 bits is temp
	andlw	b'11100000'			; Mask
	movwf	rega0
	bcf		status,c			; Clear carry
	swapf	rega0,1
	rrf		rega0,1				; move bits 7..5 to 3..0

	clrf	rega1
	clrf	rega2
	clrf	rega3

	movlw	.10					; Multiply by 10. Thus we display the temperature as the midpoint in every interval, ie
	movwf	regb0				; the interval 20-30 is shown as 25C

	clrf	regb1
	clrf	regb2
	clrf	regb3

	fcall	multiply

	movlw	.5					; Subtract 5
	movwf	regb0

	clrf	regb1
	clrf	regb2
	clrf	regb3

	fcall	subtract

	fcall	b2bcd				; convert to 32-bit binary to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd
	movlw   b'00000010'     	; Display/curs home
	fcall    lcd_send_cmd
;
; Convert and print "15" as "15"
;
	movlw	.2					;2d bytes of ascii
	movwf	strlen
	movlw	bcd+8				;based at this location
	fcall	printstr
;
;
	movlw	0xdf
	fcall	lcd_send_data
	movlw	'C'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	movlw	'('
	fcall	lcd_send_data
	movf	templo,w
	fcall	prthex				; Display data
	movlw	')'
	fcall	lcd_send_data
	lgoto 	ourloop


;----------------------------------------------------------------------------------------------------------------------------
; Display mode 2 - Show the counters in hex
;
; Ct XXXX Cc XXXX
; Dt XXXX Dc XXXX
displaymode2
	movlw   b'00000010'     	; Display/curs home
	fcall    lcd_send_cmd
	movlw	'C'
	fcall	lcd_send_data
	movlw	't'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	;
	movf	ctc_hi,w
	fcall	prthex				; Display data
	movf	ctc_lo,w
	fcall	prthex				; Display data

	movlw	' '
	fcall	lcd_send_data
	movlw	'C'
	fcall	lcd_send_data
	movlw	'c'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data


	movf	ccr_hi,w
	fcall	prthex				; Display data
	movf	ccr_lo,w
	fcall	prthex

	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall    lcd_send_cmd

	movlw	'D'
	fcall	lcd_send_data
	movlw	't'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	;
	movf	dtc_hi,w
	fcall	prthex				; Display data
	movf	dtc_lo,w
	fcall	prthex				; Display data


	movlw	' '
	fcall	lcd_send_data
	movlw	'D'
	fcall	lcd_send_data
	movlw	'c'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data


	movf	dcr_hi,w
	fcall	prthex				; Display data
	movf	dcr_lo,w
	fcall	prthex

	lgoto	ourloop
;----------------------------------------------------------------------------------------------------------------------------
; Display mode 3
;
; Amps (hex)
; Amps (ascii) 
displaymode3
	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd
	lcd_text lbl_amps

	banksel	amps0

	movf	amps3,W				; Print amps in hex
	fcall	prthex
	movf	amps2,W
	fcall	prthex
	movf	amps1,W
	fcall	prthex
	movf	amps0,W
	fcall	prthex

	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall    lcd_send_cmd

	movf	sign,w
	fcall	lcd_send_data

	movf	amps0,W				; Print amps in decimal
	movwf	rega0
	movf	amps1,W
	movwf	rega1
	movf	amps2,W
	movwf	rega2
	movf	amps3,W
	movwf	rega3

	fcall	b2bcd				; convert to 32-bit binary to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

	
	movlw	.10					;10d bytes of ascii
	movwf	strlen
	movlw	bcd					;based at this location
	fcall	printstr
	lgoto	ourloop

displaymode4
;----------------------------------------------------------------------------------------------------------------------------
; Display mode 4 - Raw (non-adjusted) charge, plus mincounter and readcounter
;
; 9999999999mAh
; XX XXXX


	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd

	banksel	charge

	movf	charge,W			; Get charge into REGA
	movwf	rega0
	movf	charge+1,W
	movwf	rega1
	movf	charge+2,W
	movwf	rega2
	movf	charge+3,w
	movwf	rega3

	movf	ccr_lo,w			; Get charge count into REGB
	movwf	regb0
	movf	ccr_hi,w
	movwf	regb1
	clrf	regb2				; ccr is a 16-bit value so zero the upper 16 bita
	clrf	regb3

	fcall	add					; Add up REGA+REGB-> REGA

	movf	dcr_lo,w			; Get discharge count into REGB
	movwf	regb0
	movf	dcr_hi,w
	movwf	regb1
	clrf	regb2				; dcr is a 16-bit value so zero the upper 16 bita
	clrf	regb3

	fcall	subtract			; REGA - REGB -> REGA

	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii


	movlw   b'00000010'     	; Display/curs home
	fcall    lcd_send_cmd

	movf	sign,W
	fcall	lcd_send_data

	movlw	.10					;10d bytes of ascii, ie 10 digits "999Ah"
	movwf	strlen
	movlw	bcd					;based at this location
	fcall	printstr
;
	movlw	'A'
	fcall	lcd_send_data
	movlw	'h'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	fcall    delay_5ms

	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall    lcd_send_cmd
	movf	mincounter,W
	fcall	prthex
	movlw	' '
	fcall	lcd_send_data
	movf	readcounter_hi,W
	fcall	prthex
	movf	readcounter_lo,W
	fcall	prthex

	lgoto ourloop
displaymode5
;----------------------------------------------------------------------------------------------------------------------------
; Display mode 5 - Offset and serial error counter, idle volts, selfdischarge counter
;
; Of XX Er XX
; 99,99V SD XXXX


	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd
	movlw	'O'
	fcall	lcd_send_data
	movlw	'f'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	movf	offset,W
	fcall	prthex

	movlw	' '
	fcall	lcd_send_data
	movlw	'E'
	fcall	lcd_send_data
	movlw	'r'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	movf	rd_err,W
	fcall	prthex

	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall    lcd_send_cmd

	movf	idlevoltslo,W		; Get idlevolts
	movwf	rega0
	movf	idlevoltshi,W
	movwf	rega1
	clrf	rega2
	clrf	rega3

	fcall	b2bcd				; convert to 32-bit binary to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

;
; Convert and print saved idle volts
;
	movlw	.2					;2d bytes of ascii
	movwf	strlen
	movlw	bcd+6				;based at this location
	fcall	printstr
;
	movlw	DECIMAL_POINT		;','
	fcall	lcd_send_data
;
	movlw	.2					;2d bytes of ascii
	movwf	strlen
	movlw	bcd+8				;based at this location
	fcall	printstr
;
;
	movlw	'V'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data
	movlw	'S'
	fcall	lcd_send_data
	movlw	'D'
	fcall	lcd_send_data
	movlw	'C'
	fcall	lcd_send_data
	movlw	' '
	fcall	lcd_send_data


	fcall	send_break
	movlw	regd0				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_SDC				; Set up address of discharge time low register
	fcall	read_16				; fcall routine to read it

	movf	regd1,W
	fcall	prthex
	movf	regd0,W
	fcall	prthex
	lgoto	ourloop
;
; Rest of modes, just show mode for now
;
displaymode6
	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd
	lcd_text lbl_dspmode
	banksel	dspmode
	movf	dspmode,W
	addlw	'0'
	fcall	lcd_send_data
	lgoto	ourloop

displaymode7
	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd
	lcd_text lbl_maint			; "Maintenance"
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall	lcd_send_cmd
	lcd_text2 lbl_maint3a		; "Enter.."

kbloop
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall    lcd_send_cmd
	fcall	getkey
	btfss	status,c			; Carry set if key was pressed
	goto	kbloop				; No key..
	movwf	rega0
	;fcall	prthex

	btfss	rega0,0				; UP?
	goto	kbloop_not_up
	banksel	dspmode
	clrf	dspmode
	goto	ourloop

kbloop_not_up
	btfss	rega0,1				; DN?
	goto	kbloop
	;fcall	delay_1s
	;goto	ourloop				; Nope
	lgoto	maintenance
	;goto 	ourloop

BQ2018_STUFF CODE
;************************************************ SUBROUTINES *******************************************************
;
; Poll - Subroutine that gets fcalled approximately twice a minute (OK every 30,015s)
; Read counters from bq2018
;
;
; http://www.datasheetcatalog.org/datasheet/texasinstruments/bq26200.pdf
; "For self-discharge calculation, the self-discharge count register (SCR) counts at a rate of 1 count every hour
; at a nominal 25°C. The SCR count rate doubles approximately every 10°C up to 60°C. The SCR count rate is
; halved every 10°C below 25°C down to 0°C. The value in SCR is useful in estimating the battery self-discharge
; based on capacity and storage temperature conditions

poll
	movf	dcr_lo,w			; Save the old values
	movwf	dcr0_lo
	movf	dcr_hi,w
	movwf	dcr0_hi
	movf	ccr_lo,w
	movwf	ccr0_lo
	movf	ccr_hi,w
	movwf	ccr0_hi
	movf	dtc_lo,w
	movwf	dtc0_lo
	movf	dtc_hi,w
	movwf	dtc0_hi
	movf	ctc_lo,w
	movwf	ctc0_lo
	movf	ctc_hi,w
	movwf	ctc0_hi

								; Read new values here
    bcf		INTCON, GIE			; Disable interrupts
	fcall	send_break
	movlw	dcr_lo				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_DCR				; Set up address of discharge low register
	fcall	read_16				; fcall routine to read it

	fcall	Delay_5us
	movlw	ccr_lo				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_CCR				; Set up address of charge low register
	fcall	read_16				; fcall routine to read it

	fcall	Delay_5us
	fcall	send_break
	movlw	dtc_lo				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_DTC				; Set up address of discharge time low register
	fcall	read_16				; fcall routine to read it
;
	fcall	Delay_5us
	movlw	ctc_lo				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_CTC				; Set up address of discharge low register
	fcall	read_16				; fcall routine to read it

	MOVLW 	BQ_TEMP				; Temperature register read
	MOVWF 	HSERDAT
	fcall 	SNDA_IT
	fcall	hs_serva			; Read data
	movf	hserdat,w
	movwf	templo

    bsf		INTCON, GIE			; enable interrupts
	;
	bcf		BACKLIGHT			; Turn off backlight
;-----------------------------------------------------------------------------------------------------
; Dump out NMEA data over the serial port
;
#ifdef NMEA
	fcall	nmea_data
#endif

	return

;-----------------------------------------------------------------------------------------------------
; Read counters from bq2018 and zero them. Add up to total charge
;
; Read values here, then zero counters
;
read
	fcall	send_break
	movlw	dcr_lo				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_DCR				; Set up address of discharge low register
	fcall	read_16				; fcall routine to read it

	movlw	ccr_lo				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_CCR				; Set up address of charge low register
	fcall	read_16				; fcall routine to read it

	movlw	dtc_lo				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_DTC				; Set up address of discharge time low register
	fcall	read_16				; fcall routine to read it

	movlw	ctc_lo				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_CTC				; Set up address of discharge low register
	fcall	read_16				; fcall routine to read it
;
; Values read
;
	movlw 	BQ_TEMP+BQ_WRITE	; tmp/clr register
	movwf 	hserdat
	fcall 	snda_it
	movlw 	b'00011011'			; reset all registers, except SCR
	movwf 	hserdat
	fcall 	snda_it

; OK. Charge and discharge in dcr and ccr. Start calculating

								; Calculate running time since last reset

	movf	ctc_lo,w			; Get charge time into REGA
	movwf	rega0
	movf	ctc_hi,w
	movwf	rega1
	clrf	rega2				; ctc is a 16-bit value so zero the upper 16 bita
	clrf	rega3

	movf	dtc_lo,w			; Get discharge time into REGB
	movwf	regb0
	movf	dtc_hi,w
	movwf	regb1
	clrf	regb2				; dtc is a 16-bit value so zero the upper 16 bita
	clrf	regb3

	fcall	add					; CTC + DTC -> REGA. We should store this as we will need it for SDC calculation later

								; Now that we have the running time, calculate hours by dividing by 4096, then
								; multiply offset by hours. The actual calculation is done the other way around though
								; Ie the offset is multiplied by the time, then the result is divided by 4096. This
								; yields better accuracy and worstcase 6 * 128 * 65535 * 2 will not overflow a 32-bit integer

								; Offset is a 8-bit value, if positive say 0x01 it needs to be extended to 0x00000001, if negative
								; say 0xff (or -1), then it needs to be extended into 0xffffffff

	movf	offset,w			; Get offset (positive or negative) into REGB
	movwf	regb0
	btfsc	offset,7			; Is offset negative
	goto	offset_negative		; Yes
	clrf	regb1				; No, positive offset, zero out upper 24 bits
	clrf	regb2
	clrf	regb3
	goto	offset_calc
offset_negative					; Negative offset, make all upper 24 bits ones
	movlw	0xff
	movwf	regb1
	movwf	regb2
	movwf	regb3
offset_calc
	
	fcall	multiply			; Running time times 4096 * offset -> REGA

	clrf	regb0				; Put 4096 (1000h) in REGB
	clrf	regb2
	clrf	regb3
	movlw	0x10
	movwf	regb1

	fcall	divide				; REGA / 4096 -> REGA. REGA now holds time-adjusted offset

	fcall	movab				; REGA -> REGB

	movf	charge,W			; Get charge into REGA
	movwf	rega0
	movf	charge+1,W
	movwf	rega1
	movf	charge+2,W
	movwf	rega2
	movf	charge+3,W
	movwf	rega3

	fcall	subtract			; Charge - Offset -> REGA

	movf	rega0,w				; Save it
	movwf	charge
	movf	rega1,w
	movwf	charge+1
	movf	rega2,w
	movwf	charge+2
	movf	rega3,w
	movwf	charge+3			; We have now adjusted for offset

								; Add the new charge/discharge. Start by adjusting charge for efficiency

	banksel	ccr_lo

	movf	ccr_lo,w			; Get charge count into REGA
	movwf	rega0
	movf	ccr_hi,w
	movwf	rega1
	clrf	rega2				; ccr is a 16-bit value so zero the upper 16 bita
	clrf	rega3

	ee_address	efficiency		; Get efficiency into REGB
	movlw	.1					;1d bytes
	movwf	strlen
	movlw	regb0
	fcall	copybytes

	clrf	regb1				; ccr is a 16-bit value so zero the upper 16 bits
	clrf	regb2
	clrf	regb3

	fcall	multiply			; REGA * REGB-> REGA

	movf	rega1,w				; Shift everyting one byte, thus dividing by 256
	movwf	rega0
	movf	rega2,w
	movwf	rega1
	movf	rega3,w
	movwf	rega2
	clrf	rega3				; REGA now holds adjusted charge

	;fcall	add					; Add Adjusted charge to total delta charge

	movf	dcr_lo,w			; Get discharge count into REGB
	movwf	regb0
	movf	dcr_hi,w
	movwf	regb1
	clrf	regb2				; dcr is a 16-bit value so zero the upper 16 bits
	clrf	regb3

	fcall	subtract			; Subtract discharge from total delta, result in REGA

	movf	charge,W			; Get total charge into REGB
	movwf	regb0
	movf	charge+1,W
	movwf	regb1
	movf	charge+2,W
	movwf	regb2
	movf	charge+3,W
	movwf	regb3

	fcall	add					; Total charge + delta charge -> REGA

	movf	rega0,w
	movwf	charge
	movf	rega1,w
	movwf	charge+1
	movf	rega2,w
	movwf	charge+2
	movf	rega3,w
	movwf	charge+3			; New total charge stored

								; Self discharge check goes here. Read the SCR

								; Assuming 100Ah and 5%, self-discharge @20C is 5Ah. If 1Ah = 800 (assuming a 0,01ohm shunt)
								; then self-discharge is 4000/month or 132/day. So we wait until SCR is above 24 and when it is
								; we multiply by SCR, subtract from the total charge and zero SCR.

	banksel	rega0
	ee_address	capacity		; Read capacity from eeprom
	movlw	.4					; 4d bytes
	movwf	strlen
	movlw	rega0
	fcall	copybytes			; Copy capacity to REGBA

	ee_address	discharge		; Read discharge from eeprom
	fcall	read_flash_ee
	movwf	regb0				; Discharge is one byte, 100 = 100%
	clrf	regb1
	clrf	regb2
	clrf	regb3

	call	multiply			; capacity * discharge -> REGA

	movbyte	regb0,.12132,0		; Divide by 100 and factor to convert self-discharge, expressed in %/month to
	movbyte	regb1,.12132,1		; value per six hours, ie our read interval 30,33 * 4, ie 100 * 30,33 * 4 = 12132
	movbyte	regb2,.12132,2
	movbyte	regb3,.12132,3

	call	divide				; Self-discharge value for six hours now in REGA

	fcall	send_break
	movlw	regb0				; Set up address to receive data
	movwf	fsr					; in fsr
	movlw	BQ_SDC				; Set up address of discharge time low register
	fcall	read_16				; fcall routine to read it into REGB

	call	multiply			; Total self-discharge now in REGA
	fcall	movab				; REGA -> REGB

test_charge
	movf	charge,W			; Get total charge into REGA
	movwf	rega0
	movf	charge+1,W
	movwf	rega1
	movf	charge+2,W
	movwf	rega2
	movf	charge+3,W
	movwf	rega3

	fcall	subtract			; Total charge - delta selfdischarge -> REGA

	movf	rega0,w				; Save it.
	movwf	charge
	movf	rega1,w
	movwf	charge+1
	movf	rega2,w
	movwf	charge+2
	movf	rega3,w
	movwf	charge+3
								; Todo: Check that we have not exceeded the maximum possible charge goes here! 12345

	banksel	regb0
	ee_address	capacity		; Read capacity from eeprom into REGB
	movlw	.4					; 4d bytes
	movwf	strlen
	movlw	regb0
	fcall	copybytes			; Copy capacity to REGB

	fcall	subtract			; charge - capacity -> REGA

	rlf		REGA3,w				; Negative, ie charge is less than capacity?
	skpnc
	goto	read_battery_not_full	

	fcall	reset_counter		; Oops.. charge is > capacity, reset charge
	fcall	reset_bq2018

read_battery_not_full
	movlw 	BQ_TEMP+BQ_WRITE	; tmp/clr register
	movwf 	hserdat
	fcall 	snda_it
	movlw 	b'00000100'			; reset SCR
	movwf 	hserdat
	fcall 	snda_it

	lgoto	ourloop
STRING_STUFF CODE
;*******************************************************************************************************************************
;
; Subroutine that prints strlen characters starting at w
;
printstr
		movwf	fsr
next1	movf	indf,w			;get a byte and print it
		fcall	lcd_send_data
		incf	fsr				;point to next
		decfsz	strlen			;decrement string length
		goto	next1			;Not done yet?
								;Done! Continue here
		return
;
; Subroutine that copies strlen bytes from eeprom to registers starting at w
;
copybytes
		movwf	fsr
copybytes1
		fcall	read_flash_ee	;Read byte from EE-Prom
		fcall	inc_eeadr		;and increment pointer
		movwf	indf			;put byte in register
		incf	fsr				;point to next
		decfsz	strlen			;decrement string length
		goto	copybytes1		;Not done yet?
								;Done! Continue here
		banksel	mcount
		return

;
; Subroutine that writes strlen bytes from register starting at w to eeprom
;
writebytes
		movwf	fsr
writebytes1
		movf	indf,w			;Get byte to write
		fcall	write_flash_ee	;write to EE-Prom
		fcall	inc_eeadr		;and increment pointer
		incf	fsr				;point to next
		decfsz	strlen			;decrement string length
		goto	writebytes1		;Not done yet?
								;Done! Continue here
		banksel	0				;Leave routine in bank 0
		return

; Subroutine that resets counter to battery capacity.
;
reset_counter
	ee_address	capacity
	movlw	.4					;4d bytes
	movwf	strlen
	movlw	charge
	banksel	charge
	call	copybytes
	return
;*******************************************************************************************************
; Subroutine that converts a voltage value in lovolts to an approximate charge in %
; returns the charge as two nibbles in w, ie 95% = 0x95
; Note that we only check lovolts, as the span between empty and full is 0.9V for a 12V Lead-Acid battery.
; Make sure that you scale your input so that this span is within one byte.
find_charge
	ee_address	chargetable		;Get address of our table into the ee address registers
find_next
	fcall	read_flash			;get byte
	btfsc	status,z			;is it zero? Then we have reached the end of the table
	goto	not_in_table
	banksel	idlevoltslo
	subwf	idlevoltslo,0		;subtract and store in w
	btfss	status,c			;if w<=idlevoltslo, then carry will be set
	goto	nomatch
	fcall	inc_eeadr			;we have a match, increment ee pointer to point to data
	fcall	read_flash			;get byte
	return						;..and return it to fcaller
nomatch
	fcall	inc_eeadr			; No match, point to next pair
	fcall	inc_eeadr
	goto	find_next			;..and loop
not_in_table
	movlw	0xec				;Value not in table				
	return						;return 0xec to show

;
; The actual table, end table with 0
;	
chargetable
#ifdef VOLTS12
	data d'123',0x99			; Approximation: V = -0,4114 * P^2 + 1,2754 * P + 11,841
	data d'122',0x95
	data d'121',0x90
	data d'119',0x85
	data d'118',0x80
	data d'116',0x75
	data d'115',0x70
	data d'113',0x65
	data d'111',0x60
	data d'109',0x55
	data d'107',0x50
	data d'105',0x45
	data d'102',0x40
	data d'100',0x35
	data d'97',0x30
	data d'95',0x25
	data d'92',0x20
	data d'89',0x15
	data d'86',0x10
	data d'83',0x05
	data d'80',0x00
	data 0, 0
#endif
#ifdef VOLTS48					; Aopproximation: V = -1,6457 * P^2 + 5,1017* P + 47,362
	data d'215',0x99
	data d'214',0x95
	data d'212',0x90
	data d'211',0x85
	data d'209',0x80
	data d'207',0x75
	data d'205',0x70
	data d'203',0x65
	data d'201',0x60
	data d'199',0x55
	data d'196',0x50
	data d'194',0x45
	data d'191',0x40
	data d'188',0x35
	data d'185',0x30
	data d'182',0x25
	data d'179',0x20
	data d'176',0x15
	data d'173',0x10
	data d'169',0x05
	data d'166',0x00
	data 0, 0
#endif
MATH_STUFF	CODE
;*******************************************************************************************************
; MATH ROUTINES
;
; prthex	prints a hexadecimal byte in W
prthex
	banksel	char_hi
	movwf CHAR_HI           	; Place W in CHAR_HI to store
	swapf CHAR_HI,W         	; Place CHAR_HI in W with most
								; significant nibble in lower
								; part of W
	andlw 0x0F					; Clear upper part of W
	
	addlw -.10 					; Same as single digit, do conversion
	btfsc STATUS,C
	addlw 'A'-'0'-.10
	addlw '0'+.10
								; Swap W and CHAR_HI
	xorwf CHAR_HI,F         	; CHAR_HI = CHAR_HI xor W
	xorwf CHAR_HI,W         	; W = W xor CHAR_HI xor W
								; = (W xor W) xor CHAR_HI
								; = 0 xor CHAR_HI = CHAR_HI
	xorwf CHAR_HI,F         	; CHAR_HI = CHAR_HI xor W xor CHAR_HI
								; = (CHAR_HI xor CHAR_HI) xor W
								; = 0 xor W = W
	
	andlw 0x0F					; Now we have original W back in W
	                         	; Clear upper part so we are left
	                         	; with original least significant
	                         	; nibble
	
	addlw -.10               	; Same as single digit, do conversion
	btfsc STATUS,C
	addlw 'A'-'0'-.10
	addlw '0'+.10            	; We're done
;
	xorwf CHAR_HI,F         	; CHAR_HI = CHAR_HI xor W
	xorwf CHAR_HI,W         	; W = W xor CHAR_HI xor W
	xorwf CHAR_HI,F         	; CHAR_HI = CHAR_HI xor W xor CHAR_HI

	fcall	lcd_send_data
	banksel	char_hi
	xorwf CHAR_HI,F         	; CHAR_HI = CHAR_HI xor W
	xorwf CHAR_HI,W         	; W = W xor CHAR_HI xor W
	xorwf CHAR_HI,F         	; CHAR_HI = CHAR_HI xor W xor CHAR_HI
	fcall	lcd_send_data
	return

;
; Convert a 32-bit BCD-number to ascii
;
bcd2a	movlw	bcd+9
	movwf	pto					; destination pointer
	movlw	bcd+4
	movwf	pti					; source pointer
	movlw	5					; 5 bytes to process
	movwf	cnt

bcd2a1	movf	pti,w			; get current input pointer
	movwf	fsr
	decf	pti,f				; prepare for next
	movf	indf,w				; get 2 bcds
	movwf	temp				; save for later
	movf	pto,w				; get current output pointer
	movwf	fsr
	decf	pto,f				; prepare for next
	decf	pto,f
	movf	temp,w				; get digits back
	andlw	0x0f				; process lsd
	addlw	"0"
	movwf	indf				; to output
	decf	fsr,f
	swapf	temp,w				; process msd
	andlw	0x0f
	addlw	"0"
	movwf	indf				; to output
	decfsz	cnt					; all digits?
	goto	bcd2a1
	return						; yes


;******************************************************************
; Convert 32-bit binary number in REGA into a bcd number
; at . Uses Mike Keitz's procedure for handling bcd 
; adjust; Modified Microchip AN526 for 32-bits.
;
; Note: Destroys REGA

b2bcd	
	btfss	rega3,7				;Check if egative
	goto	positive			; No

	fcall	negatea

	movlw	'-'					; and et sign
	movwf	sign
	goto	positive1

positive
	movlw	'+'
	movwf	sign
positive1
	movlw	.32					; 32-bits
	movwf	mcount					; make cycle counter
	clrf	bcd					; clear result area
	clrf	bcd+1
	clrf	bcd+2
	clrf	bcd+3
	clrf	bcd+4
	
b2bcd2	
	movlw	bcd					; make pointer
	movwf	fsr
	movlw	.5
	movwf	cnt

; Mike's routine:

b2bcd3	movlw	0x33		
	addwf	indf,f				; add to both nybbles
	btfsc	indf,3				; test if low result > 7
	andlw	0xf0				; low result >7 so take the 3 out
	btfsc	indf,7				; test if high result > 7
	andlw	0x0f				; high result > 7 so ok
	subwf	indf,f				; any results <= 7, subtract back
	incf	fsr,f				; point to next
	decfsz	cnt
	goto	b2bcd3
	
	rlf	rega0,f					; get another bit
	rlf	rega1,f
	rlf	rega2,f
	rlf	rega3,f
	rlf	bcd+4,f					; put it into bcd
	rlf	bcd+3,f
	rlf	bcd+2,f
	rlf	bcd+1,f
	rlf	bcd+0,f
	decfsz	mcount,f			; all done?
	goto	b2bcd2				; no, loop
	return						; yes

;
; 32-bit math routines
;
;*** SIGNED 32-BIT INTEGER MATHS ROUTINES FOR PIC16 SERIES BY PETER HEMSLEY ***
;
;Functions:
;	add
;	subtract
;	multiply
;	divide
;	round
;	sqrt
;	bin2dec
;	dec2bin


;*** 32 BIT SIGNED SUTRACT ***
;REGA - REGB -> REGA
;Return carry set if overflow

subtract
	fcall	negateb				;Negate REGB
	skpnc
	return						;Overflow

;*** 32 BIT SIGNED ADD ***
;REGA + REGB -> REGA
;Return carry set if overflow

add	movf	REGA3,w				;Compare signs
	xorwf	REGB3,w
	movwf	MTEMP

	call	addba				;Add REGB to REGA

	clrc						;Check signs
	movf	REGB3,w				;If signs are same
	xorwf	REGA3,w				;so must result sign
	btfss	MTEMP,7				;else overflow
	addlw	0x80
	return

;*** 32 BIT SIGNED MULTIPLY ***
;REGA * REGB -> REGA
;Return carry set if overflow

multiply
	clrf	MTEMP				;Reset sign flag
	call	absa				;Make REGA positive
	skpc
	call	absb				;Make REGB positive
	skpnc
	return						;Overflow

	call	movac				;Move REGA to REGC
	call	clra				;Clear product

	movlw	D'31'				;Loop counter
	movwf	MCOUNT

muloop	fcall	slac			;Shift left product and multiplicand
	
	rlf	REGC3,w					;Test MSB of multiplicand
	skpnc						;If multiplicand bit is a 1 then
	call	addba				;add multiplier to product

	skpc						;Check for overflow
	rlf	REGA3,w
	skpnc
	return

	decfsz	MCOUNT,f			;Next
	goto	muloop

	btfsc	MTEMP,0				;Check result sign
	call	negatea				;Negative
	return


;*** 32 BIT SIGNED DIVIDE ***
;REGA / REGB -> REGA
;Remainder in REGC
;Return carry set if overflow or division by zero

divide	clrf	MTEMP			;Reset sign flag
	movf	REGB0,w				;Trap division by zero
	iorwf	REGB1,w
	iorwf	REGB2,w
	iorwf	REGB3,w
	sublw	0
	skpc
	call	absa				;Make dividend (REGA) positive
	skpc
	call	absb				;Make divisor (REGB) positive
	skpnc
	return						;Overflow

	clrf	REGC0				;Clear remainder
	clrf	REGC1
	clrf	REGC2
	clrf	REGC3
	call	slac				;Purge sign bit

	movlw	D'31'				;Loop counter
	movwf	MCOUNT

dvloop	fcall	slac			;Shift dividend (REGA) msb into remainder (REGC)

	movf	REGB3,w				;Test if remainder (REGC) >= divisor (REGB)
	subwf	REGC3,w
	skpz
	goto	dtstgt
	movf	REGB2,w
	subwf	REGC2,w
	skpz
	goto	dtstgt
	movf	REGB1,w
	subwf	REGC1,w
	skpz
	goto	dtstgt
	movf	REGB0,w
	subwf	REGC0,w
dtstgt	skpc					;Carry set if remainder >= divisor
	goto	dremlt

	movf	REGB0,w				;Subtract divisor (REGB) from remainder (REGC)
	subwf	REGC0,f
	movf	REGB1,w
	skpc
	incfsz	REGB1,w
	subwf	REGC1,f
	movf	REGB2,w
	skpc
	incfsz	REGB2,w
	subwf	REGC2,f
	movf	REGB3,w
	skpc
	incfsz	REGB3,w
	subwf	REGC3,f
	clrc
	bsf	REGA0,0					;Set quotient bit

dremlt	decfsz	MCOUNT,f		;Next
	goto	dvloop

	btfsc	MTEMP,0				;Check result sign
	call	negatea				;Negative
	return

;*** ROUND RESULT OF DIVISION TO NEAREST INTEGER ***

;round	clrf	MTEMP			;Reset sign flag
;	call	absa				;Make positive
;	clrc
;	call	slc					;Multiply remainder by 2
;	movf	REGB3,w				;Test if remainder (REGC) >= divisor (REGB)
;	subwf	REGC3,w
;	skpz
;	goto	rtstgt
;	movf	REGB2,w
;	subwf	REGC2,w
;	skpz
;	goto	dtstgt
;	movf	REGB1,w
;	subwf	REGC1,w
;	skpz
;	goto	rtstgt
;	movf	REGB0,w
;	subwf	REGC0,w
;rtstgt	skpc					;Carry set if remainder >= divisor
;	goto	rremlt
;	incfsz	REGA0,f				;Add 1 to quotient
;	goto	rremlt
;	incfsz	REGA1,f
;	goto	rremlt
;	incfsz	REGA2,f
;	goto	rremlt
;	incf	REGA3,f
;	skpnz
;	return						;Overflow,return carry set
;rremlt	btfsc	MTEMP,0			;Restore sign
;	call	negatea
;	return
;
;
;;*** 32 BIT SQUARE ROOT ***
;;sqrt(REGA) -> REGA
;;Return carry set if negative
;
;sqrt	rlf	REGA3,w				;Trap negative values
;	skpnc
;	return
;
;	call	movac				;Move REGA to REGC
;	call	clrba				;Clear remainder (REGB) and root (REGA)
;
;	movlw	D'16'				;Loop counter
;	movwf	MCOUNT
;
;sqloop	rlf	REGC0,f				;Shift two msb's
;	rlf	REGC1,f					;into remainder
;	rlf	REGC2,f
;	rlf	REGC3,f
;	rlf	REGB0,f
;	rlf	REGB1,f
;	rlf	REGB2,f
;	rlf	REGC0,f
;	rlf	REGC1,f
;	rlf	REGC2,f
;	rlf	REGC3,f
;	rlf	REGB0,f
;	rlf	REGB1,f
;	rlf	REGB2,f
;
;	setc						;Add 1 to root
;	rlf	REGA0,f					;Align root
;	rlf	REGA1,f
;	rlf	REGA2,f
;
;	movf	REGA2,w				;Test if remdr (REGB) >= root (REGA)
;	subwf	REGB2,w
;	skpz
;	goto	ststgt
;	movf	REGA1,w
;	subwf	REGB1,w
;	skpz
;	goto	ststgt
;	movf	REGA0,w
;	subwf	REGB0,w
;ststgt	skpc					;Carry set if remdr >= root
;	goto	sremlt
;
;	movf	REGA0,w				;Subtract root (REGA) from remdr (REGB)
;	subwf	REGB0,f
;	movf	REGA1,w
;	skpc
;	incfsz	REGA1,w
;	subwf	REGB1,f
;	movf	REGA2,w
;	skpc
;	incfsz	REGA2,w
;	subwf	REGB2,f
;	bsf	REGA0,1					;Set current root bit
;
;sremlt	bcf	REGA0,0				;Clear test bit
;	decfsz	MCOUNT,f			;Next
;	goto	sqloop
;
;	clrc
;	rrf	REGA2,f					;Adjust root alignment
;	rrf	REGA1,f
;	rrf	REGA0,f
;	return
;
;
;
;UTILITY ROUTINES


;Add REGB to REGA (Unsigned)
;Used by add, multiply,

addba	movf	REGB0,w			;Add lo byte
	addwf	REGA0,f

	movf	REGB1,w				;Add mid-lo byte
	skpnc						;No carry_in, so just add
	incfsz	REGB1,w				;Add carry_in to REGB
	addwf	REGA1,f				;Add and propagate carry_out

	movf	REGB2,w				;Add mid-hi byte
	skpnc
	incfsz	REGB2,w
	addwf	REGA2,f

	movf	REGB3,w				;Add hi byte
	skpnc
	incfsz	REGB3,w
	addwf	REGA3,f
	return


;Move REGA to REGC
;Used by multiply, sqrt

movac	movf	REGA0,w
	movwf	REGC0
	movf	REGA1,w
	movwf	REGC1
	movf	REGA2,w
	movwf	REGC2
	movf	REGA3,w
	movwf	REGC3
	return

;Move REGB to REGA
movba
	movf	REGb0,w
	movwf	REGa0
	movf	REGb1,w
	movwf	REGa1
	movf	REGb2,w
	movwf	REGa2
	movf	REGb3,w
	movwf	REGa3
	return

;Move REGA to REGB
movab
	movf	REGa0,w
	movwf	REGb0
	movf	REGa1,w
	movwf	REGb1
	movf	REGa2,w
	movwf	REGb2
	movf	REGa3,w
	movwf	REGb3
	return

;Clear REGB and REGA
;Used by sqrt

clrba	clrf	REGB0
	clrf	REGB1
	clrf	REGB2
	clrf	REGB3

;Clear REGA
;Used by multiply, sqrt

clra	clrf	REGA0
	clrf	REGA1
	clrf	REGA2
	clrf	REGA3
	return


;Check sign of REGA and convert negative to positive
;Used by multiply, divide, bin2dec, round

absa	rlf	REGA3,w
	skpc
	return						;Positive

;Negate REGA
;Used by absa, multiply, divide, bin2dec, dec2bin, round

negatea	movf	REGA3,w			;Save sign in w
	andlw	0x80

	comf	REGA0,f				;2's complement
	comf	REGA1,f
	comf	REGA2,f
	comf	REGA3,f
	incfsz	REGA0,f
	goto	nega1
	incfsz	REGA1,f
	goto	nega1
	incfsz	REGA2,f
	goto	nega1
	incf	REGA3,f
nega1
	incf	MTEMP,f				;flip sign flag
	addwf	REGA3,w				;Return carry set if -2147483648
	return


;Check sign of REGB and convert negative to positive
;Used by multiply, divide

absb	rlf	REGB3,w
	skpc
	return						;Positive

;Negate REGB
;Used by absb, subtract, multiply, divide

negateb	movf	REGB3,w			;Save sign in w
	andlw	0x80

	comf	REGB0,f				;2's complement
	comf	REGB1,f
	comf	REGB2,f
	comf	REGB3,f
	incfsz	REGB0,f
	goto	negb1
	incfsz	REGB1,f
	goto	negb1
	incfsz	REGB2,f
	goto	negb1
	incf	REGB3,f
negb1
	incf	MTEMP,f				;flip sign flag
	addwf	REGB3,w				;Return carry set if -2147483648
	return


;Shift left REGA and REGC
;Used by multiply, divide, round

slac	rlf	REGA0,f
	rlf	REGA1,f
	rlf	REGA2,f
	rlf	REGA3,f
slc	rlf	REGC0,f
	rlf	REGC1,f
	rlf	REGC2,f
	rlf	REGC3,f
	return

;Inc32z 
;From Dmitry Kiryashov
inc32z:
        movwf   FSR
        clrz

        incfsz  INDF,F
        return

        incfsz  FSR,F
        incfsz  INDF,F
        return

        incfsz  FSR,F
        incfsz  INDF,F
        return

        incfsz  FSR,F
        incf    INDF,F
        return

;Dec32z
;From Rich Leggitt, Andrew Warren, and Dmitry Kiryashov
; 99.2% of the time, this takes 10 cycles w/fcall
dec32z:
        movwf   FSR
        decfsz  INDF,F
        goto    dec32nz

        incf    FSR,F
        movfw   INDF

        incf    FSR,F
        iorwf   INDF,W

        incf    FSR,F
        iorwf   INDF,W  ;get _Z finally
        return

dec32nz:
        clrz            ;set _Z=0
        incfsz  INDF,W
        return

        incfsz  FSR,F   ;doesn't corrupt _Z
        decfsz  INDF,F
        incfsz  INDF,W
        return

        incfsz  FSR,F   ;...
        decfsz  INDF,F
        incfsz  INDF,W
        return

        incfsz  FSR,F   ;...
        decfsz  INDF,F
        return
        return

write_flash_ee
;
; Write one byte in W to flash (EE memory)
; EEADR and EEADRH must be initialised
;
	banksel	EEDAT
	movwf	EEDAT				; EEDAT  = W
								; Prep for write
	banksel	EECON1
	bcf		EECON1, EEPGD		; Use for DATA memory
	bsf		EECON1, WREN		; Allow writing

	bcf		INTCON, GIE			; Disable interrupts
	btfsc	INTCON, GIE			; during write process.
	goto	$-2					; Make sure has taken effect

	; Required Sequence ---
	movlw	0x55					
	movwf	EECON2				; Write 0x55
	movlw	0xAA					
	movwf	EECON2				; Write 0xAA
	bsf		EECON1, WR			; Set WR to begin write
	; ---------------------


	; Clear settings
	bsf		INTCON, GIE			; Re-enable interrupts
	bcf		EECON1, WREN		; Disable writes


	; Wait until write complete
	btfsc	EECON1, WR			; Check if WR complete
	goto	$-1					; Once clear.. exit routine

	banksel	0					; Leave flow in bank 0
	return

;**********************************************************************************************************
; 
; Subs for HD44780. I am indebted to Jan-Erik Söderholm for his routines at http://www.jescab.se/PIC16.html
;
; Temporary variables for the LCD routies in shared memory (above)
;
LCD_CODE        CODE
;
lcd_init_hd44780
;
	fcall	delay_1s        ; Give the LCD time to start up
	bcf     LCD_E           ; Zero E and RS
	bcf     LCD_RS          ; 
;
; Init HD44780.
; To set 4-bit mode you have to wait a minimum 30ms after Vdd. Then send 0010xxxx twice. Then tell the LCD
; if you want one or two lines and turn it on
;
	movlw   b'00100000'
	fcall	lcd_send_4_bit
	fcall	delay_5ms
	movlw   b'00100000'
	fcall    lcd_send_4_bit
	fcall    delay_5ms
	movlw   b'11000000'		; 11000000
							;  |- 0 = Display off, 1 = display on
							; |- 0 = 1 line, 1 = 2 line
	fcall    lcd_send_4_bit
	fcall    delay_5ms
	movlw   b'11100000' 	; 11100000
							;  |- 0 = Display off, 1 = Display on
							;   |- 0 = Cursor off, 1 = Cursor on
							;    |- 0 = Blink off, 1 = Blink on
;       
    fcall    lcd_send_4_bit
    fcall    delay_100us

    movlw   b'00010000' 	; Clear display     
    fcall    lcd_send_4_bit
	fcall    delay_100us

	movlw   b'01110000' 	; 01110000
							;   |- 0 = Decrement, 1 = Increment
							;    |- 0 = Enrire shift off, 1 = Entire shift on
;       
	fcall    lcd_send_4_bit
	fcall    delay_100us
;
; Rest of the init data is sent as 2 nibbles (4-bit), first (bit 4-7), then (bit 0-3)
; See lcd_send_cmd och lcd_send_data för details.
;
	movlw   b'00101000'     ; Function set
	fcall    lcd_send_cmd
	movlw   b'00010100'     ; Display/curs shift
	fcall    lcd_send_cmd
	movlw   b'00001110'     ; Display/curs on/off
	fcall    lcd_send_cmd
	movlw   b'00000110'     ; Entry mode set
	fcall    lcd_send_cmd
	movlw   b'00000010'     ; Display/curs home
	fcall    lcd_send_cmd
	movlw   b'00000001'     ; Display clear
	fcall    lcd_send_cmd

#ifdef DEN
;Writing to LCD_RAM for a "5x8 custom" ae char. ;æ	00 00 1B 05 1F 14 1F 00

        movlw   0x08            ;address of 1st byte
        call    lcd_chargen_address
        movlw   0x00            ;1st data
        call    lcd_send_data	;write to LCD
        movlw   0x09
        call    lcd_chargen_address
        movlw   0x00            ;2nd data
        call    lcd_send_data
        movlw   0x0A
        call    lcd_chargen_address
        movlw   0x1b            ;3rd data
        call    lcd_send_data
        movlw   0x0B
        call    lcd_chargen_address
        movlw   0x05            ;4th data
        call    lcd_send_data
        movlw   0x0C
        call    lcd_chargen_address
        movlw   0x1f            ;5th data
        call    lcd_send_data
        movlw   0x0D
        call    lcd_chargen_address
        movlw   0x14            ;6th data
        call    lcd_send_data
        movlw   0x0E
        call    lcd_chargen_address
        movlw   0x1f            ;7th data
        call    lcd_send_data
        movlw   0x0F
        call    lcd_chargen_address
        movlw   0x00            ;8th data (cursor)
        call    lcd_send_data
;								;ø	00 00 0E 13 15 19 0E 00
        movlw   0x10            ;address of 1st byte
        call    lcd_chargen_address
        movlw   0x00           ;1st data
        call    lcd_send_data	;write to LCD
        movlw   0x11
        call    lcd_chargen_address
        movlw   0x00            ;2nd data
        call    lcd_send_data
        movlw   0x12
        call    lcd_chargen_address
        movlw   0x0e            ;3rd data
        call    lcd_send_data
        movlw   0x13
        call    lcd_chargen_address
        movlw   0x13            ;4th data
        call    lcd_send_data
        movlw   0x14
        call    lcd_chargen_address
        movlw   0x15            ;5th data
        call    lcd_send_data
        movlw   0x15
        call    lcd_chargen_address
        movlw   0x19            ;6th data
        call    lcd_send_data
        movlw   0x16
        call    lcd_chargen_address
        movlw   0x0e            ;7th data
        call    lcd_send_data
        movlw   0x17
        call    lcd_chargen_address
        movlw   0x00            ;8th data (cursor)
        call    lcd_send_data
        return
#endif
	return
;
lcd_toggle_E
;
; Toggle E on LCD so that it reads D4-D7 .
;
	banksel lcd_port
	bsf     lcd_e
	nop
	bcf     lcd_e
	nop
	return
;
lcd_send_4_bit
; Transfer data in W-reg bit 4-7 to LCD_PORT bit 4-7.
; Bit 0-3 is not touched.
;
	banksel lcd_port
	movwf   lcd_tmp1        ; Save LCD data...
	movlw   b'00001111'     ; "Mask" for LCD_PORT
	andwf   lcd_port, w     ; Read LCD_PORT bits 0-3
	iorwf   lcd_tmp1, w     ; Combine with LCD data bits 4-7
	movwf   lcd_port        ; Write out to LCD_PORT.
	fcall    lcd_toggle_e    ; Transfer to LCD.
	return
;
; lcd_chargen_address
; Sets Character-Generator-RAM address. CGRAM is read/written after this setting.
;  Required CGRAM address must be set in W
;  b0-5        : required CGRAM address
;  b6-7        : don't care
 
lcd_chargen_address
	ANDLW   0x3F            ; Strip upper bits
	IORLW   0x40            ; Function set
	fcall    lcd_send_cmd
	RETURN

lcd_send_cmd
; Transfer data in W to LCD as a command.
;
	banksel lcd_port
	bcf     LCD_RS          ; RS = "0" for command.
	fcall    lcd_send_byte   ; Send
	fcall    delay_5ms       ; wait 5 ms after a command.
	return
;
lcd_send_data
; Transfer data in W to LCD as data.
;
	banksel lcd_port
	bsf     LCD_RS          ; RS = "1" for data
	fcall    lcd_send_byte   ; Send
	fcall    delay_100us     ; Give LCD time to digest
	return
;
lcd_send_byte
; Transfer Data in W as two nibbles
;
	movwf   lcd_tmp2        ; Save W for now
	movlw   B'11110000'     ; Mask bits 4-7
	andwf   lcd_tmp2, w     ; Zero bits 0-3
	fcall    lcd_send_4_bit  ; Send bits 4-7
	swapf   lcd_tmp2, f     ; Swap 0-3 <-> 4-7
	movlw   B'11110000'     ; Mask for bits 4-7 (now 0-3 !!)
	andwf   lcd_tmp2, w     ; Zero bits 0-3 (now 4-7 !!)
	fcall    lcd_send_4_bit  ; Send bits 4-7 (now 0-3 !!)
	return
;
lcd_send_text
; Send a text in EE ram to LCD.
; You have to set up EEADR and EEADRH holding the address to the text. A suitable macro is provided
; Position calculation assumes 16x2 LCD
;
; Read the first position (row)
;
	fcall read_flash
	movwf   lcd_tmp1        ; Store row
;
; read next pos in text row (column)
;
	fcall    inc_eeadr
	fcall    read_flash
	movwf   lcd_tmp2        ; Store column
;
; Adjust row/column
;
	decf    lcd_tmp1, f     ; Row 1-2 => 0-1
	bcf     status, c
	rrf     lcd_tmp1, f
	rrf     lcd_tmp1, f
	rrf     lcd_tmp1, f     ; Lcd_tmp1 h'00' or h'40'...
	decf    lcd_tmp2, f     ; Pos 1-40 => 0-39
;
; Add row and column and send to LCD.
;
	movlw   h'80'           ; Bas-kommandot för LCD-position.
	addwf   lcd_tmp1, w     ; Lägg till rad (h'00' eller h'40')
	addwf   lcd_tmp2, w     ; Lägg till pos.
	fcall    lcd_send_cmd    ; Skicka kommandot (i W-reg)
;
; Read rest of the text, up to 0x00 and sen to LCD
;
send_text_loop
;
; Read next and send
;End if 0x00
;
	fcall    inc_eeadr
lcd_send_text2
	fcall    read_flash
	btfsc   status, z       ; Was it h'00' (EOT) ?
	goto    send_text_end   ; Yes, we are done.
                                ; No, keep going...      
	fcall    lcd_send_data   ; Send char in W-reg.
	goto    send_text_loop  ; Until done
;
send_text_end
;
	return
;
read_flash
;
; Read one byte from flash (program memory)
; EEADR and EEADRH must be initialised
;
	banksel eecon1
	bsf     eecon1, eepgd   ; Read flash, program memory...
	bsf     eecon1, rd      ; Indicate read..
	nop                     ; Wait for the read to complete
	nop                     ;  
	banksel eedat
	movf    eedat, w        ; Return with the value in W-reg.
	banksel 0				; and in bank 0
	return

read_flash_ee
;
; Read one byte from flash (EE memory)
; EEADR and EEADRH must be initialised
;
	banksel eecon1
	bcf     eecon1, eepgd   ; Read flash, access data memory
	bsf     eecon1, rd      ; Indicate read
	nop                     ; Wait for the read to complete
	nop                     ; 
	banksel eedat
	movf    eedat, w        ; Return with the value in W-reg.
	banksel 0				; and in bank 0
	return
;
; Increment eeadr/eeadrh 
;
inc_eeadr
	banksel eeadr
	incf    eeadrh, f       	; Increment eeadrh
	incfsz  eeadr, f        	; Increment eeadr, = 0 ?
	decf    eeadrh, f       	; eeadr <> 0, decrement eeadrh...
	banksel 0					; return in bank 0
	return
;-------------------------------------------------------------------------------------
; Check our "keyboard". We have four buttons:  Up, Down, left, right
; return with carry set and keycode in W if key pressed. Carry clear if not
; returns in bank 0
;
; Will also (optionally) turn on the LCD backlight and leave it running for the
; remainder of POLLTIME
;
; Note! Keys are defined as macros, but do check that the banksels match!!
;
getkey

#ifdef	NO_KEYBOARD
	goto	nokey
#endif
; Test, just return a left
;	movlw	DN
;	goto	havekey
;
;	movlw   0x80+0x40     		; Goto first pos of line 2
;	fcall    lcd_send_cmd
;	movlw	'u'
;	fcall	lcd_send_data

	banksel	porta
	btfsc	UPKEY				; Up key pressed?
	goto	check_dnkey			; No, check next key
	fcall	delay_100ms			; Yes, wait and check again

	btfsc	UPKEY				; Up key still pressed?
	goto	check_dnkey			; No, check next key
	;
wait_for_release_up
	btfss	UPKEY				; Up key still pressed?
	goto	wait_for_release_up
	fcall	delay_100ms
	movlw	UP
	goto	havekey

check_dnkey						; Then check down key
	banksel	porta
	btfsc	DNKEY				; Down key pressed?
	goto	check_ltkey			; No, check next key
	fcall	delay_100ms			; Yes, wait and check again

	btfsc	DNKEY				; Down key still pressed?
	goto	check_ltkey			; No, check next key
wait_for_release_dn
	btfss	DNKEY				; Up key still pressed?
	goto	wait_for_release_dn
	fcall	delay_100ms
	movlw	DN
	goto	havekey

check_ltkey						; Then check left key
	banksel	portb
	btfsc	LTKEY				; Left key pressed?
	goto	check_rtkey			; No, check next key
	fcall	delay_100ms			; Yes, wait and check again
	btfsc	LTKEY				; Left key still pressed?
	goto	check_rtkey			; No, check next key
wait_for_release_lt
	btfss	LTKEY				; Up key still pressed?
	goto	wait_for_release_lt
	fcall	delay_100ms
	movlw	LT
	goto	havekey

check_rtkey						; Last check right key
	banksel	portc
	btfsc	RTKEY				; Right key pressed?
	goto	nokey				; No - done
	fcall	delay_100ms			; Yes, wait and check again

	btfsc	RTKEY				; Right key still pressed?
	goto	nokey				; No - done
wait_for_release_rt
	btfss	RTKEY				; Right key still pressed?
	goto	wait_for_release_rt
	fcall	delay_100ms
	movlw	RT
	goto	havekey

nokey
	banksel	porta
	bcf		status,c			; Clear carry
	movlw	0
	return						; No key pressed, return with cleared carry and in bank 0
havekey
	banksel	porta
	bsf		BACKLIGHT			; Turn on backlight
	bsf		status,c			; Key pressed set carry flag and
	return						; return with keycode in W and in bank 0

;********************************************************************************************************************
; Maintenance menu - Creates a menu of options. You select option with the <> keys and arrow down to accept an option
;
MAINT	CODE
maintenance
	banksel	dspmode
	clrf	dspmode				; Initial display mode (menu item) is zero
	movlw   b'00000001'     	; Clear display
	fcall    lcd_send_cmd
	lcd_text lbl_maint
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall	lcd_send_cmd
	lcd_text2 lbl_mainta		; Show "Use <> to select"

maintenance1
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall	lcd_send_cmd
	fcall	getkey				; Check for key
	btfss	status,C
	goto	maintenance1		; No key, loop
	movwf	regd0

	btfss	regd0,0				; Up ie 1?
	goto	not_up
	lgoto	ourloop

not_up
	btfss	regd0,1				; DN ie 2?
	goto	not_down
	goto	do_command

not_down
	btfss	regd0,2				; LT ie 4?
	goto	not_left

	decf	dspmode,1			; Decrement displaymode
	btfss	dspmode,7			; Rolled over?
	goto	maint	 			; no
	movlw	.7					; yes, put back to 7
	movwf	dspmode
	goto	maint

not_left
	btfss	regd0,3				; RT ie 8?
	goto	not_right
	incf	dspmode,1			; Increment the display mode (0..7)
	btfss	dspmode,3			; reached end?
	goto	not_right			; no
	clrf	dspmode				; yes, back to zero
	goto	maint

not_right

maint
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall	lcd_send_cmd


								; Page-safe jumptable from AN556. Checks for carry when adding offset and
								; adjusts PCLATH accordingly
	banksel	dspmode
	movf	dspmode,w			; Get display mode in W
	andlw	.7					; Make sure it is within bounds (0..7)
	movwf	rega0				; save in temp register

	movlw	LOW mainttable		; Get low address of table
	addwf	rega0,F				; add offset
	movlw	HIGH mainttable		; Get high 5 bits of address
	btfsc	status,c			; Did we cross a page?
	addlw	1					; Yes, increment high address
	movwf	pclath				; Load high address in latch
	movf	rega0,w
	
	movwf	pcl					; Load computed offset into program counter
mainttable
	goto	maint0 				; Jump table to text handlers
	goto	maint1
	goto	maint2
	goto	maint3
	goto	maint4
	goto	maint5
	goto	maint6
	goto	maint7

maint0
	lcd_text2 lbl_maint0
	goto maintenance1
maint1
	lcd_text2 lbl_maint1
	goto maintenance1
maint2
	lcd_text2 lbl_maint2
	goto maintenance1
maint3
	lcd_text2 lbl_maint3
	goto maintenance1
maint4
	lcd_text2 lbl_maint4
	goto maintenance1
maint5
	lcd_text2 lbl_maint5
	goto maintenance1
maint6
	lcd_text2 lbl_maint6
	goto maintenance1
maint7
	lcd_text2 lbl_maint7
	goto maintenance1

do_command						; Execute one of the maintenance options
								; Page-safe jumptable from AN556. Checks for carry when adding offset and
								; adjusts PCLATH accordingly
	banksel	dspmode
	movf	dspmode,w			; Get display mode in W
	andlw	.7					; Make sure it is within bounds (0..7)
	movwf	rega0				; save in temp register

	movlw	LOW cmdtable		; Get low address of table
	addwf	rega0,F				; add offset
	movlw	HIGH cmdtable		; Get high 5 bits of address
	btfsc	status,c			; Did we cross a page?
	addlw	1					; Yes, increment high address
	movwf	pclath				; Load high address in latch
	movf	rega0,w
	movwf	pcl					; Load computed offset into program counter
cmdtable
	goto	do_cmd0 			; Jump table to menu commands
	goto	do_cmd1
	goto	do_cmd2
	goto	do_cmd3
	goto	do_cmd4
	goto	do_cmd5
	goto	do_cmd6
	goto	do_cmd7

;-----------------------------------------------------------------------------------------------------------------------
								; Change nominal capacity of battery
do_cmd0
	movlw   b'00000001'     	; Clear display
	fcall	lcd_send_cmd
	lcd_text2 lbl_maint0
	;
	banksel	rega0
	ee_address	capacity		; Read capacity from eeprom
	movlw	.4					;4d bytes
	movwf	strlen
	movlw	rega0

	fcall	copybytes			; Copy capacity to REGB?? (REGA)
#ifdef SCALE_200AH
	movbyte	regb0,.80,0
	movbyte	regb1,.80,1
	movbyte	regb2,.80,2
	movbyte	regb3,.80,3
#endif
#ifdef SCALE_20AH
	movbyte	regb0,.800,0
	movbyte	regb1,.800,1
	movbyte	regb2,.800,2
	movbyte	regb3,.800,3
#endif
	fcall	divide				; REGA / REGB -> REGA
	fcall	movab				; REGA -> REGB

	call	enter_regb			; If this returns with carry set, then save the value in eeprom
	btfss	status,C
	goto 	maintenance			; No carry, return and do nothing, else fall through and...
								; ....save value here!
#ifdef SCALE_200AH
	movbyte	rega0,.80,0
	movbyte	rega1,.80,1
	movbyte	rega2,.80,2
	movbyte	rega3,.80,3
#endif
#ifdef SCALE_20AH
	movbyte	rega0,.800,0
	movbyte	rega1,.800,1
	movbyte	rega2,.800,2
	movbyte	rega3,.800,3
#endif
	fcall	multiply			; REGB * REGA -> REGA

	ee_address	capacity
	movlw	.4					;4d bytes
	movwf	strlen
	movlw	rega0

	fcall	writebytes			; Write new capacity to EEProm

	goto 	maintenance
;-----------------------------------------------------------------------------------------------------------------------
do_cmd1							; Change efficiency in %

	movlw   b'00000001'     	; Clear display
	fcall	lcd_send_cmd
	lcd_text2 lbl_maint1
	;
	banksel	rega0
	ee_address	efficiency		; Read efficiency from eeprom
	fcall	read_flash_ee
	movwf	rega0				; Efficiency is one byte, 255 = 100%
	clrf	rega1
	clrf	rega2
	clrf	rega3

	movlw	.100
	movwf	regb0
	clrf	regb1
	clrf	regb2
	clrf	regb3

	fcall	multiply			; 255 * 100 = 25500

	movlw	.255
	movwf	regb0
	clrf	regb1
	clrf	regb2
	clrf	regb3

	fcall	divide				; REGA / REGB -> REGA , 25500 / 255 = 100

	fcall	movab				; REGA -> REGB

	call	enter_regb			; If this returns with carry set, then save the value in eeprom
	btfss	status,C
	goto 	maintenance			; No carry, return and do nothing, else fall through and...
								; ....save value here!
	movlw	.255				; Assume we have 99%,  multiply by 255 = 25245
	movwf	rega0
	clrf	rega1
	clrf	rega2
	clrf	rega3

	fcall	multiply			; REGB * REGA -> REGA

	movlw	.100
	movwf	regb0
	clrf	regb1
	clrf	regb2
	clrf	regb3

	fcall	divide				; REGA / REGB -> REGA , 25245 / 100 = 252

	ee_address	efficiency
	movf	rega0,0				; Put rega0 in W
	fcall	write_flash_ee		; write to eeprom

	goto 	maintenance
;-----------------------------------------------------------------------------------------------------------------------
do_cmd2							; Change selfdischarge in %

	movlw   b'00000001'     	; Clear display
	fcall	lcd_send_cmd
	lcd_text2 lbl_maint2
	;
	banksel	rega0
	ee_address	discharge		; Read discharge from eeprom
	fcall	read_flash_ee
	movwf	regb0				; Discharge is one byte, 100 = 100%
	clrf	regb1
	clrf	regb2
	clrf	regb3

	call	enter_regb			; If this returns with carry set, then save the value in eeprom
	btfss	status,C
	goto 	maintenance			; No carry, return and do nothing, else fall through and...
								; ....save value here!

	ee_address	discharge
	movf	regb0,0				; Put regb0 in W
	fcall	write_flash_ee		; write to eeprom
	goto 	maintenance
;-----------------------------------------------------------------------------------------------------------------------
do_cmd3							; Set the running charge counter to full
	movlw   b'00000001'     	; Clear display
	fcall	lcd_send_cmd
	lcd_text2 lbl_maint3
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall	lcd_send_cmd
	lcd_text2 lbl_maint3a

do_cmd3_getkey
	fcall	getkey				; check for key
	btfss	status,C
	goto	do_cmd3_getkey		; No key, loop
	movwf	regd0
	btfss	regd0,1				; Down ie 2? Equals enter
	goto 	maintenance			; No carry, return and do nothing, else fall through and...
	fcall	reset_counter		; .. reset counter
	fcall	reset_bq2018		; .. and sensor
	goto 	maintenance
;-----------------------------------------------------------------------------------------------------------------------
do_cmd4							; Change offset

	movlw   b'00000001'     	; Clear display
	fcall	lcd_send_cmd
	lcd_text2 lbl_maint4
	;
	banksel	rega0
	ee_address	stored_offset	; Read offset from eeprom
	fcall	read_flash_ee		; into W

	movwf	regb0
	btfsc	regb0,7				; Is offset negative?
	goto	do_cmd4_offset_negative		; Yes
	clrf	regb1				; No, positive offset, zero out upper 24 bits
	clrf	regb2
	clrf	regb3
	goto	do_cmd4_offset_calc
do_cmd4_offset_negative			; Negative offset, make all upper 24 bits ones
	movlw	0xff
	movwf	regb1
	movwf	regb2
	movwf	regb3
do_cmd4_offset_calc

	call	enter_regb			; If this returns with carry set, then save the value in eeprom
	btfss	status,C
	goto 	maintenance			; No carry, return and do nothing, else fall through and...
								; ....save value here!

	ee_address	stored_offset
	movf	regb0,0				; Put regb0 in W
	movwf	offset
	fcall	write_flash_ee		; write to eeprom

	ee_address	flags
	fcall	read_flash_ee		; read flags from eeprom into w
	iorlw	b'00000001'			; flip calibrated bit to show we have a valid calibration
	fcall	write_flash_ee		; write back flags to eeprom

	goto 	maintenance
;-----------------------------------------------------------------------------------------------------------------------
do_cmd5
	goto 	maintenance
;-----------------------------------------------------------------------------------------------------------------------
do_cmd6
	goto 	maintenance
;-----------------------------------------------------------------------------------------------------------------------
do_cmd7
	goto	maintenance
;-----------------------------------------------------------------------------------------------------------------------
; Input a decimal value between REGC and REGD into REGD. Value is presented and incremented/decremented by pressing
; left/right keys. Key up aborts without saving and returns with carry clear. Key down enters and returns with carry set
;
; Todo: Implement range checking
;
enter_regb
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall	lcd_send_cmd

	banksel	regb0
	fcall	movba
	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

	movf	sign,W
	fcall	lcd_send_data

	movlw	.10					;10d bytes of ascii, ie 10 digits "9999999999"
	movwf	strlen
	movlw	bcd					;based at this location
	fcall	printstr
enter_regb_getkey
	fcall	getkey
	btfss	status,C
	goto	enter_regb_getkey
	movwf	regd0

	btfss	regd0,2				; LT ie 4? Decrement value
	goto	enter_regb_not_left
	banksel	regb0
	movlw	regb0				; Put address of rega in W
	fcall	dec32z				; Decrement value
	goto	enter_regb

enter_regb_not_left
	btfss	regd0,3				; RT ie 8? Increment value
	goto	enter_regb_not_right
	banksel	regb0
	movlw	regb0				; Put address of rega in W
	fcall 	inc32z				; Increment value
	goto	enter_regb

enter_regb_not_right
	btfss	regd0,0				; UP ie 1? Abort
	goto	enter_regb_not_up
	bcf		status,c			; Clear carry
	return

enter_regb_not_up
	btfss	regd0,1				; Down ie 2? Equals enter, save value
	goto	enter_regb_nokey
	bsf		status,c			; Set carry
	return

enter_regb_nokey
	goto	enter_regb
;**********************************************************************
; Sundry delay routines from:
; http://www.piclist.com/techref/piclist/codegen/delay.htm
; Assumes 4 Mhz clock. 
;
DLY_VAR      UDATA_SHR
d1           RES 1
d2           RES 1
d3           RES 1
;
;
DLY_CODE        CODE
;
delay_1s
 movlw 0x08
 movwf d1
 movlw 0x2F
 movwf d2
 movlw 0x03
 movwf d3
Delay_1s_0
 decfsz d1, f
 goto $+2
 decfsz d2, f
 goto $+2
 decfsz d3, f
 goto Delay_1s_0
 goto $+1
 nop
    return
;
delay_5ms
        movlw 0xE7
     movwf d1
     movlw 0x04
     movwf d2
Delay_5ms_0
     decfsz d1, f
     goto $+2
     decfsz d2, f
     goto Delay_5ms_0
     goto $+1
        return
;
delay_100us
     movlw 0x21
     movwf d1
Delay_100us_0
     decfsz d1, f
     goto Delay_100us_0
       return

Delay_5us
			;5 cycles
	goto	$+1
	goto	$+1
	nop
	return

; Delay = 0.1 seconds
; Clock frequency = 8 MHz

; Actual delay = 0.1 seconds = 200000 cycles
; Error = 0 %
delay_100ms
			;199998 cycles
	movlw	0x3F
	movwf	d1
	movlw	0x9D
	movwf	d2
delay_100ms_0
	decfsz	d1, f
	goto	$+2
	decfsz	d2, f
	goto	delay_100ms_0

			;2 cycles
	goto	$+1
	return

HDQ_SUBS	CODE
; 
;---------------------------------------------------------------------------------------------------------
;  H D Q - S U B R O U T I N E S
;
; HDQ communication between a host and slave device uses a single-wire, open-drain interface. The
; communication protocol is asynchronous return-to-one referenced to Vss. A passive pullup resistance is
; required to pull the HDQ line to a high state when neither the host nor the slave is pulling the line low
; during the two-way communication over the single wire interface. The interface uses a command-based
; protocol, where the host sends a command byte to the HDQ slave device. The command directs the slave
; either to store the next eight bits of data received to a register specified by the command byte (write
; command), or to output the eight bits of data from a register specified by the command byte (read
; command). Command and data bytes consist of a stream of bits that have a maximum transmission rate
; of 5 Kbits/s. The least-significant bit of a command or data byte is transmitted first. The first 7 bits of the
; command word are the register address and the last command bit transmitted is the read/write (R/W) bit.
;
; The HDQ line may remain high for an indefinite period of time between each bit of address or between
; each bit of data on a write cycle. After the last bit of address is sent on a read cycle, the HDQ slave starts
; outputting the data after the specified response time, t(RSPS). Some have interpreted the response time as
; the time after the last command bit before the first data bit of the response begins. This is incorrect. The
; response time is measured from the fall time of the command R/W bit to the fall time of the first data bit
; returned by the slave and therefore includes the entire bit time for the R/W bit. Because the minimum
; response time is equal to the minimum bit cycle time, this means that the first data bit may begin as soon
; as the command R/W bit time ends.
; From: http://focus.tij.co.jp/jp/lit/an/slua408a/slua408a.pdf
;
; Code is adapted from: http://focus.ti.com/lit/an/slua016/slua016.pdf
;
; The logic is somewhat unintuitive because of the one-wire open-drain interface. Tris is used to "wiggle" the
; data bits. Note that timing is somewhat sensitive and better defined in the bq2019 datasheet
;
; ________                  _______        _________________________
;         |                 |      |       |              |         |
;         |                 |      |       |              |         |
;         |_________________|      |_______|______________|         |_
;          Break, min 190us  Min    Start    Data 68-90us   Stop
;                            40us   32-50us  (high or low)  45-90us
;
;                                  |<----------- 190 us ----------->|<----next bit- 
;                                          One bit time
;
; 
; high-speed service for battery receive. Receive one serial byte into hserdat
; timeout is FF on error, 00 otherwise
;
hs_serva 
#ifdef __DEBUG					;Will hang unless bq2018 is connected, so skip if debug build
	return
#else
	nop
#endif
	movlw 	08h 				;load bit counter
	banksel	hserbit
	movwf 	hserbit 			;with 8 for 8 bits
readit 
	clrf 	wstack 				;get timeout ready
	clrf 	timeout
habqr0 
	btfss 	porta,0 			;request for hs
	goto 	habqr1
	decfsz 	wstack,1 			;count for timeout
	goto 	habqr0
;
; time-out on receive
;
	movlw 	0ffh
	movwf 	timeout
	incf	rd_err
	goto 	breakit
	;
habqr1 
	rrf 	hserdat,1 			;shift data
	clrf 	wstack 				;time low time
	;
habqr2 
	btfsc 	porta,0 			;check for stop bit
	goto 	habqr3
	incf 	wstack,1
	btfss 	wstack,6 			;break during read low 144us
	goto 	habqr2
;
;break detected
;
breakit 
	clrf 	hcmd 				; cancel pending command
	movlw 	08h 				;load bit counter
	movwf 	hserbit 			; with 8 for 8 bits
habqr5 
	nop
	btfss 	porta,0 			;check for stop bit
	goto 	habqr5 				;will loop forever if line stays low
	retlw 	00h 				;done
;
habqr3 
	bsf 	hserdat,7
	movlw 	30h
	andwf 	wstack,w
	btfss 	status,2
	bcf 	hserdat,7
	decfsz 	hserbit,1
	;
	goto 	readit 				;more to do!
	;
	movf 	hserdat,w
	movwf 	hcmd
done_wa 
	movlw 	08h 				;load bit counter
	movwf 	hserbit 			; with 8 for 8 bits
	retlw 	00h
;
; Send one serial byte from hserdat
;
snda_it 
	movlw 	08h 				;load bit counter
	movwf 	hserbit 			;with 8 for 8 bits
;
;delay a bit
;
	clrf hcmd
snda_1 
	banksel	hcmd
	incf 	hcmd,1
	btfss 	hcmd,6
	goto 	snda_1
;
snda_2
	banksel	btris
	bcf 	btris,0
	movf 	btris,w
	;tris 	porta
	banksel	trisa
	movwf	trisa
	clrf 	hcmd
;
snda_3 
	banksel	hcmd
	incf 	hcmd,1
	btfss 	hcmd,3 ;4
	goto 	snda_3
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
;
snda_5 
	btfss 	hserdat,0 			;test data bit
	goto 	snda_4
;
	banksel	btris
	bsf 	btris,0
	movf 	btris,w
	;tris 	porta
	banksel	trisa
	movwf	trisa
;
snda_4 
	clrf hcmd
;
snda_7 
	banksel	hcmd
	incf 	hcmd,1
	btfss 	hcmd, 5
	goto 	snda_7
	nop
	nop
	nop
	nop
	nop
	nop
;
	bsf 	btris,0
	movf 	btris,w
	;tris 	porta
	banksel	trisa
	movwf	trisa
;
	banksel	hcmd
	clrf 	hcmd
	bsf 	hcmd,4
;
snda_9 
	incf 	hcmd,1
	btfss 	hcmd,6
	goto 	snda_9
;
	rrf 	hserdat,1 			;shift data, for next bit
	decfsz 	hserbit,1 			;dec counter
	goto 	snda_2 				;more bits to send
;
	clrf 	hcmd
	movlw 	08h 				;load bit counter
	movwf 	hserbit 			; with 8 for 8 bits
	retlw 	00h 				;no, done
;
;
; Send a break to reset communications. A break is defined as a low of minimum 190us, followed by
; a minimum 40us break recovery

send_break
	banksel	hserdat
	clrf	hserdat
	banksel	btris
	BCF 	btris,0
	MOVF 	btris,W
	banksel	trisa
	movwf	trisa

	fcall	delay_100us
	fcall	delay_100us
	fcall	delay_100us
	fcall	delay_100us

	banksel	btris
	BsF 	btris,0
	MOVF 	btris,W
	banksel	trisa
	movwf	trisa

	fcall	delay_100us
	fcall	delay_100us
	return
;
; Read 16 bits from bq2018. Address of register to read (low) in W. Address of register to store (low) in fsr
;
; example:
;   charge_lo			equ	0x23
;   charge_hi			equ	0x24
;
;	movlw	charge_lo			; Set up address to receive data
;	movwf	fsr					; in fsr
;	movlw	0x7c				; Set up address of charge time low register
;	fcall	read_16				; fcall routine to read it
;
; result is that bq2018 registers 0x7c and 0x7d are read into charge_lo and charge_hi
;
read_16

	banksel	hserdat
	;
	MOVWF 	HSERDAT				; Address of low byte in W
	movwf	temp				; Save for next read
	incf	hserdat,1			; Point to high byte
	;
	banksel	hserbit
	MOVLW 	08H 				;LOAD BIT COUNTER
	MOVWF 	HSERBIT 			;WITH 8 FOR 8 BIT
	;
	CALL 	SNDA_IT
	CALL 	HS_SERVA
	;
	banksel	timeout
	btfsc	timeout,0			; Valid data? timeout is FF if an error occurred
	goto	read_err			; Nope
;
	movf	hserdat,w			; Yes, data valid
	movwf	temp2				; Tuck away high bit

	banksel	hserdat
	movf 	temp,w				; register lo
	MOVWF 	HSERDAT
	CALL 	SNDA_IT
	CALL 	HS_SERVA
	;
	banksel	timeout
	btfsc	timeout,0			; Valid data?
	goto	read_err			; Nope
								; Yes, we now have the complete 16-bita
	movf	hserdat,w			; 
	movwf	indf				; Store low byte
	incf	fsr,1				; Point to high byte
	movf	temp2,w				; 
	movwf	indf				; Store high byte

read_err:						; In case of error, we just return with nothing stored in either hig/low
								; and timeout set
	return

reset_bq2018:					; Reset the sensor
	movlw 	74h+80h				; tmp/clr register
	movwf 	hserdat
	fcall 	snda_it
	movlw 	b'00011111'			; reset all registers
	movwf 	hserdat
	fcall 	snda_it
	return

HDQ_CALIBRATE	CODE
;---------------------------------------------------------------------------------------------------------
; Initiate calibration of bq2018. This is done by setting
; the calibration bit in the MODE/WOE register (Bit 6) to
; 1. The bq2018 then enters calibration mode when the
; HDQ line is low for greater than 10 seconds and when
; the signal between SR1 and SR2 is below VWOE.
;
; If HDQ remains low for one hour and |VSR| < VWOE for
; the entire time, the measured VOS is latched into the
; OFR register, and the calibration bit is reset to zero, indicating
; to the system that the calibration cycle is complete.
;
calibrate
	lcd_text    lbl_calib
	;
	fcall	send_break

	MOVLW 	BQ_MODE				; Mode/woe register read
	MOVWF 	HSERDAT
	fcall 	SNDA_IT
	fcall	hs_serva			; Read data
	bsf		hserdat,6			; Set calibration bit
	movf	hserdat,w
	movwf	temp				; Save in temp

	MOVLW 	BQ_MODE+BQ_WRITE	; Mode/woe register write
	MOVWF 	HSERDAT
	fcall 	SNDA_IT
	MOVf 	temp,w				; Get value to write
	MOVWF 	HSERDAT
	fcall 	SNDA_IT

	banksel	hserdat				; Pull HDQ low
	clrf	hserdat
	banksel	btris
	BCF 	btris,0
	MOVF 	btris,W
	banksel	trisa
	movwf	trisa
	;
	; Ok. We have kicked off a calibration cycle. Wait one hour
	;
	banksel	regd0				; Prime rega with suitable value
	;
	movbyte	regd0,CALIBHOUR,0
	movbyte	regd1,CALIBHOUR,1
	movbyte	regd2,CALIBHOUR,2
	movbyte	regd3,CALIBHOUR,3
calibrate1						
	banksel	prtflag
	btfss	prtflag,1			; Flag is set by Timer1 interrupt approx twice per second.
	goto    calibrate1         	; no, loop forever
								; Yes, do our stuff
	bcf		prtflag,1			; and clear print flag

	banksel	regd0
	movlw	regd0				; Put address of regd in W
	fcall	dec32z				; Decrement counter..
								; ..and show it so we know what is happening...

	movf	regd0,w
	movwf	rega0				; Copy regd to rega
	movf	regd1,w
	movwf	rega1
	movf	regd2,w
	movwf	rega2
	movf	regd3,w
	movwf	rega3

								; REGA -> DIGITS 1 (MSD) TO 10 (LSD) & DSIGN
	fcall	b2bcd				; convert to 32-bit binary to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

	;
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall    lcd_send_cmd
	;
	movlw	.10					;10d bytes of ascii,
	movwf	strlen
	movlw	bcd					;based at this location
	fcall	printstr
	fcall	delay_1s	
	;
	btfss	regd2,7				;Finished counting down (wrapped round to 0xffff..) Note kludge because b2bcd touches regd3 for some reason??
	goto	calibrate1			; No, keep counting
	;							; yes, done calibrating
	fcall	delay_100us			; Wait...

	banksel	btris				; Then bring HDQ back up
	BsF 	btris,0
	MOVF 	btris,W
	banksel	trisa
	movwf	trisa

	movlw   b'00000001'			; Clear display
	fcall    lcd_send_cmd

	fcall	delay_100us			; Wait a little then
	fcall	send_break			; send a break to reset communications

	MOVLW 	BQ_MODE				; Mode/woe register read
	MOVWF 	HSERDAT
	fcall 	SNDA_IT
	fcall	hs_serva			; Read data
	btfsc	hserdat,6			; check calibration bit
	goto	calibrate_error 	; Ooops, bit still set
	;							; Ok. bit clear, read the offset
	MOVLW 	BQ_OFFSET			; Offset register read
	MOVWF 	HSERDAT
	fcall 	SNDA_IT
	fcall	hs_serva			; Read data
	movf	hserdat,w
	movwf	offset				; Store offset

								; Save in EEPROM and flip flag that shows we are calibrated

	ee_address	stored_offset
	fcall	write_flash_ee		; write to eeprom

	ee_address	flags
	fcall	read_flash_ee		; read flags from eeprom into w
	iorlw	b'00000001'			; flip calibrated bit
	fcall	write_flash_ee		; write back flags to eeprom

	movlw   b'00000010'     	; Display/curs home
	lcd_text lbl_offset
	;
	; Print actual offset in dec and hex
	;
	btfsc	offset,7			; Is offset negative
	goto	cal_offset_negative	; Yes
	clrf	rega1				; No, positive offset, zero out upper 24 bits
	clrf	rega2
	clrf	rega3
	goto	offset_calc
cal_offset_negative				; Negative offset, make all upper 24 bits ones
	movlw	0xff
	movwf	rega1
	movwf	rega2
	movwf	rega3

	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

	movf	sign,W
	fcall	lcd_send_data

	movlw	.3					;4d bytes of ascii, ie 4 digits "9999"
	movwf	strlen
	movlw	bcd+7				;based at this location
	fcall	printstr
	;
	movlw	' '
	fcall	lcd_send_data
	movlw	'('
	fcall	lcd_send_data
	movf	offset,w
	fcall	prthex				; Display data in hex
	movlw	'h'
	fcall	lcd_send_data
	movlw	')'
	fcall	lcd_send_data
	;
	;
	; wait for keystroke here
	;
calibrate_waitkey
	banksel	trisa
	bsf		trisa,1				; Make sure key can be read

	banksel porta
	btfsc	UPKEY				; Up key pressed?
	goto	calibrate_waitkey 	; no, wait
								; continue
	fcall	delay_1s
	lgoto	start
calibrate_error
	lcd_text    lbl_calerr
	movlw   0x80+0x40     		; Goto first pos of line 2
	fcall    lcd_send_cmd
	movf	hserdat,w
	fcall	prthex				; Print mode/woe
	banksel	trisa
	bsf		trisa,1				; Make sure key can be read
	banksel porta
calibrate_error_waitkey
	btfsc	UPKEY				; Up key pressed?
	goto	calibrate_error_waitkey ; no, wait
								; continue
	fcall	delay_1s

	lgoto	start

; 
;---------------------------------------------------------------------------------------------------------
;  N M E A - S U B R O U T I N E S
;
; The checksum is very simple, basically xor every byte transmitted between $ and * and then print the sum as two hex digits
;
#ifdef NMEA
NMEA_STUFF       CODE
;
;	Init serial port forn NMEA
;
nmea_init
	banksel	trisb
	bcf		trisb,7				; B7/UART TX, port is output

	banksel	BAUDCTL
	bsf     BAUDCTL, SCKP   	; Inverted output. Use when connecting pin directly to serial port pin 2
	bcf     BAUDCTL, BRG16  	; BRG16 = 0 -> 8 bit BRG	

	banksel	SPBRG				; SET BAUD RATE for 8MHz clock, BRGH=1, BRG16=0
	movlw 	.102              	; .102=4800 bps, .51=9600 bps (.25=19200 bps) 
	movwf 	SPBRG 
	banksel	SPBRGH
	clrf	SPBRGH

	banksel	TXSTA
	movlw 	b'00100100'       	; Transmit enabled, brgh = high (2) 
	movwf 	TXSTA             	; enable Async Transmission, set brgh 

	banksel	RCSTA

;	movlw 	b'10010000'       	; enable Async Reception 
	movlw 	b'10000000'       	; enable serial port, but not Async Reception 
	movwf 	RCSTA
	banksel	0					; Leave in bank 0

;testing
;	movlw	0x55				; This can be used for testing. Loop, outputting 0x55 will give a nice
;	fcall	send				; squarewave you can check on the scope...
;	nop
;	nop
;	nop
;	nop
;	nop
;	nop
;	movlw	0xaa
;	fcall	send
;	nop
;	nop
;	nop
;	nop
;	nop
;	nop
;	goto testing
	return
;
; Subroutine that sends strlen characters starting at w
;
sendstr
	movwf	fsr
sendstr1
	movf	indf,w				;get a byte and send it
	fcall	send
	incf	fsr					;point to next
	decfsz	strlen				;decrement string length
	goto	sendstr1			;Not done yet?
								;Done! Continue here
	return

;
; 	Send one character in w via rs232 and wait until finished sending, then add it to checksum 
;	Input:	W = Char to send
;	Output:	REGD1 = Checksum
;	Trashes:REGD0

send
	banksel	txreg
	movwf 	TXREG             	; send data in W 

TransWt 
	banksel	TXSTA
WtHere  
	btfss TXSTA,TRMT        	; (1) transmission is complete if hi 
	goto WtHere 
	banksel	TXREG				; OK. Character sent, calculate checksum
	movwf	regd0				; Save character in REGD0

	; Snippet:	if (A == CONST)
	; Inputs:	W
								; Check if start of NMEA string ($)
	sublw	'$'					; W = $-A
	btfss	STATUS, Z			; Check if zero
	goto	ck1_false			; Z=0, A!=0, false cond'n

								; Place result TRUE code here.
	clrf	regd1				; Clear checksum
	goto	send_exit			; and return

ck1_false:						; Place result FALSE code here.

	; Snippet:	if (A == CONST)
	; Inputs:	regd0
	movf	regd0, W			; Get char
	sublw	'*'					; W = *-A
	btfss	STATUS, Z			; Check if zero
	goto	ck2_false			; Z=0, A!=0, false cond'n

								; Place result TRUE code here.

	goto	send_exit			; End of string, do nothing

ck2_false:
								; Place result FALSE code here.
	movf	regd0, W			; Get char
	xorwf	regd1,1				; XOR char with checksum and put result in regd1

send_exit
	return
;
; Subroutine that sends the NMEA checksum in REGD1 as two ascii chars
; Thanks to the author of this nifty checksum checker: http://www.hhhh.org/wiml/proj/nmeaxor.html
;
nmea_checksum
	movf	regd1,w
nmea_prthex
	movwf	regd2				; Move checksum to regd1 since send (below) will trash the checksum
	swapf 	regd2,W         	; Place REGD2 in W with most
								; significant nibble in lower
								; part of W
	andlw 	0x0F				; Clear upper part of W
	
	addlw -.10 					; Same as single digit, do conversion
	btfsc 	STATUS,C
	addlw 	'A'-'0'-.10
	addlw 	'0'+.10
								; Swap W and CHAR_HI
	xorwf 	REGD2,F         	; CHAR_HI = CHAR_HI xor W
	xorwf 	REGD2,W         	; W = W xor CHAR_HI xor W
								; = (W xor W) xor CHAR_HI
								; = 0 xor CHAR_HI = CHAR_HI
	xorwf 	REGD2,F         	; CHAR_HI = CHAR_HI xor W xor CHAR_HI
								; = (CHAR_HI xor CHAR_HI) xor W
								; = 0 xor W = W
	
	andlw 	0x0F				; Now we have original W back in W
	                         	; Clear upper part so we are left
	                         	; with original least significant
	                         	; nibble
	
	addlw 	-.10               	; Same as single digit, do conversion
	btfsc 	STATUS,C
	addlw 	'A'-'0'-.10
	addlw 	'0'+.10            	; We're done
;
	xorwf 	REGD2,F         	; CHAR_HI = CHAR_HI xor W
	xorwf 	REGD2,W         	; W = W xor CHAR_HI xor W
	xorwf 	REGD2,F         	; CHAR_HI = CHAR_HI xor W xor CHAR_HI

	fcall	send

	xorwf 	REGD2,F         	; CHAR_HI = CHAR_HI xor W
	xorwf 	REGD2,W         	; W = W xor CHAR_HI xor W
	xorwf 	REGD2,F         	; CHAR_HI = CHAR_HI xor W xor CHAR_HI
	fcall	send
	return
;
; Subroutine that sends a NMEA header ($IIXDR,)
;
nmea_head
	movlw	'$'
	fcall	send
	movlw	'I'
	fcall	send
	movlw	'I'
	fcall	send
	movlw	'X'
	fcall	send
	movlw	'D'
	fcall	send
	movlw	'R'
	fcall	send
	movlw	','
	fcall	send
	return
;
; Subroutine that sends NMEA data. In our case
; 
;	$IIXDR,U,vvvvvv*CS
;	$IIXDR,A,aaaaaa*CS
;	$IIXDR,G,hhhhhh*CS
;
; mV, mA and mAh
;
; This is to dump out data in csv-format:
; ctc;ccr;dtc;dcr;ctc0;ccr0;dtc0;dcr0;charge;amps;volts
;
nmea_data
#ifdef IDEBUG
	movf	ctc_hi,w
	fcall	nmea_prthex	
	movf	ctc_lo,w
	fcall	nmea_prthex
	movlw	';'
	fcall	send

	movf	ccr_hi,w
	fcall	nmea_prthex	
	movf	ccr_lo,w
	fcall	nmea_prthex
	movlw	';'
	fcall	send

	movf	dtc_hi,w
	fcall	nmea_prthex	
	movf	dtc_lo,w
	fcall	nmea_prthex	
	movlw	';'
	fcall	send

	movf	dcr_hi,w
	fcall	nmea_prthex
	movf	dcr_lo,w
	fcall	nmea_prthex
	movlw	';'
	fcall	send

;-------
	movf	ctc0_hi,w
	fcall	nmea_prthex	
	movf	ctc0_lo,w
	fcall	nmea_prthex
	movlw	';'
	fcall	send

	movf	ccr0_hi,w
	fcall	nmea_prthex	
	movf	ccr0_lo,w
	fcall	nmea_prthex
	movlw	';'
	fcall	send

	movf	dtc0_hi,w
	fcall	nmea_prthex	
	movf	dtc0_lo,w
	fcall	nmea_prthex	
	movlw	';'
	fcall	send

	movf	dcr0_hi,w
	fcall	nmea_prthex
	movf	dcr0_lo,w
	fcall	nmea_prthex
	movlw	';'
	fcall	send

	movf	charge,W			; Get charge counter into REGA
	movwf	rega0
	movf	charge+1,W
	movwf	rega1
	movf	charge+2,W
	movwf	rega2
	movf	charge+3,w
	movwf	rega3

	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii
	movlw	.10					;10d bytes of ascii
	movwf	strlen
	movlw	bcd					;based at this location
	fcall	sendstr
	movlw	';'
	fcall	send

	movf	amps0,W				; Print amps in decimal
	movwf	rega0
	movf	amps1,W
	movwf	rega1
	movf	amps2,W
	movwf	rega2
	movf	amps3,W
	movwf	rega3

	fcall	b2bcd				; convert to 32-bit binary to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii

	
	movlw	.10					;10d bytes of ascii
	movwf	strlen
	movlw	bcd					;based at this location
	fcall	sendstr
	movlw	';'
	fcall	send

	movf	voltshi,w
	movwf	rega1
	movf	voltslo,w
	movwf	rega0
	clrf	rega2
	clrf	rega3

	movlw	VOLTSDIV
	movwf	regb0				; 1024 is 20.48V so multiply by two (Or you could rotate, but we have the multiply routine anyway..)
	clrf	regb1
	clrf	regb2				
	clrf	regb3

	fcall	multiply

	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii
	movlw	.5					;5d bytes of ascii
	movwf	strlen
	movlw	bcd+5				;based at this location
	fcall	sendstr

	movlw	0x0a				; End with cr/lf
	fcall	send
	movlw	0x0d
	fcall	send

#else

	fcall	nmea_head			; First line, Send $IIXDR, then millivolts
	movlw	'U'					; U volts
	fcall	send
	movlw	','
	fcall	send
	movf	voltshi,w
	movwf	rega1
	movf	voltslo,w
	movwf	rega0
	clrf	rega2
	clrf	rega3

	movlw	VOLTSDIV
	movwf	regb0				; 1024 is 20.48V so multiply by two (Or you could rotate, but we have the multiply routine anyway..)
	clrf	regb1
	clrf	regb2				
	clrf	regb3

	fcall	multiply

	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii
	movlw	.5					;5d bytes of ascii
	movwf	strlen
	movlw	bcd+5				;based at this location
	fcall	sendstr
	movlw	'0'
	fcall	send				; Tack on a zero to make mV
	movlw	'*'
	fcall	send

	fcall 	nmea_checksum

	movlw	0x0a
	fcall	send
	movlw	0x0d
	fcall	send

	fcall	nmea_head			; Second line Send $IIXDR, then milliamps
	movlw	'A'					; A amps
	fcall	send
	movlw	','
	fcall	send
	movf	amps0,w
	movwf	rega0
	movf	amps1,w
	movwf	rega1
	movf	amps2,w
	movwf	rega2
	movf	amps3,w
	movwf	rega3
	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii
	movlw	.5					;5d bytes of ascii
	movwf	strlen
	movlw	bcd+5				;based at this location
	fcall	sendstr
	movlw	'*'
	fcall	send

	fcall 	nmea_checksum

	movlw	0x0a
	fcall	send
	movlw	0x0d
	fcall	send

	fcall	nmea_head			; Third line, Send $IIXDR, then charge
	movlw	'G'					; A Ah
	fcall	send
	movlw	','
	fcall	send
	movf	charge,W			; Get charge into REGA
	movwf	rega0
	movf	charge+1,W
	movwf	rega1
	movf	charge+2,W
	movwf	rega2
	movf	charge+3,W
	movwf	rega3

	movf	ccr_lo,w			; Get charge count into REGB
	movwf	regb0
	movf	ccr_hi,w
	movwf	regb1
	clrf	regb2				; ccr is a 16-bit value so zero the upper 16 bita
	clrf	regb3

	fcall	add					; Add up REGA+REGB-> REGA

	movf	dcr_lo,w			; Get discharge count into REGB
	movwf	regb0
	movf	dcr_hi,w
	movwf	regb1
	clrf	regb2				; dcr is a 16-bit value so zero the upper 16 bita
	clrf	regb3

	fcall	subtract			; REGA - REGB -> REGA

	movlw	.8					; adjust: 8000 = 10Ah, so divide by 8, then multiply by 10 when presenting
	movwf	regb0
	clrf	regb1
	clrf	regb2
	clrf	regb3

	fcall	divide

	fcall	b2bcd				; convert 32-bit binary in REGA to 10 bcd
	fcall	bcd2a				; convert 10 bcd to 10 ascii
	movlw	.10					;10d bytes of ascii
	movwf	strlen
	movlw	bcd					;based at this location
	fcall	sendstr
	movlw	'*'
	fcall	send

	fcall 	nmea_checksum

	movlw	0x0a
	fcall	send
	movlw	0x0d
	fcall	send
#endif	;IDEBUG
	return
;nmea_test
;	movlw	'1'
;	fcall	send
;	movlw	'0'
;	fcall	send
;	movlw	'0'
;	fcall	send
;	movlw	'*'
;	fcall	send
;	return
#endif	; nmea
;**********************************************************************
LCD_texts   CODE
; LCD Display strings
; The first two bytes are row (1,2,3 or 4)
; and column (1-40). Rest is text, end with h'00'.
;
; Language neutral texts
;
lbl_text2   data    d'1', d'12', '0', '.', '4', h'00'
lbl_text3   data    d'2', d'1', '2','0','1','0','-','0','8','-','2','0',' ',' ',' ',' ', h'00'
;
#ifdef ENG
lbl_text1   data    d'1', d'1', 'B', 'a', 't', 't', 'm', 'e', 't', 'e', 'r', h'00'

lbl_amps	data	d'1', d'1', 'A','m','p','s',' ',h'00'
lbl_dspmode	data	d'1', d'1', 'D','s','p','m','o','d','e',' ',h'00'
;
lbl_calib   data    d'1', d'1', 'C', 'a', 'l', 'i', 'b', 'r', 'a', 't', 'i', 'n', 'g', h'00'
lbl_calerr	data    d'1', d'1', 'C', 'a', 'l', 'i', 'b', '.', ' ', 'e', 'r', 'r', h'00'
lbl_offset	data    d'1', d'1', 'O', 'f', 'f', 's', 'e', 't', ' ', h'00'
lbl_maint	data    d'1', d'1', 'S', 'e', 't', 't', 'i', 'n', 'g', 's' , ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_mainta	data    'S', 'e', 'l', 'e', 'c', 't', ' ', 'w' , 'i', 't', 'h', '<', '-', '>', ' ', ' ', h'00'
lbl_maint0	data    'C', 'a', 'p', 'a', 'c', 'i', 't', 'y', ' ', 'i','n', ' ' ,'A', 'h', ' ', ' ', h'00'
lbl_maint1	data    'E', 'f', 'f', 'i', 'c', 'i', 'e', 'n', 'c', 'y', ' ', 'i' ,'n', ' ', '%', ' ', h'00'
lbl_maint2	data    'S', 'e', 'l', 'f', '.', 'd', 'i', 's', 'c', 'h', 'a', 'r', 'g', 'e', ' ', '%', h'00'
lbl_maint3	data    'S', 'e', 't', ' ', 'f', 'u', 'l', 'l', ' ', ' ',' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint3a	data    'D', 'o', 'w', 'n','=', 's', 'e', 'c', 't',' ', 'U', 'p', '=', 'e', 's', 'c', h'00'
lbl_maint4	data    'O', 'f', 'f', 's', 'e', 't', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint5	data    'M', 'e', 'n', 'u', '5', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint6	data    'M', 'e', 'n', 'u', '6', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint7	data    'M', 'e', 'n', 'u', '7', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
#endif
#ifdef SWE
lbl_text1   data    d'1', d'1', 'B', 'a', 't', 't', 'm', 'e', 't', 'e', 'r', h'00'

lbl_amps	data	d'1', d'1', 'A','m','p','s',' ',h'00'
lbl_dspmode	data	d'1', d'1', 'D','s','p','m','o','d','e',' ',h'00'
;
lbl_calib   data    d'1', d'1', 'K', 'a', 'l', 'i', 'b', 'r', 'e', 'r', 'a', 'r', h'00'
lbl_calerr	data    d'1', d'1', 'K', 'a', 'l', 'i', 'b', '.', ' ', 'f', 'e', 'l', h'00'
lbl_offset	data    d'1', d'1', 'O', 'f', 'f', 's', 'e', 't', ' ', h'00'
lbl_maint	data    d'1', d'1', 'I', 'n', 's', 't', 0x01, 'l', 'l', 'n' , 'i', 'n', 'g', ' ', ' ', ' ', h'00'
lbl_mainta	data    'V', 0x01, 'l', 'j', ' ', 'm', 'e', 'd' , ' ', '<', '-', '>', ' ', ' ', h'00'
lbl_maint0	data    'K', 'a', 'p', 'a', 'c', 'i', 't', 'e', 't', ' ', 'i', ' ' ,'A', 'h', ' ', ' ', h'00'
lbl_maint1	data    'V', 'e', 'r', 'k', 'n', 'i', 'n', 'g', 's', 'g', 'r', 'a' ,'d', ' ', '%', ' ', h'00'
lbl_maint2	data    'S', 'j', '.', 'u', 'r', 'l', 'a', 'd', 'd', 'n', 'i', 'n', 'g', ' ', '%', ' ', h'00'
lbl_maint3	data    'S', 'a', 't', 't', ' ', 'f', 'u', 'l', 'l', 't', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint3a	data    'N', 'e', 'd', '=', 'v', 0x01, 'l', 'j', ' ', 'U', 'p', 'p', '=', 'a', 'v', 'b', h'00'
lbl_maint4	data    'O', 'f', 'f', 's', 'e', 't', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint5	data    'M', 'e', 'n', 'y', '5', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint6	data    'M', 'e', 'n', 'y', '6', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint7	data    'M', 'e', 'n', 'y', '7', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
#endif
#ifdef DEN
lbl_text1   data    d'1', d'1', 'B', 'a', 't', 't', 'm', 'e', 't', 'e','r',0x01, h'00'

lbl_amps 	data d'1', d'1', 'A','m','p','s',' ',h'00'
lbl_dspmode data d'1', d'1', 'D','s','p','m','o','d','e',' ',h'00'
;
lbl_calib   data    d'1', d'1', 'K', 'a', 'l', 'i', 'b', 'r', 'e', 'r', 'e', 'r',0x01, h'00'
;lbl_calib   data    d'1', d'1', 'K', 'a', 'l', 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,0x01, h'00'
lbl_calerr 	data    d'1', d'1', 'K', 'a', 'l', 'i', 'b', '.', 'f', 'e', 'j', 'l', h'00'
lbl_offset 	data    d'1', d'1', 'O', 'f', 'f', 's', 'e', 't', ' ', h'00'
lbl_maint 	data    d'1', d'1', 'I', 'n', 's', 't', 'i', 'l', 'l', 'i' , 'n', 'g', ' ', ' ', ' ', ' ', h'00'
lbl_mainta 	data    'V', 0x01, 'l', 'g', ' ', 'm', 'e', 'd' , ' ', '<', '-', '>', ' ', ' ', h'00'
lbl_maint0 	data    'K', 'a', 'p', 'a', 'c', 'i', 't', 'e', 't', ' ', 'i', ' ' ,'A', 'h', ' ', ' ', h'00'
lbl_maint1 	data    'V', 'i', 'r', 'k', 'n', 'i', 'n', 'g', 's', 'g', 'r', 'a' ,'d', ' ', '%', ' ', h'00'
lbl_maint2 	data    'S', 'e', 'l', 'v', 'a', 'f', 'l', 'a', 'd', 'n', 'i', 'n', 'g', ' ', '%', ' ', h'00'
lbl_maint3 	data    'S', 0x01, 't', ' ', 'f', 'u', 'l', 'd', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint3a data    'N', 'e', 'd', '=', 'v', 0x01, 'l', 'g', ' ', 'O', 'p', '=', 'e', 's', 'c', ' ', h'00'
lbl_maint4 	data    'O', 'f', 'f', 's', 'e', 't', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint5 	data    'M', 'e', 'n', 'u', '5', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint6 	data    'M', 'e', 'n', 'u', '6', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
lbl_maint7 	data    'M', 'e', 'n', 'u', '7', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', h'00'
;
; Danish fonts from http://symlink.dk/electro/hd44780/
;
;æ	00 00 1B 05 1F 14 1F 00
;ø	00 00 0E 13 15 19 0E 00
;å	0E 0A 0E 01 0F 11 0F 00
;Æ	0F 14 14 1F 14 14 17 00
;Ø	0E 13 15 15 15 19 0E 00
;Å	0E 0A 0E 11 1F 11 11 00
#endif	

;
;**********************************************************************
; initialize eeprom locations

EE      CODE    0x2100
;
; Battery capacity, 8000 = 10Ah
; 120Ah = 96000 = 0x17700
; Order is LSB-MSB
;
capacity
        ;DE    0x00, 0x00,0x9c,0x40
        ;DE    0x80, 0x38,0x01,0x00		; 100Ah at 20A max scale
		DE    0x40, 0x1f,0x00,0x00		; 100Ah at 200A max scale 8000
;
; Battery efficiency. In 1/256, ie 128 = 50%
; 
; Note that this is a gross simplification. In reality, efficiency varies with the charge current so that a lower
; current yields a better efficiency and with charge so that efficiency decreases with charge.
;
efficiency
		DE	.243	; Or 95%, which is a reasonable apporximation, assuming small charge currents (1..5A)
;
; Battery self-discharge. 32-bit value to be multiplied by the SCR, then subtracted from the charge
;
discharge
		DE	.1		; or 1% per month

;
; Calculated offset from bq2018
;
stored_offset
		DE 	0xf7	; -9
;
; Sundry flags:
; 00000001 - Calibrated
;
flags	DE 0

        END                       ; directive 'end of program'

This information and the circuits are provided as is without any express or implied warranties. While every effort has been taken to ensure the accuracy of the information contained in this text, the authors/maintainers/contributors assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein. I disclaim everything. The contents of the articles below might be totally inaccurate, inappropriate, or misguided. There is no guarantee as to the suitability of said circuits and information for any purpose whatsoever other than as a self-training aid. I.E. If it blows your equipments, trashes your hard disc, wipes your backup, burns your building down or just plain don't work, IT ISN'T MY FAULT. In the event of judicial ruling to the contrary, any liability shall be limited to the sum charged on you by us for the aforementioned document or nothing, whichever is the lower. I will not be held responsible for any damages or costs which might occur as a result of my advice or designs. Nor are you allowed to use any of my designs for commercial purposes without my written authorisation.

Bild

Valid HTML 4.0!