nxdn48 initial checkin for rx

max
Max 4 years ago
parent 538ae6015b
commit 140a76ac76
  1. 3
      op25/gr-op25_repeater/apps/multi_rx.py
  2. 4
      op25/gr-op25_repeater/apps/p25_demodulator.py
  3. 38
      op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py
  4. 1
      op25/gr-op25_repeater/lib/CMakeLists.txt
  5. 176
      op25/gr-op25_repeater/lib/nxdn.cc
  6. 28
      op25/gr-op25_repeater/lib/nxdn.h
  7. 32
      op25/gr-op25_repeater/lib/nxdn_const.h
  8. 14
      op25/gr-op25_repeater/lib/rx_sync.cc
  9. 11
      op25/gr-op25_repeater/lib/rx_sync.h

@ -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,

@ -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':

@ -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)

@ -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})

@ -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 <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#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<len; i++) {
uint8_t bit = buf[i] & 1;
crc = ((crc << 1) | bit) & 0x1ffff;
if (crc & 0x10000)
crc = (crc & 0xffff) ^ poly;
}
crc = crc ^ 0xffff;
return crc & 0xffff;
}
// trellis_1_2 encode: source is in bits, result in bits
static inline void trellis_encode(uint8_t result[], const uint8_t source[], int result_len, int reg)
{
for (int i=0; i<result_len; i+=2) {
reg = (reg << 1) | source[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<NTESTC; i++) {
bt[0] = (i&8)>>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<NTEST*2; j++) {
sum += tt[j] ^ source[p*2+j];
}
if (i == 0 || sum < min_d) {
min_d = sum;
min_bt = bt[0];
}
}
result[p] = min_bt;
reg = (reg << 1) | min_bt;
dstats[(min_d > 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<sizeof(scramble_t); i++) {
if (scramble_t[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; */
}

@ -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 */

@ -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 <stdint.h>
/* 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 */

@ -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;

@ -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 {

Loading…
Cancel
Save