Batterimonitor
A PIC based battery monitor |
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.