Overview
The HC-12 is the easiest long-range radio you'll ever use, because it doesn't feel like a radio at all — it feels like a very long serial cable. Wire it to a UART, and whatever bytes go into one module's RXD come out the far module's TXD, transparently, at ranges up to a kilometer-plus line-of-sight. No packet framing, no addressing, no radio registers: Serial.print() on one end, Serial.read() on the other.
Under the hood a Si44xx-family sub-GHz transceiver and an onboard MCU handle everything: 100 selectable channels across 433.4-473.0 MHz, up to 100 mW transmit power, and several FU transmission modes trading speed against range (FU3 default balances both; FU4 stretches maximum distance at low baud). Configuration happens over the same UART — pull the SET pin low and the module answers AT commands for channel, power, baud, and mode; release SET and it's transparent again.
Pair two of them for remote sensors, wireless PC links, robot telemetry and control, or a whole party-line network (every module on a channel hears every transmission — add simple message prefixes to address nodes). It runs from 3.2-5.5V, plays nicely with 3.3V and 5V logic, and asks only two things: solder on the included spring antenna (never transmit without one) and give it a solid supply, since 100 mW transmit bursts punish flimsy wiring.
At a Glance
Specifications
| Parameter | Value |
| Module | HC-12 wireless serial (Si44xx RF + STM8 controller) |
| Frequency | 433.4 - 473.0 MHz, 100 channels at 400 kHz spacing |
| TX Power | 8 steps, -1 to +20 dBm (AT+P1..P8) |
| Sensitivity | Down to -117 dBm (low-baud modes) |
| Serial Baud | 1200 - 115200 (default 9600 8N1) |
| Modes | FU1 power-save, FU2 ultra-low-power polling, FU3 default balanced, FU4 max-range (1200 baud) |
| Supply | 3.2 - 5.5V; ≥100 mA capable supply recommended for TX bursts |
| Idle / TX Current | ~16 mA idle (FU3); ~100+ mA peaks at full TX power |
| Config | AT commands with SET pin held LOW |
| Antenna | Spring antenna on ANT1 solder pad or U.FL socket — required |
| Pins | VCC, GND, RXD, TXD, SET |
| Size | 27.8 x 14.4 mm |
Pinout Diagram
Bottom edge: VCC, GND, RXD, TXD, SET — the whole working interface. Top edge: the antenna options (U.FL connector at ANT1, solder pad at ANT2 for the spring antenna) plus their grounds. SET floats high internally; ground it (directly or via a GPIO) to enter AT command mode.
Wiring Guide
Cross the UART (module TXD to your RX, RXD to your TX), power it, antenna on. SET can stay unconnected for normal use — wire it to a GPIO if you want to change channels/power from code.
Arduino Wiring (SoftwareSerial)
| HC-12 Pin | Arduino Pin | Details |
|---|---|---|
| VCC | 5V | Solid supply; add 100 uF nearby |
| GND | GND | |
| TXD | D10 | SoftwareSerial RX |
| RXD | D11 | SoftwareSerial TX |
| SET | D7 (optional) | LOW = AT mode |
ESP32 Wiring (UART2)
| HC-12 Pin | ESP32 Pin | Details |
|---|---|---|
| VCC | VIN (5V) or 3V3 | 5V gives the strongest TX |
| GND | GND | |
| TXD | GPIO 16 (RX2) | HC-12 TX is 3.3V-level — safe |
| RXD | GPIO 17 (TX2) | |
| SET | GPIO 4 (optional) |
Raspberry Pi Wiring (UART0)
| HC-12 Pin | Pi Pin | Details |
|---|---|---|
| VCC | Pin 2 (5V) | |
| GND | Pin 6 (GND) | |
| TXD | Pin 10 (GPIO 15, RXD) | 3.3V logic from module — safe |
| RXD | Pin 8 (GPIO 14, TXD) | |
| SET | Pin 11 (GPIO 17, optional) |
Raspberry Pi Pico Wiring (UART0)
| HC-12 Pin | Pico Pin | Details |
|---|---|---|
| VCC | VBUS (5V) or 3V3(OUT) | |
| GND | GND (pin 38) | |
| TXD | GP1 (UART0 RX) | |
| RXD | GP0 (UART0 TX) | |
| SET | GP2 (optional) |
Code Examples
The Arduino example is a serial-monitor chat bridge (flash two boards, type on one, read on the other). The ESP32 example sends sensor-style telemetry. The Pi listens/logs, and the Pico example includes AT configuration via the SET pin.
Arduino — wireless chat
// HC-12 - Arduino transparent link (chat bridge)
// TXD->D10, RXD->D11, VCC->5V | flash the same sketch on both boards
#include <SoftwareSerial.h>
SoftwareSerial hc12(10, 11); // RX, TX
void setup() {
Serial.begin(9600);
hc12.begin(9600); // HC-12 default baud
Serial.println("HC-12 chat ready - type and press enter");
}
void loop() {
// radio -> serial monitor
while (hc12.available()) {
Serial.write(hc12.read());
}
// serial monitor -> radio
while (Serial.available()) {
hc12.write(Serial.read());
}
}
ESP32 (Arduino IDE) — telemetry sender
// HC-12 - ESP32 telemetry sender (UART2)
// TXD->GPIO16, RXD->GPIO17
unsigned long counter = 0;
void setup() {
Serial.begin(115200);
Serial2.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17
Serial.println("HC-12 telemetry sender");
}
void loop() {
// Send a labeled reading once a second (replace with real sensor data)
float fakeTemp = 20.0 + (millis() % 10000) / 1000.0;
Serial2.printf("NODE1,%lu,%.2f\n", counter++, fakeTemp);
Serial.printf("sent NODE1,%lu,%.2f\n", counter - 1, fakeTemp);
// print anything that comes back
while (Serial2.available()) Serial.write(Serial2.read());
delay(1000);
}
Raspberry Pi (Python) — receiver/logger
#!/usr/bin/env python3
# HC-12 - Raspberry Pi receiver/logger
# TXD->GPIO15, RXD->GPIO14 | Install: pip3 install pyserial
import serial, time
port = serial.Serial("/dev/serial0", 9600, timeout=1)
print("Listening for HC-12 packets... Ctrl+C to stop")
try:
with open("hc12_log.csv", "a") as log:
while True:
line = port.readline().decode(errors="ignore").strip()
if line:
stamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{stamp}] {line}")
log.write(f"{stamp},{line}\n")
log.flush()
except KeyboardInterrupt:
print("Stopped by user")
Raspberry Pi Pico (MicroPython) — with AT configuration
# HC-12 - Pico MicroPython Example with AT config
# TXD->GP1, RXD->GP0, SET->GP2
from machine import UART, Pin
import time
uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1), timeout=300)
set_pin = Pin(2, Pin.OUT, value=1) # HIGH = transparent mode
def at(cmd):
"""Send one AT command with SET low, return the reply."""
set_pin.value(0)
time.sleep_ms(50)
uart.write(cmd + "\r\n")
time.sleep_ms(150)
reply = uart.read()
set_pin.value(1)
time.sleep_ms(80)
return reply.decode(errors="ignore").strip() if reply else "(no reply)"
# One-time setup: channel 5, full power, confirm firmware
print("AT :", at("AT")) # expect OK
print("Channel :", at("AT+C005")) # both ends must match!
print("Power :", at("AT+P8")) # +20 dBm
print("Version :", at("AT+V"))
counter = 0
while True:
uart.write("PICO,{}\n".format(counter))
print("sent PICO,{}".format(counter))
counter += 1
data = uart.read()
if data:
print("recv:", data.decode(errors="ignore").strip())
time.sleep(1)