ESP32-PaxCounter/src/dcf77.cpp

219 lines
6.3 KiB
C++
Raw Normal View History

//
// 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