;               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
