The Infrared Module was designed to be able to interact with the remotely controlled devices around us and has a sort of infamy amongst the team. It had a rough birth and an even rougher upbringing. Maybe one day I’ll be proud of what it becomes, but for now, it’s just the problem child.
Several months ago, the team had to make a really tough decision. We had initially designed the Ambient Module to be able to transmit and receive infrared (IR) light as well as detect ambient light and sound. This presented two problems: our design was not very modular (infrared communication and ambient sensing are two very different needs) and it was spreading our hardware thin. By jamming all that functionality on to one module, we would have to perform some tricky software engineering feats to make it work, thus increasing the chance we would have bug-ridden firmware and potentially longer development time.
After several heavy discussions, we decided to split the Ambient Module into two: an Ambient Module for light and sound, and a dedicated Infrared Module, at the risk of not shipping on time.
This decision committed us to one more module to design, code, manufacture, test, and ship– which, at 14 modules, was starting to reach the capacity of our small team’s brainpower. It was especially upsetting because infrared is not likely to be a popular module and it was taking up more of our time than modules with higher expectations like Bluetooth Low Energy. But in the long term, by splitting the modules, we’ll have two stable modules instead of one (potentially) flaky one and we’re staying true to our mission of making each module with only one particular task that is done really well.
By December, we were already a little grouchy about the Infrared Module because of time spent discussing its separation from the Ambient Module. I was tasked with writing the code for it and I hoped to complete it as efficiently as possible.
Whoever designed the Infrared protocol made it rather complicated in an effort to avoid detectors receiving false positives from incoming sunlight (the sun also emits in the infrared band of light). At its simplest, the infrared protocol is turning on and off a modulated signal (see image below) for various amounts of time. An on is known as a “mark” and an off is known as a “gap”.
But of course, it’s not quite that simple. Every manufacturer (Phillips, Sony, Sharp, etc.) has their own definition for an on/off duration that constitutes a one or a zero. For example, Sharp defines a binary “1” as 315 microseconds of a mark followed by 1779 microseconds of a gap.
To add to the complexity of all the minor timing tweaks in the protocol from all these different manufacturers, the signal can also be modulated at different frequencies. During a mark, it is actually modulating (switching between on and off) thousands of times a second. The most popular modulation frequency is 38 kHz (36 and 40 kHz are also quite common).
Our IR module has its own tiny microprocessor, the attiny84, which is responsible for offloading all the infrared transmission logic from Tessel. The Tessel simply passes a modulation frequency and array of marks and gaps, and the microcontroller takes care of the managing the intricate timing required to transmit the signal (thus freeing up Tessel for more important things).
Fortunately, Arduino has a great IR library written by Ken Shirriff that I was able to adapt for the module pretty quickly. However, testing out my implementation was tricky. The only IR activated device I had in my apartment was an old Sharp television. I needed to find the code that could be converted to its underlying binary representation of 1’s and 0’s, which in turn could be translated into the right on/off durations in order to turn on the TV. But where does one find IR codes? After a bit of Googling, I found a database of IR codes for each manufacturer.
To my dismay, Sharp has dozens of different codes for different devices, and I couldn’t find a model number anywhere on my TV. The only identifier I could find was an obscure, white code I located on the bottom of the TV remote. I Googled that alphanumeric code, which took me to an Ebay auction item for the same remote, which happened to list a bunch of TV model codes that the remote works with. I found a fairly similar model code (“G1059J is pretty close to G1059SA, right?”) on the infrared code database, and found a power code of “0x00000000000041A2”. I ran the number through the Arduino library and it produced an array of on/off durations (negative numbers are gaps):
[4011, -3875, 561, -1906, 561, -1906, 561, -1906, 561, -1906, 561, -921, 561, -921, 561, -1906, 561, -921, 561, -1906, 561, -921, 561, -1906, 561, -921, 561, -921, 561, -921, 561, -921, 561, -921, 561, -1906, 561, -921, 561, -1906, 561, -921, 561, -921, 561, -921, 561]
I hardcoded the array into my module firmware and stood in front of the TV. I don’t think I’d ever felt more pessimistic about a test. I pointed the module at the TV, powered it up to send the signal and… my TV turned on! I really couldn’t believe that it worked. I stood there 5 minutes, rebooting the module over and over again, giggling as my TV turned on and off.
[Warning: Things get [more] Technical]
The happiness I felt about that success would soon be outweighed by a devious little bug that infested my code for days. After building out more of the firmware that allowed Tessel to send over the IR data to transmit, I stumbled across an issue when I sent arrays larger than 96 bytes. If I read the array back after sending, it started writing back inconsistent bytes after the 96th. It was always 96. My first thought was that I was out of RAM. I was using the attiny44, which had 256 bytes of RAM, but I was only using up 240 after compilation. Then I thought maybe it could have been an issue with the transmission from Tessel to the module (over SPI): Was I sending data too fast? Was I sending it in the wrong format? Was the interrupt handler infinitely recursing and causing the module to crash? I tested these theories out by saving the byte I was sending over to a temporary variable instead of my buffer and echoing it back in the same interrupt - there were no issues.
Finally, Kevin was nice enough to put his work aside and give me a hand because he’s worked with the AVR microcontroller family extensively. Kevin was first alerted by how close I was to using up all the RAM with my transmit buffer (240/256 bytes). After poking around in the assembly code, he noticed that when the chip select line for SPI (which tells the microcontroller that data is about to come down the pipe) is pulled low and the corresponding interrupt is called, 19 different registers are pushed onto the stack. The stack grows down from the top of RAM so it was actually overwriting down into my buffer, causing the data to be corrupt (256 - 19 = 237 which overwrites bytes 97-100 of my buffer). To make the fix, we simply upgraded from the attiny44 to the attiny84, which boasts twice as much RAM.
Now, I’m off to writing the test bench for the module so hopefully we can ship it out this week and I can wash my hands off this module forever knock on wood.