Initial esp-idf based implentation

This commit is contained in:
Christopher Liebman 2018-01-27 07:13:34 -08:00
parent aa265b1280
commit 7dfffbc246
26 changed files with 2426 additions and 1086 deletions

57
.cproject Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="cdt.managedbuild.toolchain.gnu.cross.base.285975342">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cross.base.285975342" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration buildProperties="" id="cdt.managedbuild.toolchain.gnu.cross.base.285975342" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.285975342.940832650" name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.1246611223" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
<option id="cdt.managedbuild.option.gnu.cross.prefix.351169990" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
<option id="cdt.managedbuild.option.gnu.cross.path.109715206" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1180611588" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
<builder id="cdt.managedbuild.builder.gnu.cross.1292138490" managedBuildOn="false" name="Gnu Make Builder.Default" superClass="cdt.managedbuild.builder.gnu.cross"/>
<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.914240970" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1395483982" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1686276063" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.427328797" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
</tool>
<tool id="cdt.managedbuild.tool.gnu.cross.c.linker.1240604479" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.linker.827040114" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.23406645" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
<tool id="cdt.managedbuild.tool.gnu.cross.archiver.568903855" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
<tool id="cdt.managedbuild.tool.gnu.cross.assembler.1777681043" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.835434270" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
</tool>
</toolChain>
</folderInfo>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="esp_gps_ntp.null.219935928" name="esp_gps_ntp"/>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="refreshScope"/>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
</cproject>

12
.gitignore vendored
View File

@ -1,12 +1,6 @@
.metadata
.DS_Store
.ino.cpp
.project
.cproject
.settings
Release
Debug
libraries
core
*.bak
*-bak
*.old
build
sdkconfig

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "components/u8g2"]
path = components/u8g2
url = git@github.com:liebman/u8g2

27
.project Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>esp_gps_ntp</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project>
<configuration id="cdt.managedbuild.toolchain.gnu.cross.base.285975342" name="Default">
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser" keep-relative-paths="false" name="CDT GCC Build Output Parser" parameter="xtensa-esp32-elf-(g?cc)|([gc]\+\+)|(clang)" prefer-non-shared="true"/>
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="432386854730345216" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="xtensa-esp32-elf-gcc ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<language-scope id="org.eclipse.cdt.core.gcc"/>
<language-scope id="org.eclipse.cdt.core.g++"/>
</provider>
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
</extension>
</configuration>
</project>

View File

@ -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

View File

@ -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 <lwip/def.h> // 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);
}

View File

@ -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 <time.h>
#include "SSD1306Wire.h"
#if defined(USE_ASYNC_UDP)
#include "ESPAsyncUDP.h"
#else
#include <WiFiUdp.h>
#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_ */

View File

@ -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;

View File

@ -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 <Arduino.h>
#include <ESP8266WiFi.h>
#define USE_TCP
//#define USE_NETWORK
#ifdef USE_NETWORK
#ifdef USE_TCP
#include <WiFiClient.h>
#else
#include <WiFiUDP.h>
#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_ */

9
Makefile Normal file
View File

@ -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

View File

@ -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();

View File

@ -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 <Arduino.h>
class WireUtilsC {
public:
WireUtilsC();
int clearBus();
};
extern WireUtilsC WireUtils;
#endif /* WIREUTILS_H_ */

1
components/u8g2 Submodule

@ -0,0 +1 @@
Subproject commit 6bcf57f2ae2d604e4954ea776d1eef1c0f3aed5e

17
main/Kconfig.projbuild Normal file
View File

@ -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

8
main/component.mk Normal file
View File

@ -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)

133
main/display_task.c Normal file
View File

@ -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 <time.h>
#include <string.h>
#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);
}

60
main/esp_gps_ntp.h Normal file
View File

@ -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_ */

152
main/esp_gps_ntp_main.c Normal file
View File

@ -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 <stdio.h>
#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();
}

480
main/gps_task.c Normal file
View File

