qtsoundmodem/dw9600.c

4332 lines
116 KiB
C
Raw Normal View History

2023-09-12 21:38:15 +01:00
// 4800/9600 RHU Mode code for QtSoundModem
// Based on code from Dire Wolf Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
#define MODE_RUH 7
typedef struct string_T
{
unsigned char * Data;
int Length;
int AllocatedLength; // A reasonable sized block is allocated at the start to speed up adding chars
}string;
typedef struct TStringList_T
{
int Count;
string ** Items;
} TStringList;
#include <stddef.h>
#include "dw9600.h"
2024-07-23 21:26:30 +01:00
#define stringAdd(s1, s2, c) mystringAdd(s1, s2, c, __FILE__, __LINE__)
string * mystringAdd(string * Msg, unsigned char * Chars, int Count, char * FILE, int LINE);
2023-09-12 21:38:15 +01:00
extern int fx25_mode[4];
extern int il2p_mode[4];
2023-12-17 13:53:32 +00:00
extern int il2p_crc[4];
2023-09-12 21:38:15 +01:00
extern short rx_baudrate[5];
2024-04-13 17:48:58 +01:00
extern short tx_bitrate[5];
2023-09-12 21:38:15 +01:00
#define FX25_MODE_NONE 0
#define FX25_MODE_RX 1
#define FX25_MODE_TXRX 2
#define FX25_TAG 0
#define FX25_LOAD 1
#define IL2P_MODE_NONE 0
#define IL2P_MODE_RX 1 // RX il2p + HDLC
#define IL2P_MODE_TXRX 2
#define IL2P_MODE_ONLY 3 // RX only il2p, TX il2p
extern unsigned short * DMABuffer;
extern int SampleNo;
void ProcessRXFrames(int snd_ch);
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//
// 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 2 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 <math.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdint.h> // uint64_t
#define RRBB_C 1
/********************************************************************************
*
* File: hdlc_rec.c
*
* Purpose: Extract HDLC frames from a stream of bits.
*
*******************************************************************************/
//#define TEST 1 /* Define for unit testing. */
//#define DEBUG3 1 /* monitor the data detect signal. */
/*
* Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS.
*/
#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)
/*
* This is the current state of the HDLC decoder.
*
* It is possible to run multiple decoders concurrently by
* having a separate set of state variables for each.
*
* Should have a reset function instead of initializations here.
*/
struct hdlc_state_s {
int prev_raw; /* Keep track of previous bit so */
/* we can look for transitions. */
/* Should be only 0 or 1. */
int lfsr; /* Descrambler shift register for 9600 baud. */
int prev_descram; /* Previous descrambled for 9600 baud. */
unsigned char pat_det; /* 8 bit pattern detector shift register. */
/* See below for more details. */
unsigned int flag4_det; /* Last 32 raw bits to look for 4 */
/* flag patterns in a row. */
unsigned char oacc; /* Accumulator for building up an octet. */
int olen; /* Number of bits in oacc. */
/* When this reaches 8, oacc is copied */
/* to the frame buffer and olen is zeroed. */
/* The value of -1 is a special case meaning */
/* bits should not be accumulated. */
unsigned char frame_buf[MAX_FRAME_LEN];
/* One frame is kept here. */
int frame_len; /* Number of octets in frame_buf. */
/* Should be in range of 0 .. MAX_FRAME_LEN. */
rrbb_t rrbb; /* Handle for bit array for raw received bits. */
uint64_t eas_acc; /* Accumulate most recent 64 bits received for EAS. */
int eas_gathering; /* Decoding in progress. */
int eas_plus_found; /* "+" seen, indicating end of geographical area list. */
int eas_fields_after_plus; /* Number of "-" characters after the "+". */
};
static struct hdlc_state_s hdlc_state[MAX_CHANS];
static int num_subchan[MAX_CHANS]; //TODO1.2 use ptr rather than copy.
static int composite_dcd[MAX_CHANS][MAX_SUBCHANS + 1];
/***********************************************************************************
*
* Name: hdlc_rec_init
*
* Purpose: Call once at the beginning to initialize.
*
* Inputs: None.
*
***********************************************************************************/
int was_init[4] = { 0, 0, 0, 0 };
struct audio_s *g_audio_p;
extern struct audio_s pa[4];
void hdlc_rec_init(struct audio_s *pa)
{
int ch;
struct hdlc_state_s *H;
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("hdlc_rec_init (%p) \n", pa);
assert(pa != NULL);
g_audio_p = &pa;
memset(composite_dcd, 0, sizeof(composite_dcd));
for (ch = 0; ch < MAX_CHANS; ch++)
{
pa->achan[ch].num_subchan = 1;
num_subchan[ch] = pa->achan[ch].num_subchan;
assert(num_subchan[ch] >= 1 && num_subchan[ch] <= MAX_SUBCHANS);
H = &hdlc_state[ch];
H->olen = -1;
H->rrbb = rrbb_new(ch, 0, 0, pa->achan[ch].modem_type == MODEM_SCRAMBLE, H->lfsr, H->prev_descram);
}
hdlc_rec2_init(pa);
}
/* Own copy of random number generator so we can get */
/* same predictable results on different operating systems. */
/* TODO: Consolidate multiple copies somewhere. */
#define MY_RAND_MAX 0x7fffffff
static int seed = 1;
static int my_rand(void) {
// Perform the calculation as unsigned to avoid signed overflow error.
seed = (int)(((unsigned)seed * 1103515245) + 12345) & MY_RAND_MAX;
return (seed);
}
/***********************************************************************************
*
* Name: eas_rec_bit
*
* Purpose: Extract EAS trasmissions from a stream of bits.
*
* Inputs: chan - Channel number.
*
* subchan - This allows multiple demodulators per channel.
*
* slice - Allows multiple slicers per demodulator (subchannel).
*
* raw - One bit from the demodulator.
* should be 0 or 1.
*
* future_use - Not implemented yet. PSK already provides it.
*
*
* Description: This is called once for each received bit.
* For each valid transmission, process_rec_frame()
* is called for further processing.
*
***********************************************************************************/
#define PREAMBLE 0xababababababababULL
#define PREAMBLE_ZCZC 0x435a435aababababULL
#define PREAMBLE_NNNN 0x4e4e4e4eababababULL
#define EAS_MAX_LEN 268 // Not including preamble. Up to 31 geographic areas.
static void eas_rec_bit(int chan, int subchan, int slice, int raw, int future_use)
{
struct hdlc_state_s *H;
/*
* Different state information for each channel / subchannel / slice.
*/
H = &hdlc_state[chan];
//dw_printf ("slice %d = %d\n", slice, raw);
// Accumulate most recent 64 bits.
H->eas_acc >>= 1;
if (raw) {
H->eas_acc |= 0x8000000000000000ULL;
}
int done = 0;
if (H->eas_acc == PREAMBLE_ZCZC) {
//dw_printf ("ZCZC\n");
H->olen = 0;
H->eas_gathering = 1;
H->eas_plus_found = 0;
H->eas_fields_after_plus = 0;
strcpy((char*)(H->frame_buf), "ZCZC");
H->frame_len = 4;
}
else if (H->eas_acc == PREAMBLE_NNNN) {
//dw_printf ("NNNN\n");
H->olen = 0;
H->eas_gathering = 1;
strcpy((char*)(H->frame_buf), "NNNN");
H->frame_len = 4;
done = 1;
}
else if (H->eas_gathering) {
H->olen++;
if (H->olen == 8) {
H->olen = 0;
char ch = H->eas_acc >> 56;
H->frame_buf[H->frame_len++] = ch;
H->frame_buf[H->frame_len] = '\0';
//dw_printf ("frame_buf = %s\n", H->frame_buf);
// What characters are acceptable?
// Only ASCII is allowed. i.e. the MSB must be 0.
// The examples show only digits but the geographical area can
// contain anything in range of '!' to DEL or CR or LF.
// There are no restrictions listed for the originator and
// examples contain a slash.
// It's not clear if a space can occur in other places.
if (!((ch >= ' ' && ch <= 0x7f) || ch == '\r' || ch == '\n')) {
//#define DEBUG_E 1
#ifdef DEBUG_E
dw_printf("reject %d invalid character = %s\n", slice, H->frame_buf);
#endif
H->eas_gathering = 0;
return;
}
if (H->frame_len > EAS_MAX_LEN) { // FIXME: look for other places with max length
#ifdef DEBUG_E
dw_printf("reject %d too long = %s\n", slice, H->frame_buf);
#endif
H->eas_gathering = 0;
return;
}
if (ch == '+') {
H->eas_plus_found = 1;
H->eas_fields_after_plus = 0;
}
if (H->eas_plus_found && ch == '-') {
H->eas_fields_after_plus++;
if (H->eas_fields_after_plus == 3) {
done = 1; // normal case
}
}
}
}
if (done) {
#ifdef DEBUG_E
dw_printf("frame_buf %d = %s\n", slice, H->frame_buf);
#endif
alevel_t alevel = demod_get_audio_level(chan, subchan);
multi_modem_process_rec_frame(chan, subchan, slice, H->frame_buf, H->frame_len, alevel, 0, 0);
H->eas_gathering = 0;
}
} // end eas_rec_bit
/*
EAS has no error detection.
Maybe that doesn't matter because we would normally be dealing with a reasonable
VHF FM or TV signal.
Let's see what happens when we intentionally introduce errors.
When some match and others don't, the multislice voting should give preference
to those matching others.
$ src/atest -P+ -B EAS -e 3e-3 ../../ref-doc/EAS/same.wav
Demodulator profile set to "+"
96000 samples per second. 16 bits per sample. 1 audio channels.
2079360 audio bytes in file. Duration = 10.8 seconds.
Fix Bits level = 0
Channel 0: 521 baud, AFSK 2083 & 1563 Hz, D+, 96000 sample rate / 3.
case 1: Slice 6 is different than others (EQS vs. EAS) so we want one of the others that match.
Slice 3 has an unexpected character (in 0120u7) so it is a mismatch.
At this point we are not doing validity checking other than all printable characters.
We are left with 0 & 4 which don't match (012057 vs. 012077).
So I guess we don't have any two that match so it is a toss up.
reject 7 invalid character = ZCZC-EAS-RWT-0120
reject 5 invalid character = ZCZC-ECW-RWT-012057-012081-012101-012103-012115+003
frame_buf 6 = ZCZC-EQS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
frame_buf 4 = ZCZC-EAS-RWT-012077-012081-012101-012103-012115+0030-2780415-WTSP/TV-
frame_buf 3 = ZCZC-EAS-RWT-0120u7-012281-012101-012103-092115+0038-2780415-VTSP/TV-
frame_buf 0 = ZCZC-EAS-RWT-012057-412081-012101-012103-012115+0030-2780415-WTSP/TV-
DECODED[1] 0:01.313 EAS audio level = 194(106/108) |__||_|__
[0.0] EAS>APDW16:{DEZCZC-EAS-RWT-012057-412081-012101-012103-012115+0030-2780415-WTSP/TV-
Case 2: We have two that match so pick either one.
reject 5 invalid character = ZCZC-EAS-RW
reject 7 invalid character = ZCZC-EAS-RWT-0
reject 3 invalid character = ZCZC-EAS-RWT-012057-012080-012101-012103-01211
reject 0 invalid character = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-W
frame_buf 6 = ZCZC-EAS-RWT-012057-012081-012!01-012103-012115+0030-2780415-WTSP/TV-
frame_buf 1 = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
DECODED[2] 0:03.617 EAS audio level = 194(106/108) _|____|__
[0.1] EAS>APDW16:{DEZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
Case 3: Slice 6 is a mismatch (EAs vs. EAS).
Slice 7 has RST rather than RWT.
2 & 4 don't match either (012141 vs. 012101).
We have another case where no two match so there is no clear winner.
reject 5 invalid character = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+
frame_buf 7 = ZCZC-EAS-RST-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
frame_buf 6 = ZCZC-EAs-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
frame_buf 4 = ZCZC-EAS-RWT-112057-012081-012101-012103-012115+0030-2780415-WTSP/TV-
frame_buf 2 = ZCZC-EAS-RWT-012057-012081-012141-012103-012115+0030-2780415-WTSP/TV-
DECODED[3] 0:05.920 EAS audio level = 194(106/108) __|_|_||_
[0.2] EAS>APDW16:{DEZCZC-EAS-RWT-012057-012081-012141-012103-012115+0030-2780415-WTSP/TV-
Conclusions:
(1) The existing algorithm gives a higher preference to those frames matching others.
We didn't see any cases here where that would be to our advantage.
(2) A partial solution would be more validity checking. (i.e. non-digit where
digit is expected.) But wait... We might want to keep it for consideration:
(3) If I got REALLY ambitious, some day, we could compare all of them one column
at a time and take the most popular (and valid for that column) character and
use all of the most popular characters. Better yet, at the bit level.
Of course this is probably all overkill because we would normally expect to have pretty
decent signals. The designers didn't even bother to add any sort of checksum for error checking.
The random errors injected are also not realistic. Actual noise would probably wipe out the
same bit(s) for all of the slices.
The protocol specification suggests comparing all 3 transmissions and taking the best 2 out of 3.
I think that would best be left to an external application and we just concentrate on being
a good modem here and providing a result when it is received.
*/
/***********************************************************************************
*
* Name: hdlc_rec_bit
*
* Purpose: Extract HDLC frames from a stream of bits.
*
* Inputs: chan - Channel number.
*
* subchan - This allows multiple demodulators per channel.
*
* slice - Allows multiple slicers per demodulator (subchannel).
*
* raw - One bit from the demodulator.
* should be 0 or 1.
*
* is_scrambled - Is the data scrambled?
*
* descram_state - Current descrambler state. (not used - remove)
* Not so fast - plans to add new parameter. PSK already provides it.
*
*
* Description: This is called once for each received bit.
* For each valid frame, process_rec_frame()
* is called for further processing.
*
***********************************************************************************/
void hdlc_rec_bit(int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove)
{
2024-04-13 17:48:58 +01:00
int dbit; /* Data bit after undoing NRZI. */
2023-09-12 21:38:15 +01:00
/* Should be only 0 or 1. */
2024-04-13 17:48:58 +01:00
2023-09-12 21:38:15 +01:00
struct hdlc_state_s *H;
2024-04-13 17:48:58 +01:00
int descram;
2023-09-12 21:38:15 +01:00
/*
* Different state information for each channel / subchannel / slice.
*/
H = &hdlc_state[chan];
/*
* Using NRZI encoding,
* A '0' bit is represented by an inversion since previous bit.
* A '1' bit is represented by no change.
*/
2024-04-13 17:48:58 +01:00
if (is_scrambled)
{
2023-09-12 21:38:15 +01:00
descram = descramble(raw, &(H->lfsr));
2024-04-13 17:48:58 +01:00
dbit = (descram == H->prev_descram); // This does nrzi
2023-09-12 21:38:15 +01:00
H->prev_descram = descram;
H->prev_raw = raw;
}
else {
dbit = (raw == H->prev_raw);
2024-04-13 17:48:58 +01:00
H->prev_raw = raw; // This does nrzi
2023-09-12 21:38:15 +01:00
}
2024-04-13 17:48:58 +01:00
// dbit should be after nrzi, descram after descramble but before nrzi
2023-09-12 21:38:15 +01:00
// After BER insertion, NRZI, and any descrambling, feed into FX.25 decoder as well.
// fx25_rec_bit (chan, subchan, slice, dbit);
2024-04-13 17:48:58 +01:00
if (il2p_mode[chan])
{
il2p_rec_bit(chan, subchan, slice, raw); // not scrambled, not nrzi
if (il2p_mode[chan] == IL2P_MODE_ONLY) // Dont try HDLC decode
return;
}
2023-09-12 21:38:15 +01:00
/*
* Octets are sent LSB first.
* Shift the most recent 8 bits thru the pattern detector.
*/
H->pat_det >>= 1;
if (dbit) {
H->pat_det |= 0x80;
}
H->flag4_det >>= 1;
if (dbit) {
H->flag4_det |= 0x80000000;
}
rrbb_append_bit(H->rrbb, raw);
if (H->pat_det == 0x7e) {
rrbb_chop8(H->rrbb);
/*
* The special pattern 01111110 indicates beginning and ending of a frame.
* If we have an adequate number of whole octets, it is a candidate for
* further processing.
*
* It might look odd that olen is being tested for 7 instead of 0.
* This is because oacc would already have 7 bits from the special
* "flag" pattern before it is detected here.
*/
#if OLD_WAY
#if TEST
text_color_set(DW_COLOR_DEBUG);
dw_printf("\nfound flag, olen = %d, frame_len = %d\n", olen, frame_len);
#endif
if (H->olen == 7 && H->frame_len >= MIN_FRAME_LEN) {
unsigned short actual_fcs, expected_fcs;
#if TEST
int j;
dw_printf("TRADITIONAL: frame len = %d\n", H->frame_len);
for (j = 0; j < H->frame_len; j++) {
dw_printf(" %02x", H->frame_buf[j]);
}
dw_printf("\n");
#endif
/* Check FCS, low byte first, and process... */
/* Alternatively, it is possible to include the two FCS bytes */
/* in the CRC calculation and look for a magic constant. */
/* That would be easier in the case where the CRC is being */
/* accumulated along the way as the octets are received. */
/* I think making a second pass over it and comparing is */
/* easier to understand. */
actual_fcs = H->frame_buf[H->frame_len - 2] | (H->frame_buf[H->frame_len - 1] << 8);
expected_fcs = fcs_calc(H->frame_buf, H->frame_len - 2);
if (actual_fcs == expected_fcs) {
alevel_t alevel = demod_get_audio_level(chan, subchan);
multi_modem_process_rec_frame(chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE, 0); /* len-2 to remove FCS. */
}
else {
#if TEST
dw_printf("*** actual fcs = %04x, expected fcs = %04x ***\n", actual_fcs, expected_fcs);
#endif
}
}
#else
/*
* New way - Decode the raw bits in later step.
*/
#if TEST
text_color_set(DW_COLOR_DEBUG);
dw_printf("\nfound flag, channel %d.%d, %d bits in frame\n", chan, subchan, rrbb_get_len(H->rrbb) - 1);
#endif
if (rrbb_get_len(H->rrbb) >= MIN_FRAME_LEN * 8) {
alevel_t alevel = demod_get_audio_level(chan, subchan);
rrbb_set_audio_level(H->rrbb, alevel);
hdlc_rec2_block(H->rrbb);
/* Now owned by someone else who will free it. */
H->rrbb = rrbb_new(chan, 0, 0, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */
}
else {
rrbb_clear(H->rrbb, is_scrambled, H->lfsr, H->prev_descram);
}
H->olen = 0; /* Allow accumulation of octets. */
H->frame_len = 0;
rrbb_append_bit(H->rrbb, H->prev_raw); /* Last bit of flag. Needed to get first data bit. */
/* Now that we are saving other initial state information, */
/* it would be sensible to do the same for this instead */
/* of lumping it in with the frame data bits. */
#endif
}
//#define EXPERIMENT12B 1
#if EXPERIMENT12B
else if (H->pat_det == 0xff) {
/*
* Valid data will never have seven 1 bits in a row.
*
* 11111110
*
* This indicates loss of signal.
* But we will let it slip thru because it might diminish
2024-04-13 17:48:58 +01:00
* our float bit fixup effort. Instead give up on frame
2023-09-12 21:38:15 +01:00
* only when we see eight 1 bits in a row.
*
* 11111111
*
* What is the impact? No difference.
*
* Before: atest -P E -F 1 ../02_Track_2.wav = 1003
* After: atest -P E -F 1 ../02_Track_2.wav = 1003
*/
#else
else if (H->pat_det == 0xfe) {
/*
* Valid data will never have 7 one bits in a row.
*
* 11111110
*
* This indicates loss of signal.
*/
#endif
H->olen = -1; /* Stop accumulating octets. */
H->frame_len = 0; /* Discard anything in progress. */
rrbb_clear(H->rrbb, is_scrambled, H->lfsr, H->prev_descram);
}
else if ((H->pat_det & 0xfc) == 0x7c) {
/*
* If we have five '1' bits in a row, followed by a '0' bit,
*
* 0111110xx
*
* the current '0' bit should be discarded because it was added for
* "bit stuffing."
*/
;
}
else {
/*
* In all other cases, accumulate bits into octets, and complete octets
* into the frame buffer.
*/
if (H->olen >= 0) {
H->oacc >>= 1;
if (dbit) {
H->oacc |= 0x80;
}
H->olen++;
if (H->olen == 8) {
H->olen = 0;
if (H->frame_len < MAX_FRAME_LEN) {
H->frame_buf[H->frame_len] = H->oacc;
H->frame_len++;
}
}
}
}
}
// 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.
/*-------------------------------------------------------------------
*
* Name: dcd_change
*
* Purpose: Combine DCD states of all subchannels/ into an overall
* state for the channel.
*
* Inputs: chan
*
* subchan 0 to MAX_SUBCHANS-1 for HDLC.
* SPECIAL CASE --> MAX_SUBCHANS for DTMF decoder.
*
* slice slicer number, 0 .. MAX_SLICERS - 1.
*
* state 1 for active, 0 for not.
*
* Returns: None. Use hdlc_rec_data_detect_any to retrieve result.
*
* Description: DCD for the channel is active if ANY of the subchannels/slices
* are active. Update the DCD indicator.
*
* version 1.3: Add DTMF detection into the final result.
* This is now called from dtmf.c too.
*
*--------------------------------------------------------------------*/
void dcd_change(int chan, int subchan, int slice, int state)
{
}
/*-------------------------------------------------------------------
*
* Name: hdlc_rec_data_detect_any
*
* Purpose: Determine if the radio channel is currently busy
* with packet data.
* This version doesn't care about voice or other sounds.
* This is used by the transmit logic to transmit only
* when the channel is clear.
*
* Inputs: chan - Audio channel.
*
* Returns: True if channel is busy (data detected) or
* false if OK to transmit.
*
*
* Description: We have two different versions here.
*
* hdlc_rec_data_detect_any sees if ANY of the decoders
* for this channel are receiving a signal. This is
* used to determine whether the channel is clear and
* we can transmit. This would apply to the 300 baud
* HF SSB case where we have multiple decoders running
* at the same time. The channel is busy if ANY of them
* thinks the channel is busy.
*
* Version 1.3: New option for input signal to inhibit transmit.
*
*--------------------------------------------------------------------*/
int hdlc_rec_data_detect_any(int chan)
{
int sc;
assert(chan >= 0 && chan < MAX_CHANS);
for (sc = 0; sc < num_subchan[chan]; sc++) {
if (composite_dcd[chan][sc] != 0)
return (1);
}
return (0);
} /* end hdlc_rec_data_detect_any */
/* end hdlc_rec.c */
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//
// 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 2 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/>.
//
/********************************************************************************
*
* File: hdlc_rec2.c
*
* Purpose: Extract HDLC frame from a block of bits after someone
* else has done the work of pulling it out from between
* the special "flag" sequences.
*
*
* New in version 1.1:
*
* Several enhancements provided by Fabrice FAURE:
*
* - Additional types of attempts to fix a bad CRC.
* - Optimized code to reduce execution time.
* - Improved detection of duplicate packets from different fixup attempts.
* - Set limit on number of packets in fix up later queue.
*
* One of the new recovery attempt cases recovers three additional
* packets that were lost before. The one thing I disagree with is
* use of the word "swap" because that sounds like two things
* are being exchanged for each other. I would prefer "flip"
* or "invert" to describe changing a bit to the opposite state.
* I took "swap" out of the user-visible messages but left the
* rest of the source code as provided.
*
* Test results: We intentionally use the worst demodulator so there
* is more opportunity to try to fix the frames.
*
* atest -P A -F n 02_Track_2.wav
*
* n description frames sec
* -- ----------- ------ ---
* 0 no attempt 963 40 error-free frames
* 1 invert 1 979 41 16 more
* 2 invert 2 982 42 3 more
* 3 invert 3 982 42 no change
* 4 remove 1 982 43 no change
* 5 remove 2 982 43 no change
* 6 remove 3 982 43 no change
* 7 insert 1 982 45 no change
* 8 insert 2 982 47 no change
* 9 invert two sep 993 178 11 more, some visually obvious errors.
* 10 invert many? 993 190 no change
* 11 remove many 995 190 2 more, need to investigate in detail.
* 12 remove two sep 995 201 no change
*
* Observations: The "insert" and "remove" techniques had no benefit. I would not expect them to.
* We have a phase locked loop that attempts to track any slight variations in the
* timing so we sample near the middle of the bit interval. Bits can get corrupted
* by noise but not disappear or just appear. That would be a gap in the timing.
* These should probably be removed in a future version.
*
*
* Version 1.2: Now works for 9600 baud.
* This was more complicated due to the data scrambling.
* It was necessary to retain more initial state information after
* the start flag octet.
*
* Version 1.3: Took out all of the "insert" and "remove" cases because they
* offer no benenfit.
*
* Took out the delayed processing and just do it realtime.
* Changed SWAP to INVERT because it is more descriptive.
*
*******************************************************************************/
//#define DEBUG 1
//#define DEBUGx 1
//#define DEBUG_LATER 1
/* Audio configuration. */
static struct audio_s *save_audio_config_p;
/*
* Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS.
*/
#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)
/*
* This is the current state of the HDLC decoder.
*
* It is possible to run multiple decoders concurrently by
* having a separate set of state variables for each.
*
* Should have a reset function instead of initializations here.
*/
// TODO: Clean up. This is a remnant of splitting hdlc_rec.c into 2 parts.
// This is not the same as hdlc_state_s in hdlc_rec.c
// "2" was added to reduce confusion. Can be trimmed down.
struct hdlc_state2_s {
int prev_raw; /* Keep track of previous bit so */
/* we can look for transitions. */
/* Should be only 0 or 1. */
int is_scrambled; /* Set for 9600 baud. */
int lfsr; /* Descrambler shift register for 9600 baud. */
int prev_descram; /* Previous unscrambled for 9600 baud. */
unsigned char pat_det; /* 8 bit pattern detector shift register. */
/* See below for more details. */
unsigned char oacc; /* Accumulator for building up an octet. */
int olen; /* Number of bits in oacc. */
/* When this reaches 8, oacc is copied */
/* to the frame buffer and olen is zeroed. */
unsigned char frame_buf[MAX_FRAME_LEN];
/* One frame is kept here. */
int frame_len; /* Number of octets in frame_buf. */
/* Should be in range of 0 .. MAX_FRAME_LEN. */
};
typedef enum retry_mode_e {
RETRY_MODE_CONTIGUOUS = 0,
RETRY_MODE_SEPARATED = 1,
} retry_mode_t;
typedef enum retry_type_e {
RETRY_TYPE_NONE = 0,
RETRY_TYPE_SWAP = 1
} retry_type_t;
typedef struct retry_conf_s {
retry_t retry;
retry_mode_t mode;
retry_type_t type;
union {
struct {
int bit_idx_a; /* */
int bit_idx_b; /* */
int bit_idx_c; /* */
} sep; /* RETRY_MODE_SEPARATED */
struct {
int bit_idx;
int nr_bits;
} contig; /* RETRY_MODE_CONTIGUOUS */
} u_bits;
int insert_value;
} retry_conf_t;
#if defined(DIREWOLF_C) || defined(ATEST_C) || defined(UDPTEST_C)
static const char * retry_text[] = {
"NONE",
2024-04-13 17:48:58 +01:00
"float",
2023-09-12 21:38:15 +01:00
"DOUBLE",
"TRIPLE",
"TWO_SEP",
"PASSALL" };
#endif
static int try_decode(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall);
static int try_to_fix_quick_now(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel);
static int sanity_check(unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test);
/***********************************************************************************
*
* Name: hdlc_rec2_init
*
* Purpose: Initialization.
*
* Inputs: p_audio_config - Pointer to configuration settings.
* This is what we care about for each channel.
*
* enum retry_e fix_bits;
* Level of effort to recover from
* a bad FCS on the frame.
* 0 = no effort
2024-04-13 17:48:58 +01:00
* 1 = try inverting a float bit
2023-09-12 21:38:15 +01:00
* 2... = more techniques...
*
* enum sanity_e sanity_test;
* Sanity test to apply when finding a good
* CRC after changing one or more bits.
* Must look like APRS, AX.25, or anything.
*
* int passall;
* Allow thru even with bad CRC after exhausting
* all fixup attempts.
*
* Description: Save pointer to configuration for later use.
*
***********************************************************************************/
void hdlc_rec2_init(struct audio_s *p_audio_config)
{
save_audio_config_p = p_audio_config;
}
/***********************************************************************************
*
* Name: hdlc_rec2_block
*
* Purpose: Extract HDLC frame from a stream of bits.
*
* Inputs: block - Handle for bit array.
*
* Description: The other (original) hdlc decoder took one bit at a time
* right out of the demodulator.
*
* This is different in that it processes a block of bits
* previously extracted from between two "flag" patterns.
*
* This allows us to try decoding the same received data more
* than once.
*
* Version 1.2: Now works properly for G3RUH type scrambling.
*
***********************************************************************************/
void hdlc_rec2_block(rrbb_t block)
{
int chan = rrbb_get_chan(block);
int subchan = rrbb_get_subchan(block);
int slice = rrbb_get_slice(block);
alevel_t alevel = rrbb_get_audio_level(block);
retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
int passall = save_audio_config_p->achan[chan].passall;
int ok;
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf("\n--- try to decode ---\n");
#endif
/* Create an empty retry configuration */
retry_conf_t retry_cfg;
memset(&retry_cfg, 0, sizeof(retry_cfg));
/*
* For our first attempt we don't try to alter any bits.
* Still let it thru if passall AND no retries are desired.
*/
retry_cfg.type = RETRY_TYPE_NONE;
retry_cfg.mode = RETRY_MODE_CONTIGUOUS;
retry_cfg.retry = RETRY_NONE;
retry_cfg.u_bits.contig.nr_bits = 0;
retry_cfg.u_bits.contig.bit_idx = 0;
ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, passall & (fix_bits == RETRY_NONE));
if (ok) {
#if DEBUG
text_color_set(DW_COLOR_INFO);
dw_printf("Got it the first time.\n");
#endif
rrbb_delete(block);
return;
}
/*
* Not successful with frame in original form.
* See if we can "fix" it.
*/
if (try_to_fix_quick_now(block, chan, subchan, slice, alevel)) {
rrbb_delete(block);
return;
}
if (passall) {
/* Exhausted all desired fix up attempts. */
/* Let thru even with bad CRC. Of course, it still */
/* needs to be a minimum number of whole octets. */
ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 1);
rrbb_delete(block);
}
else {
rrbb_delete(block);
}
} /* end hdlc_rec2_block */
/***********************************************************************************
*
* Name: try_to_fix_quick_now
*
* Purpose: Attempt some quick fixups that don't take very long.
*
* Inputs: block - Stream of bits that might be a frame.
* chan - Radio channel from which it was received.
* subchan - Which demodulator when more than one per channel.
* alevel - Audio level for later reporting.
*
* Global In: configuration fix_bits - Maximum level of fix up to attempt.
*
* RETRY_NONE (0) - Don't try any.
2024-04-13 17:48:58 +01:00
* RETRY_INVERT_float (1) - Try inverting float bits.
2023-09-12 21:38:15 +01:00
* etc.
*
* configuration passall - Let it thru with bad CRC after exhausting
* all fixup attempts.
*
*
* Returns: 1 for success. "try_decode" has passed the result along to the
* processing step.
* 0 for failure. Caller might continue with more aggressive attempts.
*
* Original: Some of the attempted fix up techniques are quick.
* We will attempt them immediately after receiving the frame.
* Others, that take time order N**2, will be done in a later section.
*
* Version 1.2: Now works properly for G3RUH type scrambling.
*
* Version 1.3: Removed the extra cases that didn't help.
* The separated bit case is now handled immediately instead of
* being thrown in a queue for later processing.
*
***********************************************************************************/
static int try_to_fix_quick_now(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel)
{
int ok;
int len, i;
retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
//int passall = save_audio_config_p->achan[chan].passall;
len = rrbb_get_len(block);
/* Prepare the retry configuration */
retry_conf_t retry_cfg;
memset(&retry_cfg, 0, sizeof(retry_cfg));
/* Will modify only contiguous bits*/
retry_cfg.mode = RETRY_MODE_CONTIGUOUS;
/*
* Try inverting one bit.
*/
if (fix_bits < RETRY_INVERT_SINGLE) {
2024-04-13 17:48:58 +01:00
/* Stop before float bit fix up. */
2023-09-12 21:38:15 +01:00
return 0; /* failure. */
}
/* Try to swap one bit */
retry_cfg.type = RETRY_TYPE_SWAP;
retry_cfg.retry = RETRY_INVERT_SINGLE;
retry_cfg.u_bits.contig.nr_bits = 1;
for (i = 0; i < len; i++) {
/* Set the index of the bit to swap */
retry_cfg.u_bits.contig.bit_idx = i;
ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 0);
if (ok) {
#if DEBUG
text_color_set(DW_COLOR_ERROR);
2024-04-13 17:48:58 +01:00
dw_printf("*** Success by flipping float bit %d of %d ***\n", i, len);
2023-09-12 21:38:15 +01:00
#endif
return 1;
}
}
/*
* Try inverting two adjacent bits.
*/
if (fix_bits < RETRY_INVERT_DOUBLE) {
return 0;
}
/* Try to swap two contiguous bits */
retry_cfg.retry = RETRY_INVERT_DOUBLE;
retry_cfg.u_bits.contig.nr_bits = 2;
for (i = 0; i < len - 1; i++) {
retry_cfg.u_bits.contig.bit_idx = i;
ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 0);
if (ok) {
#if DEBUG
text_color_set(DW_COLOR_ERROR);
dw_printf("*** Success by flipping DOUBLE bit %d of %d ***\n", i, len);
#endif
return 1;
}
}
/*
* Try inverting adjacent three bits.
*/
if (fix_bits < RETRY_INVERT_TRIPLE) {
return 0;
}
/* Try to swap three contiguous bits */
retry_cfg.retry = RETRY_INVERT_TRIPLE;
retry_cfg.u_bits.contig.nr_bits = 3;
for (i = 0; i < len - 2; i++) {
retry_cfg.u_bits.contig.bit_idx = i;
ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 0);
if (ok) {
#if DEBUG
text_color_set(DW_COLOR_ERROR);
dw_printf("*** Success by flipping TRIPLE bit %d of %d ***\n", i, len);
#endif
return 1;
}
}
/*
2024-04-13 17:48:58 +01:00
* Two non-adjacent ("separated") float bits.
2023-09-12 21:38:15 +01:00
* It chews up a lot of CPU time. Usual test takes 4 times longer to run.
*
* Processing time is order N squared so time goes up rapidly with larger frames.
*/
if (fix_bits < RETRY_INVERT_TWO_SEP) {
return 0;
}
retry_cfg.mode = RETRY_MODE_SEPARATED;
retry_cfg.type = RETRY_TYPE_SWAP;
retry_cfg.retry = RETRY_INVERT_TWO_SEP;
retry_cfg.u_bits.sep.bit_idx_c = -1;
#ifdef DEBUG_LATER
tstart = dtime_now();
dw_printf("*** Try flipping TWO SEPARATED BITS %d bits\n", len);
#endif
len = rrbb_get_len(block);
for (i = 0; i < len - 2; i++) {
retry_cfg.u_bits.sep.bit_idx_a = i;
int j;
ok = 0;
for (j = i + 2; j < len; j++) {
retry_cfg.u_bits.sep.bit_idx_b = j;
ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, 0);
if (ok) {
break;
}
}
if (ok) {
#if DEBUG
text_color_set(DW_COLOR_ERROR);
dw_printf("*** Success by flipping TWO SEPARATED bits %d and %d of %d \n", i, j, len);
#endif
return (1);
}
}
return 0;
}
// TODO: Remove this. but first figure out what to do in atest.c
int hdlc_rec2_try_to_fix_later(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel)
{
int ok;
//int len;
//retry_t fix_bits = save_audio_config_p->achan[chan].fix_bits;
int passall = save_audio_config_p->achan[chan].passall;
#if DEBUG_LATER
double tstart, tend;
#endif
retry_conf_t retry_cfg;
memset(&retry_cfg, 0, sizeof(retry_cfg));
//len = rrbb_get_len(block);
/*
* All fix up attempts have failed.
* Should we pass it along anyhow with a bad CRC?
* Note that we still need a minimum number of whole octets.
*/
if (passall) {
retry_cfg.type = RETRY_TYPE_NONE;
retry_cfg.mode = RETRY_MODE_CONTIGUOUS;
retry_cfg.retry = RETRY_NONE;
retry_cfg.u_bits.contig.nr_bits = 0;
retry_cfg.u_bits.contig.bit_idx = 0;
ok = try_decode(block, chan, subchan, slice, alevel, retry_cfg, passall);
return (ok);
}
return (0);
} /* end hdlc_rec2_try_to_fix_later */
/*
* Check if the specified index of bit has been modified with the current type of configuration
* Provide a specific implementation for contiguous mode to optimize number of tests done in the loop
*/
inline static char is_contig_bit_modified(int bit_idx, retry_conf_t retry_conf) {
int cont_bit_idx = retry_conf.u_bits.contig.bit_idx;
int cont_nr_bits = retry_conf.u_bits.contig.nr_bits;
if (bit_idx >= cont_bit_idx && (bit_idx < cont_bit_idx + cont_nr_bits))
return 1;
else
return 0;
}
/*
* Check if the specified index of bit has been modified with the current type of configuration in separated bit index mode
* Provide a specific implementation for separated mode to optimize number of tests done in the loop
*/
inline static char is_sep_bit_modified(int bit_idx, retry_conf_t retry_conf) {
if (bit_idx == retry_conf.u_bits.sep.bit_idx_a ||
bit_idx == retry_conf.u_bits.sep.bit_idx_b ||
bit_idx == retry_conf.u_bits.sep.bit_idx_c)
return 1;
else
return 0;
}
/***********************************************************************************
*
* Name: try_decode
*
* Purpose:
*
* Inputs: block - Bit string that was collected between "flag" patterns.
*
* chan, subchan - where it came from.
*
* alevel - audio level for later reporting.
*
* retry_conf - Controls changes that will be attempted to get a good CRC.
*
* retry:
* Level of effort to recover from a bad FCS on the frame.
* RETRY_NONE = 0
2024-04-13 17:48:58 +01:00
* RETRY_INVERT_float = 1
2023-09-12 21:38:15 +01:00
* RETRY_INVERT_DOUBLE = 2
* RETRY_INVERT_TRIPLE = 3
* RETRY_INVERT_TWO_SEP = 4
*
* mode: RETRY_MODE_CONTIGUOUS - change adjacent bits.
* contig.bit_idx - first bit position
* contig.nr_bits - number of bits
*
* RETRY_MODE_SEPARATED - change bits not next to each other.
* sep.bit_idx_a - bit positions
* sep.bit_idx_b - bit positions
* sep.bit_idx_c - bit positions
*
* type: RETRY_TYPE_NONE - Make no changes.
* RETRY_TYPE_SWAP - Try inverting.
*
* passall - All it thru even with bad CRC.
* Valid only when no changes make. i.e.
* retry == RETRY_NONE, type == RETRY_TYPE_NONE
*
* Returns: 1 = successfully extracted something.
* 0 = failure.
*
***********************************************************************************/
static int try_decode(rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall)
{
struct hdlc_state2_s H2;
int blen; /* Block length in bits. */
int i;
int raw; /* From demodulator. Should be 0 or 1. */
#if DEBUGx
int crc_failed = 1;
#endif
int retry_conf_mode = retry_conf.mode;
int retry_conf_type = retry_conf.type;
int retry_conf_retry = retry_conf.retry;
H2.is_scrambled = rrbb_get_is_scrambled(block);
H2.prev_descram = rrbb_get_prev_descram(block);
H2.lfsr = rrbb_get_descram_state(block);
H2.prev_raw = rrbb_get_bit(block, 0); /* Actually last bit of the */
/* opening flag so we can derive the */
/* first data bit. */
/* Does this make sense? */
/* This is the last bit of the "flag" pattern. */
/* If it was corrupted we wouldn't have detected */
/* the start of frame. */
if ((retry_conf.mode == RETRY_MODE_CONTIGUOUS && is_contig_bit_modified(0, retry_conf)) ||
(retry_conf.mode == RETRY_MODE_SEPARATED && is_sep_bit_modified(0, retry_conf))) {
H2.prev_raw = !H2.prev_raw;
}
H2.pat_det = 0;
H2.oacc = 0;
H2.olen = 0;
H2.frame_len = 0;
blen = rrbb_get_len(block);
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
if (retry_conf.type == RETRY_TYPE_NONE)
dw_printf("try_decode: blen=%d\n", blen);
#endif
for (i = 1; i < blen; i++) {
/* Get the value for the current bit */
raw = rrbb_get_bit(block, i);
/* If swap two sep mode , swap the bit if needed */
if (retry_conf_retry == RETRY_INVERT_TWO_SEP) {
if (is_sep_bit_modified(i, retry_conf))
raw = !raw;
}
/* Else handle all the others contiguous modes */
else if (retry_conf_mode == RETRY_MODE_CONTIGUOUS) {
if (retry_conf_type == RETRY_TYPE_SWAP) {
/* If this is the bit to swap */
if (is_contig_bit_modified(i, retry_conf))
raw = !raw;
}
}
else {
}
/*
* Octets are sent LSB first.
* Shift the most recent 8 bits thru the pattern detector.
*/
H2.pat_det >>= 1;
/*
* Using NRZI encoding,
* A '0' bit is represented by an inversion since previous bit.
* A '1' bit is represented by no change.
* Note: this code can be factorized with the raw != H2.prev_raw code at the cost of processing time
*/
int dbit;
if (H2.is_scrambled) {
int descram;
descram = descramble(raw, &(H2.lfsr));
dbit = (descram == H2.prev_descram);
H2.prev_descram = descram;
H2.prev_raw = raw;
}
else {
dbit = (raw == H2.prev_raw);
H2.prev_raw = raw;
}
if (dbit) {
H2.pat_det |= 0x80;
/* Valid data will never have 7 one bits in a row: exit. */
if (H2.pat_det == 0xfe) {
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf("try_decode: found abort, i=%d\n", i);
#endif
return 0;
}
H2.oacc >>= 1;
H2.oacc |= 0x80;
}
else {
/* The special pattern 01111110 indicates beginning and ending of a frame: exit. */
if (H2.pat_det == 0x7e) {
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf("try_decode: found flag, i=%d\n", i);
#endif
return 0;
/*
* If we have five '1' bits in a row, followed by a '0' bit,
*
* 011111xx
*
* the current '0' bit should be discarded because it was added for
* "bit stuffing."
*/
}
else if ((H2.pat_det >> 2) == 0x1f) {
continue;
}
H2.oacc >>= 1;
}
/*
* Now accumulate bits into octets, and complete octets
* into the frame buffer.
*/
H2.olen++;
if (H2.olen & 8) {
H2.olen = 0;
if (H2.frame_len < MAX_FRAME_LEN) {
H2.frame_buf[H2.frame_len] = H2.oacc;
H2.frame_len++;
}
}
} /* end of loop on all bits in block */
/*
* Do we have a minimum number of complete bytes?
*/
#if DEBUGx
text_color_set(DW_COLOR_DEBUG);
dw_printf("try_decode: olen=%d, frame_len=%d\n", H2.olen, H2.frame_len);
#endif
if (H2.olen == 0 && H2.frame_len >= MIN_FRAME_LEN) {
unsigned short actual_fcs, expected_fcs;
#if DEBUGx
if (retry_conf.type == RETRY_TYPE_NONE) {
int j;
text_color_set(DW_COLOR_DEBUG);
dw_printf("NEW WAY: frame len = %d\n", H2.frame_len);
for (j = 0; j < H2.frame_len; j++) {
dw_printf(" %02x", H2.frame_buf[j]);
}
dw_printf("\n");
}
#endif
/* Check FCS, low byte first, and process... */
/* Alternatively, it is possible to include the two FCS bytes */
/* in the CRC calculation and look for a magic constant. */
/* That would be easier in the case where the CRC is being */
/* accumulated along the way as the octets are received. */
/* I think making a second pass over it and comparing is */
/* easier to understand. */
actual_fcs = H2.frame_buf[H2.frame_len - 2] | (H2.frame_buf[H2.frame_len - 1] << 8);
expected_fcs = get_fcs(H2.frame_buf, H2.frame_len - 2);
if (actual_fcs == expected_fcs &&
sanity_check(H2.frame_buf, H2.frame_len - 2, retry_conf.retry, save_audio_config_p->achan[chan].sanity_test)) {
// TODO: Shouldn't be necessary to pass chan, subchan, alevel into
// try_decode because we can obtain them from block.
// Let's make sure that assumption is good...
assert(rrbb_get_chan(block) == chan);
assert(rrbb_get_subchan(block) == subchan);
multi_modem_process_rec_frame(chan, subchan, slice, H2.frame_buf, H2.frame_len - 2, alevel, retry_conf.retry, 0); /* len-2 to remove FCS. */
return 1; /* success */
}
else if (passall) {
if (retry_conf_retry == RETRY_NONE && retry_conf_type == RETRY_TYPE_NONE) {
//text_color_set(DW_COLOR_ERROR);
//dw_printf ("ATTEMPTING PASSALL PROCESSING\n");
multi_modem_process_rec_frame(chan, subchan, slice, H2.frame_buf, H2.frame_len - 2, alevel, RETRY_MAX, 0); /* len-2 to remove FCS. */
return 1; /* success */
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("try_decode: internal error passall = %d, retry_conf_retry = %d, retry_conf_type = %d\n",
passall, retry_conf_retry, retry_conf_type);
}
}
else {
goto failure;
}
}
else {
#if DEBUGx
crc_failed = 0;
#endif
goto failure;
}
failure:
#if DEBUGx
if (retry_conf.type == RETRY_TYPE_NONE) {
int j;
text_color_set(DW_COLOR_ERROR);
if (crc_failed)
dw_printf("CRC failed\n");
if (H2.olen != 0)
dw_printf("Bad olen: %d \n", H2.olen);
else if (H2.frame_len < MIN_FRAME_LEN) {
dw_printf("Frame too small\n");
goto end;
}
dw_printf("FAILURE with frame: frame len = %d\n", H2.frame_len);
dw_printf("\n");
for (j = 0; j < H2.frame_len; j++) {
dw_printf(" %02x", H2.frame_buf[j]);
}
dw_printf("\nDEC\n");
for (j = 0; j < H2.frame_len; j++) {
dw_printf("%c", H2.frame_buf[j] >> 1);
}
dw_printf("\nORIG\n");
for (j = 0; j < H2.frame_len; j++) {
dw_printf("%c", H2.frame_buf[j]);
}
dw_printf("\n");
}
end:
#endif
return 0; /* failure. */
} /* end try_decode */
/***********************************************************************************
*
* Name: sanity_check
*
* Purpose: Try to weed out bogus packets from initially failed FCS matches.
*
* Inputs: buf
*
* blen
*
* bits_flipped
*
* sanity How much sanity checking to perform:
* SANITY_APRS - Looks like APRS. See User Guide,
* section that discusses bad apples.
* SANITY_AX25 - Has valid AX.25 address part.
* No checking of the rest. Useful for
* connected mode packet.
* SANITY_NONE - No checking. Would be suitable
* only if using frames that don't conform
* to AX.25 standard.
*
* Returns: 1 if it passes the sanity test.
*
* Description: This is NOT a validity check.
* We don't know if modifying the frame fixed the problem or made it worse.
* We can only test if it looks reasonable.
*
***********************************************************************************/
static int sanity_check(unsigned char *buf, int blen, retry_t bits_flipped, enum sanity_e sanity_test)
{
int alen; /* Length of address part. */
int j;
/*
* No sanity check if we didn't try fixing the data.
* Should we have different levels of checking depending on
* how much we try changing the raw data?
*/
if (bits_flipped == RETRY_NONE) {
return 1;
}
/*
* If using frames that do not conform to AX.25, it might be
* desirable to skip the sanity check entirely.
*/
if (sanity_test == SANITY_NONE) {
return (1);
}
/*
* Address part must be a multiple of 7.
*/
alen = 0;
for (j = 0; j < blen && alen == 0; j++) {
if (buf[j] & 0x01) {
alen = j + 1;
}
}
if (alen % 7 != 0) {
#if DEBUGx
text_color_set(DW_COLOR_ERROR);
dw_printf("sanity_check: FAILED. Address part length %d not multiple of 7.\n", alen);
#endif
return 0;
}
/*
* Need at least 2 addresses and maximum of 8 digipeaters.
*/
if (alen / 7 < 2 || alen / 7 > 10) {
#if DEBUGx
text_color_set(DW_COLOR_ERROR);
dw_printf("sanity_check: FAILED. Too few or many addresses.\n");
#endif
return 0;
}
/*
* Addresses can contain only upper case letters, digits, and space.
*/
for (j = 0; j < alen; j += 7) {
char addr[7];
addr[0] = buf[j + 0] >> 1;
addr[1] = buf[j + 1] >> 1;
addr[2] = buf[j + 2] >> 1;
addr[3] = buf[j + 3] >> 1;
addr[4] = buf[j + 4] >> 1;
addr[5] = buf[j + 5] >> 1;
addr[6] = '\0';
if ((!isupper(addr[0]) && !isdigit(addr[0])) ||
(!isupper(addr[1]) && !isdigit(addr[1]) && addr[1] != ' ') ||
(!isupper(addr[2]) && !isdigit(addr[2]) && addr[2] != ' ') ||
(!isupper(addr[3]) && !isdigit(addr[3]) && addr[3] != ' ') ||
(!isupper(addr[4]) && !isdigit(addr[4]) && addr[4] != ' ') ||
(!isupper(addr[5]) && !isdigit(addr[5]) && addr[5] != ' ')) {
#if DEBUGx
text_color_set(DW_COLOR_ERROR);
dw_printf("sanity_check: FAILED. Invalid characters in addresses \"%s\"\n", addr);
#endif
return 0;
}
}
/*
* That's good enough for the AX.25 sanity check.
* Continue below for additional APRS checking.
*/
if (sanity_test == SANITY_AX25) {
return (1);
}
/*
* The next two bytes should be 0x03 and 0xf0 for APRS.
*/
if (buf[alen] != 0x03 || buf[alen + 1] != 0xf0) {
return (0);
}
/*
* Finally, look for bogus characters in the information part.
* In theory, the bytes could have any values.
* In practice, we find only printable ASCII characters and:
*
* 0x0a line feed
* 0x0d carriage return
* 0x1c MIC-E
* 0x1d MIC-E
* 0x1e MIC-E
* 0x1f MIC-E
* 0x7f MIC-E
* 0x80 "{UIV32N}<0x0d><0x9f><0x80>"
* 0x9f "{UIV32N}<0x0d><0x9f><0x80>"
* 0xb0 degree symbol, ISO LATIN1
* (Note: UTF-8 uses two byte sequence 0xc2 0xb0.)
* 0xbe invalid MIC-E encoding.
* 0xf8 degree symbol, Microsoft code page 437
*
* So, if we have something other than these (in English speaking countries!),
* chances are that we have bogus data from twiddling the wrong bits.
*
* Notice that we shouldn't get here for good packets. This extra level
* of checking happens only if we twiddled a couple of bits, possibly
* creating bad data. We want to be very fussy.
*/
for (j = alen + 2; j < blen; j++) {
int ch = buf[j];
if (!((ch >= 0x1c && ch <= 0x7f)
|| ch == 0x0a
|| ch == 0x0d
|| ch == 0x80
|| ch == 0x9f
|| ch == 0xc2
|| ch == 0xb0
|| ch == 0xf8)) {
#if DEBUGx
text_color_set(DW_COLOR_ERROR);
dw_printf("sanity_check: FAILED. Probably bogus info char 0x%02x\n", ch);
#endif
return 0;
}
}
return 1;
}
/* end hdlc_rec2.c */
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2015, 2019, 2021 John Langner, WB2OSZ
//
// 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 2 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/>.
//
//#define DEBUG4 1 /* capture 9600 output to log files */
/*------------------------------------------------------------------
*
* Module: demod_9600.c
*
* Purpose: Demodulator for baseband signal.
* This is used for AX.25 (with scrambling) and IL2P without.
*
* Input: Audio samples from either a file or the "sound card."
*
* Outputs: Calls hdlc_rec_bit() for each bit demodulated.
*
*---------------------------------------------------------------*/
// Fine tuning for different demodulator types.
// Don't remove this section. It is here for a reason.
void gen_lowpass(float fc, float *lp_filter, int filter_size, bp_window_t wtype);
void hdlc_rec_bit(int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove);
static float slice_point[MAX_SUBCHANS];
/* Add sample to buffer and shift the rest down. */
static inline void push_sample(float val, float *buff, int size)
{
memmove(buff + 1, buff, (size - 1) * sizeof(float));
buff[0] = val;
}
/* FIR filter kernel. */
static inline float convolve(const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
{
float sum = 0.0f;
int j;
//#pragma GCC ivdep // ignored until gcc 4.9
for (j = 0; j < filter_size; j++) {
sum += filter[j] * data[j];
}
return (sum);
}
/* Automatic gain control. */
/* Result should settle down to 1 unit peak to peak. i.e. -0.5 to +0.5 */
static inline float agc(float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
{
if (in >= *ppeak) {
*ppeak = in * fast_attack + *ppeak * (1.0f - fast_attack);
}
else {
*ppeak = in * slow_decay + *ppeak * (1.0f - slow_decay);
}
if (in <= *pvalley) {
*pvalley = in * fast_attack + *pvalley * (1.0f - fast_attack);
}
else {
*pvalley = in * slow_decay + *pvalley * (1.0f - slow_decay);
}
if (*ppeak > *pvalley) {
return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
}
return (0.0);
}
/*------------------------------------------------------------------
*
* Name: demod_9600_init
*
* Purpose: Initialize the 9600 (or higher) baud demodulator.
*
* Inputs: modem_type - Determines whether scrambling is used.
*
* samples_per_sec - Number of samples per second for audio.
*
* upsample - Factor to upsample the incoming stream.
* After a lot of experimentation, I discovered that
* it works better if the data is upsampled.
* This reduces the jitter for PLL synchronization.
*
* baud - Data rate in bits per second.
*
* D - Address of demodulator state.
*
* Returns: None
*
*----------------------------------------------------------------*/
void demod_9600_init(enum modem_t modem_type, int original_sample_rate, int upsample, int baud, struct demodulator_state_s *D)
{
float fc;
int j;
if (upsample < 1) upsample = 1;
if (upsample > 4) upsample = 4;
memset(D, 0, sizeof(struct demodulator_state_s));
D->modem_type = modem_type;
D->num_slicers = 1;
// Multiple profiles in future?
// switch (profile) {
// case 'J': // upsample x2 with filtering.
// case 'K': // upsample x3 with filtering.
// case 'L': // upsample x4 with filtering.
D->lp_filter_len_bits = 1.0; // -U4 = 61 4.59 samples/symbol
// Works best with odd number in some tests. Even is better in others.
//D->lp_filter_size = ((int) (0.5f * ( D->lp_filter_len_bits * (float)original_sample_rate / (float)baud ))) * 2 + 1;
// Just round to nearest integer.
D->lp_filter_size = (int)((D->lp_filter_len_bits * (float)original_sample_rate / baud) + 0.5f);
D->lp_window = BP_WINDOW_COSINE;
D->lpf_baud = 1.00;
D->agc_fast_attack = 0.080;
D->agc_slow_decay = 0.00012;
D->pll_locked_inertia = 0.89;
D->pll_searching_inertia = 0.67;
// break;
// }
#if 0
text_color_set(DW_COLOR_DEBUG);
dw_printf("---------- %s (%d, %d) -----------\n", __func__, samples_per_sec, baud);
dw_printf("filter_len_bits = %.2f\n", D->lp_filter_len_bits);
dw_printf("lp_filter_size = %d\n", D->lp_filter_size);
dw_printf("lp_window = %d\n", D->lp_window);
dw_printf("lpf_baud = %.2f\n", D->lpf_baud);
dw_printf("samples per bit = %.1f\n", (double)samples_per_sec / baud);
#endif
// PLL needs to use the upsampled rate.
D->pll_step_per_sample =
(int)round(TICKS_PER_PLL_CYCLE * (double)baud / (double)(original_sample_rate * upsample));
#ifdef TUNE_LP_WINDOW
D->lp_window = TUNE_LP_WINDOW;
#endif
#if TUNE_LP_FILTER_SIZE
D->lp_filter_size = TUNE_LP_FILTER_SIZE;
#endif
#ifdef TUNE_LPF_BAUD
D->lpf_baud = TUNE_LPF_BAUD;
#endif
#ifdef TUNE_AGC_FAST
D->agc_fast_attack = TUNE_AGC_FAST;
#endif
#ifdef TUNE_AGC_SLOW
D->agc_slow_decay = TUNE_AGC_SLOW;
#endif
#if defined(TUNE_PLL_LOCKED)
D->pll_locked_inertia = TUNE_PLL_LOCKED;
#endif
#if defined(TUNE_PLL_SEARCHING)
D->pll_searching_inertia = TUNE_PLL_SEARCHING;
#endif
// Initial filter (before scattering) is based on upsampled rate.
fc = (float)baud * D->lpf_baud / (float)(original_sample_rate * upsample);
//dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size);
gen_lowpass(fc, D->u.bb.lp_filter, D->lp_filter_taps * upsample, D->lp_window);
// New in 1.7 -
// Use a polyphase filter to reduce the CPU load.
// Originally I used zero stuffing to upsample.
// Here is the general idea.
//
// Suppose the input samples are 1 2 3 4 5 6 7 8 9 ...
// Filter coefficients are a b c d e f g h i ...
//
// With original sampling rate, the filtering would involve multiplying and adding:
//
// 1a 2b 3c 4d 5e 6f ...
//
// When upsampling by 3, each of these would need to be evaluated
// for each audio sample:
//
// 1a 0b 0c 2d 0e 0f 3g 0h 0i ...
// 0a 1b 0c 0d 2e 0f 0g 3h 0i ...
// 0a 0b 1c 0d 0e 2f 0g 0h 3i ...
//
// 2/3 of the multiplies are always by a stuffed zero.
// We can do this more efficiently by removing them.
//
// 1a 2d 3g ...
// 1b 2e 3h ...
// 1c 2f 3i ...
//
// We scatter the original filter across multiple shorter filters.
// Each input sample cycles around them to produce the upsampled rate.
//
// a d g ...
// b e h ...
// c f i ...
//
// There are countless sources of information DSP but this one is unique
// in that it is a college course that mentions APRS.
// https://www2.eecs.berkeley.edu/Courses/EE123
//
// Was the effort worthwhile? Times on an RPi 3.
//
// command: atest -B9600 ~/walkabout9600[abc]-compressed*.wav
//
// These are 3 recordings of a portable system being carried out of
// range and back in again. It is a real world test for weak signals.
//
// options num decoded seconds x realtime
// 1.6 1.7 1.6 1.7 1.6 1.7
// --- --- --- --- --- ---
// -P- 171 172 23.928 17.967 14.9 19.9
// -P+ 180 180 54.688 48.772 6.5 7.3
// -P- -F1 177 178 32.686 26.517 10.9 13.5
//
// So, it turns out that -P+ doesn't have a dramatic improvement, only
// around 4%, for drastically increased CPU requirements.
// Maybe we should turn that off by default, especially for ARM.
//
int k = 0;
for (int i = 0; i < D->lp_filter_size; i++) {
D->u.bb.lp_polyphase_1[i] = D->u.bb.lp_filter[k++];
if (upsample >= 2) {
D->u.bb.lp_polyphase_2[i] = D->u.bb.lp_filter[k++];
if (upsample >= 3) {
D->u.bb.lp_polyphase_3[i] = D->u.bb.lp_filter[k++];
if (upsample >= 4) {
D->u.bb.lp_polyphase_4[i] = D->u.bb.lp_filter[k++];
}
}
}
}
/* Version 1.2: Experiment with different slicing levels. */
// Really didn't help that much because we should have a symmetrical signal.
for (j = 0; j < MAX_SUBCHANS; j++) {
slice_point[j] = 0.02f * (j - 0.5f * (MAX_SUBCHANS - 1));
//dw_printf ("slice_point[%d] = %+5.2f\n", j, slice_point[j]);
}
} /* end fsk_demod_init */
/*-------------------------------------------------------------------
*
* Name: demod_9600_process_sample
*
* Purpose: (1) Filter & slice the signal.
* (2) Descramble it.
* (2) Recover clock and data.
*
* Inputs: chan - Audio channel. 0 for left, 1 for right.
*
* sam - One sample of audio.
* Should be in range of -32768 .. 32767.
*
* Returns: None
*
* Descripion: "9600 baud" packet is FSK for an FM voice transceiver.
* By the time it gets here, it's really a baseband signal.
* At one extreme, we could have a 4800 Hz square wave.
* A the other extreme, we could go a considerable number
* of bit times without any transitions.
*
* The trick is to extract the digital data which has
* been distorted by going thru voice transceivers not
* intended to pass this sort of "audio" signal.
*
* For G3RUH mode, data is "scrambled" to reduce the amount of DC bias.
* The data stream must be unscrambled at the receiving end.
*
* We also have a digital phase locked loop (PLL)
* to recover the clock and pick out data bits at
* the proper rate.
*
* For each recovered data bit, we call:
*
* hdlc_rec (channel, demodulated_bit);
*
* to decode HDLC frames from the stream of bits.
*
* Future: This could be generalized by passing in the name
* of the function to be called for each bit recovered
* from the demodulator. For now, it's simply hard-coded.
*
* After experimentation, I found that this works better if
* the original signal is upsampled by 2x or even 4x.
*
* References: 9600 Baud Packet Radio Modem Design
* http://www.amsat.org/amsat/articles/g3ruh/109.html
*
* The KD2BD 9600 Baud Modem
* http://www.amsat.org/amsat/articles/kd2bd/9k6modem/
*
* 9600 Baud Packet Handbook
* ftp://ftp.tapr.org/general/9600baud/96man2x0.txt
*
*
*--------------------------------------------------------------------*/
inline static void nudge_pll(int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D);
static void process_filtered_sample(int chan, float fsam, struct demodulator_state_s *D);
void demod_9600_process_sample(int chan, int sam, int upsample, struct demodulator_state_s *D)
{
float fsam;
#if DEBUG4
static FILE *demod_log_fp = NULL;
static int log_file_seq = 0; /* Part of log file name */
#endif
int subchan = 0;
assert(chan >= 0 && chan < MAX_CHANS);
assert(subchan >= 0 && subchan < MAX_SUBCHANS);
/* Scale to nice number for convenience. */
/* Consistent with the AFSK demodulator, we'd like to use */
/* only half of the dynamic range to have some headroom. */
/* i.e. input range +-16k becomes +-1 here and is */
/* displayed in the heard line as audio level 100. */
fsam = (float)sam / 16384.0f;
// Low pass filter
push_sample(fsam, D->u.bb.audio_in, D->lp_filter_size);
fsam = convolve(D->u.bb.audio_in, D->u.bb.lp_polyphase_1, D->lp_filter_size);
process_filtered_sample(chan, fsam, D);
if (upsample >= 2) {
fsam = convolve(D->u.bb.audio_in, D->u.bb.lp_polyphase_2, D->lp_filter_size);
process_filtered_sample(chan, fsam, D);
if (upsample >= 3) {
fsam = convolve(D->u.bb.audio_in, D->u.bb.lp_polyphase_3, D->lp_filter_size);
process_filtered_sample(chan, fsam, D);
if (upsample >= 4) {
fsam = convolve(D->u.bb.audio_in, D->u.bb.lp_polyphase_4, D->lp_filter_size);
process_filtered_sample(chan, fsam, D);
}
}
}
}
static void process_filtered_sample(int chan, float fsam, struct demodulator_state_s *D)
{
int subchan = 0;
/*
* Version 1.2: Capture the post-filtering amplitude for display.
* This is similar to the AGC without the normalization step.
* We want decay to be substantially slower to get a longer
* range idea of the received audio.
* For AFSK, we keep mark and space amplitudes.
* Here we keep + and - peaks because there could be a DC bias.
*/
// TODO: probably no need for this. Just use D->m_peak, D->m_valley
if (fsam >= D->alevel_mark_peak) {
D->alevel_mark_peak = fsam * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
}
else {
D->alevel_mark_peak = fsam * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
}
if (fsam <= D->alevel_space_peak) {
D->alevel_space_peak = fsam * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
}
else {
D->alevel_space_peak = fsam * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
}
/*
* The input level can vary greatly.
* More importantly, there could be a DC bias which we need to remove.
*
* Normalize the signal with automatic gain control (AGC).
* This works by looking at the minimum and maximum signal peaks
* and scaling the results to be roughly in the -1.0 to +1.0 range.
*/
float demod_out;
int demod_data; /* Still scrambled. */
demod_out = agc(fsam, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
// TODO: There is potential for multiple decoders with one filter.
//dw_printf ("peak=%.2f valley=%.2f fsam=%.2f norm=%.2f\n", D->m_peak, D->m_valley, fsam, norm);
if (D->num_slicers <= 1) {
/* Normal case of one demodulator to one HDLC decoder. */
/* Demodulator output is difference between response from two filters. */
/* AGC should generally keep this around -1 to +1 range. */
demod_data = demod_out > 0;
nudge_pll(chan, subchan, 0, demod_out, D);
}
else {
int slice;
/* Multiple slicers each feeding its own HDLC decoder. */
for (slice = 0; slice < D->num_slicers; slice++) {
demod_data = demod_out - slice_point[slice] > 0;
nudge_pll(chan, subchan, slice, demod_out - slice_point[slice], D);
}
}
// demod_data is used only for debug out.
// suppress compiler warning about it not being used.
(void)demod_data;
#if DEBUG4
if (chan == 0) {
if (1) {
//if (D->slicer[slice].data_detect) {
char fname[30];
int slice = 0;
if (demod_log_fp == NULL) {
log_file_seq++;
snprintf(fname, sizeof(fname), "demod/%04d.csv", log_file_seq);
//if (log_file_seq == 1) mkdir ("demod", 0777);
if (log_file_seq == 1) mkdir("demod");
demod_log_fp = fopen(fname, "w");
text_color_set(DW_COLOR_DEBUG);
dw_printf("Starting demodulator log file %s\n", fname);
fprintf(demod_log_fp, "Audio, Filtered, Max, Min, Normalized, Sliced, Clock\n");
}
fprintf(demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %d, %.2f\n",
fsam + 6,
fsam + 4,
D->m_peak + 4,
D->m_valley + 4,
demod_out + 2,
demod_data + 2,
(D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0);
fflush(demod_log_fp);
}
else {
if (demod_log_fp != NULL) {
fclose(demod_log_fp);
demod_log_fp = NULL;
}
}
}
#endif
} /* end demod_9600_process_sample */
/*-------------------------------------------------------------------
*
* Name: nudge_pll
*
* Purpose: Update the PLL state for each audio sample.
*
* (2) Descramble it.
* (2) Recover clock and data.
*
* Inputs: chan - Audio channel. 0 for left, 1 for right.
*
* subchan - Which demodulator. We could have several running in parallel.
*
* slice - Determines which Slicing level & HDLC decoder to use.
*
* demod_out_f - Demodulator output, possibly shifted by slicing level
* It will be compared with 0.0 to bit binary value out.
*
* D - Demodulator state for this channel / subchannel.
*
* Returns: None
*
* Description: A PLL is used to sample near the centers of the data bits.
*
* D->data_clock_pll is a SIGNED 32 bit variable.
* When it overflows from a large positive value to a negative value, we
* sample a data bit from the demodulated signal.
*
* Ideally, the the demodulated signal transitions should be near
* zero we we sample mid way between the transitions.
*
* Nudge the PLL by removing some small fraction from the value of
* data_clock_pll, pushing it closer to zero.
*
* This adjustment will never change the sign so it won't cause
* any erratic data bit sampling.
*
* If we adjust it too quickly, the clock will have too much jitter.
* If we adjust it too slowly, it will take too long to lock on to a new signal.
*
* I don't think the optimal value will depend on the audio sample rate
* because this happens for each transition from the demodulator.
*
* Version 1.4: Previously, we would always pull the PLL phase toward 0 after
* after a zero crossing was detetected. This adds extra jitter,
* especially when the ratio of audio sample rate to baud is low.
* Now, we interpolate between the two samples to get an estimate
* on when the zero crossing happened. The PLL is pulled toward
* this point.
*
* Results??? TBD
*
* Version 1.6: New experiment where filter size to extract clock is not the same
* as filter to extract the data bit value.
*
*--------------------------------------------------------------------*/
inline static void nudge_pll(int chan, int subchan, int slice, float demod_out_f, struct demodulator_state_s *D)
{
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
// Perform the add as unsigned to avoid signed overflow error.
D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample));
if (D->slicer[slice].prev_d_c_pll > 1000000000 && D->slicer[slice].data_clock_pll < -1000000000) {
/* 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);
}
/*
* Zero crossing?
*/
if ((D->slicer[slice].prev_demod_out_f < 0 && demod_out_f > 0) ||
(D->slicer[slice].prev_demod_out_f > 0 && demod_out_f < 0)) {
// Note: Test for this demodulator, not overall for channel.
pll_dcd_signal_transition2(D, slice, D->slicer[slice].data_clock_pll);
float target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f);
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 {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia + target * (1.0f - D->pll_searching_inertia));
}
}
#if DEBUG5
//if (chan == 0) {
if (D->slicer[slice].data_detect) {
char fname[30];
if (demod_log_fp == NULL) {
seq++;
snprintf(fname, sizeof(fname), "demod96/%04d.csv", seq);
if (seq == 1) mkdir("demod96"
#ifndef __WIN32__
, 0777
#endif
);
demod_log_fp = fopen(fname, "w");
text_color_set(DW_COLOR_DEBUG);
dw_printf("Starting 9600 decoder log file %s\n", fname);
fprintf(demod_log_fp, "Audio, Peak, Valley, Demod, SData, Descram, Clock\n");
}
fprintf(demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n",
0.5f * fsam + 3.5,
0.5f * D->m_peak + 3.5,
0.5f * D->m_valley + 3.5,
0.5f * demod_out + 2.0,
demod_data ? 1.35 : 1.0,
descram ? .9 : .55,
(D->data_clock_pll & 0x80000000) ? .1 : .45);
}
else {
if (demod_log_fp != NULL) {
fclose(demod_log_fp);
demod_log_fp = NULL;
}
}
//}
#endif
/*
* Remember demodulator output (pre-descrambling) so we can compare next time
* for the DPLL sync.
*/
D->slicer[slice].prev_demod_out_f = demod_out_f;
} /* end nudge_pll */
/* end demod_9600.c */
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//
// 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 2 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/>.
//
/********************************************************************************
*
* File: rrbb.c
*
* Purpose: Raw Received Bit Buffer.
* An array of bits used to hold data out of
* the demodulator before feeding it into the HLDC decoding.
*
* Version 1.2: Save initial state of 9600 baud descrambler so we can
* attempt bit fix up on G3RUH/K9NG scrambled data.
*
* Version 1.3: Store as bytes rather than packing 8 bits per byte.
*
*******************************************************************************/
#define MAGIC1 0x12344321
#define MAGIC2 0x56788765
static int new_count = 0;
static int delete_count = 0;
/***********************************************************************************
*
* Name: rrbb_new
*
* Purpose: Allocate space for an array of samples.
*
* Inputs: chan - Radio channel from whence it came.
*
* subchan - Which demodulator of the channel.
*
* slice - multiple thresholds per demodulator.
*
* is_scrambled - Is data scrambled? (true, false)
*
* descram_state - State of data descrambler.
*
* prev_descram - Previous descrambled bit.
*
* Returns: Handle to be used by other functions.
*
* Description:
*
***********************************************************************************/
rrbb_t rrbb_new(int chan, int subchan, int slice, int is_scrambled, int descram_state, int prev_descram)
{
rrbb_t result;
result = malloc(sizeof(struct rrbb_s));
if (result == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf("FATAL ERROR: Out of memory.\n");
exit(0);
}
result->magic1 = MAGIC1;
result->chan = chan;
result->subchan = subchan;
result->slice = slice;
result->magic2 = MAGIC2;
new_count++;
if (new_count > delete_count + 100) {
text_color_set(DW_COLOR_ERROR);
dw_printf("MEMORY LEAK, rrbb_new, new_count=%d, delete_count=%d\n", new_count, delete_count);
}
rrbb_clear(result, is_scrambled, descram_state, prev_descram);
return (result);
}
/***********************************************************************************
*
* Name: rrbb_clear
*
* Purpose: Clear by setting length to zero, etc.
*
* Inputs: b -Handle for sample array.
*
* is_scrambled - Is data scrambled? (true, false)
*
* descram_state - State of data descrambler.
*
* prev_descram - Previous descrambled bit.
*
***********************************************************************************/
void rrbb_clear(rrbb_t b, int is_scrambled, int descram_state, int prev_descram)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
assert(is_scrambled == 0 || is_scrambled == 1);
assert(prev_descram == 0 || prev_descram == 1);
b->nextp = NULL;
b->alevel.rec = 9999; // TODO: was there some reason for this instead of 0 or -1?
b->alevel.mark = 9999;
b->alevel.space = 9999;
b->len = 0;
b->is_scrambled = is_scrambled;
b->descram_state = descram_state;
b->prev_descram = prev_descram;
}
/***********************************************************************************
*
* Name: rrbb_append_bit
*
* Purpose: Append another bit to the end.
*
* Inputs: Handle for sample array.
* Value for the sample.
*
***********************************************************************************/
/* Definition in header file so it can be inlined. */
/***********************************************************************************
*
* Name: rrbb_chop8
*
* Purpose: Remove 8 from the length.
*
* Inputs: Handle for bit array.
*
* Description: Back up after appending the flag sequence.
*
***********************************************************************************/
void rrbb_chop8(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
if (b->len >= 8) {
b->len -= 8;
}
}
/***********************************************************************************
*
* Name: rrbb_get_len
*
* Purpose: Get number of bits in the array.
*
* Inputs: Handle for bit array.
*
***********************************************************************************/
int rrbb_get_len(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
return (b->len);
}
/***********************************************************************************
*
* Name: rrbb_get_bit
*
* Purpose: Get value of bit in specified position.
*
* Inputs: Handle for sample array.
* Index into array.
*
***********************************************************************************/
/* Definition in header file so it can be inlined. */
/***********************************************************************************
*
* Name: rrbb_flip_bit
*
* Purpose: Complement the value of bit in specified position.
*
* Inputs: Handle for bit array.
* Index into array.
*
***********************************************************************************/
//void rrbb_flip_bit (rrbb_t b, unsigned int ind)
//{
// unsigned int di, mi;
//
// assert (b != NULL);
// assert (b->magic1 == MAGIC1);
// assert (b->magic2 == MAGIC2);
//
// assert (ind < b->len);
//
// di = ind / SOI;
// mi = ind % SOI;
//
// b->data[di] ^= masks[mi];
//}
/***********************************************************************************
*
* Name: rrbb_delete
*
* Purpose: Free the storage associated with the bit array.
*
* Inputs: Handle for bit array.
*
***********************************************************************************/
void rrbb_delete(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
b->magic1 = 0;
b->magic2 = 0;
free(b);
delete_count++;
}
/***********************************************************************************
*
* Name: rrbb_set_netxp
*
* Purpose: Set the nextp field, used to maintain a queue.
*
* Inputs: b Handle for bit array.
* np New value for nextp.
*
***********************************************************************************/
void rrbb_set_nextp(rrbb_t b, rrbb_t np)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
b->nextp = np;
}
/***********************************************************************************
*
* Name: rrbb_get_netxp
*
* Purpose: Get value of nextp field.
*
* Inputs: b Handle for bit array.
*
***********************************************************************************/
rrbb_t rrbb_get_nextp(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
return (b->nextp);
}
/***********************************************************************************
*
* Name: rrbb_get_chan
*
* Purpose: Get channel from which bit buffer was received.
*
* Inputs: b Handle for bit array.
*
***********************************************************************************/
int rrbb_get_chan(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
assert(b->chan >= 0 && b->chan < MAX_CHANS);
return (b->chan);
}
/***********************************************************************************
*
* Name: rrbb_get_subchan
*
* Purpose: Get subchannel from which bit buffer was received.
*
* Inputs: b Handle for bit array.
*
***********************************************************************************/
int rrbb_get_subchan(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
assert(b->subchan >= 0 && b->subchan < MAX_SUBCHANS);
return (b->subchan);
}
/***********************************************************************************
*
* Name: rrbb_get_slice
*
* Purpose: Get slice number from which bit buffer was received.
*
* Inputs: b Handle for bit array.
*
***********************************************************************************/
int rrbb_get_slice(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
assert(b->slice >= 0 && b->slice < MAX_SLICERS);
return (b->slice);
}
/***********************************************************************************
*
* Name: rrbb_set_audio_level
*
* Purpose: Set audio level at time the frame was received.
*
* Inputs: b Handle for bit array.
* alevel Audio level.
*
***********************************************************************************/
void rrbb_set_audio_level(rrbb_t b, alevel_t alevel)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
b->alevel = alevel;
}
/***********************************************************************************
*
* Name: rrbb_get_audio_level
*
* Purpose: Get audio level at time the frame was received.
*
* Inputs: b Handle for bit array.
*
***********************************************************************************/
alevel_t rrbb_get_audio_level(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
return (b->alevel);
}
/***********************************************************************************
*
* Name: rrbb_get_is_scrambled
*
* Purpose: Find out if using scrambled data.
*
* Inputs: b Handle for bit array.
*
* Returns: True (for 9600 baud) or false (for slower AFSK).
*
***********************************************************************************/
int rrbb_get_is_scrambled(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
return (b->is_scrambled);
}
/***********************************************************************************
*
* Name: rrbb_get_descram_state
*
* Purpose: Get data descrambler state before first data bit of frame.
*
* Inputs: b Handle for bit array.
*
***********************************************************************************/
int rrbb_get_descram_state(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
return (b->descram_state);
}
/***********************************************************************************
*
* Name: rrbb_get_prev_descram
*
* Purpose: Get previous descrambled bit before first data bit of frame.
*
* Inputs: b Handle for bit array.
*
***********************************************************************************/
int rrbb_get_prev_descram(rrbb_t b)
{
assert(b != NULL);
assert(b->magic1 == MAGIC1);
assert(b->magic2 == MAGIC2);
return (b->prev_descram);
}
/* end rrbb.c */
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2014, 2015, 2016, 2019 John Langner, WB2OSZ
//
// 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 2 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/>.
//
/*------------------------------------------------------------------
*
* Module: gen_tone.c
*
* Purpose: Convert bits to AFSK for writing to .WAV sound file
* or a sound device.
*
*
*---------------------------------------------------------------*/
// Properties of the digitized sound stream & modem.
static struct audio_s *save_audio_config_p = NULL;
/*
* 8 bit samples are unsigned bytes in range of 0 .. 255.
*
* 16 bit samples are signed short in range of -32768 .. +32767.
*/
/* Constants after initialization. */
#define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
static int ticks_per_sample[MAX_CHANS]; /* Same for both channels of same soundcard */
/* because they have same sample rate */
/* but less confusing to have for each channel. */
static int ticks_per_bit[MAX_CHANS];
static int f1_change_per_sample[MAX_CHANS];
static int f2_change_per_sample[MAX_CHANS];
static short sine_table[256];
/* Accumulators. */
static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation.
// Upper bits are used as index into sine table.
#define PHASE_SHIFT_180 ( 128u << 24 )
#define PHASE_SHIFT_90 ( 64u << 24 )
#define PHASE_SHIFT_45 ( 32u << 24 )
static int bit_len_acc[MAX_CHANS]; // To accumulate fractional samples per bit.
static int lfsr[MAX_CHANS]; // Shift register for scrambler.
static int bit_count[MAX_CHANS]; // Counter incremented for each bit transmitted
// on the channel. This is only used for QPSK.
// The LSB determines if we save the bit until
// next time, or send this one with the previously saved.
// The LSB+1 position determines if we add an
// extra 180 degrees to the phase to compensate
// for having 1.5 carrier cycles per symbol time.
// For 8PSK, it has a different meaning. It is the
// number of bits in 'save_bit' so we can accumulate
// three for each symbol.
static int save_bit[MAX_CHANS];
static int prev_dat[MAX_CHANS]; // Previous data bit. Used for G3RUH style.
/*------------------------------------------------------------------
*
* Name: gen_tone_init
*
* Purpose: Initialize for AFSK tone generation which might
* be used for RTTY or amateur packet radio.
*
* Inputs: audio_config_p - Pointer to modem parameter structure, modem_s.
*
* The fields we care about are:
*
* samples_per_sec
* baud
* mark_freq
* space_freq
* samples_per_sec
*
* amp - Signal amplitude on scale of 0 .. 100.
*
* 100% uses the full 16 bit sample range of +-32k.
*
* gen_packets - True if being called from "gen_packets" utility
* rather than the "direwolf" application.
*
* Returns: 0 for success.
* -1 for failure.
*
* Description: Calculate various constants for use by the direct digital synthesis
* audio tone generation.
*
*----------------------------------------------------------------*/
static int amp16bit; /* for 9600 baud */
int gen_tone_init(struct audio_s *pa, int amp, int gen_packets)
{
int j;
int chan = 0;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("gen_tone_init ( audio_config_p=%p, amp=%d, gen_packets=%d )\n",
audio_config_p, amp, gen_packets);
#endif
/*
* Save away modem parameters for later use.
*/
// This should be in config somewhere
pa->adev[0].num_channels = DEFAULT_NUM_CHANNELS;
pa->adev[0].samples_per_sec = 48000;
pa->adev[0].bits_per_sample = 16;
pa->achan[0].baud = rx_baudrate[0];
pa->adev[1].num_channels = DEFAULT_NUM_CHANNELS;
pa->adev[1].samples_per_sec = 48000;
pa->adev[1].bits_per_sample = 16;
pa->achan[1].baud = rx_baudrate[1];
pa->chan_medium[0] = MEDIUM_RADIO;
pa->chan_medium[1] = MEDIUM_RADIO;
pa->achan[0].modem_type = MODEM_SCRAMBLE;
pa->achan[1].modem_type = MODEM_SCRAMBLE;
save_audio_config_p = pa;
amp16bit = (int)((32767 * amp) / 100);
for (chan = 0; chan < MAX_CHANS; chan++) {
if (pa->chan_medium[chan] == MEDIUM_RADIO) {
int a = ACHAN2ADEV(chan);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("gen_tone_init: chan=%d, modem_type=%d, bps=%d, samples_per_sec=%d\n",
chan,
save_pa->achan[chan].modem_type,
pa->achan[chan].baud,
pa->adev[a].samples_per_sec);
#endif
tone_phase[chan] = 0;
bit_len_acc[chan] = 0;
lfsr[chan] = 0;
ticks_per_sample[chan] = (int)((TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
// The terminology is all wrong here. Didn't matter with 1200 and 9600.
// The config speed should be bits per second rather than baud.
// ticks_per_bit should be ticks_per_symbol.
switch (pa->achan[chan].modem_type) {
case MODEM_QPSK:
pa->achan[chan].mark_freq = 1800;
pa->achan[chan].space_freq = pa->achan[chan].mark_freq; // Not Used.
// symbol time is 1 / (half of bps)
ticks_per_bit[chan] = (int)((TICKS_PER_CYCLE / ((double)pa->achan[chan].baud * 0.5)) + 0.5);
f1_change_per_sample[chan] = (int)(((double)pa->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used.
tone_phase[chan] = PHASE_SHIFT_45; // Just to mimic first attempt.
break;
case MODEM_8PSK:
pa->achan[chan].mark_freq = 1800;
pa->achan[chan].space_freq = pa->achan[chan].mark_freq; // Not Used.
// symbol time is 1 / (third of bps)
ticks_per_bit[chan] = (int)((TICKS_PER_CYCLE / ((double)pa->achan[chan].baud / 3.)) + 0.5);
f1_change_per_sample[chan] = (int)(((double)pa->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used.
break;
case MODEM_BASEBAND:
case MODEM_SCRAMBLE:
case MODEM_AIS:
// Tone is half baud.
ticks_per_bit[chan] = (int)((TICKS_PER_CYCLE / (double)pa->achan[chan].baud) + 0.5);
f1_change_per_sample[chan] = (int)(((double)pa->achan[chan].baud * 0.5 * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
break;
default: // AFSK
ticks_per_bit[chan] = (int)((TICKS_PER_CYCLE / (double)pa->achan[chan].baud) + 0.5);
f1_change_per_sample[chan] = (int)(((double)pa->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
f2_change_per_sample[chan] = (int)(((double)pa->achan[chan].space_freq * TICKS_PER_CYCLE / (double)pa->adev[a].samples_per_sec) + 0.5);
break;
}
}
}
for (j = 0; j < 256; j++) {
double a;
int s;
a = ((double)(j) / 256.0) * (2 * M_PI);
s = (int)(sin(a) * 32767 * amp / 100.0);
/* 16 bit sound sample must fit in range of -32768 .. +32767. */
if (s < -32768) {
text_color_set(DW_COLOR_ERROR);
dw_printf("gen_tone_init: Excessive amplitude is being clipped.\n");
s = -32768;
}
else if (s > 32767) {
text_color_set(DW_COLOR_ERROR);
dw_printf("gen_tone_init: Excessive amplitude is being clipped.\n");
s = 32767;
}
sine_table[j] = s;
}
return (0);
} /* end gen_tone_init */
/*-------------------------------------------------------------------
*
* Name: tone_gen_put_bit
*
* Purpose: Generate tone of proper duration for one data bit.
*
* Inputs: chan - Audio channel, 0 = first.
*
* dat - 0 for f1, 1 for f2.
*
* -1 inserts half bit to test data
* recovery PLL.
*
* Assumption: fp is open to a file for write.
*
* Version 1.4: Attempt to implement 2400 and 4800 bps PSK modes.
*
* Version 1.6: For G3RUH, rather than generating square wave and low
* pass filtering, generate the waveform directly.
* This avoids overshoot, ringing, and adding more jitter.
* Alternating bits come out has sine wave of baud/2 Hz.
*
* Version 1.6: MFJ-2400 compatibility for V.26.
*
*--------------------------------------------------------------------*/
static const int gray2phase_v26[4] = { 0, 1, 3, 2 };
static const int gray2phase_v27[8] = { 1, 0, 2, 3, 6, 7, 5, 4 };
// We are only using this for RUH modes
2024-04-13 17:48:58 +01:00
void tone_gen_put_bit(int chan, int dat, int scramble)
2023-09-12 21:38:15 +01:00
{
int a = 0;
// scramble
2024-04-13 17:48:58 +01:00
if (scramble)
{
int x;
2023-09-12 21:38:15 +01:00
2024-04-13 17:48:58 +01:00
x = (dat ^ (lfsr[chan] >> 16) ^ (lfsr[chan] >> 11)) & 1;
lfsr[chan] = (lfsr[chan] << 1) | (x & 1);
dat = x;
}
2023-09-12 21:38:15 +01:00
do { /* until enough audio samples for this symbol. */
int sam;
2024-04-13 17:48:58 +01:00
if (dat != prev_dat[chan])
2023-09-12 21:38:15 +01:00
{
tone_phase[chan] += f1_change_per_sample[chan];
2024-04-13 17:48:58 +01:00
}
else
{
if (tone_phase[chan] & 0x80000000)
tone_phase[chan] = 0xc0000000; // 270 degrees.
2023-09-12 21:38:15 +01:00
else
2024-04-13 17:48:58 +01:00
tone_phase[chan] = 0x40000000; // 90 degrees.
}
2023-09-12 21:38:15 +01:00
2024-04-13 17:48:58 +01:00
sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
gen_tone_put_sample(chan, a, sam);
2023-09-12 21:38:15 +01:00
/* Enough for the bit time? */
bit_len_acc[chan] += ticks_per_sample[chan];
} while (bit_len_acc[chan] < ticks_per_bit[chan]);
bit_len_acc[chan] -= ticks_per_bit[chan];
prev_dat[chan] = dat; // Only needed for G3RUH baseband/scrambled.
} /* end tone_gen_put_bit */
#define ARDOPBufferSize 12000 * 100 // May need to be bigger for 48K
extern short ARDOPTXBuffer[4][ARDOPBufferSize]; // Enough to hold whole frame of samples
void gen_tone_put_sample(int chan, int a, int sam)
{
// This replaces the DW code
ARDOPTXBuffer[chan][SampleNo++] = sam;
}
/* end gen_tone.c */
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2013, 2014, 2019, 2021 John Langner, WB2OSZ
//
// 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 2 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/>.
//
static void send_control_nrzi(int, int);
static void send_data_nrzi(int, int);
static void send_bit_nrzi(int, int);
static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags"
/*-------------------------------------------------------------
*
* Name: layer2_send_frame
*
* Purpose: Convert frames to a stream of bits.
* Originally this was for AX.25 only, hence the file name.
* Over time, FX.25 and IL2P were shoehorned in.
*
* Inputs: chan - Audio channel number, 0 = first.
*
* pp - Packet object.
*
* bad_fcs - Append an invalid FCS for testing purposes.
* Applies only to regular AX.25.
*
* Outputs: Bits are shipped out by calling tone_gen_put_bit().
*
* Returns: Number of bits sent including "flags" and the
* stuffing bits.
* The required time can be calculated by dividing this
* number by the transmit rate of bits/sec.
*
* Description: For AX.25, send:
* start flag
* bit stuffed data
* calculated FCS
* end flag
* NRZI encoding for all but the "flags."
*
*
* Assumptions: It is assumed that the tone_gen module has been
* properly initialized so that bits sent with
* tone_gen_put_bit() are processed correctly.
*
*--------------------------------------------------------------*/
static int ax25_only_hdlc_send_frame(int chan, unsigned char *fbuf, int flen, int bad_fcs);
int layer2_send_frame(int chan, packet_t pp, int bad_fcs, struct audio_s *audio_config_p)
{
if (audio_config_p->achan[chan].layer2_xmit == LAYER2_IL2P) {
int n = il2p_send_frame(chan, pp, audio_config_p->achan[chan].il2p_max_fec,
audio_config_p->achan[chan].il2p_invert_polarity);
if (n > 0) {
return (n);
}
text_color_set(DW_COLOR_ERROR);
dw_printf("Unable to send IL2p frame. Falling back to regular AX.25.\n");
// Not sure if we should fall back to AX.25 or not here.
}
else if (audio_config_p->achan[chan].layer2_xmit == LAYER2_FX25)
{
unsigned char fbuf[AX25_MAX_PACKET_LEN + 2];
int flen = ax25_pack(pp, fbuf);
int n = fx25_send_frame(chan, fbuf, flen, audio_config_p->achan[chan].fx25_strength);
if (n > 0) {
return (n);
}
text_color_set(DW_COLOR_ERROR);
dw_printf("Unable to send FX.25. Falling back to regular AX.25.\n");
// Definitely need to fall back to AX.25 here because
// the FX.25 frame length is so limited.
}
unsigned char fbuf[AX25_MAX_PACKET_LEN + 2];
int flen = ax25_pack(pp, fbuf);
return (ax25_only_hdlc_send_frame(chan, fbuf, flen, bad_fcs));
}
static int ax25_only_hdlc_send_frame(int chan, unsigned char *fbuf, int flen, int bad_fcs)
{
int j, fcs;
number_of_bits_sent[chan] = 0;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d, bad_fcs = %d)\n", chan, fbuf, flen, bad_fcs);
fflush(stdout);
#endif
send_control_nrzi(chan, 0x7e); /* Start frame */
for (j = 0; j < flen; j++) {
send_data_nrzi(chan, fbuf[j]);
}
fcs = get_fcs(fbuf, flen);
if (bad_fcs) {
/* For testing only - Simulate a frame getting corrupted along the way. */
send_data_nrzi(chan, (~fcs) & 0xff);
send_data_nrzi(chan, ((~fcs) >> 8) & 0xff);
}
else {
send_data_nrzi(chan, fcs & 0xff);
send_data_nrzi(chan, (fcs >> 8) & 0xff);
}
send_control_nrzi(chan, 0x7e); /* End frame */
return (number_of_bits_sent[chan]);
}
// The following are only for HDLC.
// All bits are sent NRZI.
// Data (non flags) use bit stuffing.
static int stuff[MAX_CHANS]; // Count number of "1" bits to keep track of when we
// need to break up a long run by "bit stuffing."
// Needs to be array because we could be transmitting
// on multiple channels at the same time.
static void send_control_nrzi(int chan, int x)
{
int i;
for (i = 0; i < 8; i++) {
send_bit_nrzi(chan, x & 1);
x >>= 1;
}
stuff[chan] = 0;
}
static void send_data_nrzi(int chan, int x)
{
int i;
for (i = 0; i < 8; i++) {
send_bit_nrzi(chan, x & 1);
if (x & 1) {
stuff[chan]++;
if (stuff[chan] == 5) {
send_bit_nrzi(chan, 0);
stuff[chan] = 0;
}
}
else {
stuff[chan] = 0;
}
x >>= 1;
}
}
/*
* NRZI encoding.
* data 1 bit -> no change.
* data 0 bit -> invert signal.
*/
static void send_bit_nrzi(int chan, int b)
{
static int output[MAX_CHANS];
if (b == 0) {
output[chan] = !output[chan];
}
2024-04-13 17:48:58 +01:00
tone_gen_put_bit(chan, output[chan], MODEM_SCRAMBLE);
2023-09-12 21:38:15 +01:00
number_of_bits_sent[chan]++;
}
/* end hdlc_send.c */
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2015, 2019 John Langner, WB2OSZ
//
// 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 2 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/>.
//
/*------------------------------------------------------------------
*
* Name: dsp.c
*
* Purpose: Generate the filters used by the demodulators.
*
*----------------------------------------------------------------*/
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
// Don't remove this. It serves as a reminder that an experiment is underway.
#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_MS2_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE)
#define DEBUG1 1 // Don't remove this.
#endif
/*------------------------------------------------------------------
*
* Name: window
*
* Purpose: Filter window shape functions.
*
* Inputs: type - BP_WINDOW_HAMMING, etc.
* size - Number of filter taps.
* j - Index in range of 0 to size-1.
*
* Returns: Multiplier for the window shape.
*
*----------------------------------------------------------------*/
static float window(bp_window_t type, int size, int j)
{
float center;
float w;
center = 0.5 * (size - 1);
switch (type) {
case BP_WINDOW_COSINE:
w = cos((j - center) / size * M_PI);
//w = sin(j * M_PI / (size - 1));
break;
case BP_WINDOW_HAMMING:
w = 0.53836 - 0.46164 * cos((j * 2 * M_PI) / (size - 1));
break;
case BP_WINDOW_BLACKMAN:
w = 0.42659 - 0.49656 * cos((j * 2 * M_PI) / (size - 1))
+ 0.076849 * cos((j * 4 * M_PI) / (size - 1));
break;
case BP_WINDOW_FLATTOP:
w = 1.0 - 1.93 * cos((j * 2 * M_PI) / (size - 1))
+ 1.29 * cos((j * 4 * M_PI) / (size - 1))
- 0.388 * cos((j * 6 * M_PI) / (size - 1))
+ 0.028 * cos((j * 8 * M_PI) / (size - 1));
break;
case BP_WINDOW_TRUNCATED:
default:
w = 1.0;
break;
}
return (w);
}
/*------------------------------------------------------------------
*
* Name: gen_lowpass
*
* Purpose: Generate low pass filter kernel.
*
* Inputs: fc - Cutoff frequency as fraction of sampling frequency.
* filter_size - Number of filter taps.
* wtype - Window type, BP_WINDOW_HAMMING, etc.
* lp_delay_fract - Fudge factor for the delay value.
*
* Outputs: lp_filter
*
* Returns: Signal delay thru the filter in number of audio samples.
*
*----------------------------------------------------------------*/
void gen_lowpass(float fc, float *lp_filter, int filter_size, bp_window_t wtype)
{
int j;
float G;
#if DEBUG1
text_color_set(DW_COLOR_DEBUG);
dw_printf("Lowpass, size=%d, fc=%.2f\n", filter_size, fc);
dw_printf(" j shape sinc final\n");
#endif
assert(filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
for (j = 0; j < filter_size; j++) {
float center;
float sinc;
float shape;
center = 0.5 * (filter_size - 1);
if (j - center == 0) {
sinc = 2 * fc;
}
else {
sinc = sin(2 * M_PI * fc * (j - center)) / (M_PI*(j - center));
}
shape = window(wtype, filter_size, j);
lp_filter[j] = sinc * shape;
#if DEBUG1
dw_printf("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, lp_filter[j]);
#endif
}
/*
* Normalize lowpass for unity gain at DC.
*/
G = 0;
for (j = 0; j < filter_size; j++) {
G += lp_filter[j];
}
for (j = 0; j < filter_size; j++) {
lp_filter[j] = lp_filter[j] / G;
}
return;
} /* end gen_lowpass */
#undef DEBUG1
/*------------------------------------------------------------------
*
* Name: gen_bandpass
*
* Purpose: Generate band pass filter kernel for the prefilter.
* This is NOT for the mark/space filters.
*
* Inputs: f1 - Lower cutoff frequency as fraction of sampling frequency.
* f2 - Upper cutoff frequency...
* filter_size - Number of filter taps.
* wtype - Window type, BP_WINDOW_HAMMING, etc.
*
* Outputs: bp_filter
*
* Reference: http://www.labbookpages.co.uk/audio/firWindowing.html
*
* Does it need to be an odd length?
*
*----------------------------------------------------------------*/
void gen_bandpass(float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype)
{
int j;
float w;
float G;
float center = 0.5 * (filter_size - 1);
#if DEBUG1
text_color_set(DW_COLOR_DEBUG);
dw_printf("Bandpass, size=%d\n", filter_size);
dw_printf(" j shape sinc final\n");
#endif
assert(filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
for (j = 0; j < filter_size; j++) {
float sinc;
float shape;
if (j - center == 0) {
sinc = 2 * (f2 - f1);
}
else {
sinc = sin(2 * M_PI * f2 * (j - center)) / (M_PI*(j - center))
- sin(2 * M_PI * f1 * (j - center)) / (M_PI*(j - center));
}
shape = window(wtype, filter_size, j);
bp_filter[j] = sinc * shape;
#if DEBUG1
dw_printf("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, bp_filter[j]);
#endif
}
/*
* Normalize bandpass for unity gain in middle of passband.
* Can't use same technique as for lowpass.
* Instead compute gain in middle of passband.
* See http://dsp.stackexchange.com/questions/4693/fir-filter-gain
*/
w = 2 * M_PI * (f1 + f2) / 2;
G = 0;
for (j = 0; j < filter_size; j++) {
G += 2 * bp_filter[j] * cos((j - center)*w); // is this correct?
}
#if DEBUG1
dw_printf("Before normalizing, G=%.3f\n", G);
#endif
for (j = 0; j < filter_size; j++) {
bp_filter[j] = bp_filter[j] / G;
}
} /* end gen_bandpass */
/*------------------------------------------------------------------
*
* Name: gen_ms
*
* Purpose: Generate mark and space filters.
*
* Inputs: fc - Tone frequency, i.e. mark or space.
* sps - Samples per second.
* filter_size - Number of filter taps.
* wtype - Window type, BP_WINDOW_HAMMING, etc.
*
* Outputs: bp_filter
*
* Reference: http://www.labbookpages.co.uk/audio/firWindowing.html
*
* Does it need to be an odd length?
*
*----------------------------------------------------------------*/
void gen_ms(int fc, int sps, float *sin_table, float *cos_table, int filter_size, int wtype)
{
int j;
float Gs = 0, Gc = 0;;
for (j = 0; j < filter_size; j++) {
float center = 0.5f * (filter_size - 1);
float am = ((float)(j - center) / (float)sps) * ((float)fc) * (2.0f * (float)M_PI);
float shape = window(wtype, filter_size, j);
sin_table[j] = sinf(am) * shape;
cos_table[j] = cosf(am) * shape;
Gs += sin_table[j] * sinf(am);
Gc += cos_table[j] * cosf(am);
#if DEBUG1
dw_printf("%6d %6.2f %6.2f %6.2f\n", j, shape, sin_table[j], cos_table[j]);
#endif
}
/* Normalize for unity gain */
#if DEBUG1
dw_printf("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc);
#endif
for (j = 0; j < filter_size; j++) {
sin_table[j] = sin_table[j] / Gs;
cos_table[j] = cos_table[j] / Gc;
}
} /* end gen_ms */
/*------------------------------------------------------------------
*
* Name: rrc
*
* Purpose: Root Raised Cosine function.
* Why do they call it that?
* It's mostly the sinc function with cos windowing to taper off edges faster.
*
* Inputs: t - Time in units of symbol duration.
* i.e. The centers of two adjacent symbols would differ by 1.
*
* a - Roll off factor, between 0 and 1.
*
* Returns: Basically the sinc (sin(x)/x) function with edges decreasing faster.
* Should be 1 for t = 0 and 0 at all other integer values of t.
*
*----------------------------------------------------------------*/
float rrc(float t, float a)
{
float sinc, window, result;
if (t > -0.001 && t < 0.001) {
sinc = 1;
}
else {
sinc = sinf(M_PI * t) / (M_PI * t);
}
if (fabsf(a * t) > 0.499 && fabsf(a * t) < 0.501) {
window = M_PI / 4;
}
else {
window = cos(M_PI * a * t) / (1 - powf(2 * a * t, 2));
// This made nicer looking waveforms for generating signal.
//window = cos(M_PI * a * t);
// Do we want to let it go negative?
// I think this would happen when a > 0.5 / (filter width in symbol times)
if (window < 0) {
//printf ("'a' is too large for range of 't'.\n");
//window = 0;
}
}
result = sinc * window;
#if DEBUGRRC
// t should vary from - to + half of filter size in symbols.
// Result should be 1 at t=0 and 0 at all other integer values of t.
printf("%.3f, %.3f, %.3f, %.3f\n", t, sinc, window, result);
#endif
return (result);
}
// The Root Raised Cosine (RRC) low pass filter is suppposed to minimize Intersymbol Interference (ISI).
void gen_rrc_lowpass(float *pfilter, int filter_taps, float rolloff, float samples_per_symbol)
{
int k;
float t;
for (k = 0; k < filter_taps; k++) {
t = (k - ((filter_taps - 1.0) / 2.0)) / samples_per_symbol;
pfilter[k] = rrc(t, rolloff);
}
// Scale it for unity gain.
t = 0;
for (k = 0; k < filter_taps; k++) {
t += pfilter[k];
}
for (k = 0; k < filter_taps; k++) {
pfilter[k] = pfilter[k] / t;
}
}
/* end dsp.c */
int ax25_pack(packet_t this_p, unsigned char * result)
{
int len = 0;
// assert(this_p->magic1 == MAGIC);
// assert(this_p->magic2 == MAGIC);
// assert(this_p->frame_len >= 0 && this_p->frame_len <= AX25_MAX_PACKET_LEN);
// memcpy(result, this_p->frame_data, this_p->frame_len);
return (len);
}
int audio_flush(int a)
{
return 0;
}
int fx25_send_frame(int chan, unsigned char *fbuf, int flen, int fx_mode)
{
return 0;
}
struct demodulator_state_s D[4];
struct audio_s pa[4];
extern int was_init[4];
extern short rx_baudrate[5];
extern unsigned char modem_mode[5];
void dw9600ProcessSample(int snd_ch, short Sample)
{
demod_9600_process_sample(snd_ch, Sample, 1, &D[snd_ch]);
}
void init_RUH48(int snd_ch)
{
modem_mode[snd_ch] = MODE_RUH;
2024-04-13 17:48:58 +01:00
tx_bitrate[snd_ch] = rx_baudrate[snd_ch] = 4800;
2023-09-12 21:38:15 +01:00
if (was_init[snd_ch] == 0)
{
hdlc_rec_init(&pa[snd_ch]);
gen_tone_init(&pa[snd_ch], 100, 0);
was_init[snd_ch] = 1;
}
demod_9600_init(2,
48000,
1, //upsample
4800,
&D[snd_ch]);
}
void init_RUH96(int snd_ch)
{
modem_mode[snd_ch] = MODE_RUH;
2024-04-13 17:48:58 +01:00
tx_bitrate[snd_ch] = rx_baudrate[snd_ch] = 9600;
2023-09-12 21:38:15 +01:00
if (was_init[snd_ch] == 0)
{
hdlc_rec_init(&pa[snd_ch]);
gen_tone_init(&pa[snd_ch], 100, 0);
was_init[snd_ch];
}
demod_9600_init(2,
48000,
1, //upsample
9600,
&D[snd_ch]);
}
void text_color_set(dw_color_t c)
{
return;
}
2024-04-13 17:48:58 +01:00
typedef struct TMChannel_t
{
float prev_LPF1I_buf[4096];
float LPF1Q_buf[4096];
float prev_dLPFI_buf[4096];
float prev_dLPFQ_buf[4096];
float prev_AFCI_buf[4096];
float prev_AFCQ_buf[4096];
float AngleCorr;
float MUX_osc;
float AFC_IZ1;
float AFC_IZ2;
float AFC_QZ1;
float AFC_QZ2;
float AFC_bit_buf1I[1024];
float AFC_bit_buf1Q[1024];
float AFC_bit_buf2[1024];
float AFC_IIZ1;
float AFC_QQZ1;
} TMChannel;
typedef struct TFX25_t
{
string data;
uint8_t status;
uint8_t bit_cnt;
uint8_t uint8_t_rx;
unsigned long long tag;
uint8_t size;
uint8_t rs_size;
uint8_t size_cnt;
} TFX25;
typedef struct TDetector_t
{
struct TFX25_t fx25[4];
TStringList mem_ARQ_F_buf[5];
TStringList mem_ARQ_buf[5];
float pll_loop[5];
float last_sample[5];
uint8_t ones[5];
uint8_t zeros[5];
float bit_buf[5][1024];
float bit_buf1[5][1024];
uint8_t sample_cnt[5];
uint8_t last_bit[5];
float PSK_IZ1[5];
float PSK_QZ1[5];
float PkAmpI[5];
float PkAmpQ[5];
float PkAmp[5];
float PkAmpMax[5];
int newpkpos[5];
float AverageAmp[5];
float AngleCorr[5];
float MinAmp[5];
float MaxAmp[5];
float MUX3_osc[5];
float MUX3_1_osc[5];
float MUX3_2_osc[5];
float Preemphasis6[5];
float Preemphasis12[5];
float PSK_AGC[5];
float AGC[5];
float AGC1[5];
float AGC2[5];
float AGC3[5];
float AGC_max[5];
float AGC_min[5];
float AFC_IZ1[5];
float AFC_IZ2[5];
float AFC_QZ1[5];
float AFC_QZ2[5];
uint8_t last_rx_bit[5];
uint8_t bit_stream[5];
uint8_t uint8_t_rx[5];
uint8_t bit_stuff_cnt[5];
uint8_t bit_cnt[5];
float bit_osc[5];
uint8_t frame_status[5];
string rx_data[5];
string FEC_rx_data[5];
//
uint8_t FEC_pol[5];
unsigned short FEC_err[5];
unsigned long long FEC_header1[5][2];
unsigned short FEC_blk_int[5];
unsigned short FEC_len_int[5];
unsigned short FEC_len[5];
unsigned short FEC_len_cnt[5];
uint8_t rx_intv_tbl[5][4];
uint8_t rx_intv_sym[5];
uint8_t rx_viterbi[5];
uint8_t viterbi_cnt[5];
// SurvivorStates [1..4,0..511] of TSurvivor;
//
TMChannel MChannel[5][4];
float AFC_dF_avg[5];
float AFC_dF[5];
float AFC_bit_osc[5];
float AFC_bit_buf[5][1024];
unsigned short AFC_cnt[5];
string raw_bits1[5];
string raw_bits[5];
uint8_t last_nrzi_bit[5];
float BPF_core[5][2048];
float LPF_core[5][2048];
float src_INTR_buf[5][8192];
float src_INTRI_buf[5][8192];
float src_INTRQ_buf[5][8192];
float src_LPF1I_buf[5][8192];
float src_LPF1Q_buf[5][8192];
float src_BPF_buf[5][2048];
float src_Loop_buf[5][8192];
float prev_BPF_buf[5][4096];
float prev_LPF1I_buf[5][4096];
float prev_LPF1Q_buf[5][4096];
float prev_INTR_buf[5][16384];
float prev_INTRI_buf[5][16384];
float prev_INTRQ_buf[5][16384];
uint8_t emph_decoded;
uint8_t rx_decoded;
uint8_t errors;
} TDetector;
2023-09-12 21:38:15 +01:00
extern TStringList detect_list[5];
extern TStringList detect_list_c[5];
2024-04-13 17:48:58 +01:00
#define nr_emph 2
extern struct TDetector_t DET[nr_emph + 1][16];
#define decodedNormal 4 //'-'
#define decodedFEC 3 //'F'
#define decodedMEM 2 //'#'
#define decodedSingle 1 //'$'
2023-09-12 21:38:15 +01:00
int multi_modem_process_rec_frame(int chan, int subchan, int slice, unsigned char *fbuf, int flen, int alevel, int retries, int is_fx25)
{
// Convert to QtSM internal format
2024-04-13 17:48:58 +01:00
struct TDetector_t * pDET = &DET[0][subchan];
2023-09-12 21:38:15 +01:00
string * data = newString();
char Mode[16] = "RUH";
int i, found;
stringAdd(data, fbuf, flen + 2); // QTSM assumes a CRC
if (detect_list[chan].Count > 0 &&
my_indexof(&detect_list[chan], data) >= 0)
{
// Already have a copy of this frame
freeString(data);
Debugprintf("Discarding copy rcvr %d emph %d", subchan, 0);
2024-04-13 17:48:58 +01:00
return 0;
2023-09-12 21:38:15 +01:00
}
string * xx = newString();
memset(xx->Data, 0, 16);
Add(&detect_list_c[chan], xx);
Add(&detect_list[chan], data);
2024-04-13 17:48:58 +01:00
pDET->rx_decoded = decodedNormal;
pDET->emph_decoded = decodedNormal;
pDET->errors = 0;
2023-09-12 21:38:15 +01:00
// if (retries)
// sprintf(Mode, "IP2P-%d", retries);
stringAdd(xx, Mode, strlen(Mode));
return 0;
}
extern unsigned short * DMABuffer;
extern int Number;
extern int SampleNo;
extern short txtail[5];
extern short txdelay[5];
extern short tx_baudrate[5];
extern int ARDOPTXLen[4]; // Length of frame
extern int ARDOPTXPtr[4]; // Tx Pointer
void RUHEncode(unsigned char * Data, int Len, int chan)
{
// Generate audio samples from frame data
int bitcount;
int txdcount;
2024-04-13 17:48:58 +01:00
int i, j;
2023-09-12 21:38:15 +01:00
unsigned short CRC;
number_of_bits_sent[chan] = 0;
SampleNo = 0;
2024-04-13 17:48:58 +01:00
if (il2p_mode[chan] >= IL2P_MODE_TXRX)
{
// il2p generates TXDELAY as part of the frame, so just build frame
string * result;
packet_t pp = ax25_new();
int polarity = 0;
// Call il2p_send_frame to build the bit stream
pp->frame_len = Len;
memcpy(pp->frame_data, Data, Len + 2); // Copy the crc in case we are going to send it
result = il2p_send_frame(chan, pp, 1, 0);
for (j = 0; j < result->Length; j++)
{
int x = result->Data[j];
for (i = 0; i < 8; i++)
{
int dbit = (x & 0x80) != 0;
tone_gen_put_bit(chan, (dbit ^ polarity) & 1, 0); // No Scramble
x <<= 1;
number_of_bits_sent[chan]++;
}
}
freeString(result);
ax25_delete(pp);
// sample No should now contain number of (stereo) samples
ARDOPTXLen[chan] = SampleNo;
ARDOPTXPtr[chan] = 0;
return;
}
2023-09-12 21:38:15 +01:00
// First do TX delay
// Set up txd worth of flags
txdcount = (txdelay[chan] * tx_baudrate[chan]) / 8000; // 8 for bits, 1000 for mS
if (txdcount > 1024)
txdcount = 1024;
while (txdcount--)
send_control_nrzi(chan, 0x7e); // Flag
for (j = 0; j < Len; j++)
{
send_data_nrzi(chan, Data[j]);
}
CRC = get_fcs(Data, Len);
send_data_nrzi(chan, (CRC & 0xff));
send_data_nrzi(chan, (CRC >> 8));
// do we need tail here??
send_control_nrzi(chan, 0x7e); // Flag
ARDOPTXLen[chan] = SampleNo;
ARDOPTXPtr[chan] = 0;
// sampleNo should now contain number of (stereo) samples
}