kopia lustrzana https://github.com/pabr/leansdr
481 wiersze
14 KiB
C++
481 wiersze
14 KiB
C++
#ifndef LEANSDR_SDR_H
|
|
#define LEANSDR_SDR_H
|
|
|
|
namespace leansdr {
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// SDR blocks
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
typedef unsigned char u8;
|
|
typedef unsigned short u16;
|
|
typedef signed char s8;
|
|
typedef float f32;
|
|
|
|
typedef complex<f32> cf32;
|
|
typedef complex<f32> iqsymbol;
|
|
typedef complex<u8> cu8;
|
|
typedef complex<s8> cs8;
|
|
|
|
template<typename T>
|
|
struct auto_notch : runnable {
|
|
int decimation;
|
|
float k;
|
|
auto_notch(scheduler *sch, pipebuf< complex<T> > &_in,
|
|
pipebuf< complex<T> > &_out, int _nslots,
|
|
T _agc_rms_setpoint)
|
|
: runnable(sch, "auto_notch"),
|
|
decimation(1024), k(0.002), // k(0.01)
|
|
fft(4096),
|
|
in(_in), out(_out,fft.n),
|
|
nslots(_nslots), slots(new slot[nslots]),
|
|
phase(0), gain(1), agc_rms_setpoint(_agc_rms_setpoint) {
|
|
for ( int s=0; s<nslots; ++s ) {
|
|
slots[s].i = -1;
|
|
slots[s].expj = new complex<float>[fft.n];
|
|
}
|
|
}
|
|
void run() {
|
|
while ( in.readable()>=fft.n && out.writable()>=fft.n ) {
|
|
if ( ! phase ) detect();
|
|
if ( ++phase >= decimation ) phase = 0;
|
|
process();
|
|
in.read(fft.n);
|
|
out.written(fft.n);
|
|
}
|
|
}
|
|
void detect() {
|
|
complex<T> *pin = in.rd();
|
|
complex<float> data[fft.n];
|
|
float m0=0, m2=0;
|
|
for ( int i=0; i<fft.n; ++i ) {
|
|
data[i].re = pin[i].re;
|
|
data[i].im = pin[i].im;
|
|
m2 += (float)pin[i].re*pin[i].re + (float)pin[i].im*pin[i].im;
|
|
if ( gen_abs(pin[i].re) > m0 ) m0 = gen_abs(pin[i].re);
|
|
if ( gen_abs(pin[i].im) > m0 ) m0 = gen_abs(pin[i].im);
|
|
}
|
|
if ( agc_rms_setpoint && m2 ) {
|
|
float rms = gen_sqrt(m2/fft.n);
|
|
if ( sch->debug ) fprintf(stderr, "(pow %f max %f)", rms, m0);
|
|
float new_gain = agc_rms_setpoint / rms;
|
|
gain = gain*0.9 + new_gain*0.1;
|
|
}
|
|
fft.inplace(data, true);
|
|
float amp[fft.n];
|
|
for ( int i=0; i<fft.n; ++i ) amp[i] = hypotf(data[i].re, data[i].im);
|
|
for ( slot *s=slots; s<slots+nslots; ++s ) {
|
|
int iamax = 0;
|
|
for ( int i=0; i<fft.n; ++i )
|
|
if ( amp[i] > amp[iamax] ) iamax=i;
|
|
if ( iamax != s->i ) {
|
|
if ( sch->debug )
|
|
fprintf(stderr, "%s: slot %d new peak %d -> %d\n",
|
|
name, (int)(s-slots), s->i, iamax);
|
|
s->i = iamax;
|
|
s->estim.re = 0;
|
|
s->estim.im = 0;
|
|
s->estt = 0;
|
|
for ( int i=0; i<fft.n; ++i ) {
|
|
float a = 2 * M_PI * s->i * i / fft.n;
|
|
s->expj[i].re = cosf(a);
|
|
s->expj[i].im = sinf(a);
|
|
}
|
|
}
|
|
amp[iamax] = 0;
|
|
if ( iamax-1 >= 0 ) amp[iamax-1] = 0;
|
|
if ( iamax+1 < fft.n ) amp[iamax+1] = 0;
|
|
}
|
|
}
|
|
void process() {
|
|
complex<T> *pin=in.rd(), *pend=pin+fft.n, *pout=out.wr();
|
|
for ( slot *s=slots; s<slots+nslots; ++s ) s->ej = s->expj;
|
|
for ( ; pin<pend; ++pin,++pout ) {
|
|
complex<float> out = *pin;
|
|
// TODO Optimize for nslots==1 ?
|
|
for ( slot *s=slots; s<slots+nslots; ++s->ej,++s ) {
|
|
complex<float> bb(pin->re*s->ej->re + pin->im*s->ej->im,
|
|
-pin->re*s->ej->im + pin->im*s->ej->re);
|
|
s->estim.re = bb.re*k + s->estim.re*(1-k);
|
|
s->estim.im = bb.im*k + s->estim.im*(1-k);
|
|
complex<float> sub(s->estim.re*s->ej->re - s->estim.im*s->ej->im,
|
|
s->estim.re*s->ej->im + s->estim.im*s->ej->re);
|
|
out.re -= sub.re;
|
|
out.im -= sub.im;
|
|
}
|
|
pout->re = gain * out.re;
|
|
pout->im = gain * out.im;
|
|
}
|
|
}
|
|
|
|
private:
|
|
cfft_engine<float> fft;
|
|
pipereader< complex<T> > in;
|
|
pipewriter< complex<T> > out;
|
|
int nslots;
|
|
struct slot {
|
|
int i;
|
|
complex<float> estim;
|
|
complex<float> *expj, *ej;
|
|
int estt;
|
|
} *slots;
|
|
int phase;
|
|
float gain;
|
|
T agc_rms_setpoint;
|
|
};
|
|
|
|
template<typename T>
|
|
struct ss_estimator : runnable {
|
|
unsigned long window_size; // Samples per estimation
|
|
unsigned long decimation; // Output rate
|
|
ss_estimator(scheduler *sch, pipebuf< complex<T> > &_in, pipebuf<T> &_out)
|
|
: runnable(sch, "SS estimator"),
|
|
window_size(1024), decimation(1),
|
|
in(_in), out(_out),
|
|
phase(0) {
|
|
}
|
|
void run() {
|
|
while ( in.readable()>=window_size && out.writable()>=1 ) {
|
|
if ( ! phase ) {
|
|
complex<T> *p=in.rd(), *pend=p+window_size;
|
|
float s = 0;
|
|
for ( ; p<pend; ++p )
|
|
s += (float)p->re*p->re + (float)p->im*p->im;
|
|
*out.wr() = sqrtf(s/window_size);
|
|
out.written(1);
|
|
}
|
|
in.read(window_size);
|
|
if ( ++phase >= decimation ) phase = 0;
|
|
}
|
|
}
|
|
private:
|
|
pipereader< complex<T> > in;
|
|
pipewriter<T> out;
|
|
unsigned long phase;
|
|
};
|
|
|
|
typedef unsigned short u_angle; // [0,2PI[ in 65536 steps
|
|
typedef signed short s_angle; // [-PI,PI[ in 65536 steps
|
|
|
|
// GENERIC CONSTELLATION DECODING BY LOOK-UP TABLE.
|
|
// R must be a power of 2.
|
|
// Up to 256 symbols.
|
|
|
|
struct softsymbol {
|
|
unsigned char symbol; // 000000IQ for QPSK
|
|
unsigned char metric;
|
|
};
|
|
|
|
const float cstln_amp = 64;
|
|
|
|
template<int R>
|
|
struct cstln_lut {
|
|
complex<signed char> *symbols;
|
|
enum predef { QPSK };
|
|
cstln_lut(predef type) {
|
|
signed char a;
|
|
switch ( type ) {
|
|
case QPSK:
|
|
a = cstln_amp * sqrtf(2)/2;
|
|
symbols = new complex<signed char>[4];
|
|
symbols[0].re = a; symbols[0].im = a;
|
|
symbols[1].re = a; symbols[1].im = -a;
|
|
symbols[2].re = -a; symbols[2].im = a;
|
|
symbols[3].re = -a; symbols[3].im = -a;
|
|
make_lut_from_symbols(4);
|
|
break;
|
|
// TBD: BPSK, 8PSK, 16QAM, 16APSK, 32APSK
|
|
default:
|
|
fail("Constellation not implemented");
|
|
}
|
|
}
|
|
struct result {
|
|
struct softsymbol ss;
|
|
s_angle phase_error;
|
|
};
|
|
inline result *lookup(int I, int Q) {
|
|
return &lut[(unsigned char)I][(unsigned char)Q];
|
|
}
|
|
private:
|
|
result lut[R][R];
|
|
void make_lut_from_symbols(int M) {
|
|
for ( int I=-R/2; I<R/2; ++I )
|
|
for ( int Q=-R/2; Q<R/2; ++Q ) {
|
|
unsigned int dmin = R*2;
|
|
unsigned char smin = 0;
|
|
for ( int s=0; s<M; ++s ) {
|
|
unsigned int d = hypotf(I-symbols[s].re, Q-symbols[s].im);
|
|
if ( d < dmin ) { dmin=d; smin=s; }
|
|
}
|
|
float ph_symbol = atan2f(symbols[smin].im,symbols[smin].re);
|
|
float ph_err = atan2f(Q,I) - ph_symbol;
|
|
result *pr = &lut[I&(R-1)][Q&(R-1)];
|
|
if ( dmin > 255 ) fail("dmin overflow");
|
|
pr->ss.symbol = smin;
|
|
pr->ss.metric = dmin;
|
|
//pr->ss.metric = 255 * (int)dmin / dmin2;
|
|
pr->phase_error = ph_err * 65536 / (2*M_PI);
|
|
}
|
|
}
|
|
};
|
|
|
|
// CONSTELLATION RECEIVER
|
|
|
|
template<typename T>
|
|
struct cstln_receiver : runnable {
|
|
cstln_lut<256> *cstln;
|
|
unsigned long meas_decimation; // Measurement rate
|
|
float omega, min_omega, max_omega; // Samples per symbol
|
|
u_angle freqw, min_freqw, max_freqw; // Freq offset in angle per sample
|
|
static const unsigned int chunk_size = 128;
|
|
float kest;
|
|
|
|
cstln_receiver(scheduler *sch,
|
|
pipebuf< complex<T> > &_in,
|
|
pipebuf<softsymbol> &_out,
|
|
pipebuf<float> *_freq_out=NULL,
|
|
pipebuf<float> *_ss_out=NULL,
|
|
pipebuf<float> *_mer_out=NULL,
|
|
pipebuf<cf32> *_cstln_out=NULL)
|
|
: runnable(sch, "Constellation receiver"),
|
|
cstln(NULL),
|
|
meas_decimation(1048576),
|
|
kest(0.01),
|
|
in(_in), out(_out, chunk_size),
|
|
est_insp(cstln_amp*cstln_amp), agc_gain(1),
|
|
mu(0), phase(0),
|
|
est_sp(0), est_ep(0),
|
|
meas_count(0) {
|
|
set_omega(1);
|
|
set_freq(0);
|
|
freq_out = _freq_out ? new pipewriter<float>(*_freq_out) : NULL;
|
|
ss_out = _ss_out ? new pipewriter<float>(*_ss_out) : NULL;
|
|
mer_out = _mer_out ? new pipewriter<float>(*_mer_out) : NULL;
|
|
cstln_out = _cstln_out ? new pipewriter<cf32>(*_cstln_out) : NULL;
|
|
memset(hist, 0, sizeof(hist));
|
|
init_trig_tables();
|
|
}
|
|
|
|
void set_omega(float _omega, float tol=10e-6) {
|
|
omega = _omega;
|
|
min_omega = omega * (1-tol);
|
|
max_omega = omega * (1+tol);
|
|
}
|
|
|
|
void set_freq(float freq) {
|
|
freqw = freq * 65536;
|
|
min_freqw = freqw - 65536/8;
|
|
max_freqw = freqw + 65536/8;
|
|
}
|
|
|
|
void run() {
|
|
if ( ! cstln ) fail("constellation not set");
|
|
|
|
// Magic constants that work with the qa recordings.
|
|
const signed long freq_alpha = 0.04 * 65536;
|
|
const signed long freq_beta = 0.001 * 65536;
|
|
float gain_mu = 0.02 / (cstln_amp*cstln_amp) * 2;
|
|
|
|
int max_meas = chunk_size/meas_decimation + 1;
|
|
while ( in.readable() >= chunk_size &&
|
|
out.writable() >= chunk_size/min_omega+1 &&
|
|
( !freq_out || freq_out ->writable()>=max_meas ) &&
|
|
( !ss_out || ss_out ->writable()>=max_meas ) &&
|
|
( !mer_out || mer_out ->writable()>=max_meas ) &&
|
|
( !cstln_out || cstln_out->writable()>=max_meas ) ) {
|
|
|
|
complex<T> *pin=in.rd(), *pin0=pin, *pend=pin+chunk_size;
|
|
softsymbol *pout=out.wr(), *pout0=pout;
|
|
|
|
// These are scoped outside the loop for SS and MER estimation.
|
|
complex<float> s; // For MER estimation and constellation viewer
|
|
complex<signed char> *cstln_point = NULL;
|
|
|
|
while ( pin < pend ) {
|
|
// Here mu is the time of the next symbol counted from 0 at pin.
|
|
if ( mu < 1 ) {
|
|
// Here 0<=mu<1 is the fractional time of the next symbol
|
|
// between pin and pin+1.
|
|
|
|
// Derotate pin[0] and pin[1]
|
|
float cosph, sinph;
|
|
cosph = fastcos(-phase);
|
|
sinph = fastsin(-phase);
|
|
complex<float> s0(pin[0].re*cosph - pin[0].im*sinph,
|
|
pin[0].re*sinph + pin[0].im*cosph);
|
|
cosph = fastcos(-(phase+freqw));
|
|
sinph = fastsin(-(phase+freqw));
|
|
complex<float> s1(pin[1].re*cosph - pin[1].im*sinph,
|
|
pin[1].re*sinph + pin[1].im*cosph);
|
|
|
|
// Interpolate linearly
|
|
float cmu = 1 - mu;
|
|
s.re = (s0.re*cmu + s1.re*mu) * agc_gain;
|
|
s.im = (s0.im*cmu + s1.im*mu) * agc_gain;
|
|
|
|
// Constellation look-up
|
|
cstln_lut<256>::result *cr = cstln->lookup(s.re, s.im);
|
|
*pout = cr->ss;
|
|
++pout;
|
|
|
|
// PLL
|
|
#if 0
|
|
signed short c1 = (cr->phase_error * freq_alpha + 1) >> 16;
|
|
signed short c2 = (cr->phase_error * freq_alpha) / 65536;
|
|
// if ( c1 != c2 ) fprintf(stderr, "\n### %d %d %d\n", cr->phase_error, c1, c2);
|
|
phase += (cr->phase_error * freq_alpha) / 65536;
|
|
freqw += (cr->phase_error * freq_beta) / 65536;
|
|
#else
|
|
phase += (cr->phase_error * freq_alpha + 32768) >> 16;
|
|
freqw += (cr->phase_error * freq_beta + 32768) >> 16;
|
|
#endif
|
|
|
|
// Modified Mueller and Müller
|
|
// mu[k]=real(c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1]))
|
|
// =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1])
|
|
// p = received signals
|
|
// c = decisions (constellation points)
|
|
hist[2] = hist[1];
|
|
hist[1] = hist[0];
|
|
hist[0].p.re = s.re;
|
|
hist[0].p.im = s.im;
|
|
cstln_point = &cstln->symbols[cr->ss.symbol];
|
|
hist[0].c.re = cstln_point->re;
|
|
hist[0].c.im = cstln_point->im;
|
|
float muerr =
|
|
( (hist[0].p.re-hist[2].p.re)*hist[1].c.re +
|
|
(hist[0].p.im-hist[2].p.im)*hist[1].c.im ) -
|
|
( (hist[0].c.re-hist[2].c.re)*hist[1].p.re +
|
|
(hist[0].c.im-hist[2].c.im)*hist[1].p.im );
|
|
float mucorr = muerr * gain_mu;
|
|
const float max_mucorr = 0.1;
|
|
// TBD Optimize out statically
|
|
if ( mucorr < -max_mucorr ) mucorr = -max_mucorr;
|
|
if ( mucorr > max_mucorr ) mucorr = max_mucorr;
|
|
mu += mucorr;
|
|
mu += omega; // Next symbol time;
|
|
} // mu<1
|
|
|
|
// Next sample
|
|
++pin;
|
|
--mu;
|
|
phase += freqw;
|
|
} // chunk_size
|
|
|
|
in.read(pin-pin0);
|
|
out.written(pout-pout0);
|
|
|
|
// Output the last interpolated PSK symbol, once per chunk_size
|
|
if ( cstln_out ) {
|
|
*cstln_out->wr() = s;
|
|
cstln_out->written(1);
|
|
}
|
|
|
|
// AGC
|
|
float insp = pin0->re*pin0->re + pin0->im*pin0->im;
|
|
est_insp = insp*kest + est_insp*(1-kest);
|
|
if ( est_insp )
|
|
agc_gain = cstln_amp / gen_sqrt(est_insp);
|
|
|
|
// SS and MER
|
|
float sig_power = s.re*s.re+s.im*s.im;
|
|
est_sp = sig_power*kest + est_sp*(1-kest);
|
|
if ( ! cstln_point ) fatal("No sample");
|
|
complex<float> errvect(s.re-cstln_point->re, s.im-cstln_point->im);
|
|
float errvect_power = errvect.re*errvect.re + errvect.im*errvect.im;
|
|
est_ep = errvect_power*kest + est_ep*(1-kest);
|
|
|
|
// This is best done periodically ouside the inner loop,
|
|
// but will cause non-deterministic output.
|
|
|
|
if ( (signed short)(freqw-min_freqw)<0 ||
|
|
(signed short)(max_freqw-freqw)<0 )
|
|
freqw = min_freqw + (unsigned short)(max_freqw-min_freqw) / 2;
|
|
|
|
// Output measurements
|
|
|
|
meas_count += pin-pin0;
|
|
while ( meas_count >= meas_decimation ) {
|
|
meas_count -= meas_decimation;
|
|
if ( freq_out ) {
|
|
*freq_out->wr() = (float)(signed short)freqw / 65536;
|
|
freq_out->written(1);
|
|
}
|
|
if ( ss_out ) {
|
|
*ss_out->wr() = sqrtf(est_sp);
|
|
ss_out->written(1);
|
|
}
|
|
if ( mer_out ) {
|
|
float mer = est_ep ? 10*logf(est_sp/est_ep)/logf(10) : 0;
|
|
*mer_out->wr() = mer;
|
|
mer_out->written(1);
|
|
}
|
|
|
|
}
|
|
|
|
} // Work to do
|
|
}
|
|
|
|
private:
|
|
struct {
|
|
complex<float> p; // Received symbol
|
|
complex<float> c; // Matched constellation point
|
|
} hist[3];
|
|
pipereader< complex<T> > in;
|
|
pipewriter<softsymbol> out;
|
|
float est_insp, agc_gain;
|
|
float mu; // PSK time expressed in clock ticks
|
|
u_angle phase;
|
|
float est_sp; // Estimated RMS signal power
|
|
float est_ep; // Estimated RMS error vector power
|
|
unsigned long meas_count;
|
|
pipewriter<float> *freq_out, *ss_out, *mer_out;
|
|
pipewriter<cf32> *cstln_out;
|
|
|
|
float lut_cos[65536];
|
|
float fastcos(u_angle a) { return lut_cos[a]; }
|
|
float fastsin(u_angle a) { return lut_cos[(u_angle)(a-16384)]; }
|
|
|
|
void init_trig_tables() {
|
|
for ( int a=0; a<65536; ++a )
|
|
lut_cos[a] = cosf(a*2*M_PI/65536);
|
|
}
|
|
};
|
|
|
|
// FREQUENCY SHIFTER
|
|
// Resolution is sample_freq/65536
|
|
|
|
template<typename T>
|
|
struct rotator : runnable {
|
|
rotator(scheduler *sch, pipebuf< complex<T> > &_in,
|
|
pipebuf< complex<T> > &_out, float freq)
|
|
: runnable(sch, "rotator"),
|
|
in(_in), out(_out), index(0) {
|
|
int ifreq = freq * 65536;
|
|
for ( int i=0; i<65536; ++i )
|
|
lut_cos[i] = cosf(2*M_PI * i * ifreq / 65536);
|
|
}
|
|
void run() {
|
|
unsigned long count = min(in.readable(), out.writable());
|
|
complex<T> *pin = in.rd(), *pend = pin+count;
|
|
complex<T> *pout = out.wr();
|
|
for ( ; pin<pend; ++pin,++pout,++index ) {
|
|
float c = lut_cos[index];
|
|
float s = lut_cos[index-16384U];
|
|
pout->re = pin->re*c - pin->im*s;
|
|
pout->im = pin->re*s + pin->im*c;
|
|
}
|
|
in.read(count);
|
|
out.written(count);
|
|
}
|
|
private:
|
|
pipereader< complex<T> > in;
|
|
pipewriter< complex<T> > out;
|
|
float lut_cos[65536];
|
|
unsigned short index; // Current phase
|
|
};
|
|
|
|
} // namespace
|
|
|
|
#endif // LEANSDR_SDR_H
|