GPS Speedometer

by LEKtronics in Circuits > Arduino

1794 Views, 19 Favorites, 0 Comments

GPS Speedometer

gps_final_1.jpg
gps_final_2.jpg

Do you ever have a need for an accurate speedometer?

I wanted to calibrate the speedometer on my Fiat 500 Abarth. I have two sets of wheels for the car, a set of 17’s with performance tires for Autocrossing, and a set of 16’s with All Season tires for everyday driving.

The speedometer is accurate with the 17’s even though they didn’t come on the car. The car came with the 16’s, and when they are in use, the speedometer reading is 2-3 MPH low. How do I know this?

This is what this tutorial is all about.

Supplies

240x320 2.4" EVE TFT Display from Crystalfontz.com This is the new sunlight readable display

Adafruit Ultimate GPS This is an improved edition. The one I used is NLA, but the function is the same.

Reset Button (any N.O. push button) Header pins and sockets

The following were from the old display (for reference only).

Adafruit Monochrome 1.54 128x64 OLED Display. This is a discontinued item, but it wasn't bright enough anyway.

Arduino MKR 1000 WIFI I am going to try and use the new display with this processor when I have time.

Lithium Ion Battery 3.7v 2000mAh For the MKR100 (the MKR has a built-in charger)

The Original

old_gps.jpg

I built a GPS-based Speedometer with a GPS receiver and OLED display from Adafruit powered by an Arduino MKR1000. I chose the components based on their small footprint and low power consumption.

Everything fit nicely in a 3”x4”x2”h project box. All parts used are listed in the description.

(The upper display in the picture is the original. The lower display is a $4 unit from Temu. As you can see, it is not very accurate !)

I used my new GPS Speedometer to accurately obtain my speed, and using AlfaOBD, I was able to calibrate the Fiat’s speedometer. I am making a separate video to show how this is accomplished.

I have attached the files for the original GPS Speedometer just to show how much simpler it was to program.

New and Improved

gps_done.jpg

Everything seemed all good, but I had trouble reading the display in the bright sun. I complained about this on a forum that I belong to, and Brent from Crystalfontz came to my rescue. “We have a display for that!” he said. He hooked me up with a 240x320 2.4” EVE TFT Display. It has a very bright screen, readable in sunlight. The resolution and EVE acceleration allowed me to increase the size of my Speed reading, and to fancy the display up. As before, all parts used are listed in the description.

If you want to know how I did it, keep reading.

If not, thanks for your interest so far.

Construction

GPS_fritzing.jpg

The new 2.4 EVE TFT display came with its own development kit.The kit includes the display, a Seeduino V4.3 (basically a Arduino Uno clone), a breakout board that interfaces the display with the Seeeduino, a ribbon cable that goes between the breakout board and the display, and some jumper wires. I chose not to use the jumper wires. I hardwired (soldered) everything to header pins that make a better connection than the jumpers.

The wiring is as follows:

Breakout to Seeeduino

Pin 1 ----------- 3v3 Orange +3v

Pin 2 ----------- GND Black GND

Pin 3 ----------- D13 White SCK

Pin 4 ----------- D11 Yellow MOSI

Pin 5 ----------- D12 Green MISO

Pin 9 ----------- D9 Brown CS (EVE)

Pin 10 --------- D7 Violet not used

Pin 11 --------- D8 Blue PD_N (EVE Reset)

GPS to Seeeduino

Pin 1 ---------- 5v White +5v

Pin 2 ---------- GND Black GND

Pin 3 ---------- D4 TX Yellow to GPS RX

Pin 4 ---------- D5 RX Green to GPS TX

Reset button to Seeeduino

Pin 1 -------- RST

Pin 2 -------- GND


I 3D printed a clear case to mount the display and contain the components. I chose clear so that the status LED's on the GPS and Seeeduino would be visible. I will attach the .stl file below.

Downloads

Programming

LEK_Base_Bitmap.png
LEK_Dish_Bitmap.png
LEK_Mockup.png

The programming is the most difficult step in the entire project. The EVE driver for the display is very powerful and very complicated. There is a lot that EVE will do, but only a small fraction is required for this project.

