Tank Speedometer Programming Problem

Hi Guys

I'm building a ride on tracked vehicle ( Tank ) for my grandson and I need to include a speedometer which will read speeds limited from 0 - 6mph or so. At some point I will need to include a memory that keeps a note of the max speed achieved in the last movement, but I'm nowhere near that yet.

Being an absolute novice with Arduino, I found a project that fitted the bill nicely and I modified the sketch to display a reading at 100 times the actual, so 6 mph becomes 600 mph and then I've tried and tried to put decimal point between the number and the noughts, but .. .. .

I can't see the forest for all them trees !

There is a section in the sketch that rounds up a decimal to a whole number but when I try to comment it out I get all kinds of compiling issues.

I've not used the section on brightness, nothing connected on the breadboard.

Please would somebody show me where I'm going wrong .. .. .

Thanks

S

// Matthew McMillan

// @matthewmcmillan

// http://matthewcmcmillan.blogspot.com

//

// Digital speedometer

//

// VSS on car connects to pin 5

// CLK on display to Analog pin 5

// DAT on display to Analog pin 4

//

// Requires two Adafruit libraries:

//   Adafruit_GFX

//   Adafruit_LEDBackpack



#include "Adafruit_GFX.h"    // Adafruit Core graphics library

#include "Adafruit_LEDBackpack.h" // Seven Segment display library

#include "SPI.h"

#include "Wire.h"



void setBrightness(uint8_t b, byte segment_address) {

  if (b > 15) b = 15; //Max brightness on this display is 15.

  if (b < 1) b = 1; // Brightness of 0 is too dim so make 1 the min.

  Wire.beginTransmission(segment_address);

  Wire.write(0xE0 | b); // write the brightness value to the hex address.

  Wire.endTransmission();

}



const int lightPin = 0;

const int hardwareCounterPin = 5;

const int samplePeriod = 1000; //in milliseconds

const float pulsesPerMile = 42240; // this is pulses per mile for Toyota. Other cars are different.

const float convertMph = pulsesPerMile / 3600;

unsigned int count;

float mph;

unsigned int imph;

int roundedMph;

int previousMph;

int prevCount;



const int numReadings = 30;     // the number of readings for average brightness

int readings[numReadings];      // the readings array for the analog input

int index = 0;                  // the index of the current reading

int total = 0;                  // the running total

int average = 3;



Adafruit_7segment matrix = Adafruit_7segment();

byte segment_address = 0x70; //This is hex address of the 7 segment display

boolean drawDots = true;



void setup(void) {

  Serial.begin(9600);



  // initialize all the brightness readings to 3:

  for (int thisReading = 0; thisReading < numReadings; thisReading++)

    readings[thisReading] = 3;



  // Start up the 7 segment display and set initial vaules

  matrix.begin(segment_address);

  setBrightness(3, segment_address);

  matrix.println(0);

  matrix.writeDisplay();



  TCCR1A = 0; //Configure hardware counter

  TCNT1 = 0;  // Reset hardware counter to zero

}



