kopia lustrzana https://git.code.sf.net/p/tinypythonpanadapter/code
Improvements for RPi, new options
rodzic
37f36cfc86
commit
1e18d4b6e4
|
@ -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
|
||||
|
|
104
iq.py
104
iq.py
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -335,8 +333,7 @@ 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
|
||||
v_min, v_max = opt.v_min, opt.v_max # lower/higher end (dB)
|
||||
nsteps = 50 # number of distinct colors
|
||||
|
||||
if opt.waterfall:
|
||||
|
@ -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,13 +406,13 @@ 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
|
||||
|
||||
# Each time through this loop, we receive an audio chunk, containing
|
||||
|
@ -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
|
||||
|
@ -446,15 +456,14 @@ while True:
|
|||
|
||||
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')
|
||||
|
@ -466,6 +475,9 @@ while True:
|
|||
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))]
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -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,11 +669,11 @@ 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_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
|
||||
# Showing 2d spectrum gain/offset adjustments
|
||||
|
@ -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()
|
||||
|
||||
|
|
132
iq_af.py
132
iq_af.py
|
@ -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__':
|
||||
|
|
11
iq_dsp.py
11
iq_dsp.py
|
@ -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.
|
||||
|
|
69
iq_opt.py
69
iq_opt.py
|
@ -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,10 +102,10 @@ 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
|
||||
|
@ -95,14 +115,20 @@ op.set_defaults(
|
|||
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
|
||||
# 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 = 48000, # (stereo) frames/second (Hz) (RTL up to 2048000)
|
||||
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
|
||||
taking = -1, # 0 < taking < buffers to cut cpu load, -1=all
|
||||
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
|
||||
|
@ -111,25 +137,18 @@ op.set_defaults(
|
|||
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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
5
iq_wf.py
5
iq_wf.py
|
@ -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
|
||||
|
|
|
@ -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
2
pa.py
|
@ -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.
|
||||
|
|
|
@ -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
|
Ładowanie…
Reference in New Issue