Improved electronic calibration

- Added automatic port detection mode
- Faster coefficient readout
- LibreCAL using asynchronous libusb API
This commit is contained in:
Jan Käberich 2024-09-25 15:30:42 +02:00
parent 392cb6dc3f
commit 2c639d8080
14 changed files with 570 additions and 314 deletions

View file

@ -336,6 +336,7 @@ void CalDevice::loadCoefficientSetsThreadFast(QStringList names)
auto createCoefficient = [&](QString setName, QString paramName) -> CoefficientSet::Coefficient* {
CoefficientSet::Coefficient *c = new CoefficientSet::Coefficient();
// ask for the whole set at once
usb->flushReceived();
usb->send(":COEFF:GET? "+setName+" "+paramName);
// handle incoming lines
if(paramName.endsWith("THROUGH")) {
@ -346,7 +347,7 @@ void CalDevice::loadCoefficientSetsThreadFast(QStringList names)
c->t.setFilename("LibreCAL/"+paramName);
while(true) {
QString line;
if(!usb->receive(&line, 100)) {
if(!usb->receive(&line)) {
// failed to receive something, abort
return c;
}

View file

@ -41,15 +41,21 @@ LibreCALDialog::LibreCALDialog(Calibration *cal) :
connect(device, &CalDevice::updateCoefficientsPercent, ui->progressCoeff, &QProgressBar::setValue);
connect(device, &CalDevice::updateCoefficientsDone, this, [=](bool success){
busy = false;
updateCalibrationStartStatus();
if(success) {
ui->progressCoeff->setValue(100);
ui->lCoefficientStatus->setText("Coefficients loaded.");
ui->lCalibrationStatus->setText("Coefficients loaded.");
coeffSet = device->getCoefficientSets()[0];
if(validateCoefficients()) {
startCalibration();
} else {
enableUI();
}
} else {
ui->progressCal->setValue(0);
ui->lCoefficientStatus->setText("Failed to load coefficients");
ui->lCalibrationStatus->setText("Failed to load coefficients");
}
updateCalibrationStartStatus();
}, Qt::QueuedConnection);
ui->cbCoefficients->clear();
@ -58,6 +64,10 @@ LibreCALDialog::LibreCALDialog(Calibration *cal) :
ui->cbCoefficients->addItem(c);
}
ui->cbCoefficients->setEnabled(true);
// select first available coefficient set
if(ui->cbCoefficients->count() > 1) {
ui->cbCoefficients->setCurrentIndex(1);
}
} else {
ui->cbCoefficients->clear();
ui->cbCoefficients->setEnabled(false);
@ -68,19 +78,9 @@ LibreCALDialog::LibreCALDialog(Calibration *cal) :
connect(ui->cbCoefficients, &QComboBox::currentTextChanged, [=](){
// no coefficient set selected
ui->progressCoeff->setValue(0);
ui->lCoefficientStatus->setText("No coefficients loaded");
coeffSet = CalDevice::CoefficientSet();
ui->lCalibrationStatus->setText("No coefficients loaded");
updateCalibrationStartStatus();
if(ui->cbCoefficients->currentIndex() > 0) {
if(!device) {
qWarning() << "Coefficients selected without connected device";
return;
}
busy = true;
ui->lCoefficientStatus->setText("Loading coefficients...");
device->loadCoefficientSets({ui->cbCoefficients->currentText()});
}
});
auto deviceList = USBDevice::GetDevices();
@ -88,7 +88,8 @@ LibreCALDialog::LibreCALDialog(Calibration *cal) :
ui->cbDevice->addItem(device);
}
connect(ui->start, &QPushButton::clicked, this, &LibreCALDialog::startCalibration);
connect(ui->start, &QPushButton::clicked, this, &LibreCALDialog::determineAutoPorts);
connect(this, &LibreCALDialog::autoPortComplete, this, &LibreCALDialog::loadCoefficients);
updateCalibrationStartStatus();
updateDeviceStatus();
@ -105,29 +106,28 @@ LibreCALDialog::~LibreCALDialog()
delete ui;
}
void LibreCALDialog::updateCalibrationStartStatus()
bool LibreCALDialog::validatePortSelection(bool autoAllowed)
{
bool canStart = true;
QString status = "Ready to start";
if(!device) {
status = "Not connected to a LibreCAL device.";
canStart = false;
}
set<int> usedCalPorts;
if(canStart) {
// Check port mapping for duplicate entries (and at least one used port)
for(auto port : portAssignment) {
if(port < 1) {
// skip unused ports
continue;
}
if(usedCalPorts.count(port)) {
status = "LibreCAL port "+QString::number(port)+" is assigned to multiple VNA ports.";
canStart = false;
break;
} else {
usedCalPorts.insert(port);
}
QString status;
bool canStart = true;
// Check port mapping for duplicate entries (and at least one used port)
for(auto port : portAssignment) {
if(autoAllowed && port == -1) {
// auto port, to be determined later
usedCalPorts.insert(port);
// skip duplicate selection
continue;
} else if(port < 1) {
// skip unused ports
continue;
}
if(usedCalPorts.count(port)) {
status = "LibreCAL port "+QString::number(port)+" is assigned to multiple VNA ports.";
canStart = false;
break;
} else {
usedCalPorts.insert(port);
}
}
if(canStart) {
@ -137,68 +137,104 @@ void LibreCALDialog::updateCalibrationStartStatus()
canStart = false;
}
}
if(canStart) {
// check if coefficients have been loaded
if(coeffSet.opens.size() != device->getNumPorts()) {
status = "Coefficients not loaded";
if(!canStart) {
ui->lCalibrationStatus->setText(status);
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
}
return canStart;
}
bool LibreCALDialog::validateCoefficients()
{
bool canStart = true;
QString status;
double coeffMinFreq = numeric_limits<double>::max();
double coeffMaxFreq = numeric_limits<double>::lowest();
auto checkCoefficient = [&](CalDevice::CoefficientSet::Coefficient *c) -> bool {
if(c->t.points() == 0) {
return false;
} else {
if(c->t.minFreq() < coeffMinFreq) {
coeffMinFreq = c->t.minFreq();
}
if(c->t.maxFreq() > coeffMaxFreq) {
coeffMaxFreq = c->t.maxFreq();
}
return true;
}
};
set<int> usedCalPorts;
// Check port mapping for duplicate entries (and at least one used port)
for(auto port : portAssignment) {
if(port > 0) {
usedCalPorts.insert(port);
}
}
// check if coefficients for all ports are available
for(auto i : usedCalPorts) {
// Check if OSL coefficients are there
if(!checkCoefficient(coeffSet.opens[i-1])) {
status = "Open coefficient for LibreCAL port "+QString::number(i)+" is missing.";
canStart = false;
break;
}
if(!checkCoefficient(coeffSet.shorts[i-1])) {
status = "Short coefficient for LibreCAL port "+QString::number(i)+" is missing.";
canStart = false;
break;
}
if(!checkCoefficient(coeffSet.loads[i-1])) {
status = "Load coefficient for LibreCAL port "+QString::number(i)+" is missing.";
canStart = false;
break;
}
for(auto j : usedCalPorts) {
if(j <= i) {
continue;
}
if(!checkCoefficient(coeffSet.getThrough(i,j))) {
status = "Through coefficient for LibreCAL port "+QString::number(i)+" to "+QString::number(j)+" is missing.";
canStart = false;
break;
}
}
}
if(!canStart) {
ui->lCalibrationStatus->setText(status);
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
}
return canStart;
}
void LibreCALDialog::updateCalibrationStartStatus()
{
bool canStart = true;
if(!device) {
canStart = false;
ui->lCalibrationStatus->setText("Not connected to a LibreCAL device");
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
}
if(canStart) {
double coeffMinFreq = numeric_limits<double>::max();
double coeffMaxFreq = numeric_limits<double>::lowest();
auto checkCoefficient = [&](CalDevice::CoefficientSet::Coefficient *c) -> bool {
if(c->t.points() == 0) {
return false;
} else {
if(c->t.minFreq() < coeffMinFreq) {
coeffMinFreq = c->t.minFreq();
}
if(c->t.maxFreq() > coeffMaxFreq) {
coeffMaxFreq = c->t.maxFreq();
}
return true;
}
};
// check if coefficients for all ports are available
for(auto i : usedCalPorts) {
// Check if OSL coefficients are there
if(!checkCoefficient(coeffSet.opens[i-1])) {
status = "Open coefficient for LibreCAL port "+QString::number(i)+" is missing.";
canStart = false;
break;
}
if(!checkCoefficient(coeffSet.shorts[i-1])) {
status = "Short coefficient for LibreCAL port "+QString::number(i)+" is missing.";
canStart = false;
break;
}
if(!checkCoefficient(coeffSet.loads[i-1])) {
status = "Load coefficient for LibreCAL port "+QString::number(i)+" is missing.";
canStart = false;
break;
}
for(auto j : usedCalPorts) {
if(j <= i) {
continue;
}
if(!checkCoefficient(coeffSet.getThrough(i,j))) {
status = "Through coefficient for LibreCAL port "+QString::number(i)+" to "+QString::number(j)+" is missing.";
canStart = false;
break;
}
}
if(ui->cbCoefficients->currentIndex() == 0) {
canStart = false;
ui->lCalibrationStatus->setText("No coefficient set selected");
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
}
}
ui->lCalibrationStatus->setText(status);
if(canStart) {
canStart = validatePortSelection(true);
}
ui->start->setEnabled(canStart);
if(canStart) {
ui->lCalibrationStatus->setText("Ready to start");
ui->lCalibrationStatus->setStyleSheet("QLabel { color : black; }");
} else {
ui->lCalibrationStatus->setStyleSheet("QLabel { color : red; }");
}
}
@ -222,6 +258,153 @@ void LibreCALDialog::updateDeviceStatus()
}
}
void LibreCALDialog::determineAutoPorts()
{
ui->progressCal->setValue(0);
ui->lCalibrationStatus->setText("Autodetecting port connections...");
ui->lCalibrationStatus->setStyleSheet("QLabel { color : green; }");
// check if any ports are set to auto
bool usesAuto = false;
for(auto port : portAssignment) {
if(port == -1) {
usesAuto = true;
break;
}
}
if(usesAuto) {
driver = DeviceDriver::getActiveDriver();
emit driver->acquireControl();
// Determine ports by setting all ports to open and then switching them to short one at a time while observing the change in the VNA measurement
for(unsigned int i=1;i<=device->getNumPorts();i++) {
device->setStandard(i, CalDevice::Standard::Type::Open);
}
autoPortMeasurements.clear();
startSweep();
} else {
// no auto ports
emit autoPortComplete();
}
}
void LibreCALDialog::loadCoefficients()
{
// validate ports again, at this point no "auto" selection is allowed anymore
if(!validatePortSelection(false)) {
enableUI();
updateCalibrationStartStatus();
} else {
// can continue with loading coefficients
// they might already be loaded from a previous calibration run, check first
// if(!validateCoefficients()) {
// TODO only load required coefficients
ui->progressCal->setValue(0);
ui->lCalibrationStatus->setText("Loading coefficients...");
ui->lCalibrationStatus->setStyleSheet("QLabel { color : green; }");
busy = true;
device->loadCoefficientSets({ui->cbCoefficients->currentText()});
// } else {
// // can proceed with calibration directly
// startCalibration();
// }
}
}
void LibreCALDialog::handleIncomingMeasurement(DeviceDriver::VNAMeasurement m)
{
stopSweep();
autoPortMeasurements.push_back(m);
auto nextPort = autoPortMeasurements.size();
// switch LibreCAL to next port
if(nextPort <= device->getNumPorts()) {
device->setStandard(nextPort, CalDevice::Standard::Type::Short);
}
if(nextPort > 1) {
device->setStandard(nextPort-1, CalDevice::Standard::Type::Open);
}
if(nextPort > device->getNumPorts()) {
// auto port measurements complete
// reset LibreCAL ports
for(unsigned int i=1;i<=device->getNumPorts();i++) {
device->setStandard(i, CalDevice::Standard::Type::None);
}
// evaluate results
for(unsigned int p=0;p<portAssignment.size();p++) {
auto port = portAssignment[p];
if(port != -1) {
// not set to auto, ignore
continue;
}
auto param = "S"+QString::number(p+1)+QString::number(p+1);
std::vector<std::complex<double>> measurements;
for(auto m : autoPortMeasurements) {
if(m.measurements.count(param)) {
measurements.push_back(m.measurements[param]);
}
}
if(measurements.size() != device->getNumPorts()+1) {
// not all measurements available (maybe the port has no stimulus?), set to unused
portAssignmentComboBoxes[p]->setCurrentIndex(0);
continue;
}
// got all required measurements, determine which one deviates the most from the baseline
double maxDeviation = 0.0;
int maxDevIndex = 0;
for(unsigned int i=1;i<=device->getNumPorts();i++) {
auto diff = abs(measurements[i] - measurements[0]);
if (diff > maxDeviation) {
maxDeviation = diff;
maxDevIndex = i;
}
}
constexpr double minRequiredDeviation = 0.25;
if(maxDeviation > minRequiredDeviation) {
portAssignmentComboBoxes[p]->setCurrentIndex(maxDevIndex+1);
} else {
// not enough deviation, probably unused
portAssignmentComboBoxes[p]->setCurrentIndex(0);
}
}
emit driver->releaseControl();
emit autoPortComplete();
} else {
// trigger the next measurement
startSweep();
}
}
void LibreCALDialog::startSweep()
{
// set up a sweep with a single measurement point at the start frequency
auto info = driver->getInfo();
DeviceDriver::VNASettings s = {};
s.dBmStart = info.Limits.VNA.maxdBm;
s.dBmStop = info.Limits.VNA.maxdBm;
auto freq = info.Limits.VNA.minFreq == 0 ? 100000 : info.Limits.VNA.minFreq;
s.freqStart = freq;
s.freqStop = freq;
s.IFBW = 100;
s.logSweep = false;
s.points = 1;
for(unsigned int i=1;i<=info.Limits.VNA.ports;i++) {
s.excitedPorts.push_back(i);
}
driver->setVNA(s, [=](bool){
connect(driver, &DeviceDriver::VNAmeasurementReceived, this, &LibreCALDialog::handleIncomingMeasurement, Qt::DirectConnection);
});
}
void LibreCALDialog::stopSweep()
{
disconnect(driver, &DeviceDriver::VNAmeasurementReceived, this, &LibreCALDialog::handleIncomingMeasurement);
driver->setIdle();
}
void LibreCALDialog::startCalibration()
{
disableUI();
@ -441,7 +624,7 @@ void LibreCALDialog::createPortAssignmentUI()
if(device) {
calPorts = device->getNumPorts();
}
QStringList choices = {"Unused"};
QStringList choices = {"Unused", "Auto"};
for(int i=1;i<=calPorts;i++) {
choices.push_back("Port "+QString::number(i));
}
@ -450,16 +633,16 @@ void LibreCALDialog::createPortAssignmentUI()
auto comboBox = new QComboBox();
comboBox->addItems(choices);
connect(comboBox, qOverload<int>(&QComboBox::currentIndexChanged), [=](){
portAssignment[p-1] = comboBox->currentIndex();
if(comboBox->currentText().startsWith("Unused")) {
portAssignment[p-1] = 0;
} else if(comboBox->currentText().startsWith("Auto")) {
portAssignment[p-1] = -1;
} else if(comboBox->currentText().startsWith("Port")) {
portAssignment[p-1] = comboBox->currentText().back().digitValue();
}
emit portAssignmentChanged();
});
// try to set the default
if(comboBox->count() > (int) p) {
comboBox->setCurrentIndex(p);
} else {
// port not available, set to unused
comboBox->setCurrentIndex(0);
}
comboBox->setCurrentIndex(1);
layout->addRow(label, comboBox);
portAssignmentComboBoxes.push_back(comboBox);
}

View file

@ -3,6 +3,7 @@
#include "Calibration/calibration.h"
#include "caldevice.h"
#include "Device/devicedriver.h"
#include <QDialog>
#include <QTimer>
@ -23,10 +24,20 @@ public:
private:
signals:
void portAssignmentChanged();
void autoPortComplete();
private slots:
bool validatePortSelection(bool autoAllowed);
bool validateCoefficients();
void updateCalibrationStartStatus();
void updateDeviceStatus();
void determineAutoPorts();
void loadCoefficients();
void startCalibration();
// auto port slots
void handleIncomingMeasurement(DeviceDriver::VNAMeasurement m);
void startSweep();
void stopSweep();
private:
void disableUI();
void enableUI();
@ -37,8 +48,11 @@ private:
CalDevice::CoefficientSet coeffSet;
QTimer updateTimer;
bool busy;
// 0 for unused port, -1 for auto port, otherwise the port number
std::vector<int> portAssignment;
std::vector<QComboBox*> portAssignmentComboBoxes;
std::vector<DeviceDriver::VNAMeasurement> autoPortMeasurements;
DeviceDriver *driver;
int measurementsTaken;
};

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>346</width>
<height>395</height>
<width>399</width>
<height>405</height>
</rect>
</property>
<property name="windowTitle">
@ -58,20 +58,6 @@
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="lCoefficientStatus">
<property name="text">
<string>COEFFICIENT_STATUS_PLACEHOLDER</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressCoeff">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -116,10 +102,47 @@
<string>Start</string>
</property>
<property name="icon">
<iconset theme="media-playback-start"/>
<iconset theme="media-playback-start">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Coefficients:</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressCoeff">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Calibration:</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressCal">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="lCalibrationStatus">
<property name="text">
@ -127,13 +150,6 @@
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressCal">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View file

@ -6,6 +6,7 @@
#include <QDebug>
#include <QString>
#include <QMessageBox>
#include <QDateTime>
#include <mutex>
using namespace std;
@ -21,7 +22,6 @@ static constexpr USBID IDs[] = {
USBDevice::USBDevice(QString serial)
{
rx_cnt = 0;
m_handle = nullptr;
libusb_init(&m_context);
#if LIBUSB_API_VERSION >= 0x01000106
@ -67,18 +67,28 @@ USBDevice::USBDevice(QString serial)
throw std::runtime_error(message.toStdString());
}
qInfo() << "USB connection established" << Qt::flush;
connected = true;
m_receiveThread = new std::thread(&USBDevice::USBHandleThread, this);
usbBuffer = new USBInBuffer(m_handle, LIBUSB_ENDPOINT_IN | 0x03, 65536);
connect(usbBuffer, &USBInBuffer::DataReceived, this, &USBDevice::ReceivedData, Qt::DirectConnection);
}
USBDevice::~USBDevice()
{
delete usbBuffer;
connected = false;
libusb_release_interface(m_handle, 2);
libusb_close(m_handle);
m_receiveThread->join();
libusb_exit(m_context);
delete m_receiveThread;
}
bool USBDevice::Cmd(QString cmd)
{
QString rcv;
flushReceived();
bool success = send(cmd) && receive(&rcv);
if(success && rcv == "") {
// empty response expected by commad
@ -92,6 +102,7 @@ bool USBDevice::Cmd(QString cmd)
QString USBDevice::Query(QString query)
{
flushReceived();
if(send(query)) {
QString rcv;
if(receive(&rcv)) {
@ -201,7 +212,7 @@ void USBDevice::SearchDevices(std::function<bool (libusb_device_handle *, QStrin
bool USBDevice::send(const QString &s)
{
qDebug() << "Send:"<<s;
// qDebug() << "Send:"<<s;
unsigned char data[s.size()+2];
memcpy(data, s.toLatin1().data(), s.size());
memcpy(&data[s.size()], "\r\n", 2);
@ -216,67 +227,58 @@ bool USBDevice::send(const QString &s)
bool USBDevice::receive(QString *s, unsigned int timeout)
{
auto checkForCompleteLine = [this, s]() -> bool {
for(unsigned int i=1;i<rx_cnt;i++) {
if(rx_buf[i] == '\n' && rx_buf[i-1] == '\r') {
rx_buf[i-1] = '\0';
if(s) {
*s = QString(rx_buf);
// qDebug() << "Receive:"<<*s;
}
memmove(rx_buf, &rx_buf[i+1], sizeof(rx_buf) - (i+1));
rx_cnt -= i+1;
rx_buf[rx_cnt] = 0;
// qDebug() << "Remaining in buffer:" << QString(rx_buf);
return true;
}
// check if we already have a line queued
unique_lock<mutex> lck(mtx);
while(lineBuffer.size() == 0) {
// needs to wait for an incoming line
using namespace std::chrono_literals;
if(cv.wait_for(lck, std::chrono::milliseconds(timeout)) == cv_status::timeout) {
qWarning() << "Timed out while waiting for received line";
return false;
}
return false;
};
int res = 0;
if(!checkForCompleteLine()) {
// needs to receive data
int actual;
do {
res = libusb_bulk_transfer(m_handle, LIBUSB_ENDPOINT_IN | 0x03, (unsigned char*) &rx_buf[rx_cnt], 512, &actual, timeout);
rx_cnt += actual;
// qDebug() << "received" << actual << "bytes. Total in buffer:" << rx_cnt;
if(checkForCompleteLine()) {
return true;
}
} while(res == 0);
}
return res == 0;
// char data[512];
// memset(data, 0, sizeof(data));
// int actual;
// int rcvCnt = 0;
// bool endOfLineFound = false;
// int res;
// do {
// res = libusb_bulk_transfer(m_handle, LIBUSB_ENDPOINT_IN | 0x03, (unsigned char*) &data[rcvCnt], sizeof(data) - rcvCnt, &actual, 2000);
// for(int i=rcvCnt;i<rcvCnt+actual;i++) {
// if(i == 0) {
// continue;
// }
// if(data[i] == '\n' && data[i-1] == '\r') {
// endOfLineFound = true;
// data[i-1] = '\0';
// break;
// }
// }
// rcvCnt += actual;
// } while(res == 0 && !endOfLineFound);
// if(res == 0) {
// if(s) {
// *s = QString(data);
//// qDebug() << "Receive:"<<*s;
// }
// return true;
// } else {
// return false;
// }
*s = lineBuffer.takeFirst();
return true;
}
void USBDevice::ReceivedData()
{
uint16_t handled_len;
// qDebug() << "received new data";
unique_lock<mutex> lck(mtx);
do {
handled_len = 0;
auto firstLinebreak = (uint8_t*) memchr(usbBuffer->getBuffer(), '\n', usbBuffer->getReceived());
if(firstLinebreak) {
handled_len = firstLinebreak - usbBuffer->getBuffer();
auto line = QString::fromLatin1((const char*) usbBuffer->getBuffer(), handled_len - 1);
// add received line to buffer
lineBuffer.append(line);
// qDebug() << "append" << line << "size" << lineBuffer.size();
usbBuffer->removeBytes(handled_len + 1);
}
} while(handled_len > 0);
if(lineBuffer.size() > 0) {
cv.notify_one();
}
// qDebug() << "notify done";
}
void USBDevice::flushReceived()
{
unique_lock<mutex> lck(mtx);
lineBuffer.clear();
}
void USBDevice::USBHandleThread()
{
qDebug() << "Receive thread started";
while (connected) {
libusb_handle_events(m_context);
}
qDebug() << "Disconnected, receive thread exiting";
}
QString USBDevice::serial() const

View file

@ -1,10 +1,14 @@
#ifndef USBDEVICE_H
#define USBDEVICE_H
#include "Util/usbinbuffer.h"
#include <libusb-1.0/libusb.h>
#include <QString>
#include <set>
#include <functional>
#include <mutex>
#include <thread>
#include <QObject>
@ -25,18 +29,27 @@ public:
bool send(const QString &s);
bool receive(QString *s, unsigned int timeout = 2000);
void flushReceived();
signals:
void communicationFailure();
private slots:
void ReceivedData();
private:
void USBHandleThread();
bool connected;
std::thread *m_receiveThread;
static void SearchDevices(std::function<bool (libusb_device_handle *, QString)> foundCallback, libusb_context *context, bool ignoreOpenError);
libusb_device_handle *m_handle;
libusb_context *m_context;
QString m_serial;
USBInBuffer *usbBuffer;
std::mutex mtx;
std::condition_variable cv;
QStringList lineBuffer;
char rx_buf[1024];
unsigned int rx_cnt;
QString m_serial;
};
#endif // DEVICE_H

View file

@ -17,107 +17,6 @@ static constexpr USBID IDs[] = {
{0x1209, 0x4121},
};
USBInBuffer::USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size) :
buffer_size(buffer_size),
received_size(0),
inCallback(false),
cancelling(false)
{
buffer = new unsigned char[buffer_size];
memset(buffer, 0, buffer_size);
transfer = libusb_alloc_transfer(0);
libusb_fill_bulk_transfer(transfer, handle, endpoint, buffer, buffer_size, CallbackTrampoline, this, 0);
libusb_submit_transfer(transfer);
}
USBInBuffer::~USBInBuffer()
{
if(transfer) {
cancelling = true;
libusb_cancel_transfer(transfer);
// wait for cancellation to complete
mutex mtx;
unique_lock<mutex> lck(mtx);
using namespace std::chrono_literals;
if(cv.wait_for(lck, 100ms) == cv_status::timeout) {
qWarning() << "Timed out waiting for mutex acquisition during disconnect";
}
}
delete[] buffer;
}
void USBInBuffer::removeBytes(int handled_bytes)
{
if(!inCallback) {
throw runtime_error("Removing of bytes is only allowed from within receive callback");
}
if(handled_bytes >= received_size) {
received_size = 0;
} else {
// not removing all bytes, have to move remaining data to the beginning of the buffer
memmove(buffer, &buffer[handled_bytes], received_size - handled_bytes);
received_size -= handled_bytes;
}
}
int USBInBuffer::getReceived() const
{
return received_size;
}
void USBInBuffer::Callback(libusb_transfer *transfer)
{
if(cancelling || (transfer->status == LIBUSB_TRANSFER_CANCELLED)) {
// destructor called, do not resubmit
libusb_free_transfer(transfer);
this->transfer = nullptr;
cv.notify_all();
return;
}
switch(transfer->status) {
case LIBUSB_TRANSFER_COMPLETED:
received_size += transfer->actual_length;
inCallback = true;
emit DataReceived();
inCallback = false;
break;
case LIBUSB_TRANSFER_NO_DEVICE:
qCritical() << "LIBUSB_TRANSFER_NO_DEVICE";
libusb_free_transfer(transfer);
return;
case LIBUSB_TRANSFER_ERROR:
case LIBUSB_TRANSFER_OVERFLOW:
case LIBUSB_TRANSFER_STALL:
qCritical() << "LIBUSB_ERROR" << transfer->status;
libusb_free_transfer(transfer);
this->transfer = nullptr;
emit TransferError();
return;
break;
case LIBUSB_TRANSFER_TIMED_OUT:
// nothing to do
break;
case LIBUSB_TRANSFER_CANCELLED:
// already handled before switch-case
break;
}
// Resubmit the transfer
transfer->buffer = &buffer[received_size];
transfer->length = buffer_size - received_size;
libusb_submit_transfer(transfer);
}
void USBInBuffer::CallbackTrampoline(libusb_transfer *transfer)
{
auto usb = (USBInBuffer*) transfer->user_data;
usb->Callback(transfer);
}
uint8_t *USBInBuffer::getBuffer() const
{
return buffer;
}
LibreVNAUSBDriver::LibreVNAUSBDriver()
: LibreVNADriver()
{

View file

@ -2,40 +2,14 @@
#define LIBREVNAUSBDRIVER_H
#include "librevnadriver.h"
#include "Util/usbinbuffer.h"
#include <libusb-1.0/libusb.h>
#include <condition_variable>
#include <thread>
#include <QQueue>
#include <QTimer>
class USBInBuffer : public QObject {
Q_OBJECT
public:
USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size);
~USBInBuffer();
void removeBytes(int handled_bytes);
int getReceived() const;
uint8_t *getBuffer() const;
signals:
void DataReceived();
void TransferError();
private:
void Callback(libusb_transfer *transfer);
static void LIBUSB_CALL CallbackTrampoline(libusb_transfer *transfer);
libusb_transfer *transfer;
unsigned char *buffer;
int buffer_size;
int received_size;
bool inCallback;
bool cancelling;
std::condition_variable cv;
};
class LibreVNAUSBDriver : public LibreVNADriver
{
Q_OBJECT

View file

@ -132,6 +132,7 @@ HEADERS += \
Traces/tracepolarchart.h \
Util/prbs.h \
Util/qpointervariant.h \
Util/usbinbuffer.h \
Util/util.h \
Util/app_common.h \
VNA/Deembedding/deembedding.h \
@ -280,6 +281,7 @@ SOURCES += \
Traces/waterfallaxisdialog.cpp \
Traces/xyplotaxisdialog.cpp \
Util/prbs.cpp \
Util/usbinbuffer.cpp \
Util/util.cpp \
VNA/Deembedding/deembedding.cpp \
VNA/Deembedding/deembeddingdialog.cpp \

View file

@ -0,0 +1,109 @@
#include "usbinbuffer.h"
#include <mutex>
#include <QDebug>
using namespace std;
USBInBuffer::USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size) :
buffer_size(buffer_size),
received_size(0),
inCallback(false),
cancelling(false)
{
buffer = new unsigned char[buffer_size];
memset(buffer, 0, buffer_size);
transfer = libusb_alloc_transfer(0);
libusb_fill_bulk_transfer(transfer, handle, endpoint, buffer, buffer_size, CallbackTrampoline, this, 0);
libusb_submit_transfer(transfer);
}
USBInBuffer::~USBInBuffer()
{
if(transfer) {
cancelling = true;
libusb_cancel_transfer(transfer);
// wait for cancellation to complete
mutex mtx;
unique_lock<mutex> lck(mtx);
using namespace std::chrono_literals;
if(cv.wait_for(lck, 100ms) == cv_status::timeout) {
qWarning() << "Timed out waiting for mutex acquisition during disconnect";
}
}
delete[] buffer;
}
void USBInBuffer::removeBytes(int handled_bytes)
{
if(!inCallback) {
throw runtime_error("Removing of bytes is only allowed from within receive callback");
}
if(handled_bytes >= received_size) {
received_size = 0;
} else {
// not removing all bytes, have to move remaining data to the beginning of the buffer
memmove(buffer, &buffer[handled_bytes], received_size - handled_bytes);
received_size -= handled_bytes;
}
}
int USBInBuffer::getReceived() const
{
return received_size;
}
void USBInBuffer::Callback(libusb_transfer *transfer)
{
if(cancelling || (transfer->status == LIBUSB_TRANSFER_CANCELLED)) {
// destructor called, do not resubmit
libusb_free_transfer(transfer);
this->transfer = nullptr;
cv.notify_all();
return;
}
switch(transfer->status) {
case LIBUSB_TRANSFER_COMPLETED:
case LIBUSB_TRANSFER_TIMED_OUT:
if(transfer->actual_length > 0) {
received_size += transfer->actual_length;
inCallback = true;
emit DataReceived();
inCallback = false;
}
break;
case LIBUSB_TRANSFER_NO_DEVICE:
qCritical() << "LIBUSB_TRANSFER_NO_DEVICE";
libusb_free_transfer(transfer);
return;
case LIBUSB_TRANSFER_ERROR:
case LIBUSB_TRANSFER_OVERFLOW:
case LIBUSB_TRANSFER_STALL:
qCritical() << "LIBUSB_ERROR" << transfer->status;
libusb_free_transfer(transfer);
this->transfer = nullptr;
emit TransferError();
return;
break;
case LIBUSB_TRANSFER_CANCELLED:
// already handled before switch-case
break;
}
// Resubmit the transfer
transfer->buffer = &buffer[received_size];
transfer->length = buffer_size - received_size;
transfer->length = (transfer->length / 512) * 512;
libusb_submit_transfer(transfer);
}
void USBInBuffer::CallbackTrampoline(libusb_transfer *transfer)
{
auto usb = (USBInBuffer*) transfer->user_data;
usb->Callback(transfer);
}
uint8_t *USBInBuffer::getBuffer() const
{
return buffer;
}

View file

@ -0,0 +1,35 @@
#ifndef USBINBUFFER_H
#define USBINBUFFER_H
#include <libusb-1.0/libusb.h>
#include <condition_variable>
#include <QObject>
class USBInBuffer : public QObject {
Q_OBJECT
public:
USBInBuffer(libusb_device_handle *handle, unsigned char endpoint, int buffer_size);
~USBInBuffer();
void removeBytes(int handled_bytes);
int getReceived() const;
uint8_t *getBuffer() const;
signals:
void DataReceived();
void TransferError();
private:
void Callback(libusb_transfer *transfer);
static void LIBUSB_CALL CallbackTrampoline(libusb_transfer *transfer);
libusb_transfer *transfer;
unsigned char *buffer;
int buffer_size;
int received_size;
bool inCallback;
bool cancelling;
std::condition_variable cv;
};
#endif // USBINBUFFER_H

View file

@ -917,6 +917,8 @@ void VNA::NewDatapoint(DeviceDriver::VNAMeasurement m)
qDebug() << "Sweep took"<<sweepTime<<"milliseconds";
}
emit newRawDatapoint(m);
if(singleSweep && average.getLevel() == averages) {
Stop();
return;

View file

@ -80,6 +80,10 @@ public:
double firstPointTime; // timestamp of the first point in the sweep, only use when zerospan is used
};
signals:
void newRawDatapoint(DeviceDriver::VNAMeasurement m);
public slots:
void Run();
void Stop();

View file

@ -126,6 +126,7 @@ SOURCES += \
../LibreVNA-GUI/Traces/xyplotaxisdialog.cpp \
../LibreVNA-GUI/Util/prbs.cpp \
../LibreVNA-GUI/Util/util.cpp \
../LibreVNA-GUI/Util/usbinbuffer.cpp \
../LibreVNA-GUI/VNA/Deembedding/deembedding.cpp \
../LibreVNA-GUI/VNA/Deembedding/deembeddingdialog.cpp \
../LibreVNA-GUI/VNA/Deembedding/deembeddingoption.cpp \
@ -317,6 +318,7 @@ HEADERS += \
../LibreVNA-GUI/Traces/xyplotaxisdialog.h \
../LibreVNA-GUI/Util/prbs.h \
../LibreVNA-GUI/Util/util.h \
../LibreVNA-GUI/Util/usbinbuffer.h \
../LibreVNA-GUI/VNA/Deembedding/deembedding.h \
../LibreVNA-GUI/VNA/Deembedding/deembeddingdialog.h \
../LibreVNA-GUI/VNA/Deembedding/deembeddingoption.h \