Spas Tech

nRF52840 Zephyr CSC

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

CSC Sensor

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

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 / ContextRole
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-longpress driver
  • 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:

FunctionGPIO PinNotes
Reed SwitchP0.22Pull-up, Active Low, Falling Edge IRQ (gpio-keys)
Power ButtonP0.08Pull-up, Active Low, 2s long press to power off
Heartbeat LEDP0.15Active High, DK_LED1 via dk_buttons_and_leds
Battery ADC (int)VDDHDIV5Internal 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

  1. Mount the reed switch on your bike frame near the wheel
  2. Attach a magnet to a wheel spoke, aligned to pass the reed switch
  3. Press the power button to wake from System OFF
  4. LED starts blinking fast (200ms) - device is advertising
  5. Open your cycling app and scan for nRF52840 Zephyr CSC
  6. Pair - LED slows down (1000ms) indicating active connection
  7. Start riding - speed will be calculated from wheel revolutions
  8. Long press power button (2s) to power off

Testing

  1. Connect via USB and open serial console (115200 baud)
  2. Use nRF Connect app to verify BLE advertising
  3. Subscribe to CSC Measurement characteristic (UUID: 0x2A5B)
  4. 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ć

References