The original design of my DAC provided an interface for a single pushbutton or a remote logic signal to advance the currently selected channel. While this worked at first relying on an external signal from another device using sensitive logic gave rise to spurious triggering of the signal. The solution was to create a local remote control interface for the DAC.
This very boring schematic is nothing more than an Atmel AVR microcontroller connected to an infrared receiver and to the SEL[0..1] pins of the receiver in the DAC. The receiver is a TSOP1136 and is purpose built for remote controls which modulate their signal at 36KHz which covers most remotes. The microcontroller chosen was an ATMEGA88, it was chosen mainly for it’s cost and 16bit timer with input capture interrupts (why will become apparent later).
Remotes transmit data using IR light with a ~36kHz carrier. The data is typically encoded in one of two formats, RC-5 a bi-phase based protocol, or NEC a mark-space based protocol. The remote I was working with uses the NEC protocol.
The mark-space encoding method defines bits using the following timing:
- Logical ‘0’ – a 563µs pulse burst followed by a 563µs space, with a total transmit time of 1.125ms
- Logical ‘1’ – a 562µs pulse burst followed by a 1.69ms space, with a total transmit time of 2.25ms
Data transmissions are broken up into two different transmission types, data codes and repeat codes. On a button press the relevant data is sent. If the button is held down a repeat code is sent every 108ms until the button is released
A data transmission looks like this:
- A 9ms leading pulse
- A 4.5ms space
- The 8bit address for the receiving device
- The logical inverse of the 8bit address for error checking.
- The 8bit command code for the button press.
- The logical inverse of the 8bit command for error checking.
- A 563μs pulse to indicate the end of the transmission
A repeat code also starts with a 9ms leading pulse however is only followed by a 2.25ms space and it is repeated every 108ms while the remote button is held down.
To decode the above signal we need to count the time between pulses, this drove the choice of microcontroller. The ATMEGA88 has a 16bit timer with an input capture interrupt. The input capture will trigger an interrupt routine and save the exact value of the timer at the time it was triggered. By comparing the captured value to the value saved at the previous capture we can calculate the duration of each mark-space pair. Additionally the 16bit timer running at clk/64 or 125kHz provides enough resolution to accurately measure the microseconds between data bits as well as the 108ms between repeat pulses without overflowing.
Decoding the data is achieved via a state machine with four states, IDLE, START, DATA, and ERR.
During the IDLE state Timer1 is inactive and the microcontroller is in sleep mode awaiting an external interrupt to trigger. When an external interrupt is triggered Timer1 is activated, the external interrupt is disabled, and input capture interrupts are enabled. The remainder of the decoding is done during the input capture interrupt service routine.
At the first trigger of input capture while we’re in the START state the input capture register will hold the length of the leading pulse and space. If the leading pulse and space is less than 12ms long we’ve registered a repeat bit. No further data is expected and we revert back to the IDLE state. The main program code will then action the previously recorded command.
If the leading pulse is longer than 12ms then we’ve recorded the start pulse and advanced our state to DATA.
In this state we compare the current input capture timer to its previous value which gives us a current mark-space length. If that length is greater than 1.5ms we record a logic 1, otherwise we record a logic 0. These bits are left shifted into a 32bit register which holds the recorded data. Also a counter is increased to indicate how many bits we have captured. This repeats for each bit recorded.
Once this counter reaches 32 we assume that all data has been received so we divide the data into 4 separate chunks, the command and address, and the logical inverse of the command and address. Error checking the result is as simple as performing a bitwise XOR on the data with its logical inverse. If the result is not 0 then we have failed to record the data correctly. After correctly receiving data we end up back in the IDLE state.
This state is for catching errors. It resets all values and counters and drops the microcontroller back into the IDLE state awaiting the next remote signal. Each of the other states perform some error checking along the way. The following conditions can put us into this state:
- During STATE_START the recorded pulse is longer than 15ms. No pulse should ever exceed the 13.5ms length of the starting pulse.
- During STATE_DATA the recorded pulse is longer than 3ms. No data pulse should be longer than the logic 1 pulse.
- The counter reaches 80ms. The entire transmission should occur within 67.5ms. The space between repeat bits are not timed so if we reach the 80ms mark chances are we’ve missed a bit along the line and we’re waiting for data that won’t arrive.
- XOR error checking fails. The ERR state is just a convenient way to bring everything back to a known state.
Results were as expected. The remote receiver was connected to a computer serial port to dump the received command and address signals. The button we were trying to capture was then hardcoded into the software ignoring repeats. Now I can use a button on my hifi remote to change the DAC channel.