TITLE "switch.asm: power switch controller @3.6864MHz" LIST p=16F84 ERRORLEVEL 0, -302 ; Controller for externel AC power switches (solid state relais). ; There are 4 outputs, 4 key inputs and an asynchronous serial port. ; The device allows switching off a PC by writing a command to the ; serial port (9600 bps, 8 bit, no parity). ; ; The command is similar to the +++ sequence on Hayes modems to allow ; still using the serial port for an other application. When only a ; power off command is sent the other serial device probably won't mind ; because is is shut off anyway. Other features like locking and status ; evaluation wont be usable if the serial port is shared. ; ; For operating systems that don't like to be shut off by taking away ; the power (Linux, OS/2, ..) commands may be delayed up to 254 sec. ; Up to four commands may be pendent (eg. automated cold reboot). ; The key inputs may be locked to prevent the system from being turned ; off manually while it is running. Locking of keys is not recommended ; on systems which tend to hang up spontanously (like M$ winXY) since ; then unlocking is no more possible. There is an unlock input to get ; around this problem (so it is not necessary to connect some other ; serial source to issue an unlock command). Tying unlock to GND will ; disable locking permanently. A device reset will unlock and turn off ; all outputs. (I did not install an unlock or reset key on my device: ; booting the PC in an emergency will do also). For active high outputs ; connect the invert input to MCLR for active low outputs to GND. ; Command Format: NCHH ; N switch 0..3; C command o=off, x=on, O=lockoff, X=lockon, s=status; ; HH delay 00..FE sec.; is , or . ; ; Time between command characters must not exceed 500ms. ; Response: ssss , s = current status o, x, O, X of out0..out3. ; At a key hit its number (0..3) is transmitted regardless of locking. ; The remote pin transmits on or off commands for my wireless switches: ; FM-coded 3 bytes frames address(0xA0..0xA3)-value(0..0xFF)-checksum. ; The s command allows individual control over remote switches. ; 0sHH preset address to HH. 1sHH send value HH to preset address. ; rem Example 1: Power off from DOS ; MODE COM1:9600,N,8,1 ; ECHO 0o00> COM1 ; # Example 2: Power off from Linux ; echo 0o30 > /dev/ttyS0 ; /sbin/shutdown -h now ; # Example 3: Dimm remote lamp (addr B3, value 7F) from Linux ; echo 0sB3 > /dev/ttyS0 ; sleep 1 ; echo 1s7F > /dev/ttyS0 ; ********** ********** ; * *** * ; out2 A2 ** 1 18 ** A1 out1 ; * * ; out3 A3 ** 2 17 ** A0 out0 ; * * ; invert A4 ** 3 16 ** Xin xtal ; * * ; reset MCLR ** 4 15 ** Xout xtal ; * * ; GND ** 5 14 ** VCC ; * * ; rxd B0 ** 6 13 ** B7 key3 ; * * ; txd B1 ** 7 12 ** B6 key2 ; * * ; remote B2 ** 8 11 ** B5 key1 ; * * ; unlock B3 ** 9 10 ** B4 key0 ; * PIC16C71/84 * ; *********************** ; rxd from host over 10k to B0 (no RS232 receiver required). ; optional: txd B1 direct to host (no RS232 driver required). ; reset over 10k to VCC, port B inputs have internal pullup. ; outputs can source 20mA each, 50mA total. __IDLOCS H'3456' __CONFIG H'3FF9' ; WDT off, XT osc RTCC EQU 1 ; new name is TMR0 PC EQU 2 STATUS EQU 3 FSR EQU 4 PORTA EQU 5 PORTB EQU 6 PCLATH EQU H'0A' INTCON EQU H'0B' _OPTION EQU H'81' ; OPTION register in page 1 _TRISA EQU H'85' ; TRISA in page 1 _TRISB EQU H'86' ; TRISB in page 1 _ADCON1 EQU H'88' ; ADCON1 in page 1 C EQU 0 Z EQU 2 P EQU 5 tmp0 EQU H'0C' ; temporary storage tmp1 EQU H'0D' tmp2 EQU H'0E' tmp3 EQU H'0F' sav0 EQU H'10' ; storage for interrupt handler sav1 EQU H'11' ticks EQU H'12' ; 225 ticks per second ridle EQU H'13' ; rx idle time (ticks) dkey0 EQU H'14' ; debouncing of key switches dkey1 EQU H'15' dkey2 EQU H'16' dkey3 EQU H'17' shed0 EQU H'18' ; shedule time (seconds) shed1 EQU H'19' shed2 EQU H'1A' shed3 EQU H'1B' next0 EQU H'1C' ; next (sheduled command) next1 EQU H'1D' next2 EQU H'1E' next3 EQU H'1F' outst EQU H'20' ; output status lockm EQU H'21' ; lock mask for key inputs raddr EQU H'22' ; remote address txshft EQU H'24' ; transmitter shift register rxshft EQU H'25' ; receiver shift register bitdly EQU H'26' ; receiver bit timer rxcnt EQU H'27' ; receive byte counter rxbuf EQU H'28' ; receive buffer (8 bytes) ORG H'00' ; reset GOTO main ORG H'04' ; interrupt intserv MOVWF sav0 ; [4] save working register SWAPF STATUS,W ; [5] status to W BCF STATUS,P ; [6] set bank 0 MOVWF sav1 ; [7] save status register MOVLW H'80' ; [8] startbit MOVWF rxshft ; [9] MOVLW D'14' ; [10] adjust to poll middle of bits MOVWF bitdly ; [W*3] ints0 DECFSZ bitdly,F ; [.] GOTO ints0 ; [.] ints1 MOVLW D'29' ; [1] bit time delay [96] MOVWF bitdly ; [W*3] ints2 DECFSZ bitdly,F ; [.] GOTO ints2 ; [.] NOP ; [89] RRF rxshft,F ; [90] shift in new bit BCF rxshft,7 ; [91] BTFSS PORTB,0 ; [92] poll rx line BSF rxshft,7 ; [93] BTFSS STATUS,C ; [94] startbit rotated to carry? GOTO ints1 ; [96] MOVF rxcnt,W ; accept first char only if ridle > 500ms BTFSS STATUS,Z ; GOTO wrbuf ; MOVLW D'112' ; SUBWF ridle,W ; carry set if ridle ge 500ms BTFSS STATUS,C ; GOTO intend ; wrbuf MOVF rxcnt,W ; write rx byte to buf ADDLW rxbuf ; MOVWF FSR ; MOVF rxshft,W ; MOVWF 0 ; INCF rxcnt,W ; rx count saturation at 7 BTFSS rxcnt,3 ; INCF rxcnt,F ; intend CLRF ridle ; reset rx idle timer CLRF INTCON ; clear all flags and enable-bits BSF INTCON,4 ; reenable B0 interrupt SWAPF sav1,W ; [1] restore status register MOVWF STATUS ; [2] SWAPF sav0,F ; [3] restore working register SWAPF sav0,W ; [4] RETFIE ; [6] ORG H'40' main CLRF outst ; PORTA outputs 0..3 off CALL outpin ; MOVLW B'11111001' ; PORTB txd+remote low when idle MOVWF PORTB ; MOVLW B'00000000' ; PCLATH to data ROM MOVWF PCLATH ; BSF STATUS,P ; set page 1 MOVLW B'01010011' ; pullup, rising int, rtcc:16 MOVWF _OPTION ; MOVLW B'00010000' ; A4 input, other outputs MOVWF _TRISA ; MOVLW B'11111001' ; B1+B2 output MOVWF _TRISB ; MOVLW B'00000011' ; port A all digital MOVWF _ADCON1 ; (_EECON1 on PIC16C/F84) BCF STATUS,P ; set page 0 MOVLW H'0C' ; reset vars MOVWF FSR ; initvar CLRF 0 ; INCF FSR,F ; MOVLW H'30' ; XORWF FSR,W ; BTFSS STATUS,Z ; GOTO initvar ; CLRF RTCC ; reset rtcc CLRF INTCON ; clear all flags and enable-bits BSF INTCON,4 ; enable B0 interrupt BSF INTCON,7 ; global interrupt enable loop CALL keypol ; CALL commck ; CALL shedck ; CALL outpin ; GOTO loop ; getbit ANDLW 3 ; bit decoder (PCLATH ok?) ADDWF PC,F ; RETLW B'00000001' ; RETLW B'00000010' ; RETLW B'00000100' ; RETLW B'00001000' ; ; looped code would be easier to maintain and shorter than ; straightline code: maybe this should be rewritten (this ; would heavily use the FSR, so save it in intserv) keypol BTFSS PORTB,3 ; clear lock mask if unlock low CLRF lockm ; MOVF PORTB,W ; MOVWF tmp3 ; SWAPF tmp3,F ; key0a BTFSS tmp3,0 ; key pressed? GOTO key0b ; DECF dkey0,W ; no: dec dkey BTFSS dkey0,7 ; if accepted CLRW ; else reset MOVWF dkey0 ; GOTO key0c ; done key0b INCF dkey0,W ; yes: inc dkey BTFSC dkey0,7 ; if not accepted MOVLW H'E0' ; else set bit 7 + key release min MOVWF dkey0 ; XORLW H'70' ; if not key press min BTFSS STATUS,Z ; GOTO key0c ; done MOVLW H'E0' ; else accept key MOVWF dkey0 ; MOVLW 0 ; CALL keyexe ; key0c NOP ; key1a BTFSS tmp3,1 ; key pressed? GOTO key1b ; DECF dkey1,W ; no: dec dkey BTFSS dkey1,7 ; if accepted CLRW ; else reset MOVWF dkey1 ; GOTO key1c ; done key1b INCF dkey1,W ; yes: inc dkey BTFSC dkey1,7 ; if not accepted MOVLW H'E0' ; else set bit 7 + key release min MOVWF dkey1 ; XORLW H'70' ; if not key press min BTFSS STATUS,Z ; GOTO key1c ; done MOVLW H'E0' ; else accept key MOVWF dkey1 ; MOVLW 1 ; CALL keyexe ; key1c NOP ; key2a BTFSS tmp3,2 ; key pressed? GOTO key2b ; DECF dkey2,W ; no: dec dkey BTFSS dkey2,7 ; if accepted CLRW ; else reset MOVWF dkey2 ; GOTO key2c ; done key2b INCF dkey2,W ; yes: inc dkey BTFSC dkey2,7 ; if not accepted MOVLW H'E0' ; else set bit 7 + key release min MOVWF dkey2 ; XORLW H'70' ; if not key press min BTFSS STATUS,Z ; GOTO key2c ; done MOVLW H'E0' ; else accept key MOVWF dkey2 ; MOVLW 2 ; CALL keyexe ; key2c NOP ; key3a BTFSS tmp3,3 ; key pressed? GOTO key3b ; DECF dkey3,W ; no: dec dkey BTFSS dkey3,7 ; if accepted CLRW ; else reset MOVWF dkey3 ; GOTO key3c ; done key3b INCF dkey3,W ; yes: inc dkey BTFSC dkey3,7 ; if not accepted MOVLW H'E0' ; else set bit 7 + key release min MOVWF dkey3 ; XORLW H'70' ; if not key press min BTFSS STATUS,Z ; GOTO key3c ; done MOVLW H'E0' ; else accept key MOVWF dkey3 ; MOVLW 3 ; CALL keyexe ; key3c NOP ; RETLW 0 ; keyexe MOVWF tmp2 ; store output to toggle ADDLW '0' ; transmit key number CALL txchar ; MOVF tmp2,W ; key bit to tmp1 CALL getbit ; MOVWF tmp1 ; COMF lockm,W ; invert lockmask ANDWF tmp1,W ; if locked BTFSC STATUS,Z ; RETLW 0 ; done XORWF outst,F ; else toggle output MOVLW H'A0' ; address IORWF tmp2,W ; MOVWF tmp0 ; MOVF tmp1,W ; value ANDWF outst,W ; BTFSS STATUS,Z ; MOVLW H'FF' ; MOVWF tmp1 ; GOTO txcmdb ; return from there commck MOVF rxcnt,W ; something in buffer? BTFSC STATUS,Z ; RETLW 0 ; MOVLW D'112' ; SUBWF ridle,W ; carry set if ridle ge 500ms BTFSS STATUS,C ; RETLW 0 ; MOVLW D'5' ; SUBWF rxcnt,W ; carry set if rxcnt ge 5 BTFSS STATUS,C ; GOTO comerr ; MOVLW D'7' ; SUBWF rxcnt,W ; carry set if rxcnt ge 7 BTFSC STATUS,C ; GOTO comerr ; MOVLW D'5' ; five bytes: check for cr or lf XORWF rxcnt,W ; BTFSS STATUS,Z ; GOTO ckcrlf ; six bytes: check for cr, lf CLRF rxbuf+5 ; MOVLW H'0D' ; cr? XORWF rxbuf+4,W ; BTFSC STATUS,Z ; GOTO eofok ; MOVLW H'0A' ; lf? XORWF rxbuf+4,W ; BTFSC STATUS,Z ; GOTO eofok ; GOTO comerr ; ckcrlf MOVLW H'0D' ; cr? XORWF rxbuf+4,W ; BTFSS STATUS,Z ; GOTO comerr ; MOVLW H'0A' ; lf? XORWF rxbuf+5,W ; BTFSS STATUS,Z ; GOTO comerr ; eofok MOVF rxbuf+0,W ; switch number '0'..'3'? ANDLW B'11111100' ; XORLW '0' ; BTFSS STATUS,Z ; GOTO comerr ; MOVLW '0' ; switch number binary to rxbuf[0] SUBWF rxbuf+0,F ; MOVF rxbuf+2,W ; first hex digit CALL hexbin ; BTFSC STATUS,C ; GOTO comerr ; MOVWF tmp1 ; MOVF rxbuf+3,W ; second hex digit CALL hexbin ; BTFSC STATUS,C ; GOTO comerr ; SWAPF tmp1,F ; ADDWF tmp1,W ; MOVWF rxbuf+2 ; delay time now binary in rxbuf[2] CLRF tmp1 ; command 'o', 'x', 'O', 'X', 's' or 'S'? MOVLW 'o' ; XORWF rxbuf+1,W ; BTFSC STATUS,Z ; GOTO comok ; tmp1=0 INCF tmp1,F ; MOVLW 'x' ; XORWF rxbuf+1,W ; BTFSC STATUS,Z ; GOTO comok ; tmp1=1 INCF tmp1,F ; MOVLW 'O' ; XORWF rxbuf+1,W ; BTFSC STATUS,Z ; GOTO comok ; tmp1=2 INCF tmp1,F ; MOVLW 'X' ; XORWF rxbuf+1,W ; BTFSC STATUS,Z ; GOTO comok ; tmp1=3 INCF tmp1,F ; MOVLW 's' ; XORWF rxbuf+1,W ; BTFSS STATUS,Z ; GOTO comerr ; MOVF rxbuf,W ; switch 0? BTFSS STATUS,Z ; GOTO cksend ; MOVF rxbuf+2,W ; MOVWF raddr ; GOTO txsta ; cksend DECF rxbuf,W ; switch 1? BTFSS STATUS,Z ; GOTO txsta ; MOVF raddr,W ; MOVWF tmp0 ; MOVF rxbuf+2,W ; MOVWF tmp1 ; GOTO txcmdb ; return from there comok MOVLW D'240' ; force seconds timeout MOVWF ticks ; SWAPF rxbuf+0,W ; switch number to upper nibble tmp1 IORWF tmp1,F ; command number to lower nibble tmp1 INCF rxbuf+2,W ; shed time+1 to W como0 MOVF shed0,F ; free entry in shed table? BTFSS STATUS,Z ; GOTO como1 ; MOVWF shed0 ; store entry MOVF tmp1,W ; MOVWF next0 ; GOTO txsta ; como1 MOVF shed1,F ; free entry in shed table? BTFSS STATUS,Z ; GOTO como2 ; MOVWF shed1 ; store entry MOVF tmp1,W ; MOVWF next1 ; GOTO txsta ; como2 MOVF shed2,F ; free entry in shed table? BTFSS STATUS,Z ; GOTO como3 ; MOVWF shed2 ; store entry MOVF tmp1,W ; MOVWF next2 ; GOTO txsta ; como3 MOVF shed3,F ; free entry in shed table? BTFSS STATUS,Z ; GOTO comerr ; table full: no acknowledge MOVWF shed3 ; store entry MOVF tmp1,W ; MOVWF next3 ; txsta MOVLW 'o' ; acknowledge: status of out0 BTFSC outst,0 ; MOVLW 'x' ; BTFSC lockm,0 ; ANDLW H'5F' ; CALL txchar ; MOVLW 'o' ; status of out1 BTFSC outst,1 ; MOVLW 'x' ; BTFSC lockm,1 ; ANDLW H'5F' ; CALL txchar ; MOVLW 'o' ; status of out2 BTFSC outst,2 ; MOVLW 'x' ; BTFSC lockm,2 ; ANDLW H'5F' ; CALL txchar ; MOVLW 'o' ; status of out3 BTFSC outst,3 ; MOVLW 'x' ; BTFSC lockm,3 ; ANDLW H'5F' ; CALL txchar ; MOVF rxbuf+4,W ; newline1 CALL txchar ; MOVF rxbuf+5,W ; newline2 if XORLW H'0A' ; BTFSS STATUS,Z ; GOTO comerr ; MOVLW H'0A' ; CALL txchar ; comerr CLRF rxcnt ; RETLW 0 ; shedck BTFSS INTCON,2 ; rtc overflow 3686400:4:256:16=225Hz RETLW 0 ; BCF INTCON,2 ; clear rtc overflow flag INCFSZ ridle,W ; advance ridle (saturation at 255) INCF ridle,F ; INCF ticks,F ; advance ticks MOVLW D'225' ; SUBWF ticks,W ; carry set if ticks ge 225 BTFSS STATUS,C ; RETLW 0 ; CLRF ticks ; shdck0 MOVF shed0,F ; do nothing if shed is 0 BTFSC STATUS,Z ; GOTO shdck1 ; DECF shed0,F ; execute command if shed was 1 BTFSS STATUS,Z ; GOTO shdck1 ; MOVF next0,W ; CALL cmdexe ; shdck1 MOVF shed1,F ; do nothing if shed is 0 BTFSC STATUS,Z ; GOTO shdck2 ; DECF shed1,F ; execute command if shed was 1 BTFSS STATUS,Z ; GOTO shdck2 ; MOVF next1,W ; CALL cmdexe ; shdck2 MOVF shed2,F ; do nothing if shed is 0 BTFSC STATUS,Z ; GOTO shdck3 ; DECF shed2,F ; execute command if shed was 1 BTFSS STATUS,Z ; GOTO shdck3 ; MOVF next2,W ; CALL cmdexe ; shdck3 MOVF shed3,F ; do nothing if shed is 0 BTFSC STATUS,Z ; RETLW 0 ; DECF shed3,F ; execute command if shed was 1 BTFSS STATUS,Z ; RETLW 0 ; MOVF next3,W ; cmdexe MOVWF tmp0 ; store command SWAPF tmp0,W ; key mask to W CALL getbit ; IORWF outst,F ; output on if command 1 or 3 BTFSS tmp0,0 ; XORWF outst,F ; else output off IORWF lockm,F ; lock if command >= 2 BTFSS tmp0,1 ; XORWF lockm,F ; else unlock txcmda CLRF tmp1 ; on = FF, off = 00 BTFSC tmp0,0 ; COMF tmp1,F ; SWAPF tmp0,W ; address ANDLW H'03' ; IORLW H'A0' ; MOVWF tmp0 ; txcmdb BCF INTCON,7 ; disable global interrupt BTFSC INTCON,7 ; GOTO txcmdb ; CLRF rxbuf+5 ; calculate checksum byte MOVF tmp0,W ; address MOVWF rxbuf+3 ; SUBWF rxbuf+5,F ; MOVF tmp1,W ; value MOVWF rxbuf+4 ; SUBWF rxbuf+5,F ; CALL txcmdc ; CALL txcmdc ; CALL txcmdc ; CALL txcmdc ; CALL txcmdc ; CLRF rxcnt ; BSF INTCON,7 ; enable global interrupt RETLW 0 ; txcmdc MOVF rxbuf+3,W ; copy to buffer MOVWF rxbuf+0 ; MOVF rxbuf+4,W ; MOVWF rxbuf+1 ; MOVF rxbuf+5,W ; MOVWF rxbuf+2 ; MOVLW D'24' ; bit counter MOVWF rxcnt ; CLRF tmp2 ; initialize toggle mask BSF tmp2,2 ; txbit MOVF tmp2,W ; [184] toggle OUT XORWF PORTB,F ; [185] MOVLW D'59' ; [1] MOVWF tmp0 ; [2] dly1 DECFSZ tmp0,F ; (59 * 3) - 1 GOTO dly1 ; RRF rxbuf+2,F ; [179] next bit to carry RRF rxbuf+1,F ; [180] RRF rxbuf,F ; [181] NOP ; [182] MOVF tmp2,W ; [183] toggle OUT if bit 0 BTFSS STATUS,C ; [184] XORWF PORTB,F ; [185] MOVLW D'59' ; [1] MOVWF tmp1 ; [2] dly2 DECFSZ tmp1,F ; (59 * 3) - 1 GOTO dly2 ; NOP ; [179] NOP ; [180] DECFSZ rxcnt,F ; [181] all bits done? GOTO txbit ; [182] MOVF tmp2,W ; toggle OUT XORWF PORTB,F ; MOVLW D'61' ; [1] MOVWF tmp0 ; [2] dly3 DECFSZ tmp0,F ; (61 * 3) - 1 GOTO dly3 ; BCF PORTB,2 ; [185] transmitter off CLRF tmp1 ; 256 * 3 = 768 (clock error) dly4 DECFSZ tmp1,F ; GOTO dly4 ; RETLW 0 ; outpin MOVF outst,W ; write to port A BTFSS PORTA,4 ; invert outputs? XORLW B'00001111' ; MOVWF PORTA ; RETLW 0 ; mul10 MOVWF tmp0 ; W * 10 (W max 25) BCF STATUS,C ; RLF tmp0,F ; * 2 MOVF tmp0,W ; copy to W RLF tmp0,F ; * 4 RLF tmp0,F ; * 8 ADDWF tmp0,W ; * 10 hexbin MOVWF tmp0 ; 'A'..'F' SUBLW 'F' ; carry cleared if tmp0 gt 'F' BTFSS STATUS,C ; GOTO binerr ; MOVLW 'A' ; SUBWF tmp0,W ; carry set if tmp0 ge 'A' BTFSS STATUS,C ; GOTO hexbbb ; MOVLW '7' ; SUBWF tmp0,F ; binary digit GOTO binok ; hexbbb MOVF tmp0,W ; decbin MOVWF tmp0 ; '0'..'9' SUBLW '9' ; carry cleared if tmp0 gt '9' BTFSS STATUS,C ; GOTO binerr ; MOVLW '0' ; SUBWF tmp0,W ; carry set if tmp0 ge '0' BTFSS STATUS,C ; GOTO binerr ; MOVLW '0' ; SUBWF tmp0,F ; binary digit GOTO binok ; binerr BSF STATUS,C ; MOVF tmp0,W ; RETURN ; binok BCF STATUS,C ; MOVF tmp0,W ; RETURN ; txchar BCF INTCON,7 ; disable global interrupt BTFSC INTCON,7 ; GOTO txchar ; BSF PORTB,1 ; [1] startbit MOVWF txshft ; [2] save char MOVLW H'08' ; [3] bitcounter MOVWF tmp1 ; [4] MOVLW D'29' ; [5] startbit delay MOVWF tmp0 ; [6] txchara DECFSZ tmp0,F ; [29 * 3 - 1] GOTO txchara ; [92] txcharb RRF txshft,F ; [93] data bit BTFSS STATUS,C ; [94] GOTO txchar0 ; [96] NOP ; [96] txchar1 BCF PORTB,1 ; [1] 1 bit GOTO txcharc ; [3] txchar0 BSF PORTB,1 ; [1] 0 bit GOTO txcharc ; [3] txcharc MOVLW D'28' ; [4] databit delay MOVWF tmp0 ; [5] txchard DECFSZ tmp0,F ; [28 * 3 - 1] GOTO txchard ; [88] NOP ; [89] DECFSZ tmp1,F ; [90] done? GOTO txcharb ; [92] GOTO txchare ; [93] txchare GOTO txcharf ; [95] txcharf NOP ; [96] BCF PORTB,1 ; [1] stopbit MOVLW D'30' ; [2] stopbit delay MOVWF tmp0 ; [3] txcharg DECFSZ tmp0,F ; [30 * 3 - 1] GOTO txcharg ; [92] BSF INTCON,7 ; [93] enable global interrupt RETLW 0 ; [95] END ;