Merge pull request #347 from cyberman54/development

Development
This commit is contained in:
Verkehrsrot 2019-04-10 21:44:23 +02:00 committed by GitHub
commit 03d4b502d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 191 additions and 173 deletions

View File

@ -8,8 +8,9 @@
//#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds]
#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);
int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len);
void process_timesync_req(void *taskparameter);

View File

@ -46,7 +46,7 @@ board_build.partitions = min_spiffs.csv
monitor_speed = 115200
lib_deps_lora =
;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 =
U8g2@>=2.25.7
lib_deps_rgbled =

View File

@ -170,8 +170,6 @@ void bme_loop(void *pvParameters) {
}
}
#endif
ESP_LOGE(TAG, "BME task ended");
vTaskDelete(BmeTask); // should never be reached
} // bme_loop()

View File

@ -40,7 +40,7 @@ void doHousekeeping() {
ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask));
#endif
#ifdef HAS_DCF77
#if (defined HAS_DCF77 || defined HAS_IF482)
ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask));
#endif

View File

@ -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),
second(t), millisecond(), sec);
// induce 10 pulses
for (uint8_t pulse = 0; pulse <= 9; pulse++) {
// induce a DCF Pulse
for (uint8_t pulse = 0; pulse <= 2; pulse++) {
switch (pulse) {
@ -45,9 +45,6 @@ void DCF77_Pulse(time_t t, uint8_t const *DCFpulse) {
digitalWrite(HAS_DCF77, dcf_high);
break;
case 9: // 900ms after start -> last pulse
break;
} // switch
// pulse pause

View File

@ -123,8 +123,6 @@ void gps_loop(void *pvParameters) {
} // end of infinite loop
vTaskDelete(GpsTask); // shoud never be reached
} // gps_loop()
#endif // HAS_GPS

View File

@ -9,6 +9,7 @@
/* Hardware related definitions for TTGO V2.1 Board
// ATTENTION: check your board version!
// 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
@ -32,6 +33,7 @@
#define LORA_MISO (19)
#define LORA_MOSI (27)
#define LORA_RST LMIC_UNUSED_PIN
// #define LORA_RST (12) // v1.5 labelled with pcb date 20180523
#define LORA_IRQ (26)
#define LORA_IO1 (33)
#define LORA_IO2 (32)

View File

@ -56,7 +56,6 @@ void irqHandler(void *pvParameters) {
if (InterruptStatus & SENDCYCLE_IRQ)
sendCounter();
}
vTaskDelete(NULL); // shoud never be reached
}
// esp32 hardware timer triggered interrupt service routines

View File

@ -212,7 +212,6 @@ void ledLoop(void *parameter) {
// give yield to CPU
delay(2);
} // while(1)
vTaskDelete(ledLoopTask); // shoud never be reached
}; // ledloop()
#endif // #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)

View File

@ -223,6 +223,10 @@ void onEvent(ev_t ev) {
// show effective LoRa parameters after join
ESP_LOGI(TAG, "ADR=%d, SF=%d, TXPOWER=%d", cfg.adrmode, cfg.lorasf,
cfg.txpower);
#if (TIME_SYNC_LORASERVER)
// kickoff first time sync
send_timesync_req();
#endif
break;
case EV_RFU1:
@ -241,9 +245,8 @@ void onEvent(ev_t ev) {
#if (TIME_SYNC_LORASERVER)
// 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
}
#endif
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK")
@ -263,7 +266,7 @@ void onEvent(ev_t ev) {
rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
break;
default: // unknown port -> display info
default:
#if (TIME_SYNC_LORASERVER)
// timesync answer -> call timesync processor
@ -274,6 +277,7 @@ void onEvent(ev_t ev) {
break;
}
#endif
// unknown port -> display info
ESP_LOGI(TAG, "Received data on unsupported port #%d",
LMIC.frame[LMIC.dataBeg - 1]);
break;
@ -309,6 +313,13 @@ void onEvent(ev_t ev) {
case EV_TXSTART:
if (!(LMIC.opmode & OP_JOINING))
#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;

View File

@ -17,7 +17,7 @@ Copyright 2018 Klaus Wilting <verkehrsrot@arcor.de>
See the License for the specific language governing permissions and
limitations under the License.
NOTICE:
NOTE:
Parts of the source files in this repository are made available under different
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
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
clockloop 1 3 generates realtime telegrams for external clock
looptask 1 1 arduino core -> runs the LMIC LoRa stack
irqhandler 1 1 executes tasks triggered by timer irq
clockloop 1 4 generates realtime telegrams for external clock
timesync_req 1 3 processes realtime time sync requests
irqhandler 1 2 display, timesync, etc. tasks triggered by timer
gpsloop 1 2 reads data from GPS via serial or 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
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
(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
-------------------------------------------------------------------------------
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;
TaskHandle_t irqHandlerTask, ClockTask;
TaskHandle_t irqHandlerTask = NULL, ClockTask = NULL;
SemaphoreHandle_t I2Caccess;
bool volatile TimePulseTick = false;
time_t userUTCTime = 0;
@ -360,7 +363,7 @@ void setup() {
"irqhandler", // name of task
4096, // stack size of task
(void *)1, // parameter of the task
1, // priority of the task
2, // priority of the task
&irqHandlerTask, // task handle
1); // CPU core
@ -411,35 +414,40 @@ void setup() {
#endif // HAS_BUTTON
#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
#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
ESP_LOGI(TAG, "Starting Timekeeper...");
assert(timepulse_init()); // setup timepulse
timepulse_start();
timeSync(); // init systime
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
#endif
#if defined HAS_IF482 || defined HAS_DCF77
#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
#endif // TIME_SYNC_INTERVAL
} // setup()
void loop() {
while (1) {
#if (HAS_LORA)
os_runloop_once(); // execute lmic scheduled jobs and events
#endif
#else
delay(2); // yield to CPU
#endif
}
vTaskDelete(NULL); // shoud never be reached
}

View File

@ -73,9 +73,9 @@
#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
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging
#define TIME_SYNC_CYCLE 20 // delay between two time samples [seconds]
#define TIME_SYNC_TIMEOUT 120 // timeout waiting for timeserver answer [seconds]
#define TIME_SYNC_SAMPLES 2 // number of time requests for averaging
#define TIME_SYNC_CYCLE 60 // delay between two time samples [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
#define DAYLIGHT_TIME {"CEST", Last, Sun, Mar, 2, 120} // Central European Summer Time

View File

@ -20,7 +20,9 @@ HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
Ticker timesyncer;
void timeSync() { xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL); }
void timeSync() {
xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL);
}
time_t timeProvider(void) {
@ -123,9 +125,10 @@ void IRAM_ATTR CLOCKIRQ(void) {
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,
&xHigherPriorityTaskWoken);
#endif
#ifdef HAS_DISPLAY
#if (defined GPS_INT || defined RTC_INT)
@ -196,7 +199,7 @@ void clock_init(void) {
"clockloop", // name of task
2048, // stack size of task
(void *)&userUTCTime, // start time as task parameter
3, // priority of the task
4, // priority of the task
&ClockTask, // task handle
1); // CPU core
@ -213,7 +216,6 @@ void clock_loop(void *taskparameter) { // ClockTask
static bool led1_state = false;
uint32_t printtime;
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
TickType_t startTime;
#ifdef HAS_DCF77
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
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
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
(t == last_printtime))
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
#ifdef HAS_TWO_LED
@ -249,23 +270,7 @@ void clock_loop(void *taskparameter) { // ClockTask
led1_state = !led1_state;
#endif
#if defined HAS_IF482
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
last_printtime = t;
} // for
} // clock_loop()

View File

@ -13,87 +13,78 @@ algorithm in applications without granted license by the patent holder.
#include "timesync.h"
using namespace std::chrono;
// Local logging tag
static const char TAG[] = __FILE__;
TaskHandle_t timeSyncReqTask;
static uint8_t time_sync_seqNo = TIMEANSWERPORT_MIN;
static bool lora_time_sync_pending = false;
using namespace std::chrono;
typedef std::chrono::system_clock myClock;
typedef myClock::time_point myClock_timepoint;
typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
myClock_msecTick;
myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
TaskHandle_t timeSyncReqTask = NULL;
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
void send_timesync_req() {
// if a timesync handshake is pending then exit
if (lora_time_sync_pending) {
// ESP_LOGI(TAG, "Timeserver sync request already pending");
// if a timesync handshake is pending or we are not joined then exit
if (timeSyncPending || !LMIC.devaddr)
return;
} else {
// else unblock timesync task
else {
timeSyncPending = true;
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
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
xTaskNotifyGive(timeSyncReqTask);
}
}
// task for sending time sync requests
void process_timesync_req(void *taskparameter) {
uint8_t k = 0;
uint8_t k;
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();
// wait until we are joined
while (!LMIC.devaddr) {
vTaskDelay(pdMS_TO_TICKS(2000));
}
while (1) {
// enqueue timestamp samples in lora sendqueue
// reset all timestamps before next sync run
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();
// wait for kickoff
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
// collect timestamp samples
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
// send sync request to server
payload.reset();
payload.addByte(time_sync_seqNo);
SendPayload(TIMEPORT, prio_high);
// process answer, wait for notification from recv_timesync_ans()
// wait for notification from recv_timesync_ans()
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) ||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) ==
pdFALSE) ||
(seq_no != time_sync_seqNo))
goto error; // no valid sequence received before timeout
else { // calculate time diff from collected timestamps
// process answer
else {
k = seq_no % TIME_SYNC_SAMPLES;
// cumulate timepoint diffs
// calculate time diff from collected timestamps
time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) -
time_point_cast<milliseconds>(time_sync_tx[k]);
// wrap around seqNo keeping it in time port range
// wrap around seqNo, keeping it in time port range
time_sync_seqNo = (time_sync_seqNo < TIMEANSWERPORT_MAX)
? time_sync_seqNo + 1
: TIMEANSWERPORT_MIN;
@ -103,25 +94,25 @@ void process_timesync_req(void *taskparameter) {
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
} else { // before sending last time sample...
// ...send flush to open a receive window for last time_sync_answer
// payload.reset();
// payload.addByte(0x99);
// SendPayload(RCMDPORT, prio_high);
payload.reset();
payload.addByte(0x99);
SendPayload(RCMDPORT, prio_high);
// ...send a alive open a receive window for last time_sync_answer
LMIC_sendAlive();
// LMIC_sendAlive();
}
}
} // for
} // end of for loop to collect timestamp samples
// 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())
goto error; // failure
// average time offset from collected diffs
// average time offset over all collected diffs
time_offset_ms /= TIME_SYNC_SAMPLES;
// calculate time offset with millisecond precision using LMIC's time base,
// 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 +=
milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP);
@ -133,32 +124,31 @@ void process_timesync_req(void *taskparameter) {
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();
finish:
goto finish;
lora_time_sync_pending = false;
timeSyncReqTask = NULL;
vTaskDelete(NULL); // end task
error:
error:
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
millis() / 1000.0);
goto finish; // end task
finish:
timeSyncPending = false;
} // infinite while(1)
}
// called from lorawan.cpp after time_sync_req was sent
void store_time_sync_req(uint32_t timestamp) {
if (lora_time_sync_pending) {
if (timeSyncPending) {
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
time_sync_tx[k] += milliseconds(timestamp);
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d",
millis() / 1000.0, time_sync_seqNo, timestamp / 1000,
timestamp % 1000);
millis() / 1000.0, k, 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) {
// if no timesync handshake is pending then exit
if (!lora_time_sync_pending)
if (!timeSyncPending)
return 0; // failure
// if no time is available or spurious buffer then exit
@ -198,13 +188,12 @@ int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) {
// construct the timepoint when message was seen on gateway
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]))) {
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
if (timeSyncReqTask)
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
return 1; // success
@ -252,4 +241,15 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec) {
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