Most of the files came from a demo program from Crystalfontz written by Brent A. Crosby and are used with his permission.

The first step is to make the GPS and the display communicate with the Seeeduino. This is accomplished by defining the Seeeduino pins using the Arduino IDE software and creating a .ino file. It is beyond the scope of this project to explain all of the ins and outs of this program, but I will try to hit the important points.

The complete .ino file and associated header and .cpp files are attached below.

This is the first step in connecting to the GPS:

#include <Adafruit_GPS.h>// Adafuit GPS library
#include <SoftwareSerial.h> // the Seeduino only has one serial port and it is used for programming so Software Serial needs to be enabled
// Connect the GPS Power pin (pin 1) to 5V (White)
// Connect the GPS Ground pin (pin 2) to ground (Black)
// Connect the GPS TX (transmit) pin (pin 4) to Digital 5 RX (Green)
// Connect the GPS RX (receive) pin (pin 3) to Digital 4 TX (Yellow)
SoftwareSerial mySerial(5, 4);//RX_TX Green _ Yellow;
// Just to be clear - the TX of the GPS connects to the RX of the Seeeduino
// The RX of the GPS connects to the TX of the Seeeduino
// Connect to the GPS on the software port
Adafruit_GPS GPS(&mySerial);

Then we need to communicate with the display:

 //Initialize GPIO port states
 // Set CS# high to start - SPI inactive
 SET_EVE_CS_NOT;
 // Set PD# high to start
 SET_EVE_PD_NOT;
 SET_SD_CS_NOT;

 //Initialize port directions
 // EVE interrupt output (not used in this example)
 pinMode(EVE_INT, INPUT_PULLUP);
 // EVE Power Down (reset) input
 pinMode(EVE_PD_NOT, OUTPUT);
 // EVE SPI bus CS# input
 pinMode(EVE_CS_NOT, OUTPUT);
 // USD card CS
 pinMode(SD_CS, OUTPUT);
 // Optional pin used for LED or oscilloscope debugging.
 pinMode(DEBUG_LED, OUTPUT);

 // Initialize SPI
 SPI.begin();
 //Bump the clock to 8MHz. Appears to be the maximum.
 SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
 DBG_GEEK("SPI initialzed to: 8MHz\n");

The CFA10109_defines.h file contains the pin assignments for the display:

//============================================================================
// Wiring for prototypes.
//  ARD     | Port | 10098/EVE          | Color
// -----------+------+---------------------|--------
// #3/D3    | PD3 | DEBUG_LED          | N/C
// #7/D7    | PD7 | EVE_INT            | Purple
// #8/D8    | PB0 | EVE_PD_NOT         | Blue
// #9/D9    | PB1 | EVE_CS_NOT         | Brown
// #10/D10   | PB2 | SD_CS_NOT          | Grey
// #11/D11   | PB3 | MOSI (hardware SPI) | Yellow
// #12/D12   | PB4 | MISO (hardware SPI) | Green
// #13/D13   | PB5 | SCK (hardware SPI) | White
//Arduino style pin defines
// Debug LED, or used for scope trigger or precise timing
#define DEBUG_LED  (3)
// Interrupt from EVE to Arduino - input, not used in this example.
#define EVE_INT    (7)
// PD_N from Arduino to EVE - effectively EVE reset
#define EVE_PD_NOT (8)
// SPI chip select - defined separately since it's manipulated with GPIO calls
#define EVE_CS_NOT (9)
// Reserved for use with the SD card library
#define SD_CS      (10)
#define MOSI_PIN   (11)
#define MISO_PIN   (12)
#define SCK_PIN    (13)

