nRF52840 Zephyr CSC
A Bluetooth Low Energy Cycling Speed and Cadence (CSC) sensor built on Zephyr RTOS for the nRF52840.

Overview
This project implements a BLE CSC sensor that broadcasts wheel revolution data to cycling computers and smartphone apps (however Strava doesn’t support directly connecting CSC sensor). It uses a reed switch triggered by a magnet on the wheel to count revolutions and transmits the data over Bluetooth.
I’ve previously built a similar CSC bike meter using an ESP32 with ESP-IDF, but the ESP32 proved to be too power hungry for a battery-powered sensor. After completing Nordic Semiconductor’s Zephyr courses, I decided to migrate the project to the nRF52840 microcontroller to take advantage of its superior low-power capabilities.
Lots of different Zephyr functions and features are used in this project, including GPIO interrupts (well duhh), atomic variables for thread-safe counting, BLE stack, internal SAR ADC for battery monitoring, and more. The code is structured to be modular and easy to understand, making it a great reference for anyone looking to build their own BLE sensor with Zephyr.
How It Works
Software Flow

The device starts in System OFF and wakes on a power button press via the nRF52840 GPIO SENSE mechanism. On boot, three concurrent activities run:
| Thread / Context | Role |
|---|---|
| Main thread (1s loop) | Reads reed switch counter, sends CSC data over BLE, reports battery level via BAS |
Power LED thread (K_THREAD_DEFINE) | Heartbeat LED on P0.15 — fast blink (200ms) when disconnected, slow (1000ms) when connected |
Power control (INPUT_CALLBACK_DEFINE) | 2-second long press triggers graceful shutdown (disable IRQs, stop BLE, kill LED) then System OFF |
Reed switch & CSC: Each magnet pass triggers a falling-edge GPIO interrupt. The ISR applies 20ms timestamp-based debouncing and atomically increments a counter. The main thread reads this every second and transmits it as a CSC notification containing Cumulative Wheel Revolutions (32-bit) and Last Wheel Event Time (16-bit, 1/1024s units). The receiving app calculates speed:
Speed = (wheel_circumference x Δrevolutions) / Δtime
Battery monitoring: Two sources — internal VDDHDIV5 ADC channel and an external 100k+100k voltage divider on AIN7 (P0.31) read through a fuel gauge composite driver with an OCV lookup table for SoC estimation.
Features
- Bluetooth Low Energy (BLE) Cycling Speed and Cadence Service
- Reed switch input with 20ms timestamp-based debouncing
- Power on/off via long press (2s) using Zephyr
input-longpressdriver - System OFF with GPIO SENSE wake-up (~1uA sleep current)
- Heartbeat LED with BLE connection status indication (fast/slow blink)
- Dual battery monitoring: internal VDDHDIV5 + external voltage divider with fuel gauge
- BLE Battery Service (BAS) notifications with OCV-based SoC estimation
- USB CDC ACM console output
- Segger RTT debugging support
Hardware Used
- Board: Nice!Nano / Pro Micro nRF52840 (or compatible clone)
- Reed Switch: Magnetic reed switch (normally open)
- Magnet: Small neodymium magnet attached to wheel spoke
- Battery: 3.7V LiPo battery (optional, can run from USB)
Pin Configuration
Device tree overlay file translation:
| Function | GPIO Pin | Notes |
|---|---|---|
| Reed Switch | P0.22 | Pull-up, Active Low, Falling Edge IRQ (gpio-keys) |
| Power Button | P0.08 | Pull-up, Active Low, 2s long press to power off |
| Heartbeat LED | P0.15 | Active High, DK_LED1 via dk_buttons_and_leds |
| Battery ADC (int) | VDDHDIV5 | Internal 1/5 divider, 12-bit, 8x oversampling |
| Battery ADC (ext) | P0.31 (AIN7) | External 100k+100k voltage divider, fuel gauge composite |
Building
west build -b promicro_nrf52840 .
Flashing
west flash
Or using nrfjprog:
nrfjprog --program build/zephyr/zephyr.hex --chiperase --verify --reset
Configuration
Bluetooth Settings
- Device Name:
nRF52840 Zephyr CSC - Device Appearance: Cycling Speed and Cadence Sensor (1157)
Adjusting Debounce Time
Edit src/reed_switch/reed_switch.h:
#define REED_SWITCH_DEBOUNCE_MS 20 // Adjust if needed
Usage and demo
- Mount the reed switch on your bike frame near the wheel
- Attach a magnet to a wheel spoke, aligned to pass the reed switch
- Press the power button to wake from System OFF
- LED starts blinking fast (200ms) - device is advertising
- Open your cycling app and scan for
nRF52840 Zephyr CSC - Pair - LED slows down (1000ms) indicating active connection
- Start riding - speed will be calculated from wheel revolutions
- Long press power button (2s) to power off
Testing
- Connect via USB and open serial console (115200 baud)
- Use nRF Connect app to verify BLE advertising
- Subscribe to CSC Measurement characteristic (UUID:
0x2A5B) - Trigger reed switch manually - console shows event counts
Example console output:
Battery ADC initialized successfully
Starting Cycling Speed and Cadence application
Reed switch initialized on gpio@50000000 pin 22 with 20 ms debounce
GPIO interrupt will wake system from sleep
CSC sensor ready - reed switch events will be sent as wheel revolutions
Reed switch event detected! Total count: 1
CSC notify: wheel_revs=1, event_time=1024
Battery voltage: 4150 mV
Battery level: 95%
Future Improvements
- Add crank cadence sensor support (second reed switch)
- Further improve battery consumption
- Add non-volatile storage for cumulative distance
- Create custom PCB design
- Add OTA firmware update via MCUboot
License
MIT License - See LICENSE file for details.
Author
Ivan Spasić
