Raspberry Pi Infrared TV Controller

Background

I have a Raspberry Pi 3B sitting on my desk doing nothing, and for fun I’d like to see if I can figure out how to work with the I/O pins. An interesting project might be a TV remote control, where the Raspi sits near the TV for its whole life, receiving a signal via IP port or from a timer, and simulates remote control button presses to change channels at specified times, and perhaps other things.

So the first step would be to download and install the latest Raspian OS. It’s probably been a couple of years since this card has run, so let’s download the full version from here.

I don’t know why I’ve never tried NOOBS, and while I have tried the lighter downloads, I’ll just stick with the full version with GUI for now. It seems to also include things I will need later, such as the C compiler and a package called wiringPi that looks like a good way to control the I/O pins from C code.

So I won’t go into those details about writing the OS to the SAN card, first boot, setting up network, etc. That should all be normal stuff.

Side Note: Around 1979 I was trying to learn about digital circuits and built things like my own 8080 wire-wrap board, floppy disk interface, and even my own design for a terminal display board. So I still remember a little about electronics, although it’s been a long time. So bear with me since this is now 2019.

Infrared LED and Receiver

It takes a special LED to spit out infrared, and a transistor with the base connected to a photodiode or similar to receive. While I don’t need a receiver for the final product, I’ll still need it to examine and record the pulses from my TV remote, so we can play them back later.

Instead of buying separate parts and wiring things myself, let’s just buy an IR Shield Infrared Remote Hat card which seems to be popular. Now no matter how hard I try, I can’t seem to find a schematic for this little card. I can try to trace the board but it’s tough. Here’s basically what I came up with, but I’m not really sure:

ir_hat_1 

The board also has a couple of buttons that connect to other I/O pins, but we probably won’t be using those. 

ir_hat_2

The board connects right up and sits on the Pi… I guess that’s why they call it a “Hat”. Since I won’t be using this Pi for anything else, it’s no problem that most of the other connectors are a bit covered.

ir_hat_3

Note that in the picture above I’ve removed the HDMI monitor, USB keyboard and mouse, and Ethernet. I used all those for initial boot, but after that I can disconnect them and just connect via SSH from my Windows 10 computer on the same network.

LIRC and GPIO_IR

LIRC means Linux Infrared Remote Control. GPIO_IR means General Purpose Input Output – Infrared. I googled and googled and tried various things and could barely get LIRC to install, let alone work, which it would not. Probably that’s because I don’t know what it’s supposed to do, or based on some of the web pages I found, nobody seems to be able to get LIRC to work on a modern Raspberry Pi. And I couldn’t find enough about GPIO_IR to really get anything done.

So let’s skip those for now and see if we can just send and receive with our own code.

wiringPi Coding

There’s a package that comes installed with the full Raspian distribution called wiringPi, which is a C interface to the GPIO pins, among other neat things. In fact, apparently when you type the gpio command you are calling wiringPi subroutines, such as this one:

pi@raspberrypi:~$ gpio readall
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 0 | IN   | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | IN   | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 1 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+

That chart (while odd because it is double sided) is pretty helpful because it shows the various numbers the GPIO pins go by, depending on who is talking, such as:

IR Shield Function Physical Pin BCM wPi
LED Output 11 17 0
Phototransitor Input 12 18 1
Button #1 13 27 2
Button #2 15 22 3

So we can also issue the gpio command to check a single bit, such as this command to read button #1 (while holding it down. Note that we are specifying “2″ which is the wPi number for that button in the chart above. Also note that pressing the button results in a “1″ being printed. The button wiring sets the pin to 3.3V when pressed, so at least in this case, a “1″ means that pin is set high.

pi@raspberrypi:~$ gpio read 2
1

Since gpio is probably written in C, and from what I understand, calls wiringPi routines, then we should be able to do the same with our own code.

Let’s try reading the Phototransistor output, which is wPi pin 1:

pi@raspberrypi:~$ gpio read 1
1

Without pressing any keys on a TV remote control, that pin always returns 1. But when pressing a remote button and producing an IR signal, that pin does show a 0 intermittently. That shows the card is working – well, at least somewhat.

pi@raspberrypi:~$ gpio read 1
0

Initial Testing of the Phototransistor Input

The C compiler is already installed on the full Raspian distribution, along with wiringPi routines. So here’s an example for reading the phototransistor a little faster.  Edit this code into a file named read_ir.c

//*********************************************************************
// read_ir.c – Testing GPIO with IR Card on Raspberry Pi 3
//*********************************************************************

