; Wh Energy Display
; Filename: whd.asm
; The current into my GTI is measured and integrated (as power) to get the generated fed-in energy.
; It is stored in EEPROM when below threshold (end of cycling session).
; GPL Copyleft 2021 pic@polonai.se
; v0.0 210319 New project: Lamp Test
; v0.1 210319 Read ADC
; v0.2 210320 Calculate session energy from sampled power
; v0.3 210320 Convert hex to decimal display [Wmin]
; v0.4 210321 Convert hex to decimal display [Wh]
; v0.5 210321 TOTAL/SESSION display (Multiplex)
; v0.6 210322 EEPROM storage of totals (TOTHI, TOTLO, TOT3)
; v0.7 210326 Display session energy in xxx.x Wh, not x.xxx kWh (max 0.127 kWh)
; v1.0 210326 Release
; v1.1 210423 Overflow test TOTLO (STATUS,Z was STATUS,C)
LIST P=16F818, F=INHX8M
#include <p16f818.inc>
__CONFIG 0x2738
; _WDT_OFF & _PWRTE_OFF & _INTRC_IO & _MCLR_ON & _BODEN_OFF & _LVP_OFF & _CPD_ON & _WRT_ENABLE_OFF & _DEBUG_OFF & _CCP1_RB2 & _CP_ALL
ERRORLEVEL -302 ; remove compiler message about using proper bank
; Equates
RESET_V EQU 0x00 ; Address of RESET Vector
OSC_FREQ EQU D'4000000' ; Oscillator Frequency is 4 MHz
; Registers
S0 EQU 0x20 ; Ones (10^0) Session [Wh]
S1 EQU 0x21 ; Tens
S2 EQU 0x22 ; Hundreds
S3 EQU 0x23 ; Thousands (never used)
TEMP0 EQU 0x24 ; Temp register 0
TEMP1 EQU 0x25 ; Temp register 1
LTCNTR EQU 0x26 ; Lamp Test Counter
SESLO EQU 0x27 ; Low byte session energy [Wmin]
SESHI EQU 0x28 ; High byte session energy
TOTLO EQU 0x29 ; Low byte interim total energy [Wh]
TOTHI EQU 0x2A ; High byte interim total energy
PWRLO EQU 0x2B ; Low byte instantaneous power (energy in Ws)
PWRHI EQU 0x2C ; High byte instantaneous power (energy in Ws)
PWRCNTR EQU 0x2D ; Keep track of the summation of IMEAS
T0 EQU 0x30 ; Ones (10^0) Total [Wh]
T1 EQU 0x31 ; Tens
T2 EQU 0x32 ; Hundreds
T3 EQU 0x33 ; Thousands, only used to check for decimal overflow of interim total
TOT3 EQU 0x34 ; Displayed thousands
SESSION EQU 0x35 ; Session [Wmin] converted to [Wh]
SESPREV EQU 0x36 ; Previous value needed for totals
EEBUF EQU 0x37 ; Byte to write into EEPROM
DIVREG0 EQU 0x38 ; LSB divisor
DIVREG1 EQU 0x39 ; MSB divisor
MODREG0 EQU 0x3A ; LSB dividend
MODREG1 EQU 0x3B ; MSB dividend
COUNT EQU 0x3C ; Used in divider function
FLAGS EQU 0x7F ; General Purpose Flags
; Defines
;A/D input PORTA,0 has no name defined
#define DIG1 PORTA,1 ; Display 1 (MSB) Anode (via PNP like BC857B)
#define DIG2 PORTA,2 ; Display 2 Anode
#define DIG3 PORTA,3 ; Display 3 Anode
#define DIG4 PORTA,4 ; Display 4 (LSB) Anode
#define TETS PORTA,6 ; Test output
#define MODE PORTA,7 ; SESSION/TOTAL display (SESSION=1)
#define SEGA PORTB,0 ; Display segment a
#define SEGB PORTB,1 ; Display segment b
#define SEGC PORTB,2 ; Display segment c
#define SEGD PORTB,3 ; Display segment d
#define SEGE PORTB,4 ; Display segment e
#define SEGF PORTB,5 ; Display segment f
#define SEGG PORTB,6 ; Display segment g
#define SEGDP PORTB,7 ; Display segment dp
#define LAMPTST FLAGS,0 ; Lamp Test at poweron
#define STARTED FLAGS,1 ; Started generating power
#define SESEND FLAGS,2 ; Stopped generating power, store totals in EEPROM then clear
; EEPROM DATA, make sure these locations are cleared
ORG 0x2100
DE 0x00 ; TOTHI
DE 0x18 ; TOTLO
DE 0x00 ; TOT3
ORG 0x00
GOTO Main
ORG 0x04
; Interrupt handler
BTFSC INTCON,TMR0IF ; Every 4.1 ms
GOTO UPDATE ; Measure current (IMEAS)
BTFSC PIR1,TMR2IF ; Check to see if the interrupt was caused by a TIMER2 PR2 match
GOTO Multiplex ; 4-ish ms multiplex time for LED display
CLRF PIR1
RETFIE ; If none of the above is true, then just leave the interrupt handler (shouldn't occur)
Multiplex
BCF PIR1,TMR2IF ; Clear Interrupt source
BTFSC LAMPTST ; Lamp Test ongoing?
GOTO LAMPTEST ; Yes
; Get the data which we want displayed
; Test Digit
BTFSS DIG1 ; Digit 1?
GOTO DIG1HANDLER
BTFSS DIG2 ; Digit 2?
GOTO DIG2HANDLER
BTFSS DIG3 ; Digit 3?
GOTO DIG3HANDLER
; We're at the 4th digit (RA4)
BSF DIG4 ; Switch off 4th digit
MOVFW S1 ; Get value to be displayed on 3rd digit
BTFSS MODE
MOVFW T1
CALL bin2seg
MOVWF PORTB
BTFSC MODE
BCF SEGDP ; Switch on decimal point if session energy (xxx.x Wh)
BCF DIG3 ; Switch on 3rd digit
RETFIE
DIG3HANDLER
BSF DIG3 ; Switch off 3rd digit
MOVFW S2 ; Get value to be displayed on 2nd digit
BTFSS MODE
MOVFW T2
CALL bin2seg
MOVWF PORTB
BCF DIG2 ; Switch on 2nd digit
RETFIE
DIG2HANDLER
BSF DIG2 ; Switch off 2nd digit
CLRW ; MSB is always zero
BTFSS MODE
MOVFW TOT3 ; Get value to be displayed on 1st digit (MSB)
CALL bin2seg
MOVWF PORTB
BTFSS MODE
BCF SEGDP ; Switch on decimal point if total energy (x.xxx kWh)
BCF DIG1 ; Switch on 1st digit
RETFIE
DIG1HANDLER
BSF DIG1 ; Switch off 1st digit
MOVFW S0 ; Get value to be displayed on 4th digit (LSB)
BTFSS MODE
MOVFW T0
CALL bin2seg
MOVWF PORTB
BCF DIG4 ; Switch on 4th digit
RETFIE
; 7-segment lookup
bin2seg ADDWF PCL, F ; Jump into the lookup table
RETLW 0xC0 ; Return segment code for 0
RETLW 0xF9 ; Return segment code for 1
RETLW 0xA4 ; Return segment code for 2
RETLW 0xB0 ; Return segment code for 3
RETLW 0x99 ; Return segment code for 4
RETLW 0x92 ; Return segment code for 5
RETLW 0x82 ; Return segment code for 6
RETLW 0xF8 ; Return segment code for 7
RETLW 0x80 ; Return segment code for 8
RETLW 0x90 ; Return segment code for 9
RETLW 0x88 ; Return segment code for A
RETLW 0x83 ; Return segment code for b
RETLW 0xC6 ; Return segment code for C
RETLW 0xA1 ; Return segment code for d
RETLW 0x86 ; Return segment code for E
RETLW 0x8E ; Return segment code for F
RETLW 0xA7 ; Return segment code for c
RETLW 0xC7 ; Return segment code for L
RETLW 0xAB ; Return segment code for n
RETLW 0xA3 ; Return segment code for o
RETLW 0xAF ; Return segment code for r
RETLW 0xFF ; Return segment code for blank
RETLW 0x7F ; Decimal Point Only
LAMPTEST
CALL NEXTDIGIT
BTFSC DIG1
RETFIE
DECFSZ LTCNTR,F
RETFIE
MOVLW 0x20
MOVWF LTCNTR
BTFSS PORTB,0
GOTO OFFSEGA
BTFSS PORTB,1
GOTO OFFSEGB
BTFSS PORTB,2
GOTO OFFSEGC
BTFSS PORTB,3
GOTO OFFSEGD
BTFSS PORTB,4
GOTO OFFSEGE
BTFSS PORTB,5
GOTO OFFSEGF
BTFSS PORTB,6
GOTO OFFSEGG
BSF PORTB,7
BCF LAMPTST
RETFIE
OFFSEGA ; Switch off segment a, switch on segment b
BSF PORTB,0
BCF PORTB,1
RETFIE
OFFSEGB ; Switch off segment b, switch on segment c
BSF PORTB,1
BCF PORTB,2
RETFIE
OFFSEGC ; Switch off segment c, switch on segment d
BSF PORTB,2
BCF PORTB,3
RETFIE
OFFSEGD ; Switch off segment d, switch on segment e
BSF PORTB,3
BCF PORTB,4
RETFIE
OFFSEGE ; Switch off segment e, switch on segment f
BSF PORTB,4
BCF PORTB,5
RETFIE
OFFSEGF ; Switch off segment f, switch on segment g
BSF PORTB,5
BCF PORTB,6
RETFIE
OFFSEGG ; Switch off segment g, switch on segment dp
BSF PORTB,6
BCF PORTB,7
RETFIE
NEXTDIGIT
BTFSS DIG1 ; Digit 1 ON?
GOTO ONDIG2 ; Yes, switch it OFF and switch Digit 2 ON
BTFSS DIG2 ; Digit 2?
GOTO ONDIG3
BTFSS DIG3 ; Digit 3?
GOTO ONDIG4
BSF DIG4
BCF DIG1
RETURN
ONDIG2
BSF DIG1
BCF DIG2
RETURN
ONDIG3
BSF DIG2
BCF DIG3
RETURN
ONDIG4
BSF DIG3
BCF DIG4
RETURN
UPDATE ; Every 4.1 ms
BCF INTCON,TMR0IF ; Clear interrupt flag
BSF ADCON0,GO ; Start A/D (read IMEAS)
CONV0
NOP
BTFSC ADCON0,GO ; Test if A/D conversion done
GOTO CONV0
BSF STATUS,RP0 ; Select Bank 1
MOVFW ADRESL ; Get A/D result
BCF STATUS,RP0 ; Select Bank 0
ADDWF PWRLO,F ; Add to lower byte
BTFSS STATUS,C ; Carry set?
GOTO $+2 ; No
INCF PWRHI,F ; Yes, increment higher byte
DECFSZ PWRCNTR,F ; Power summation done?
RETFIE ; No
MOVFW PWRLO ; Get lower byte for accumulate session
MOVWF TEMP0
RRF PWRHI,F ; Rotate lower nibble PWRHI into high nibble TEMP
RRF TEMP0,F
RRF PWRHI,F
RRF TEMP0,F
RRF PWRHI,F
RRF TEMP0,F
RRF PWRHI,F
RRF TEMP0,F
MOVLW 0x08 ; Ignore noise when idle
SUBWF TEMP0,W
BTFSS STATUS,C ; equal or greater?
GOTO DISCARD ; No, discard
BSF STARTED
RRF TEMP0,F
RRF TEMP0,F
RRF TEMP0,F ; Divide by 8
MOVLW 0x1F
ANDWF TEMP0,W ; Blank three highest bits
ADDWF SESLO,F ; Add to session aggregate
BTFSS STATUS,C ; Carry set?
GOTO CLRPWR ; No
INCF SESHI,F ; Yes, increment higher byte session
GOTO CLRPWR
DISCARD
BTFSC STARTED
BSF SESEND ; Session End: write totals to EEPROM
CLRPWR ; Destroy power sample
CLRF PWRHI
CLRF PWRLO
MOVFW SESHI
MOVWF MODREG1 ; Set up for conversion Wmin to Wh for session (1)
MOVWF SESSION
BCF STATUS,C ; Clear carry to prevent spurions ones in SESSION
RRF SESSION,F ; Divide [SESHI:SESLO] by 512 to get [Wh] for totals
MOVFW SESLO
MOVWF MODREG0 ; Set up for conversion Wmin to Wh for session (2)
CALL DIV16_6 ; Get result from division by 0x33, max energy is 25.5 Wh (overflow to 0)
CALL Bin2BCD ; Calculate BCD values to display session and total energy values [Wh]
RETFIE
; END Interrupthandlers
Main
;Init stuff
BCF STATUS,RP0 ; Go to bank 0
BCF STATUS,RP1 ; Go to bank 0
CLRF INTCON
MOVWF 0xFF
MOVFW PORTB ; Make sure LEDs are off
BSF STATUS,RP0 ; Go to bank 1
MOVLW B'10000001' ; AN0, RA7 Input
MOVWF TRISA
CLRF TRISB ; Set port B as all outputs
MOVLW B'11001110' ; AN0 Analog input, AN1-4 Digital IO, ADRESL only
MOVWF ADCON1
MOVLW B'00000011' ; Prescaler 1:16
MOVWF OPTION_REG
MOVLW B'01100000' ; 4 MHz clock
MOVWF OSCCON
BCF STATUS,RP0 ; Go back to bank 0
MOVLW B'01000001' ; AD conv ON, AN0 Selected, Fosc/8 (T(AD)=2us)
MOVWF ADCON0
BSF T1CON,T1CKPS1 ; These set the TIMER1 prescaler. Here are the possible values:
BSF T1CON,T1CKPS0 ; 00=1:1 01=1:2 10=1:4 11=1:8
BCF T1CON,T1OSCEN ; Turn off the TIMER1 oscillator to save power (we don't need it because we're using the internal oscillator)
BCF T1CON,TMR1CS ; Select the internal oscillator for TIMER1
BSF T1CON,TMR1ON ; Enable TIMER1
MOVLW B'00000101'
MOVWF T2CON ; Pre/postscaler 1:4, Timer 2 enabled
; Enable interrupts
BCF INTCON,INTF ; Clear INTF flag before enabling interrupts
BCF INTCON,TMR0IF ; Clear TMR0IF (TIMER0 Interrupt Flag) before enabling interrupts
CLRF PIR1 ; Clear peripheral interrupt flags register
BCF INTCON,INTE ; Clear interrupt on RB0/INT pin (an External interrupt)
BSF INTCON,TMR0IE ; Enable Timer 0 interrupt
BSF INTCON,PEIE ; Enable PEIE (PEripheral Interrupt Enable - for TIMER2)
BSF STATUS,RP0 ; Go to bank 1
CLRF PIE1
BSF PIE1,TMR2IE ; Enable interrupt on TIMER2 overflow (multiplexer)
BCF STATUS,RP0 ; Go to bank 0
; INIT Registers
CLRF FLAGS
CLRF SESSION
CLRF SESPREV
CLRF SESHI
CLRF SESLO
CLRF TMR1H
CLRF TMR1L
CLRF TMR2
; Restore Totals from EEPROM
CLRW
CALL EEPROMREAD
BCF STATUS,RP0
MOVWF TOTHI ; Restored TOTHI
MOVLW 0x01
CALL EEPROMREAD
BCF STATUS,RP0
MOVWF TOTLO ; Restored TOTLO
MOVLW 0x02
CALL EEPROMREAD
BCF STATUS,RP0
MOVWF TOT3 ; Restored TOT3
MOVLW B'00011100'
MOVWF PORTA ; Digit 1 on (lamp test)
MOVLW 0xFE
MOVWF PORTB ; Blank display except segment a (lamptest)
MOVLW 0x20
MOVWF LTCNTR ; First Lamp Test period
BSF LAMPTST ; Run Lamp Test (Multiplex)
BSF INTCON,GIE ; Enable global interrupts
WaitForInterrupt
GOTO WaitForInterrupt
; Leaner and Meaner, 36 instructions
; Ideal for displaying A/D values up to 4095
; Keep here because of lookup table above-
; Source: http://www.piclist.com/techref/microchip/math/radix/b2bu-10b4d-eag.htm
Bin2BCD ; 12 bit routine because 10 bit routine was borked and I didn't want to fix it...
; First calculate totals
MOVFW SESSION
SUBWF SESPREV,W
BTFSC STATUS,Z ; Has SESSION increased? Note that it is not likely for this to have increased by more than one in one second
GOTO CONV1 ; No
INCF TOTLO,F
BTFSC STATUS,Z ; Overflow? (register is zero)
INCF TOTHI,F
MOVFW SESSION
MOVWF SESPREV
CONV1
MOVLW 0x14
MOVWF S0
MOVLW 0xD0
MOVWF S1
MOVLW 0xDB
movwf S2
swapf DIVREG0,W
iorlw 0xF0 ;w=H1-16
addwf S1,f ;S1=H2*3+H1-64
addwf S0,f ;S0=H2+H1+4, C=1
rlf S0,f ;S0=(H2+H1)*2+9, C=0
comf S0,f ;S0=-(H2+H1)*2-10
rlf S0,f ;S0=-(H2+H1)*4-20
movf DIVREG0,w
andlw 0x0F ;w=H0
addwf S0,f ;S0=H0-(H2+H1)*4-20 Done!
rlf S1,f ;C=0, S1=H2*6+H1*2-128 Done!
movlw D'5'
movwf S3
movlw D'10'
mod0
addwf S0,f ;D(X)=D(X)mod10
decf S1,f ;D(X+1)=D(X+1)+D(X)div10
skpc
goto mod0
mod1
addwf S1,f
decf S2,f
skpc
goto mod1
mod2
addwf S2,f
decf S3,f
skpc
goto mod2
; Calculate total energy BCD
movf TOTHI,w
iorlw 0xF0 ;w=H2-16
movwf T1 ;S1=H2-16
addwf T1,f ;S1=H2*2-32
addwf T1,f ;S1=H2*3-48
movwf T2 ;S2=H2-16
addlw -D'5' ;w=H2-21
addwf T2,f ;S2=H2*2-37 Done!
addlw D'41' ;w=H2+20
movwf T0 ;S0=H2+20
swapf TOTLO,w
iorlw 0xF0 ;w=H1-16
addwf T1,f ;S1=H2*3+H1-64
addwf T0,f ;S0=H2+H1+4, C=1
rlf T0,f ;S0=(H2+H1)*2+9, C=0
comf T0,f ;S0=-(H2+H1)*2-10
rlf T0,f ;S0=-(H2+H1)*4-20
movf TOTLO,w
andlw 0x0F ;w=H0
addwf T0,f ;S0=H0-(H2+H1)*4-20 Done!
rlf T1,f ;C=0, S1=H2*6+H1*2-128 Done!
movlw D'5'
movwf T3
movlw D'10'
mod0t
addwf T0,f ;D(X)=D(X)mod10
decf T1,f ;D(X+1)=D(X+1)+D(X)div10
skpc
goto mod0t
mod1t
addwf T1,f
decf T2,f
skpc
goto mod1t
mod2t
addwf T2,f
decf T3,f
skpc
goto mod2t
; Test for overflow (999->000)
OVF
BTFSS T3,0 ; T3 = 1?
GOTO PRERETURN ; No
CLRF T3
INCF TOT3,F
CLRF TOTHI
CLRF TOTLO
MOVLW 0x09
SUBWF TOT3,W ; Test for overflow (10,000 Wh)
BTFSS STATUS,Z ; Overflow?
GOTO PRERETURN ; No
CLRF TOT3
PRERETURN
BTFSS SESEND ; Session ended?
RETURN ; No
; Session ended, store totals in EEPROM
MOVFW TOTHI
MOVWF EEBUF
CLRW ; EEPROM Address in W
CALL EEPROMWRITE
MOVFW TOTLO
MOVWF EEBUF
MOVLW 0x01 ; EEPROM Address in W
CALL EEPROMWRITE
MOVFW TOT3
MOVWF EEBUF
MOVLW 0x02 ; EEPROM Address in W
CALL EEPROMWRITE
BCF STARTED
BCF SESEND
RETURN
EEPROMREAD
; W contains address to read, at return contains read data
BANKSEL EEADR ; Select Bank of EEADR
MOVWF EEADR ; Data Memory Address to read from W
BANKSEL EECON1 ; Select Bank of EECON1
BCF EECON1, EEPGD ; Point to Data memory
BSF EECON1, RD ; EE Read
BANKSEL EEDATA ; Select Bank of EEDATA
MOVF EEDATA, W ; W = EEDATA
RETURN
EEPROMWRITE
; W contains address to write, data is in EEBUF
BANKSEL EECON1 ; Select Bank of EECON1
BTFSC EECON1, WR ; Wait for write
GOTO $-1 ; to complete
BANKSEL EEADR ; Select Bank of EEADR
MOVWF EEADR ; Data Memory Address to write
BANKSEL EEBUF
MOVFW EEBUF ; Data to write from buffer
BANKSEL EEDATA
MOVWF EEDATA ; Data Memory Value to write
BANKSEL EECON1 ; Select Bank of EECON1
BCF EECON1, EEPGD ; Point to DATA memory
BSF EECON1, WREN ; Enable writes
MOVLW 0x55
MOVWF EECON2 ; Write 55h
MOVLW 0xAA
MOVWF EECON2 ; Write AAh
BSF EECON1, WR ; Set WR bit to begin write
BCF EECON1, WREN ; Disable writes
BANKSEL PORTB ; Select BANK0
RETURN
; Division function
;;DIVIDE A 16-BIT NUMBER BY A 6-BIT WREG. RESULT IS 11-BIT WIDE
;
; Source: http://www.piclist.com/techref/microchip/math/div/16by8lz.htm
DIV16_6
MOVLW 0x33 ; Preload divisor
MOVWF TEMP1
CLRF TEMP0
BCF STATUS,C
RLF TEMP1,F
RLF TEMP1,F
MOVLW 0x0B
MOVWF COUNT
CLRF DIVREG0 ; Previously SESSION
CLRF DIVREG1
DIV16_6_LOOP
MOVF TEMP0,W ;W=MOD-DIVISOR
SUBWF MODREG0,W
MOVF TEMP1,W
BTFSS STATUS,C ;PROCESS BORROW
ADDLW 1
SUBWF MODREG1,W
BTFSS STATUS,C ;IF W<0
GOTO DIV16_6_NOSUB
MOVF TEMP0,W ;MOD=MOD-DIVISOR
SUBWF MODREG0,F
BTFSS STATUS,C
DECF MODREG1,F
MOVF TEMP1,W
SUBWF MODREG1,F
BSF STATUS,C
DIV16_6_NOSUB
RLF DIVREG0,F ;DIV << 1 + CARRY
RLF DIVREG1,F
BCF STATUS,C ;DIVISOR>>=1
RRF TEMP1,F
RRF TEMP0,F
DECFSZ COUNT,F
GOTO DIV16_6_LOOP
RETURN
END