The mmWave C4002 Human Presence Sensor is a radar-based module designed for accurate human detection. It uses 24 GHz FMCW millimeter-wave technology to detect both motion and micro-movements such as breathing, allowing it to identify people even when they are completely still.
Unlike traditional PIR sensors, this module provides presence detection. It can distinguish between no target, moving targets, and stationary human presence within a range of up to 10 meters. In this tutorial you will learn how to connect the mmWave C4002 sensor to an Arduino or an ESP32 for presence and motion detection.
Required Parts
You can get the mmWave C4002 Sensor at DFRobot. You also will need an Arduino or an ESP32. I am using an Arduino UNO and an ESP32-C3 SuperMini in this tutorial but any other Arduino, ESP32 or ESP8266 will work as long as it has a 5V output pin. Finally, a breadboard and some Dupont cables for wiring will be useful.

mmWave C4002 Radar Sensor

ESP32-C3 SuperMini

USB C Cable

Arduino Uno

USB Cable for Arduino UNO

Dupont Wire Set

Breadboard
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Hardware of the mmWave C4002 Sensor
The mmWave C4002 is based on 24 GHz FMCW (Frequency Modulated Continuous Wave) radar technology. It continuously emits electromagnetic waves in the 24.0 GHz to 24.25 GHz band and measures the reflected signals from objects in the environment. The picture below shows the back and front of the module. You can see the two golden radar antennas at the front of the board.

You can also the an LED labelled “RUN” and an LED labelled “OUT” on the front of the board. The green RUN LED will blink when the sensor is actively detecting and the blue OUT LED will light up if a person is detected.
The FMCW modulation technique allows the sensor to extract distance, velocity, and motion characteristics from the reflected signal. The sensor can detect very small changes in position, such as chest movement during breathing. This enables the detection of stationary humans, which is not possible with passive infrared sensors.
The radar operates independently of ambient light and temperature. It is not affected by environmental conditions like darkness, dust, or heat variations. This improves stability in indoor smart home applications.
Detection Capabilities and Signal Processing
The C4002 supports multi-mode human detection, including motion, micro-motion, and complete stillness. It can classify the target state into categories such as no target, moving target, and stationary presence.
The maximum detection range is up to 11 meters for motion and 10 meters for stationary presence. The detection area covers approximately a 10 × 10 meter space, depending on installation conditions.
The sensor uses internal signal processing to extract additional parameters. It can report distance to the target, signal energy, movement speed, and direction. These values are derived from Doppler shift and frequency changes in the reflected radar signal.
An adaptive background filtering algorithm is implemented to reduce false detections. The sensor learns the static environment and suppresses interference from non-human sources such as moving curtains or airflow.
Field of View and Spatial Coverage
The module provides a wide detection angle of 120° horizontally and vertically. This allows a single sensor to cover large indoor areas with minimal blind spots.
The detection range can be configured in software. This allows developers to limit the sensing distance to a specific zone within a room. It helps prevent unwanted detections through walls or in adjacent areas.
Mounting position affects the effective coverage. Ceiling mounting provides a top-down detection pattern, while wall mounting results in a forward-facing detection cone.
Electrical Characteristics and Interfaces
The sensor operates from a 3.6 V to 5.5 V supply, making it compatible with common microcontroller platforms such as Arduino and ESP32.
It provides two main output interfaces. A UART interface with a baud rate of 9600 is used for detailed data communication, including full radar measurements and configuration commands. A digital OUT pin can be configured as a simple presence signal for low-complexity applications.
The picture below shows the pinout of the module with pins for power (VIN), ground (GND), serial transmit (TX), serial receive (RX), and the digital output signal (OUT):

