switched back to esp8266 and use Serial1 for debug output and Serial for GPS

This commit is contained in:
Christopher Liebman 2018-03-03 06:00:45 -08:00
parent 174888b6a1
commit ac5eaf36fa
30 changed files with 1285 additions and 1396 deletions

View File

@ -1,57 +0,0 @@
<?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,6 +1,12 @@
.metadata
.DS_Store .DS_Store
.ino.cpp
.project
.cproject
.settings
Release
Debug
libraries
core
*.bak *.bak
*-bak *-bak
*.old
build
sdkconfig

9
.gitmodules vendored
View File

@ -1,9 +0,0 @@
[submodule "components/u8g2"]
path = components/u8g2
url = https://github.com/olikraus/u8g2.git
[submodule "components/minmea/minmea"]
path = components/minmea/minmea
url = https://github.com/kosma/minmea.git
[submodule "components/esp32-snippets/esp32-snippets"]
path = components/esp32-snippets/esp32-snippets
url = https://github.com/nkolban/esp32-snippets.git

View File

@ -1,27 +0,0 @@
<?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

@ -1,15 +0,0 @@
<?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

@ -1,12 +0,0 @@
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

170
ESPNTPServer.cpp Normal file
View File

@ -0,0 +1,170 @@
/*
* 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()
#include "GPS.h"
#include "NTP.h"
//#include "Logger.h"
GPS gps(Serial, SYNC_PIN);
NTP ntp(gps);
#if defined(USE_OLED_DISPLAY)
SSD1306Wire display(0x3c, SDA_PIN, SCL_PIN);
#endif
#include "Log.h"
const char* SETUP_TAG = "setup";
const char* LOOP_TAG = "loop";
void logTimeFirst(Print* print)
{
static struct timeval tv;
gps.getTime(&tv);
static struct tm tm;
gmtime_r(&tv.tv_sec, &tm);
print->printf("%04d/%02d/%02d %02d:%02d:%02d.%06ld ",
tm.tm_year+1900,
tm.tm_mon+1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
tv.tv_usec);
}
void setup()
{
delay(5000); // delay for IDE to re-open serial
Serial1.begin(115200);
logger.setSize(256);
logger.setPrint(&Serial1);
logger.setPreFunc(&logTimeFirst);
logger.info(SETUP_TAG, "\nStartup!");
#if !defined(USE_NO_WIFI)
logger.info(SETUP_TAG, "initializing wifi");
WiFiManager wifi;
wifi.setDebugStream(Serial1);
//wifi.setDebugOutput(false);
String ssid = "SynchroClock" + String(ESP.getChipId());
wifi.autoConnect(ssid.c_str(), NULL);
#endif
#if defined(USE_OLED_DISPLAY)
logger.info(SETUP_TAG, "initializing display");
if (!display.init())
{
logger.info(SETUP_TAG, "display.init() failed!");
}
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
#endif
logger.info(SETUP_TAG, "initializing serial for GPS");
Serial.begin(9600);
Serial.swap();
logger.info(SETUP_TAG, "initializing GPS");
gps.begin();
logger.info(SETUP_TAG, "initializing NTP");
ntp.begin();
}
void loop()
{
static uint32_t min_loop;
static uint32_t max_loop;
static uint32_t last_loop;
uint32_t start_loop = millis();
gps.process();
static time_t last_seconds;
struct timeval tv;
gps.getTime(&tv);
if (tv.tv_sec != last_seconds)
{
struct tm tm;
gmtime_r(&tv.tv_sec, &tm);
char ts[64];
snprintf(ts, 63, "%04d/%02d/%02d %02d:%02d:%02d.%06ld",
tm.tm_year+1900,
tm.tm_mon+1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
tv.tv_usec);
if (tv.tv_sec != last_seconds && ((tv.tv_sec % 60) == 0 || gps.getValidDelay()))
{
logger.info("loop", "jitter:%lu valid_count:%lu valid:%s gpsvalid:%s numsat:%d heap:%ld valid_delay:%d",
gps.getJitter(),
gps.getValidCount(),
gps.isValid() ? "true" : "false",
gps.isGPSValid() ? "true" : "false",
gps.getSatelliteCount(),
ESP.getFreeHeap(),
gps.getValidDelay());
}
if (tv.tv_sec < last_seconds)
{
logger.warning(LOOP_TAG, "%s: OOPS: time went backwards: last:%lu now:%lu\n", ts, last_seconds, tv.tv_sec);
}
#if defined(USE_OLED_DISPLAY)
//
// Update the display
//
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_10);
display.drawString(0, 0, ts);
display.drawString(0, 10, "Address: "+WiFi.localIP().toString());
display.drawString(0, 20, "Sat Count: " + String(gps.getSatelliteCount()));
display.drawString(0, 30, "Requests: " + String(ntp.getReqCount()));
display.drawString(0, 40, "Responses: " + String(ntp.getRspCount()));
snprintf(ts, 63, "loop: %d / %d / %d", last_loop, min_loop, max_loop);
display.drawString(0, 50, String(ts));
// write the buffer to the display
display.display();
#endif
}
last_seconds = tv.tv_sec;
last_loop = millis() - start_loop;
if (min_loop == 0 || last_loop < min_loop)
{
min_loop = last_loop;
}
if (max_loop == 0 || last_loop > max_loop)
{
max_loop = last_loop;
}
}

43
ESPNTPServer.h Normal file
View File

@ -0,0 +1,43 @@
/*
* 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_OLED_DISPLAY
//#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 <time.h>
#include "SSD1306Wire.h"
// pin definitions
#define SYNC_PIN 14 // (GPIO14) pin tied to 1hz square wave from GPS
#define SDA_PIN 4
#define SCL_PIN 5
#endif /* _ESPNTPServer_H_ */

