Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e219a5b975 | ||
|
|
2a530e5d11 | ||
|
|
ceab3bed26 |
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
# This provides support for the SDR Micron TRX by David Fainitski, N7DDC
|
||||||
|
|
||||||
|
# On Windows this uses Python module ftd2xx. Install with pip.
|
||||||
|
# On Linux it uses ftdi1. Install python3-ftdi1 with your package manager.
|
||||||
|
|
||||||
|
import sys, wx, traceback, time
|
||||||
|
from quisk_hardware_model import Hardware as BaseHardware
|
||||||
|
|
||||||
|
DEBUG = 1 # print debug messages during operation
|
||||||
|
WRITE_LOG = 1 # write the data received from the MicronTRX during self.log_time to the file self.log_name
|
||||||
|
#WRITE_LOG = 2 # receive data from the file self.log_name instead of from USB
|
||||||
|
|
||||||
|
# https://github.com/Dfinitski/SDR-Micron
|
||||||
|
|
||||||
|
# Short Control Packet type "SCP" from PC to Micron TRX, 16 bytes, MSB first (big endian)
|
||||||
|
#
|
||||||
|
# (0 - 7) Preamble, seven 0x55 and 0xD5
|
||||||
|
# (8, 9, 10) "SCP"
|
||||||
|
#(11) RX0 enable, 0 - 1
|
||||||
|
#(12) RX1 enable, 0 - 1
|
||||||
|
#(13) BS0 enable, 0 - 1
|
||||||
|
#(14) TX0 enable, 0 - 1
|
||||||
|
#(15) CDC enable, 0 - 1
|
||||||
|
|
||||||
|
# Universal control packet type "UCP" from PC to TRX or from TRX to PC, 64 bytes, MSB first (big-endian)
|
||||||
|
#
|
||||||
|
# (0 - 7) Preamble, seven 0x55 and 0xD5
|
||||||
|
# (8, 9, 10) "UCP"
|
||||||
|
# (11) read only FW prefix MSB
|
||||||
|
# (12) read only FW prefix
|
||||||
|
# (13) read only FW prefix LSB
|
||||||
|
# (14) read only char '-'
|
||||||
|
# (15) Not used
|
||||||
|
# (16) Not used
|
||||||
|
# (17) Not used
|
||||||
|
# (18, 19, 20, 21) - 4 bytes RX0 frequency
|
||||||
|
# (22, 23, 24, 25) - 4 bytes RX1 frequency
|
||||||
|
# (26, 27, 28, 29) - 4 bytes BS0 frequency
|
||||||
|
# (30, 31, 32, 33) - 4 bytes TX0 frequency
|
||||||
|
# (34) - Attenuator, 0 - 31 (dB value)
|
||||||
|
# (35) - BS0 Rate
|
||||||
|
# 0 - 48 kHz,
|
||||||
|
# 1 - 96 kHz,
|
||||||
|
# 2 - 192 kHz,
|
||||||
|
# 3 - 384 kHz,
|
||||||
|
# 4 - 768 kHz,
|
||||||
|
# 5 - 1536 kHz
|
||||||
|
# (36) LNA enable
|
||||||
|
# (37) TX level 0 - 255
|
||||||
|
# (38) RX BPF bypass 0 - 1
|
||||||
|
# (39) BS0 mode
|
||||||
|
# 0 - 4096 buffered samples
|
||||||
|
# 1 - 8192 buffered samples
|
||||||
|
# 2 - 16384 buffered samples
|
||||||
|
# (40) Not used
|
||||||
|
# (41) Not used
|
||||||
|
# (42) read-only PTT state from device 0-1
|
||||||
|
# (43) Mic Boost, 0 - 1
|
||||||
|
# (44) TX LPF Bypass 0 - 1
|
||||||
|
# (45) PTT to device 1 - 0
|
||||||
|
# (46) CW mode on 0 - 1
|
||||||
|
# (47) CW key manipulation from PC to device 1 - 0
|
||||||
|
# (48) Read only PWR, Output power in Watts
|
||||||
|
# (49) Read only SWR x.x, from 1.0 to 9.9 as a natural number without decimal point
|
||||||
|
# (50) Key Speed 10 - 63 ppm (20 by default)
|
||||||
|
# (51) Paddle swap 0, 1
|
||||||
|
# (52) Key mode, 0 = straight, 01 = Mode A, 10 = Mode B
|
||||||
|
# (53) Key weight, 33 to 66, (50 by default)
|
||||||
|
# (54) Key auto spacing, 0, 1
|
||||||
|
# (55) ST Level, 0 - 255 sight tone level
|
||||||
|
# (56) ST Freq in tens Hertz, 40 - 100 (400 - 1000Hz), 70 by default
|
||||||
|
# (57) Not used
|
||||||
|
# (58) Delay ON, 8 - 255 ms (25 by default) - time for TX carrier delay is needed for transmitter preparing to TX
|
||||||
|
# (59) Hang out time in tens ms 0 - 255 (0 to 2550 msec) (250 ms by default) needed to keep transmitter ready after PTT
|
||||||
|
# (60) Read only CW key manipulation from device to PC
|
||||||
|
# (61) Read only FW2 - firmware version MSB
|
||||||
|
# (62) Read only FW1
|
||||||
|
# (63) Read only FW0 - firmware version LSB
|
||||||
|
|
||||||
|
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
|
||||||
|
################ Receivers SdrMicronTRX, The SDR Micron TRX project by David Fainitski.
|
||||||
|
## hardware_file_name Hardware file path, rfile
|
||||||
|
# This is the file that contains the control logic for each radio.
|
||||||
|
#hardware_file_name = 'microntrxpkg/quisk_hardware.py'
|
||||||
|
|
||||||
|
|
||||||
|
class Hardware(BaseHardware):
|
||||||
|
sample_rates = [48] #[48, 96, 192, 384, 768, 1536]
|
||||||
|
BS0_samples = {0:4096, 1:8192, 2:16384}
|
||||||
|
BS0_bytes = {0:4096 * 2, 1:8192 * 2, 2:16384 * 2} # Two bytes per sample
|
||||||
|
def __init__(self, app, conf):
|
||||||
|
BaseHardware.__init__(self, app, conf)
|
||||||
|
self.usb_windows = None
|
||||||
|
self.usb_linux = None
|
||||||
|
self.prefix = b'\x55\x55\x55\x55\x55\x55\x55\xD5'
|
||||||
|
self.read_data = bytearray()
|
||||||
|
self.tx_samples = b''
|
||||||
|
self.rf_gain_labels = ('RF 0', 'RF -10', 'RF -20', 'RF -30')
|
||||||
|
self.index = 1
|
||||||
|
self.old_vfo = 0
|
||||||
|
self.sdrmicron_clock = 76800000
|
||||||
|
self.sdrmicron_decim = 1600
|
||||||
|
self.bscope_data = bytearray()
|
||||||
|
self.have_version = False
|
||||||
|
self.frame_msg = ''
|
||||||
|
self.remaining_BS0 = -1
|
||||||
|
self.log_name = "microntrxpkg/microntrx_log.dat"
|
||||||
|
self.log_time = 5.0 # seconds of log data
|
||||||
|
self.log_time0 = None # starting time.time() to start log file
|
||||||
|
self.log_fp = None
|
||||||
|
self.pc_control = bytearray(64)
|
||||||
|
self.pc_control[0:8] = self.prefix
|
||||||
|
self.pc_control[8:11] = b'UCP'
|
||||||
|
self.got_defaults = 0
|
||||||
|
self.pc_enables = bytearray(16)
|
||||||
|
self.pc_enables[0:8] = self.prefix
|
||||||
|
self.pc_enables[8:11] = b'SCP'
|
||||||
|
self.fw_version = bytearray
|
||||||
|
|
||||||
|
if conf.fft_size_multiplier == 0:
|
||||||
|
conf.fft_size_multiplier = 3 # Set size needed by VarDecim
|
||||||
|
|
||||||
|
def open(self): # This method must return a string showing whether the open succeeded or failed.
|
||||||
|
if WRITE_LOG == 1:
|
||||||
|
self.log_fp = open(self.log_name, "wb")
|
||||||
|
elif WRITE_LOG == 2:
|
||||||
|
self.log_fp = open(self.log_name, "rb")
|
||||||
|
rx_bytes = 3 # rx_bytes is the number of bytes in each I or Q sample: 1, 2, 3, or 4
|
||||||
|
rx_endian = 1 # rx_endian is the order of bytes in the sample array: 0 == little endian; 1 == big endian
|
||||||
|
self.InitSamples(rx_bytes, rx_endian) # Initialize: read samples from this hardware file and send them to Quisk
|
||||||
|
self.InitTxSamples(2, 1, 48000) # Initialize Tx samples to send to the hardware
|
||||||
|
bs_bytes = 2
|
||||||
|
bs_endian = 1
|
||||||
|
#self.InitBscope(bs_bytes, bs_endian, self.sdrmicron_clock, self.BS1_samples[self.pc_control[40]]) # Initialize bandscope
|
||||||
|
if WRITE_LOG == 2:
|
||||||
|
return "Read data from %s" % self.log_name
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
return self.open_win()
|
||||||
|
else:
|
||||||
|
return self.open_linux()
|
||||||
|
|
||||||
|
def open_win(self):
|
||||||
|
try:
|
||||||
|
import ftd2xx as d2xx
|
||||||
|
except:
|
||||||
|
return 'The ftd2xx module is missing'
|
||||||
|
enum = d2xx.createDeviceInfoList() # quantity of FTDI devices
|
||||||
|
if enum == 0:
|
||||||
|
return 'There are no FTDI devices available'
|
||||||
|
for i in range(enum): # Searching and opening needed device
|
||||||
|
a = d2xx.getDeviceInfoDetail(i)
|
||||||
|
if DEBUG:
|
||||||
|
print ("%d: description %s, serial %s" % (i, a['description'], a['serial']))
|
||||||
|
if a['description']==b'SDR-Micron':
|
||||||
|
try:
|
||||||
|
usb = d2xx.openEx(a['serial'])
|
||||||
|
except:
|
||||||
|
return 'Device SDR-Micron could not be opened'
|
||||||
|
usb.setBitMode(255, 0) # reset
|
||||||
|
time.sleep(0.1)
|
||||||
|
usb.setBitMode(255, 64) # Configure FT2232H into Sync FIFO mode
|
||||||
|
usb.setTimeouts(100, 100) # read, write
|
||||||
|
usb.setLatencyTimer(2)
|
||||||
|
usb.setUSBParameters(32, 32) # in_tx_size, out_tx_size
|
||||||
|
time.sleep(1.5) # waiting for device initialisation
|
||||||
|
usb.read(usb.getQueueStatus()) # clean the usb data buffer
|
||||||
|
self.frame_msg = a['description'].decode('utf-8') + ' S/N - ' + a['serial'].decode('utf-8')
|
||||||
|
self.usb_windows = usb
|
||||||
|
return self.frame_msg
|
||||||
|
return 'Device SDR-Micron was not found'
|
||||||
|
|
||||||
|
def open_linux(self): # Not finished
|
||||||
|
try:
|
||||||
|
import ftdi1
|
||||||
|
except:
|
||||||
|
return 'The ftdi1 module is missing'
|
||||||
|
usb = ftdi1.new()
|
||||||
|
# Open Channel A (interface 1 in libftdi)
|
||||||
|
ftdi1.set_interface(usb, ftdi1.INTERFACE_A)
|
||||||
|
rc = ftdi1.usb_open(usb, 0x0403, 0x6014) # 0x6014 is the standard FT232H PID
|
||||||
|
if rc == 0:
|
||||||
|
ftdi1.usb_reset(usb)
|
||||||
|
ftdi1.set_usb_read_timout(usb, 1)
|
||||||
|
ftdi1.set_usb_write_timout(usb, 5)
|
||||||
|
ftdi1.set_latency_timer(usb, 2)
|
||||||
|
# Enable Sync FIFO mode
|
||||||
|
# 0xFF = pin mask, 0x40 = BITMODE_SYNCFF
|
||||||
|
ftdi1.set_bitmode(usb, 0xFF, ftdi1.BITMODE_SYNCFF)
|
||||||
|
if DEBUG:
|
||||||
|
print("Sync FIFO active via ftdi1.")
|
||||||
|
self.usb_linux = usb
|
||||||
|
return "Device SDR-Micron opened"
|
||||||
|
return 'Device SDR-Micron was not found'
|
||||||
|
|
||||||
|
def usb_read(self):
|
||||||
|
if WRITE_LOG == 2:
|
||||||
|
data = self.log_fp.read(120)
|
||||||
|
return data
|
||||||
|
elif self.usb_windows:
|
||||||
|
count = self.usb_windows.getQueueStatus() # Read all available bytes
|
||||||
|
if count > 0:
|
||||||
|
return self.usb_windows.read(count) # return Python bytes
|
||||||
|
elif self.usb_linux:
|
||||||
|
count, data = ftdi1.read_data(self.usb_linux, 1024)
|
||||||
|
if count > 0:
|
||||||
|
return data # data is Python bytes
|
||||||
|
elif count < 0 and DEBUG:
|
||||||
|
print("Error %d in usb_read", count)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def usb_write(self, data):
|
||||||
|
data = bytes(data) # Convert bytearray to bytes
|
||||||
|
if self.usb_windows:
|
||||||
|
try:
|
||||||
|
self.usb_windows.write(data)
|
||||||
|
except:
|
||||||
|
if DEBUG:
|
||||||
|
print('Error while usb_write')
|
||||||
|
elif self.usb_linux:
|
||||||
|
count = ftdi1.write_data(self.usb_linux, data)
|
||||||
|
if count != len(data) and DEBUG:
|
||||||
|
print("Failure in usb_write_data %d %d", count, len(data))
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.usb_windows:
|
||||||
|
self.usb_windows.setBitMode(255, 0) # reset
|
||||||
|
time.sleep(0.5)
|
||||||
|
self.usb_windows.close()
|
||||||
|
self.usb_windows = None
|
||||||
|
elif self.usb_linux:
|
||||||
|
ftdi1.usb_close(self.usb_linux)
|
||||||
|
ftdi1.free(self.usb_linux)
|
||||||
|
self.usb_linux = None
|
||||||
|
|
||||||
|
def OnButtonRfGain(self, event):
|
||||||
|
btn = event.GetEventObject()
|
||||||
|
n = btn.index
|
||||||
|
if n == 0:
|
||||||
|
self.pc_control[34] = 0
|
||||||
|
elif n == 1:
|
||||||
|
self.pc_control[34] = 10
|
||||||
|
elif n == 2:
|
||||||
|
self.pc_control[34] = 20
|
||||||
|
elif n == 3:
|
||||||
|
self.pc_control[34] = 30
|
||||||
|
if DEBUG:
|
||||||
|
print ('pc_control[34]', self.pc_control[34])
|
||||||
|
self.rx_control_upd()
|
||||||
|
|
||||||
|
def ChangeFrequency(self, tx_freq, vfo, source='', band='', event=None):
|
||||||
|
if DEBUG:
|
||||||
|
print('ChangeFrequency tx_freq', tx_freq, 'vfo', vfo)
|
||||||
|
if tx_freq and tx_freq > 0:
|
||||||
|
tx_freq = int(tx_freq - self.transverter_offset)
|
||||||
|
self.pc_control[30] = self.pc_control[18] = (tx_freq >> 24) & 0xFF # MSB
|
||||||
|
self.pc_control[31] = self.pc_control[19] = (tx_freq >> 16) & 0xFF
|
||||||
|
self.pc_control[32] = self.pc_control[20] = (tx_freq >> 8) & 0xFF
|
||||||
|
self.pc_control[33] = self.pc_control[21] = (tx_freq ) & 0xFF #LSB
|
||||||
|
self.rx_control_upd()
|
||||||
|
if vfo:
|
||||||
|
vfo = int(vfo - self.transverter_offset)
|
||||||
|
if vfo != self.old_vfo:
|
||||||
|
self.old_vfo = vfo
|
||||||
|
self.pc_control[18] = (vfo >> 24) & 0xFF # MSB
|
||||||
|
self.pc_control[19] = (vfo >> 16) & 0xFF
|
||||||
|
self.pc_control[20] = (vfo >> 8) & 0xFF
|
||||||
|
self.pc_control[21] = (vfo ) & 0xFF #LSB
|
||||||
|
self.rx_control_upd()
|
||||||
|
return tx_freq, vfo
|
||||||
|
|
||||||
|
def OnSpot(self, level):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def OnChangeRxTx(self, is_rx):
|
||||||
|
self.pc_control[45] = is_rx
|
||||||
|
self.rx_control_upd()
|
||||||
|
|
||||||
|
def VarDecimGetChoices(self): # Return a list/tuple of strings for the decimation control.
|
||||||
|
return list(map(str, self.sample_rates)) # convert integer to string
|
||||||
|
|
||||||
|
def VarDecimGetLabel(self): # return a text label for the control
|
||||||
|
return "Sample rate ksps"
|
||||||
|
|
||||||
|
def VarDecimGetIndex(self): # return the current index
|
||||||
|
return self.index
|
||||||
|
|
||||||
|
def VarDecimSet(self, index=None): # return sample rate
|
||||||
|
if index is None: # initial call to set the sample rate before the call to open()
|
||||||
|
rate = self.application.vardecim_set
|
||||||
|
try:
|
||||||
|
self.index = self.sample_rates.index(rate // 1000)
|
||||||
|
except:
|
||||||
|
self.index = 0
|
||||||
|
else:
|
||||||
|
self.index = index
|
||||||
|
self.pc_control[35] = self.index
|
||||||
|
rate = self.sample_rates[self.index] * 1000
|
||||||
|
if DEBUG:
|
||||||
|
print("varDecimSet index", self.index, "rate", rate)
|
||||||
|
self.rx_control_upd()
|
||||||
|
return rate
|
||||||
|
|
||||||
|
def VarDecimRange(self): # Return the lowest and highest sample rate.
|
||||||
|
#return (48000, 1536000)
|
||||||
|
return (48000, 48000)
|
||||||
|
|
||||||
|
def StartSamples(self): # called by the sound thread
|
||||||
|
self.pc_enables[11] = 1 # RX0 enable
|
||||||
|
#self.pc_enables[13] = 1 # BS0 enable
|
||||||
|
self.usb_write(self.pc_enables)
|
||||||
|
if DEBUG:
|
||||||
|
print ("Start samples")
|
||||||
|
|
||||||
|
def StopSamples(self): # called by the sound thread
|
||||||
|
self.pc_enables[11] = 0
|
||||||
|
self.pc_enables[13] = 0
|
||||||
|
self.usb_write(self.pc_enables)
|
||||||
|
if DEBUG:
|
||||||
|
print ("Stop Samples")
|
||||||
|
|
||||||
|
def rx_control_upd(self):
|
||||||
|
if self.got_defaults:
|
||||||
|
self.usb_write(self.pc_control)
|
||||||
|
|
||||||
|
def GetRxSamples(self): # Read from the MicronTRX. Called frequently from the sound thread.
|
||||||
|
# Send Tx samples to the hardware
|
||||||
|
self.tx_samples += self.GetTxSamples()
|
||||||
|
while len(self.tx_samples) >= 492:
|
||||||
|
data = self.prefix + b'TX0\x00\x00\x00\x00\x00' + self.tx_samples[0:492]
|
||||||
|
self.usb_write(data)
|
||||||
|
#print(data[0:24], len(data))
|
||||||
|
#i = data[16] << 8 | data[17]
|
||||||
|
#print ("%8d" % i)
|
||||||
|
self.tx_samples = self.tx_samples[492:]
|
||||||
|
# Read messages from USB
|
||||||
|
data = self.usb_read()
|
||||||
|
if data:
|
||||||
|
self.read_data += data
|
||||||
|
if DEBUG > 1:
|
||||||
|
print("Read %s bytes, buffer length %d" % (len(data), len(self.read_data)))
|
||||||
|
if WRITE_LOG == 1:
|
||||||
|
if self.log_fp:
|
||||||
|
if self.log_time0 is None:
|
||||||
|
self.log_time0 = time.time()
|
||||||
|
if time.time() - self.log_time0 < self.log_time:
|
||||||
|
self.log_fp.write(data)
|
||||||
|
else:
|
||||||
|
self.log_fp.close()
|
||||||
|
self.log_fp = None
|
||||||
|
while True:
|
||||||
|
start = self.read_data.find(self.prefix)
|
||||||
|
if start < 0: # No prefix
|
||||||
|
return
|
||||||
|
if start > 0:
|
||||||
|
del self.read_data[0:start]
|
||||||
|
if DEBUG:
|
||||||
|
print ("USB blocks lost sync")
|
||||||
|
if len(self.read_data) < 11:
|
||||||
|
return
|
||||||
|
block_type = self.read_data[8:11]
|
||||||
|
if block_type == b'BS0':
|
||||||
|
if len(self.read_data) < 508:
|
||||||
|
return
|
||||||
|
packet_number = self.read_data[11]
|
||||||
|
if packet_number == 0: # start of a block of data
|
||||||
|
self.remaining_BS0 = self.BS0_bytes[self.pc_control[39]]
|
||||||
|
self.bscope_data = self.read_data[16:508] # 492 bytes
|
||||||
|
self.remaining_BS0 -= 492
|
||||||
|
elif self.remaining_BS0 >= 492:
|
||||||
|
self.bscope_data += self.read_data[16:508] # 492 bytes
|
||||||
|
self.remaining_BS0 -= 492
|
||||||
|
elif self.remaining_BS0 <= 0:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.bscope_data += self.read_data[16:self.remaining_BS0 + 16]
|
||||||
|
self.remaining_BS0 = -1
|
||||||
|
# self.AddBscopeSamples(self.bscope_data)
|
||||||
|
if DEBUG > 1:
|
||||||
|
print("BS0 buffer length %d" % len(self.bscope_data))
|
||||||
|
del self.read_data[0:508]
|
||||||
|
elif block_type == b'CDM':
|
||||||
|
if len(self.read_data) < 180:
|
||||||
|
return
|
||||||
|
if DEBUG > 1:
|
||||||
|
print("CDM")
|
||||||
|
del self.read_data[0:180]
|
||||||
|
elif block_type == b'RX0':
|
||||||
|
if len(self.read_data) < 508:
|
||||||
|
return
|
||||||
|
if self.read_data[12]:
|
||||||
|
self.GotClip()
|
||||||
|
self.AddRxSamples(self.read_data[16:508])
|
||||||
|
if DEBUG > 1:
|
||||||
|
print("RX0")
|
||||||
|
del self.read_data[0:508]
|
||||||
|
elif block_type == b'RX1':
|
||||||
|
if len(self.read_data) < 508:
|
||||||
|
return
|
||||||
|
if DEBUG > 1:
|
||||||
|
print("RX1")
|
||||||
|
del self.read_data[0:508]
|
||||||
|
elif block_type == b'UCP':
|
||||||
|
if len(self.read_data) < 64:
|
||||||
|
return
|
||||||
|
if self.have_version is False:
|
||||||
|
self.have_version = True
|
||||||
|
self.fw_version = self.read_data[11:15] + self.read_data[61:64]
|
||||||
|
self.frame_msg += ' F/W version - ' + self.fw_version.decode('utf-8')
|
||||||
|
self.application.main_frame.SetConfigText(self.frame_msg)
|
||||||
|
self.pc_control[11:64] = self.read_data[11:64] # save UCP content with defaults
|
||||||
|
self.got_defaults = 1;
|
||||||
|
if DEBUG > 1:
|
||||||
|
print("UCP")
|
||||||
|
del self.read_data[0:64]
|
||||||
|
else:
|
||||||
|
del self.read_data[0:11]
|
||||||
|
if DEBUG:
|
||||||
|
print("Unknown USB block type")
|
||||||
+2
-2
@@ -73,7 +73,7 @@ static double modulation_index = 1.6; // For FM transmit, the modulation index
|
|||||||
static int is_vox = 0; // Is the VOX level exceeded?
|
static int is_vox = 0; // Is the VOX level exceeded?
|
||||||
static int vox_level = CLIP16; // VOX trigger level as a number 0 to CLIP16
|
static int vox_level = CLIP16; // VOX trigger level as a number 0 to CLIP16
|
||||||
static int timeVOX = 2000; // VOX hang time in milliseconds
|
static int timeVOX = 2000; // VOX hang time in milliseconds
|
||||||
static int tx_sample_rate = 48000; // Used for SoapySDR
|
static int tx_sample_rate = 48000; // Used for SoapySDR and Tx samples from Python
|
||||||
static int reverse_tx_sideband;
|
static int reverse_tx_sideband;
|
||||||
static int PsEnable = 0;
|
static int PsEnable = 0;
|
||||||
static int PsCal = 0;
|
static int PsCal = 0;
|
||||||
@@ -1305,7 +1305,7 @@ int quisk_process_microphone(int mic_sample_rate, complex double * cSamples, int
|
|||||||
; // hermes handles this itself
|
; // hermes handles this itself
|
||||||
else if (quisk_play_state == SOFTWARE_CWKEY)
|
else if (quisk_play_state == SOFTWARE_CWKEY)
|
||||||
serial_key_samples(cSamples, count);
|
serial_key_samples(cSamples, count);
|
||||||
if(quisk_pt_sample_write) { // Used for SoapySDR
|
if(quisk_pt_sample_write && key_down) { // Used for SoapySDR and samples from Python
|
||||||
// Interpolate the mic samples to the Tx sample rate
|
// Interpolate the mic samples to the Tx sample rate
|
||||||
//printf("Tx sample rate %i\n", tx_sample_rate);
|
//printf("Tx sample rate %i\n", tx_sample_rate);
|
||||||
switch (tx_sample_rate) {
|
switch (tx_sample_rate) {
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ int quiskTxHoldState; // hold Tx until the repeater frequency shift is complet
|
|||||||
int quisk_is_vna; // zero for normal program, one for the VNA program
|
int quisk_is_vna; // zero for normal program, one for the VNA program
|
||||||
static int py_sample_rx_bytes=2; // number of bytes in each I or Q sample: 1, 2, 3, or 4
|
static int py_sample_rx_bytes=2; // number of bytes in each I or Q sample: 1, 2, 3, or 4
|
||||||
static int py_sample_rx_endian; // order of sample array: 0 == little endian; 1 == big endian
|
static int py_sample_rx_endian; // order of sample array: 0 == little endian; 1 == big endian
|
||||||
|
static int py_sample_tx_bytes; // number of bytes in each I or Q sample: 2, 3, or 4
|
||||||
|
static int py_sample_tx_endian; // order of sample array: 0 == little endian; 1 == big endian
|
||||||
static int py_bscope_bytes;
|
static int py_bscope_bytes;
|
||||||
static int py_bscope_endian;
|
static int py_bscope_endian;
|
||||||
static int quisk_auto_notch; // auto notch control
|
static int quisk_auto_notch; // auto notch control
|
||||||
@@ -159,6 +161,8 @@ static int softrock_correct_active; // 0 No correction adjustment; 1 Manual adju
|
|||||||
|
|
||||||
static complex double PySampleBuf[SAMP_BUFFER_SIZE]; // buffer for samples returned from Python
|
static complex double PySampleBuf[SAMP_BUFFER_SIZE]; // buffer for samples returned from Python
|
||||||
static int PySampleCount; // count of samples in buffer
|
static int PySampleCount; // count of samples in buffer
|
||||||
|
static uint8_t PySampleTxBytes[SAMP_BUFFER_SIZE * 8]; // buffer for Tx sample bytes to send to the hardware
|
||||||
|
static int PySampleTxCount;
|
||||||
static bool quisk_tx_inhibit;
|
static bool quisk_tx_inhibit;
|
||||||
|
|
||||||
static int multirx_data_width; // width of graph data to return
|
static int multirx_data_width; // width of graph data to return
|
||||||
@@ -3018,6 +3022,17 @@ static PyObject * add_bscope_samples(PyObject * self, PyObject * args)
|
|||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject * get_tx_samples(PyObject * self, PyObject * args)
|
||||||
|
{
|
||||||
|
PyObject * by;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple (args, ""))
|
||||||
|
return NULL;
|
||||||
|
by = PyBytes_FromStringAndSize((const char *)PySampleTxBytes, PySampleTxCount);
|
||||||
|
PySampleTxCount = 0;
|
||||||
|
return by;
|
||||||
|
}
|
||||||
|
|
||||||
static void py_sample_start(void)
|
static void py_sample_start(void)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -3040,6 +3055,83 @@ static int py_sample_read(complex double * cSamples)
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int py_sample_tx_save(complex double * cSamples, int nSamples)
|
||||||
|
{ // These cSamples are 16-bit integers from the microphone
|
||||||
|
int i;
|
||||||
|
int16_t rr, ii;
|
||||||
|
uint8_t rrH, rrL, iiH, iiL;
|
||||||
|
|
||||||
|
if (PySampleTxCount + nSamples * 8 >= SAMP_BUFFER_SIZE * 8) {
|
||||||
|
QuiskPrintf ("py_sample_tx_save: buffer is too full\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (i = 0; i < nSamples; i++) {
|
||||||
|
rr = (int16_t)creal(cSamples[i]);
|
||||||
|
ii = (int16_t)cimag(cSamples[i]);
|
||||||
|
rrH = 0xFF & (rr >> 8);
|
||||||
|
rrL = 0xFF & rr;
|
||||||
|
iiH = 0xFF & (ii >> 8);
|
||||||
|
iiL = 0xFF & ii;
|
||||||
|
if (py_sample_tx_endian == 0) { // byte order of samples is little-endian
|
||||||
|
switch(py_sample_tx_bytes) {
|
||||||
|
case 2:
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiH;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiH;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // byte order of samples is big-endian
|
||||||
|
switch(py_sample_tx_bytes) {
|
||||||
|
case 2:
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiL;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = rrL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiH;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = iiL;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
PySampleTxBytes[PySampleTxCount++] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject * get_params(PyObject * self, PyObject * args)
|
static PyObject * get_params(PyObject * self, PyObject * args)
|
||||||
{
|
{
|
||||||
const char * name;
|
const char * name;
|
||||||
@@ -3093,18 +3185,22 @@ static PyObject * set_params(PyObject * self, PyObject * args, PyObject * keywds
|
|||||||
{ /* Call with keyword arguments ONLY; change local parameters */
|
{ /* Call with keyword arguments ONLY; change local parameters */
|
||||||
static char * kwlist[] = {"quisk_is_vna", "rx_bytes", "rx_endian", "read_error", "clip",
|
static char * kwlist[] = {"quisk_is_vna", "rx_bytes", "rx_endian", "read_error", "clip",
|
||||||
"bscope_bytes", "bscope_endian", "bscope_size", "bandscopeScale", "hermes_pause",
|
"bscope_bytes", "bscope_endian", "bscope_size", "bandscopeScale", "hermes_pause",
|
||||||
|
"tx_bytes", "tx_endian",
|
||||||
NULL} ;
|
NULL} ;
|
||||||
int i, nbytes, read_error, clip, bscope_size, hermes_pause;
|
int i, nbytes, read_error, clip, bscope_size, hermes_pause;
|
||||||
|
|
||||||
nbytes = read_error = clip = bscope_size = hermes_pause = -1;
|
nbytes = read_error = clip = bscope_size = hermes_pause = -1;
|
||||||
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiiidii", kwlist,
|
if (!PyArg_ParseTupleAndKeywords (args, keywds, "|iiiiiiiidiii", kwlist,
|
||||||
&quisk_is_vna, &nbytes, &py_sample_rx_endian, &read_error, &clip,
|
&quisk_is_vna, &nbytes, &py_sample_rx_endian, &read_error, &clip,
|
||||||
&py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause))
|
&py_bscope_bytes, &py_bscope_endian, &bscope_size, &bandscopeScale, &hermes_pause,
|
||||||
|
&py_sample_tx_bytes, &py_sample_tx_endian))
|
||||||
return NULL;
|
return NULL;
|
||||||
if (nbytes != -1) {
|
if (nbytes != -1) {
|
||||||
py_sample_rx_bytes = nbytes;
|
py_sample_rx_bytes = nbytes;
|
||||||
quisk_sample_source4(py_sample_start, py_sample_stop, py_sample_read, NULL);
|
quisk_sample_source(py_sample_start, py_sample_stop, py_sample_read);
|
||||||
}
|
}
|
||||||
|
if (py_sample_tx_bytes)
|
||||||
|
quisk_pt_sample_write = py_sample_tx_save;
|
||||||
if (read_error != -1)
|
if (read_error != -1)
|
||||||
quisk_sound_state.read_error++;
|
quisk_sound_state.read_error++;
|
||||||
if (clip != -1)
|
if (clip != -1)
|
||||||
@@ -6090,6 +6186,7 @@ static PyMethodDef QuiskMethods[] = {
|
|||||||
{"get_filter_rate", get_filter_rate, METH_VARARGS, "Return the sample rate used for the filters."},
|
{"get_filter_rate", get_filter_rate, METH_VARARGS, "Return the sample rate used for the filters."},
|
||||||
{"get_tx_filter", quisk_get_tx_filter, METH_VARARGS, "Return the frequency response of the transmit filter."},
|
{"get_tx_filter", quisk_get_tx_filter, METH_VARARGS, "Return the frequency response of the transmit filter."},
|
||||||
{"get_audio_graph", get_audio_graph, METH_VARARGS, "Return a tuple of the audio graph data."},
|
{"get_audio_graph", get_audio_graph, METH_VARARGS, "Return a tuple of the audio graph data."},
|
||||||
|
{"get_tx_samples", get_tx_samples, METH_VARARGS, "Return Tx samples to send to the hardware."},
|
||||||
{"softrock_corrections", softrock_corrections, METH_VARARGS, "Control and return SoftRock amplitude and phase corrections."},
|
{"softrock_corrections", softrock_corrections, METH_VARARGS, "Control and return SoftRock amplitude and phase corrections."},
|
||||||
{"measure_frequency", measure_frequency, METH_VARARGS, "Set the method, return the measured frequency."},
|
{"measure_frequency", measure_frequency, METH_VARARGS, "Set the method, return the measured frequency."},
|
||||||
{"measure_audio", measure_audio, METH_VARARGS, "Set the method, return the measured audio voltage."},
|
{"measure_audio", measure_audio, METH_VARARGS, "Set the method, return the measured audio voltage."},
|
||||||
|
|||||||
@@ -65,13 +65,15 @@ else:
|
|||||||
Q3StringTypes = (str, unicode)
|
Q3StringTypes = (str, unicode)
|
||||||
|
|
||||||
class StdOutput(wx.Frame):
|
class StdOutput(wx.Frame):
|
||||||
def __init__(self, app):
|
def __init__(self, app): # Called from the GUI thread
|
||||||
self.app = app
|
self.app = app
|
||||||
self.ctrl = None
|
self.ctrl = None
|
||||||
self.old_stdout = sys.stdout
|
self.old_stdout = sys.stdout
|
||||||
self.old_stderr = sys.stderr
|
self.old_stderr = sys.stderr
|
||||||
self.old_excepthook = sys.excepthook
|
self.old_excepthook = sys.excepthook
|
||||||
self.path = os.path.join(app.QuiskFilesDir, "quisk_logfile.txt")
|
self.path = os.path.join(app.QuiskFilesDir, "quisk_logfile.txt")
|
||||||
|
self.gui_thread = threading.get_native_id()
|
||||||
|
self.text_buffer = ''
|
||||||
try:
|
try:
|
||||||
size = os.path.getsize(self.path)
|
size = os.path.getsize(self.path)
|
||||||
except:
|
except:
|
||||||
@@ -116,6 +118,9 @@ class StdOutput(wx.Frame):
|
|||||||
self.fp.write(text)
|
self.fp.write(text)
|
||||||
self.fp.write("\n")
|
self.fp.write("\n")
|
||||||
def write(self, text):
|
def write(self, text):
|
||||||
|
if threading.get_native_id() != self.gui_thread:
|
||||||
|
self.text_buffer += text # When called by non-GUI thread, save text
|
||||||
|
return
|
||||||
if self.fp:
|
if self.fp:
|
||||||
self.fp.write(text)
|
self.fp.write(text)
|
||||||
if self.old_stdout:
|
if self.old_stdout:
|
||||||
@@ -124,6 +129,10 @@ class StdOutput(wx.Frame):
|
|||||||
self.ctrl.write(text)
|
self.ctrl.write(text)
|
||||||
def flush(self):
|
def flush(self):
|
||||||
pass
|
pass
|
||||||
|
def PrintBuffer(self): # Print any saved text from non-GUI thread
|
||||||
|
if self.text_buffer:
|
||||||
|
print(self.text_buffer)
|
||||||
|
self.text_buffer = ''
|
||||||
def ExceptHook(self, typ, value, traceb):
|
def ExceptHook(self, typ, value, traceb):
|
||||||
self.Show(True)
|
self.Show(True)
|
||||||
self.Raise()
|
self.Raise()
|
||||||
@@ -1502,9 +1511,7 @@ class SoundThread(threading.Thread):
|
|||||||
#print ("Quisk thread %12.3f" % ((tm - self.time0) * 1E3))
|
#print ("Quisk thread %12.3f" % ((tm - self.time0) * 1E3))
|
||||||
#self.time0 = tm
|
#self.time0 = tm
|
||||||
if self.samples_from_python:
|
if self.samples_from_python:
|
||||||
samples = Hardware.GetRxSamples()
|
Hardware.GetRxSamples()
|
||||||
if samples:
|
|
||||||
QS.set_params(rx_samples=samples)
|
|
||||||
if self.poll_cw:
|
if self.poll_cw:
|
||||||
Hardware.PollCwKey()
|
Hardware.PollCwKey()
|
||||||
if conf.midi_cwkey_device:
|
if conf.midi_cwkey_device:
|
||||||
@@ -4694,6 +4701,7 @@ class App(wx.App):
|
|||||||
msg = QS.GetQuiskPrintf()
|
msg = QS.GetQuiskPrintf()
|
||||||
if msg:
|
if msg:
|
||||||
print(msg, end='')
|
print(msg, end='')
|
||||||
|
self.std_out_err.PrintBuffer()
|
||||||
QS.close_key()
|
QS.close_key()
|
||||||
if QS.tci_get_params("tci_clients_count") > 0:
|
if QS.tci_get_params("tci_clients_count") > 0:
|
||||||
QS.tci_set_params(close=1)
|
QS.tci_set_params(close=1)
|
||||||
@@ -4727,6 +4735,7 @@ class App(wx.App):
|
|||||||
msg = QS.GetQuiskPrintf()
|
msg = QS.GetQuiskPrintf()
|
||||||
if msg:
|
if msg:
|
||||||
print(msg, end='')
|
print(msg, end='')
|
||||||
|
self.std_out_err.PrintBuffer()
|
||||||
def OnBtnOnOff(self, event):
|
def OnBtnOnOff(self, event):
|
||||||
if event.GetEventObject().GetValue(): # Start samples
|
if event.GetEventObject().GetValue(): # Start samples
|
||||||
try:
|
try:
|
||||||
@@ -6894,6 +6903,7 @@ class App(wx.App):
|
|||||||
msg = QS.GetQuiskPrintf()
|
msg = QS.GetQuiskPrintf()
|
||||||
if msg:
|
if msg:
|
||||||
print(msg, end='')
|
print(msg, end='')
|
||||||
|
self.std_out_err.PrintBuffer()
|
||||||
if self.screen == self.config_screen:
|
if self.screen == self.config_screen:
|
||||||
self.screen.OnGraphData() # Send message to draw new data
|
self.screen.OnGraphData() # Send message to draw new data
|
||||||
if self.add_version and Hardware.GetFirmwareVersion() is not None:
|
if self.add_version and Hardware.GetFirmwareVersion() is not None:
|
||||||
|
|||||||
+11
-2
@@ -118,17 +118,24 @@ class Hardware:
|
|||||||
# length is the number of samples in each block of ADC samples, and equals the FFT size.
|
# length is the number of samples in each block of ADC samples, and equals the FFT size.
|
||||||
QS.set_params(bscope_bytes=int_size, bscope_endian=endian, bscope_size=length)
|
QS.set_params(bscope_bytes=int_size, bscope_endian=endian, bscope_size=length)
|
||||||
self.application.bandscope_clock = clock
|
self.application.bandscope_clock = clock
|
||||||
|
def InitTxSamples(self, int_size, endian, rate): # Tx sample initialization; you must call this from your hardware __init__().
|
||||||
|
# int_size is the number of bytes in each I or Q sample: 2, 3, or 4
|
||||||
|
# endian is the order of bytes in the sample: 0 == little endian; 1 == big endian
|
||||||
|
# This can be called again to change the format. For example, a different number of bytes for different sample rates.
|
||||||
|
QS.set_tx_audio(tx_sample_rate=rate) # Available rates are 48, 50, 96, 100, 192 ksps
|
||||||
|
QS.set_params(tx_bytes=int_size, tx_endian=endian)
|
||||||
#def PollCwKey(self): # Optional. Called frequently by the sound thread to check the CW key status.
|
#def PollCwKey(self): # Optional. Called frequently by the sound thread to check the CW key status.
|
||||||
# pass # Do not define if not needed.
|
# pass # Do not define if not needed.
|
||||||
#def PollGuiControl(self): # Optional. Called frequently by the GUI thread to change GUI settings (PTT etc.)
|
#def PollGuiControl(self): # Optional. Called frequently by the GUI thread to change GUI settings (PTT etc.)
|
||||||
# pass # Do not define if not needed.
|
# pass # Do not define if not needed.
|
||||||
def StartSamples(self): # Quisk calls this from the sound thread to start sending samples.
|
def StartSamples(self): # Quisk calls this from the sound thread to start samples.
|
||||||
# If you return a string, it replaces the string returned from hardware open()
|
# If you return a string, it replaces the string returned from hardware open()
|
||||||
pass
|
pass
|
||||||
def StopSamples(self): # Quisk calls this from the sound thread to stop sending samples.
|
def StopSamples(self): # Quisk calls this from the sound thread to stop samples.
|
||||||
pass
|
pass
|
||||||
def GetRxSamples(self): # Quisk calls this frequently from the sound thread. Poll your hardware for samples.
|
def GetRxSamples(self): # Quisk calls this frequently from the sound thread. Poll your hardware for samples.
|
||||||
# Return any available samples by calling AddRxSamples() and perhaps AddBscopeSamples() from within this method.
|
# Return any available samples by calling AddRxSamples() and perhaps AddBscopeSamples() from within this method.
|
||||||
|
# Call GetTxSamples() from within this method to return any Tx samples that must be sent.
|
||||||
pass
|
pass
|
||||||
def AddRxSamples(self, samples): # Call this from within GetRxSamples() to record the Rx samples.
|
def AddRxSamples(self, samples): # Call this from within GetRxSamples() to record the Rx samples.
|
||||||
# "samples" is int_size of integer I data followed by int_size of integer Q data, repeated.
|
# "samples" is int_size of integer I data followed by int_size of integer Q data, repeated.
|
||||||
@@ -142,6 +149,8 @@ class Hardware:
|
|||||||
# For Python 2, "samples" must be a byte array or bytes or a string.
|
# For Python 2, "samples" must be a byte array or bytes or a string.
|
||||||
# The number of bytes in "samples" must equal the block length times the bytes per sample.
|
# The number of bytes in "samples" must equal the block length times the bytes per sample.
|
||||||
QS.add_bscope_samples(samples)
|
QS.add_bscope_samples(samples)
|
||||||
|
def GetTxSamples(self): # Call this from within GetRxSamples() to return the Tx samples to send.
|
||||||
|
return QS.get_tx_samples() # Return a Python bytes object
|
||||||
def GotClip(self): # Call this to indicate that samples were received with the clip (overrange) indicator true.
|
def GotClip(self): # Call this to indicate that samples were received with the clip (overrange) indicator true.
|
||||||
QS.set_params(clip=1)
|
QS.set_params(clip=1)
|
||||||
def GotReadError(self, print_msg, msg): # Call this to indicate an error in receiving the Rx samples.
|
def GotReadError(self, print_msg, msg): # Call this to indicate an error in receiving the Rx samples.
|
||||||
|
|||||||
Reference in New Issue
Block a user