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

> Six jumper wires join a CC1101 radio module to a CJMCU-232H (FT232H) board, and a Python script on the laptop turns it into a 433 MHz scanner. A spectrum sweep, and a listen mode that decodes fixed-code remotes. No microcontroller, no soldering, receive only.

Guide · Published 2026-11-02 · Updated 2026-11-02  
Web page: https://blog.jacq.cc/guides/cc1101-433mhz-scanner-ft232h/  
Discuss on Hacker News: https://news.ycombinator.com/item?id=50336557

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.

[Image: 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.*

:::note[Status, November 2026]
The rig and the scanner work. I tested them with the copy remote, which sends a fixed code: the scanner decodes all 4 of its buttons. The gate remotes come next, in their own post.
:::

## What you need

- 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](https://eblot.github.io/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.

:::danger
The CC1101 runs on 1.8 to 3.6 V. The CJMCU-232H's 4-pin header has `+5V` right next to `+3.3V`. 5 V can kill the module, so check that wire twice.
:::

## Step 1: Download the files

- [rf-scan-ft232h.py](https://blog.jacq.cc/cc1101-scanner/rf-scan-ft232h.py): the scanner
- [keeloq-capture-ft232h.py](https://blog.jacq.cc/cc1101-scanner/keeloq-capture-ft232h.py): sets up the CC1101 and samples its data. The scanner uses it, and on its own it captures KeeLoq remote presses
- [keeloq-check.py](https://blog.jacq.cc/cc1101-scanner/keeloq-check.py): the KeeLoq decoder both of them use
- [70-ftdi-ft232h.rules](https://blog.jacq.cc/cc1101-scanner/70-ftdi-ft232h.rules): a udev rule so your user can open the board

All four are [MIT licensed](https://blog.jacq.cc/cc1101-scanner/LICENSE). Keep the three scripts in one folder.

```bash
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
```

## Step 2: Install pyftdi and the udev rule

```bash
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.

## Step 3: Wire it

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 |

[Image: 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.

[Image: 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.

[Image: 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`.*

[Image: 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.

## Step 4: Run it

```bash
.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.

## Step 5: Sweep

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.

[Image: 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.

## Step 6: Listen

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`.

[Image: 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.

[Image: 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.*

[Image: 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.*

[Image: 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](https://blog.jacq.cc/devices/aliexpress-433-mhz-copy-remote/).

[Image: 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.

[Image: 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.*

## How it works

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.

## How it turned out

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

## Next: the gate remotes

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](https://blog.jacq.cc/guides/ge-rcv1-garage-gate-remote/). 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.

## Sources

- [TI CC1101 datasheet](https://www.ti.com/lit/ds/symlink/cc1101.pdf) (PDF): the registers, `GDO1` on the `SO` pin while `CSN` is high, and RSSI to dBm
- [Ebyte E07-M1101D-SMA user manual](https://dn721305.ca.archive.org/0/items/ebytecdebytedl0719/1168_E07-M1101D-SMA_Usermanual_EN_v1.20.pdf) (PDF): the module's pins, and its 1.8 to 3.6 V supply
- [pyftdi](https://eblot.github.io/pyftdi/): SPI on the FT232H, and its [installation page](https://eblot.github.io/pyftdi/installation.html) for the udev rule
- [Flipper Zero firmware, `cc1101_configs.c`](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/lib/subghz/devices/cc1101_configs.c): the AM650 register settings

## Change history

- 2026-11-02: 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.
- 2026-11-02: First published.

Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Credit "HomeLab Series (blog.jacq.cc)" with a link to https://blog.jacq.cc/guides/cc1101-433mhz-scanner-ft232h/. Code downloads: MIT. Details: https://blog.jacq.cc/licence/
