;; ;; This program is for the microcontroller that goes into the DARS ;; six -pad controllers known as the dwarfs. ;; ;; It performs the following funtions: ;; ;; 1) Monitor pad select and actuate a piezo buzzer. ;; a) single beep for one pad selected. ;; b) double beep for multiple pads ;; ;; 2) Monitor arm status and sound continuous tone on piezo buzzer ;; when armed. ;; ;; 3) Monitor battery status and activate dual LED. ;; a) Green when battery condition good. ;; b) Yellow (red and green together) when battery marginal. ;; c) Red when battery condition bad. ;; d) Off when battery is toasted. :-) ;; ;; Instead of monitoring everything continuously, the timer is used ;; to wake up the processor 10 times a second to update the status. ;; The timer is also used to operate the piezo buzzer and time the ;; beeps. ;; ;; Target is the Microchip 16F818 microcontroller ;; ;; COnfig bits: ;; ;; Oscillator is set to "INTIO2" which is the internal RC oscillator with ;; I/O on pins RA6 and RA7. ;; ;; Version: 1.0 - April 2004 include p16f818.inc cblock 0x040 BEEP_TICK BEEP_STATUS TFLAG PINS COUNT DELAY T0 ;; Temporary location for battery code T1 ;; ditto W_TEMP STATUS_TEMP endc ;; Definitions for the bits inside BEEP_STATUS ARMED equ 0 ONCE equ 1 TWICE equ 2 ; Names for PORT A BEEP_OUT equ 4 GLED equ 6 RLED equ 7 ;; Transition levels for battery LED code radix dec GREEN equ (((23)*1024)/50) ; if voltage greater than 11.5, LED is green YELLOW equ (((21)*1024)/50) ; if voltage is greater than 10.5 (but less than 11.5) LED is yellow ; LED is red for voltage less than 11 volts radix hex org 0x00 ; locate at very start of memory goto start ; This is the reset vector nop nop nop goto interupt ; This is the interupt vector org 0x08 ;; Initialization for power on reset. ;; Setup all ports, timers, interupts, etc. start: clrf PCLATH ; paranoia bcf STATUS,RP0 bcf STATUS,RP1 ;; Setup ports ;; ;; RB0 through 5 are the pad enable inputs ;; RB6 and 7 are from another controller. They are treated exactly like the ;; other pad enable inputs after being inverted. ;; ;; RA0 - Battery monitor ;; RA1 - Arm monitor ;; RA4 - buzzer output ;; RA6 - battery condition LED (green) ;; RA7 - battery condition LED (red) ;; ;; All other pins are unused. bcf PORTA,BEEP_OUT ; beeper off movlw D'20' movwf BEEP_TICK ; initialize beep counter bcf PORTA,GLED ; battery condition LED off bcf PORTA,RLED movlw 0x23 bsf STATUS,RP0 ; data page 1 movwf TRISA ; RA0-1,5 = input, RA2-4,6,7 = output movlw 0xff movwf TRISB ; RB0-7 pins inputs bcf OPTION_REG,NOT_RBPU ; enable week pullups on PORT B ;; The internal clock source is configured to 32KHz at power on. ;; That seems a bit slow so the first order of business is to change it. ;; movlw 0x70 ; set clock to 8MHz movwf OSCCON cl_stable: btfss OSCCON,2 ; check clock stable bit goto cl_stable ;; ;; Setup Timer 1 to overflow every 1/10 second ;; ;; Uses the internal 8MHz clock, prescaled by 8. So the pre-scaler ;; output is 8,000,000/(8*4) = 250,000Hz. So to get this down to 10Hz, ;; the required count is 25,000. ;; bcf STATUS,RP0 ; page 0 movlw high (D'65536' - D'25000') movwf TMR1H movlw low (D'65536' - D'25000') movwf TMR1L movlw 0x31 movwf T1CON ; prescale = 8, Timer 1 on ;; Now enable the interupts. We only use the timer interupt. bsf STATUS,RP0 ; page 1 bsf PIE1,0 ; enable timer interupt bcf STATUS,RP0 bsf INTCON,PEIE ; enable peripheral interupts bsf INTCON,GIE ; global interupt enable ; ; ADC module setup ; bsf STATUS,RP0 movlw 0xc4 ; Use Vdd and Vss as reference, AN0,1, 3 as analog inputs movwf ADCON1 bcf STATUS,RP0 movlw 0x41 ; select Fosc/16 as conversion clock and turn on ADC movwf ADCON0 goto main ;; Interupt handler ;; interupt: movwf W_TEMP swapf STATUS,W bcf STATUS,RP0 bcf STATUS,RP1 ; make sure on data page 0 movwf STATUS_TEMP btfsc PIR1,TMR1IF ; check for Timer 1 interupt goto timer1int ;; ;; If we get here, we are in serious trouble. An interupt ;; has arrived that we have no code to handle. ;; ;; Punt goto end_interupt timer1int: bsf TFLAG,0 ; set timer flag bcf T1CON,0 ; Turn off Timer1 for a moment so we can reload movlw high (D'65536' - D'12500') movwf TMR1H movlw low (D'65536' - D'12500') movwf TMR1L bcf PIR1,TMR1IF ; Reset Timer 1 interupt flag bsf T1CON,0 ; Turn it back on end_interupt: bcf STATUS,RP0 bcf STATUS,RP1 swapf STATUS_TEMP,W movwf STATUS swapf W_TEMP,F swapf W_TEMP,W retfie main: mloop: btfss TFLAG,0 ; check for timer flag telling us it is time to go to work. goto mloop bcf TFLAG,0 ; clear timer flag call check_arm ; see if we are armed call check_pads ; count enabled pads call check_battery call beep ; run the beeper code goto mloop ; repeat forever check_pads: ; ; Check PORT B to see how many pads are selected. The pins will be driven low when ; a pad is selected. Read port B and then see how many bits are set. This is a three value ; solution: zero, one, many. ; movf PORTB,W ; read the pins movwf PINS ; store the read value for further work movlw 0x06 ; setup a loop counter for six bits movwf COUNT ploop0: rrf PINS,F ; rotate LSB into carry btfss STATUS,C ; check if zero (zero is pad selected) goto pone ; we found one pad selected, check for more decfsz COUNT,F goto ploop0 ; ; No pads are selected. ; bcf BEEP_STATUS,ONCE bcf BEEP_STATUS,TWICE return ; One pad has been found selected. Check see if there are more. pone: rrf PINS,F ; rotate LSB into carry btfss STATUS,C ; check if zero (zero is pad selected) goto pmany ; we found another pad selected. decfsz COUNT,F goto pone ; ; Only one pad selected. ; bsf BEEP_STATUS,ONCE bcf BEEP_STATUS,TWICE return pmany: bsf BEEP_STATUS,ONCE bsf BEEP_STATUS,TWICE return ; ; Check to see if the controller is armed. ; ; The common (hot) side of the power rail to the pads goes to the relay which is not ; energized until the controller is armed. This common rail also has a pullup to 5 volts ; through a 5K resistor. If the controller isn't armed, there should never be more than ; 5 volts here. ; ; In practice, the maximum voltage on the input pin in the unarmed state is about ; 0.75 volts. A threshold of 1 volt is used and there is no hysterisis. I don't think ; that it is required because of the large gap between the threshold and the nominal ; unarmed level. ; check_arm: ; Select Arm input bsf ADCON0,CHS0 ; wait 50us for acquisition call delay50 ; start the conversion bsf ADCON0,GO ; wait until conversion complete aloop: btfsc ADCON0,GO goto aloop ; ; This input should never exceed 5 volts unless the arm relay is active (or defective). ; So check to see if the voltage greater than 5V. There is a 40K/10K resistive divider ; on the input so we are looking for a reading of 1024counts/5V * 5V * 10/(10+40) = 204 ; ; First check the MSB of the result. If it is non zero, we are armed. ; movf ADRESH,W btfss STATUS,Z goto armed bsf STATUS,RP0 ; data page 1 movf ADRESL,W bcf STATUS,RP0 ; data page 0 sublw D'204' ; subtract 204 FROM W btfss STATUS,C ; C = 0 means that W was greater than 204 goto armed bcf BEEP_STATUS,ARMED return ; More than 5 volts is present on the common rail to the pads. ; This is either a hazardous fault condition or the controller has been armed. ; In either case, a continuous tone is sounded to warn the operator. ; armed: bsf BEEP_STATUS,ARMED bsf PORTA,BEEP_OUT return ; ; Check battery condition. ; ; Because of the input voltage divider, the full scale range of this input is 0-25 volts. ; check_battery: ; Select battery input bcf ADCON0,CHS0 ; wait 50us for acquisition call delay50 ; start the conversion bsf ADCON0,GO ; wait until conversion complete bloop: btfsc ADCON0,GO goto bloop ;; ;; Compare ADC result with transition level for a good battery indication (green) ;; ;; First copy ADC result to temporary location bsf STATUS,RP0 ; data page 1 movf ADRESL,W bcf STATUS,RP0 ; data page 0 movwf T0 movf ADRESH,W movwf T1 movlw low GREEN ; handle the low byte first subwf T0,F movlw high GREEN ; handle the high byte, and borrow btfss STATUS,C addlw 1 subwf T1,F ;; ;; Now check the result. If result is positive, then the voltage is high enough for ;; the LED to be green. ;; btfss T1,7 ; msb of result is the sign goto green_led ;; ;; That test failed so lets try for yellow ;; ;; First copy ADC result to temporary location bsf STATUS,RP0 ; data page 1 movf ADRESL,W bcf STATUS,RP0 ; data page 0 movwf T0 movf ADRESH,W movwf T1 movlw low YELLOW ; handle the low byte first subwf T0,F movlw high YELLOW ; handle the high byte, and borrow btfss STATUS,C addlw 1 subwf T1,F ;; ;; Now check the result. If result is positive, then the voltage is high enough for ;; the LED to be yellow. ;; btfss T1,7 ; msb of result is the sign goto yellow_led ;; ;; That failed so the LED is set to red ;; bsf PORTA,RLED bcf PORTA,GLED return green_led: bsf PORTA,GLED bcf PORTA,RLED return yellow_led: bsf PORTA,GLED bsf PORTA,RLED return ; ; The beep code runs on a two second cycle. (20 timer ticks) ; The following actions occur: ; ; Tick Action ; 0 If (beep_one) beeper on. ; 1 Beeper off ; 2 If (beep_many) beeper on ; 3-19 Beeper off ; ; On each cycle the count is incremented but before any checks are made ; to see if we need to beep once or twice, the arm status is checked. If we are ; armed, the beeper is on. ; beep: decfsz BEEP_TICK,F goto check2 movlw 0x14 ; reload the beep counter movwf BEEP_TICK btfsc BEEP_STATUS,ONCE bsf PORTA,BEEP_OUT return check2: movf BEEP_TICK,W xorlw 2 ; check for count of two btfsc STATUS,Z goto b2 ; ; default action is to turn the beeper off. Unless we are armed. ; The code that checks to see if we are armed turns the beeper on ; so we don't need to do that here. ; btfss BEEP_STATUS,ARMED bcf PORTA,BEEP_OUT ; Turn the beeper off return ; b2: btfsc BEEP_STATUS,TWICE bsf PORTA,BEEP_OUT ; turn on the beeper return ; ; subroutine to generate a 50us delay. Clock is nominally 8Mhz so each instruction takes ; about 500ns to execute. Therefore we need to execute 50us/500ns or 100 instructions. ; delay50: movlw D'50' movwf DELAY dloop: decfsz DELAY,F goto dloop return end