From 2c639d80808b5ff1c1d3beb5ecd6e2f95a181c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Wed, 25 Sep 2024 15:30:42 +0200 Subject: [PATCH] Improved electronic calibration - Added automatic port detection mode - Faster coefficient readout - LibreCAL using asynchronous libusb API --- .../Calibration/LibreCAL/caldevice.cpp | 3 +- .../Calibration/LibreCAL/librecaldialog.cpp | 377 +++++++++++++----- .../Calibration/LibreCAL/librecaldialog.h | 14 + .../Calibration/LibreCAL/librecaldialog.ui | 64 +-- .../Calibration/LibreCAL/usbdevice.cpp | 124 +++--- .../Calibration/LibreCAL/usbdevice.h | 19 +- .../Device/LibreVNA/librevnausbdriver.cpp | 101 ----- .../Device/LibreVNA/librevnausbdriver.h | 28 +- .../LibreVNA-GUI/LibreVNA-GUI.pro | 2 + .../LibreVNA-GUI/Util/usbinbuffer.cpp | 109 +++++ .../LibreVNA-GUI/Util/usbinbuffer.h | 35 ++ .../PC_Application/LibreVNA-GUI/VNA/vna.cpp | 2 + .../PC_Application/LibreVNA-GUI/VNA/vna.h | 4 + .../LibreVNA-Test/LibreVNA-Test.pro | 2 + 14 files changed, 570 insertions(+), 314 deletions(-) create mode 100644 Software/PC_Application/LibreVNA-GUI/Util/usbinbuffer.cpp create mode 100644 Software/PC_Application/LibreVNA-GUI/Util/usbinbuffer.h diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/caldevice.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/caldevice.cpp index f90c4ca..92f3814 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/caldevice.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/caldevice.cpp @@ -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; } diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.cpp index b49a125..fdd5ea4 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.cpp @@ -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 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::max(); + double coeffMaxFreq = numeric_limits::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 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::max(); - double coeffMaxFreq = numeric_limits::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> 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(&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); } diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.h b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.h index 997061c..ca36b64 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.h +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.h @@ -3,6 +3,7 @@ #include "Calibration/calibration.h" #include "caldevice.h" +#include "Device/devicedriver.h" #include #include @@ -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 portAssignment; std::vector portAssignmentComboBoxes; + std::vector autoPortMeasurements; + DeviceDriver *driver; int measurementsTaken; }; diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.ui b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.ui index ecc4e0c..7b3867c 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.ui +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/librecaldialog.ui @@ -6,8 +6,8 @@ 0 0 - 346 - 395 + 399 + 405 @@ -58,20 +58,6 @@ - - - - COEFFICIENT_STATUS_PLACEHOLDER - - - - - - - 0 - - - @@ -116,10 +102,47 @@ Start - + + .. + + + + + + Coefficients: + + + + + + + 0 + + + + + + + + + + + Calibration: + + + + + + + 0 + + + + + @@ -127,13 +150,6 @@ - - - - 0 - - - diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp index d160879..a6d89e7 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include 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 { - for(unsigned int i=1;i 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 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 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 diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.h b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.h index 1c6ff5a..c351c73 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.h +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/LibreCAL/usbdevice.h @@ -1,10 +1,14 @@ #ifndef USBDEVICE_H #define USBDEVICE_H +#include "Util/usbinbuffer.h" + #include #include #include #include +#include +#include #include @@ -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 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 diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnausbdriver.cpp b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnausbdriver.cpp index 7a9c33a..e3eb188 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnausbdriver.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnausbdriver.cpp @@ -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 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() { diff --git a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnausbdriver.h b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnausbdriver.h index 6ec4ce7..39a99e0 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnausbdriver.h +++ b/Software/PC_Application/LibreVNA-GUI/Device/LibreVNA/librevnausbdriver.h @@ -2,40 +2,14 @@ #define LIBREVNAUSBDRIVER_H #include "librevnadriver.h" +#include "Util/usbinbuffer.h" #include -#include #include #include #include -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 diff --git a/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro index 678616c..7e58790 100644 --- a/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI/LibreVNA-GUI.pro @@ -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 \ diff --git a/Software/PC_Application/LibreVNA-GUI/Util/usbinbuffer.cpp b/Software/PC_Application/LibreVNA-GUI/Util/usbinbuffer.cpp new file mode 100644 index 0000000..9ad2fd9 --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/Util/usbinbuffer.cpp @@ -0,0 +1,109 @@ +#include "usbinbuffer.h" + +#include + +#include + +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 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; +} diff --git a/Software/PC_Application/LibreVNA-GUI/Util/usbinbuffer.h b/Software/PC_Application/LibreVNA-GUI/Util/usbinbuffer.h new file mode 100644 index 0000000..21b4ef0 --- /dev/null +++ b/Software/PC_Application/LibreVNA-GUI/Util/usbinbuffer.h @@ -0,0 +1,35 @@ +#ifndef USBINBUFFER_H +#define USBINBUFFER_H + +#include +#include + +#include + +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 diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp index 87c3492..f294bd3 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/vna.cpp @@ -917,6 +917,8 @@ void VNA::NewDatapoint(DeviceDriver::VNAMeasurement m) qDebug() << "Sweep took"<