TinyPCRemote – An ATtiny85 Based Infrared PC remote control

Here’s a cheap way to build your own fully customisable infrared PC remote control. If you already have a suitable infrared remote control going spare you can build one of these for under £4, it will allow you to use most infrared remote controls to issue keyboard commands (single characters or a string) on your PC. Using a surplus remote control it could be used as a cheap media centre remote control with XBMC etc. or would be great for causing some mischief by covertly taking control of someone’s computer. I’ve tested it on several Linux and Windows boxes and one Mac and it has worked fine on them all so far.

At the heart is an 8 pin ATtiny85 microcontroller running at 16MHz using the internal oscillator with a Vishay TSOP31238 IR sensor handling the IR reception and USB implemented with V-USB. Each remote control button can trigger a single keyboard character or series if characters as well as meta keys such as shift and alt.

I couldn’t find any guides for using V-USB on the ATtiny85 under the Arduino environment so have detailed what I did in full here. Other than changing the default PIND, pin and timer settings the key seems to be that when using the internal oscillator like I am here it needs to be calibrated for each individual chip as the timing for USB is so critical.

Schematic and Stripboard layout

Here is the schematic for the USB infrared receiver:

and a stripboard layout:


Parts List:
Atmel ATtiny85-20PU Microcontroller
8 pin DIP socket
Vishay TSOP31238 IR sensor
100nF ceramic capacitor
1 x 2K2 resistor
2 x 68R resistors
2 x 3.6V Zener diodes (must be 0.5W or less)
Through hole male USB A connector
Stripboard, 18 holes by 6 rows


You can download my TinyPCRemote code for the ATtiny85 on Github here.
You will also need: vusb-for-arduino and Tiny-tuner


Getting your remote control codes
To get the codes from your chosen remote control I’ve provided a sketch in the above repository that can be run on an Arduino or compatible board which will output the code on the serial monitor as each button is pressed, it’s designed so that you can just plug the IR sensor straight into the headers across Ground, D13 and D11 as shown below:

Once the sketch is loaded on the Arduino open the serial monitor at 9600 baud and press each button on your remote control in turn and you will see the corresponding code output on the serial monitor, don’t worry if the code repeats for each button press. Simply copy and paste the codes into the TinyPCRemote sketch and define the key/s that you want it to press when detected, replacing the existing definitions and duplicating as necessary for as many buttons as you need. There will be an upper limit as to how many can be configured, limited by flash and RAM, I haven’t tested to find the limit but it should allow for plenty of codes.


V-USB Configuration
The USB side of things is handled by V-USB, a very useful open source software-only implementation of the USB 1.1 standard for Atmel AVRs created by Objective Development. I used the vusb-for-arduino variant.

Unzip the file and copy the UsbKeyboard directory to your Arduino library location and restart the Arduino IDE if it was running. Now you need to edit a couple of files to change the port and pin numbers to be compatible with the ATtiny85.

Firstly edit usbconfig.h and under “Hardware Config” change




and under “Optional Hardware Config” change:


Note that we’re not actually connecting the pullup via a pin but this still needs to be set to a valid port in order to compile.

Optionally you can also change the manufacturer and device name in the following defines:


Finally, in UsbKeyboard.h change:

PORTD = 0; // TODO: Only for USB pins?
PORTB = 0; // TODO: Only for USB pins?


Programming the ATtiny85
Programming the ATtiny85 can be accomplished via an Arduino and the ArduinoISP sketch that is included in the examples section of the Arduino IDE or a dedicated ICSP device.

To program the ATtiny85 via an Arduino connect it as follows:

Arduino ATtiny85
D13 Pin 7
D12 Pin 6
D11 Pin 5
D10 Pin 1
5V Pin 8
GND Pin 4


