# MSP430 - TV Remote Decoder

You can use your handheld TV remote controller to turn on/off lights, appliances, gadgets, robots, etc.

Here I will show you how you can receive and decode the infra-red (IR) signals transmitted by the controller. This is also an excellent exercise to learn and use the intricate functions of the timer module on the MSP430G2553 microcontroller (mcu).

Be forewarned that this exercise is not for the faint of heart and requires multiple in-depth reading of manufacturers' datasheets and specifications manuals. The task is complicated in two ways. Firstly, we have to learn how the IR signal is encoded and transmitted. Secondly, we have to devise clever ways for the mcu to decode the received signals while at the same time having to learn and understand the intricacies of the timer module.

So let's get cracking.

TV Remote Codes

To begin, it would help if we understood how the IR signal is transmitted and what digital code is sent when a particular button is pressed. There are literally hundreds of remote controllers each with its particular way of encoding and assigning codes to each button. You can search for the codes for your particular controller on the internet. Here is one such link where you can find a large collection of remote controllers listed:

http://www.lirc.org

In reality, you don't need this information because this project will reveal the codes for your remote controller. It is nice to have this info to confirm that you are on the right track.

IR Detector

This is probably the easiest part. Most IR controllers transmit a digital code using a 38kHz IR signal. (Some transmit at 40kHz.) You can purchase an IR receiver and decoder that comes in a 3-pin package. Simply connect to Vcc (+5V) and GND and you get a digital signal out. I am using an IR detector, PNA4602M from Panasonic simply because that is what I have on hand.

I have also used Radio Shack 276-137A. Note that the pin-outs of the Panasonic device differs from that of the Radio Shack device.

Panasonic PNA4602M
1 - Signal
2 - GND
3 - +5V

1- Signal
2 - +5V
3 - GND

With a number of controllers that I have tested the data format follows the following pattern.

The idle signal is high at +5V.

When any button is pressed the data sequence begins with a low pulse ~9.2ms wide.

The first data pulse follows ~4.4ms later.
Data pulses are low pulses ~0.74ms wide.

The spacing before the next data pulse determines whether the bit is zero or one.
For a "zero" the spacing to the next pulse is ~1.2ms
For a "one" the spacing to the next pulse is ~2.4ms

[picture of data stream to come]

The total number of bits transmitted is either 32 or 64 bits.

There are four ways of interpreting the bits received.

Firstly, we can consider a short time spacing between clocks (1.2ms) to be a "zero" and a long interval (2.4ms) to be a "one". Or we can invert the logic of each bit.

Secondly, we can choose if the first bit received is the least significant bit (LSB) or the most significant bit (MSB) of the data stream.

Timer Module

Now we come to the difficult part. We have to come up with clever mechanisms in order to capture and decode the received data stream. Here the timer module of the MSP430G2553 comes to the rescue.

So far we have only used the timer module for basic timing functions. Here are some more advanced features which you will find in most mcu implementation of timers:

1) Input capture. This feature allows you to take a time stamp of when an external event occurs. The trigger can be a rising edge, a falling edge or both rising and falling edges of an input signal

2) Output compare. You can set a timer register to a specific count and an interrupt is generated when the timer register reaches this preset count. We have already used this feature in the basic timer exercise. It is also useful for creating pulse-width modulated (PWM) signals.

The IR Decoder Program

I will use the input capture function to determine the time between successive falling edges. From this I should be able to decode the 'zeros' and 'ones' in the data stream.

I also need to know when the end of the data stream occurs. For this I will use the output compare feature to create a timeout when there is no falling edges appearing within ~120ms.

Timer Module

The MSP430G2553 has two timer modules, TIMER0 and TIMER1. We are going to use TIMER0 only. There are two interrupt vectors assigned to TIMER0.

TIMER0_A0_VECTOR is used when the Capture/Compare interrupt flag CCIFG in register TA0CCTL0 is set.

TIMER0_A1_VECTOR is used for all other interrupts, including CCIFG in TA0CCTL1, CCIFG in TA0CCTL2 and TAIFG in TA0CTL. In this case, TA0IV is used as an efficient interrupt priority encoder in order to process multiple interrupts.

If TA0IV = 0x02, capture or compare associated with register TA0CCR1 occurred and interrupt flag CCIFG in TA0CCTL1 is set.

If TA0IV = 0x04, capture or compare associated with register TA0CCR2 occurred and interrupt flag CCIFG in TA0CCTL2 is set.

If TA0IV = 0x0A, timer register TA0R overflow occurred and interrupt flag TAIFG in TA0CTL is set.

When servicing TIMER0_A1 interrupts the TA0IV register must be read in order to clear the interrupt request flags.

How can we take advantage of the timer features with multiple interrupt capabilities?

Timer0 Input Capture0 is assigned to port P1.1 (pin-3). We connect the output of the IR Sensor Module to this pin. We initialize the mcu hardware to use P1.1 as Input Capture whenever a falling edge is detected.

We use TIMER0_A1_VECTOR to process a timeout condition to tell us that the data transmission has ended.