#include <stdio.h>
#include <wiringPi.h>

int main(int argc, char *argv[])
{
 int b;

 wiringPiSetup();
 pinMode(1, INPUT);

 while (1)
 {
  b = digitalRead(1);
  printf("%d", b);
 }
}

So basically, we call wiringPiSetup() which I assume initializes things and checks to make sure we’re running as root (or at least have access to certain required things). Then we call pinMode() to set pin 1 to INPUT mode. It already is INPUT by default, but we should get used to doing this because we’ll have to set the LED pin to output later. Then we call digitalRead() to read pin 1, returning either a 1 or 0 depending on the state of the phototransistor. With the LED, the only addition will be the digitalWrite() routine, and perhaps some other routines such as delayMicroseconds() which comes with wiringPi.

Here’s a simple makefile for the code above. Make sure there is a tab character in front of the gcc lines.

#**********************************************************************
# read_ir.mak
#**********************************************************************

OBJ= read_ir.o
DEPS=
CFLAGS=
LFLAGS=
LIBS= -lwiringPi

%.o: %.c $(DEPS)
	gcc –g –c –o $@ $< $(CFLAGS)

read_ir: $(OBJ)
	gcc –o $@ $^ $(LFLAGS) $(LIBS)

To compile, name the C source code “read_ir.c” and the make file “read_ir.mak”, then issue the make command pointing to our *.mak file, such as:

pi@raspberrypi:~$ make -f read_ir.mak
 gcc -g -c -o read_ir.o read_ir.c
 gcc -o read_ir read_ir.o -lwiringPi

Then if you have no errors, that should produce executable file read_ir in that same directory, and we can try to run the program. Note that wiringPi needs to run as root, so we’ll issue this with the sudo command, such as:

pi@raspberrypi:~$ sudo ./read_ir
111111111111111111111111111111111111111111111111111111...

That code reads the phototransistor, spits out a 1 or 0, and keeps going. So you get a pile of 1′s on the screen, but as you press buttons on a remote, zeros should appear and fly by. When you ctrl-c to exit the loop, you can scroll back your console messages and hopefully find some zeros. Note that you get hundreds of zeros packed together. This is a good sign because it means the input from the TV remote is much slower than our Pi processor, which will be important because we’re going to attempt to work with this device without any additional hardware.

Initial Testing of the LED Output

Let’s make another *.c program and associated *.mak file. This one will be for testing the IR LED on the board by pulsing it on and off in 1 second intervals – write_led:

//*********************************************************************
// write_led.c - Testing GPIO with IR Card on Raspberry Pi 3
//*********************************************************************

#include <stdio.h>
#include <unistd.h>
#include <wiringPi.h>

int main(int argc, char *argv[])
{
 int b = 0;

 wiringPiSetup();
 pinMode(0, OUTPUT);

 while (1)
 {
  if (b == 0) b = 1; else b = 0;
  digitalWrite(0,b);
  sleep(1);
 }
}
#**********************************************************************
# write_led.mak
#**********************************************************************

OBJ= write_led.o
DEPS=
CFLAGS=
LFLAGS=
LIBS= -lwiringPi

%.o: %.c $(DEPS)
	gcc -g -c -o $@ $< $(CFLAGS)

write_led: $(OBJ)
	gcc -o $@ $^ $(LFLAGS) $(LIBS)

So we can compile that and run as before. This time we get no output from the screen because there’s no printf() call, and no reason to. Output is from the LED on the IR board, which should be blinkling slowly. I can’t see it because it’s beyond my vision, but if I point my cell-phone camera at it, I can see a dim purple light blinking on and off every second, so this portion of the board works too.

ir_hat_4

Probably best to wait until the light blinks off before hitting ctrl-c and exiting this program, so we don’t burn the LED too much, although I doubt if this is a real issue.

Standard IR Pulse Methods

When you look at a TV Remote Control through a cell phone camera and push a button, you can see a short set of pulses – maybe 1/4 of a second or so. Here’s a web page that helps describe the details of what is happening.

Probably most importantly, IR sensors apparently had trouble picking the signal out of all the IR noise you might have around the house, so they modulate the signal at 38KHz. This isn’t like modulating a carrier wave though. Instead, when the TV remote LED is off, it’s just off. But when it’s on, it’s really pulsating at 38KHz. That way, the TV can filter that frequency and not respond to other IR sources.

So let’s steal an image from that web page, thank you very much…

ir_signal_1

