From b136342c2c76848cfb40ac4e8a4eca96577d45ae Mon Sep 17 00:00:00 2001 From: Youen Toupin Date: Sat, 19 Mar 2022 23:55:51 +0100 Subject: [PATCH] finished power meter and speed meter --- ESP32/ADC.cpp | 29 ++++++ ESP32/ADC.h | 33 +++++++ ESP32/IDECompat.h | 5 + ESP32/vehicle-monitor.cpp | 138 +++++++++++++++++++-------- WebApp/src/main-page.tsx | 1 + WebApp/src/monitor-api.ts | 8 +- schema/MCU_board/MCU_board.kicad_pcb | 8 +- 7 files changed, 174 insertions(+), 48 deletions(-) create mode 100644 ESP32/ADC.cpp create mode 100644 ESP32/ADC.h create mode 100644 ESP32/IDECompat.h diff --git a/ESP32/ADC.cpp b/ESP32/ADC.cpp new file mode 100644 index 0000000..dc3a86b --- /dev/null +++ b/ESP32/ADC.cpp @@ -0,0 +1,29 @@ +#include "ADC.h" + +namespace +{ + const int8_t calibrationNumValues = sizeof(ADC_calibration)/sizeof(ADC_calibration[0]); +} + +ADC::ADC(int8_t gpioPin) + : pin_(gpioPin) +{ +} + +float ADC::read() +{ + int16_t rawValue = analogRead(pin_); + + for(int8_t i = 1; i < calibrationNumValues; ++i) + { + if(i == calibrationNumValues - 1 || ADC_calibration[i].ADC_Value >= rawValue) + { + const auto& p = ADC_calibration[i - 1]; + const auto& n = ADC_calibration[i]; + return (float)(rawValue - p.ADC_Value) / (float)(n.ADC_Value - p.ADC_Value) * (n.MeasuredVoltage - p.MeasuredVoltage) + p.MeasuredVoltage; + } + } + + // This should never happen if the calibration array contains at least 2 entries + return -std::numeric_limits::max(); +} diff --git a/ESP32/ADC.h b/ESP32/ADC.h new file mode 100644 index 0000000..c9e700d --- /dev/null +++ b/ESP32/ADC.h @@ -0,0 +1,33 @@ +#include + +struct ADC_CalibrationValue +{ + float MeasuredVoltage; + int16_t ADC_Value; +}; + +// To improve ADC precision, these values must be measured with a voltmeter +// You can use a potentiometer to generate different voltages, measure them, and add an entry with the corresponding ADC value returned by analogRead() +const ADC_CalibrationValue ADC_calibration[] = { + { 0.118f, 1 }, + { 0.343f, 253 }, + { 0.791f, 801 }, + { 1.512f, 1705 }, + { 2.013f, 2306 }, + { 2.406f, 2796 }, + { 2.606f, 3058 }, + { 2.839f, 3423 }, + { 2.996f, 3726 }, + { 3.16f, 4094 } +}; + +class ADC +{ +public: + ADC(int8_t gpioPin); + + float read(); + +private: + int8_t pin_; +}; diff --git a/ESP32/IDECompat.h b/ESP32/IDECompat.h new file mode 100644 index 0000000..b11d24d --- /dev/null +++ b/ESP32/IDECompat.h @@ -0,0 +1,5 @@ +#include + +#ifndef IRAM_ATTR +#define IRAM_ATTR +#endif diff --git a/ESP32/vehicle-monitor.cpp b/ESP32/vehicle-monitor.cpp index ef5c742..9d2feb5 100644 --- a/ESP32/vehicle-monitor.cpp +++ b/ESP32/vehicle-monitor.cpp @@ -1,54 +1,94 @@ #include +#include "IDECompat.h" #include #include #include #include +#include "ADC.h" + #include "wifi-credentials.h" AsyncWebServer server(80); -float averageBatteryVoltage = 0.0f; -int16_t batteryVoltage = -1; //in mV +ADC currentSensor(36); +ADC batterySensor(39); +const int8_t speedSensorPin = 13; +const int8_t debugLedPin = 12; -struct ADC_CalibrationValue -{ - float Measure; - int16_t ADC_Value; -}; - -const ADC_CalibrationValue calibration[] = { - { 0.118f, 1 }, - { 0.343f, 253 }, - { 0.791f, 801 }, - { 1.512f, 1705 }, - { 2.013f, 2306 }, - { 2.406f, 2796 }, - { 2.606f, 3058 }, - { 2.839f, 3423 }, - { 2.996f, 3726 }, - { 3.16f, 4094 } -}; -const int8_t calibrationNumValues = sizeof(calibration)/sizeof(calibration[0]); - -float getCalibratedVoltage(int16_t adcOutput) +const float wheelDiameterInches = 20; +const int numImpulsesPerTurn = 2; +const float wheelCircumferenceMeters = wheelDiameterInches * 0.0254f * 3.1415f / (float)numImpulsesPerTurn; + +int16_t batteryVoltage = -1; // in mV +int16_t batteryCurrent = -1; // in mV + +volatile bool debugLedState = true; + +volatile bool speedSensorState = false; +volatile unsigned long speedSensorRiseTime = 0; +volatile unsigned long speedSensorLastImpulseTime = 0; +volatile unsigned long speedSensorLastImpulseInterval = (unsigned long)-1; // in milliseconds +void IRAM_ATTR onSpeedSensorChange(bool newState) { - for(int8_t i = 1; i < calibrationNumValues; ++i) + if(speedSensorState == newState) return; + unsigned long now = millis(); + speedSensorState = newState; + + bool magnetDetected = !speedSensorState; // the magnet closes the contact which pulls the pin low + + if(magnetDetected) + { + speedSensorRiseTime = now; + } + else { - if(i == calibrationNumValues - 1 || calibration[i].ADC_Value >= adcOutput) + unsigned long impulseDuration = now > speedSensorRiseTime ? now - speedSensorRiseTime : (4294967295 - speedSensorRiseTime) + now; // if now is lower than speedSensorRiseTime, it means millis() has overflowed (happens every 50 days) + if(impulseDuration > 1000) return; // impulse was too long, ignore it (maybe magnet stopped near the sensor) + + debugLedState = !debugLedState; + digitalWrite(debugLedPin, debugLedState ? HIGH : LOW); + + unsigned long timeSinceLastImpulse = now > speedSensorLastImpulseTime ? now - speedSensorLastImpulseTime : (4294967295 - speedSensorLastImpulseTime) + now; + speedSensorLastImpulseTime = now; + if(timeSinceLastImpulse > 30 && timeSinceLastImpulse < 4000) + { + speedSensorLastImpulseInterval = timeSinceLastImpulse; + } + else { - const auto& p = calibration[i - 1]; - const auto& n = calibration[i]; - return (float)(adcOutput - p.ADC_Value) / (float)(n.ADC_Value - p.ADC_Value) * (n.Measure - p.Measure) + p.Measure; + speedSensorLastImpulseInterval = (unsigned long)-1; } } +} - return -1.0f; +void IRAM_ATTR onSpeedSensorChange() { onSpeedSensorChange(digitalRead(speedSensorPin) == HIGH); } + +float getSpeed() +{ + unsigned long now = millis(); + + unsigned long lastImpulseInterval = speedSensorLastImpulseInterval; + unsigned long lastImpulseTime = speedSensorLastImpulseTime; + unsigned long timeSinceLastImpulse = now > lastImpulseTime ? now - lastImpulseTime : (4294967295 - lastImpulseTime) + now; + + unsigned long interval = timeSinceLastImpulse > lastImpulseInterval * 10 / 9 ? timeSinceLastImpulse : lastImpulseInterval; + + float speed = wheelCircumferenceMeters / (float)interval * 1000.0f; // in meters per second + + if(speed < 0.25f) return 0.0f; // if speed is very low (less than 1km/h) it probably means we've stopped + return speed; } void setup() { + pinMode(speedSensorPin, INPUT_PULLUP); + attachInterrupt(speedSensorPin, &onSpeedSensorChange, CHANGE); + + pinMode(debugLedPin, OUTPUT); + digitalWrite(debugLedPin, debugLedState ? HIGH : LOW); + Serial.begin(115200); if(!SPIFFS.begin(false)){ @@ -70,8 +110,11 @@ void setup() server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){ int v = batteryVoltage; + int c = batteryCurrent; + int s = (int)(getSpeed() * 1000.0f + 0.5f); + char json[128]; - sprintf(json, "{\"v\":%d,\"c\":1000}", v); + sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d}", v, c, s); request->send(200, "text/json", json); }); @@ -86,22 +129,33 @@ void setup() Serial.println("HTTP server started"); } -float avgAnalogV = 0.0f; - void loop() { - const int potPin = 34; + const int numSamples = 100; - const float minV = 0.14f; // 1 - const float maxV = 3.16f; // 4094 + float averageV = 0.0f; + float averageC = 0.0f; + for(int sample = 0; sample < numSamples; ++sample) + { + delay(1); + float v = batterySensor.read(); + float c = currentSensor.read(); - delay(10); - int16_t analogV = analogRead(potPin); - float v = getCalibratedVoltage(analogV); + averageV += v; + averageC += c; + } + + averageV /= (float)numSamples; + averageC /= (float)numSamples; - averageBatteryVoltage = averageBatteryVoltage * 0.95f + v * 0.05f; - batteryVoltage = (int16_t)(averageBatteryVoltage * 1000.0f + 0.5f); + if(averageV < 0.2f) averageV = 0.0f; - //avgAnalogV = avgAnalogV * 0.9f + (float)analogV * 0.1f; - //batteryVoltage = (int16_t)(avgAnalogV + 0.5f); + averageV *= 27.000f; // account for voltage divider to retrieve the input voltage + averageC = max(0.0f, averageC - 2.5f) / 0.0238f; // convert voltage to current, according to the sensor linear relation + + // TODO: mutex ? + batteryVoltage = (int16_t)(averageV * 1000.0f + 0.5f); + batteryCurrent = (int16_t)(averageC * 1000.0f + 0.5f); + + delay(10); } diff --git a/WebApp/src/main-page.tsx b/WebApp/src/main-page.tsx index 7aafc25..e0bb99d 100644 --- a/WebApp/src/main-page.tsx +++ b/WebApp/src/main-page.tsx @@ -26,6 +26,7 @@ export default class MainPage {

