Add notes. Start building Python module with useful algorithms.
This commit is contained in:
parent
c71a39d3d2
commit
d4e12c75ce
|
@ -0,0 +1,17 @@
|
|||
|
||||
This file contains random notitions
|
||||
-----------------------------------
|
||||
|
||||
Sample rates between 301000 Hz and 900000 Hz (inclusive) are not supported.
|
||||
They cause an invalid configuration of the RTL chip.
|
||||
rsamp_ratio = 28.8 MHz * 2**22 / sample_rate
|
||||
If bit 27 and bit 28 of rsamp_ratio are different, the RTL chip malfunctions.
|
||||
|
||||
The RTL chip has a configurable 32-tap FIR filter.
|
||||
RTL-SDR currently configures it for cutoff at 1.2 MHz (2.4 MS/s).
|
||||
|
||||
Casual test of ADC errors:
|
||||
* DC offset in order of 1 code step
|
||||
* I/Q gain mismatch in order of 4%
|
||||
* I/Q phase mismatch in order of 1% of sample interval
|
||||
|
12
TODO.txt
12
TODO.txt
|
@ -1,3 +1,15 @@
|
|||
* (experiment) measure raw signal for radio3, radio4 for ~ 1 minute
|
||||
* different gain settings
|
||||
* with/without RTL AGC mode
|
||||
* with several IF gain settings
|
||||
* confirm theories about effect of gain, IF gain, AGC
|
||||
* look for effect of gain on baseband SNR
|
||||
* look for effect of ADC calibration on baseband SNR
|
||||
* look for effect of IF bandwidth on baseband SNR
|
||||
|
||||
* (experiment) try if RTL2832 FIR filter can be optimized
|
||||
* (feature) support 'M' 'k' suffixes for sample rates and tuning frequency
|
||||
* (feature) implement off-line FM decoder in Python for experimentation
|
||||
* (feature) implement stereo pilot pulse-per-second
|
||||
* (quality) consider DC offset calibration
|
||||
* (speedup) maybe replace high-order FIR downsampling filter with 2nd order butterworth followed by lower order FIR filter
|
||||
|
|
103
plltest.py
103
plltest.py
|
@ -1,103 +0,0 @@
|
|||
"""
|
||||
Test PLL algorithm.
|
||||
|
||||
Usage: testpll.py baseband.dat centerfreq bandwidth > output.dat
|
||||
|
||||
baseband.dat Raw 16-bit signed little-endian sample stream
|
||||
centerfreq Center frequency relative to sample frequency (0.5 = Nyquist)
|
||||
bandwidth Approximate bandwidth of PLL relative to sample frequency
|
||||
output.dat ASCII file with space-separated data
|
||||
"""
|
||||
|
||||
import sys
|
||||
import numpy
|
||||
|
||||
|
||||
def pll(d, centerfreq, bandwidth):
|
||||
|
||||
minfreq = (centerfreq - bandwidth) * 2 * numpy.pi
|
||||
maxfreq = (centerfreq + bandwidth) * 2 * numpy.pi
|
||||
|
||||
w = bandwidth * 2 * numpy.pi
|
||||
phasor_a = numpy.poly([ numpy.exp(-1.146*w), numpy.exp(-5.331*w) ])
|
||||
phasor_b = numpy.array([ sum(phasor_a) ])
|
||||
|
||||
loopfilter_b = numpy.poly([ numpy.exp(-0.1153*w) ])
|
||||
loopfilter_b *= 0.62 * w
|
||||
|
||||
n = len(d)
|
||||
y = numpy.zeros(n)
|
||||
phasei = numpy.zeros(n)
|
||||
phaseq = numpy.zeros(n)
|
||||
phaseerr = numpy.zeros(n)
|
||||
freq = numpy.zeros(n)
|
||||
phase = numpy.zeros(n)
|
||||
freq[0] = centerfreq * 2 * numpy.pi
|
||||
|
||||
phasor_i1 = phasor_i2 = 0
|
||||
phasor_q1 = phasor_q2 = 0
|
||||
loopfilter_x1 = 0
|
||||
|
||||
for i in xrange(n):
|
||||
|
||||
psin = numpy.sin(phase[i])
|
||||
pcos = numpy.cos(phase[i])
|
||||
y[i] = pcos
|
||||
|
||||
pi = pcos * d[i]
|
||||
pq = psin * d[i]
|
||||
|
||||
pi = phasor_b[0] * pi - phasor_a[1] * phasor_i1 - phasor_a[2] * phasor_i2
|
||||
pq = phasor_b[0] * pq - phasor_a[1] * phasor_q1 - phasor_a[2] * phasor_q2
|
||||
phasor_i2 = phasor_i1
|
||||
phasor_i1 = pi
|
||||
phasor_q2 = phasor_q1
|
||||
phasor_q1 = pq
|
||||
|
||||
phasei[i] = pi
|
||||
phaseq[i] = pq
|
||||
|
||||
if pi > abs(pq):
|
||||
perr = pq / pi
|
||||
elif pq > 0:
|
||||
perr = 1
|
||||
else:
|
||||
perr = -1
|
||||
phaseerr[i] = perr
|
||||
|
||||
dfreq = loopfilter_b[0] * perr + loopfilter_b[1] * loopfilter_x1
|
||||
loopfilter_x1 = perr
|
||||
|
||||
if i + 1 < n:
|
||||
freq[i+1] = min(maxfreq, max(minfreq, freq[i] - dfreq))
|
||||
p = phase[i] + freq[i+1]
|
||||
if p > 2 * numpy.pi: p -= 2 * numpy.pi
|
||||
if p < -2 * numpy.pi: p += 2 * numpy.pi
|
||||
phase[i+1] = p
|
||||
|
||||
return y, phasei, phaseq, phaseerr, freq, phase
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
if len(sys.argv) != 4:
|
||||
print >>sys.stderr, __doc__
|
||||
sys.exit(1)
|
||||
|
||||
infile = sys.argv[1]
|
||||
centerfreq = float(sys.argv[2])
|
||||
bandwidth = float(sys.argv[3])
|
||||
|
||||
d = numpy.fromfile(infile, '<i2')
|
||||
d = d.astype(numpy.float64) / 32767.0
|
||||
|
||||
(y, phasei, phaseq, phaseerr, freq, phase) = pll(d, centerfreq, bandwidth)
|
||||
|
||||
print '#output phasei, phaseq, phaseerr freq phase'
|
||||
for i in xrange(len(y)):
|
||||
print y[i], phasei[i], phaseq[i], phaseerr[i], freq[i], phase[i]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
"""
|
||||
Test lab for FM decoding algorithms.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
import numpy
|
||||
import numpy.fft
|
||||
|
||||
|
||||
def readRawSamples(fname):
|
||||
"""Read raw sample file from rtl_sdr."""
|
||||
|
||||
d = numpy.fromfile(fname, dtype=numpy.uint8)
|
||||
d = d.astype(numpy.float64)
|
||||
d = (d - 128) / 128.0
|
||||
|
||||
return d[::2] - 1j * d[1::2]
|
||||
|
||||
|
||||
def lazyRawSamples(fname, blocklen):
|
||||
"""Return generator over blocks of raw samples."""
|
||||
|
||||
f = file(fname, 'rb')
|
||||
while 1:
|
||||
d = f.read(2 * blocklen)
|
||||
if len(d) < 2 * blocklen:
|
||||
break
|
||||
d = numpy.fromstring(d, dtype=numpy.uint8)
|
||||
d = d.astype(numpy.float64)
|
||||
d = (d - 128) / 128.0
|
||||
yield d[::2] - 1j * d[1::2]
|
||||
|
||||
|
||||
def freqShiftIQ(d, freqshift):
|
||||
"""Shift frequency by multiplication with complex phasor."""
|
||||
|
||||
def g(d, freqshift):
|
||||
p = 0
|
||||
for b in d:
|
||||
n = len(b)
|
||||
w = numpy.exp((numpy.arange(n) + p) * (2j * numpy.pi * freqshift))
|
||||
p += n
|
||||
yield b * w
|
||||
|
||||
if isinstance(d, types.GeneratorType):
|
||||
return g(d, freqshift)
|
||||
else:
|
||||
n = len(d)
|
||||
w = numpy.exp(numpy.arange(n) * (2j * numpy.pi * freqshift))
|
||||
return d * w
|
||||
|
||||
|
||||
def spectrum(d, fs=1, nfft=None, sortfreq=False):
|
||||
"""Calculate Welch-style power spectral density.
|
||||
|
||||
fs :: sample rate, default to 1
|
||||
nfft :: FFT length, default to block length
|
||||
sortfreq :: True to put negative freqs in front of positive freqs
|
||||
|
||||
Use Hann window with 50% overlap.
|
||||
|
||||
Return (freq, Pxx)."""
|
||||
|
||||
if not isinstance(d, types.GeneratorType):
|
||||
d = [ d ]
|
||||
|
||||
prev = None
|
||||
|
||||
if nfft is not None:
|
||||
assert nfft > 0
|
||||
w = numpy.hanning(nfft)
|
||||
q = numpy.zeros(nfft)
|
||||
|
||||
pos = 0
|
||||
i = 0
|
||||
for b in d:
|
||||
|
||||
if nfft is None:
|
||||
nfft = len(b)
|
||||
assert nfft > 0
|
||||
w = numpy.hanning(nfft)
|
||||
q = numpy.zeros(nfft)
|
||||
|
||||
while pos + nfft <= len(b):
|
||||
|
||||
if pos < 0:
|
||||
t = numpy.concatenate((prev[pos:], b[:pos+nfft]))
|
||||
else:
|
||||
t = b[pos:pos+nfft]
|
||||
|
||||
t *= w
|
||||
tq = numpy.fft.fft(t)
|
||||
tq *= numpy.conj(tq)
|
||||
q += numpy.real(tq)
|
||||
|
||||
del t
|
||||
del tq
|
||||
|
||||
pos += (nfft+(i%2)) // 2
|
||||
i += 1
|
||||
|
||||
pos -= len(b)
|
||||
if pos + len(b) > 0:
|
||||
prev = b
|
||||
else:
|
||||
prev = numpy.concatenate((prev[pos+len(b):], b))
|
||||
|
||||
if i > 0:
|
||||
q /= (i * numpy.sum(numpy.square(w)) * fs)
|
||||
|
||||
f = numpy.arange(nfft) * (fs / float(nfft))
|
||||
f[nfft//2:] -= fs
|
||||
|
||||
if sortfreq:
|
||||
f = numpy.concatenate((f[nfft//2:], f[:nfft//2]))
|
||||
q = numpy.concatenate((q[nfft//2:], q[:nfft//2]))
|
||||
|
||||
return f, q
|
||||
|
||||
|
||||
def pll(d, centerfreq, bandwidth):
|
||||
|
||||
minfreq = (centerfreq - bandwidth) * 2 * numpy.pi
|
||||
maxfreq = (centerfreq + bandwidth) * 2 * numpy.pi
|
||||
|
||||
w = bandwidth * 2 * numpy.pi
|
||||
phasor_a = numpy.poly([ numpy.exp(-1.146*w), numpy.exp(-5.331*w) ])
|
||||
phasor_b = numpy.array([ sum(phasor_a) ])
|
||||
|
||||
loopfilter_b = numpy.poly([ numpy.exp(-0.1153*w) ])
|
||||
loopfilter_b *= 0.62 * w
|
||||
|
||||
n = len(d)
|
||||
y = numpy.zeros(n)
|
||||
phasei = numpy.zeros(n)
|
||||
phaseq = numpy.zeros(n)
|
||||
phaseerr = numpy.zeros(n)
|
||||
freq = numpy.zeros(n)
|
||||
phase = numpy.zeros(n)
|
||||
freq[0] = centerfreq * 2 * numpy.pi
|
||||
|
||||
phasor_i1 = phasor_i2 = 0
|
||||
phasor_q1 = phasor_q2 = 0
|
||||
loopfilter_x1 = 0
|
||||
|
||||
for i in xrange(n):
|
||||
|
||||
psin = numpy.sin(phase[i])
|
||||
pcos = numpy.cos(phase[i])
|
||||
y[i] = pcos
|
||||
|
||||
pi = pcos * d[i]
|
||||
pq = psin * d[i]
|
||||
|
||||
pi = phasor_b[0] * pi - phasor_a[1] * phasor_i1 - phasor_a[2] * phasor_i2
|
||||
pq = phasor_b[0] * pq - phasor_a[1] * phasor_q1 - phasor_a[2] * phasor_q2
|
||||
phasor_i2 = phasor_i1
|
||||
phasor_i1 = pi
|
||||
phasor_q2 = phasor_q1
|
||||
phasor_q1 = pq
|
||||
|
||||
phasei[i] = pi
|
||||
phaseq[i] = pq
|
||||
|
||||
if pi > abs(pq):
|
||||
perr = pq / pi
|
||||
elif pq > 0:
|
||||
perr = 1
|
||||
else:
|
||||
perr = -1
|
||||
phaseerr[i] = perr
|
||||
|
||||
dfreq = loopfilter_b[0] * perr + loopfilter_b[1] * loopfilter_x1
|
||||
loopfilter_x1 = perr
|
||||
|
||||
if i + 1 < n:
|
||||
freq[i+1] = min(maxfreq, max(minfreq, freq[i] - dfreq))
|
||||
p = phase[i] + freq[i+1]
|
||||
if p > 2 * numpy.pi: p -= 2 * numpy.pi
|
||||
if p < -2 * numpy.pi: p += 2 * numpy.pi
|
||||
phase[i+1] = p
|
||||
|
||||
return y, phasei, phaseq, phaseerr, freq, phase
|
||||
|
||||
|
Loading…
Reference in New Issue