;****************************************************************************** ; BATTERY-METER-1 * ; * ; A simple voltage meter intented to check AA and AAA bateries. * ; * ;****************************************************************************** ; * ; Pin assignments: * ; GP0 - output latch * ; GP1 - output data * ; AN2 - zener reference voltage * ; GP3 - reset * ; AN3 - battery voltage input * ; GP5 - output clock * ; * ;****************************************************************************** ; * ; Copyright (C) 2011 Sergio García-Cuevas González. * ; * ; This program is free software; you can redistribute it and/or modify * ; it under the terms of the GNU General Public License as published by * ; the Free Software Foundation; either version 3 of the License, or * ; (at your option) any later version. * ; * ; This program is distributed in the hope that it will be useful, * ; but WITHOUT ANY WARRANTY; without even the implied warranty of * ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * ; GNU General Public License for more details. * ; * ; You should have received a copy of the GNU General Public License * ; along with this program. If not, see . * ; * ;****************************************************************************** list p=12f675 #include ;***** CONFIGURATION ; _CPD_OFF: off course you can copy data ; _CP_OFF: off course you can copy code ; _BODEN_ON: brown-out detection is enabled ; _MCLRE_OFF: no reset button ; _PWRTE_ON: wait 72 ms at power-up for supply-voltage stabilisation ; _WDT_ON: watchdog timer is on ; _INTRC_OSC_NOCLKOUT: use internal, 4 MHz oscillator with no output __CONFIG _CPD_OFF & _CP_OFF & _BODEN_ON & _MCLRE_OFF & _PWRTE_ON & _WDT_ON & _INTRC_OSC_NOCLKOUT ;***** VARIABLES AND CONSTANTS REFV EQU 20H BATV EQU 21H sGPIO EQU 22H #define OUTCLK sGPIO,5 #define OUTDAT sGPIO,1 #define OUTLTCH sGPIO,0 COUNTER EQU 23H MULT1 EQU 24H MULT2H EQU 25H MULT2L EQU 26H PRODH EQU 27H PRODL EQU 28H REMH EQU 29H REML EQU 2AH DENH EQU 2BH DENL EQU 2CH QUOT EQU 2DH BINNUM EQU 2EH BCDNUM EQU 2FH ZENV EQU d'154' ; Zener diode voltage: 154 cV VOLT EQU d'100' ; 1 V = 100 cV DECIV EQU d'10' ; 1 dV = 10 cV READREF EQU b'00001011' ; ADCON0 for reading the reference voltage ; 0------- ADFM: left-justified ; -0------ VCFG: reference is VDD ; ----10-- CHS<1:0> read AN2 ; ------1- GO: perform conversion ; -------1 ADON: ADC powered on READBAT EQU b'00001111' ; ADCON0 for reading the battery voltage ; 0------- ADFM: left-justified ; -0------ VCFG: reference is VDD ; ----11-- CHS<1:0>: read AN3 ; ------1- GO: perform conversion ; -------1 ADON: ADC powered on ANON EQU b'00111100' ; ANSEL (ADC configuration) ; -011---- ACS<2:0>: AD RC oscillator (always safe) ; ----1100 ; ANS<3:0>: analog inputs are AN3 and AN2 CMOFF EQU b'00000000' ; CMCON for disabling the comparator ; -----111 CM<2:0>: comparator powered off OUTS EQU b'00011100' ; TRISIO for GP2, GP4 and GP5 as outputs ; --0----- TRISIO5: GP5 is an output ; ------0- TRISIO4: GP1 is an output ; -------0 TRISIO0: GP0 is an output ;***** RESET VECTOR reset_vector ORG 000H ; address 000H, reset vector GOTO setup ;***** RUN-TIME SETUP setup ; we will not use the analog comparator MOVLW CMOFF MOVWF CMCON ; ADC setup BSF STATUS,RP0 ; select bank 1 for ANSEL MOVLW ANON ; update ANSEL MOVWF ANSEL BCF STATUS,RP0 ; go back to bank 0 ; oscillator calibration CALL 3FFH ; load cal value (stored by the chip ; manufacturer at instruction 3FFH) BSF STATUS,RP0 ; select bank 1 for OSCCAL MOVWF OSCCAL ; update OSCCAL BCF STATUS,RP0 ; go back to bank 0 ; output setup CLRF GPIO ; init GPIO BSF STATUS,RP0 ; select bank 1 for TRISIO MOVLW OUTS MOVWF TRISIO BCF STATUS,RP0 ; go back to bank 0 ;***** MAIN LOOP main_loop CLRWDT ; we are still alive! query_refv MOVLW READREF ; query the reference zener voltage MOVWF ADCON0 read_refv_loop BTFSC ADCON0,GO ; wait until it is ready GOTO read_refv_loop read_refv MOVF ADRESH,W ; copy the reading (only the MSB) MOVWF REFV MOVF ADRESL,W ; round up if the LSB is high enough SUBLW b'10000000' BTFSS STATUS,C INCF REFV,F query_batv MOVLW READBAT ; query the battery voltage MOVWF ADCON0 read_batv_loop BTFSC ADCON0,GO ; wait until it is ready GOTO read_batv_loop read_batv MOVF ADRESH,W ; copy the reading (only the MSB) MOVWF BATV MOVF ADRESL,W ; round up if the LSB is high enough SUBLW b'10000000' BTFSS STATUS,C INCF BATV,F process_and_display CALL scale CALL bin_to_bcd CALL display GOTO main_loop ; repeat forever scale ; callibration curve: ZENV BATV / REFV product ; first obtain ZENV times BATV MOVLW ZENV ; multiply two 8-bit numbers and MOVWF MULT1 ; get a 16-bit product CLRF MULT2H ; first, set up variables MOVF BATV,W MOVWF MULT2L CLRF PRODH CLRF PRODL MOVLW d'8' MOVWF COUNTER prod_loop MOVF COUNTER,F ; if we finished BTFSC STATUS,Z GOTO division ; then go to the division step DECF COUNTER,F ; else continue multiplying BCF STATUS,C ; multiply the product by two RLF PRODL,F RLF PRODH,F BCF STATUS,C ; if next bit of the multiplier is not set RLF MULT1,F BTFSS STATUS,C GOTO prod_loop ; then do nothing MOVF MULT2L,W ; else add the multiplicand to the product ADDWF PRODL,F BTFSC STATUS,C INCF PRODH,F MOVF MULT2H,W ADDWF PRODH,F GOTO prod_loop division ; now divide by REFV CLRF QUOT ; poor man's division by direct counting MOVF PRODH,W ; 16-bit numerator, 8-bit denominator MOVWF REMH ; 8-bit quotient and 16-bit remainder MOVF PRODL,W ; (but only the LSB will be non-zero) MOVWF REML div_loop MOVF REFV,W ; test for remainder >= REFV SUBWF REML,W MOVLW 0 BTFSS STATUS,C ADDLW 1 SUBWF REMH,W BTFSS STATUS,C ; if remainder < REFV GOTO scale_finish ; then finish INCF QUOT,F ; else increment the quotient MOVF REFV,W ; and now remainder := remainder - REFV SUBWF REML,F MOVLW 0 BTFSS STATUS,C ADDLW 1 SUBWF REMH,F GOTO div_loop scale_finish MOVF QUOT,W ; final processing MOVWF BINNUM ; BINNUM is the callibrated battery voltage BCF STATUS,C ; round up if REML - REFV / 2 > 0 RRF REFV,W SUBWF REML,W BTFSC STATUS,C INCF BINNUM,F RETURN bin_to_bcd CLRF BCDNUM ; set up volts_loop ; counting to obtain the volts digit MOVLW VOLT SUBWF BINNUM,W BTFSS STATUS,C GOTO decivolts MOVWF BINNUM INCF BCDNUM,F GOTO volts_loop decivolts ; counting to obtain the decivolts digit SWAPF BCDNUM,F ; volts goes in the upper nybble decivolts_loop MOVLW DECIV SUBWF BINNUM,W BTFSS STATUS,C GOTO centivolts MOVWF BINNUM INCF BCDNUM,F GOTO decivolts_loop centivolts ; round up if centivolts > 5 MOVLW d'5' SUBWF BINNUM,W BTFSC STATUS,C INCF BCDNUM,F RETURN display ; transfer BCDNUM to the shift register MOVLW d'8' ; set up counter MOVWF COUNTER CLRF sGPIO ; clear GPIO BSF OUTLTCH ; but leave the output latch enabled MOVF sGPIO,W ; without it, we would only see a blur MOVWF GPIO ; as the output register changes display_loop RLF BCDNUM,F ; read one bit from BCDNUM BCF OUTDAT ; and copy this bit to OUTDAT BTFSC STATUS,C BSF OUTDAT MOVF sGPIO,W ; update GPIO MOVWF GPIO BSF OUTCLK ; OUTCLK up to write to the shift register MOVF sGPIO,W MOVWF GPIO BCF OUTCLK ; and OUTCLK down MOVF sGPIO,W MOVWF GPIO DECF COUNTER,F ; decrease counter BTFSS STATUS,Z ; if counter did not reach zero GOTO display_loop ; then continue in the loop display_finish CLRF sGPIO ; else we finished MOVF sGPIO,W ; so commit the output MOVWF GPIO ; by disabling the output latch temporarily BSF OUTLTCH ; and then enable it again MOVF sGPIO,W MOVWF GPIO RETURN END