Skip to content

Guide

CC1101 on an FT232H: a 433 MHz scanner in the terminal

Published · Updated · Discuss on Hacker News

Our gate remotes send on 433 MHz, and I wanted to see what those signals looked like. A cheap AliExpress copy remote couldn’t learn them. So I put a CC1101 radio module on a CJMCU-232H, an FT232H board that plugs straight into the laptop. Six jumper wires and a Python script: no microcontroller to flash, nothing to solder.

This setup and test was just to prove that the FTDI board could be connected to the 433 MHz board (bought from AliExpress), and that together they could scan and visualise simple signals from a bootleg remote.

A drawing of the rig: a terminal window shows green signal bars with one tall spike at 433.92 MHz, a purple FT232H board with a USB plug sends six coloured wires to a small blue CC1101 radio module with a black antenna, and a key fob sends radio waves at the antenna

The rig, drawn: the FT232H board, six wires, the CC1101 and its antenna.

  • A CC1101 433 MHz module and its antenna. Mine is an E07-M1101D (Ebyte’s board layout, with an SMA socket), about AU$3.40 on AliExpress.
  • A CJMCU-232H: a purple board with an FTDI FT232H chip and a USB-A plug.
  • A breadboard, 4 male-female jumper wires and 2 female-female ones.
  • Linux with Python 3. The script uses pyftdi.
  • Something that sends on 433 MHz to test with. I used the copy remote.

The CC1101 talks SPI. A plain USB-serial stick (CP2102, CH340) only does serial, so it can’t drive it. The FT232H does SPI from USB, so Python on the laptop talks to the radio directly. The usual way is an ESP32 running ESPHome, which means building and flashing firmware every time you change something.

All four are MIT licensed. Keep the three scripts in one folder.

Terminal window
mkdir -p ~/cc1101 && cd ~/cc1101
curl -O https://blog.jacq.cc/cc1101-scanner/rf-scan-ft232h.py \
-O https://blog.jacq.cc/cc1101-scanner/keeloq-capture-ft232h.py \
-O https://blog.jacq.cc/cc1101-scanner/keeloq-check.py \
-O https://blog.jacq.cc/cc1101-scanner/70-ftdi-ft232h.rules
Terminal window
python3 -m venv .venv
.venv/bin/pip install pyftdi
sudo install -m 644 70-ftdi-ft232h.rules /etc/udev/rules.d/
sudo udevadm control --reload
.venv/bin/python rf-scan-ft232h.py --self-test

The rule tags the FT232H (USB ID 0403:6014) with uaccess, so the logged-in user can open it: no sudo to run the scanner. Plug the board in after reloading the rules, or unplug it and plug it in again. --self-test needs no hardware: it decodes synthetic noisy presses, and a real frame from my copy remote.

The CC1101’s header prints only pins 1, 2, 7 and 8. Pin 1 is the square pad, the odd pins run along the board’s edge, and the even pins sit on the inside.

CC1101 pin Name CJMCU-232H
1 GND GND on the 4-pin header
2 VCC +3.3V on the 4-pin header. Never +5V, and not VCORE
4 CSN AD3
5 SCK AD0
6 MOSI AD1
7 MISO / GDO1 AD2
3, 8 GDO0, GDO2 not connected

Photos of both boards side by side with the wires drawn in. On the purple CJMCU-232H, the 4-pin header at the top is marked +5V crossed out, +3.3V and GND, and the side row is labelled AD0 to AD7. On the blue CC1101, its 8 header pins are numbered. Red joins pin 2 to +3.3V, black pin 1 to GND, orange pin 4 to AD3, green pin 7 to AD2, blue pin 6 to AD1 and yellow pin 5 to AD0. Text: pins 3 and 8 stay empty, never +5V

Six wires. The CJMCU-232H prints its pin names only on the back, so they’re drawn in here.

The CJMCU-232H goes on the breadboard. The CC1101 stays off it: its 2 × 4 header would short its pins in pairs along the breadboard’s rows. So the signal wires go male end into the breadboard beside the AD pins, female end onto the CC1101. The power pair goes female to female, straight onto the 4-pin header.

Breadboard drawing: the CJMCU-232H straddles the breadboard in rows 21 to 30 with its USB plug at the bottom. Four jumpers go into column j beside AD3, AD2, AD1 and AD0, and two go to the GND and +3.3V pins of its 4-pin header. The CC1101 sits beside the breadboard

My layout: the board’s AD row in rows 21 to 30, the jumpers in column j beside it.

  1. Screw the antenna onto the CC1101.
  2. Power first, with female-female wires: CC1101 pin 1 to GND and pin 2 to +3.3V on the 4-pin header.
  3. Signals, with male-female wires: pin 4 to AD3, pin 7 to AD2, pin 6 to AD1, pin 5 to AD0.
  4. Leave +5V and CC1101 pins 3 and 8 empty.

