/**[ X10 Sniffer II ]********************************************************* Copyright (C) 2001 Neil Cherry (ncherry@linuxha.com) 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. Date: 11/04/2001 http://www.linuxha.com/ Target: PIC16F877 / PIC16F628 Baud: 9600 Bits: 8 Parity: none Stop bits : 1 Warning: Baud rate Clock frequency dependant (using a 4MHz xtal) The X10 sniffer is a very simple device, It monitors the ZC, when a ZC occurs it samples the Rx ~400ms later and then outputs either an ASCII 1 or a 0 to the RS232 port. It will send a continious stream of data, 1 bit for each ZC it encounters. ---------------------------------------------------------------------------- Date Who Comments ---- --- ------------------------------------------------------------------- 01/00 njc Well this is the first go around with the C2C compiler. It has an idiosynchronousys of it own. You have to be careful with if statements and compares. But it does keep track of the variables. I've removed much of the assembly langauge (except for a few debug sections of code). Currently the RS232 code is working and so is the External Interrupt code. I'm having a great deal of trouble with the Timer0 code and I'll need to find an example of how it works and copy it. 01/00 njc I have all the code working but I have the X10 Rx input on PortD for now. I'm having some kind of trouble with PortA. It's probably the A/D converter. I'll check this later. 01/00 njc Here is a sample output for a On P3 (See below [ X10 Sniffer]). We no longer send the '.' or the ':'. 06/01 njc I finally have it outputting an even number of 1's & 0's. This C (C2C) compiler can be a great pain sometimes! 10/01 njc I need to work on a way for the X10sniffer to output bits on the line. The easiest method I can come up with is to take the data directly from the RS232 port and use each bit as the bit value to send. This would allow me to create any pattern I wish (including incorrect patterns). It would also move the translation from the users input to raw bits much easier. I would only need to twiddle the bits external to the sniffer. I may prepend the data with an 'X' so I know its what needs to be transmitted. 11/01 njc I'm in the midst of writing an article for Circuit Cellar and I've decided that I want every bit to be seen (even if it's a continuous stream of 0's). I think I'll move the 7th zero crossing logic out to the user's app where it makes more sense. I need to figure out how to send the data and know it's done sending. Flow control will be handled by the user's app. This can be accomplished by monitoring the output bits and knowing a fixed buffer size. I'd like to see the app handle a line of data ending with the 7th ZC. The next line woould contain the number of zc's before the next 1 bit sent and then the next line of data. 2 Things to remember, 1st when transmitting we must send the 'data' on the first half of the cycle and the /data on the second half. And only when we have data to send. 2nd at 9.6K we can send 8 characters in the time it takes a single half cycle of AC. [ X10 Sniffer ] ************************************************************** See the Perl code (x10_sniffer_b.pl) for the X10 strings ******************************************************************************/ #define ICD 1 // Turn on ICD reserved memory locations #define SNIFFER 1 #include "x10.h" //----------------------------------------------------------------------------------- // Start of MAIN //----------------------------------------------------------------------------------- char cnt; void main(void) { char BufIndex; char RxIndex; // char TxIndex; // Was BufIndex chcount = 0; BufferIndex = 0; // zero_bits = 7; cnt = 0; Setup(); /* Setup the PIC */ BufIndex = 0; // Extremely simple Hello message SendChar( '*' ); /* Send a message to the terminal */ SendChar( CR ); SendChar( NL ); // This is the main loop, we really don't doo much here. If we have // a character to send back to the user we send it. while( 1 ) { while( chcount ) { SendChar( RxFifo[ BufIndex++ ] ); if (!(BufIndex < RX_BUFFER_SIZE)) { BufIndex = 0; } if(chcount) { chcount--; } } }// end while 1 } // end of Main() /***[ Interrupt routine ]**************************************************** Three things can interrupt us: 1) A Character from the serial port, we buffer that to be sent on the AC. 2) A Zero crossing, that starts the sample timer (Ignore Phases for now) 3) A Sample timer, we take a sample of the X10 Rx (from the power line) *) If a bit must be sent we set up the other timer and set the bit. Turning it off when we're done. Hmm, we need to remember to negate the bit on the second half of the AC cycles only when we actually have something to send. The interrupts and flags we need are: Timer0 - TOIF (flag, INTCON<2>) and T0IE (mask, INTCON<5>) - X10 Rx Sample timer Timer1 - T1IE (Flag, ..<>) and T1IE (mask, ..<>) - X10 Tx timer RB0 - INTF (flag, INTCON<1>) and INTE (mask, INTCON<4>) - X10 Zero Crossing Rxd - RCEF (flag, PIR1<5>) and PIE1 (mask, PIE1<5>) - RS232 Rxd ------------------------------------------------------------------------ A 1 A 1 SOH | Letter : Number | SOH | Letter : Number | (silence) 1110 | 01101001 | 0110100101 | 1110 | 01101001 | 0110100101 | 000000 Above is the first half of an X10 command, the same 1/2 command is sent twice. Then the second half is sent (again twice). This sniffer will send out the above (without the spaces or '|'s) and send a CR/NL after the 6 silence 1/2 bits. Echo will be used as a form of flow control (not done yet). *****************************************************************************/ void interrupt(void) { char i, flag; // ----------------------------------------------------------------------- if((PIR1 & RCIF_MASK) != 0) { // If USART RX Interrupt RxChars(); // Process the received character clear_bit(INTCON, T0IF); // Reset Timer0 interrupt flag clear_bit( PIR1, RCIF ); // Clear flag } // ----------------------------------------------------------------------- if((INTCON & INTF_MASK) != 0) { // If RB0/INT - Zero Crossing asm:L2; asm nop ; clear_bit(INTCON, INTF); if( Opt == Opt1) { // Toggle edge detect for ZC OPTION_REG = Opt2; // Set Option register 1100 0111 Opt = Opt2; } else { OPTION_REG = Opt1; // Set Option register 1000 0111 Opt = Opt1; } // Start sample timer (400ms) // Init_TRM0 = 256 - ((DELAY * Frequency)/(4 * Prescaler)) // Init_TRM0 = 256 - ((x uS * 4 MHz)/(4 * 2)) // uS cancels MHz, 4/4 = 1, - 3 for initial instruction delay // Set the time to 400 ms TMR0 = 59; // 59 = 256-((400/2)-3) clear_bit(INTCON, T0IF); // Reset Timer0 interrupt flag enable_interrupt( T0IE ); // Enable Timer0 interrupt // ----------------------------------------------------------------------- } else if((INTCON & TOIF_MASK) != 0) { // Timer 0 - Sample X10 Rx clear_bit(INTCON, T0IF); // Reset Timer0 interrupt flag disable_interrupt( T0IE ); // Disable Timer0 interrupt // When the timer goes off we sample //i = input_pin_port_a( Rx ); // Sample the pin asm { ; // i = input_pin_port_D( Rx ); // Sample the pin clrw ; // btfsc PORTD, D'0' ; movlw D'1' ; movwf _i_interrupt ; } if(zero_bits < 7) cnt++; // Then buffer either a 0 or 1 if(i != 1) { // Bits are inverted buf_char('1'); // Send '1' } else { buf_char('0'); // Send '0' } clear_bit(INTCON, T0IF); } // ----------------------------------------------------------------------- // Return from Interrupt } /*****************************************************/ /* setup PIC16F877 options,ports,interrupts */ /*****************************************************/ void Setup(void) { INTCON = 0x00; // Disable all interrupts Opt = Opt1; OPTION_REG = Opt1; // Set Option register 1000 0000 // RBPU off (<7> = 1) // Prescaler = Timer0 // TMR0 rate := 1:2 // And what of ADCON0 ??? ADCON0 = 0x00; // Turn off A/D ADCON1 = 0x06; // Disable ADC on Ports A & E TRISA = PortAConfig; asm clrf PORTA ; TRISB = PortBConfig; asm clrf PORTB ; TRISC = PortCConfig; asm clrf PORTC ; TRISD = PortDConfig; asm clrf PORTD ; TRISE = PortEConfig; asm clrf PORTE ; PIR1 = 0; ConfigureComms(); // Configure USART for Asyncronous //INTCON = (GIE_MASK | PEIE_MASK | INTE_MASK | ~T0IE_MASK); 1101 0000 INTCON = 0xD0; // 1101 0000 // Enable Global Interrupts // Enable all Peripheral Interrupts // Enable External Interrupt on RB0 } /*******************************************************/ /* Configure USART for communications */ /* */ /* Asynchronous mode */ /* 9,600 Baud ( With 4.00 Mhz Clock ) */ /* 8 data bits ( For other rates see PIC16F8XX Data ) */ /* 1 stop bits */ /* No Parity */ /* */ /*******************************************************/ void ConfigureComms(void) { set_bit( RCSTA, SPEN ); // Enable Serial port clear_bit( RCSTA, RX9 ); // 8 bit receive mode clear_bit( TXSTA, TX9 ); // 8 bit transmit mode SPBRG = 25; // SPBRG = 22 ( Set Baud rate 9,600 ) set_bit( TXSTA, BRGH ); // RRGH = 1 ( High speed mode ) clear_bit( TXSTA, SYNC ); // Asyncronous mode; set_bit( TXSTA, TXEN ); // Enable Transmitter set_bit( PIE1, RCIE ); // Enable Receive Interrupt set_bit( RCSTA, CREN ); // Enable continuous receive clear_bit( PIR1, RCIF ); // Clear Receive Interrupt flag set_bit( INTCON, PEIE ); // Enable all Peripheral Interrupts set_bit( INTCON, GIE ); // Enable Global Interrupts } /*****************************************************/ /* Send a character over the RS232 Port */ /* */ /* */ /*****************************************************/ void SendChar(char ch) { while ((TXSTA & TRMT_MASK) == 0); // Wait for TX Empty TXREG = ch; // Load the TXREG } /*****************************************************/ /* Receive a character over the RS232 Port */ /* */ /* Called from Interrupt service routine */ /* */ /* Returns the char received */ /* and saves it in the buffer */ /* */ /*****************************************************/ char RxChars(void) { if ( ( RCSTA & 6 ) == 0 ) // Then if no errors { // Process received character // buf_char( RCREG ); // Save the data (this caused echo back set_bit( RCSTA, CREN ); // Enable receiver. } else { // process any errors here // Beware, we are in the Interrupt routine. // ... clear_bit( RCSTA, CREN ); // Clear any errors set_bit( RCSTA, CREN ); // Enable receiver. } return RCREG; } void buf_char( char c ) { if (!(BufferIndex < RX_BUFFER_SIZE)) { BufferIndex = 0; } RxFifo[ BufferIndex++] = c; // Save the data if(!(chcount == RX_BUFFER_SIZE)) chcount++; }