Improvements for RPi, new options

testing
Martin Ewing 2014-05-17 20:33:52 -04:00
rodzic 37f36cfc86
commit 1e18d4b6e4
10 zmienionych plików z 274 dodań i 153 usunięć

Wyświetl plik

@ -1,5 +1,5 @@
#!/usr/bin/env ipython
# FFT timing benchmarks (requires ipython package)
# FFT timing benchmarks (requires ipython and numpy packages)
import math
import numpy as np

148
iq.py
Wyświetl plik

@ -1,7 +1,7 @@
#!/usr/bin/env python
# Program iq.py - spectrum displays from quadrature sampled IF data.
# Copyright (C) 2013 Martin Ewing
# Copyright (C) 2013-2014 Martin Ewing
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -27,8 +27,14 @@
# underlying C/C++ libraries PortAudio, SDL, and rtl-sdr.
#
# TO DO:
# Document sources of non-std modules
# HISTORY
# 01-04-2014 Initial release (QST article 4/2014)
# 05-17-2014 Improvements for RPi timing, etc.
# Add REV, skip, sp_max/min, v_max/min options
# Note for directfb use (i.e. without X11/Xorg):
# User must be a member of the following Linux groups:
# adm dialout audio video input (plus user's own group, e.g., pi)
import sys,time, threading, os, subprocess
import pygame as pg
@ -68,10 +74,11 @@ opt = options.opt # Get option object from options module
print "identification:", opt.ident
print "source :", opt.source
print "waterfall :", opt.waterfall
print "rev i/q :", opt.rev_iq
print "sample rate :", opt.sample_rate
print "size :", opt.size
print "buffers :", opt.buffers
print "taking :", opt.taking
print "skipping :", opt.skip
print "hamlib :", opt.hamlib
print "hamlib rigtype:", opt.hamlib_rigtype
print "hamlib device :", opt.hamlib_device
@ -83,7 +90,9 @@ print "hamlib intvl :", opt.hamlib_interval
print "cpu load intvl:", opt.cpu_load_interval
print "wf accum. :", opt.waterfall_accumulation
print "wf palette :", opt.waterfall_palette
print "max queue dept:", opt.max_queue
print "sp_min, max :", opt.sp_min, opt.sp_max
print "v_min, max :", opt.v_min, opt.v_max
#print "max queue dept:", opt.max_queue
print "PCM290x lagfix:", opt.lagfix
if opt.lcd4:
print "LCD4 brightnes:", opt.lcd4_brightness
@ -113,7 +122,6 @@ class LED(object):
"""
self.surface = pg.Surface((width, width))
self.wd2 = width/2
#self.colors = colors
return
def get_LED_surface(self, color):
@ -132,12 +140,13 @@ class LED(object):
class Graticule(object):
""" Create a pygame surface with freq / power (dB) grid
and units.
input: options, pg font, graticule height, width, line color, and text color
input: options, pg font, graticule height, width, line color,
and text color
"""
def __init__(self, opt, font, h, w, color_l, color_t):
self.opt = opt
self.sp_max = -20 # default max value (dB)
self.sp_min = -120 # default min value
self.sp_max = opt.sp_max #-20 # default max value (dB)
self.sp_min = opt.sp_min #-120 # default min value
self.font = font # font to use for text
self.h = h # height of graph area
self.w = w # width
@ -251,7 +260,7 @@ if opt.lcd4: # setup for directfb (non-X) graphics
SCREEN_SIZE = (480,272) # default size for the 4" LCD (480x272)
SCREEN_MODE = pg.FULLSCREEN
# If we are root, we can set up LCD4 brightness.
brightness = str(min(100, max(0, opt.lcd4_brightness))) # validated string
brightness = str(min(100, max(0, opt.lcd4_brightness))) # validated string
# Find path of script (same directory as iq.py) and append brightness value
cmd = os.path.join( os.path.split(sys.argv[0])[0], "lcd4_brightness.sh") \
+ " %s" % brightness
@ -278,7 +287,7 @@ w_middle = w_spectra/2 # mid point of spectrum
x_spectra = (w_main-w_spectra) / 2.0 # x coord. of spectrum on screen
h_2d = 2*SCREEN_SIZE[1]/3 if opt.waterfall \
else SCREEN_SIZE[1] # height of 2d spectrum display
else SCREEN_SIZE[1] # height of 2d spectrum display
h_2d -= 25 # compensate for LCD4 overscan?
y_2d = 20. # y position of 2d disp. (screen top = 0)
@ -288,22 +297,11 @@ if opt.size > w_spectra:
for n in [1024, 512, 256, 128]:
if n <= w_spectra:
print "*** Size was reset from %d to %d." % (opt.size, n)
opt.size = n # Force size to be 2**k (ok, but may not be best choice)
opt.size = n # Force size to be 2**k (ok, reasonable choice?)
break
chunk_size = opt.buffers * opt.size # No. samples per chunk (pyaudio callback)
chunk_time = float(chunk_size) / opt.sample_rate
# Initialize input mode, RTL or AF
if opt.source=="rtl": # input from RTL dongle
import iq_rtl as rtl
dataIn = rtl.RTL_In(opt)
elif opt.source=='audio': # input from audio card
import iq_af as af
dataIn = af.DataInput(opt)
else:
print "unrecognized mode"
quit_all()
myDSP = dsp.DSP(opt) # Establish DSP logic
# Surface for the 2d spectrum
@ -315,17 +313,17 @@ led_urun = LED(10)
led_clip = LED(10)
# Waterfall geometry
h_wf = SCREEN_SIZE[1]/3 # Height of waterfall (3d spectrum)
h_wf = SCREEN_SIZE[1]/3 # Height of waterfall (3d spectrum)
y_wf = y_2d + h_2d # Position just below 2d surface
# Surface for waterfall (3d) spectrum
surf_wf = pg.Surface((w_spectra, h_wf))
pg.display.set_caption(opt.ident) # Title for main window
pg.display.set_caption(opt.ident) # Title for main window
# Establish fonts for screen text.
lgfont = pg.font.SysFont('sans', 16)
lgfont_ht = lgfont.get_linesize() # text height
lgfont_ht = lgfont.get_linesize() # text height
medfont = pg.font.SysFont('sans', 12)
medfont_ht = medfont.get_linesize()
smfont = pg.font.SysFont('mono', 9)
@ -335,9 +333,8 @@ smfont_ht = smfont.get_linesize()
wf_pixel_size = (w_spectra/opt.size, h_wf/WF_LINES)
# min, max dB for wf palette
v_min = -120 # lower end (dB)
v_max = -20 # higher end
nsteps = 50 # number of distinct colors
v_min, v_max = opt.v_min, opt.v_max # lower/higher end (dB)
nsteps = 50 # number of distinct colors
if opt.waterfall:
# Instantiate the waterfall and palette data
@ -354,7 +351,8 @@ if opt.hamlib:
# Create thread for Hamlib freq. checking.
# Helps to even out the loop timing, maybe.
hl_thread = threading.Thread(target=updatefreq, args = (opt.hamlib_interval, rig))
hl_thread = threading.Thread(target=updatefreq,
args = (opt.hamlib_interval, rig))
hl_thread.daemon = True
hl_thread.start()
print "Hamlib thread started."
@ -369,7 +367,7 @@ print "CPU monitor thread started."
# Create graticule providing 2d graph calibration.
mygraticule = Graticule(opt, smfont, h_2d, w_spectra, GRAT_COLOR, GRAT_COLOR_2)
sp_min, sp_max = sp_min_def, sp_max_def = -120, -20
sp_min, sp_max = sp_min_def, sp_max_def = opt.sp_min, opt.sp_max
mygraticule.set_range(sp_min, sp_max)
surf_2d_graticule = mygraticule.make()
@ -380,9 +378,23 @@ parms_msg = "Fs = %d Hz; Res. = %.1f Hz;" \
(opt.sample_rate, float(opt.sample_rate)/opt.size, opt.size, w_spectra,
float(opt.size*opt.buffers)/opt.sample_rate)
wparms, hparms = medfont.size(parms_msg)
parms_matter = pg.Surface((wparms, hparms) )#, flags=pg.SRCALPHA)
parms_matter = pg.Surface((wparms, hparms) )
parms_matter.blit(medfont.render(parms_msg, 1, TCOLOR2), (0,0))
print "Update interval = %.2f ms" % float(1000*chunk_time)
# Initialize input mode, RTL or AF
# This starts the input stream, so place it close to start of main loop.
if opt.source=="rtl": # input from RTL dongle
import iq_rtl as rtl
dataIn = rtl.RTL_In(opt)
elif opt.source=='audio': # input from audio card
import iq_af as af
mainqueueLock = af.queueLock # queue and lock only for soundcard
dataIn = af.DataInput(opt)
else:
print "unrecognized mode"
quit_all()
# ** MAIN PROGRAM LOOP **
@ -394,14 +406,14 @@ t_last_data = 0.
nframe = 0
t_frame0 = time.time()
led_overflow_ct = 0
print "Update interval = %.2f ms" % float(1000*chunk_time)
startqueue = True
while True:
nframe += 1 # keep track of loops for possible bookkeeping
nframe += 1 # keep track of loop count FWIW
# Each time through the main loop, we reconstruct the main screen
surf_main.fill(BGCOLOR) # Erase with background color
surf_main.fill(BGCOLOR) # Erase with background color
# Each time through this loop, we receive an audio chunk, containing
# multiple buffers. The buffers have been transformed and the log power
@ -409,8 +421,6 @@ while True:
# plotted in the "2d" graph area. After a number of log spectra are
# displayed in the "2d" graph, a new line of the waterfall is generated.
#surf_main.blit(top_matter, (10,10)) # static operating info
# Line of text with receiver center freq. if available
if opt.hamlib:
msg = "%.3f kHz" % rigfreq # take current rigfreq from hamlib thread
@ -444,33 +454,35 @@ while True:
surf_main.blit(medfont.render(msg, 1, BLACK, BGCOLOR), (25, y_2d-hh))
surf_main.blit(sled, (10, y_2d-hh))
if opt.source=='rtl': # Input from RTL-SDR dongle
if opt.source=='rtl': # Input from RTL-SDR dongle
iq_data_cmplx = dataIn.ReadSamples(chunk_size)
if opt.rev_iq: # reverse spectrum?
iq_data_cmplx = np.imag(iq_data_cmplx)+1j*np.real(iq_data_cmplx)
time.sleep(0.05) # slow down if fast PC
stats = [ 0, 0] # for now...
else: # Input from audio card
# In its separate thread, a chunk of audio data has accumulated.
# When ready, pull log power spectrum data out of queue.
while dataIn.dataqueue.qsize() < 2:
time.sleep(0.1 * chunk_time )
my_in_data_s = dataIn.dataqueue.get(True, 2.0) # block w/timeout
dataIn.dataqueue.task_done()
my_in_data_s = dataIn.get_queued_data() # timeout protected
# Convert string of 16-bit I,Q samples to complex floating
iq_local = np.fromstring(my_in_data_s,dtype=np.int16).astype('float32')
re_d = np.array(iq_local[1::2]) # right input (I)
im_d = np.array(iq_local[0::2]) # left input (Q)
re_d = np.array(iq_local[1::2]) # right input (I)
im_d = np.array(iq_local[0::2]) # left input (Q)
# The PCM290x chip has 1 lag offset of R wrt L channel. Fix, if needed.
if opt.lagfix:
im_d = np.roll(im_d, 1)
# Get some stats (max values) to monitor gain settings, etc.
stats = [int(np.amax(re_d)), int(np.amax(im_d))]
iq_data_cmplx = np.array(re_d + im_d*1j)
if opt.rev_iq: # reverse spectrum?
iq_data_cmplx = np.array(im_d + re_d*1j)
else: # normal spectrum
iq_data_cmplx = np.array(re_d + im_d*1j)
sp_log = myDSP.GetLogPowerSpectrum(iq_data_cmplx)
if opt.source=='rtl': # Boost rtl spectrum (arbitrary amount)
sp_log += 60 # RTL data were normalized to +/- 1.
if opt.source=='rtl': # Boost rtl spectrum (arbitrary amount)
sp_log += 60 # RTL data were normalized to +/- 1.
yscale = float(h_2d)/(sp_max-sp_min) # yscale is screen units per dB
# Set the 2d surface to background/graticule.
@ -499,7 +511,8 @@ while True:
# This takes cpu time, so don't recompute it too often. (DSP & graphics
# are still running.)
info_counter = ( info_counter + 1 ) % INFO_CYCLE
if info_counter == 1: # First time through, and every INFO_CYCLE-th time thereafter.
if info_counter == 1:
# First time through, and every INFO_CYCLE-th time thereafter.
# Some button labels to show at right of LCD4 window
# Add labels for LCD4 buttons.
place_buttons = False
@ -515,11 +528,11 @@ while True:
# Info comes in 4 phases (0 - 3), cycle among them with <return>
if info_phase == 1:
lines = [ "KEYBOARD CONTROLS:",
"(R) Reset display; (Q) Quit program",
"Change upper plot dB limit: (U) increase; (u) decrease",
"Change lower plot dB limit: (L) increase; (l) decrease",
"Change WF palette upper limit: (B) increase; (b) decrease",
"Change WF palette lower limit: (D) increase; (d) decrease" ]
"(R) Reset display; (Q) Quit program",
"Change upper plot dB limit: (U) increase; (u) decrease",
"Change lower plot dB limit: (L) increase; (l) decrease",
"Change WF palette upper limit: (B) increase; (b) decrease",
"Change WF palette lower limit: (D) increase; (d) decrease" ]
if opt.source=='rtl' or opt.hamlib:
lines.append("Change rcvr freq: (rt arrow) increase; (lt arrow) decrease")
lines.append(" Use SHIFT for bigger steps")
@ -544,7 +557,7 @@ while True:
wh = (0, 0)
for il in lines: # Find max line width, height
wh = map(max, wh, medfont.size(il))
help_matter = pg.Surface((wh[0]+24, len(lines)*wh[1]+15) )#, flags=pg.SRCALPHA)
help_matter = pg.Surface((wh[0]+24, len(lines)*wh[1]+15) )
for ix,x in enumerate(lines):
help_matter.blit(medfont.render(x, 1, TCOLOR2), (20,ix*wh[1]+15))
@ -574,11 +587,15 @@ while True:
surf_main.blit(live_surface,(20,SCREEN_SIZE[1]-60))
# Check for pygame events - keyboard, etc.
# Note: A key press is not recorded as a PyGame event if you are
# connecting via SSH. In that case, use --sp_min/max and --v_min/max
# command line options to set scales.
for event in pg.event.get():
if event.type == pg.QUIT:
quit_all()
elif event.type == pg.KEYDOWN:
if info_phase <= 1: # Normal operation (0) or help phase 1 (1)
if info_phase <= 1: # Normal op. (0) or help phase 1 (1)
# We usually want left or right shift treated the same!
shifted = event.mod & (pg.KMOD_LSHIFT | pg.KMOD_RSHIFT)
if event.key == pg.K_q:
@ -624,8 +641,8 @@ while True:
if opt.waterfall:
v_min, v_max = mywf.reset_range()
# Note that LCD peripheral buttons are Right, Left, Up, Down arrows
# and "Enter". (Same as keyboard buttons)
# Note that LCD peripheral buttons are Right, Left, Up, Down
# arrows and "Enter". (Same as keyboard buttons)
elif event.key == pg.K_RIGHT: # right arrow + freq
if opt.source=='rtl':
@ -652,13 +669,13 @@ while True:
elif event.key == pg.K_DOWN:
print "Down"
elif event.key == pg.K_RETURN:
info_phase += 1 # Jump to phase 1 or phase 2 overlay
info_counter = 0 # (next time)
info_phase += 1 # Jump to phase 1 or 2 overlay
info_counter = 0 # (next time)
# We can have an alternate set of keyboard (LCD button) responses for each
# "phase" of the on-screen help system.
# We can have an alternate set of keyboard (LCD button) responses
# for each "phase" of the on-screen help system.
elif info_phase == 2: # Listen for info phase 2 keys
elif info_phase == 2: # Listen for info phase 2 keys
# Showing 2d spectrum gain/offset adjustments
# Note: making graticule is moderately slow.
# Do not repeat range changes too quickly!
@ -684,10 +701,10 @@ while True:
surf_2d_graticule = mygraticule.make()
elif event.key == pg.K_RETURN:
info_phase = 3 if opt.waterfall \
else 0 # Next is phase 3 unless no WF.
else 0 # Next is phase 3 unless no WF.
info_counter = 0
elif info_phase == 3: # Listen for info phase 3 keys
elif info_phase == 3: # Listen for info phase 3 keys
# Showing waterfall pallette adjustments
# Note: recalculating palette is quite slow.
# Do not repeat range changes too quickly! (1 per second max?)
@ -710,7 +727,6 @@ while True:
elif event.key == pg.K_RETURN:
info_phase = 0 # Turn OFF overlay
info_counter = 0
# Finally, update display for user
pg.display.update()

134
iq_af.py
Wyświetl plik

@ -1,7 +1,7 @@
#!/usr/bin/env python
# Program iq_af.py - manage I/Q audio from soundcard using pyaudio
# Copyright (C) 2013 Martin Ewing
# Copyright (C) 2013-2014 Martin Ewing
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -21,29 +21,73 @@
# Part of the iq.py program.
#
# HISTORY
# 01-04-2014 Initial release (QST article)
# 05-17-2014 timing improvements, esp for Raspberry Pi, etc.
# implement 'skip'
import sys, time, threading
import Queue
import pyaudio as pa
# Global variables (in this module's namespace!)
# globals are required to communicate with callback thread.
led_underrun_ct = 0 # buffer underrun LED
cbcount = 0
cbqueue = None # will be queue to transmit af data
# CALLBACK ROUTINE
# pyaudio callback routine is called when in_data buffer is ready.
# See pyaudio and portaudio documentation for details.
# Callback may not be called at a uniform rate.
def pa_callback_iqin(in_data, f_c, time_info, status):
global cbcount, cbqueue
global led_underrun_ct
# "skip = N" means "discard every (N+1)th buffer" (N > 0) or
# "only use every (-N+1)th buffer" (N < 0)
# i.e. skip=2 -> discard every 3rd buffer;
# skip=-2 -> use every 3rd buffer.
# (skip=1 and skip=-1 have same effect!)
# skip=0 means take all data.
# Global variables (in this module's namespace!)
# globals are required to communicate with callback thread.
led_underrun_ct = 0 # buffer underrun LED
cbcount = 0
MAXQUEUELEN = 32 # Don't use iq-opt for this?
cbqueue = Queue.Queue(MAXQUEUELEN) # will be queue to transmit af data
cbskip_ct = 0
queueLock = threading.Lock() # protect queue accesses
cbfirst = 1 # Skip this many buffers at start
#err_status = None
#err_time_info = None
def pa_callback_iqin(in_data, f_c, time_info, status):
global cbcount, cbqueue, cbskip, cbskip_ct
#global err_status, err_time_info
global led_underrun_ct, queueLock, cbfirst
#err_status = status # for debugging in case of hangup
#err_time_info = time_info
cbcount += 1
if status == pa.paAbort:
led_underrun_ct = 1 # signal LED "underrun"
if status == pa.paInputOverflow:
led_underrun_ct = 1 # signal LED "underrun" (really, overflow)
# Decide if we should skip this buffer or take it.
# First, are we dropping every Nth buffer?
if cbskip > 0: # Yes, we must check cbskip_ct
if cbskip_ct >= cbskip:
cbskip_ct = 0
return (None, pa.paContinue) # Discard this buffer
else:
cbskip_ct += 1 # OK to process buffer
# Or, are we accepting every Nth buffer?
if cbskip < 0:
if cbskip_ct >= -cbskip:
cbskip_ct = 0 # OK to process buffer
else:
cbskip_ct += 1
return (None, pa.paContinue) # Discard this buffer
# Having decided to take the current buffer, or cbskip==0,
# send it to main thread.
if cbfirst > 0:
cbfirst -= 1
return (None, pa.paContinue) # Toss out first N data
try:
cbqueue.put_nowait(in_data) # send to queue for iq main to pick up
queueLock.acquire()
cbqueue.put_nowait(in_data) # queue should sync with main thread
queueLock.release()
except Queue.Full:
print "ERROR: Internal queue is filled. Reconfigure to use less CPU."
print "\n\n (Ignore remaining errors!)"
@ -52,33 +96,40 @@ def pa_callback_iqin(in_data, f_c, time_info, status):
# END OF CALLBACK ROUTINE
class DataInput(object):
""" Set up audio input, optionally using callback mode.
""" Set up audio input with callbacks.
"""
def __init__(self, opt=None):
global cbqueue
self.opt = opt # command line options, as parsed.
# Initialize pyaudio (A python mapping of PortAudio)
# Consult pyaudio documentation.
self.audio = pa.PyAudio() # generates lots of warnings.
print
self.Restart(opt)
return
def Restart(self, opt): # Maybe restart after error?
global cbqueue, cbskip
cbskip = opt.skip
print
# set up stereo / 48K IQ input channel. Stream will be started.
if self.opt.index < 0: # Find pyaudio's idea of default index
if opt.index < 0: # Find pyaudio's idea of default index
defdevinfo = self.audio.get_default_input_device_info()
print "Default device index is %d; id='%s'"% (defdevinfo['index'], defdevinfo['name'])
print "Default device index is %d; id='%s'"% \
(defdevinfo['index'], defdevinfo['name'])
af_using_index = defdevinfo['index']
else:
af_using_index = opt.index # Use user's choice of index
devinfo = self.audio.get_device_info_by_index(af_using_index)
print "Using device index %d; id='%s'" % (devinfo['index'], devinfo['name'])
print "Using device index %d; id='%s'" % \
(devinfo['index'], devinfo['name'])
try:
# Verify this is a supported mode.
support = self.audio.is_format_supported(
input_format=pa.paInt16, # 16 bit samples
input_channels=2, # 2 channels
rate=self.opt.sample_rate, # typ. 48000
input_device=af_using_index) # maybe the default device?
rate=opt.sample_rate, # typ. 48000
input_device=af_using_index)
except ValueError as e:
print "ERROR self.audio.is_format_supported", e
sys.exit()
@ -86,27 +137,44 @@ class DataInput(object):
self.afiqstream = self.audio.open(
format=pa.paInt16, # 16 bit samples
channels=2, # 2 channels
rate=self.opt.sample_rate, # typ. 48000
frames_per_buffer= self.opt.buffers*opt.size,
input_device_index=af_using_index, # maybe the default device
input=True, # being used for input, not output
rate=opt.sample_rate, # typ. 48000
frames_per_buffer= opt.buffers * opt.size,
input_device_index=af_using_index,
input=True, # being used for input only
stream_callback=pa_callback_iqin )
self.dataqueue = Queue.Queue(opt.max_queue) # needs to be "big enough"
cbqueue = self.dataqueue
return
def get_queued_data(self):
timeout = 40
while cbqueue.qsize() < 4:
timeout -= 1
if timeout <= 0:
print "timeout waiting for queue to become non-empty!"
sys.exit()
time.sleep(.1)
queueLock.acquire()
data = cbqueue.get(True, 4.) # Why addnl timeout set?
queueLock.release()
return data
def CPU_load(self):
load = self.afiqstream.get_cpu_load()
return load
def isActive(self):
return self.afiqstream.is_active()
def Start(self): # Start pyaudio stream
self.afiqstream.start_stream()
return
def Stop(self): # Stop pyaudio stream
self.afiqstream.stop_stream()
return
def Terminate(self): # Stop and release all resources
def CloseStream(self):
self.afiqstream.stop_stream()
self.afiqstream.close()
def Terminate(self): # Stop and release all resources
self.audio.terminate()
if __name__ == '__main__':