void loop() {

  /////////////////////////////////////////////////////////////

  // Set the LCD brightness using a running average of

  // values to help smooth out changes in brightness.

  //

  // read from the sensor:

  int reading  = analogRead(lightPin);

  int brightness = (reading / 2) / 15;

  if (brightness > 15) {

    brightness = 15;

  }

  if (brightness < 1) {

    brightness = 1;

  }



  readings[index] = brightness;

  // add the reading to the total:

  total = total + readings[index];

  // advance to the next position in the array:

  index = index + 1;

  // if we're at the end of the array...

  if (index >= numReadings)

    // ...wrap around to the beginning:

    index = 0;

  // calculate the average:

  total = 0;

  for (int thisReading = 0; thisReading < numReadings; thisReading++) {

    total = total + readings[thisReading];

  }

  average = total / numReadings;

  setBrightness(average, segment_address); //Set the brightness using the average





  /////////////////////////////////////////////////////////////

  // This uses the hardware pulse counter on the Arduino.

  // Currently it collects samples for one second.

  //

  bitSet(TCCR1B, CS12); // start counting pulses

  bitSet(TCCR1B, CS11); // Clock on rising edge

  delay(samplePeriod); // Allow pulse counter to collect for samplePeriod

  TCCR1B = 0; // stop counting

  count = TCNT1; // Store the hardware counter in a variable

  TCNT1 = 0;     // Reset hardware counter to zero

  mph = (count / convertMph) * 100; // Convert pulse count into mph.

  imph = (unsigned int) mph; // Cast to integer. 10x allows retaining 10th of mph resolution.



  int x = imph / 1.0;

  int y = imph % 10;



  // Round to whole mile per hour

  if (y >= 5) {

    roundedMph = x + 1;

  } else {

    roundedMph = x ;

  }



  //If mph is less than 1mph just show 0mph.

  //Readings of 0.9mph or lower are some what erratic and can

  //occasionally be triggered by electrical noise.

  // if (x == 1) {

  //     roundedMph = 1;

  // }



  // Don't display mph readings that are more than 50 mph higher than the

  // previous reading because it is probably a spurious reading.

  // Accelerating 50mph in one second is rocketship fast so it is probably

  // not real.

  if ((roundedMph - previousMph) > 50) {

    matrix.println(previousMph);

  } else {

    matrix.println(roundedMph);

  }



  matrix.writeDisplay(); // Write the value to the 7 segment display.



  previousMph = roundedMph; // Set previousMph for use in next loop.

}

Reading the code it looks more like an imph value of 600 would be 60.0 mph.

did you change this code:

int x = imph / 1.0;

from something more like this:

int x = imph / 10.0;

Because that would make a lot more sense. In the second case, x is your whole number and y is the decimal which you can use for printing, but I'd still do the sanity checks.

A gigantic part of that code is redundant.

You convert pulses into mph by dividing by 42240 (comment says this is for a Toyota) then multiplying by 3600 then multiplying by 100. Then there's a homebrew rounding function and you throw out a few readings. Since it's entirely possible that your tank may accelerate by 0.5mph, which you store as 50, then the thrower-outer probably has a lot of work to do.

You've killed the important part of the rounding function. Notice the comment that says "x10"? You've lost the multiply-by-ten.

All of that is unnecessary. The matrix library obviously uses the standard println() function. println() accepts floats and it prints them with arbitrary decimal places.

 matrix.println(mph,2);

It is possible that println() is special to that library. Then you might consider using the standard C library rounding functions.

Hi Guys

Thanks for the prompt replies .. .. .. ..

I realise that the code imph value @ 600 would be 60mph. I changed it when I was calculating the pulse to mph so that I could easily see when the figures were right.

The conversion at 42240 is correct, I should have deleted the Toyota reference from the original code which had it set at 4000.

Yes, I did change the int x = imph / 10.0 to int x = imph / 1.0

I'm not good enough at this to take in your suggestions without concentrated thought, which I will do, have a further play and then come back to you .. ..

Much appreciate the help

S

I think your confusion stems from trying to convert code that rounds to the nearest whole MPH to code that preserves the fractional part. You want to round the number to 2 decimal places, no?

The easiest route is to use the dtostrf() function, which will format a string rounding to the desired number of decimal places.

void setup()
{
  Serial.begin(9600);

  char mphStr[6];   // buffer for mph as string
  float mph;

  // convert mph to string, rounding to 2 decimal places
  mph = 6.04456;
  dtostrf(mph, 3, 2, mphStr);

  Serial.println(mphStr);

  // this value should round up
  mph = 6.04556;
  dtostrf(mph, 3, 2, mphStr);

  Serial.println(mphStr);

}

void loop()
{


}

Serial output:

6.04
6.05

Morning

First a note to say Thank You for your help, I believe I've cracked it having implemented a cornucopia of your advice. I've tidied up the sketch somewhat and attached it for your info.

My next step .. .. .. the law in the UK says a minor may not be in control of a powered vehicle in a public place that is capable of more than 4mph;

I have a PWM Controller for the 2 motors which can be limited to 2, 4 or 6 mph but if there was a challenge I want to be able to prove the max speed achieved during the last movement which means adding a memory segment and recording the highest count to that memory which can be displayed either on the same 7 seg. when the vehicle is stationary or on a separate 7 seg.

I have absolutely no idea how to go about that, but then, I had no idea how to build an Arduino Speedometer 5 days ago !!

Thanks again

S

