In this series we will analyze and discuss the inner workings of an infrared AC remote. These little devices are as common as well-known, so I will obviate most of the stuff which can be found online (but provide useful links for the curious reader :p).
0x0: target
The remote has several buttons and a simple LCD screen showing the current settings.
By pressing 'mode' it switches between four different modes, attending to the icon (or lack of it) displayed on the screen:
- Snow flake
- Arrow-like rounded A
- Water drop
- Blank
There are also two icons representing the fan status, and of course, the current temperature setting.
The first thing to do would be looking up in the user guide for what that tiny icons do, but we may be anxious to capture some signals and (luckily) decode some bits out of them so maybe later :p
Anyway, the buttons are pretty self-explaining except for the 'turbo' one which seems to switch to some preset state, displaying 16º and full fan speed on display (annoyingly overriding any previous setting).
Almost every one of these type of remotes usually send one full packet every time a button gets released (this one even waits for some time to actually send the packet if you repeatedly press quickly enough) containing the current state which the AC unit should be on.
0x1: playset
I used a handy DS1054Z hooked up to and a very simple circuit described above to acquire the signal.
0x1[0] Part/tool list
- 1 x breadboard
- 1x tsop382 infrared sensor (datasheet)
- 6 x jumper cables
- 1 x 3v3 power source
- 1 x oscilloscope
Pins 2 and 3 are connected to GND and 3v3 respectively, and pin 1 is connected to DS1054Z's channel one. In my setup I'm using an Arduino just to power the receiver.
0x1[1] a little bit of theory
As Giorgos Lazaridis writes on Remote Control Basics:
It is important to understand the difference between modulation and protocol. In words, modulation is the method that the signal is being transmitted by the transmitter so that the receiver will recognize it as a valid signal. On the other hand, the protocol is the method that the data to be transmitted are arranged and sent to the receiver - Both the receiver and transmitter must have the same modulation-demodulation method and also they must have the same protocol encoding and decoding method.
[...]
Here is how this works: First of all we select a frequency for the carrier frequency such that it cannot be mistaken with other ambient IR frequencies. A very common range for IR transmitters is 36 to 46 KHz. Suppose that we choose a carrier frequency of 38 KHz.
Suppose now that we want to transmit a pulse with 8 mSec ON duration and 2 mSec OFF duration (digital ACE). Instead of simply turning ON the LED for 8 mSec, we will set the LED to oscillate at the carrier frequency for 8 mSec. This way, instead of transmitting a continuous light beam, we transmit a series of very fast light pulses (38 KHz) for a duration of 8 mSec. The receiver has a narrow band filter that discards all other IR light sources that do not have a 38 KHz frequency. If the transmitted signal reaches the receiver, it will go through the narrow-band filter. Then, with a set of low-pass filters or other filtering devices, it will be translated into a pulse back again.
Luckily for us, it turns out that the tsop382 filters and demodulates the signal for us, so we are left with bit decoding and protocol reversing. Nice!
As the tsop382 datasheet states:
These products are miniaturized IR receiver modules for infrared remote control systems. A PIN diode and a preamplifier are assembled on a leadframe, the epoxy package contains an IR filter. The demodulated output signal can be directly connected to a microprocessor for decoding.
So now it's time to power the scope and start pressing buttons, adjusting time and voltage settings to try and get a visual image of what's going on the remote.
0x2: acquiring the signal
Once we visualize our signal in 'live' mode, we have to capture as many signals as we can to get an idea of what's being sent. It's a pretty tedious (and slow!) job: set SINGLE mode, press button, export, set SINGLE mode again, press another button, repeat.
There are several projects over the Internet looking to speed things up a little; this one was particularly convenient to grab the displayed signal voltage values in a nice csv over the network (much quicker than using a USB drive to transfer them!). I also used it to take the PNG screenshots of the scope for this post ^^.
The process I followed was basically to capture ON/OFF packets without changing any setting (21º max fan snowflake) and then increasing the temperature one step each capture. I didn't knew anything about the protocol, not did I care, yet, but it seemed logical that only a few bits of the sequence would change on one-step-changes (unless I were trying to reverse-engineer a cheap AC control featuring Military Grade© encryption).
Note: In my first attempt to transfer the signal values to my PC I did realize that every capture was around 20MB, so I lowered the sample rate to 500kS/s.
0x3: from signal to actual bits
If we take a closer look to the signal we can appreciate a long low pulse first and long and short high ones after, conforming the rest of the packet. So the long low must be the preamble:
Then we have the long high:
Finally the short high:
With the help of the built-in cursor we can measure the actual length of the pulses (BX-AX value):
- Preamble: ~3,3ms
- Long: ~1,1 ms
- Short: ~0.3 ms
It looks like a certain ratio exists between preamble, long and short pulses that will help us to properly decode the signal; if we name the short pulse 'S', the long 'L' and the preamble 'P' we have:
P ~= 3*L ~= 11S
So by any means the preamble length could be used by a receiver to synchronize its clock to the expected long and short pulse lengths.
Once the data samples have been transfered to a computer we can run the following python script to plot the voltage values contained in the csv file:
#!/usr/bin/python3
import csv
import numpy as np
import matplotlib.pyplot as plt
import argparse
import sys
if __name__ == "__main__":
p = argparse.ArgumentParser (description='Extracts values from Rigol CSV.')
p.add_argument('file', metavar = 'file', type = str, help = 'File to process.')
args = p.parse_args()
data = np.genfromtxt(args.file, delimiter=',', skip_header=0, names=['x', 'y'])
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(data['x'], data['y'], color='r', label='the data')
plt.show()
The script will take the csv file as argument, extracting its data by using the numpy lib and plotting it with matplotlib, showing the following output:
After playing a little with the pan/zoom controls we get the same wave displayed at the scope:
The signal oscillates between ~3 and ~0 volts; it looks like some kind of pulse width encoding is being used (see: PWM). As we don't really know how the protocol was designed, we will (randomly) assign logic 'one' to long pulses and logic 'zero' to the short ones. It doesn't really matter (in fact, the program I wrote lets you decode the file in both ways) how we assign the symbols as long as we do it in a proper way.
The same problem arises with the endianness: we don't really know if the remote is sending the most or the least significant bit first... no problem! Our little python program will let us play with the endianness too: