Design Notes

Here are some notes on how the TV-B-Gone kit is designed!

Be sure to read the accompanying firmware for details on how the PWM and timers are initialized!

Power supply

The kit is powered by 2 AA batteries. It's important that the power supply voltage be between 2.5V and 5V. Higher than that can damage the kit. Lower is too low for it to work. It's also important that the power supply be able to supply 400-1000mA easily. For AA's this is not a problem. AAA's may have issues. C or D cells are fine as are many Lithium Ion/Polymer batteries. Check your batteries if you're not using standard Alkaline AA's.

Better batteries will get you more range. "Lithium" 1.5V are excellent, Alkalines are good, and then recharagables are OK but not fantastic. Coin cells will not work at all. 9V batteries will kill the device. See this tutorial on batteries for ideas.

Using 3 batteries will also greatly improve range. 4 gets a little dangerous because 4 Alkalines is 6V which is above the suggested voltage for the chip and may damage it. 4 NiMH cells is a safe voltage for the chip, but tends to burn out the LEDs.

Stick to 2 or 3 batteries!

IR LEDs

The 4 blaster LEDs are IR LEDs. They are 940nm wavelength emitters which is the most common IR used for remotes. The blue ones are narrow output (longer range but narrower), the clear ones are wide output (shorter range but wider throw). Together they should let you to hit most TVs!

Button

There is one button. Pressing & releasing will start the kit to emit IR codes. The button is actually hooked up to the microcontroller's reset pin and tells the chip to reset. When the chip resets it automatically emits all the codes in its library. When it's done it goes into a very-low-power sleep mode and awaits another button press.

Crystal

