// sony.c - Serial <==> Sony IR Remote conversion
// Version 1.0
//
// Robert Poor
// MIT Media Laboratory
// January, 2000
//
// This program is designed to run on an iRX 2.1 board.  It loops,
// waiting for incoming serial or IR data.  If the incoming data is
// serial, it reads three hexadecimal bytes and transmits those bytes
// using "Sony style" encoding.  If the incoming data is infrared,
// it reads a 12 bit string of IR data and, if valid, prints it to
// the serial output as a hexadecimal string.
//
// Sony IR Encoding
//
// Sony Remote Controls use a 40Khz modulated IR pulse stream.  Each
// 12 bit command starts with a header followed by 12 data bits, LSB
// first.
//
// Header: 2.4 mSec of pulses (96 pulses) then .6 mSec of darkness.
// 1 bit:  1.2 mSec of pulses (48 pulses) then .6 mSec of darkness.
// 0 bit:  0.6 mSec of pulses (24 pulses) then .6 mSec of darkness.
#include <16F84.H>
#fuses HS,NOWDT,NOPROTECT,PUT
#use DELAY(clock=10000000)

#use fast_io(A)
#use fast_io(B)

// Standard definitions for the irx2_1 board
//
#define RS232_XMIT      PIN_B1  // (output) RS232 serial transmit
#define RED_LED         PIN_B2  // (output) Red LED (low true)
#define IR_LED          PIN_B3  // (output) Infrared LED (low true)
#define IR_SENSOR       PIN_B4  // (input) IR sensor (Sharp IS1U30)
#define RS232_RCV       PIN_B5  // (input) RS232 serial receive

// Macros to simplify I/O operations
//
#define RED_LED_ON      output_low(RED_LED)
#define RED_LED_OFF     output_high(RED_LED)
#define IR_LED_ON       output_low(IR_LED)
#define IR_LED_OFF      output_high(IR_LED)
#define	IR_RECEIVED	(!input(IR_SENSOR))

// Default TRIS bits: RS232_RCV and IR_SENSOR are inputs, all
// others are outputs
//
#define IRX_B_TRIS      0b00110000

// ===================================================================
// General definitions
//

#byte TMR0 = 0x01

struct {
  short int RBIF;
  short int INTF;
  short int T0IF;
  short int RBIE;
  short int INTE;
  short int T0IE;
  short int PEIE;
  short int GIE;
} INTCON;
#byte	INTCON	= 0x0B

// ===================================================================
// SONY IR definitions

// Sony uses 40Khz AM pulses.  One 40Khz period is
// 25 uSec, or 62 RTCC tics at 400 nSec per tic.
//
#define TICS_25US	62

// set the timer to roll over in given number of tics
//
#define SET_TMR0(tics)		\
  TMR0 = 256+2-tics;		\
  INTCON.T0IF = 0

// update the timer to roll over in the given number of tics
// (including compensation for tics already overshot)
//
#define UPDATE_TMR0(tics)	\
  TMR0 += 256+2-tics;		\
  INTCON.T0IF = 0

// Wait for RTCC to roll over, then set it to roll
// over at <tics> RTCC tics in the future.
//

#define WAIT_FOR_TMR0(tics)	\
  while (!INTCON.T0IF) ;	\
  UPDATE_TMR0(tics)

// buffer for data received or sent
//
long int gCmd;

// ===================================================================
// sending SONY IR
//
// These routines use RTCC (aka TMR0) via the SET_TMR0 and UPDATE_TMR0
// macros to maintain accurate 40Khz timing.

// Send npulses of 40Khz pulses
//
void send_pulse(int npulses) {
  do {
    WAIT_FOR_TMR0(TICS_25US);
    IR_LED_ON;
    delay_us(4);
    IR_LED_OFF;
  } while (--npulses);
}

// Send 600 uSec of "silence"
//
void send_silence600() {
  int i;
  i=24;
  do {
    WAIT_FOR_TMR0(TICS_25US);
  } while (--i);
}

void send_command(long int c) {
  int i;

  SET_TMR0(TICS_25US);		/* prime the timer */

  RED_LED_ON;
  i=12;				// 12 data bits
  send_pulse(96);		// send header: 96 pulses on, 16 off
  send_silence600();
  do {
    if (shift_right(&c, 2, 0)) {
      send_pulse(48);		// send one bit: 48 pulses on, 24 off
      send_silence600();
    } else {
      send_pulse(24);		// send zero bit: 24 pulses on, 24 off
      send_silence600();
    }
  } while (--i);
  RED_LED_OFF;
}

