Implementation of DCF77 transmitter and receiver
This commit is contained in:
parent
8c382431b5
commit
4758f16324
|
@ -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
|
||||
|
|
3
README
3
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!
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -62,7 +62,8 @@ SUBDIRS += \
|
|||
zeitansage \
|
||||
sim \
|
||||
magnetic \
|
||||
fuvst
|
||||
fuvst \
|
||||
dcf77
|
||||
|
||||
if HAVE_ALSA
|
||||
if HAVE_FUSE
|
||||
|
|
|
@ -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
|
||||
|
|
@ -0,0 +1,585 @@
|
|||
|
||||
/* implementation of DCF77 transmitter and receiver
|
||||
*
|
||||
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#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++;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libfilter/iir_filter.h"
|
||||
#include "../libdisplay/display.h"
|
||||
#include <time.h>
|
||||
|
||||
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);
|
|
@ -0,0 +1,513 @@
|
|||
/* DCF77 main
|
||||
*
|
||||
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <termios.h>
|
||||
#include <sched.h>
|
||||
#include <time.h>
|
||||
#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] [<options>]\n", app);
|
||||
}
|
||||
|
||||
void print_help(void)
|
||||
{
|
||||
/* - - */
|
||||
printf(" -h --help\n");
|
||||
printf(" This help\n");
|
||||
printf(" --config [~/]<path to config file>\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:<card>,<device>\n");
|
||||
printf(" Sound card and device number (default = '%s')\n", dsp_device);
|
||||
printf(" -s --samplerate <rate>\n");
|
||||
printf(" Sample rate of sound device (default = '%d')\n", dsp_samplerate);
|
||||
printf(" -b --buffer <ms>\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: <year> <month> <day> <hour> <min< <sec>.\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 <prio>\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;
|
||||
}
|
||||
|
|
@ -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 }
|
||||
};
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
#define DTEL 47
|
||||
#define DUK0 48
|
||||
#define DPH 49
|
||||
#define DDCF77 50
|
||||
|
||||
void lock_debug(void);
|
||||
void unlock_debug(void);
|
||||
|
|
Loading…
Reference in New Issue