Normally, we would use register TA0CCR0 as the default timeout counter as in the previous timer examples. However, since we are using P1.1 as the input pin, we have already assigned TA0CCR0 to be the input capture register. Hence we have to use the timer in the CONTINUOUS COUNT mode and will stop the timer when the timeout is reached.

Code:
//************************************************
//   MSP430G2553 IR Decoder
//************************************************

/************************************************

2013.07.09 - MrChips

************************************************/

#include "io430.h"

//---------------------------------------------------
// Global Variables
//---------------------------------------------------

unsigned short IC_Count, Count, DataReceived, WhichIRQ;;
unsigned long DataBits;
unsigned long DataBitsReversed;
unsigned short time[128];

char d[8];
char hex[16] = "0123456789ABCDEF";

//---------------------------------------------------
// Defines
//---------------------------------------------------

#define title1 "=== Mr Chips ==="
#define title2 " Remote Decoder "

#define TRUE  1
#define FALSE 0
#define ON    1
#define OFF   0

#define NUMBER_OF_BITS  32
#define ZERO_LIMIT     225    // zero/one threshold
#define TIMEOUT      15000    // set for 120ms

#define LED1 P1OUT_bit.P0

// ASCII codes recognized by this terminal

#define HOME  0x01
#define LINE2 0x02
#define CLR   0x03
#define LF    0x0A
#define CR    0x0D
#define BS    0x08
#define DEL1  0x09
#define DEL2  0x7F
#define SP    0x20

//---------------------------------------------------
// LCD Interface
//---------------------------------------------------

void LCD_init(void);
void LCD_ir(char ch);
void LCD_dr(char ch);
void LCD_txt(char *s);
void LCD_Home(void);
void LCD_Clear(void);
void LCD_Line1(void);
void LCD_Line2(void);
void LCD_Line3(void);
void LCD_Line4(void);
void LCD_Posn(char n);

// define LCD interface on PORT2
#define LCD_OUT  P2OUT
#define LCD_IN   P2IN
#define LCD_DDR  P2DIR
#define LCD_RS   P2OUT_bit.P4
#define LCD_RW   P2OUT_bit.P5
#define LCD_E    P2OUT_bit.P6

void LCD_init(void)
{
LCD_ir(0x28);         // dual line, 4 bits
LCD_ir(0x06);         // increment mode
LCD_ir(0x0C);         // cursor turned off
LCD_ir(0x10);         // cursor right
LCD_ir(0x01);         // clear display
}

void LCD_Clear(void)
{
LCD_ir(0x01);
}

void LCD_Home(void)
{
LCD_ir(0x02);
}

void LCD_Line1(void)
{
unsigned char i;
LCD_ir(0x80);
for (i = 0; i < 16; i++)
{
LCD_dr(SP);
}
LCD_ir(0x80);
}

void LCD_Line2(void)
{
unsigned char i;
LCD_ir(0xC0);
for (i = 0; i < 16; i++)
{
LCD_dr(SP);
}
LCD_ir(0xC0);
}

void LCD_Line3(void)
{
LCD_ir(0x94);
}

void LCD_Line4(void)
{
LCD_ir(0xD4);
}

void LCD_Posn(char n)
// direct positioning where n = 0-79
{
char ch;
ch = n;
if (ch  >= 40) ch += 24;
LCD_ir(ch | 0x80);
}

{
char busy;
LCD_DDR = 0xF0;
LCD_RW = 1;
LCD_RS = 0;
do
{
LCD_E  = 1;
// high 4 bits read first
// busy flag is D7 of LCD = bit 3 of P2IN
busy = (LCD_IN & 0x08);
LCD_E  = 0;
LCD_E  = 1;
LCD_E  = 0;
} while (busy);
LCD_OUT = 0;
LCD_DDR = 0xFF;
}

void LCD_ir(char ch)
{
//LCD_RS = 0;     // not needed      // select instruction register
//LCD_RW = 0;     // not needed
LCD_OUT |= ((ch >> 4) & 0x0F);
LCD_E = 1;
LCD_E = 0;
LCD_OUT = 0;
LCD_OUT |= (ch & 0x0F);
LCD_E = 1;
LCD_E = 0;
}

void LCD_dr(char ch)
{
LCD_RS = 1;        // select data register
//LCD_RW = 0;      // not needed
LCD_OUT |= ((ch >> 4) & 0x0F);
LCD_E = 1;
LCD_E = 0;
LCD_OUT = 0;
LCD_RS = 1;
LCD_OUT |= (ch & 0x0F);
LCD_E = 1;
LCD_E = 0;
}

void LCD_txt(char *s)
{
while(*s) LCD_dr(*s++);
}

//---------------------------------------------------
// Conversion Routines
//---------------------------------------------------

void display_HEX(unsigned long v)
{ // enter with v = 32-bit unsigned integer
// fill global array d[]
short i;
for (i = 0; i < 8; i++)
{
d[i] = hex[v  & 0x000F];
v = v >> 4;
}
}

