Introduction: Digispark IR Receiver Keyboard

In this project we are gonna build an IR receiver that will act as a keyboard (and mouse!) and will be controlled by any IR remote.
I got this idea when I wanted to control my HTPC connected to the TV remotely with the same controller that is used for the TV.
You can buy a dedicated IR receiver, but these are quite expensive and you cannot customize them that much.

With this approach, you can:

  • Use almost any remote controller you have lying around
  • Connect the device to any computer with USB, without a need for additional drivers (HID device)
  • Create macros
  • Map any key from the remote you want to any key you want on the PC
  • Anything that you can think of! (and can code it)

You need to have basic knowledge about Arduino and electronics. Some easy soldering is also involved.

Supplies

  • Digispark attiny85, preferably MicroUSB version (you can get clones of these for around 1.5$)
  • Any IR receiver diode, i.e. TL1838 (can be salvaged from any appliance controlled by IR, or bought in a pack for around 10 cents each)

Step 1: Getting Digispark to Work

If you never used digispark before, take a look at this page and follow the instructions: https://digistump.com/wiki/digispark/tutorials/con...

Your digispark may seem stuck in a boot loop, until you upload your first sketch to it. That is normal. Upload your first sketch from the tutorial to fix it. To be able to act as a standalone USB device, digispark awaits flashing a sketch only around 3 seconds upon connecting, so every time you want to flash it you have to reconnect it.

After your LED on the digispark starts blinking as advertised, move to the next step.

Step 2: Soldering the IR Receiver

Check the datasheet for your IR receiver for pinout or find out using a multimeter (tutorials online). Most of these IR receivers are wired like in the picture, but some can be wired differently and you don't want to blow your receiver/digispark by connecting it wrong. The middle pin is usually ground, but the other two can be switched on different products.

After finding out the pinout for you receiver, solder it to the digispark like this (IR -> digispark):

  • GND -> GND
  • VCC -> 5V
  • OUT -> P2

Double-check that the wires are not touching, that could lead to permanent damage to the digispark.

Step 3: Preparing the Libraries

Install the TrinketHidCombo library. You can download it from here: https://github.com/adafruit/Adafruit-Trinket-USB .

We don't need any other library from this repository, so you can extract only the TrinketHidCombo folder from the downloaded zip to your arduino folder in documents.

This library will be used to let the digispark simulate the keyboard/mouse. Digispark sadly cannot send/receive data over serial the way like Arduino does, so we will be debugging by printing the information like a keyboard to your PC.

Step 4: Uploading Code

Copy and paste this code to your IDE. The code was remixed from dimax'es code from this forum: http://arduino.ru/forum/proekty/ik-distantsionnoe-.... Upload the sketch to your digispark. If you have done previous steps correctly, the upload should be successful. If not, check that you have installed the TrinketHidCombo library correctly.

// **** TSOP is connected to port PB2 **** //////
#define MOUSE_SENSITIVITY 1
#define REPEAT_DELAY 220

//Set to 0 after finding your codes
#define DEBUG 1

//Specify your remote codes here:
#define REMOTE_OK 0x4BF80010
#define REMOTE_LEFT 0x4BF8004A
#define REMOTE_RIGHT 0x4BF800CA
#define REMOTE_DOWN 0x4BF8008A
#define REMOTE_UP 0x4BF8000A
#define REMOTE_MOUSE_SWITCH 0x4B98E11E
#define REMOTE_POWER 0x4B9821DE
#define REMOTE_VOL_UP 0x4B9841BE
#define REMOTE_VOL_DOWN 0x4B98C13E
#define REMOTE_RETURN 0x4BF80090
#define REMOTE_PLAYPAUSE 0x4BF800D8
#define REMOTE_NEXT 0x4BF800B8
#define REMOTE_PREV 0x4BF80078


volatile uint8_t m = 0, tcnt = 0, startflag = 0;
uint32_t irdata = 0, keydata = 0 ;
bool mouse = false;
bool pressed = false;
bool complete = false;
int lastMouseX=0, lastMouseY=0;

#include <avr/delay.h>
#include "TrinketHidCombo.h"


void setup() {
  //delay(30000);
  DDRB |= (1 << DDB1); //P1 (LED) OUT not used in sketch
  PORTB |= 1 << PB2; // a PB2 lift will not hurt.
  GIMSK |= 1 << INT0; //interrupt int0 enable
  MCUCR |= 1 << ISC00; //Any logical change on INT0 generates an interrupt request
  GTCCR |= 1 << PSR0; TCCR0A = 0;
  TCCR0B = (1 << CS02) | (1 << CS00); // divider /1024
  TIMSK = 1 << TOIE0; //interrupt Timer/Counter1 Overflow  enable
  TrinketHidCombo.begin(); // start the USB device engine and enumerate
}

void loop() {

  if (complete) { // if a code has been received

    if (keydata != 0) //if a code is new
    {
      Action(keydata);
      pressed = true;
    }
    else if(mouse)//Make mouse movements accelerate
      {
        lastMouseX*=2;
        if(lastMouseX>64)
          lastMouseX=64;
        else if(lastMouseX<-64)
          lastMouseX=-64;  
        lastMouseY*=2;
        if(lastMouseY>64)
          lastMouseY=64;
        else if(lastMouseY<-64)
          lastMouseY=-64; 
      }
       
      TrinketHidCombo.mouseMove(lastMouseX, lastMouseY, 0);

    complete = false;
    ms_delay(REPEAT_DELAY);// to balance repeating/input delay of the remote

  }
  else if (pressed)
  {
    digitalWrite(1, LOW);
    if (mouse)
      TrinketHidCombo.mouseMove(0, 0, 0);
    else
      TrinketHidCombo.pressKey(0, 0);
    pressed = false;
  }
  else
  {
    _delay_ms(1);//restrain USB polling on empty cycles
    TrinketHidCombo.poll(); // check if USB needs anything done
  }

}