#include "Adafruit_GFX.h"    // Adafruit Core graphics library

#include "Adafruit_LEDBackpack.h" // Seven Segment display library

#include "SPI.h"

#include "Wire.h"



void setBrightness(uint8_t b, byte segment_address) {

  if (b > 15) b = 15; //Max brightness on this display is 15.

  if (b < 1) b = 1; // Brightness of 0 is too dim so make 1 the min.

  Wire.beginTransmission(segment_address);

  Wire.write(0xE0 | b); // write the brightness value to the hex address.

  Wire.endTransmission();

}

const int lightPin = 0;

const int hardwareCounterPin = 5;

const int samplePeriod = 1000; //in milliseconds

const float pulsesPerMile = 42240; // this is calculated for tank

const float convertMph = pulsesPerMile / 3600;

unsigned int count;

float mph;

unsigned int imph;

int roundedMph;

int previousMph;

int prevCount;


Adafruit_7segment matrix = Adafruit_7segment();

byte segment_address = 0x70; //This is hex address of the 7 segment display

boolean drawDots = true;



void setup(void) {

  Serial.begin(9600);


  // Start up the 7 segment display and set initial vaules

  matrix.begin(segment_address);

  setBrightness(8, segment_address);

  matrix.println(0);

  matrix.writeDisplay();



  TCCR1A = 0; //Configure hardware counter

  TCNT1 = 0;  // Reset hardware counter to zero

}

void loop() {


  // This uses the hardware pulse counter on the Arduino.
  // Currently it collects samples for one second.


  bitSet(TCCR1B, CS12); // start counting pulses

  bitSet(TCCR1B, CS11); // Clock on rising edge

  delay(samplePeriod); // Allow pulse counter to collect for samplePeriod

  TCCR1B = 0; // stop counting

  count = TCNT1; // Store the hardware counter in a variable

  TCNT1 = 0;     // Reset hardware counter to zero

  mph = (count / convertMph) * 1; // Convert pulse count into mph.

  imph = (unsigned int) mph; // Cast to integer. 10x allows retaining 10th of mph resolution.


  int x = imph / 1.0;

  int y = imph % 10;


  matrix.println(mph, 1);


  matrix.writeDisplay(); // Write the value to the 7 segment display.


  previousMph = roundedMph; // Set previousMph for use in next loop.

}

To measure and store max speed you just need another global float variable, say MaxSpeed. Set it zero in setup and use an if to check whether the speed you just measured is greater. If so, set MaxSpeed to mph.

You could add a switch to control whether your display shows current speed or session max. You could also store the speed in EEPROM for a more permanent record.

Hi Guys .. ..

Firstly a reminder, I'm an Arduino novice .. .. ..

I've attached the code for the latest version of my tank speedometer.

In outline, pin 5 receives a square wave pulse from a sensor on one of the final drives; the pulse count is converted into mph by a calculation, seen in the code and the result is shown on a 7 segment display;

Also, there is a max speed achieved calculation that is written to Eeprom and when the vehicle is stopped, ie 0 mph is displayed on the serial monitor.
Ultimately, I want to replace that serial monitor with a second 7 segment array, but I'm not there yet.

I have a couple of foibles I don't understand and I hoping one of you whizzkids can help, please .. ..

  1. When the serial is triggered ( at 0 mph ) it continuously displays the Eeprom, over and over again. Once is enough, but I can't stop it scrolling.

  2. When the thing is powered but idle, the Eeprom display shows '0.19' but I can't figure where it's getting that number from ?

Remember if you don't think of me as an Arduino Dumbkopf you're overestimating my abilities !

Any advice would be much appreciated.

Thanks

S

#include "Adafruit_GFX.h"    // Adafruit Core graphics library

#include "Adafruit_LEDBackpack.h" // Seven Segment display library

#include "SPI.h"

#include "Wire.h"

#include "EEPROM.h"

void setBrightness(uint8_t b, byte segment_address) {

  if (b > 15) b = 15; //Max brightness on this display is 15.

  if (b < 1) b = 1; // Brightness of 0 is too dim so make 1 the min.

  Wire.beginTransmission(segment_address);

  Wire.write(0xE0 | b); // write the brightness value to the hex address.

  Wire.endTransmission();

}

const int hardwareCounterPin = 5;