Calibrating the oscillator
The timing for USB is critical and the oscillator for each individual ATtiny85 chip will almost certainly need to be calibrated, to do this download TinyTuner from here and put it in your Arduino libraries directory. You are also going to need an FTDI adapter or some other sort of serial converter so you can read the output.
Connect your ATtiny85 up to the Arduino and use the burn bootloader function of the Arduino IDE to set it to run at 8MHz by choosing the “ATtiny85 @ 8MHz (internal oscillator;BOD disabled)” board setting under Tools > board and then using the Tools > Burn Bootloader option. Note that as with the ATtiny84 this doesn’t actually burn a bootloader, all we are doing is setting the relevant fuses to configure the microcontroller, in this case we are temporarily setting it to run at 8MHz as tiny-tuner won’t run at our final 16MHz speed.
Load the “Interactive_to_Serial_with_Details” TinyTuner sketch from the tiny-tuner examples onto the ATtiny85, connect PB3 (pin 2) to RXD on the FTDI adapter and PB4 (pin 3) to TXD as well as 5V and ground.

Now open a terminal app on your PC and connect to the port of the serial adapter, eg. on linux “ /dev/ttyUSB1“, reset the ATtiny85 and you should get a welcome message, now repeatedly press x slowly (once a second or so is fine) until you get a result like:

Copy-and-paste the following line of code at the top of setup…

Copy the result from tiny-tuner into the TinyPCRemote sketch replacing the OSCCAL = 0x9C; line in setup() as appropriate.


Final Setup
Once you have entered the OSCCAL calibration value and your remote control codes along with the resulting keys you want to be entered  then you are ready to load the TinyPCRemote sketch onto the ATtiny85.

First use the burn bootloader function again to set the ATtiny 85 to 16MHz by using the “ATtiny85 @ 16MHz (internal PLL;4.3V BOD)” board setting then upload the sketch in the normal manner.

If you are testing it on a breadboard as I did when developing it then you will probably had to disconnect the connections to the Arduino for the USB to work, I got away with just disconnecting the SCK connection (wire from Arduino D13 to ATtiny pin 7/PB2).

If you now plug the the TinyPCRemote into a USB port hopefully pressing the remote control buttons will result in the corresponding characters appearing on your screen as if typed on your keyboard.

If not run dmesg and look for any errors, if everything is working you should see something like the following (I don’t know how you can view similar logs on Windows or Mac):

input: TinyPCRemote as /devices/pci0000:00/0000:00:13.5/usb1/1-9/1-9.2/1-9.2.1/1-9.2.1:1.0/input/input18
generic-usb 0003:4242:E131.0006: input,hidraw2: USB HID v1.01 Keyboard [ TinyPCRemote] on usb-0000:00:13.5-9.2.1/input0

Note that I’ve changed the manufacturer and device names in usbconfig.h to be and TinyPCRemote respectively.

If you get device descriptor read errors like:

usb 1-9.2.1: new low-speed USB device number 28 using ehci_hcd
usb 1-9.2.1: device descriptor read/64, error -32

then your computer is seeing the device but you probably have a timing problem. Make sure your ATtiny is set to 16MHz using the internal PLL and that you have calibrated it correctly using tiny-tuner.


