TV-B-Gone for the Arduino

I've ported the TV-B-Gone code to run on the Arduino board. If you haven't seen a TV-B-Gone, it's a cute gadget that you point at a TV that's bothering you, and it turns the TV off. Internally, it's an infrared remote that broadcasts more than 100 different off codes that work on almost any TV. I figured it would be interesting to get the TV-B-Gone running on the Arduino.
TV-B-Gone running on an Arduinio

Update (November 2010)

I have a new, improved version of the software. Details and download are here.

The software

I started with the TV-B-Gone firmware, which is available under Creative Commons license. The TV-B-Gone runs on an ATtiny85 microcontroller, not the ATmega328 used by the Arduino, so some porting was required. One tricky part of the port is the IR signals are generated using the low-level hardware timer for PWM (pulse-width modulation). Since the ATtiny85 runs at 8MHz, and the Arduino runs at 16MHz, the timing code needed to be re-written, both the 10us delay loop and the timer register operations. The quick summary is I'm using Timer 2, scaling the clock by 8, using OCR2A to control the frequency and OCR2B to control the duty cycle, and have output on pin 3 (OC2B). (You are not expected to understand this. See my article on Secrets of Arduino PWM for details of how these timers work.)
#define freq_to_timerval(x) (F_CPU / 8 / x - 1)
    pinMode(IRLED, OUTPUT);
    TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
    TCCR2B = _BV(WGM22) | _BV(CS21);
    OCR2A = freq; 
    OCR2B = freq / 3; // 33% duty cycle
