Last Updated on 10 augustus 2023 by Syds
Cycling cadence & Speed sensor
Onlangs m’n Polar M600 sporthorloge vervangen door een Garmin Forerunner 955. Daar moeten natuurlijk van allerlei sensoren aan gekoppeld worden. Je kunt die sensoren uiteraard kant-en-klaar kopen van de bekende merken zoals Polar, Garmin, Wahoo etc. Maar veel leuker is om ze zelf te maken. Als inspiratiebron heb ik de Wahoo Blue SC Speed en Candanssensor genomen:
Mijn opzet is als volgt:
- Centraal staat een Seeed Xiao Studio ESP32C3 microcontroller, deze zorgt voor de rekenkracht en (BLE, Bluetooth) connectiviteit, is ultraklein en gebruikt weinig stroom
- Deze wordt gevoed door een 3.7v oplaadbare Lipo batterij
- De magneten op de spaak en crank worden uitgelezen met een reed switch
- Om stroom te besparen wordt de ESP32C3 microcontroller automatisch in ‘Deepsleep’ modus gezet na 60 seconden idle te zijn geweest
- Een draai aan het wiel of de crank zorgt ervoor dat de ESP32C3 microcontroller wordt opgestart
- De wiel- en crank rotaties worden op basis van de GATT-specificaties via bluetooth gecommuniceerd naar afnemende devices (Garmin horloge, app op telefoon etc.)
Te bestellen artikelen:
Artikel | Aantal | Prijs p/stuk | Totaal | Waar verkrijgbaar |
Seeed Xiao Studio ESP32C3 | 1 | € 6,50 | € 6,50 | kiwi-electronics.com |
3.7v Lipo accu 2000mAh | 1 | € 4,42 | € 4,42 | Aliexpress.com |
Reed switch | 2 | € 0,08 | € 0,16 | Aliexpress.com |
RGB led cathode 5mm | 1 | € 0,07 | € 0,15 | Aliexpress.com |
Weerstand 220Ohm | 3 | Op voorraad | ||
Weerstand 10K Ohm | 2 | Op voorraad | ||
Weerstand 220K Ohm | 2 | Op voorraad | ||
JST connector 2-voudig PCB mount | 1 | Op voorraad | ||
Male header 2-voudig, haaks | 3 | Op voorraad | ||
Male headers 7-voudig | 2 | Op voorraad | ||
PLA Filament 1.75mm | ? meter | € 0,068 | Op voorraad | |
Rrskit 360 Graden Roterende Stuur Houder Voor Garmin | 1 | € 4,15 | € 4,15 | Aliexpress.com |
Benodigdheden:
- USB-C kabel + laptop/desktop
Gereedschap:
- Soldeerbout + tin
- Tangetje
- 3D printer
Geinstalleerd en up-and-running:
- python 3.10.7 of hoger
- esptool (pip install esptool)
- ampy (pip install adafruit-ampy)
Stap 1. Proefopstelling gemaakt
De Seeed Xiao Studio ESP32C3 heb ik voorzien van Micropython (zie ook mijn tutorial Micropython flashen op Seeed studio Xiao ESP32C3). Om de in de stap 2. ontwikkelde python code te testen heb ik twee 7-pins headers op het Xiao bordje gesoldeerd (alleen die pins gesoldeerd die gebruikt worden zodat ik ze gemakkelijk weer los kan solderen). Vervolgens het bordje op een breadboard gedrukt. Om de reed switch sensoren te emuleren heb ik twee tactile switches (vlakke puls schakelaars) m.b.v. een 10K weerstand op de breadboard gedrukt en een en ander met jumperkabels verbonden conform onderstaand schema:
Om het meten van het batterijspanning te simuleren heb ik een voltage devider van 10 en 47K toegevoegd die is aangesloten op de 5v pin van de ESP32C3. Dit brengt het voltage terug naar ongeveer 4.1v, vergelijkbaar met de piekspanning van een 3.7v Lipo batterij die volledig opgeladen is. Uiteraard kan de ESP32C3 geen 4.1v verdragen op zijn pins, daarom een twee voltage divider toepast in de vorm van een 10K en 4.7K weerstand in serie met een B100K potentiometer. Met behulp van de potentiometer kan het verloop van de batterijspanning gesimuleerd worden.
Tot slot een RGB Led toegevoegd die de status van de Cadencesensor gaat aangeven, namelijk:
- Rood: Onvoldoende batterijspanning
- Groen: Sensor operationeel
- Blauw: Sensor via bluetooth geconnect aan device
Stap 2. Python script ontwikkeld
Als basis heb ik de C++ code van Neal Markham gebruikt, te vinden op hackster.io: https://particle.hackster.io/neal_markham/ble-bicycle-speed-sensor-f60b80. Deze code heb ik omgezet naar Micropython en verder vervolmaakt.
from machine import deepsleep, Pin, ADC
import machine
import bluetooth
from ble_advertising import advertising_payload
from micropython import const
import time
import struct
import esp32
UPDATE_INTERVAL_MS = 2000
DEEP_SLEEP_INTERVAL_MS = 60000
lastUpdate = 0
lastwheelUpdate = 0
lastcrankUpdate = 0
swCount = 0
wheel_rev_period = 0
crank_rev_period = 0
debouncePeriod = const(150) # interrupt debounce period, per magnet passby
lastInterruptMillis = 0
# Battery level Variables
lastBattery = 33
minBattery = 30 # minBattery/10 = minimum voltage to prevent Lipo battery from damage
# Cycling Variables
cum_wheel_rev = 0
last_wheel_event = 0
cum_cranks = 0
last_crank_event = 0
# Cycling Speed and Cadence configuration
GATT_CSC_UUID = const(0x1816)
GATT_CSC_MEASUREMENT_UUID = const(0x2A5B)
GATT_CSC_FEATURE_UUID = const(0x2A5C)
GATT_SENSOR_LOCATION_UUID = const(0x2A5D)
GATT_SC_CONTROL_POINT_UUID = const(0x2A55)
# Device Information configuration
GATT_DEVICE_INFO_UUID = const(0x180A)
GATT_MANUFACTURER_NAME_UUID = const(0x2A29)
GATT_MODEL_NUMBER_UUID = const(0x2A24)
# CSC Measurement flags
CSC_MEASUREMENT_WHEEL_REV_PRESENT = const(0x01)
CSC_MEASUREMENT_CRANK_REV_PRESENT = const(0x02)
# CSC feature flags
CSC_FEATURE_WHEEL_REV_DATA = const(0x01)
CSC_FEATURE_CRANK_REV_DATA = const(0x02)
CSC_FEATURE_MULTIPLE_SENSOR_LOC = const(0x04)
# Sensor location enum
SENSOR_LOCATION_OTHER = const(0)
SENSOR_LOCATION_TOP_OF_SHOE = const(1)
SENSOR_LOCATION_IN_SHOE = const(2)
SENSOR_LOCATION_HIP = const(3)
SENSOR_LOCATION_FRONT_WHEEL = const(4)
SENSOR_LOCATION_LEFT_CRANK = const(5)
SENSOR_LOCATION_RIGHT_CRANK = const(6)
SENSOR_LOCATION_LEFT_PEDAL = const(7)
SENSOR_LOCATION_RIGHT_PEDAL = const(8)
SENSOR_LOCATION_FROT_HUB = const(9)
SENSOR_LOCATION_REAR_DROPOUT = const(10)
SENSOR_LOCATION_CHAINSTAY = const(11)
SENSOR_LOCATION_REAR_WHEEL = const(12)
SENSOR_LOCATION_REAR_HUB = const(13)
SENSOR_LOCATION_CHEST = const(14)
SENSOR_LOCATION_SPIDER = const(15)
SENSOR_LOCATION_CHAIN_RING = const(16)
# Bluetooth
_ADV_APPEARANCE_CADENCE_SENSOR = const(1155) # org.bluetooth.characteristic.gap.appearance.xml
sensor_name = const("myCadenceSensor")
# SC Control Point op codes
SC_CP_OP_SET_CUMULATIVE_VALUE = const(1)
SC_CP_OP_START_SENSOR_CALIBRATION = const(2)
SC_CP_OP_UPDATE_SENSOR_LOCATION = const(3)
SC_CP_OP_REQ_SUPPORTED_SENSOR_LOCATIONS = const(4)
SC_CP_OP_RESPONSE = const(16)
# SC Control Point response values
SC_CP_RESPONSE_SUCCESS = const(1)
SC_CP_RESPONSE_OP_NOT_SUPPORTED = const(2)
SC_CP_RESPONSE_INVALID_PARAM = const(3)
SC_CP_RESPONSE_OP_FAI = const(4)
# Battery level
BLE_SIG_UUID_BATTERY_SVC = const(0x180F)
BLE_SIG_BATTERY_LEVEL = const(0x2A19)
# IRQ
_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_INDICATE_DONE = const(20)
# CSC simulation configuration
CSC_FEATURES = (CSC_FEATURE_WHEEL_REV_DATA | CSC_FEATURE_CRANK_REV_DATA | CSC_FEATURE_MULTIPLE_SENSOR_LOC)
CSC_UUID = bluetooth.UUID(GATT_CSC_UUID) # "Cycling Speed and Cadence" org.bluetooth.service.cycling_speed_and_cadence 0x1816 GSS
CSC_Measurement = (bluetooth.UUID(GATT_CSC_MEASUREMENT_UUID), bluetooth.FLAG_NOTIFY,) # "CSC Measurement" org.bluetooth.characteristic.csc_measurement 0x2A5B
CSC_Feature = (bluetooth.UUID(GATT_CSC_FEATURE_UUID), bluetooth.FLAG_READ,) # "CSC Feature" org.bluetooth.characteristic.csc_feature 0x2A5C
Sensor_Location = (bluetooth.UUID(GATT_SENSOR_LOCATION_UUID), bluetooth.FLAG_READ,) # "Sensor location" org.bluetooth.characteristic.sensor_location 0x2A5D
SC_ControlPoint = (bluetooth.UUID(GATT_SC_CONTROL_POINT_UUID), bluetooth.FLAG_INDICATE | bluetooth.FLAG_WRITE,) # "SC ControlPoint" org.bluetooth.characteristic.sc_control_point 0x2A55
CyclingSpeedAndCadenceService = (CSC_UUID, (CSC_Measurement, CSC_Feature, Sensor_Location, SC_ControlPoint,),)
# The battery level service allows the battery level to be monitored
BatteryLevel_UUID = bluetooth.UUID(BLE_SIG_UUID_BATTERY_SVC)
BatteryLevel = (bluetooth.UUID(BLE_SIG_BATTERY_LEVEL), bluetooth.FLAG_NOTIFY,)
BatteryLevel_service = (BatteryLevel_UUID, (BatteryLevel,),)
Services = (CyclingSpeedAndCadenceService, BatteryLevel_service,)
def millis():
return round(time.time()*1000)
def switchIsr(pin):
global swCount, wheel_rev_period, lastwheelUpdate, lastcrankUpdate, cum_wheel_rev, last_wheel_event, debouncePeriod
global cum_cranks, crank_rev_period, last_crank_event, lastInterruptMillis
if (millis() - lastInterruptMillis > debouncePeriod):
swCount += 1
lastInterruptMillis = millis()
if (pin == Pin(4)):
wheel_rev_period = millis() - lastwheelUpdate
lastwheelUpdate = millis()
cum_wheel_rev += 1
last_wheel_event += wheel_rev_period
if (pin == Pin(2)):
crank_rev_period = millis() - lastcrankUpdate
lastcrankUpdate = millis()
cum_cranks += 1
last_crank_event += crank_rev_period
measurements_update()
print("Interrupt handling")
def blecsc_simulate_speed_and_cadence():
global wheel_rev_period, crank_rev_period
global cum_wheel_rev, last_wheel_event, cum_cranks, last_crank_event
lastInterruptMillis = millis() # fake Interrupt on D0 & D2
# Calculate simulated measurement values
wheel_rev_period = (36*64*WHEEL_CIRCUMFERENCE_MM) / (625*csc_sim_speed_kph)
cum_wheel_rev += 1
last_wheel_event += wheel_rev_period
crank_rev_period = (60*64) / csc_sim_crank_rpm
cum_cranks += 1
last_crank_event += crank_rev_period
def measurements_update():
global swCount
print("Measurement Update Loop")
if (swCount):
# unsigned long currentTime = millis()
swCount = 0
print("Wheel_Rev_Period : %d", wheel_rev_period)
print("LastwheelUpdate : %d", lastwheelUpdate)
print("Cum_Wheel_Rev : %d", cum_wheel_rev)
print("Crank_Rev_Period : %d", crank_rev_period)
print("LastcrankUpdate : %d", lastcrankUpdate)
print("Cum. Cranks : %d", cum_cranks)
print("Finished Measurement Update Loop")
def convert(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min
class BLEcsc:
def __init__(self, ble, name=sensor_name):
self._ble = ble
self._ble.active(True)
self._ble.irq(self._irq)
((self._CSC_Measurement_handle, self._CSC_Feature_handle, self._Sensor_Location_handle, self._SC_ControlPoint_handle,), (self._BatteryLevel_handle,), ) = self._ble.gatts_register_services((Services))
self._connections = set()
self._payload = advertising_payload(
name=name, services=[CSC_UUID], appearance=_ADV_APPEARANCE_CADENCE_SENSOR
)
self._advertise()
def _irq(self, event, data):
# Track connections so we can send notifications.
if event == _IRQ_CENTRAL_CONNECT:
conn_handle, _, _ = data
self._connections.add(conn_handle)
elif event == _IRQ_CENTRAL_DISCONNECT:
conn_handle, _, _ = data
self._connections.remove(conn_handle)
# Start advertising again to allow a new connection.
self._advertise()
elif event == _IRQ_GATTS_INDICATE_DONE:
conn_handle, value_handle, status = data
def set_csc(self, csc, notify=False, indicate=False):
# Write CSC Feature
self._ble.gatts_write(self._CSC_Feature_handle, struct.pack("2s", CSC_FEATURES.to_bytes(2, 'little'))) # Change to your setup
if notify or indicate:
for conn_handle in self._connections:
if notify:
# Notify connected centrals.
self._ble.gatts_notify(conn_handle, self._CSC_Feature_handle)
if indicate:
# Indicate connected centrals.
self._ble.gatts_indicate(conn_handle, self._CSC_Feature_handle)
# Write sensor location
self._ble.gatts_write(self._Sensor_Location_handle, struct.pack("1s", SENSOR_LOCATION_RIGHT_CRANK.to_bytes(1, 'little'))) # Change to your own sensor location
if notify or indicate:
for conn_handle in self._connections:
if notify:
# Notify connected centrals.
self._ble.gatts_notify(conn_handle, self._Sensor_Location_handle)
if indicate:
# Indicate connected centrals.
self._ble.gatts_indicate(conn_handle, self._Sensor_Location_handle)
# Write the local value, ready for a central to read.
self._ble.gatts_write(self._CSC_Measurement_handle, struct.pack("%ds" % 11, csc))
if notify or indicate:
for conn_handle in self._connections:
if notify:
# Notify connected centrals.
self._ble.gatts_notify(conn_handle, self._CSC_Measurement_handle)
if indicate:
# Indicate connected centrals.
self._ble.gatts_indicate(conn_handle, self._CSC_Measurement_handle)
# Write batterylevel
self._ble.gatts_write(self._BatteryLevel_handle, struct.pack("<B", convert(lastBattery, 29, 33, 0, 100)))
if notify or indicate:
for conn_handle in self._connections:
if notify:
# Notify connected centrals.
self._ble.gatts_notify(conn_handle, self._BatteryLevel_handle)
if indicate:
# Indicate connected centrals.
self._ble.gatts_indicate(conn_handle, self._BatteryLevel_handle)
def _advertise(self, interval_us=500000):
self._ble.gap_advertise(interval_us, adv_data=self._payload)
# Init lastInterruptMillis, lastwheelUpdate and lastcrankUpdate
lastInterruptMillis = millis()
lastwheelUpdate = millis()
lastcrankUpdate = millis()
# Set up pin modes for input, interrupt for input
D0 = Pin(2, Pin.IN, Pin.PULL_DOWN) # Wheel reed-sensor GPIO2
D0.irq(trigger=Pin.IRQ_FALLING, handler=switchIsr)
D2 = Pin(4, Pin.IN, Pin.PULL_DOWN) # Crank reed-sensor GPIO4
D2.irq(trigger=Pin.IRQ_FALLING, handler=switchIsr)
D1 = ADC(Pin(3)) # Batterylevel GPIO3
D1.atten(ADC.ATTN_11DB) # Full range: 0 - 3.3v
BLE = bluetooth.BLE()
csc = BLEcsc(BLE)
# Setup RGB Led
Red = Pin(5, Pin.OUT, Pin.PULL_DOWN) # Red led
Green = Pin(6, Pin.OUT, Pin.PULL_DOWN) # Green led
Green.value(1)
Blue = Pin(7, Pin.OUT, Pin.PULL_DOWN) # Blue led
# Set up deep_sleep mode
esp32.wake_on_ext1(pins = (D0, D2,), level = esp32.WAKEUP_ANY_HIGH)
# Loop
while True:
if (millis() - lastUpdate >= UPDATE_INTERVAL_MS):
lastUpdate = millis()
if len(csc._connections) != 0:
Red.value(0)
Green.value(0)
Blue.value(1)
# blecsc_simulate_speed_and_cadence() # Simulate wheel and crank movement
swCount += 1
data_buf = bytearray(11)
# The Cycle Measurement Characteristic data is defined here:
# https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml
# First byte is flags. Wheel Revolution Data Present (0 = false, 1 = true) = 1, Crank Revolution Data Present (0 = false, 1 = true), so the flags are 0x03 (binary 11 converted to HEX).
# buf[0]=0x03;
data_buf[0] |= CSC_MEASUREMENT_WHEEL_REV_PRESENT | CSC_MEASUREMENT_CRANK_REV_PRESENT
# Setting values for cycling measures (from the Characteristic File)
# Cumulative Wheel Revolutions (unitless)
# Last Wheel Event Time (Unit has a resolution of 1/1024s)
# Cumulative Crank Revolutions (unitless)
# Last Crank Event Time (Unit has a resolution of 1/1024s)
data_buf[1:4] = cum_wheel_rev.to_bytes(4, 'little')
data_buf[5:6] = int(last_wheel_event).to_bytes(2, 'little')
data_buf[7:8] = cum_cranks.to_bytes(2, 'little')
data_buf[9:10] = int(last_crank_event).to_bytes(2, 'little')
csc.set_csc(data_buf, notify=True, indicate=False)
# Read batterylevel
lastBattery = convert(D1.read(), 0, 4095, 0, 33) # Read batterylevel
print("Battery voltage:", lastBattery)
time.sleep_ms(1000)
else:
Red.value(0)
Green.value(1)
Blue.value(0)
print("Not yet connected")
cum_wheel_rev = 0 # keep the cumulative wheel revolutions at 0 whilst not connected
cum_cranks = 0 # keep the cumulative crank revolutions at 0 whilst not connected
last_wheel_event = 0 # keep the last wheel event at 0 whilst not connected
last_crank_event = 0 # keep the last wheel event at 0 whilst not connected
if ((millis() - lastInterruptMillis >= DEEP_SLEEP_INTERVAL_MS) or (lastBattery < minBattery)):
if (lastBattery < minBattery):
Green.value(0)
Blue.value(0)
Red.value(1)
time.sleep(10)
print("Going to sleep now")
deepsleep()
Toelichting:
- Voor het advertisen van de bluetooth services maak ik gebruik van het ble_advertising example van de micropython github pagina: https://github.com/micropython/micropython/blob/master/examples/bluetooth/ble_advertising.py
- Na de initialisatie van de verschillende micropython bibliotheken worden de verschillende variabelen en constanten gedefinieerd
- Vervolgens worden de bluetooth objecten en characteristic voor CSC measurement, feature, … en batterylevel gedefinieerd, dit conform de GATT specificatie supplement voor bluetooth: https://www.bluetooth.com/specifications/specs/gatt-specification-supplement-5/
- Daarna worden er een aantal functies gedefinieerd, namelijk:
- millis(), python equivalent voor de C++ functie millis()
- switchIsr(), deze functie wordt aangeroepen bij iedere interrupt (lees iedere keer dat de reed-switch contact maakt) van de GPIO’s GPIO0 (D0) en GPIO4 (D2), deze functie zorgt voor ophoging van het aantal wiel- en crank omwentelingen
- blecsc_simulate_speed_and_cadence(), simulatie functie, kun je aanroepen als je nog geen proefopstelling of reed-switches hebt, deze simuleert wiel- en crank omwentelingen
- convert(), equivalent voor de C++ functie map()
- Vervolgens wordt de class BLEcsc gedefinieerd, met daarbinnen de volgende functies:
- __init__(): initialiseert bluetooth en start advertising van de services
- _irq(): interrupt afhandeling van een nieuwe connectie
- set_csc(): schrijft de gemeten wiel- en crankomwentelingen en batterijlevel naar de geconnecteerde GATT-clients
- _advertise(): hulp functie voor het adverteren van de services
- In de Setup sectie worden een aantal variabelen van een waarde voorzien, de GPIO’s geinitialiseerd en de wake_on_ext1 functie van GPIO’s GPIO0 en GPIO4 geactiveerd, ook worden de GPIO’s voor de RGB led geinitialiseerd
- Daarna volgt de loop die er iedere 2 seconden voor zorgt dat als er een GATT-client geconnecteerd is de gemeten wiel- en crank omwentelingen en het level van de batterij doorgegeven wordt aan betreffende GATT-client
De benodigde files kun je uiteraard downloaden:
File | download locatie | Toelichting |
ble_advertising.py | Github | Bibliotheek voor het advertisen van bluetooth services |
myCadenceSensor.py | https://drive.google.com/file/d/1Mlt9k7HuetQoRqaNb_Dhp0hOzAs3T0m1/view?usp=share_link | |
install.bat | https://drive.google.com/file/d/1-gwOdbaXByOzhqYYPvZa_Vde9yyI_Aqi/view?usp=share_link | Installatie script, pas wel eerst de COM poort en eventueel bestandslocaties aan |
main.py | https://drive.google.com/file/d/19i0vmKWKnd4CZCgp9zUTcgJQYYvQg8vf/view?usp=share_link | Zorgt ervoor dat myCadenceSensor.py automatish gestart wordt na (re)boot van de ESP32C3 |
Om de myCadenceSensor software én Micropython software te installeren op de ESP32C3 kun je het batch-commando bestand install.bat gebruiken. Pas wel eerst de COM-poort en eventueel bestandslocatie van de files aan. Sluit de ESP32C3 middels een USB-C kabel aan op je laptop/desktop.
Stap 3. Testen python script + connectie met GATT-client
Na de proefopstelling bij elkaar gestoken te hebben op een breadboard, en de ESP32C3 geprepareerd te hebben m.b.v. het install.bat script is het tijd om één en ander te testen.
Zodra de ESP32C3 aangesloten wordt op een stroombron (laptop of adapter) begint hij met het adverteren van de bluetooth services onder de naam “myCadenceSensor” (pas eventueel deze naam aan op regel 70 van myCadenceSensory.py). Let op, probeer niet vanaf je telefoon of laptop het device te pairen, dit is namelijk niet geimplementeerd binnen de bluetooth stack van de Micropython distributie van de generic esp32c3 en resulteert in een crash van de software.
Voor een eerste test kun je het beste de app “nRF Connect” van Nordic Semiconductor installeren op je telefoon, deze is te vinden in de Google play store: https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=nl&gl=US
Nadat je de app gestart hebt, kies binnen “Devices” voor het tabblad “Scanner” en klik op “SCAN”:
Als het goed is verschijnt nu de device “myCadenceSensor” in het overzicht, klik op “CONNECT”, er wordt nu een nieuw tabblad geopend:
Klik vervolgens op “Cycling Speed and Cadence”, en je krijgt de characteristics van de bluetooth service te zien, inclusief hun waarden:
Druk nu een paar keer op de pushbuttons van de proefopstelling om wiel- en crank omwentelingen te simuleren, je ziet vervolges de waarden van Wheel rev en Crank rev oplopen:
Let op, als er 60 seconden (DEEP_SLEEP_INTERVAL_MS op regel 11) geen wiel- of crank omwentelingen zijn gesignaleerd, gaat het device automatisch op Deepsleep en wordt de connectie verbroken !
Nu dit werkt is de volgende stap om een verbinding te maken met je sporthorloge, in mijn geval een Garmin Forerunner 955 Solar. De volgende instructies zijn speficiek voor dit horloge, raadpleeg de handleiding van jou horloge/device voor de instellingen die nodig zijn om verbinding te maken met een Cycling speed- and cadence sensor.
Hou de knop (3) “UP” ingedrukt, het volgende scherm verschijnt:
Druk de knop (4) “DOWN” meerdere keren in totdat de instelling “Sensors en accessoires” verschijnt:
Druk op de knop (5) “Start-Stop” om deze keuze te bevestigen, en het volgende scherm verschijnt:
Druk nogmaals op de knop (5) “Start-Stop” om de keuze te “Voeg nieuw toe” te bevestigen, en het volgende scherm verschijnt:
Druk de knop (4) “DOWN” meerdere keren in totdat de optie “Snelheid/cadanssensor” verschijnt. Bevestig deze keuze met de knop (5) “Start-stop”, en het horloge gaat op zoek naar de “myCadenceSensor” en vind deze
Druk op de knop (5) “Start-stop” om de “myCadenceSensor” toe te voegen. De sensor wordt automatisch verbonden. Druk daarna meerdere keren op de knop (6) “Back” om terug te keren naar het hoofdscherm.
Om de werking van de Cycling speed en cadence sensor te testen starten we nu een fietstraining. Druk langere tijd op de knop (5) “Start-stop”, het volgende scherm verschijnt:
Druk meerdere keren op de knop (4) “DOWN” om naar de activiteit fietsen te navigeren:
Druk tweemaal op de knop (5) “Start-stop” om de training te starten, de “myCadenceSensor” wordt automatisch verbonden:
Druk nu wederom een aantal keren op de pushbuttons van de proefopstelling, en je ziet de afstand en snelheid oplopen:
Nadat je de training hebt opgeslagen (Druk knop (5) “Start-stop” langere tijd in, en kies binnen het menu daarna voor “Sla op”) kun je de training raadplegen in Garmin Connect:
De software werkt, nu de hardware.
Stap 3. PCB ontwerp
M.b.v. Autodesk Eagle heb ik het volgende ontwerp voor een printplaat gemaakt.
Partlist:
Toelichting:
- Voorlopig twee 7-pins female headers ingetekend, dit om de ESP32C3 eenvoudig te kunnen vervangen indien nodig. Als de beschikbare ruimte in de behuizing dit niet toelaat, dan vervang ik die door male headers en soldeer ik de ESP32C3 hierop vast.
Note: 10-8 Bij het assembleren van de PCB kwam ik tot de conclusie dat het monteren van de ESP32C3 op de PCB m.b.v. male- en female headers teveel hoogte/ruimte zou innemen, daarom besloten om de ESP32C3 rechtstreeks m.b.v. male headers op de PCB te solderen. Is wat minder onderhoudsvriendelijk, maar het is niet anders.
- De RGB led komt haaks op de printplaat gesoldeerd en steekt aan de bovenkant door de behuizing, aandacht voor waterdicht monteren hiervan
- In Pad 1 en Pad 2 worden twee draadjes gesoldeerd, en aan de achterkant van de ESP32C3 op de batterypads gesoldeerd, misschien vervang ik dit nog door een JST aansluiting, afhankelijk van uitkomst 1e bullit
- Op de aansluitingen voor de Crank reel, Charger en Switch worden 90 graden 2 pins male headers gesoldeerd, zodat de behuizing van de electronica en batterij eenvoudig van het frame afgeklikt kan worden om de batterij op te kunnen laden.
Uiteraard zijn de CAD en gerber files weer te downloaden:
File | Download link | Toelichting |
cadence_sensor.brd | https://drive.google.com/file/d/1ex3KKgROhpnCS2rucznjsG9N9lS3dZpj/view?usp=drive_link | Autodesk Eagle: Board |
cadense_sensor.sch | https://drive.google.com/file/d/1zHE4fW12rUPSz7ChkjH9MyqzBjXZU-FX/view?usp=drive_link | Autodesk Eagle: Schema |
cadense_sensor_2023_07_08.zip | https://drive.google.com/file/d/1N4K_KAIcQnwwZ3KpXSGLUUkTolDhAHhP/view?usp=drive_link | ZIP-file met de gerber files |
Stap 4. PCB besteld
Bij PCB gogo de in stap 3. ontwikkelde PCB besteld, hiervoor de volgende instellingen gebruikt:
De kosten bedragen maar liefst $ 5,- voor 5 printplaten. Heb deze bestelling gecombineerd met 2 andere PCB’s en daardoor de verzend- en inklaringskosten laag kunnen houden. Totaal komen de kosten op zo’n €12,50 inclusief inklaringskosten.
Stap 5. PCB assembleren
De PCB’s zijn binnengekomen, zien er goed uit. Jammer is dat de partnummers en -waarden ontbreken op de PCB, maar zal wel een verkeerde keuze hebben gemaakt in het toekennen een label aan een layer of het exporteren naar Gerber.
We gaan de componenten op de PBC solderen, benodigd zijn:
- 1x Reed switch
- 1x RGB led cathode 5mm
- 3x Weerstand 220 Ohm
- 2x Weerstand 10K Ohm
- 2x Weerstand 220K Ohm
- 4x JST connector 2-voudig PCB mount, haaks
- 2x male headers 7-voudig
- 1x PCB
- 1x ESP32C3
- Soldeerbout + tin
- Tangetje
De headers had ik al eerder op de ESP32C3 gesoldeerd, dus deze stap sla ik nu over. Heb er voor gekozen om alleen die pinnen vast te solderen die gebruikt worden, dus D0, D1, D2, GND en 3V3. Mocht de ESP32C3 het begeven, dan hoef ik alleen die pinnen los te solderen om een vervangende ESP32C3 te plaatsen. We beginnen met het solderen van de weerstanden op de PCB, benodigd zijn:
- 3x Weerstand 220 Ohm
- 2x Weerstand 10K Ohm
- 2x Weerstand 220K Ohm
Steek de weerstanden op de juiste plek door de PCB, en soldeer ze vast.