Additional Sensing and Environmental Features
The C4002 includes a light intensity sensor with a range of 0 to 50 lux. This allows combined presence and ambient light detection in a single module.
The module supports automatic environmental calibration. During operation, it can adapt to the surrounding environment to improve detection accuracy. This reduces manual tuning effort during deployment.
Technical Specification
The following table summarizes the Technical Specification of the mmWave C4004 Sensor.
| Parameter | Specification |
|---|---|
| Operating Voltage | 3.6 V to 5.5 V |
| Operating Frequency | 24.0 GHz to 24.25 GHz |
| Detection Capability | Motion, micro-motion, and stationary human detection |
| Maximum Detection Distance | 11 m (motion), 10 m (stationary presence) |
| Detection Angle | 120° × 120° |
| Detection Area | Up to 10 m × 10 m coverage |
| Output Interfaces | UART and configurable digital OUT pin |
| Light Detection Range | 0 to 50 lux |
| Operating Temperature | −20 °C to 85 °C |
| Module Dimensions | 22 mm × 26 mm |
Using the mmWave C4002 Sensor without Microcontroller
You need to use a microcontroller (or computer) to program specific settings of the mmWave C4002 Sensor. For instance, you can set the detection distance or the detection sensitivity. But once, configured you can use the C4002 without a microcontroller.
The wiring diagram below shows you how to connect an LED the lights up, if the sensor detects a person. You simply need to provide power (5V) to the VIN and GND pins and then can connect an LED with a resistor to the OUP pin:

For most practical applications you probably want to connect a Relay instead of an LED to switch a higher voltage or current device. This is possible but you will need to use a Relay Module with an integrated amplifier circuit, since the OUT pin can’t drive the relay coil directly. Below is an example circuit:

For more information about relay modules see the How To Use A Relay With Arduino, Interfacing a Relay Module With ESP32 and the Control AC devices with Solid State Relay tutorials.
In the next sections I will show you how to connect the mmWave C4002 Sensor to an Arduino or an ESP32.
Connecting the mmWave C4002 Sensor to Arduino
Connecting the mmWave C4002 Sensor to an Arduino UNO is easy. Start by connecting VIN to 5V of the Arduino. Next connect GND to GND. Finally, we connect the UART interface by wiring RX to GPIO 5 and TX to GPIO 4. The picture below shows the complete wiring:

For convenience here a table with the connections you need to make:
| C4002 | Arduino Uno |
|---|---|
| VIN | 5V |
| GND | GND |
| RX | GPIO 5 |
| TX | GPIO 4 |
You won’t even need a breadboard to connect the sensor to an Arduino UNO. Four Dupont cables are sufficient. The photo below shows my wiring:

Connecting the mmWave C4002 Sensor to ESP32
The ESP32 has three serial interfaces and you can configure the pins and interface you want to use. Here I connect TX to GPIO 3 and RX to GPIO 4. We will need to remember this when writing the code. Finally, we connect VIN to 5V and GND to G. The picture below shows the complete wiring:

Note that you need an ESP32 with a 5V output pin and that you need to connect VIN of the C4002 to the 5V pin and not the common 3.3V pin! The picture below shows the pinout of the ESP32-C3 Supermini, I am using here.

Remember that the C4002 needs 3.6V … 5V at VIN and 3.3V are therefore not sufficient. For convenience here a table with the connections you need to make.
| C4002 | ESP32-C3 Supermini |
|---|---|
| VIN | 5V |
| GND | G |
| RX | 4 |
| TX | 3 |
Installing the DFRobot_C4002 library
Before we can write any code, we will need to install the DFRobot_C4002 library. To install this library go to the DFRobot_C4002 repo, click on the green “<> Code” button and then “Download ZIP” to download the library as a ZIP file as shown below:

Then create a new Arduino Sketch, go to Sketch -> Include Library -> Add .ZIP Library … to install the downloaded ZIP library (DFRobot_C4002-master.zip):

