499 lines
14 KiB
C
Executable File
499 lines
14 KiB
C
Executable File
/*
|
|
*
|
|
* 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
|
|
|