432 lines
14 KiB
C++
Raw Normal View History

#include <utils/flog.h>
2020-12-22 20:00:51 +01:00
#include <module.h>
2020-11-25 21:52:37 +01:00
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/style.h>
2022-01-21 20:22:13 +01:00
#include <gui/smgui.h>
2020-11-25 21:52:37 +01:00
#include <iio.h>
2020-11-26 19:25:58 +01:00
#include <ad9361.h>
#include <utils/optionlist.h>
2020-11-25 21:52:37 +01:00
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
2020-12-08 04:36:37 +01:00
/* Name: */ "plutosdr_source",
/* Description: */ "PlutoSDR source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 2, 0,
2020-12-08 04:36:37 +01:00
/* Max instances */ 1
2020-11-25 21:52:37 +01:00
};
2020-11-28 23:24:45 +01:00
ConfigManager config;
2020-12-08 04:36:37 +01:00
class PlutoSDRSourceModule : public ModuleManager::Instance {
2020-11-25 21:52:37 +01:00
public:
PlutoSDRSourceModule(std::string name) {
this->name = name;
// Load configuration
config.acquire();
if (config.conf.contains("sampleRate")) {
sampleRate = config.conf["sampleRate"];
}
if (config.conf.contains("bandwidth")) {
bandwidth = config.conf["bandwidth"];
}
if (config.conf.contains("gainMode")) {
gainMode = config.conf["gainMode"];
}
if (config.conf.contains("gain")) {
gain = config.conf["gain"];
}
config.release();
// Define valid samplerates
for (int sr = 1000000; sr <= 61440000; sr += 500000) {
samplerates.define(sr, getBandwdithScaled(sr), sr);
}
samplerates.define(61440000, getBandwdithScaled(61440000.0), 61440000.0);
// Set samplerate ID
if (samplerates.keyExists(sampleRate)) {
srId = samplerates.keyId(sampleRate);
}
else {
srId = 0;
sampleRate = samplerates.value(srId);
}
2020-11-25 21:52:37 +01:00
// Define valid bandwidths
bandwidths.define(0, "Auto", 0);
for (int bw = 1000000.0; bw <= 52000000; bw += 500000) {
bandwidths.define(bw, getBandwdithScaled(bw), bw);
}
// Set bandwidth ID
if (bandwidths.keyExists(bandwidth)) {
bwId = bandwidths.keyId(bandwidth);
}
else {
bwId = 0;
bandwidth = bandwidths.value(bwId);
}
// Define gain modes
gainModes.define(0, "Manual", "manual");
gainModes.define(1, "Fast Attack", "fast_attack");
gainModes.define(2, "Slow Attack", "slow_attack");
gainModes.define(3, "Hybrid", "hybrid");
// Enumerate devices
refresh();
// Select device
// TODO
// Register source
2020-11-25 21:52:37 +01:00
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
handler.menuHandler = menuHandler;
handler.startHandler = start;
handler.stopHandler = stop;
handler.tuneHandler = tune;
handler.stream = &stream;
sigpath::sourceManager.registerSource("PlutoSDR", &handler);
}
~PlutoSDRSourceModule() {
2021-07-26 03:11:51 +02:00
stop(this);
sigpath::sourceManager.unregisterSource("PlutoSDR");
2020-11-25 21:52:37 +01:00
}
2021-07-26 03:11:51 +02:00
void postInit() {}
2020-12-08 04:36:37 +01:00
void enable() {
enabled = true;
}
void disable() {
enabled = true;
}
bool isEnabled() {
return enabled;
}
2020-11-25 21:52:37 +01:00
private:
std::string getBandwdithScaled(double bw) {
char buf[1024];
if (bw >= 1000000.0) {
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
}
else if (bw >= 1000.0) {
sprintf(buf, "%.1lfKHz", bw / 1000.0);
}
else {
sprintf(buf, "%.1lfHz", bw);
}
return std::string(buf);
}
void refresh() {
// Clear device list
devices.clear();
// Create scan context
iio_scan_context* sctx = iio_create_scan_context(NULL, 0);
if (!sctx) {
flog::error("Failed get scan context");
return;
}
// Enumerate devices
iio_context_info** ctxInfoList;
ssize_t count = iio_scan_context_get_info_list(sctx, &ctxInfoList);
if (count < 0) {
flog::error("Failed to enumerate contexts");
return;
}
for (ssize_t i = 0; i < count; i++) {
iio_context_info* info = ctxInfoList[i];
std::string desc = iio_context_info_get_description(info);
std::string uri = iio_context_info_get_uri(info);
2024-01-28 00:25:46 +01:00
devices.define(uri, desc, uri);
}
iio_context_info_list_free(ctxInfoList);
// Destroy scan context
iio_scan_context_destroy(sctx);
2024-01-28 00:25:46 +01:00
#ifdef __ANDROID__
// On Android, a default IP entry must be made (TODO: This is not ideal since the IP cannot be changed)
const char* androidURI = "ip:192.168.2.1";
devices.define(androidURI, "Default (192.168.2.1)", androidURI);
#endif
}
void select(const std::string& nuri) {
uri = nuri;
}
2020-11-25 21:52:37 +01:00
static void menuSelected(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
flog::info("PlutoSDRSourceModule '{0}': Menu Select!", _this->name);
2020-11-25 21:52:37 +01:00
}
static void menuDeselected(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
flog::info("PlutoSDRSourceModule '{0}': Menu Deselect!", _this->name);
2020-11-25 21:52:37 +01:00
}
2020-11-25 21:52:37 +01:00
static void start(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
2021-07-26 04:16:00 +02:00
if (_this->running) { return; }
2024-01-27 21:35:13 +01:00
// Open context
_this->ctx = iio_create_context_from_uri(_this->uri.c_str());
2020-11-25 21:52:37 +01:00
if (_this->ctx == NULL) {
flog::error("Could not open pluto ({})", _this->uri);
2020-11-25 21:52:37 +01:00
return;
}
2024-01-27 21:35:13 +01:00
// Get phy and device handle
_this->phy = iio_context_find_device(_this->ctx, "ad9361-phy");
2020-11-25 21:52:37 +01:00
if (_this->phy == NULL) {
flog::error("Could not connect to pluto phy");
2020-11-25 21:52:37 +01:00
iio_context_destroy(_this->ctx);
return;
}
_this->dev = iio_context_find_device(_this->ctx, "cf-ad9361-lpc");
if (_this->dev == NULL) {
flog::error("Could not connect to pluto dev");
2020-11-25 21:52:37 +01:00
iio_context_destroy(_this->ctx);
return;
}
// Get RX channels
2024-01-27 21:35:13 +01:00
_this->rxChan = iio_device_find_channel(_this->phy, "voltage0", false);
_this->rxLO = iio_device_find_channel(_this->phy, "altvoltage0", true);
2024-01-27 21:35:13 +01:00
// Enable RX LO and disable TX
2020-11-26 19:25:58 +01:00
iio_channel_attr_write_bool(iio_device_find_channel(_this->phy, "altvoltage1", true), "powerdown", true);
iio_channel_attr_write_bool(_this->rxLO, "powerdown", false);
2020-11-26 19:25:58 +01:00
// Configure RX channel
2024-01-27 21:35:13 +01:00
iio_channel_attr_write(_this->rxChan, "rf_port_select", "A_BALANCED");
iio_channel_attr_write_longlong(_this->rxLO, "frequency", round(_this->freq)); // Freq
iio_channel_attr_write_bool(_this->rxChan, "filter_fir_en", true); // Digital filter
iio_channel_attr_write_longlong(_this->rxChan, "sampling_frequency", round(_this->sampleRate)); // Sample rate
iio_channel_attr_write_longlong(_this->rxChan, "hardwaregain", round(_this->gain)); // Gain
iio_channel_attr_write(_this->rxChan, "gain_control_mode", _this->gainModes.value(_this->gainMode).c_str()); // Gain mode
_this->setBandwidth(_this->bandwidth);
// Configure the ADC filters
2020-11-26 19:25:58 +01:00
ad9361_set_bb_rate(_this->phy, round(_this->sampleRate));
// Start worker thread
2020-11-25 21:52:37 +01:00
_this->running = true;
_this->workerThread = std::thread(worker, _this);
flog::info("PlutoSDRSourceModule '{0}': Start!", _this->name);
2020-11-25 21:52:37 +01:00
}
2020-11-25 21:52:37 +01:00
static void stop(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
2021-07-26 04:16:00 +02:00
if (!_this->running) { return; }
// Stop worker thread
2020-11-25 21:52:37 +01:00
_this->running = false;
_this->stream.stopWriter();
_this->workerThread.join();
_this->stream.clearWriteStop();
// Close device
2020-11-25 21:52:37 +01:00
if (_this->ctx != NULL) {
iio_context_destroy(_this->ctx);
_this->ctx = NULL;
}
flog::info("PlutoSDRSourceModule '{0}': Stop!", _this->name);
2020-11-25 21:52:37 +01:00
}
2020-11-25 21:52:37 +01:00
static void tune(double freq, void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
2020-11-26 19:25:58 +01:00
_this->freq = freq;
2020-11-25 21:52:37 +01:00
if (_this->running) {
// Tune device
iio_channel_attr_write_longlong(iio_device_find_channel(_this->phy, "altvoltage0", true), "frequency", round(freq));
2020-11-25 21:52:37 +01:00
}
flog::info("PlutoSDRSourceModule '{0}': Tune: {1}!", _this->name, freq);
2020-11-25 21:52:37 +01:00
}
2020-11-25 21:52:37 +01:00
static void menuHandler(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
2022-01-21 20:22:13 +01:00
if (_this->running) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo("##plutosdr_dev_sel", &_this->devId, _this->devices.txt)) {
_this->select(_this->devices.value(_this->devId));
// TODO: Save
2020-11-28 23:24:45 +01:00
}
if (SmGui::Combo(CONCAT("##_pluto_sr_", _this->name), &_this->srId, _this->samplerates.txt)) {
_this->sampleRate = _this->samplerates.value(_this->srId);
2021-04-10 15:05:45 +02:00
core::setInputSampleRate(_this->sampleRate);
config.acquire();
2021-04-10 15:05:45 +02:00
config.conf["sampleRate"] = _this->sampleRate;
config.release(true);
2020-11-26 19:25:58 +01:00
}
// Refresh button
SmGui::SameLine();
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Button(CONCAT("Refresh##_pluto_refr_", _this->name))) {
_this->refresh();
_this->select(_this->uri);
}
2022-01-21 20:22:13 +01:00
if (_this->running) { SmGui::EndDisabled(); }
2020-11-25 21:52:37 +01:00
SmGui::LeftLabel("Bandwidth");
SmGui::FillWidth();
if (SmGui::Combo(CONCAT("##_pluto_bw_", _this->name), &_this->bwId, _this->bandwidths.txt)) {
_this->bandwidth = _this->bandwidths.value(_this->bwId);
if (_this->running) {
_this->setBandwidth(_this->bandwidth);
}
config.acquire();
config.conf["bandwidth"] = _this->bandwidth;
config.release(true);
}
2022-01-21 20:22:13 +01:00
SmGui::LeftLabel("Gain Mode");
SmGui::FillWidth();
SmGui::ForceSync();
if (SmGui::Combo(CONCAT("##_gainmode_select_", _this->name), &_this->gainMode, _this->gainModes.txt)) {
2020-11-26 19:25:58 +01:00
if (_this->running) {
2024-01-27 21:35:13 +01:00
iio_channel_attr_write(_this->rxChan, "gain_control_mode", _this->gainModes.value(_this->gainMode).c_str());
2020-11-26 19:25:58 +01:00
}
config.acquire();
2020-11-28 23:24:45 +01:00
config.conf["gainMode"] = _this->gainMode;
config.release(true);
2020-11-25 21:52:37 +01:00
}
2022-01-21 20:22:13 +01:00
SmGui::LeftLabel("PGA Gain");
if (_this->gainMode) { SmGui::BeginDisabled(); }
SmGui::FillWidth();
if (SmGui::SliderFloat(CONCAT("##_gain_select_", _this->name), &_this->gain, 0, 76)) {
2020-11-25 21:52:37 +01:00
if (_this->running) {
2024-01-27 21:35:13 +01:00
iio_channel_attr_write_longlong(_this->rxChan, "hardwaregain", round(_this->gain));
2020-11-25 21:52:37 +01:00
}
config.acquire();
2020-11-28 23:24:45 +01:00
config.conf["gain"] = _this->gain;
config.release(true);
2020-11-25 21:52:37 +01:00
}
2022-01-21 20:22:13 +01:00
if (_this->gainMode) { SmGui::EndDisabled(); }
2020-11-25 21:52:37 +01:00
}
void setBandwidth(int bw) {
if (bw > 0) {
iio_channel_attr_write_longlong(rxChan, "rf_bandwidth", bw);
}
else {
iio_channel_attr_write_longlong(rxChan, "rf_bandwidth", std::min<int>(sampleRate, 52000000));
}
}
2020-11-25 21:52:37 +01:00
static void worker(void* ctx) {
PlutoSDRSourceModule* _this = (PlutoSDRSourceModule*)ctx;
int blockSize = _this->sampleRate / 200.0f;
2020-11-25 21:52:37 +01:00
// Acquire channels
iio_channel* rx0_i = iio_device_find_channel(_this->dev, "voltage0", 0);
iio_channel* rx0_q = iio_device_find_channel(_this->dev, "voltage1", 0);
// Start streaming
2020-11-25 21:52:37 +01:00
iio_channel_enable(rx0_i);
iio_channel_enable(rx0_q);
// Allocate buffer
iio_buffer* rxbuf = iio_device_create_buffer(_this->dev, blockSize, false);
2020-11-25 21:52:37 +01:00
if (!rxbuf) {
flog::error("Could not create RX buffer");
2020-11-25 21:52:37 +01:00
return;
}
// Receive loop
2020-11-25 21:52:37 +01:00
while (true) {
// Read samples
2020-11-25 21:52:37 +01:00
iio_buffer_refill(rxbuf);
// Get buffer pointer
2020-11-25 21:52:37 +01:00
int16_t* buf = (int16_t*)iio_buffer_first(rxbuf, rx0_i);
// Convert samples to CF32
volk_16i_s32f_convert_32f((float*)_this->stream.writeBuf, buf, 32768.0f, blockSize * 2);
2021-06-30 02:48:36 +02:00
// Send out the samples
if (!_this->stream.swap(blockSize)) { break; };
2020-11-25 21:52:37 +01:00
}
// Stop streaming
iio_channel_disable(rx0_i);
iio_channel_disable(rx0_q);
// Free buffer
2020-11-25 21:52:37 +01:00
iio_buffer_destroy(rxbuf);
}
std::string name;
2020-12-08 04:36:37 +01:00
bool enabled = true;
2020-11-25 21:52:37 +01:00
dsp::stream<dsp::complex_t> stream;
SourceManager::SourceHandler handler;
std::thread workerThread;
iio_context* ctx = NULL;
iio_device* phy = NULL;
iio_device* dev = NULL;
iio_channel* rxLO = NULL;
2024-01-27 21:35:13 +01:00
iio_channel* rxChan = NULL;
2020-11-25 21:52:37 +01:00
bool running = false;
std::string uri = "";
2020-11-25 21:52:37 +01:00
double freq;
float sampleRate = 4000000;
int bandwidth = 0;
2020-11-25 21:52:37 +01:00
int gainMode = 0;
float gain = 0;
int devId = 0;
int srId = 0;
int bwId = 0;
OptionList<std::string, std::string> devices;
OptionList<int, double> samplerates;
OptionList<int, double> bandwidths;
OptionList<int, std::string> gainModes;
2020-11-25 21:52:37 +01:00
};
MOD_EXPORT void _INIT_() {
json defConf = {};
config.setPath(core::args["root"].s() + "/plutosdr_source_config.json");
2020-11-28 23:24:45 +01:00
config.load(defConf);
config.enableAutoSave();
2020-11-25 21:52:37 +01:00
}
2020-12-08 04:36:37 +01:00
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
2020-11-25 21:52:37 +01:00
return new PlutoSDRSourceModule(name);
}
2020-12-08 04:36:37 +01:00
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
2020-11-25 21:52:37 +01:00
delete (PlutoSDRSourceModule*)instance;
}
2020-12-08 04:36:37 +01:00
MOD_EXPORT void _END_() {
2020-11-28 23:24:45 +01:00
config.disableAutoSave();
config.save();
2020-11-25 21:52:37 +01:00
}