Wyświetl plik

@ -1,7 +1,7 @@
#!/usr/bin/env python
# Program iq_dsp.py - Compute spectrum from I/Q data.
# Copyright (C) 2013 Martin Ewing
# Copyright (C) 2013-2014 Martin Ewing
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -20,6 +20,9 @@
#
# Part of the iq.py program.
# HISTORY
# 01-04-2014 Initial Release
import math, time
import numpy as np
import numpy.fft as fft
@ -41,10 +44,6 @@ class DSP(object):
def GetLogPowerSpectrum(self, data):
size = self.opt.size # size of FFT in I,Q samples.
power_spectrum = np.zeros(size)
if self.opt.taking > 0:
nbuf_taking = min(self.opt.taking, self.opt.buffers) # if need to shuck load
else:
nbuf_taking = self.opt.buffers # faster systems
# Time-domain analysis: Often we have long normal signals interrupted
# by huge wide-band pulses that degrade our power spectrum average.
@ -61,7 +60,7 @@ class DSP(object):
# Calculate our current threshold relative to measured median.
td_threshold = self.opt.pulse * td_median
nbuf_taken = 0 # Actual number of buffers accumulated
for ic in range(nbuf_taking):
for ic in range(self.opt.buffers):
td_segment = data[ic*size:(ic+1)*size]
td_max = np.amax(np.abs(td_segment)) # Do we have a noise pulse?
if td_max < td_threshold: # No, get pwr spectrum etc.

