Merge pull request #273 from cyberman54/development

v1.7.31
This commit is contained in:
Verkehrsrot 2019-02-17 19:23:36 +01:00 committed by GitHub
commit 4608a3b063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 459 additions and 485 deletions

16
include/clockcontroller.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef _CLOCKCONTROLLER_H
#define _CLOCKCONTROLLER_H
#include "globals.h"
#ifdef HAS_IF482
#include "if482.h"
#elif defined HAS_DCF77
#include "dcf77.h"
#endif
void clock_init(void);
void clock_loop(void *pvParameters);
time_t telegram_time(void);
#endif // _CLOCKCONTROLLER_H

View File

@ -5,15 +5,14 @@
#include "senddata.h" #include "senddata.h"
#include "rcommand.h" #include "rcommand.h"
#include "spislave.h" #include "spislave.h"
#include "rtctime.h"
#include <lmic.h> #include <lmic.h>
#ifdef HAS_BME #ifdef HAS_BME
#include "bme680mems.h" #include "bme680mems.h"
#endif #endif
#ifdef HAS_RTC
#include "rtctime.h"
#endif
void doHousekeeping(void); void doHousekeeping(void);
uint64_t uptime(void); uint64_t uptime(void);

View File

@ -2,16 +2,17 @@
#define _DCF77_H #define _DCF77_H
#include "globals.h" #include "globals.h"
#include "rtctime.h"
#define DCF77_FRAME_SIZE (60)
#define DCF77_PULSE_LENGTH (100)
extern uint8_t DCFpulse[];
enum dcf_pulses { dcf_off, dcf_zero, dcf_one }; enum dcf_pulses { dcf_off, dcf_zero, dcf_one };
enum dcf_pinstate { dcf_low, dcf_high }; enum dcf_pinstate { dcf_low, dcf_high };
int dcf77_init(void); void DCF_Pulse(time_t t);
void dcf77_loop(void *pvParameters); void IRAM_ATTR DCF77_Frame(time_t t);
void sendDCF77(void);
void DCF_Out(uint8_t startsec);
void generateTimeframe(time_t t);
void set_DCF77_pin(dcf_pinstate state); void set_DCF77_pin(dcf_pinstate state);
uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, uint8_t pArray[]); uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, uint8_t pArray[]);

View File

