Kit compteur vélo
Aller à la navigation
Aller à la recherche
Le kit Compteur vélo s'améliore, n'hésitez pas à le passer à la moulinette ChatGPT pour l'analyser.
Il gère vitesse, cadence de pédalage et km total, avec une mise en sommeil automatique. Une des difficultés est de trouver un bon algorithme pour le calcul de vitesse, de façon à obtenir un affichage stable mais qui reste réactif aux changements de vitesse.
Le kilométrage total est sauvegardé même en cas de débranchement.
// ----- LIBRARIES -----
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <esp_sleep.h>
#include <DHT.h>
#include <Preferences.h>
Preferences prefs; // pour sauvegarde en flash
float total_km = 0.0; // total km permanent
// Polices GFX
#include <Fonts/FreeSansBold12pt7b.h> // cadence
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold24pt7b.h> // vitesse
#include <Fonts/FreeSans9pt7b.h> // debug
// ----- OLED CONFIG -----
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SDA_PIN 21
#define SCL_PIN 22
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// ----- CONTRASTE OLED -----
// 2 niveaux : moyenne et forte
#define CONTRASTE_MOYEN 10
#define CONTRASTE_FORTE 255
// Choix du niveau d'intensité (pas une grosse différence)
bool oledStrong = false; // false = moyen, true = fort
// ----- PINS -----
#define HALL_SENSOR_PEDAL_PIN 14
#define HALL_SENSOR_WHEEL_PIN 25
#define DHTPIN 26
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// ----- MODE DEBUG -----
bool DEBUG_MODE = false;
int tour_count = 0;
int wheel_count = 0;
unsigned long total_time_ms = 0;
unsigned long session_start_time = 0;
volatile unsigned long wheel_ticks = 0;
const int cadence_window_ms = 3000;
unsigned long pedal_times[20];
int pedal_index = 0;
unsigned long last_pedal_time = 0;
unsigned long last_activity_time = 0;
volatile bool pedal_detected = false;
float vitesse_kmh = 0.0; // vitesse filtrée
float alpha_base = 0.2; // coefficient de lissage minimal
float alpha_max = 0.7; // coefficient de lissage maximal pour variations rapides
float cadence_rpm = 0;
// Circonférence de la roue
#define WHEEL_CIRCUMFERENCE_M 1.993
// ----- ISR PEDAL -----
void IRAM_ATTR onPedal() {
pedal_detected = true;
}
// ----- ISR WHEEL -----
void IRAM_ATTR onWheel() {
wheel_ticks++;
}
void setup() {
pinMode(HALL_SENSOR_PEDAL_PIN, INPUT_PULLUP);
pinMode(HALL_SENSOR_WHEEL_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(HALL_SENSOR_PEDAL_PIN), onPedal, FALLING);
attachInterrupt(digitalPinToInterrupt(HALL_SENSOR_WHEEL_PIN), onWheel, FALLING);
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN);
dht.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("Erreur d'initialisation OLED"));
while (true);
}
display.clearDisplay();
last_pedal_time = millis();
last_activity_time = millis();
session_start_time = millis();
// Appliquer l'intensité choisie
uint8_t contrast = oledStrong ? CONTRASTE_FORTE : CONTRASTE_MOYEN;
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(contrast);
// Ouvrir l'espace de stockage "bike" en lecture/écriture
prefs.begin("bike", false);
// Charger total_km depuis la flash (0.0 par défaut si jamais écrit)
total_km = prefs.getFloat("total_km", 0.0);
Serial.print("Kilométrage restauré: ");
Serial.println(total_km, 2);
}
void loop() {
unsigned long now = millis();
// ----- GESTION PEDALE -----
if (pedal_detected) {
pedal_detected = false;
last_pedal_time = now;
last_activity_time = now;
session_start_time = now;
tour_count++;
pedal_times[pedal_index % 20] = now;
pedal_index++;
int count = 0;
unsigned long first = 0, last = 0;
int entries = min(pedal_index, 20);
for (int i = 0; i < entries; i++) {
int idx = (pedal_index - i - 1 + 20) % 20;
unsigned long t = pedal_times[idx];
if (now - t < cadence_window_ms) {
last = (count == 0) ? t : last;
first = t;
count++;
} else break;
}
if (count >= 2) {
float elapsed_min = (last - first) / 60000.0;
cadence_rpm = (count - 1) / elapsed_min;
} else {
cadence_rpm = 0;
}
// ----- CHANGEMENT INTENSITÉ OLED SI CADENCE > 150 -----
if (cadence_rpm > 150) {
oledStrong = !oledStrong; // permute moyen <-> fort
uint8_t contrast = oledStrong ? CONTRASTE_FORTE : CONTRASTE_MOYEN;
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(contrast);
delay(2000); // pause de 2 secondes
}
}
// ----- CALCUL VITESSE (MOYENNE GLISSANTE) -----
static unsigned long last_speed_calc = 0;
static unsigned long last_tick_count = 0;
if (now - last_speed_calc >= 3000) { // calcul toutes les 3 s
unsigned long ticks = wheel_ticks;
unsigned long delta_ticks = ticks - last_tick_count;
float delta_time_s = (now - last_speed_calc) / 1000.0;
last_speed_calc = now;
last_tick_count = ticks;
// vitesse instantanée sur le dernier intervalle
float vitesse_mesuree = (delta_ticks * WHEEL_CIRCUMFERENCE_M) / delta_time_s * 3.6;
// coefficient alpha adaptatif selon variation
float delta_speed = abs(vitesse_mesuree - vitesse_kmh);
float alpha = alpha_base + (alpha_max - alpha_base) * min(delta_speed / 5.0, 1.0); // 5 km/h = seuil de réactivité
// alpha limité entre alpha_base et alpha_max
// EMA adaptatif
vitesse_kmh = alpha * vitesse_mesuree + (1.0 - alpha) * vitesse_kmh;
// compter les tours et les km
wheel_count += delta_ticks;
total_km += (delta_ticks * WHEEL_CIRCUMFERENCE_M) / 1000.0; // km cumulés sauvegardés
// mettre à jour last_activity_time si la roue a tourné
if (delta_ticks > 0) {
last_activity_time = now;
}
}
// ----- LECTURE DHT -----
float temp = dht.readTemperature();
float hum = dht.readHumidity();
// ----- AFFICHAGE OLED -----
display.clearDisplay();
display.setTextColor(WHITE);
// Cadence
display.setFont(&FreeSansBold18pt7b);
display.setCursor(0, 25);
display.print((int)cadence_rpm);
// km total
display.setFont(&FreeSans9pt7b);
display.setCursor(80, 12);
display.printf("%.2f", total_km);
// Vitesse
display.setFont(&FreeSansBold24pt7b);
char vitesseStr[10];
snprintf(vitesseStr, sizeof(vitesseStr), "%.1f", vitesse_kmh);
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(vitesseStr, 0, 0, &x1, &y1, &w, &h);
int xPos = SCREEN_WIDTH - w - 5;
display.setCursor(xPos, 63);
display.print(vitesseStr);
// Debug
if (DEBUG_MODE) {
display.setFont(&FreeSans9pt7b);
display.setCursor(0, 50);
display.print(tour_count);
display.print("/");
display.print(wheel_count);
}
if (oledStrong) {
display.setFont(&FreeSansBold12pt7b);
display.setCursor(0, 63);
display.print("^");
}
display.display();
// ----- DEEP SLEEP après 10s sans activité -----
if (now - last_activity_time > 10000) {
prefs.putFloat("total_km", total_km); // sauvegarde finale avant veille
prefs.end();
display.clearDisplay();
display.display();
display.ssd1306_command(SSD1306_DISPLAYOFF);
esp_sleep_enable_ext0_wakeup(GPIO_NUM_14, 0);
delay(100);
esp_deep_sleep_start();
}
delay(50);
}