added DCF77 function (experimental, not fully working yet)

This commit is contained in:
Klaus K Wilting 2019-02-03 21:19:08 +01:00
parent 9cb828bfbd
commit 39e2df7a05
9 changed files with 300 additions and 19 deletions

View File

@ -11,11 +11,14 @@
#include "bme680mems.h" #include "bme680mems.h"
#endif #endif
// Needed for RTC time sync if RTC present on board
#ifdef HAS_RTC #ifdef HAS_RTC
#include "rtctime.h" #include "rtctime.h"
#endif #endif
#ifdef HAS_DCF77
#include "dcf77.h"
#endif
void doHousekeeping(void); void doHousekeeping(void);
uint64_t uptime(void); uint64_t uptime(void);
void reset_counters(void); void reset_counters(void);

16
include/dcf77.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef _DCF77_H
#define _DCF77_H
#include "globals.h"
extern TaskHandle_t DCF77Task;
extern hw_timer_t *dcfCycle;
enum dcf_pulses { dcf_off, dcf_zero, dcf_one };
int dcf77_init(void);
void dcf77_loop(void *pvParameters);
void IRAM_ATTR DCF77IRQ(void);
void sendDCF77(void);
#endif

View File

@ -2,7 +2,6 @@
#define _IF482_H #define _IF482_H
#include "globals.h" #include "globals.h"
#include "irqhandler.h"
extern TaskHandle_t IF482Task; extern TaskHandle_t IF482Task;

View File

