Wednesday, December 30, 2015

Infrared-to-Android gateway implementation with interrupts

In the previous parts of this series the infrared-to-Android gateway based on the RFDuino hardware and an improved version of the hardware were presented. The improved hardware offered quite reliable IR code recognition even when the BLE connection was in operation. Trouble with that implementation was the polling nature of the code; even though the IR reader support hardware is capable of raising an interrupt when a new time measurement is available, the code did not handle that interrupt, instead it polled the interrupt signal.

Click here for the updated gateway code. Click here for the (unchanged) Android application serving the gateway.

The reason I did not implement proper interrupt handling was the I2C library (called "Wire") coming with the RFDuino. Even though the nRF51822 (on which the RFDuino is based) is able to handle its I2C interface (called TWI, two-wire interface) by means of interrupts, it was not implemented in the "Wire" library. When the MCP23008 GPIO port extender raised an interrupt, the MCU was expected to read the MCP23008's capture register by means of an I2C transaction. As the "Wire" library was polling-based, this transaction held back the GPIO-handling interrupt for too long time, freezing the system.

The solution was transforming the "Wire" library into interrupt-based implementation. Now as my goal was a limited functionality (reading one register of an I2C periphery) I did not do it properly. I moved the entire "Wire" library into the application project (see it under the "lib" directory), renamed it to "Wire2" and introduced a couple of new methods, more importantly sendReceiveInt (in lib/Wire2.cpp). This method initiates the write transaction of a data array followed by a read transaction of another data array over I2C, all handled by TWI interrupts. This means that sendReceiveInt returns immediately and the actual data transfer happens in the background. This new method is invoked in the GPIO interrupt handler (GPIOTE_Interrupt in sketch/irblegw3.ino) but this time the GPIO interrupt handler completes very quickly as its job is only to initiate the TWI transaction handler. When the TWI transaction is finished, the TWI interrupt handler invokes the onReceive callback that ends in the application code (twi_complete in sketch/irblegw3.ino).

The most important outcome of this - quite significant - change is that the MCU does not spend its time spinning on the GPIO port reading loop. Instead, it waits for interrupts in ultra-low power mode (sketch/irblegw3.ino, IRrecvRFDuinoPCI::GetResults method, RFduino_ULPDelay invocation) which is important if the infrared-to-Android gateway is powered by a battery. As you may have guessed, my goal is not to fiddle with IR remote controllers, I intend to build a short-range network comprising of infrared, BLE and cellular links and the infrared-to-BLE gateway was just one step.

Wednesday, December 16, 2015

Improved hardware for the infrared-to-Android gateway

In the previous post I presented the results of my experiments with the nRF51822-based RFDuino as infrared-to-Bluetooth Low Energy gateway, accompanied by an Android client app. The outcome of that experiment was that the nRF51822 BLE soft stack and the purely software-based IR receiver is not a good match as the BLE soft stack "steals" enough cycles from the Cortex M0 CPU so that the IR reading becomes very unreliable no matter which implementation alternative we go for (3 different alternatives were attempted). I promised an improved hardware that overcomes this limitation and this post is about this improved hardware.

The essence of our problems with the BLE soft stack was that in case of very tight timings that the IR receiver requires, the main CPU is not suitable anymore for measuring time periods. Typical IR timing is in the 500 microsec-2 millisec range, this is the time period we should be able to measure reliably. With the BLE soft stack in operation, delays are introduced into the time measurement code by the background interrupt routine serving the soft stack and time measurement of this precision will be wildly off. I considered two options.

  • The most evident option is to drop the integrated MCU-BLE radio combo that the nRF51822 is and go for a separate MCU-BLE modem option. For example an Arduino Pro Mini with a BLE121LR modem would have been a perfect fit as there are both of these modules in my drawer. While this hardware would have been definitely more hassle-free than the nRF51822, setting it up would have required two different programming tools (I have both but that's not necessarily true for the general blog reader out there) and I am still uneasy about soldering the BLE121LR - those pads are smaller than my capabilities.
  • Extending the RFDuino with a dedicated hardware for time period measurement sounded more attractive for me as this was less evident. The functionality we expect is that the MCU is not doing any time measurement. The external hardware must be capable of measuring the time periods between the edges of the TSOP1738 output signal and deliver these measurements to the MCU. The measuring range is about 500 microsec-2 msec. Larger time periods are still measured by the MCU but in this case the disturbances caused by the soft stack are not that relevant.
Further complication is that while the nRF51822 has 31 general-purpose pins, RFDuino makes only 7 of those available and 2 of them are reserved for USB communication. This requires that the circuit is interfaced with the RFDuino with the lowest number of wires and I2C is the best option (2 wires). nRF51822 has I2C option (called TWI, two-wire interface) so this would work. I was not able to find a single-chip solution that measures and captures time periods in this time range with I2C interface but the circuit is not that complicated.

A 74HC4060 is set up as oscillator and counter. The frequency of the oscillator is about 350 kHz, yielding about 43 microsec time resolution for one counter step, making it convenient to measure between 43 microsec and 5.5 sec with 7-bit resolution. An MCP23008 GPIO-extender with I2C interface provides the conversion to I2C two-wire connection and also has a capture logic. This means that whenever the output level of the IR receiver changes, the MCP23008 stores the current value of the counter in its capture register and raises an interrupt. This way the MCU is not doing any time measurements and the time measurement hardware is able to survive about 1 msec autonomously without service from the MCU.

Click here to download the schematic in Eagle SCH format.

Click here to download the updated RFDuino gateway sources.

As with the previous version, edit the Makefile in the irblegw2/sketch directory and update the RFDUINO_BASE_DIR and the AVRDUDE_COM_OPTS variables according to the layout of the file system and the USB port mapping of the RFDuino USB shield. Then you can simply say "make upload" and the gateway is installed into the RFDuino. The Android client code and its usage is unchanged, check it out in the previous post.

The gateway code represents a step in the right direction. The I2C support library ("Wire") coming with the RFDuino gets stuck when invoked from interrupt handler so this time the MCP23008 interrupt signal is handled by means of polling. The result is that when BLE is active, occasionally there are still lost IR codes even though the quality of the recognition has improved considerably.  I intend to turn the I2C access interrupt-driven but that requires going deep into the nRF51822 that I was not yet able to accomplish in this iteration.

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

if( v == IR_KEY_VOLUME_PLUS ) {


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).