Receiving IR signals with RTL-SDR dongles

R. X. Seger
15 min readJul 4, 2016

--

RTL-SDR dongles like the NooElec NESDR Mini 2+ often come with an infrared remote control, intended to control TV player software when the device is used in DVB-T mode. But when used as an SDR, what can you do with it? Besides removing the sensor to potentially reduce noise?

The RTL remote’s infrared LED turned on, as viewed through a digital camera

Update 2016/07/06: here is an annotated picture of the NESDR’s insides, including the IR sensor, only labeled “CHOD” (likely a TSOP382 or similar):

NooElec NESDR Mini 2+ teardown

The IR sensor is adjacent to the power LED, via a small window in the case:

The outside of the NooElec NESDR Mini 2+ showing the location of the IR sensor

In this article, I show how you can use your RTL-SDR dongle to receive IR remote control signals, including those from the RTL’s bundled remote, while continuing to use the same device for software-defined radio.

The RTL’s infrared receiver is surprisingly powerful, it was able to pickup signals from all the IR remotes I had readily available, and returns the raw IR pulse width data to the client software for protocol decoding. This article serves as an introduction to what can be done, I have only scratched the surface and my hope is the techniques & software described herein can be further extended for other interesting uses.

Beyond RF into the IR spectrum

The Rafael Micro R820T can tune within 24 MHz–1.7 GHz, aka 2.4x10⁷ Hz to 1.7x10⁹ Hz. Elonics E4000 tuners have a slightly wider range, but both are still within the lower end of the complete electromagnetic spectrum:

Electromagnetic spectrum showing visible light

At higher frequencies, or equivalently shorter wavelengths, we reach infrared, visible light, ultraviolet, and beyond. A wavelength of 650 nm, common with red LEDs, has a frequency of c/650 nm = 4.61x10¹⁴ Hz = 461 THz. Terahertz! Infrared is at a lower frequency than red, common IR LED wavelengths include 780, 810, 850, 880, 940, 970, and 1050 nm, but both are far beyond the frequency range of the RTL-SDR’s tuner, or any ordinary SDR, for that matter:

Terahertz gap: technologies exist to rx/tx above (IR, etc.) and beyond (RF)

The infrared sensor, a separate component of (most? all?) RTL-SDR dongles, can receive these signals, with some limitations.

To see how, let’s first take a look at the original/non-SDR RTL DVB software.

The Original DVB Tuner Software

Windows

What are now commonly known as “RTL-SDR dongles” were originally intended for watching DVB-T broadcasts, primarily European television. The OEM software is a complete package, including support for receiving IR to control the video player using the bundled remote control.

Locating this original software for watching DVB-T proved unexpectedly challenging. The NooElec NESDR Mini 2+ I had didn’t come with software, instead assuming the user uses SDR software — a fair assumption, given the intended target audience for NooElec and RTL-SDR Blog.

Some of the other brands include CD-ROMs, however. The Newsky TV28T kit comes with a CD-ROM, support site has downloads but not for the dongle, and PadTV is an .apk for Android.

The Realtek page for RTL2832U “DVB-T COFDM Demodulator + USB 2.0” has no downloads.

Mesinton’s RTL-SDR bundle comes with a CD-ROM, Amazon comments say it is “Blaze HDTV 6.0. You can get it off of the net.” So I installed the free trial of BlazeVideo HDTV Player, pointed IR remote at the dongle and tapped the volume adjustment buttons, but it had no effect.

Windows detects the device as RTL2832UHIDIR:

RTL28328UHIDIR detected by Windows

Installing the default Windows driver:

REALTEK 2832U Device (RTL2832UBDA.sys, RTL2832UUSB.sys)

Other users have had trouble using the internal IR receiver under Windows. In theory it should be possible using the original software, but I didn’t investigate further, and instead opted to use the fully-fledged Linux driver.

Linux: the dvb_usb_rtl28xxu driver

linux/drivers/media/usb/dvb-usb-v2/rtl28xxu.c is included in Linux, the conventional dvb_usb_rtl28xxu driver. Searching for this driver mostly returns results about how to disable it, in favor of librtlsdr. But it has its uses: DVB and IR, as the dongle was intended for.

