/**[ 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++;
}
