; 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