That’s the entire signal for one button push. Now I’m terrible at math, but the total 67.5ms time frame would be about 1/15 of a second, nowhere near 1/4 of a second as it looks on my cell phone camera. I’ll take a guess that’s simply because of the slow response of the camera/display and skip that little detail for now.  Or maybe I was holding the TV button and it was repeating.

The green portion in the image above where the light is On is actually being pulsed at 38KHz or 26.3 uSec. That’s about 13.2 uSec On and 13.2 uSec Off, by the way. So for that first 9ms of On time we would have about 342 modulation pulses. Then we are Off for 4.5ms. Note that some of my remotes seem to be Off for 9ms for this part of the signal, which may be something to record rather than guess.

Then we see 4 groups of data pulses, Address, Inverse Address, Command, and Inverse Command. Each of these sections is 8 bits. Interestingly, because we give the inverse of both Address and Command, no matter what the bits are for Address and Command the button press always turns out to be the same length, 67.5, no matter what the data. And there will always be the same number of On pulses (34). The only reason I can guess they do this is that the inverse helps validate the signal and eliminate noise.

Side note: You may ask yourself this question: With only 8 bits of address, how can a TV company ensure that their remote control won’t interfere with some other company’s appliance? Well, apparently nobody thought of that at first, but soon there were so many manufacturers using this method that they changed the inverse address section to a high-address section. The commands still were left at 8 bits with the inverse. With the new system, the length of the whole button-press can change, but that shouldn’t be an issue.

Now within each of the 4 groups, we need to identify either a zero or a one for the byte we’re sending. The web page says:

Logical ’0′ – a 562.5µs pulse burst followed by a 562.5µs space, with a total transmit time of 1.125ms
Logical ’1′ – a 562.5µs pulse burst followed by a 1.6875ms space, with a total transmit time of 2.25ms

Ok, here are those statements in a picture. That means a 1 is twice as long as a zero. The only way this method can work is by inverting the bits and sending again. Very odd, I wonder who thought of this.

ir_signal_2

Reading Button Presses

It turns out the receiver that comes with the IR card I bought has a 38KHz filter built in. That means we only receive data modulated on 38KHz and more importantly, we never see the 38KHz pulses. So let’s write some code that looks at the whole button press we’re receiving.

We’re going to need some microsecond timing. Turns out we can call the gettimeofday() function which works with microseconds. So this subroutine should help with that.

#define START_TIMER 0
#define GET_TIMER 1

//=============================================================================
// get_timestamp
//=============================================================================

int get_timestamp(int flag)
{
 struct timeval now;
 struct timeval total;
 static struct timeval start;

 gettimeofday(&now,0);
 switch (flag)
 {
  case START_TIMER:
   start.tv_sec = now.tv_sec;
   start.tv_usec = now.tv_usec;
   return 0;
  case GET_TIMER:
   timersub(&now, &start, &total);
   start.tv_sec = now.tv_sec;
   start.tv_usec = now.tv_usec;
   return total.tv_usec;
 }
}

This returns the time in microseconds between two calls – the first with START_TIMER as the flag, and the second with GET_TIMER as the flag. You call the routine with START_TIMER, then do something, and then call it again with GET_TIMER, such as:

t1 = get_timestamp(START_TIMER);
 <do something #1 that take time>
t2 = get_timestamp(GET_TIMER);
 <do something #2>
t3 = get_timestamp(GET_TIMER);

The result in t1 will actually always be zero, since we’re starting the timer. Variable t2 in that example will contain the time in microseconds that it took for something #1. Then each time we call GET_TIMER, the time returned is from the previous GET_TIMER, not from the START_TIMER as you may have suspected. So t3 in this example shows the number of microseconds between the end of something #1 and the end of something #2. Easy as cake.

Then we need a subroutine to read the IR receiver and return pulse time lengths, one time value for the time the light is on, and another for the time the light is off, such as:

#define PIN 1 

//=============================================================================
// get_single_pulse
//=============================================================================

int get_single_pulse(int *t1, int *t2)
{
#define MAX_COUNT 300000
 int b;
 int count = 0;

 while (digitalRead(PIN) == 1);
 get_timestamp(START_TIMER);

 while (1)
 {
  if (digitalRead(PIN) == 1) break;
  count++;
  if (count >= MAX_COUNT)
  {
   printf("Error 4 - Pulse is longer than expected (MAX COUNT)\n");
   exit(8);
  }
 }
 *t1 = get_timestamp(GET_TIMER);

 while (1)
 {
  if (digitalRead(PIN) == 0) break;
  count++;
  if (count >= MAX_COUNT)
  {
   printf("Error 5 - Pulse is longer than expected (MAX COUNT)\n");
   exit(8);
  }
 }
 *t2 = get_timestamp(GET_TIMER);
 return TRUE;
}