void display_BCD(unsigned short v)
{ // enter with v = 16-bit unsigned integer
// fill global array d[]
short i;
for (i = 0; i < 5; i++)
{
d[i] = hex[v % 10];
v = v/10;
}
}

//---------------------------------------------------
// Hardware Initialization
//---------------------------------------------------

void init(void)
{
// Stop watchdog timer to prevent time out reset
WDTCTL = WDTPW + WDTHOLD;

// set DCO - Digitally-Controlled Oscillator frequency
// change from 1MHz to 16MHz
//BCSCTL1 = CALBC1_8MHZ;
//DCOCTL  = CALDCO_8MHZ;

P1DIR_bit.P0 = 1;
LED1 = OFF;

P2DIR = 0xFF;
P2SEL = 0x00;  //select for I/O function
P2OUT = 0x00;

P1SEL2_bit.P1 = 0;   // TIM0 input capture function
P1SEL_bit.P1  = 1;   // TIM0 input capture function

// Initialize Timer0_A
// mode control
// MC_0 = stop
// MC_1 = count up to TA0CCR0
// MC_2 = countinuous
// MC_3 = up/down from 0 to TA0CCR0

TA0CTL = TASSEL_2 + ID_3 + MC_0;

// set up TAC0CTL1 for compare mode
TA0CCTL1 = CCIE;
TA0CCR1 = TIMEOUT;   // end of data timeout

// set up TAC0CTL0 for capture mode
TA0CCTL0 = CM_2 + CCIS_0 + CAP + CCIE;

__enable_interrupt();    // set GIE in SR
}

#pragma vector = TIMER0_A0_VECTOR
__interrupt void my_capture_IRQ(void)
// interrupts from CCR0
{
LED1 = ON;
TA0CTL = TASSEL_2 + ID_3 + TACLR + MC_2;  // start timer in continuous mode
time[Count]  = TA0CCR0;                   // read capture time
Count = ++Count % 128;
time[Count] = 0;
}

#pragma vector = TIMER0_A1_VECTOR
__interrupt void my_timeout_IRQ(void)
// interrupt from CCIFG in TA0CCTL1 - highest pro=iority
// interrupt from CCIFG in TA0CCTL2
// interrupt from TAIFG in TA0CTL
{
WhichIRQ = TA0IV;  // must read to clear flags
TA0CTL = TACLR;    // stop and clear timer
LED1 = OFF;
Count = 0;
}

//---------------------------------------------------
// software delay
//---------------------------------------------------

void delay(unsigned long d)
{
unsigned long i;
for (i = 0; i < d; i++);
}

//-----------------------------------
//  Main
//-----------------------------------

void main(void)
{ // main
char ch;
short i, j;
unsigned long thisbit;
init();
LCD_init();
LCD_txt(title1);
LCD_Line2();
LCD_txt(title2);
Count = 0;

while(1)
{ // while loop
{ // data received
DataBits = 0;
DataBitsReversed = 0;
// get bits
for (i = 0, j = 2; i < NUMBER_OF_BITS; i++, j++)
{
thisbit = time[j] > ZERO_LIMIT;
DataBits = (DataBits << 1) | thisbit;
DataBitsReversed = (DataBitsReversed >> 1) | (thisbit * 0x80000000);
}

LCD_Clear();
display_HEX(DataBits);
LCD_dr(d[7]);
LCD_dr(d[6]);
LCD_dr(d[5]);
LCD_dr(d[4]);
LCD_dr(d[3]);
LCD_dr(d[2]);
LCD_dr(d[1]);
LCD_dr(d[0]);

LCD_Line2();
display_HEX(DataBitsReversed);
LCD_dr(d[7]);
LCD_dr(d[6]);
LCD_dr(d[5]);
LCD_dr(d[4]);
LCD_dr(d[3]);
LCD_dr(d[2]);
LCD_dr(d[1]);
LCD_dr(d[0]);
} // data received
} // while loop
}  // main

//-----------------------------------
//  end of Main
//-----------------------------------

Notes

1) The basic mcu internal clock is 1MHz. The timer is configured with a divide-by-8 prescaler. Hence each timer count represents 8μs.

2) LED1 at P1.0 (pin-2) is used as an indicator when data is being received. LED1 goes off when the timeout has occurred.

3) The TIMEOUT has been set for about 120ms to avoid repeated action when the key is held down continuously. If repeat key action is desired this will have to programmed differently.

4) I have used the same LCD interface from the previous LCD project.

LCD Programming Example

5) The LCD will show the 32-bit data in hexadecimal format, in normal order (MSB first) and bit-reversed order (LSB first). You can simply use the relevant 16-bits of either order to decode the key pressed.

This example is likely the most difficult exercise I will present. It was tricky to get working because of the intricacies of detecting the IR data and working with the timer module. Hopefully, future exercises will not be as difficult.

Coming next:

A 24-hour clock with a twist.

PREVIOUS NEXT

MSP430 Tutorial - Index

Author
MrChips
Views
7,664