The purple CJMCU-232H on a white breadboard, USB plug at the bottom. A red and a brown jumper run from the blue CC1101 module, lying on the bench with its black antenna, to two pins of the 4-pin header at the top of the board

Power first: red from pin 2 to +3.3V, brown from pin 1 to GND.

Both boards wired. Four jumpers in grey, purple, blue and green run from the CC1101’s header into the breadboard beside the CJMCU-232H, and the red and brown power pair goes onto its 4-pin header

All six wired. The loose black and orange jumpers aren’t part of it.

The USB plug hangs off the end of the breadboard. A short USB extension cable helps.

Terminal window
.venv/bin/python rf-scan-ft232h.py

Use a terminal window at least 60 × 20. Before the screen starts, the script checks the radio: it reads the chip’s version (mine says 0x14), writes its settings and reads them back, and checks it’s receiving. If something fails, it names the wires to check. “No CC1101 answers” means power, SCK, MISO or CSN. Settings that read back wrong mean MOSI.

It only receives. It never transmits.

There are two screens, and Tab switches between them.

Key Sweep Listen
Tab listen at the marker (Enter too) back to the sweep
← → move the marker one column tune ±10 kHz
[ ] move the marker 10 columns tune ±100 kHz
p marker to the strongest signal
z x zoom in or out around the marker
b next band
c clear clear
q quit and print a summary quit and print a summary

Other options:

  • --band 315 starts on another band: 433 (the default, 432 to 436 MHz), 315, 300-348, 387-464, 779-928, all, or any START-STOP in MHz.
  • --listen 433.92 starts on the listen screen.
  • --text prints plain lines instead of the screen, and --seconds 30 stops it by itself.
  • --replay /tmp/rf-listen.log decodes a saved log instead of the radio.

The CC1101 only tunes 300 to 348, 387 to 464 and 779 to 928 MHz. A 433 MHz module and antenna hear only strong signals in the other bands.

The sweep steps across the band and reads the signal strength (RSSI) at each step. It shows bars with peak hold (yellow dots), a waterfall of past sweeps underneath (newest at the top), and the strongest signals seen along the bottom.

The sweep screen zoomed to 402 to 466 MHz, 0.28 s per sweep. Green bars sit at a noise floor around -94 dBm, with steady peaks near 408, 416, 418 to 420, 442 and 456 MHz; the one near 416 MHz reaches about -65 dBm. Yellow peak-hold dots climb to about -26 dBm around 434 MHz, where a remote was pressed. A red marker stands at 433.920 MHz. The waterfall below shows the steady signals as vertical lines of dots and dashes, and two short bursts near 434 MHz. The bottom line lists the strongest: 434.119 MHz at -22 dBm

Zoomed out to 402 to 466 MHz, with a remote pressed near 434 MHz.

  • The 433 band sweeps in 0.16 s. All three bands take 0.6 s.
  • The noise floor here is about −90 dBm.
  • Steady signals sit near 408, 416, 418 to 420, 442 and 456 MHz, pressed or not. The one near 416 MHz is the strongest, at about −65 dBm.
  • A press near 434 MHz reads up to −22 dBm. Close to the antenna, it spills into the steps around it, from 431.7 to 436.5 MHz.
  • On all bands, two more peaks show up at 782 and 883 MHz. Both are in mobile phone tower bands.

Put the marker on a signal and press Tab. The listen screen stays on that one frequency and records what the CC1101 demodulates. It shows the signal strength over the last minutes, the last frame’s pulses and bits, and every code heard more than once, with a guess at the protocol. The timings go to /tmp/rf-listen.log.

The copy remote: a chrome and black key fob with four round buttons, A, B, C and D, and a key ring

The copy remote. It sends a fixed code: the same bits on every press.

Inside is a board marked 520B V2.1: four buttons (K1 to K4), a 433 MHz SAW resonator (R433) and a 12 V 27A battery. The chip is an FMD FT60E021, a general-purpose 8-bit microcontroller, not a dedicated remote encoder, so the code it sends comes from its firmware. The gate remotes have a KeeLoq encoder chip instead, the HCS300.

The copy remote’s board, front: an FMD FT60E021 chip marked U2 at the top left, a silver R433 resonator marked X1 below it, four white push buttons K1 to K4 down the right side, a red and blue 5.1 V diode, small transistors and resistors, a battery clip at the top right and a spring at the bottom right

The board: the chip (U2), the resonator (X1) and the four buttons.