If the pulse lasts too long (over 300000 loops seems to work but you may need to change that depending on processor speed), this routine fails with an error message. Otherwise the t1 and t2 values are filled with how long they last in microseconds, and we return TRUE. t1 is the time of the light on pulse, and t2 is the time of the following light off before the next pulse comes along.

Seeing the Signal with Modulation

I mentioned that the receiver in the little Pi board must filter out the 38KHz modulation, but I’d like to see that for myself anyway. So for $2 I ordered a few pairs of emitters and IR transistors. The transistors are something like these pictures I found on the web (since no doc was included with the $2 order)…

ir_transistor_1

So we should be able to borrow +V from the Pi and connect one up and run the output into one of the unused GPIO pins. Hmm… we should be able to borrow one of the button circuits for power and a 10K resister. And we can put my old scope on pin 13 to see how that matches up with what the Pi receives.

ir_transistor_2

Ok, the IR transistor circuit works fine and responds to TV remote pulses. But now we’re moving really fast at about 13uS between pulse changes (at 38KHz). Can the Pi keep up? We obviously have no time to print out 1′s and 0′s, so let’s record the pulse changes in an array and print them out when the array gets full:

//*********************************************************************
// read_transistor.c - Testing GPIO with IR Card on Raspberry Pi 3
//*********************************************************************

#include <stdio.h>
#include <wiringPi.h>

#define PIN 2
#define MAX_BITS 1000000 

char bits[MAX_BITS];

int main(int argc, char *argv[])
{
 wiringPiSetup();
 pinMode(PIN, INPUT);
 int b;
 int i = 0;

 while (digitalRead(PIN) == 0);
 while (1)
 {
  bits[i] = digitalRead(PIN);
  i++;
  if (i > MAX_BITS) break;
 }

 for (i = 0; i < MAX_BITS; i++)
 {
  printf("%d", bits[i]);
 }
}