// ===================================================================
// receiving SONY IR
//

// Count consecutive 40Khz pulses, returning at the first missed pulse
// Assumes that a starting pulse has already been seen, and that TMR0
// was set up to roll over in 25 uSec after the start pulse.
//
// In sony parlance:
// 96 pulses = header
// 48 pulses = 1 bit
// 24 pulses = 0 bit
// 
// Note that this routine doesn't measure the silence between pulse
// periods.
//
// The routine expects a pulse before 25 uSec elapses.  If a pulse
// arrives, it resets the clock for another 25 uSec and increments
// the pulse counter.  If no pulse has arrived by the time TMR0 rolls
// over, it drops out of the loop and returns the pulse count.

int count_pulses() {
  int count=1;

  output_high(PIN_B7);		// debugging
  INTCON.INTF = 0;		// clear pulse seen flag
  SET_TMR0(TICS_25US);		// expire 25 us hence...
  do {
    if (INTCON.INTF) {
      count++;
      SET_TMR0(TICS_25US);	// reset TMR0
      INTCON.INTF = 0;		// and pulse seen flag
    }
  } while (!INTCON.T0IF);	// drop out of loop if TMR0 expires
  output_low(PIN_B7);

  return count;
}

// Try for 12 sony-style bits of IR data.  Returns 1 on success,
// 0 on unrecognized.  Bits are stored in global gCmd buffer.
//
int recv_command() {
  int pulses, b;

  pulses = count_pulses();
  if ((pulses < 86) || (pulses > 106)) return 0;
  gCmd = 0;
  b = 12;
  do {
    while (!INTCON.INTF) ;	// wait for IR pulse to be seen
    SET_TMR0(TICS_25US);	// tell count_pulses() to check 25 uS hence

    pulses = count_pulses();
    if (pulses > 72) {
      return 0;
    } else if (pulses > 32) {
      shift_right(&gCmd, 2, 1);	// accept between 32 and 72 pulses as a 1
    } else if (pulses > 16) {
      shift_right(&gCmd, 2, 0);	// accept between 16 and 32 pulses as a 0
    } else {
      return 0;
    }
  } while (--b);

  // shift 4 more times to right justity gCmd
  b = 4;
  do {
    shift_right(&gCmd, 2, 0);
  } while (--b);

  return 1;
}

// ===================================================================
// serial I/O
//

#use rs232(baud=9600, xmit=RS232_XMIT, rcv=RS232_RCV)

void crlf() {
  putc('\r');
  putc('\n');
}

void putnibble(int value) {
  value &= 0x0f;
  if (value < 10) {
    value += '0';
  } else {
    value += 'a'-10;
  }
  putc(value);
}

void puthex(int value) {
  swap(value);
  putnibble(value);
  swap(value);
  putnibble(value);
}

void put_command(long int value) {
  RED_LED_ON;
  putnibble(value >> 8);
  puthex(value & 0xff);
  RED_LED_OFF;
}

// read the serial line to fetch exactly three nibbles into 
// gCmd and return.  No error checking is done for valid hex
// chars...
//
void getnibble() {
  char c;
  c = getc();
  if (c <= '9') 
    c = c-'0';
  else
    c = (c-'A')+10;

  c &= 0x0f;
  gCmd <<= 4;
  gCmd |= c;
}

void get_command() {
  gCmd = 0;
  getnibble();
  getnibble();
  getnibble();
}  

// ===================================================================
// main()
//

main() {
  set_tris_a(0x00);				// all outputs
  set_tris_b(IRX_B_TRIS | 0b00000001);		// b0 is also (INTF) input
  setup_counters(RTCC_INTERNAL, WDT_18MS);	// 400 nSec per tic
  ext_int_edge(H_TO_L);		// watch for leading edge of IR pulse

  RED_LED_ON;			// reality check at startup
  delay_ms(200);
  RED_LED_OFF;

  printf("Sony 1.0\r\n");

  while (1) { 
    if (kbhit()) {
      // here comes serial data...
      get_command();		// fetch 12 bits from serial into gCmd
      send_command(gCmd);	// send 12 bits via IR using Sony code

    } else if (INTCON.INTF) {
      // here comes IR data...
      SET_TMR0(TICS_25US);	// tell count_pulses() to check 25 uS hence
      if (recv_command()) {	// fetched a valid 12 bits?	
        put_command(gCmd);	// yes, print as hex
      } else {
        putc('?');
      }
      crlf();
    }
  }
}