39 comments to TinyPCRemote – An ATtiny85 Based Infrared PC remote control

  • Sam

    Thanks a million, I was looking for this everywhere. If I wanted to use this with a crystal, what changes in software?


  • Just removing the OSCCAL line should be enough, no need to calibrate if using a crystal but everything else should be fine as is. Just put a crystal and bypass caps on and use the correct “burn bootloader” option to set the fuses.


  • Carl

    Would this work on an arduino uno?

  • Yes, you would still have to add the USB connector/diodes/resistors though, you can’t use the Uno USB connector for V-USB.

    The connection shown on the schematic going from D- on the USB plug to pin5/PB0 on the ATtiny would have to go to D2 on the Arduino instead and the one going from D+ to pin7/PB2 would have to go to D4. You wouldn’t have to make the changes in usbconfig.h or do the calibration.

    In the main code you would need to drop the OSCCAL = 0x9C; line and change TIMSK&=!(1<

    and change:
    #define IRpin_PIN PINB
    #define IRpin 4


    #define IRpin_PIN PINB
    #define IRpin 3


    pinMode(4, INPUT); // Make sure IR pin is set as input


    pinMode(11, INPUT); // Set IR pin as input

    and put the IR receiver across ground, D13 and D11 as shown in picture for the code learner above.

    That should do it.

  • I really appreciate your detailed blog post. Thanks!

  • I want to use a crystal for this circuit, but I have a question.

    According to your circuit, the TSOP31238 IR is connected to Tiny85 Pin3, this will occupied the pin for crystal. Based on Tiny85 datasheet, the crystal must connect to Pin2 & Pin3 of Tiny85.

    Should I connect the TSOP31238 IR to Pin6 of Tiny85 since Pin6 is not using currently.

  • Yes, pin 6 would be fine, note that pin 6 is PB1/Arduino D1 so in the code you would have to change the 4 to 1 in these 2 lines:

    #define IRpin 4

    pinMode(4, INPUT);

  • Thank you very much. This is what I’m looking for my project.

  • oliver

    The tinycodereader always assumes that pin 3 (digital pin 11) is used. Thus it only works on the Uno’s. On the ATMEGA 2560 Digital pin 11 is actually mapped to pin 5. Why digitalread(11) wasn’t used I don’t know. But in its current form, IRPin 3 needs to be changed to IRPin 5 for the 2560. Only took me 2 hrs to figure out why it wasn’t working :)

  • Glad you sussed it. If I recall correctly there are timing issues with digitalRead and infrared, doing a direct read is faster.

  • oliver

    Also took me a little while to figure out that I had to install the attiny libs into my sketchbook’s hardware directory to get those boards to show up.

    Unfortunatly on the current Ubuntu LTS (12.04) tinytuner fails to compile :(

  • What’s the error? I was using 12.04 (with Arduino 1.0.1) when I did this, just checked and it is still working on 12.10 and Arduino 1.0.3.

  • oliver

    Well using a 12 MHz crystal now so it’s ok (I will try compiling it again later), but alas. I think It is using the crystal right and V-usb is configured right, it does show up as a usb keyboard with my usbid name.

    However I get no keys from it. I’ve added the loop to always print KEY_A initially (so loop() { usb.update(); sendKey(KEY_A); but then it doesn’t even get recognized anymore. So it’s very picky as to when sending of keys is allowed. I quickly checked with a scope and the crystal is nicely resonating and the IR receiver is nicely sending pulses on input (also the keyscanner program ran just fine on the atmega of course).

    So kinda puzzled as to why it won’t work on the tiny. Gotta figure out a way to output some debugging first :)

  • Tom Ellingham

    Fantastic write up. Nathan, fancy making the USB stick for me? I’m happy to do the programming! This would be so handy in my line of work…

  • Temoor (@none on Twitter)

    Hi folks,

    Is there a way to make Attiny85 emitter that complements this little gadget? I just need to send a few inputs such as scrolling with a mouse but using IR instead. Is this possible?

  • P Mehta

    What other AVR can be used instead of ATTiny85? We have difficulty procuring ATTiny85 in India. ATMega8L, ATMega16A, ATMega32A, ATMega168, ATTiny2313A-PU are the only available options. Thanks in advance and need not to mention that it was indeed a great article!

  • @P Mehta It could be modified to work with anything that can be loaded with an Arduino bootloader so any of those should be possible. I’d probably use the 2313 as it is smallest and can be used with the arduino-tiny bootloader.

    @Temoor Yes, hardware wise you would just need to replace the IR receiver with an IR emitter and resistor. There is some code here using a modified version of Ken Shirriff’s IR library that works on the ATtiny and could be used as a basis to build a transmitter from.
    I used Ken’s library when I made my web based IR remote control here:

  • Dave

    Hi Nathan,

    Really appreciate your work.
    I use the code of your listenForIR() function on an ATtiny13 to remote control a couple of relays but it doesn’t work, though it does compile without problems. It seems that the system can’t read the code correctly. Is this some kind of a timing problem?

    On the ATtiny13 I am using the internal oscillator at 9.6MHz as guided here:

    Any advice please?

    Thanks in advance.

  • Pretty sure it will be timing related, just changing the setting in boards.txt doesn’t correct the timings as it says there, it needs more changes in the core. There is another ATtiny13 core here, I don’t know if it has had all those issues worked out but it seems quite active and there is a lengthy thread on the Arduino forums about it.

  • Peter (@ituxcoza on Twitter)


    I checked and does compile for DigiSpark, but after upload does not enumerate. Likely they are using different pinouts on the USB

    Can you do a version of the writeup on which pin defines to change for DigiSpark?

  • Peter (@ituxcoza on Twitter)

    Actually ignore that – DigiSpark already ported the Library: This works for me:

    #include “DigiKeyboard.h”
    // IR sensor connected to PB4 = ATtiny85 physical pin 3
    #define IRpin_PIN PINB
    #define IRpin 0

    #define MAXPULSE 5000 // max IR pulse length, default 5 milliseconds
    #define NUMPULSES 100 // max IR pulse pairs to sample
    #define RESOLUTION 2 // time between IR measurements

    uint16_t pulses[NUMPULSES][2]; // pair is high and low pulse
    uint8_t currentpulse = 0; // index for pulses we’re storing
    void setup() {
    // don’t need to set anything up to use DigiKeyboard
    pinMode(0, INPUT); // Make sure IR pin is set as input

    void loop() {

    unsigned long irCode=listenForIR(); // Wait for an IR Code

    // Process the pulses to get our code
    for (int i = 0; i < 32; i++) {
    irCode=irCode<0&&(pulses[i][0] * RESOLUTION)<500) {
    } else {

    // —————————————————————————————
    // Enter IR codes and keystrokes to send below, see keyboard_commands.txt for list of keys
    // —————————————————————————————

    if (irCode==3225414803) { // Single character example, "1"

    } else if (irCode==3631585319) { // String example with trailing space, "hello "

    } else if (irCode==3631589399) { // String example with enter, "world ”

    } else if (irCode==3631583789) { // Modifier key example “^c”
    DigiKeyboard.sendKeyStroke(KEY_C, MOD_CONTROL_LEFT);

    } else if (irCode==3631592969) { // Raw code example Home key = 74

    // Duplicate for as many buttons as required


    } // loop end

    // IR receive code
    int listenForIR() {
    currentpulse = 0;
    while (1) {
    unsigned int highpulse, lowpulse; // temporary storage timing
    highpulse = lowpulse = 0; // start out with no pulse length

    while (IRpin_PIN & _BV(IRpin)) { // got a high pulse
    DigiKeyboard.update(); // needs to be called often
    if (((highpulse >= MAXPULSE) && (currentpulse != 0))|| currentpulse == NUMPULSES) {
    return currentpulse;
    pulses[currentpulse][0] = highpulse;

    while (! (IRpin_PIN & _BV(IRpin))) { // got a low pulse
    DigiKeyboard.update(); // needs to be called often
    if (((lowpulse >= MAXPULSE) && (currentpulse != 0))|| currentpulse == NUMPULSES) {
    return currentpulse;
    pulses[currentpulse][1] = lowpulse;

  • Peter (@ituxcoza on Twitter)

    IR Pin moved to P0

  • newbieJ

    THIS circuit is similar to what digispark is? how is attiny communicating with usb?can this not be directly programmed by usb just like digispark,,,without the need of arduino as ISP

  • No, as it stands it has no bootloader and needs to be programmed via ISP. I think it should be possible to put the Micronucleus bootloader that the Digispark uses on it but will probably need a crystal (to get round the calibration issue) and might need some pin changes, I haven’t looked to see what the Digispark uses. Reminds me, I really need to do something useful with those.

  • Neil

    Thanks for publishing this tutorial! I am not a programmer and I am new to electronics…can you point me in the right direction to use an IR signal as a toggle switch using an ATtiny 84? I am not having any problems using Ken Shirriff’s IR library with an UNO, but I can’t get anything going on the tiny.

  • The key for me was the calibration with TinyTuner, other than that I didn’t have any problems.

    Also note that the zener diodes must be 0.5W or less, apparently anything above that won’t work.

  • Bill

    I’m struggling with getting the calibration sketch to compile. Specifically, I’m using an ATTiny85 with the Sparkfun/Adafruit Tiny Programmer and the 1.05 IDE. I’ve got the blink sketch to work and have burned it at 8Mhz (with a corresponding change in blink rate).

    When I tried to verify the Interactive_to_Serial_with_Details sketch, I get an error “‘Serial’ was not declared in this scope”. Any thoughts on what I’m missing?

    Interactive_to_Serial_with_Details.pde: In function ‘void setup()’:
    Interactive_to_Serial_with_Details:138: error: ‘Serial’ was not declared in this scope
    Interactive_to_Serial_with_Details.pde: In function ‘void loop()’:
    Interactive_to_Serial_with_Details:169: error: ‘Serial’ was not declared in this scope
    Interactive_to_Serial_with_Details:232: error: ‘Serial’ was not declared in this scope
    Interactive_to_Serial_with_Details.pde: In function ‘void Serial_printP(const char*)’:
    Interactive_to_Serial_with_Details:244: error: ‘Serial’ was not declared in this scope
    Interactive_to_Serial_with_Details.pde: In function ‘void Serial_printPaddedIntger(long int, int)’:
    Interactive_to_Serial_with_Details:285: error: ‘Serial’ was not declared in this scope
    Interactive_to_Serial_with_Details:291: error: ‘Serial’ was not declared in this scope
    Interactive_to_Serial_with_Details:294: error: ‘Serial’ was not declared in this scope

  • Are you using the Arduino Tiny for 1.0 core from here? not the damellis/MIT one?

  • Bill

    I switched to your recommended core and am making progress. Thanks.
    Now, in my terminal window I see the text telling me to type x and the column headers but there is no additional text after sending the x’s.
    It appears that I’ve got it wired up correctly. I may try to download a different terminal emulator as a fix.

  • Nickson Yap

    I really appreciate this tutorial of yours!
    I bought the same components and did everything you said, it all works well!

    I have an attiny85v, the low power version of attiny85. It runs up to 10MHz and can run on 3.3V.
    So, I wonder if I need to change the code to work on 10MHz so that the Attiny85v runs on 3.3V?
    This is because I do not have to use two Zener diodes for the two data lines and only one Zener diode for the attiny85v.

    My attiny85v, 10MHz OSCCAL = 0xAC
    My attiny85, 20MHz OSCCAL = 0x8F
    Maybe the OSCCAL itself settles the problem?

    Thanks! :)

  • I don’t think you’ll have any luck running at 10MHz, the V-USB page says:

    Can be clocked with 12 MHz, 15 MHz, 16 MHz or 20 MHz crystal or from a 12.8 MHz or 16.5 MHz internal RC oscillator.

  • Nickson Yap

    Wow, thank you very much for the heads up! Really saved me a lot of time, instead of trying to get it to work on 10Mhz.

    Just now I tried using this attiny remote to do volume up and volume down, both of them does not work, but others like keyboard left and right works very well.
    page 56,
    It says Usage ID for volume up and volume down are 128 and 129 respectively. Is it that V-USB does not support up to that range?

    (I did some research just now and seems like in Windows, they require the USB device to be as an Audio HID.)


  • Think I’ve found the problem, nothing over code 101 was working due to a limitation in the UsbKeyboard library. With the following mod it is working on my Linux box now at least.

    Look in UsbKeyboard.h for the following:

    0x25, 0x65, // LOGICAL_MAXIMUM (101)

    and change to:

    0x25, 0xE7, // LOGICAL_MAXIMUM (231)

    and change:

    0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)


    0x29, 0xE7, // USAGE_MAXIMUM (Keyboard Application)

    You should be now be able to just use those codes in the sketch, eg.
    or add names for them in the UsbKeyboard.h file.

  • Nickson Yap

    Hello, sorry for responding late…
    I have tried changing the LOGICAL and USAGE MAXIMUM, and the result is still the same.
    I have also tried sending keystrokes:
    UsbKeyboard.sendKeyStroke(129); //volume down
    UsbKeyboard.sendKeyStroke(0xAE); //volume down
    UsbKeyboard.sendKeyStroke(0xEA); //volume down
    UsbKeyboard.sendKeyStroke(128); //volume up
    UsbKeyboard.sendKeyStroke(0xAF); //volume up
    UsbKeyboard.sendKeyStroke(0xE9); //volume up


    From what I have read on forums, the 129 and 128 keystroke seems to be working on Linux but not Windows.

    And also there seems to be a need to run the USB as Consumer Device or somekind of multimedia device (not normal keyboard) but I don’t know how to deal with those…

  • atomos

    Nickson Yap you need to create multimedia keyboard device for this purpose.
    I will make such a thing soon..

  • Nickson Yap

    Hi, atomos! How is your project going? I’d like to see your progress :)
    And also, if you don’t mind, make a tutorial on how to make it xD

  • ben

    Any idea if the usb interface could be made to work with an android phone (usb on the go) port?

    The reason I ask, I’ve seen many diy ir remotes for the android that’s connected to the audio jack, and has a 2 leds back-to-back for the ir transmission.

    One (possible) exception to that, are the commercial products out there… ex.

    They too plug into a standard 3.5mm jack, but the emitter side is so small on those that I don’t see how they would have 2 leds. How the heck are they able to generate the usual 38khz needed for most remote controls?

  • Moe

    Hi Nathan,

    Was there any reason you didn’t build off of Ken Shirriff’s IR library? It would be great if we code get the IR code maker, like SHARP, SONY, ect. It would seem that Ken’s library requires ISR and other decoding logic that would give the V-USB library issues. Did you find that to be the case?

    Also, I got your code working on my ATTiny85 but one thing isn’t correct (for Sharp remotes at least) is the return codes spit out by your decoder app. Example, the Sharp IR code for the number ’4′ button is:

    0×4082 (in binary: 100000010000010)

    But your decoder spits out:
    0xC082 (in binary: 1100000010000010)

    There seems to be an extra bit.
    Out of the box the decoder app didn’t work for my Sharp remote, I had to alter the following line otherwise I get the same repeated IR code regardless of what button on the Sharp remote I hit:

    #define RESOLUTION 2 // time between IR measurements

    #define RESOLUTION 1 // time between IR measurements

    What values would I supply the following if I wanted to capture IR pulses at 50 microsecond intervals?

    #define MAXPULSE 5000 // max IR pulse length, default 5 milliseconds
    #define NUMPULSES 100 // max IR pulse pairs to sample
    #define RESOLUTION 2 // time between IR measurements


  • Hi Moe,

    It’s been a while since I looked at this but I recall there was a lot that would need changing to get Ken’s library working on the ATtiny85 as it uses a lot of lower level and timing specific stuff so I went with this simpler solution which got me up and running quickly with the remotes I wanted to use.

    Not sure why the code differs but as long as it is consistent then it should still be usable?

    The MAXPULSE define is in microseconds so for 50 you should just change to:
    #define MAXPULSE to 50


Leave a Reply




You can use these HTML tags
(links will be automatically marked with rel="nofollow")

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Notify me of followup comments via e-mail. You can also subscribe without commenting.

By submitting a comment you agree that it can be reproduced under the same licensing terms as the rest of the content on this site.