commit
03d4b502d6
@ -8,8 +8,9 @@
|
|||||||
|
|
||||||
//#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds]
|
//#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds]
|
||||||
#define TIME_SYNC_FRAME_LENGTH 0x05 // timeserver answer frame length [bytes]
|
#define TIME_SYNC_FRAME_LENGTH 0x05 // timeserver answer frame length [bytes]
|
||||||
#define TIME_SYNC_FIXUP 6 // calibration to fixup processing time [milliseconds]
|
#define TIME_SYNC_FIXUP 4 // calibration to fixup processing time [milliseconds]
|
||||||
|
|
||||||
|
void timesync_init(void);
|
||||||
void send_timesync_req(void);
|
void send_timesync_req(void);
|
||||||
int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len);
|
int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len);
|
||||||
void process_timesync_req(void *taskparameter);
|
void process_timesync_req(void *taskparameter);
|
||||||
|
@ -46,7 +46,7 @@ board_build.partitions = min_spiffs.csv
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
lib_deps_lora =
|
lib_deps_lora =
|
||||||
;MCCI LoRaWAN LMIC library@>=2.3.2
|
;MCCI LoRaWAN LMIC library@>=2.3.2
|
||||||
https://github.com/mcci-catena/arduino-lmic.git#6e5ebbe
|
https://github.com/mcci-catena/arduino-lmic.git#e5503ff
|
||||||
lib_deps_display =
|
lib_deps_display =
|
||||||
U8g2@>=2.25.7
|
U8g2@>=2.25.7
|
||||||
lib_deps_rgbled =
|
lib_deps_rgbled =
|
||||||
|
@ -170,8 +170,6 @@ void bme_loop(void *pvParameters) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGE(TAG, "BME task ended");
|
|
||||||
vTaskDelete(BmeTask); // should never be reached
|
|
||||||
|
|
||||||
} // bme_loop()
|
} // bme_loop()
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ void doHousekeeping() {
|
|||||||
ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d",
|
ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d",
|
||||||
uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask));
|
uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask));
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAS_DCF77
|
#if (defined HAS_DCF77 || defined HAS_IF482)
|
||||||
ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d",
|
ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d",
|
||||||
uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask));
|
uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask));
|
||||||
#endif
|
#endif
|
||||||
|
@ -26,8 +26,8 @@ void DCF77_Pulse(time_t t, uint8_t const *DCFpulse) {
|
|||||||
ESP_LOGD(TAG, "[%02d:%02d:%02d.%03d] DCF second %d", hour(t), minute(t),
|
ESP_LOGD(TAG, "[%02d:%02d:%02d.%03d] DCF second %d", hour(t), minute(t),
|
||||||
second(t), millisecond(), sec);
|
second(t), millisecond(), sec);
|
||||||
|
|
||||||
// induce 10 pulses
|
// induce a DCF Pulse
|
||||||
for (uint8_t pulse = 0; pulse <= 9; pulse++) {
|
for (uint8_t pulse = 0; pulse <= 2; pulse++) {
|
||||||
|
|
||||||
switch (pulse) {
|
switch (pulse) {
|
||||||
|
|
||||||
@ -45,9 +45,6 @@ void DCF77_Pulse(time_t t, uint8_t const *DCFpulse) {
|
|||||||
digitalWrite(HAS_DCF77, dcf_high);
|
digitalWrite(HAS_DCF77, dcf_high);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 9: // 900ms after start -> last pulse
|
|
||||||
break;
|
|
||||||
|
|
||||||
} // switch
|
} // switch
|
||||||
|
|
||||||
// pulse pause
|
// pulse pause
|
||||||
|
@ -123,8 +123,6 @@ void gps_loop(void *pvParameters) {
|
|||||||
|
|
||||||
} // end of infinite loop
|
} // end of infinite loop
|
||||||
|
|
||||||
vTaskDelete(GpsTask); // shoud never be reached
|
|
||||||
|
|
||||||
} // gps_loop()
|
} // gps_loop()
|
||||||
|
|
||||||
#endif // HAS_GPS
|
#endif // HAS_GPS
|
@ -9,6 +9,7 @@
|
|||||||
/* Hardware related definitions for TTGO V2.1 Board
|
/* Hardware related definitions for TTGO V2.1 Board
|
||||||
// ATTENTION: check your board version!
|
// ATTENTION: check your board version!
|
||||||
// This settings are for boards without label on pcb, or labeled v1.5 on pcb
|
// This settings are for boards without label on pcb, or labeled v1.5 on pcb
|
||||||
|
// see https://github.com/manuelbl/ttn-esp32/wiki/Boards-and-Pins
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#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
|
||||||
@ -32,6 +33,7 @@
|
|||||||
#define LORA_MISO (19)
|
#define LORA_MISO (19)
|
||||||
#define LORA_MOSI (27)
|
#define LORA_MOSI (27)
|
||||||
#define LORA_RST LMIC_UNUSED_PIN
|
#define LORA_RST LMIC_UNUSED_PIN
|
||||||
|
// #define LORA_RST (12) // v1.5 labelled with pcb date 20180523
|
||||||
#define LORA_IRQ (26)
|
#define LORA_IRQ (26)
|
||||||
#define LORA_IO1 (33)
|
#define LORA_IO1 (33)
|
||||||
#define LORA_IO2 (32)
|
#define LORA_IO2 (32)
|
||||||
|
@ -56,7 +56,6 @@ void irqHandler(void *pvParameters) {
|
|||||||
if (InterruptStatus & SENDCYCLE_IRQ)
|
if (InterruptStatus & SENDCYCLE_IRQ)
|
||||||
sendCounter();
|
sendCounter();
|
||||||
}
|
}
|
||||||
vTaskDelete(NULL); // shoud never be reached
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// esp32 hardware timer triggered interrupt service routines
|
// esp32 hardware timer triggered interrupt service routines
|
||||||
|
@ -212,7 +212,6 @@ void ledLoop(void *parameter) {
|
|||||||
// give yield to CPU
|
// give yield to CPU
|
||||||
delay(2);
|
delay(2);
|
||||||
} // while(1)
|
} // while(1)
|
||||||
vTaskDelete(ledLoopTask); // shoud never be reached
|
|
||||||
}; // ledloop()
|
}; // ledloop()
|
||||||
|
|
||||||
#endif // #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
|
#endif // #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
|
||||||
|
@ -223,6 +223,10 @@ void onEvent(ev_t ev) {
|
|||||||
// show effective LoRa parameters after join
|
// show effective LoRa parameters after join
|
||||||
ESP_LOGI(TAG, "ADR=%d, SF=%d, TXPOWER=%d", cfg.adrmode, cfg.lorasf,
|
ESP_LOGI(TAG, "ADR=%d, SF=%d, TXPOWER=%d", cfg.adrmode, cfg.lorasf,
|
||||||
cfg.txpower);
|
cfg.txpower);
|
||||||
|
#if (TIME_SYNC_LORASERVER)
|
||||||
|
// kickoff first time sync
|
||||||
|
send_timesync_req();
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EV_RFU1:
|
case EV_RFU1:
|
||||||
@ -241,9 +245,8 @@ void onEvent(ev_t ev) {
|
|||||||
|
|
||||||
#if (TIME_SYNC_LORASERVER)
|
#if (TIME_SYNC_LORASERVER)
|
||||||
// if last packet sent was a timesync request, store TX timestamp
|
// if last packet sent was a timesync request, store TX timestamp
|
||||||
if (LMIC.pendTxPort == TIMEPORT) {
|
if (LMIC.pendTxPort == TIMEPORT)
|
||||||
store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
|
store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK")
|
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK")
|
||||||
@ -263,10 +266,10 @@ void onEvent(ev_t ev) {
|
|||||||
rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
|
rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // unknown port -> display info
|
default:
|
||||||
|
|
||||||
#if (TIME_SYNC_LORASERVER)
|
#if (TIME_SYNC_LORASERVER)
|
||||||
// timesync answer -> call timesync processor
|
// timesync answer -> call timesync processor
|
||||||
if ((LMIC.frame[LMIC.dataBeg - 1] >= TIMEANSWERPORT_MIN) &&
|
if ((LMIC.frame[LMIC.dataBeg - 1] >= TIMEANSWERPORT_MIN) &&
|
||||||
(LMIC.frame[LMIC.dataBeg - 1] <= TIMEANSWERPORT_MAX)) {
|
(LMIC.frame[LMIC.dataBeg - 1] <= TIMEANSWERPORT_MAX)) {
|
||||||
recv_timesync_ans(LMIC.frame[LMIC.dataBeg - 1],
|
recv_timesync_ans(LMIC.frame[LMIC.dataBeg - 1],
|
||||||
@ -274,6 +277,7 @@ void onEvent(ev_t ev) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
// unknown port -> display info
|
||||||
ESP_LOGI(TAG, "Received data on unsupported port #%d",
|
ESP_LOGI(TAG, "Received data on unsupported port #%d",
|
||||||
LMIC.frame[LMIC.dataBeg - 1]);
|
LMIC.frame[LMIC.dataBeg - 1]);
|
||||||
break;
|
break;
|
||||||
@ -309,7 +313,14 @@ void onEvent(ev_t ev) {
|
|||||||
|
|
||||||
case EV_TXSTART:
|
case EV_TXSTART:
|
||||||
if (!(LMIC.opmode & OP_JOINING))
|
if (!(LMIC.opmode & OP_JOINING))
|
||||||
strcpy_P(buff, PSTR("TX START"));
|
#if (TIME_SYNC_LORASERVER)
|
||||||
|
// if last packet sent was a timesync request, store TX time
|
||||||
|
// if ((LMIC.pendTxPort == TIMEPORT) && timeSyncPending)
|
||||||
|
if (LMIC.pendTxPort == TIMEPORT)
|
||||||
|
strcpy_P(buff, PSTR("TX TIMESYNC"));
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
strcpy_P(buff, PSTR("TX START"));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EV_TXCANCELED:
|
case EV_TXCANCELED:
|
||||||
|
48
src/main.cpp
48
src/main.cpp
@ -17,7 +17,7 @@ Copyright 2018 Klaus Wilting <verkehrsrot@arcor.de>
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
NOTICE:
|
NOTE:
|
||||||
Parts of the source files in this repository are made available under different
|
Parts of the source files in this repository are made available under different
|
||||||
licenses. Refer to LICENSE.txt file in repository for more details.
|
licenses. Refer to LICENSE.txt file in repository for more details.
|
||||||
|
|
||||||
@ -31,12 +31,12 @@ ledloop 0 3 blinks LEDs
|
|||||||
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
|
||||||
|
|
||||||
clockloop 1 3 generates realtime telegrams for external clock
|
clockloop 1 4 generates realtime telegrams for external clock
|
||||||
looptask 1 1 arduino core -> runs the LMIC LoRa stack
|
timesync_req 1 3 processes realtime time sync requests
|
||||||
irqhandler 1 1 executes tasks triggered by timer irq
|
irqhandler 1 2 display, timesync, etc. tasks triggered by timer
|
||||||
gpsloop 1 2 reads data from GPS via serial or i2c
|
gpsloop 1 2 reads data from GPS via serial or i2c
|
||||||
bmeloop 1 1 reads data from BME sensor via i2c
|
bmeloop 1 1 reads data from BME sensor via i2c
|
||||||
timesync_req 1 2 temporary task for processing time sync requests
|
looptask 1 1 runs the LMIC LoRa stack (arduino loop)
|
||||||
IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
|
IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
|
||||||
|
|
||||||
Low priority numbers denote low priority tasks.
|
Low priority numbers denote low priority tasks.
|
||||||
@ -44,6 +44,9 @@ Low priority numbers denote low priority tasks.
|
|||||||
Tasks using i2c bus all must have same priority, because using mutex semaphore
|
Tasks using i2c bus all must have same priority, because using mutex semaphore
|
||||||
(irqhandler, bmeloop)
|
(irqhandler, bmeloop)
|
||||||
|
|
||||||
|
NOTE: Changing any timings will have impact on time accuracy of whole code.
|
||||||
|
So don't do it if you do not own a digital oscilloscope.
|
||||||
|
|
||||||
// ESP32 hardware timers
|
// ESP32 hardware timers
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS)
|
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS)
|
||||||
@ -83,7 +86,7 @@ uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0,
|
|||||||
|
|
||||||
hw_timer_t *ppsIRQ = NULL, *displayIRQ = NULL;
|
hw_timer_t *ppsIRQ = NULL, *displayIRQ = NULL;
|
||||||
|
|
||||||
TaskHandle_t irqHandlerTask, ClockTask;
|
TaskHandle_t irqHandlerTask = NULL, ClockTask = NULL;
|
||||||
SemaphoreHandle_t I2Caccess;
|
SemaphoreHandle_t I2Caccess;
|
||||||
bool volatile TimePulseTick = false;
|
bool volatile TimePulseTick = false;
|
||||||
time_t userUTCTime = 0;
|
time_t userUTCTime = 0;
|
||||||
@ -360,7 +363,7 @@ void setup() {
|
|||||||
"irqhandler", // name of task
|
"irqhandler", // name of task
|
||||||
4096, // stack size of task
|
4096, // stack size of task
|
||||||
(void *)1, // parameter of the task
|
(void *)1, // parameter of the task
|
||||||
1, // priority of the task
|
2, // priority of the task
|
||||||
&irqHandlerTask, // task handle
|
&irqHandlerTask, // task handle
|
||||||
1); // CPU core
|
1); // CPU core
|
||||||
|
|
||||||
@ -411,35 +414,40 @@ void setup() {
|
|||||||
#endif // HAS_BUTTON
|
#endif // HAS_BUTTON
|
||||||
|
|
||||||
#if (TIME_SYNC_INTERVAL)
|
#if (TIME_SYNC_INTERVAL)
|
||||||
#if (!defined(TIME_SYNC_LORAWAN) && !defined(TIME_SYNC_LORASERVER) && \
|
|
||||||
!defined HAS_GPS && !defined HAS_RTC)
|
#if (!(TIME_SYNC_LORAWAN) && !(TIME_SYNC_LORASERVER) && !defined HAS_GPS && \
|
||||||
|
!defined HAS_RTC)
|
||||||
#warning you did not specify a time source, time will not be synched
|
#warning you did not specify a time source, time will not be synched
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if (defined HAS_IF482 || defined HAS_DCF77)
|
||||||
|
ESP_LOGI(TAG, "Starting Clock Controller...");
|
||||||
|
clock_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (TIME_SYNC_LORASERVER)
|
||||||
|
// create time sync task
|
||||||
|
timesync_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
// start pps timepulse
|
// start pps timepulse
|
||||||
ESP_LOGI(TAG, "Starting Timekeeper...");
|
ESP_LOGI(TAG, "Starting Timekeeper...");
|
||||||
assert(timepulse_init()); // setup timepulse
|
assert(timepulse_init()); // setup timepulse
|
||||||
timepulse_start();
|
timepulse_start();
|
||||||
timeSync(); // init systime
|
timeSync(); // init systime
|
||||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
|
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined HAS_IF482 || defined HAS_DCF77
|
#endif // TIME_SYNC_INTERVAL
|
||||||
#if (!TIME_SYNC_INTERVAL)
|
|
||||||
#error for clock controller function TIME_SNYC_INTERVAL must be defined in paxcounter.conf
|
|
||||||
#endif
|
|
||||||
ESP_LOGI(TAG, "Starting Clock Controller...");
|
|
||||||
clock_init();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // setup()
|
} // setup()
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
#if (HAS_LORA)
|
#if (HAS_LORA)
|
||||||
os_runloop_once(); // execute lmic scheduled jobs and events
|
os_runloop_once(); // execute lmic scheduled jobs and events
|
||||||
#endif
|
#else
|
||||||
delay(2); // yield to CPU
|
delay(2); // yield to CPU
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelete(NULL); // shoud never be reached
|
|
||||||
}
|
}
|
||||||
|
@ -73,9 +73,9 @@
|
|||||||
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
|
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
|
||||||
|
|
||||||
// settings for syncing time with timeserver applications
|
// settings for syncing time with timeserver applications
|
||||||
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging
|
#define TIME_SYNC_SAMPLES 2 // number of time requests for averaging
|
||||||
#define TIME_SYNC_CYCLE 20 // delay between two time samples [seconds]
|
#define TIME_SYNC_CYCLE 60 // delay between two time samples [seconds]
|
||||||
#define TIME_SYNC_TIMEOUT 120 // timeout waiting for timeserver answer [seconds]
|
#define TIME_SYNC_TIMEOUT 600 // timeout waiting for timeserver answer [seconds]
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -20,7 +20,9 @@ HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
|
|||||||
|
|
||||||
Ticker timesyncer;
|
Ticker timesyncer;
|
||||||
|
|
||||||
void timeSync() { xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL); }
|
void timeSync() {
|
||||||
|
xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
time_t timeProvider(void) {
|
time_t timeProvider(void) {
|
||||||
|
|
||||||
@ -123,9 +125,10 @@ void IRAM_ATTR CLOCKIRQ(void) {
|
|||||||
|
|
||||||
SyncToPPS(); // calibrates UTC systime and advances it +1, see microTime.h
|
SyncToPPS(); // calibrates UTC systime and advances it +1, see microTime.h
|
||||||
|
|
||||||
if (ClockTask != NULL)
|
#if (defined HAS_IF482 || defined HAS_DCF77)
|
||||||
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
|
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
|
||||||
&xHigherPriorityTaskWoken);
|
&xHigherPriorityTaskWoken);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_DISPLAY
|
#ifdef HAS_DISPLAY
|
||||||
#if (defined GPS_INT || defined RTC_INT)
|
#if (defined GPS_INT || defined RTC_INT)
|
||||||
@ -196,7 +199,7 @@ void clock_init(void) {
|
|||||||
"clockloop", // name of task
|
"clockloop", // name of task
|
||||||
2048, // stack size of task
|
2048, // stack size of task
|
||||||
(void *)&userUTCTime, // start time as task parameter
|
(void *)&userUTCTime, // start time as task parameter
|
||||||
3, // priority of the task
|
4, // priority of the task
|
||||||
&ClockTask, // task handle
|
&ClockTask, // task handle
|
||||||
1); // CPU core
|
1); // CPU core
|
||||||
|
|
||||||
@ -213,7 +216,6 @@ void clock_loop(void *taskparameter) { // ClockTask
|
|||||||
static bool led1_state = false;
|
static bool led1_state = false;
|
||||||
uint32_t printtime;
|
uint32_t printtime;
|
||||||
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
|
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
|
||||||
TickType_t startTime;
|
|
||||||
|
|
||||||
#ifdef HAS_DCF77
|
#ifdef HAS_DCF77
|
||||||
uint8_t *DCFpulse; // pointer on array with DCF pulse bits
|
uint8_t *DCFpulse; // pointer on array with DCF pulse bits
|
||||||
@ -225,20 +227,39 @@ void clock_loop(void *taskparameter) { // ClockTask
|
|||||||
|
|
||||||
// output the next second's pulse after timepulse arrived
|
// output the next second's pulse after timepulse arrived
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// ensure the notification state is not already pending
|
|
||||||
xTaskNotifyWait(0x00, ULONG_MAX, &printtime,
|
|
||||||
portMAX_DELAY); // wait for timepulse
|
|
||||||
|
|
||||||
startTime = xTaskGetTickCount();
|
|
||||||
|
|
||||||
t = time_t(printtime); // UTC time seconds
|
|
||||||
|
|
||||||
|
// wait for timepulse and store UTC time in seconds got
|
||||||
|
xTaskNotifyWait(0x00, ULONG_MAX, &printtime, portMAX_DELAY);
|
||||||
|
t = time_t(printtime);
|
||||||
|
|
||||||
// no confident or no recent time -> suppress clock output
|
// no confident or no recent time -> suppress clock output
|
||||||
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
|
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
|
||||||
(t == last_printtime))
|
(t == last_printtime))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
last_printtime = t;
|
#if defined HAS_IF482
|
||||||
|
|
||||||
|
// wait until moment to fire. Normally we won't get notified during this
|
||||||
|
// timespan, except when next pps pulse arrives while waiting, because pps
|
||||||
|
// was adjusted by recent time sync
|
||||||
|
if (xTaskNotifyWait(0x00, ULONG_MAX, &printtime, txDelay) == pdTRUE)
|
||||||
|
t = time_t(printtime); // new adjusted UTC time seconds
|
||||||
|
|
||||||
|
// send IF482 telegram
|
||||||
|
IF482.print(IF482_Frame(t + 1)); // note: telegram is for *next* second
|
||||||
|
|
||||||
|
#elif defined HAS_DCF77
|
||||||
|
|
||||||
|
if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame?
|
||||||
|
DCFpulse = DCF77_Frame(nextmin(t)); // generate frame for next minute
|
||||||
|
|
||||||
|
if (minute(nextmin(t)) == // do we still have a recent frame?
|
||||||
|
DCFpulse[DCF77_FRAME_SIZE]) // (timepulses could be missed!)
|
||||||
|
DCF77_Pulse(t, DCFpulse); // then output current second's pulse
|
||||||
|
|
||||||
|
// else we have no recent frame, thus suppressing clock output
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// pps blink on secondary LED if we have one
|
// pps blink on secondary LED if we have one
|
||||||
#ifdef HAS_TWO_LED
|
#ifdef HAS_TWO_LED
|
||||||
@ -249,23 +270,7 @@ void clock_loop(void *taskparameter) { // ClockTask
|
|||||||
led1_state = !led1_state;
|
led1_state = !led1_state;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined HAS_IF482
|
last_printtime = t;
|
||||||
|
|
||||||
vTaskDelayUntil(&startTime, txDelay); // wait until moment to fire
|
|
||||||
IF482.print(IF482_Frame(t + 1)); // note: if482 telegram for *next* second
|
|
||||||
|
|
||||||
#elif defined HAS_DCF77
|
|
||||||
|
|
||||||
if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame?
|
|
||||||
DCFpulse = DCF77_Frame(nextmin(t)); // generate frame for next minute
|
|
||||||
|
|
||||||
if (minute(nextmin(t)) == // do we still have a recent frame?
|
|
||||||
DCFpulse[DCF77_FRAME_SIZE]) // (timepulses could be missed!)
|
|
||||||
DCF77_Pulse(t, DCFpulse); // then output current second's pulse
|
|
||||||
else
|
|
||||||
continue; // no recent frame -> we suppress clock output
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // for
|
} // for
|
||||||
} // clock_loop()
|
} // clock_loop()
|
||||||
|
200
src/timesync.cpp
200
src/timesync.cpp
@ -13,152 +13,142 @@ algorithm in applications without granted license by the patent holder.
|
|||||||
|
|
||||||
#include "timesync.h"
|
#include "timesync.h"
|
||||||
|
|
||||||
using namespace std::chrono;
|
|
||||||
|
|
||||||
// Local logging tag
|
// Local logging tag
|
||||||
static const char TAG[] = __FILE__;
|
static const char TAG[] = __FILE__;
|
||||||
|
|
||||||
TaskHandle_t timeSyncReqTask;
|
using namespace std::chrono;
|
||||||
|
|
||||||
static uint8_t time_sync_seqNo = TIMEANSWERPORT_MIN;
|
|
||||||
static bool lora_time_sync_pending = false;
|
|
||||||
|
|
||||||
typedef std::chrono::system_clock myClock;
|
typedef std::chrono::system_clock myClock;
|
||||||
typedef myClock::time_point myClock_timepoint;
|
typedef myClock::time_point myClock_timepoint;
|
||||||
typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
|
typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
|
||||||
myClock_msecTick;
|
myClock_msecTick;
|
||||||
|
|
||||||
myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
TaskHandle_t timeSyncReqTask = NULL;
|
||||||
myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
|
||||||
|
static uint8_t time_sync_seqNo = random(TIMEANSWERPORT_MIN, TIMEANSWERPORT_MAX);
|
||||||
|
static bool timeSyncPending = false;
|
||||||
|
static myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
||||||
|
static myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
||||||
|
|
||||||
// send time request message
|
// send time request message
|
||||||
void send_timesync_req() {
|
void send_timesync_req() {
|
||||||
|
|
||||||
// if a timesync handshake is pending then exit
|
// if a timesync handshake is pending or we are not joined then exit
|
||||||
if (lora_time_sync_pending) {
|
if (timeSyncPending || !LMIC.devaddr)
|
||||||
// ESP_LOGI(TAG, "Timeserver sync request already pending");
|
|
||||||
return;
|
return;
|
||||||
} else {
|
// else unblock timesync task
|
||||||
|
else {
|
||||||
|
timeSyncPending = true;
|
||||||
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
|
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
|
||||||
|
xTaskNotifyGive(timeSyncReqTask);
|
||||||
lora_time_sync_pending = true;
|
|
||||||
|
|
||||||
// clear timestamp array
|
|
||||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++)
|
|
||||||
time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint();
|
|
||||||
|
|
||||||
// kick off temporary task for timeserver handshake processing
|
|
||||||
if (!timeSyncReqTask)
|
|
||||||
xTaskCreatePinnedToCore(process_timesync_req, // task function
|
|
||||||
"timesync_req", // name of task
|
|
||||||
2048, // stack size of task
|
|
||||||
(void *)1, // task parameter
|
|
||||||
2, // priority of the task
|
|
||||||
&timeSyncReqTask, // task handle
|
|
||||||
1); // CPU core
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// task for sending time sync requests
|
// task for sending time sync requests
|
||||||
void process_timesync_req(void *taskparameter) {
|
void process_timesync_req(void *taskparameter) {
|
||||||
|
|
||||||
uint8_t k = 0;
|
uint8_t k;
|
||||||
uint16_t time_to_set_fraction_msec;
|
uint16_t time_to_set_fraction_msec;
|
||||||
uint32_t seq_no = 0, time_to_set;
|
uint32_t seq_no, time_to_set;
|
||||||
auto time_offset_ms = myClock_msecTick::zero();
|
auto time_offset_ms = myClock_msecTick::zero();
|
||||||
|
|
||||||
// wait until we are joined
|
while (1) {
|
||||||
while (!LMIC.devaddr) {
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
|
||||||
}
|
|
||||||
|
|
||||||
// enqueue timestamp samples in lora sendqueue
|
// reset all timestamps before next sync run
|
||||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
time_offset_ms = myClock_msecTick::zero();
|
||||||
|
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++)
|
||||||
|
time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint();
|
||||||
|
|
||||||
// send sync request to server
|
// wait for kickoff
|
||||||
payload.reset();
|
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||||
payload.addByte(time_sync_seqNo);
|
|
||||||
SendPayload(TIMEPORT, prio_high);
|
|
||||||
|
|
||||||
// process answer, wait for notification from recv_timesync_ans()
|
// collect timestamp samples
|
||||||
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
||||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) ||
|
// send sync request to server
|
||||||
(seq_no != time_sync_seqNo))
|
payload.reset();
|
||||||
goto error; // no valid sequence received before timeout
|
payload.addByte(time_sync_seqNo);
|
||||||
|
SendPayload(TIMEPORT, prio_high);
|
||||||
|
|
||||||
else { // calculate time diff from collected timestamps
|
// wait for notification from recv_timesync_ans()
|
||||||
k = seq_no % TIME_SYNC_SAMPLES;
|
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
||||||
|
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) ==
|
||||||
|
pdFALSE) ||
|
||||||
|
(seq_no != time_sync_seqNo))
|
||||||
|
goto error; // no valid sequence received before timeout
|
||||||
|
|
||||||
// cumulate timepoint diffs
|
// process answer
|
||||||
time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) -
|
else {
|
||||||
time_point_cast<milliseconds>(time_sync_tx[k]);
|
k = seq_no % TIME_SYNC_SAMPLES;
|
||||||
|
|
||||||
// wrap around seqNo keeping it in time port range
|
// calculate time diff from collected timestamps
|
||||||
time_sync_seqNo = (time_sync_seqNo < TIMEANSWERPORT_MAX)
|
time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) -
|
||||||
? time_sync_seqNo + 1
|
time_point_cast<milliseconds>(time_sync_tx[k]);
|
||||||
: TIMEANSWERPORT_MIN;
|
|
||||||
|
|
||||||
if (i < TIME_SYNC_SAMPLES - 1) {
|
// wrap around seqNo, keeping it in time port range
|
||||||
// wait until next cycle
|
time_sync_seqNo = (time_sync_seqNo < TIMEANSWERPORT_MAX)
|
||||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
? time_sync_seqNo + 1
|
||||||
} else { // before sending last time sample...
|
: TIMEANSWERPORT_MIN;
|
||||||
// ...send flush to open a receive window for last time_sync_answer
|
|
||||||
// payload.reset();
|
if (i < TIME_SYNC_SAMPLES - 1) {
|
||||||
// payload.addByte(0x99);
|
// wait until next cycle
|
||||||
// SendPayload(RCMDPORT, prio_high);
|
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||||
// ...send a alive open a receive window for last time_sync_answer
|
} else { // before sending last time sample...
|
||||||
LMIC_sendAlive();
|
// ...send flush to open a receive window for last time_sync_answer
|
||||||
|
payload.reset();
|
||||||
|
payload.addByte(0x99);
|
||||||
|
SendPayload(RCMDPORT, prio_high);
|
||||||
|
// ...send a alive open a receive window for last time_sync_answer
|
||||||
|
// LMIC_sendAlive();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} // end of for loop to collect timestamp samples
|
||||||
} // for
|
|
||||||
|
|
||||||
// begin of time critical section: lock I2C bus to ensure accurate timing
|
// begin of time critical section: lock app irq's and I2C bus
|
||||||
if (!mask_user_IRQ())
|
if (!mask_user_IRQ())
|
||||||
goto error; // failure
|
goto error; // failure
|
||||||
|
|
||||||
// average time offset from collected diffs
|
// average time offset over all collected diffs
|
||||||
time_offset_ms /= TIME_SYNC_SAMPLES;
|
time_offset_ms /= TIME_SYNC_SAMPLES;
|
||||||
|
|
||||||
// calculate time offset with millisecond precision using LMIC's time base,
|
// calculate time offset with millisecond precision using LMIC's time base,
|
||||||
// since we use LMIC's ostime_t txEnd as tx timestamp.
|
// since we use LMIC's ostime_t txEnd as tx timestamp.
|
||||||
// Finally apply calibration const for processing time.
|
// Also apply calibration const to compensate processing time.
|
||||||
time_offset_ms +=
|
time_offset_ms +=
|
||||||
milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP);
|
milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP);
|
||||||
|
|
||||||
// calculate absolute time in UTC epoch: convert to whole seconds, round to
|
// calculate absolute time in UTC epoch: convert to whole seconds, round to
|
||||||
// ceil, and calculate fraction milliseconds
|
// ceil, and calculate fraction milliseconds
|
||||||
time_to_set = (uint32_t)(time_offset_ms.count() / 1000) + 1;
|
time_to_set = (uint32_t)(time_offset_ms.count() / 1000) + 1;
|
||||||
// calculate fraction milliseconds
|
// calculate fraction milliseconds
|
||||||
time_to_set_fraction_msec = (uint16_t)(time_offset_ms.count() % 1000);
|
time_to_set_fraction_msec = (uint16_t)(time_offset_ms.count() % 1000);
|
||||||
|
|
||||||
setMyTime(time_to_set, time_to_set_fraction_msec);
|
setMyTime(time_to_set, time_to_set_fraction_msec);
|
||||||
|
|
||||||
// end of time critical section: release I2C bus
|
// end of time critical section: release I2C bus and re-enable app irq's
|
||||||
unmask_user_IRQ();
|
unmask_user_IRQ();
|
||||||
|
|
||||||
finish:
|
goto finish;
|
||||||
|
|
||||||
lora_time_sync_pending = false;
|
error:
|
||||||
timeSyncReqTask = NULL;
|
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
|
||||||
vTaskDelete(NULL); // end task
|
millis() / 1000.0);
|
||||||
|
|
||||||
error:
|
finish:
|
||||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
|
timeSyncPending = false;
|
||||||
millis() / 1000.0);
|
|
||||||
goto finish; // end task
|
} // infinite while(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// called from lorawan.cpp after time_sync_req was sent
|
// called from lorawan.cpp after time_sync_req was sent
|
||||||
void store_time_sync_req(uint32_t timestamp) {
|
void store_time_sync_req(uint32_t timestamp) {
|
||||||
|
|
||||||
if (lora_time_sync_pending) {
|
if (timeSyncPending) {
|
||||||
|
|
||||||
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
|
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
|
||||||
time_sync_tx[k] += milliseconds(timestamp);
|
time_sync_tx[k] += milliseconds(timestamp);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d",
|
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d",
|
||||||
millis() / 1000.0, time_sync_seqNo, timestamp / 1000,
|
millis() / 1000.0, k, timestamp / 1000, timestamp % 1000);
|
||||||
timestamp % 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +156,7 @@ void store_time_sync_req(uint32_t timestamp) {
|
|||||||
int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) {
|
int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) {
|
||||||
|
|
||||||
// if no timesync handshake is pending then exit
|
// if no timesync handshake is pending then exit
|
||||||
if (!lora_time_sync_pending)
|
if (!timeSyncPending)
|
||||||
return 0; // failure
|
return 0; // failure
|
||||||
|
|
||||||
// if no time is available or spurious buffer then exit
|
// if no time is available or spurious buffer then exit
|
||||||
@ -198,14 +188,13 @@ int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) {
|
|||||||
// construct the timepoint when message was seen on gateway
|
// construct the timepoint when message was seen on gateway
|
||||||
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec);
|
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec);
|
||||||
|
|
||||||
// guess timepoint is recent if newer than code compile date
|
// we guess timepoint is recent if it newer than code compile date
|
||||||
if (timeIsValid(myClock::to_time_t(time_sync_rx[k]))) {
|
if (timeIsValid(myClock::to_time_t(time_sync_rx[k]))) {
|
||||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d rcvd at %d.%03d",
|
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d rcvd at %d.%03d",
|
||||||
millis() / 1000.0, seq_no, timestamp_sec, timestamp_msec);
|
millis() / 1000.0, k, timestamp_sec, timestamp_msec);
|
||||||
|
|
||||||
// inform processing task
|
// inform processing task
|
||||||
if (timeSyncReqTask)
|
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
||||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
|
||||||
|
|
||||||
return 1; // success
|
return 1; // success
|
||||||
} else {
|
} else {
|
||||||
@ -252,4 +241,15 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec) {
|
|||||||
millis() / 1000.0);
|
millis() / 1000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void timesync_init() {
|
||||||
|
// create task for timeserver handshake processing, called from main.cpp
|
||||||
|
xTaskCreatePinnedToCore(process_timesync_req, // task function
|
||||||
|
"timesync_req", // name of task
|
||||||
|
2048, // stack size of task
|
||||||
|
(void *)1, // task parameter
|
||||||
|
3, // priority of the task
|
||||||
|
&timeSyncReqTask, // task handle
|
||||||
|
1); // CPU core
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
Loading…
Reference in New Issue
Block a user