I have looked at the examples and the code sent. Seems simple enough and elegant. Just needed to get my head around what Millis() is doing.Appreciate the help.
Sorry Guys I thought I had this but obviously not.
The web examples referenced either set start time as 0 permanently as a reference or use a time difference count to reset.
The example in this thread from John doesn't do that and I cant see how it works. My notes/questions are in the sketch below...
boolean Running = false;
unsigned long StartTime = 0;
unsigned long StopTime = 0;
void setup()
{
Serial.begin(115200);
}
void loop()
{
unsigned long currentTime = millis();// set current time to millis
if (! Running && StartEvent)
{
StartTime = currentTime;// when start event occurs set start time to current time
Running = true;//and move to (Running)
}
if (Running && StopEvent)
{
StopTime = currentTime - StartTime;
Running = false;
}
if (Running)
{
Serial.println(currentTime - StartTime);//This is where i get confused. aren't these the same? Both were made equal in first IF and are current Millis?
}
else
{
Serial.println(StopTime);
}
}
I get a miles from EEprom, run the trip when the start event (rfid trigger) happens and save back to eeprom when the stop event happens.
I have a separate oled showing the total (odo) miles and a trip miles that resets to zero on every stop event.
It almost works but the numbers sometimes increment then decrement a little when running.
I have not included the OLED or RFID code for clarity. The water sender pin is just a pot simulating miles per hour.
The 700 value simulates 0 MPH to stop adding time when the vehicle is stopped.
#include <EEPROM.h>
float MPHour;
int waterSenderPin = A4;
float distanceMiles;//
float distanceMiles2;//
int odoDistance;
float distanceMilesEEPROM;//read distance from eeprom
//EEprom setup
int eeAddress = 0; //Location I want the Odomoter data to be put.
float OdoTotal = 0.00f;
//Odometer setup
boolean Running = false;
unsigned long StartTime = 0;
unsigned long currentTime;
void setup(void) {
pinMode (waterSenderPin, INPUT);//Pot used to sim miles
}
void loop()
{ odoMeter();
}
void odoMeter ()
{
MPHour = analogRead(waterSenderPin); //test pot to simulate speed//(KMHour/1.60934);test add maths back later
currentTime = millis();
if (! Running && powerpinState == HIGH && MPHour > 700)//uses rfid for on event. Code not included but it works
{
StartTime = currentTime;
EEPROM.get(eeAddress, distanceMilesEEPROM); //Get the float data from the EEPROM at position 'eeAddress'
Serial.println("Read float from EEPROM: ");
Serial.print(distanceMilesEEPROM, 2);
Serial.println("distanceMilesEEPROM ");
Serial.println("");
Running = true;
}
if (Running && powerpinState == LOW)
{
EEPROM.put(eeAddress, distanceMiles);//One simple call, with the address first and the object second.
Serial.println("Written float data type!");
Serial.println("");
Running = false;
}
if (Running)
{
Serial.println("");
int cormph = (MPHour - 700);// adjusted pot value to make mph
Serial.print((currentTime - StartTime) / 1000);
Serial.print (" Trip time secs ");
Serial.println("");
distanceMiles = (MPHour - 700) * ((currentTime - StartTime) / 3600000.00);
distanceMiles2 = (MPHour - 700) * ((currentTime - StartTime) / 3600000.00);// variable sent to lcd display as a resetting trip display before resave to eeprom
distanceMiles = (distanceMiles + distanceMilesEEPROM);//product of previous miles save and new miles
OdoTotal = distanceMiles;// value sent to lcd to show total miles
Serial.print(OdoTotal, 2);
Serial.println(" OdoTotal");
}
}
It makes sense now and works. Thanks for the pointer.
So expanding it a little to calculate distance. While the basic maths of distance = speed * time is used, i get strange results in that the trip distance, while increasing, occasionally jumps back and forwards a little.
My speed it being simulated by an analog read input which does vary by a few miles per hour up and down but that is a vehicles behavior as well.
Do I need some form of averaging to get a stable and incrementing distance?
Odometer ()
{
unsigned long currentTime = millis();
float MPHour;
if (! Running && powerpinState == HIGH && MPHour > 1)//powerpinState is from RFID
{
StartTime = currentTime;
Running = true;
}
if (Running && powerpinState == LOW)
{
TripTime = currentTime - StartTime;//declared in setup as UL's
tripdistanceMiles = ((MPHour * (TripTime / 3600000.00));// trip distance on power down
Running = false;
}
if (Running)
{
Serial.print(currentTime - StartTime);
rundistanceMiles = ((MPHour * (currentTime - StartTime) / 3600000.00));
Serial.print(rundistanceMiles);// cumulative trip distance while running
}
else
{
Serial.print(TripTime / 1000);// in seconds
}
}
Instead of MPH which has miles and hours scale, you would not need inherently inaccurate 32-bit floats (Arduino doubles are also 32-bit not 64) to divide MPH-baked-in hours to get milliseconds if your speed units are on a scale of feet/second.
What's more is that Arduinos in general have no FPU, floating point calculations are very slow on them on top of the inherent dirt-in-sand way that IEEE floating point does. When I wrote business apps, especially payroll and billing where people go nuts over a single penny I used integers and small units (it's like fixed-point only simpler) to calculate with.
Oh BTW, Arduino does have 64-bit long long and unsigned long long integers with 19 places that can be 0 to 9.
.................
I don't see code that deals with speed changes. It looks like you use whatever speed you're going times elapsed time to get your final distance. I hope I missed the part where it handles speed change but those print statements in Odometer() seem to say no.
With every speed change I would calculate a segment of the trip and add it to the total. There will be segments/second.
I would want an Arduino with a crystal, not any with a resonator (temperature sensitive clock source) even if I had to roll my own.
Maybe think of adding an SD module and recording a log of time and speed recordings, at least 100/second to see what's happening. Maybe your trip can be calculated in 10ms or even 1ms segments? With more math using last and next segment speed values, rises and falls can be averaged a bit more accurately but do not expect to do more than get closer to real.
I think you have highlighted what I dont understand.
MPH comes in serially from another arduino with a simple revolution counter on it and as you say, I am not accounting for changes in speed, I am just using distance = speed x time which clearly isnt right. I'm pretty new to this so may struggle to understand a bit.
I have EEPROm save code but at the moment am just saving trip time, recalling it and adding it to new trip time to get total (Odo) time (and distance using the simple formula).
It sounds like I need some complex code to look at rising and falling edges etc.
I scanned the forum but cant find anything that helps much understanding the issue.
MPH comes in serially from another arduino with a simple revolution counter on it.
It sounds like I need some complex code to look at rising and falling edges etc.
You get speed that the other Arduino can give in small units that need no decimal point via serial, your code looks for Serial.available and not rising/falling edges.
The Arduino that reads the wheel may detect edges using fast polling (read the pin 50+K/sec, no biggie with non-blocking code) or use an interrupt if you want to get closer than +/- 10 microseconds.
Nick Gammon's Interrupts Tutorial:
His site has a load of microprocessor how-to tutorials, all fully-practical detailed with commonsense explanations to help and line by line code & explanations. Nick wants to make sure you get good help, bookmarking the MCU page and some lesson addys is a really good idea even if it will be a while before you get to them.
Yes, the other arduino speedo code uses interupts and seems pretty good at generating MPH.
On the Odometer, I was just wondering if some simple averaging would make any significant improvement (like only calculate miles every 5 seconds etc.)? and maybe ignore any results that is < last result.
I know it wont be finitely accurate but would that help much?
The finer you slice it, the more accurate your approximation will be. I take my techniques from integral calculus and the maths like Simpson's that pre-dated Newton.
See how thicker slices miss filling under the curve by bigger margins than thinner slices?
And the finest slices fill under the curve most exactly?
So if part of the sketch watches the speedometer and updates a variable holding speed then perhaps 1000x a second the known speed is used to add a tiny bit to distance with an even tinier bit of error. Our slice is 1 ms wide and as high as our speed, the area is distance.
Do you see how the slices in the pictures are all flat topped? By keeping 3 reads in RAM it is possible to give the slice corresponding to the middle slice a triangle on top connecting the corners and a slice at the top or bottom of a curve might get an extra up or down pointing triangle filling under the real curve even better.
Keeping a running average without much RAM involves squaring and taking square roots that would run like a lead turtle on Arduinos that have no FPU and only 32-bit floating point. You might get 10 reads processed in a second and lose some accuracy with every FP operation.
The integer math to do this is area of rectangle and triangle that can be done quickly on AVRs if floats are avoided.
Choose small enough working units, you don't need decimal points.
Consider that 60 miles is 60 * 5280 * 12 * 1000 = 3801600000 * 0.001 inch.
60 MPH is that per hour but the same speed as thousandths of an inch per millisecond
3801600000 / 3600000 = 1056 * 0.001" per millisecond that would add to the trip distance.
Speedometer would give 1056 for 60MPH, 105 for 6MPH. These have room to divide without rounding badly.
At this fine scale a 32-bit unsigned long could hold a trip 67.7867... miles long.
A 64-bit unsigned long long could hold a trip over 4 billion times as long so can drive past Jupiter.
This version of my speedo code uses KM per Hour to make it a bit simpler and KMHour is an Int.;
int BCDStart = 3;
int commonStart = 7;
boolean commonHigh = false;
int hall_pin = 2;
int wheelCirc = 1000;
int KMHour = 0;
int hall_count = 0;
#define SPEED_MEAS_WINDOW 500 //mS update speed at 2Hz (500mS/update)
#define WHEEL_CIRC 1000 //mm
#define NUM_TICKS_PER_REV 1
#define SPEED_K (float)((WHEEL_CIRC * 3600.0)/(1.0E+06 * (float)NUM_TICKS_PER_REV * ((float)SPEED_MEAS_WINDOW/1000.0) ))
#define SPEED_STATE_WAIT 0
#define SPEED_STATE_INIT 1
#define SPEED_STATE_MEASURE 2
#define SPEED_STATE_COMPUTE 3
#define BLANK 0x0a //invalid BCD digit used to discern blank digit; 4511 produces "blank" for this 0b1010
#define DISP_UPDATE_TIME 5 //update rate of digits (30mS "frame rate")
#define DISP_STATE_SETUP 0
#define DISP_STATE_DIGIT_HUNDS 1
#define DISP_STATE_DIGIT_TENS 2
#define DISP_STATE_DIGIT_ONES 3
#define COMMON_CATHODE 1
//#define COMMON_ANODE 1//alternate display option
#ifdef COMMON_CATHODE
#define COM_ON LOW
#define COM_OFF HIGH
#endif
#ifdef COMMON_ANODE
#define COM_ON HIGH
#define COM_OFF LOW
#endif
void setup()
{
int
i;
//set up Hall interrupt
pinMode(hall_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(hall_pin), HallInterrupt, FALLING);
// Enable all bcd outputs,
// set ABCD to blank (send 0b1010 which causes 4511 to blank outputs)
for ( i = 0; i < 4; i++ )
{
pinMode(BCDStart + i, OUTPUT);
digitalWrite(BCDStart + i, (BLANK & (1 << i)) ? HIGH : LOW);
}//for
//num digits == 3
for ( i = 0; i < 3; i++ )
{
pinMode(commonStart + i, OUTPUT);
digitalWrite(commonStart + i, COM_OFF );
}//for
KMHour = 0;
Serial.begin(9600);
}
//////////////////////////////////////////////////////////////////////////////////
// HallInterrupt
// on each falling edge of hall input, hall_count is incremented
//////////////////////////////////////////////////////////////////////////////////
void HallInterrupt( void )
{
hall_count++;
}//HallInterrupt
void MeasureSpeed( void )
{
int
calc_hall_count;
static byte
speed_state = SPEED_STATE_INIT;
static unsigned long
timeWindow;
unsigned long
timeNow;
switch ( speed_state )
{
case SPEED_STATE_INIT:
noInterrupts();
timeNow = millis();
hall_count = 0;
//integration window is SPEED_MEAS_WINDOW mS
timeWindow = timeNow + SPEED_MEAS_WINDOW;
interrupts();
speed_state = SPEED_STATE_MEASURE;
break;
case SPEED_STATE_MEASURE:
timeNow = millis();
if ( timeNow >= timeWindow )
{
noInterrupts();
//get a copy of the hall_count and move to compute speed
calc_hall_count = hall_count;
interrupts();
KMHour = (int)((float)calc_hall_count * SPEED_K);
//limit displayed value to 200kph
if ( KMHour > 200 )
KMHour = 200;
Serial.println(KMHour);
speed_state = SPEED_STATE_INIT;
}//if
break;
default:
speed_state = SPEED_STATE_INIT;
break;
}//switch
}//MeasureSpeed
void UpdateDisplay( void )
{
//update the display at 30Hz (~330mS) overall; requires us to have a 10mS step through this
//state machine
static int
last_digit_num = 0;
static int
ones, tens, hunds;
static byte
displayState = DISP_STATE_SETUP;
static unsigned long
displayUpdate = millis() + DISP_UPDATE_TIME;
if ( millis() < displayUpdate )
return;
//turn off last digit
digitalWrite(commonStart + last_digit_num, COM_OFF);
int
tempKMHour;
switch ( displayState )
{
case DISP_STATE_SETUP:
//get digits for latest speed
tempKMHour = KMHour;
hunds = (int)(tempKMHour / 100);
tempKMHour = tempKMHour - 100 * hunds;
tens = (int)(tempKMHour / 10);
tempKMHour = tempKMHour - 10 * tens;
ones = tempKMHour;
displayState = DISP_STATE_DIGIT_HUNDS;
break;
case DISP_STATE_DIGIT_HUNDS:
//if hundreds value is 0 skip it (leading zero)
if ( hunds == 0 )
writeDigitToDisplay( BLANK );// first send remainder to function then 2nd pass send 10's to function
else
writeDigitToDisplay( hunds );
digitalWrite(commonStart + 2, COM_ON);
last_digit_num = 2;
displayUpdate = millis() + DISP_UPDATE_TIME;
displayState = DISP_STATE_DIGIT_TENS;
break;
case DISP_STATE_DIGIT_TENS:
//if hundreds value is 0 and tens is zero, skip it (another leading zero)
//if hundreds value is 0 skip it (leading zero)
if ( hunds == 0 && tens == 0 )
writeDigitToDisplay( BLANK );
else
writeDigitToDisplay( tens );
digitalWrite(commonStart + 1, COM_ON);
last_digit_num = 1;
displayUpdate = millis() + DISP_UPDATE_TIME;
displayState = DISP_STATE_DIGIT_ONES;
break;
case DISP_STATE_DIGIT_ONES:
//always display ones
writeDigitToDisplay( ones );
digitalWrite(commonStart + 0, COM_ON);
last_digit_num = 0;
displayUpdate = millis() + DISP_UPDATE_TIME;
displayState = DISP_STATE_SETUP;
break;
}//switch
}//UpdateDisplay
void loop()
{
MeasureSpeed();
UpdateDisplay();
}//loop
void writeDigitToDisplay(int value)
{
int
i;
for ( i = 0; i < 4; i++ )
digitalWrite( BCDStart + i, (value & (1 << i)) ? HIGH : LOW );
}//writeDigitToDisplay
I am trying to understand how to use integer maths and how your last post would apply to this?
I can keep it all in KM and just do the miles convert to the display later which probably helps.