289
GPS.cpp Normal file
View File

@ -0,0 +1,289 @@
/*
* GPS.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: Feb 26, 2018
* Author: chris.l
*/
#include <functional>
#include "GPS.h"
#include <time.h>
#include <sys/time.h>
#include <stdarg.h>
#include "Log.h"
static const char* TAG = "GPS";
static std::function<void()> _pps;
static ICACHE_RAM_ATTR void _pps_isr()
{
if (_pps)
{
_pps();
}
}
static ICACHE_RAM_ATTR void _timer_handler(std::function<void()> *func)
{
(*func)();
}
GPS::GPS(Stream& gps_stream, int pps_pin) :
_stream(gps_stream),
_nmea(_buffer, NMEA_BUFFER_SIZE),
_pps_timer(),
_seconds(0),
_valid_delay(0),
_valid_count(0),
_min_micros(0),
_max_micros(0),
_last_micros(0),
_timeouts(0),
_pps_pin(pps_pin),
_gps_valid(false),
_valid(false),
_nmea_late(true)
{
_reason[0] = '\0';
}
GPS::~GPS()
{
end();
}
void GPS::begin()
{
PPS_TIMIMG_PIN_INIT();
_pps = std::bind( &GPS::pps, this);
_invalidate = std::bind( &GPS::timeout, this);
_nmea_timeout = std::bind( &GPS::nmeaTimeout, this);
_pps_timer.attach_ms(VALID_TIMER_MS, _timer_handler, &_invalidate);
pinMode(_pps_pin, INPUT);
attachInterrupt(_pps_pin, _pps_isr, RISING);
}
void GPS::end()
{
_pps_timer.detach();
detachInterrupt(_pps_pin);
_pps = nullptr;
}
void GPS::getTime(struct timeval* tv)
{
uint32_t cur_micros = micros();
tv->tv_sec = _seconds;
tv->tv_usec = (uint32_t)(cur_micros - _last_micros);
//
// if micros_delta is at or bigger than one second then
// use the max just under 1 second.
//
if (tv->tv_usec >= 1000000 || tv->tv_usec < 0)
{
tv->tv_usec = 999999;
}
}
double GPS::getDispersion()
{
return us2s(MAX(abs(MICROS_PER_SEC-_max_micros), abs(MICROS_PER_SEC-_min_micros)));
}
void GPS::process()
{
if (_reason[0] != '\0')
{
logger.warning(TAG, "REASON: %s", _reason);
_reason[0] = '\0';
}
while (_stream.available() > 0)
{
if (_nmea.process(_stream.read()))
{
struct timeval tv;
getTime(&tv);
logger.debug(TAG, "'%s'", _nmea.getSentence());
if (_nmea.isValid() && _nmea.getNumSatellites() >= 4)
{
//
// if it was a GGA and its valid then check and maybe update the time
//
const char * id = _nmea.getMessageID();
if (_nmea.getYear() > 2000 && strcmp("RMC", id) == 0)
{
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);
//
// we only update seconds if the message arrived in the last half of a second,
// if its in the first half then its most likely delayed from the previous second.
time_t old_seconds = _seconds;
if (old_seconds != new_seconds)
{
if (!_nmea_late)
{
_seconds = new_seconds;
logger.info(TAG, "adjusting seconds from %lu to %lu from:'%s'", old_seconds, new_seconds, _nmea.getSentence());
}
else
{
logger.warning(TAG, "ignoring late NMEA time: '%s'",_nmea.getSentence());
}
}
_nmea_late = false;
_nmea_timer.attach_ms(NMEA_TIMER_MS, _timer_handler, &_nmea_timeout);
//
// if gps was not valid, it is now
//
if (!_gps_valid)
{
_valid_delay = VALID_DELAY;
_gps_valid = true;
logger.warning(TAG, "GPS valid!");
}
}
}
else /* nmea not valid or sat count < 4 */
{
if (_gps_valid)
{
invalidate("NMEA:%s SATS:%d from: '%s'",
_nmea.isValid() ? "valid" : "invalid",
_nmea.getNumSatellites(), _nmea.getSentence());
}
}
}
}
}
void GPS::nmeaTimeout()
{
_nmea_late = true;
}
/*
* Mark as not valid
*/
void ICACHE_RAM_ATTR GPS::timeout()
{
if (_valid)
{
invalidate("timeout!");
}
_pps_timer.attach_ms(VALID_TIMER_MS, _timer_handler, &_invalidate);
}
/*
* Mark as not valid
*/
void ICACHE_RAM_ATTR GPS::invalidate(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(_reason, REASON_SIZE-1, fmt, ap);
_reason[REASON_SIZE-1] = '\0';
va_end(ap);
_valid = false;
_gps_valid = false;
_last_micros = 0;
}
/*
* Interrupt handler for a PPS (Pulse Per Second) signal from GPS module.
*/
void ICACHE_RAM_ATTR GPS::pps()
{
PPS_TIMING_PIN_ON();
uint32_t cur_micros = micros();
(void)cur_micros;
//
// don't trust PPS if GPS is not valid.
//
if (!_gps_valid)
{
PPS_TIMING_PIN_OFF();
return;
}
//
// if we are still counting down then keep waiting
//
if (_valid_delay)
{
--_valid_delay;
if (_valid_delay == 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.
//
_pps_timer.attach_ms(VALID_TIMER_MS, &_timer_handler, &_invalidate);
//
// increment seconds
//
_seconds += 1;
//
// the first time around we just initialize the last value
//
if (_last_micros == 0)
{
_last_micros = cur_micros;
PPS_TIMING_PIN_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;
}
PPS_TIMING_PIN_OFF();
}

104
GPS.h Normal file
View File

@ -0,0 +1,104 @@
/*
* GPS.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: Feb 26, 2018
* Author: chris.l
*/
#ifndef GPS_H_
#define GPS_H_
#include "Arduino.h"
#include "MicroNMEA.h"
#include "Ticker.h"
#define REASON_SIZE 128
#define NMEA_BUFFER_SIZE 128
#define PPS_TIMING_PIN 12 // (GPIO12) if defined PPS interrupt will make high during processing
#define VALID_DELAY 120 // delay (seconds) from gps valid to valid
#define VALID_TIMER_MS 1001 // if this timer expires we invalidate!
#define NMEA_TIMER_MS 1100 // if this timer expires we mark NMEA time late
#define MICROS_PER_SEC 1000000
#define us2s(x) (((double)x)/(double)MICROS_PER_SEC) // microseconds to seconds
// 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))
#if defined(PPS_TIMING_PIN)
#define PPS_TIMIMG_PIN_INIT() {digitalWrite(PPS_TIMING_PIN, LOW); pinMode(PPS_TIMING_PIN, OUTPUT);}
#define PPS_TIMING_PIN_ON() digitalWrite(PPS_TIMING_PIN, HIGH)
#define PPS_TIMING_PIN_OFF() digitalWrite(PPS_TIMING_PIN, LOW)
#else
#define PPS_TIMIMG_PIN_INIT()
#define PPS_TIMING_PIN_ON()
#define PPS_TIMING_PIN_OFF()
#endif
class GPS
{
public:
GPS(Stream& gps_serial, int pps_pin);
virtual ~GPS();
void begin();
void process();
void end();
bool isValid() { return _valid; }
bool isGPSValid() { return _gps_valid; }
uint32_t getJitter() { return _max_micros - _min_micros; }
uint32_t getValidCount() { return _valid_count; }
uint32_t getValidDelay() { return _valid_delay; }
uint8_t getSatelliteCount() { return _nmea.getNumSatellites(); }
time_t getSeconds() { return _seconds; }
void getTime(struct timeval* tv);
double getDispersion();
// we don't allow copying this guy!
GPS(const GPS&) = delete;
GPS& operator=(const GPS&) = delete;
private:
Stream& _stream;
char _buffer[NMEA_BUFFER_SIZE];
MicroNMEA _nmea;
Ticker _pps_timer;
Ticker _nmea_timer;
std::function<void()> _invalidate;
std::function<void()> _nmea_timeout;
volatile time_t _seconds;
volatile uint32_t _valid_delay; // delay (seconds) from gps_valid until we thing we are valid
volatile uint32_t _valid_count; // number of times we have gone valid
volatile uint32_t _min_micros;
volatile uint32_t _max_micros;
volatile uint32_t _last_micros;
volatile uint32_t _timeouts;
uint8_t _pps_pin;
bool _gps_valid;
volatile bool _valid;
volatile bool _nmea_late;
char _reason[REASON_SIZE];
void pps(); // interrupt handler
void timeout();
void invalidate(const char* fmt, ...);
void nmeaTimeout();
};
#endif /* GPS_H_ */

160
Log.cpp Normal file
View File

@ -0,0 +1,160 @@
/*
* Log.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: Mar 2, 2018
* Author: chris.l
*/
#include "Arduino.h"
#include "Log.h"
static const char* level_names[LOG_LEVEL_MAX] = {
"NONE",
"ERROR",
"WARN",
"INFO",
"DEBUG",
"TRACE"
};
Log& Log::getLog()
{
static Log log;
return log;
}
Log::Log() :
_level(LOG_LEVEL_INFO),
_size(LOG_MAX_SIZE),
_buffer(nullptr),
_print(nullptr)
{
}
Log::~Log()
{
if (_buffer != nullptr)
{
delete[] _buffer;
}
}
void Log::setSize(size_t size)
{
_size = size;
if (_buffer)
{
delete[] _buffer;
_buffer = nullptr;
}
}
void Log::setLevel(LogLevel level)
{
_level = level;
}
void Log::setLevel(const char* name, LogLevel level)
{
_levels[name] = level;
}
void Log::setPrint(Print *print)
{
_print = print;
}
void Log::setPreFunc(std::function<void(Print* print)> func)
{
pre_func = func;
}
void Log::error(const char* name, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print(name, LOG_LEVEL_ERROR, fmt, ap);
va_end(ap);
}
void Log::warning(const char* name, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print(name, LOG_LEVEL_WARNING, fmt, ap);
va_end(ap);
}
void Log::info(const char* name, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print(name, LOG_LEVEL_INFO, fmt, ap);
va_end(ap);
}
void Log::debug(const char* name, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print(name, LOG_LEVEL_DEBUG, fmt, ap);
va_end(ap);
}
void Log::trace(const char* name, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print(name, LOG_LEVEL_TRACE, fmt, ap);
va_end(ap);
}
void Log::print(const char* name, LogLevel level, const char* fmt, va_list ap)
{
LogLevel limit = _level;
if (_levels.find(name) != _levels.end())
{
limit = _levels[name];
}
if (level > limit)
{
return;
}
//
// allocate the buffer on first use
//
if (_buffer == nullptr)
{
_buffer = new char[_size];
}
if (_print == nullptr)
{
_print = &Serial;
}
vsnprintf(_buffer, _size-1, fmt, ap);
if (pre_func)
{
pre_func(_print);
}
_print->printf("%s %5s %s\n", name, level_names[level], _buffer);
}
Log& logger = Log::getLog();

82
Log.h Normal file
View File

@ -0,0 +1,82 @@
/*
* Log.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: Mar 2, 2018
* Author: chris.l
*/
#ifndef LOG_H_
#define LOG_H_
#include "Print.h"
#include <string>
#include <map>
#include <stdarg.h>
typedef enum log_level
{
LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR,
LOG_LEVEL_WARNING,
LOG_LEVEL_INFO,
LOG_LEVEL_DEBUG,
LOG_LEVEL_TRACE,
LOG_LEVEL_MAX,
} LogLevel;
#define LOG_MAX_SIZE 80
#define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO
class Log
{
public:
static Log& getLog();
void setSize(size_t size);
void setLevel(LogLevel level);
void setLevel(const char* name, LogLevel level);
void setPrint(Print *print);
void setPreFunc(std::function<void(Print* print)> func);
void error(const char* name, const char* fmt, ...);
void warning(const char* name, const char* fmt, ...);
void info(const char* name, const char* fmt, ...);
void debug(const char* name, const char* fmt, ...);
void trace(const char* name, const char* fmt, ...);
// we don't allow copying this guy!
Log(const Log&) = delete;
Log& operator=(const Log&) = delete;
private:
LogLevel _level; // default log level
size_t _size;
char* _buffer;
std::map<std::string, LogLevel> _levels;
Print* _print;
std::function<void(Print* print)> pre_func;
Log();
virtual ~Log();
void print(const char* name, LogLevel level, const char*fmt, va_list ap);
};
extern Log& logger;
#endif /* LOG_H_ */

View File

@ -1,9 +0,0 @@
#
# 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

221
NTP.cpp Normal file
View File

@ -0,0 +1,221 @@
/*
* NTP.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: Feb 27, 2018
* Author: chris.l
*/
#include <functional>
#include <lwip/def.h> // htonl() & ntohl()
#include "NTP.h"
#include "Log.h"
static const char* TAG = "NTP";
//#define NTP_PACKET_DEBUG
#define NTP_PORT 123
#define PRECISION_COUNT 10000
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)
#ifdef NTP_PACKET_DEBUG
#include <time.h>
char* timestr(long int t)
{
t = toEPOCH(t);
return ctime(&t);
}
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
NTP::NTP(GPS& gps) :
_gps(gps),
_udp(),
_req_count(0),
_rsp_count(0),
_precision(0)
{
}
NTP::~NTP()
{
}
void NTP::begin()
{
_precision = computePrecision();
while (!_udp.listen(NTP_PORT))
{
logger.error(TAG, "failed to listen on port %d! Will retry in a bit...", NTP_PORT);
delay(1000);
logger.warning(TAG, "setup: retrying!");
}
using namespace std::placeholders; // for _1, _2, _3...
_udp.onPacket(std::bind( &NTP::ntp, this, _1));
}
int8_t NTP::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);
logger.info(TAG, "computePrecision: total:%f time:%f prec:%f (%d)", total, time, prec, (int8_t)prec);
return (int8_t)prec;
}
void NTP::getNTPTime(NTPTime *time)
{
struct timeval tv;
_gps.getTime(&tv);
time->seconds = toNTP(tv.tv_sec);
double percent = us2s(tv.tv_usec);
time->fraction = (uint32_t)(percent * (double)4294967296L);
}
void NTP::ntp(AsyncUDPPacket& aup)
{
++_req_count;
NTPPacket ntp;
NTPTime recv_time;
getNTPTime(&recv_time);
if (aup.length() != sizeof(NTPPacket))
{
logger.warning(TAG, "recievePacket: ignoring packet with bad length: %d < %d", aup.length(), sizeof(NTPPacket));
return;
}
if (!_gps.isValid())
{
logger.warning(TAG, "recievePacket: GPS data not valid!");
return;
}
memcpy(&ntp, aup.data(), sizeof(ntp));
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 = 1; //(uint32)(0.000001 * 65536.0);
ntp.dispersion = 1; //(uint32_t)(_gps.getDispersion() * 65536.0); // TODO: pre-calculate this?
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);
aup.write((uint8_t*)&ntp, sizeof(ntp));
++_rsp_count;
}

