;; ;; $Id: uptime.asm,v 1.8 2002/11/23 03:06:58 james Exp $ ;; ;; uptime.asm, a PIC 16F877-04P with LCD pump control timer. ;; Copyright (C) 2002 James Cameron (quozl@us.netrek.org) ;; ;; 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 2 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, write to the Free Software ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ;; ;; ;; A simple pump control timer. Counts seconds, minutes, hours and days of ;; uptime and displays it on an LCD panel. Updates once a second. ;; ;; At 00:01:00 each day, starts a pump if it is wet and the supply ;; voltage is above a low point, and continues to run the pump ;; until either it runs dry, or two hours have expired. ;; ;; ;; Hardware assumptions ;; ;; LCD panel connected via Dontronics DT106 PCB ;; 4MHz crystal oscillator ;; processor 16f877 list f=inhx8m include "p16f877.inc" __config _lvp_off & _wdt_off & _hs_osc arg_scale equ d'22' ; voltage scale factor for a/d conversion ; recalibrate voltage sense potentiometer arg_limit equ d'1300' ; voltage below which we will not start arg_run equ d'120' ; run time in minutes ;; symbol naming conventions ;; p_ port address of ;; t_ tris address of ;; m_ mask bits of ;; b_ bit number ;; c_ command mask ;; file register allocations base equ 0x20 ; first free file register address stack equ 0x7f+1 ; end address of stack ;; port bit allocations (a Dontronics DT106 PCB) p_lcdc equ portb ; lcd control port address t_lcdc equ trisb ; lcd control tris address b_lcd_rs equ 1 ; lcd register select (pin 4) b_lcd_rw equ 2 ; lcd read/write (pin 5) b_lcd_e equ 3 ; lcd enable (pin 6) m_lcdc equ b'00001110' ; lcd control mask bits p_lcdd equ portb ; lcd data port address t_lcdd equ trisb ; lcd data tris address b_lcd_db4 equ 4 b_lcd_db5 equ 5 b_lcd_db6 equ 6 b_lcd_db7 equ 7 m_lcdd equ b'11110000' ; lcd data mask bits p_pump equ portc t_pump equ trisc b_ok equ 2 ; input, push button for user b_wet equ 3 ; input, pump is under water b_sense equ 4 ; input, pump voltage above mosfet b_run equ 5 ; output, mosfet gate to pump b_limit equ 6 ; output, voltage ok m_pump equ (1<>d'8'&0xff subwf t1,f btfss status,c decf t2,f movlw d'1000000'>>d'16'&0xff subwf t2,f btfss status,c goto isr_clock_end ; underflow, ignore the subtraction ;; did not underflow, save the result, occurs once per second mov24 t0,u0 cblock ss ; seconds mm ; minutes hh ; hours dl ; days dh pr ; pump run time endc ;; save fsr movf fsr,w movwf saved_fsr ;; point to counter movlw ss movwf fsr incf indf,f ; increment second counter ;;; start of insert for pump control ;; turn off pump if well goes dry (check once per second) btfss p_pump,b_wet bcf p_pump,b_run ;;; end of insert for pump control movlw d'60' subwf indf,w bnz isr_kick_end ; hasn't reached a minute ;;; start of insert for pump control ;; update pump run time counter movf pr,w ; test for zero bnz is_running is_stopped: ;; check for start time movf hh,w ; test for hour zero bnz is_to_stop ; not yet zero movf mm,w ; test for end of minute zero bnz is_to_stop ; not yet end of minute zero ;; start time 00:01:00 reached (we're rolling over from 00:00:59) ;; set run time in minutes movlw arg_run movwf pr ;; record current data set at run start (b_run will be off) ;; (programming one eeprom cell max 10ms, 65.536ms between isr calls) call record is_to_run: btfss p_pump,b_limit ; only if enough voltage goto is_done btfss p_pump,b_wet ; only if it is wet goto is_done ;; turn on pump bsf p_pump,b_run goto is_done is_running: decf pr,f ; pull off a minute bnz is_done ; not yet zero, branch ; bnz is_to_run ; (restart if possible) ;; record current data set at run start (b_run will be on, probably) call record is_to_stop: ;; end of period, turn pump off bcf p_pump,b_run is_done: ;;; end of insert for pump control clrf indf ; clear the second counter incf fsr,f ; point to minute counter incf indf,f ; increment minute counter movlw d'60' subwf indf,w bnz isr_kick_end ; hasn't reached an hour clrf indf ; clear the minute counter incf fsr,f ; point to hour counter incf indf,f ; increment hour counter movlw d'24' subwf indf,w bnz isr_kick_end ; hasn't reached an hour clrf indf ; clear the hour counter incf fsr,f ; point to day counter incf indf,f ; increment day counter bnz isr_kick_end incf fsr,f incf indf,f isr_kick_end ;; restore fsr movf saved_fsr,w movwf fsr ;; wake mainline bsf flag_wake isr_clock_end isr_skip_clock swapf saved_status,w ; restore registers saved movwf status swapf saved_w,f swapf saved_w,w retfie ;;; ;;; record data set to eeprom ;;; context: interrupt ;;; record cblock rech ; holding place for data set to record recl endc movf p_pump,w ; get flags movwf recl ; store in our temporary variable rrf recl,f ; align to right hand side of byte rrf recl,f rrf recl,w andlw b'00001111' ; keep meaningful bits bsf status,rp0 iorwf adresl,w ; pull in the voltage reading bcf status,rp0 movwf recl movf adresh,w movwf rech bsf flag_record ; ask mainline to store data return table macro popw ; grab offset addwf pcl,f ; vector to appropriate retlw endm t_revision table dt "$Revision: 1.8 $" ; ---------------- retlw 0 ; terminating zero for caller t_welcome table dt "Diurnal Pumper" ; ---------------- retlw 0 ; terminating zero for caller t_stack table dt "Stack Pointer is" ; ---------------- retlw 0 ; terminating zero for caller t_playback table dt "Showing History" ; ---------------- retlw 0 ; terminating zero for caller include "table.asm" ; table handler include "lcd.asm" ; lcd display functions include "bcd.asm" ; binary to decimal conversion include "hex.asm" ; binary to hexadecimal conversion include "stack.asm" ; stack functions include "ad.asm" ; analog to digital conversion include "sb.asm" ; sixteen bit math functions include "ee.asm" ; eeprom functions include "delay.asm" ; delay functions ;; ??? add temperature ;; 0123456789012345 ;; $Revision: 1.8 $ ;; dd hh:mm:ss rrrr ;; rr ffff vv.vvV pos_message equ lcd_row_0 pos_data equ lcd_row_1 pos_welcome equ pos_message pos_revision equ pos_data pos_record equ pos_data pos_volts equ pos_data+d'10' pos_flags equ pos_data+d'4' pos_debug equ pos_data pos_days equ pos_message ;; ??? button to scroll through different output messages, value containing current message to display, button increments value, rolls over to zero ;; or increment it ourselves initialise ;; clear stack movlw stack movwf fsr ;; clear output port clrf porta clrf portb clrf portc clrf portd clrf porte ;; set tristate latches movlw ~(m_lcdd|m_lcdc) ; set lcd data and control to output movwf r_trisb bsf status,rp0 movwf trisb movlw b'11111111' ; inputs so we can use analog movwf trisa movlw b'00000000' ; rest outputs movwf trisd movwf trise movlw m_pump movwf t_pump bcf status,rp0 ;; clear counters clr24 u0 clrf ss clrf mm clrf hh clr16 dl clrf pr clrf flag ; clear synchronisation flags ;; initialise option register bsf status,rp0 bcf option_reg,t0cs ; set timer to use oscillator/4 bcf option_reg,psa ; set prescaler to TMR0 bsf option_reg,ps2 ; set prescaler to 1:256 bsf option_reg,ps1 bsf option_reg,ps0 bcf status,rp0 ;; lastly, enable interrupts bsf intcon,t0ie ; enable timer interrupt bsf intcon,gie ; enable all interrupts ;; initialise lcd call lcd_initialise ;; initialise analog to digital converter call ad_initialise return ;; reset the wait flag and wait until the next second passes wait bcf flag_wake ; reset our wait flag wait_loop btfsc p_pump,b_ok ; return if button pressed return btfss flag_wake ; wait until isr is executed goto wait_loop ;; check for record request btfsc flag_record call put_eeprom return show_welcome pushl pos_welcome call lcd_move pers lcd_put,t_welcome pushl pos_revision call lcd_move pers lcd_put,t_revision return ;; repeat trisb setting (mysteriously clearing on prototype) ;; ( -- ) retris bsf status,rp0 movlw m_pump movwf t_pump bcf status,rp0 return scale_volts bcf status,c ; divide by two to make unsigned rrf indf,f pop rrf indf,f push return ;; update analog to digital conversion ;; ( -- vl vh ) get_volts call ad_get ; get voltage sample call scale_volts ;; check voltage movlw high(arg_scale*arg_limit) subwf indf,w bc get_volts_ok get_volts_bad: bcf p_pump,b_limit goto get_volts_done get_volts_ok: bsf p_pump,b_limit get_volts_done: return ;; fetch data from isr, interlocked ;; ( -- pr ss mm hh dl ) get_time bcf intcon,gie ; disable interrupts btfsc intcon,gie ; test we suceeded [paranoia] goto $-2 ; loop until it worked ;; grab a copy of the counters (interlocked) pushf pr pushf ss pushf mm pushf hh pushf16 dl bsf intcon,gie ; enable interrupts return ;; display the days, hours, minutes and seconds since reset ;; ( pr ss mm hh dl -- ) show_time pushl pos_days call lcd_move call sb_bcd ; dl/dh drop drop call lcd_put_hex pushl ' ' call lcd_put pushl d'0' ; hh call sb_bcd drop drop call lcd_put_hex pushl ':' call lcd_put pushl d'0' ; mm call sb_bcd drop drop call lcd_put_hex pushl ':' call lcd_put pushl d'0' ; ss call sb_bcd drop drop call lcd_put_hex ;; display the pump run timer (minutes) pushl ' ' call lcd_put pushl d'0' ; pr call sb_bcd drop call lcd_put_hex call lcd_put_hex return ;; show voltage ;; ( vl vh -- ) show_volts pushl16 arg_scale ; scale down to 0-15v call sb_divide pushl pos_volts call lcd_move call sb_bcd drop call lcd_put_hex pushl '.' call lcd_put call lcd_put_hex pushl 'v' call lcd_put pushl ' ' call lcd_put popl16 ; ignore remainder from division return ;; fetch status flags ;; ( -- flags ) get_flags pushf p_pump return ;; update status flags ;; ( flags -- ) show_flags pushl pos_flags call lcd_move movlw '-' btfsc indf,b_limit movlw 'L' pushw call lcd_put movlw '-' btfsc indf,b_wet movlw 'W' pushw call lcd_put movlw '-' btfsc indf,b_sense movlw 'S' pushw call lcd_put movlw '-' btfsc indf,b_run movlw 'R' pushw call lcd_put #ifdef klkl pushl ' ' call lcd_put pushl ' ' call lcd_put pushl ' ' call lcd_put pushl ' ' call lcd_put #endif drop return ;; find first zero spot in eeprom ;; ( -- address ) get_eeprom_last pushl d'0' get_eeprom_last_loop: dup ; ( address address ) call ee_get ; ( address data ) popz bz get_eeprom_last_skip ; found a zero spot, use it incf indf,f bnz get_eeprom_last_loop drop goto loop ; eeprom full get_eeprom_last_skip: return ;; record the data set requested by the isr into eeprom ;; ( -- ) put_eeprom bcf flag_record ; reset the request call get_eeprom_last ; get last address used in eeprom ;; ( address ) pushf recl ; store the new record values over call ee_put incf indf,f ; point to following byte ;; ( address ) pushf rech over call ee_put drop ;; ( -- ) return ok_open btfsc p_pump,b_ok ; loop until button release goto $-1 pushl d'50' ; debounce call ms btfsc p_pump,b_ok goto ok_open return ok_close btfss p_pump,b_ok ; loop waiting for ok button goto $-1 pushl d'10' ; debounce call ms btfss p_pump,b_ok goto ok_close return ok pushl d'50' ; debounce call ms call ok_open ; wait for button contact open call ok_close ; wait for button contact close return ;;; ;;; main program ;;; loop is woken every second by isr ;;; main call initialise call show_welcome call wait call lcd_clear goto resume loop call wait ; wait for next second resume call retris ; repeat trisb settings call get_volts ; ( -- vl vh ) call get_time ; ( -- pr ss mm hh dl ) call show_time ; ( pr ss mm hh dl -- ) call show_volts ; ( vl vh -- ) call get_flags ; ( -- flags ) call show_flags ; ( flags -- ) btfss p_pump,b_ok ; button down? goto loop call lcd_clear pushl pos_message call lcd_move pers lcd_put,t_stack pushl pos_data ; dump the stack pointer call lcd_move pushl '0' call lcd_put pushl 'x' call lcd_put pushf fsr call lcd_put_hex call ok call get_eeprom_last ; ( -- address ) peekw bz skip_2 decf indf,f ; step back to latest used (high) bsf indf,0 ; force it to be odd loop_2 ;; ( address ) call lcd_clear ; momentarily blank the display pushl d'100' call ms pushl pos_message ; display the playback message call lcd_move pers lcd_put,t_playback pushl pos_record ; display address call lcd_move dup bcf status,c rrf indf,f pushl d'0' call sb_bcd drop drop call lcd_put_hex dup decf indf,f call ee_get ; get low byte ;; ( address recl ) over call ee_get ; get high byte ;; ( address recl rech ) over rlf indf,f ; reversion of alignment rlf indf,f rlf indf,f call show_flags call scale_volts call show_volts ;; ( address ) call ok ;; step address down two bytes and repeat decfsz indf,f goto skip_1 goto skip_2 skip_1 decfsz indf,f goto loop_2 skip_2 drop call ok_open ; wait for button contact open call lcd_clear goto resume ; org 0x2100 ; de 03,0c4,0cb,0e5,4b,0f0,4b,0f0,0d,0e7 #ifdef clear_eeprom org 0x2100 de 0,0,0,0,0,0,0,0,0,0,0,0,0,0 de 0,0,0,0,0,0,0,0,0,0,0,0,0,0 de 0,0,0,0,0,0,0,0,0,0,0,0,0,0 de 0,0,0,0,0,0,0,0,0,0,0,0,0,0 #endif end