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<