From 7dfffbc246382ab8b1341983d8dca5b56f156305 Mon Sep 17 00:00:00 2001 From: Christopher Liebman Date: Sat, 27 Jan 2018 07:13:34 -0800 Subject: [PATCH] Initial esp-idf based implentation --- .cproject | 57 +++ .gitignore | 12 +- .gitmodules | 3 + .project | 27 ++ .settings/language.settings.xml | 15 + .settings/org.eclipse.cdt.core.prefs | 12 + ESPNTPServer.cpp | 552 ----------------------- ESPNTPServer.h | 123 ----- Logger.cpp | 163 ------- Logger.h | 92 ---- Makefile | 9 + WireUtils.cpp | 112 ----- WireUtils.h | 35 -- components/u8g2 | 1 + main/Kconfig.projbuild | 17 + main/component.mk | 8 + main/display_task.c | 133 ++++++ main/esp_gps_ntp.h | 60 +++ main/esp_gps_ntp_main.c | 152 +++++++ main/gps_task.c | 480 ++++++++++++++++++++ main/minmea/COPYING | 13 + main/minmea/minmea.c | 645 +++++++++++++++++++++++++++ main/minmea/minmea.h | 261 +++++++++++ main/ntp_task.c | 255 +++++++++++ main/u8g2/u8g2_esp32_hal.c | 234 ++++++++++ main/u8g2/u8g2_esp32_hal.h | 41 ++ 26 files changed, 2426 insertions(+), 1086 deletions(-) create mode 100644 .cproject create mode 100644 .gitmodules create mode 100644 .project create mode 100644 .settings/language.settings.xml create mode 100644 .settings/org.eclipse.cdt.core.prefs delete mode 100644 ESPNTPServer.cpp delete mode 100644 ESPNTPServer.h delete mode 100644 Logger.cpp delete mode 100644 Logger.h create mode 100644 Makefile delete mode 100644 WireUtils.cpp delete mode 100644 WireUtils.h create mode 160000 components/u8g2 create mode 100644 main/Kconfig.projbuild create mode 100644 main/component.mk create mode 100644 main/display_task.c create mode 100644 main/esp_gps_ntp.h create mode 100644 main/esp_gps_ntp_main.c create mode 100644 main/gps_task.c create mode 100644 main/minmea/COPYING create mode 100644 main/minmea/minmea.c create mode 100644 main/minmea/minmea.h create mode 100644 main/ntp_task.c create mode 100644 main/u8g2/u8g2_esp32_hal.c create mode 100644 main/u8g2/u8g2_esp32_hal.h diff --git a/.cproject b/.cproject new file mode 100644 index 0000000..7cedfd6 --- /dev/null +++ b/.cproject @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index 0db0561..7911b6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,6 @@ -.metadata .DS_Store -.ino.cpp -.project -.cproject -.settings -Release -Debug -libraries -core *.bak *-bak +*.old +build +sdkconfig diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..40fab32 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "components/u8g2"] + path = components/u8g2 + url = git@github.com:liebman/u8g2 diff --git a/.project b/.project new file mode 100644 index 0000000..6f9dbee --- /dev/null +++ b/.project @@ -0,0 +1,27 @@ + + + esp_gps_ntp + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml new file mode 100644 index 0000000..8a0f91f --- /dev/null +++ b/.settings/language.settings.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs new file mode 100644 index 0000000..530eb67 --- /dev/null +++ b/.settings/org.eclipse.cdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/BATCH_BUILD/delimiter=\: +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/BATCH_BUILD/operation=append +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/BATCH_BUILD/value=1 +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/IDF_PATH/delimiter=\: +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/IDF_PATH/operation=append +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/IDF_PATH/value=${HOME}/esp/esp-idf +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/PATH/delimiter=\: +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/PATH/operation=replace +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/PATH/value=/bin\:/usr/bin\:/usr/sbin\:/sbin\:${HOME}/esp/xtensa-esp32-elf/bin +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/append=true +environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/appendContributed=true diff --git a/ESPNTPServer.cpp b/ESPNTPServer.cpp deleted file mode 100644 index 6587d52..0000000 --- a/ESPNTPServer.cpp +++ /dev/null @@ -1,552 +0,0 @@ -/* - * ESPNTPServer.cpp - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: Oct 29, 2017 - * Author: liebman - */ - -#include "ESPNTPServer.h" -#include // htonl() & ntohl() -#define DEBUG -//define NTP_PACKET_DEBUG -#include "Logger.h" - -Ticker validityTimer; -bool valid; -bool gps_valid; -uint32_t pps_valid_count; -volatile uint32_t valid_count; // how many times we have gone from invalid to valid -bool sentence_unknown; -uint32_t bad_checksum_count; -uint32_t req_count; -uint32_t rsp_count; -int8_t precision; -volatile uint32_t dispersion; - -volatile time_t seconds; - -volatile uint32_t last_micros; -volatile uint32_t min_micros; -volatile uint32_t max_micros; - -#if defined(MICROS_HISTORY_SIZE) -volatile uint32_t micros_history[MICROS_HISTORY_SIZE]; -volatile uint16_t micros_history_count; -volatile uint16_t micros_history_index; -#endif - -#if defined(USE_ASYNC_UDP) -AsyncUDP udp; -#else -WiFiUDP udp; -#endif - -SoftwareSerial gps(GPS_RX_PIN, GPS_TX_PIN, false, SERIAL_BUFFER_SIZE); -char nmeaBuffer[NMEA_BUFFER_SIZE]; -MicroNMEA nmea(nmeaBuffer, NMEA_BUFFER_SIZE); - -SSD1306Wire display(0x3c, SDA, SCL); - -#ifdef NTP_PACKET_DEBUG -void dumpNTPPacket(NTPPacket* ntp) -{ - dbprintf("size: %u\n", sizeof(*ntp)); - dbprintf("firstbyte: 0x%02x\n", *(uint8_t*)ntp); - dbprintf("li: %u\n", getLI(ntp->flags)); - dbprintf("version: %u\n", getVERS(ntp->flags)); - dbprintf("mode: %u\n", getMODE(ntp->flags)); - dbprintf("stratum: %u\n", ntp->stratum); - dbprintf("poll: %u\n", ntp->poll); - dbprintf("precision: %d\n", ntp->precision); - dbprintf("delay: %u\n", ntp->delay); - dbprintf("dispersion: %u\n", ntp->dispersion); - dbprintf("ref_id: %02x:%02x:%02x:%02x\n", ntp->ref_id[0], ntp->ref_id[1], ntp->ref_id[2], ntp->ref_id[3]); - dbprintf("ref_time: %08x:%08x\n", ntp->ref_time.seconds, ntp->ref_time.fraction); - dbprintf("orig_time: %08x:%08x\n", ntp->orig_time.seconds, ntp->orig_time.fraction); - dbprintf("recv_time: %08x:%08x\n", ntp->recv_time.seconds, ntp->recv_time.fraction); - dbprintf("xmit_time: %08x:%08x\n", ntp->xmit_time.seconds, ntp->xmit_time.fraction); -} -#else -#define dumpNTPPacket(x) -#endif - -void invalidate() -{ - valid = false; - gps_valid = false; - last_micros = 0; -} - -void oneSecondInterrupt() -{ - uint32_t cur_micros = micros(); - - // - // don't trust PPS if GPS is not valid. - // - if (!gps_valid) - { - return; - } - - // - // if we are still counting down then keep waiting - // - if (pps_valid_count) - { - --pps_valid_count; - if (pps_valid_count == 0) - { - // clear stats and mark us valid - min_micros = 0; - max_micros = 0; - valid = true; - ++valid_count; - } - } - - // - // restart the validity timer, if it runs out we invalidate our data. - // - validityTimer.attach_ms(VALIDITY_CHECK_MS, &invalidate); - - // - // increment seconds - // - seconds += 1; - - // - // the first time around we just initialize the last value - // - if (last_micros == 0) - { - last_micros = cur_micros; - return; - } - - uint32_t micros_count = cur_micros - last_micros; - last_micros = cur_micros; - - if (min_micros == 0 || micros_count < min_micros) - { - min_micros = micros_count; - } - - if (micros_count > max_micros) - { - max_micros = micros_count; - } - -#if defined(MICROS_HISTORY_SIZE) - micros_history[micros_history_index++] = micros_count; - if (micros_history_index >= MICROS_HISTORY_SIZE) - { - micros_history_index = 0; - } - if (micros_history_count < MICROS_HISTORY_SIZE) - { - micros_history_count++; - } -#endif - -#if defined(DEBUG) && defined(LED_PIN) - digitalWrite(LED_PIN, digitalRead(LED_PIN) ? LOW : HIGH); -#endif -} - -void getNTPTime(NTPTime *time) -{ - time->seconds = toNTP(seconds); - uint32_t cur_micros = micros(); - uint32_t micros_delta = cur_micros - last_micros; - - // - // if micros_delta is at or bigger than one second then - // use the max fraction. - // - if (micros_delta >= 1000000) - { - time->fraction = 0xffffffff; - return; - } - - double percent = us2s(micros_delta); - time->fraction = (uint32_t)(percent * (double)4294967296L); -} - -int8_t computePrecision() -{ - NTPTime t; - unsigned long start = micros(); - for (int i = 0; i < PRECISION_COUNT; ++i) - { - getNTPTime(&t); - } - unsigned long end = micros(); - double total = (double)(end - start) / 1000000.0; - double time = total / PRECISION_COUNT; - double prec = log2(time); - dbprintf("computePrecision: total:%f time:%f prec:%f\n", total, time, prec); - return (int8_t)prec; -} - -#if defined(USE_ASYNC_UDP) -void recievePacket(AsyncUDPPacket aup) -#else -void recievePacket() -#endif -{ - ++req_count; - NTPPacket ntp; - NTPTime recv_time; - getNTPTime(&recv_time); -#if defined(USE_ASYNC_UDP) - if (aup.length() != sizeof(NTPPacket)) - { - dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", aup.length(), sizeof(NTPPacket)); - return; - } -#else - if (udp.available() != sizeof(NTPPacket)) - { - dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", udp.available(), sizeof(NTPPacket)); - return; - } -#endif - - if (!valid) - { - dbprintln("recievePacket: GPS data not valid!"); - return; - } - -#if defined(USE_ASYNC_UDP) - memcpy(&ntp, aup.data(), sizeof(ntp)); -#else - udp.read((unsigned char*)&ntp, sizeof(ntp)); - -#endif - ntp.delay = ntohl(ntp.delay); - ntp.dispersion = ntohl(ntp.dispersion); - ntp.orig_time.seconds = ntohl(ntp.orig_time.seconds); - ntp.orig_time.fraction = ntohl(ntp.orig_time.fraction); - ntp.ref_time.seconds = ntohl(ntp.ref_time.seconds); - ntp.ref_time.fraction = ntohl(ntp.ref_time.fraction); - ntp.recv_time.seconds = ntohl(ntp.recv_time.seconds); - ntp.recv_time.fraction = ntohl(ntp.recv_time.fraction); - ntp.xmit_time.seconds = ntohl(ntp.xmit_time.seconds); - ntp.xmit_time.fraction = ntohl(ntp.xmit_time.fraction); - dumpNTPPacket(&ntp); - - // - // Build the response - // - ntp.flags = setLI(LI_NONE) | setVERS(NTP_VERSION) | setMODE(MODE_SERVER); - ntp.stratum = 1; - ntp.precision = precision; - // TODO: compute actual root delay, and root dispersion - ntp.delay = (uint32)(0.000001 * 65536); - ntp.dispersion = dispersion; - strncpy((char*)ntp.ref_id, REF_ID, sizeof(ntp.ref_id)); - ntp.orig_time = ntp.xmit_time; - ntp.recv_time = recv_time; - getNTPTime(&(ntp.ref_time)); - dumpNTPPacket(&ntp); - ntp.delay = htonl(ntp.delay); - ntp.dispersion = htonl(ntp.dispersion); - ntp.orig_time.seconds = htonl(ntp.orig_time.seconds); - ntp.orig_time.fraction = htonl(ntp.orig_time.fraction); - ntp.ref_time.seconds = htonl(ntp.ref_time.seconds); - ntp.ref_time.fraction = htonl(ntp.ref_time.fraction); - ntp.recv_time.seconds = htonl(ntp.recv_time.seconds); - ntp.recv_time.fraction = htonl(ntp.recv_time.fraction); - getNTPTime(&(ntp.xmit_time)); - ntp.xmit_time.seconds = htonl(ntp.xmit_time.seconds); - ntp.xmit_time.fraction = htonl(ntp.xmit_time.fraction); -#if defined(USE_ASYNC_UDP) - aup.write((uint8_t*)&ntp, sizeof(ntp)); - ++rsp_count; -#else - IPAddress address = udp.remoteIP(); - uint16_t port = udp.remotePort(); - udp.beginPacket(address, port); - udp.write((uint8_t*)&ntp, sizeof(ntp)); - udp.flush(); - udp.endPacket(); -#endif -} - -void badChecksum(MicroNMEA& mn) -{ - ++bad_checksum_count; - dbprintf("badChecksum: '%s'\n", mn.getSentence()); -} - -void unknownSentence(MicroNMEA& mn) -{ - const char* sentence = mn.getSentence(); - dbprintf("unknownSentence: %s\n", sentence); - - if (!strncmp("$PMTK", sentence, 5)) - { - return; - } - - sentence_unknown = true; -} - -void resetGPS() -{ - dbprintln("resetGPS: starting!"); - // Empty input buffer - while (gps.available()) - { - gps.read(); - } - - digitalWrite(GPS_EN_PIN, LOW); - delay(100); - digitalWrite(GPS_EN_PIN, HIGH); - -#if 0 - dbprintln("resetGPS: waiting on first sentence"); - dbflush(); - - // Reset is complete when the first valid message is received - while (1) - { - delay(1); - while (gps.available()) - { - char c = gps.read(); - if (nmea.process(c)) - { - const char* sentence = nmea.getSentence(); - dbprintf("resetGPS: done, sentence: '%s'\n", sentence); - dbflush(); - return; - } - } - } -#endif -} - -void processGPS() -{ - static boolean last_valid = false; - static boolean last_gps_valid = false; - - // - // Print valid or invalid if stats has changed. - // - if ((last_valid && !valid) || (last_gps_valid && !gps_valid)) - { - dbprintln("INVALID!"); - } - else if (!last_valid && valid) - { - dbprintln("VALID!"); - } - last_valid = valid; - last_gps_valid = gps_valid; - - while (gps.available() > 0) - { - if (nmea.process(gps.read())) - { - // - // if it was a GGA and its valid then check and maybe update the time - // - const char * id = nmea.getMessageID(); - if (nmea.isValid() && nmea.getYear() > 2000 && strcmp("GGA", id) == 0) - { - static struct tm tm; - tm.tm_year = nmea.getYear() - 1900; - tm.tm_mon = nmea.getMonth() - 1; - tm.tm_mday = nmea.getDay(); - tm.tm_hour = nmea.getHour(); - tm.tm_min = nmea.getMinute(); - tm.tm_sec = nmea.getSecond(); - time_t new_seconds = mktime(&tm); - - time_t old_seconds = seconds; - if (old_seconds != new_seconds) - { - seconds = new_seconds; - dbprintf("%010lu: %s adjusting seconds from %lu to %lu\n", millis(), nmea.getMessageID(), old_seconds, new_seconds); - } - - // - // if gps was not valid, it is now - // - if (!gps_valid) - { - gps_valid = true; - pps_valid_count = PPS_VALID_COUNT; - dbprintln("gps valid!"); - } - } - } - } -} - -void sendSentence(const char* sentence) -{ - static char cksum[3]; - MicroNMEA::generateChecksum(sentence, cksum); - cksum[2] = '\0'; - dbprintf("sendSentence: '%s*%s'\n", sentence, cksum); - gps.printf("%s*%s\r\n", sentence, cksum); - gps.flush(); -} - -void setup() -{ - delay(5000); // delay for IDE to re-open serial - dbbegin(115200); - dbprintln("\n\nStartup!"); - - pinMode(SYNC_PIN, INPUT); -#if defined(LED_PIN) - pinMode(LED_PIN, OUTPUT); -#endif - - valid = false; - valid_count = 0; - seconds = 0; - max_micros = 0; - min_micros = 0; - last_micros = 0; -#if defined(MICROS_HISTORY_SIZE) - micros_history_count = 0; - micros_history_index = 0; -#endif - -#if !defined(USE_NO_WIFI) - WiFiManager wifi; - wifi.setDebugOutput(false); - String ssid = "SynchroClock" + String(ESP.getChipId()); - wifi.autoConnect(ssid.c_str(), NULL); -#endif - - if (!display.init()) - { - dbprintln("display.init() failed!"); - } - display.flipScreenVertically(); - display.setFont(ArialMT_Plain_10); - - gps.begin(9600); - nmea.setBadChecksumHandler(&badChecksum); - nmea.setUnknownSentenceHandler(&unknownSentence); - resetGPS(); - - precision = computePrecision(); - - // - // initialize UDP handler - // -#if defined(USE_ASYNC_UDP) - while (!udp.listen(NTP_PORT)) - { -#else - while(!udp.begin(NTP_PORT)) - { -#endif - dbprintf("setup: failed to listen on port %d! Will retry in a bit...\n", NTP_PORT); - delay(1000); - dbprintf("setup: retrying!\n"); - } - - attachInterrupt(SYNC_PIN, &oneSecondInterrupt, FALLING); - -#if defined(USE_ASYNC_UDP) - udp.onPacket(recievePacket); -#endif -} - -void loop() -{ -#if !defined(USE_ASYNC_UDP) - if (udp.parsePacket()) - { - recievePacket(); - } -#endif - - processGPS(); - if (sentence_unknown) - { - sentence_unknown = false; - // Send only RMC and GGA messages. - sendSentence("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); - } - - static time_t last_seconds; - if (seconds != last_seconds) - { - if (seconds != last_seconds && ((seconds % 60) == 0 || pps_valid_count)) - { - -#if defined(MICROS_HISTORY_SIZE) - double mean = 0.0; - for (int i = 0; i < micros_history_count; ++i) - { - mean += us2s(micros_history[i]); - } - mean = mean / micros_history_count; - - double stdev = 0.0; - for (int i = 0; i < micros_history_count; ++i) - { - stdev += pow(us2s(micros_history[i]) - mean, 2); - } - stdev = sqrt(stdev / micros_history_count); - dbprintf("mean:%f stdev:%f ", mean, stdev); -#endif - double disp = us2s(MAX(abs(MICROS_PER_SEC-max_micros), abs(MICROS_PER_SEC-min_micros))); - dispersion = (uint32_t)(disp * 65536.0); - dbprintf("min:%lu max:%lu jitter:%lu valid_count:%lu valid:%s numsat:%d heap:%ld pps_valid_count:%d badcs: %lu\n", min_micros, max_micros, - max_micros - min_micros, valid_count, valid ? "true" : "false", nmea.getNumSatellites(), ESP.getFreeHeap(), pps_valid_count, - bad_checksum_count); - } - - if (seconds < last_seconds) - { - dbprintf("OOPS: time went backwards: last:%lu now:%lu\n", last_seconds, seconds); - } - - // - // Update the display - // - display.clear(); - display.setTextAlignment(TEXT_ALIGN_LEFT); - display.setFont(ArialMT_Plain_10); - const char* current_time = ctime(&last_seconds); - display.drawString(0, 0, current_time); - display.drawString(0, 10, "Address: "+WiFi.localIP().toString()); - display.drawString(0, 20, "Sat Count: " + String(nmea.getNumSatellites())); - display.drawString(0, 30, "Requests: " + String(req_count)); - display.drawString(0, 40, "Responses: " + String(rsp_count)); - // write the buffer to the display - display.display(); - } - - last_seconds = seconds; - delay(1); -} diff --git a/ESPNTPServer.h b/ESPNTPServer.h deleted file mode 100644 index b63a421..0000000 --- a/ESPNTPServer.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * ESPNTPServer.h - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: Oct 29, 2017 - * Author: liebman - */ - -#define USE_ASYNC_UDP -//#define USE_NO_WIFI - -#ifndef _ESPNTPServer_H_ -#define _ESPNTPServer_H_ -#include "Arduino.h" -#include "Ticker.h" -#if !defined(USE_NO_WIFI) -#include "WiFiManager.h" -#endif -#include "SoftwareSerial.h" -#include "MicroNMEA.h" -#include - -#include "SSD1306Wire.h" - -#if defined(USE_ASYNC_UDP) -#include "ESPAsyncUDP.h" -#else -#include -#endif - -// pin definitions -#define LED_PIN BUILTIN_LED // LED on pin, active low -#define SYNC_PIN D5 // (GPIO14) pin tied to 1hz square wave from GPS -#define GPS_RX_PIN D6 // (GPIO12) -#define GPS_TX_PIN D7 // (GPIO13) -#define GPS_EN_PIN D4 // (GPIO2) - -#define NTP_PORT 123 - -#define SYNC_EDGE_RISING 1 -#define SYNC_EDGE_FALLING 0 - -#define WARMUP_SECONDS 2 -#define PRECISION_COUNT 10000 -#define MICROS_PER_SEC 1000000 -#define SERIAL_BUFFER_SIZE 128 -#define NMEA_BUFFER_SIZE 128 -#define VALIDITY_CHECK_MS 1100 -#define PPS_VALID_COUNT 60 // must have at least this many "good" PPS interrupts to be valid -//#define MICROS_HISTORY_SIZE 1000 - -#define us2s(x) (((double)x)/(double)MICROS_PER_SEC) // microseconds to seconds - -typedef struct ntp_time -{ - uint32_t seconds; - uint32_t fraction; -} NTPTime; - -typedef struct ntp_packet -{ - uint8_t flags; - uint8_t stratum; - uint8_t poll; - int8_t precision; - uint32_t delay; - uint32_t dispersion; - uint8_t ref_id[4]; - NTPTime ref_time; - NTPTime orig_time; - NTPTime recv_time; - NTPTime xmit_time; -} NTPPacket; - -#define LI_NONE 0 -#define LI_SIXTY_ONE 1 -#define LI_FIFTY_NINE 2 -#define LI_NOSYNC 3 - -#define MODE_RESERVED 0 -#define MODE_ACTIVE 1 -#define MODE_PASSIVE 2 -#define MODE_CLIENT 3 -#define MODE_SERVER 4 -#define MODE_BROADCAST 5 -#define MODE_CONTROL 6 -#define MODE_PRIVATE 7 - -#define NTP_VERSION 4 - -#define REF_ID "PPS " // "GPS " when we have one! - -#define setLI(value) ((value&0x03)<<6) -#define setVERS(value) ((value&0x07)<<3) -#define setMODE(value) ((value&0x07)) - -#define getLI(value) ((value>>6)&0x03) -#define getVERS(value) ((value>>3)&0x07) -#define getMODE(value) (value&0x07) - -#define SEVENTY_YEARS 2208988800L -#define toEPOCH(t) ((uint32_t)t-SEVENTY_YEARS) -#define toNTP(t) ((uint32_t)t+SEVENTY_YEARS) - -// simple versions - we don't worry about side effects -#define MAX(a, b) ((a) < (b) ? (b) : (a)) -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - -#endif /* _ESPNTPServer_H_ */ diff --git a/Logger.cpp b/Logger.cpp deleted file mode 100644 index 32dabfc..0000000 --- a/Logger.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Logger.cpp - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: May 31, 2017 - * Author: liebman - */ - -#include "Logger.h" - -#define DEBUG - -#ifdef DEBUG - -#undef dbprintf -#undef dbprint -#undef dbprintln -#undef dbflush - -#define DBP_BUF_SIZE 256 -extern unsigned int snprintf(char*, unsigned int, ...); -#define dbprintf(...) {char dbp_buf[DBP_BUF_SIZE]; snprintf(dbp_buf, DBP_BUF_SIZE-1, __VA_ARGS__); Serial.print(dbp_buf);} -#define dbprintln(x) Serial.println(x) -#define dbflush() Serial.flush() -#endif - -Logger::Logger() -{ - _host = NULL; - _port = 0; - _failed = 0; -#if defined(USE_NETWORK) && defined(USE_TCP) - _client.setTimeout(1000); // 1 second connect timeout -#endif -} - -void Logger::begin() -{ - begin(LOGGER_DEFAULT_BAUD); -} - -void Logger::begin(long int baud) -{ - Serial.begin(baud); -} - -void Logger::end() -{ -#if defined(USE_NETWORK) && defined(USE_TCP) - _client.stop(); -#endif -} - -void Logger::setNetworkLogger(const char* host, uint16_t port) -{ - _host = host; - _port = port; -} - -void Logger::println(const char* message) -{ - snprintf(_buffer, LOGGER_BUFFER_SIZE-1, "%s\n", message); - Serial.print(_buffer); - Serial.flush(); -#if defined(USE_NETWORK) - send(_buffer); -#endif -} - -extern int vsnprintf(char* buffer, size_t size, const char* fmt, va_list args); - -void Logger::printf(const char* fmt, ...) -{ - va_list argp; - va_start(argp, fmt); - - vsnprintf(_buffer, LOGGER_BUFFER_SIZE-1, fmt, argp); - va_end(argp); - - Serial.print(_buffer); - Serial.flush(); -#if defined(USE_NETWORK) - send(_buffer); -#endif -} - -void Logger::flush() -{ - Serial.flush(); -#if defined(USE_NETWORK) && defined(USE_TCP) - if (_client.connected()) - { - _client.flush(); - } -#endif -} - -#if defined(USE_NETWORK) -void Logger::send(const char* message) -{ - // if we are not configured for TCP then just return - if (_host == NULL) - { - return; - } - - IPAddress ip; - - if (!WiFi.isConnected()) - { - dbprintln("Logger::log: not connected!"); - return; - } - - if (!WiFi.hostByName(_host, ip)) - { - dbprintf("Logger::log failed to resolve address for:%s\n", _host); - return; - } - -#if defined(USE_TCP) - if (!_client.connected() && _failed < 3) // try 3 times at most to connect then give up. - { - if(!_client.connect(ip, _port) && _failed < 3) - { - _failed++; - dbprintf("Logger::send failed to connect!\n"); - return; - } - } - - if (_client.write(message) != strlen(message)) - { - dbprintf("Logger::write failed to write message: %s\n", message); - return; - } - _client.flush(); -#else - _udp.beginPacket(ip, _port); - _udp.write(message); - if (!_udp.endPacket()) - { - dbprintln("Logger::log failed to send packet!"); - } -#endif -} -#endif - -Logger logger; diff --git a/Logger.h b/Logger.h deleted file mode 100644 index 708ecf0..0000000 --- a/Logger.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Logger.h - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: May 31, 2017 - * Author: liebman - */ - -#ifndef LOGGER_H_ -#define LOGGER_H_ - -#include -#include - -#define USE_TCP -//#define USE_NETWORK - -#ifdef USE_NETWORK -#ifdef USE_TCP -#include -#else -#include -#endif -#endif - -#define LOGGER_DEFAULT_BAUD 115200L -#define LOGGER_BUFFER_SIZE 256 - -class Logger { -public: - Logger(); - void begin(); - void begin(long int baud); - void end(); - void setNetworkLogger(const char* host, uint16_t port); - void println(const char*message); - void printf(const char*message, ...); - void flush(); - -private: -#ifdef USE_NETWORK -#ifdef USE_TCP - WiFiClient _client; -#else - WiFiUDP _udp; -#endif -#endif - const char* _host; - uint16_t _port; - uint16_t _failed; - char _buffer[LOGGER_BUFFER_SIZE]; -#ifdef USE_NETWORK - void send(const char* message); -#endif -}; - -extern Logger logger; - -#ifdef DEBUG -#define dbbegin(x) logger.begin(x) -#define dbnetlog(h, p) {if (strlen(h) && p) logger.setNetworkLogger(h, p);} -#define dbend() logger.end() -#define dbprintf(...) logger.printf(__VA_ARGS__) -#define dbprintln(x) logger.println(x) -#define dbprint64(l,v) logger.printf("%s %08x:%08x (%Lf)\n", l, (uint32_t)(v>>32), (uint32_t)(v & 0xffffffff), ((long double)v / 4294967296.)) -#define dbprint64s(l,v) logger.printf("%s %08x:%08x (%Lf)\n", l, (int32_t)(v>>32), (uint32_t)(v & 0xffffffff), ((long double)v / 4294967296.)) -#define dbflush() logger.flush() -#else -#define dbbegin(x) -#define dbnetlog(h, p) -#define dbend() -#define dbprintf(...) -#define dbprintln(x) -#define dbprint64(l,v) -#define dbprint64s(l,v) -#define dbflush() -#endif -#endif /* LOGGER_H_ */ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..30c4733 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := esp-gps-ntp + +include $(IDF_PATH)/make/project.mk + diff --git a/WireUtils.cpp b/WireUtils.cpp deleted file mode 100644 index 295c2fc..0000000 --- a/WireUtils.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * WireUtils.cpp - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: May 26, 2017 - * Author: liebman - */ - -#include "WireUtils.h" - -#define DEBUG -#include "Logger.h" - -WireUtilsC::WireUtilsC() -{ -} - -/** - * This routine turns off the I2C bus and clears it - * on return SCA and SCL pins are tri-state inputs. - * You need to call Wire.begin() after this to re-enable I2C - * This routine does NOT use the Wire library at all. - * - * returns 0 if bus cleared - * 1 if SCL held low. - * 2 if SDA held low by slave clock stretch for > 2sec - * 3 if SDA held low after 20 clocks. - */ -int WireUtilsC::clearBus() -{ - dbprintln("WireUtils::ClearBus: attempting to clean i2c bus"); -#if defined(TWCR) && defined(TWEN) - TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly -#endif - pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup. - pinMode(SCL, INPUT_PULLUP); - - boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low. - if (SCL_LOW) - { //If it is held low Arduno cannot become the I2C master. - dbprintln("WireUtils::ClearBus: Failed! SCL held low!"); - return 1; //I2C bus error. Could not clear SCL clock line held low - } - - boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input. - int clockCount = 20; // > 2x9 clock - - while (SDA_LOW && (clockCount > 0)) - { // vii. If SDA is Low, - clockCount--; - // Note: I2C bus is open collector so do NOT drive SCL or SDA high. - pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW - pinMode(SCL, OUTPUT); // then clock SCL Low - delayMicroseconds(10); // for >5uS - pinMode(SCL, INPUT); // release SCL LOW - pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again - // do not force high as slave may be holding it low for clock stretching. - delayMicroseconds(10); // for >5uS - // The >5uS is so that even the slowest I2C devices are handled. - SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low. - int counter = 20; - while (SCL_LOW && (counter > 0)) - { // loop waiting for SCL to become High only wait 2sec. - counter--; - delay(100); - SCL_LOW = (digitalRead(SCL) == LOW); - } - if (SCL_LOW) - { // still low after 2 sec error - dbprintln("WireUtils::ClearBus: Failed! SCL clock line held low by slave clock stretch for >2sec"); - return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec - } - SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop - } - if (SDA_LOW) - { // still low - dbprintln("WireUtils::ClearBus: Failed! SDA data line still held low"); - return 3; // I2C bus error. Could not clear. SDA data line held low - } - - // else pull SDA line low for Start or Repeated Start - pinMode(SDA, INPUT); // remove pullup. - pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control. - // When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus. - /// A Repeat Start is a Start occurring after a Start with no intervening Stop. - delayMicroseconds(10); // wait >5uS - pinMode(SDA, INPUT); // remove output low - pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control. - delayMicroseconds(10); // x. wait >5uS - pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset - pinMode(SCL, INPUT); - dbprintln("WireUtils::ClearBus: Success!"); - return 0; // all ok -} - -// act like wire library (even though I don't like it) -WireUtilsC WireUtils = WireUtilsC(); - diff --git a/WireUtils.h b/WireUtils.h deleted file mode 100644 index f20dd54..0000000 --- a/WireUtils.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * WireUtils.h - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: May 26, 2017 - * Author: liebman - */ - -#ifndef WIREUTILS_H_ -#define WIREUTILS_H_ -#include - -class WireUtilsC { -public: - WireUtilsC(); - int clearBus(); -}; - -extern WireUtilsC WireUtils; - -#endif /* WIREUTILS_H_ */ diff --git a/components/u8g2 b/components/u8g2 new file mode 160000 index 0000000..6bcf57f --- /dev/null +++ b/components/u8g2 @@ -0,0 +1 @@ +Subproject commit 6bcf57f2ae2d604e4954ea776d1eef1c0f3aed5e diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..6efe7cf --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,17 @@ +menu "ESP GPS NTP Server Configuration" + +choice WPS_TYPE + prompt "WPS mode" + default WPS_TYPE_PBC + help + WPS type for the esp32 to use. + +config WPS_TYPE_PBC + bool "PBC" +config WPS_TYPE_PIN + bool "PIN" +config WPS_TYPE_DISABLE + bool "disable" +endchoice + +endmenu diff --git a/main/component.mk b/main/component.mk new file mode 100644 index 0000000..b4d9655 --- /dev/null +++ b/main/component.mk @@ -0,0 +1,8 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_ADD_INCLUDEDIRS := . minmea u8g2 + +COMPONENT_SRCDIRS := $(COMPONENT_ADD_INCLUDEDIRS) diff --git a/main/display_task.c b/main/display_task.c new file mode 100644 index 0000000..a821dc1 --- /dev/null +++ b/main/display_task.c @@ -0,0 +1,133 @@ +/* + * display_task.c + * + * Created on: Jan 21, 2018 + * Author: chris.l + */ + +#include "esp_gps_ntp.h" +#include "freertos/queue.h" +#include "u8g2.h" +#include "u8g2_esp32_hal.h" +#include +#include +#include "tcpip_adapter.h" + +static const char *TAG = "DSP"; +static xQueueHandle dsp_queue = NULL; +static u8g2_t dsp; + +void IRAM_ATTR triggerDisplay() +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + bool value = true; + + if (dsp_queue != NULL) + { + xQueueSendFromISR(dsp_queue, &value, &xHigherPriorityTaskWoken ); + } +} + +static void dsp_task() +{ + GPSTaskStatus gps; + NTPTaskStatus ntp; + static long unsigned int duration; + static bool blink; + const int buf_size = 64; + char buf[buf_size]; + struct tm t; + tcpip_adapter_ip_info_t ip; + memset(&ip, 0, sizeof(tcpip_adapter_ip_info_t)); + bool value; + ESP_LOGI(TAG, "starting!"); + + dsp_queue = xQueueCreate(10, sizeof(value)); + + u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT; + u8g2_esp32_hal.sda = SDA_PIN; + u8g2_esp32_hal.scl = SCL_PIN; + u8g2_esp32_hal_init(u8g2_esp32_hal); + + u8g2_Setup_ssd1306_i2c_128x64_noname_f(&dsp, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb); // init u8g2 structure + u8g2_SetI2CAddress(&dsp, 0x3c<<1); + ESP_LOGD(TAG, "initializing display"); + u8g2_InitDisplay(&dsp); // send init sequence to the display, display is in sleep mode after this, + ESP_LOGD(TAG, "waking display"); + u8g2_ClearDisplay(&dsp); + u8g2_SetPowerSave(&dsp, 0); // wake up display + u8g2_SetFont(&dsp, u8g2_font_timR08_tr); + u8g2_SetFontMode(&dsp, 0); + + while(1) + { + if (xQueueReceive(dsp_queue, &value, pdMS_TO_TICKS(10000))) + { + micros_t start = micros(); + getGPSTaskStatus(&gps); + getNTPTaskStatus(&ntp); + u8g2_ClearBuffer(&dsp); + gmtime_r(&gps.seconds, &t); + snprintf(buf, buf_size-1, "UTC: %04d/%02d/%02d %02d:%02d:%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); + u8g2_DrawStr(&dsp, 0, 8, buf); + snprintf(buf, buf_size-1, "Req: %u", ntp.req_count); + u8g2_DrawStr(&dsp, 0, 16, buf); + snprintf(buf, buf_size-1, "Rsp: %u", ntp.rsp_count); + u8g2_DrawStr(&dsp, 64, 16, buf); + snprintf(buf, buf_size-1, "Sat: %d", gps.sat_count); + u8g2_DrawStr(&dsp, 0, 24, buf); + snprintf(buf, buf_size-1, "Fix: %d", gps.fix_quality); + u8g2_DrawStr(&dsp, 64, 24, buf); + snprintf(buf, buf_size, "Valid: %d", gps.valid_count); + u8g2_DrawStr(&dsp, 0, 32, buf); + snprintf(buf, buf_size, "Timeout: %d", gps.timeouts); + u8g2_DrawStr(&dsp, 64, 32, buf); + + if (gps.valid_in) + { + snprintf(buf, buf_size, "Valid In: %d", gps.valid_in); + u8g2_DrawStr(&dsp, 0, 40, buf); + } + + gmtime_r(&gps.valid_since, &t); + if (gps.valid) + { + snprintf(buf, buf_size-1, "Valid: %04d/%02d/%02d %02d:%02d:%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); + u8g2_DrawStr(&dsp, 0, 48, buf); + } + else + { + if (blink) + { + snprintf(buf, buf_size-1, "Invld: %04d/%02d/%02d %02d:%02d:%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); + u8g2_DrawStr(&dsp, 0, 48, buf); + } + } + gmtime_r(&gps.initial_valid, &t); + snprintf(buf, buf_size-1, "Start: %04d/%02d/%02d %02d:%02d:%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); + u8g2_DrawStr(&dsp, 0, 56, buf); + if (tcpip_adapter_get_ip_info(ESP_IF_WIFI_STA, &ip) == 0) { + snprintf(buf, buf_size-1, "IP: "IPSTR, IP2STR(&ip.ip)); + u8g2_DrawStr(&dsp, 0, 64, buf); + } + u8g2_SendBuffer(&dsp); + micros_t end = micros(); + duration = end - start; + blink = !blink; + } + else + { + ESP_LOGD(TAG, "timeout waiting for display queue!"); + } + } +} + + +void start_display() +{ + esp_log_level_set(TAG, ESP_LOG_INFO); + esp_log_level_set("u8g2_hal", ESP_LOG_INFO); + + xTaskCreatePinnedToCore(dsp_task, "dsp_task", 1024*4, NULL, DSP_TASK_PRI, NULL, 1); + +} diff --git a/main/esp_gps_ntp.h b/main/esp_gps_ntp.h new file mode 100644 index 0000000..c0729d8 --- /dev/null +++ b/main/esp_gps_ntp.h @@ -0,0 +1,60 @@ +/* + * esp_gps_ntp.h + * + * Created on: Jan 21, 2018 + * Author: chris.l + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +#ifndef MAIN_ESP_GPS_NTP_H_ +#define MAIN_ESP_GPS_NTP_H_ + +#define MICROS_PER_SEC 1000000 +#define us2s(x) (((double)x)/(double)MICROS_PER_SEC) // microseconds to seconds + +#define SDA_PIN (GPIO_NUM_21) +#define SCL_PIN (GPIO_NUM_22) + +typedef uint64_t micros_t; +typedef struct gps_task_status +{ + uint32_t timeouts; + time_t seconds; + micros_t min_micros; + micros_t max_micros; + bool gps_valid; + bool valid; + uint32_t valid_in; + time_t valid_since; + uint32_t valid_count; + time_t initial_valid; + int sat_count; + int fix_quality; +} GPSTaskStatus; + +typedef struct ntp_task_status +{ + uint32_t req_count; + uint32_t rsp_count; +} NTPTaskStatus; + +#define GPS_TASK_PRI configMAX_PRIORITIES +#define NTP_TASK_PRI (configMAX_PRIORITIES-1) +#define DSP_TASK_PRI (configMAX_PRIORITIES-2) + +extern void start_gps(); +extern void start_ntp(); +extern void start_display(); + +extern void triggerDisplay(); +extern void getTime(time_t *secs, time_t *usecs); +extern void getGPSTaskStatus(GPSTaskStatus* status); +extern void getNTPTaskStatus(NTPTaskStatus* status); +extern bool isTimeValid(); +extern micros_t micros(); +extern uint32_t getDispersion(); + +#endif /* MAIN_ESP_GPS_NTP_H_ */ diff --git a/main/esp_gps_ntp_main.c b/main/esp_gps_ntp_main.c new file mode 100644 index 0000000..ee2de1e --- /dev/null +++ b/main/esp_gps_ntp_main.c @@ -0,0 +1,152 @@ +/* Hello World Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" + +#include "esp_wifi.h" +#include "esp_log.h" +#include "esp_wps.h" +#include "esp_event_loop.h" +#include "nvs_flash.h" + +#include "esp_gps_ntp.h" + +/*set wps mode via "make menuconfig"*/ +#if CONFIG_WPS_TYPE_PBC +#define WPS_TEST_MODE WPS_TYPE_PBC +#elif CONFIG_WPS_TYPE_PIN +#define WPS_TEST_MODE WPS_TYPE_PIN +#else +#define WPS_TEST_MODE WPS_TYPE_DISABLE +#endif /*CONFIG_WPS_TYPE_PBC*/ + + +#ifndef PIN2STR +#define PIN2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5], (a)[6], (a)[7] +#define PINSTR "%c%c%c%c%c%c%c%c" +#endif + + +static const char *TAG = "ESP_GPS_NTP"; + +static esp_wps_config_t config = WPS_CONFIG_INIT_DEFAULT(WPS_TEST_MODE); + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) + { + case SYSTEM_EVENT_STA_START: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); + break; + + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); + ESP_LOGI(TAG, "got ip:%s\n", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + start_ntp(); + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED"); + ESP_ERROR_CHECK(esp_wifi_connect()); + break; + + case SYSTEM_EVENT_STA_WPS_ER_SUCCESS: + /*point: the function esp_wifi_wps_start() only get ssid & password + * so call the function esp_wifi_connect() here + * */ + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_WPS_ER_SUCCESS"); + ESP_ERROR_CHECK(esp_wifi_wps_disable()); + ESP_ERROR_CHECK(esp_wifi_connect()); + break; + + case SYSTEM_EVENT_STA_WPS_ER_FAILED: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_WPS_ER_FAILED"); + ESP_ERROR_CHECK(esp_wifi_wps_disable()); + ESP_ERROR_CHECK(esp_wifi_wps_enable(&config)); + ESP_ERROR_CHECK(esp_wifi_wps_start(0)); + break; + + case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_WPS_ER_TIMEOUT"); + ESP_ERROR_CHECK(esp_wifi_wps_disable()); + ESP_ERROR_CHECK(esp_wifi_wps_enable(&config)); + ESP_ERROR_CHECK(esp_wifi_wps_start(0)); + break; + + case SYSTEM_EVENT_STA_WPS_ER_PIN: + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_WPS_ER_PIN"); + /*show the PIN code here*/ + ESP_LOGI(TAG, "WPS_PIN = "PINSTR, PIN2STR(event->event_info.sta_er_pin.pin_code)); + break; + + default: + break; + } + return ESP_OK; +} + +/*init wifi as sta and start wps*/ +static void start_wifi(void) +{ + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + + if (esp_wifi_connect()) + { + ESP_LOGI(TAG, "wifi not configured, starting wps..."); + + + ESP_ERROR_CHECK(esp_wifi_wps_enable(&config)); + ESP_ERROR_CHECK(esp_wifi_wps_start(0)); + } +} + +micros_t IRAM_ATTR micros() +{ + return esp_timer_get_time(); +} + +void app_main() +{ + printf("ESP32 GPS NTP Server\n"); + + /* Print chip information */ + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + printf("This is ESP32 chip with %d CPU cores, WiFi%s%s, ", + chip_info.cores, + (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", + (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); + + printf("silicon revision %d, ", chip_info.revision); + + printf("\n*****\nRUNNING on core %d\n*****\n", xPortGetCoreID()); + fflush(stdout); + + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK( ret ); + + start_wifi(); + start_gps(); + start_display(); +} diff --git a/main/gps_task.c b/main/gps_task.c new file mode 100644 index 0000000..3651520 --- /dev/null +++ b/main/gps_task.c @@ -0,0 +1,480 @@ +/* + * gps_task.c + * + * Created on: Jan 20, 2018 + * Author: chris.l + */ + +#include "esp_gps_ntp.h" + +#include "driver/uart.h" +#include "freertos/timers.h" +#include "sys/time.h" +#include "minmea.h" + +static const char *TAG = "GPS"; + +#define RX_BUF_SIZE 255 + +#define ESP_INTR_FLAG_DEFAULT 0 + +// simple versions - we don't worry about side effects +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define VALIDITY_TIMER_MS 1010 +#define PPS_VALID_COUNT 10 // must have at least this many "good" PPS interrupts to be valid + +#define PPS_PIN (GPIO_NUM_19) +#define TXD_PIN (GPIO_NUM_17) +#define RXD_PIN (GPIO_NUM_16) +#define LED_PIN (GPIO_NUM_15) + +#define GPS_UART (UART_NUM_2) + +static TimerHandle_t timer; +static volatile uint32_t timeouts; +static volatile time_t seconds; +static volatile micros_t last_micros; +static volatile micros_t min_micros; +static volatile micros_t max_micros; +static volatile bool gps_valid; +static volatile bool valid; +static volatile uint32_t valid_in; +static volatile time_t valid_since; +static volatile uint32_t valid_count; +static volatile time_t initial_valid; +static uint32_t dispersion; +static volatile char reason[128]; + +typedef struct gps_status +{ + bool valid; + int sat_count; + int fix_quality; +} GPSStatus; + +static GPSStatus gps_status; + + + +#ifdef LED_PIN +#define DEBUG_LED_ON() gpio_set_level(LED_PIN, 1) +#define DEBUG_LED_OFF() gpio_set_level(LED_PIN, 0) +#define DEBUG_LED_INIT() init_debug_led() +static void init_debug_led() +{ + // + // set up the LED pin + // + + gpio_config_t io_conf; + //interrupt disabled + io_conf.intr_type = GPIO_PIN_INTR_DISABLE; + //set as input mode + io_conf.mode = GPIO_MODE_OUTPUT; + //bit mask of the pins that you want to set,e.g.GPIO18/19 + io_conf.pin_bit_mask = (1ULL<timeouts = timeouts; + status->seconds = seconds; + status->min_micros = min_micros; + status->max_micros = max_micros; + status->gps_valid = gps_valid; + status->valid = valid; + status->valid_in = valid_in; + status->valid_since = valid_since; + status->valid_count = valid_count; + status->initial_valid = initial_valid; + status->sat_count = gps_status.sat_count; + status->fix_quality = gps_status.fix_quality; +} + +static void IRAM_ATTR pps_isr_handler(void* arg) +{ + DEBUG_LED_ON(); + micros_t cur_micros = micros(); + // restart the validity timer + xTimerReset(timer, 0); + + // + // trigger a display update each second + // + triggerDisplay(); + + // + // don't trust PPS if GPS is not valid. + // + if (!gps_valid) + { + DEBUG_LED_OFF(); + return; + } + + // + // if we are still counting down then keep waiting + // + if (valid_in) + { + --valid_in; + if (valid_in == 0) + { + // clear stats and mark us valid + min_micros = 0; + max_micros = 0; + valid = true; + valid_since = seconds; + ++valid_count; + reason[0] = '\0'; + if (initial_valid == 0) + { + initial_valid = seconds; + } + } + } + + seconds++; + // + // the first time around we just initialize the last value + // + if (last_micros == 0) + { + last_micros = cur_micros; + DEBUG_LED_OFF(); + return; + } + + uint32_t micros_count = cur_micros - last_micros; + last_micros = cur_micros; + + if (min_micros == 0 || micros_count < min_micros) + { + min_micros = micros_count; + } + + if (micros_count > max_micros) + { + max_micros = micros_count; + } + + DEBUG_LED_OFF(); +} + +// +// read a single NMEA record +// +static char *readNMEA() +{ + static uint8_t line[RX_BUF_SIZE+1]; + int size; + uint8_t *p = line; + *p = '\0'; + while(1) + { + if (p >= line+RX_BUF_SIZE) + { + ESP_LOGI(TAG, "Read buffer overflow! size %d bytes: '%s'", p-line, line); + } + size = uart_read_bytes(GPS_UART, (unsigned char *)p, 1, portMAX_DELAY); + if (size == 1) + { + if (*p == '$') + { + // + // '$' starts a record so reset pointer to start + // + p = line; + *p = '$'; + } + else if (*p == '\r' || line[0] != '$') + { + // + // skip newlin and any chars if buffer does not start with '$' + continue; + } + else if (*p == '\n') + { + // + // got a record! + // + *p = 0; + break; + } + p++; + } + } + + return (char*)line; +} + + + +static void IRAM_ATTR invalidate(const char* fmt, ...) +{ + va_list argp; + va_start(argp, fmt); + if (reason[0] == '\0') + { + vsnprintf((char*)reason, sizeof(reason)-1, fmt, argp); + } + va_end(argp); + + if (valid) + { + valid_since = seconds; + } + + valid = false; + gps_valid = false; + last_micros = 0; + valid_in = 0; +} + +static void init_pps() +{ + // + // set up the PPS interrupt + // + + gpio_config_t io_conf; + //interrupt of falling edge + io_conf.intr_type = GPIO_PIN_INTR_POSEDGE; + //set as input mode + io_conf.mode = GPIO_MODE_INPUT; + //bit mask of the pins that you want to set,e.g.GPIO18/19 + io_conf.pin_bit_mask = (1ULL< ts.tv_sec) + { + timewarps += 1; + ESP_LOGD(TAG, "$RMC: ignoring timewarp back! delayed serial? %lu -> %lu", seconds, ts.tv_sec); + if (timewarps > 1) + { + invalidate("time warped backwards too many (%d) times!", timewarps); + timewarps = 0; + } + } + else if (seconds != ts.tv_sec) + { + ESP_LOGW(TAG, "$RMC: adjusting seconds %lu -> %lu", seconds, ts.tv_sec); + seconds = ts.tv_sec; + } + else + { + timewarps = 0; + } + + // + // if gps was not valid, it is now + // + if (!gps_valid) + { + gps_valid = true; + valid_in = PPS_VALID_COUNT; + ESP_LOGI(TAG, "gps valid!"); + } + } + else + { + if (valid) + { + invalidate("GPS $RMC"); + } + } + } + else + { + ESP_LOGE(TAG, "$RMC: failed to parse line: %s", line); + } + } + break; + + case MINMEA_SENTENCE_GGA: + { + struct minmea_sentence_gga frame; + if (minmea_parse_gga(&frame, line)) { + ESP_LOGD(TAG, "$GGA: seconds: %lu timeouts: %u fix: %d sats: %d", seconds, timeouts, frame.fix_quality, frame.satellites_tracked); + gps_status.sat_count = frame.satellites_tracked; + gps_status.fix_quality = frame.fix_quality; + } + else + { + ESP_LOGE(TAG, "$GGA: failed to parse line: %s", line); + } + } + break; + + case MINMEA_SENTENCE_GSV: + case MINMEA_SENTENCE_GSA: + case MINMEA_SENTENCE_GLL: + case MINMEA_SENTENCE_GST: + case MINMEA_SENTENCE_VTG: + case MINMEA_SENTENCE_ZDA: + ESP_LOGV(TAG, "IGNORING: %s", line); + break; + + case MINMEA_INVALID: + ESP_LOGE(TAG, "INVALID: '%s'", line); + break; + + case MINMEA_UNKNOWN: + ESP_LOGW(TAG, "UNKNOWN: '%s'", line); + break; + } + + // + // Recompute dispersion periodically + // + static time_t last_seconds; + if (seconds != last_seconds) + { + double disp = us2s(MAX(abs(MICROS_PER_SEC-max_micros), abs(MICROS_PER_SEC-min_micros))); + dispersion = (uint32_t)(disp * 65536.0); +#if 0 + ESP_LOGI(TAG, "min: %llu max: %llu jitter: %llu sats: %d fix: %d valid_count: %u valid_in: %u valid: %s", + min_micros, max_micros, max_micros - min_micros, gps_status.sat_count, gps_status.fix_quality, valid_count, valid_in, valid ? "true" : "false"); +#endif + } + last_seconds = seconds; + + + } +} + +void start_gps() +{ + esp_log_level_set(TAG, ESP_LOG_INFO); + + xTaskCreatePinnedToCore(gps_task, "gps_task", 1024*2, NULL, GPS_TASK_PRI, NULL, 1); + +} diff --git a/main/minmea/COPYING b/main/minmea/COPYING new file mode 100644 index 0000000..c6c7def --- /dev/null +++ b/main/minmea/COPYING @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/main/minmea/minmea.c b/main/minmea/minmea.c new file mode 100644 index 0000000..e45b019 --- /dev/null +++ b/main/minmea/minmea.c @@ -0,0 +1,645 @@ +/* + * Copyright © 2014 Kosma Moczek + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +#include "minmea.h" + +#include +#include +#include +#include +#include + +#define boolstr(s) ((s) ? "true" : "false") + +static int hex2int(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; +} + +uint8_t minmea_checksum(const char *sentence) +{ + // Support senteces with or without the starting dollar sign. + if (*sentence == '$') + sentence++; + + uint8_t checksum = 0x00; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while (*sentence && *sentence != '*') + checksum ^= *sentence++; + + return checksum; +} + +bool minmea_check(const char *sentence, bool strict) +{ + uint8_t checksum = 0x00; + + // Sequence length is limited. + if (strlen(sentence) > MINMEA_MAX_LENGTH + 3) + return false; + + // A valid sentence starts with "$". + if (*sentence++ != '$') + return false; + + // The optional checksum is an XOR of all bytes between "$" and "*". + while (*sentence && *sentence != '*' && isprint((unsigned char) *sentence)) + checksum ^= *sentence++; + + // If checksum is present... + if (*sentence == '*') { + // Extract checksum. + sentence++; + int upper = hex2int(*sentence++); + if (upper == -1) + return false; + int lower = hex2int(*sentence++); + if (lower == -1) + return false; + int expected = upper << 4 | lower; + + // Check for checksum mismatch. + if (checksum != expected) + return false; + } else if (strict) { + // Discard non-checksummed frames in strict mode. + return false; + } + + // The only stuff allowed at this point is a newline. + if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n")) + return false; + + return true; +} + +static inline bool minmea_isfield(char c) { + return isprint((unsigned char) c) && c != ',' && c != '*'; +} + +bool minmea_scan(const char *sentence, const char *format, ...) +{ + bool result = false; + bool optional = false; + va_list ap; + va_start(ap, format); + + const char *field = sentence; +#define next_field() \ + do { \ + /* Progress to the next field. */ \ + while (minmea_isfield(*sentence)) \ + sentence++; \ + /* Make sure there is a field there. */ \ + if (*sentence == ',') { \ + sentence++; \ + field = sentence; \ + } else { \ + field = NULL; \ + } \ + } while (0) + + while (*format) { + char type = *format++; + + if (type == ';') { + // All further fields are optional. + optional = true; + continue; + } + + if (!field && !optional) { + // Field requested but we ran out if input. Bail out. + goto parse_error; + } + + switch (type) { + case 'c': { // Single character field (char). + char value = '\0'; + + if (field && minmea_isfield(*field)) + value = *field; + + *va_arg(ap, char *) = value; + } break; + + case 'd': { // Single character direction field (int). + int value = 0; + + if (field && minmea_isfield(*field)) { + switch (*field) { + case 'N': + case 'E': + value = 1; + break; + case 'S': + case 'W': + value = -1; + break; + default: + goto parse_error; + } + } + + *va_arg(ap, int *) = value; + } break; + + case 'f': { // Fractional value with scale (struct minmea_float). + int sign = 0; + int_least32_t value = -1; + int_least32_t scale = 0; + + if (field) { + while (minmea_isfield(*field)) { + if (*field == '+' && !sign && value == -1) { + sign = 1; + } else if (*field == '-' && !sign && value == -1) { + sign = -1; + } else if (isdigit((unsigned char) *field)) { + int digit = *field - '0'; + if (value == -1) + value = 0; + if (value > (INT_LEAST32_MAX-digit) / 10) { + /* we ran out of bits, what do we do? */ + if (scale) { + /* truncate extra precision */ + break; + } else { + /* integer overflow. bail out. */ + goto parse_error; + } + } + value = (10 * value) + digit; + if (scale) + scale *= 10; + } else if (*field == '.' && scale == 0) { + scale = 1; + } else if (*field == ' ') { + /* Allow spaces at the start of the field. Not NMEA + * conformant, but some modules do this. */ + if (sign != 0 || value != -1 || scale != 0) + goto parse_error; + } else { + goto parse_error; + } + field++; + } + } + + if ((sign || scale) && value == -1) + goto parse_error; + + if (value == -1) { + /* No digits were scanned. */ + value = 0; + scale = 0; + } else if (scale == 0) { + /* No decimal point. */ + scale = 1; + } + if (sign) + value *= sign; + + *va_arg(ap, struct minmea_float *) = (struct minmea_float) {value, scale}; + } break; + + case 'i': { // Integer value, default 0 (int). + int value = 0; + + if (field) { + char *endptr; + value = strtol(field, &endptr, 10); + if (minmea_isfield(*endptr)) + goto parse_error; + } + + *va_arg(ap, int *) = value; + } break; + + case 's': { // String value (char *). + char *buf = va_arg(ap, char *); + + if (field) { + while (minmea_isfield(*field)) + *buf++ = *field++; + } + + *buf = '\0'; + } break; + + case 't': { // NMEA talker+sentence identifier (char *). + // This field is always mandatory. + if (!field) + goto parse_error; + + if (field[0] != '$') + goto parse_error; + for (int f=0; f<5; f++) + if (!minmea_isfield(field[1+f])) + goto parse_error; + + char *buf = va_arg(ap, char *); + memcpy(buf, field+1, 5); + buf[5] = '\0'; + } break; + + case 'D': { // Date (int, int, int), -1 if empty. + struct minmea_date *date = va_arg(ap, struct minmea_date *); + + int d = -1, m = -1, y = -1; + + if (field && minmea_isfield(*field)) { + // Always six digits. + for (int f=0; f<6; f++) + if (!isdigit((unsigned char) field[f])) + goto parse_error; + + char dArr[] = {field[0], field[1], '\0'}; + char mArr[] = {field[2], field[3], '\0'}; + char yArr[] = {field[4], field[5], '\0'}; + d = strtol(dArr, NULL, 10); + m = strtol(mArr, NULL, 10); + y = strtol(yArr, NULL, 10); + } + + date->day = d; + date->month = m; + date->year = y; + } break; + + case 'T': { // Time (int, int, int, int), -1 if empty. + struct minmea_time *time_ = va_arg(ap, struct minmea_time *); + + int h = -1, i = -1, s = -1, u = -1; + + if (field && minmea_isfield(*field)) { + // Minimum required: integer time. + for (int f=0; f<6; f++) + if (!isdigit((unsigned char) field[f])) + goto parse_error; + + char hArr[] = {field[0], field[1], '\0'}; + char iArr[] = {field[2], field[3], '\0'}; + char sArr[] = {field[4], field[5], '\0'}; + h = strtol(hArr, NULL, 10); + i = strtol(iArr, NULL, 10); + s = strtol(sArr, NULL, 10); + field += 6; + + // Extra: fractional time. Saved as microseconds. + if (*field++ == '.') { + uint32_t value = 0; + uint32_t scale = 1000000LU; + while (isdigit((unsigned char) *field) && scale > 1) { + value = (value * 10) + (*field++ - '0'); + scale /= 10; + } + u = value * scale; + } else { + u = 0; + } + } + + time_->hours = h; + time_->minutes = i; + time_->seconds = s; + time_->microseconds = u; + } break; + + case '_': { // Ignore the field. + } break; + + default: { // Unknown. + goto parse_error; + } break; + } + + next_field(); + } + + result = true; + +parse_error: + va_end(ap); + return result; +} + +bool minmea_talker_id(char talker[3], const char *sentence) +{ + char type[6]; + if (!minmea_scan(sentence, "t", type)) + return false; + + talker[0] = type[0]; + talker[1] = type[1]; + talker[2] = '\0'; + + return true; +} + +enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict) +{ + if (!minmea_check(sentence, strict)) + return MINMEA_INVALID; + + char type[6]; + if (!minmea_scan(sentence, "t", type)) + return MINMEA_INVALID; + + if (!strcmp(type+2, "RMC")) + return MINMEA_SENTENCE_RMC; + if (!strcmp(type+2, "GGA")) + return MINMEA_SENTENCE_GGA; + if (!strcmp(type+2, "GSA")) + return MINMEA_SENTENCE_GSA; + if (!strcmp(type+2, "GLL")) + return MINMEA_SENTENCE_GLL; + if (!strcmp(type+2, "GST")) + return MINMEA_SENTENCE_GST; + if (!strcmp(type+2, "GSV")) + return MINMEA_SENTENCE_GSV; + if (!strcmp(type+2, "VTG")) + return MINMEA_SENTENCE_VTG; + if (!strcmp(type+2, "ZDA")) + return MINMEA_SENTENCE_ZDA; + + return MINMEA_UNKNOWN; +} + +bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence) +{ + // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62 + char type[6]; + char validity; + int latitude_direction; + int longitude_direction; + int variation_direction; + if (!minmea_scan(sentence, "tTcfdfdffDfd", + type, + &frame->time, + &validity, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->speed, + &frame->course, + &frame->date, + &frame->variation, &variation_direction)) + return false; + if (strcmp(type+2, "RMC")) + return false; + + frame->valid = (validity == 'A'); + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + frame->variation.value *= variation_direction; + + return true; +} + +bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence) +{ + // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 + char type[6]; + int latitude_direction; + int longitude_direction; + + if (!minmea_scan(sentence, "tTfdfdiiffcfci_", + type, + &frame->time, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->fix_quality, + &frame->satellites_tracked, + &frame->hdop, + &frame->altitude, &frame->altitude_units, + &frame->height, &frame->height_units, + &frame->dgps_age)) + return false; + if (strcmp(type+2, "GGA")) + return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence) +{ + // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39 + char type[6]; + + if (!minmea_scan(sentence, "tciiiiiiiiiiiiifff", + type, + &frame->mode, + &frame->fix_type, + &frame->sats[0], + &frame->sats[1], + &frame->sats[2], + &frame->sats[3], + &frame->sats[4], + &frame->sats[5], + &frame->sats[6], + &frame->sats[7], + &frame->sats[8], + &frame->sats[9], + &frame->sats[10], + &frame->sats[11], + &frame->pdop, + &frame->hdop, + &frame->vdop)) + return false; + if (strcmp(type+2, "GSA")) + return false; + + return true; +} + +bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence) +{ + // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$; + char type[6]; + int latitude_direction; + int longitude_direction; + + if (!minmea_scan(sentence, "tfdfdTc;c", + type, + &frame->latitude, &latitude_direction, + &frame->longitude, &longitude_direction, + &frame->time, + &frame->status, + &frame->mode)) + return false; + if (strcmp(type+2, "GLL")) + return false; + + frame->latitude.value *= latitude_direction; + frame->longitude.value *= longitude_direction; + + return true; +} + +bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence) +{ + // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58 + char type[6]; + + if (!minmea_scan(sentence, "tTfffffff", + type, + &frame->time, + &frame->rms_deviation, + &frame->semi_major_deviation, + &frame->semi_minor_deviation, + &frame->semi_major_orientation, + &frame->latitude_error_deviation, + &frame->longitude_error_deviation, + &frame->altitude_error_deviation)) + return false; + if (strcmp(type+2, "GST")) + return false; + + return true; +} + +bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence) +{ + // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74 + // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D + // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75 + // $GPGSV,4,4,13,39,31,170,27*40 + // $GPGSV,4,4,13*7B + char type[6]; + + if (!minmea_scan(sentence, "tiii;iiiiiiiiiiiiiiii", + type, + &frame->total_msgs, + &frame->msg_nr, + &frame->total_sats, + &frame->sats[0].nr, + &frame->sats[0].elevation, + &frame->sats[0].azimuth, + &frame->sats[0].snr, + &frame->sats[1].nr, + &frame->sats[1].elevation, + &frame->sats[1].azimuth, + &frame->sats[1].snr, + &frame->sats[2].nr, + &frame->sats[2].elevation, + &frame->sats[2].azimuth, + &frame->sats[2].snr, + &frame->sats[3].nr, + &frame->sats[3].elevation, + &frame->sats[3].azimuth, + &frame->sats[3].snr + )) { + return false; + } + if (strcmp(type+2, "GSV")) + return false; + + return true; +} + +bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence) +{ + // $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48 + // $GPVTG,156.1,T,140.9,M,0.0,N,0.0,K*41 + // $GPVTG,096.5,T,083.5,M,0.0,N,0.0,K,D*22 + // $GPVTG,188.36,T,,M,0.820,N,1.519,K,A*3F + char type[6]; + char c_true, c_magnetic, c_knots, c_kph, c_faa_mode; + + if (!minmea_scan(sentence, "tfcfcfcfc;c", + type, + &frame->true_track_degrees, + &c_true, + &frame->magnetic_track_degrees, + &c_magnetic, + &frame->speed_knots, + &c_knots, + &frame->speed_kph, + &c_kph, + &c_faa_mode)) + return false; + if (strcmp(type+2, "VTG")) + return false; + // check chars + if (c_true != 'T' || + c_magnetic != 'M' || + c_knots != 'N' || + c_kph != 'K') + return false; + frame->faa_mode = c_faa_mode; + + return true; +} + +bool minmea_parse_zda(struct minmea_sentence_zda *frame, const char *sentence) +{ + // $GPZDA,201530.00,04,07,2002,00,00*60 + char type[6]; + + if(!minmea_scan(sentence, "tTiiiii", + type, + &frame->time, + &frame->date.day, + &frame->date.month, + &frame->date.year, + &frame->hour_offset, + &frame->minute_offset)) + return false; + if (strcmp(type+2, "ZDA")) + return false; + + // check offsets + if (abs(frame->hour_offset) > 13 || + frame->minute_offset > 59 || + frame->minute_offset < 0) + return false; + + return true; +} + +int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_) +{ + if (date->year == -1 || time_->hours == -1) + return -1; + + struct tm tm; + memset(&tm, 0, sizeof(tm)); + if (date->year < 80) { + tm.tm_year = 2000 + date->year - 1900; // 2000-2079 + } else if (date->year >= 1900) { + tm.tm_year = date->year - 1900; // 4 digit year, use directly + } else { + tm.tm_year = date->year; // 1980-1999 + } + tm.tm_mon = date->month - 1; + tm.tm_mday = date->day; + tm.tm_hour = time_->hours; + tm.tm_min = time_->minutes; + tm.tm_sec = time_->seconds; + + time_t timestamp = mktime(&tm); + if (timestamp != -1) { + ts->tv_sec = timestamp; + ts->tv_nsec = time_->microseconds * 1000; + return 0; + } else { + return -1; + } +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/main/minmea/minmea.h b/main/minmea/minmea.h new file mode 100644 index 0000000..b80124c --- /dev/null +++ b/main/minmea/minmea.h @@ -0,0 +1,261 @@ +/* + * Copyright © 2014 Kosma Moczek + * This program is free software. It comes without any warranty, to the extent + * permitted by applicable law. You can redistribute it and/or modify it under + * the terms of the Do What The Fuck You Want To Public License, Version 2, as + * published by Sam Hocevar. See the COPYING file for more details. + */ + +#ifndef MINMEA_H +#define MINMEA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include +#ifdef MINMEA_INCLUDE_COMPAT +#include +#endif + +#define MINMEA_MAX_LENGTH 80 + +enum minmea_sentence_id { + MINMEA_INVALID = -1, + MINMEA_UNKNOWN = 0, + MINMEA_SENTENCE_RMC, + MINMEA_SENTENCE_GGA, + MINMEA_SENTENCE_GSA, + MINMEA_SENTENCE_GLL, + MINMEA_SENTENCE_GST, + MINMEA_SENTENCE_GSV, + MINMEA_SENTENCE_VTG, + MINMEA_SENTENCE_ZDA, +}; + +struct minmea_float { + int_least32_t value; + int_least32_t scale; +}; + +struct minmea_date { + int day; + int month; + int year; +}; + +struct minmea_time { + int hours; + int minutes; + int seconds; + int microseconds; +}; + +struct minmea_sentence_rmc { + struct minmea_time time; + bool valid; + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_float speed; + struct minmea_float course; + struct minmea_date date; + struct minmea_float variation; +}; + +struct minmea_sentence_gga { + struct minmea_time time; + struct minmea_float latitude; + struct minmea_float longitude; + int fix_quality; + int satellites_tracked; + struct minmea_float hdop; + struct minmea_float altitude; char altitude_units; + struct minmea_float height; char height_units; + int dgps_age; +}; + +enum minmea_gll_status { + MINMEA_GLL_STATUS_DATA_VALID = 'A', + MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V', +}; + +// FAA mode added to some fields in NMEA 2.3. +enum minmea_faa_mode { + MINMEA_FAA_MODE_AUTONOMOUS = 'A', + MINMEA_FAA_MODE_DIFFERENTIAL = 'D', + MINMEA_FAA_MODE_ESTIMATED = 'E', + MINMEA_FAA_MODE_MANUAL = 'M', + MINMEA_FAA_MODE_SIMULATED = 'S', + MINMEA_FAA_MODE_NOT_VALID = 'N', + MINMEA_FAA_MODE_PRECISE = 'P', +}; + +struct minmea_sentence_gll { + struct minmea_float latitude; + struct minmea_float longitude; + struct minmea_time time; + char status; + char mode; +}; + +struct minmea_sentence_gst { + struct minmea_time time; + struct minmea_float rms_deviation; + struct minmea_float semi_major_deviation; + struct minmea_float semi_minor_deviation; + struct minmea_float semi_major_orientation; + struct minmea_float latitude_error_deviation; + struct minmea_float longitude_error_deviation; + struct minmea_float altitude_error_deviation; +}; + +enum minmea_gsa_mode { + MINMEA_GPGSA_MODE_AUTO = 'A', + MINMEA_GPGSA_MODE_FORCED = 'M', +}; + +enum minmea_gsa_fix_type { + MINMEA_GPGSA_FIX_NONE = 1, + MINMEA_GPGSA_FIX_2D = 2, + MINMEA_GPGSA_FIX_3D = 3, +}; + +struct minmea_sentence_gsa { + char mode; + int fix_type; + int sats[12]; + struct minmea_float pdop; + struct minmea_float hdop; + struct minmea_float vdop; +}; + +struct minmea_sat_info { + int nr; + int elevation; + int azimuth; + int snr; +}; + +struct minmea_sentence_gsv { + int total_msgs; + int msg_nr; + int total_sats; + struct minmea_sat_info sats[4]; +}; + +struct minmea_sentence_vtg { + struct minmea_float true_track_degrees; + struct minmea_float magnetic_track_degrees; + struct minmea_float speed_knots; + struct minmea_float speed_kph; + enum minmea_faa_mode faa_mode; +}; + +struct minmea_sentence_zda { + struct minmea_time time; + struct minmea_date date; + int hour_offset; + int minute_offset; +}; + +/** + * Calculate raw sentence checksum. Does not check sentence integrity. + */ +uint8_t minmea_checksum(const char *sentence); + +/** + * Check sentence validity and checksum. Returns true for valid sentences. + */ +bool minmea_check(const char *sentence, bool strict); + +/** + * Determine talker identifier. + */ +bool minmea_talker_id(char talker[3], const char *sentence); + +/** + * Determine sentence identifier. + */ +enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict); + +/** + * Scanf-like processor for NMEA sentences. Supports the following formats: + * c - single character (char *) + * d - direction, returned as 1/-1, default 0 (int *) + * f - fractional, returned as value + scale (int *, int *) + * i - decimal, default zero (int *) + * s - string (char *) + * t - talker identifier and type (char *) + * T - date/time stamp (int *, int *, int *) + * Returns true on success. See library source code for details. + */ +bool minmea_scan(const char *sentence, const char *format, ...); + +/* + * Parse a specific type of sentence. Return true on success. + */ +bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence); +bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence); +bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence); +bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence); +bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence); +bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence); +bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence); +bool minmea_parse_zda(struct minmea_sentence_zda *frame, const char *sentence); + +/** + * Convert GPS UTC date/time representation to a UNIX timestamp. + */ +int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_); + +/** + * Rescale a fixed-point value to a different scale. Rounds towards zero. + */ +static inline int_least32_t minmea_rescale(struct minmea_float *f, int_least32_t new_scale) +{ + if (f->scale == 0) + return 0; + if (f->scale == new_scale) + return f->value; + if (f->scale > new_scale) + return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale/new_scale/2) / (f->scale/new_scale); + else + return f->value * (new_scale/f->scale); +} + +/** + * Convert a fixed-point value to a floating-point value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tofloat(struct minmea_float *f) +{ + if (f->scale == 0) + return NAN; + return (float) f->value / (float) f->scale; +} + +/** + * Convert a raw coordinate to a floating point DD.DDD... value. + * Returns NaN for "unknown" values. + */ +static inline float minmea_tocoord(struct minmea_float *f) +{ + if (f->scale == 0) + return NAN; + int_least32_t degrees = f->value / (f->scale * 100); + int_least32_t minutes = f->value % (f->scale * 100); + return (float) degrees + (float) minutes / (60 * f->scale); +} + +#ifdef __cplusplus +} +#endif + +#endif /* MINMEA_H */ + +/* vim: set ts=4 sw=4 et: */ diff --git a/main/ntp_task.c b/main/ntp_task.c new file mode 100644 index 0000000..5860bd1 --- /dev/null +++ b/main/ntp_task.c @@ -0,0 +1,255 @@ +/* + * ntp_task.c + * + * Created on: Jan 21, 2018 + * Author: chris.l + */ + +#include "esp_gps_ntp.h" + +#include +#include +#include + + +static const char *TAG = "NTP"; + +#define NTP_PORT 123 + + +typedef struct ntp_time +{ + uint32_t seconds; + uint32_t fraction; +} NTPTime; + +typedef struct ntp_packet +{ + uint8_t flags; + uint8_t stratum; + uint8_t poll; + int8_t precision; + uint32_t delay; + uint32_t dispersion; + uint8_t ref_id[4]; + NTPTime ref_time; + NTPTime orig_time; + NTPTime recv_time; + NTPTime xmit_time; +} NTPPacket; + +#ifdef NTP_PACKET_DEBUG +void dumpNTPPacket(NTPPacket* ntp) +{ + dbprintf("size: %u\n", sizeof(*ntp)); + dbprintf("firstbyte: 0x%02x\n", *(uint8_t*)ntp); + dbprintf("li: %u\n", getLI(ntp->flags)); + dbprintf("version: %u\n", getVERS(ntp->flags)); + dbprintf("mode: %u\n", getMODE(ntp->flags)); + dbprintf("stratum: %u\n", ntp->stratum); + dbprintf("poll: %u\n", ntp->poll); + dbprintf("precision: %d\n", ntp->precision); + dbprintf("delay: %u\n", ntp->delay); + dbprintf("dispersion: %u\n", ntp->dispersion); + dbprintf("ref_id: %02x:%02x:%02x:%02x\n", ntp->ref_id[0], ntp->ref_id[1], ntp->ref_id[2], ntp->ref_id[3]); + dbprintf("ref_time: %08x:%08x\n", ntp->ref_time.seconds, ntp->ref_time.fraction); + dbprintf("orig_time: %08x:%08x\n", ntp->orig_time.seconds, ntp->orig_time.fraction); + dbprintf("recv_time: %08x:%08x\n", ntp->recv_time.seconds, ntp->recv_time.fraction); + dbprintf("xmit_time: %08x:%08x\n", ntp->xmit_time.seconds, ntp->xmit_time.fraction); +} +#else +#define dumpNTPPacket(x) +#endif + +#define PRECISION_COUNT 10000 + +#define LI_NONE 0 +#define LI_SIXTY_ONE 1 +#define LI_FIFTY_NINE 2 +#define LI_NOSYNC 3 + +#define MODE_RESERVED 0 +#define MODE_ACTIVE 1 +#define MODE_PASSIVE 2 +#define MODE_CLIENT 3 +#define MODE_SERVER 4 +#define MODE_BROADCAST 5 +#define MODE_CONTROL 6 +#define MODE_PRIVATE 7 + +#define NTP_VERSION 4 + +#define REF_ID "PPS " // "GPS " when we have one! + +#define setLI(value) ((value&0x03)<<6) +#define setVERS(value) ((value&0x07)<<3) +#define setMODE(value) ((value&0x07)) + +#define getLI(value) ((value>>6)&0x03) +#define getVERS(value) ((value>>3)&0x07) +#define getMODE(value) (value&0x07) + +#define SEVENTY_YEARS 2208988800L +#define toEPOCH(t) ((uint32_t)t-SEVENTY_YEARS) +#define toNTP(t) ((uint32_t)t+SEVENTY_YEARS) + +static int8_t precision; +static uint32_t req_count; +static uint32_t rsp_count; + +void getNTPTaskStatus(NTPTaskStatus* status) +{ + status->req_count = req_count; + status->rsp_count = rsp_count; +} + +static void getNTPTime(NTPTime *time) +{ + time_t seconds; + time_t usecs; + getTime(&seconds, &usecs); + time->seconds = toNTP(seconds); + + // + // if micros_delta is at or bigger than one second then + // use the max fraction. + // + if (usecs >= 1000000) + { + time->fraction = 0xffffffff; + return; + } + + time->fraction = (uint32_t)(us2s(usecs) * (double)4294967296L); +} + +int8_t computePrecision() +{ + NTPTime t; + unsigned long start = micros(); + for (int i = 0; i < PRECISION_COUNT; ++i) + { + getNTPTime(&t); + } + unsigned long end = micros(); + double total = (double)(end - start) / 1000000.0; + double time = total / PRECISION_COUNT; + double prec = log2(time); + ESP_LOGI(TAG, "computePrecision: total:%f time:%f prec:%f\n", total, time, prec); + return (int8_t)prec; +} + +static void ntp_task() +{ + static NTPPacket ntp; + static int udp_server = -1; + + ESP_LOGI(TAG, "starting!"); + + precision = computePrecision(); + ESP_LOGI(TAG, "precision: %d", precision); + if ((udp_server=socket(AF_INET, SOCK_DGRAM, 0)) == -1) + { + ESP_LOGI(TAG, "could not create socket: %d", errno); + return; + } + + int yes = 1; + if (setsockopt(udp_server,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) + { + ESP_LOGI(TAG, "could not set socket option: %d", errno); + return; + } + + struct sockaddr_in addr; + memset((char *) &addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(NTP_PORT); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + if(bind(udp_server , (struct sockaddr*)&addr, sizeof(addr)) == -1) + { + ESP_LOGI(TAG, "could not bind socket: %d", errno); + return; + } + + while(1) + { + int len; + int slen = sizeof(addr); + if ((len = recvfrom(udp_server, &ntp, sizeof(ntp), 0, (struct sockaddr *) &addr, (socklen_t *)&slen)) == -1) + { + ESP_LOGI(TAG, "could not receive data: %d", errno); + continue; + } + + ++req_count; + + NTPTime recv_time; + getNTPTime(&recv_time); + + if (len != sizeof(NTPPacket)) + { + ESP_LOGI(TAG, "ignoring packet with bad length: %d < %d\n", len, sizeof(ntp)); + continue; + } + + if (!isTimeValid()) + { + ESP_LOGI(TAG, "GPS time data not valid!"); + continue; + } + + ntp.delay = ntohl(ntp.delay); + ntp.dispersion = ntohl(ntp.dispersion); + ntp.orig_time.seconds = ntohl(ntp.orig_time.seconds); + ntp.orig_time.fraction = ntohl(ntp.orig_time.fraction); + ntp.ref_time.seconds = ntohl(ntp.ref_time.seconds); + ntp.ref_time.fraction = ntohl(ntp.ref_time.fraction); + ntp.recv_time.seconds = ntohl(ntp.recv_time.seconds); + ntp.recv_time.fraction = ntohl(ntp.recv_time.fraction); + ntp.xmit_time.seconds = ntohl(ntp.xmit_time.seconds); + ntp.xmit_time.fraction = ntohl(ntp.xmit_time.fraction); + dumpNTPPacket(&ntp); + + // + // Build the response + // + ntp.flags = setLI(LI_NONE) | setVERS(NTP_VERSION) | setMODE(MODE_SERVER); + ntp.stratum = 1; + ntp.precision = precision; + // TODO: compute actual root delay, and root dispersion + ntp.delay = (uint32_t)(0.000001 * 65536); + ntp.dispersion = getDispersion(); + strncpy((char*)ntp.ref_id, REF_ID, sizeof(ntp.ref_id)); + ntp.orig_time = ntp.xmit_time; + ntp.recv_time = recv_time; + getNTPTime(&(ntp.ref_time)); + dumpNTPPacket(&ntp); + ntp.delay = htonl(ntp.delay); + ntp.dispersion = htonl(ntp.dispersion); + ntp.orig_time.seconds = htonl(ntp.orig_time.seconds); + ntp.orig_time.fraction = htonl(ntp.orig_time.fraction); + ntp.ref_time.seconds = htonl(ntp.ref_time.seconds); + ntp.ref_time.fraction = htonl(ntp.ref_time.fraction); + ntp.recv_time.seconds = htonl(ntp.recv_time.seconds); + ntp.recv_time.fraction = htonl(ntp.recv_time.fraction); + getNTPTime(&(ntp.xmit_time)); + ntp.xmit_time.seconds = htonl(ntp.xmit_time.seconds); + ntp.xmit_time.fraction = htonl(ntp.xmit_time.fraction); + + int sent = sendto(udp_server, &ntp, sizeof(ntp), 0, (struct sockaddr*) &addr, sizeof(addr)); + if(sent < 0) + { + ESP_LOGI(TAG, "could not send data: %d", errno); + continue; + } + ++rsp_count; + } +} + +void start_ntp() +{ + esp_log_level_set(TAG, ESP_LOG_INFO); + + xTaskCreatePinnedToCore(ntp_task, "ntp_task", 1024*2, NULL, NTP_TASK_PRI, NULL, 1); +} diff --git a/main/u8g2/u8g2_esp32_hal.c b/main/u8g2/u8g2_esp32_hal.c new file mode 100644 index 0000000..67a0695 --- /dev/null +++ b/main/u8g2/u8g2_esp32_hal.c @@ -0,0 +1,234 @@ +#include +#include + +#include "sdkconfig.h" +#include "esp_log.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "u8g2_esp32_hal.h" + +static const char *TAG = "u8g2_hal"; +static const unsigned int I2C_TIMEOUT_MS = 1000; + +static spi_device_handle_t handle_spi; // SPI handle. +static i2c_cmd_handle_t handle_i2c; // I2C handle. +static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data. + +#undef ESP_ERROR_CHECK +#define ESP_ERROR_CHECK(x) do { esp_err_t rc = (x); if (rc != ESP_OK) { ESP_LOGE("err", "esp_err_t = %d", rc); assert(0 && #x);} } while(0); + +/* + * Initialze the ESP32 HAL. + */ +void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param) { + u8g2_esp32_hal = u8g2_esp32_hal_param; +} // u8g2_esp32_hal_init + +/* + * HAL callback function as prescribed by the U8G2 library. This callback is invoked + * to handle SPI communications. + */ +uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "spi_byte_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); + switch(msg) { + case U8X8_MSG_BYTE_SET_DC: + if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { + gpio_set_level(u8g2_esp32_hal.dc, arg_int); + } + break; + + case U8X8_MSG_BYTE_INIT: { + if (u8g2_esp32_hal.clk == U8G2_ESP32_HAL_UNDEFINED || + u8g2_esp32_hal.mosi == U8G2_ESP32_HAL_UNDEFINED || + u8g2_esp32_hal.cs == U8G2_ESP32_HAL_UNDEFINED) { + break; + } + + spi_bus_config_t bus_config; + bus_config.sclk_io_num = u8g2_esp32_hal.clk; // CLK + bus_config.mosi_io_num = u8g2_esp32_hal.mosi; // MOSI + bus_config.miso_io_num = -1; // MISO + bus_config.quadwp_io_num = -1; // Not used + bus_config.quadhd_io_num = -1; // Not used + //ESP_LOGI(TAG, "... Initializing bus."); + ESP_ERROR_CHECK(spi_bus_initialize(HSPI_HOST, &bus_config, 1)); + + spi_device_interface_config_t dev_config; + dev_config.address_bits = 0; + dev_config.command_bits = 0; + dev_config.dummy_bits = 0; + dev_config.mode = 0; + dev_config.duty_cycle_pos = 0; + dev_config.cs_ena_posttrans = 0; + dev_config.cs_ena_pretrans = 0; + dev_config.clock_speed_hz = 10000; + dev_config.spics_io_num = u8g2_esp32_hal.cs; + dev_config.flags = 0; + dev_config.queue_size = 200; + dev_config.pre_cb = NULL; + dev_config.post_cb = NULL; + //ESP_LOGI(TAG, "... Adding device bus."); + ESP_ERROR_CHECK(spi_bus_add_device(HSPI_HOST, &dev_config, &handle_spi)); + + break; + } + + case U8X8_MSG_BYTE_SEND: { + spi_transaction_t trans_desc; + trans_desc.addr = 0; + trans_desc.cmd = 0; + trans_desc.flags = 0; + trans_desc.length = 8 * arg_int; // Number of bits NOT number of bytes. + trans_desc.rxlength = 0; + trans_desc.tx_buffer = arg_ptr; + trans_desc.rx_buffer = NULL; + + //ESP_LOGI(TAG, "... Transmitting %d bytes.", arg_int); + ESP_ERROR_CHECK(spi_device_transmit(handle_spi, &trans_desc)); + break; + } + } + return 0; +} // u8g2_esp32_spi_byte_cb + +/* + * HAL callback function as prescribed by the U8G2 library. This callback is invoked + * to handle I2C communications. + */ +uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "i2c_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); + + switch(msg) { + case U8X8_MSG_BYTE_SET_DC: { + if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { + gpio_set_level(u8g2_esp32_hal.dc, arg_int); + } + break; + } + + case U8X8_MSG_BYTE_INIT: { + if (u8g2_esp32_hal.sda == U8G2_ESP32_HAL_UNDEFINED || + u8g2_esp32_hal.scl == U8G2_ESP32_HAL_UNDEFINED) { + break; + } + + i2c_config_t conf; + conf.mode = I2C_MODE_MASTER; + ESP_LOGI(TAG, "sda_io_num %d", u8g2_esp32_hal.sda); + conf.sda_io_num = u8g2_esp32_hal.sda; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + ESP_LOGI(TAG, "scl_io_num %d", u8g2_esp32_hal.scl); + conf.scl_io_num = u8g2_esp32_hal.scl; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + ESP_LOGI(TAG, "clk_speed %d", I2C_MASTER_FREQ_HZ); + conf.master.clk_speed = I2C_MASTER_FREQ_HZ; + ESP_LOGI(TAG, "i2c_param_config %d", conf.mode); + ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf)); + ESP_LOGI(TAG, "i2c_driver_install %d", I2C_MASTER_NUM); + ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0)); + break; + } + + case U8X8_MSG_BYTE_SEND: { + uint8_t* data_ptr = (uint8_t*)arg_ptr; + ESP_LOG_BUFFER_HEXDUMP(TAG, data_ptr, arg_int, ESP_LOG_VERBOSE); + + while( arg_int > 0 ) { + ESP_ERROR_CHECK(i2c_master_write_byte(handle_i2c, *data_ptr, ACK_CHECK_EN)); + data_ptr++; + arg_int--; + } + break; + } + + case U8X8_MSG_BYTE_START_TRANSFER: { + uint8_t i2c_address = u8x8_GetI2CAddress(u8x8); + handle_i2c = i2c_cmd_link_create(); + ESP_LOGD(TAG, "Start I2C transfer to %02X.", i2c_address); + ESP_ERROR_CHECK(i2c_master_start(handle_i2c)); + ESP_ERROR_CHECK(i2c_master_write_byte(handle_i2c, i2c_address | I2C_MASTER_WRITE, ACK_CHECK_EN)); + break; + } + + case U8X8_MSG_BYTE_END_TRANSFER: { + ESP_LOGD(TAG, "End I2C transfer."); + ESP_ERROR_CHECK(i2c_master_stop(handle_i2c)); + ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, handle_i2c, I2C_TIMEOUT_MS / portTICK_RATE_MS)); + i2c_cmd_link_delete(handle_i2c); + break; + } + } + return 0; +} // u8g2_esp32_i2c_byte_cb + +/* + * HAL callback function as prescribed by the U8G2 library. This callback is invoked + * to handle callbacks for GPIO and delay functions. + */ +uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "gpio_and_delay_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); + + switch(msg) { + // Initialize the GPIO and DELAY HAL functions. If the pins for DC and RESET have been + // specified then we define those pins as GPIO outputs. + case U8X8_MSG_GPIO_AND_DELAY_INIT: { + uint64_t bitmask = 0; + if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { + bitmask = bitmask | (1<