finished power meter and speed meter
This commit is contained in:
parent
3d9aee6d0a
commit
b136342c2c
29
ESP32/ADC.cpp
Normal file
29
ESP32/ADC.cpp
Normal file
@ -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
Normal file
33
ESP32/ADC.h
Normal file
@ -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
Normal file
5
ESP32/IDECompat.h
Normal file
@ -0,0 +1,5 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef IRAM_ATTR
|
||||
#define IRAM_ATTR
|
||||
#endif
|
@ -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
|
||||
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)
|
||||
{
|
||||
float Measure;
|
||||
int16_t ADC_Value;
|
||||
};
|
||||
if(speedSensorState == newState) return;
|
||||
unsigned long now = millis();
|
||||
speedSensorState = newState;
|
||||
|
||||
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]);
|
||||
bool magnetDetected = !speedSensorState; // the magnet closes the contact which pulls the pin low
|
||||
|
||||
float getCalibratedVoltage(int16_t adcOutput)
|
||||
{
|
||||
for(int8_t i = 1; i < calibrationNumValues; ++i)
|
||||
if(magnetDetected)
|
||||
{
|
||||
if(i == calibrationNumValues - 1 || calibration[i].ADC_Value >= adcOutput)
|
||||
speedSensorRiseTime = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 = timeSinceLastImpulse;
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
|
||||
averageV += v;
|
||||
averageC += c;
|
||||
}
|
||||
|
||||
averageV /= (float)numSamples;
|
||||
averageC /= (float)numSamples;
|
||||
|
||||
if(averageV < 0.2f) averageV = 0.0f;
|
||||
|
||||
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);
|
||||
int16_t analogV = analogRead(potPin);
|
||||
float v = getCalibratedVoltage(analogV);
|
||||
|
||||
averageBatteryVoltage = averageBatteryVoltage * 0.95f + v * 0.05f;
|
||||
batteryVoltage = (int16_t)(averageBatteryVoltage * 1000.0f + 0.5f);
|
||||
|
||||
//avgAnalogV = avgAnalogV * 0.9f + (float)analogV * 0.1f;
|
||||
//batteryVoltage = (int16_t)(avgAnalogV + 0.5f);
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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…
Reference in New Issue
Block a user