Another tricky thing is the original firmware stores the IR codes in program space, not RAM. The microcontrollers use a Harvard architecture, which means they have separate memory and data paths for code and data, unlike normal processors that use a von Neumann architecture. Because RAM storage is so small on these microcontrollers, the IR codes are stored in program space, with the result you need to use special methods to access them, rather than normal C pointers. Program memory storage is indicated with the PROGMEM macro and pulled out of memory using special functions such as pgm_read_byte. This makes the code somewhat confusing, for example:
const struct IrCode *NApowerCodes[] PROGMEM = {

data_ptr = (PGM_P)pgm_read_word(NApowerCodes+i);  

The original firmware stores the codes as compressed indices into tables of durations. See the TV-B-Gone design for details. Unfortunately, the Arduino's compiler didn't like the zero-length array in the IrCode structure, so I needed to rewrite the long file of codes to include another level of indirection.

Apart from these factors, porting was straightforward, and I tried to keep the original code where possible. I ripped out all the power-up and watchdog code. I replaced the low-level serial code with the Arduino's Serial library. Finally, I packaged it up into an Arduino sketch, which you can download. (Update: get the improved version here.)

The hardware

The hardware is straightfoward. I use pin 13 for the status LED; some Arduinos conveniently have an LED already wired up. Pin 3 is the PWM output to the IR LED through a 100 ohm resistor. If you want more range, add a driver transistor and use multiple IR LEDs. Pin 0 (update: pin 5) selects North American or European codes; leave it unconnected for North America and ground it for European. Pin 1 (update: 2) is connected via a pushbutton to ground to trigger the code transmission. (Update: pins have changed in the new version.)

To use the Arduino TV-B-Gone, point the IR LED at your TV, push the button, and your TV should turn off when the circuit hits the right code, which could take up to a minute. The visible LED should flash for each code that is transmitted. If the circuit doesn't work, use a cellphone camera to verify that the IR LED is transmitting. Don't expect more than a few feet range unless you use a transistor to increase the power. The code will print debugging output to the serial port if you set DEBUG in main.h.


Is the Arduino TV-B-Gone practical? If you want a TV-B-Gone that you can carry around to amaze your friends, the kit or product is much more compact, has high-powered IR outputs for longer range, and is nicely packaged. However, the Arduino platform is convenient for experimenting, and many people already have it, so I expect people will find it interesting.

You might wonder why I'm not using my Arduino Infrared Library for this project. Actually, I plan to at some point, but I wanted to get the straightforward port working first. In addition, my library will need some extensions to support all the codes that the TV-B-Gone supports.

I hope you enjoy the Arduino TV-B-Gone, and remember to use it for good, not evil!


Orikata said...


I use an atmega 328 on my arduino and i don't have light on pin 3 for the IrLED

thank you for your article and the sources

[email protected]

Orikata said...

Sorry, it works very well on philips tv

Generic Nerd said...

Great work! I've been waiting for this since I first laid eyes on the Arduino and the TV-B-Gone.

Unknown said...

very good job works on my 168

robingchan said...

I loved this so much i built a PCB for it.

h4v1nfun said...

hi there, i've tried your circuit on a breadboard with half success, i can get it to work with the pin 0 grounded for the EU codes, my option was to set the default one in the sketch to EU instead of the US, can you please post the full circuit schematic, perhaps in fritzing

Gratz, Afonso

Anonymous said...

this is neat, but could you make a version to turn every tv to the discovery channel?

Imagine a packed sportsbar on the big game switched over to mythbusters!

Blowed 'em up real good!

Unknown said...

very bad tutorial

Anonymous said...

I've added sleep() to this, as leaving the arduino running full bore, all the time, the batteries wouldn't last long!

I didnt want to toss yet-another-version out there, so I'll leave it to the reader to add, and Ken to incorporate into Version 2. You can read about it on my blog...

Just Plain Bob said...

Re ladyada's four LED model (1.2).
( See ).

Unless I'm mistaken, the PNP transistor that drives the four NPNs will introduce an inversion in the output. Am I misreading the schematic? Is this an issue? Or does she provide inverted input to the PNP in this model?

Unknown said...

What could be the range with one IR led ?

Unknown said...

Hello i am using arduino diecimila (USB Powered) and sketch works only when using DEBUG = 1. I've tried to comment out Serial.begin(9600); line to disable serial even with DEBUG enabled, but it also didn't worked. When i left serial debuging enabled, i was able to turn my s*ny TV off and on, but i think it causes suboptimal speed of transmission, when i need to wait for serial line before each code is transmited...

and BTW i am getting
NA Codesize: some_reasonable number,
EU Codesize: 0

(i live in EU so i have shorted 0 and GND together. I don't know what should i think about this...)

Anonymous said...

It seems the EU codes are disabled by default. You can add "#define EU_CODES" to the beginning of main.h to enable them.

James said...


When I try and compile the sketch I get the errors:

o:( multiple definition of `NApowerCodes'
/var/folders/sJ/sJhEJAz6HZiIGNVVaTTBwk+++TI/-Tmp-/build7674028882894173597.tmp/TVB/WORLDcodes.cpp.o:( first defined here

o:( multiple definition of `EUpowerCodes'
/var/folders/sJ/sJhEJAz6HZiIGNVVaTTBwk+++TI/-Tmp-/build7674028882894173597.tmp/TVB/WORLDcodes.cpp.o:( first defined here

o:(.data.num_NAcodes+0x0): multiple definition of `num_NAcodes'
/var/folders/sJ/sJhEJAz6HZiIGNVVaTTBwk+++TI/-Tmp-/build7674028882894173597.tmp/TVB/WORLDcodes.cpp.o:(.data.num_NAcodes+0x0): first defined here

o:(.data.num_EUcodes+0x0): multiple definition of `num_EUcodes'
/var/folders/sJ/sJhEJAz6HZiIGNVVaTTBwk+++TI/-Tmp-/build7674028882894173597.tmp/TVB/WORLDcodes.cpp.o:(.bss.num_EUcodes+0x0): first defined here

Can anybody help me with this?
Am I missing something obvious?


Anonymous said...

Does anyone know if the codes work on Stereo equipment rather than TV's?

My neighbour has loud parties and this would put a spoke in the wheels :-)

Anonymous said...

i built this using a arduino pro mini 16mhz 5v

it runs and you can see the IR flashing away (using a phone cam), but it never turns my tv off :(

im from the uk and have it set to EU

Anonymous said...

The EU_CODES seems to be broken.

Anonymous said...

iv added the define for that EU codes and it is running through like 120 of them but none seam to switch any of the tvs off in my house, could it be a timing issue?

Ken Shirriff said...

I haven't tested the EU codes. Since I don't have a European TV, it's hard for me to figure out what the problem is. (By the way, is it the same Anonymous or 3 different Anonymouses who are trying to get EU codes working?)

Unknown said...

two of them where me hehe, it runs fine but does not turn any tvs off, should i try it on a few more tvs?

im running the 328 arduino mini

Ken Shirriff said...

@russdx: I haven't tried the Arduino 328 mini, so I don't know if that's causing the problems. My first thought was the frequency might be different, but the 328 mini and the Duemilanove both run at 16 MHz. My second thought is the pin connections might be different with the mini, so make sure you're actually getting an output from the IR LED. (You can use a cellphone camera to see the IR.) My third thought is that the EU code might not be working (since I didn't test it at all, not having European televisions lying around). Have any of my European readers managed to get the EU codes working?

It's also possible the codes just don't work with your TVs, but that seems unlikely since the TV-B-Gone has so many codes. You could try the US codes and see if that works any better.

phae said...

Hi !
I've just tried your TV-B-Gone with a duemilanove (so atmega 328P) and worked very well with my french TV (so EU codes worked fine for me). Thanks a lot !
I'm currently working on a transportable version so i can test it in a TV shop tomorrow ;)
Since i'm planning to organize a workshop to build your arduino tvBgone, i'd like to know if it works with Atmega168. Because they're cheaper than the 328P and would reduce the workshop participation fee.
Thanks again,

phae said...

Hi !

Is it working well on ATmega 168 ?
(as I said earlier the 168's are cheaper to build a barebone arduino)


Unknown said...

yeah the little ir sensor is flashing away

very strange, i will try it on a few more tvs trying us/eu codes

zoobab said...

Mitch Altman fixed your code here:

The EU codes were not working properly.

Ken Shirriff said...

Mitch Altman and I have made some improvements to the code; among other things, the European codes now work. See Improved Arduino TV-B-Gone for details.

Claudio Miklos said...

Works very well for me, thank you!

Veerendraputtu said...
This comment has been removed by the author.
Johnny K said...

Hey. Which compiler do you use??? How could you include main.h and so on??? If i load TVB.pde a have millions of mistakes. Could you help me please.

Hilary said...

I have the same error described here:;wap2

(same as quoted by James above)

I am using BoArdino that I made in Mitch's "Arduino for newbies" workshop.
I put the sketch in the library folder thus:

thanks for advice,
[email protected]

Nev said...

I would like to run this on 3.3v 8MHz arduino. Seems timing will be an issue. What you would recommend?

peterparker - Servant of BHAGWAN PARASHURAMA said...

Hello Ken,

Really wonderful work.

I am from India, and trying to make this circuit.

So do I need to put India codes for this, as I can see north america and European codes or is this an universal circuit?

Also, I am not able to compile the code, receiving errors

TVB\WORLDcodes.cpp.o:( multiple definition of `NApowerCodes'
WORLDcodes.cpp.o:( first defined here
TVB\WORLDcodes.cpp.o:( multiple definition of `EUpowerCodes'
WORLDcodes.cpp.o:( first defined here
TVB\WORLDcodes.cpp.o:(.data.num_NAcodes+0x0): multiple definition of `num_NAcodes'
WORLDcodes.cpp.o:(.data.num_NAcodes+0x0): first defined here
TVB\WORLDcodes.cpp.o:(.data.num_EUcodes+0x0): multiple definition of `num_EUcodes'
WORLDcodes.cpp.o:(.data.num_EUcodes+0x0): first defined here

I have not put the code in the libraries folder, but inside the sketchbook folder, still getting the error.

What could be the reason for this?


Anonymous said...


I'm trying to make it working with my Arduino nano v3, but it doesn't work...
Actually, it works with my Arduino Uno, but not with the Nano.

Could you tell me what to do ?

Anonymous said...

it gives an error for my :p
I tried it on arduino mega adk...

Unknown said...

I updated the TV-B-Gone sketch so that it works on all version of the Arduino software to date (through V1.6.5). Please see my Arduino For Total Newbies workshop page for the link to download the TV-B-Gone sketch (download #3):

Unknown said...

Oh, and this new sketch also works with Leonardo.

Anonymous said...

Did Ken Shirriff ever port his TV-B-Gone Code over to use his Arduino Infrared Library?

If so, anyone with a URL to the ported code?

Ken Shirriff said...

No, I never ported the TV-B-Gone code to use my IR library. It worked fine without it, so I didn't see the point.

Anonymous said...

Has anyone experimented in removing all the 38Khz timing code from the sketch, and switch the code over to use the library Ken has supplied, and then using an lm555, triggering pin 4 on the lm555 which supplies the 38Khz modulation, and frees the arduino to ONLY be responsible for sending the code bits?

This should allow us to simply use the lirc codes, and be any to choose any and all codes from there, and it simply becomes, "plug in the code and play!"

Anonymous said...

hi, does this work on an digispark?
(with an attiny at16 mhz)

Mike said...

I have put the latest version of TV-B gone on a solderless breadboard.

Using only one IR LED thus far but will add more and spray IR stuff from a great distance.
I am using direct drive from pin 3 on my Uno with a 10 ohm resistor in series with the LED to ground. Might add 2N2222 transistors instead of the direct from the Uno pin #3.
I also changed the pinMode for the NA / EU option to default pulled up rather than rely "floating" which could change while running due to all sorts of stuff.
Temperature change, RF interference, static charge etc.
pinMode INPUT_PULLUP worked with out problem.

Only problem so far is that for my Sony TV it turns the TV off and then shortly after turns it back on again. And quick test of another TV, a Visio gets no action.
Further testing on that is needed.
Would be cool to serial.print (while debugging) the BRAND NAME as the codes are output by the LED.

I wish the codes were commented by brand in the list. The construction of codes is not really user friendly. It must have been terribly tedious to come up with the format.

But happy to know that my initial try is 99.9% functional. See me smile.

Mike said...

I came across info to enable Debugging by setting DEBUG 1. So I did
And I can see using Serial.print whats going on but it's too fast to really read.
Anyone got an idea of where / how to put a conditional delay statement in it.

I tried to insert a DELAY (1000); it didn't work. I thought I took that out but now getting a compiler error. I probably typo'ed the code. I shall now replace the stuff in the library and try again.

And. . .has anyone made TVB Gone work on a Nano? I'd love to have the small physical size.

Mike said...

After a bit of "no that didn't work either..."
I found that the DEBUGP is a function that appears to be called to send
the debug code IF DEBUG is set to 1

Here's the code I added

DEBUGP (delay (1000)); // Insert this line at approx line #348
// ^^^^^^^^^^^ ONE SECOND DELAY.

// print out the address in ROM memory we're reading
DEBUGP(putstring("\n\rAddr: ");

// Read the carrier frequency from the first byte of code structure
const uint8_t freq = pgm_read_byte(data_ptr++);
// set OCR for Timer1 to output this POWER code's carrier frequency
#ifdef __AVR_ATmega32U4__
OCR0A = freq;
OCR0B = freq / 3; // 33% duty cycle
OCR2A = freq;
OCR2B = freq / 3; // 33% duty cycle