;******************************************************************************
; 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