Having Fun with IR Consumer Remotes
First of all we will need a way to capture those infrared signal coming from our remotes, the easiest way (and cheapest) is to build a small receiver with a photo-diode. (on the sides you see the electric diagram and my test bench)
Now that we built our receiver we can test if it’s working like it should. I used Audacity (a Linux audio multifunctional tool) to analyze the signal coming from the photodiode. The results:
Now we have a signal composed by some waves, great, let’s try to explain how CIR (Consumer infrared) works.
First of all, quite each brand has his own solution, so we can’t really generalize the discussion. Anyway the most used is the RC5 from Philips. As you can remember, the lesser the frequency the the fair signal can get. The modulation (how you encode your data on the wave) used by fast every brand is called Amplitude-Shift Keying (ASK).
RC5 Structure:
Each frame is composed by:
- 2 start bits which are used by the AGC (Automatic Gain Controller) from the receiver (TV)
- 1 toggle bit which varies every time a button is pressed
- 5 bits which represents the device address
- 6 bits with the command
Now that we have a minimal understanding of the ground let’s try code something with this. First of all we have to access our audio mic line (where we connected our fancy IR receiver) and prepare a buffer that our system will fill up with sampled data. Fortunately Linux provide a simple way to capture data from your soundcard.
1: void init_audio(void)
2: {
3: int rc;
4: int val, dir;
5:
6: // Open PCM device for capture
7: rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
8: if (rc < 0) {
9: fprintf(stderr,
10: "Unable to open pcm device: %s\n",
11: snd_strerror(rc));
12: exit(1);
13: }
14:
15: // Setting default samples
16: snd_pcm_hw_params_alloca(¶ms);
17: snd_pcm_hw_params_any(handle, params);
18: snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
19:
20: // 8bits Signed samples
21: snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S8);
22:
23: // Single channel (mono)
24: snd_pcm_hw_params_set_channels(handle, params, 1);
25:
26: // Setting sampling rate
27: val = SAMPLERATE;
28: snd_pcm_hw_params_set_rate_near(handle, params,
29: &val, &dir);
30:
31: // Set period size to 300 frames.
32: frames = 300;
33: snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
34:
35: // Write the parameters to the driver
36: rc = snd_pcm_hw_params(handle, params);
37:
38: if (rc < 0) {
39: fprintf(stderr,
40: "Unable to set hw parameters: %s\n",
41: snd_strerror(rc));
42: exit(1);
43: }
44:
45: // Use a buffer large enough to hold one period
46: snd_pcm_hw_params_get_period_size(params, &frames, &dir);
47: size = frames * 1; // 1 bytes/sample, 1 channels
48:
49: buffer = (char *) malloc(size);
50:
51: // We want to loop for 2 seconds
52: snd_pcm_hw_params_get_period_time(params,
53: &val, &dir);
54:
55: printf("Audio initialized!\n");
56: }
57:
58: // listen function used as a dedicated thread (pthread_create())
59: void *listen_function(void)
60: {
61: int rc;
62: int j;
63:
64: while (!QUIT) {
65: // variable has been initialized in init_audio function
66: rc = snd_pcm_readi(handle, buffer, frames); // read "frames" from soundcard
67:
68: if (rc == -EPIPE) {
69: // EPIPE means overrun
70: fprintf(stderr, "overrun occurred\n");
71: snd_pcm_prepare(handle);
72:
73: } else if (rc < 0) {
74: fprintf(stderr, "error from read: %s\n", snd_strerror(rc));
75: }
76:
77: // draw signal to stdout
78: //graph(buffer, rc);
79:
80: if(DEBUG)
81: {
82: for(j=0; j<rc;j++){
83: fprintf(fp, "%.1f\t%d\n", (double)(delta*1000.0*(double)counter), buffer[j]);
84: counter++;
85: }
86: }
87: // elaborate buffer
88: computeDelta(buffer,rc);
89: }
90: printf("ListenThread terminated!\n");
91: closeAudio();
92: pthread_exit(NULL);
93: }
1: #define SAMPLERATE 44100
2: #define DELTATHRESHOLD 0.5
3: #define TRUE 1
4: #define FALSE 0
5: #define THRESOLDLIMIT 6 // Under this no calculation is made [range 1-127]
6: #define DEBUG 0 // If true: verbose output + sampled data written to logfile.txt
7:
8: char OVERTH=FALSE; // FLAG used to know if signal is below or above threshold
9: char PEAK = FALSE; // FLAG used to let know when a peak occurred
10:
11: /* delta is time (in milliseconds) between each sample */
12: double delta=(1.0/(double)SAMPLERATE*1000);
13:
14: void computeDelta(char *buffer, int length)
15: {
16: int k,j;
17: int maxgraph= -100;
18: int threshold;
19: int temp;
20:
21: int countstep=0;
22: double dpeak; // delta peak
23:
24: // Find max value in sampled buffer
25: for(j=0; j<length;j++)
26: {
27: if(buffer[j]>maxgraph)
28: maxgraph=buffer[j];
29: }
30:
31: // Compute threshold against max value
32: threshold = (int)((double)maxgraph*(double)DELTATHRESHOLD);
33:
34: // Check if signal rise above threshold
35: if(threshold > THRESOLDLIMIT)
36: {
37: if(DEBUG) printf("Threshold: %d [MAX: %d]\n", threshold, maxgraph);
38: for(k=0;k<length;k++)
39: {
40: // peak length
41: dpeak = ((double)(delta)*(double)countstep);
42:
43: // signal low (below 0)
44: if(buffer[k]>threshold && OVERTH == FALSE){
45: if(DEBUG) printf("Low Peak length: %8.3f ms [Steps: %d][Value: %4d]\n", dpeak,countstep, buffer[k]);
46: // peak triggered when delta peak is longer than 0.4 ms
47: if(dpeak>0.4)
48: PEAK=TRUE;
49: // reset counterstep for next peak
50: countstep=0;
51: countstep++;
52: OVERTH = TRUE;
53: }
54: // signal high (above 0)
55: else if(buffer[k]<threshold && OVERTH==TRUE){
56: if(DEBUG) printf("High Peak length: %8.3lf ms [Steps: %d][Value: %4d]\n", dpeak,countstep, buffer[k]);
57: // peak triggered when delta peak is longer than 0.4 ms
58: if(dpeak>0.4)
59: PEAK=TRUE;
60: // reset counterstep for next peak
61: countstep=0;
62: countstep++;
63: OVERTH=FALSE;
64: }
65: // Peak detected
66: if(PEAK==TRUE)
67: {
68: // I know the peak length should be within 800 us - 1.8 ms
69: if(dpeak<2.0)
70: {
71: temp=(int)dpeak;
72: // temp is one if peak is longer than 1 ms or zero if shorter
73: cmd = (cmd<<1) | temp;
74: }
75: else
76: {
77: // command ready
78: if(cmd > 0)
79: {
80: printf("CMD: %3d\n", cmd);
81: cmd=0;
82: }
83: }
84: PEAK = FALSE;
85: }
86: countstep++;
87: }
88: }
89: }
A simple proof of this values is done by comparing the graph (gnuplot) generated on the raw sampled data and the program result. If we compare the peak type (long/short) and the peak length we will see a “perfect” match.
Now we have do identify and differentiate the remote’s command (which button pressed).
A simple way to do this is using some bitwise operator. Let’s say that if the peak is longer than 1 ms we insert a one (bit) into our command (8 bit uchar), if shorter we insert a zero (bit).
If you take a look in the code above you will see that there is no length check when shifting and inserting new bit into command. therefore the algorithm will take only the last 8 peaks into the command (the other will be “overwritten”).
Trying the program will results in a series of command number that are associated to remote buttons. In the screenshot is shown the result of pressing two different buttons: channel – (CMD: 194), and volume + (CMD: 57) from a Philips remote.
The algorithm needs of course some more improvements since as you see the commands some time are wrong (0, 98, 25).
Now that we have a list of commands associated with buttons we could move the mouse respectfully with the remote you’re using:
1: if(cmd==62 || cmd==254)
2: {
3: printf("Move 10 px left\n");
4: mouse_move(-10,0);
5: }
6: if(cmd==57 || cmd==249)
7: {
8: printf("Move 10 px right\n");
9: mouse_move(10,0);
10: }
11: if(cmd==194)
12: {
13: printf("Move 10 px down\n");
14: mouse_move(0,10);
15: }
16: if(cmd==192)
17: {
18: printf("Move 10 px up\n");
19: mouse_move(0,-10);
20: }
21:
22: // Move mouse
23: // Current Position + x / y
24: void mouse_move(int x, int y)
25: {
26: Display *displayMain = XOpenDisplay(NULL);
27:
28: if(displayMain == NULL)
29: {
30: fprintf(stderr, "Error Opening Display!\n");
31: exit(EXIT_FAILURE);
32: }
33:
34: XWarpPointer(displayMain, None, None, 0, 0, 0, 0, x, y);
35:
36: XCloseDisplay(displayMain);
37: }
1: // Simple ascii graph function
2: // Draw char buffer to stdout using printf
3: void graph(char *buffer, int length)
4: {
5:
6: int iy;
7: char mingraph=126, maxgraph=-126;
8: int temp;
9: unsigned int val;
10: int i,j,k;
11:
12: // Find max / min
13: for(j=0; j<length;j++)
14: {
15: if(buffer[j]>maxgraph)
16: maxgraph=buffer[j];
17: if(buffer[j]<mingraph)
18: mingraph=buffer[j];
19: }
20:
21: for(k=0;k<length;k++)
22: {
23: val=buffer[k];
24: if(mingraph<0)
25: val-=mingraph;
26:
27: iy=(val*maxgraph)/(maxgraph-mingraph);
28: printf("%4d ", val);
29: for(i=0;i<iy;i++)
30: printf(" ");
31: printf("*\n");
32: }
33: }