Well this is odd. When I run the program above which reads pin 2 (the button #1 connection I just made to the IR transistor) we see the pulses, but there’s no 38KHz carrier. What’s going on here? Let’s get my old 1978 oscilloscope out of the garage. It hasn’t been turned on in probably 5 years but hey, it starts right up and shows the output on pin 13 above the 10K resistor as this:

ir_scope_1

So that’s the start and address portion easily visible on the scope, but there’s no 38KHz carrier. So all I can guess is that there’s a low filter in the IR Transistor. Can you do that with only 2 leads? I guess it’s possible to make a device that is simply so slow that it doesn’t respond to anything but lower frequencies and that’s basically a filter. Oh well, we don’t get to see the 38KHz modulations, at least not today.

Hmm… so if we point the IR transistor at the LED on the IR board, and pulse the LED on and off every second using the write_led program we previously wrote, we can see the pulse up and down on the transistor. So it handles DC light signals ok. So what if we keep bumping up the frequency in that test program?

Well, that kind of proves my theory. When I set a pulse time (both up and down) of 1000 uS I can see the wave just fine on the scope, although it’s got some rise time issues. Then I drop to 100 uS and the signal rises way up, not hitting zero at all. Then I drop to 20 uS and the signal is basically gone, with the voltage at the top. So we have a low filter here, by whatever means. Note: This is under the assumption that the LED output on the IR board is actually spitting out the high frequency. It has to be, because in a later test I found I could not control the TV via the LED unless I modulated at 38KHz.

Producing the 38KHz Modulation

So do I need more hardware, or is there some kind of UART function in the Pi pin 11 (IR LED) that can be used to produce the 38KHz modulation, or can I just do this in software and maybe try to run the button output uninterrupted? Note there is a piHiPri (int priority) function that supposedly can be used to set the priority of this particular thread. Zero is the default, and 99 is the max (keeps others from getting very much CPU time).

Now we can’t see the 38KHz modulation via either the IR receiver on the card, or via the IR tranistor I bought, but we certainly can send very short pulses to the LED and see what’s up on the oscilloscope. So let’s try that.

//*********************************************************************
// write_modulation.c - Testing GPIO with IR Card on Raspberry Pi 3
//*********************************************************************

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <wiringPi.h>
#include <signal.h> 

void ctrl_c(int signal_number);

int main(int argc, char *argv[])
{
 int b = 0;

 wiringPiSetup();
 pinMode(0, OUTPUT);
 signal(SIGINT, ctrl_c);

 while (1)
 {
  digitalWrite(0,0);
  delayMicroseconds(13);
  digitalWrite(0, 1);
  delayMicroseconds(13);
 }
}

void ctrl_c(int signal_number)
{
 digitalWrite(0, 0);
 printf("\nTurning off light since you pressed ctrl-c\n");
 fflush(stdout);
 exit(0);
}

Since we’ll never be able to hit ctrl-c when the light is out (like we did with the 1 second LED blink program), let’s add subroutine ctrl-c() that gets called when we click ctrl-c, and turns the light out for us.

Otherwise, this program is basically like the 1 second blink program (write_led.c) except we wait 13 uSec between up/down pulses. That should be 26 uSec which is about 38KHz. Hmm… the scope shows we’re right about there – divisions are set to 5 uSec so by my scope we’re just a little over 26 uSec. That’s really good.

ir_scope_2

Let’s see if the time gets a tiny bit shorter if I set the task priority to 99. Nope… no change visible on the scope. Maybe if I had a frequency analyzer. Now if I set the pulse to something really short, say 2 uSec, the scope seems to show some variation as the wave goes by, making it difficult for the scope to get an accurate sync. Setting piHiPri(99) doesn’t seem to do anything to help that. We should probably still use it though, because I don’t want something like TCPIP taking over in the middle of one of these modulation pulses. Remember, right now this Pi is basically doing nothing.

Side note: When I run the program above with 2 uSec pulses instead of 13 uSec, the scope really shows 2 uSec and 2 uSec down, not counting very few pulses extended to about 3 uSec (the noise I mentioned). Quite amazing. All I can guess is the delayMicrosecond function is subtracting a bit of time to allow processing time. Or this Pi is way faster than I thought it would be.

Graph of Button Presses

It might be interesting to display the button presses in a kind of graphical form on the screen. Here’s a program I wrote to do that based on loops, and not based on timer values.

//*********************************************************************
//*********************************************************************
// read_chart.c - Testing GPIO with IR Card on Raspberry Pi 3
//*********************************************************************
//*********************************************************************

#include <stdio.h>
#include <stdlib.h>
#include <wiringPi.h>
#include <math.h>

//*********************************************************************
//*********************************************************************
// main routine
//*********************************************************************
//*********************************************************************

#define PIN 1
#define MAX_COUNT 300000
#define MAX_BITS 100

int p;
int ps;
int count;
int stored;
int cnt[MAX_BITS + 1];
int bit[MAX_BITS + 1];
int ix;

int display_result();

int main (int argc, char *argv[])
{
 wiringPiSetup();
 pinMode(PIN, INPUT);
 ps = digitalRead(PIN);
 ix = 0;
 int s;

 while (1)
 {
  p = digitalRead(PIN);
  if (p == ps)
   { 
    count++;
    if (stored && count > MAX_COUNT && p == 1)
     {
      display_result();
      continue;
     }
   }
  else
   {
    cnt[ix] = count;
    bit[ix] = ps;
    ix++;
    if (ix > MAX_BITS) ix = MAX_BITS;
    stored = 1;
    count = 0;
    ps = p;
  }
 }
}

int display_result()
{
 int i,j;
 int b;
 int c;
 char ch;
 int cr = 0;

 for (i = 1; i < ix; i++)
  {
   b = bit[i]; 
   c = cnt[i];
   c = round(c / 4000);
   if (b) ch = '_'; else ch = '-';
   for (j = 0; j < c; j++) printf("%c", ch);
   cr = 1;
 }

 if (cr) printf("\n");
 ix = 0;
 stored = 0;
 count = 0;

 return 0;
}

That program displays button presses as a chart, such as these examples of buttons 0, 1, and 2 I pressed on my TV remote control:

ir_chart

I added colors in that image, not in the program. Red is the start bit, green are the first address bits, blue the second address bits, orange the command bits, and black the inverse command bits. Note that the addresses are the same for each button, as expected, but the command and inverse command changes.

Recording Button Presses

Ok, enough fun playing with the scope and stuff. Now we need to use the subroutines we previously coded (get_timestamp and get_single_pulse) to record a button press from my TV remote control. First, let’s use those to gather the pulse widths, in a loop like this:

//*********************************************************************
// read_button_timing.c - Testing GPIO with IR Card on Raspberry Pi 3
//*********************************************************************

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wiringPi.h>
#include <time.h>
#include <sys/time.h>

#define PIN 1
#define START_TIMER 0
#define GET_TIMER 1

int get_timestamp(int flag);
int get_single_pulse(int *t1, int *t2);

int main(int argc, char *argv[])
{
 int b;
 int rc;
 int t1, t2;

 wiringPiSetup();
 pinMode(PIN, INPUT);

 while (1)
 {
  rc = get_single_pulse(&t1, &t2);
  if (rc == 0) break;
  printf("%d %d\n", t1, t2);
 }
}

So when we run that code and press the zero button on my TV remote, we get the following numbers(with comments in red added here). First number is the length in microseconds up the light-on portion of the pulse, the second number is the length of the light-off portion of the pulse.

ir_timing

Ok… that looks pretty good and matches my previous examination of this particular TV remote zero button. It’s a bit odd that the Address byte 2 matches Address byte 1, but no big deal I guess.

So we need to turn those pulse lengths into bits, and do some validity checking while we’re at it, such as:

  •  Make sure the start pulse is within limits (watch out for this, because different controllers do different things)
  •  Make sure the high pulse is somewhere around 562 (watch out because at least on my TV remote, the first data pulse is a bit shorter than the others (492 in the case above). So be reasonable.
  •  Check the low pulse for either around 562 or around 1687. These form the bits that we place in 4 bytes for a button press.

Easy, huh?

For checking, let’s add this subroutine to our collection:

#define CHECK_START 2

//=============================================================================
// check_pulse
//=============================================================================

int check_pulse(int t1, int t2)
{
 if (t1 > 3000 && t1 < 20000) return CHECK_START;

 if (t1 < 300 || t1 > 800)
 {
  printf("Error 2 - Pulse cannot be processed - %d up and %d down\n", t1, t2);
  exit(8);
 }

 if (t2 > 300 && t2 < 800) return 0; 
 if (t2 > 1400 && t2 < 1800) return 1;
 printf("Error 3 - Pulse cannot be processed - %d up and %d down\n", t1, t2);
 exit(8);
}

This routine takes the t1 (light on in microseconds) and t2 (light off in microseconds) and converts them to a bit value of 0 or 1, or a value of CHECK_START which is a value of 2.

And instead of duplicating code, let’s make a subroutine that will grab 8 bits from the button press stream and shift those bits into a single byte. Note that the shifting is done based on the least significant bit coming in first.

//=============================================================================
// get_byte
//=============================================================================

unsigned char get_byte()
{
 unsigned char byte = 0;
 int i;
 int t1, t2;
 int b;

 for (i = 0; i < 8; i++)  
 {   
  get_single_pulse(&t1, &t2);   
  b = check_pulse(t1, t2);   
  byte = byte >> 1;
  byte = byte | (b << 7);
 }

 return byte;
}

That’s pretty simple. We assume at this point we’re at the point in the button press stream where we’re about to receive one of the 4 bytes. So 8 times we get a pulse and shift them into the byte variable, returning it when we’re done with all 8 bits.

Then we need to make a main routine that will call all these subroutines to produce 4 bytes per button press. Note that this code includes the #define statements for most routines, and the function prototype statements too. All the called subroutines (code listed above) obviously needs to be added in order to compile.

//*********************************************************************
// read_button.c - Testing GPIO with IR Card on Raspberry Pi 3
//*********************************************************************

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wiringPi.h>
#include <sys/time.h>

#define PIN 1
#define START_TIMER 0
#define GET_TIMER 1
#define CHECK_START 2

int get_timestamp(int flag);
int get_single_pulse(int *t1, int *t2);
int check_pulse(int t1, int t2);
unsigned char get_byte();

int main(int argc, char *argv[])
{
 int b;
 int t1, t2;
 unsigned char address1, address2, command1, command2;

 wiringPiSetup();
 pinMode(PIN, INPUT);

loop:
 while (digitalRead(PIN) == 0);
 get_single_pulse(&t1, &t2);
 b = check_pulse(t1, t2);
 if (b != CHECK_START)
 {
  printf("Error 1 - Initial pulse does not look like a start pulse\n");
  exit(8); 
 }

 address1 = get_byte();
 address2 = get_byte();
 command1 = get_byte();
 command2 = get_byte();

 printf("%02X %02X %02X %02X\n", address1, address2, command1, command2);
 goto loop;
}

So this main routine initializes wireingPi and sets the PIN (pin 1) to input mode. Then we loop as long as the PIN is zero. Note that normally the PIN is 1 because there is no light (remember this pin is reverse of the light). But since we added a loop, we’ll be coming back while there is still light active from the last pulse, so this statement waits for that light to go away.

Then most of the waiting is in the get_single_pulse() call which will wait as long as necessary for someone to push a button on the remote. Note that I see spurious signals at this point, resulting in error messages, but that’s no big deal because I’m just recording buttons by hand for later use.
When a button is pushed, we return with the start pulse t1 and t2 times and check them. If this doesn’t look like a start pulse, we issue an error message and exit.

If it is a start pulse, we then call get_byte() 4 times to fill the various address and command bytes, and spit them out to the console. Then we go back up to the loop assuming we’ll want to press and record more buttons. Just ctrl-c to exit the program.

Some examples:

p1@raspberrypi:~$ sudo ./read_button
 07 07 11 EE
 07 07 04 FB
 07 07 05 FA
 07 07 06 F9
 07 07 08 F7
 07 07 09 F6
 07 07 0A F5
 07 07 0C F3
 07 07 0D F2
 07 07 0E F1

Those are codes for buttons 0-9 on my TV remote. With that info, we can store this data in a program and spit things out through the IR LED as needed to control the TV from the Pi. That’s the next part of this fun.

Playing Button Presses

We should be able to write a short program that takes the hex codes we gathered from the read_button program and spits them out to the LED. But we’ll also need to modulate the light-on portion at 38KHz or the TV won’t see the signal (i.e. I tried without modulation – it does nothing).

So we know we have to have a start pulse of light for 4500 uSec on and 4500 uSec off, but we need to modulate the “on” portion it at 13uSec on and 13uS off, like we did for the write_modulation program. We can code this up in a subroutine and use the same routine for our other pulses I think.

Let’s try writing out the on pulse based on the 26 uSec we need for a single modulation, and for the off pulse we’ll just use the delayMicroseconds() function.

#define PULSE_ZERO 0
#define PULSE_ONE 1
#define PULSE_START 2

#define PULSE_START_ON 4500
#define PULSE_START_OFF 4500
#define PULSE_ZERO_ON 562
#define PULSE_ZERO_OFF 562
#define PULSE_ONE_ON 562
#define PULSE_ONE_OFF 1687

#define PULSE_MODULATION 13

//*********************************************************************
// write_pulse
//*********************************************************************

int write_pulse(int type)
{
 int loops;
 int i;
 int time_on;
 int time_off;

 switch (type)
 {
  case PULSE_START:
   time_on = PULSE_START_ON;
   time_off = PULSE_START_OFF;
   break;

  case PULSE_ZERO:
   time_on = PULSE_ZERO_ON;
   time_off = PULSE_ZERO_OFF;
   break;

  case PULSE_ONE:
   time_on = PULSE_ONE_ON;
   time_off = PULSE_ONE_OFF;
   break;
 }

 loops = time_on / (PULSE_MODULATION * 2);
 for (i = 0; i<loops; i++)
 {
  digitalWrite(0, 1);
  delayMicroseconds(PULSE_MODULATION);
  digitalWrite(0, 0);
  delayMicroseconds(PULSE_MODULATION);
 }
 delayMicroseconds(time_off);
}

So that subroutine can be called for each pulse type (START, ZERO, or ONE) and it will figure out the number of modulations and write out both the light on and light off portion of the pulse. With that, we can write another subroutine that outputs each byte of data:

//*********************************************************************
// write_byte
//*********************************************************************

int write_byte(unsigned char byte)
{
 int i;
 int b;

 for (i = 0; i < 8; i++)
 {
  b = byte & 1;
  write_pulse(b);
  byte = byte >> 1; 
 } 
}

And with that, we can write code that produces a single button press, such as this test:

int main(int argc, char *argv[])
{
 int b = 0;

 wiringPiSetup();
 pinMode(0, OUTPUT);
 signal(SIGINT, ctrl_c);
 piHiPri(99);

 write_pulse(PULSE_START);
 write_byte(0x07);
 write_byte(0x07);
 write_byte(0x11);
 write_byte(0xEE);
 write_pulse(PULSE_ZERO);
}

That should press the “0″ button on my TV, and sure enough, it works as designed. Notice the extra PULSE_ZERO is needed at the end of the button press. This makes sense because the TV needs to know the length of the last light-off period, and the only way to know that is with an extra light-on pulse. Probably part of the original system design.

We can change that code into a subroutine that has all the buttons we need pre-coded, such as this bit. We pass a pre-defined character for that particuled button. Note that at the end, we make sure the light is off and wait 1/4 second as some space between multiple button presses.

//*********************************************************************
// write_button
//*********************************************************************

int write_button(unsigned char code)
{
 unsigned int button;
 unsigned int byte;
 int i;

 code = toupper(code);

 switch (code)
 {
  case '0': button = 0x070711EE; break; // 0
  case '1': button = 0x070704FB; break; // 1
  case '2': button = 0x070705FA; break; // 2
  case '3': button = 0x070706F9; break; // 3
  case '4': button = 0x070708F7; break; // 4
  case '5': button = 0x070709F6; break; // 5
  case '6': button = 0x07070AF5; break; // 6
  case '7': button = 0x07070CF3; break; // 7
  case '8': button = 0x07070DF2; break; // 8
  case '9': button = 0x07070EF1; break; // 9
  case '-': button = 0x070723DC; break; // Dash
  case 'B': button = 0x070713EC; break; // Back to prev channel
  case 'V': button = 0x070707F8; break; // Volume up
  case 'W': button = 0x07070BF4; break; // Volume Down
  case 'U': button = 0x070712ED; break; // Channel Up
  case 'D': button = 0x070710EF; break; // Channel Down
  case 'M': button = 0x07070FF0; break; // Mute
  case 'P': button = 0x070702FD; break; // Power
 }

 write_pulse(PULSE_START);

 for (i = 0; i < 4; i++)
 {
  byte = button >> 24;
  button = button << 8;
  write_byte(byte); 
 } 

 write_pulse(PULSE_ZERO);
 delayMicroseconds(250000);
}

Putting a Usable Program Together

Let’s put together a program that can do some actual work, perhaps included as a C program, or maybe called from a bash script. This main routine calls some of the subroutines listed above.

//*********************************************************************
// write_button.c - Testing GPIO with IR Card on Raspberry Pi 3
//*********************************************************************

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <wiringPi.h>
#include <signal.h>
#include <ctype.h>

#define PULSE_ZERO 0
#define PULSE_ONE 1
#define PULSE_START 2

#define PULSE_START_ON 4500
#define PULSE_START_OFF 4500
#define PULSE_ZERO_ON 562
#define PULSE_ZERO_OFF 562
#define PULSE_ONE_ON 562
#define PULSE_ONE_OFF 1687

#define PULSE_MODULATION 13

int write_button(unsigned char code);
int write_byte(unsigned char byte);
int write_pulse(int type);
void ctrl_c(int signal_number);

int main(int argc, char *argv[])
{
 int b = 0;
 char * p;

 if (argc != 2)
 {
  printf("Command format is \"sudo ./write_button 13-1\" to switch to channel 13-1\n\n");
  exit(8); 
 }

 wiringPiSetup();
 pinMode(0, OUTPUT);
 signal(SIGINT, ctrl_c);
 piHiPri(99);

 digitalWrite(0, 0);
 delayMicroseconds(250000);

 p = argv[1];

 while (*p)
 {
  write_button(*p);
  p++; 
 }

 digitalWrite(0, 0);
 exit(0);
}

So this program requires and reads argv[1] as a command line parameter, and for each character coded, converts that to a button press and sends it out. So if we were to type something like this:

sudo ./write_button 13-4

Then channel 13-4 should appear on the TV.

Side note: I added code near the top to make sure the light is off and to wait 1/4 of a second before starting to send the button presses. This was because the first button was being intermittently missed by my TV for some reason – about 1 out of 10 times. I thought at first this was because of the position of the LED but after adding the delay up front, that seems to have cured the problem. So my guess is that at startup of the program it’s more susceptible to interrupts or similar, let’s say something like disk interrupts are still pending from the binary load – who knows. But when the problem occurred it only occurred on the first of a series of buttons. For example, I’d type 13-4 and intermittently the TV would receive 3-4. It never received 1-4 or 13- or 134. It only dropped the first button. Anyway, that seems to be solved now.

And Here is my First Script

# Change to channel 11 at 7:30pm

time='193000'

while true
do
 t=`date '+%H%M%S'`
 if [ "$t" -eq "$time" ] ; then
  echo "Time to change channels..."
  sudo ./write_button 11
 fi
 sleep 1
done

This is a quick test to see if the TV will automatically change from Channel 13 (Big Bang Theory that started at 7pm) to Channel 11 (Big Bang Theory that starts at 7:30pm on a different channel). 3 minutes until the timer hits… I need to do something in the script to eliminate the need to look at the seconds, and instead just check for the hour and minute match, and have a variable so we don’t repeat the button pressing. P.S. I’m terrible at bash shell scripting.

19:30:00 – It worked! The only problem was that the switch from channel 13 to 11 occurred about 10 seconds late (the episode intro had already started). So I probably need to back off a minute so the switch is during the commercials.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>