added DCF77 function (experimental, not fully working yet)
This commit is contained in:
parent
9cb828bfbd
commit
39e2df7a05
@ -11,11 +11,14 @@
|
||||
#include "bme680mems.h"
|
||||
#endif
|
||||
|
||||
// Needed for RTC time sync if RTC present on board
|
||||
#ifdef HAS_RTC
|
||||
#include "rtctime.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAS_DCF77
|
||||
#include "dcf77.h"
|
||||
#endif
|
||||
|
||||
void doHousekeeping(void);
|
||||
uint64_t uptime(void);
|
||||
void reset_counters(void);
|
||||
|
16
include/dcf77.h
Normal file
16
include/dcf77.h
Normal 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
|
@ -2,7 +2,6 @@
|
||||
#define _IF482_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "irqhandler.h"
|
||||
|
||||
extern TaskHandle_t IF482Task;
|
||||
|
||||
|
@ -60,6 +60,10 @@ void doHousekeeping() {
|
||||
ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d",
|
||||
uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask));
|
||||
#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)
|
||||
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);
|
||||
#endif
|
||||
|
||||
// generate DCF77 timeframes
|
||||
#ifdef HAS_DCF77
|
||||
sendDCF77();
|
||||
#endif
|
||||
|
||||
// check free heap memory
|
||||
if (ESP.getMinFreeHeap() <= MEM_LOW) {
|
||||
ESP_LOGI(TAG,
|
||||
@ -86,8 +95,8 @@ void doHousekeeping() {
|
||||
"free heap = %d bytes)",
|
||||
ESP.getMinFreeHeap(), ESP.getFreeHeap());
|
||||
SendPayload(COUNTERPORT, prio_high); // send data before clearing counters
|
||||
reset_counters(); // clear macs container and reset all counters
|
||||
get_salt(); // get new salt for salting hashes
|
||||
reset_counters(); // clear macs container and reset all counters
|
||||
get_salt(); // get new salt for salting hashes
|
||||
|
||||
if (ESP.getMinFreeHeap() <= MEM_LOW) // check again
|
||||
do_reset(); // memory leak, reset device
|
||||
@ -98,8 +107,8 @@ void doHousekeeping() {
|
||||
if (ESP.getMinFreePsram() <= MEM_LOW) {
|
||||
ESP_LOGI(TAG, "PSRAM full, counter cleared");
|
||||
SendPayload(COUNTERPORT, prio_high); // send data before clearing counters
|
||||
reset_counters(); // clear macs container and reset all counters
|
||||
get_salt(); // get new salt for salting hashes
|
||||
reset_counters(); // clear macs container and reset all counters
|
||||
get_salt(); // get new salt for salting hashes
|
||||
|
||||
if (ESP.getMinFreePsram() <= MEM_LOW) // check again
|
||||
do_reset(); // memory leak, reset device
|
||||
|
219
src/dcf77.cpp
Normal file
219
src/dcf77.cpp
Normal 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
|
@ -145,12 +145,14 @@ void refreshtheDisplay() {
|
||||
|
||||
uint8_t msgWaiting;
|
||||
char buff[16]; // 16 chars line buffer
|
||||
#if defined HAS_RTC || defined HAS_GPS
|
||||
const char timeNosyncSymbol = '?';
|
||||
#ifdef HAS_IF482
|
||||
#if defined HAS_IF482 || defined HAS_DCF77
|
||||
const char timesyncSymbol = '+';
|
||||
#else
|
||||
const char timesyncSymbol = '*';
|
||||
#endif
|
||||
#endif // HAS_RTC
|
||||
|
||||
// update counter (lines 0-1)
|
||||
snprintf(
|
||||
@ -214,17 +216,16 @@ void refreshtheDisplay() {
|
||||
u8x8.printf("%4dKB", getFreeRAM() / 1024);
|
||||
|
||||
#ifdef HAS_LORA
|
||||
|
||||
u8x8.setCursor(0, 6);
|
||||
#ifndef HAS_RTC
|
||||
#if (!defined HAS_RTC) && (!defined HAS_GPS)
|
||||
// update LoRa status display (line 6)
|
||||
u8x8.printf("%-16s", display_line6);
|
||||
#else
|
||||
#else // HAS_RTC or HAS_GPS
|
||||
// update time/date display (line 6)
|
||||
time_t t = myTZ.toLocal(now());
|
||||
char timeState =
|
||||
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)
|
||||
timeState = ' ';
|
||||
#endif // RTC_INT
|
||||
|
@ -73,7 +73,7 @@ time_t get_gpstime(void) {
|
||||
if ((gps.time.age() < 1500) && (gps.time.isValid())) {
|
||||
t = tmConvert_t(gps.date.year(), gps.date.month(), gps.date.day(),
|
||||
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));
|
||||
} else {
|
||||
ESP_LOGW(TAG, "GPS has no confident time");
|
||||
|
@ -55,6 +55,9 @@
|
||||
// Settings for IF482 interface
|
||||
#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
|
||||
#define LORA_SCK (5)
|
||||
#define LORA_CS (18)
|
||||
|
45
src/main.cpp
45
src/main.cpp
@ -28,7 +28,8 @@ Uused tasks and timers:
|
||||
Task Core Prio Purpose
|
||||
====================================================================================
|
||||
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
|
||||
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
|
||||
================================
|
||||
0 triggers display refresh
|
||||
1 unused (reserved for DCF77)
|
||||
1 triggers DCF77 clock signal
|
||||
2 triggers send payload cycle
|
||||
3 triggers housekeeping cycle
|
||||
|
||||
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
|
||||
uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0,
|
||||
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;
|
||||
SemaphoreHandle_t I2Caccess;
|
||||
|
||||
@ -327,6 +332,16 @@ void setup() {
|
||||
setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60);
|
||||
#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
|
||||
ESP_LOGI(TAG, "Features:%s", features);
|
||||
|
||||
@ -403,8 +418,6 @@ void setup() {
|
||||
#endif
|
||||
|
||||
#if defined HAS_IF482 && defined RTC_INT
|
||||
strcat_P(features, " IF482");
|
||||
assert(if482_init());
|
||||
ESP_LOGI(TAG, "Starting IF482 Generator...");
|
||||
xTaskCreatePinnedToCore(if482_loop, // task function
|
||||
"if482loop", // name of task
|
||||
@ -419,6 +432,24 @@ void setup() {
|
||||
attachInterrupt(digitalPinToInterrupt(RTC_INT), IF482IRQ, FALLING);
|
||||
#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()
|
||||
|
||||
void loop() {
|
||||
|
Loading…
Reference in New Issue
Block a user