Browse Source

finished power meter and speed meter

master
Youen Toupin 3 years ago
parent
commit
b136342c2c
  1. 29
      ESP32/ADC.cpp
  2. 33
      ESP32/ADC.h
  3. 5
      ESP32/IDECompat.h
  4. 134
      ESP32/vehicle-monitor.cpp
  5. 1
      WebApp/src/main-page.tsx
  6. 8
      WebApp/src/monitor-api.ts
  7. 8
      schema/MCU_board/MCU_board.kicad_pcb

29
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<float>::max();
}

33
ESP32/ADC.h

@ -0,0 +1,33 @@
#include <Arduino.h>
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_;
};

5
ESP32/IDECompat.h

@ -0,0 +1,5 @@
#include <Arduino.h>
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif

134
ESP32/vehicle-monitor.cpp

@ -1,54 +1,94 @@
#include <Arduino.h> #include <Arduino.h>
#include "IDECompat.h"
#include <WiFi.h> #include <WiFi.h>
#include <FS.h> #include <FS.h>
#include <SPIFFS.h> #include <SPIFFS.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "ADC.h"
#include "wifi-credentials.h" #include "wifi-credentials.h"
AsyncWebServer server(80); AsyncWebServer server(80);
float averageBatteryVoltage = 0.0f; ADC currentSensor(36);
ADC batterySensor(39);
const int8_t speedSensorPin = 13;
const int8_t debugLedPin = 12;
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 batteryVoltage = -1; // in mV
int16_t batteryCurrent = -1; // in mV
volatile bool debugLedState = true;
struct ADC_CalibrationValue 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)
{ {
float Measure; if(speedSensorState == newState) return;
int16_t ADC_Value; unsigned long now = millis();
}; speedSensorState = newState;
const ADC_CalibrationValue calibration[] = { bool magnetDetected = !speedSensorState; // the magnet closes the contact which pulls the pin low
{ 0.118f, 1 },
{ 0.343f, 253 }, if(magnetDetected)
{ 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)
{ {
for(int8_t i = 1; i < calibrationNumValues; ++i) 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]; speedSensorLastImpulseInterval = (unsigned long)-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;
} }
} }
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() void setup()
{ {
pinMode(speedSensorPin, INPUT_PULLUP);
attachInterrupt(speedSensorPin, &onSpeedSensorChange, CHANGE);
pinMode(debugLedPin, OUTPUT);
digitalWrite(debugLedPin, debugLedState ? HIGH : LOW);
Serial.begin(115200); Serial.begin(115200);
if(!SPIFFS.begin(false)){ if(!SPIFFS.begin(false)){
@ -70,8 +110,11 @@ void setup()
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){
int v = batteryVoltage; int v = batteryVoltage;
int c = batteryCurrent;
int s = (int)(getSpeed() * 1000.0f + 0.5f);
char json[128]; 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); request->send(200, "text/json", json);
}); });
@ -86,22 +129,33 @@ void setup()
Serial.println("HTTP server started"); Serial.println("HTTP server started");
} }
float avgAnalogV = 0.0f;
void loop() void loop()
{ {
const int potPin = 34; const int numSamples = 100;
const float minV = 0.14f; // 1 float averageV = 0.0f;
const float maxV = 3.16f; // 4094 float averageC = 0.0f;
for(int sample = 0; sample < numSamples; ++sample)
{
delay(1);
float v = batterySensor.read();
float c = currentSensor.read();
delay(10); averageV += v;
int16_t analogV = analogRead(potPin); averageC += c;
float v = getCalibratedVoltage(analogV); }
averageV /= (float)numSamples;
averageC /= (float)numSamples;
averageBatteryVoltage = averageBatteryVoltage * 0.95f + v * 0.05f; if(averageV < 0.2f) averageV = 0.0f;
batteryVoltage = (int16_t)(averageBatteryVoltage * 1000.0f + 0.5f);
//avgAnalogV = avgAnalogV * 0.9f + (float)analogV * 0.1f; averageV *= 27.000f; // account for voltage divider to retrieve the input voltage
//batteryVoltage = (int16_t)(avgAnalogV + 0.5f); 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);
} }

1
WebApp/src/main-page.tsx

@ -26,6 +26,7 @@ export default class MainPage {
<p>Tension batterie : {this.status.batteryVoltage.toFixed(3)}V</p> <p>Tension batterie : {this.status.batteryVoltage.toFixed(3)}V</p>
<p>Courant : {this.status.motorCurrent.toFixed(3)}A</p> <p>Courant : {this.status.motorCurrent.toFixed(3)}A</p>
<p>Puissance : {(this.status.batteryVoltage * this.status.motorCurrent).toFixed(1)}W</p> <p>Puissance : {(this.status.batteryVoltage * this.status.motorCurrent).toFixed(1)}W</p>
<p>Vitesse : {(this.status.speed * 3.6).toFixed(1)}km/h</p>
</div> </div>
: <p>Chargement...</p>; : <p>Chargement...</p>;
} }

8
WebApp/src/monitor-api.ts

@ -3,11 +3,13 @@ import m from 'mithril';
export interface Status { export interface Status {
batteryVoltage: number; batteryVoltage: number;
motorCurrent: number; motorCurrent: number;
speed: number; // in meters per second
}; };
interface ApiStatus { interface ApiStatus {
v: number; v: number;
c: number; c: number;
s: number;
} }
export class MonitorApi { export class MonitorApi {
@ -21,7 +23,8 @@ export class MonitorApi {
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 200));
apiStatus = { apiStatus = {
v: Math.random() * 20000 + 20000, v: Math.random() * 20000 + 20000,
c: Math.random() * 30000 c: Math.random() * 30000,
s: Math.random() * 14000
} }
setTimeout(() => m.redraw(), 0); setTimeout(() => m.redraw(), 0);
} else { } else {
@ -33,7 +36,8 @@ export class MonitorApi {
return { return {
batteryVoltage: apiStatus.v / 1000, batteryVoltage: apiStatus.v / 1000,
motorCurrent: apiStatus.c / 1000 motorCurrent: apiStatus.c / 1000,
speed: apiStatus.s / 1000
}; };
} }
} }

8
schema/MCU_board/MCU_board.kicad_pcb

@ -122,11 +122,11 @@
(effects (font (size 1 1) (thickness 0.15))) (effects (font (size 1 1) (thickness 0.15)))
(tstamp 7f5881c0-cd18-48ee-b734-9fc646780ef5) (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))) (effects (font (size 1 1) (thickness 0.15)))
(tstamp 5a0e8fb0-01f2-49ab-8dc9-86484b088050) (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))) (effects (font (size 1 1) (thickness 0.15)))
(tstamp 8c5fdfe4-7881-43aa-b104-783c04c26c7c) (tstamp 8c5fdfe4-7881-43aa-b104-783c04c26c7c)
) )
@ -462,11 +462,11 @@
(effects (font (size 1 1) (thickness 0.15))) (effects (font (size 1 1) (thickness 0.15)))
(tstamp c00665c7-238f-42f0-9cc8-e3a8fa63e130) (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))) (effects (font (size 1 1) (thickness 0.15)))
(tstamp 2f1d6d2c-caeb-48e9-bf74-39571b5d016d) (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))) (effects (font (size 1 1) (thickness 0.15)))
(tstamp 6d29d228-5ab1-4850-93ed-cc89252531fb) (tstamp 6d29d228-5ab1-4850-93ed-cc89252531fb)
) )

Loading…
Cancel
Save