#define CLR_EVE_PD_NOT       (digitalWrite(8,LOW))//set to 0
#define SET_EVE_PD_NOT       (digitalWrite(8,HIGH))//set to 1
#define CLR_EVE_CS_NOT       (digitalWrite(9,LOW))//set to 0
#define SET_EVE_CS_NOT       (digitalWrite(9,HIGH))//set to 1
#define CLR_SD_CS_NOT        (digitalWrite(10,LOW))//set to 0
#define SET_SD_CS_NOT        (digitalWrite(10,HIGH))//set to 1
#define CLR_MOSI             (digitalWrite(11,LOW))//set to 0
#define SET_MOSI             (digitalWrite(11,HIGH))//set to 1
#define CLR_MISO             (digitalWrite(12,LOW))//set to 0
#define SET_MISO             (digitalWrite(12,HIGH))//set to 1
#define CLR_SCK              (digitalWrite(13,LOW))//set to 0
#define SET_SCK              (digitalWrite(13,HIGH))//set to 1
#define CLR_DEBUG_LED        (digitalWrite(3,LOW))//set to 0
The next step is to load the images for the display.
The code can be found in the demos.cpp file
//----------------------BACKGND LOGO-------------------------------
 //Load and INFLATE our 8-bit A2R2G2B2 (lossless) image into RAM_G
 //You have to know before hand how big the logo is. The INFLATE
 //is not aware of the content or format of the data.
 Logo_Width=BACKGND_LOGO_WIDTH_ARGB2;
 Logo_Height=BACKGND_LOGO_HEIGHT_ARGB2;


 FWol=EVE_Inflate_to_RAM_G(FWol,
                           LEK_GPS_240X320_LOGO,
                           BACKGND_LOGO_SIZE_ARGB2,
                           RAM_G_Unused_Start);
 //Pass our updated offset back to the caller
 return(FWol);
 }

The LEK_GPS_240X320_LOGO (first image above) was created using PaintShopPro 2019 and converted to a .cpp file using the EVE Asset Builder. I will not go into detail as how this is done as I don't have a full understanding of the software. I kind of fumbled my way through it.

The LEK_DISH.cpp file was created the same way. It is displayed in multiple locations on the screen dependent on the number of satellites the GPS is receiving from. The code for this is also found in the demos.cpp file and is as follows:

//----------------------IMAGE LOAD-------------------------------
 //Load and INFLATE our 8-bit A2R2G2B2 (lossless) image into RAM_G
 //You have to know before hand how big the logo is. The INFLATE
 //is not aware of the content or format of the data.
 Image_Width=DISH_LOGO_WIDTH_ARGB2;
 Image_Height=DISH_LOGO_HEIGHT_ARGB2;
 FWol=EVE_Inflate_to_RAM_G(FWol,
                           LEK_DISH_LOGO,
                           DISH_LOGO_SIZE_ARGB2,
                           RAM_G_Unused_Start);
 //Pass our updated offset back to the caller
 return(FWol);
 }
 uint16_t Add_Image_To_Display_List(uint16_t FWol)
 {
 //========== PUT IMAGE ON SCREEN ==========
 // Set the drawing color to white
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_COLOR_RGB(0xFF,0xFF,0xFF));
 //Solid color -- not transparent
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_COLOR_A(255));
 //Point to the uncompressed logo in RAM_G
 //gpsSatellites = 6;
//*************************** 1 *********************************************
if (gpsSatellites == 0) {
       return(FWol);
   }
 else {
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BEGIN(EVE_BEGIN_BITMAPS));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_SOURCE(Image_RAM_G_Address));
 //ARGB2 comes in as EVE_FORMAT_ARGB2, 1 byte per pixel
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_LAYOUT(EVE_FORMAT_ARGB2,Image_Width,Image_Height));
 //Render the bitmap to the current frame
 FWol=EVE_Cmd_Dat_0(FWol,
                    EVE_ENC_VERTEX2F(225,3400));                            
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_END());
  }
//***************************** 2 *********************************************
 if (gpsSatellites >= 2) {
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BEGIN(EVE_BEGIN_BITMAPS));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_SOURCE(Image_RAM_G_Address));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_LAYOUT(EVE_FORMAT_ARGB2,Image_Width,Image_Height));
 FWol=EVE_Cmd_Dat_0(FWol,
                    EVE_ENC_VERTEX2F(825,3400));// location,location,location :>)                            
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_END());
 }
//***************************** 3 *********************************************
 if (gpsSatellites >= 3) { 
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BEGIN(EVE_BEGIN_BITMAPS));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_SOURCE(Image_RAM_G_Address));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_LAYOUT(EVE_FORMAT_ARGB2,Image_Width,Image_Height));
 FWol=EVE_Cmd_Dat_0(FWol,
                    EVE_ENC_VERTEX2F(1425,3400));// location,location,location :>)                            
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_END());
 }
