I'm not a granny, and I don't drive like one either, but a couple of weeks ago I was driving my fiancee's car and noticed that people were just flying by me on the freeway. The speedometer said I was driving a little over the speed limit. By chance I passed one of those speed trap trailers that the police put on the side of the road. To my surprise there was a 10 mph differential between the speed indicated on the trap and the speed indicated on my speedometer! I instantly wondered how often my fiancee was honked at. So I did what any good enginerd/boyfriend would do; I built her a new one! In this tutorial I'll show you how I used 4 different SparkFun products to build a pretty cool GPS powered digital speedometer.
I started with the Package Tracker (because I had a spare one on my desk), but you can use any GPS device you around that has a UART available. The code in this tutorial was written for the LPC2148 ARM7, which is on the Package Tracker, but it would be pretty simple to modify it to work on any platform. Along with the Package Tracker I needed a GPS module; I chose the EM408 because I'm familiar with it. I also needed a microSD card so that I could use the USB ARM Bootloader to load my code onto the board.
For the display I decided to use the
monochrome Graphic LCD 128x64. I could have used a smaller text based
LCD but I wanted to make the speedometer replicate the analog speedometer typically seen in your dashboard. To make interfacing
with the LCD easier I decided to use the Serial Graphic LCD Backpack
that Pete made for the Graphic LCD Huge 160x128. The only other
product I ended up needing was the NCP1400 5V DC-DC Step-Up Converter Breakout Board so I could power the LCD.
There are four different products we have to connect to get this thing ready for programming: the Package Tracker, the Graphic LCD, the Serial Graphic Backpack, and the 5V Step-Up board. As should be obvious, the GPS module plugs directly into the Package Tracker and the microSD card also just gets inserted into the microSD socket on the board. Getting the LCD Backpack onto the LCD is as easy as soldering a female header onto one of the boards, and a male header onto the other, and plugging them in.
After reading the LCD datasheet I found that I had to power the LCD with 5 volts. Unfortunately the Package Tracker is a 3.3V system, and is powered by a 3.7V LiPo battery. Luckily SparkFun has a 5V DC-DC converter breakout board! Now we just have to figure out how to get power from the Package Tracker to the 5V step-up, and how to get the 5 volts from the step-up board to the LCD. The Package Tracker has a nice 6 pin header on one side that has VCC and GND. Maybe I can get power from this? Better check the schematic first though. Turns out we can't use this VCC for the 5V step up board; the VCC on the header is from the 3.3V regulator on the Package Tracker. While 3.3V is enough for the step-up board we can only source 150 mA from this regulator. Considering the regulator has to power the LPC2148 plus all of the peripheral components on the Package Tracker I don't want to risk adding another pretty big load to it by asking it to power the LCD and the Serial Backpack.
Since we can't use the VCC on the header we'll just have to take power directly from the battery to the 5V step-up board. I put the step-up board on a breadboard so that it was easier to work with, but you could probably figure out a way to get this working without it. Once we've got power to the breakout board, we'll just connect the 5V output to the Serial Backpack and we're good to go!
Writing the Code
The code for this project can be split up into two general parts: getting the speed data from the GPS module, and sending the data to the LCD. The first thing we have to do, though, is draw the speedometer. The speedometer is really just a circle with a bunch of numbers placed inside of it. Pete did a great job on the Serial Graphic Backpack firmware and included a circle command, as well as text commands. All I had to do was figure out how big I wanted the circle to be and where to place it, along with where I wanted to place all of the text. Once I knew these things I just had to send the serial commands to the LCD Backpack. The serial commands for the LCD Backpack can be found here in the Serial Graphic Backpack datasheet.
To be honest I found the size and location of the circle and numbers by using trial and error (...a lot of error, let me tell you that!). You can see all of the numbers in the drawSpeedometerOutline function of the Speedometer.c file and the Speedometer.h file, but I can quickly tell you that I used a circle with a radius of 34 pixels centered at the X,Y coordinates 34,26. Does it seem weird to you that the center of the circle is at a height of 26 pixels, but the radius of the circle is 34? It should seem as if the circle would run off the bottom of the LCD. And it does! But this was precisely the look I was going for.
Now that I have the outline of my speedometer I just need a ?needle? to indicate the current speed. I thought the easiest way to do this would be to use the LINE command on the serial backpack, however I would still need to know the coordinates for the line that corresponded to each tick on the speedometer. Unfortunately this called for a little bit of trigonometry! I suppose I could have stuck with my trial and error method, but I wanted to finish sometime this century; so I busted out the calculator (well, actually I busted out Excel but you get the picture). Take a look at this figure and the equations to see how I was able to get the line coordinates.
We know one set of coordinates for the needle will be the center of the circle; the length of the needle is up to you. I chose my needle to be 20 pixels long because it seemed like the needle could go all the way around the speedometer without running over the numbers inside the circle. Knowing these two things would allow me to find the second set of X,Y coordinates for each MPH on the speedometer. The first thing I did was relate the degrees of the circle to a speed. I want the speedometer to display up to 100 mph (even though there's no way that car will EVER hit that speed). So if I estimated that if I used the entire circle for speed I would have 133 mph. With this number I found that (1 MPH) is equal to (2.7 Degrees) on the circle. Now I can use basic trigonometric functions to find all of my coordinates. To find the X coordinate I use the equation:
X Length = sin(MPH * 2.7) * 20
And to find the Y coordinate I use the equation:
Y Length = cos(MPH * 2.7) * 20
I would need to perform this equation for each MPH unit on the speedometer to get all of the X,Y coordinates; but a simple spreadsheet can perform this for me in a flash. You can see the spreadsheet I used in this file. After running the calculation I just copied all of the coordinates into an array in my code.
Whew! That's definitely enough math for one day. After running some tests with my new coordinates I had some good news and some bad news. The good news is that the equations worked and the speedometer needle seemed to be right on track. The bad news was that the LINE command wasn't going to be fast enough to use in the system. This wasn't too much of a hassle, I took the code for the LCD backpack and added a new command called SPEED; it basically just takes an input speed and draws a line.
So now we have a speedometer drawing, and we can send a speed to it. All we have left is to get our speed! We'll get our speed from the GPS module on the LPC2148. If you've never used a GPS module before, don't be intimidated. It's very easy. In fact the only thing we do in this application is specify which NMEA message we want to receive, and then listen to the messages on the UART. You can find more about NMEA messages in this document. I chose to listen to the RMC message because it includes ground speed in the output. How simple is that! Every time I receive an RMC message from the GPS module, I just extract the speed from the message. The ground speed is reported in meters per second, so I just convert the units to miles per hour by multiplying the value by 1.151. It's not a perfect conversion but it will do.
Once I have the speed from the GPS module, I just need to post it to the LCD by sending my SPEED command. However, my GPS module only updates once every second. This means that my speed value can jump around. I don't want the needle on my speedometer jumping around! I want the needle to increment from it's current position to it's new position one mile per hour at a time. This is pretty easy to do by using two different variables for speed: one of the variables keeps track of the GPS speed, and the other variable acts like a ?chaser? that is always trying to catch the GPS speed. So, the GPS speed might go from 0 to 5 MPH in one reading; but the ?chaser? value has to increment it's speed by going from 0 to 1, 1 to 2 and so on. Then instead of sending the GPS speed straight to the LCD, we send the ?chaser? speed to the LCD so we get a more realistic visual on the LCD.
Where To Go From Here
The digital speedometer works great, but what can you do to it to make it better? I've created a list of improvements that could be made to our project. I'd love to see what you come up with!
I hope you enjoyed this tutorial! If you have any problems, or see any mistakes be sure to let us know by leaving a comment!