nxdn48 initial checkin for rx
This commit is contained in:
parent
538ae6015b
commit
140a76ac76
|
@ -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…
Reference in New Issue