;********************************************************************************* ; W A T T M E T E R ;********************************************************************************* ; Bruce Abbott bhabbott@paradise.net.nz ; ; - Measures up to 25 Volts and 50 Amps ; - Displayed on 16 character x 2 line LCD ; - Calculates Watts and Ampere Hours ; - Measures propeller rpm, 1 to 7 blades ; - Hold function. ; ; ;--------------------------------------------------------------------------------- ; Changes:- ; 2008-12-01 V0.095 BLDBTN = PORTA,7 to match PCB layout (16F88 only) ; 2008-12-21 V0.096 enabled weak pullups (required for Page and Hold buttons) ; 2008-12-22 V0.097 enabled digital I/O on RB6,7 (16F88) ; 2009-01-05 V0.098 increased blades from 2~6 to 1~7 (for brushless rpm sensor) ; 2009-02-04 support 16F876 ; 2009-02-06 V0.099 fixed bug:- ADCON1 not set properly on 16f88 ; 2010-02-07 V0.100 fixed bug:- was checking for max Volts with MAX_AMPS flag! ; 2010-10-17 V0.101 increased delay after switching A/D channel ;--------------------------------------------------------------------------------- radix dec ; default number format is DECIMAL!!! ;#DEFINE SIMULATE ; for simulator debugging only. Must be disabled for release!!! ;#DEFINE LCD8x2 1 ; using 8x2 or 16x1 LCD display #DEFINE REFRESH_DELAY1 140 ; Timer0 initial load value, to slow down display refresh #DEFINE REFRESH_DELAY2 39 ; Timer0 reload value (to complete display refresh delay) ; processor PIC16F88 ; processor PIC16f870 ; processor PIC16F876 ifdef __16F88 include P16F88.inc else #DEFINE TMR0IF T0IF endif ifdef __16F870 include P16f870.inc endif ifdef __16F876 include P16f876.inc endif errorlevel -302,-305 ; not reporting bank<>0, dest=file #DEFINE FALSE 0 #DEFINE movfw ERROR movwf? ifdef __16F88 __config _CONFIG1,_BODEN_OFF&_WDT_OFF&_LVP_OFF&_MCLR_OFF&_PWRTE_ON&_INTRC_IO else ; 16F870 __config _BODEN_OFF&_WDT_OFF&_LVP_OFF&_PWRTE_ON&_HS_OSC endif ; I/O pin assignments ifdef __16F88 #DEFINE PGBTN PORTB,6 ; page button (S3) #DEFINE HDBTN PORTB,7 ; hold button (S2) #DEFINE BLDBTN PORTA,7 ; blades button (S1) #DEFINE RPM_IN PORTB,0 ; rpm input #DEFINE LCD_D7 PORTA,4 ; \ #DEFINE LCD_D6 PORTB,1 ; | #DEFINE LCD_D5 PORTB,2 ; | LCD panel #DEFINE LCD_D4 PORTB,3 ; | #DEFINE LCD_E PORTB,4 ; | #DEFINE LCD_RS PORTB,5 ; / else ; 16f870 etc. #DEFINE PGBTN PORTC,0 ; page button #DEFINE HDBTN PORTC,1 ; hold button #DEFINE BLDBTN PORTC,3 ; blades button #DEFINE RPMIN PORTC,2 ; rpm input #DEFINE LCD_D7 PORTB,0 ; \ #DEFINE LCD_D6 PORTB,1 ; | #DEFINE LCD_D5 PORTB,2 ; | LCD panel #DEFINE LCD_D4 PORTB,3 ; | #DEFINE LCD_E PORTB,4 ; | #DEFINE LCD_RS PORTB,5 ; / endif ;==================================================================== ; Macros to create variables in RAM ; ByteAddr SET 32 ; user RAM starts here BYTE MACRO ByteName ByteName EQU ByteAddr ByteAddr SET ByteAddr+1 ENDM WORD MACRO WordName WordName EQU ByteAddr ByteAddr SET ByteAddr+2 ENDM LONG MACRO LongName LongName EQU ByteAddr ByteAddr SET ByteAddr+4 ENDM ;==================================================================== ; 16 bit macros (Big-Endian) ; mov16 MACRO src,dst movf src,w movwf dst movf src+1,w movwf dst+1 ENDM movi16 MACRO lit,dst movlw HIGH(lit) movwf dst movlw LOW(lit) movwf dst+1 ENDM tst16 MACRO src tstf src+1 skpz goto $+1 tstf src ENDM cmpi16 MACRO src,lit movlw (lit>>8)&0xff subwf src,w skpz goto $+3 movlw lit&0xff subwf src+1,w ENDM clr16 MACRO dst clrf dst clrf dst+1 ENDM inc16 MACRO dst incf dst+1 skpnz incf dst ENDM dec16 MACRO dst tstf dst+1 skpnz decf dst decf dst+1 ENDM add16 MACRO src,dst movf src+1,w addwf dst+1 skpnc incf dst movf src,w addwf dst ENDM addi16 MACRO lit,dst movf LOW(lit),w addwf dst+1 skpnc incf dst movf HIGH(lit),w addwf dst ENDM sub16 MACRO src,dst movf src+1,w subwf dst+1 movf src,w skpc incf src,w subwf dst ENDM subi16 MACRO lit,dst movlw LOW(lit) subwf dst+1 movlw HIGH(lit) skpc movlw (HIGH(lit))+1 subwf dst ENDM rr16 MACRO dst rrf dst rrf dst+1 ENDM rl16 MACRO dst rlf dst+1 rlf dst ENDM com16 MACRO dst comf dst+1 comf dst ENDM ;--------------------------------------------- ; 16->32 bit macros (Big-Endian) ; ; move 16 bit to 32 bit mov1632 MACRO src,dst clrf dst clrf dst+1 movf src,w movwf dst+2 movf src+1,w movwf dst+3 ENDM ; add 16 bit to 32 bit add1632 MACRO src,dst ; src(15:0), dst(31:0) movf src+1,w addwf dst+3 ; dst = dst + src(7:0) skpc goto $+6 incf dst+2 skpnz incf dst+1 skpnz incf dst movf src,w addwf dst+2 ; dst = dst + src (15:8) skpc goto $+4 incf dst+1 skpnz incf dst ENDM ;--------------------------------------------------- ; 32 bit macros (Big-Endian) clr32 MACRO dst clrf dst clrf dst+1 clrf dst+2 clrf dst+3 ENDM mov32 MACRO src,dst movf src,w movwf dst movf src+1,w movwf dst+1 movf src+2,w movwf dst+2 movf src+3,w movwf dst+3 ENDM movi32 MACRO lit,dst movlw lit>>24 movwf dst movlw (lit>>16)&0xff movwf dst+1 movlw (lit>>8)&0xff movwf dst+2 movlw lit&0xff movwf dst+3 ENDM ; 32 bit left shift rr32 MACRO dst rrf dst rrf dst+1 rrf dst+2 rrf dst+3 ENDM ; 32 bit right shift rl32 MACRO dst rlf dst+3 rlf dst+2 rlf dst+1 rlf dst ENDM ; 32 bit 1's complememt com32 MACRO dst comf dst+3 comf dst+2 comf dst+1 comf dst ENDM ; test 32 bit for zero tst32 MACRO src tstf src+3 skpz goto $+8 tstf src+2 skpz goto $+5 tstf src+1 skpz goto $+1 tstf src ENDM ; add 32 bit to 32 bit add32 MACRO src,dst movf src+3,w addwf dst+3 skpc goto $+6 incf dst+2 skpnz incf dst+1 skpnz incf dst movf src+2,w addwf dst+2 skpc goto $+4 incf dst+1 skpnz incf dst movf src+1,w addwf dst+1 skpnc incf dst movf src,w addwf dst ENDM ; compare 32 bit to literal cmpi32 MACRO src,lit movlw (lit>>24)&0xff subwf src,w skpz goto $+11 movlw (lit>>16)&0xff subwf src+1,w skpz goto $+7 movlw (lit>>8)&0xff subwf src+2,w skpz goto $+3 movlw lit&0xff subwf src+3,w ENDM ;==================================================================== ; Five digit decimal number BYTE TenT BYTE Thou BYTE Hund BYTE Tens BYTE Ones BYTE dp ; position of decimal point ; 16 bit binary number WORD Number ; 16 bit register 'a' WORD aa ; 16 bit register 'b' WORD bb ; 16 bit register 'c' WORD cc ; 32 bit register 'dd' LONG dd ; Volts WORD Volts ; Volts*100 (0.01V resolution) ; Amps WORD Amps ; Amps*100 (0.01A resolution) ; Watts WORD Watts ; Watts*10 (0.1W resolution) ; Amps Accumulated LONG AmpSum ; addition of all Amp readings ; AmpereHours WORD AmpHours ; Amps * Hours ; copy of CCPR1 register WORD CCPR ; recorded period = 1/rpm WORD Period ; previous period WORD OldPeriod ; rpm LONG rpm ; revolutions per minute LONG oldrpm ; previous rpm reading ; number of prop blades BYTE NumBlades ; Blade button debounce timer BYTE Blade_Timer ; page button debounce timer BYTE Page_Timer ; Zero Amps WORD ZeroAmps ; Number of Readings to Average BYTE Readings ; pointer to character in message string BYTE msg_pointer ; display paging BYTE pagenum ; display refresh timer BYTE ref_timer ; misc variables BYTE Temp BYTE Temp1 BYTE Temp2 ; boolean flags BYTE Flags #DEFINE GOT_ZERO 0 ; Amps zero'd #DEFINE NEG_AMPS 1 ; Amps < zero #DEFINE MAX_VOLTS 2 ; Volts >= max #DEFINE MAX_AMPS 3 ; Amps >= max #DEFINE PAGE_DWN 4 ; Page button was pressed #DEFINE LEAD_ZERO 5 ; leading zeros blanked #DEFINE LOCK_P 6 ; CCPR variable in use, don't touch! #DEFINE CCP_RUN 7 ; detected 1st blade, now waiting for 2nd BYTE Flags2 #DEFINE BLD_DWN 0 ; blade button was pressed ; reset vector ;//////////////////////////////////////////////////////////////////// ORG 0 goto Start ; interrupt vector ;//////////////////////////////////////////////////////////////////// ORG 4 goto Interrupt ;//////////////////////////////////////////////////////////////////// ORG 8 dt "WOTMTR" ifdef __16F88 dt "88" endif ifdef __16F870 dt "70" endif ifdef __16F876 dt "76" endif dt "-V0.100-" ;-------------------------------------------------------------------- ; get message characters ; ; Input: W = address of character in message ; Output: W = character ; ; NOTE: Table read:- Must NOT traverse a program page boundary! ; get_message: movwf PCL signon_msg: RETLW 'W' RETLW 'O' RETLW 'T' RETLW ' ' RETLW 'M' RETLW 'e' RETLW 't' RETLW 'e' RETLW 'r' RETLW ' ' RETLW 'V' RETLW '0' RETLW '.' RETLW '1' RETLW '0' RETLW '0' RETLW 0 ;-------------------------------------------------------------------- ; Interrupt Handler ; WorkSave = 0x7f StatSave = 0x7e Interrupt: movwf WorkSave swapf STATUS,w ; save working registers movwf StatSave clrf STATUS ; bank 0 btfss PIR1,CCP1IF ; CCP1 capture Int? goto TMR1_Int ; No CCP1_Int: bcf T1CON,TMR1ON ; yes, stop timer1 movlw 4 movwf TMR1L ; timer1 = 0 + overhead clrf TMR1H bsf T1CON,TMR1ON ; restart timer1 btfsc Flags,CCP_RUN ; waiting for 1st blade ? goto Capture ; No bsf Flags,CCP_RUN ; Yes, now waiting for second blade goto Capture_Done Capture: btfsc Flags,LOCK_P ; CCPR variable in use ? goto Capture_Done ; Yes, don't touch! movf CCPR1L,w ; No, safe to update movwf CCPR+1 movf CCPR1H,w ; update CCPR variable movwf CCPR Capture_Done: bcf PIR1,CCP1IF ; reset Capture Int flag TMR1_Int: btfss PIR1,TMR1IF ; Timer1 Overflow int? goto Int_done ; No bcf T1CON,TMR1ON ; yes, stop timer1 clrf TMR1L clrf TMR1H ; timer1 = 0 bsf T1CON,TMR1ON ; restart timer1 btfsc Flags,LOCK_P ; CCPR variable in use ? goto TMR1_Done ; yes, don't touch! clrf CCPR ; no, clrf CCPR+1 ; CCPR = 0 TMR1_Done: bcf Flags,CCP_RUN ; now waiting for 1st blade bcf PIR1,TMR1IF ; reset Timer1 Overflow Flag Int_done: swapf StatSave,W movwf STATUS ; restore working registers swapf WorkSave,F swapf WorkSave,w RETFIE ;-------------------------------------------------------------------- ; LCD Display Driver include lcd.asm ;-------------------------------------------------------------------- ; Display Sign-on Message on LCD ; show_signon: movlw 0 call Set_Cursor movlw signon_msg ; ; Display Text Message on LCD ; show_message: movwf msg_pointer call get_message ; get next char in message andlw 0xFF ; end of message ? skpnz return call Print_Char ; show character movlw LCDLENGTH subwf lcd_address,w ; if end of 1st line skpz ; then skip to 2nd line goto show_msg movlw LCDLINE2 call Set_Cursor show_msg: movf msg_pointer,W addlw 1 ; next character goto show_message ;-------------------------------------------------------------------- ; Wait W * 100uS ; waitx100: movwf Temp ; Temp = delay _w100: movlw (100-8)/4 _w101: addlw -1 skpnc ; wait 100uS goto _w101 clrwdt ; avoid watchdog timeout decfsz Temp goto _w100 ; next 100uS return ;-------------------------------------------------------------------- ; Wait W * 20mS ; waitx20k: movwf Temp ; Temp = w _w200: movlw 80 movwf Temp1 _w201: movlw 61 movwf Temp2 _w202: clrwdt ; avoid watchdog timeout decfsz Temp2 ; wait (4*61)+2 = 246uS goto _w202 nop nop decfsz Temp1 ; wait 250uS*80 = 20mS goto _w201 decfsz Temp ; wait w*20mS goto _w200 RETURN ;-------------------------------------------------------------------- ; Big-Endian 16x16 bit Multiply (32 bit result) ; ; dd = aa * bb ; ; Input: multiplier = aa(15:0), multiplicand = bb(15:0) ; ; Output: result = dd(31:0) ; ; Uses: aa, w ; Mult16: clrf dd clrf dd+1 clrf dd+2 movlw 0x80 movwf dd+3 m16_next: rrf aa,f rrf aa+1,f skpc goto m16_nobit_l movf bb+1,w addwf dd+2,f movf bb, w skpnc incfsz bb, w addwf dd+1, f skpnc incf dd, f clrc m16_nobit_l: btfss aa+1, 7 goto m16_nobit_h movf bb+1,w addwf dd+1,f movf bb, w skpnc incfsz bb, w addwf dd, f m16_nobit_h: rrf dd,f rrf dd+1,f rrf dd+2,f rrf dd+3,f skpc goto m16_next return ;-------------------------------------------------------------------- ; Big-Endian 32 x 16 bit integer divide ; ; dd = dd / aa ; ; Input: dividend = dd(31:0), divisor = aa(15:0) ; ; Output: quotient = dd(31:0), remainder = bb(15:0) ; ; Uses: bb, Temp, w ; Div32: movlw 32 ; 32 bits to divide movwf Temp clrf bb ; Clear remainder clrf bb+1 div32_loop: clrc ; Set quotient bit to 0 rlf dd+3 rlf dd+2 rlf dd+1 ; Shift dividend left into quotient rlf dd rlf bb+1 rlf bb skpnc ; Check for overflow goto div32_sd movf aa,w ; Compare partial remainder and divisor subwf bb,w skpz ; High Bytes equal? goto div32_gt movf aa+1,w ; Yes, compare Low Bytes subwf bb+1,w div32_gt: skpc ; Carry set if remainder >= divisor goto dvi32_lt div32_sd: movf aa+1,w ; Subtract divisor from partial remainder subwf bb+1 skpc ; Test for borrow decf bb ; Subtract borrow movf aa,w subwf bb bsf dd+3,0 ; Set quotient bit (quotient replaces dividend) dvi32_lt: decfsz Temp ; next bit goto div32_loop return ;-------------------------------------------------------------------- ; Big-Endian 16 bit Integer Divide by 12.8 ; ; Input: W->dst ; ; output: dst(15:0) = dst / 12.8 ; ; uses: FSR, Temp, w ; ; ; ALGORITHM: ; Add dst / 4 to dst ; Add dst / 16 to dst ; Shift dst right (Bit0 to carry) ; If carry set then increment dst, ie. round up Divx12: movwf FSR movf INDF, w movwf Temp incf FSR movf INDF, w decf FSR ;shift dst right twice clrc rrf INDF, f incf FSR rrf INDF, f decf FSR clrc rrf INDF, f incf FSR rrf INDF, f ;add dst to dst / 4 addwf INDF, f decf FSR movf Temp, w skpnc incfsz Temp, w addwf INDF, f ;shift dst right 3 times rrf INDF, f incf FSR rrf INDF, f decf FSR clrc rrf INDF, f incf FSR rrf INDF, f decf FSR clrc rrf INDF, f incf FSR rrf INDF, f decf FSR ;shift dst right once and increment if carry set clrc rrf INDF, f incf FSR rrf INDF, f skpc goto divx12_done incf INDF, f skpz goto divx12_done decf FSR incf INDF, f divx12_done: return ;-------------------------------------------------------------------- ; Big-Endian 16 bit Binary to Decimal ; ; Input: Number(15:0) ; ; Output: TenT, Thou, Hund, Tens, Ones ; ; Uses: Number, w ; ; bin2dec: clrf TenT goto $+2 sub10k: incf TenT movlw 10000&255 subwf Number+1 movlw 10000>>8 skpc movlw (10000>>8)+1 subwf Number skpnc goto sub10k movlw 10 movwf Thou add1k: decf Thou movlw 1000&255 addwf Number+1 movlw 1000>>8 skpnc movlw (1000>>8)+1 addwf Number skpc goto add1k clrf Hund movlw 100 goto $+2 sub100: incf Hund subwf Number+1 skpnc goto sub100 decf Number btfss Number,7 goto sub100 movlw 10 movwf Tens add10: decf Tens addwf Number+1 skpc goto add10 movf Number+1,w movwf Ones return ;-------------------------------------------------------------------- ; Display 5 digit Fixed-point Decimal on LCD ; ; - leading zero blanked ; - 1 digit after decimal point ; ; "9999.9" ; " 999.9" ; " 99.9" ; " 0.0" ; ShowDec1: bsf Flags,LEAD_ZERO movf TenT,w ; leading 0 ? skpz bcf Flags,LEAD_ZERO btfss Flags,LEAD_ZERO addlw '0'-' ' ; convert to ASCII '0'~'9' addlw ' ' call Print_Char ; show Ten Thousands or ' ' movf Thou,w skpz bcf Flags,LEAD_ZERO btfss Flags,LEAD_ZERO addlw '0'-' ' addlw ' ' call Print_Char ; show Thousands or ' ' movf Hund,w skpz bcf Flags,LEAD_ZERO btfss Flags,LEAD_ZERO addlw '0'-' ' addlw ' ' call Print_Char ; show Hundreds or ' ' movf Tens,w addlw '0' call Print_Char ; show Tens movlw '.' call Print_Char ; show '.' movf Ones,w addlw '0' call Print_Char ; show Ones RETURN ;-------------------------------------------------------------------- ; Display 4 digit Fixed-point Decimal on LCD ; ; - leading zero blanked ; - 2 digits after decimal point ; ; "99.99" ; " 9.99" ; " 0.00" ; ShowDec2: movf Thou,w skpz ; leading zero ? addlw '0'-' ' ; convert to ASCII '0'~'9' addlw ' ' call Print_Char ; show thousands or ' ' movf Hund,w addlw '0' call Print_Char ; show hundreds movlw '.' call Print_Char ; show '.' movf Tens,w addlw '0' call Print_Char ; show tens movf Ones,w addlw '0' call Print_Char ; show ones RETURN ;-------------------------------------------------------------------- ; Display 5 digit Decimal on LCD ; ; - leading zero blanked ; ; "99999" ; " 9999" ; " 99" ; " 0" ; ShowDec: bsf Flags,LEAD_ZERO movf TenT,w skpz bcf Flags,LEAD_ZERO btfss Flags,LEAD_ZERO addlw '0'-' ' addlw ' ' call Print_Char ; show ten thousands or ' ' movf Thou,w skpz bcf Flags,LEAD_ZERO btfss Flags,LEAD_ZERO addlw '0'-' ' addlw ' ' call Print_Char ; show thousands or ' ' movf Hund,w skpz bcf Flags,LEAD_ZERO btfss Flags,LEAD_ZERO addlw '0'-' ' addlw ' ' call Print_Char ; show hundreds or ' ' movf Tens,w skpz bcf Flags,LEAD_ZERO btfss Flags,LEAD_ZERO addlw '0'-' ' addlw ' ' call Print_Char ; show tens or ' ' movf Ones,w addlw '0' call Print_Char ; show ones RETURN ;------------------------------------------------------------------------------- ; Convert Period to rpm ; ; Period time = time for 16 blades to pass sensor (250kHz CCPR1 clock) ; ; rpm = 1/(Period*4uS/16/numblades)*60 ; = (240,000,000/numblades)/Period ; ; numblades dividend min rpm max rpm (for < 0.1% error) ; 1 240,000,000 3662 240,000 ) ; 2 120,000,000 1831 120,000 ) limited to 65,535 in Show_rpm ; 3 80,000,000 1220 80,000 ) ; 4 60,000,000 916 60,000 ; 5 48,000,000 732 48,000 ; 6 40,000,000 610 40,000 ; 7 34,285,714 523 34,286 ; Calc_rpm: movf NumBlades,w addlw -1 skpnz goto cr_1 addlw -1 skpnz goto cr_2 addlw -1 skpnz goto cr_3 addlw -1 skpnz goto cr_4 addlw -1 skpnz goto cr_5 addlw -1 skpnz goto cr_6 addlw -1 skpnz goto cr_7 goto cr_norpm cr_1: movi32 240000000,dd goto cr_calc cr_2: movi32 120000000,dd goto cr_calc cr_3: movi32 80000000,dd goto cr_calc cr_4: movi32 60000000,dd goto cr_calc cr_5: movi32 48000000,dd goto cr_calc cr_6: movi32 40000000,dd goto cr_calc cr_7: movi32 34285714,dd cr_calc: mov16 Period,aa ; get period movf aa,w iorwf aa+1,w skpnz ; period = 0 ? goto cr_norpm ; yes call Div32 mov32 dd,rpm ; no, rpm = 1/period goto cr_done cr_norpm: clr32 rpm ; rpm = 0 cr_done: return ;--------------------------------------------------------------------- ; Display rpm on LCD ; Show_rpm: movlw ' ' call Print_Char ; show ' ' call Calc_rpm mov16 rpm+2,Number movf rpm,w iorwf rpm+1,w ; rpm > 65535 ? skpnz goto sr_good ; no sr_overload: movlw '-' ; yes, show '-----' call Print_Char movlw '-' call Print_Char movlw '-' call Print_Char movlw '-' call Print_Char movlw '-' call Print_Char goto sr_rpm sr_good: call bin2dec call ShowDec ; show "xxxxx" sr_rpm: movlw 'r' call Print_Char ; show 'rp' movlw 'p' call Print_Char return ;--------------------------------------------------------------------- ; Display Number of Propellor Blades on LCD ; Show_Blades: movlw 'm' ; show 'm' (end of 'rpm') call Print_Char movlw ' ' call Print_Char movlw ' ' call Print_Char movlw ' ' ; show ' ' call Print_Char movlw ' ' call Print_Char movlw 'x' ; show 'x' call Print_Char movf NumBlades,w addlw '0' ; show numblades 2~6 call Print_Char movlw ' ' call Print_Char return ;-------------------------------------------------------------------- ; Display Watts on LCD ; Show_Watts: mov16 Watts,Number call bin2dec call ShowDec1 ; display "xxxx.x" movlw 'W' call Print_Char ; display 'W' movlw ' ' call Print_Char return ;-------------------------------------------------------------------- ; Display Volts on LCD ; Show_Volts: mov16 Volts,Number call bin2dec movlw ' ' call Print_Char call ShowDec2 ; display "xx.xx" movlw 'V' call Print_Char movlw ' ' btfsc Flags,MAX_VOLTS movlw '+' ; display '+' if overflow call Print_Char return ;-------------------------------------------------------------------- ; Display Amps on LCD ; Show_Amps: mov16 Amps,Number movlw ' ' btfsc Flags,NEG_AMPS movlw '-' ; display '-' if negative call Print_Char call bin2dec call ShowDec2 ; display "xx.xx" movlw 'A' call Print_Char movlw ' ' btfsc Flags,MAX_AMPS movlw '+' ; display '+' if overflow call Print_Char return ;-------------------------------------------------------------------- ; Display Amperes * Hours on LCD ; Show_AmpHours: mov16 AmpHours,Number call bin2dec movlw ' ' call Print_Char call ShowDec2 ; display "xx.xx" movlw 'A' call Print_Char movlw 'h' call Print_Char return ;--------------------------------------------------------------------- ; Page Button ; page_button: btfsc PGBTN ; is page button pressed ? goto page_up ; no btfsc Flags,PAGE_DWN ; yes, was page button pressed ? goto page_down ; yes incf pagenum ; no, select next page ifdef LCD8x2 movlw 3 else ; past last page ? movlw 2 endif subwf pagenum,w skpnc ; back to page 1 clrf pagenum page_down: bsf Flags,PAGE_DWN ; page button is pressed movlw 4 movwf Page_Timer ; start 222mS debounce timer goto page_done page_up: btfss Flags,PAGE_DWN ; was page button pressed ? goto page_done ; no decfsz Page_Timer ; debounce timer timed out ? goto page_done ; no bcf Flags,PAGE_DWN ; yes, button debounced page_done: return ;--------------------------------------------------------------------- ; Blades Button ; blade_button: movlw 1 subwf pagenum,w ; on page 1 ? skpz goto blade_done ; no, ignore blade button btfsc BLDBTN ; yes, is blade button pressed ? goto blade_up ; no btfsc Flags2,BLD_DWN ; yes, was blade button pressed ? goto blade_down ; yes incf NumBlades ; no, select next blades number movlw 7+1 subwf NumBlades,w skpc ; blades > 7 ? goto blade_down ; no movlw 1 ; yes, blades = 1 movwf NumBlades blade_down: bsf Flags2,BLD_DWN ; blade button is pressed movlw 4 movwf Blade_Timer ; start 222mS debounce timer goto blade_done blade_up: btfss Flags2,BLD_DWN ; was blade button pressed ? goto blade_done ; no decfsz Blade_Timer ; debounce timer timed out ? goto blade_done ; no bcf Flags2,BLD_DWN ; yes, button debounced blade_done: return ;-------------------------------------------------------------------- ; Main Program Starts Here! ; Start: clrf PORTA clrf PORTB bsf STATUS,RP0 ; bank 1 ifdef __16F88 movlw B'11101111' ; PortA directions (16F88) movwf TRISA movlw B'11000001' ; PortB directions (16F88) movwf TRISB movlw B'01100000' ; 4MHz internal osc (16F88) movwf OSCCON else movlw B'11111111' ; PortA directions (16F870) movwf TRISA movlw B'00000000' ; PortB directions (16F870) movwf TRISB movlw B'11111111' ; PortC directions (16F870) movwf TRISC endif movlw B'01010111' ; Weak pullups enabled, Timer0 src = clkout/256 movwf OPTION_REG bcf STATUS,RP0 ; bank 0 ; Set up Interrupts clrf INTCON ; clear any pending ints clrf PIR1 bsf INTCON,PEIE ; enable peripheral ints bsf INTCON,GIE ; enable interrupts ; set up LCD Display movlw 25 ; wait 500mS call waitx20k call LCDinit ; Set up the LCD display call show_signon ; show signon message movlw 50 ; wait 1 second call waitx20k ; set up A/D convertor bsf STATUS,RP0 ; bank 1 ifdef __16F88 movlw b'10110000' ; right-justify, ext vref (16F88) movwf ADCON1 movlw b'00001111' movwf ANSEL ; Analog inputs on RA0~RA3 (16F88) else movlw b'10001101' ; right-justify, ext vref, RA1/RA0 (16F870) movwf ADCON1 endif bcf STATUS,RP0 ; bank 0 movlw b'01000000' ; CLK/8 movwf ADCON0 ; initialize variables clrf Flags clrf Flags2 movlw 0 movwf pagenum clrf Page_Timer clrf Blade_Timer clr16 CCPR clr16 Period clr32 AmpSum clr32 rpm movlw 2 movwf NumBlades ; Set up CCP1 and Timer1 for rpm capture clrf TMR1L clrf TMR1H ; clear Timer1 movlw b'00100001' movwf T1CON ; 4:1 prescaler, Timer1 ON bcf PIR1,TMR1IF bcf PIR1,CCP1IF ; clear Int flags banksel PIE1 bsf PIE1,CCP1IE ; enable CCP1 Ints bsf PIE1,TMR1IE ; enable TMR1 Ints banksel 0 movlw b'00000111' ; capture every 16th leading edge movwf CCP1CON ; enable Capture ReadInputs: clr16 Volts clr16 Amps clr16 Watts movlw 64 ; 64 times oversampling movwf Readings NextRead: movlw b'01000001' ; CLK/8, select RA0 (Volts), A/D on movwf ADCON0 movlw 10 call waitx100 ; wait 1mS to stabilize analog input bsf ADCON0,GO_DONE ; start A/D conversion waitv: btfsc ADCON0,GO_DONE goto waitv ; wait until conversion Done movf ADRESH,w movwf Temp1 addwf Volts bsf STATUS,RP0 movf ADRESL,w ; add 10 bit A/D result to Volts bcf STATUS,RP0 movwf Temp2 addwf Volts+1 skpnc incf Volts bcf Flags,MAX_VOLTS movf Temp1,w xorlw b'00000011' skpnz bsf Flags,MAX_VOLTS ; Volts overloaded ? movf Temp2,w xorlw b'11111111' skpz bcf Flags,MAX_VOLTS movlw b'01001001' ; CLK/8, select RA1 (Amps), A/D on movwf ADCON0 movlw 10 call waitx100 ; wait 1mS to stabilize analog input bsf ADCON0,GO_DONE ; start A/D conversion waita: btfsc ADCON0,GO_DONE goto waita ; wait until conversion Done movf ADRESH,w movwf Temp1 addwf Amps bsf STATUS,RP0 movf ADRESL,w ; get 10 bit A/D result bcf STATUS,RP0 movwf Temp2 addwf Amps+1 skpnc incf Amps bcf Flags,MAX_AMPS movf Temp1,w xorlw b'00000011' skpnz bsf Flags,MAX_AMPS ; Amps overloaded ? movf Temp2,w xorlw b'11111111' skpz bcf Flags,MAX_AMPS decfsz Readings goto NextRead ; accumulate readings clrc rr16 Volts ; Volts / 2 movlw Volts call Divx12 ; Volts / 12.8 movlw Amps call Divx12 ; Amps / 12.8 btfsc Flags,GOT_ZERO ; have Amps been zeroed ? goto sub_zero get_zero: mov16 Amps,ZeroAmps ; record zero Amps value bsf Flags,GOT_ZERO sub_zero: bcf Flags,NEG_AMPS sub16 ZeroAmps,Amps ; subtract zero value from Amps skpnc goto got_amps bsf Flags,NEG_AMPS com16 Amps ; Amps = -0 to -0.99 got_amps: mov16 Amps,aa mov16 Volts,bb call Mult16 ; Watts = Volts * Amps movi16 1000,aa call Div32 ; Watts = Watts / 1000 subi16 500,bb skpc ; remainder > 0.5 ? goto store_watts inc16 dd+2 ; yes, round up store_watts: mov16 dd+2,Watts calc_Ah: add1632 Amps,AmpSum ; AmpSum = accumulated Amps mov32 AmpSum,dd movi16 3600*3,aa ; 3 reads per second, 3600 seconds per hour call Div32 ; AmpHours = AmpSum / (reads per hour) store_Ah: mov16 dd+2,AmpHours ; CCPR is a copy of the CCPR1 register. It is updated from the Interrupt ; routine. This may happen at any time, so we must prevent it from being ; updated while copying it. We cannot disable interrupts while copying ; because it could upset capture timing. ; bsf Flags,LOCK_P ; lock CCPR mov16 CCPR,Period ; get period (1/rpm) bcf Flags,LOCK_P ; unlock CCPR ; reduce rpm jitter by averaging this and previous measurement tst16 Period skpz ; got good rpm ? goto sp_good clr16 OldPeriod ; no, old period = 0 goto sp_done sp_good: tst16 OldPeriod skpnz ; got previous good rpm ? goto sp_done ; no mov1632 Period,dd add1632 OldPeriod,dd rr32 dd ; dd = (Period+OldPeriod)/2 mov16 Period,OldPeriod ; old period = current period mov16 dd+2,Period ; Period now averaged sp_done: movlw 4 ; set display refresh for 3 readings per second movwf ref_timer ; (value reduced to account for measurement time) movlw REFRESH_DELAY1 movwf TMR0 ; initialize Timer0 bcf INTCON,TMR0IF ; clear timer0 interrupt goto display_page ; show results on LCD screen do_display: movlw 6 ; 6*55.555mS = 333mS = 3 readings per second movwf ref_timer display_page: movlw 0 call Set_Cursor ; set cursor to start of line movf pagenum,w skpnz ; page 0 ? goto page0 addlw -1 skpnz ; page 1 ? goto page1 ifdef LCD8x2 ; 8x2 or 16x1 display, 3 pages addlw -1 skpnz ; page 2 ? goto page2 page0: call Show_Volts movlw LCDLINE2 call Set_Cursor ; "99.99V 99.99A" call Show_Amps goto ref_delay page1: call Show_Watts ; "9999.9W 99.99Ah" movlw LCDLINE2 call Show_AmpHours goto ref_delay page2: call Show_rpm movlw LCDLINE2 call Set_Cursor ; "99999rpm x 9" call Show_Blades else ; 16x2 display, 2 pages page0: call Show_Volts call Show_Amps ; "99.99V 99.99A" movlw LCDLINE2 call Set_Cursor call Show_Watts call Show_AmpHours ; "9999.9W 99.99Ah" goto ref_delay page1: call Show_Volts call Show_Amps ; "99.99V 99.99A" movlw LCDLINE2 call Set_Cursor call Show_rpm ; "99999rpm x 9" call Show_Blades endif ; Wait 1/3 second (3 readings per second) ; Check buttons while waiting ref_delay: btfss INTCON,TMR0IF goto ref_delay ; wait for Timer0 overflow (55.555mS) movlw REFRESH_DELAY2 addwf TMR0 ; recharge Timer0 bcf INTCON,TMR0IF call page_button ; handle page button call blade_button ; handle blades button decfsz ref_timer goto display_page ; wait for 333.3mS (1/3 Second) check_hold: ifndef SIMULATE btfss HDBTN ; hold switch on ? goto do_display ; yes, hold last readings endif goto ReadInputs ; no, get new readings END