Friday, December 4, 2015

Controlling Android device with an infrared remote

I came across several posts about microcontrollers decoding signals of infrared remote controllers and started to think, how an IR remote can be integrated with an Android smartphone. This post is about a simple use case when I control the media volume of the Android phone with an IR remote.

Long time ago, in a distant galaxy, IR transmitter-receiver was a standard feature of almost any mobile phone. Technological progress has eliminated that feature so we are now forced to build some hardware. We need infrared sensor for sure to capture the remote's infrared signal. But how can we connect that to the phone? Last year's experiments prompted me to choose Bluetooth Low Energy (BLE). Also, the microcontroller platform was determined by my experiences with RFDuino and I happened to have an RFDuino set in my drawer.

The idea is the following. An infrared receiver is connected to the microcontroller that captures and interprets the infrared signals. If the phone is connected to the BLE radio, then the key codes sent by the IR remote are then sent to the phone over BLE. The phone does whatever it wants with the key codes, in my example the volume up, down and mute buttons are handled and are used to influence the volume of the music played by the media player.

The infrared implementation is based on the excellent IRLib code. IRLib assumes that the IR receiver is connected to one pin of the microcontroller so I built the circuit below. A TSOP1738 IR receiver is directly connected to a GPIO pin of the RFDuino which is expected to do all the decoding. SV1 header is only for monitoring debug messages, it is not crucial for the operation of the gateway. If you intend to use it, connect an RS232C level converter similar to this one.



Then came the complications. "Arduino code" is used with great liberty on the internet, as if Arduino meant a single platform. But in reality, Arduino is a very thin layer on top of the underlying microcontroller. Most of the code, tools and articles out there are based on Atmel's extremely popular AVR family of MCUs. These are 8-bit CPUs, equipped with a host of peripherials. The challenger in this domain is not Intel but ARM's Cortex family. RFDuino is a Nordic Semiconductor's nRF51822 System-on-Chip (SoC) which is a Cortex-M0 core with integrated 2.4 GHz radio. BLE is supported by means of a software stack. ARM Cortex is not supported as well as AVRs by the Arduino community and this miniproject is a cautionary tale about this fact.

Click here to download the RFDuino project. In order to compile it, you need to install the Arduino IDE with the RFDuino support as described here.

For starter, the Arduino Makefile I used with such a great success previously does not support Cortex-based systems, only AVRs. A short explanation: I don't like IDEs, particularly not in a project where I would like to see exactly what goes into the compile/link process. Arduino IDE is nice for beginners but is a very limiting environment for more ambitious projects. After several days of heavy modifications, I adapted this makefile to the ARM Cortex tool chain of the RFDuino. Go into the irblegw/sketch directory, open the Makefile and look for the following line:

RFDUINO_BASE_DIR = /home/paller/.arduino15/packages/RFduino

Adapt this according to the layout of your file system. Then type "make" and the entire code should compile. "make upload" uploads the compiled code into the RFDuino, provided that the port (AVRDUDE_COM_OPTS =  /dev/ttyUSB0 in the Makefile) is also correct.

Then came further complications. The IRLib code is also AVR-specific. The differences between Cortex-M0 and AVR are mainly related to interrupt handling, on-chip counters and GPIO options. Polluting the original code with ARM-specific fragments seemed too confusing so I rather disabled the AVR-specific parts if the code is compiled on ARM architecture and implemented the ARM-specific functionality in Cortex-specific subclasses (in sketch/irblegw.ino). There are three implementations (like in the base library) IRrecvRFDuino (which uses 50 microsec periodic interrupt to sample the IR receiver input), IRrecvRFDuinoLoop (which is purely polling-based with no interrupt support) and IRrecvRFDuinoPCI (which sets up an interrupt to detect when GPIO02 changes state). This line determines, which one is used:

IRrecvRFDuinoPCI My_Receiver(RECV_PIN);

And now the biggest surprise. If BLE is not active, all the three implementations work. But if BLE is active, the software stack running behind the scenes on the RFDuino steals enough cycles so that IR reading becomes completely unreliable. The best result is provided by IRrecvRFDuinoPCI (which is activated in the download version) but even that version, after a significant loosening of the matching rules drops about 1 IR key out of 4. Well, folks, that's what I could achieve with this hardware and that's the second important take-away: SoCs with integrated communication stacks (this time BLE, but can be WiFi, GSM, whatever) are notoriously tricky if the sensor processing logic is time-critical.

Click here to download the Android code. To decrease download size, I packed only the files under the app/src/main directory of the Android Studio project.

The Android implementation is quite self-explanatory and is heavily related to this earlier RFDuino example program. This time I wanted to make sure that once the gateway is connected through BLE, the application can be sent to the background so I implemented the BLE connection logic in a service that significantly complicated the code. But after all this wizardry with Android services, the important part is here in IRGWService.java:

if( v == IR_KEY_VOLUME_PLUS ) {
     audioManager.adjustStreamVolume(  
                AudioManager.STREAM_MUSIC,
                AudioManager.ADJUST_RAISE,
                AudioManager.FLAG_SHOW_UI);

...

This is the fragment that maps IR remote keys to actions on the phone. The RFDuino-based gateway does not do any key mapping so it is the Android application that needs to know the meaning of the IR key codes. The following comes from the Philips remote I grabbed for these experiments.



    private static final long IR_KEY_VOLUME_PLUS = 0x20df12edL;

It is highly likely that you will have to change this value according to your remote's key code map. Just press the desired button and check the code.



This project was not a complete success as IR reading is not as reliable as it should be. But it is indeed fun to control the Android phone with an ordinary IR remote. I plan to improve the hardware a bit to make key recognition better so stay tuned (if you care about IR remotes).

Update: check out the follow-up blog posts (this and this).

No comments: