commit
4608a3b063
16
include/clockcontroller.h
Normal file
16
include/clockcontroller.h
Normal 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
|
@ -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);
|
||||||
|
@ -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[]);
|
||||||
|
|
||||||
|
@ -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
|
@ -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
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
@ -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
|
@ -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
94
src/clockcontroller.cpp
Normal 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
|
@ -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),
|
||||||
|
230
src/dcf77.cpp
230
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:
|
// 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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
112
src/if482.cpp
112
src/if482.cpp
@ -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
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
89
src/main.cpp
89
src/main.cpp
@ -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
|
||||||
@ -321,23 +334,17 @@ void setup() {
|
|||||||
strcat_P(features, " LPPPKD");
|
strcat_P(features, " LPPPKD");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// initialize RTC
|
// initialize RTC
|
||||||
#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
|
||||||
|
@ -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
|
||||||
|
188
src/rtctime.cpp
188
src/rtctime.cpp
@ -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();
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user