Initial esp-idf based implentation
This commit is contained in:
parent
aa265b1280
commit
7dfffbc246
57
.cproject
Normal file
57
.cproject
Normal 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
12
.gitignore
vendored
|
|
@ -1,12 +1,6 @@
|
||||||
.metadata
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.ino.cpp
|
|
||||||
.project
|
|
||||||
.cproject
|
|
||||||
.settings
|
|
||||||
Release
|
|
||||||
Debug
|
|
||||||
libraries
|
|
||||||
core
|
|
||||||
*.bak
|
*.bak
|
||||||
*-bak
|
*-bak
|
||||||
|
*.old
|
||||||
|
build
|
||||||
|
sdkconfig
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "components/u8g2"]
|
||||||
|
path = components/u8g2
|
||||||
|
url = git@github.com:liebman/u8g2
|
||||||
27
.project
Normal file
27
.project
Normal 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>
|
||||||
15
.settings/language.settings.xml
Normal file
15
.settings/language.settings.xml
Normal 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 "${INPUTS}"" 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>
|
||||||
12
.settings/org.eclipse.cdt.core.prefs
Normal file
12
.settings/org.eclipse.cdt.core.prefs
Normal 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
|
||||||
552
ESPNTPServer.cpp
552
ESPNTPServer.cpp
|
|
@ -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);
|
|
||||||
}
|
|
||||||
123
ESPNTPServer.h
123
ESPNTPServer.h
|
|
@ -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_ */
|
|
||||||
163
Logger.cpp
163
Logger.cpp
|
|
@ -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;
|
|
||||||
92
Logger.h
92
Logger.h
|
|
@ -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
9
Makefile
Normal 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
|
||||||
|
|
||||||
112
WireUtils.cpp
112
WireUtils.cpp
|
|
@ -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();
|
|
||||||
|
|
||||||
35
WireUtils.h
35
WireUtils.h
|
|
@ -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
1
components/u8g2
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 6bcf57f2ae2d604e4954ea776d1eef1c0f3aed5e
|
||||||
17
main/Kconfig.projbuild
Normal file
17
main/Kconfig.projbuild
Normal 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
8
main/component.mk
Normal 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
133
main/display_task.c
Normal 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
60
main/esp_gps_ntp.h
Normal 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
152
main/esp_gps_ntp_main.c
Normal 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
480
main/gps_task.c
Normal 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
13
main/minmea/COPYING
Normal 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
645
main/minmea/minmea.c
Normal 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
261
main/minmea/minmea.h
Normal 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
255
main/ntp_task.c
Normal 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
234
main/u8g2/u8g2_esp32_hal.c
Normal 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
|
||||||
41
main/u8g2/u8g2_esp32_hal.h
Normal file
41
main/u8g2/u8g2_esp32_hal.h
Normal 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_ */
|
||||||
Loading…
Reference in New Issue
Block a user