Spas Tech

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:

  1. Cumulative Wheel Revolutions (32-bit) - Total wheel rotations since power-on
  2. 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

  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. Power on the device (USB or battery)
  4. Open your cycling app and scan for nRF52840 Zephyr CSC
  5. Pair and start riding - speed will be calculated from wheel revolutions

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)
  • 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ć

References