From 0661e23f21344af05e7f014d82e9c6ee118620d7 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Thu, 21 May 2020 21:37:34 -0400 Subject: [PATCH] Issue 271 - DCD dropping at wrong time. --- src/atest.c | 45 ++++++++++- src/demod_9600.c | 23 ++++-- src/demod_afsk.c | 12 +-- src/demod_psk.c | 16 +++- src/fsk_demod_state.h | 111 +++++++++++++++++++++++++- src/gen_packets.c | 19 +++-- src/hdlc_rec.c | 139 ++------------------------------- test/scripts/check-modem19200 | 4 +- test/scripts/check-modem2400-a | 4 +- test/scripts/check-modem2400-b | 2 +- test/scripts/check-modem2400-g | 2 +- test/scripts/check-modem4800 | 4 +- 12 files changed, 219 insertions(+), 162 deletions(-) diff --git a/src/atest.c b/src/atest.c index e395c09..f7e40b3 100644 --- a/src/atest.c +++ b/src/atest.c @@ -82,7 +82,7 @@ #include "ptt.h" #include "dtime_now.h" #include "fx25.h" - +#include "hdlc_rec.h" #if 0 /* Typical but not flexible enough. */ @@ -188,6 +188,9 @@ static int J_opt = 0; /* 2400 bps PSK compatible MFJ-2400 and maybe others. */ static int h_opt = 0; // Hexadecimal display of received packet. static char P_opt[16] = ""; // Demodulator profiles. static int d_x_opt = 1; // FX.25 debug. +static int d_o_opt = 0; // "-d o" option for DCD output control. */ +static int dcd_count = 0; +static int dcd_missing_errors = 0; int main (int argc, char *argv[]) @@ -382,6 +385,7 @@ int main (int argc, char *argv[]) for (char *p=optarg; *p!='\0'; p++) { switch (*p) { case 'x': d_x_opt++; break; // FX.25 + case 'o': d_o_opt++; break; // DCD output control default: break; } } @@ -686,6 +690,10 @@ int main (int argc, char *argv[]) elapsed = dtime_now() - start_time; dw_printf ("%d packets decoded in %.3f seconds. %.1f x realtime\n", packets_decoded_total, elapsed, total_filetime/elapsed); + if (d_o_opt) { + dw_printf ("DCD count = %d\n", dcd_count); + dw_printf ("DCD missing erors = %d\n", dcd_missing_errors); + } if (error_if_less_than != -1 && packets_decoded_total < error_if_less_than) { text_color_set(DW_COLOR_ERROR); @@ -744,6 +752,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE]; packets_decoded_one++; + if ( ! hdlc_rec_data_detect_any(chan)) dcd_missing_errors++; ax25_format_addrs (pp, stemp); @@ -777,7 +786,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev int min = (int)(sec / 60.); sec -= min * 60; - dw_printf ("%d:%07.4f ", min, sec); + dw_printf ("%d:%06.3f ", min, sec); if (h != AX25_SOURCE) { dw_printf ("Digipeater "); @@ -876,6 +885,38 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev void ptt_set (int ot, int chan, int ptt_signal) { + // Should only get here for DCD output control. + static double dcd_start_time[MAX_CHANS]; + + if (d_o_opt) { + double t = (double)sample_number / my_audio_config.adev[0].samples_per_sec; + double sec1, sec2; + int min1, min2; + + text_color_set(DW_COLOR_INFO); + + if (ptt_signal) { + //sec1 = t; + //min1 = (int)(sec1 / 60.); + //sec1 -= min1 * 60; + //dw_printf ("DCD[%d] = ON %d:%06.3f\n", chan, min1, sec1); + dcd_count++; + dcd_start_time[chan] = t; + } + else { + //dw_printf ("DCD[%d] = off %d:%06.3f %3.0f\n", chan, min, sec, (t - dcd_start_time[chan]) * 1000.); + + sec1 = dcd_start_time[chan]; + min1 = (int)(sec1 / 60.); + sec1 -= min1 * 60; + + sec2 = t; + min2 = (int)(sec2 / 60.); + sec2 -= min2 * 60; + + dw_printf ("DCD[%d] %d:%06.3f - %d:%06.3f = %3.0f\n", chan, min1, sec1, min2, sec2, (t - dcd_start_time[chan]) * 1000.); + } + } return; } diff --git a/src/demod_9600.c b/src/demod_9600.c index a0e1ff0..2f98983 100644 --- a/src/demod_9600.c +++ b/src/demod_9600.c @@ -44,14 +44,24 @@ #include #include +// Fine tuning for different demodulator types. + +#define DCD_THRESH_ON 32 // Hysteresis: Can miss 0 out of 32 for detecting lock. + // This is best for actual on-the-air signals. + // Still too many brief false matches. +#define DCD_THRESH_OFF 8 // Might want a little more fine tuning. +#define DCD_GOOD_WIDTH 1024 // No more than 1024!!! +#include "fsk_demod_state.h" // Values above override defaults. + #include "tune.h" -#include "fsk_demod_state.h" #include "hdlc_rec.h" #include "demod_9600.h" #include "textcolor.h" #include "dsp.h" + + static float slice_point[MAX_SUBCHANS]; @@ -407,7 +417,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D if (chan == 0) { if (1) { - //if (hdlc_rec_gathering (chan, subchan, slice)) { + //if (D->slicer[slice].data_detect) { char fname[30]; int slice = 0; @@ -516,6 +526,7 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ /* Overflow. Was large positive, wrapped around, now large negative. */ hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, D->modem_type == MODEM_SCRAMBLE, D->slicer[slice].lfsr); + pll_dcd_each_symbol2 (D, chan, subchan, slice); } /* @@ -526,11 +537,11 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ // Note: Test for this demodulator, not overall for channel. - float target = 0; + pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); - target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f); + float target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f); - if (hdlc_rec_gathering (chan, subchan, slice)) { + if (D->slicer[slice].data_detect) { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia + target * (1.0f - D->pll_locked_inertia) ); } else { @@ -542,7 +553,7 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ #if DEBUG5 //if (chan == 0) { - if (hdlc_rec_gathering (chan,subchan,slice)) { + if (D->slicer[slice].data_detect) { char fname[30]; diff --git a/src/demod_afsk.c b/src/demod_afsk.c index 7d47aa0..76defd0 100644 --- a/src/demod_afsk.c +++ b/src/demod_afsk.c @@ -762,7 +762,7 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat #if DEBUG4 if (chan == 0) { - if (hdlc_rec_gathering (chan, subchan)) { + if (D->slicer[slice].data_detect) { char fname[30]; @@ -839,16 +839,18 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) { - /* Overflow. */ + /* Overflow - this is where we sample. */ hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1); + pll_dcd_each_symbol2 (D, chan, subchan, slice); } - // Even if we used alternative method to extract the data bit, - // we still use the low pass output for the PLL. + // Transitions nudge the DPLL phase toward the incoming signal. if (demod_data != D->slicer[slice].prev_demod_data) { - if (hdlc_rec_gathering (chan, subchan, slice)) { + pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); + + if (D->slicer[slice].data_detect) { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); } else { diff --git a/src/demod_psk.c b/src/demod_psk.c index a1565e9..017c640 100644 --- a/src/demod_psk.c +++ b/src/demod_psk.c @@ -86,16 +86,25 @@ #include #include +// Fine tuning for different demodulator types. + +#define DCD_THRESH_ON 30 // Hysteresis: Can miss 2 out of 32 for detecting lock. +#define DCD_THRESH_OFF 6 // Might want a little more fine tuning. +#define DCD_GOOD_WIDTH 512 +#include "fsk_demod_state.h" // Values above override defaults. #include "audio.h" #include "tune.h" -#include "fsk_demod_state.h" #include "fsk_gen_filter.h" #include "hdlc_rec.h" #include "textcolor.h" #include "demod_psk.h" #include "dsp.h" + + + + static const int phase_to_gray_v26[4] = {0, 1, 3, 2}; static const int phase_to_gray_v27[8] = {1, 0, 2, 3, 7, 6, 4, 5}; @@ -800,6 +809,7 @@ static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, bit_quality[1]); hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, bit_quality[0]); } + pll_dcd_each_symbol2 (D, chan, subchan, slice); } /* @@ -813,7 +823,9 @@ static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct if (demod_bits != D->slicer[slice].prev_demod_data) { - if (hdlc_rec_gathering (chan, subchan, slice)) { + pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); + + if (D->slicer[slice].data_detect) { D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_locked_inertia); } else { diff --git a/src/fsk_demod_state.h b/src/fsk_demod_state.h index a12c1e5..f7b5650 100644 --- a/src/fsk_demod_state.h +++ b/src/fsk_demod_state.h @@ -226,6 +226,8 @@ struct demodulator_state_s signed int data_clock_pll; // PLL for data clock recovery. // It is incremented by pll_step_per_sample // for each audio sample. + // Must be 32 bits!!! + // So far, this is the case for every compiler used. signed int prev_d_c_pll; // Previous value of above, before // incrementing, to detect overflows. @@ -238,6 +240,18 @@ struct demodulator_state_s int lfsr; // Descrambler shift register. + // This is for detecting phase lock to incoming signal. + + int good_flag; // Set if transition is near where expected, + // i.e. at a good time. + int bad_flag; // Set if transition is not where expected, + // i.e. at a bad time. + unsigned char good_hist; // History of good transitions for past octet. + unsigned char bad_hist; // History of bad transitions for past octet. + unsigned int score; // History of whether good triumphs over bad + // for past 32 symbols. + int data_detect; // True when locked on to signal. + } slicer [MAX_SLICERS]; // Actual number in use is num_slicers. // Should be in range 1 .. MAX_SLICERS, /* @@ -336,5 +350,100 @@ struct demodulator_state_s }; + +/*------------------------------------------------------------------- + * + * Name: pll_dcd_signal_transition2 + * dcd_each_symbol2 + * + * Purpose: New DCD strategy for 1.6. + * + * Inputs: D Pointer to demodulator state. + * + * chan Radio channel: 0 to MAX_CHANS - 1 + * + * subchan Which of multiple demodulators: 0 to MAX_SUBCHANS - 1 + * + * slice Slicer number: 0 to MAX_SLICERS - 1. + * + * dpll_phase Signed 32 bit counter for DPLL phase. + * Wraparound is where data is sampled. + * Ideally transitions would occur close to 0. + * + * Output: D->slicer[slice].data_detect - true when PLL is locked to incoming signal. + * + * Description: From the beginning, DCD was based on finding several flag octets + * in a row and dropping when eight bits with no transitions. + * It was less than ideal but we limped along with it all these years. + * This fell apart when FX.25 came along and a couple of the + * correlation tags have eight "1" bits in a row. + * + * Our new strategy is to keep a running score of how well demodulator + * output transitions match to where expected. + * + *--------------------------------------------------------------------*/ + +#include "hdlc_rec.h" // for dcd_change + +// These are good for 1200 bps AFSK. +// Might want to override for other modems. + +#ifndef DCD_THRESH_ON +#define DCD_THRESH_ON 30 // Hysteresis: Can miss 2 out of 32 for detecting lock. + // 31 is best for TNC Test CD. 30 almost as good. + // 30 better for 1200 regression test. +#endif + +#ifndef DCD_THRESH_OFF +#define DCD_THRESH_OFF 6 // Might want a little more fine tuning. +#endif + +#ifndef DCD_GOOD_WIDTH +#define DCD_GOOD_WIDTH 512 // No more than 1024!!! +#endif + +__attribute__((always_inline)) +inline static void pll_dcd_signal_transition2 (struct demodulator_state_s *D, int slice, int dpll_phase) +{ + if (dpll_phase > - DCD_GOOD_WIDTH * 1024 * 1024 && dpll_phase < DCD_GOOD_WIDTH * 1024 * 1024) { + D->slicer[slice].good_flag = 1; + } + else { + D->slicer[slice].bad_flag = 1; + } +} + +__attribute__((always_inline)) +inline static void pll_dcd_each_symbol2 (struct demodulator_state_s *D, int chan, int subchan, int slice) +{ + D->slicer[slice].good_hist <<= 1; + D->slicer[slice].good_hist |= D->slicer[slice].good_flag; + D->slicer[slice].good_flag = 0; + + D->slicer[slice].bad_hist <<= 1; + D->slicer[slice].bad_hist |= D->slicer[slice].bad_flag; + D->slicer[slice].bad_flag = 0; + + D->slicer[slice].score <<= 1; + // 2 is to detect 'flag' patterns with 2 transitions per octet. + D->slicer[slice].score |= (signed)__builtin_popcount(D->slicer[slice].good_hist) + - (signed)__builtin_popcount(D->slicer[slice].bad_hist) >= 2; + + int s = __builtin_popcount(D->slicer[slice].score); + if (s >= DCD_THRESH_ON) { + if (D->slicer[slice].data_detect == 0) { + D->slicer[slice].data_detect = 1; + dcd_change (chan, subchan, slice, D->slicer[slice].data_detect); + } + } + else if (s <= DCD_THRESH_OFF) { + if (D->slicer[slice].data_detect != 0) { + D->slicer[slice].data_detect = 0; + dcd_change (chan, subchan, slice, D->slicer[slice].data_detect); + } + } +} + + #define FSK_DEMOD_STATE_H 1 -#endif \ No newline at end of file +#endif diff --git a/src/gen_packets.c b/src/gen_packets.c index c05ff22..b097790 100644 --- a/src/gen_packets.c +++ b/src/gen_packets.c @@ -117,6 +117,11 @@ static void send_packet (char *str) } else { pp = ax25_from_text (str, 1); + if (pp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\"%s\" is not valid TNC2 monitoring format.\n", str); + return; + } flen = ax25_pack (pp, fbuf); for (c=0; cflag4_det |= 0x80000000; } - -/* - * "Data Carrier detect" function based on data patterns rather than - * audio signal strength. - * - * Idle time, at beginning of transmission should be filled - * with the special "flag" characters. - * - * Idle time of all zero bits (alternating tones at maximum rate) - * has also been observed rarely. It is easy to understand the reasoning. - * The tones alternate at the maximum rate, making it symmetrical and providing - * the most opportunity for the PLL to lock on to the edges. - * It also violates the published protocol spec. - * - * Recognize zero(s) followed by a single flag even though it violates the spec. - * - * It has been reported that the TinyTrak4 does this. - * https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/1207 - */ - -/* - * Originally, this looked for 4 flags in a row or 3 zeros and a flag. - * Is that too fussy? - * Here are the numbers of start of DCD for our favorite Track 2 test. - * - * 7e7e7e7e 504 7e000000 32 - * 7e7e7e-- 513 7e0000-- 33 - * 7e7e---- 555 7e00---- 42 - * 7e------ 2088 - * - * I don't think we want to look for a single flag because that would - * make DCD too sensitive to noise and it would interfere with waiting for a - * clear channel to transmit. Even a two byte match causes a lot of flickering - * when listening to live signals. Let's try 3 and see how that works out. - */ - - - //if (H->flag4_det == 0x7e7e7e7e) { - if ((H->flag4_det & 0xffffff00) == 0x7e7e7e00) { // three seems good - //if ((H->flag4_det & 0xffff0000) == 0x7e7e0000) { // two in a row - //if ((H->flag4_det & 0xff000000) == 0x7e000000) { // single flag - if ( ! H->data_detect) { - H->data_detect = 1; - dcd_change (chan, subchan, slice, 1); - } - } - //else if (H->flag4_det == 0x7e000000) { - else if ((H->flag4_det & 0xffffff00) == 0x7e000000) { - //else if ((H->flag4_det & 0xffff0000) == 0x7e000000) { - - if ( ! H->data_detect) { - H->data_detect = 1; - dcd_change (chan, subchan, slice, 1); - } - } - - -/* - * Loss of signal should result in lack of transitions. - * (all '1' bits) for at least a little while. - * - * When this was written, I was only concerned about 1200 baud. - * For 9600, added later, there is a (de)scrambling function. - * So if there is no change in the signal, we would get pseudo random bits here. - * Maybe we need to put in another check earlier so DCD is not held on too long - * after loss of signal for 9600. - * No, that would not be a good idea. part of a valid frame, when scrambled, - * could have seven or more "1" bits in a row. - * Needs more study. - */ - - - if (H->pat_det == 0xff) { - - if ( H->data_detect ) { - H->data_detect = 0; - dcd_change (chan, subchan, slice, 0); - } - } - - -/* - * End of data carrier detect. - * - * The rest is concerned with framing. - */ - - rrbb_append_bit (H->rrbb, raw); if (H->pat_det == 0x7e) { @@ -554,47 +464,10 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, } } - - -/*------------------------------------------------------------------- - * - * Name: hdlc_rec_gathering - * - * Purpose: Report whether bits are currently being gathered into a frame. - * This is used to influence the PLL inertia. - * The idea is that the PLL should be a little more agreeable to - * synchronize with the incoming data stream when not in a frame - * and resist changing a little more when capturing a frame. - * - * Inputs: chan - * subchan - * slice - * - * Returns: True if we are currently gathering bits. - * In this case we want the PLL to have more inertia. - * - * Discussion: This simply returns the data carrier detect state. - * A couple other variations were tried but turned out to - * be slightly worse. - * - *--------------------------------------------------------------------*/ - -int hdlc_rec_gathering (int chan, int subchan, int slice) -{ - assert (chan >= 0 && chan < MAX_CHANS); - assert (subchan >= 0 && subchan < MAX_SUBCHANS); - assert (slice >= 0 && slice < MAX_SLICERS); - - // Counts from Track 1 & Track 2 - // data_detect 992 988 - // olen>=0 992 985 - // OR-ed 992 985 - - return ( hdlc_state[chan][subchan][slice].data_detect ); - -} /* end hdlc_rec_gathering */ - - +// TODO: Data Carrier Detect (DCD) is now based on DPLL lock +// rather than data patterns found here. +// It would make sense to move the next 2 functions to demod.c +// because this is done at the modem level, rather than HDLC decoder. /*------------------------------------------------------------------- * diff --git a/test/scripts/check-modem19200 b/test/scripts/check-modem19200 index 8d08a8e..ae49e46 100755 --- a/test/scripts/check-modem19200 +++ b/test/scripts/check-modem19200 @@ -3,5 +3,5 @@ @GEN_PACKETS_BIN@ -r 96000 -B19200 -a 170 -o test19.wav @ATEST_BIN@ -B19200 -F0 -L4 test19.wav @GEN_PACKETS_BIN@ -r 96000 -B19200 -n 100 -o test19.wav -@ATEST_BIN@ -B19200 -F0 -L60 -G64 test19.wav -@ATEST_BIN@ -B19200 -F1 -L64 -G68 test19.wav +@ATEST_BIN@ -B19200 -F0 -L60 -G66 test19.wav +@ATEST_BIN@ -B19200 -F1 -L64 -G69 test19.wav diff --git a/test/scripts/check-modem2400-a b/test/scripts/check-modem2400-a index 1faa7d1..33dfebf 100755 --- a/test/scripts/check-modem2400-a +++ b/test/scripts/check-modem2400-a @@ -1,5 +1,5 @@ @CUSTOM_SHELL_SHABANG@ @GEN_PACKETS_BIN@ -B2400 -j -n 100 -o test24-a.wav -@ATEST_BIN@ -B2400 -j -F0 -L76 -G80 test24-a.wav -@ATEST_BIN@ -B2400 -j -F1 -L84 -G88 test24-a.wav +@ATEST_BIN@ -B2400 -j -F0 -L76 -G83 test24-a.wav +@ATEST_BIN@ -B2400 -j -F1 -L84 -G89 test24-a.wav diff --git a/test/scripts/check-modem2400-b b/test/scripts/check-modem2400-b index e1823a6..913a608 100755 --- a/test/scripts/check-modem2400-b +++ b/test/scripts/check-modem2400-b @@ -1,5 +1,5 @@ @CUSTOM_SHELL_SHABANG@ @GEN_PACKETS_BIN@ -B2400 -J -n 100 -o test24-b.wav -@ATEST_BIN@ -B2400 -J -F0 -L84 -G88 test24-b.wav +@ATEST_BIN@ -B2400 -J -F0 -L81 -G88 test24-b.wav @ATEST_BIN@ -B2400 -J -F1 -L86 -G90 test24-b.wav diff --git a/test/scripts/check-modem2400-g b/test/scripts/check-modem2400-g index b505370..da7e233 100755 --- a/test/scripts/check-modem2400-g +++ b/test/scripts/check-modem2400-g @@ -1,4 +1,4 @@ @CUSTOM_SHELL_SHABANG@ @GEN_PACKETS_BIN@ -B2400 -g -n 100 -o test24-g.wav -@ATEST_BIN@ -B2400 -g -F0 -L99 -G100 test24-g.wav +@ATEST_BIN@ -B2400 -g -F0 -L99 -G101 test24-g.wav diff --git a/test/scripts/check-modem4800 b/test/scripts/check-modem4800 index 2ad033d..7d511bb 100755 --- a/test/scripts/check-modem4800 +++ b/test/scripts/check-modem4800 @@ -1,5 +1,5 @@ @CUSTOM_SHELL_SHABANG@ @GEN_PACKETS_BIN@ -B4800 -n 100 -o test48.wav -@ATEST_BIN@ -B4800 -F0 -L70 -G74 test48.wav -@ATEST_BIN@ -B4800 -F1 -L79 -G84 test48.wav +@ATEST_BIN@ -B4800 -F0 -L68 -G74 test48.wav +@ATEST_BIN@ -B4800 -F1 -L72 -G84 test48.wav