v1.7.31 (clockcontroller deeply reworked)
This commit is contained in:
parent
c8978a0745
commit
05a3f3b2a8
@ -11,5 +11,6 @@
|
||||
|
||||
void clock_init(void);
|
||||
void clock_loop(void *pvParameters);
|
||||
time_t telegram_time(void);
|
||||
|
||||
#endif // _CLOCKCONTROLLER_H
|
@ -5,15 +5,14 @@
|
||||
#include "senddata.h"
|
||||
#include "rcommand.h"
|
||||
#include "spislave.h"
|
||||
#include "rtctime.h"
|
||||
#include <lmic.h>
|
||||
|
||||
#ifdef HAS_BME
|
||||
#include "bme680mems.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAS_RTC
|
||||
#include "rtctime.h"
|
||||
#endif
|
||||
|
||||
|
||||
void doHousekeeping(void);
|
||||
uint64_t uptime(void);
|
||||
|
@ -3,11 +3,16 @@
|
||||
|
||||
#include "globals.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_pinstate { dcf_low, dcf_high };
|
||||
|
||||
void DCF_Pulse(time_t startTime);
|
||||
void DCF77_Frame(time_t t);
|
||||
void DCF_Pulse(time_t t);
|
||||
void IRAM_ATTR DCF77_Frame(time_t t);
|
||||
void set_DCF77_pin(dcf_pinstate state);
|
||||
uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, uint8_t pArray[]);
|
||||
|
||||
|
@ -45,13 +45,6 @@
|
||||
pdTRUE
|
||||
#define I2C_MUTEX_UNLOCK() xSemaphoreGive(I2Caccess)
|
||||
|
||||
// Clock controller settings
|
||||
#define PPS (1000) // on board time pulse frequency in ms
|
||||
#define IF482_FRAME_SIZE (17)
|
||||
#define IF482_PULSE_LENGTH (1000)
|
||||
#define DCF77_FRAME_SIZE (60)
|
||||
#define DCF77_PULSE_LENGTH (100)
|
||||
|
||||
// Struct holding devices's runtime configuration
|
||||
typedef struct {
|
||||
uint8_t lorasf; // 7-12, lora spreadfactor
|
||||
@ -104,20 +97,19 @@ typedef struct {
|
||||
|
||||
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 char display_line6[], display_line7[]; // screen buffers
|
||||
extern uint8_t volatile channel; // wifi channel rotation counter
|
||||
extern uint16_t volatile macs_total, macs_wifi, macs_ble,
|
||||
batt_voltage; // display values
|
||||
extern bool volatile TimePulseTick;
|
||||
extern hw_timer_t *sendCycle, *displaytimer;
|
||||
extern SemaphoreHandle_t I2Caccess, TimePulse;
|
||||
|
||||
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 TaskHandle_t irqHandlerTask, ClockTask;
|
||||
extern TimerHandle_t WifiChanTimer;
|
||||
extern Timezone myTZ;
|
||||
|
||||
|
@ -3,10 +3,13 @@
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
#define IF482_FRAME_SIZE (17)
|
||||
#define IF482_PULSE_LENGTH (1000)
|
||||
|
||||
extern HardwareSerial IF482;
|
||||
|
||||
void IF482_Pulse(time_t t);
|
||||
String IF482_Frame(time_t tt);
|
||||
String IRAM_ATTR IF482_Frame(time_t tt);
|
||||
TickType_t tx_Ticks(unsigned long baud, uint32_t config, int8_t rxPin,
|
||||
int8_t txPins);
|
||||
|
||||
|
@ -10,16 +10,14 @@
|
||||
#include <SPI.h>
|
||||
#include <arduino_lmic_hal_boards.h>
|
||||
#include "loraconf.h"
|
||||
#include "rtctime.h"
|
||||
|
||||
// Needed for 24AA02E64, does not hurt anything if included and not used
|
||||
#ifdef MCP_24AA02E64_I2C_ADDRESS
|
||||
#include <Wire.h>
|
||||
#endif
|
||||
|
||||
// Needed for RTC time sync if RTC present on board
|
||||
#ifdef HAS_RTC
|
||||
#include "rtctime.h"
|
||||
#endif
|
||||
|
||||
|
||||
extern QueueHandle_t LoraSendQueue;
|
||||
|
||||
|
@ -17,5 +17,6 @@
|
||||
#include "led.h"
|
||||
#include "spislave.h"
|
||||
#include "lorawan.h"
|
||||
#include "rtctime.h"
|
||||
#include "clockcontroller.h"
|
||||
#endif
|
@ -11,20 +11,17 @@
|
||||
|
||||
extern RtcDS3231<TwoWire> Rtc; // make RTC instance globally available
|
||||
|
||||
extern TaskHandle_t ClockTask;
|
||||
extern hw_timer_t *clockCycle;
|
||||
extern bool volatile TimePulseTick;
|
||||
|
||||
int rtc_init(void);
|
||||
int set_rtctime(uint32_t t);
|
||||
int set_rtctime(time_t t);
|
||||
void sync_rtctime(void);
|
||||
time_t get_rtctime(void);
|
||||
float get_rtctemp(void);
|
||||
void IRAM_ATTR CLOCKIRQ();
|
||||
void IRAM_ATTR CLOCKIRQ(void);
|
||||
int timepulse_init(void);
|
||||
void timepulse_start();
|
||||
void timepulse_start(void);
|
||||
int sync_TimePulse(void);
|
||||
int sync_SysTime(time_t);
|
||||
int sync_SysTime(uint32_t t);
|
||||
|
||||
#endif // _RTCTIME_H
|
@ -30,7 +30,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
|
||||
|
||||
[common]
|
||||
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
|
||||
release_version = 1.7.24
|
||||
release_version = 1.7.31
|
||||
; 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
|
||||
debug_level = 0
|
||||
@ -67,7 +67,7 @@ lib_deps_all =
|
||||
build_flags_basic =
|
||||
-include "src/hal/${PIOENV}.h"
|
||||
-include "src/paxcounter.conf"
|
||||
-w
|
||||
; -w
|
||||
'-DARDUINO_LMIC_PROJECT_CONFIG_H=../../../src/lmic_config.h'
|
||||
'-DCORE_DEBUG_LEVEL=${common.debug_level}'
|
||||
'-DLOG_LOCAL_LEVEL=${common.debug_level}'
|
||||
|
@ -6,25 +6,16 @@
|
||||
#error You must define at most one of IF482 or DCF77!
|
||||
#endif
|
||||
|
||||
#if (PPS < IF482_PULSE_LENGTH) || (PPS < DCF77_PULSE_LENGTH)
|
||||
#error On board timepulse too fast for clockcontroller
|
||||
#endif
|
||||
|
||||
// Local logging tag
|
||||
static const char TAG[] = "main";
|
||||
|
||||
void clock_init(void) { // ClockTask
|
||||
void clock_init(void) {
|
||||
|
||||
timepulse_init(); // setup timepulse
|
||||
|
||||
// setup output interface
|
||||
// setup clock output interface
|
||||
#ifdef HAS_IF482
|
||||
// initialize and configure IF482 Generator
|
||||
IF482.begin(HAS_IF482);
|
||||
#elif defined HAS_DCF77
|
||||
// initialize and configure DCF77 output
|
||||
pinMode(HAS_DCF77, OUTPUT);
|
||||
set_DCF77_pin(dcf_low);
|
||||
#endif
|
||||
|
||||
xTaskCreatePinnedToCore(clock_loop, // task function
|
||||
@ -36,37 +27,68 @@ void clock_init(void) { // ClockTask
|
||||
0); // CPU core
|
||||
|
||||
assert(ClockTask); // has clock task started?
|
||||
timepulse_start(); // start pulse
|
||||
} // clock_init
|
||||
|
||||
void clock_loop(void *pvParameters) { // ClockTask
|
||||
|
||||
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
|
||||
|
||||
TickType_t wakeTime, txDelay;
|
||||
uint32_t pulseCycle;
|
||||
void (*pTimeTx)(time_t); // pointer to time telegram output function
|
||||
TickType_t wakeTime;
|
||||
time_t t;
|
||||
|
||||
#ifdef HAS_IF482
|
||||
txDelay = pdMS_TO_TICKS(1000) - tx_Ticks(HAS_IF482);
|
||||
pulseCycle = PPS / IF482_PULSE_LENGTH;
|
||||
pTimeTx = IF482_Pulse;
|
||||
#elif defined HAS_DCF77
|
||||
txDelay = pdMS_TO_TICKS(DCF77_PULSE_LENGTH);
|
||||
pulseCycle = PPS / DCF77_PULSE_LENGTH;
|
||||
pTimeTx = DCF_Pulse;
|
||||
#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 triggered by timepulse
|
||||
// output time telegram for second following sec beginning with timepulse
|
||||
for (;;) {
|
||||
if (timeStatus() == timeSet) // do we have valid time?
|
||||
xTaskNotifyWait(0x00, ULONG_MAX, &wakeTime,
|
||||
portMAX_DELAY); // wait for timepulse
|
||||
for (uint8_t i = 1; i <= pulseCycle; i++) {
|
||||
pTimeTx(now());
|
||||
vTaskDelayUntil(&wakeTime, txDelay);
|
||||
}
|
||||
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()
|
||||
|
||||
#endif // HAS_DCF77 || HAS_IF482
|
||||
// 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
|
@ -9,7 +9,7 @@ static const char TAG[] = "main";
|
||||
|
||||
time_t userUTCTime; // Seconds since the UTC epoch
|
||||
unsigned long nextLoraTimeSync = millis();
|
||||
unsigned long nextRTCTimeSync = millis() + TIME_WRITE_INTERVAL_RTC * 60000;
|
||||
unsigned long nextGPSTimeSync = millis();
|
||||
|
||||
// do all housekeeping
|
||||
void doHousekeeping() {
|
||||
@ -24,8 +24,26 @@ void doHousekeeping() {
|
||||
spi_housekeeping();
|
||||
lora_housekeeping();
|
||||
|
||||
// do cyclic time sync with LORA network
|
||||
#ifdef TIME_SYNC_INTERVAL_LORA
|
||||
// do cyclic sync of systime with GPS timepulse, if present
|
||||
#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) {
|
||||
nextLoraTimeSync = millis() + TIME_SYNC_INTERVAL_LORA *
|
||||
60000; // set up next time sync period
|
||||
@ -35,20 +53,6 @@ void doHousekeeping() {
|
||||
}
|
||||
#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
|
||||
sync_TimePulse(); // wait for next start of second
|
||||
if (!set_rtctime(now())) // epoch time
|
||||
ESP_LOGE(TAG, "RTC set time failure");
|
||||
else
|
||||
ESP_LOGI(TAG, "RTC time updated");
|
||||
}
|
||||
#endif
|
||||
|
||||
// task storage debugging //
|
||||
ESP_LOGD(TAG, "IRQhandler %d bytes left | Taskstate = %d",
|
||||
uxTaskGetStackHighWaterMark(irqHandlerTask),
|
||||
|
157
src/dcf77.cpp
157
src/dcf77.cpp
@ -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:
|
||||
https://www-user.tu-chemnitz.de/~heha/viewzip.cgi/hs/Funkuhr.zip/
|
||||
@ -13,36 +13,32 @@ https://www-user.tu-chemnitz.de/~heha/viewzip.cgi/hs/Funkuhr.zip/
|
||||
// Local logging tag
|
||||
static const char TAG[] = "main";
|
||||
|
||||
// array of dcf pulses for three minutes
|
||||
uint8_t DCFpulse[DCF77_FRAME_SIZE];
|
||||
// array of dcf pulses for one minute
|
||||
uint8_t DCFpulse[DCF77_FRAME_SIZE + 1];
|
||||
|
||||
// called by timepulse interrupt to ticker out DCF signal
|
||||
void DCF_Pulse(time_t startTime) {
|
||||
// triggered by 1 second timepulse to ticker out DCF signal
|
||||
void DCF_Pulse(time_t t) {
|
||||
|
||||
static uint8_t current_second = second(startTime);
|
||||
static uint8_t pulse = 0;
|
||||
static bool SecondsPending = false;
|
||||
uint8_t sec = second(t);
|
||||
|
||||
if (!SecondsPending) {
|
||||
// prepare dcf timeframe to send for next minute
|
||||
DCF77_Frame(now() + DCF77_FRAME_SIZE + 1);
|
||||
ESP_LOGD(TAG, "DCF77 minute %d", minute(now() + DCF77_FRAME_SIZE + 1));
|
||||
// begin output of dcf timeframe
|
||||
SecondsPending = true;
|
||||
}
|
||||
TickType_t startTime = xTaskGetTickCount();
|
||||
|
||||
// ticker out current DCF frame
|
||||
if (SecondsPending) {
|
||||
switch (pulse++) {
|
||||
ESP_LOGD(TAG, "DCF77 sec %d", sec);
|
||||
|
||||
// induce 10 pulses
|
||||
for (uint8_t pulse = 0; pulse <= 9; pulse++) {
|
||||
|
||||
switch (pulse) {
|
||||
|
||||
case 0: // start of second -> start of timeframe for logic signal
|
||||
if (DCFpulse[current_second] != dcf_off)
|
||||
if (DCFpulse[sec] != dcf_off)
|
||||
set_DCF77_pin(dcf_low);
|
||||
ESP_LOGD(TAG, "DCF77 bit %d", current_second);
|
||||
else // 59th second reached, nothing more to do
|
||||
return;
|
||||
break;
|
||||
|
||||
case 1: // 100ms after start of second -> end of timeframe for logic 0
|
||||
if (DCFpulse[current_second] == dcf_zero)
|
||||
if (DCFpulse[sec] == dcf_zero)
|
||||
set_DCF77_pin(dcf_high);
|
||||
break;
|
||||
|
||||
@ -50,22 +46,69 @@ void DCF_Pulse(time_t startTime) {
|
||||
set_DCF77_pin(dcf_high);
|
||||
break;
|
||||
|
||||
case 9: // 900ms after start -> last pulse before next second starts
|
||||
pulse = 0;
|
||||
if (current_second++ >=
|
||||
(DCF77_FRAME_SIZE - 1)) // end of DCF77 frame (59th second)
|
||||
{
|
||||
current_second = 0;
|
||||
SecondsPending = false;
|
||||
};
|
||||
break;
|
||||
case 9: // 900ms after start -> last pulse
|
||||
return;
|
||||
|
||||
}; // switch
|
||||
}; // if
|
||||
} // switch
|
||||
|
||||
vTaskDelayUntil(&startTime, pdMS_TO_TICKS(DCF77_PULSE_LENGTH));
|
||||
|
||||
} // for
|
||||
} // 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
|
||||
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 data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10);
|
||||
@ -80,52 +123,6 @@ uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos,
|
||||
return parity;
|
||||
}
|
||||
|
||||
void DCF77_Frame(time_t tt) {
|
||||
|
||||
uint8_t Parity;
|
||||
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++)
|
||||
DCFpulse[n] = dcf_zero;
|
||||
// bits 17..18: adjust for DayLightSaving
|
||||
DCFpulse[18 - (myTZ.locIsDST(t) ? 1 : 0)] = dcf_one;
|
||||
// bit 20: must be 1 to indicate time active
|
||||
DCFpulse[20] = dcf_one;
|
||||
|
||||
// ENCODE MINUTE (bits 21..28)
|
||||
Parity = dec2bcd(minute(t), 21, 27, DCFpulse);
|
||||
DCFpulse[28] = (Parity & 1) ? dcf_one : dcf_zero;
|
||||
|
||||
// ENCODE HOUR (bits 29..35)
|
||||
Parity = dec2bcd(hour(t), 29, 34, DCFpulse);
|
||||
DCFpulse[35] = (Parity & 1) ? dcf_one : dcf_zero;
|
||||
|
||||
// ENCODE DATE (bits 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 (bit 59)
|
||||
DCFpulse[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] = DCFpulse[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
|
||||
void set_DCF77_pin(dcf_pinstate state) {
|
||||
switch (state) {
|
||||
@ -146,6 +143,4 @@ void set_DCF77_pin(dcf_pinstate state) {
|
||||
} // switch
|
||||
} // DCF77_pulse
|
||||
|
||||
// helper function calculates next minute
|
||||
|
||||
#endif // HAS_DCF77
|
@ -96,8 +96,6 @@ time_t get_gpstime(void) {
|
||||
time_t t =
|
||||
tmConvert_t(gps.date.year(), gps.date.month(), gps.date.day(),
|
||||
gps.time.hour(), gps.time.minute(), gps.time.second());
|
||||
ESP_LOGD(TAG, "GPS time: %4d/%02d/%02d %02d:%02d:%02d", year(t), month(t),
|
||||
day(t), hour(t), minute(t), second(t));
|
||||
return t;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "GPS has no confident time");
|
||||
|
@ -86,12 +86,16 @@ static const char TAG[] = "main";
|
||||
|
||||
HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS)
|
||||
|
||||
// called by timepulse interrupt to ticker out DCF signal
|
||||
void IF482_Pulse(time_t startTime) {
|
||||
IF482.print(IF482_Frame(startTime + 1)); // if482 telegram for next second
|
||||
// triggered by timepulse to ticker out DCF signal
|
||||
void IF482_Pulse(time_t t) {
|
||||
|
||||
TickType_t startTime = xTaskGetTickCount();
|
||||
static const TickType_t txDelay = pdMS_TO_TICKS(IF482_PULSE_LENGTH) - tx_Ticks(HAS_IF482);
|
||||
vTaskDelayUntil(&startTime, txDelay);
|
||||
IF482.print(IF482_Frame(t+1)); // note: if482 telegram for *next* second
|
||||
}
|
||||
|
||||
String IF482_Frame(time_t startTime) {
|
||||
String IRAM_ATTR IF482_Frame(time_t startTime) {
|
||||
|
||||
time_t t = myTZ.toLocal(startTime);
|
||||
char mon, buf[14], out[IF482_FRAME_SIZE];
|
||||
@ -103,19 +107,14 @@ String IF482_Frame(time_t startTime) {
|
||||
case timeNeedsSync: // time had been set but sync attempt did not succeed
|
||||
mon = 'M';
|
||||
break;
|
||||
default: // time not set, no valid time
|
||||
default: // unknown time status (should never be reached)
|
||||
mon = '?';
|
||||
break;
|
||||
} // switch
|
||||
|
||||
// do we have confident time/date?
|
||||
if (timeStatus() == timeSet)
|
||||
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));
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "000000F000000"); // no confident time/date
|
||||
|
||||
// output IF482 telegram
|
||||
// generate IF482 telegram
|
||||
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));
|
||||
snprintf(out, sizeof(out), "O%cL%s\r", mon, buf);
|
||||
ESP_LOGD(TAG, "IF482 = %s", out);
|
||||
return out;
|
||||
@ -127,8 +126,11 @@ TickType_t tx_Ticks(unsigned long baud, uint32_t config, int8_t rxPin,
|
||||
|
||||
uint32_t datenbits = ((config & 0x0c) >> 2) + 5;
|
||||
uint32_t stopbits = ((config & 0x20) >> 5) + 1;
|
||||
return pdMS_TO_TICKS(
|
||||
round(((1 + datenbits + stopbits) * IF482_FRAME_SIZE * 1000.0 / baud)));
|
||||
uint32_t tx_delay =
|
||||
(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
|
@ -399,7 +399,7 @@ esp_err_t lora_stack_init() {
|
||||
}
|
||||
|
||||
return ESP_OK; // continue main program
|
||||
#endif
|
||||
#endif // HAS_LORA
|
||||
}
|
||||
|
||||
void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
|
||||
@ -439,6 +439,7 @@ void lora_housekeeping(void) {
|
||||
|
||||
void user_request_network_time_callback(void *pVoidUserUTCTime,
|
||||
int flagSuccess) {
|
||||
#ifdef HAS_LORA
|
||||
// Explicit conversion from void* to uint32_t* to avoid compiler errors
|
||||
uint32_t *pUserUTCTime = (uint32_t *)pVoidUserUTCTime;
|
||||
lmic_time_reference_t lmicTimeReference;
|
||||
@ -468,11 +469,14 @@ void user_request_network_time_callback(void *pVoidUserUTCTime,
|
||||
*pUserUTCTime += requestDelaySec;
|
||||
|
||||
// Update system time with time read from the network
|
||||
setTime(*pUserUTCTime);
|
||||
ESP_LOGI(TAG, "LoRaWAN network has set the system time");
|
||||
if (sync_TimePulse()) { // wait for start of next second
|
||||
if (sync_SysTime(*pUserUTCTime)) { // do we have a valid time?
|
||||
#ifdef HAS_RTC
|
||||
sync_TimePulse(); // wait for next start of second
|
||||
if (!set_rtctime(*pUserUTCTime+1)) // epoch time
|
||||
ESP_LOGE(TAG, "RTC set time failure");
|
||||
set_rtctime(now()); // epoch time
|
||||
#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
|
59
src/main.cpp
59
src/main.cpp
@ -27,9 +27,8 @@ Uused tasks and timers:
|
||||
|
||||
Task Core Prio Purpose
|
||||
====================================================================================
|
||||
clockloop 0 4 generates realtime telegrams for external clock
|
||||
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
|
||||
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
|
||||
|
||||
@ -71,8 +70,9 @@ hw_timer_t *sendCycle = NULL, *homeCycle = NULL;
|
||||
hw_timer_t *displaytimer = NULL;
|
||||
#endif
|
||||
|
||||
TaskHandle_t irqHandlerTask;
|
||||
TaskHandle_t irqHandlerTask, ClockTask;
|
||||
SemaphoreHandle_t I2Caccess, TimePulse;
|
||||
bool volatile TimePulseTick = false;
|
||||
|
||||
// container holding unique MAC address hashes with Memory Alloctor using PSRAM,
|
||||
// if present
|
||||
@ -334,24 +334,17 @@ void setup() {
|
||||
strcat_P(features, " LPPPKD");
|
||||
#endif
|
||||
|
||||
// initialize RTC
|
||||
// initialize RTC
|
||||
#ifdef HAS_RTC
|
||||
strcat_P(features, " RTC");
|
||||
assert(rtc_init());
|
||||
sync_TimePulse(); // wait for next start of second
|
||||
setSyncProvider(get_rtctime); // sync time now and then
|
||||
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
|
||||
#endif
|
||||
|
||||
#if defined HAS_DCF77
|
||||
strcat_P(features, " DCF77");
|
||||
#endif
|
||||
|
||||
#if defined HAS_IF482 && defined RTC_INT
|
||||
#if defined HAS_IF482
|
||||
strcat_P(features, " IF482");
|
||||
#endif
|
||||
|
||||
@ -365,6 +358,13 @@ void setup() {
|
||||
#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
|
||||
ESP_LOGI(TAG, "Starting Wifi...");
|
||||
wifi_sniffer_init();
|
||||
@ -417,19 +417,29 @@ void setup() {
|
||||
#endif // HAS_BUTTON
|
||||
|
||||
#ifdef HAS_GPS
|
||||
sync_TimePulse(); // wait for next start of second
|
||||
setSyncProvider(get_gpstime); // sync time now and then
|
||||
if (timeStatus() != timeSet)
|
||||
ESP_LOGI(TAG, "Unable to sync system time with GPS");
|
||||
else {
|
||||
ESP_LOGI(TAG, "GPS has set the system time");
|
||||
// sync systime on next timepulse
|
||||
ESP_LOGI(TAG, "GPS is setting system time");
|
||||
if (sync_SysTime(get_gpstime())) {
|
||||
//setSyncProvider(get_gpstime); // reset sync cycle on top of second
|
||||
//setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60);
|
||||
// calibrate RTC
|
||||
#ifdef HAS_RTC
|
||||
if (!set_rtctime(now())) // epoch time
|
||||
ESP_LOGE(TAG, "RTC set time failure");
|
||||
#endif
|
||||
}
|
||||
setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60);
|
||||
set_rtctime(now()); // epoch time
|
||||
#endif
|
||||
} else
|
||||
ESP_LOGI(TAG, "Unable to sync system time with GPS");
|
||||
#endif // HAS_GPS
|
||||
|
||||
// initialize systime from timesource
|
||||
#ifdef HAS_RTC
|
||||
// sync systime on next timepulse
|
||||
ESP_LOGI(TAG, "RTC is setting system time");
|
||||
if (sync_SysTime(get_rtctime())) {
|
||||
//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...");
|
||||
@ -439,7 +449,6 @@ void setup() {
|
||||
} // setup()
|
||||
|
||||
void loop() {
|
||||
|
||||
while (1) {
|
||||
#ifdef HAS_LORA
|
||||
os_runloop_once(); // execute lmic scheduled jobs and events
|
||||
|
@ -84,7 +84,6 @@
|
||||
// settings for syncing time of node and external time sources
|
||||
#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_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
|
||||
|
||||
// time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino
|
||||
|
@ -3,9 +3,7 @@
|
||||
// Local logging tag
|
||||
static const char TAG[] = "main";
|
||||
|
||||
TaskHandle_t ClockTask;
|
||||
hw_timer_t *clockCycle = NULL;
|
||||
bool volatile TimePulseTick = false;
|
||||
|
||||
// helper function to setup a pulse per second for time synchronisation
|
||||
int timepulse_init() {
|
||||
@ -41,7 +39,6 @@ int timepulse_init() {
|
||||
#else
|
||||
// use ESP32 hardware timer as time base with adjustable frequency
|
||||
clockCycle = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
|
||||
//timerAttachInterrupt(clockCycle, &CLOCKIRQ, true);
|
||||
timerAlarmWrite(clockCycle, 10000, true); // 1000ms
|
||||
ESP_LOGI(TAG, "Time base: internal (ESP32 hardware timer)");
|
||||
return 1; // success
|
||||
@ -60,31 +57,10 @@ void timepulse_start(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// helper function to sync systime on start of next second
|
||||
int sync_SysTime(time_t t) {
|
||||
if (sync_TimePulse()) {
|
||||
setTime(t + 1);
|
||||
ESP_LOGD(TAG, "Systime synced on timepulse");
|
||||
return 1; // success
|
||||
} else
|
||||
return 0; // failure
|
||||
}
|
||||
|
||||
// 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(PPS)) == pdTRUE) {
|
||||
return 1;
|
||||
} // success
|
||||
else
|
||||
ESP_LOGW(TAG, "Missing timepulse, time not synced");
|
||||
return 0; // failure
|
||||
}
|
||||
|
||||
// interrupt service routine triggered by either rtc pps or esp32 hardware
|
||||
// timer
|
||||
void IRAM_ATTR CLOCKIRQ() {
|
||||
xTaskNotifyFromISR(ClockTask, xTaskGetTickCountFromISR(), eSetBits, NULL);
|
||||
// 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
|
||||
@ -92,6 +68,31 @@ void IRAM_ATTR CLOCKIRQ() {
|
||||
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
|
||||
|
||||
RtcDS3231<TwoWire> Rtc(Wire); // RTC hardware i2c interface
|
||||
@ -153,6 +154,7 @@ int set_rtctime(time_t t) { // t is seconds epoch time starting 1.1.1970
|
||||
ESP_LOGI(TAG, "RTC calibrated");
|
||||
return 1; // success
|
||||
}
|
||||
ESP_LOGE(TAG, "RTC set time failure");
|
||||
return 0; // failure
|
||||
} // set_rtctime()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user