diff --git a/.gitignore b/.gitignore index 05d15bc..0d4cdc7 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,7 @@ src/sim/cnetz_sim src/magnetic/cnetz_magnetic src/fuvst/fuvst src/fuvst/fuvst_sniffer +src/dcf77/dcf77 extra/cnetz_memory_card_generator src/test/test_filter src/test/test_sendevolumenregler diff --git a/README b/README index 474650f..7921371 100644 --- a/README +++ b/README @@ -27,8 +27,7 @@ Additionally the following communication services are implemented: * Analog Modem Emulation (AM7911) * German classic 'Zeitansage' (talking clock) * POCSAG transmitter / receiver - * 5-Ton-Folge + Sirenensteuerung - + * DCF77 time signal transmitter and receiver USE AT YOUR OWN RISK! diff --git a/configure.ac b/configure.ac index e82f821..236d945 100644 --- a/configure.ac +++ b/configure.ac @@ -114,6 +114,7 @@ AC_OUTPUT( src/sim/Makefile src/magnetic/Makefile src/fuvst/Makefile + src/dcf77/Makefile src/test/Makefile src/Makefile extra/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 665a6a8..034fd4c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -62,7 +62,8 @@ SUBDIRS += \ zeitansage \ sim \ magnetic \ - fuvst + fuvst \ + dcf77 if HAVE_ALSA if HAVE_FUSE diff --git a/src/dcf77/Makefile.am b/src/dcf77/Makefile.am new file mode 100644 index 0000000..22c133d --- /dev/null +++ b/src/dcf77/Makefile.am @@ -0,0 +1,23 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +if HAVE_ALSA +bin_PROGRAMS = \ + dcf77 + +dcf77_SOURCES = \ + dcf77.c \ + main.c +dcf77_LDADD = \ + $(COMMON_LA) \ + $(top_builddir)/src/liboptions/liboptions.a \ + $(top_builddir)/src/libdebug/libdebug.a \ + $(top_builddir)/src/libdisplay/libdisplay.a \ + $(top_builddir)/src/libfilter/libfilter.a \ + $(top_builddir)/src/libwave/libwave.a \ + $(top_builddir)/src/libsample/libsample.a \ + $(top_builddir)/src/libsound/libsound.a \ + $(top_builddir)/src/libaaimage/libaaimage.a \ + $(ALSA_LIBS) \ + -lm +endif + diff --git a/src/dcf77/dcf77.c b/src/dcf77/dcf77.c new file mode 100644 index 0000000..81b41e4 --- /dev/null +++ b/src/dcf77/dcf77.c @@ -0,0 +1,585 @@ + +/* implementation of DCF77 transmitter and receiver + * + * (C) 2022 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "dcf77.h" + +double get_time(void); + +#define CARRIER_FREQUENCY 77500 +#define TEST_FREQUENCY 1000 +#define CARRIER_BANDWIDTH 10.0 +#define SAMPLE_CLOCK 1000 +#define CLOCK_1S 1.0 +#define CLOCK_BANDWIDTH 0.1 +#define REDUCTION_FACTOR 0.15 +#define REDUCTION_TH 0.575 +#define TX_LEVEL 0.9 + +#define level2db(level) (20 * log10(level)) + +static int fast_math = 0; +static float *sin_tab = NULL, *cos_tab = NULL; + +const char *time_zone[4] = { "???", "CEST", "CET", "???" }; +const char *week_day[8] = { "???", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; +const char *month_name[13] = { "???", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +/* global init */ +int dcf77_init(int _fast_math) +{ + fast_math = _fast_math; + + if (fast_math) { + int i; + + sin_tab = calloc(65536+16384, sizeof(*sin_tab)); + if (!sin_tab) { + fprintf(stderr, "No mem!\n"); + return -ENOMEM; + } + cos_tab = sin_tab + 16384; + + /* generate sine and cosine */ + for (i = 0; i < 65536+16384; i++) + sin_tab[i] = sin(2.0 * M_PI * (double)i / 65536.0); + } + + return 0; +} + +/* global exit */ +void dcf77_exit(void) +{ + if (sin_tab) { + free(sin_tab); + sin_tab = cos_tab = NULL; + } +} + +dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone) +{ + dcf77_t *dcf77 = NULL; + dcf77_tx_t *tx; + dcf77_rx_t *rx; + + dcf77 = calloc(1, sizeof(*dcf77)); + if (!dcf77) { + PDEBUG(DDCF77, DEBUG_ERROR, "No mem!\n"); + return NULL; + } + tx = &dcf77->tx; + rx = &dcf77->rx; + + /* measurement */ + display_wave_init(&dcf77->dispwav, (double)samplerate, "DCF77"); + display_measurements_init(&dcf77->dispmeas, samplerate, "DCF77"); + + /* prepare tx */ + if (use_tx) { + tx->enable = 1; + if (fast_math) + tx->phase_360 = 65536.0; + else + tx->phase_360 = 2.0 * M_PI; + + /* carrier generation */ + tx->carrier_phase_step = tx->phase_360 * (double)CARRIER_FREQUENCY / ((double)samplerate); + tx->test_phase_step = tx->phase_360 * (double)TEST_FREQUENCY / ((double)samplerate); + tx->waves_0 = CARRIER_FREQUENCY / 10; + tx->waves_1 = CARRIER_FREQUENCY / 5; + tx->waves_sec = CARRIER_FREQUENCY; + + tx->test_tone = test_tone; + } + + /* prepare rx */ + if (use_rx) { + rx->enable = 1; + if (fast_math) + rx->phase_360 = 65536.0; + else + rx->phase_360 = 2.0 * M_PI; + + /* carrier filter */ + rx->carrier_phase_step = rx->phase_360 * (double)CARRIER_FREQUENCY / ((double)samplerate); + /* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */ + iir_lowpass_init(&rx->carrier_lp[0], CARRIER_BANDWIDTH, (double)samplerate, 2); + iir_lowpass_init(&rx->carrier_lp[1], CARRIER_BANDWIDTH, (double)samplerate, 2); + + /* signal rate */ + rx->sample_step = (double)SAMPLE_CLOCK / (double)samplerate; + + /* delay buffer */ + rx->delay_size = ceil((double)SAMPLE_CLOCK * 0.1); + rx->delay_buffer = calloc(rx->delay_size, sizeof(*rx->delay_buffer)); + if (!rx->delay_buffer) { + PDEBUG(DDCF77, DEBUG_ERROR, "No mem!\n"); + return NULL; + } + + /* count clock signal */ + rx->clock_count = -1; + + /* measurement parameters */ + dcf77->dmp_input_level = display_measurements_add(&dcf77->dispmeas, "Input Level", "%.0f dB", DISPLAY_MEAS_AVG, DISPLAY_MEAS_LEFT, -100.0, 0.0, -INFINITY); + dcf77->dmp_signal_level = display_measurements_add(&dcf77->dispmeas, "Signal Level", "%.0f dB", DISPLAY_MEAS_AVG, DISPLAY_MEAS_LEFT, -100.0, 0.0, -INFINITY); + dcf77->dmp_signal_quality = display_measurements_add(&dcf77->dispmeas, "Signal Qualtiy", "%.0f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 100.0, -INFINITY); + } + + if (tx->enable) + PDEBUG(DDCF77, DEBUG_INFO, "DCF77 transmitter has been created.\n"); + if (rx->enable) + PDEBUG(DDCF77, DEBUG_INFO, "DCF77 receiver has been created.\n"); + + return dcf77; +} + +void dcf77_destroy(dcf77_t *dcf77) +{ + if (dcf77) { + dcf77_rx_t *rx = &dcf77->rx; + free(rx->delay_buffer); + free(dcf77); + } + + PDEBUG(DDCF77, DEBUG_INFO, "DCF77 has been destroyed.\n"); +} + +/* set inital time stamp at the moment the stream starts */ +void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp) +{ + dcf77_tx_t *tx = &dcf77->tx; + double now; + time_t t; + + /* get time stamp */ + if (timestamp < 0) + now = get_time(); + else + now = timestamp; + t = floor(now); + /* current second within minute */ + tx->second = t % 60; + /* time stamp of next minute */ + tx->timestamp = t - tx->second + 60; + /* wave within current second */ + tx->wave = floor(fmod(now, 1.0) * (double)tx->waves_sec); + /* silence until next second begins */ + tx->symbol = 'm'; tx->level = 0; +} + +static char tx_symbol(dcf77_t *dcf77, time_t timestamp, int second) +{ + dcf77_tx_t *tx = &dcf77->tx; + char symbol; + + /* generate frame */ + if (second == 0 || !tx->data_frame) { + struct tm *tm; + int isdst_next_hour, wday, zone; + uint64_t frame = 0, p; + + timestamp += 3600; + tm = localtime(×tamp); + timestamp -= 3600; + if (!tm) { +error_tm: + PDEBUG(DDCF77, DEBUG_ERROR, "Failed to get local time of time stamp!\n"); + return 'm'; + } + isdst_next_hour = tm->tm_isdst; + tm = localtime(×tamp); + if (!tm) + goto error_tm; + + if (tm->tm_wday > 0) + wday = tm->tm_wday; + else + wday = 7; + + if (tm->tm_isdst > 0) + zone = 1; + else + zone = 2; + + PDEBUG(DDCF77, DEBUG_NOTICE, "The time transmitting: %s %s %d %02d:%02d:00 %s %02d\n", week_day[wday], month_name[tm->tm_mon + 1], tm->tm_mday, tm->tm_hour, tm->tm_min, time_zone[zone], tm->tm_year + 1900); + + if ((tm->tm_isdst > 0) != (isdst_next_hour > 0)) + frame |= (uint64_t)1 << 16; + if (tm->tm_isdst > 0) + frame |= (uint64_t)1 << 17; + else + frame |= (uint64_t)2 << 17; + frame |= 1 << 20; + + frame |= (uint64_t)(tm->tm_min % 10) << 21; + frame |= (uint64_t)(tm->tm_min / 10) << 25; + p = (frame >> 21) & 0x7f; + p = p ^ (p >> 4); + p = p ^ (p >> 2); + p = p ^ (p >> 1); + frame |= (uint64_t)(p & 1) << 28; + + frame |= (uint64_t)(tm->tm_hour % 10) << 29; + frame |= (uint64_t)(tm->tm_hour / 10) << 33; + p = (frame >> 29) & 0x3f; + p = p ^ (p >> 4); + p = p ^ (p >> 2); + p = p ^ (p >> 1); + frame |= (uint64_t)(p & 1) << 35; + + frame |= (uint64_t)(tm->tm_mday % 10) << 36; + frame |= (uint64_t)(tm->tm_mday / 10) << 40; + frame |= (uint64_t)(wday) << 42; + frame |= (uint64_t)((tm->tm_mon + 1) % 10) << 45; + frame |= (uint64_t)((tm->tm_mon + 1) / 10) << 49; + frame |= (uint64_t)(tm->tm_year % 10) << 50; + frame |= (uint64_t)((tm->tm_year / 10) % 10) << 54; + p = (frame >> 36) & 0x3fffff; + p = p ^ (p >> 16); + p = p ^ (p >> 8); + p = p ^ (p >> 4); + p = p ^ (p >> 2); + p = p ^ (p >> 1); + frame |= (uint64_t)(p & 1) << 58; + + tx->data_frame = frame; + } + + if (second == 59) + symbol = 'm'; + else symbol = ((tx->data_frame >> second) & 1) + '0'; + + PDEBUG(DDSP, DEBUG_DEBUG, "Trasmitting symbol '%c' (Bit %d)\n", symbol, second); + + return symbol; +} + +void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length) +{ + dcf77_tx_t *tx = &dcf77->tx; + double carrier_phase, test_phase; + int i; + + if (!tx->enable) { + memset(samples, 0, sizeof(*samples) * length); + return; + } + + carrier_phase = tx->carrier_phase; + test_phase = tx->test_phase; + for (i = 0; i < length; i++) { + if (fast_math) + samples[i] = sin_tab[(uint16_t)carrier_phase] * tx->level; + else + samples[i] = sin(carrier_phase) * tx->level; + carrier_phase += tx->carrier_phase_step; + if (carrier_phase >= tx->phase_360) { + carrier_phase -= tx->phase_360; + tx->wave++; + if (tx->wave >= tx->waves_sec) { + tx->wave -= tx->waves_sec; + if (++tx->second == 60) { + tx->second = 0; + tx->timestamp += 60; + } + tx->symbol = tx_symbol(dcf77, tx->timestamp, tx->second); + } + switch (tx->symbol) { + case '0': + if (tx->wave < tx->waves_0) + tx->level = TX_LEVEL * REDUCTION_FACTOR; + else + tx->level = TX_LEVEL; + break; + case '1': + if (tx->wave < tx->waves_1) + tx->level = TX_LEVEL * REDUCTION_FACTOR; + else + tx->level = TX_LEVEL; + break; + case 'm': + tx->level = TX_LEVEL; + break; + } + if (tx->test_tone) + tx->level *= 0.9; /* 90 % */ + } + if (tx->test_tone) { + if (fast_math) + samples[i] += sin_tab[(uint16_t)test_phase] * tx->level / 10.0; /* 10 % */ + else + samples[i] += sin(test_phase) * tx->level / 10.0; /* 10 % */ + if (test_phase >= tx->phase_360) + test_phase -= tx->phase_360; + test_phase += tx->test_phase_step; + } + } + tx->carrier_phase = carrier_phase; + tx->test_phase = test_phase; +} + +static void rx_frame(uint64_t frame) +{ + int zone; + int minute_one, minute_ten, minute = -1; + int hour_one, hour_ten, hour = -1; + int day_one, day_ten, day = -1; + int wday = -1; + int month_one, month_ten, month = -1; + int year_one, year_ten, year = -1; + uint64_t p; + + PDEBUG(DFRAME, DEBUG_INFO, "Bit 0 is '0'? : %s\n", ((frame >> 0) & 1) ? "no" : "yes"); + PDEBUG(DFRAME, DEBUG_INFO, "Bits 1..14 : 0x%04x\n", (int)(frame >> 1) & 0x3fff); + PDEBUG(DFRAME, DEBUG_INFO, "Call Bit : %d\n", (int)(frame >> 15) & 1); + PDEBUG(DFRAME, DEBUG_INFO, "Change Time Zone : %s\n", ((frame >> 16) & 1) ? "yes" : "no"); + zone = ((frame >> 17) & 3); + PDEBUG(DFRAME, DEBUG_INFO, "Time Zone : %s\n", time_zone[zone]); + PDEBUG(DFRAME, DEBUG_INFO, "Add Leap Second : %s\n", ((frame >> 19) & 1) ? "yes" : "no"); + PDEBUG(DFRAME, DEBUG_INFO, "Bit 20 is '1'? : %s\n", ((frame >> 20) & 1) ? "yes" : "no"); + + minute_one = (frame >> 21 & 0xf); + minute_ten = ((frame >> 25) & 0x7); + p = (frame >> 21) & 0xff; + p = p ^ (p >> 4); + p = p ^ (p >> 2); + p = p ^ (p >> 1); + if (minute_one > 9 || minute_ten > 5 || (p & 1)) + PDEBUG(DFRAME, DEBUG_INFO, "Minute : ???\n"); + else { + minute = minute_ten * 10 + minute_one; + PDEBUG(DFRAME, DEBUG_INFO, "Minute : %02d\n", minute); + } + + hour_one = (frame >> 29 & 0xf); + hour_ten = ((frame >> 33) & 0x3); + p = (frame >> 29) & 0x7f; + p = p ^ (p >> 4); + p = p ^ (p >> 2); + p = p ^ (p >> 1); + if (hour_one > 9 || hour_ten > 2 || (hour_ten == 2 && hour_one > 3) || (p & 1)) + PDEBUG(DFRAME, DEBUG_INFO, "Hour : ???\n"); + else { + hour = hour_ten * 10 + hour_one; + PDEBUG(DFRAME, DEBUG_INFO, "Hour : %02d\n", hour); + } + + day_one = (frame >> 36 & 0xf); + day_ten = ((frame >> 40) & 0x3); + wday = (frame >> 42 & 0x7); + month_one = (frame >> 45 & 0xf); + month_ten = ((frame >> 49) & 0x1); + year_one = (frame >> 50 & 0xf); + year_ten = ((frame >> 54) & 0xf); + p = (frame >> 36) & 0x7fffff; + p = p ^ (p >> 16); + p = p ^ (p >> 8); + p = p ^ (p >> 4); + p = p ^ (p >> 2); + p = p ^ (p >> 1); + if (day_one > 9 || day_ten > 3 || (day_ten == 3 && day_one > 1) || (day_ten == 0 && day_one == 0) || (p & 1)) + PDEBUG(DFRAME, DEBUG_INFO, "Day : ???\n"); + else { + day = day_ten * 10 + day_one; + PDEBUG(DFRAME, DEBUG_INFO, "Day : %d\n", day); + } + if (wday < 1 || wday > 7 || (p & 1)) { + PDEBUG(DFRAME, DEBUG_INFO, "Week Day : ???\n"); + wday = -1; + } else + PDEBUG(DFRAME, DEBUG_INFO, "Week Day : %s\n", week_day[wday]); + if (month_one > 9 || month_ten > 1 || (month_ten == 1 && month_one > 2) || (month_ten == 0 && month_one == 0) || (p & 1)) + PDEBUG(DFRAME, DEBUG_INFO, "Month : ???\n"); + else { + month = month_ten * 10 + month_one; + PDEBUG(DFRAME, DEBUG_INFO, "Month : %d\n", month); + } + if (year_one > 9 || year_ten > 9 || (p & 1)) + PDEBUG(DFRAME, DEBUG_INFO, "Year : ???\n"); + else { + year = year_ten * 10 + year_one; + PDEBUG(DFRAME, DEBUG_INFO, "Year : %02d\n", year); + } + + if (minute >= 0 && hour >= 0 && day >= 0 && wday >= 0 && month >= 0 && year >= 0) + PDEBUG(DDCF77, DEBUG_NOTICE, "The received time is: %s %s %d %02d:%02d:00 %s 20%02d\n", week_day[wday], month_name[month], day, hour, minute, time_zone[zone], year); + else + PDEBUG(DDCF77, DEBUG_NOTICE, "The received time is invalid!\n"); +} + +static void rx_symbol(dcf77_t *dcf77, char symbol) +{ + dcf77_rx_t *rx = &dcf77->rx; + + PDEBUG(DDSP, DEBUG_DEBUG, "Received symbol '%c'\n", symbol); + + if (!rx->data_receive) { + if (symbol == 'm') { + PDEBUG(DDSP, DEBUG_INFO, "Reception of frame has started\n"); + rx->data_receive = 1; + rx->data_index = 0; + } + } else { + if (symbol == 'm') { + if (rx->data_index == 59) { + rx->data_string[rx->data_index] = '\0'; + rx->data_index = 0; + PDEBUG(DDSP, DEBUG_INFO, "Received complete frame: %s (0x%016" PRIx64 ")\n", rx->data_string, rx->data_frame); + rx_frame(rx->data_frame); + } else { + PDEBUG(DDSP, DEBUG_INFO, "Short read, frame too short\n"); + rx->data_index = 0; + } + } else { + if (rx->data_index == 59) { + PDEBUG(DDSP, DEBUG_INFO, "Long read, frame too long\n"); + rx->data_receive = 0; + } else { + rx->data_string[rx->data_index++] = symbol; + rx->data_frame >>= 1; + rx->data_frame |= (uint64_t)(symbol & 1) << 58; + } + } + } +} + +//#define DEBUG_SAMPLE + +void dcf77_decode(dcf77_t *dcf77, sample_t *samples, int length) +{ + dcf77_rx_t *rx = &dcf77->rx; + sample_t I[length], Q[length]; + double phase, level, delayed_level, reduction, quality; + int i; + + display_wave(&dcf77->dispwav, samples, length, 1.0); + + if (!rx->enable) + return; + + /* rotate spectrum */ + phase = rx->carrier_phase; + for (i = 0; i < length; i++) { + /* mix with carrier frequency */ + if (fast_math) { + I[i] = cos_tab[(uint16_t)phase] * samples[i]; + Q[i] = sin_tab[(uint16_t)phase] * samples[i]; + } else { + I[i] = cos(phase) * samples[i]; + Q[i] = sin(phase) * samples[i]; + } + phase += rx->carrier_phase_step; + if (phase >= rx->phase_360) + phase -= rx->phase_360; + } + rx->carrier_phase = phase; + + level = sqrt(I[0] * I[0] + Q[0] * Q[0]); + if (level > 0.0) // don't average with level of 0.0 (-inf dB) + display_measurements_update(dcf77->dmp_input_level, level2db(level), 0.0); + + /* filter carrier */ + iir_process(&rx->carrier_lp[0], I, length); + iir_process(&rx->carrier_lp[1], Q, length); + + for (i = 0; i < length; i++) { + rx->sample_counter += rx->sample_step; + if (rx->sample_counter >= 1.0) { + rx->sample_counter -= 1.0; + /* level */ + level = sqrt(I[i] * I[i] + Q[i] * Q[i]); + if (level > 0.0) // don't average with level of 0.0 (-inf dB) + display_measurements_update(dcf77->dmp_signal_level, level2db(level), 0.0); + +#ifdef DEBUG_SAMPLE + printf("%s amplitude= %.6f\n", debug_amplitude(level/rx->value_level), level/rx->value_level); +#endif + + /* delay sample */ + delayed_level = rx->delay_buffer[rx->delay_index]; + rx->delay_buffer[rx->delay_index] = level; + if (++rx->delay_index == rx->delay_size) + rx->delay_index = 0; + + if (rx->clock_count < 0 || rx->clock_count > 900) { + if (level / delayed_level < REDUCTION_TH) + rx->clock_count = 0; + } + if (rx->clock_count >= 0) { + if (rx->clock_count == 0) { +#ifdef DEBUG_SAMPLE + puts("got clock"); +#endif + rx->value_level = delayed_level; + } + if (rx->clock_count == 50) { +#ifdef DEBUG_SAMPLE + puts("*short*"); +#endif + rx->value_short = level; + reduction = rx->value_short / rx->value_level; + if (reduction < REDUCTION_TH) { +#ifdef DEBUG_SAMPLE + printf("reduction is %.3f\n", reduction); +#endif + if (reduction < REDUCTION_FACTOR) + reduction = REDUCTION_FACTOR; + quality = 1.0 - (reduction - REDUCTION_FACTOR) / (REDUCTION_TH - REDUCTION_FACTOR); + display_measurements_update(dcf77->dmp_signal_quality, quality * 100.0, 0.0); + } + } + if (rx->clock_count == 150) { +#ifdef DEBUG_SAMPLE + puts("*long*"); +#endif + rx->value_long = level; + if (rx->value_long / rx->value_level < REDUCTION_TH) + rx_symbol(dcf77, '1'); + else + rx_symbol(dcf77, '0'); + } + if (rx->clock_count == 1100) { +#ifdef DEBUG_SAMPLE + puts("*missing clock*"); +#endif + rx->clock_count = -1; + rx_symbol(dcf77, 'm'); + } + } + if (rx->clock_count >= 0) + rx->clock_count++; + + + } + } +} + + + diff --git a/src/dcf77/dcf77.h b/src/dcf77/dcf77.h new file mode 100644 index 0000000..123e081 --- /dev/null +++ b/src/dcf77/dcf77.h @@ -0,0 +1,57 @@ + +#include "../libsample/sample.h" +#include "../libfilter/iir_filter.h" +#include "../libdisplay/display.h" +#include + +typedef struct dcf77_tx { + int enable; + double phase_360; + double carrier_phase, carrier_phase_step; /* uncorrected phase */ + double test_phase, test_phase_step; + double level; + int wave, waves_0, waves_1, waves_sec; + time_t timestamp; + int second; + char symbol; + uint64_t data_frame; + int test_tone; +} dcf77_tx_t; + +typedef struct dcf77_rx { + int enable; + double phase_360; + double carrier_phase, carrier_phase_step; /* uncorrected phase */ + iir_filter_t carrier_lp[2]; /* filters received carrier signal */ + double sample_counter, sample_step; /* when to sample */ + double *delay_buffer; + int delay_size, delay_index; + int clock_count; + double value_level, value_short, value_long; /* measured values */ + int data_receive, data_index; + char data_string[60]; /* 59 digits + '\0' */ + uint64_t data_frame; + iir_filter_t clock_lp[2]; /* filters received carrier signal */ +} dcf77_rx_t; + +typedef struct dcf77 { + dcf77_tx_t tx; + dcf77_rx_t rx; + + /* measurements */ + dispmeas_t dispmeas; /* display measurements */ + dispmeasparam_t *dmp_input_level; + dispmeasparam_t *dmp_signal_level; + dispmeasparam_t *dmp_signal_quality; + + /* wave */ + dispwav_t dispwav; /* display wave form */ +} dcf77_t; + +int dcf77_init(int _fast_math); +void dcf77_exit(void); +dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone); +void dcf77_destroy(dcf77_t *dcf77); +void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp); +void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length); +void dcf77_decode(dcf77_t *dcf77, sample_t *samples, int length); diff --git a/src/dcf77/main.c b/src/dcf77/main.c new file mode 100755 index 0000000..8f7ab45 --- /dev/null +++ b/src/dcf77/main.c @@ -0,0 +1,513 @@ +/* DCF77 main + * + * (C) 2022 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../liboptions/options.h" +#include "../libsample/sample.h" +#include "../libsound/sound.h" +#include "dcf77.h" + +int num_kanal = 1; +dcf77_t *dcf77 = NULL; +static void *soundif = NULL; +static const char *dsp_device = ""; +static int dsp_samplerate = 192000; +static int dsp_buffer = 50; +static int rx = 0, tx = 0; +static time_t timestamp = -1; +static int double_amplitude = 0; +static int test_tone = 0; +static int dsp_interval = 1; /* ms */ +static int rt_prio = 1; +static int fast_math = 0; + +/* not static, in case we add libtimer some day, then compiler hits an error */ +double get_time(void) +{ + static struct timespec tv; + + clock_gettime(CLOCK_REALTIME, &tv); + + return (double)tv.tv_sec + (double)tv.tv_nsec / 1000000000.0; +} + +static time_t parse_time(char **argv) +{ + time_t t; + struct tm *tm; + int val; + + t = get_time(); + tm = localtime(&t); + if (!tm) + return -1; + + val = atoi(argv[0]); + if (val < 1900) + return -1; + tm->tm_year = val - 1900; + + val = atoi(argv[1]); + if (val < 1 || val > 12) + return -1; + tm->tm_mon = val - 1; + + val = atoi(argv[2]); + if (val < 1 || val > 31) + return -1; + tm->tm_mday = val; + + val = atoi(argv[3]); + if (val < 0 || val > 23) + return -1; + tm->tm_hour = val; + + val = atoi(argv[4]); + if (val < 0 || val > 59) + return -1; + tm->tm_min = val; + + val = atoi(argv[5]); + if (val < 0 || val > 59) + return -1; + tm->tm_sec = val; + + tm->tm_isdst = -1; + + return mktime(tm); +} + +static time_t feierabend_time() +{ + time_t t; + struct tm *tm; + + t = get_time(); + tm = localtime(&t); + if (!tm) + return -1; + + tm->tm_hour = 17; + tm->tm_min = 0; + tm->tm_sec = 0; + + tm->tm_isdst = -1; + + return mktime(tm); +} + +static void print_usage(const char *app) +{ + printf("Usage: %s [-a hw:0,0] []\n", app); +} + +void print_help(void) +{ + /* - - */ + printf(" -h --help\n"); + printf(" This help\n"); + printf(" --config [~/]\n"); + printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n"); + printf(" Each line in config file is one option, '-' or '--' must not be given!\n"); + debug_print_help(); + printf(" -a --audio-device hw:,\n"); + printf(" Sound card and device number (default = '%s')\n", dsp_device); + printf(" -s --samplerate \n"); + printf(" Sample rate of sound device (default = '%d')\n", dsp_samplerate); + printf(" -b --buffer \n"); + printf(" How many milliseconds are processed in advance (default = '%d')\n", dsp_buffer); + printf(" A buffer below 10 ms requires low interval like 0.1 ms.\n"); + printf(" -T --tx\n"); + printf(" Transmit time signal (default)\n"); + printf(" -R --rx\n"); + printf(" Receive time signal\n"); + printf(" -F --fake\n"); + printf(" Use given time stamp: .\n"); + printf(" All values have to be numerical. The year must have 4 digits.\n"); + printf(" --feierabend\n"); + printf(" --end-of-working-day\n"); + printf(" Use fake time stamp that equals 5 O'Clock PM.\n"); + printf(" --geburtstag\n"); + printf(" --birthday\n"); + printf(" Use fake time stamp that equals birth of the author.\n"); + printf(" -D --double-amplitude\n"); + printf(" Transmit with double amplitude by using differential stereo output.\n"); + printf(" --test-tone\n"); + printf(" Transmit a test tone (10%% level, 1000 Hz) with the carrier.\n"); + printf(" -r --realtime \n"); + printf(" Set prio: 0 to disable, 99 for maximum (default = %d)\n", rt_prio); + printf(" --fast-math\n"); + printf(" Use fast math approximation for slow CPU / ARM based systems.\n"); + printf("\n"); + printf("Press 'w' key to toggle display of RX wave form.\n"); + printf("Press 'm' key to toggle display of measurement values.\n"); +} + +#define OPT_F1 1001 +#define OPT_F2 1002 +#define OPT_G1 1003 +#define OPT_G2 1004 +#define OPT_TEST_TONE 1005 +#define OPT_FAST_MATH 1006 + +static void add_options(void) +{ + option_add('h', "help", 0); + option_add('v', "verbose", 1); + option_add('a', "audio-device", 1); + option_add('s', "samplerate", 1); + option_add('b', "buffer", 1); + option_add('T', "tx", 0); + option_add('R', "rx", 0); + option_add('F', "fake", 6); + option_add(OPT_F1, "feierabend", 0); + option_add(OPT_F2, "end-of-working-day", 0); + option_add(OPT_G1, "geburtstag", 0); + option_add(OPT_G2, "birthday", 0); + option_add(OPT_TEST_TONE, "test-tone", 0); + option_add('D', "double-amplitude", 0); + option_add('r', "realtime", 1); + option_add(OPT_FAST_MATH, "fast-math", 0); +} + +static int handle_options(int short_option, int argi, char **argv) +{ + int rc; + + switch (short_option) { + case 'h': + print_usage(argv[0]); + print_help(); + return 0; + case 'v': + if (!strcasecmp(argv[argi], "list")) { + debug_list_cat(); + return 0; + } + rc = parse_debug_opt(argv[argi]); + if (rc < 0) { + fprintf(stderr, "Failed to parse debug option, please use -h for help.\n"); + return rc; + } + break; + case 'a': + dsp_device = options_strdup(argv[argi]); + break; + case 's': + dsp_samplerate = atoi(argv[argi]); + break; + case 'b': + dsp_buffer = atoi(argv[argi]); + break; + case 'T': + tx = 1; + break; + case 'R': + rx = 1; + break; + case 'F': + timestamp = parse_time(argv + argi); + printf("%ld\n",timestamp); + if (timestamp < 0) { + fprintf(stderr, "Given time stamp is invalid, please use -h for help.\n"); + return -EINVAL; + } + break; + case OPT_F1: + case OPT_F2: + timestamp = feierabend_time() - 70; + break; + case OPT_G1: + case OPT_G2: + timestamp = 115099200 - 70; + break; + case OPT_TEST_TONE: + test_tone = 1; + break; + case 'D': + double_amplitude = 1; + break; + case 'r': + rt_prio = atoi(argv[argi]); + break; + case OPT_FAST_MATH: + fast_math = 1; + break; + default: + return -EINVAL; + } + + return 1; +} + +static int quit = 0; +static void sighandler(int sigset) +{ + if (sigset == SIGHUP || sigset == SIGPIPE) + return; + + fprintf(stderr, "\nSignal %d received.\n", sigset); + + quit = 1; +} + +static int get_char() +{ + struct timeval tv = {0, 0}; + fd_set fds; + char c = 0; + int __attribute__((__unused__)) rc; + + FD_ZERO(&fds); + FD_SET(0, &fds); + select(0+1, &fds, NULL, NULL, &tv); + if (FD_ISSET(0, &fds)) { + rc = read(0, &c, 1); + return c; + } else + return -1; +} + +int soundif_open(const char *audiodev, int samplerate, int buffer_size) +{ + if (!audiodev || !audiodev[0]) { + PDEBUG(DDSP, DEBUG_ERROR, "No audio device given!\n"); + return -EINVAL; + } + + /* open audiodev */ + soundif = sound_open(audiodev, NULL, NULL, NULL, (double_amplitude) ? 2 : 1, 0.0, samplerate, buffer_size, 1.0, 1.0, 0.0, 2.0); + if (!soundif) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to open sound device!\n"); + return -EIO; + } + + return 0; +} + +void soundif_start(void) +{ + sound_start(soundif); + PDEBUG(DDSP, DEBUG_DEBUG, "Starting audio stream!\n"); +} + +void soundif_close(void) +{ + /* close audiodev */ + if (soundif) { + sound_close(soundif); + soundif = NULL; + } +} + +void soundif_work(int buffer_size) +{ + int count; + sample_t buff1[buffer_size], buff2[buffer_size], *samples[2] = { buff1, buff2 }; + double rf_level_db[2]; + int rc; + int i; + + /* encode and write */ + count = sound_get_tosend(soundif, buffer_size); + if (count < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count); + return; + } + if (count) { + dcf77_encode(dcf77, samples[0], count); + if (double_amplitude) { + for (i = 0; i < count; i++) + samples[1][i] = -samples[0][i]; + } + rc = sound_write(soundif, samples, NULL, count, NULL, NULL, (double_amplitude) ? 2 : 1); + if (rc < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc); + return; + } + } + + /* read */ + count = sound_read(soundif, samples, buffer_size, 1, rf_level_db); + if (count < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to read from audio device (rc = %d)!\n", count); + return; + } + + /* decode */ + dcf77_decode(dcf77, samples[0], count); +} + +int main(int argc, char *argv[]) +{ + int rc, argi; + int buffer_size; + struct termios term, term_orig; + double begin_time, now, sleep; + char c; + + /* handle options / config file */ + add_options(); + rc = options_config_file(argc, argv, "~/.osmocom/dcf77/dcf77.conf", handle_options); + if (rc < 0) + return 0; + argi = options_command_line(argc, argv, handle_options); + if (argi <= 0) + return argi; + + if (dsp_samplerate < 192000) { + fprintf(stderr, "The sample rate must be at least 192000 to TX or RX 77.5 kHz. Quitting!\n"); + goto error; + } + + /* default to TX, if --tx and --rx was not set */ + if (!tx && !rx) + tx = 1; + + /* inits */ + dcf77_init(fast_math); + + /* size of dsp buffer in samples */ + buffer_size = dsp_samplerate * dsp_buffer / 1000; + + rc = soundif_open(dsp_device, dsp_samplerate, buffer_size); + if (rc < 0) { + printf("Failed to open sound for DCF77, use '-h' for help.\n"); + goto error; + } + + dcf77 = dcf77_create(dsp_samplerate, tx, rx, test_tone); + if (!dcf77) { + fprintf(stderr, "Failed to create \"DCF77\" instance. Quitting!\n"); + goto error; + } + + printf("\n"); + printf("DCF77 ready.\n"); + + /* prepare terminal */ + tcgetattr(0, &term_orig); + term = term_orig; + term.c_lflag &= ~(ISIG|ICANON|ECHO); + term.c_cc[VMIN]=1; + term.c_cc[VTIME]=2; + tcsetattr(0, TCSANOW, &term); + + /* set real time prio */ + if (rt_prio) { + struct sched_param schedp; + + memset(&schedp, 0, sizeof(schedp)); + schedp.sched_priority = rt_prio; + rc = sched_setscheduler(0, SCHED_RR, &schedp); + if (rc) + fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio); + } + + signal(SIGINT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGPIPE, sighandler); + + soundif_start(); + if (tx) + dcf77_tx_start(dcf77, timestamp); + + while (!quit) { + int w; + + begin_time = get_time(); + + soundif_work(buffer_size); + do { + w = 0; + } while (w); + + c = get_char(); + switch (c) { + case 3: + printf("CTRL+c received, quitting!\n"); + quit = 1; + break; + case 'w': + /* toggle wave display */ + display_measurements_on(0); + display_wave_on(-1); + break; + case 'm': + /* toggle measurements display */ + display_wave_on(0); + display_measurements_on(-1); + break; + default: + break; + } + + display_measurements(dsp_interval / 1000.0); + + now = get_time(); + + /* sleep interval */ + sleep = ((double)dsp_interval / 1000.0) - (now - begin_time); + if (sleep > 0) + usleep(sleep * 1000000.0); + } + + signal(SIGINT, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + /* reset real time prio */ + if (rt_prio > 0) { + struct sched_param schedp; + + memset(&schedp, 0, sizeof(schedp)); + schedp.sched_priority = 0; + sched_setscheduler(0, SCHED_OTHER, &schedp); + } + + /* reset terminal */ + tcsetattr(0, TCSANOW, &term_orig); + +error: + /* destroy UK0 instances */ + if (dcf77) + dcf77_destroy(dcf77); + + soundif_close(); + + dcf77_exit(); + + options_free(); + + return 0; +} + diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c index 3dd02dd..63e2687 100755 --- a/src/libdebug/debug.c +++ b/src/libdebug/debug.c @@ -90,8 +90,9 @@ struct debug_cat { { "dss1", "\033[1;34m" }, { "sip", "\033[1;35m" }, { "telephone", "\033[1;34m" }, - { "UK0", "\033[1;34m" }, + { "uk0", "\033[1;34m" }, { "ph", "\033[0;33m" }, + { "dcf77", "\033[1;34m" }, { NULL, NULL } }; diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h index 6ef3ec0..3c13f7d 100644 --- a/src/libdebug/debug.h +++ b/src/libdebug/debug.h @@ -54,6 +54,7 @@ #define DTEL 47 #define DUK0 48 #define DPH 49 +#define DDCF77 50 void lock_debug(void); void unlock_debug(void);