A high-efficiency, battery-powered wind speed sensor (Anemometer) using the ESP32-C6. This project leverages the ULP (Ultra-Low Power) co-processor to monitor wind pulses while the main CPU is in Deep Sleep, allowing for years of battery life.
The project uses the LP/ULP core to count anemometer pulses while the main CPU stays in deep sleep, then wakes periodically to compute wind speed and advertise telemetry over BLE using BTHome v2 format.
- Ultra-Low Power: Can be as low as ~15µA in deep sleep on the ESP32-C6 alone. Actual current depends on the board and any attached hardware. The main cores only wake up to calculate and transmit data.
- BTHome V2 Protocol: Works natively with Home Assistant via Bluetooth—no custom integration or ESPHome YAML required. See bthome.io.
- Waterproof: Designed to be waterproof for outdoor use.
- Temperature Sensor: Includes the ESP32-C6 internal temperature reading alongside wind speed and battery level telemetry.
- Intelligent Reporting:
- Wind Detected: Typically reports on the next 5-second wake cycle while wind is present.
- Change Detection: Tries to report a speed change as soon as it is noticed on the next wake cycle.
- Heartbeat: Sends a periodic "still alive" update, usually about every 60 seconds during calm periods.
- Hardware Debouncing: ULP-based software sampling filters out mechanical reed switch "bounce."
If you are building a similar low-power sensor, these patterns are practical to reuse:
- Keep the main CPU in deep sleep and let the LP/ULP RISC-V core handle pulse counting.
- Wake on a fixed interval, compute pulse delta and wind speed, then advertise only when needed.
- Use BTHome v2 so the sensor works directly with Home Assistant over BLE.
- Add a temperature reading to the same BLE payload so you can track the device environment without extra hardware.
- Add a heartbeat report so the device still publishes when conditions are calm.
- Filter reed-switch bounce in ULP software instead of relying only on hardware.
- Use RTC-capable GPIOs for ULP inputs on ESP32-C6, and avoid strapping pins.
- Add a capacitor across the battery sense divider to stabilize high-impedance ADC readings.
- Keep timing, calibration, BLE, and ADC values in a single header so they are easy to tune.
- Treat wind-speed calibration as a single empirical factor that can be adjusted without changing the rest of the flow.
- Preserve the PlatformIO + ESP-IDF setup and the debug/release sdkconfig defaults for repeatable builds.
- ESP32-C6 Board (e.g., ESP32-C6 Super Mini).
- For ESP32 and ESP32-S3 boards, check the ESP32/ESP32-S3 anemometer project.
- Anemometer (3-cup type with a Reed Switch).
- Battery: Li-ion 18650 or LiPo.
| Item | Quantity | Notes |
|---|---|---|
| ESP32-C6 Super Mini | 1 | Very cheap and low power board |
| 3D-printed anemometer | 1 | STL files for the 3D-printed parts |
| 608 bearing 8x22x7 mm | 1 | Very common bearing for shaft support |
| M3 heat-set insert | 3 | For connecting the main body to the support and collar |
| M3 hex socket cap screw M3x10 | 3 | Fastens the main body to the support |
| Reed switch | 1 | Wind pulse sensor |
| Magnet 6x3 mm | 2 | For triggering the reed switch |
| O-Ring 8x2 mm | 1 | To seal the base of the rotor and the support |
| Battery | 1 | Li-ion 18650 or LiPo |
| Battery Holder | 1 |
Connect the reed switch between SENSOR_GPIO pin and GND (no need of external pull-up resistor because internal pull-up resistor is used).
SENSOR_PIN
|
|
+ |
+--- Reed switch
+ |
|
|
GND
For battery monitoring, a 470 kΩ resistor divider is required. For stable ADC readings with high-value resistors, place a 0.1uF capacitor from ADC pin to GND.
BATTERY_ADC_GPIO
|
|
B+ -----/\/\/\/------+-----/\/\/\/-----+----- GND (B-)
470k | 470k |
| |
+-------||--------+
0.1uF
(optional)
- PlatformIO
- ESP-IDF framework via PlatformIO (already defined in
platformio.ini)
Advertising payload includes BTHome v2 service data with:
- Packet ID (object id
0x00) - Battery percentage (object id
0x01) - ESP32-C6 internal temperature in 0.01 °C units (object id
0x02) - Wind speed in 0.01 m/s units (object id
0x44)
Device name in advertisement: Wind Sensor
Note: the temperature value is the ESP32-C6 internal chip temperature, not an ambient room sensor. Because the CPU spends most of its time in deep sleep, this reading is typically close to ambient. The internal temperature sensor is configured for a -10 to 80 °C range.
Edit src/anemometer.h:
#define SENSOR_PIN 6 // GPIO used for the anemometer pulse input
// Must be RTC-capable pin: GPIO0-GPIO7 on ESP32-C6
// Avoid GPIO4 and GPIO5 which are used for strapping
#define SLEEP_DURATION 5 // Deep-sleep interval in seconds between wakeups
#define HEARTBEAT_INTERVAL 60 // Force a periodic telemetry heartbeat every N seconds
#define DEBOUNCE_INTERVAL_CYCLES 1000 // Minimum ULP clock cycles between valid edges to filter out noise (debouncing)
/* Anemometer conversion constants */
#define PULSES_PER_ROTATION 2.0f // Sensor pulses generated for one full rotor rotation
#define RADIUS 0.078f // Rotor radius in meters (center to cup midpoint)
#define CALIBRATION_FACTOR 2.5f // Empirical multiplier to match real wind speed
/* BLE/BTHome beacon behavior */
#define BLE_ADV_DURATION_MS 1000 // Advertising window duration after each wakeup
#define BLE_ADV_INTERVAL_UNITS 32 // BLE adv interval units (0.625 ms each): 32 = 20 ms
/* Battery measurement via ADC */
#define BATTERY_ADC_GPIO 0 // GPIO connected to battery sense divider output
// Must be an ADC-capable pin: GPIO0-GPIO6 on ESP32-C6
// Avoid GPIO4 and GPIO5 which are used for strapping
#define BATTERY_ADC_SAMPLES 1 // Number of ADC readings averaged per measurement
#define BATTERY_VOLTAGE_DIVIDER_RATIO 2.0f // Scale factor from ADC node voltage to battery voltage
#define BATTERY_MIN_MV 3200 // Battery voltage mapped to 0%
#define BATTERY_MAX_MV 4100 // Battery voltage mapped to 100%- The main CPU enters deep sleep.
- LP/ULP firmware monitors the sensor pin (
SENSOR_PIN) and increments pulse count on valid rising edges. - Every wake interval (
SLEEP_DURATION), the main CPU wakes up. - Firmware computes:
- pulse delta
- RPM
- wind speed (m/s and km/h)
- battery voltage and percentage
- ESP32-C6 internal temperature
- BLE advertising is sent when:
- wind value changed, or
- heartbeat interval is reached
- Device returns to deep sleep.
This project is licensed under the MIT License - see the LICENSE file for details.



