Updated local repository to 4.1.54
This commit is contained in:
parent
4d5471cc5d
commit
65732f3d1a
@ -1,3 +1,12 @@
|
||||
Quisk Version 4.1.54 March 2020
|
||||
================================
|
||||
There are now two new radios available in Quisk. David Fainitski contributed code for the Sdr Micron,
|
||||
and Andrea Montefusco IW0HDV contributed code for the Perseus SDR. The radios should appear on the
|
||||
list of supported radios in Config/Radios screen. Check the Config/radio/Hardware screen to see if you
|
||||
need any more options. Please test these radios to make sure everything is working.
|
||||
|
||||
I added a general way to add new radios to the configuration screens. See docs.html under Custom Hardware.
|
||||
|
||||
Quisk Version 4.1.53 March 2020
|
||||
================================
|
||||
I changed the Afedri radio module to be compatible with Python3. The graph Zoom feature now is available
|
||||
@ -8,7 +17,7 @@ noise floor such as PlutoSDR. You will have to readjust your waterfall colors.
|
||||
|
||||
I added a hardware setting for the LNA gain during transmit for the HL2. I made some changes to the CW
|
||||
and PTT hardware interface for the HL2. These work great with the HL2, but test the new code if you have
|
||||
diferent Hermes hardware such as Red Pitaya.
|
||||
different Hermes hardware such as Red Pitaya.
|
||||
|
||||
Quisk Version 4.1.52 December 2019
|
||||
===================================
|
||||
|
@ -7,4 +7,6 @@ graft n2adr
|
||||
graft sdriqpkg
|
||||
graft softrock
|
||||
graft soapypkg
|
||||
graft sdrmicronpkg
|
||||
graft perseuspkg
|
||||
global-exclude *.pyc
|
||||
|
2
PKG-INFO
2
PKG-INFO
@ -1,6 +1,6 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: quisk
|
||||
Version: 4.1.53
|
||||
Version: 4.1.54
|
||||
Summary: QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.
|
||||
Home-page: http://james.ahlstrom.name/quisk/
|
||||
Author: James C. Ahlstrom
|
||||
|
@ -1 +1 @@
|
||||
#Quisk version 4.1.53
|
||||
#Quisk version 4.1.54
|
||||
|
34
configure.py
34
configure.py
@ -157,8 +157,16 @@ class Configuration:
|
||||
try:
|
||||
if fmt4 == 'text': # Note: JSON returns Unicode strings !!!
|
||||
setattr(conf, k, v)
|
||||
elif fmt4 in ('dict', 'list'):
|
||||
setattr(conf, k, v)
|
||||
elif fmt4 == 'dict':
|
||||
if isinstance(v, dict):
|
||||
setattr(conf, k, v)
|
||||
else:
|
||||
raise ValueError()
|
||||
elif fmt4 == 'list':
|
||||
if isinstance(v, list):
|
||||
setattr(conf, k, v)
|
||||
else:
|
||||
raise ValueError()
|
||||
elif fmt4 == 'inte':
|
||||
setattr(conf, k, int(v, base=0))
|
||||
elif fmt4 == 'numb':
|
||||
@ -448,13 +456,27 @@ class Configuration:
|
||||
# Then some help text starting with "# "
|
||||
# Then a list of possible value#explain with the default first
|
||||
# Then a blank line to end.
|
||||
|
||||
self.format4name = {}
|
||||
self.format4name['hardware_file_type'] = 'text'
|
||||
self._ParserConf('quisk_conf_defaults.py')
|
||||
# Read any user-defined radio types
|
||||
for dirname in os.listdir('.'):
|
||||
if not os.path.isdir(dirname) or dirname[-3:] != 'pkg':
|
||||
continue
|
||||
if dirname in ('freedvpkg', 'sdriqpkg', 'soapypkg'):
|
||||
continue
|
||||
filename = os.path.join(dirname, 'quisk_hardware.py')
|
||||
if not os.path.isfile(filename):
|
||||
continue
|
||||
try:
|
||||
self._ParserConf(filename)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
def _ParserConf(self, filename):
|
||||
re_AeqB = re.compile("^#?(\w+)\s*=\s*([^#]+)#*(.*)") # item values "a = b"
|
||||
section = None
|
||||
data_name = None
|
||||
fp = open("quisk_conf_defaults.py", "r")
|
||||
fp = open(filename, "r")
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
@ -486,7 +508,7 @@ class Configuration:
|
||||
value_list = []
|
||||
if data_name in self.format4name:
|
||||
if self.format4name[data_name] != fmt:
|
||||
print ("Inconsistent format for", data_name, self.format4name[data_name], fmt)
|
||||
print (filename, ": Inconsistent format for", data_name, self.format4name[data_name], fmt)
|
||||
else:
|
||||
self.format4name[data_name] = fmt
|
||||
section_data.append([data_name, dspl, fmt, '', value_list])
|
||||
@ -495,7 +517,7 @@ class Configuration:
|
||||
mo = re_AeqB.match(line)
|
||||
if mo:
|
||||
if data_name != mo.group(1):
|
||||
print ("Parse error for", data_name)
|
||||
print (filename, ": Parse error for", data_name)
|
||||
continue
|
||||
value = mo.group(2).strip()
|
||||
expln = mo.group(3).strip()
|
||||
|
55
docs.html
55
docs.html
@ -50,6 +50,7 @@ div.contents {
|
||||
<li><a href="#Configuration">Configuration</a></li>
|
||||
<li><a href="#SoundCards">Sound Cards</a></li>
|
||||
<li><a href="#SDRIQ">SDR-IQ</a></li>
|
||||
<li><a href="#Perseus">Perseus</a></li>
|
||||
<li><a href="#Timing">Timing</a></li>
|
||||
<li><a href="#USBControl">USB Control</a></li>
|
||||
<li><a href="#CustomHardware">Custom Hardware</a></li>
|
||||
@ -80,6 +81,7 @@ complete transceiver. Quisk works with this hardware:
|
||||
<li>SoftRock connected to the sound card</li>
|
||||
<li>Many other SDR's connected to the sound card</li>
|
||||
<li>SDR-IQ connected by USB</li>
|
||||
<li>Perseus connected by USB</li>
|
||||
<li>N2ADR hardware connected by Ethernet and IP</li>
|
||||
<li>HiQSDR hardware connected by Ethernet and IP</li>
|
||||
<li>The Hermes-Lite project at <a href="http://hermeslite.com">hermeslite.com</a></li>
|
||||
@ -874,6 +876,35 @@ In earlier versions of Windows, port names are COM1, COM2 etc. and use the "USB
|
||||
Windows should find this driver by itself.
|
||||
</p>
|
||||
<br>
|
||||
<h2 id="Perseus">Perseus as Input </h2>
|
||||
<p>
|
||||
Quisk can use an Perseus HF receiver from Microtelecom instead of a sound card as input.
|
||||
Set up a radio of type Perseus. The Perseus uses a native USB interface to connect to Quisk.
|
||||
The Quisk perseuspkg extension relies on <a href="http://github.com/Microtelecom/libpserseus-sdr">libperseus-sdr</a>
|
||||
open source library to manage Perseus hardware and receive the I/Q samples stream.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
Follow the instruction into GitHub repository to compile and install the library.
|
||||
On Suse distribution the library is available as binary package.
|
||||
Next compile the perseuspkg using the command:
|
||||
</p>
|
||||
<br>
|
||||
<pre>
|
||||
make perseus3
|
||||
</pre>
|
||||
</br>
|
||||
<p>
|
||||
The several sample rates can be selected opening Config panel: in
|
||||
the Config tab there is the Samples rates dropdown.
|
||||
</p>
|
||||
The input analog filter can be switched in using the button Wideband.<br>
|
||||
The input attenuator is operate via the button RF, that allows to select
|
||||
the four attenuator steps.<br>
|
||||
The ADC commands for dithering and preamplifier are found on
|
||||
left bottom corner as ADC Dither and ADC Preamp.<br>
|
||||
</p>
|
||||
<br>
|
||||
<h2 id="Timing">Timing </h2>
|
||||
<br>
|
||||
There are several configuration parameters devoted to tuning; read the
|
||||
@ -988,6 +1019,7 @@ can put code there to poll a serial port or to perform other
|
||||
housekeeping functions (try to be efficient). The two remaining
|
||||
functions deserve more documentation.
|
||||
<br>
|
||||
<br>
|
||||
<h3 id="g0.8.1">ChangeFrequency(self, tune, vfo, source='', band='', event=None)</h3>
|
||||
Quisk calls the ChangeFrequency() function when the user changes the Tx
|
||||
frequency with a mouse click on the graph or waterfall, with the entry
|
||||
@ -1117,6 +1149,29 @@ return the new frequencies from ReturnFrequency() or else Quisk will be
|
||||
unaware of the change.
|
||||
<br>
|
||||
|
||||
<br>
|
||||
<h3 id="g0.8.3">Adding Custom Hardware to the Config/Radios Screen</h3>
|
||||
Once you write your own hardware file for your custom hardware, you still need to add it to the
|
||||
list of available radio types, and add its configuration options to its Config/radios/Hardware screen.
|
||||
First create a subdirectory of the Quisk directory with a name ending in "pkg"; for example,
|
||||
"myradiopkg". Then put your hardware file in this subdirectory with the name "quisk_hardware.py".
|
||||
You will need at least one configuration option to specify the hardware file name.
|
||||
Add this code (all comments) near the top of quisk_hardware.py:
|
||||
<pre>
|
||||
|
||||
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
|
||||
################ Receivers MyRadio, The special radio that I own
|
||||
## hardware_file_name Hardware file path, rfile
|
||||
# This is the file that contains the control logic for each radio.
|
||||
#hardware_file_name = 'myradiopkg/quisk_hardware.py'
|
||||
|
||||
</pre>
|
||||
Of course, change the names of the radio and subdirectory as appropriate. Your radio of general type "MyRadio"
|
||||
will now appear in the list of radios on the Config/Radios screen, and its configuration items will appear
|
||||
on the Config/radio/Hardware screen. You can add additional items. See quisk_conf_defaults.py and the *pkg
|
||||
subdirectories for examples.
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<h2 id="ExtensionPackages">Extension Packages </h2>
|
||||
Quisk comes with two extension packages. The freedvpkg package
|
||||
|
8
makefile
8
makefile
@ -7,11 +7,13 @@ quisk2:
|
||||
python2 setup.py build_ext --force --inplace
|
||||
@echo
|
||||
@echo 'Use "make soapy2" to make the Python2 soapy module'
|
||||
@echo 'Use "make perseus2" to make the Python2 perseus package'
|
||||
|
||||
quisk3:
|
||||
python3 setup.py build_ext --force --inplace
|
||||
@echo
|
||||
@echo 'Use "make soapy3" to make the Python3 soapy module'
|
||||
@echo 'Use "make perseus3" to make the Python3 perseus package'
|
||||
|
||||
soapy2:
|
||||
(cd soapypkg; make soapy2)
|
||||
@ -19,5 +21,11 @@ soapy2:
|
||||
soapy3:
|
||||
(cd soapypkg; make soapy3)
|
||||
|
||||
perseus2:
|
||||
(cd perseuspkg; make perseus2)
|
||||
|
||||
perseus3:
|
||||
(cd perseuspkg; make perseus3)
|
||||
|
||||
macports:
|
||||
env ARCHFLAGS="-arch x86_64" python setup.py build_ext --force --inplace -D USE_MACPORTS
|
||||
|
10
perseuspkg/README.txt
Executable file
10
perseuspkg/README.txt
Executable file
@ -0,0 +1,10 @@
|
||||
Microtelecom Perseus HF receiver
|
||||
|
||||
Prerequisite are:
|
||||
|
||||
libusb-1.0-0-dev (found in all major Linux distro)
|
||||
|
||||
libperseus-sdr as found in https://github.com/Microtelecom/libperseus-sdr/archive/master.zip
|
||||
|
||||
|
||||
|
1
perseuspkg/__init__.py
Executable file
1
perseuspkg/__init__.py
Executable file
@ -0,0 +1 @@
|
||||
#
|
7
perseuspkg/makefile
Executable file
7
perseuspkg/makefile
Executable file
@ -0,0 +1,7 @@
|
||||
|
||||
perseus2:
|
||||
python2 setup.py build_ext --force --inplace
|
||||
|
||||
perseus3:
|
||||
python3 setup.py build_ext --force --inplace
|
||||
|
498
perseuspkg/perseus.c
Executable file
498
perseuspkg/perseus.c
Executable file
@ -0,0 +1,498 @@
|
||||
/*
|
||||
*
|
||||
* Microtelecom perseus HF receiver
|
||||
*
|
||||
* access module: exposes Python functions needed in quisk_hardware.py
|
||||
* to control hardware
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <complex.h>
|
||||
#include <perseus-sdr.h>
|
||||
|
||||
#define IMPORT_QUISK_API
|
||||
#include "quisk.h"
|
||||
#include "filter.h"
|
||||
|
||||
// This module was written by Andrea Montefusco IW0HDV.
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
int32_t i;
|
||||
int32_t q;
|
||||
} __attribute__((__packed__)) iq;
|
||||
struct {
|
||||
uint8_t i1;
|
||||
uint8_t i2;
|
||||
uint8_t i3;
|
||||
uint8_t i4;
|
||||
uint8_t q1;
|
||||
uint8_t q2;
|
||||
uint8_t q3;
|
||||
uint8_t q4;
|
||||
} __attribute__((__packed__)) ;
|
||||
} iq_sample;
|
||||
|
||||
|
||||
// buffer size for libperseus-sdr
|
||||
const static int nb = 6;
|
||||
const static int bs = 1024;
|
||||
|
||||
|
||||
// This module uses the Python interface to import symbols from the parent _quisk
|
||||
// extension module. It must be linked with import_quisk_api.c. See the documentation
|
||||
// at the start of import_quisk_api.c.
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
static int num_perseus = 0;
|
||||
static perseus_descr *descr = 0;
|
||||
static int sr = 48000;
|
||||
static float freq = 7050000.0;
|
||||
static int adc_dither = 0;
|
||||
static int adc_preamp = 0;
|
||||
|
||||
static void quisk_stop_samples(void);
|
||||
|
||||
static const char *fname = "/tmp/quiskperseus";
|
||||
static int rfd = 0;
|
||||
static int wfd = 0;
|
||||
static int running = 0;
|
||||
static int wb_filter = 0;
|
||||
|
||||
// Called in a loop to read samples; called from the sound thread.
|
||||
static int quisk_read_samples(complex double * cSamples)
|
||||
{
|
||||
//fprintf (stderr, "r"); fflush(stderr);
|
||||
|
||||
int n = read(rfd, cSamples, sizeof(complex double)*SAMP_BUFFER_SIZE);
|
||||
//fprintf(stderr, "%d ", n);
|
||||
if (n >= 0)
|
||||
return n/sizeof(complex double); // return number of samples
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Called in a loop to write samples; called from the sound thread.
|
||||
static int quisk_write_samples(complex double * cSamples, int nSamples)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// callback that writes in the output pipe IQ values as
|
||||
// complex floating point
|
||||
//
|
||||
static int user_data_callback_c_f(void *buf, int buf_size, void *extra)
|
||||
{
|
||||
// The buffer received contains 24-bit IQ samples (6 bytes per sample)
|
||||
// Here we save the received IQ samples as 32 bit
|
||||
// (msb aligned) integer IQ samples.
|
||||
|
||||
uint8_t *samplebuf = (uint8_t*)buf;
|
||||
int nSamples = buf_size/6;
|
||||
int k;
|
||||
iq_sample s;
|
||||
|
||||
// the 24 bit data is scaled to a 32bit value (so that the machine's
|
||||
// natural signed arithmetic will work)
|
||||
for (k=0; k < nSamples; k++) {
|
||||
s.i1 = s.q1 = 0;
|
||||
s.i2 = *samplebuf++;
|
||||
s.i3 = *samplebuf++;
|
||||
s.i4 = *samplebuf++;
|
||||
s.q2 = *samplebuf++;
|
||||
s.q3 = *samplebuf++;
|
||||
s.q4 = *samplebuf++;
|
||||
|
||||
// move I/Q to complex number
|
||||
complex double x = (double)(s.iq.i)*10 + (double)(s.iq.q)*10 * _Complex_I;
|
||||
if (wfd > 0) {
|
||||
int n = write(wfd, &x, sizeof(complex double));
|
||||
if (n<0 && ! -EAGAIN )
|
||||
fprintf(stderr, "perseus c: Can't write output file: %s, descriptor: %d\n", strerror(errno), wfd);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Start sample capture; called from the sound thread.
|
||||
static void quisk_start_samples(void)
|
||||
{
|
||||
fprintf (stderr, "perseus c: quisk_start_samples\n"); fflush(stderr);
|
||||
|
||||
int rc = mkfifo(fname, 0666);
|
||||
|
||||
if ((rc == -1) && (errno != EEXIST)) {
|
||||
perror("perseus c: Error creating the named pipe");
|
||||
}
|
||||
|
||||
rfd = open(fname, O_RDONLY|O_NONBLOCK);
|
||||
if (rfd < 0) fprintf(stderr, "perseus c: Can't open read FIFO (%s)\n", strerror(errno));
|
||||
else fprintf(stderr, "perseus c: read FIFO (%d)\n", rfd);
|
||||
|
||||
wfd = open(fname, O_WRONLY|O_NONBLOCK);
|
||||
if (wfd < 0) fprintf(stderr, "perseus c: Can't open write FIFO (%s)\n", strerror(errno));
|
||||
else fprintf(stderr, "perseus c: write FIFO (%d)\n", wfd);
|
||||
|
||||
if (perseus_set_sampling_rate(descr, sr) < 0) { // specify the sampling rate value in Samples/second
|
||||
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
|
||||
} else {
|
||||
fprintf(stderr, "perseus c: sampling rate set to: %d\n", sr);
|
||||
|
||||
// Re-enable preselection filters (WB_MODE Off)
|
||||
perseus_set_ddc_center_freq(descr, freq, wb_filter);
|
||||
// start sampling ops
|
||||
if (perseus_start_async_input(descr, nb*bs, user_data_callback_c_f, 0)<0) {
|
||||
fprintf(stderr, "perseus c: start async input error: %s\n", perseus_errorstr());
|
||||
} else
|
||||
fprintf(stderr, "perseus c: start async input\n");
|
||||
|
||||
running = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop sample capture; called from the sound thread.
|
||||
static void quisk_stop_samples(void)
|
||||
{
|
||||
fprintf (stderr, "perseus c: quisk_stop_samples\n"); fflush(stderr);
|
||||
|
||||
// We stop the acquisition...
|
||||
fprintf(stderr, "perseus c: stopping async data acquisition...\n");
|
||||
perseus_stop_async_input(descr);
|
||||
running = 0;
|
||||
// clearing FIFO...
|
||||
close(rfd);
|
||||
close(wfd);
|
||||
unlink(fname);
|
||||
}
|
||||
|
||||
|
||||
// Called to close the sample source; called from the GUI thread.
|
||||
static PyObject * close_device(PyObject * self, PyObject * args)
|
||||
{
|
||||
fprintf (stderr, "perseus c: close_device\n");
|
||||
int sample_device; // for now one only Perseus can be managed
|
||||
|
||||
if (!PyArg_ParseTuple (args, "i", &sample_device))
|
||||
return NULL;
|
||||
|
||||
if (descr) {
|
||||
// We stop the acquisition...
|
||||
if (running) {
|
||||
perseus_stop_async_input(descr);
|
||||
running = 0;
|
||||
}
|
||||
perseus_close(descr);
|
||||
descr = 0;
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
// Called to open the Perseus SDR device; called from the GUI thread.
|
||||
static PyObject * open_device(PyObject * self, PyObject * args)
|
||||
{
|
||||
char buf128[128] = "Capture Microtelecom Perseus HF receiver";
|
||||
eeprom_prodid prodid;
|
||||
|
||||
fprintf (stderr, "perseus c: open device (%d)\n", num_perseus); fflush(stderr);
|
||||
|
||||
|
||||
// Check how many Perseus receivers are connected to the system
|
||||
if (num_perseus == 0) num_perseus = perseus_init();
|
||||
fprintf(stderr, "perseus c: %d Perseus receivers found\n",num_perseus);
|
||||
|
||||
if (num_perseus == 0) {
|
||||
sprintf(buf128, "No Perseus receivers detected\n");
|
||||
perseus_exit();
|
||||
goto main_cleanup;
|
||||
}
|
||||
|
||||
// Open the first one...
|
||||
if ((descr = perseus_open(0)) == NULL) {
|
||||
sprintf(buf128, "error: %s\n", perseus_errorstr());
|
||||
fprintf(stderr, "perseus c: open error: %s\n", perseus_errorstr());
|
||||
goto main_cleanup;
|
||||
}
|
||||
|
||||
// Download the standard firmware to the unit
|
||||
fprintf(stderr, "perseus c: Downloading firmware...\n");
|
||||
if (perseus_firmware_download(descr,NULL)<0) {
|
||||
sprintf(buf128, "perseus c: firmware download error: %s", perseus_errorstr());
|
||||
goto main_cleanup;
|
||||
}
|
||||
// Dump some information about the receiver (S/N and HW rev)
|
||||
if (perseus_is_preserie(descr, 0) == PERSEUS_SNNOTAVAILABLE)
|
||||
fprintf(stderr, "perseus c: The device is a preserie unit");
|
||||
else
|
||||
if (perseus_get_product_id(descr,&prodid)<0)
|
||||
fprintf(stderr, "perseus c: get product id error: %s", perseus_errorstr());
|
||||
else
|
||||
fprintf(stderr, "perseus c: Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n",
|
||||
(uint16_t) prodid.sn,
|
||||
(uint16_t) prodid.signature[5],
|
||||
(uint16_t) prodid.signature[4],
|
||||
(uint16_t) prodid.signature[3],
|
||||
(uint16_t) prodid.signature[2],
|
||||
(uint16_t) prodid.signature[1],
|
||||
(uint16_t) prodid.signature[0],
|
||||
(uint16_t) prodid.hwrel,
|
||||
(uint16_t) prodid.hwver);
|
||||
|
||||
// Printing all sampling rates available .....
|
||||
{
|
||||
int buf[BUFSIZ];
|
||||
|
||||
if (perseus_get_sampling_rates (descr, buf, sizeof(buf)/sizeof(buf[0])) < 0) {
|
||||
fprintf(stderr, "perseus c: get sampling rates error: %s\n", perseus_errorstr());
|
||||
goto main_cleanup;
|
||||
} else {
|
||||
int i = 0;
|
||||
while (buf[i]) {
|
||||
fprintf(stderr, "perseus c: #%d: sample rate: %d\n", i, buf[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the receiver for 2 MS/s operations
|
||||
fprintf(stderr, "perseus c: Configuring FPGA...\n");
|
||||
if (perseus_set_sampling_rate(descr, sr) < 0) { // specify the sampling rate value in Samples/second
|
||||
//if (perseus_set_sampling_rate_n(descr, 0)<0) // specify the sampling rate value as ordinal in the vector
|
||||
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
|
||||
goto main_cleanup;
|
||||
}
|
||||
|
||||
// ADC settings
|
||||
perseus_set_adc (descr, adc_dither, adc_preamp);
|
||||
|
||||
// Disable preselection filters (WB_MODE On)
|
||||
//perseus_set_ddc_center_freq(descr, freq, 0);
|
||||
//sleep(1);
|
||||
// Re-enable preselection filters (WB_MODE Off)
|
||||
perseus_set_ddc_center_freq(descr, freq, wb_filter);
|
||||
|
||||
quisk_sample_source4(&quisk_start_samples, &quisk_stop_samples, &quisk_read_samples, &quisk_write_samples);
|
||||
|
||||
fprintf (stderr, "perseus c: quisk sample source callbacks established\n"); fflush(stderr);
|
||||
goto exit_success;
|
||||
|
||||
|
||||
|
||||
main_cleanup:
|
||||
return PyString_FromString("ERROR");
|
||||
|
||||
exit_success:
|
||||
|
||||
return PyString_FromString(buf128);
|
||||
|
||||
|
||||
}
|
||||
|
||||
static PyObject * set_frequency(PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{
|
||||
float param;
|
||||
|
||||
if (!PyArg_ParseTuple (args, "f", ¶m))
|
||||
return NULL;
|
||||
if (DEBUG)
|
||||
fprintf (stderr, "perseus c: set DDC frequency%lf\n", param);
|
||||
freq= param;
|
||||
if (descr) perseus_set_ddc_center_freq(descr, freq, wb_filter == 0);
|
||||
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
||||
static PyObject * set_input_filter(PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{
|
||||
int param;
|
||||
|
||||
if (!PyArg_ParseTuple (args, "i", ¶m))
|
||||
return NULL;
|
||||
if (DEBUG)
|
||||
fprintf (stderr, "perseus c: set input filter%d\n", param);
|
||||
wb_filter = param;
|
||||
if (descr) perseus_set_ddc_center_freq(descr, freq, wb_filter == 0);
|
||||
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
||||
static PyObject * set_sampling_rate(PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{
|
||||
int param;
|
||||
|
||||
if (!PyArg_ParseTuple (args, "i", ¶m))
|
||||
return NULL;
|
||||
|
||||
fprintf (stderr, "perseus c: Set sampling rate %d\n", param);
|
||||
if (param < 48000) sr = param * 1000;
|
||||
else sr = param;
|
||||
|
||||
if (descr) {
|
||||
if (running) {
|
||||
fprintf(stderr, "perseus c: stop async input\n");
|
||||
perseus_stop_async_input(descr);
|
||||
}
|
||||
|
||||
// specify the sampling rate value in Samples/secon
|
||||
if (perseus_set_sampling_rate(descr, sr) < 0) {
|
||||
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
|
||||
}
|
||||
|
||||
if (running) {
|
||||
if (perseus_start_async_input(descr, nb*bs, user_data_callback_c_f, 0)<0) {
|
||||
fprintf(stderr, "perseus c: start async input error: %s\n", perseus_errorstr());
|
||||
} else
|
||||
fprintf(stderr, "perseus c: start async input @%d\n", sr);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "perseus c: tryng to start async input with no device open\n");
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
||||
static PyObject * set_attenuator(PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{ int param;
|
||||
|
||||
if (!PyArg_ParseTuple (args, "i", ¶m))
|
||||
return NULL;
|
||||
if (DEBUG)
|
||||
fprintf (stderr, "perseus c: Set attenuator %d\n", param);
|
||||
|
||||
if (descr) {
|
||||
// specify the sampling rate value in Samples/secon
|
||||
if (perseus_set_attenuator_n(descr, (int)(param / -10)) < 0) {
|
||||
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
|
||||
}
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
||||
// Enable ADC Dither, Disable ADC Preamp
|
||||
// perseus_set_adc(descr, true, false);
|
||||
|
||||
static PyObject * set_adc_dither (PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{ int dither_;
|
||||
|
||||
if (!PyArg_ParseTuple (args, "i", &dither_))
|
||||
return NULL;
|
||||
if (DEBUG)
|
||||
fprintf (stderr, "perseus c: Set ADC: dither %d\n", dither_);
|
||||
|
||||
adc_dither = dither_;
|
||||
if (descr) {
|
||||
// specify the ADC dithering
|
||||
if (perseus_set_adc(descr, adc_dither == 1, adc_preamp == 1) < 0) {
|
||||
fprintf(stderr, "perseus c: ADC configuration error: %s\n", perseus_errorstr());
|
||||
}
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
static PyObject * set_adc_preamp (PyObject * self, PyObject * args) // Called from GUI thread
|
||||
{ int preamp_;
|
||||
|
||||
if (!PyArg_ParseTuple (args, "i", &preamp_))
|
||||
return NULL;
|
||||
if (DEBUG)
|
||||
fprintf (stderr, "perseus c: Set ADC: preamp: %d\n", preamp_);
|
||||
|
||||
adc_preamp = preamp_;
|
||||
if (descr) {
|
||||
// specify the sampling rate value in Samples/secon
|
||||
if (perseus_set_adc(descr, adc_dither == 1, adc_preamp == 1) < 0) {
|
||||
fprintf(stderr, "perseus c: ADC configuration error: %s\n", perseus_errorstr());
|
||||
}
|
||||
}
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
||||
static PyObject * deinit(PyObject * self, PyObject * args) // Called from dctor
|
||||
{
|
||||
perseus_exit();
|
||||
|
||||
Py_INCREF (Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
||||
// Functions callable from Python are listed here:
|
||||
static PyMethodDef QuiskMethods[] = {
|
||||
{"open_device", open_device, METH_VARARGS, "Open the hardware."},
|
||||
{"close_device", close_device, METH_VARARGS, "Close the hardware"},
|
||||
{"set_frequency", set_frequency, METH_VARARGS, "set frequency"},
|
||||
{"set_input_filter", set_input_filter, METH_VARARGS, "set input filter"},
|
||||
{"set_sampling_rate", set_sampling_rate, METH_VARARGS, "set sampling rate"},
|
||||
{"set_attenuator", set_attenuator, METH_VARARGS, "set attenuator"},
|
||||
{"set_adc_dither", set_adc_dither, METH_VARARGS, "set ADC dither"},
|
||||
{"set_adc_preamp", set_adc_preamp, METH_VARARGS, "set ADC preamplifier"},
|
||||
{"deinit", deinit, METH_VARARGS, "deinit"},
|
||||
// {"get_device_list", get_device_list, METH_VARARGS, "Return a list of Perseus SDR devices"},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
// Python 2.7:
|
||||
// Initialization, and registration of public symbol "initperseus":
|
||||
PyMODINIT_FUNC initperseus (void)
|
||||
{
|
||||
if (Py_InitModule ("perseus", QuiskMethods) == NULL) {
|
||||
fprintf(stderr, "perseus c: Py_InitModule failed!\n");
|
||||
return;
|
||||
}
|
||||
// Import pointers to functions and variables from module _quisk
|
||||
if (import_quisk_api()) {
|
||||
fprintf(stderr, "perseus c: Failure to import pointers from _quisk\n");
|
||||
return; //Error
|
||||
}
|
||||
}
|
||||
|
||||
// Python 3:
|
||||
#else
|
||||
static struct PyModuleDef perseusmodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"perseus",
|
||||
NULL,
|
||||
-1,
|
||||
QuiskMethods
|
||||
} ;
|
||||
|
||||
PyMODINIT_FUNC PyInit_perseus(void)
|
||||
{
|
||||
PyObject * m;
|
||||
|
||||
m = PyModule_Create(&perseusmodule);
|
||||
if (m == NULL)
|
||||
return NULL;
|
||||
|
||||
// Import pointers to functions and variables from module _quisk
|
||||
if (import_quisk_api()) {
|
||||
fprintf(stderr, "perseus c: Failure to import pointers from _quisk\n");
|
||||
return m; //Error
|
||||
}
|
||||
return m;
|
||||
}
|
||||
#endif
|
||||
|
187
perseuspkg/quisk_hardware.py
Executable file
187
perseuspkg/quisk_hardware.py
Executable file
@ -0,0 +1,187 @@
|
||||
# This is the hardware file to support radios accessed by the PerseusSDR interface.
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import socket, traceback, time, math
|
||||
import _quisk as QS
|
||||
try:
|
||||
from perseuspkg import perseus
|
||||
except:
|
||||
#traceback.print_exc()
|
||||
perseus = None
|
||||
print ("Error: Perseus package not found.\n")
|
||||
|
||||
from quisk_hardware_model import Hardware as BaseHardware
|
||||
|
||||
DEBUG = 1
|
||||
|
||||
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
|
||||
################ Receivers PerseusSDR, The PerseusSDR interface to multiple hardware SDRs.
|
||||
## hardware_file_name Hardware file path, rfile
|
||||
# This is the file that contains the control logic for each radio.
|
||||
#hardware_file_name = 'perseuspkg/quisk_hardware.py'
|
||||
|
||||
## widgets_file_name Widget file path, rfile
|
||||
# This optional file adds additional controls for the radio.
|
||||
#widgets_file_name = 'perseuspkg/quisk_widgets.py'
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
def __init__(self, app, conf):
|
||||
BaseHardware.__init__(self, app, conf)
|
||||
|
||||
self.rf_gain_labels = ('RF +0', 'RF -10', 'RF -20', 'RF -30')
|
||||
self.antenna_labels = ('Wide Band', 'Band Filter')
|
||||
|
||||
self.vardecim_index = 0
|
||||
self.fVFO = 0.0 # Careful, this is a float
|
||||
print ("__init__: %s" % conf)
|
||||
self.rates = [ 48000, \
|
||||
95000, \
|
||||
96000, \
|
||||
125000, \
|
||||
192000, \
|
||||
250000, \
|
||||
500000, \
|
||||
1000000, \
|
||||
1600000, \
|
||||
2000000 \
|
||||
]
|
||||
self.current_rate = 192000
|
||||
self.att = 0;
|
||||
self.wb = 0
|
||||
|
||||
def __del__(self):
|
||||
# try to clear hardware
|
||||
if perseus:
|
||||
perseus.close()
|
||||
perseus.deinit()
|
||||
|
||||
def get_hw (self):
|
||||
return perseus
|
||||
|
||||
def pre_open(self):
|
||||
print ("pre_open")
|
||||
pass
|
||||
|
||||
def set_parameter(self, *args):
|
||||
pass
|
||||
|
||||
def open(self): # Called once to open the Hardware
|
||||
|
||||
if not perseus:
|
||||
return "Perseus module not available"
|
||||
|
||||
txt = perseus.open_device("perseus",2,3)
|
||||
print ("perseus hardware: open")
|
||||
|
||||
return txt
|
||||
|
||||
def close(self): # Called once to close the Hardware
|
||||
print ("perseus hardware: close")
|
||||
if perseus:
|
||||
perseus.close_device(1)
|
||||
|
||||
def ChangeGain(self, rxtx): # rxtx is '_rx' or '_tx'
|
||||
if not perseus:
|
||||
return
|
||||
print ("perseus hardware: ChangeGain", rxtx)
|
||||
pass
|
||||
|
||||
def OnButtonRfGain(self, event):
|
||||
#btn = event.GetEventObject()
|
||||
n = event.GetEventObject().index
|
||||
self.att = n * -10
|
||||
print ("perseus hardware: OnButtonRfGain: %d new attenuation: %d" % (n, self.att))
|
||||
perseus.set_attenuator (self.att)
|
||||
|
||||
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
|
||||
fVFO = float(vfo)
|
||||
if self.fVFO != fVFO:
|
||||
self.fVFO = fVFO
|
||||
perseus.set_frequency(fVFO)
|
||||
return tune, vfo
|
||||
|
||||
|
||||
def ReturnFrequency(self):
|
||||
# Return the current tuning and VFO frequency. If neither have changed,
|
||||
# you can return (None, None). This is called at about 10 Hz by the main.
|
||||
# return (tune, vfo) # return changed frequencies
|
||||
return None, None # frequencies have not changed
|
||||
|
||||
def ReturnVfoFloat(self):
|
||||
# Return the accurate VFO frequency as a floating point number.
|
||||
# You can return None to indicate that the integer VFO frequency is valid.
|
||||
return self.fVFO
|
||||
|
||||
# def OnBtnFDX(self, fdx): # fdx is 0 or 1
|
||||
# pass
|
||||
#
|
||||
# def OnButtonPTT(self, event):
|
||||
# pass
|
||||
#
|
||||
# def OnSpot(self, level):
|
||||
# # level is -1 for Spot button Off; else the Spot level 0 to 1000.
|
||||
# pass
|
||||
#
|
||||
# def ChangeMode(self, mode): # Change the tx/rx mode
|
||||
# # mode is a string: "USB", "AM", etc.
|
||||
# pass
|
||||
#
|
||||
# def ChangeBand(self, band):
|
||||
# pass
|
||||
#
|
||||
# def HeartBeat(self): # Called at about 10 Hz by the main
|
||||
# pass
|
||||
|
||||
def ImmediateChange(self, name, value):
|
||||
print ("perseus hardware: ImmediateChange: perseus: name: %s value: %s" % (name, value))
|
||||
if name == 'perseus_setSampleRate_rx':
|
||||
value = int(value)
|
||||
self.application.OnBtnDecimation(rate=value)
|
||||
perseus.set_sampling_rate(value)
|
||||
self.curren_dec = value
|
||||
|
||||
|
||||
def VarDecimGetChoices(self): # Not used to set sample rate
|
||||
print ("perseus hardware: VarDecimGetChoices")
|
||||
return list(map(str, self.rates)) # convert integer to string
|
||||
|
||||
def VarDecimGetLabel(self): # Return a text label for the decimation control.
|
||||
return 'Sample rates: '
|
||||
|
||||
def VarDecimGetIndex(self): # Return the index 0, 1, ... of the current decimation.
|
||||
for i in range(len(self.rates)):
|
||||
if self.rates[i] == self.current_rate:
|
||||
return i
|
||||
return 0
|
||||
|
||||
def VarDecimSet(self, index=None): # Called when the control is operated; if index==None, called on startup.
|
||||
print ("perseus hardware: VarDecimSet: index: %s" % (index))
|
||||
if index == None:
|
||||
print ("perseus hardware: VarDecimSet: current sampling rate: %d" % self.current_rate)
|
||||
new_rate = self.current_rate
|
||||
else:
|
||||
new_rate = self.rates[index]
|
||||
|
||||
print ("perseus hardware: VarDecimSet: New sampling rate: %d" % new_rate)
|
||||
perseus.set_sampling_rate(int(new_rate))
|
||||
|
||||
return int(new_rate)
|
||||
|
||||
def VarDecimRange(self): # Return the lowest and highest sample rate.
|
||||
print ("perseus hardware: VarDecimRange: %s" % self.rates)
|
||||
return (self.rates[0], self.rates[-1])
|
||||
|
||||
def OnButtonAntenna(self, event):
|
||||
btn = event.GetEventObject()
|
||||
self.wb_filter = n = btn.index
|
||||
print ("OnButtonAntenna: %d" % n)
|
||||
perseus.set_input_filter (self.wb_filter)
|
||||
|
||||
# def StartSamples(self): # called by the sound thread
|
||||
# print("perseus hardware: StartSamples")
|
||||
|
||||
# def StopSamples(self): # called by the sound thread
|
||||
# print("perseus hardware: StopSamples")
|
37
perseuspkg/quisk_widgets.py
Executable file
37
perseuspkg/quisk_widgets.py
Executable file
@ -0,0 +1,37 @@
|
||||
# Please do not change this widgets module for Quisk. Instead copy
|
||||
# it to your own quisk_widgets.py and make changes there.
|
||||
#
|
||||
# This module is used to add extra widgets to the QUISK screen.
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import math, wx
|
||||
|
||||
class BottomWidgets: # Add extra widgets to the bottom of the screen
|
||||
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
|
||||
self.config = conf
|
||||
self.hardware = hardware
|
||||
self.application = app
|
||||
self.start_row = app.widget_row # The first available row
|
||||
self.start_col = app.button_start_col # The start of the button columns
|
||||
self.Widgets_0x06(app, hardware, conf, frame, gbs, vertBox)
|
||||
|
||||
def Widgets_0x06(self, app, hardware, conf, frame, gbs, vertBox):
|
||||
self.num_rows_added = 1
|
||||
start_row = self.start_row
|
||||
b1 = app.QuiskCheckbutton(frame, self.OnADC_dither, 'ADC Dither')
|
||||
gbs.Add(b1, (start_row, self.start_col), (1, 2), flag=wx.EXPAND)
|
||||
b2 = app.QuiskCheckbutton(frame, self.OnADC_preamp, 'ADC Preamp')
|
||||
gbs.Add(b2, (start_row, self.start_col + 2), (1, 2), flag=wx.EXPAND)
|
||||
|
||||
def OnADC_dither(self, event):
|
||||
btn = event.GetEventObject()
|
||||
value = btn.GetValue()
|
||||
self.hardware.get_hw().set_adc_dither (value)
|
||||
|
||||
def OnADC_preamp(self, event):
|
||||
btn = event.GetEventObject()
|
||||
value = btn.GetValue()
|
||||
self.hardware.get_hw().set_adc_preamp (value)
|
48
perseuspkg/setup.py
Executable file
48
perseuspkg/setup.py
Executable file
@ -0,0 +1,48 @@
|
||||
from distutils.core import setup, Extension
|
||||
import sys
|
||||
|
||||
module2 = Extension ('perseus',
|
||||
libraries = ['m', 'perseus-sdr'],
|
||||
sources = ['../import_quisk_api.c', 'perseus.c'],
|
||||
include_dirs = ['.', '..'],
|
||||
)
|
||||
|
||||
modulew2 = Extension ('perseus',
|
||||
sources = ['../import_quisk_api.c', 'perseus.c'],
|
||||
include_dirs = ['.', '..'],
|
||||
libraries = ['WS2_32', 'perseus-sdr'],
|
||||
)
|
||||
|
||||
if sys.platform == "win32":
|
||||
Modules = [modulew2]
|
||||
else:
|
||||
Modules = [module2]
|
||||
|
||||
setup (name = 'perseus',
|
||||
version = '0.1',
|
||||
description = 'perseus is an extension to Quisk to support Microtelecom Perseus SDR hardware',
|
||||
long_description = """Microtelecom Perseus SDR HF receiver.
|
||||
""",
|
||||
author = 'Andrea Montefusco IW0HDV',
|
||||
author_email = 'andrew@montefusco.com',
|
||||
url = 'http://www.montefusco.com',
|
||||
download_url = 'http://james.ahlstrom.name/quisk/',
|
||||
packages = ['quisk.perseuspkg'],
|
||||
package_dir = {'perseus' : '.'},
|
||||
ext_modules = Modules,
|
||||
classifiers = [
|
||||
'Development Status :: 6 - Mature',
|
||||
'Environment :: X11 Applications',
|
||||
'Environment :: Win32 (MS Windows)',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: C',
|
||||
'Topic :: Communications :: Ham Radio',
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: quisk
|
||||
Version: 4.1.53
|
||||
Version: 4.1.54
|
||||
Summary: QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.
|
||||
Home-page: http://james.ahlstrom.name/quisk/
|
||||
Author: James C. Ahlstrom
|
||||
|
@ -7,7 +7,6 @@ WinQuisk.pyw
|
||||
WinQuiskVna.pyw
|
||||
__init__.py
|
||||
__main__.py
|
||||
_quisk.pyd
|
||||
configure.py
|
||||
defaults.html
|
||||
docs.html
|
||||
@ -51,7 +50,6 @@ quisk_hardware_hamlib.py
|
||||
quisk_hardware_model.py
|
||||
quisk_hardware_sdr8600.py
|
||||
quisk_hardware_sdriq.py
|
||||
quisk_hardware_sdrmicron.py
|
||||
quisk_utils.py
|
||||
quisk_vna.py
|
||||
quisk_widgets.py
|
||||
@ -99,7 +97,6 @@ winsound.txt
|
||||
./quisk_hardware_model.py
|
||||
./quisk_hardware_sdr8600.py
|
||||
./quisk_hardware_sdriq.py
|
||||
./quisk_hardware_sdrmicron.py
|
||||
./quisk_utils.py
|
||||
./quisk_vna.py
|
||||
./quisk_widgets.py
|
||||
@ -151,6 +148,11 @@ winsound.txt
|
||||
./n2adr/uhf_conf.py
|
||||
./n2adr/uhf_hardware.py
|
||||
./n2adr/uhf_widgets.py
|
||||
./perseuspkg/README.txt
|
||||
./perseuspkg/__init__.py
|
||||
./perseuspkg/quisk_hardware.py
|
||||
./perseuspkg/quisk_widgets.py
|
||||
./perseuspkg/setup.py
|
||||
./sdriqpkg/README.txt
|
||||
./sdriqpkg/__init__.py
|
||||
./sdriqpkg/quisk_hardware.py
|
||||
@ -217,6 +219,13 @@ n2adr/uhf_conf.py
|
||||
n2adr/uhf_hardware.old
|
||||
n2adr/uhf_hardware.py
|
||||
n2adr/uhf_widgets.py
|
||||
perseuspkg/README.txt
|
||||
perseuspkg/__init__.py
|
||||
perseuspkg/makefile
|
||||
perseuspkg/perseus.c
|
||||
perseuspkg/quisk_hardware.py
|
||||
perseuspkg/quisk_widgets.py
|
||||
perseuspkg/setup.py
|
||||
quisk.egg-info/PKG-INFO
|
||||
quisk.egg-info/SOURCES.txt
|
||||
quisk.egg-info/dependency_links.txt
|
||||
@ -228,6 +237,7 @@ sdriqpkg/quisk_hardware.py
|
||||
sdriqpkg/sdriq.c
|
||||
sdriqpkg/sdriq.h
|
||||
sdriqpkg/sdriq.pyd
|
||||
sdrmicronpkg/quisk_hardware.py
|
||||
soapypkg/__init__.py
|
||||
soapypkg/makefile
|
||||
soapypkg/quisk_hardware.py
|
||||
|
266
sdrmicronpkg/quisk_hardware.py
Executable file
266
sdrmicronpkg/quisk_hardware.py
Executable file
@ -0,0 +1,266 @@
|
||||
# -*- coding: cp1251 -*-
|
||||
#
|
||||
# It provides support for the SDR Micron
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import sys, wx, traceback
|
||||
|
||||
import ftd2xx as d2xx
|
||||
import time
|
||||
|
||||
from quisk_hardware_model import Hardware as BaseHardware
|
||||
|
||||
DEBUG = 0
|
||||
|
||||
# https://github.com/Dfinitski/SDR-Micron
|
||||
#
|
||||
# RX control, to device, 32 bytes total
|
||||
# Preamble + 'RX0' + enable + rate + 4 bytes frequency + attenuation + 14 binary zeroes
|
||||
#
|
||||
# where:
|
||||
# Preamble is 7*0x55, 0xd5
|
||||
# bytes:
|
||||
# enable - binary 0 or 1, for enable receiver
|
||||
# rate:
|
||||
# binary
|
||||
# 0 for 48 kHz
|
||||
# 1 for 96 kHz
|
||||
# 2 for 192 kHz
|
||||
# 3 for 240 kHz
|
||||
# 4 for 384 kHz
|
||||
# 5 for 480 kHz
|
||||
# 6 for 640 kHz
|
||||
# 7 for 768 kHz
|
||||
# 8 for 960 kHz
|
||||
# 9 for 1536 kHz
|
||||
# 10 for 1920 kHz
|
||||
#
|
||||
# frequency - 32 bits of tuning frequency, MSB is first
|
||||
# attenuation - binary 0, 10, 20, 30 for needed attenuation
|
||||
#
|
||||
# RX data, to PC, 508 bytes total
|
||||
# Preamble + 'RX0' + FW1 + FW2 + CLIP + 2 zeroes + 492 bytes IQ data
|
||||
#
|
||||
# Where:
|
||||
# FW1 and FW2 - char digits firmware version number
|
||||
# CLIP - ADC overflow indicator, 0 or 1 binary
|
||||
# IQ data for 0 - 7 rate:
|
||||
# 82 IQ pairs formatted as "I2 I1 I0 Q2 Q1 Q0.... ", MSB is first, 24 bits per sample
|
||||
# IQ data for 8 - 10 rate:
|
||||
# 123 IQ pairs formatted as "I1 I0 Q1 Q0..... ", MSB is first, 16 bits per sample
|
||||
#
|
||||
# Band Scope control, to device, 32 bytes total
|
||||
# Preamble + 'BS0' + enable + period + 19 binary zeroes
|
||||
#
|
||||
# Where period is the full frame period in ms, from 50 to 255ms, 100ms is recommended
|
||||
# for 10Hz refresh rate window.
|
||||
#
|
||||
# Band Scope data, to PC, 16384 16bit samples, 32768 bytes by 492 in each packet
|
||||
# Preamble + 'BS0' + FW1 + FW2 + CLIP + PN + 1 zero + 492 bytes BS data
|
||||
#
|
||||
# Where PN is packet number 0, 1, 2, ..., 66
|
||||
# BS data in format "BS1, BS0, BS1, BS0, ...", MSB is first
|
||||
#
|
||||
# 66 packets with PN = 0 - 65 contain 492 bytes each, and 67-th packet with PN = 66 contains
|
||||
# the remaining 296 bytes of data and junk data to full 492 bytes size
|
||||
#
|
||||
|
||||
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
|
||||
################ Receivers SdrMicron, The SDR Micron 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 = 'sdrmicronpkg/quisk_hardware.py'
|
||||
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
sample_rates = [48, 96, 192, 240, 384, 480, 640 ,768, 960, 1536, 1920]
|
||||
def __init__(self, app, conf):
|
||||
BaseHardware.__init__(self, app, conf)
|
||||
self.device = None
|
||||
self.usb = None
|
||||
self.rf_gain_labels = ('RF +10', 'RF 0', 'RF -10', 'RF -20')
|
||||
self.index = 1
|
||||
self.enable = 0
|
||||
self.rate = 0
|
||||
self.att = 10
|
||||
self.freq = 7220000
|
||||
self.old_freq = 0
|
||||
self.sdrmicron_clock = 76800000
|
||||
self.sdrmicron_decim = 1600
|
||||
self.bscope_data = bytearray(0)
|
||||
self.fw_ver = None
|
||||
self.frame_msg = ''
|
||||
|
||||
if conf.fft_size_multiplier == 0:
|
||||
conf.fft_size_multiplier = 3 # Set size needed by VarDecim
|
||||
|
||||
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
|
||||
bs_bytes = 2
|
||||
bs_endian = 1
|
||||
self.InitBscope(bs_bytes, bs_endian, self.sdrmicron_clock, 16384) # Initialize bandscope
|
||||
|
||||
def open(self): # This method must return a string showing whether the open succeeded or failed.
|
||||
enum = d2xx.createDeviceInfoList() # quantity of FTDI devices
|
||||
if(enum==0):
|
||||
return 'Device was not found'
|
||||
for i in range(enum): # Searching and openinq needed device
|
||||
a = d2xx.getDeviceInfoDetail(i)
|
||||
if(a['description']==b'SDR-Micron'):
|
||||
try: self.usb = d2xx.openEx(a['serial'])
|
||||
except:
|
||||
return 'Device was not found'
|
||||
Mode = 64 # Configure FT2232H into 0x40 Sync FIFO Mode
|
||||
self.usb.setBitMode(255, 0) # reset
|
||||
time.sleep(0.1)
|
||||
self.usb.setBitMode(255, Mode) #configure FT2232H into Sync FIFO mode
|
||||
self.usb.setTimeouts(100, 100) # read, write
|
||||
self.usb.setLatencyTimer(2)
|
||||
self.usb.setUSBParameters(32, 32) # in_tx_size, out_tx_size
|
||||
time.sleep(1.5) # waiting for initialisation device
|
||||
data = self.usb.read(self.usb.getQueueStatus()) # clean the usb data buffer
|
||||
self.device = 'Opened'
|
||||
self.frame_msg = a['description'].decode('utf-8') + ' S/N - ' + a['serial'].decode('utf-8')
|
||||
return self.frame_msg
|
||||
return 'Device was not found'
|
||||
|
||||
def close(self):
|
||||
if(self.usb):
|
||||
if(self.device=='Opened'):
|
||||
enable = 0
|
||||
self.device = None
|
||||
self.rx_control_upd()
|
||||
time.sleep(0.5)
|
||||
self.usb.setBitMode(255, 0) # reset
|
||||
self.usb.close()
|
||||
|
||||
def OnButtonRfGain(self, event):
|
||||
btn = event.GetEventObject()
|
||||
n = btn.index
|
||||
self.att = n * 10
|
||||
self.rx_control_upd()
|
||||
|
||||
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
|
||||
if vfo:
|
||||
self.freq = (vfo - self.transverter_offset)
|
||||
if(self.freq!=self.old_freq):
|
||||
self.old_freq = self.freq
|
||||
self.rx_control_upd()
|
||||
return tune, vfo
|
||||
|
||||
def ChangeBand(self, band):
|
||||
# band is a string: "60", "40", "WWV", etc.
|
||||
BaseHardware.ChangeBand(self, band)
|
||||
btn = self.application.BtnRfGain
|
||||
if btn:
|
||||
if band in ('160', '80', '60', '40'):
|
||||
btn.SetLabel('RF -10', True)
|
||||
elif band in ('20',):
|
||||
btn.SetLabel('RF 0', True)
|
||||
else:
|
||||
btn.SetLabel('RF +10', True)
|
||||
|
||||
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
|
||||
rate = self.sample_rates[self.index] * 1000
|
||||
self.rate = self.index
|
||||
if(rate>=960000):
|
||||
rx_bytes = 2
|
||||
rx_endian = 1
|
||||
self.InitSamples(rx_bytes, rx_endian)
|
||||
else:
|
||||
rx_bytes = 3
|
||||
rx_endian = 1
|
||||
self.InitSamples(rx_bytes, rx_endian)
|
||||
self.rx_control_upd()
|
||||
return rate
|
||||
|
||||
def VarDecimRange(self): # Return the lowest and highest sample rate.
|
||||
return (48000, 1920000)
|
||||
|
||||
def StartSamples(self): # called by the sound thread
|
||||
self.enable = 1
|
||||
self.rx_control_upd()
|
||||
self.bscope_control_upd()
|
||||
|
||||
def StopSamples(self): # called by the sound thread
|
||||
self.enable = 0
|
||||
self.rx_control_upd()
|
||||
self.bscope_control_upd()
|
||||
|
||||
def rx_control_upd(self):
|
||||
if(self.device=='Opened'):
|
||||
work = self.freq
|
||||
freq4 = work & 0xFF
|
||||
work = work >> 8
|
||||
freq3 = work & 0xFF
|
||||
work = work >> 8
|
||||
freq2 = work & 0xFF
|
||||
work = work >> 8
|
||||
freq1 = work & 0xFF
|
||||
if sys.version_info.major <= 2:
|
||||
MESSAGE = 7*chr(0x55) + chr(0xd5) + 'RX0' + chr(self.enable) + chr(self.rate)
|
||||
MESSAGE += chr(freq1) + chr(freq2) + chr(freq3) + chr(freq4) + chr(self.att) + 14*chr(0)
|
||||
else:
|
||||
MESSAGE = b"\x55\x55\x55\x55\x55\x55\x55\xd5RX0"
|
||||
MESSAGE += bytes((self.enable, self.rate, freq1, freq2, freq3, freq4, self.att))
|
||||
MESSAGE += bytes(14)
|
||||
try: self.usb.write(MESSAGE)
|
||||
except: print('Error while rx_control_upd')
|
||||
|
||||
def bscope_control_upd(self):
|
||||
if self.device == 'Opened':
|
||||
if sys.version_info.major <= 2:
|
||||
MESSAGE = 7*chr(0x55) + chr(0xd5) + 'BS0' + chr(self.enable) + chr(100) + 19 * chr(0)
|
||||
else:
|
||||
MESSAGE = b"\x55\x55\x55\x55\x55\x55\x55\xd5BS0"
|
||||
MESSAGE += bytes((self.enable, 100))
|
||||
MESSAGE += bytes(19)
|
||||
try: self.usb.write(MESSAGE)
|
||||
except: None
|
||||
|
||||
def GetRxSamples(self): # Read all data from the SDR Micron and process it.
|
||||
if self.device == None:
|
||||
return
|
||||
while (self.usb.getQueueStatus() >= 508):
|
||||
data = self.usb.read(508)
|
||||
data = bytearray(data)
|
||||
if data[8:11] == bytearray(b'RX0'): # Rx I/Q data
|
||||
if data[13]:
|
||||
self.GotClip()
|
||||
if self.fw_ver is None:
|
||||
self.fw_ver = chr(data[11]) + '.' + chr(data[12])
|
||||
self.frame_msg += ' F/W version - ' + self.fw_ver
|
||||
self.application.main_frame.SetConfigText(self.frame_msg)
|
||||
self.AddRxSamples(data[16:])
|
||||
elif data[8:11] == bytearray(b'BS0'): # bandscope data
|
||||
packet_number = data[14]
|
||||
if packet_number == 0: # start of a block of data
|
||||
self.bscope_data = data[16:] # 492 bytes
|
||||
elif packet_number < 66:
|
||||
self.bscope_data += data[16:] # 492 bytes
|
||||
else: # end of a block of data, 296 bytes
|
||||
self.bscope_data += data[16:312]
|
||||
self.AddBscopeSamples(self.bscope_data)
|
||||
|
||||
|
4
setup.py
4
setup.py
@ -8,7 +8,7 @@ import struct
|
||||
# You must define the version here. A title string including
|
||||
# the version will be written to __init__.py and read by quisk.py.
|
||||
|
||||
Version = '4.1.53'
|
||||
Version = '4.1.54'
|
||||
|
||||
fp = open("__init__.py", "w") # write title string
|
||||
fp.write("#Quisk version %s\n" % Version)
|
||||
@ -155,7 +155,7 @@ N1MM+ and software that uses Hamlib.
|
||||
author_email = 'jahlstr@gmail.com',
|
||||
url = 'http://james.ahlstrom.name/quisk/',
|
||||
packages = ['quisk', 'quisk.sdriqpkg', 'quisk.n2adr', 'quisk.softrock', 'quisk.freedvpkg',
|
||||
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg'],
|
||||
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg', 'quisk.perseuspkg'],
|
||||
package_dir = {'quisk' : '.'},
|
||||
package_data = {'' : ['*.txt', '*.html', '*.so', '*.dll']},
|
||||
entry_points = {'gui_scripts' : ['quisk = quisk.quisk:main', 'quisk_vna = quisk.quisk_vna:main']},
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user