/* * * Microtelecom perseus HF receiver * * access module: exposes Python functions needed in quisk_hardware.py * to control hardware * */ #include #include #include #include #include #include #include #include #include #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