@ -40,7 +40,9 @@
#define SCREEN_MODE (0x80) #define SCREEN_MODE (0x80)
// I2C bus access control // I2C bus access control
#define I2C_MUTEX_LOCK() xSemaphoreTake(I2Caccess, (3 * DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == pdTRUE #define I2C_MUTEX_LOCK() \
xSemaphoreTake(I2Caccess, (3 * DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == \
pdTRUE
#define I2C_MUTEX_UNLOCK() xSemaphoreGive(I2Caccess) #define I2C_MUTEX_UNLOCK() xSemaphoreGive(I2Caccess)
// Struct holding devices's runtime configuration // Struct holding devices's runtime configuration
@ -63,7 +65,8 @@ typedef struct {
uint8_t runmode; // 0=normal, 1=update uint8_t runmode; // 0=normal, 1=update
uint8_t payloadmask; // bitswitches for payload data uint8_t payloadmask; // bitswitches for payload data
char version[10]; // Firmware version char version[10]; // Firmware version
uint8_t bsecstate[BSEC_MAX_STATE_BLOB_SIZE + 1]; // BSEC state for BME680 sensor uint8_t
bsecstate[BSEC_MAX_STATE_BLOB_SIZE + 1]; // BSEC state for BME680 sensor
} configData_t; } configData_t;
// Struct holding payload for data send queue // Struct holding payload for data send queue
@ -94,21 +97,19 @@ typedef struct {
enum sendprio_t { prio_low, prio_normal, prio_high }; enum sendprio_t { prio_low, prio_normal, prio_high };
// global variables extern std::set<uint16_t, std::less<uint16_t>, Mallocator<uint16_t>> macs;
extern std::array<uint64_t, 0xff>::iterator it;
extern std::array<uint64_t, 0xff> beacons;
extern configData_t cfg; // current device configuration extern configData_t cfg; // current device configuration
extern char display_line6[], display_line7[]; // screen buffers extern char display_line6[], display_line7[]; // screen buffers
extern uint8_t volatile channel; // wifi channel rotation counter extern uint8_t volatile channel; // wifi channel rotation counter
extern uint16_t volatile macs_total, macs_wifi, macs_ble, extern uint16_t volatile macs_total, macs_wifi, macs_ble,
batt_voltage; // display values batt_voltage; // display values
extern bool volatile TimePulseTick;
extern hw_timer_t *sendCycle, *displaytimer; extern hw_timer_t *sendCycle, *displaytimer;
extern SemaphoreHandle_t I2Caccess; extern SemaphoreHandle_t I2Caccess, TimePulse;
extern bool volatile BitsPending; extern TaskHandle_t irqHandlerTask, ClockTask;
extern std::set<uint16_t, std::less<uint16_t>, Mallocator<uint16_t>> macs;
extern std::array<uint64_t, 0xff>::iterator it;
extern std::array<uint64_t, 0xff> beacons;
extern TaskHandle_t irqHandlerTask;
extern TimerHandle_t WifiChanTimer; extern TimerHandle_t WifiChanTimer;
extern Timezone myTZ; extern Timezone myTZ;
@ -149,12 +150,4 @@ extern Timezone myTZ;
#include "bme680mems.h" #include "bme680mems.h"
#endif #endif
#ifdef HAS_IF482
#include "if482.h"
#endif
#ifdef HAS_DCF77
#include "dcf77.h"
#endif
#endif #endif

View File

@ -2,11 +2,15 @@
#define _IF482_H #define _IF482_H
#include "globals.h" #include "globals.h"
#include "rtctime.h"
int if482_init(void); #define IF482_FRAME_SIZE (17)
void if482_loop(void *pvParameters); #define IF482_PULSE_LENGTH (1000)
TickType_t tx_time(unsigned long baud, uint32_t config, int8_t rxPin,
extern HardwareSerial IF482;
void IF482_Pulse(time_t t);
String IRAM_ATTR IF482_Frame(time_t tt);
TickType_t tx_Ticks(unsigned long baud, uint32_t config, int8_t rxPin,
int8_t txPins); int8_t txPins);
#endif #endif

View File

@ -10,16 +10,14 @@
#include <SPI.h> #include <SPI.h>
#include <arduino_lmic_hal_boards.h> #include <arduino_lmic_hal_boards.h>
#include "loraconf.h" #include "loraconf.h"
#include "rtctime.h"
// Needed for 24AA02E64, does not hurt anything if included and not used // Needed for 24AA02E64, does not hurt anything if included and not used
#ifdef MCP_24AA02E64_I2C_ADDRESS #ifdef MCP_24AA02E64_I2C_ADDRESS
#include <Wire.h> #include <Wire.h>
#endif #endif
// Needed for RTC time sync if RTC present on board
#ifdef HAS_RTC
#include "rtctime.h"
#endif
extern QueueHandle_t LoraSendQueue; extern QueueHandle_t LoraSendQueue;

View File

@ -17,5 +17,6 @@
#include "led.h" #include "led.h"
#include "spislave.h" #include "spislave.h"
#include "lorawan.h" #include "lorawan.h"
#include "rtctime.h"
#include "clockcontroller.h"
#endif #endif

View File

@ -11,18 +11,17 @@
extern RtcDS3231<TwoWire> Rtc; // make RTC instance globally available extern RtcDS3231<TwoWire> Rtc; // make RTC instance globally available
extern TaskHandle_t ClockTask;
extern hw_timer_t *clockCycle;
int rtc_init(void); int rtc_init(void);
int set_rtctime(uint32_t t); int set_rtctime(uint32_t t);
int set_rtctime(time_t t); int set_rtctime(time_t t);
void sync_rtctime(void); void sync_rtctime(void);
time_t get_rtctime(void); time_t get_rtctime(void);
float get_rtctemp(void); float get_rtctemp(void);
void IRAM_ATTR CLOCKIRQ(); void IRAM_ATTR CLOCKIRQ(void);
int timepulse_init(uint32_t pps_freq); int timepulse_init(void);
void timepulse_start(); void timepulse_start(void);
uint8_t sync_clock(time_t t); int sync_TimePulse(void);
int sync_SysTime(time_t);
int sync_SysTime(uint32_t t);
#endif // _RTCTIME_H #endif // _RTCTIME_H

View File

@ -30,7 +30,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
[common] [common]
; for release_version use max. 10 chars total, use any decimal format like "a.b.c" ; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
release_version = 1.7.2 release_version = 1.7.31
; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running!
; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
debug_level = 0 debug_level = 0
@ -40,10 +40,11 @@ upload_protocol = esptool
extra_scripts = pre:build.py extra_scripts = pre:build.py
keyfile = ota.conf keyfile = ota.conf
platform_espressif32 = espressif32@1.6.0 platform_espressif32 = espressif32@1.6.0
;platform_espressif32 = https://github.com/platformio/platform-espressif32.git#feature/stage
board_build.partitions = min_spiffs.csv board_build.partitions = min_spiffs.csv
monitor_speed = 115200 monitor_speed = 115200
lib_deps_lora = lib_deps_lora =
MCCI LoRaWAN LMIC library@^2.3.1 MCCI LoRaWAN LMIC library@>=2.3.2
lib_deps_display = lib_deps_display =
U8g2@>=2.25.7 U8g2@>=2.25.7
lib_deps_rgbled = lib_deps_rgbled =
@ -66,7 +67,7 @@ lib_deps_all =
build_flags_basic = build_flags_basic =
-include "src/hal/${PIOENV}.h" -include "src/hal/${PIOENV}.h"
-include "src/paxcounter.conf" -include "src/paxcounter.conf"
-w ; -w
'-DARDUINO_LMIC_PROJECT_CONFIG_H=../../../src/lmic_config.h' '-DARDUINO_LMIC_PROJECT_CONFIG_H=../../../src/lmic_config.h'
'-DCORE_DEBUG_LEVEL=${common.debug_level}' '-DCORE_DEBUG_LEVEL=${common.debug_level}'
'-DLOG_LOCAL_LEVEL=${common.debug_level}' '-DLOG_LOCAL_LEVEL=${common.debug_level}'

94
src/clockcontroller.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "clockcontroller.h"
#if defined HAS_IF482 || defined HAS_DCF77
#if defined HAS_DCF77 && defined HAS_IF482
#error You must define at most one of IF482 or DCF77!
#endif
// Local logging tag
static const char TAG[] = "main";
void clock_init(void) {
// setup clock output interface
#ifdef HAS_IF482
IF482.begin(HAS_IF482);
#elif defined HAS_DCF77
pinMode(HAS_DCF77, OUTPUT);
#endif
xTaskCreatePinnedToCore(clock_loop, // task function
"clockloop", // name of task
2048, // stack size of task
(void *)1, // task parameter
4, // priority of the task
&ClockTask, // task handle
0); // CPU core
assert(ClockTask); // has clock task started?
} // clock_init
void clock_loop(void *pvParameters) { // ClockTask
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
TickType_t wakeTime;
time_t t;
#define t1(t) (t + DCF77_FRAME_SIZE + 1) // future time for next frame
// preload first DCF frame before start
#ifdef HAS_DCF77
DCF77_Frame(t1(telegram_time()));
#endif
// output time telegram for second following sec beginning with timepulse
for (;;) {
xTaskNotifyWait(0x00, ULONG_MAX, &wakeTime,
portMAX_DELAY); // wait for timepulse
if (timeStatus() == timeNotSet) // do we have valid time?
continue;
t = telegram_time(); // time to send to clock
#if defined HAS_IF482
IF482_Pulse(t + 1); // next second
#elif defined HAS_DCF77
if (ts == DCF77_FRAME_SIZE - 1) // moment to reload frame?
DCF77_Frame(t1(t)); // generate next frame
if (DCFpulse[DCF77_FRAME_SIZE] ==
minute(t1(t))) // do he have a recent frame?
DCF_Pulse(t + 1); // then output next second of current frame
#endif
} // for
} // clock_loop()
// helper function to fetch current second from most precise time source
time_t telegram_time(void) {
time_t t;
#ifdef HAS_GPS // gps is our primary time source if present
t = get_gpstime();
if (t) // did we get a valid time?
return t;
#endif
#ifdef HAS_RTC // rtc is our secondary time source if present
t = get_rtctime();
if (t)
return t;
#endif
// else we use systime as fallback source
return now();
}
#endif // HAS_IF482 || defined HAS_DCF77

View File

@ -9,7 +9,7 @@ static const char TAG[] = "main";
time_t userUTCTime; // Seconds since the UTC epoch time_t userUTCTime; // Seconds since the UTC epoch
unsigned long nextLoraTimeSync = millis(); unsigned long nextLoraTimeSync = millis();
unsigned long nextRTCTimeSync = millis() + TIME_WRITE_INTERVAL_RTC * 60000; unsigned long nextGPSTimeSync = millis();
// do all housekeeping // do all housekeeping
void doHousekeeping() { void doHousekeeping() {
@ -24,8 +24,26 @@ void doHousekeeping() {
spi_housekeeping(); spi_housekeeping();
lora_housekeeping(); lora_housekeeping();
// do cyclic time sync with LORA network // do cyclic sync of systime with GPS timepulse, if present
#ifdef TIME_SYNC_INTERVAL_LORA #if defined HAS_GPS && defined TIME_SYNC_INTERVAL_GPS
if (millis() >= nextGPSTimeSync) {
nextGPSTimeSync = millis() + TIME_SYNC_INTERVAL_GPS *
60000; // set up next time sync period
// sync systime on next timepulse
if (sync_SysTime(get_gpstime())) {
//setSyncProvider(get_gpstime);
#ifdef HAS_RTC
set_rtctime(now()); // epoch time
#endif
ESP_LOGI(TAG, "GPS has set the system time");
} else
ESP_LOGI(TAG, "Unable to sync system time with GPS");
} // if
#endif
// do cyclic time sync with LORA network, if present
#if defined HAS_LORA && defined TIME_SYNC_INTERVAL_LORA
if (millis() >= nextLoraTimeSync) { if (millis() >= nextLoraTimeSync) {
nextLoraTimeSync = millis() + TIME_SYNC_INTERVAL_LORA * nextLoraTimeSync = millis() + TIME_SYNC_INTERVAL_LORA *
60000; // set up next time sync period 60000; // set up next time sync period
@ -35,19 +53,6 @@ void doHousekeeping() {
} }
#endif #endif
// do cyclic write back system time to RTC if we have an external time source
#if (defined TIME_SYNC_INTERVAL_LORA || defined TIME_SYNC_INTERVAL_GPS) && \
defined HAS_RTC
if ((millis() >= nextRTCTimeSync) && (timeStatus() == timeSet)) {
nextRTCTimeSync = millis() + TIME_WRITE_INTERVAL_RTC *
60000; // set up next time sync period
if (!set_rtctime(now())) // epoch time
ESP_LOGE(TAG, "RTC set time failure");
else
ESP_LOGI(TAG, "RTC time updated");
}
#endif
// task storage debugging // // task storage debugging //
ESP_LOGD(TAG, "IRQhandler %d bytes left | Taskstate = %d", ESP_LOGD(TAG, "IRQhandler %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(irqHandlerTask), uxTaskGetStackHighWaterMark(irqHandlerTask),

View File

@ -1,5 +1,5 @@
/* /*
// Emulate a DCF77 radio receiver // Emulate a DCF77 radio receiver to control an external clock
// //
// a nice & free logic test program for DCF77 can be found here: // a nice & free logic test program for DCF77 can be found here:
https://www-user.tu-chemnitz.de/~heha/viewzip.cgi/hs/Funkuhr.zip/ https://www-user.tu-chemnitz.de/~heha/viewzip.cgi/hs/Funkuhr.zip/
@ -8,85 +8,37 @@ https://www-user.tu-chemnitz.de/~heha/viewzip.cgi/hs/Funkuhr.zip/
#ifdef HAS_DCF77 #ifdef HAS_DCF77
#ifdef IF_482
#error You must define at most one of IF482 or DCF77!
#endif
#include "dcf77.h" #include "dcf77.h"
// Local logging tag // Local logging tag
static const char TAG[] = "main"; static const char TAG[] = "main";
#define DCF77_FRAME_SIZE (60) // array of dcf pulses for one minute
#define DCF77_PULSE_DURATION (100) uint8_t DCFpulse[DCF77_FRAME_SIZE + 1];
// select internal / external clock // triggered by 1 second timepulse to ticker out DCF signal
#if defined RTC_INT && defined RTC_CLK void DCF_Pulse(time_t t) {
#define PPS RTC_CLK
#elif defined GPS_INT && defined GPS_CLK
#define PPS GPS_CLK
#else
#define PPS DCF77_PULSE_DURATION
#endif
// array of dcf pulses for three minutes uint8_t sec = second(t);
uint8_t DCFtimeframe[DCF77_FRAME_SIZE];
// initialize and configure DCF77 output TickType_t startTime = xTaskGetTickCount();
int dcf77_init(void) {
BitsPending = false; ESP_LOGD(TAG, "DCF77 sec %d", sec);
pinMode(HAS_DCF77, OUTPUT); // induce 10 pulses
set_DCF77_pin(dcf_low); for (uint8_t pulse = 0; pulse <= 9; pulse++) {
timepulse_init(PPS); // setup timepulse
xTaskCreatePinnedToCore(dcf77_loop, // task function switch (pulse) {
"dcf77loop", // name of task
2048, // stack size of task
(void *)1, // parameter of the task
3, // priority of the task
&ClockTask, // task handle
0); // CPU core
assert(ClockTask); // has clock task started?
DCF_Out(sync_clock(now())); // sync DCF time on next second
timepulse_start(); // start pulse
return 1; // success
} // ifdcf77_init
// called every 100msec by hardware timer to pulse out DCF signal
void DCF_Out(uint8_t startOffset) {
static uint8_t bit = startOffset;
static uint8_t pulse = 0;
#ifdef TIME_SYNC_INTERVAL_DCF
static uint32_t nextDCFsync = millis() + TIME_SYNC_INTERVAL_DCF * 60000;
#endif
if (!BitsPending) {
// do we have confident time/date?
if ((timeStatus() == timeSet) || (timeStatus() == timeNeedsSync)) {
// prepare frame to send for next minute
generateTimeframe(now() + DCF77_FRAME_SIZE + 1);
// start blinking symbol on display and kick off timer
BitsPending = true;
} else
return;
}
// ticker out current DCF frame
if (BitsPending) {
switch (pulse++) {
case 0: // start of second -> start of timeframe for logic signal case 0: // start of second -> start of timeframe for logic signal
if (DCFtimeframe[bit] != dcf_off) if (DCFpulse[sec] != dcf_off)
set_DCF77_pin(dcf_low); set_DCF77_pin(dcf_low);
else // 59th second reached, nothing more to do
return;
break; break;
case 1: // 100ms after start of second -> end of timeframe for logic 0 case 1: // 100ms after start of second -> end of timeframe for logic 0
if (DCFtimeframe[bit] == dcf_zero) if (DCFpulse[sec] == dcf_zero)
set_DCF77_pin(dcf_high); set_DCF77_pin(dcf_high);
break; break;
@ -94,59 +46,69 @@ void DCF_Out(uint8_t startOffset) {
set_DCF77_pin(dcf_high); set_DCF77_pin(dcf_high);
break; break;
case 9: // 900ms after start -> last pulse before next second starts case 9: // 900ms after start -> last pulse
pulse = 0; return;
if (bit++ == (DCF77_FRAME_SIZE - 1)) // end of DCF77 frame (59th second)
{
bit = 0;
BitsPending = false;
// recalibrate clock after a fixed timespan, do this in 59th second
#ifdef TIME_SYNC_INTERVAL_DCF
if ((millis() >= nextDCFsync)) {
sync_clock(now()); // in second 58,90x -> waiting for second 59
nextDCFsync = millis() + TIME_SYNC_INTERVAL_DCF *
60000; // set up next time sync period
}
#endif
};
break;
}; // switch } // switch
}; // if
} // DCF_Out()
void dcf77_loop(void *pvParameters) { vTaskDelayUntil(&startTime, pdMS_TO_TICKS(DCF77_PULSE_LENGTH));
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
TickType_t wakeTime;
// task remains in blocked state until it is notified by isr
for (;;) {
xTaskNotifyWait(
0x00, // don't clear any bits on entry
ULONG_MAX, // clear all bits on exit
&wakeTime, // receives moment of call from isr
portMAX_DELAY); // wait forever (missing error handling here...)
// select clock scale
#if (PPS == DCF77_PULSE_DURATION) // we don't need clock rescaling
DCF_Out(0);
#elif (PPS > DCF77_PULSE_DURATION) // we need upclocking
for (uint8_t i = 1; i <= PPS / DCF77_PULSE_DURATION; i++) {
DCF_Out(0);
vTaskDelayUntil(&wakeTime, pdMS_TO_TICKS(DCF77_PULSE_DURATION));
}
#elif (PPS < DCF77_PULSE_DURATION) // we need downclocking, not yet implemented
#error Timepulse is too low for DCF77!
#endif
} // for } // for
} // dcf77_loop() } // DCF_Pulse()
void IRAM_ATTR DCF77_Frame(time_t tt) {
uint8_t Parity;
time_t t = myTZ.toLocal(tt); // convert to local time
ESP_LOGD(TAG, "DCF77 minute %d", minute(t));
// ENCODE HEAD
// secs 0..19 initialized with zeros
for (int n = 0; n <= 19; n++)
DCFpulse[n] = dcf_zero;
// secs 17..18: adjust for DayLightSaving
DCFpulse[18 - (myTZ.locIsDST(t) ? 1 : 0)] = dcf_one;
// sec 20: must be 1 to indicate time active
DCFpulse[20] = dcf_one;
// ENCODE MINUTE (secs 21..28)
Parity = dec2bcd(minute(t), 21, 27, DCFpulse);
DCFpulse[28] = (Parity & 1) ? dcf_one : dcf_zero;
// ENCODE HOUR (secs 29..35)
Parity = dec2bcd(hour(t), 29, 34, DCFpulse);
DCFpulse[35] = (Parity & 1) ? dcf_one : dcf_zero;
// ENCODE DATE (secs 36..58)
Parity = dec2bcd(day(t), 36, 41, DCFpulse);
Parity += dec2bcd((weekday(t) - 1) ? (weekday(t) - 1) : 7, 42, 44, DCFpulse);
Parity += dec2bcd(month(t), 45, 49, DCFpulse);
Parity += dec2bcd(year(t) - 2000, 50, 57,
DCFpulse); // yes, we have a millenium 3000 bug here ;-)
DCFpulse[58] = (Parity & 1) ? dcf_one : dcf_zero;
// ENCODE TAIL (sec 59)
DCFpulse[59] = dcf_off;
// !! missing code here for leap second !!
// timestamp the frame with minute pointer
DCFpulse[60] = minute(t);
/*
// for debug: print the DCF77 frame buffer
char out[DCF77_FRAME_SIZE + 1];
uint8_t i;
for (i = 0; i < DCF77_FRAME_SIZE; i++) {
out[i] = DCFpulse[i] + '0'; // convert int digit to printable ascii
}
out[DCF77_FRAME_SIZE] = '\0'; // string termination char
ESP_LOGD(TAG, "DCF minute %d = %s", DCFpulse[DCF77_FRAME_SIZE], out);
*/
}
// helper function to convert decimal to bcd digit // helper function to convert decimal to bcd digit
uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, uint8_t IRAM_ATTR dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos,
uint8_t pArray[]) { uint8_t pArray[]) {
uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10); uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10);
@ -161,54 +123,6 @@ uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos,
return parity; return parity;
} }
void generateTimeframe(time_t tt) {
uint8_t ParityCount;
time_t t = myTZ.toLocal(tt); // convert to local time
// ENCODE HEAD
// bits 0..19 initialized with zeros
for (int n = 0; n <= 19; n++)
DCFtimeframe[n] = dcf_zero;
// bits 17..18: adjust for DayLightSaving
DCFtimeframe[18 - (myTZ.locIsDST(t) ? 1 : 0)] = dcf_one;
// bit 20: must be 1 to indicate time active
DCFtimeframe[20] = dcf_one;
// ENCODE MINUTE (bits 21..28)
ParityCount = dec2bcd(minute(t), 21, 27, DCFtimeframe);
DCFtimeframe[28] = (ParityCount & 1) ? dcf_one : dcf_zero;
// ENCODE HOUR (bits 29..35)
ParityCount = dec2bcd(hour(t), 29, 34, DCFtimeframe);
DCFtimeframe[35] = (ParityCount & 1) ? dcf_one : dcf_zero;
// ENCODE DATE (bits 36..58)
ParityCount = dec2bcd(day(t), 36, 41, DCFtimeframe);
ParityCount +=
dec2bcd((weekday(t) - 1) ? (weekday(t) - 1) : 7, 42, 44, DCFtimeframe);
ParityCount += dec2bcd(month(t), 45, 49, DCFtimeframe);
ParityCount +=
dec2bcd(year(t) - 2000, 50, 57,
DCFtimeframe); // yes, we have a millenium 3000 bug here ;-)
DCFtimeframe[58] = (ParityCount & 1) ? dcf_one : dcf_zero;
// ENCODE TAIL (bit 59)
DCFtimeframe[59] = dcf_off;
// !! missing code here for leap second !!
/*
// for debug: print the DCF77 frame buffer
char out[DCF77_FRAME_SIZE + 1];
uint8_t i;
for (i = 0; i < DCF77_FRAME_SIZE; i++) {
out[i] = DCFtimeframe[i] + '0'; // convert int digit to printable ascii
}
out[DCF77_FRAME_SIZE] = '\0'; // string termination char
ESP_LOGD(TAG, "DCF Timeframe = %s", out);
*/
}
// helper function to switch GPIO line with DCF77 signal // helper function to switch GPIO line with DCF77 signal
void set_DCF77_pin(dcf_pinstate state) { void set_DCF77_pin(dcf_pinstate state) {
switch (state) { switch (state) {

View File

@ -139,7 +139,7 @@ void refreshtheDisplay() {
u8x8.setPowerSave(!cfg.screenon); u8x8.setPowerSave(!cfg.screenon);
} }
// if display is switched off we don't refresh it and save time // if display is switched off we don't refresh it to relax cpu
if (!DisplayState) if (!DisplayState)
return; return;
@ -220,16 +220,13 @@ void refreshtheDisplay() {
#if (!defined HAS_DCF77) && (!defined HAS_IF482) #if (!defined HAS_DCF77) && (!defined HAS_IF482)
// update LoRa status display (line 6) // update LoRa status display (line 6)
u8x8.printf("%-16s", display_line6); u8x8.printf("%-16s", display_line6);
#else // we want a time display instead LoRa status #else // we want a systime display instead LoRa status
// update time/date display (line 6)
time_t t = myTZ.toLocal(now()); time_t t = myTZ.toLocal(now());
char timeState = char timeState =
timeStatus() == timeSet ? timesyncSymbol : timeNosyncSymbol; (timeStatus() == timeSet) ? timesyncSymbol : timeNosyncSymbol;
// make timestatus symbol blinking if pps line char timePulse = TimePulseTick ? '.' : ':';
if ((BitsPending) && (second(t) % 2)) u8x8.printf("%02d:%02d%c%02d%c %2d.%3s", hour(t), minute(t), timePulse,
timeState = ' '; second(t), timeState, day(t), printmonth[month(t)]);
u8x8.printf("%02d:%02d:%02d%c %2d.%3s", hour(t), minute(t), second(t),
timeState, day(t), printmonth[month(t)]);
#endif #endif
// update LMiC event display (line 7) // update LMiC event display (line 7)

View File

@ -88,17 +88,19 @@ time_t tmConvert_t(uint16_t YYYY, uint8_t MM, uint8_t DD, uint8_t hh,
// function to fetch current time from gps // function to fetch current time from gps
time_t get_gpstime(void) { time_t get_gpstime(void) {
// never call now() in this function, this would cause a recursion! // !! never call now() or delay in this function, this would break this
time_t t = 0; // function to be used as SyncProvider for Time.h
if ((gps.time.age() < 1500) && (gps.time.isValid())) { if ((gps.time.age() < 1500) && (gps.time.isValid())) {
t = tmConvert_t(gps.date.year(), gps.date.month(), gps.date.day(), // get current gps time
time_t t =
tmConvert_t(gps.date.year(), gps.date.month(), gps.date.day(),
gps.time.hour(), gps.time.minute(), gps.time.second()); gps.time.hour(), gps.time.minute(), gps.time.second());
ESP_LOGD(TAG, "GPS time: %4d/%02d/%02d %02d:%02d:%02d", year(t), month(t), return t;
day(t), hour(t), minute(t), second(t));
} else { } else {
ESP_LOGW(TAG, "GPS has no confident time"); ESP_LOGW(TAG, "GPS has no confident time");
return 0; // sync failure, 0 effects calling SyncProvider() to not set time
} }
return t;
} // get_gpstime() } // get_gpstime()
// GPS serial feed FreeRTos Task // GPS serial feed FreeRTos Task

View File

@ -43,8 +43,7 @@
// GPS settings // GPS settings
#define HAS_GPS 1 // use on board GPS #define HAS_GPS 1 // use on board GPS
#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_12, GPIO_NUM_15 // UBlox NEO 6M #define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_12, GPIO_NUM_15 // UBlox NEO 6M
#define GPS_INT GPIO_NUM_34 // 30ns accurary timepulse, to be external wired on pcb: NEO 6M Pin#3 -> GPIO34 #define GPS_INT GPIO_NUM_13 // 30ns accurary timepulse, to be external wired on pcb: NEO 6M Pin#3 -> GPIO13
#define GPS_CLK (1000) // pulse length 100ms, accuracy +/- 3 *e-8 [nanoseconds] = 0,95sec / year
// Pins for I2C interface of OLED Display // Pins for I2C interface of OLED Display
#define MY_OLED_SDA (4) #define MY_OLED_SDA (4)
@ -54,7 +53,6 @@
// Settings for on board DS3231 RTC chip // Settings for on board DS3231 RTC chip
#define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL #define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL
#define RTC_INT GPIO_NUM_34 // timepulse with accuracy +/- 2*e-6 [microseconds] = 0,1728sec / day #define RTC_INT GPIO_NUM_34 // timepulse with accuracy +/- 2*e-6 [microseconds] = 0,1728sec / day
#define RTC_CLK (1000) // pulse length 1000ms
// Settings for IF482 interface // Settings for IF482 interface
//#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters //#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters

View File

@ -8,6 +8,7 @@
// Hardware related definitions for Pycom LoPy4 Board // Hardware related definitions for Pycom LoPy4 Board
#define HAS_LORA 1 // comment out if device shall not send data via LoRa #define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define LORA_RST LMIC_UNUSED_PIN // reset pin of lora chip is not wired von LoPy4
//#defin HAS_SPI 1 // comment out if device shall not send data via SPI //#defin HAS_SPI 1 // comment out if device shall not send data via SPI
// pin definitions for local wired SPI slave interface // pin definitions for local wired SPI slave interface

View File

@ -21,10 +21,9 @@
#define BATT_FACTOR 2 // voltage divider 100k/100k on board #define BATT_FACTOR 2 // voltage divider 100k/100k on board
// GPS settings // GPS settings
#define HAS_GPS 1 // use on board GPS //#define HAS_GPS 1 // use on board GPS
#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_12, GPIO_NUM_15 // UBlox NEO 6M //#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_12, GPIO_NUM_15 // UBlox NEO 6M
//#define GPS_INT GPIO_NUM_34 // 30ns accurary timepulse, to be external wired on pcb: NEO 6M Pin#3 -> GPIO34 //#define GPS_INT GPIO_NUM_34 // 30ns accurary timepulse, to be external wired on pcb: NEO 6M Pin#3 -> GPIO34
//#define GPS_CLK (1000) // pulse length 100ms, accuracy +/- 3 *e-8 [nanoseconds] = 0,95sec / year
// enable only if device has these sensors, otherwise comment these lines // enable only if device has these sensors, otherwise comment these lines
// BME680 sensor on I2C bus // BME680 sensor on I2C bus

View File

@ -22,8 +22,7 @@
// Settings for on board DS3231 RTC chip // Settings for on board DS3231 RTC chip
#define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL #define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL
//#define RTC_INT GPIO_NUM_34 // timepulse with accuracy +/- 2*e-6 [microseconds] = 0,1728sec / day #define RTC_INT GPIO_NUM_34 // timepulse with accuracy +/- 2*e-6 [microseconds] = 0,1728sec / day
//#define RTC_CLK (1000) // pulse length 1000ms
// Settings for IF482 interface // Settings for IF482 interface
//#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters //#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters

View File

@ -79,55 +79,25 @@ not evaluated by model BU-190
#ifdef HAS_IF482 #ifdef HAS_IF482
#ifdef HAS_DCF77
#error You must define at most one of IF482 or DCF77!
#endif
#include "if482.h" #include "if482.h"
// Local logging tag // Local logging tag
static const char TAG[] = "main"; static const char TAG[] = "main";
#define IF482_FRAME_SIZE (17)
#define IF482_PULSE_DURATION (1000)
// select internal / external clock
#if defined RTC_INT && defined RTC_CLK
#define PPS RTC_CLK
#elif defined GPS_INT && defined GPS_CLK
#define PPS GPS_CLK
#else
#define PPS IF482_PULSE_DURATION
#endif
HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS) HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS)
// initialize and configure IF482 Generator // triggered by timepulse to ticker out DCF signal
int if482_init(void) { void IF482_Pulse(time_t t) {
// open serial interface TickType_t startTime = xTaskGetTickCount();
IF482.begin(HAS_IF482); static const TickType_t txDelay = pdMS_TO_TICKS(IF482_PULSE_LENGTH) - tx_Ticks(HAS_IF482);
// setup timepulse vTaskDelayUntil(&startTime, txDelay);
timepulse_init(PPS); IF482.print(IF482_Frame(t+1)); // note: if482 telegram for *next* second
}
// start if482 serial output feed task String IRAM_ATTR IF482_Frame(time_t startTime) {
xTaskCreatePinnedToCore(if482_loop, // task function
"if482loop", // name of task
2048, // stack size of task
(void *)1, // parameter of the task
3, // priority of the task
&ClockTask, // task handle
0); // CPU core
assert(ClockTask); // has clock task started? time_t t = myTZ.toLocal(startTime);
timepulse_start(); // start pulse
return 1; // success
} // if482_init
String IF482_Out(time_t tt) {
time_t t = myTZ.toLocal(tt);
char mon, buf[14], out[IF482_FRAME_SIZE]; char mon, buf[14], out[IF482_FRAME_SIZE];
switch (timeStatus()) { // indicates if time has been set and recently synced switch (timeStatus()) { // indicates if time has been set and recently synced
@ -137,74 +107,30 @@ String IF482_Out(time_t tt) {
case timeNeedsSync: // time had been set but sync attempt did not succeed case timeNeedsSync: // time had been set but sync attempt did not succeed
mon = 'M'; mon = 'M';
break; break;
default: // time not set, no valid time default: // unknown time status (should never be reached)
mon = '?'; mon = '?';
break; break;
} // switch } // switch
// do we have confident time/date? // generate IF482 telegram
if ((timeStatus() == timeSet) || (timeStatus() == timeNeedsSync))
snprintf(buf, sizeof(buf), "%02u%02u%02u%1u%02u%02u%02u", year(t) - 2000, snprintf(buf, sizeof(buf), "%02u%02u%02u%1u%02u%02u%02u", year(t) - 2000,
month(t), day(t), weekday(t), hour(t), minute(t), second(t)); month(t), day(t), weekday(t), hour(t), minute(t), second(t));
else
snprintf(buf, sizeof(buf), "000000F000000"); // no confident time/date
// output IF482 telegram
snprintf(out, sizeof(out), "O%cL%s\r", mon, buf); snprintf(out, sizeof(out), "O%cL%s\r", mon, buf);
ESP_LOGD(TAG, "IF482 = %s", out); ESP_LOGD(TAG, "IF482 = %s", out);
return out; return out;
} }
void if482_loop(void *pvParameters) { // calculate serial tx time from IF482 serial settings
TickType_t tx_Ticks(unsigned long baud, uint32_t config, int8_t rxPin,
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
TickType_t wakeTime;
const TickType_t timeOffset =
tx_time(HAS_IF482); // duration of telegram transmit
const TickType_t startTime = xTaskGetTickCount(); // now
sync_clock(now()); // wait until begin of a new second
BitsPending = true; // start blink in display
// take timestamp at moment of start of new second
const TickType_t shotTime = xTaskGetTickCount() - startTime - timeOffset;
// task remains in blocked state until it is notified by isr
for (;;) {
xTaskNotifyWait(
0x00, // don't clear any bits on entry
ULONG_MAX, // clear all bits on exit
&wakeTime, // receives moment of call from isr
portMAX_DELAY); // wait forever (missing error handling here...)
// select clock scale
#if (PPS == IF482_PULSE_DURATION) // we don't need clock rescaling
// wait until it's time to start transmit telegram for next second
vTaskDelayUntil(&wakeTime, shotTime); // sets waketime to moment of shot
IF482.print(IF482_Out(now() + 1));
#elif (PPS > IF482_PULSE_DURATION) // we need upclocking
for (uint8_t i = 1; i <= PPS / IF482_PULSE_DURATION; i++) {
vTaskDelayUntil(&wakeTime, shotTime); // sets waketime to moment of shot
IF482.print(IF482_Out(now() + 1));
}
#elif (PPS < IF482_PULSE_DURATION) // we need downclocking, not yet implemented
#error Timepulse is too low for IF482!
#endif
}
} // if482_loop()
// helper function to calculate IF482 telegram serial tx time from serial
// settings
TickType_t tx_time(unsigned long baud, uint32_t config, int8_t rxPin,
int8_t txPins) { int8_t txPins) {
uint32_t datenbits = ((config & 0x0c) >> 2) + 5; uint32_t datenbits = ((config & 0x0c) >> 2) + 5;
uint32_t startbits = ((config & 0x20) >> 5) + 1; uint32_t stopbits = ((config & 0x20) >> 5) + 1;
return pdMS_TO_TICKS( uint32_t tx_delay =
round(((datenbits + startbits + 1) * IF482_FRAME_SIZE * 1000.0 / baud))); (2 + datenbits + stopbits) * IF482_FRAME_SIZE * 1000.0 / baud;
// +2 ms margin for the startbit and the clock's processing time
return pdMS_TO_TICKS(round(tx_delay));
} }
#endif // HAS_IF482 #endif // HAS_IF482

View File

@ -16,7 +16,7 @@ void irqHandler(void *pvParameters) {
0x00, // Don't clear any bits on entry 0x00, // Don't clear any bits on entry
ULONG_MAX, // Clear all bits on exit ULONG_MAX, // Clear all bits on exit
&InterruptStatus, // Receives the notification value &InterruptStatus, // Receives the notification value
portMAX_DELAY); // wait forever (missing error handling here...) portMAX_DELAY); // wait forever
// button pressed? // button pressed?
#ifdef HAS_BUTTON #ifdef HAS_BUTTON

View File

@ -34,6 +34,7 @@
// faster or slower. This causes the transceiver to be earlier switched on, // faster or slower. This causes the transceiver to be earlier switched on,
// so consuming more power. You may sharpen (reduce) this value if you are // so consuming more power. You may sharpen (reduce) this value if you are
// limited on battery. // limited on battery.
// ATTN: VALUES > 7 WILL CAUSE RECEPTION AND JOIN PROBLEMS WITH HIGH SF RATES
#define CLOCK_ERROR_PROCENTAGE 3 #define CLOCK_ERROR_PROCENTAGE 3
// Set this to 1 to enable some basic debug output (using printf) about // Set this to 1 to enable some basic debug output (using printf) about

View File

@ -6,6 +6,10 @@ static const char TAG[] = "lora";
#ifdef HAS_LORA #ifdef HAS_LORA
#if CLOCK_ERROR_PROCENTAGE > 7
#warning CLOCK_ERROR_PROCENTAGE value in lmic_config.h is too high; values > 7 will cause side effects
#endif
osjob_t sendjob; osjob_t sendjob;
QueueHandle_t LoraSendQueue; QueueHandle_t LoraSendQueue;
@ -395,7 +399,7 @@ esp_err_t lora_stack_init() {
} }
return ESP_OK; // continue main program return ESP_OK; // continue main program
#endif #endif // HAS_LORA
} }
void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) { void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
@ -435,6 +439,7 @@ void lora_housekeeping(void) {
void user_request_network_time_callback(void *pVoidUserUTCTime, void user_request_network_time_callback(void *pVoidUserUTCTime,
int flagSuccess) { int flagSuccess) {
#ifdef HAS_LORA
// Explicit conversion from void* to uint32_t* to avoid compiler errors // Explicit conversion from void* to uint32_t* to avoid compiler errors
uint32_t *pUserUTCTime = (uint32_t *)pVoidUserUTCTime; uint32_t *pUserUTCTime = (uint32_t *)pVoidUserUTCTime;
lmic_time_reference_t lmicTimeReference; lmic_time_reference_t lmicTimeReference;
@ -464,10 +469,14 @@ void user_request_network_time_callback(void *pVoidUserUTCTime,
*pUserUTCTime += requestDelaySec; *pUserUTCTime += requestDelaySec;
// Update system time with time read from the network // Update system time with time read from the network
setTime(*pUserUTCTime); if (sync_TimePulse()) { // wait for start of next second
ESP_LOGI(TAG, "LoRaWAN network has set the system time"); if (sync_SysTime(*pUserUTCTime)) { // do we have a valid time?
#ifdef HAS_RTC #ifdef HAS_RTC
if (!set_rtctime(*pUserUTCTime)) // epoch time set_rtctime(now()); // epoch time
ESP_LOGE(TAG, "RTC set time failure");
#endif #endif
ESP_LOGI(TAG, "LORA has set the system time");
} }
} else
ESP_LOGI(TAG, "Unable to sync system time with LORA");
#endif // HAS_LORA
} // user_request_network_time_callback

View File

@ -27,9 +27,8 @@ Uused tasks and timers:
Task Core Prio Purpose Task Core Prio Purpose
==================================================================================== ====================================================================================
clockloop 0 4 generates realtime telegrams for external clock
ledloop 0 3 blinks LEDs ledloop 0 3 blinks LEDs
if482loop 0 3 generates serial feed of IF482 time telegrams
dcf77loop 0 3 generates DCF77 timeframe pulses
spiloop 0 2 reads/writes data on spi interface spiloop 0 2 reads/writes data on spi interface
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
@ -65,15 +64,15 @@ char display_line6[16], display_line7[16]; // display buffers
uint8_t volatile channel = 0; // channel rotation counter uint8_t volatile channel = 0; // channel rotation counter
uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0, uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0,
batt_voltage = 0; // globals for display batt_voltage = 0; // globals for display
bool volatile BitsPending = false; // DCF77 or IF482 ticker indicator
hw_timer_t *sendCycle = NULL, *homeCycle = NULL; hw_timer_t *sendCycle = NULL, *homeCycle = NULL;
#ifdef HAS_DISPLAY #ifdef HAS_DISPLAY
hw_timer_t *displaytimer = NULL; hw_timer_t *displaytimer = NULL;
#endif #endif
TaskHandle_t irqHandlerTask; TaskHandle_t irqHandlerTask, ClockTask;
SemaphoreHandle_t I2Caccess; SemaphoreHandle_t I2Caccess, TimePulse;
bool volatile TimePulseTick = false;
// container holding unique MAC address hashes with Memory Alloctor using PSRAM, // container holding unique MAC address hashes with Memory Alloctor using PSRAM,
// if present // if present
@ -97,9 +96,12 @@ void setup() {
char features[100] = ""; char features[100] = "";
// create some semaphores for syncing / mutexing tasks
I2Caccess = xSemaphoreCreateMutex(); // for access management of i2c bus I2Caccess = xSemaphoreCreateMutex(); // for access management of i2c bus
if (I2Caccess) if (I2Caccess)
xSemaphoreGive((I2Caccess)); // Flag the i2c bus available for use xSemaphoreGive(I2Caccess); // Flag the i2c bus available for use
TimePulse = xSemaphoreCreateBinary(); // as signal that shows time pulse flip
// disable brownout detection // disable brownout detection
#ifdef DISABLE_BROWNOUT #ifdef DISABLE_BROWNOUT
@ -141,10 +143,21 @@ void setup() {
ESP.getChipRevision(), ESP.getCpuFreqMHz(), ESP.getSdkVersion()); ESP.getChipRevision(), ESP.getCpuFreqMHz(), ESP.getSdkVersion());
ESP_LOGI(TAG, "Flash Size %d, Flash Speed %d", ESP.getFlashChipSize(), ESP_LOGI(TAG, "Flash Size %d, Flash Speed %d", ESP.getFlashChipSize(),
ESP.getFlashChipSpeed()); ESP.getFlashChipSpeed());
ESP_LOGI(TAG, "Wifi/BT software coexist version: %s", esp_coex_version_get()); ESP_LOGI(TAG, "Wifi/BT software coexist version %s", esp_coex_version_get());
#ifdef HAS_LORA
ESP_LOGI(TAG, "IBM LMIC version %d.%d.%d", LMIC_VERSION_MAJOR,
LMIC_VERSION_MINOR, LMIC_VERSION_BUILD);
ESP_LOGI(TAG, "Arduino LMIC version %d.%d.%d.%d",
ARDUINO_LMIC_VERSION_GET_MAJOR(ARDUINO_LMIC_VERSION),
ARDUINO_LMIC_VERSION_GET_MINOR(ARDUINO_LMIC_VERSION),
ARDUINO_LMIC_VERSION_GET_PATCH(ARDUINO_LMIC_VERSION),
ARDUINO_LMIC_VERSION_GET_LOCAL(ARDUINO_LMIC_VERSION));
#endif
#ifdef HAS_GPS #ifdef HAS_GPS
ESP_LOGI(TAG, "TinyGPS+ v%s", TinyGPSPlus::libraryVersion()); ESP_LOGI(TAG, "TinyGPS+ version %s", TinyGPSPlus::libraryVersion());
#endif #endif
#endif // verbose #endif // verbose
@ -325,19 +338,13 @@ void setup() {
#ifdef HAS_RTC #ifdef HAS_RTC
strcat_P(features, " RTC"); strcat_P(features, " RTC");
assert(rtc_init()); assert(rtc_init());
setSyncProvider(&get_rtctime); #endif
if (timeStatus() != timeSet)
ESP_LOGI(TAG, "Unable to sync system time with RTC");
else
ESP_LOGI(TAG, "RTC has set the system time");
setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60);
#endif // HAS_RTC
#if defined HAS_DCF77 #if defined HAS_DCF77
strcat_P(features, " DCF77"); strcat_P(features, " DCF77");
#endif #endif
#if (defined HAS_IF482) && (defined RTC_INT) #if defined HAS_IF482
strcat_P(features, " IF482"); strcat_P(features, " IF482");
#endif #endif
@ -351,6 +358,13 @@ void setup() {
#endif #endif
#endif #endif
// start pps timepulse
ESP_LOGI(TAG, "Starting timepulse...");
if (timepulse_init()) // setup timepulse
timepulse_start(); // start pulse
else
ESP_LOGE(TAG, "No timepulse, systime will not be synced!");
// start wifi in monitor mode and start channel rotation timer // start wifi in monitor mode and start channel rotation timer
ESP_LOGI(TAG, "Starting Wifi..."); ESP_LOGI(TAG, "Starting Wifi...");
wifi_sniffer_init(); wifi_sniffer_init();
@ -403,31 +417,38 @@ void setup() {
#endif // HAS_BUTTON #endif // HAS_BUTTON
#ifdef HAS_GPS #ifdef HAS_GPS
setSyncProvider(&get_gpstime); // sync systime on next timepulse
if (timeStatus() != timeSet) ESP_LOGI(TAG, "GPS is setting system time");
ESP_LOGI(TAG, "Unable to sync system time with GPS"); if (sync_SysTime(get_gpstime())) {
else { //setSyncProvider(get_gpstime); // reset sync cycle on top of second
ESP_LOGI(TAG, "GPS has set the system time"); //setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60);
// calibrate RTC
#ifdef HAS_RTC #ifdef HAS_RTC
if (!set_rtctime(now())) // epoch time set_rtctime(now()); // epoch time
ESP_LOGE(TAG, "RTC set time failure");
#endif
}
setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60);
#endif #endif
} else
ESP_LOGI(TAG, "Unable to sync system time with GPS");
#endif // HAS_GPS
#ifdef HAS_IF482 // initialize systime from timesource
ESP_LOGI(TAG, "Starting IF482 Generator..."); #ifdef HAS_RTC
assert(if482_init()); // sync systime on next timepulse
#elif defined HAS_DCF77 ESP_LOGI(TAG, "RTC is setting system time");
ESP_LOGI(TAG, "Starting DCF77 Generator..."); if (sync_SysTime(get_rtctime())) {
assert(dcf77_init()); //setSyncProvider(get_rtctime); // reset sync cycle on top of second
//setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60);
} else
ESP_LOGI(TAG, "Unable to sync system time with RTC");
#endif // HAS_RTC
#if defined HAS_IF482 || defined HAS_DCF77
ESP_LOGI(TAG, "Starting Clock Controller...");
clock_init();
#endif #endif
} // setup() } // setup()
void loop() { void loop() {
while (1) { while (1) {
#ifdef HAS_LORA #ifdef HAS_LORA
os_runloop_once(); // execute lmic scheduled jobs and events os_runloop_once(); // execute lmic scheduled jobs and events

View File

@ -82,11 +82,9 @@
#define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds] #define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds]
// settings for syncing time of node and external time sources // settings for syncing time of node and external time sources
#define TIME_SYNC_INTERVAL_GPS 5 // sync time each .. minutes from source GPS [default = 5], comment out means off #define TIME_SYNC_INTERVAL_GPS 5 // sync time each .. minutes from GPS [default = 5], comment out means off
#define TIME_SYNC_INTERVAL_RTC 60 // sync time each .. minutes from RTC [default = 60], comment out means off #define TIME_SYNC_INTERVAL_RTC 60 // sync time each .. minutes from RTC [default = 60], comment out means off
#define TIME_WRITE_INTERVAL_RTC 60 // write time each .. minutes from GPS/LORA to RTC [default = 60], comment out means off
//#define TIME_SYNC_INTERVAL_LORA 60 // sync time each .. minutes from LORA network [default = 60], comment out means off //#define TIME_SYNC_INTERVAL_LORA 60 // sync time each .. minutes from LORA network [default = 60], comment out means off
#define TIME_SYNC_INTERVAL_DCF 60 // sync DCF signal time each .. minutes from internal time [default = 60], comment out means off
// time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino // time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino
#define DAYLIGHT_TIME {"CEST", Last, Sun, Mar, 2, 120} // Central European Summer Time #define DAYLIGHT_TIME {"CEST", Last, Sun, Mar, 2, 120} // Central European Summer Time

View File

@ -3,9 +3,96 @@
// Local logging tag // Local logging tag
static const char TAG[] = "main"; static const char TAG[] = "main";
TaskHandle_t ClockTask;
hw_timer_t *clockCycle = NULL; hw_timer_t *clockCycle = NULL;
// helper function to setup a pulse per second for time synchronisation
int timepulse_init() {
// use time pulse from GPS as time base with fixed 1Hz frequency
#ifdef GPS_INT
// setup external interupt for active low RTC INT pin
pinMode(GPS_INT, INPUT_PULLDOWN);
// setup external rtc 1Hz clock as pulse per second clock
ESP_LOGI(TAG, "Time base: external (GPS)");
return 1; // success
// use pulse from on board RTC chip as time base with fixed frequency
#elif defined RTC_INT
// setup external interupt for active low RTC INT pin
pinMode(RTC_INT, INPUT_PULLUP);
// setup external rtc 1Hz clock as pulse per second clock
if (I2C_MUTEX_LOCK()) {
Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock);
I2C_MUTEX_UNLOCK();
ESP_LOGI(TAG, "Time base: external (RTC)");
return 1; // success
} else {
ESP_LOGE(TAG, "I2c bus busy - RTC initialization error");
return 0; // failure
}
return 1; // success
#else
// use ESP32 hardware timer as time base with adjustable frequency
clockCycle = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
timerAlarmWrite(clockCycle, 10000, true); // 1000ms
ESP_LOGI(TAG, "Time base: internal (ESP32 hardware timer)");
return 1; // success
#endif
} // timepulse_init
void timepulse_start(void) {
#ifdef GPS_INT // start external clock gps pps line
attachInterrupt(digitalPinToInterrupt(GPS_INT), CLOCKIRQ, RISING);
#elif defined RTC_INT // start external clock rtc
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
#else // start internal clock esp32 hardware timer
timerAttachInterrupt(clockCycle, &CLOCKIRQ, true);
timerAlarmEnable(clockCycle);
#endif
}
// interrupt service routine triggered by either pps or esp32 hardware timer
void IRAM_ATTR CLOCKIRQ(void) {
if (ClockTask != NULL)
xTaskNotifyFromISR(ClockTask, xTaskGetTickCountFromISR(), eSetBits, NULL);
#if defined GPS_INT || defined RTC_INT
xSemaphoreGiveFromISR(TimePulse, NULL);
TimePulseTick = !TimePulseTick; // flip ticker
#endif
portYIELD_FROM_ISR();
}
// helper function to sync systime on start of next second
int sync_SysTime(time_t t) {
if (sync_TimePulse() && (t)) { // wait for start of next second by timepulse
setTime(t + 1);
ESP_LOGD(TAG, "Systime synced on second");
return 1; // success
} else
return 0; // failure
}
int sync_SysTime(uint32_t t) { // t is epoch seconds starting 1.1.1970
return sync_SysTime(static_cast<time_t>(t));
}
// helper function to sync moment on timepulse
int sync_TimePulse(void) {
// sync on top of next second by timepulse
if (xSemaphoreTake(TimePulse, pdMS_TO_TICKS(1100)) == pdTRUE) {
return 1;
} // success
else
ESP_LOGW(TAG, "Missing timepulse, time not synced");
return 0; // failure
}
#ifdef HAS_RTC // we have hardware RTC #ifdef HAS_RTC // we have hardware RTC
RtcDS3231<TwoWire> Rtc(Wire); // RTC hardware i2c interface RtcDS3231<TwoWire> Rtc(Wire); // RTC hardware i2c interface
@ -60,12 +147,14 @@ error:
} // rtc_init() } // rtc_init()
int set_rtctime(time_t t) { // t is epoch time starting 1.1.1970 int set_rtctime(time_t t) { // t is seconds epoch time starting 1.1.1970
if (I2C_MUTEX_LOCK()) { if (I2C_MUTEX_LOCK()) {
Rtc.SetDateTime(RtcDateTime(t)); Rtc.SetDateTime(RtcDateTime(t));
I2C_MUTEX_UNLOCK(); // release i2c bus access I2C_MUTEX_UNLOCK(); // release i2c bus access
ESP_LOGI(TAG, "RTC calibrated");
return 1; // success return 1; // success
} }
ESP_LOGE(TAG, "RTC set time failure");
return 0; // failure return 0; // failure
} // set_rtctime() } // set_rtctime()
@ -75,7 +164,8 @@ int set_rtctime(uint32_t t) { // t is epoch seconds starting 1.1.1970
} }
time_t get_rtctime(void) { time_t get_rtctime(void) {
// never call now() in this function, this would cause a recursion! // !! never call now() or delay in this function, this would break this
// function to be used as SyncProvider for Time.h
time_t t = 0; time_t t = 0;
// block i2c bus access // block i2c bus access
if (I2C_MUTEX_LOCK()) { if (I2C_MUTEX_LOCK()) {
@ -101,95 +191,3 @@ float get_rtctemp(void) {
} // get_rtctemp() } // get_rtctemp()
#endif // HAS_RTC #endif // HAS_RTC
// helper function to setup a pulse for time synchronisation
int timepulse_init(uint32_t pulse_period_ms) {
// use time pulse from GPS as time base with fixed 1Hz frequency
#if defined GPS_INT && defined GPS_CLK
// setup external interupt for active low RTC INT pin
pinMode(GPS_INT, INPUT_PULLDOWN);
// setup external rtc 1Hz clock as pulse per second clock
ESP_LOGI(TAG, "Time base: GPS timepulse");
switch (GPS_CLK) {
case 1000:
break; // default GPS timepulse 1000ms
default:
goto pulse_period_error;
}
return 1; // success
// use pulse from on board RTC chip as time base with fixed frequency
#elif defined RTC_INT && defined RTC_CLK
// setup external interupt for active low RTC INT pin
pinMode(RTC_INT, INPUT_PULLUP);
// setup external rtc 1Hz clock as pulse per second clock
ESP_LOGI(TAG, "Time base: external RTC timepulse");
if (I2C_MUTEX_LOCK()) {
switch (RTC_CLK) {
case 1000: // 1000ms
Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz);
break;
case 1: // 1ms
Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1kHz);
break;
default:
I2C_MUTEX_UNLOCK();
goto pulse_period_error;
}
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock);
I2C_MUTEX_UNLOCK();
} else {
ESP_LOGE(TAG, "I2c bus busy - RTC initialization error");
return 0; // failure
}
return 1; // success
#else
// use ESP32 hardware timer as time base with adjustable frequency
if (pulse_period_ms) {
ESP_LOGI(TAG, "Time base: ESP32 hardware timer");
clockCycle =
timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
timerAttachInterrupt(clockCycle, &CLOCKIRQ, true);
timerAlarmWrite(clockCycle, 10 * pulse_period_ms, true); // ms
} else
goto pulse_period_error;
return 1; // success
#endif
pulse_period_error:
ESP_LOGE(TAG, "Unknown timepulse period value");
return 0; // failure
}
void timepulse_start() {
#ifdef GPS_INT // start external clock
attachInterrupt(digitalPinToInterrupt(GPS_INT), CLOCKIRQ, RISING);
#elif defined RTC_INT // start external clock
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
#else // start internal clock
timerAlarmEnable(clockCycle);
#endif
}
// helper function to sync phase of DCF output signal to start of second t
uint8_t sync_clock(time_t t) {
time_t tt = t;
// delay until start of next second
do {
tt = now();
} while (t == tt);
ESP_LOGI(TAG, "Sync on Sec %d", second(tt));
return second(tt);
}
// interrupt service routine triggered by either rtc pps or esp32 hardware
// timer
void IRAM_ATTR CLOCKIRQ() {
xTaskNotifyFromISR(ClockTask, xTaskGetTickCountFromISR(), eSetBits, NULL);
portYIELD_FROM_ISR();
}