Skip to content

Instantly share code, notes, and snippets.

@yo3hjv
Created December 6, 2023 19:40
Show Gist options
  • Save yo3hjv/222118e1197ed2e71337443016a038fd to your computer and use it in GitHub Desktop.
Save yo3hjv/222118e1197ed2e71337443016a038fd to your computer and use it in GitHub Desktop.
A nice portable ham project
/*
UTC/Baro GPS Clock
Final release V4
Copyright Dec. 2023 - Adrian, YO3HJV
https://yo3hjv.blogspot.com
Required parts:
-NodeMCU
-uBlox Neo-6 GPS unit
-BME280 I2C Baro-Temp module
-1.28" SPI TFT ST7735
-300 - 470KOhm resistor (something in this range).
The ratio of the voltage divider will be calculated and included in the code.
!!!!!!!!!!!!!!!!!!!!!!!!-ATTENTION-!!!!!!!!!!!!!!!!!!!!!!
All Vcc from GPS, TFT, BME goes to 3V pin on the NodeMCU
DO NOT CONNECT TO THE BATTERY UNDER NO CIRCUMSTANCES !!!
*********************************************************
Connections, relative to NodeMCU pins numbering:
TFT NodeMCU pin#
________________________
CS D8
D/C D4
DIN D7
CLK D5
RST RST
BL 3V
GPS NodeMCU pin#
________________________
Tx D2
Rx D6
GND GNC
Vcc 3V
BME280
________________________
I2C SDA D1
I2C SCL D3
GND GND
Vcc 3V
*/
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <maidenhead.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_BME280.h>
// TFT
#define TFT_RST -1
#define TFT_CS D8
#define TFT_DC D4
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
// BME280 address: 0x76 or 0x77 (0x77 is library default address)
#define BME280_I2C_ADDRESS 0x76
Adafruit_BME280 bme;
//GPS
TinyGPSPlus gps;
TinyGPSCustom pdop(gps, "GPGSA", 15);
SoftwareSerial SerialGPS(D2, D0);
int NO_OF_SAT = 0;
float ACT_HDOP = 0;
float Latitude, Longitude;
int year, month, date, hour, minute, second;
double Altitude;
String DateString, TimeString, LatitudeString, LongitudeString, SatString, HdopString, MaidString, PresString, AltString;
// COLORS
int BLACK = 0;
int WHITE = 65535;
int RED = 63488;
int ORANGE = 64096;
int YELLOW = 65504; // OK
int GREEN = 2016;
int CYAN = 2047; //
int AQUA = 1279;
int BLUE = 31;
int MAGENTA = 63519;
int PINK = 63743;
// DEFINE SPECIFIC COLORS FOR TFT ELEMENTS
int BackGroundColor = BLACK;
int HLinesColor = MAGENTA;
int BoxColor = RED;
int VLinesColor = WHITE;
int DotColor = WHITE;
int FixedTextColor = WHITE;
int CoordColor = YELLOW;
int STextColor = WHITE;
int BTextColor = YELLOW;
int QNHtextColor = RED;
int DATEtextColor = YELLOW;
int CelsiusColor = WHITE;
int DotGraphColor = WHITE;
int BoxFillColor = BLUE;
unsigned long previous_BME_Millis = 0;
unsigned long current_BME_Millis = 0;
const long interval_TFTbme = 300; // Pressure polling interval
unsigned long previous_BATT_Millis = 0;
unsigned long current_BATT_Millis = 0;
const long interval_BATT = 300; // Battery voltage polling interval
// Barometer graphic area
const int graphWidth = 128;
const int graphHeight = 20;
int xPos = 1;
int yPos = 123;
float pres = 0;
float temp = 0;
int i = 0;
int hdop = 9;
const float V_divider_ratio = 5.500; // Replace with your value calculated from real values
// then fine tune the value with DEBUG_BATT function
float Vbat = 0;
const int vReadings = 4; // Number of readings to average voltage
float voltageReadings[vReadings] = {4};
int readIndex = 0;
int j = 0;
/* Uncomment for specific debugging on Serial
I strongly reccomend to use one at a time and not all of them
*/
//#define DEBUG 1 // Uncomment to start serial scaffold debugging
//#define DEBUG_GPS 1 // GPS Library tools for debug
//#define DEBUG_HDOP 1
//#define DEBUG_TFT_BME 1 // Pressure and Temperature value
//#define DEBUG_TFT_BAROGRAPH 1 // Pressure graphic math
//#define DEBUG_BATT 1 // Battery readings
//#define DEBUG_XPOS 1
unsigned long previous_BARO_Millis = 0;
unsigned long current_BARO_Millis = 0;
const long interval_BARO = 1500; // msec - when the Pressure readings are made - time interval between readings
const int numPresReadings = 10; // Number of readings to average
float pressures[numPresReadings];
int currentReadingIndex = 0;
float sumPressures = 0;
int result = 10;
void setup() {
Wire.begin(D1, D3);
Serial.begin(115200);
SerialGPS.begin(9600);
tft.initR(INITR_BLACKTAB);
tft.fillScreen(ST7735_BLACK);
TFT_static();
bool status;
status = bme.begin(0x76);
if (!status) {
Serial.println("BME280 Error! Check wiring first");
while (1)
;
}
}
void loop() {
TFT_Baro_Graph();
current_BATT_Millis = millis();
if (current_BATT_Millis - previous_BATT_Millis >= interval_BATT) {
previous_BATT_Millis = current_BATT_Millis;
TFT_batt();
j=++j;
}
current_BME_Millis = millis();
if (current_BME_Millis - previous_BME_Millis >= interval_TFTbme) {
previous_BME_Millis = current_BME_Millis;
TFT_BME();
}
while (SerialGPS.available() > 0)
if (gps.encode(SerialGPS.read())) {
if (gps.location.isUpdated()) {
Latitude = gps.location.lat();
LatitudeString = String(Latitude, 6);
Longitude = gps.location.lng();
LongitudeString = String(Longitude, 6);
TFT_LatLon();
TFT_Maidenhead();
}
if (gps.date.isUpdated()) {
DateString = "";
date = gps.date.day();
month = gps.date.month();
year = gps.date.year();
if (date < 10)
DateString = '0';
DateString += String(date);
DateString += "/";
if (month < 10)
DateString += '0';
DateString += String(month);
DateString += "/";
if (year < 10)
DateString += '0';
DateString += String(year);
TFT_Date();
}
if (gps.time.isUpdated()) {
TimeString = "";
hour = gps.time.hour(); //adjust UTC by adding "+3"
minute = gps.time.minute();
second = gps.time.second();
if (hour < 10)
TimeString = '0';
TimeString += String(hour);
TimeString += ":";
if (minute < 10)
TimeString += '0';
TimeString += String(minute);
TimeString += ":";
if (second < 10)
TimeString += '0';
TimeString += String(second);
TFT_Time();
}
if (gps.altitude.isUpdated() || gps.satellites.isUpdated() || gps.hdop.isUpdated()) {
TFT_MetaGPS();
#ifdef DEBUG
Serial.print("Altitude set: ");
Serial.println(gps.altitude.meters());
#endif
}
}
#ifdef DEBUG_GPS
Serial.print("Chars Processed= ");
Serial.println(gps.charsProcessed());
Serial.print("Sentences that passed checksum= ");
Serial.println(gps.passedChecksum());
Serial.print("Sentences that failed checksum= ");
Serial.println(gps.failedChecksum());
Serial.print("Soft Serial device overflowed? ");
Serial.println(SerialGPS.overflow() ? "YES!" : "No");
Serial.print("Sentences with Fix: ");
Serial.println(gps.sentencesWithFix() );
Serial.println();
Serial.print(F("ALTITUDE Fix Age= "));
Serial.print(gps.altitude.age());
Serial.print(F("ms Raw= "));
Serial.print(gps.altitude.value());
Serial.println(F(" Meters="));
#endif
}
void TFT_MetaGPS() {
NO_OF_SAT = gps.satellites.value();
SatString = "";
SatString = String(NO_OF_SAT);
ACT_HDOP = gps.hdop.hdop();
hdop = map(ACT_HDOP, 1, 100, 0, 9);
#ifdef DEBUG_HDOP
Serial.println();
Serial.print("Sat: ");
Serial.println(SatString);
Serial.print("HDOP: ");
Serial.println(hdop);
#endif
// Sat#
tft.setTextColor(STextColor, ST7735_BLACK);
tft.setTextSize(1);
tft.setCursor(112, 10);
tft.setTextWrap(false);
tft.print(SatString);
// HDOP
tft.setTextColor(STextColor);
tft.setTextSize(1);
tft.setCursor(6, 151);
tft.setTextWrap(false);
tft.print(hdop);
// GPS a.s.l.
tft.setTextColor(BTextColor);
tft.setTextSize(2);
tft.setCursor(42, 71);
tft.setTextWrap(false);
if(gps.altitude.isValid()) {
tft.print(Altitude);
}
else
tft.print(" -");
}
void TFT_Maidenhead() {
char* locator6 = get_mh(Latitude, Longitude, 6);
MaidString = String(locator6);
tft.setTextColor(BTextColor);
tft.setTextSize(2);
tft.setCursor(42, 48);
tft.setTextWrap(false);
tft.print(MaidString);
#ifdef DEBUG
Serial.print("Loc QTH 6 char locator is ");
Serial.println(MaidString);
#endif
}
void TFT_Time(){
tft.setTextColor(BTextColor ,ST7735_BLACK);
tft.setTextSize(2);
tft.setCursor(32, 25);
tft.setTextWrap(false);
tft.print(TimeString);
#ifdef DEBUG
Serial.print("Time: ");
Serial.println(TimeString);
#endif
}
void TFT_Date(){
tft.setTextColor(DATEtextColor ,ST7735_BLACK);
tft.setTextSize(1);
tft.setCursor(67, 95);
tft.setTextWrap(false);
tft.print(DateString);
#ifdef DEBUG
Serial.print("Date: ");
Serial.println(DateString);
#endif
}
void TFT_LatLon() {
tft.setTextColor(CoordColor ,ST7735_BLACK);
tft.setTextSize(1);
tft.setCursor(27, 0);
tft.setTextWrap(false);
tft.print(LatitudeString);
tft.setTextColor(CoordColor ,ST7735_BLACK);
tft.setTextSize(1);
tft.setCursor(27, 10);
tft.setTextWrap(false);
tft.print(LongitudeString);
#ifdef DEBUG
Serial.print("Lat: ");
Serial.println(LatitudeString); //
Serial.print("Lon: ");
Serial.println(LongitudeString); //
#endif
}
void TFT_BME() { // Just show Pres and Temp on TFT
temp = bme.readTemperature();
pres = bme.readPressure();
tft.setTextColor(STextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(73, 151);
tft.setTextWrap(false);
if (temp < 0)
tft.printf("-%02u", (int)abs(temp));
else
tft.printf(" %02u", (int)temp);
tft.setTextColor(STextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(29, 151);
tft.setTextWrap(false);
if (pres < 1000) {
tft.printf("%3u.%01u", (int)(pres / 100), (int)((uint32_t)pres % 100) / 10);
} else {
tft.printf("%4u.%01u", (int)(pres / 100), (int)((uint32_t)pres % 100) / 10);
}
#ifdef DEBUG_TFT_BME
Serial.println("TFT_BME()");
Serial.print("Pressure: ")
Serial.println(pres);
Serial.print("Temperature: ")
Serial.println(temp);
Serial.println();
#endif
}
void TFT_Baro_Graph () {
unsigned long current_BARO_Millis = millis();
if (current_BARO_Millis - previous_BARO_Millis >= interval_BARO) {
previous_BARO_Millis = current_BARO_Millis;
float pres = bme.readPressure();
pressures[currentReadingIndex] = pres;
sumPressures += pres;
currentReadingIndex = (currentReadingIndex + 1) % numPresReadings;
if (currentReadingIndex == 0) {
float averagePressure = sumPressures / numPresReadings;
#ifdef DEBUG_TFT_BAROGRAPH
Serial.println();
Serial.print("Average Pressure: ");
Serial.println(averagePressure, 2 );
#endif
// Convert averagePressure to a string
char buffer[15]; // Adjust the buffer size based on the expected length
snprintf(buffer, sizeof(buffer), "%.2f", averagePressure);
#ifdef DEBUG_TFT_BAROGRAPH
Serial.print("String buffer:");
Serial.println(buffer);
#endif
// Extract the 5th and 6th digits from the right, counting from the decimal point
int length = strlen(buffer);
int fifthDigit = (length >= 5) ? (buffer[length - 5] - '0') : 0;
int sixthDigit = (length >= 6) ? (buffer[length - 6] - '0') : 0;
#ifdef DEBUG_TFT_BAROGRAPH
Serial.print("Fifth Digit: ");
Serial.println(fifthDigit);
Serial.print("Sixth Digit: ");
Serial.println(sixthDigit);
#endif
// Combine the second and third digits into a single value
int extractedValue = sixthDigit * 10 + fifthDigit;
#ifdef DEBUG_TFT_BAROGRAPH
Serial.print("Extracted Value: ");
Serial.println(extractedValue);
#endif
drawPixel();
currentReadingIndex = 0;
sumPressures = 0;
}
}
}
void drawPixel(){
xPos += 1;
if(xPos >126){
tft.fillRect(0, 114, 128, 20, BoxFillColor);
tft.drawRect(0, 114, 128, 20, BoxColor);
xPos = 1;
}
yPos = map (result, 0, 19, 132, 115);
tft.drawPixel(xPos, yPos, DotGraphColor);
#ifdef DEBUG_XPOS
Serial.print("xPos= ");
Serial.println(xPos);
#endif
}
void TFT_batt() {
int adc = analogRead(A0);
#ifdef DEBUG_BATT
Serial.print("analogRead: ");
Serial.println(adc);
#endif
float referenceVoltage = 1.0; // ADC reference voltage ESP8266
float voltage = adc * (referenceVoltage / 1023.0) * V_divider_ratio; // 5.500 is ADC resistive voltage divider ratio
voltageReadings[readIndex] = voltage;
readIndex = (readIndex + 1) % vReadings;
float sumVoltages = 0;
for (int j = 0; j < vReadings; ++j) {
sumVoltages += voltageReadings[i];
#ifdef DEBUG_BATT
Serial.print("j= ");
Serial.println(j);
#endif
}
float averageVoltage = sumVoltages / vReadings;
tft.setTextColor(STextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(97, 151);
tft.setTextWrap(false);
tft.printf(" %0.1f", averageVoltage);
#ifdef DEBUG_BATT
Serial.print("Average Voltage: ");
Serial.println(averageVoltage);
#endif
}
void TFT_static() {
tft.drawLine(0, 20, 127, 20, HLinesColor); // Orizontala de sub Lat/Lon - prima de sus
tft.drawLine(0, 43, 127, 43, HLinesColor); // Orizontala de sub UTC - a doua de sus
tft.drawLine(0, 66, 127, 66, HLinesColor); // Orizontala de sub QTH Locator - a treia de sus
tft.drawLine(0, 137, 127, 137, HLinesColor); // Orizontala de sub GPSasl - a patra de sus
tft.drawLine(0, 89, 127, 89, HLinesColor); // Orizontala de sub graficul QNH - ultima de jos, sub Box
tft.drawRect(0, 114, 128, 20, BoxColor); // Box pentru grafic QNH
tft.fillRect(0, 114, 128, 20, BoxFillColor);
tft.drawPixel(0, 112, DotColor); // Box ticks
tft.drawPixel(7, 112, DotColor);
tft.drawPixel(15, 112, DotColor);
tft.drawPixel(23, 112, DotColor);
tft.drawPixel(31, 112, DotColor);
tft.drawPixel(39, 112, DotColor);
tft.drawPixel(47, 112, DotColor);
tft.drawPixel(55, 112, DotColor);
tft.drawPixel(63, 112, DotColor);
tft.drawPixel(71, 112, DotColor);
tft.drawPixel(79, 112, DotColor);
tft.drawPixel(87, 112, DotColor);
tft.drawPixel(95, 112, DotColor);
tft.drawPixel(103, 112, DotColor);
tft.drawPixel(111, 112, DotColor);
tft.drawPixel(119, 112, DotColor);
tft.drawPixel(127, 112, DotColor);
tft.drawLine(97, 1, 97, 16, VLinesColor); // vertical Up Right Sat number
tft.drawLine(21, 140, 21, 158, VLinesColor); // First vertical down left
tft.drawLine(69, 140, 69, 158, VLinesColor); // Second
tft.drawLine(96, 140, 96, 158, VLinesColor); // Third
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(1, 0);
tft.setTextWrap(false);
tft.print("Lat:");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(1, 10);
tft.setTextWrap(false);
tft.print("Lon:");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(106, 0);
tft.setTextWrap(false);
tft.print("SAT:");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(1, 29);
tft.setTextWrap(false);
tft.print("UTC:");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(1, 50);
tft.setTextWrap(false);
tft.print("Loc:");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(1, 74);
tft.setTextWrap(false);
tft.print("G.asl:");
tft.setTextColor(BTextColor, BackGroundColor);
tft.setTextSize(2);
tft.setCursor(108, 71);
tft.setTextWrap(false);
tft.print("m");
tft.setTextColor(QNHtextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(1, 95);
tft.setTextWrap(false);
tft.print("QNH survey:");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(2, 140);
tft.setTextWrap(false);
tft.print("DOP");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(26, 140);
tft.setTextWrap(false);
tft.print("QNH hPa");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(73, 140);
tft.setTextWrap(false);
tft.print("T");
tft.drawCircle(85, 142, 2, FixedTextColor);
tft.setTextColor(FixedTextColor);
tft.setTextSize(1);
tft.setCursor(89, 140);
tft.setTextWrap(false);
tft.print("C");
tft.setTextColor(FixedTextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(102, 140);
tft.setTextWrap(false);
tft.print("Batt");
tft.setTextColor(STextColor, BackGroundColor);
tft.setTextSize(1);
tft.setCursor(121, 151);
tft.setTextWrap(false);
tft.print("V");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment