This is more a "What's the best way to do this" than a "my code doesn't work" question. I made a bike speedometer last year with a Nano Every, a GPS module and a 3.5" TFT display. For the most part I'm happy with it, it shows, latitude, longitude, a compass display and I can log the route I took and download that onto Google Maps. It also has the distance traveled and elapsed time of the ride, Neither one of those are very accurate and that is what I would like some help with. Both of those use a 1 second loop update using millis().
I'm going to change the distance traveled using a Ebike sensor that detects a magnet on the wheel. Looks like a hall effect sensor so that should be fairly straight forward. The time has got me stumped.
I've tried a couple of different ways of using gps.time.second() from the TinyGPS++ library I'm using. There's problems when the seconds resets back to "0" I've tried a few ways but obviously none are working well or I wouldn't be asking. I was think I could make a accurate 1 second pulse from the GPS time and just count the pulses but have the same basic problems. I could use a RTC but currently everything is mounted on a PCB board I had made and wanted to see if I could use what I have before adding more hardware, which I will if that's the best way.
So that's the question, how do I get a accurate elapsed time?
I've included the code I currently have.
Thanks for any help and suggestions.
John
/*
Changed to Nano Every
with Adafruit 3.5 TFT display
Rev 2 Added touch, made 2 pages for displaing info
No logging function
Rev 6 added compass by G6EJD - David
*/
// Include required libraries
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_HX8357.h> // Hardware-specific library
#include "TouchScreen.h"
// GPS Connections
static const int RXPin = 6, TXPin = 7;
// GPS Baud rate (change if required)
static const uint32_t GPSBaud = 9600;
// SD Card Select pin
const int chipSelect = 5;
// Write LED
const int recLED = 8;
// String to hold GPS data
String gpstext;
// GPS write delay counter variables
// Change gpsttlcount as required
int gpscount = 0;
int gpsttlcount = 70;
int fileMade = 0; // check to see if a new file was made on SD card
// TinyGPS++ object
TinyGPSPlus gps;
// SoftwareSerial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);
// display
#define TFT_CS 10
#define TFT_DC 9
Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC);
// These are the four touchscreen analog pins
#define YP A2 // must be an analog pin, use "An" notation!
#define XM A3 // must be an analog pin, use "An" notation!
#define YM 3 // can be a digital pin
#define XP 4 // can be a digital pin
// This is calibration data for the raw touch data to the screen coordinates
#define TS_MINX 110
#define TS_MINY 80
#define TS_MAXX 900
#define TS_MAXY 940
#define MINPRESSURE 10
#define MAXPRESSURE 1000
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
// Color definitions
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
bool newData = false;
float log_Dist = 0.0;
unsigned long int scan_time = millis();
int currentPage = 1;
int SD_Status = 0;
int GPS_Status = 0;
float Heading = 0.0;
String RideTime = "";
int RideHr = 0;
int RideMin = 0;
int RideSec = 0;
int StartHr = 0;
int StartMin = 0;
int StartSec = 0;
int readStartTime = 0;
char RideTm[80];
int TestTm = 0;
// Compass
float Bearing;
const int centreX = 160; // Location of the compass display on screen
const int centreY = 280;
const int diameter = 70; // Size of the compass
int dx = centreX, dy = centreY;
int last_dx = centreX, last_dy = centreY - diameter * 0.85;
// the logging file
File logfile;
char logFileName[20] = " "; // current log file name
int errorNumb = 0; // system error number to display message
int RunLogger = 0; // run logging file
int LogPBOld = 0;
int LogPBNew = 0;
File dataFile;
long int lastLogTime = 0; // last time log sample was read
int logDelay = 10000; // time delay between log samples
void setup()
{
// Set all chip selects high to avoid bus contention during initialisation of each peripheral
// digitalWrite(21, HIGH); // Touch controller chip select (if used)
digitalWrite(10, HIGH); // TFT screen chip select
digitalWrite( 5, HIGH); // SD card chips select, must use GPIO 5 (ESP32 SS)
// Set LED pin as output
pinMode(recLED, OUTPUT);
// Start Serial Monitor for debugging
Serial.begin(115200);
// Start SoftwareSerial
ss.begin(GPSBaud);
// start display driver
tft.begin(); // Initialize screen
//tft.fillScreen(BLACK); //clears screen, sets to Black
tft.setRotation(0); // rotates screen 90' for landscape mode
// load Speedometer page
tft.fillScreen(BLACK); //clears screen, sets to Black
SpeedometerPage();
// Initialize SD card
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
//don't do anything more:
tft.fillCircle(30, 30, 10, RED);
while (1);
}
Serial.println("card initialized.");
// Blink LED so we know we are ready
//digitalWrite(recLED, HIGH);
tft.fillCircle(30, 30, 10, BLACK);
delay(500);
//digitalWrite(recLED, LOW);
tft.fillCircle(30, 30, 10, GREEN);
delay(500);
//digitalWrite(recLED, HIGH);
tft.fillCircle(30, 30, 10, BLACK);
delay(500);
//digitalWrite(recLED, LOW);
tft.fillCircle(30, 30, 10, GREEN);
delay(500);
//digitalWrite(recLED, HIGH);
tft.fillCircle(30, 30, 10, BLACK);
delay(500);
//digitalWrite(recLED, LOW);
tft.fillCircle(30, 30, 10, GREEN);
scan_time = millis(); // reset scan time
} // end setup
void loop()
{
// Turn off LED
digitalWrite(recLED, LOW);
// Retrieve a point on touch screen
TSPoint p = ts.getPoint();
// See if GPS data available
while (ss.available() > 0)
if (gps.encode(ss.read()))
// If we press the Speedometer Button
if ((p.x>=130) && (p.x<=290) && (p.y>=810) && (p.y<=890)) {
currentPage = 1; // Indicates that we are the first example
tft.fillScreen(BLACK); //clears screen, sets to Black
Serial.println("Speed pressed");
SpeedometerPage(); // It is called only once, because in the next iteration of the loop, this above if statement will be false so this funtion won't be called. This function will draw the graphics of the first example.
} // end if for Speedometer button pressed
// If we press the GPS Info Button
if ((p.x>=420) && (p.x<=600) && (p.y>=810) && (p.y<=890)) {
currentPage = 2;
tft.fillScreen(BLACK); //clears screen, sets to Black
Serial.println("GPS Pressed");
GPS_Info_Page();
} // end if for GPS Info page
// Start/Stop logging file with Log PB
if ((p.x>=725) && (p.x<=890) && (p.y>=810) && (p.y<=890)) {
RunLogger = !RunLogger; // toggle RunLogger
delay(500);
} // end RunLogger = !RunLogger
// Data logger control PB
if(RunLogger == 0 && LogPBOld == 1){
tft.fillRoundRect(240, 410, 70, 50, 10, RED);
tft.setCursor(257, 427);
tft.setTextColor(WHITE, RED);
tft.setTextSize(2);
tft.print("LOG");
LogPBOld = 0;
fileMade = 0;
} // end RunLogger == 0
else if(RunLogger == 1 && LogPBOld == 0){
tft.fillRoundRect(240, 410, 70, 50, 10, GREEN);
tft.setCursor(257, 427);
tft.setTextColor(BLACK, GREEN);
tft.setTextSize(2);
tft.print("LOG");
LogPBOld = 1;
} // end else if
// call logger function
if(RunLogger == 1){
// logger();
} // end if RunLogger == 1
else {
logfile.close();
fileMade = 0;
} // end else
// Update variables
// display GPS status
//if(GPS_Status == 1){
if(gps.location.isValid()){
tft.fillCircle(290, 30, 10, GREEN);
}
//if(GPS_Status == 0) {
if(!gps.location.isValid()){
tft.fillCircle(290, 30, 10, RED);
}
newData = true;
if (((millis() - scan_time) > 1000)) { // Update every second
//if (gps.location.isValid())
if(newData == true)
{
newData = false;
// ride start time
if(readStartTime == 0){
StartHr = gps.time.hour();
StartMin = gps.time.minute();
StartSec = gps.time.second();
readStartTime = 1;
} // end if(readStartTime == 0)
} // if(newdata == true)
else
{
Serial.println(F("INVALID"));
} // end else
// Update screen variables
if(currentPage == 1){
tft.setCursor(50, 120);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(6);
tft.print(gps.speed.mph(), 1);
Bearing = gps.course.deg();
Display_Compass(Bearing); // show compass
} // end if current page =1
if(currentPage == 2){
tft.setCursor(50, 110);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(4);
tft.print(gps.location.lng(), 4);
// Display Latitude
tft.setCursor(50, 190);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(4);
tft.print(gps.location.lat(), 4);
// Display log_Dist
tft.setCursor(50, 270);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(4);
tft.print(log_Dist, 1);
// Display Ride Time
tft.setCursor(50, 350);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(4);
sprintf(RideTm, "%02d:%02d:%02d", RideHr, RideMin, RideSec);
tft.print(RideTm);
} // end if current page = 2
if(millis() > 5000 && gps.charsProcessed() > 10){
GPS_Status = 1;
}
if (millis() > 5000 && gps.charsProcessed() < 10)
{
Serial.println(F("No GPS detected: check wiring."));
GPS_Status = 0;
//while(true);
}
// calculate ride time
if(RunLogger == 1){
RideSec = (RideSec + 1);
if(RideHr >= 24){
RideHr = 0;
}
if(RideMin >= 60){
RideMin = 0;
RideHr = (RideHr + 1);
}
if(RideSec >= 60){
RideSec = 0;
RideMin = (RideMin + 1);
}
// calculate log_Dist
if(gps.speed.mph() > 0.5){
log_Dist = log_Dist+(gps.speed.mph()/3600);
} // end if(GPS.speed.mph() > 0.5)
} // end if(RunLogger == 1)
if(RunLogger == 0){
log_Dist = 0; //clear log distance
RideHr = 0; // clear log hours
RideMin = 0; // clear log minutes
RideSec = 0; // clear log seconds
} // end if(RunLogger == 0)
scan_time = millis(); // reset scan time
} // end (((millis() - scan_time) > 1000)
// Run GPS Logger
if(RunLogger == 1){
// Turn off LED
digitalWrite(recLED, LOW);
// ************* Dronebot GPS logging code **************
// See if data available
while (ss.available() > 0)
if (gps.encode(ss.read()))
// See if we have a complete GPS data string
if (displayInfo() != "0")
{
// Check GPS Count
// Serial.println(gpscount);
if (gpscount == gpsttlcount) {
// LED On to indicate data to write to SD card
digitalWrite(recLED, HIGH);
// create a new data logging file
if(RunLogger == 1 && fileMade == 0){
Serial.println("Create file");
char filename[] = "File00.CSV";
for (uint8_t i = 0; i < 100; i++) {
filename[4] = i / 10 + '0';
filename[5] = i % 10 + '0';
if (! SD.exists(filename)) {
// strcpy(logFileName, filename);
// only open a new file if it doesn't exist
dataFile = SD.open(filename, FILE_WRITE);
Serial.println(filename);
strcpy(logFileName, filename);
fileMade = 1;
break; // leave the loop!
} // end if (! SD.exist)
} // end for (uint8_t i = 0; i < 100; i++)
} // end if RunLogger == 1 && fileMade == 0
//Open the file on card for writing
dataFile = SD.open(logFileName, FILE_WRITE);
Serial.println(logFileName);
//File dataFile = SD.open("gpslog.csv", FILE_WRITE);
// Get GPS string
gpstext = displayInfo();
if (dataFile) {
// If the file is available, write to it and close the file
dataFile.println(gpstext);
dataFile.close();
// Serial print GPS string for debugging
Serial.println(gpstext);
}
// If the file isn't open print an error message for debugging
else {
Serial.println("error opening datalog.txt");
}
}
// Increment GPS Count
gpscount = gpscount + 1;
if (gpscount > gpsttlcount) {
gpscount = 0;
}
} // end if (displayInfo() != "0")
} // end run logger
} // end loop()
void SpeedometerPage(){
// Prints the title on the screen
tft.setCursor(65, 20);
tft.setTextColor(WHITE);
tft.setTextSize(3);
tft.print("Speedometer");
tft.drawFastHLine(10, 60, 320, RED);
// Display SS Initialized status
tft.fillCircle(30, 30, 10, GREEN);
// Display Speed
tft.setCursor(50, 90);
tft.setTextColor(WHITE);
tft.setTextSize(2);
tft.print("Speed");
// Draw buttons
// fillRoundRect(X, Y, width, height, radius, color);
// Speedometer page select PB
tft.fillRoundRect(10, 410, 70, 50, 10, BLUE);
tft.setCursor(16, 427);
tft.setTextColor(WHITE, BLUE);
tft.setTextSize(2);
tft.print("Speed");
// GPS Info Page select PB
tft.fillRoundRect(126, 410, 70, 50, 10, MAGENTA);
tft.setCursor(143, 427);
tft.setTextColor(BLACK, MAGENTA);
tft.setTextSize(2);
tft.print("GPS");
// Data logger control PB
if(RunLogger == 0){
tft.fillRoundRect(240, 410, 70, 50, 10, RED);
tft.setCursor(257, 427);
tft.setTextColor(WHITE, RED);
tft.setTextSize(2);
tft.print("LOG");
} // end RunLogger == 0
else if(RunLogger == 1){
tft.fillRoundRect(240, 410, 70, 50, 10, GREEN);
tft.setCursor(257, 427);
tft.setTextColor(BLACK, GREEN);
tft.setTextSize(2);
tft.print("LOG");
} // end else if
} // end Speedometer page
void GPS_Info_Page(){
// Prints the title on the screen
tft.setCursor(90, 20);
tft.setTextColor(WHITE);
tft.setTextSize(3);
tft.print("GPS Info");
tft.drawFastHLine(10, 60, 320, RED);
// Display SS Initialized status
tft.fillCircle(30, 30, 10, GREEN);
// Display Longitude
tft.setCursor(50, 80);
tft.setTextColor(WHITE);
tft.setTextSize(2);
tft.print("Longitude");
// Display Latitude
tft.setCursor(50, 160);
tft.setTextColor(WHITE);
tft.setTextSize(2);
tft.print("Latitude");
// Display Distance
tft.setCursor(50, 240);
tft.setTextColor(WHITE);
tft.setTextSize(2);
tft.print("Distance");
// Display Time
tft.setCursor(50, 320);
tft.setTextColor(WHITE);
tft.setTextSize(2);
tft.print("Time");
// Draw buttons
// fillRoundRect(X, Y, width, height, radius, color);
// Speedometer page select PB
tft.fillRoundRect(10, 410, 70, 50, 10, BLUE);
tft.setCursor(16, 427);
tft.setTextColor(WHITE, BLUE);
tft.setTextSize(2);
tft.print("Speed");
// GPS Info Page select PB
tft.fillRoundRect(126, 410, 70, 50, 10, MAGENTA);
tft.setCursor(143, 427);
tft.setTextColor(BLACK, MAGENTA);
tft.setTextSize(2);
tft.print("GPS");
// Data logger control PB
if(RunLogger == 0){
tft.fillRoundRect(240, 410, 70, 50, 10, RED);
tft.setCursor(257, 427);
tft.setTextColor(WHITE, RED);
tft.setTextSize(2);
tft.print("LOG");
} // end RunLogger == 0
else if(RunLogger == 1){
tft.fillRoundRect(240, 410, 70, 50, 10, GREEN);
tft.setCursor(257, 427);
tft.setTextColor(BLACK, GREEN);
tft.setTextSize(2);
tft.print("LOG");
} // end else if
} // End GPS_Info_Page
// Function to return GPS string
String displayInfo()
{
// Define empty string to hold output
String gpsdata = "";
// Get latitude and longitude
if (gps.location.isValid())
{
gpsdata = String(gps.location.lat(), 6);
gpsdata += (",");
gpsdata += String(gps.location.lng(), 6);
gpsdata += (",");
}
else
{
return "0";
}
// Get Date
if (gps.date.isValid())
{
gpsdata += String(gps.date.year());
gpsdata += ("-");
if (gps.date.month() < 10) gpsdata += ("0");
gpsdata += String(gps.date.month());
gpsdata += ("-");
if (gps.date.day() < 10) gpsdata += ("0");
gpsdata += String(gps.date.day());
// gpsdata += (",");
}
else
{
return "0";
}
// Space between date and time
gpsdata += (",");
// Get time
if (gps.time.isValid())
{
if (gps.time.hour() < 10) gpsdata += ("0");
gpsdata += String(gps.time.hour() - 4);
gpsdata += (":");
if (gps.time.minute() < 10) gpsdata += ("0");
gpsdata += String(gps.time.minute());
gpsdata += (":");
if (gps.time.second() < 10) gpsdata += ("0");
gpsdata += String(gps.time.second());
}
else
{
return "0";
}
// Return completed string
return gpsdata;
} // end String displayInfo()
//#####################################################################
void Display_Compass(float dBearing) {
int dxo, dyo, dxi, dyi;
tft.setCursor(0, 0);
tft.drawCircle(centreX, centreY, diameter, WHITE); // Draw compass circle
for (float i = 0; i < 360; i = i + 22.5) {
dxo = diameter * cos((i - 90) * 3.14 / 180);
dyo = diameter * sin((i - 90) * 3.14 / 180);
dxi = dxo * 0.9;
dyi = dyo * 0.9;
tft.drawLine(dxo + centreX, dyo + centreY, dxi + centreX, dyi + centreY, WHITE);
}
PrintText((centreX - 5), (centreY - diameter - 18), "N", WHITE, 2);
PrintText((centreX - 5), (centreY + diameter + 5) , "S", WHITE, 2);
PrintText((centreX + diameter + 5), (centreY - 5), "E", WHITE, 2);
PrintText((centreX - diameter - 15), (centreY - 5), "W", WHITE, 2);
dx = (0.85 * diameter * cos((dBearing - 90) * 3.14 / 180)) + centreX; // calculate X position
dy = (0.85 * diameter * sin((dBearing - 90) * 3.14 / 180)) + centreY; // calculate Y position
draw_arrow(last_dx, last_dy, centreX, centreY, 5, 5, BLACK); // Erase last arrow
draw_arrow(dx, dy, centreX, centreY, 5, 5, WHITE); // Draw arrow in new position
last_dx = dx;
last_dy = dy;
// display compass azimuthal points
tft.setTextColor(WHITE);
tft.setTextSize(3);
tft.fillRect(230, 355, 60, 22, BLACK);
tft.setCursor(230, 355);
tft.print(Bearing_to_Ordinal(Bearing));
}// end Display_Compass;
//#####################################################################
void PrintText(int x, int y, String text, int colour, byte text_size) {
tft.setCursor(x, y);
tft.setTextColor(colour);
tft.setTextSize(text_size);
tft.print(text);
tft.setTextColor(YELLOW); // Default colour
tft.setTextSize(2); // Default Text Size
} // end Printtext
//#####################################################################
void draw_arrow(int x2, int y2, int x1, int y1, int alength, int awidth, int colour) {
float distance;
int dx, dy, x2o, y2o, x3, y3, x4, y4, k;
distance = sqrt(pow((x1 - x2), 2) + pow((y1 - y2), 2));
dx = x2 + (x1 - x2) * alength / distance;
dy = y2 + (y1 - y2) * alength / distance;
k = awidth / alength;
x2o = x2 - dx;
y2o = dy - y2;
x3 = y2o * k + dx;
y3 = x2o * k + dy;
x4 = dx - y2o * k;
y4 = dy - x2o * k;
tft.drawLine(x1, y1, x2, y2, colour);
tft.drawLine(x1, y1, dx, dy, colour);
tft.drawLine(x3, y3, x4, y4, colour);
tft.drawLine(x3, y3, x2, y2, colour);
tft.drawLine(x2, y2, x4, y4, colour);
} // end draw_arrow
//#####################################################################
String Bearing_to_Ordinal(float bearing) {
if (bearing >= 348.75 || bearing < 11.25) return "N ";
if (bearing >= 11.25 && bearing < 33.75) return "NNE";
if (bearing >= 33.75 && bearing < 56.25) return "NE ";
if (bearing >= 56.25 && bearing < 78.75) return "ENE";
if (bearing >= 78.75 && bearing < 101.25) return "E ";
if (bearing >= 101.25 && bearing < 123.75) return "ESE";
if (bearing >= 123.75 && bearing < 146.25) return "SE ";
if (bearing >= 146.25 && bearing < 168.75) return "SSE";
if (bearing >= 168.75 && bearing < 191.25) return "S ";
if (bearing >= 191.25 && bearing < 213.75) return "SSW";
if (bearing >= 213.75 && bearing < 236.25) return "SW ";
if (bearing >= 236.25 && bearing < 258.75) return "WSW";
if (bearing >= 258.75 && bearing < 281.25) return "W ";
if (bearing >= 281.25 && bearing < 303.75) return "WNW";
if (bearing >= 303.75 && bearing < 326.25) return "NW ";
if (bearing >= 326.25 && bearing < 348.75) return "NNW";
return "?";
} // end Bearing_to_Ordinal