//***************************** 4 *********************************************
 if (gpsSatellites >= 4) { 
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BEGIN(EVE_BEGIN_BITMAPS));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_SOURCE(Image_RAM_G_Address));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_LAYOUT(EVE_FORMAT_ARGB2,Image_Width,Image_Height));
 FWol=EVE_Cmd_Dat_0(FWol,
                    EVE_ENC_VERTEX2F(2025,3400));// location,location,location :>)                            
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_END());
 }
//***************************** 5 *********************************************
 if (gpsSatellites >= 5) {
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BEGIN(EVE_BEGIN_BITMAPS));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_SOURCE(Image_RAM_G_Address));
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_BITMAP_LAYOUT(EVE_FORMAT_ARGB2,Image_Width,Image_Height));
 FWol=EVE_Cmd_Dat_0(FWol,
                    EVE_ENC_VERTEX2F(2625,3400));// location,location,location :>)                            
 FWol=EVE_Cmd_Dat_0(FWol, EVE_ENC_END());
 }
//******************************** + **********************************************
  if (gpsSatellites >= 6) {
       //Set font color to black
       FWol=EVE_Cmd_Dat_0(FWol,
                            EVE_ENC_COLOR_RGB(0x00,0x00,0x00));                       
               // display # of satellites from GPS
       FWol=EVE_PrintF(FWol,
                         214, //X
                         228, //Y
                         31,        //Font
                         EVE_OPT_CENTER, //Options
                         "",
                         "+"); 
  }                    

Now that the background and satellites are taken care of all that is left is to display the actual GPS data: Speed, Heading and Elevation. This is found int the root FT811 file.

   //========== FRAME SYNCHRONIZING ==========
   // Wait for graphics processor to complete executing the current command
   // list. This happens when EVE_REG_CMD_READ matches EVE_REG_CMD_WRITE, indicating
   // that all commands have been executed. We have a local copy of
   // EVE_REG_CMD_WRITE in FWo.
   //
   // This appears to only occur on frame completion, which is nice since it
   // allows us to step the animation along at a reasonable rate.
   //
   // If possible, I have tweaked the timing on the Crystalfontz displays
   // to all have ~60Hz frame rate.
   FWo=Wait_for_EVE_Execution_Complete(FWo);
       //========== START THE DISPLAY LIST ==========
   // Start the display list
   FWo=EVE_Cmd_Dat_0(FWo,
                     (EVE_ENC_CMD_DLSTART));
   // Set the default clear color to black
   FWo=EVE_Cmd_Dat_0(FWo,
                     EVE_ENC_CLEAR_COLOR_RGB(0,0,0));
   // Clear the screen - this and the previous prevent artifacts between lists
   FWo=EVE_Cmd_Dat_0(FWo,
                     EVE_ENC_CLEAR(1 /*CLR_COL*/,1 /*CLR_STN*/,1 /*CLR_TAG*/));
   //========== ADD GRAPHIC ITEMS TO THE DISPLAY LIST ==========
   //Fill background with white
   FWo=EVE_Filled_Rectangle(FWo,
                            0,0,LCD_WIDTH-1,LCD_HEIGHT-1);
   #if (0 != LOGO_DEMO)
   FWo=Add_Logo_To_Display_List(FWo);
   #endif // (0 != LOGO_DEMO)
           //Set font color to red             
       FWo=EVE_Cmd_Dat_0(FWo,
                             EVE_ENC_COLOR_RGB(0xFF,0x00,0x00));
       // Set font 16 to 34 size
       FWo=EVE_Cmd_Dat_2(FWo,
                             EVE_ENC_CMD_ROMFONT,16,34);
       // display gpsMph from GPS
       FWo=EVE_PrintF(FWo,
                         120, //X
                         64, //Y
                         16,        //Font
                          EVE_OPT_CENTER, //Options
                         "%d",//"%d MPH"
                         gpsMph);