109
iq_opt.py
Wyświetl plik

@ -1,7 +1,7 @@
#!/usr/bin/env python
# Program iq_opt.py - Handle program options and command line parameters.
# Copyright (C) 2013 Martin Ewing
# Copyright (C) 2013-2014 Martin Ewing
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -20,10 +20,16 @@
#
# Part of the iq.py program.
# HISTORY
# 01-04-2014 Initial release
# 05-05-2014 Changed options
import optparse
# This module gets command-line options from the invocation of the main program,
# iq.py.
# This module handles command-line options.
# Note options changed:
# Add "skip", "REV", remove "RPI", "taking", "max_queue"
# Set up command line parser. (Use iq.py --help to see a formatted qlisting.)
op = optparse.OptionParser()
@ -37,10 +43,12 @@ op.add_option("--LAGFIX", action="store_true", dest="lagfix",
help="Special mode to fix PCM290x R/L offset.")
op.add_option("--LCD4", action="store_true", dest="lcd4",
help='Use 4" LCD instead of large screen')
op.add_option("--RPI", action="store_true", dest="device_rpi",
help="Set up some defaults for Raspberry Pi")
#op.add_option("--RPI", action="store_true", dest="device_rpi",
# help="Set up some defaults for Raspberry Pi")
op.add_option("--RTL", action="store_true", dest="source_rtl",
help="Set source to RTL-SDR")
op.add_option("--REV", action="store_true", dest="rev_iq",
help="Reverse I & Q to reverse spectrum display")
op.add_option("--WATERFALL", action="store_true", dest="waterfall",
help="Use waterfall display.")
@ -61,8 +69,9 @@ op.add_option("--index", action="store", type="int", dest="index",
"selects default input device.")
op.add_option("--lcd4_brightness", action="store", type="int", dest="lcd4_brightness",
help="LCD4 display brightness 0 - 100")
op.add_option("--max_queue", action="store", type="int", dest="max_queue",
help="Real-time queue depth")
# Now set as constant in iq_af.py
#op.add_option("--max_queue", action="store", type="int", dest="max_queue",
# help="Real-time queue depth")
op.add_option("--n_buffers", action="store", type="int", dest="buffers",
help="Number of FFT buffers in 'chunk', default 12")
op.add_option("--pulse_clip", action="store", type="int", dest="pulse",
@ -73,8 +82,19 @@ op.add_option("--rtl_gain", action="store", type="int", dest="rtl_gain",
help="RTL_SDR gain, default 0.")
op.add_option("--size", action="store", type="int", dest="size",
help="size of FFT. Default is 512.")
op.add_option("--take", action="store", type="int", dest="taking",
help="No. of buffers to take per chunk, must be <= buffers.")
#op.add_option("--take", action="store", type="int", dest="taking",
# help="No. of buffers to take per chunk, must be <= buffers.")
op.add_option("--skip", action="store", type="int", dest="skip",
help="Skipping input data parameter >= 0")
op.add_option("--sp_min", action="store", type="int", dest="sp_min",
help="spectrum level, low end, dB")
op.add_option("--sp_max", action="store", type="int", dest="sp_max",
help="spectrum level, hi end, dB")
op.add_option("--v_min", action="store", type="int", dest="v_min",
help="palette level, low end, dB")
op.add_option("--v_max", action="store", type="int", dest="v_max",
help="palette level, hi end, dB")
op.add_option("--waterfall_acc", action="store", type="int", dest="waterfall_accumulation",
help="No. of spectra per waterfall line")
op.add_option("--waterfall_palette", action="store", type="int", dest="waterfall_palette",
@ -82,54 +102,53 @@ op.add_option("--waterfall_palette", action="store", type="int", dest="waterfall
# The following are the default values which are used if not specified in the
# command line. You may want to edit them to be close to your normal operating needs.
DEF_SAMPLE_RATE = 48000
op.set_defaults(
buffers = 12, # no. buffers in sample chunk (RPi-40)
cpu_load_interval = 3.0, # cycle time for CPU monitor thread
device = None, # Possibly "BBB" or "RPI" (set up appropriately)
fullscreen = False, # Use full screen mode? (if not LCD4)
hamlib = False, # Using Hamlib? T/F (RPi-False)
hamlib_device = "/dev/ttyUSB0", # Device address for Hamlib I/O
hamlib_interval = 1.0, # Wait between hamlib freq. checks (secs)
hamlib_rigtype = 229, # Elecraft K3/KX3.
index = -1, # index of audio device (-1 use default)
lagfix = False, # Fix up PCM 290x bug
lcd4 = False, # default large screen
lcd4_brightness = 75, # brightness 0 - 100
max_queue = 30, # max depth of queue from audio callback
pulse = 10, # pulse clip threshold
rtl_frequency = 146.e6, # RTL center freq. Hz
rtl_gain = 0, # auto
sample_rate = 48000, # (stereo) frames/second (Hz) (RTL up to 2048000)
size = 384, # size of FFT --> freq. resolution (RPi-256)
source_rtl = False, # Use sound card, not RTL-SDR input
taking = -1, # 0 < taking < buffers to cut cpu load, -1=all
waterfall = False, # Using waterfall? T/F
waterfall_accumulation = 4, # No. of spectra per waterfall line
waterfall_palette = 2 # choose a waterfall color scheme
)
buffers = 12, # no. buffers in sample chunk (RPi-40)
cpu_load_interval = 3.0, # cycle time for CPU monitor thread
fullscreen = False, # Use full screen mode? (if not LCD4)
hamlib = False, # Using Hamlib? T/F (RPi-False)
hamlib_device = "/dev/ttyUSB0", # Device address for Hamlib I/O
hamlib_interval = 1.0, # Wait between hamlib freq. checks (secs)
hamlib_rigtype = 229, # Elecraft K3/KX3.
index = -1, # index of audio device (-1 use default)
lagfix = False, # Fix up PCM 290x bug
lcd4 = False, # default large screen
lcd4_brightness = 75, # brightness 0 - 100
# max_queue = 30, # max depth of queue from audio callback
pulse = 10, # pulse clip threshold
rev_iq = False, # Reverse I & Q
rtl_frequency = 146.e6, # RTL center freq. Hz
rtl_gain = 0, # auto
sample_rate = DEF_SAMPLE_RATE, # (stereo) frames/second (Hz)
size = 384, # size of FFT --> freq. resolution (RPi-256)
skip = 0, # if not =0, skip some input data
source_rtl = False, # Use sound card, not RTL-SDR input
sp_min =-120, # dB relative to clipping, at bottom of grid
sp_max =-20, # dB relative to clipping, at top of grid
v_min =-120, # palette starts at this level
v_max =-20, # palette ends at this level
# taking = -1, # 0 < taking < buffers to cut cpu load, -1=all
waterfall = False, # Using waterfall? T/F
waterfall_accumulation = 4, # No. of spectra per waterfall line
waterfall_palette = 2 # choose a waterfall color scheme
)
opt, args = op.parse_args()
# This is an "option" that the user can't change.
opt.ident = "IQ.PY v. 0.30 de AA6E"
opt.ident = "IQ.PY v. 0.40 de AA6E"
# --RTL option forces source=rtl, but normally source=audio
opt.source = "rtl" if opt.source_rtl else "audio"
if opt.device_rpi:
# adjust to comfortable settings for Raspberry Pi
opt.buffers = 15
opt.taking = 4 # reduce CPU load (to 4/15 of max.)
opt.size = 256
# Change default Freq for RTL to an appropriate (legal) value (tnx KF3EB)
# However, do not override user's --rate setting, if present.
if opt.source_rtl and (opt.sample_rate == DEF_SAMPLE_RATE):
opt.sample_rate = 1024000
# Main module will use: options.opt to pick up this 'opt' instance.
if __name__ == '__main__':
print 'debug'
# Print the variables in opt. Opt is a weird thing, not a dictionary.
#print dir(opt)
for x in dir(opt):
if x[0] != "_" and x.find("read_") < 0 and x != "ensure_value":
y = eval("opt."+x)
print x, "=", y, type(y)

Wyświetl plik

@ -1,7 +1,7 @@
#!/usr/bin/env python
# Program iq_rtl.py - Manage input from RTL_SDR dongle.
# Copyright (C) 2013 Martin Ewing
# Copyright (C) 2013-2014 Martin Ewing
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -20,6 +20,9 @@
#
# Part of the iq.py program.
# HISTORY
# 01-04-2014 Initial release
import rtlsdr
class RTL_In(object):

Wyświetl plik

@ -1,7 +1,7 @@
#!/usr/bin/env python
# Program iq_wf.py - Create waterfall spectrum display.
# Copyright (C) 2013 Martin Ewing
# Copyright (C) 2013-2014 Martin Ewing
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -20,6 +20,9 @@
#
# Part of the iq.py program.
# HISTORY
# 01-04-2014 Initial release
import pygame as pg
import numpy as np
import math, sys

Wyświetl plik

@ -1,4 +1,7 @@
#! /bin/bash
# Applies to BeagleBone Black with LCD4 or compatible display.
# Set LCD4 brightness 0-100 from command line.
# Insist on being root
if [[ $EUID -ne 0 ]]; then

2
pa.py
Wyświetl plik

@ -4,6 +4,8 @@
# This program prints out your system's audio input configuration as seen
# by pyaudio (PortAudio).
# Copyright 2013-2014 Martin Ewing
import pyaudio as pa
print """First, you will receive a number of ALSA warnings about unknown PCM cards, etc.

8
try.sh 100755
Wyświetl plik

@ -0,0 +1,8 @@
#!/bin/bash
# Audio test, Raspberry Pi, iMic soundcard, USB 1.1 ~85% cpu load
# Use 'nice -20 ...' when running at highest CPU utilization.
python iq.py --rate=48000 --size=384 --index=1 --skip=-1 --n_buffers=6 --WATERFALL --sp_min=-90 --sp_max=0 --v_min=-90 --v_max=0
# RTL Test, Raspberry Pi
#python iq.py --RTL --WATERFALL --rtl_gain=0 --n_buffers=12 --size=384 --REV