; X-10 WALL SWITCH NODE ;EDWARD CHEUNG, PH.D. - edward.b.cheung.1@gsfc.nasa.gov ; http://members.tripod.com/~edward_cheung ; Spring 1997 ; ;This code has been tested using a 16C74 since I had those in ample ;supply. The 16C71 is much smaller and will fit into the wall switch ;so after the code is debugged a '71 should be used. ;This code will support the '74's UART, which is handy for sending ;debug info to a terminal. Connect the output of the UART (pin 25 ;on a '74) to a RS232 driver such as the MAXIM232. And set the terminal ;speed to 1200 baud 8N1. ;Since the original LC oscillator in a wall switch is used, this code ;will run quite slowly (240kHz clock). However this code has been tested ;to be able to receive X-10 on, off, dim, and bright commands. It is ;then able to control a light bulb in the normal way. ;To enable the wall switch's power supply to power the PIC, one needs ;to change the zener in the power supply to a 5.0 to 5.6 Volt unit! ;This will be the only change needed to the circuit. ;See my web page for links to the wall switch's schematic. ;Pin connection diagram from old (PICO) to new (PIC16C71): ;16C71 PICO function ; 17 1 carrier input ; 14 2 Vcc ; 16 3 oscillator ; 15 4 oscillator ; 1 5 triac out ; 3 6 zero cross in ; 18 7 pushbutton ; 8 n/c ; 9 n/c ; 10 unit code ; 11 unit code ; 12 unit code ; 13 unit code ; 14 house code ; 15 house code ; 16 house code ; 17 house code ; 5 18 gnd ; ;The code works by rapidly taking many samples of the X-10 input carrier ;pin right after a zero crossing of the power line. The data is then ;analyzed afterwards to see if it has the X-10 carrier burst by the ;function 'X10_PROCESS'. The actual protocol decoder (see document ;that comes with the TW523) is done by 'X10_RX'. Once it is determined ;that a real X-10 command is sent, 'TW_NEW' decides which command was ;sent (on or off etc.). This latter function sets the brightness level ;of the lamp in the variable 'SW_LEVEL'. GOTO MN_MAIN #DEFINE __16C74 ;define processor used INCLUDE "P16CXX.INC" ;include file from Microchip ;Constants set by user F_OSC EQU D'240000' ;free running frequency of PIC X10_HI EQU H'04' ;amount over x10_lv considered to be x10 hi IFDEF __16C71 SAMPLE_MAX EQU H'2F' ;place to put last sample SAMPLE_MIN EQU H'20' ;place to put first sample ENDIF IFDEF __16C74 SAMPLE_MAX EQU H'7F' ;place to put last sample SAMPLE_MIN EQU H'70' ;place to put first sample ENDIF R4_BAUDRATE EQU D'1200' TW_LED EQU PORTD ;port for x10 led TW_RXLED EQU H'00' ;pin for led ;Constants derived from user input R4_BAUD EQU (F_OSC/(R4_BAUDRATE*D'16')) - D'1' ;SEE 2-459 of Databook INS_CYCLE EQU F_OSC / (D'4' * D'120') ;instructions / power cycle PRESCALE EQU INS_CYCLE/D'256' ;required division by prescaler I=PRESCALE>>1 PS_FACTOR = 0 WHILE I>0 I=I>>1 PS_FACTOR = PS_FACTOR + 1 ENDW ;Port assignments for this project TW_PORT EQU PORTA TW_IN EQU H'0' ;carrier in TW_BUTTON EQU H'1' ;pushbutton in TW_TRIAC EQU H'2' ;triac control out TW_60 EQU H'4' ;60 Hz in TW_TEST EQU H'5' ;TEST OUT ;Select page 1 PAGE_1 MACRO BSF STATUS,RP0 ENDM ;Select page 0 PAGE_0 MACRO BCF STATUS,RP0 ENDM ;***** Memory Management ;Assign memory location to input variable 'name' ;Addresses will asm wtart at MEM_FIRST, and last one allowed is at MEM_LAST ;0CH to 2FH inclusive are available on '71. ;20H to BFH inclusive are available on '64, except for interval between ;80H to 9F ;20H to FFH inclusive are available on '74, except for interval between ;80H to 9F ;See .lst file for actual addresses. In that file, ;MEM_INDEX will be one address past the last one. ;Thus max for MEM_INDEX is MEM_LAST + 1 IFDEF __16C71 MEM_FIRST EQU H'0C' MEM_LAST EQU H'2F' ENDIF IFDEF __16C64 MEM_FIRST EQU H'20' MEM_LAST EQU H'BF' ENDIF IFDEF __16C74 MEM_FIRST EQU H'20' MEM_LAST EQU H'FF' ENDIF MEM_INDEX SET MEM_FIRST ALLOC MACRO NAME NAME EQU MEM_INDEX IF MEM_INDEX > MEM_LAST ERROR "OUT_OF_MEMORY" ;If this line is encountered, reduce the number of modules in use ELSE MEM_INDEX SET MEM_INDEX + 1 IF MEM_INDEX == 80 MEM_INDEX SET H'A0';to next page of memory ERROR "CROSS PAGE BOUNDARY" ;Remaining vars can only be accessed by setting page bit ENDIF ENDIF ENDM ;Memory location assignment ;If the minimum value of 'sw_level' is violated, the interrupt to ;turn on the TRIAC will not come before the next crossing of the ;60Hz cycle. This minimum is TBD ALLOC SW_LEVEL ;current dim level 0xff=full on ;parameter below is threshold above which the load is turned on ;as soon as possible. This is because the interrupt from timer0 ;would occur during the interrupt handler SW_FULL EQU H'FA' ; 0xff - (d'11'/2) ALLOC SAMPLE_HI ;value of largest x10 sample and difference ALLOC SAMPLE_LO ;value of smallest x10 sample ALLOC INT_W ;temporary value for W ALLOC MAIN_FLAGS ;control flags, one of the following: MN_RUN EQU H'00' ; process sampled x-10 data CK_60 EQU H'01' ; clock source is 60 hz X10_CA EQU H'02' ; carrier present at last sampling X10_2ND EQU H'03' ; second x-10 message in pair ALLOC TW_PHASE ;which x10 bit being sampled ALLOC TW_KEY ;key code ALLOC TW_HOUSE ;house code ALLOC TW_1_KEY ;key code of first x-10 command ALLOC TW_1_HOUSE ;house code of first x-10 command ALLOC TW_O_PHASE ;which x10 bit being transmitted ALLOC X10_LV ;LO level standard ;Called when there is an interrupt INT_VECT ORG H'04' BTFSS MAIN_FLAGS,CK_60 ;if (ck_60 == 1) { GOTO INT_RTS0 ; // interrupt from 60 edge X10_GET BSF ADCON0,2 ;start conversion BSF PORTD,1 NOP NOP I = SAMPLE_MIN WHILE I<=SAMPLE_MAX MOVFW ADRES ;put result in W BSF ADCON0,2 ;start conversion NOP MOVWF I ;put W to register I = I+1 ENDW BCF PORTD,1 BSF MAIN_FLAGS,MN_RUN X10_DONE BCF MAIN_FLAGS,CK_60 PAGE_1 BCF OPTION_REG^H'80',T0CS ; // clear rts: setup for internal CLRWDT BCF OPTION_REG^H'80',PSA ; // clear psa: prescaler to tmr0 PAGE_0 ; MOVFW SW_LEVEL ; if (sw_level > sw_full) ; SUBLW SW_FULL ; SKPC ; BSF TW_PORT,TW_TRIAC ; // trigger triac right away ; BCF TW_PORT,TW_TRIAC ;Clear interrupt sources BCF INTCON,T0IF ; // clear interrupt from timer 0 MOVFW SW_LEVEL ; timer0 = sw_level MOVWF TMR0 RETFIE INT_RTS0 ;} else { ; // interrupt from internal edge PAGE_0 BSF TW_PORT,TW_TRIAC ; // trigger triac BCF TW_PORT,TW_TRIAC BSF MAIN_FLAGS,CK_60 MOVWF INT_W ; // save W PAGE_1 BSF OPTION_REG^H'80',T0CS ; // set rts: setup for 60 hz CLRWDT BSF OPTION_REG^H'80',PSA ; // set psa: prescaler to WDT MOVLW B'00010000' ; // setup for next edge on 60 hz ; // RTE bit hard coded here XORWF OPTION_REG^H'80',F; PAGE_0 MOVLW H'FF' ; // setup timer0 to trip on next edge MOVWF TMR0 ; timer0 = 0xff; DEBUG. FF for all edges MOVFW INT_W ; // restore W ;Clear interrupt sources BCF INTCON,T0IF ; // clear interrupt from timer 0 INT_NEXT ;} RETFIE ;AD STUFF CH0 EQU 00H CH1 EQU 08H CH2 EQU 10H CH3 EQU 18H ;Select CHANNEL as the desired A/D input ;Usage: AD_SELECT CH0 AD_SELECT MACRO CHANNEL MOVLW B'11000001' ;use internal clock, ad on IORLW CHANNEL ;program channel MOVWF ADCON0 ;setup ad BCF INTCON,ADIE ;disable A/D interrupt ENDM ;Read the currently selected A/D input into W AD_READ BSF ADCON0,2 ;start conversion NOP AD_TEST BTFSC ADCON0,2 ;test ad done GOTO AD_TEST ;test again MOVF ADRES,W ;put result in W RETURN SER_SEND CLRWDT BTFSS PIR1,TXIF ; while (txif == 0) { GOTO SER_SEND SER_OK MOVWF TXREG ; // send to serial RETURN SER_INIT ;SERIAL PORT MOVLW R4_BAUD PAGE_1 MOVWF SPBRG^H'80' ;baud rate register CLRF TXSTA^H'80' BSF TXSTA^H'80',BRGH;baud rate select BSF TXSTA^H'80',TXEN;enable tx BCF PIE1^H'80',RCIE;disable interrupt on rx BCF PORTC,6 ;tx is output PAGE_0 CLRF RCSTA BSF RCSTA,SPEN ;pins for serial BSF RCSTA,CREN ;enable rx RETURN MN_INIT ;PortB no pullup, Prescaler to WDT. See Page 2-355 '94 edition PAGE_1 MOVLW PS_FACTOR ANDLW H'7' IORLW B'10111000' ;option_reg = option | (ps_factor&0x7) MOVWF OPTION_REG^H'80' BSF TW_PORT,TW_60 ;1 for input BSF TW_PORT,TW_IN BSF TW_PORT,TW_BUTTON BCF TW_PORT,TW_TRIAC ;0 for output BCF TW_PORT,TW_TEST BCF PORTC,6 ;SERIAL OUTPUT BCF PORTD,1 MOVLW B'00000010' ;RA0 and RA1 analog P16C71 MOVLW B'00000100' ;RA0 and RA1 analog P16C74 MOVWF ADCON1^H'80' ;setup PORTA function PAGE_0 ;Interrupt control register MOVLW B'10100000' MOVWF INTCON ;Setup Timer 0 MOVLW H'FF' MOVWF TMR0 CALL SER_INIT ;Init vars MOVLW H'60' MOVWF SW_LEVEL CLRF SAMPLE_HI MOVLW H'FF' MOVWF SAMPLE_LO CALL TW_RESET CLRF MAIN_FLAGS CLRF TW_O_PHASE BSF MAIN_FLAGS,CK_60 MOVLW H'05' MOVWF X10_LV RETURN MN_MAIN CALL MN_INIT MOVLW 'H' CALL SER_SEND MOVLW 'I' CALL SER_SEND AD_SELECT CH1 CALL AD_READ MOVWF SW_LEVEL CALL SER_SEND AD_SELECT CH0 MN_LOOP ; MOVFW TMR0 ; SKPZ ; GOTO MN_LOOP ; CALL X10_GET BSF TW_PORT,TW_TEST BCF TW_PORT,TW_TEST BTFSS MAIN_FLAGS,MN_RUN ;if (mn_run) { GOTO MN_LOOP BCF MAIN_FLAGS,MN_RUN ; mn_run = 0; CALL X10_PROCESS2 ;decide if carrier on CALL X10_RX ;convert carrier status to x-10 command ; MOVFW TW_PHASE ; CALL SER_SEND ; DEBUG. current progress of x-10 input GOTO MN_LOOP ;Decides if samples taken are a carrier on or off ;based on limited high pass filter algorithm X10_PROCESS2 CLRF SAMPLE_HI I = SAMPLE_MIN WHILE I< SAMPLE_MAX MOVFW I ;W = DATA[I+1] - DATA[I]; SUBWF I+1,W SKPC ;if (W < 0) XORLW H'FF' ; W = ~W; MOVWF SAMPLE_LO ;sample_lo = W; SUBLW H'10' ;if (W > 16) MOVLW H'10' SKPC ; sample_lo = 16 MOVWF SAMPLE_LO MOVFW SAMPLE_LO ;W = sample_lo; ADDWF SAMPLE_HI,F ;sample_hi += W; MOVLW H'FA' SKPNC ;if (carry == 1) MOVWF SAMPLE_HI ; sample_hi = 0xfA; I = I+1 ENDW MOVFW SAMPLE_HI CALL SER_SEND ;DEBUG. show max diff ; MOVLW H'FE' ; CALL SER_SEND ;DEBUG. carriage return MOVFW SAMPLE_HI ;W = X10_LV - sample_hi SUBLW H'08' BCF MAIN_FLAGS,X10_CA;x10_ca = 0; SKPC ;if (W < 0) BSF MAIN_FLAGS,X10_CA; x10_ca = 1; RETURN ;Decides if samples taken are a carrier on or off ;based on max-min algorithm X10_PROCESS CLRF SAMPLE_HI ;//reset vars MOVLW H'FF' MOVWF SAMPLE_LO I = SAMPLE_MIN WHILE I<=SAMPLE_MAX MOVFW I ; CALL SER_SEND ; DEBUG. show all data sampled SUBWF SAMPLE_HI,W ;if (sample_hi < sample[i]) MOVFW I ; sample_hi = sample[i]; SKPC MOVWF SAMPLE_HI MOVFW SAMPLE_LO ;if (sample_lo > sample[i]) SUBWF I,W MOVFW I ; sample_lo = sample[i]; SKPC MOVWF SAMPLE_LO I = I+1 ENDW MOVFW SAMPLE_LO ;W = sample_hi - sample_lo; SUBWF SAMPLE_HI,W MOVWF SAMPLE_HI SUBWF X10_LV,W ;W = X10_LV - W BCF MAIN_FLAGS,X10_CA;x10_ca = 0; SKPC ;if (W < 0) BSF MAIN_FLAGS,X10_CA; x10_ca = 1; ; MOVFW SAMPLE_HI ; CALL SER_SEND ;DEBUG. show max diff ; MOVLW H'FE' ; CALL SER_SEND ;DEBUG. carriage return RETURN ;decodes 1/0 on x-10 carrier to actual x-10 command X10_RX MOVLW D'12' ; if (tw_phase >= 12) { SUBWF TW_PHASE,W SKPC GOTO TW_HOUSECODE ; // sample key code INCF TW_PHASE,F ; tw_phase ++; BTFSS TW_PHASE,W ; if (tw_phase,W == 1) { GOTO TW_KEY_HALF CLRC ; // first half bit RRF TW_KEY,F ; tw_key >> BTFSC MAIN_FLAGS,X10_CA; if (X10_CA = 1) BSF TW_KEY,4 ; set tw_key,4; GOTO TW_SAMPLE_END TW_KEY_HALF ; } else { ; // second half bit BTFSS MAIN_FLAGS,X10_CA; if (X10_CA == 1) { GOTO TW_KEY_ELSE BTFSC TW_KEY,4 ; if (tw_key,4 != 0) CALL TW_RESET ; tw_reset; GOTO TW_SAMPLE_END TW_KEY_ELSE ; } else { BTFSS TW_KEY,4 ; if (tw_key,4 != 1) CALL TW_RESET ; tw_reset; GOTO TW_SAMPLE_END ; } TW_HOUSECODE ; } MOVLW D'4' ; } else if (tw_phase >= 4) { SUBWF TW_PHASE,W ; // sample house code SKPC GOTO TW_SYNC_B INCF TW_PHASE,F ; tw_phase ++; BTFSS TW_PHASE,W ; if (tw_phase,W == 1) { GOTO TW_HOUSE_HALF CLRC ; // first half bit RRF TW_HOUSE,F ; tw_house >> BTFSC MAIN_FLAGS,X10_CA; if (X10_CA = 1) BSF TW_HOUSE,3 ; set tw_house,3; GOTO TW_SAMPLE_END TW_HOUSE_HALF ; } else { ; // second half bit BTFSS MAIN_FLAGS,X10_CA; if (X10_CA == 1) { GOTO TW_HOUSE_ELSE BTFSC TW_HOUSE,3 ; if (tw_house,3 != 0) CALL TW_RESET ; tw_reset; GOTO TW_SAMPLE_END TW_HOUSE_ELSE ; } else { BTFSS TW_HOUSE,3 ; if (tw_house,3 != 1) CALL TW_RESET ; tw_reset; GOTO TW_SAMPLE_END ; } TW_SYNC_B MOVLW D'3' ; } else if tw_phase == 3) { SUBWF TW_PHASE,W SKPZ GOTO TW_SYNC_A BSF TW_LED,TW_RXLED; tw_indicator(on); INCF TW_PHASE,F ; tw_phase ++; BTFSC MAIN_FLAGS,X10_CA; if (X10_CA == 1) CLRF TW_PHASE ; tw_phase = 0; GOTO TW_SAMPLE_END ; } TW_SYNC_A ; } else { // check for first sync MOVFW TW_O_PHASE ; if (tw_o_phase != 0) SKPZ ; return; RETURN INCF TW_PHASE,F ; tw_phase ++; BTFSC MAIN_FLAGS,X10_CA; if (X10_CA == 0) RETURN ; // no message, reset and cancel CLRF TW_PHASE ; tw_phase = 0; BCF MAIN_FLAGS,X10_2ND; x10_2nd = 0; RETURN TW_SAMPLE_END ; } MOVLW D'22' ; if (tw_phase == 22) { SUBWF TW_PHASE,W SKPZ RETURN CALL TW_NEW ; new_tw(); CALL TW_RESET ; tw_reset(); BCF TW_LED,TW_RXLED; tw_indicator(off); RETURN ;} X10_ON EQU H'14' X10_OFF EQU H'1C' X10_DIM EQU H'12' X10_BRIGHT EQU H'1A' X10_AL_ON EQU H'18' X10_AL_OFF EQU H'16' X10_AU_OFF EQU H'10' TW_NEW ;RETURN ;DEBUG. react to received commands BTFSC MAIN_FLAGS,X10_2ND ;if (x10_2nd == 0) { GOTO TW_SECOND ; // first of two commands BSF MAIN_FLAGS,X10_2ND ; x10_2nd = 1; MOVFW TW_HOUSE ; tw_1_house = tw_house; MOVWF TW_1_HOUSE MOVFW TW_KEY ; tw_1_key = tw_key; MOVWF TW_1_KEY RETURN TW_SECOND ;} else { BCF MAIN_FLAGS,X10_2ND ; x10_2nd = 0; MOVFW TW_HOUSE ; if (tw_house == tw_1_house) { SUBWF TW_1_HOUSE,W SKPZ RETURN MOVFW TW_KEY SUBWF TW_1_KEY,W ; if (tw_key = tw_1_key) { SKPZ RETURN ; // valid x-10 command MOVFW TW_KEY ; if (tw_key == X10_ON) SUBLW X10_ON SKPZ GOTO TW_NEW_1 MOVLW H'EF' ; sw_level = 0xEf; MOVWF SW_LEVEL TW_NEW_1 MOVFW TW_KEY ; if (tw_key == X10_OFF) SUBLW X10_OFF SKPZ GOTO TW_NEW_2 MOVLW H'60' ; sw_level = 0x60; MOVWF SW_LEVEL TW_NEW_2 MOVFW TW_KEY ; if (tw_key == X10_DIM) SUBLW X10_DIM SKPZ GOTO TW_NEW_3 MOVLW H'F0' ; sw_level -= 0x10; ADDWF SW_LEVEL,F TW_NEW_3 MOVFW TW_KEY ; if (tw_key == X10_BRIGHT) SUBLW X10_BRIGHT SKPZ GOTO TW_NEW_4 MOVLW H'10' ; sw_level += 0x10; ADDWF SW_LEVEL,F TW_NEW_4 MOVFW TW_HOUSE ;DEBUG. show rx commands CALL SER_SEND MOVFW TW_KEY CALL SER_SEND RETURN ;}}} ;Reset variables for start of x10 reception TW_RESET CLRF TW_PHASE ; tw_phase = 0; CLRF TW_HOUSE ; tw_house = 0; CLRF TW_KEY ; tw_key = 0; RETURN ;} END