//++++++++++++++++++++++++++++++++++++++++++++ HEADING +++++++++++++++++++++++++++++++++++++++++++++
       // display Heading from GPS
       FWo=EVE_PrintF(FWo,
                         130,
                         140,
                         28,
                         EVE_OPT_CENTER,
                         "%d",
                         gpsDir); 

 //dirCP = 1; //troubleshooting                      
 switch (dirCP){
   case 0:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "NORTH",
                         "");
     break;
   case 1:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "NORTH EAST",
                         "");
     break;
   case 2:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "EAST",
                         "");
     break;
   case 3:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "SOUTH EAST",
                         "");
     break;
   case 4:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "SOUTH",
                         "");
     break;
   case 5:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "SOUTH WEST",
                         "");
     break;
   case 6:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "WEST",
                         "");
     break;
   case 7:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "NORTH WEST",
                         "");
     break;
   case 8:
         FWo=EVE_PrintF(FWo,
                         60,
                         143,
                         26,
                         EVE_OPT_CENTER,
                         "NORTH",
                         "");
     break;
  }   
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++                     
       // display Elevation from GPS
       FWo=EVE_PrintF(FWo,
                         120,
                         182,
                         28,
                         EVE_OPT_CENTER,
                         "%d",
                         gpsElev);
   #if (0 != IMAGE_DEMO)
   FWo=Add_Image_To_Display_List(FWo);
   #endif // (0 != IMAGE_DEMO)
       if (gpsSatellites >= 6) {
       //Set font color to black
       FWo=EVE_Cmd_Dat_0(FWo,
                            EVE_ENC_COLOR_RGB(0x00,0x00,0x00));                       
               // display # of satellites from GPS
       FWo=EVE_PrintF(FWo,
                         214, //X
                         228, //Y
                         31,        //Font
                         EVE_OPT_CENTER, //Options
                         "+",
                         ""); 
       } 
      //========== FINSH AND SHOW THE DISPLAY LIST ==========
   // Instruct the graphics processor to show the list
   FWo=EVE_Cmd_Dat_0(FWo, EVE_ENC_DISPLAY());
   // Make this list active
   FWo=EVE_Cmd_Dat_0(FWo, EVE_ENC_CMD_SWAP);
   // Update the ring buffer pointer so the graphics processor starts executing
   EVE_REG_Write_16(EVE_REG_CMD_WRITE, (FWo)); 
 } // eveDisplay()


WHEW !!

I know that is a lot to take in, but as I said, the EVE driver is a very powerful but complicated driver.

Loading & Running

lektronics with bolt.jpg

Now to put it all together and make it work.

If you haven't already done so, you have to download the Arduino IDE. The version that I am using is 1.8.19, but the latest available should work. If not, 1.8.19 is still available.

I am not go in to too much detail of the workings of the IDE, but there is plenty of information (YouTube, Instructables, etc.) out there.What I will tell you is you have to make a directory to store all the files. EG: Documents/Arduino/FT811

The directory name should be the same as the .ino file (FT811.ino) Copy all of the above files (13 in all) to this directory.

Open the FT811.ino in the IDE and all the files should show as tabs across the top. If not, close out the IDE and try again. Next you have to choose your board by clicking on Tools, Board, Board Manager and in this case, choose Arduino AVR Boards and then Arduino UNO.

Plug in a USB cable from the Seeeduino to a port on your computer. You should hear a tone that it is connected. Open your device manager and see what port is connected. It should say Arduino Uno (COMxxx).

Again click on Tools and under Port click on the port that you just opened. Now you have to verify the files. Do this by clicking on the checkmark at the upper left corner. This will take a while as there is a lot to check. Down at the bottom of the screen it will say "Compiling sketch" and ther will be a progress bar at the lower right. You may encounter a problem compiling if all the necessary libraries are not installed. The IDE will let you know. As stated, there is plenty of information on the web to help you along.

Once it says "Done compiling", click on the right arrow next to the checkmark, and the program will load in to the Seeeduino. When the program is done loading you should be up and running. If the screen is blank, try pressing the reset button.

If you have any questions, comments, please let me know!

A lot of the code can/should be cleaned up, and I will do that when I am able to do so.

Thank you for your time !!!

larry@lektronics.com