“Devices using this chip support infrared remote controls, but unlike other cases, the infrared signal is captured and passed to the kernel without any processing, delegating the detection and decoding of the protocol to the kernel.” — sounds like software-defined infrared!

Arch Linux’s wiki has an excellent guide to setting up this driver, also works well under a Ubuntu virtual machine:

Install the full DVB driver (not the librtlsdr SDR-only driver):

sudo modprobe dvb_usb_rtl28xxu

then dmesg confirms the IR driver is loaded:

Registered IR keymap rc-empty
input: Realtek RTL2832U reference design a /devices/pci0000:00/0000:00:06.0/usb1/1–2/rc/rc0/input8
rc0: Realtek RTL2832U reference design as /devices/pci0000:00/0000:00:06.0/usb1/1–2/rc/rc0
IR NEC protocol handler initialized
IR RC5(x/sz) protocol handler initialized
IR RC6 protocol handler initialized
IR Sharp protocol handler initialized
IR MCE Keyboard/mouse protocol handler initialized
IR JVC protocol handler initialized
input: MCE IR Keyboard/Mouse (dvb_usb_rtl28xxu) as /devices/virtual/input/input9
usb 1–2: dvb_usb_v2: schedule remote query interval to 200 msecs
IR SANYO protocol handler initialized
IR XMP protocol handler initialized
IR Sony protocol handler initialized
lirc_dev: IR Remote Control driver registered, major 246
rc rc0: lirc_dev: driver ir-lirc-codec (dvb_usb_rtl28xxu) registered at minor = 0
IR LIRC bridge handler initialized
usb 1–2: dvb_usb_v2: ‘Realtek RTL2832U reference design’ successfully initialized and connected

Check the protocols enabled with `ir-keytable`, enabled them all:

sudo ir-keytable -p unknown -p other -p lirc -p rc-5 -p jvc -p sony -p nec -p sanyo -p rc-6 -p sharp -p xmp