const int samplePeriod = 1000; //in milliseconds

const float pulsesPerMile = 42240; // this is calculated for tank

const float convertMph = pulsesPerMile / 3600;

unsigned int count;

float mph;

float MaxSpeed;

unsigned int imph;

int addr = 0;

int max;

float value;

Adafruit_7segment matrix = Adafruit_7segment();

byte segment_address = 0x70; //This is hex address of the 7 segment display

boolean drawDots = true;


void setup(void) {

  // Start up the 7 segment display and set initial values

  matrix.begin(segment_address);

  setBrightness(8, segment_address);

  matrix.println(0);

  matrix.writeDisplay();

  MaxSpeed = 0;

  TCCR1A = 0; //Configure hardware counter

  TCNT1 = 0;  // Reset hardware counter to zero

}

void loop() {


  EEPROM.write(addr, 0); // Returns Eeprom data to zero

  bitSet(TCCR1B, CS12); // start counting pulses

  bitSet(TCCR1B, CS11); // Clock on rising edge

  delay(samplePeriod); // Allow pulse counter to collect for samplePeriod

  TCCR1B = 0; // stop counting

  count = TCNT1; // Store the hardware counter in a variable

  TCNT1 = 0;     // Reset hardware counter to zero

  mph = (count / convertMph) * 1; // Convert pulse count into mph.

  imph = (unsigned int) mph; // Cast to integer. 10x allows retaining 10th of mph resolution.


  int x = imph / 1.0;

  int y = imph % 10;

  int val = MaxSpeed;


  matrix.println(mph, 1);

  matrix.writeDisplay(); // Write the value to the 7 segment display.

  MaxSpeed = max(mph, MaxSpeed); // Stores max achieved speed

  if (mph > 0)
  {
    EEPROM.write(addr, MaxSpeed); // Writes max achieved speed to Eeprom
  }


  else if (TCCR1B == 0)

    Serial.begin(9600);
  delay(3000);
  Serial.println(MaxSpeed);
  Serial.end();

}

Consider using EEPROM put instead of write. The EEPROM cells have a finite life; put only writes if there's change.

To print max speed once, you can use a boolean flag to tell you you've printed it. Set it when you print and clear it when you're writing the max speed on the EEPROM. Then check the flag before you print.

It looks as though you should have some braces around the serial commands - I think your intent was that they only execute if the last else if is true.

Serial.begin is usually only executed once and thus belongs in setup. serial.end is not needed at all.

Hi WB

Thanks for your advice, I appreciate it .. .. .

wildbill:
Consider using EEPROM put instead of write. The EEPROM cells have a finite life; put only writes if there's change.

I read that in the notes, but also concluded that since the mph will change every time the thing moves, accelerates even I decided it would have little benefit. However, I'll take your advice and change it.

wildbill:
To print max speed once, you can use a boolean flag to tell you you've printed it. Set it when you print and clear it when you're writing the max speed on the EEPROM. Then check the flag before you print.

I'm afraid that's completely over my head, I need to go and read, study and try that out.

wildbill:
It looks as though you should have some braces around the serial commands - I think your intent was that they only execute if the last else if is true.

Serial.begin is usually only executed once and thus belongs in setup. serial.end is not needed at all.

Now that is just taking the mick ... I tried and tried to put curly braces around the serial commands but it would not compile, so I left them out.
Having read your note, I tried again, and they compiled first time ... ... you must be better looking than me !!

The Serial.begin and Serial.end was just an attempt by me to get a 'once only' report.

I look into the Boolean flag and give it some wellie .. .. .. .

Thanks again

S

like this:

  if (mph > 0)
    {
    EEPROM.write(addr, MaxSpeed); // Writes max achieved speed to Eeprom
    PrintedMax=false;
    }
  else if (TCCR1B == 0 && !PrintedMax)
    {
    delay(3000);
    Serial.println(MaxSpeed);
    PrintedMax=true;
  }

You will need to declare PrintedMax as a global variable.

Hi WB

Thank you ... .. .. .

I was grateful for the advice, I didn't expect you to do the job for me ! And, of course, it works first time ..

All done now, until I can assembly the thing properly in the Control Box which will be a few weeks from now, I've got some serious engineering to do first.

Thanks again

S