@ -60,6 +60,10 @@ 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
ESP_LOGD(TAG, "DCF77loop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(DCF77Task), eTaskGetState(DCF77Task));
#endif
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
ESP_LOGD(TAG, "LEDloop %d bytes left | Taskstate = %d", ESP_LOGD(TAG, "LEDloop %d bytes left | Taskstate = %d",
@ -79,6 +83,11 @@ void doHousekeeping() {
bme_status.temperature, bme_status.iaq, bme_status.iaq_accuracy); bme_status.temperature, bme_status.iaq, bme_status.iaq_accuracy);
#endif #endif
// generate DCF77 timeframes
#ifdef HAS_DCF77
sendDCF77();
#endif
// check free heap memory // check free heap memory
if (ESP.getMinFreeHeap() <= MEM_LOW) { if (ESP.getMinFreeHeap() <= MEM_LOW) {
ESP_LOGI(TAG, ESP_LOGI(TAG,
@ -86,8 +95,8 @@ void doHousekeeping() {
"free heap = %d bytes)", "free heap = %d bytes)",
ESP.getMinFreeHeap(), ESP.getFreeHeap()); ESP.getMinFreeHeap(), ESP.getFreeHeap());
SendPayload(COUNTERPORT, prio_high); // send data before clearing counters SendPayload(COUNTERPORT, prio_high); // send data before clearing counters
reset_counters(); // clear macs container and reset all counters reset_counters(); // clear macs container and reset all counters
get_salt(); // get new salt for salting hashes get_salt(); // get new salt for salting hashes
if (ESP.getMinFreeHeap() <= MEM_LOW) // check again if (ESP.getMinFreeHeap() <= MEM_LOW) // check again
do_reset(); // memory leak, reset device do_reset(); // memory leak, reset device
@ -98,8 +107,8 @@ void doHousekeeping() {
if (ESP.getMinFreePsram() <= MEM_LOW) { if (ESP.getMinFreePsram() <= MEM_LOW) {
ESP_LOGI(TAG, "PSRAM full, counter cleared"); ESP_LOGI(TAG, "PSRAM full, counter cleared");
SendPayload(COUNTERPORT, prio_high); // send data before clearing counters SendPayload(COUNTERPORT, prio_high); // send data before clearing counters
reset_counters(); // clear macs container and reset all counters reset_counters(); // clear macs container and reset all counters
get_salt(); // get new salt for salting hashes get_salt(); // get new salt for salting hashes
if (ESP.getMinFreePsram() <= MEM_LOW) // check again if (ESP.getMinFreePsram() <= MEM_LOW) // check again
do_reset(); // memory leak, reset device do_reset(); // memory leak, reset device

219
src/dcf77.cpp Normal file
View File

@ -0,0 +1,219 @@
//
// source:
// https://www.elektormagazine.com/labs/dcf77-emulator-with-esp8266-elektor-labs-version-150713
//
/*
Simulate a DCF77 radio receiver
Emit a complete three minute pulses train from the GPIO output
the train is preceded by a single pulse and the lacking 59th pulse to allow
some clock model syncronization of the beginning frame. After the three pulses
train one more single pulse is sent to safely close the frame
*/
#if defined HAS_DCF77
#include "dcf77.h"
// Local logging tag
static const char TAG[] = "main";
TaskHandle_t DCF77Task;
QueueHandle_t DCFSendQueue;
hw_timer_t *dcfCycle = NULL;
#define DCF77_FRAME_SIZE 60
#define DCF_FRAME_QUEUE_SIZE (HOMECYCLE / 60 + 1)
// array of dcf pulses for three minutes
uint8_t DCFtimeframe[DCF77_FRAME_SIZE];
// initialize and configure DCF77 output
int dcf77_init(void) {
DCFSendQueue = xQueueCreate(DCF_FRAME_QUEUE_SIZE,
sizeof(DCFtimeframe) / sizeof(DCFtimeframe[0]));
if (!DCFSendQueue) {
ESP_LOGE(TAG, "Could not create DCF77 send queue. Aborting.");
return 0; // failure
}
ESP_LOGI(TAG, "DCF77 send queue created, size %d Bytes",
DCF_FRAME_QUEUE_SIZE * sizeof(DCFtimeframe) /
sizeof(DCFtimeframe[0]));
pinMode(HAS_DCF77, OUTPUT);
digitalWrite(HAS_DCF77, LOW);
return 1; // success
} // ifdcf77_init
// called every 100msec for DCF77 output
void DCF_Ticker() {
static uint8_t DCF_Frame[DCF77_FRAME_SIZE];
static uint8_t bit = 0;
static uint8_t pulse = 0;
static bool BitsPending = false;
while (BitsPending) {
switch (pulse++) {
case 0: // start of second -> start of timeframe for logic signal
if (DCF_Frame[bit] != dcf_off)
digitalWrite(HAS_DCF77, LOW);
return;
case 1: // 100ms after start of second -> end of timeframe for logic 0
if (DCF_Frame[bit] == dcf_zero)
digitalWrite(HAS_DCF77, HIGH);
return;
case 2: // 200ms after start of second -> end of timeframe for logic signal
digitalWrite(HAS_DCF77, HIGH);
return;
case 9: // last pulse before next second starts
pulse = 0;
if (bit++ != DCF77_FRAME_SIZE)
return;
else { // last pulse of DCF77 frame (59th second)
bit = 0;
BitsPending = false;
};
break;
}; // switch
}; // while
// get next frame to send from queue
if (xQueueReceive(DCFSendQueue, &DCF_Frame, (TickType_t)0) == pdTRUE)
BitsPending = true;
} // DCF_Ticker()
void dcf77_loop(void *pvParameters) {
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
// 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
NULL,
portMAX_DELAY); // wait forever (missing error handling here...)
DCF_Ticker();
}
vTaskDelete(DCF77Task); // shoud never be reached
} // dcf77_loop()
uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos,
uint8_t pArray[]) {
uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10);
uint8_t parity = 0;
for (uint8_t n = startpos; n <= endpos; n++) {
pArray[n] = (data & 1) ? dcf_one : dcf_zero;
parity += (data & 1);
data >>= 1;
}
return parity;
}
void enqueueTimeframe(time_t t) {
uint8_t ParityCount;
// 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 switching second!
/*
In unregelmäßigen Zeitabständen muss eine Schaltsekunde eingefügt werden. Dies
ist dadurch bedingt, dass sich die Erde nicht genau in 24 Stunden um sich
selbst dreht. Auf die koordinierte Weltzeitskala UTC bezogen, wird diese
Korrektur zum Ende der letzten Stunde des 31. Dezember oder 30. Juni
vorgenommen. In Mitteleuropa muss die Schaltsekunde daher am 1. Januar um 1.00
Uhr MEZ oder am 1.Juli um 2.00 MESZ eingeschoben werden. Zu den genannten
Zeiten werden daher 61 Sekunden gesendet.
*/
// post generated DCFtimeframe data to DCF SendQueue
if (xQueueSendToBack(DCFSendQueue, (void *)&DCFtimeframe[0], (TickType_t)0) !=
pdPASS)
ESP_LOGE(TAG, "Failed to send DCF data");
// 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=%s", out);
}
void sendDCF77() {
time_t t = now();
/*
if (second(t) > 56) {
delay(30000);
return;
}
*/
// enqueue DCF timeframes for each i minute
for (uint8_t i = 0; i < DCF_FRAME_QUEUE_SIZE; i++)
enqueueTimeframe(t + i * 60);
/*
// how many to the minute end ?
// don't forget that we begin transmission at second 58
delay((58 - second(t)) * 1000);
// three minutes are needed to transmit all the packet
// then wait more 30 secs to locate safely at the half of minute
// NB 150+60=210sec, 60secs are lost from main routine
delay(150000);
*/
} // Ende ReadAndDecodeTime()
// interrupt service routine triggered each 100ms by ESP32 hardware timer
void IRAM_ATTR DCF77IRQ() {
xTaskNotifyFromISR(DCF77Task, xTaskGetTickCountFromISR(), eSetBits, NULL);
portYIELD_FROM_ISR();
}
#endif // HAS_DCF77

View File

@ -145,12 +145,14 @@ void refreshtheDisplay() {
uint8_t msgWaiting; uint8_t msgWaiting;
char buff[16]; // 16 chars line buffer char buff[16]; // 16 chars line buffer
#if defined HAS_RTC || defined HAS_GPS
const char timeNosyncSymbol = '?'; const char timeNosyncSymbol = '?';
#ifdef HAS_IF482 #if defined HAS_IF482 || defined HAS_DCF77
const char timesyncSymbol = '+'; const char timesyncSymbol = '+';
#else #else
const char timesyncSymbol = '*'; const char timesyncSymbol = '*';
#endif #endif
#endif // HAS_RTC
// update counter (lines 0-1) // update counter (lines 0-1)
snprintf( snprintf(
@ -214,17 +216,16 @@ void refreshtheDisplay() {
u8x8.printf("%4dKB", getFreeRAM() / 1024); u8x8.printf("%4dKB", getFreeRAM() / 1024);
#ifdef HAS_LORA #ifdef HAS_LORA
u8x8.setCursor(0, 6); u8x8.setCursor(0, 6);
#ifndef HAS_RTC #if (!defined HAS_RTC) && (!defined HAS_GPS)
// update LoRa status display (line 6) // update LoRa status display (line 6)
u8x8.printf("%-16s", display_line6); u8x8.printf("%-16s", display_line6);
#else #else // HAS_RTC or HAS_GPS
// update time/date display (line 6) // 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;
#ifdef RTC_INT // make timestatus symbol blinking #ifdef RTC_INT // make timestatus symbol blinking if pps line
if (second(t) % 2) if (second(t) % 2)
timeState = ' '; timeState = ' ';
#endif // RTC_INT #endif // RTC_INT

View File

@ -73,7 +73,7 @@ time_t get_gpstime(void) {
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(), 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: %d/%d/%d %d:%d:%d", year(t), month(t), day(t), ESP_LOGD(TAG, "GPS time: %4d/%02d/%02d %02d:%02d:%02d", year(t), month(t), day(t),
hour(t), minute(t), second(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");

View File

@ -55,6 +55,9 @@
// 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
// Settings for DCF77 interface
#define HAS_DCF77 GPIO_NUM_13
// Pins for LORA chip SPI interface, reset line and interrupt lines // Pins for LORA chip SPI interface, reset line and interrupt lines
#define LORA_SCK (5) #define LORA_SCK (5)
#define LORA_CS (18) #define LORA_CS (18)

View File

@ -28,7 +28,8 @@ Uused tasks and timers:
Task Core Prio Purpose Task Core Prio Purpose
==================================================================================== ====================================================================================
ledloop 0 3 blinks LEDs ledloop 0 3 blinks LEDs
if482loop 1 3 serial feed of IF482 time telegrams 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
@ -46,13 +47,13 @@ Tasks using i2c bus all must have same priority, because using mutex semaphore
ESP32 hardware timers ESP32 hardware timers
================================ ================================
0 triggers display refresh 0 triggers display refresh
1 unused (reserved for DCF77) 1 triggers DCF77 clock signal
2 triggers send payload cycle 2 triggers send payload cycle
3 triggers housekeeping cycle 3 triggers housekeeping cycle
RTC hardware timer (if present) RTC hardware timer (if present)
================================ ================================
triggers IF482 clock generator triggers IF482 clock signal
*/ */
@ -64,8 +65,12 @@ 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
hw_timer_t *channelSwitch = NULL, *sendCycle = NULL, *homeCycle = NULL,
*displaytimer = NULL; // irq tasks hw_timer_t *sendCycle = NULL, *homeCycle = NULL;
#ifdef HAS_DISPLAY
hw_timer_t *displaytimer = NULL;
#endif
TaskHandle_t irqHandlerTask; TaskHandle_t irqHandlerTask;
SemaphoreHandle_t I2Caccess; SemaphoreHandle_t I2Caccess;
@ -327,6 +332,16 @@ void setup() {
setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60); setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60);
#endif // HAS_RTC #endif // HAS_RTC
#if defined HAS_DCF77
strcat_P(features, " DCF77");
assert(dcf77_init());
#endif
#if defined HAS_IF482 && defined RTC_INT
strcat_P(features, " IF482");
assert(if482_init());
#endif
// show compiled features // show compiled features
ESP_LOGI(TAG, "Features:%s", features); ESP_LOGI(TAG, "Features:%s", features);
@ -403,8 +418,6 @@ void setup() {
#endif #endif
#if defined HAS_IF482 && defined RTC_INT #if defined HAS_IF482 && defined RTC_INT
strcat_P(features, " IF482");
assert(if482_init());
ESP_LOGI(TAG, "Starting IF482 Generator..."); ESP_LOGI(TAG, "Starting IF482 Generator...");
xTaskCreatePinnedToCore(if482_loop, // task function xTaskCreatePinnedToCore(if482_loop, // task function
"if482loop", // name of task "if482loop", // name of task
@ -419,6 +432,24 @@ void setup() {
attachInterrupt(digitalPinToInterrupt(RTC_INT), IF482IRQ, FALLING); attachInterrupt(digitalPinToInterrupt(RTC_INT), IF482IRQ, FALLING);
#endif #endif
#if defined HAS_DCF77
ESP_LOGI(TAG, "Starting DCF77 Generator...");
xTaskCreatePinnedToCore(dcf77_loop, // task function
"dcf77loop", // name of task
2048, // stack size of task
(void *)1, // parameter of the task
3, // priority of the task
&DCF77Task, // task handle
0); // CPU core
// setup 100ms clock signal for DCF77 generator using esp32 hardware timer 1
assert(DCF77Task != NULL); // has dcf77 task started?
dcfCycle = timerBegin(1, 8000, true);
timerAttachInterrupt(dcfCycle, &DCF77IRQ, true);
timerAlarmWrite(dcfCycle, 1000, true);
timerAlarmEnable(dcfCycle);
#endif
} // setup() } // setup()
void loop() { void loop() {