Tension batterie : {this.status.batteryVoltage.toFixed(3)}V

Courant : {this.status.motorCurrent.toFixed(3)}A

Puissance : {(this.status.batteryVoltage * this.status.motorCurrent).toFixed(1)}W

+

Vitesse : {(this.status.speed * 3.6).toFixed(1)}km/h

:

Chargement...

; } diff --git a/WebApp/src/monitor-api.ts b/WebApp/src/monitor-api.ts index 884ee9d..ca2d908 100644 --- a/WebApp/src/monitor-api.ts +++ b/WebApp/src/monitor-api.ts @@ -3,11 +3,13 @@ import m from 'mithril'; export interface Status { batteryVoltage: number; motorCurrent: number; + speed: number; // in meters per second }; interface ApiStatus { v: number; c: number; + s: number; } export class MonitorApi { @@ -21,7 +23,8 @@ export class MonitorApi { await new Promise(resolve => setTimeout(resolve, 200)); apiStatus = { v: Math.random() * 20000 + 20000, - c: Math.random() * 30000 + c: Math.random() * 30000, + s: Math.random() * 14000 } setTimeout(() => m.redraw(), 0); } else { @@ -33,7 +36,8 @@ export class MonitorApi { return { batteryVoltage: apiStatus.v / 1000, - motorCurrent: apiStatus.c / 1000 + motorCurrent: apiStatus.c / 1000, + speed: apiStatus.s / 1000 }; } } diff --git a/schema/MCU_board/MCU_board.kicad_pcb b/schema/MCU_board/MCU_board.kicad_pcb index 5c1d8ec..c24ff27 100644 --- a/schema/MCU_board/MCU_board.kicad_pcb +++ b/schema/MCU_board/MCU_board.kicad_pcb @@ -122,11 +122,11 @@ (effects (font (size 1 1) (thickness 0.15))) (tstamp 7f5881c0-cd18-48ee-b734-9fc646780ef5) ) - (fp_text value "180K" (at 5.08 2.37 90) (layer "F.Fab") + (fp_text value "180K" (at 5.08 0 90) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))) (tstamp 5a0e8fb0-01f2-49ab-8dc9-86484b088050) ) - (fp_text user "${REFERENCE}" (at 5.08 0 90) (layer "F.Fab") + (fp_text user "${REFERENCE}" (at 5.08 -2.54 90) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))) (tstamp 8c5fdfe4-7881-43aa-b104-783c04c26c7c) ) @@ -462,11 +462,11 @@ (effects (font (size 1 1) (thickness 0.15))) (tstamp c00665c7-238f-42f0-9cc8-e3a8fa63e130) ) - (fp_text value "12K" (at 5.08 2.37 90) (layer "F.Fab") + (fp_text value "12K" (at 5.08 0 90) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))) (tstamp 2f1d6d2c-caeb-48e9-bf74-39571b5d016d) ) - (fp_text user "${REFERENCE}" (at 5.08 0 90) (layer "F.Fab") + (fp_text user "${REFERENCE}" (at 5.08 -2.54 90) (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))) (tstamp 6d29d228-5ab1-4850-93ed-cc89252531fb) )