// 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 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(); } } }