Close-up of the 8-pin chip, printed FMD FT60E021, with a small LED to its left and a transistor marked J3Y to its right

The chip: an FMD FT60E021 microcontroller.

The back of the board: bare green solder mask marked 520B V2.1, a few copper traces, and the battery contacts along the bottom edge

The back. The antenna is a copper trace printed on the board.

The copy remote’s specs, its parts and its instruction sheet are on its device page: AliExpress 433 MHz copy remote.

The listen screen at 433.92 MHz, OOK, 650 kHz filter, data sampled at 50 kHz. The signal strength graph shows seven short bursts up to -50 dBm over a -110 dBm floor, with dots under them for decoded frames. The last frame’s pulses are drawn with their bits underneath. The list of codes heard more than once has four rows: 0x0B80A4, 0x0B80A2, 0x0B80A1 and 0x0B80A8, each 24 bits, EV1527, id 0x0B80A, buttons 4, 2, 1 and 8, TE 340 microseconds, -18 to -20 dBm

All four buttons of the copy remote, one short press each.

The copy remote sends EV1527-style codes: 24 bits, a 20-bit ID (0x0B80A) and 4 button bits. Its buttons send 0x0B80A1, 0x0B80A2, 0x0B80A4 and 0x0B80A8. The shortest pulse (TE) is 340 µs. The presses came in at −18 to −20 dBm, against a floor of −88 dBm. A remote repeats its code while the button is down, so each short press gave 3 to 7 frames.

Holding the buttons down fills the list with shorter codes too, 12 to 22 bits long, like 0x80A4 and 0x0B80. They’re pieces of the real codes that noise cut short. Only the 24-bit rows are real.

The listen screen after holding the buttons down: 0x0B80A2 heard 111 times, 0x0B80A8 82 times, 0x0B80A4 62 times and 0x0B80A1 50 times, all 24-bit EV1527. Between them are a dozen shorter fixed codes of 12 to 22 bits, heard 2 to 6 times each, such as 0x80A4, 0x0B80 and 0x0B80A

Buttons held down: the four real codes, and pieces of them.

The script sets the CC1101 up as an OOK receiver on 433.92 MHz, with Flipper Zero’s AM650 settings: a 650 kHz filter and no packet handling. The chip puts the demodulated data on its GDO1 pin. On this module GDO1 shares a pin with MISO: while CSN is high, nobody is talking to the chip and MISO carries the data.

The FT232H is already in SPI mode to talk to the radio, and an SPI read clocks bits in at a steady rate. So the script opens a second SPI port on a chip-select pin that isn’t wired, AD4. Reading from it leaves CSN high and clocks MISO in at 50 kHz: a sample every 20 µs, about 17 of them per 340 µs pulse. A thread keeps the USB reads back to back, and the decoder turns runs of ones and zeros into pulse lengths.

When the radio is idle, its output on my rig is mostly high with short dips. That noise often runs straight into the start of a frame and swallows the first bit’s high pulse. Every frame of the copy remote came out 23 bits long instead of 24. The fix: read that bit from its low pulse alone. In EV1527 a 1 is a long high and a short low, and a 0 is a short high and a long low.

For the sweep, the script tunes to each step, waits 1.2 ms for the chip to calibrate and settle, and reads its RSSI register. The filter is set as wide as the step, so a sweep leaves no gaps between steps.

The test was quite successful, and the TUI proved quite handy for learning and seeing what these remotes can output.

The next project builds on this one: scanning the Richmond gate remotes to see what they output and what that looks like. They’re the same remotes that open our garage through a GE-RCV1. Inside each one is a Microchip HCS300, a KeeLoq rolling-code chip. A rolling-code remote shouldn’t send the same thing twice. Each press is a 66-bit frame: the remote’s serial number and buttons in the clear, and a 32-bit encrypted part that holds a counter, which goes up on every press.

I’ll also try to match the signal with known OEM keys: KeeLoq’s manufacturer keys, which each remote’s own key is made from. If I find a matching key, I won’t publish what the key is, only that I was able to find it, whether from an OEM list or by brute force.

I’m quite excited about this next part of the experiment: it’s becoming quite a little sleuthing puzzle to solve.

Questions or comments? Discuss on Hacker News.

Change history

  • : Step 6: photos of the copy remote's insides, the board and its FT60E021 chip, and a link to its device page with the specs and the full teardown.
  • : First published.

This guide as plain Markdown, for LLMs and scripts: cc1101-433mhz-scanner-ft232h.md. All posts: API and Markdown.

Text, photos and drawings: CC BY 4.0. Code: MIT. Share and adapt them, crediting HomeLab Series (blog.jacq.cc). Licence and privacy