I bought a green 7-Segment Display w/I2C backpack and a 2.2" color TFT LCD display from Adafruit to experiment with.
|
|
The backpack on the 7 segment display allows it to be controlled by the Arduino using the I2C protocol (also called Two Wire Interface). Without the I2C backpack you would have to directly control all eight segments of each number which would use up all the pins on the Arduino or you would have to figure out some other method which would probably end up being very similar to what Adafruit did. Each Arduino model has certain pins that are used for I2C. On the Uno pins analog 4 and analog 5 are used for this purpose. See the Wire library page for more I2C info.
What is a VSS?
Most modern computer controlled cars since the late 1990's have a sensor called a VSS or Vehicle Speed Sensor. The location of the sensor varies but they all do the same thing which is count the number of times some part of the drive train rotates. On my car the VSS is in the transmission. The output of the VSS is some number of pulses per mile in a 5 vdc square wave signal. The first step in this project was to find out how many pulses per mile my VSS puts out. This number varies from car manufacturer to car manufacturer and sometimes model to model. I found a company that makes aftermarket cruise control systems and their installation manual contained a list of cars and VSS pulses per mile. The pulses per mile value can range from 2000 all the way up to 38600. The VSS on my car puts out 4000 ppm which seems to be a common value but you must find out the correct value for your particular vehicle otherwise the readings will be incorrect. You can also consult their installation manual for the location of the VSS signal wire. It is important that you only tap into the VSS wire and not completely interrupt it. The engine and transmission computers use this signal as well.
Time for some math
So now I know my VSS puts out 4000 pulses per mile. Next I need to figure out how to convert that into miles per hour. After looking at some example code on how to measure pulses I decided I would count the VSS pulses for one second. With that info I could then convert the pulse count into mph. First I converted one hour (the hour from miles per hour) into seconds which is 3600. Then divide the number of pulses per mile by the number of seconds (4000/3600). Then you divide the number of pulses counted on the sensor by that value. Here is my final formula:
miles per hour = pulse count/(VSS pulses per mile/time period)
Building the prototype
I started with an Arduino Uno and an Adafruit Protosheild. I hacked up an old USB cable to connect the 7-segment display. A USB cable is perfect for this. Two wires for the I2C and two larger gauge wires for power and ground. I cut off the ends of the USB cable and stripped each of the wires. I tinned the wires with solder so I could plug them directly into the bread board and added some heat shrink tubing for strain relief. Here is a Fritzing diagram of the wiring:
- Connect 'C' (CLK) on the display to Analog #5. (Leonardo Digital #3, Mega digital #21)
- Connect 'D' (DAT) on the display to Analog #4. (Leonardo Digital #2, Mega digital #20)
- Connect GND on the display to common ground
- Connect VCC+ on the display to power +5V
- VSS sensor on the vehicle connects to Digital #5
- Analog #0 is used to measure a Photocell (Light Dependent Resistor)
Here is how the wiring looks
After I tested it at night I decided to add a photocell (Light Dependent Resistor) to control the brightness of the display. It took some tweaking to get the brightness changes just right. Initially the brightness of the display fluctuated with every street light I passed. I changed the code to use an average of 30 light level readings. That way the brightness changes slowly.
Here is how it looks in my car during the day.
and at night
The github repo is here https://github.com/matt448/arduino/tree/master/Speedometer_7seg
The code for the hardware pulse counting section came straight from example 18.7 in the Arduino Cookbook. My understanding of how this works is: the ATmega chip has a few hardware timers. This code uses a timer on Digital #5 on the UNO. The TCCR1B part of the code sets bits on that timer to configure it to count pulses. The code then waits for one second and reads how many pulses were stored in the hardware counter. Then the hardware counter is reset to zero for the next loop. Keep in mind this code is written specifically for an Arduino Uno. It would need to be modified to work with other boards.
Here is the current version of the code in a gist.
I made a quick little cardboard housing for the 7-segment display to shield it from the sun.
After I tested it at night I decided to add a photocell (Light Dependent Resistor) to control the brightness of the display. It took some tweaking to get the brightness changes just right. Initially the brightness of the display fluctuated with every street light I passed. I changed the code to use an average of 30 light level readings. That way the brightness changes slowly.
Here is how it looks in my car during the day.
and at night
The code
The github repo is here https://github.com/matt448/arduino/tree/master/Speedometer_7seg
The code for the hardware pulse counting section came straight from example 18.7 in the Arduino Cookbook. My understanding of how this works is: the ATmega chip has a few hardware timers. This code uses a timer on Digital #5 on the UNO. The TCCR1B part of the code sets bits on that timer to configure it to count pulses. The code then waits for one second and reads how many pulses were stored in the hardware counter. Then the hardware counter is reset to zero for the next loop. Keep in mind this code is written specifically for an Arduino Uno. It would need to be modified to work with other boards.
Here is the current version of the code in a gist.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 = 4000; // 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)*10; // Convert pulse count into mph. | |
imph = (unsigned int) mph; // Cast to integer. 10x allows retaining 10th of mph resolution. | |
int x = imph / 10; | |
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 == 0){ | |
roundedMph = 0; | |
} | |
// 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. | |
} |