Kit compteur vélo

De Wiki de Mémoire Vive
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);
}