ISR (INT0_vect) {
  if (PINB & 1 << 2) { // If log1
    TCNT0 = 0;
  }
  else {
    tcnt = TCNT0; // If log0
    if (startflag) {
      if (30 > tcnt  && tcnt > 2) {
        if (tcnt > 15 && m < 32) {
          irdata |= (2147483648 >> m);
        }
        m++;
      }
    }
    else  startflag = 1;
  }
}
ISR (TIMER0_OVF_vect) {
  if (m)
    complete = true; m = 0; startflag = 0; keydata = irdata; irdata = 0; // if the index is not 0, then create an end flag
}

void ms_delay(uint16_t x) // USB polling delay function
{
  for (uint16_t m = 0; m < (x / 10); m++) {
    _delay_ms(10);
    TrinketHidCombo.poll();
  }
}

void Action(uint32_t keycode)
{
  switch (keycode)
  {
    case REMOTE_OK:
      if (mouse)
      {
         lastMouseX=0;
         lastMouseY=0;
         TrinketHidCombo.mouseMove(lastMouseX, lastMouseY, MOUSEBTN_LEFT_MASK);
      }
      else
        TrinketHidCombo.pressKey(0, KEYCODE_ENTER);
      break;

    case REMOTE_LEFT:
      if (mouse)
      {
        lastMouseX=-MOUSE_SENSITIVITY;
        lastMouseY=0;
        TrinketHidCombo.mouseMove(lastMouseX, lastMouseY, 0);
      }
        
      else
        TrinketHidCombo.pressKey(0, KEYCODE_ARROW_LEFT);
      break;

    case REMOTE_RIGHT:
      if (mouse)
        {
          lastMouseX=MOUSE_SENSITIVITY;
          lastMouseY=0;
          TrinketHidCombo.mouseMove(lastMouseX, lastMouseY, 0);
        }
      else
        TrinketHidCombo.pressKey(0, KEYCODE_ARROW_RIGHT);
      break;
    case REMOTE_DOWN:
      if (mouse)
        {
          lastMouseX=0;
          lastMouseY=MOUSE_SENSITIVITY;
          TrinketHidCombo.mouseMove(lastMouseX, lastMouseY, 0);
        }
      else
        TrinketHidCombo.pressKey(0, KEYCODE_ARROW_DOWN);
      break;
    case REMOTE_UP:
      if (mouse)
        {
          lastMouseX=0;
          lastMouseY=-MOUSE_SENSITIVITY;
          TrinketHidCombo.mouseMove(lastMouseX, lastMouseY, 0);
        }
      else
        TrinketHidCombo.pressKey(0, KEYCODE_ARROW_UP);
      break;
    case REMOTE_POWER:
      TrinketHidCombo.pressSystemCtrlKey(SYSCTRLKEY_POWER);
      break;
    case REMOTE_RETURN:
      TrinketHidCombo.pressKey(0, KEYCODE_BACKSPACE);
      break;
    case REMOTE_MOUSE_SWITCH:
      mouse = !mouse;
      lastMouseX=0;
      lastMouseY=0;
      break;
    case REMOTE_VOL_UP:
      TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_UP);
      break;
    case REMOTE_VOL_DOWN:
      TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_DOWN);
      break;
    case REMOTE_PREV:
      TrinketHidCombo.pressMultimediaKey(MMKEY_SCAN_PREV_TRACK);
      break;
    case REMOTE_NEXT:
      TrinketHidCombo.pressMultimediaKey(MMKEY_SCAN_NEXT_TRACK);
      break;
    case REMOTE_PLAYPAUSE:
      TrinketHidCombo.pressMultimediaKey(MMKEY_PLAYPAUSE);
      break;


    default:
      if(DEBUG)
        TrinketHidCombo.println(keydata, HEX);
      else
        return;
      
  }
  digitalWrite(1, HIGH);
}

Step 5: Finding Your Remote's Codes

Open notepad or any other text editor, and check that you have your keyboard layout set to US.

Plug in the digispark and try to press something on your controller while aiming at the IR receiver. If you see a hexadecimal code, you are in luck! You should see the same code every time you press the same button. If not, try to aim at the receiver more accurately. Write down all the codes for your remote this way.

If you see the same code for all buttons, that remote is not compatible with this project. If you insist on using that remote, you must look elsewhere for a similiar project.

If you don't see anything, check your wiring or try a different remote. I tested it only with NEC remotes so I cannot say if other remotes are compatible.

Step 6: Enter Your Codes to the Sketch

After you have finished loading codes from your remote, set the DEBUG to 0 at the beginning of the sketch and write the codes for your remote below to the predefined fields. If you want more functionality, you can always add new entries, but you must also add their corresponding keyboard actions below in the Action() function.

See this for all available keyboard actions: https://github.com/adafruit/Adafruit-Trinket-USB/b...

If you want to use this as a remote for your media center app, check its keyboard actions, for example Kodi has these: https://github.com/xbmc/xbmc/blob/master/system/ke...

One problem I faced when used with some PCs is that the digispark does not work after a cold start of the computer. That can be solved by delaying the USB enumeration in setup() (uncomment the line of code).

Step 7: Put It in a Case (optional)

If you have a 3D printer, you can print this little enclosure that I designed. Otherwise you can make an enclosure from some small box that you have lying around. It is a good idea not only for looks, but you also protect the electronics from shorting out.

Step 8: Done!

Your USB IR keyboard receiver is good to go!

If you want to add another remote or extend its functionality, you can always edit the sketch and upload it again.

If you liked this project or want to add something, give your thoughts below to the comments. I would be glad to hear that it worked, since this is my first instructable. Thanks!