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 (e.g., Strava, Wahoo, Garmin Connect). 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.
This project serves as a hands-on application of my newly acquired Zephyr RTOS knowledge. What started as a BLE exercise quickly led me down the rabbit hole of GPIO interrupts, ADC configuration, and low-power optimization.
How It Works
CSC Protocol
The Bluetooth Cycling Speed and Cadence (CSC) service transmits two key values:
- Cumulative Wheel Revolutions (32-bit) - Total wheel rotations since power-on
- Last Wheel Event Time (16-bit) - Timestamp in 1/1024 second units
The receiving device (phone/bike computer) calculates speed by comparing consecutive measurements:
Speed = (wheel_circumference × Δrevolutions) / Δtime
Sensor Implementation
┌─────────────┐ magnetic ┌──────────────┐
│ Magnet │ ──── field ───▶ │ Reed Switch │
│ (on spoke) │ │ (P0.22) │
└─────────────┘ └──────┬───────┘
│
│ GPIO IRQ
▼
┌──────────────────────────────────────────────────────────────┐
│ nRF52840 │
│ │
│ ┌─────────────────────┐ │
│ │ ISR Context │ │
│ │ ┌───────────────┐ │ │
│ │ │ Debounce │ │ │
│ │ │ 20ms │ │ │
│ │ └───────┬───────┘ │ │
│ │ ▼ │ │
│ │ ┌───────────────┐ │ ┌─────────────────────────┐ │
│ │ │ Atomic Counter│──┼───────▶│ Main Thread │ │
│ │ │ + Timestamp │ │ read │ ┌───────────────────┐ │ │
│ │ └───────────────┘ │ │ │ k_sleep(1 sec) │ │ │
│ └─────────────────────┘ │ │ ↓ │ │ │
│ │ │ Read counter │ │ │
│ │ │ ↓ │ │ │
│ ┌─────────────────────┐ │ │ bt_gatt_notify() │ │ │
│ │ BLE Thread │◀───────│ └───────────────────┘ │ │
│ │ (Zephyr BT Stack) │ call └─────────────────────────┘ │
│ │ │ │ │
│ └──────────┼──────────┘ │
│ │ │
└─────────────┼────────────────────────────────────────────────┘
│
▼
Cycling App
Each magnet pass triggers a falling-edge GPIO interrupt. The ISR applies timestamp-based debouncing and increments an atomic counter. The main thread wakes every second, reads the counter, and calls bt_gatt_notify() which queues the notification for the BLE thread to transmit.
Features
- Bluetooth Low Energy (BLE) Cycling Speed and Cadence Service
- Reed switch input with 20ms hardware debouncing
- Battery level monitoring via internal VDDHDIV5 ADC
- BLE Battery Service (BAS) notifications
- USB CDC ACM console output
- Segger RTT debugging support
Hardware Requirements
- 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)
Wiring
Reed Switch
├── Pin 1 ──▶ GND
└── Pin 2 ──▶ P0.22
Battery (LiPo)
├── (+) ────▶ VBAT / RAW
└── (-) ────▶ GND
Software Requirements
- Zephyr RTOS v3.5+ / nRF Connect SDK v2.5+
- West build tool
- nRF Command Line Tools (for flashing)
I developed this project using the Ac6 Zephyr Workbench VSCode extension.
Pin Configuration
| Function | GPIO Pin | Notes |
|---|---|---|
| Reed Switch | P0.22 | Pull-up, Active Low, Falling Edge IRQ |
| Battery ADC | VDDHDIV5 | Internal 1/5 divider, 12-bit resolution |
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
- Mount the reed switch on your bike frame near the wheel
- Attach a magnet to a wheel spoke, aligned to pass the reed switch
- Power on the device (USB or battery)
- Open your cycling app and scan for
nRF52840 Zephyr CSC - Pair and start riding - speed will be calculated from wheel revolutions
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)
- Implement deep sleep between BLE advertising intervals
- Add non-volatile storage for cumulative distance
- Create custom PCB design
- Add OTA firmware update via MCUboot
License
MIT License - See LICENSE
Author
Ivan Spasić