267 lines
7.8 KiB
C++
267 lines
7.8 KiB
C++
// File: host/src/MainWindow_Serial.cpp
|
|
#include "MainWindow.h"
|
|
#include <QDateTime>
|
|
#include <complex>
|
|
|
|
void MainWindow::refreshPorts() {
|
|
portSelector->clear();
|
|
const auto infos = QSerialPortInfo::availablePorts();
|
|
bool foundTarget = false;
|
|
QString targetPort;
|
|
|
|
for (const QSerialPortInfo &info : infos) {
|
|
portSelector->addItem(info.portName());
|
|
bool isCafe =
|
|
(info.hasVendorIdentifier() && info.vendorIdentifier() == 0xCAFE);
|
|
bool isUsbModem = info.portName().contains("usbmodem", Qt::CaseInsensitive);
|
|
if ((isCafe || isUsbModem) && !foundTarget) {
|
|
targetPort = info.portName();
|
|
foundTarget = true;
|
|
logWidget->append(">> Found Target Device: " + targetPort);
|
|
}
|
|
}
|
|
if (foundTarget) {
|
|
portSelector->setCurrentText(targetPort);
|
|
if (!serial->isOpen())
|
|
connectToPort();
|
|
}
|
|
}
|
|
|
|
void MainWindow::connectToPort() {
|
|
if (serial->isOpen()) {
|
|
serial->close();
|
|
connectBtn->setText("Connect");
|
|
logWidget->append("--- Disconnected ---");
|
|
|
|
// 1. Disable Global Controls
|
|
checkIdBtn->setEnabled(false);
|
|
calibrateBtn->setEnabled(false);
|
|
comboTiaRange->setEnabled(false);
|
|
|
|
// 2. Disable Tabs
|
|
mainTabWidget->widget(0)->setEnabled(false);
|
|
mainTabWidget->widget(1)->setEnabled(false);
|
|
mainTabWidget->widget(2)->setEnabled(false);
|
|
|
|
isMeasuringImp = false;
|
|
isMeasuringAmp = false;
|
|
isSweeping = false;
|
|
lsvState = LSV_IDLE;
|
|
currentSequence = SEQ_IDLE;
|
|
setButtonBlinking(nullptr, false);
|
|
|
|
measureBtn->setText("Measure");
|
|
ampBtn->setText("Start Amp");
|
|
lsvBlankBtn->setText("Run Blank");
|
|
lsvSampleBtn->setText("Run Sample");
|
|
return;
|
|
}
|
|
|
|
if (portSelector->currentText().isEmpty())
|
|
return;
|
|
|
|
serial->setPortName(portSelector->currentText());
|
|
serial->setBaudRate(500000);
|
|
|
|
if (serial->open(QIODevice::ReadWrite)) {
|
|
connectBtn->setText("Disconnect");
|
|
logWidget->append("--- Connected and Synchronized ---");
|
|
|
|
// 1. Enable Global Controls
|
|
checkIdBtn->setEnabled(true);
|
|
calibrateBtn->setEnabled(true);
|
|
comboTiaRange->setEnabled(true);
|
|
|
|
// 2. Enable Tabs
|
|
mainTabWidget->widget(0)->setEnabled(true);
|
|
mainTabWidget->widget(1)->setEnabled(true);
|
|
mainTabWidget->widget(2)->setEnabled(true);
|
|
|
|
// Sync LPF
|
|
onLPFChanged(comboLPF->currentIndex());
|
|
} else {
|
|
logWidget->append(">> Connection Error: " + serial->errorString());
|
|
}
|
|
}
|
|
|
|
void MainWindow::onPortError(QSerialPort::SerialPortError error) {
|
|
if (error == QSerialPort::ResourceError) {
|
|
logWidget->append(">> Critical Error: Connection Lost.");
|
|
serial->close();
|
|
connectBtn->setText("Connect");
|
|
|
|
checkIdBtn->setEnabled(false);
|
|
calibrateBtn->setEnabled(false);
|
|
comboTiaRange->setEnabled(false);
|
|
|
|
mainTabWidget->widget(0)->setEnabled(false);
|
|
mainTabWidget->widget(1)->setEnabled(false);
|
|
mainTabWidget->widget(2)->setEnabled(false);
|
|
|
|
setButtonBlinking(nullptr, false);
|
|
currentSequence = SEQ_IDLE;
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSerialData() {
|
|
while (serial->canReadLine()) {
|
|
QByteArray line = serial->readLine();
|
|
QString str = QString::fromUtf8(line).trimmed();
|
|
if (str.isEmpty())
|
|
continue;
|
|
|
|
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
|
|
logWidget->append(QString("[%1] %2").arg(timestamp, str));
|
|
logWidget->moveCursor(QTextCursor::End);
|
|
|
|
// --- Sequence State Machine ---
|
|
if (currentSequence == SEQ_WAIT_BOOT) {
|
|
if (str.contains("AD5940LIB Version:v0.2.1")) {
|
|
logWidget->append(">> Sequence: Boot detected. Configuring...");
|
|
|
|
// 1. Restore Settings
|
|
int hpVal = comboTiaRange->currentData().toInt();
|
|
serial->write(QString("r %1\n").arg(hpVal).toUtf8());
|
|
|
|
serial->write(QString("t 0\n").toUtf8());
|
|
|
|
// 2. Start Calibration
|
|
logWidget->append(">> Sequence: Calibrating...");
|
|
serial->write("c\n");
|
|
|
|
currentSequence = SEQ_WAIT_CALIB;
|
|
}
|
|
} else if (currentSequence == SEQ_WAIT_CALIB) {
|
|
if (str.contains("RCAL,HSTIA")) {
|
|
logWidget->append(">> Sequence: Calibration Done. Sending Command.");
|
|
serial->write(pendingCommand.toUtf8());
|
|
serial->write("\n");
|
|
currentSequence = SEQ_IDLE;
|
|
}
|
|
}
|
|
// ------------------------------
|
|
|
|
if (str.startsWith("DATA,")) {
|
|
parseData(str);
|
|
} else if (str.startsWith("RCAL,")) {
|
|
parseData(str);
|
|
} else if (str.startsWith("AMP,")) {
|
|
parseData(str);
|
|
} else if (str.startsWith("RAMP,")) {
|
|
parseData(str);
|
|
} else if (str == "STOPPED") {
|
|
// Reset UI state
|
|
if (lsvState != LSV_IDLE)
|
|
stopLSV();
|
|
if (isSweeping) {
|
|
isSweeping = false;
|
|
sweepBtn->setText("Sweep");
|
|
setButtonBlinking(nullptr, false);
|
|
}
|
|
if (isMeasuringImp) {
|
|
isMeasuringImp = false;
|
|
measureBtn->setText("Measure");
|
|
setButtonBlinking(nullptr, false);
|
|
}
|
|
if (isMeasuringAmp) {
|
|
isMeasuringAmp = false;
|
|
ampBtn->setText("Start Amp");
|
|
setButtonBlinking(nullptr, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::parseData(const QString &data) {
|
|
QStringList parts = data.split(',');
|
|
|
|
if (parts[0] == "AMP" && parts.size() >= 3) {
|
|
bool okIdx, okCurr;
|
|
double index = parts[1].toDouble(&okIdx);
|
|
double current = parts[2].toDouble(&okCurr);
|
|
if (okIdx && okCurr)
|
|
ampGraph->addAmperometricData(index, current);
|
|
return;
|
|
}
|
|
|
|
if (parts[0] == "RAMP" && parts.size() >= 3) {
|
|
bool okIdx, okCurr;
|
|
double index = parts[1].toDouble(&okIdx);
|
|
double current = parts[2].toDouble(&okCurr);
|
|
|
|
if (okIdx && okCurr) {
|
|
// Calculate Voltage based on UI parameters (Host-side calculation)
|
|
double start = spinLsvStart->value();
|
|
double stop = spinLsvStop->value();
|
|
int steps = spinLsvSteps->value();
|
|
|
|
double voltage = start + (index * (stop - start) / steps);
|
|
|
|
if (lsvState == LSV_RUNNING_BLANK) {
|
|
lsvGraph->addLSVData(voltage, current, GraphWidget::LSV_BLANK);
|
|
lsvBlankData.append({voltage, current});
|
|
} else if (lsvState == LSV_RUNNING_SAMPLE) {
|
|
lsvGraph->addLSVData(voltage, current, GraphWidget::LSV_SAMPLE);
|
|
lsvSampleData.append({voltage, current});
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (parts[0] == "RCAL" && parts.size() >= 4) {
|
|
QString type = parts[1];
|
|
if (type == "FAIL") {
|
|
logWidget->append(QString(">> RCAL FAIL: %1").arg(data));
|
|
return;
|
|
}
|
|
bool okM, okP;
|
|
double mag = parts[2].toDouble(&okM);
|
|
double phase = parts[3].toDouble(&okP);
|
|
|
|
if (okM && okP) {
|
|
logWidget->append(QString(">> Calibration [%1]: Mag=%.2f Phase=%.2f")
|
|
.arg(type)
|
|
.arg(mag)
|
|
.arg(phase));
|
|
// TODO: Store this if needed for UI display
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (parts[0] == "DATA" && parts.size() >= 6) {
|
|
bool okF, okM, okP;
|
|
double freq = parts[1].toDouble(&okF);
|
|
double mag = parts[4].toDouble(&okM);
|
|
double phase = parts[5].toDouble(&okP);
|
|
|
|
if (okF && okM && okP) {
|
|
// 1. Plot Bode (Magnitude/Phase)
|
|
// Note: Phase from AD5940 is in Radians.
|
|
rawGraph->addBodeData(freq, mag, phase);
|
|
|
|
// 2. Convert to Cartesian for Nyquist (Real/Imaginary)
|
|
// Z = Mag * e^(j*Phase) = Mag * (cos(Phase) + j*sin(Phase))
|
|
// Note: Nyquist Plot expects Z' vs -Z''.
|
|
std::complex<double> z = std::polar(mag, phase);
|
|
double real = z.real();
|
|
double imag =
|
|
z.imag(); // This is Z''. GraphWidget negates it for the plot.
|
|
|
|
nyquistGraph->addNyquistData(real, imag, real, imag, false);
|
|
|
|
if (sweepFreqs.size() >= expectedSweepPoints) {
|
|
// Discard extra points (Artifact removal)
|
|
return;
|
|
}
|
|
|
|
sweepFreqs.append(freq);
|
|
sweepReals.append(real);
|
|
sweepImags.append(imag);
|
|
|
|
if (sweepReals.size() > 10 && sweepReals.size() % 10 == 0) {
|
|
computeHilbert();
|
|
performCircleFit();
|
|
}
|
|
}
|
|
}
|
|
} |