HiFiBerryOS: Apple IR Remote and Rotary Encoder with Arduino

Hello all. I thought I'd share that I have successfully gotten an Apple IR remote and rotary encoder to work with the HifiberryOS using an Arduino Micro. It's a fairly cheap project to boot and you can actually use just about any remote you'd like (easy to program into the code). I'm using the +DSP board.

You can increase, decrease volume with encoder or remote using up/down buttons on remote. Left/right are for prev/next. The Play/Pause button does Play/Pause. The playback keys have only worked with AirPlay for me. I did not program the center button.

I will be using the MENU button in a later project to switch sources between wireless (wifi/bluetooth), optical, and RCA. In order for the optical/RCA to work, I will have to 1) convert RCA to digital, 2) connect the new digital RCA signal and the optical to a switch, and 3) use the arduino to turn on/off the led for the optical connection. It must be done this way on the DSP board since there is no analog input and board automatically switches to optical once an optical connection is made. The arduino will power off the transmitting optical LED to effectively "disconnect" the optical input again in order to switch back over to wireless (bluetooth/WiFi). Note that this is not yet in the coding.

Components needed: 
Arduino Micro (includes native USB drivers as board emulates a keyboard)
IR receiver (I used TSOP4838, but any 38kHz receiver works)
A rotary encoder (you can use the HW-040, but I went with EN11-VSM0AF20. It has a better feel in my opinion with softer detents, but you have to do some wiring and need some 10kohm resistors)

LED to show signal received along with current limiting resistor

Here's the arduino code (note the .h libraries that are needed; note the connections are A0, A1, A2, A3, pin 16, 5V, and GND):

#include <ClickEncoder.h>
#include <TimerOne.h>
#include <HID-Project.h>
#include <IRremote.h>

#define ENCODER_CLK A0
#define ENCODER_DT A1
#define ENCODER_SW A2
#define LED A3

unsigned long lastCode;

int voldelay = 50;
int keyboarddelay = 800;
int RECV_PIN = 16;

IRrecv irrecv(RECV_PIN);
decode_results results;

ClickEncoder *encoder; // variable representing the rotary encoder
int16_t last, value; // variables for current and last rotation value

void timerIsr() {

void setup() {
Serial.begin(9600); // Opens the serial connection used for communication with the PC.
Consumer.begin(); // Initializes the media keyboard
Keyboard.begin(); // Initializes keyboard
encoder = new ClickEncoder(ENCODER_DT, ENCODER_CLK, ENCODER_SW); // Initializes the rotary encoder with the mentioned pins

Timer1.initialize(1000); // Initializes the timer, which the rotary encoder uses to detect rotation
pinMode(LED, OUTPUT);
last = -1;
irrecv.enableIRIn(); // Start the receiver

void loop() {
value += encoder->getValue();

// This part of the code is responsible for the actions when you rotate the encoder
if (value != last) { // New value is different than the last one, that means to encoder was rotated
if(last<value) // Detecting the direction of rotation
Consumer.write(MEDIA_VOLUME_UP); // Replace this line to have a different function when rotating counter-clockwise
Consumer.write(MEDIA_VOLUME_DOWN); // Replace this line to have a different function when rotating clockwise
last = value; // Refreshing the "last" varible for the next loop with the current value
Serial.print("Encoder Value: "); // Text output of the rotation value used manily for debugging (open Tools - Serial Monitor to see it)

// This next part handles the rotary encoder BUTTON
ClickEncoder::Button b = encoder->getButton(); // Asking the button for it's current state
if (b != ClickEncoder::Open) { // If the button is unpressed, we'll skip to the end of this if block
//Serial.print("Button: ");
//#define VERBOSECASE(label) case label: Serial.println(#label); break;
switch (b) {
case ClickEncoder::Clicked: // Button was clicked once
Consumer.write(MEDIA_VOLUME_UP); // Replace this line to have a different function when clicking button once

case ClickEncoder::DoubleClicked: // Button was double clicked
Consumer.write(MEDIA_VOLUME_DOWN); // Replace this line to have a different function when double-clicking
if (irrecv.decode(&results))
digitalWrite(LED, HIGH);
digitalWrite(LED, LOW);
if(results.value != 0xFFFFFFFF){
lastCode = results.value;
//Sony RMX231 Remote
case 2011287610: Consumer.write(MEDIA_VOLUME_UP); //VOL UP key
case 2011279418: Consumer.write(MEDIA_VOLUME_DOWN); //VOL DOWN Key
case 2011291706: Keyboard.write(KEY_RIGHT_ARROW); //Right Arrow, Next
case 2011238458: Keyboard.write(KEY_LEFT_ARROW); //Left Arrow, Prev
case 2011242554: Keyboard.write(KEY_ENTER); //Enter key
case 2011265594: Keyboard.write(KEY_ENTER); //Enter key, Dupilcate because my remote is dumb
irrecv.resume(); // Receive the next value

delay(100); // Wait 10 milliseconds, we definitely don't need to detect the rotary encoder any faster than that
// The end of the loop() function, it will start again from the beginning (the beginning of the loop function, not the whole document)




Please sign in to leave a comment.