@ -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<<LED_PIN);
//disable pull-down mode
io_conf.pull_down_en = 0;
//disable pull-up mode
io_conf.pull_up_en = 0;
//configure GPIO with the given settings
gpio_config(&io_conf);
DEBUG_LED_OFF();
}
#else
#define DEBUG_LED_ON()
#define DEBUG_LED_OFF()
#define DEBUG_LED_INIT()
#endif
uint32_t getDispersion()
{
return dispersion;
}
bool isTimeValid()
{
return valid;
}
void getTime(time_t *secs, time_t *usecs)
{
*secs = seconds;
*usecs = micros() - last_micros;
}
void getGPSTaskStatus(GPSTaskStatus* status)
{
status->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<<PPS_PIN);
//disable pull-down mode
io_conf.pull_down_en = 0;
//enable pull-up mode
io_conf.pull_up_en = 1;
//configure GPIO with the given settings
gpio_config(&io_conf);
//hook isr handler for specific gpio pin
gpio_isr_handler_add(PPS_PIN, pps_isr_handler, (void*)0);
}
static void init_uart()
{
//
// initialize the uart attached to the GPS module
//
const uart_config_t uart_config = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
uart_param_config(GPS_UART, &uart_config);
uart_set_pin(GPS_UART, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
// We won't use a buffer for sending data.
uart_driver_install(GPS_UART, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
}
static void IRAM_ATTR invalidateTime()
{
// only count timeouts after we have been valid once.
if (initial_valid != 0)
{
++timeouts;
}
xTimerStart(timer, 0);
invalidate("TIMEOUT");
//
// trigger a display update each timeout
//
triggerDisplay();
}
static void init_timer()
{
timer = xTimerCreate("MyTimer", pdMS_TO_TICKS(VALIDITY_TIMER_MS), pdFALSE, NULL, &invalidateTime);
xTimerStart(timer, 0);
}
static void gps_task()
{
static bool last_valid = false;
static bool last_gps_valid = false;
static int timewarps = 0;
DEBUG_LED_INIT();
init_uart();
//install gpio isr service
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
init_pps();
init_timer();
while (1) {
//
// Print valid or invalid if status has changed.
//
if ((last_valid && !valid) || (last_gps_valid && !gps_valid))
{
ESP_LOGW(TAG, "INVALID: '%s'", reason);
reason[0] = '\0';
}
else if (!last_valid && valid)
{
ESP_LOGI(TAG, "VALID!");
}
last_valid = valid;
last_gps_valid = gps_valid;
char* line = readNMEA();
switch (minmea_sentence_id(line, true))
{
case MINMEA_SENTENCE_RMC:
{
struct minmea_sentence_rmc frame;
if (minmea_parse_rmc(&frame, line))
{
ESP_LOGD(TAG, "$RMC: seconds: %lu timeouts: %u valid: %s", seconds, timeouts, frame.valid ? "true" : "false");
gps_status.valid = frame.valid;
if (frame.valid)
{
struct timespec ts;
minmea_gettime(&ts, &frame.date, &frame.time);
if (valid && seconds > 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);
}

13
main/minmea/COPYING Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
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.

645
main/minmea/minmea.c Normal file
View File

@ -0,0 +1,645 @@
/*
* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
* 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 <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <time.h>
#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: */

261
main/minmea/minmea.h Normal file
View File

@ -0,0 +1,261 @@
/*
* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
* 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 <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <time.h>
#include <math.h>
#ifdef MINMEA_INCLUDE_COMPAT
#include <minmea_compat.h>
#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: */

255
main/ntp_task.c Normal file
View File

@ -0,0 +1,255 @@
/*
* ntp_task.c
*
* Created on: Jan 21, 2018
* Author: chris.l
*/
#include "esp_gps_ntp.h"
#include <string.h>
#include <sys/socket.h>
#include <math.h>
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);
}

234
main/u8g2/u8g2_esp32_hal.c Normal file
View File

@ -0,0 +1,234 @@
#include <stdio.h>
#include <string.h>
#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<<u8g2_esp32_hal.dc);
}
if (u8g2_esp32_hal.reset != U8G2_ESP32_HAL_UNDEFINED) {
bitmask = bitmask | (1<<u8g2_esp32_hal.reset);
}
if (u8g2_esp32_hal.cs != U8G2_ESP32_HAL_UNDEFINED) {
bitmask = bitmask | (1<<u8g2_esp32_hal.cs);
}
if (bitmask==0) {
break;
}
gpio_config_t gpioConfig;
gpioConfig.pin_bit_mask = bitmask;
gpioConfig.mode = GPIO_MODE_OUTPUT;
gpioConfig.pull_up_en = GPIO_PULLUP_DISABLE;
gpioConfig.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpioConfig.intr_type = GPIO_INTR_DISABLE;
gpio_config(&gpioConfig);
break;
}
// Set the GPIO reset pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_RESET:
if (u8g2_esp32_hal.reset != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.reset, arg_int);
}
break;
// Set the GPIO client select pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_CS:
if (u8g2_esp32_hal.cs != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.cs, arg_int);
}
break;
// Set the Software I²C pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_I2C_CLOCK:
if (u8g2_esp32_hal.scl != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.scl, arg_int);
// printf("%c",(arg_int==1?'C':'c'));
}
break;
// Set the Software I²C pin to the value passed in through arg_int.
case U8X8_MSG_GPIO_I2C_DATA:
if (u8g2_esp32_hal.sda != U8G2_ESP32_HAL_UNDEFINED) {
gpio_set_level(u8g2_esp32_hal.sda, arg_int);
// printf("%c",(arg_int==1?'D':'d'));
}
break;
// Delay for the number of milliseconds passed in through arg_int.
case U8X8_MSG_DELAY_MILLI:
vTaskDelay(arg_int/portTICK_PERIOD_MS);
break;
}
return 0;
} // u8g2_esp32_gpio_and_delay_cb

View File

@ -0,0 +1,41 @@
/*
* u8g2_esp32_hal.h
*
* Created on: Feb 12, 2017
* Author: kolban
*/
#ifndef U8G2_ESP32_HAL_H_
#define U8G2_ESP32_HAL_H_
#include "u8g2.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/i2c.h"
#define U8G2_ESP32_HAL_UNDEFINED (-1)
#define I2C_MASTER_NUM I2C_NUM_0 // I2C port number for master dev
#define I2C_MASTER_TX_BUF_DISABLE 0 // I2C master do not need buffer
#define I2C_MASTER_RX_BUF_DISABLE 0 // I2C master do not need buffer
#define I2C_MASTER_FREQ_HZ 100000 // I2C master clock frequency
#define ACK_CHECK_EN 0x1 // I2C master will check ack from slave
#define ACK_CHECK_DIS 0x0 // I2C master will not check ack from slave
typedef struct {
gpio_num_t clk;
gpio_num_t mosi;
gpio_num_t sda; // data for I²C
gpio_num_t scl; // clock for I²C
gpio_num_t cs;
gpio_num_t reset;
gpio_num_t dc;
} u8g2_esp32_hal_t ;
#define U8G2_ESP32_HAL_DEFAULT {U8G2_ESP32_HAL_UNDEFINED, U8G2_ESP32_HAL_UNDEFINED, U8G2_ESP32_HAL_UNDEFINED, U8G2_ESP32_HAL_UNDEFINED, U8G2_ESP32_HAL_UNDEFINED, U8G2_ESP32_HAL_UNDEFINED, U8G2_ESP32_HAL_UNDEFINED }
void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param);
uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
#endif /* U8G2_ESP32_HAL_H_ */