Code: Calibrating the C4002
The C4002 requires a calibration process that establishes a baseline of the surrounding environment so that static objects such as walls or furniture are not falsely detected as targets. Since the sensor relies on radar reflections, it must first learn what “normal” looks like in a given space before it can reliably detect human presence or motion.
Calibration is started using the c4002.startEnvCalibration(delay, duration) function, where the delay allows time to clear the area and the duration defines how long the sensor measures the environment. During this period, the sensor records background reflections and builds an internal model. It is important that no people are present, as movement or even stationary presence can negatively affect accuracy.
Once calibration is complete, the sensor compares real-time data against this baseline and only reports meaningful changes. This enables precise detection of both motion and static presence. For best results, recalibration should be repeated whenever the environment changes significantly. The following code performs this calibration process:
// Libraries:
// - DFRobot_C4002 V 1.0.0
// https://github.com/DFRobot/DFRobot_C4002
// - ESP32 Core V 3.3.8
#include "DFRobot_C4002.h"
#if defined(ESP8266) || defined(ARDUINO_AVR_UNO)
SoftwareSerial mySerial(4, 5);
DFRobot_C4002 c4002(&mySerial, 115200);
#elif defined(ESP32)
DFRobot_C4002 c4002(&Serial1, 115200, 3, 4);
#else
DFRobot_C4002 c4002(&Serial1, 115200);
#endif
void setup() {
Serial.begin(115200);
while (!c4002.begin()) {
Serial.println("Can't find C4002!");
delay(1000);
}
c4002.setRunLedState(eLedOn);
delay(50);
c4002.setOutLedState(eLedOn);
delay(50);
delay(3000);
c4002.setReportPeriod(10);
c4002.startEnvCalibration(10, 30); // Delay time:10s,Calibration time:30s
Serial.println("Start calibration:");
}
void loop() {
sRetResult_t retResult = c4002.getNoteInfo();
if (retResult.noteType == eCalibration) {
Serial.print(retResult.calibCountdown);
Serial.println(" s");
if (retResult.calibCountdown == 0) {
Serial.println("Calibration Complete!");
}
}
delay(10);
}
Imports
The sketch begins by including the required library for the sensor. This library provides the DFRobot_C4002 class, which abstracts the communication and control of the sensor.
#include "DFRobot_C4002.h"
Serial Communication
The next code section configures how the sensor communicates with the microcontroller depending on the platform being used. The code checks which board is being compiled for and selects the appropriate serial interface.
#if defined(ESP8266) || defined(ARDUINO_AVR_UNO) SoftwareSerial mySerial(4, 5); DFRobot_C4002 c4002(&mySerial, 115200); #elif defined(ESP32) DFRobot_C4002 c4002(&Serial1, 115200, 3, 4); #else DFRobot_C4002 c4002(&Serial1, 115200); #endif
On boards like the Arduino Uno or ESP8266, hardware serial ports are limited, so a SoftwareSerial instance is created on pins 4 (RX) and 5 (TX). This serial interface is then passed to the DFRobot_C4002 object along with a baud rate of 115200.
On the ESP32, the code uses Serial1, which is a hardware UART. Additionally, pins 3 and 4 are defined for RX and TX.
For other boards, the default Serial1 interface is used without specifying pins, assuming the hardware configuration already defines them.
Setup Function
The setup() function is responsible for initializing serial communication and preparing the sensor for operation.
void setup() {
Serial.begin(115200);
The serial monitor is initialized at 115200 baud so that status messages can be printed for debugging and monitoring.
The next part ensures that the sensor is properly connected and responding.
while (!c4002.begin()) {
Serial.println("Can't find C4002!");
delay(1000);
}
The begin() function attempts to initialize communication with the sensor. If the sensor is not detected, the loop continues to print an error message every second. This effectively blocks execution until the sensor is found.
After successful initialization, the code configures the onboard LEDs of the sensor.
c4002.setRunLedState(eLedOn); delay(50); c4002.setOutLedState(eLedOn); delay(50);
The setRunLedState() function controls the internal running indicator LED, while setOutLedState() controls the output LED. Both are turned on here to provide visual feedback that the sensor is active.
Short delays are added to ensure stable communication between commands.
The code then pauses for a few seconds before continuing.
delay(3000);
Next, the reporting interval is configured.
c4002.setReportPeriod(10);
The setReportPeriod() function sets how frequently the sensor reports data. In this case, the period is set to 10 units, which results in 10 * 0.1 = 1 second reporting cycle. Finally, environmental calibration is started.
c4002.startEnvCalibration(10, 30); // Delay time:10s,Calibration time:30s
Serial.println("Start calibration:");
}
The startEnvCalibration() function initiates calibration with two parameters. The first parameter specifies a delay before calibration begins, and the second defines how long the calibration process lasts. Here, calibration starts after 10 seconds and runs for 30 seconds.
Loop Function
The loop() function continuously monitors the sensor status, particularly the calibration process.
void loop() {
sRetResult_t retResult = c4002.getNoteInfo();
The getNoteInfo() function retrieves the latest status information from the sensor. The returned structure sRetResult_t contains various fields describing the current state.
The code then checks whether the sensor is currently in calibration mode.
if (retResult.noteType == eCalibration) {
The noteType field indicates the type of notification received. When it equals eCalibration, it means the sensor is in the calibration phase. If calibration is ongoing, the remaining time is printed.
Serial.print(retResult.calibCountdown);
Serial.println(" s");
The calibCountdown field contains the number of seconds remaining until calibration completes. Finally, the code detects when calibration has finished.
if (retResult.calibCountdown == 0) {
Serial.println("Calibration Complete!");
}
}
delay(10);
}
When the countdown reaches zero, a message is printed to indicate that calibration is complete.
Output Example
The picture below shows what you will printed on the Serial Monitor while the calibration process is running. As mentioned, you should not be around the sensor during the calibration.