The 8.0 MHz resonator isn't technically a crystal but it acts the same. It generates the 8.0MHz timing clock to keep the chip on time and generating precise waveforms. There is an internal 8mhz resonator on the chip but it's not nearly precise enough and varies with battery voltage and temperature. Using an external resonator prevents that. The resonator MUST be 8.0MHz as the code is specifially written for that value. If you decide to change the crystal speed (there's no reason to but people ask) you will have to change the code for the new value by updating the Makefile and completely recompiling.

IR-Driver Transistors

The IR LEDs have more than 100mA going through them. This is way more than a microcontroller pin can supply (usually 20 to 40 mA max!) so we use a 'driving' transistor. These NPN transistors amplify the power of a microcontroller pin and are designed to turn off currents of over 100mA. When the base pin is pulled high (though the base resistor) the transistor turns on and the LED turns on too. When the base pin is pulled to ground, the transistor turns off and no current passes through the LED.

In v1.0 kits 1 pin on the microcontroller controls four transistors each.

In v1.1 kits two pins on the microcontroller each control two transistors each (4 total) - this increased the base current to give better range.

(Please note that at the DC currents we are pushing through the transistor and the LED, the beta of the transistor is not 200 but closer to maybe 20-50, Vbe is about a volt, and Vce_sat grows to a volt or so. Of course it depends on the transistor but just be aware that 'simple' transistor modeling is not valid anymore.)

IR-Driver-Transistors-Driver Transistor

In v1.2 we decided that we really wanted an extra pin to do region detection. We could go back to having one micro pin control all four transistors but the range would suffer so instead we have a PNP transistor that will buffer the weak microcontroller pin and push plenty of current into the bases of the IR driver transistors.
Now we use one pin, PB0 to drive Q5 the PNP which connects the bases of the driver NPNs with a path to VCC.

(Note we do not use base resistors because the power supply droop, the NPN's Vce_sat and the IR LED voltage drop will max out and limit the base current and then the Vce of the PNP will sit at the 'right' voltage.)

R2 was placed in case we needed 'help' with getting charge off of the base. Its not needed so ignore it.

Example of a single IR code

For this example we will use the Sony power on/off IR code. It's the first code in the North American database and the 4th in the EU database. It's very simple and commonly documented!

Let's pretend we have a Sony remote, we want to extract the code from it. We'll hookup a IR sensor (like a basic photocell!) and listen in, what we see is the following:
Basically we see pulses or IR signal. the yellow 'blocks' are when the IR LED is transmitting and when there is only a line, the IR LED is off. (Note that the voltage being at 3VDC is just because of the way I hooked up the sensor, if I had swapped the pullup for a pulldown it would be at ground.)

The first 'block' is about 2.5ms long (see the cursors and the measurement on the side)

If you zoom into one of those blocks...

You see that they're not really 'blocks' but actually very fast pulses!

If you zoom in all the way...

You can measure the frequency of the IR pulses. As you can tell by the cursors and the measurements on the side, the frequency is about 37.04KHz

OK so now we can understand how IR codes are sent. The IR transmitter LED is quickly pulsed (PWM - pulse width modulated) at a high frequency of 38KHz and then that PWM is likewise pulsed on and off much slower, at times that are about 1-3 ms long.

Why not have the LED just on and off? Why have PWM 'carrier' pulsing? Many reasons!

One reason is that this lets the LED cool off. IR LEDs can take up to 1 Amp (1000 milliamps!) of current. Most LEDs only take 20mA or so. This means IR LEDs are designed for high-power blasting BUT they can only take it for a few microseconds. By PWM'ing it, you let the LED cool off half the time.

Another reason is that the TV will only listen to certain frequencies of PWM. So a Sony remote at 37KHz won't be able to work with a JVC DVD player that only wants say 50KHz.

Finally, the most important reason is that by pulsing a carrier wave, you reduce the affects of ambient lighting. The TV only looks for changes in light levels that clock in around 37KHz. Just like it's easier for us to tell differences between audio tones than to pin down the precsise pitch of a tone (well, for most people at least)

OK so now we know the carrier frequency. Its 37KHz. Next lets find the pulse widths!

Looking back at the first scope picture:

The first pulse is 2.5ms. We can use the cursors to measure the remaining pulses. I'll spare you the 12 images and let you know that the pulses are:
PWM ON OFF
2.4 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 270 ms
In reality the code is emitted twice:
PWM ON OFF
2.4 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 270 ms
2.4 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms -
In previous versions (v1.0 and v1.1) we didn't do any compression. Instead we made the code literally spit out each pulse:
// Code 000 -- Sony, Baur, Neckermann, Otto Versand, Palladium, Quelle, SEI, Sinudyne, Sonolor, Universu
const struct powercode sonyCode PROGMEM = {
  freq_to_timerval(38400), // 38.4 KHz  
  {{240, 60},
   {120, 60},
   {60 , 60},
   {120, 60},
   {60 , 60},
   {120, 60},
   {60 , 60},
   {60 , 60},
   {120, 60},
   {60 , 60},
   {60 , 60},
   {60 , 60},
   {60 , 2700},
   {240, 60},
   {120, 60},
   {60 , 60},
   {120, 60},
   {60 , 60},
   {120, 60},
   {60 , 60},
   {60 , 60},
   {120, 60},
   {60 , 60},
   {60 , 60},
   {60 , 60},
   {60 , 0}// end of code
  }
};

You can see that the first entry is a little function that converts the 38.4 KHz (38400 since we can't use floating point here) into a 'timer value' We use the built-in 8-bit PWM module in the chip, so we can set the frequency with an 8 bit value. Note that that value is not actually the frequency but is calculated thusly: timer_val = ((F_CPU / freq - 1)/ 2) where F_CPU is the clock crystal. We use an 8 MHz crystal so to generate 38.4 KHz for example, we use the value 103 or 104. The precision isn't that important and it's definitely good enough for our uses.

Then comes a long list of all the codes (I cleaned it up a little to match the 'ideal' code readings).

The timer value takes up 1 byte. Each number in the code table is 16 bits long so it can hold numbers larger than 255. That means each code pair line takes up 4 bytes. So the total memory storage needed is 1 + 26 * 4 = 105 bytes

Code compression in v1.2

While the above is more readable, it's very 'bulky' and hard to fit more than 40 codes. That sucks! We want MORE codes. So we will compress them. (This code is similar to the Damian Good/Caitsith compression scheme but not identical. However, we are indebted to him for the great work!) Remember that the code looked like this:

PWM ON OFF
2.4 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
1.2 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 0.6 ms
0.6 ms 270 ms
Since you can see that the pairs of on/off codes repeat (not coincedentally!) we can squeeze the code down by creating a table of the "unique" pairs. I've ordered them so the first column is sorted first and then by the second column:
INDEX # PWM ON OFF
0 0.6 ms 0.6 ms
1 0.6 ms 270 ms
2 1.2 ms 0.6 ms
3 2.4 ms 0.6 ms
Then our code would look like this:
CODE PAIR INDEX
#3
#2
#0
#2
#0
#2
#0
#0
#2
#0
#0
#0
#1

Where to decode, we look up the on/off code pair in the Timing Table!

We can store the timing table in memory thusly:

const uint16_t code_na000Times[] PROGMEM = {

   60, 60,

   60, 2700,

   120, 60,
   240, 60,

};

The const keyword indicates that these are 'constants' - not variable data.
The uint16_t keyword indicates that the data is 16-bits long. We need 16 bits because some of the lengths are longer than 255 (which is the max size one can store in 8-bit data).
Then comes the name, this is the code_na000Times timing table, which goes with code na000 (sony)
Next is [] which indicates it's an array of values.
The PROGMEM tells the compiler to store it in ROM. This is important because there is only 512 bytes of RAM but 8192 bytes of ROM (flash). By default the compiler thinks we want to store stuff in RAM because we can then change it easily. ROM cannot be modified by the chip itself easily.
Finally, we have the table. We can't use 'floating points' so instead of storing "0.6 ms" we store "60" (multiplying everything by 100) This gives us the precision we want. When it's time to actually use the numbers we just take into account that they're 100 times too large.

This code table takes 8 (entries) * 2 (bytes each) = 16 bytes

Now we must store the code itself, seperately. (We don't stick the two together because multiple codes use the same timing tables so we save memory by keeping them seperate.)

Let's convert the code index list into binary. Because we only have 4 different codes, we only need 2 bits to describe each code pair. If we had 5 different codes, we'd need 3 bits which can store up to 8 different values. Once we hit 9 different code pairs, we need 4 bits, etc...

Now one thing we skipped before is that the code is actually transmitted twice so we need to double up the table!

CODE PAIR INDEX In Binary
#3 11
#2 10
#0 00
#2 10
#0 00
#2 10
#0 00
#0 00
#2 10
#0 00
#0 00
#0 00
#1 01
#3 11
#2 10
#0 00
#2 10
#0 00
#2 10
#0 00
#0 00
#2 10
#0 00
#0 00
#0 00
#1 01

Great! We have our full code now. So our code can now be turned into a chunk of data, we append each code pair index to each other to create this 'string' of bits:

1110001000100000100000000111100010001000001000000001

Which we divide into 8 bit groups:

11100010 00100000 10000000 01111000 10001000 00100000 0001

Then we 'pad' out the final chunk so it's a full 8 bits long:

11100010 00100000 10000000 01111000 10001000 00100000 00010000

In hexadecimal, we can describe the code in 7 bytes:

0xE2 0x20 0x80 0x78 0x88 0x20 0x10

That's -really- compressed. We will have to now describe the code in a way that it can be 'unpacked'

Here is how we will describe the code. First we will create a C structure so that the compiler will put things in order properly:

// The structure of compressed code entries
struct IrCode {
  uint8_t timer_val;
  uint8_t numpairs;
  uint8_t bitcompression;
  uint16_t const *times;
  uint8_t codes[];
};

The first byte timer_val is the way we set the carrier frequency. We use the built-in 8-bit PWM module in the chip, so we can set the frequency with an 8 bit value. Note that that value is not actually the frequency but is calculated thusly: timer_val = ((F_CPU / freq - 1)/ 2) where F_CPU is the clock crystal. We use an 8 MHz crystal so to generate 38.4 KHz for example, we use the value 103 or 104. The precision isn't that important and it's definitely good enough for our uses.

numpairs lets us store how many pairs are in the code. So we know when to stop!

bitcompression tells the program how many bits we used to compress the code, for example in this case we used 2 bits

times is a pointer (address) to the timing table

codes is a list of the codes!

Next is the code itself:

const struct IrCode code_na000Code PROGMEM = {
        freq_to_timerval(38400),
        26,             // # of pairs
        2,              // # of bits per index
        code_na000Times,
        {
                0xE2,
                0x20,
                0x80,
                0x78,
                0x88,
                0x20,
                0x10,
        }
};

const and PROGMEM are the same as before, just tells the compiler to stick this into ROM. struct IrCode tells the compiler this must be in the form we indicated earlier.

Then we have the function that turns the frequency (38400 is our way of writing 38.4 KHz) into the right 8 bit number. Then the number of pairs (13 in the code, emitted twice!), the 2-bit compression method, the name of the timing table, and the compressed code.

The final storage necessary for the sony code is 16 bytes (for the table) + 1 (for the timer val) + 1 (for the compression notice) + 2 (for the timer table pointer) + 7 (for the code itself) = 27 bytes Compare that to the previous version that took 105 bytes!

Of course, to do this by hand is awful. Instead, we grabbed the data from a 'univeral remote' and used a perl script (check the download page) to automate it. Because the timing is not very precise coming out of these universal remotes, we 'cleaned up' the Sony code but you'll notice that other codes are not nearly as tidy - the frequency may be off, the pulse widths varying, etc.

This guide was first published on Apr 21, 2013. It was last updated on Mar 08, 2024.

This page (Design Notes) was last updated on Mar 08, 2024.

Text editor powered by tinymce.