diff --git a/op25/gr-op25_repeater/apps/multi_rx.py b/op25/gr-op25_repeater/apps/multi_rx.py index 20cc34c..a7dde1b 100755 --- a/op25/gr-op25_repeater/apps/multi_rx.py +++ b/op25/gr-op25_repeater/apps/multi_rx.py @@ -129,7 +129,8 @@ class channel(object): if dev.args.startswith('audio:'): self.demod = p25_demodulator.p25_demod_fb( input_rate = dev.sample_rate, - filter_type = config['filter_type']) + filter_type = config['filter_type'], + symbol_rate = self.symbol_rate) else: self.demod = p25_demodulator.p25_demod_cb( input_rate = dev.sample_rate, diff --git a/op25/gr-op25_repeater/apps/p25_demodulator.py b/op25/gr-op25_repeater/apps/p25_demodulator.py index e53d3eb..334cb63 100644 --- a/op25/gr-op25_repeater/apps/p25_demodulator.py +++ b/op25/gr-op25_repeater/apps/p25_demodulator.py @@ -98,6 +98,8 @@ class p25_demod_base(gr.hier_block2): if ntaps & 1 == 0: ntaps += 1 coeffs = filter.firdes.root_raised_cosine(1.0, if_rate, symbol_rate, excess_bw, ntaps) + if filter_type == 'nxdn': + coeffs = op25_c4fm_mod.c4fm_taps(sample_rate=self.if_rate, span=9, generator=op25_c4fm_mod.transfer_function_nxdn).generate() if filter_type == 'gmsk': # lifted from gmsk.py _omega = sps @@ -393,7 +395,7 @@ class p25_demod_cb(p25_demod_base): elif src == 'diffdec': self.connect(self.diffdec, sink) elif src == 'mixer': - self.connect(self.mixer, sink) + self.connect(self.agc, sink) elif src == 'src': self.connect(self, sink) elif src == 'bpf': diff --git a/op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py b/op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py index e1f1276..5c99017 100755 --- a/op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py +++ b/op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py @@ -49,14 +49,14 @@ _def_span = 13 #desired number of impulse response coeffs, in units of symbols _def_gmsk_span = 4 _def_bt = 0.25 -def transfer_function_rx(): +def transfer_function_rx(symbol_rate=_def_symbol_rate): # p25 c4fm de-emphasis filter # Specs undefined above 2,880 Hz. It would be nice to have a sharper # rolloff, but this filter is cheap enough.... xfer = [] # frequency domain transfer function - for f in xrange(0,4800): + for f in xrange(0,symbol_rate): # D(f) - t = pi * f / 4800 + t = pi * f / symbol_rate if t < 1e-6: df = 1.0 else: @@ -64,7 +64,7 @@ def transfer_function_rx(): xfer.append(df) return xfer -def transfer_function_tx(): +def transfer_function_tx(symbol_rate=_def_symbol_rate): xfer = [] # frequency domain transfer function for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz # H(f) @@ -82,7 +82,7 @@ def transfer_function_tx(): xfer.append(pf * hf) return xfer -def transfer_function_dmr(): +def transfer_function_dmr(symbol_rate=_def_symbol_rate): xfer = [] # frequency domain transfer function for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz if f < 1920: @@ -94,6 +94,32 @@ def transfer_function_dmr(): xfer = np.sqrt(xfer) # root cosine return xfer +def transfer_function_nxdn(symbol_rate=_def_symbol_rate): + assert symbol_rate == 2400 or symbol_rate == 4800 + T = 1.0 / symbol_rate + a = 0.2 # rolloff + fl = int(0.5+(1-a)/(2*T)) + fh = int(0.5+(1+a)/(2*T)) + + xfer = [] + for f in xrange(0, symbol_rate): + if f < fl: + hf = 1.0 + elif f >= fl and f <= fh: + hf = cos((T/(4*a)) * (2*pi*f - pi*(1-a)/T)) + else: + hf = 0.0 + x = pi * f * T + if f <= fh: + if x == 0 or sin(x) == 0: + df = 1.0 + else: + df = x / sin(x) + else: + df = 2.0 + xfer.append(hf * df) + return xfer + class c4fm_taps(object): """Generate filter coefficients as per P25 C4FM spec""" def __init__(self, filter_gain = 1.0, sample_rate=_def_output_sample_rate, symbol_rate=_def_symbol_rate, span=_def_span, generator=transfer_function_tx): @@ -105,7 +131,7 @@ class c4fm_taps(object): self.generator = generator def generate(self): - impulse_response = np.fft.fftshift(np.fft.irfft(self.generator(), self.sample_rate)) + impulse_response = np.fft.fftshift(np.fft.irfft(self.generator(symbol_rate=self.symbol_rate), self.sample_rate)) start = np.argmax(impulse_response) - (self.ntaps-1) / 2 coeffs = impulse_response[start: start+self.ntaps] gain = self.filter_gain / sum(coeffs) diff --git a/op25/gr-op25_repeater/lib/CMakeLists.txt b/op25/gr-op25_repeater/lib/CMakeLists.txt index 020a689..983b5b5 100644 --- a/op25/gr-op25_repeater/lib/CMakeLists.txt +++ b/op25/gr-op25_repeater/lib/CMakeLists.txt @@ -57,6 +57,7 @@ list(APPEND op25_repeater_sources op25_audio.cc CCITTChecksumReverse.cpp value_string.cc + nxdn.cc ) add_library(gnuradio-op25_repeater SHARED ${op25_repeater_sources}) diff --git a/op25/gr-op25_repeater/lib/nxdn.cc b/op25/gr-op25_repeater/lib/nxdn.cc new file mode 100644 index 0000000..13703ce --- /dev/null +++ b/op25/gr-op25_repeater/lib/nxdn.cc @@ -0,0 +1,176 @@ +/* -*- c++ -*- */ +/* + * NXDN Encoder/Decoder (C) Copyright 2019 Max H. Parke KA1RBI + * + * This file is part of OP25 + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software 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 software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include "bit_utils.h" + +#include "nxdn.h" + +static const uint16_t perm_25_12[300] = { // perumtation schedule for 25x12 + 0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276,288, + 1,13,25,37,49,61,73,85,97,109,121,133,145,157,169,181,193,205,217,229,241,253,265,277,289, + 2,14,26,38,50,62,74,86,98,110,122,134,146,158,170,182,194,206,218,230,242,254,266,278,290, + 3,15,27,39,51,63,75,87,99,111,123,135,147,159,171,183,195,207,219,231,243,255,267,279,291, + 4,16,28,40,52,64,76,88,100,112,124,136,148,160,172,184,196,208,220,232,244,256,268,280,292, + 5,17,29,41,53,65,77,89,101,113,125,137,149,161,173,185,197,209,221,233,245,257,269,281,293, + 6,18,30,42,54,66,78,90,102,114,126,138,150,162,174,186,198,210,222,234,246,258,270,282,294, + 7,19,31,43,55,67,79,91,103,115,127,139,151,163,175,187,199,211,223,235,247,259,271,283,295, + 8,20,32,44,56,68,80,92,104,116,128,140,152,164,176,188,200,212,224,236,248,260,272,284,296, + 9,21,33,45,57,69,81,93,105,117,129,141,153,165,177,189,201,213,225,237,249,261,273,285,297, + 10,22,34,46,58,70,82,94,106,118,130,142,154,166,178,190,202,214,226,238,250,262,274,286,298, + 11,23,35,47,59,71,83,95,107,119,131,143,155,167,179,191,203,215,227,239,251,263,275,287,299}; + +static const uint8_t scramble_t[] = { + 2, 5, 6, 7, 10, 12, 14, 16, 17, 22, 23, 25, 26, 27, 28, 30, 33, 34, 36, 37, 38, 41, 45, 47, + 52, 54, 56, 57, 59, 62, 63, 64, 65, 66, 67, 69, 70, 73, 76, 79, 81, 82, 84, 85, 86, 87, 88, + 89, 92, 95, 96, 98, 100, 103, 104, 107, 108, 116, 117, 121, 122, 125, 127, 131, 132, 134, + 137, 139, 140, 141, 142, 143, 144, 145, 147, 151, 153, 154, 158, 159, 160, 162, 164, 165, + 168, 170, 171, 174, 175, 176, 177, 181}; + +static const int PARITY[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1}; + +static inline uint16_t crc16(const uint8_t buf[], int len, uint32_t crc) { + uint32_t poly = (1<<12) + (1<<5) + (1<<0); + for(int i=0; i>1]; + result[i] = PARITY[reg & 0x19]; + result[i+1] = PARITY[reg & 0x17]; + } +} + +// simplified trellis 2:1 decode; source and result in bits +// assumes that encoding was done with NTEST trailing zero bits +// result_len should be set to the actual number of data bits +// in the original unencoded message (excl. these trailing bits) +static inline void trellis_decode(uint8_t result[], const uint8_t source[], int result_len) +{ + int reg = 0; + int min_d; + int min_bt; + static const int NTEST = 4; + static const int NTESTC = 1 << NTEST; + uint8_t bt[NTEST]; + uint8_t tt[NTEST*2]; + int dstats[4]; + int sum; + for (int p=0; p < 4; p++) + dstats[p] = 0; + for (int p=0; p < result_len; p++) { + for (int i=0; i>3; + bt[1] = (i&4)>>2; + bt[2] = (i&2)>>1; + bt[3] = (i&1); + trellis_encode(tt, bt, NTEST*2, reg); + sum=0; + for (int j=0; j 3) ? 3 : min_d] += 1; + } + // fprintf (stderr, "stats\t%d %d %d %d\n", dstats[0], dstats[1], dstats[2], dstats[3]); +} + +void nxdn_descramble(uint8_t dibits[], int len) { + for (int i=0; i= len) + break; + dibits[scramble_t[i]] ^= 0x2; // invert sign of scrambled dibits + } +} + +static inline void decode_cac(const uint8_t dibits[], int len) { + uint8_t cacbits[300]; + uint8_t deperm[300]; + uint8_t depunc[350]; + uint8_t decode[171]; + int id=0; + uint16_t crc; + + dibits_to_bits(cacbits, dibits, 150); + for (int i=0; i<300; i++) { + deperm[perm_25_12[i]] = cacbits[i]; + } + for (int i=0; i<25; i++) { + depunc[id++] = deperm[i*12]; + depunc[id++] = deperm[i*12+1]; + depunc[id++] = deperm[i*12+2]; + depunc[id++] = 0; + depunc[id++] = deperm[i*12+3]; + depunc[id++] = deperm[i*12+4]; + depunc[id++] = deperm[i*12+5]; + depunc[id++] = deperm[i*12+6]; + depunc[id++] = deperm[i*12+7]; + depunc[id++] = deperm[i*12+8]; + depunc[id++] = deperm[i*12+9]; + depunc[id++] = 0; + depunc[id++] = deperm[i*12+10]; + depunc[id++] = deperm[i*12+11]; + } + trellis_decode(decode, depunc, 171); + crc = crc16(decode, 171, 0xc3ee); + if (crc != 0) + return; // ignore msg if crc failed + uint8_t msg_type = load_i(decode+8, 8) & 0x3f; + // todo: forward CAC message +} + +void nxdn_frame(const uint8_t dibits[], int ndibits) { + uint8_t descrambled[182]; + uint8_t lich; + uint8_t lich_test; + uint8_t bit72[72]; + + assert (ndibits >= 170); + memcpy(descrambled, dibits, ndibits); + nxdn_descramble(descrambled, ndibits); + lich = 0; + for (int i=0; i<8; i++) + lich |= (descrambled[i] >> 1) << (7-i); + /* todo: parity check & process LICH */ + if (lich >> 1 == 0x01) + decode_cac(descrambled+8, 150); + /* todo: process E: 12 dibits at descrambed+158; */ +} diff --git a/op25/gr-op25_repeater/lib/nxdn.h b/op25/gr-op25_repeater/lib/nxdn.h new file mode 100644 index 0000000..e74d954 --- /dev/null +++ b/op25/gr-op25_repeater/lib/nxdn.h @@ -0,0 +1,28 @@ +// +// NXDN Encoder (C) Copyright 2019 Max H. Parke KA1RBI +// thx gr-ysf fr_vch_decoder_bb_impl.cc * Copyright 2015 Mathias Weyland * +// +// This file is part of OP25 +// +// OP25 is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3, or (at your option) +// any later version. +// +// OP25 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 OP25; see the file COPYING. If not, write to the Free +// Software Foundation, Inc., 51 Franklin Street, Boston, MA +// 02110-1301, USA. + +#ifndef INCLUDED_NXDN_H +#define INCLUDED_NXDN_H + +void nxdn_frame(const uint8_t dibits[], int ndibits); +void nxdn_descramble(uint8_t dibits[], int len); + +#endif /* INCLUDED_NXDN_H */ diff --git a/op25/gr-op25_repeater/lib/nxdn_const.h b/op25/gr-op25_repeater/lib/nxdn_const.h new file mode 100644 index 0000000..fa9cbca --- /dev/null +++ b/op25/gr-op25_repeater/lib/nxdn_const.h @@ -0,0 +1,32 @@ +// +// NXDN Encoder (C) Copyright 2019 Max H. Parke KA1RBI +// thx gr-ysf fr_vch_decoder_bb_impl.cc * Copyright 2015 Mathias Weyland * +// +// This file is part of OP25 +// +// OP25 is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3, or (at your option) +// any later version. +// +// OP25 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 OP25; see the file COPYING. If not, write to the Free +// Software Foundation, Inc., 51 Franklin Street, Boston, MA +// 02110-1301, USA. + +#ifndef INCLUDED_NXDN_CONST_H +#define INCLUDED_NXDN_CONST_H + +#include + +/* postamble + frame sync (FS) */ +static const uint64_t NXDN_POSTFS_SYNC_MAGIC = 0x5775fdcdf59LL; +/* frame sync + scrambled rendition of LICH=0x6e (a halfrate voice 4V) */ +static const uint64_t NXDN_FS6E_SYNC_MAGIC = 0xcdf5975d7LL; + +#endif /* INCLUDED_NXDN_CONST_H */ diff --git a/op25/gr-op25_repeater/lib/rx_sync.cc b/op25/gr-op25_repeater/lib/rx_sync.cc index 1ca7f8e..02478a0 100644 --- a/op25/gr-op25_repeater/lib/rx_sync.cc +++ b/op25/gr-op25_repeater/lib/rx_sync.cc @@ -207,6 +207,7 @@ void rx_sync::codeword(const uint8_t* cw, const enum codeword_types codeword_typ switch(codeword_type) { case CODEWORD_DMR: + case CODEWORD_NXDN_EHR: // halfrate interleaver.process_vcw(cw, b); if (b[0] < 120) mbe_dequantizeAmbe2250Parms(&cur_mp[slot_id], &prev_mp[slot_id], b); @@ -287,6 +288,7 @@ void rx_sync::rx_sym(const uint8_t sym) bool unmute; uint8_t tmpcw[144]; bool ysf_fullrate; + uint8_t dbuf[182]; d_symbol_count ++; d_sync_reg = (d_sync_reg << 2) | (sym & 3); @@ -368,6 +370,18 @@ void rx_sync::rx_sym(const uint8_t sym) } } break; + case RX_TYPE_NXDN_CAC: + nxdn_frame(symbol_ptr+22, 170); + break; + case RX_TYPE_NXDN_EHR: + memcpy(dbuf, symbol_ptr+10, sizeof(dbuf)); + nxdn_descramble(dbuf, sizeof(dbuf)); + // todo: process SACCH + codeword(dbuf+38+36*0, CODEWORD_NXDN_EHR, 0); + codeword(dbuf+38+36*1, CODEWORD_NXDN_EHR, 0); + codeword(dbuf+38+36*2, CODEWORD_NXDN_EHR, 0); + codeword(dbuf+38+36*3, CODEWORD_NXDN_EHR, 0); + break; case RX_N_TYPES: assert(0==1); /* should not occur */ break; diff --git a/op25/gr-op25_repeater/lib/rx_sync.h b/op25/gr-op25_repeater/lib/rx_sync.h index bbad158..7ab2922 100644 --- a/op25/gr-op25_repeater/lib/rx_sync.h +++ b/op25/gr-op25_repeater/lib/rx_sync.h @@ -42,6 +42,8 @@ #include "op25_imbe_frame.h" #include "software_imbe_decoder.h" #include "op25_audio.h" +#include "nxdn_const.h" +#include "nxdn.h" namespace gr{ namespace op25_repeater{ @@ -54,6 +56,8 @@ enum rx_types { RX_TYPE_DMR, RX_TYPE_DSTAR, RX_TYPE_YSF, + RX_TYPE_NXDN_EHR, + RX_TYPE_NXDN_CAC, RX_N_TYPES }; // also used as array index @@ -69,7 +73,9 @@ static const struct _mode_data { {"P25", 48,0,864,1728, P25_FRAME_SYNC_MAGIC}, {"DMR", 48,66,144,1728, DMR_VOICE_SYNC_MAGIC}, {"DSTAR", 48,72,96,2016*2, DSTAR_FRAME_SYNC_MAGIC}, - {"YSF", 40,0,480,480*2, YSF_FRAME_SYNC_MAGIC} + {"YSF", 40,0,480,480*2, YSF_FRAME_SYNC_MAGIC}, + {"NXDN_EHR", 36,0,192,192*2, NXDN_FS6E_SYNC_MAGIC}, + {"NXDN_CAC", 44,0,192,192*2, NXDN_POSTFS_SYNC_MAGIC} }; // index order must match rx_types enum enum codeword_types { @@ -78,7 +84,8 @@ enum codeword_types { CODEWORD_DMR, CODEWORD_DSTAR, CODEWORD_YSF_FULLRATE, - CODEWORD_YSF_HALFRATE + CODEWORD_YSF_HALFRATE, + CODEWORD_NXDN_EHR }; class rx_sync {