58
NTP.h Normal file
View File

@ -0,0 +1,58 @@
/*
* NTP.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: Feb 27, 2018
* Author: chris.l
*/
#ifndef NTP_H_
#define NTP_H_
#include "Arduino.h"
#include "ESPAsyncUDP.h"
#include "GPS.h"
typedef struct ntp_time
{
uint32_t seconds;
uint32_t fraction;
} NTPTime;
class NTP
{
public:
NTP(GPS& gps);
virtual ~NTP();
void begin();
uint32_t getReqCount() { return _req_count; }
uint32_t getRspCount() { return _rsp_count; }
private:
GPS& _gps;
AsyncUDP _udp;
uint32_t _req_count;
uint32_t _rsp_count;
uint8_t _precision;
void getNTPTime(NTPTime *time);
int8_t computePrecision();
void ntp(AsyncUDPPacket& aup);
};
#endif /* NTP_H_ */

View File

@ -1,8 +1,6 @@
# ESPNTPServer # ESPNTPServer
GPS fueled ESP32 based NTP Server GPS fueled ESP8266 based NTP Server
GPS: Adafruit Untimate GPS GPS: any with standard NMEA sentences & a pulse per second signal..
This project has git submodules so please use --recursive when you clone! If you forget then run: `git submodule update --init`! After a `git pull` you should run `git submodule update --init --recursive` to make sure submodules are up to date.
Work in progress.... Work in progress....