The only supposedly-supported protocol I couldn’t enable was “mce-kbd”, unclear why (fails with: /sys/class/rc/rc0//protocols: Invalid argument, but all the other protocols can be enabled without error).

Listen for IR events:

ir-keytable -s rc0 -t

Press some buttons on the remote and see what happens…

Remotes

The standard remote that comes with many RTL-SDR dongles (model number unknown, only label on the back is “OR2025” for the coin battery cell) is supported:

A remote for the RTL-SDR, photo from @kolaCzek

tap a few buttons on the remote:

1467362168.856539: event type EV_MSC(0x04): scancode = 0x4d
1467362168.856539: event type EV_SYN(0x00).

That’s the scancode for power on/off⏻. Testing each scancode, pressing each of the 3x7 buttons on the remote:

4d 54 16
4c 05 0c
0a 40 1e
12 02 1c
09 1d 1f
0d 19 1b
11 15 17

What about other remote lying around the house?

Original Apple Remote

Apple Remote scancodes, using the 32-bit NEC protocol:

0x77e1d0e1 volume up
0x77e1b0e1 volume down
0x77e110e1 rewind
0x77e1e0e1 forward
0x77e120e1 play/pause
0x77e140e1 menu

Some other remote controls, including the Siri Remote

The Siri Remote uses Bluetooth to talk to the Apple TV, which is superior to IR since it doesn’t require line-of-sight, however it also supports IR for controlling television sets. Technical details show using NEC IR protocol, “differential PPM encoding on a 1:3 duty cycle 38 kHz 950 nm infrared carrier”. Not all sent IR signals, but those that did, for my setup at least:

0x4c4 previous
0x402 volume up
0x403 volume down
0x77e11898 menu+previous held for 5 seconds

All of the remotes I tested showed something on the RTL’s infrared receiver, not bad. This is largely because the IR protocol is decoded in software.

Remote control protocols

The RC-5 protocol:

Remote Control 5 protocol

Other protocols include RC-6 and NEC. The RTL dongle can pickup both, and more, since it expects demodulation to occur in software.

NEC protocol is used by the RTL remote:

NEC protocol modulation from SB-Projects

press power button, see it sends address 0x00, inverted address 0xff, command 0x4d, inverted command 0xb2. Holding down the button sends the repeat code (9 ms mark, 2.25 ms space, 0.560 mark). Each duration count from the dongle registers (0–127) measures 0.02 ms.

IR modulation explained

Although light phase shift can be measured, for example in a laser inferometer measuring sound bouncing off a window by detecting the reflected phase shift of an ordinary visible laser light, IR remote controls at their lowest level use on-off keying (OOK). The infrared LED is turned off and on, always emitting about the same frequency, at a constant amplitude when turned on.

The infrared spectrum as previously described is within multiples of terahertz. Near-infrared (NIR) of 750–1400 nm wavelength has a 214–400 THz frequency; short, mid, long, and far-infrared wavelengths are up to 1,000,000 nm. NIR LEDs commonly are 950 nm, or about 315 THz.

Then what is this 36 kHz “carrier wave”, seen in the RC-5 diagram? The IR is pulsed rapidly off and on, with a 25–33% duty cycle (time on), a square wave, or rather a rectangular pulse train. The receiver specifically detects this carrier wave, presence or absence, to encode symbols. Higher-level protocols encode to bits.

The Linux driver reads the raw pulses from the RTL in rtl2832u_rc_query, then passes to various functions for decoding: in the old DVB-Realtek-RTL2832U-2.2.2–10tuner-mod_kernel-3.0.0 driver since merged into Linux mainline, these were frt0 (rc6), frt1 (rc5), frt2 (nec). This is the function I seeked to port so it can run in userspace.

Introducing rtl_ir

The Linux driver works great for its intended purpose, but it has some downsides:

  • No SDR support
  • Only for Linux

librtlsdr fixes these problems, using the cross-platform libusb library in user space.

With some late-night tinkering using the Linux driver for reference, I was able to enhance librtlsdr to allowing reading the IR sensor, including a simple rtl_ir tool. Tested rtl_ir on OS X, you can see the pulses and visually manually decode the NEC protocol:

rtl_ir showing the NEC protocol pulses for the RTL power button (0x4d), then 4 repeat codes

Example of decoding these pulses by hand: pwr-nec-decoded.txt

Aside: Software-defined IR, or translating IR to IQ samples

rtl_ir raises an interesting possibility, can it be used to receive arbitrary infrared signals just like software-defined radio receivers can receive arbitrary radio signals? Can you pipe the raw samples to GNU Radio?

Not exactly. The IR receiver will only pass along bursts of IR within the carrier frequency (~36 kHz), so it cannot receive any old infrared signal. You wouldn’t want it to, anyways, at least as a remote control, since infrared is all around us, this would cause interference from spurious noise.

Still, one may wonder, what if the IR samples could be converted to I/Q data for analysis in existing SDR toolkits like GNU Radio? Certainly it is possible. At 940 nm, what if the driver was extended so that you could tune to ~319 THz, and see the IR signal on the waterfall?

Possibly interesting, but not expected to be terribly useful, for a few reasons:

  • The IR receiver doesn’t report the frequency of the received light, leaving the driver to assume a fixed 319 THz (or so) at all times.
  • The IR receiver doesn’t report the carrier frequency/phase, leaving the driver to either synthesize ~36 kHz bursts, or leave it out.
  • The RTL-SDR API uses uint32_t for frequency in hertz, a maximum of 4.3 GHz. Other devices like the HackRF support up to 6 GHz, therefore their APIs support it, so it should be possible but would require API changes, such as updating RTL-SDR’s frequency parameter to 64-bit for a maximum of 18 EHz.
  • Often you would like to receive IR and RF simultaneously, using IR as a secondary control channel, but the RTL-SDR APIs can only tune to one frequency at a time.
  • Processing the IR IQ samples would require additional decoding, undoing the encoding to IQ, extra work for not much benefit.

Could be an interesting side project, but putting this idea aside for now.

Update 2016/09/18: see also the Gladiolus neighbor for Great Scott Gadget’s GreatFET, a possible “IR SDR” in development:

Gladiolus is the code name for an infrared neighbor under development. The concept is to execute IR RX and TX in the style of Software Defined Radio (SDR), enabling implementation of arbitrary IR protocols.

Sound card IR and alternatives

“If you have the original remote control of a particular device you can easily study the used protocol by connecting a simple IR detection diode to the Line input of your PC’s sound card”, suggests SB Projects. Oona Räisänen also demonstrated this technique in The Infrared Impulse. Given the simplicity of receiving IR, no extra components needed but a LED and a sound card jack, is it even worth using RTL-SDR dongles for this purpose?

Some computer systems also include their own built-in IR receivers, but it seems to be falling out of favor, for example my old Mac mini has an IR receiver, but not the newer iMac. Not to mention IrDA, largely displaced today by Bluetooth and Wi-Fi.

Nonetheless if you still want to use IR with your RTL-SDR, read on.

RF + IR, take #1: rtl_tcp -I

My rtl_ir test program reads IR from the dongle, but that’s all it does. Since it monopolizes the device, you can’t use it for anything else.

To solve this dilemma, first I modified rtl_tcp to enable listening for IR with the “-I port_ir” flag:

rtl_tcp -I 1235

will listen on port 1235 for IR, in addition to the default port 1234 for RF.

To test this, fire up GNU Radio and set rtl_tcp=localhost:1234 in the Osmocom Source device flags, I used an FM radio receiver flowgraph. Run the flowgraph ensuring it plays audio, and simultaneously connect to 1235 to verify it shows IR signals when using the remote control. So far so good.

Next I wanted to use IR while running dump1090, but it doesn’t appear to support connecting to RTL-SDR sources over TCP, as it interfaces directly with the librtlsdr library. So a different approach was needed.

Integrating with dump1090-mutability

Using rtl_tcp and netcat

dump1090 interfaces with the RTL-SDR using librtlsdr itself, but it supports --ifile - for reading from stdin, for example:

rtl_sdr -f 1090000000 -s 2000000 -p 2 - | dump1090-mutability --ifile -

We can use netcat to pipe from TCP to stdout for dump1090:

rtl_tcp -f 1090000000 -s 2000000
nc localhost 1234 | dump1090-mutability --ifile -

This should operate the same as above. Now since rtl_tcp is enhanced to support IR, pass the -I flag to enable it:

rtl_tcp -f 1090000000 -s 2000000 -I 1235
nc localhost 1234 | dump1090-mutability --ifile -

Now you can track aircraft ADS-B, while connecting also observing IR:

nc localhost 1235 | xxd -b

Press a button on the IR remote, it should emit a signal, all the while dump1090 continues tracking in the background.

Integrating this change with dump1090 running in the background poses a bit of a problem. /usr/bin/dump1090-mutability is started by /etc/init.d/dump1090-mutability using start-stop-daemon, disconnecting from stdout and stdin. The requirement to specify the frequency, bandwidth, ppm, and other parameters in rtl_tcp instead of having dump1090 do it is also a bit awkward, so I sought another way.

Using rtl_rpcd

dump1090 doesn’t support rtl_tcp; software has to be specifically written to interface to the remote TCP server instead of directly using librtlsdr. However, @Texane enhanced librtlsdr to support remote RTL dongles without client software modification, texane/librtlsdr#rpc branch, featured in Hackaday Using librtlsdr over TCP. This branch adds a new server, rtl_rpcd (RPC for Remote Procedure Call), and allows any client to connect when RTLSDR_RPC_IS_ENABLED=1 is set. Test locally first:

rtl_rpcd
RTLSDR_RPC_IS_ENABLED=1 rtl_sdr -f 109000000 -

This should function the same as if you ran ‘rtl_sdr -f 109000000 -’. Neat.

But there’s two separate branches, pull request #7 to librtlsdr/librtlsdr for rpc, and pull request #9 for ir. Merged the two in rxseger/librtlsdr, yet would like to use rtl_rpcd to provide the SDR interface, but also IR. Added in internal pull request to myself: pull request #1, rtl_rpcd: add IR listening option, built in the two branches. To use both, build the rpc_ir branch from source, install it, then run:

rtl_rpcd -I 1235
RTLSDR_RPC_IS_ENABLED=1 dump1090-mutability

Important note: librtlsdr has to be installed for dump1090 to use it! `sudo make install` in librtlsdr. If you are using the old version, then rtl_rpcd will listen, but not open the device until told to do so, and then dump1090 will open the device itself locally, therefore blocking rtl_rpcd.

To confirm the modified librtlsdr is installed, test with rtl_ir:

rtl_ir: symbol lookup error: rtl_ir: undefined symbol: rtlsdr_ir_query

If it fails with the above error, then the tools are installed, but the enhanced librtlsdr library is not being used; if command not found, then neither. Raspbian installs librtlsdr0 under /usr/lib/arm-linux-gnueabihf/librtlsdr.so.0.5git, to use my version instead (TODO: better fix?):

sudo ln -sf /usr/local/lib/librtlsdr.so.0.5git /usr/lib/arm-linux-gnueabihf/librtlsdr.so.0.5git

Now rtl_ir shouldn’t complain about missing rtlsdr_ir_query. Stop dump1090-mutability and try running it “remotely”:

RTLSDR_RPC_IS_ENABLED=1 dump1090-mutability

it should connect to rtl_rpcd, begin displaying ADS-B data, and yet connecting to port 1235 should display IR data. To make this persistent, all we need to do is set the environment variable in the service. Add to /etc/default/dump1090-mutability, which is actually a shell script:

export RTLSDR_RPC_IS_ENABLED=“1”

Note the export is needed, or only a shell variable will be set not an environment variable. dump1090 should be working as normal now, with the 1235 port remaining open for receiving IR.

Persistent rtl_rpcd daemon

Running the rtl_rpcd daemon as a service, using a startup script:

  • /etc/init.d/rtl_rpcd — download and save in /etc/init.d
  • sudo ln -s /etc/init.d/rtl_rpcd /etc/rc5.d/S01rtl_rpcd

The service will now load when the system starts, you can check with:

/etc/init.d/rtl_rpcd status

Limitations

Polling the IR every 10000 microseconds (by default), may possibly interfere with SDR operation, increase CPU, but at least for my purposes using dump1090 on a RPi3 the impact was not noticeable, your mileage may vary. IR is still disabled by default in librtlsdr to eliminate unexpected impact, but if there is any, consider a standalone IR transceiver.

rtl2828u_get_rc_config contains this ominous warning “/* disable IR interrupts in order to avoid SDR sample loss”, before writing to IR_RX_IE 0, loading empty to enable IR. TODO: investigate IR interrupts.

A simple application of IR: powering off the Pi

Now that the system can receive IR signals, what kind of commands should we send and what should they do?

There are many possibilities, depending on what you are doing with your SDR or computer system. Perhaps if you are using the SDR to receive broadcast radio stations, the ch+/ch- and/or digit keys could be used to tune the channel, and the vol-/vol+ to adjust the volume.

For now, my goal is more humble, but solving a legitimate problem:

A juicy Raspberry Pi berry

The Raspberry Pi 3 single-board computer works great when used headless, but powering it off is problematic because it happens to lack a power switch. You could of course pull out the power cord, but this would abruptly turn it off possibly causing corruption, and is not considered good practice. To power it off gracefully, I have to SSH in remotely and run `sudo shutdown -h now`, not convenient if I want to quickly move it somewhere else.

To address this problem others have added a shutdown switch using the GPIO ports, but I’ll shutdown in style using the RTL-SDR’s IR remote, with the help of:

While I was at it, also added the “recall” button to reboot the system.

With nec-pwr and rtl_rpcd (see above) loaded, the RTL remote power button can now be used to turn off the headless Raspberry Pi:

A powered off Raspberry Pi 3

Here’s a video of it in action: power off RPi via IR.

Notice the system takes a few seconds to perform a graceful shutdown, first powering off the USB dongle, then the activity LED flashes indicating the shutdown sequence, and it finally turns off. The standby power LED stays on when power is present, but the power plug to the Pi is ready to be pulled. When the power cable is reinserted, it will start back up and be ready to turn off again in the same fashion.

--

--