Code: Motion and Presence Detection with C4002
Once the sensor is calibrated it can be used for detection. The following code examples shows you what kind of detections an measures the sensor can perform:
// Libraries:
// - DFRobot_C4002 V 1.0.0
// https://github.com/DFRobot/DFRobot_C4002
// - ESP32 Core V 3.3.8
#include "DFRobot_C4002.h"
#if defined(ESP8266) || defined(ARDUINO_AVR_UNO)
SoftwareSerial mySerial(4, 5);
DFRobot_C4002 c4002(&mySerial, 115200);
#elif defined(ESP32)
DFRobot_C4002 c4002(&Serial1, 115200, 3, 4);
#else
DFRobot_C4002 c4002(&Serial1, 115200);
#endif
void setup() {
Serial.begin(115200);
while (!c4002.begin()) {
Serial.println("Can't find C4002!");
delay(1000);
}
c4002.setRunLedState(eLedOn);
delay(50);
c4002.setOutLedState(eLedOn);
delay(50);
c4002.setOutPinMode(eOutpinMode1); // Motion detected
delay(50);
c4002.setDetectRange(0, 1100); // 0-1100 cm
delay(50);
c4002.setLightThresh(0); //0-50 lux
delay(50);
c4002.setTargetDisappearDelay(1); // 0-65535s
delay(50);
c4002.setReportPeriod(10); // 10 * 0.1s = 1s
delay(50);
}
void loop() {
sRetResult_t retResult = c4002.getNoteInfo();
if (retResult.noteType == eResult) {
Serial.println("---------- RESULTS ------------");
float light = c4002.getLightIntensity();
Serial.print("Light: ");
Serial.print(light);
Serial.println(" lux");
eTargetState_t targetState = c4002.getTargetState();
Serial.print("Target state: ");
if (targetState == eNoTarget) {
Serial.println("No Target");
} else if (targetState == ePresence) {
Serial.println("Static Presence");
} else if (targetState == eMotion) {
Serial.println("Motion");
}
sPresenceTarget_t presenceTarget = c4002.getPresenceTargetInfo();
Serial.print("Presence distance: ");
Serial.print(presenceTarget.distance);
Serial.println(" m");
Serial.print("Presence energy: ");
Serial.println(presenceTarget.energy);
sMotionTarget_t motionTarget = c4002.getMotionTargetInfo();
Serial.print("Motion distance: ");
Serial.print(motionTarget.distance);
Serial.println(" m");
Serial.print("Motion energy: ");
Serial.println(motionTarget.energy);
Serial.print("Motion speed: ");
Serial.print(motionTarget.speed);
Serial.println(" m/s");
Serial.print("Motion direction: ");
if (motionTarget.direction == eAway) {
Serial.println("Away!");
} else if (motionTarget.direction == eNoDirection) {
Serial.println("No Direction!");
} else if (motionTarget.direction == eApproaching) {
Serial.println("Approaching!");
}
}
delay(50);
}
Imports
The sketch begins by including the required sensor library. This library defines the DFRobot_C4002 class and several related data types such as sRetResult_t, sPresenceTarget_t, and sMotionTarget_t.
#include "DFRobot_C4002.h"
Conditional Compilation and Object Initialization
Next we configure the serial interface for UART communication as before. The configuration depends on the microcontroller.
#if defined(ESP8266) || defined(ARDUINO_AVR_UNO) SoftwareSerial mySerial(4, 5); DFRobot_C4002 c4002(&mySerial, 115200); #elif defined(ESP32) DFRobot_C4002 c4002(&Serial1, 115200, 3, 4); #else DFRobot_C4002 c4002(&Serial1, 115200); #endif
On boards such as the Arduino Uno or ESP8266, a SoftwareSerial instance is created using pins 4 and 5. On the ESP32, we are using pins 3 and 4 but you can pick others. Just make sure your hardware wiring matches the pins in theconfiguration.
Setup Function
The setup() function initializes communication and configures the sensor parameters in detail.
void setup() {
Serial.begin(115200);
Serial communication is started to allow debugging and data output via the serial monitor. The next section ensures that the sensor is connected and responsive.
while (!c4002.begin()) {
Serial.println("Can't find C4002!");
delay(1000);
}
The begin() function initializes the sensor. If initialization fails, the code repeatedly prints an error message and retries every second.
After successful initialization, the sensor LEDs are enabled.
c4002.setRunLedState(eLedOn); delay(50); c4002.setOutLedState(eLedOn); delay(50);
These LEDs provide visual feedback about the sensor’s operation. The “Run” LED will blink with a green light while detections are running. The “Out” LED will light up if a person has been detected.
Next we configure the output pin (OUT).
c4002.setOutPinMode(eOutpinMode1); // Motion detected delay(50);
The setOutPinMode() function determines what condition triggers the sensor’s OUT pin. In this case, eOutpinMode1 configures it to indicate motion detection. For other modes, see the eOutpinMode_t type:
typedef enum {
eOutpinMode1 = 0x01, /* Only when motion is detected will a high level be output */
eOutpinMode2 = 0x02, /* A high level is output only when its presence is detected */
eOutpinMode3 = 0x03, /* A high level only appears when motion or presence is detected */
eOutpinModex = 0xFF /* reserved */
} eOutpinMode_t;
Next, the detection range is defined.
c4002.setDetectRange(0, 1100); // 0-1100 cm delay(50);
The setDetectRange() function sets the minimum and maximum detection distances in centimeters. Here, the sensor is configured to detect targets between 0 cm and 1100 cm (11 meters).
The ambient light threshold is configured next.
c4002.setLightThresh(0); //0-50 lux delay(50);
The setLightThresh() function sets the minimum light level required for detection. A value of 0 effectively disables light-based filtering, allowing detection in all lighting conditions.
The delay before a target is considered gone is then set.
c4002.setTargetDisappearDelay(1); // 0-65535s delay(50);
The setTargetDisappearDelay() function defines how long the sensor waits before declaring that a detected target has disappeared. In this case, the delay is set to 1 second.
The reporting interval is configured last.
c4002.setReportPeriod(10); // 10 * 0.1s = 1s delay(50); }
The setReportPeriod() function determines how often the sensor sends data. A value of 10 corresponds to 1 second intervals.
Short delays between configuration commands ensure stable communication with the sensor.
Loop Function
The loop() function continuously reads and processes sensor data.
void loop() {
sRetResult_t retResult = c4002.getNoteInfo();
The getNoteInfo() function retrieves the latest notification from the sensor. The returned structure contains information about the type of data available.
The code checks whether the received data contains detection results.
if (retResult.noteType == eResult) {
The noteType field indicates the type of message. When it equals eResult, the sensor has provided measurement data.
A header is printed to separate output blocks.
Serial.println("---------- RESULTS ------------");
The ambient light intensity is then read.
float light = c4002.getLightIntensity();
Serial.print("Light: ");
Serial.print(light);
Serial.println(" lux");
The getLightIntensity() function returns the measured light level in lux.
Next, the target state is determined.
eTargetState_t targetState = c4002.getTargetState();
Serial.print("Target state: ");
The getTargetState() function indicates whether a target is present and whether it is moving or stationary.
if (targetState == eNoTarget) {
Serial.println("No Target");
} else if (targetState == ePresence) {
Serial.println("Static Presence");
} else if (targetState == eMotion) {
Serial.println("Motion");
}
The code distinguishes between no target, a stationary person, and a moving person.
The presence target information is then retrieved.
sPresenceTarget_t presenceTarget = c4002.getPresenceTargetInfo();
Serial.print("Presence distance: ");
Serial.print(presenceTarget.distance);
Serial.println(" m");
Serial.print("Presence energy: ");
Serial.println(presenceTarget.energy);
The getPresenceTargetInfo() function returns a structure containing the distance and signal energy of a stationary target. The energy value reflects the strength of the detected signal.
Next, motion target data is obtained.
sMotionTarget_t motionTarget = c4002.getMotionTargetInfo();
Serial.print("Motion distance: ");
Serial.print(motionTarget.distance);
Serial.println(" m");
Serial.print("Motion energy: ");
Serial.println(motionTarget.energy);
The getMotionTargetInfo() function provides similar data for moving targets.
Additional motion parameters are also printed.
Serial.print("Motion speed: ");
Serial.print(motionTarget.speed);
Serial.println(" m/s");
The speed of the moving target is given in meters per second.
Finally, the direction of movement is evaluated.
Serial.print("Motion direction: ");
if (motionTarget.direction == eAway) {
Serial.println("Away!");
} else if (motionTarget.direction == eNoDirection) {
Serial.println("No Direction!");
} else if (motionTarget.direction == eApproaching) {
Serial.println("Approaching!");
}
The sensor can determine whether the target is moving away, approaching, or if no clear direction is detected.
At the end of the loop, a short delay is added.
} delay(50); }
This delay prevents excessive polling and ensures stable communication.
Output Example
The following picture shows you what detection information is printed to the Serial Monitor:

Conclusions
In this tutorial you learned how to connect the mmWave C4002 sensor to an Arduino or an ESP32 for motion and presence detection. In comparison to Passive Infrared (PIR) motion sensors, the radar-based C4002 is more reliable and can detect stationary persons in specific distance ranges.
If you only need a binary detection signal (detected, not detected), you can program the sensor once (e.g. set detection distance) via a microcontroller and then use the OUT pin. For more complex control actions you can program the microcontroller to react to the sensor measurements.
Note that there is also the very similar mmWave C4001, which has a greater range of 16–25 meters but less built-in intelligence. This means you typically need to handle more logic on the microcontroller side, such as filtering, interpreting states, or building higher-level behavior, when using the C4001.
For additional information about the mmWave C4002 sensor see the Wiki page and the repo that has other code examples.
If you have any questions feel free to leave them in the comment section.
Happy Tinkering ; )
Stefan is a professional software developer and researcher. He has worked in robotics, bioinformatics, image/audio processing and education at Siemens, IBM and Google. He specializes in AI and machine learning and has a keen interest in DIY projects involving Arduino and 3D printing.