112
WireUtils.cpp Normal file
View File

@ -0,0 +1,112 @@
/*
* 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();

35
WireUtils.h Normal file
View File

@ -0,0 +1,35 @@
/*
* 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_ */

View File

@ -1,3 +0,0 @@
COMPONENT_OBJS := esp32-snippets/hardware/displays/U8G2/u8g2_esp32_hal.o
COMPONENT_SRCDIRS := esp32-snippets/hardware/displays/U8G2
COMPONENT_ADD_INCLUDEDIRS := esp32-snippets/hardware/displays/U8G2

@ -1 +0,0 @@
Subproject commit b1b56c7ef0b3cdfc6cbd1b109b2b24c48c8c52e2

View File

@ -1,4 +0,0 @@
COMPONENT_OBJS := minmea/minmea.o
COMPONENT_SRCDIRS := minmea
COMPONENT_ADD_INCLUDEDIRS := minmea
CPPFLAGS += -Dtimegm=mktime

@ -1 +0,0 @@
Subproject commit cd27e72970cd50b46e9c95c1c0076a49c1ceaf33

@ -1 +0,0 @@
Subproject commit ea7dbc407cd68419322da57cdbdfc00521c86458

View File

@ -1,162 +0,0 @@
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
choice APP_LOG_LEVEL
prompt "APP Log Level"
default APP_LOG_LEVEL_INFO
help
Logging level for APP main
config APP_LOG_LEVEL_NONE
bool "No output"
config APP_LOG_LEVEL_ERROR
bool "Error"
config APP_LOG_LEVEL_WARN
bool "Warning"
config APP_LOG_LEVEL_INFO
bool "Info"
config APP_LOG_LEVEL_DEBUG
bool "Debug"
config APP_LOG_LEVEL_VERBOSE
bool "Verbose"
endchoice
config APP_LOG_LEVEL
int
default 0 if APP_LOG_LEVEL_NONE
default 1 if APP_LOG_LEVEL_ERROR
default 2 if APP_LOG_LEVEL_WARN
default 3 if APP_LOG_LEVEL_INFO
default 4 if APP_LOG_LEVEL_DEBUG
default 5 if APP_LOG_LEVEL_VERBOSE
choice GPS_LOG_LEVEL
prompt "GPS Log Level"
default GPS_LOG_LEVEL_INFO
help
Logging level for GPS task
config GPS_LOG_LEVEL_NONE
bool "No output"
config GPS_LOG_LEVEL_ERROR
bool "Error"
config GPS_LOG_LEVEL_WARN
bool "Warning"
config GPS_LOG_LEVEL_INFO
bool "Info"
config GPS_LOG_LEVEL_DEBUG
bool "Debug"
config GPS_LOG_LEVEL_VERBOSE
bool "Verbose"
endchoice
config GPS_LOG_LEVEL
int
default 0 if GPS_LOG_LEVEL_NONE
default 1 if GPS_LOG_LEVEL_ERROR
default 2 if GPS_LOG_LEVEL_WARN
default 3 if GPS_LOG_LEVEL_INFO
default 4 if GPS_LOG_LEVEL_DEBUG
default 5 if GPS_LOG_LEVEL_VERBOSE
choice NTP_LOG_LEVEL
prompt "NTP Log Level"
default NTP_LOG_LEVEL_INFO
help
Logging level for NTP task
config NTP_LOG_LEVEL_NONE
bool "No output"
config NTP_LOG_LEVEL_ERROR
bool "Error"
config NTP_LOG_LEVEL_WARN
bool "Warning"
config NTP_LOG_LEVEL_INFO
bool "Info"
config NTP_LOG_LEVEL_DEBUG
bool "Debug"
config NTP_LOG_LEVEL_VERBOSE
bool "Verbose"
endchoice
config NTP_LOG_LEVEL
int
default 0 if NTP_LOG_LEVEL_NONE
default 1 if NTP_LOG_LEVEL_ERROR
default 2 if NTP_LOG_LEVEL_WARN
default 3 if NTP_LOG_LEVEL_INFO
default 4 if NTP_LOG_LEVEL_DEBUG
default 5 if NTP_LOG_LEVEL_VERBOSE
choice DISPLAY_LOG_LEVEL
prompt "DISPLAY Log Level"
default DISPLAY_LOG_LEVEL_INFO
help
Logging level for DISPLAY task
config DISPLAY_LOG_LEVEL_NONE
bool "No output"
config DISPLAY_LOG_LEVEL_ERROR
bool "Error"
config DISPLAY_LOG_LEVEL_WARN
bool "Warning"
config DISPLAY_LOG_LEVEL_INFO
bool "Info"
config DISPLAY_LOG_LEVEL_DEBUG
bool "Debug"
config DISPLAY_LOG_LEVEL_VERBOSE
bool "Verbose"
endchoice
config DISPLAY_LOG_LEVEL
int
default 0 if DISPLAY_LOG_LEVEL_NONE
default 1 if DISPLAY_LOG_LEVEL_ERROR
default 2 if DISPLAY_LOG_LEVEL_WARN
default 3 if DISPLAY_LOG_LEVEL_INFO
default 4 if DISPLAY_LOG_LEVEL_DEBUG
default 5 if DISPLAY_LOG_LEVEL_VERBOSE
choice U8G2_HAL_LOG_LEVEL
prompt "U8G2_HAL Log Level"
default U8G2_HAL_LOG_LEVEL_INFO
help
Logging level for U8G2_HAL implementation
config U8G2_HAL_LOG_LEVEL_NONE
bool "No output"
config U8G2_HAL_LOG_LEVEL_ERROR
bool "Error"
config U8G2_HAL_LOG_LEVEL_WARN
bool "Warning"
config U8G2_HAL_LOG_LEVEL_INFO
bool "Info"
config U8G2_HAL_LOG_LEVEL_DEBUG
bool "Debug"
config U8G2_HAL_LOG_LEVEL_VERBOSE
bool "Verbose"
endchoice
config U8G2_HAL_LOG_LEVEL
int
default 0 if U8G2_HAL_LOG_LEVEL_NONE
default 1 if U8G2_HAL_LOG_LEVEL_ERROR
default 2 if U8G2_HAL_LOG_LEVEL_WARN
default 3 if U8G2_HAL_LOG_LEVEL_INFO
default 4 if U8G2_HAL_LOG_LEVEL_DEBUG
default 5 if U8G2_HAL_LOG_LEVEL_VERBOSE
endmenu

