Overview
The LD1125H is a 24GHz millimeter-wave human presence radar from Hi-Link. It is a compact 4-pin module that outputs human-readable ASCII strings over a 3.3V UART, reporting both moving and stationary targets along with their distance. Because it streams plain text frames like mov, dis=1.23 and occ, dis=0.85, it is one of the easiest mmWave radars to integrate - no binary parser required.
Inside, the LD1125H uses FMCW radar at 24GHz to measure micro-movements down to chest rise from breathing. This lets it hold presence even when a person sits perfectly still, something a PIR sensor simply cannot do. Detection works through plastic enclosures, drywall, glass, and most fabrics, and is largely immune to temperature, light, and humidity changes.
Typical projects: occupancy-based lighting, bathroom and stairwell automation, smart desks that know you're still there, bed/sleep presence sensors, hidden security trip-wires behind drywall, and ESPHome/Home Assistant nodes.
At a Glance
Specifications
| Parameter | Value |
| Module | HLK-LD1125H |
| Operating Frequency | 24GHz ISM band |
| Operating Voltage | 5V DC (4.5V - 5.5V) |
| Average Current | ~85 mA |
| Moving Target Range | 0.4 m - 8 m (configurable) |
| Static Target Range | 0.4 m - 6 m (configurable) |
| Distance Resolution | ~0.05 m |
| Detection Angle | ±60deg horizontal / ±45deg vertical |
| UART Baud Rate | 115200, 8N1 |
| UART Logic Level | 3.3V TTL |
| Connector | 2.0 mm pitch, 4-pin |
| Operating Temperature | -20degC to +65degC |
| Dimensions | ~22 mm x 15 mm |
Pinout Diagram
Wiring Guide
Arduino Wiring
The Uno's only hardware UART is used by the Serial Monitor, so we read the radar with SoftwareSerial on D2/D3. SoftwareSerial handles 115200 unreliably on a Uno - if you see garbled frames, switch to a Mega (use Serial1) or an ESP32.
| Sensor Pin | Arduino Pin |
|---|---|
| VCC | 5V |
| GND | GND |
| UTX | D2 (SoftwareSerial RX) |
| URX | D3 (SoftwareSerial TX) |
uart_baud 9600\r\n followed by save to drop the radar to a Uno-friendly baud rate.ESP32 Wiring
The ESP32 is the recommended host - 3.3V logic, three hardware UARTs, and plenty of buffer. We use UART2 on GPIO16/17.
| Sensor Pin | ESP32 Pin |
|---|---|
| VCC | 5V (VIN) |
| GND | GND |
| UTX | GPIO16 (RX2) |
| URX | GPIO17 (TX2) |
Raspberry Pi Wiring
Disable the serial login shell with sudo raspi-config -> Interface Options -> Serial Port -> Login shell: No, Hardware serial: Yes. Reboot, then read the radar from /dev/serial0.
| Sensor Pin | Raspberry Pi Pin |
|---|---|
| VCC | Pin 2 (5V) |
| GND | Pin 6 (GND) |
| UTX | Pin 10 (GPIO15 / RXD) |
| URX | Pin 8 (GPIO14 / TXD) |
Raspberry Pi Pico Wiring
We use UART0 on GP0/GP1.
| Sensor Pin | Pico Pin |
|---|---|
| VCC | VBUS (Pin 40) |
| GND | GND (Pin 38) |
| UTX | GP1 (Pin 2, UART0 RX) |
| URX | GP0 (Pin 1, UART0 TX) |
Code Examples
Arduino
Reads ASCII frames from the LD1125H, splits them on commas, and prints the human-readable status and distance.
// LD1125H ASCII reader for Arduino Uno
// Wiring: UTX->D2, URX->D3, VCC->5V, GND->GND
// Frames look like: mov, dis=1.23 or occ, dis=0.85
#include <SoftwareSerial.h>
SoftwareSerial RadarSerial(2, 3); // RX, TX
String line;
void setup() {
Serial.begin(115200);
RadarSerial.begin(115200);
Serial.println("LD1125H reader ready");
}
void loop() {
while (RadarSerial.available()) {
char c = RadarSerial.read();
if (c == '\n') {
line.trim();
if (line.length()) {
if (line.startsWith("mov")) {
Serial.print("MOVING ");
} else if (line.startsWith("occ")) {
Serial.print("STATIC ");
} else {
Serial.print(line);
Serial.println();
line = "";
continue;
}
int eq = line.indexOf('=');
if (eq > 0) Serial.print(line.substring(eq + 1));
Serial.println(" m");
}
line = "";
} else if (c != '\r') {
line += c;
}
}
}
ESP32
Same parser on a hardware UART. Includes a helper that sends configuration commands like th1=120 (moving sensitivity) or rmax=6 (max range in meters).
// LD1125H on ESP32 - ASCII reader + config helper
#include <HardwareSerial.h>
HardwareSerial RadarSerial(2);
String line;
void sendCmd(const String &cmd) {
RadarSerial.print(cmd);
RadarSerial.print("\r\n");
}
void setup() {
Serial.begin(115200);
RadarSerial.begin(115200, SERIAL_8N1, 16, 17);
delay(500);
// Example config: 6 m max, moderate sensitivity
sendCmd("rmax=6");
sendCmd("mth1=80");
sendCmd("mth2=50");
sendCmd("mth3=30");
sendCmd("save");
Serial.println("LD1125H ESP32 reader ready");
}
void loop() {
while (RadarSerial.available()) {
char c = RadarSerial.read();
if (c == '\n') {
line.trim();
if (line.length()) {
bool moving = line.startsWith("mov");
bool stat = line.startsWith("occ");
int eq = line.indexOf('=');
float d = (eq > 0) ? line.substring(eq + 1).toFloat() : -1;
if (moving) Serial.printf("MOVING %.2f m\n", d);
else if (stat) Serial.printf("STATIC %.2f m\n", d);
else Serial.println(line);
}
line = "";
} else if (c != '\r') {
line += c;
}
}
}
Raspberry Pi (Python)
A clean Python reader that prints state + distance and exposes a callable for your own automations. Install pyserial with pip install pyserial.
#!/usr/bin/env python3
"""LD1125H ASCII reader for Raspberry Pi."""
import serial, re, time
PORT = "/dev/serial0"
BAUD = 115200
ser = serial.Serial(PORT, BAUD, timeout=1)
print("LD1125H Pi reader started")
pattern = re.compile(r"^(mov|occ)\s*,\s*dis\s*=\s*([0-9.]+)", re.IGNORECASE)
def handle(state, distance_m):
label = "MOVING" if state.lower() == "mov" else "STATIC"
print(f"{label} {distance_m:.2f} m")
while True:
raw = ser.readline()
if not raw:
continue
try:
text = raw.decode("ascii", errors="ignore").strip()
except UnicodeDecodeError:
continue
m = pattern.match(text)
if m:
handle(m.group(1), float(m.group(2)))
Raspberry Pi Pico (MicroPython)
The Pico version uses UART0. A tiny line buffer keeps it RAM-friendly.
# LD1125H on Raspberry Pi Pico (MicroPython)
from machine import UART, Pin
import time
uart = UART(0, baudrate=115200, tx=Pin(0), rx=Pin(1))
buf = ""
print("LD1125H Pico reader ready")
while True:
if uart.any():
chunk = uart.read(uart.any())
try:
buf += chunk.decode("ascii")
except UnicodeError:
buf = ""
continue
while "\n" in buf:
line, buf = buf.split("\n", 1)
line = line.strip()
if not line:
continue
if line.startswith("mov") or line.startswith("occ"):
label = "MOVING" if line.startswith("mov") else "STATIC"
eq = line.find("=")
if eq > 0:
try:
d = float(line[eq + 1:])
print(label, "%.2f m" % d)
except ValueError:
pass
time.sleep_ms(20)
Frequently Asked Questions
mov = a moving target (walking, waving). occ = an occupied/static target (sitting, sleeping). Either one is a confirmed human presence.rmax=N over UART where N is meters (e.g. rmax=4) followed by save. The setting is stored in non-volatile memory.mth1, mth2, mth3 for moving targets and oth1, oth2, oth3 for static targets. Higher numbers = less sensitive. Always finish with save.