EmbeddedRelated.com

Universal InfraRed remote pass-thru

Kenny Millar March 22, 2013 Coded in C for the Microchip PIC18

This is some code I wrote recently for a PIC18F processor. The basic prinicipal was to be able to receive any IR Remote Control, encode it for transmission (in this case over RS485) and re-transmit that code at a remote location. For example to use a TV or DVD player remote in a bedroom to control a SKY BOX or DVD playerm or whatever, tucked away in another room. The requirment was to work with just about any remote control from any vendor. This is not as easy as it sounds since there are so many different methods of encoding IR remote control codes. In the end the big break comes from the fact that we do not need to de-code the signals, just trap them, transmit them to a remote location and re-emit them as IR again.

This code snippet is the interrupt based code which detects valid IR RC signals, times the pulses and encodes that into a string for transmitting across IR, then transmits the data over the UART of the PIC. Another device receives the sreial data, decodes it and re-transmits it by ANDing the resulting data with a 38KHz clock and gates that out to a standard IR LED.

This is how it works. The Infrared reciever is attached to the INT0 input on the PIC, and the interrupt is set to fire on the -VE edge. The ISR then counts the amount of time that the signal remains either at 1 or 0 until either it has reached the maximum number of counts, or the signal has stopped (by reaching a max count in the loop). Each count is stored as a 16 bit unsigned int in an array.

The values do not represent milliseconds or micoseconds or any other unit - but they are ALL very consistent even when running off the internal oscillator clock.

So to re-transmit the infra red signal you need to know how the codes relate to real world times - and I found that by measureing the actual signal at INT0 with an oscilloscope against the counts stored in the array and found that  if you multiply the count by 1.42 it gives you the number micro-seconds the signal was either 1 or 0

All Infra red signals start with a low-going edge (0 at INT0) and end with the IR transmitter off - which gives a '1' at INT0.

I found this to work with every IR transmitter I could get my hands on - which was over 100 different remotes from dozens of different manufacturers, including SKY+ boxes, SONY, Philips, NEC, Denon etc etc

#define MAX_IR_INDEX 64
#define ESCAPE_COUNT 0x2200ul /* Could be reduced?*/

#pragma udata ircounts
unsigned int lcounts[MAX_IR_INDEX];
unsigned int hcounts[MAX_IR_INDEX];

#pragma	interrupt high_isr /*=section(".tmpdata")*/

void high_isr(void) {

	unsigned int l;
	unsigned int c;
	unsigned char th, tl;

	l = 0;
	TMR1H = 0; // Always write H before L
	TMR1L = 0;

	// Check that this is the INT0 interrupt
	// IRRX is int0
	if(INTCONbits.INT0IF)
	{
		// Disable the interrupt source
		INTCONbits.INT0IE = 0;

		// the code in the while needs to be balanced so that the ==0 and ==1 bits are the size number of cycles.
		while(lindex < MAX_IR_INDEX)
		{

			while(PORTBbits.RB0 == 0)
			{
				l++;
				if(l == ESCAPE_COUNT)
				{
					lcounts[lindex] = 0xffff;
					goto done;
				}
			}

			tl = TMR1L;
			th = TMR1H; // << 8 ;// Always read L before H
			lcounts[lindex++] = th * 256 + tl;

			l = 0;
			TMR1H = 0; // Always right H before L
			TMR1L = 0;
			while(PORTBbits.RB0 == 1)
			{
				l++;
				if(l == ESCAPE_COUNT)
				{
					hcounts[hindex] = 0xffff;
					goto done;
				}
			}

			tl = TMR1L;
			th = TMR1H; //<< 8 ;// Always read L before H
			hcounts[hindex++] = th * 256 + tl;

			l = 0;
			TMR1H = 0; // Always write H before L
			TMR1L = 0;

		}
	}

done:
	codeReady = 1;

	// reset interrupt status bit
	//INTCONbits.INT0IE = 1;
	//INTCONbits.INT0IF = 0;
	return;
}

void zeroIR() {
	unsigned char c;

	// Initialize the IR Count holders
	lindex = 0;
	hindex = 0;

	for(c = 0; c < MAX_IR_INDEX; c++)
	{
		lcounts[c] = 0xdead;
	}

	codeReady = 0;

	// reset interrupt status bit
	INTCONbits.INT0IE = 1;
	INTCONbits.INT0IF = 0;

}

void transmitIR(void) {

	unsigned char c;

	// First check that the code is valid by examining the first LCOUNT
	// if it is less than our defined threshold we will not transmit it.
	// The first lcount holds the low-going preamble, it must be a minimum length or it does not
	// represent a valid IR Transmission preamble.

	if(lcounts[0] < PREAMBLE_MINIMUM)
	{
		return;
	}
	
	sprintf(serTX, (CAST) "$%c%cIC%d,", id1, id2, lindex);
	rs485_puts(serTX);

	for(c = 0; c < lindex; c++)
	{
		sprintf(serTX, (CAST) "%04x,%04x, ", lcounts[c], hcounts[c]);
		rs485_puts(serTX);
	}

	sprintf(serTX, (CAST) "\n");
	rs485_puts(serTX);

}