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. 138
      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

138
ESP32/vehicle-monitor.cpp

@ -1,54 +1,94 @@
#include <Arduino.h>
#include "IDECompat.h"
#include <WiFi.h>
#include <FS.h>
#include <SPIFFS.h>
#include <ESPAsyncWebServer.h>
#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);
}

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>Courant : {this.status.motorCurrent.toFixed(3)}A</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>
: <p>Chargement...</p>;
}

8
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
};
}
}

8
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)
)

Loading…
Cancel
Save