View File

@ -1,8 +0,0 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
COMPONENT_ADD_INCLUDEDIRS := .
COMPONENT_SRCDIRS := $(COMPONENT_ADD_INCLUDEDIRS)

View File

@ -1,133 +0,0 @@
/*
* 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, CONFIG_DISPLAY_LOG_LEVEL);
esp_log_level_set("u8g2_hal", CONFIG_U8G2_HAL_LOG_LEVEL);
xTaskCreatePinnedToCore(dsp_task, "dsp_task", 1024*4, NULL, DSP_TASK_PRI, NULL, 1);
}

View File

@ -1,61 +0,0 @@
/*
* esp_gps_ntp.h
*
* Created on: Jan 21, 2018
* Author: chris.l
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE // we have separate config choices for all app parts
#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_ */

View File

@ -1,151 +0,0 @@
/* 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 "esp_gps_ntp.h"
#include <stdio.h>
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_wps.h"
#include "esp_event_loop.h"
#include "nvs_flash.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()
{
esp_log_level_set(TAG, CONFIG_APP_LOG_LEVEL);
ESP_LOGI(TAG, "ESP32 GPS NTP Server");
/* Print chip information */
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
ESP_LOGI(TAG, "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" : "");
ESP_LOGI(TAG, "silicon revision %d, ", chip_info.revision);
ESP_LOGI(TAG, "***** RUNNING on core %d *****", 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();
}

View File

@ -1,480 +0,0 @@
/*
* 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, CONFIG_GPS_LOG_LEVEL);
xTaskCreatePinnedToCore(gps_task, "gps_task", 1024*2, NULL, GPS_TASK_PRI, NULL, 1);
}

View File

@ -1,255 +0,0 @@
/*
* 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, CONFIG_NTP_LOG_LEVEL);
xTaskCreatePinnedToCore(ntp_task, "ntp_task", 1024*2, NULL, NTP_TASK_PRI, NULL, 1);
}