Add Si570 LO support

si570 0.3.5
Martin Ewing 2014-06-05 21:00:01 -04:00
rodzic b6a8760fd4
commit 23dd8e2084
5 zmienionych plików z 405 dodań i 27 usunięć

70
iq.py
Wyświetl plik

@ -31,6 +31,9 @@
# 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
# 05-31-2014 Add Si570 freq control option (DDS chip provided in SoftRock, eg.)
# Note: Use of Si570 requires libusb-1.0 wrapper from
# https://pypi.python.org/pypi/libusb1/1.2.0
# Note for directfb use (i.e. without X11/Xorg):
# User must be a member of the following Linux groups:
@ -73,6 +76,7 @@ opt = options.opt # Get option object from options module
# print list of parameters to console.
print "identification:", opt.ident
print "source :", opt.source
print "freq control :", opt.control
print "waterfall :", opt.waterfall
print "rev i/q :", opt.rev_iq
print "sample rate :", opt.sample_rate
@ -82,8 +86,11 @@ print "skipping :", opt.skip
print "hamlib :", opt.hamlib
print "hamlib rigtype:", opt.hamlib_rigtype
print "hamlib device :", opt.hamlib_device
print "rtl frequency :", opt.rtl_frequency
print "rtl gain :", opt.rtl_gain
if opt.source=="rtl":
print "rtl frequency :", opt.rtl_frequency
print "rtl gain :", opt.rtl_gain
if opt.control=="si570":
print "si570 frequency :", opt.si570_frequency
print "pulse :", opt.pulse
print "fullscreen :", opt.fullscreen
print "hamlib intvl :", opt.hamlib_interval
@ -340,7 +347,9 @@ if opt.waterfall:
# Instantiate the waterfall and palette data
mywf = wf.Wf(opt, v_min, v_max, nsteps, wf_pixel_size)
if opt.hamlib:
if (opt.control == "si570") and opt.hamlib:
print "Warning: Hamlib requested with si570. Si570 wins! No Hamlib."
if opt.hamlib and (opt.control != "si570"):
import Hamlib
# start up Hamlib rig connection
Hamlib.rig_set_debug (Hamlib.RIG_DEBUG_NONE)
@ -385,7 +394,7 @@ 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
if opt.source=="rtl": # input from RTL dongle (and freq control)
import iq_rtl as rtl
dataIn = rtl.RTL_In(opt)
elif opt.source=='audio': # input from audio card
@ -396,6 +405,11 @@ else:
print "unrecognized mode"
quit_all()
if opt.control=="si570":
import si570control
mysi570 = si570control.Si570control()
mysi570.setFreq(opt.si570_frequency / 1000.) # Set starting freq.
# ** MAIN PROGRAM LOOP **
run_flag = True # set false to suspend for help screen etc.
@ -422,11 +436,17 @@ while True:
# displayed in the "2d" graph, a new line of the waterfall is generated.
# Line of text with receiver center freq. if available
if opt.hamlib:
showfreq = True
if opt.control == "si570":
msg = "%.3f kHz" % (mysi570.getFreqByValue() * 1000.) # freq/4 from Si570
elif opt.hamlib:
msg = "%.3f kHz" % rigfreq # take current rigfreq from hamlib thread
elif opt.source=='rtl':
elif opt.control=='rtl':
msg = "%.3f MHz" % (dataIn.rtl.get_center_freq()/1.e6)
if opt.hamlib or (opt.source=='rtl'):
else:
showfreq = False
if showfreq:
# Center it and blit just above 2d display
ww, hh = lgfont.size(msg)
surf_main.blit(lgfont.render(msg, 1, BLACK, BGCOLOR),
@ -533,7 +553,7 @@ while True:
"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:
if opt.control != "none":
lines.append("Change rcvr freq: (rt arrow) increase; (lt arrow) decrease")
lines.append(" Use SHIFT for bigger steps")
lines.append("RETURN - Cycle to next Help screen")
@ -645,31 +665,35 @@ while True:
# arrows and "Enter". (Same as keyboard buttons)
elif event.key == pg.K_RIGHT: # right arrow + freq
if opt.source=='rtl':
if opt.control == 'rtl':
finc = 100e3 if shifted else 10e3
dataIn.rtl.center_freq = dataIn.rtl.get_center_freq()+finc
else: # audio mode
if opt.hamlib:
finc = 1.0 if shifted else 0.1
rigfreq_request = rigfreq + finc
else:
print "Rt arrow ignored, no Hamlib"
elif opt.control == 'si570':
finc = 1.0 if shifted else 0.1
mysi570.setFreqByValue(mysi570.getFreqByValue() + finc*.001)
elif opt.hamlib:
finc = 1.0 if shifted else 0.1
rigfreq_request = rigfreq + finc
else:
print "Rt arrow ignored, no Hamlib"
elif event.key == pg.K_LEFT: # left arrow - freq
if opt.source=='rtl':
if opt.control == 'rtl':
finc = -100e3 if shifted else -10e3
dataIn.rtl.center_freq = dataIn.rtl.get_center_freq()+finc
else: # audio mode
if opt.hamlib:
finc = -1.0 if shifted else -0.1
rigfreq_request = rigfreq + finc
else:
print "Lt arrow ignored, no Hamlib"
elif opt.control == 'si570':
finc = -1.0 if shifted else -0.1
mysi570.setFreqByValue(mysi570.getFreqByValue() + finc*.001)
elif opt.hamlib:
finc = -1.0 if shifted else -0.1
rigfreq_request = rigfreq + finc
else:
print "Lt arrow ignored, no Hamlib"
elif event.key == pg.K_UP:
print "Up"
elif event.key == pg.K_DOWN:
print "Down"
elif event.key == pg.K_RETURN:
info_phase += 1 # Jump to phase 1 or 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

Wyświetl plik

@ -23,6 +23,7 @@
# HISTORY
# 01-04-2014 Initial release
# 05-05-2014 Changed options
# 05-31-2014 Si570 control (vs RTL control vs None [af])
import optparse
@ -30,6 +31,7 @@ import optparse
# Note options changed:
# Add "skip", "REV", remove "RPI", "taking", "max_queue"
# Add --SI570
# Set up command line parser. (Use iq.py --help to see a formatted qlisting.)
op = optparse.OptionParser()
@ -45,6 +47,8 @@ op.add_option("--LCD4", action="store_true", dest="lcd4",
help='Use 4" LCD instead of large screen')
op.add_option("--RTL", action="store_true", dest="source_rtl",
help="Set source to RTL-SDR")
op.add_option("--SI570", action="store_true", dest="control_si570",
help="Set freq control to Si570, not RTL or Hamlib")
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",
@ -75,6 +79,8 @@ op.add_option("--rtl_freq", action="store", type="float", dest="rtl_frequency",
help="Initial RTL operating frequency (float kHz)")
op.add_option("--rtl_gain", action="store", type="int", dest="rtl_gain",
help="RTL_SDR gain, default 0.")
op.add_option("--si570_frequency", action="store", type="float", dest="si570_frequency",
help="Si570 LO initial frequency, (float kHz)")
op.add_option("--size", action="store", type="int", dest="size",
help="size of FFT. Default is 512.")
op.add_option("--skip", action="store", type="int", dest="skip",
@ -98,6 +104,7 @@ op.add_option("--waterfall_palette", action="store", type="int", dest="waterfall
DEF_SAMPLE_RATE = 48000
op.set_defaults(
buffers = 12, # no. buffers in sample chunk (RPi-40)
control_si570 = False, # normally, talk to RTL or Hamlib for freq info
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)
@ -113,6 +120,7 @@ op.set_defaults(
rtl_frequency = 146.e6, # RTL center freq. Hz
rtl_gain = 0, # auto
sample_rate = DEF_SAMPLE_RATE, # (stereo) frames/second (Hz)
si570_frequency = 7050.0, # initial freq. for Si570 LO.
size = 384, # size of FFT --> freq. resolution
skip = 0, # if not =0, skip some input data
source_rtl = False, # Use sound card, not RTL-SDR input
@ -128,10 +136,21 @@ 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.35 de AA6E"
# --RTL option forces source=rtl, but normally source=audio
opt.source = "rtl" if opt.source_rtl else "audio"
# 'source' refers to signal source (RTL or audio sound card)
# 'control' refers to freq. readout/control (RTL, si570, or none)
opt.control = "none"
if opt.hamlib:
opt.control = "hamlib"
if opt.source_rtl:
opt.source = "rtl"
opt.control= "rtl"
else:
opt.source = "audio"
if opt.control_si570:
opt.control = "si570"
# Change default Freq for RTL to an appropriate (legal) value (tnx KF3EB)
# However, do not override user's --rate setting, if present.

296
si570control.py 100755
Wyświetl plik

@ -0,0 +1,296 @@
#!/usr/bin/env python
# Si570control class gives python access to the Si570 Digital
# Sythesizer via a USB connection.
# Copyright (C) 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 program. If not, see <http://www.gnu.org/licenses/>.
#
# Contact the author by e-mail: aa6e@arrl.net
# This is "middleware" that defines an API for general user programming
# of radio systems using the Si570 Programmable VCXO as an inexpensive
# digital VFO. These routines connect via USB to an ATtiny45 USB-to-I2C
# device, which is running the usbavrsi570 code from PE0FKO.
# Tested on SoftRock RxTx Ensemble that uses the ATtiny85 MPU chip and a
# SiLabs 570 ("CAC000141G / D1HOS144") chip
# [3.3v CMOS, 61 ppm stab., 10-160 MHz]
# partially based on a subset of operations.c from Andrew Nilsson VK6JBL
# Also, see http://www.silabs.com/Support%20Documents/TechnicalDocs/si570.pdf
# and https://code.google.com/p/usbavrsi570/
# require libusb-1.0 wrapper from https://pypi.python.org/pypi/libusb1/1.2.0
import libusb1, usb1
import math, sys
from sidefs import *
# flags used for most usb i/o
#input
UFLGS1 = libusb1.LIBUSB_TYPE_VENDOR | \
libusb1.LIBUSB_RECIPIENT_DEVICE | \
libusb1.LIBUSB_ENDPOINT_IN
#output
UFLGS2 = libusb1.LIBUSB_TYPE_VENDOR | \
libusb1.LIBUSB_RECIPIENT_DEVICE | \
libusb1.LIBUSB_ENDPOINT_OUT
# Note changes from operation.c:
# 1. method names have changed to make them more regular.
# 2. get/set freq always work with floating MHz of signal frequency =
# osc frequency / multiplier.
class Si570control(object):
def __init__(self, verbose=0, fXtal=114.285, multiplier=4, i2c=0x55,
vendor_id=0x16c0, product_id=0x05dc):
self.verbose = verbose
self.fXtal = fXtal
self.multiplier = multiplier
self.i2c = i2c
self.context = usb1.USBContext()
self.device = self.context.getByVendorIDAndProductID(vendor_id, product_id)
if self.verbose:
print "device", self.device
self.handle = self.device.open()
self.version = self.getVersion()
# get version numbers
def getVersion(self):
bb = bytearray(self.handle.controlRead
(UFLGS1,REQUEST_READ_VERSION, 0x0E00, 0, 2, 500))
if len(bb)==2:
ver = "%d.%d" % (bb[1], bb[0])
if self.verbose:
print "Version", ver
return ver
else:
if self.verbose:
print "Version Unknown."
return None
def enum_devices(self):
mydevices = self.context.getDeviceList()
print "n=",len(mydevices)
for i,x in enumerate(mydevices):
print "%2d %2d %d %3d %d %d %0.4x %0.4x" % (i,
x.getBusNumber(),
x.getDeviceAddress(),
x.getDeviceClass(),
x.getDeviceProtocol(),
x.getDeviceSpeed(),
x.getVendorID(),
x.getProductID() )
def getFreqByValue(self): # return osc freq / multiplier
bb = bytearray( self.handle.controlRead
(UFLGS1, REQUEST_READ_FREQUENCY, 0, 0, 4, 500) )
fint = ((bb[3] << 8 | bb[2]) << 8 | bb[1] ) << 8 | bb[0]
if len(bb) == 4:
ans = (float(fint)/(1<<21)) / self.multiplier
return ans
else:
return None
def getRegisters(self):
bb = bytearray( self.handle.controlRead
(UFLGS1, REQUEST_READ_REGISTERS, SI570_I2C_ADDR, 0, 6, 5000) )
if len(bb) > 0:
for i in range(6):
print "Register %d = %X (%d)" % ( i+7, bb[i], bb[i] )
def calculateFreq(self, s): # s is string, could be bytearray?
si = bytearray(s)
RFREQ_int = ((si[2] & 0xf0) >> 4) + ((si[1] & 0x3f) * 16)
RFREQ_frac = ((si[2] & 0xf ) << 24) + (si[3] << 16) + (si[4] << 8) + si[5]
RFREQ = RFREQ_int + (float(RFREQ_frac) / 268435456.0)
N1 = ((si[1] & 0xc0 ) >> 6) + ((si[0] & 0x1f) << 2)
HS_DIV = (si[0] & 0xE0) >> 5
fout = self.fXtal * RFREQ / ((N1 + 1) * HS_DIV_MAP[HS_DIV])
if self.verbose >= 2:
print "RFREQ = %f" % RFREQ
print "N1 = %d" % N1
print "HS_DIV = %d" % HS_DIV
print "nHS_DIV = %d" % HS_DIV_MAP[HS_DIV]
print "fout = %f" % fout
return fout # actual osc freq.
def getFreq(self): # return osc freq / multiplier
strg = self.handle.controlRead \
(UFLGS1, REQUEST_READ_REGISTERS, SI570_I2C_ADDR, 0, 6, 5000)
# keep strg for calculateFrequency, avoiding unicode issues.
bb = bytearray( strg )
if len(bb) > 0:
if self.verbose >= 2:
for i in range(6):
print "Register %d = %X (%d)" % ( i+7, bb[i], bb[i] )
return self.calculateFreq(strg) / self.multiplier
else:
return None
def getPTT(self):
bb = bytearray( self.handle.controlRead
(UFLGS1, REQUEST_READ_KEYS, 0, 0, 1, 5000) )
if bb[0] & 0x40:
return 1
else:
return 0
def getKeys(self):
bb = bytearray( self.handle.controlRead
(UFLGS1, REQUEST_READ_KEYS, 0, 0, 1, 5000) )
if bb[0] & 0x20: # CW_KEY_1 high: not pressed low: pressed
keys = 0
else:
keys = 1
if not (bb[0] & 0x02):
keys += 2 # CW_KEY_2 high: not pressed low: pressed
return keys # keys = 3 if both pressed
def setPTT(self, value):
bb = bytearray( self.handle.controlRead
(UFLGS1, REQUEST_SET_PTT, value, 0, 3, 5000) )
if self.verbose >= 2:
print "buffer=",bb[0],bb[1],bb[2]
def calcDividers(self, f): # Returns solution = [HS_DIV, N1, f0, RFREQ]
# Instead of solution structure, use simple list for each variable.
cHS_DIV = list()
cN1 = list()
cf0 = list()
cRFREQ = list()
for i in range(7,-1,-1): # Count down through the dividers
if HS_DIV_MAP[i] > 0:
cHS_DIV.append(i)
y = (SI570_DCO_HIGH + SI570_DCO_LOW) / (2 * f)
y = y / HS_DIV_MAP[i]
if y < 1.5:
y = 1.0
else:
y = 2 * round(y/2.0)
if y > 128:
y = 128
cN1.append( math.trunc(y) - 1 )
cf0.append( f * y * HS_DIV_MAP[i] )
else:
cHS_DIV.append(None) # dummy result
cN1.append(None) # another dummy
cf0.append( 1.0E16 )
imin = -1
fmin = 1.0E16
for i in range(8):
if (cf0[i] >= SI570_DCO_LOW) & (cf0[i] <= SI570_DCO_HIGH) :
if cf0[i] < fmin:
fmin = cf0[i]
imin = i
if imin >= 0:
solution = [ cHS_DIV[imin], cN1[imin], cf0[imin], cf0[imin]/self.fXtal ]
if (self.verbose >= 2):
print "Solution:"
print " HS_DIV = %d" % solution[0]
print " N1 = %d" % solution[1]
print " f0 = %f" % solution[2]
print " RFREQ = %f" % solution[3]
else:
solution = None # This is the error return
return solution
def setLongWord(self, v ): # v = int value; return bytearray(4)
iv = int(v) # be sure of int type
b = bytearray(4)
b[0] = iv & 0xff
b[1] = ((iv & 0xff00) >> 8) & 0xff
b[2] = ((iv & 0xff0000) >> 16) & 0xff
b[3] = ((iv & 0xff000000) >> 24) & 0xff
return b # NB bytearray, not long word!
def setFreq(self, frequency):
f = self.multiplier * frequency
value = 0x700 + self.i2c
index = 0
if self.verbose:
print "Setting Si570 Frequency by registers to: %f" % f
sHS_DIV, sN1, sf0, sRFREQ = self.calcDividers(f)
RFREQ_int = math.trunc(sRFREQ)
RFREQ_frac= int( round((sRFREQ - RFREQ_int) * 268435456) ) # check int ok
intbuf = self.setLongWord( RFREQ_int )
fracbuf = self.setLongWord( RFREQ_frac)
outbuf = bytearray(6)
outbuf[5] = fracbuf[0]
outbuf[4] = fracbuf[1]
outbuf[3] = fracbuf[2]
outbuf[2] = fracbuf[3] | ((intbuf[0] & 0xf) << 4)
outbuf[1] = RFREQ_int / 16 + ((sN1 & 0x3) << 6)
outbuf[0] = sN1/4 + (sHS_DIV << 5)
sout = str()
for x in outbuf:
sout += chr(x)
r = self.handle.controlWrite \
(UFLGS2, REQUEST_SET_FREQ, value, index, sout, 5000)
if r:
if self.verbose >= 2:
print "Set Freq Buffer",
print "%x %x" % (outbuf[0], outbuf[1])
else:
print "Failed writing frequency to device"
def setFreqByValue(self, frequency):
f = self.multiplier * frequency
value = 0x700 + self.i2c
index = 0
buf = self.setLongWord(round(f * 2097152.0))
if self.verbose:
print "Setting Si570 Frequency by value to: %f" % f
if self.verbose >= 2:
print "Set Freq Buffer: %x %x %x %x" % (buf[0], buf[1],
buf[2], buf[3])
sout = str()
for x in buf:
sout += chr(x)
r = self.handle.controlWrite \
(UFLGS2, REQUEST_SET_FREQ_BY_VALUE, value, index, sout, 5000)
if r:
if self.verbose >= 2:
print "Set Freq Buffer: %x %x %x %x" % (buf[0], buf[1],
buf[2], buf[3])
else:
print "Failed setting frequency"
# End of Si570 class
if __name__ == "__main__":
# debug code goes here
si = Si570control(verbose=0)
freq = si.getFreqByValue()
print "freq by value", freq
#si.getRegisters()
#f = si.getFreq()
#print "returned freq", f
#si.setFreq( 1.8)
#print "set freq check"
#si.getFreq()
print "SET FREQ BY VALUE"
si.setFreqByValue(7.5)
print "checking"
print si.getFreqByValue()
if False:
print "Calc. dividers [HS_DIV, N1, f0, RFREQ]"
a = si.calcDividers(28.0)
print a
print "Done."

36
sidefs.py 100755
Wyświetl plik

@ -0,0 +1,36 @@
# "DG8SAQ specific values"
SI570_I2C_ADDR = 0x55
SI570_DCO_HIGH = 5670.0
SI570_DCO_LOW = 4850.0
SI570_NOMINAL_XTALL_FREQ = 114.285
SI570_XTALL_DEVIATION_PPM = 2000
SI570_DEFAULT_STARTUP_FREQ = 56.32
REQUEST_READ_VERSION = 0x00
REQUEST_SET_DDRB = 0x01
REQUEST_SET_PORTB = 0x04
REQUEST_READ_EEPROM = 0x11
REQUEST_FILTERS = 0x17
REQUEST_SET_BPF_ADDRESS = 0x18
REQUEST_READ_BPF_ADDRESS = 0x19
REQUEST_SET_LPF_ADDRESS = 0x1A
REQUEST_READ_LPF_ADDRESS = 0x1B
REQUEST_SET_FREQ = 0x30
REQUEST_SET_MULTIPLY_LO = 0x31
REQUEST_SET_FREQ_BY_VALUE = 0x32
REQUEST_SET_XTALL_FREQ = 0x33
REQUEST_SET_STARTUP_FREQ = 0x34
REQUEST_READ_MULTIPLY_LO = 0x39
REQUEST_READ_FREQUENCY = 0x3A
REQUEST_READ_SMOOTH_TUNE_PPM= 0x3B
REQUEST_READ_STARTUP = 0x3C
REQUEST_READ_XTALL = 0x3D
REQUEST_READ_REGISTERS = 0x3F
#REQUEST_SET_SI570_ADDR = 0x41
REQUEST_SET_PTT = 0x50
REQUEST_READ_KEYS = 0x51
HS_DIV_MAP = [4, 5, 6, 7, -1, 9, -1, 11]

5
try.sh
Wyświetl plik

@ -9,7 +9,10 @@
#python iq.py --RTL --WATERFALL --rtl_gain=0 --n_buffers=12 --size=384 --REV
# Audio test, BBB, iMic, USB 2.0 ~90% cpu load
python iq.py --index=-1 --size=256 --n_buffers=6 --WATERFALL --HAMLIB
#python iq.py --index=-1 --size=256 --n_buffers=6 --WATERFALL --HAMLIB
# RTL Test, BBB ~95% cpu [ Set extra RTL delay in iq.py to zero]
#python iq.py --RTL --WATERFALL --n_buffers=10 --size=384
# Audio test, PC, Si570 / SoftRock
python iq.py --SI570 --WATERFALL --index=0 --size=512 --n_buffers=8 --rate=48000