C++ question - Page 1
Forum section: electronics / general

author: No.15
author_rank: Contributor
timestamp: August 22, 2020, 01:59:48 am
content: I am trying to adapt a c++ program to work with my DC Loadin the program I have 2 measure linesport.write("MEAS:CURR?/r");port.write("MEAS:VOLT?/r");Is there a way to append an "A" to the current output when being read that is easy? (I am a total programming noob)The reason why is that later in the program it's looking for this "A". The load it was originally used for gave its measurements with the unit (V, A), but my load just returns numbers for each.Code:double value = 0;bool isCurrent = false;QString tmp = port.readAll();tmp = tmp.left(tmp.length() - 1);if (tmp.at(tmp.length() - 1) == 'A')isCurrent = true;

author: ledtester
author_rank: Super Contributor
timestamp: August 22, 2020, 02:46:15 am
content: Are you asking how to write a string that has the form: "MEAS:CURR xxx A\r" where "xxx" is a number that comes from a variable?If so, look at thesprintf()function, e.g.http://cplusplus.com/reference/cstdio/sprintf/

author: AlfBaz
author_rank: Super Contributor
timestamp: August 22, 2020, 02:53:22 am
content: I'm assuming you can not change the code in the device returning the current value (amps).Based on what you have shown us you will have to do a read between GPIB callsI'm not familiar with the QString class but a quick search on the net shows it has an overloaded append member function which you could us to add the A to the returned value.You could then append the return value of the voltage measurement to the same string since the append function handles single chars and strings

author: AlfBaz
author_rank: Super Contributor
timestamp: August 22, 2020, 03:02:10 am
content: Scratch the append function, you would have to use the insert at length-1 otherwise you would have a '/r' between the number and the AJust check what the device is actually sending back in case its returning /r/n or maybe a space between the number and the '/r'

author: langwadt
author_rank: Super Contributor
timestamp: August 22, 2020, 03:09:30 am
content: just skip the part that checks for 'A'?

author: No.15
author_rank: Contributor
timestamp: August 22, 2020, 03:07:24 pm
content: Thank you everyone who replied.ledtester I will look at sprintf and see what it does. Part of this process is to force me to learn and c++ might not be the best first choice but this program does exactly what I want but for another similar device.AlfBazI was looking at QString obviously because it's already in there. the /r and /n don't get returned in the output they are jus CR and newlineThe output from those commands both look like thisCode:OKXX.XXXXWith the X representing a numerallangwadtI have to have some wqay to differentiate the amerage and the voltage. If V = 0 or V= (X) a variable I set the program terminates the test and graphs the results.When the test starts it reads it can't tell V from A , of course A = 0 at the start of the test so it terminates.I guess I should show the code, I got it on githubCode:#include "mainwindow.h"#include "ui_mainwindow.h"#include <string>void MainWindow::update(){//qDebug() << "On timer";port.write("MEAS:CURR?/r");port.write("MEAS:VOLT?/r");time.start();}void MainWindow::readyRead(){int addms = time.elapsed();double value = 0;bool isCurrent = false;QString tmp = port.readAll();tmp = tmp.left(tmp.length() - 1);if (tmp.at(tmp.length() - 1) == 'A')isCurrent = true;value = tmp.left(tmp.length() - 1).toDouble();if (isCurrent){lastCurrent = value;if (lastCurrent > highestCurrent)highestCurrent = lastCurrent;return;} elseif (value <= ui->sbCutVolts->value()){on_btnStop_clicked();QMessageBox msgBox;msgBox.setText("Measurement finished.");msgBox.setInformativeText(QString("Voltage %1 reached cut value %2. Result is: %3mAh").arg(value).arg(ui->sbCutVolts->value()).arg(ah));msgBox.setStandardButtons(QMessageBox::Ok);msgBox.setDefaultButton(QMessageBox::Ok);msgBox.exec();return;}lastVoltage = value;if (value > highestVolt)highestVolt = value;ah += ((lastCurrent * (1 + addms / 1000)) / 60 / 60) * 1000;seriesVolts->append(ah, lastVoltage);seriesCurrent->append(ah, lastCurrent);axisX->setRange(-2, ah + 2);axisY->setRange(ui->sbCutVolts->value(), highestVolt + 0.5);axisY2->setRange(0, highestCurrent + 0.5);ui->lblResult->setText(QString("%1 mAh").arg(ah));timer->start();}MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow){ui->setupUi(this);ah = 0;highestVolt = 0;highestCurrent = 0;lastCurrent = 0;lastVoltage = 0;for (int i = 0; i < QSerialPortInfo::availablePorts().count(); i++)ui->cbPort->addItem(QSerialPortInfo::availablePorts().at(i).portName());timer = new QTimer(this);timer->setInterval(1000);timer->setSingleShot(true);connect(timer, SIGNAL(timeout()), this, SLOT(update()));connect(&port, SIGNAL(readyRead()), this, SLOT(readyRead()));QChart *chart = new QChart();seriesVolts = new QLineSeries();seriesVolts->setName("Battery voltage");seriesCurrent = new QLineSeries();seriesCurrent->setName("Battery current");chart->addSeries(seriesVolts);chart->addSeries(seriesCurrent);chart->setTitle("Discharge chart");axisX = new QValueAxis;axisX->setTitleText("Capacity (mAh)");chart->addAxis(axisX, Qt::AlignBottom);seriesVolts->attachAxis(axisX);seriesCurrent->attachAxis(axisX);axisY = new QValueAxis;axisY->setLabelFormat("%.2f");axisY->setTitleText("Voltage (V)");chart->addAxis(axisY, Qt::AlignLeft);seriesVolts->attachAxis(axisY);axisY2 = new QValueAxis;axisY2->setLabelFormat("%.2f");axisY2->setTitleText("Current (A)");chart->addAxis(axisY2, Qt::AlignRight);seriesCurrent->attachAxis(axisY2);chartView = new QChartView(chart);ui->verticalLayout->insertWidget(1, chartView);}MainWindow::~MainWindow(){delete ui;}void MainWindow::on_btnStart_clicked(){ah = 0;highestVolt = 0;lastCurrent = 0;lastVoltage = 0;ui->lblResult->setText("0");seriesVolts->clear();seriesCurrent->clear();port.write(":INPUT OFF\n");port.write(":CURR 0\n");port.write(":MODE CC\n");port.write(QString(":CURR %1\n").arg(double(ui->sbCurrent->value()) / 1000).toStdString().c_str());port.write(":INPUT ON\n");timer->start();ui->btnStop->setEnabled(true);ui->btnStart->setEnabled(false);}void MainWindow::on_btnOpen_clicked(){port.setPortName(ui->cbPort->currentText());port.setBaudRate(QSerialPort::Baud115200);port.setDataBits(QSerialPort::Data8);port.setParity(QSerialPort::NoParity);port.setStopBits(QSerialPort::OneStop);port.setFlowControl(QSerialPort::NoFlowControl);port.open(QSerialPort::ReadWrite);if (port.isOpen()){ui->btnOpen->setEnabled(false);ui->btnClose->setEnabled(true);ui->btnStart->setEnabled(true);}}void MainWindow::on_btnClose_clicked(){on_btnStop_clicked();Sleep(1000);port.close();if (port.isOpen() == false){ui->btnOpen->setEnabled(true);ui->btnClose->setEnabled(false);ui->btnStart->setEnabled(false);}}void MainWindow::on_btnStop_clicked(){timer->stop();port.write(":INPUT OFF\n");port.write(":CURR 0\n");port.write(":MODE CC\n");port.write(":INPUT OFF\n");ui->btnStart->setEnabled(true);ui->btnStop->setEnabled(false);}void MainWindow::on_btnRequest_clicked(){}Example output from a terminal

author: MarkF
author_rank: Super Contributor
timestamp: August 22, 2020, 03:32:38 pm
content: It looks like your Github project was developed with Qt.That is a heavy lift for a programmer NOOB.Qt is a cross-platform development library which also includes GUI creation tools.See:https://www.qt.io/There use to be a 'free' version you could download but I can't find it.The commercial licenses are very expensive.

author: No.15
author_rank: Contributor
timestamp: August 22, 2020, 03:44:29 pm
content: Quote from: MarkF on August 22, 2020, 03:32:38 pmThey have a community version that is free. I downloaded it and I am using it but thankfully all the heavy lifting was done by the original coder. I am just trying to modify it to work with my DC Load. The program runs, but errors because of the condition I explained above. I agree that I am mostly lost here

author: MarkF
author_rank: Super Contributor
timestamp: August 22, 2020, 04:11:30 pm
content: It would be helpful if you gave us a link to the Github project.There is too much code missing to be able to answer your question.

author: No.15
author_rank: Contributor
timestamp: August 22, 2020, 04:16:33 pm
content: Quote from: MarkF on August 22, 2020, 04:11:30 pmYes sorry, good ideahttps://github.com/r1ka/qDischarge

author: MarkF
author_rank: Super Contributor
timestamp: August 22, 2020, 06:57:06 pm
content: I was mainly interested to see howportwas declared.line 58 QSerialPort port;TheQString tmp = port.readAll();reads directly from your DC Load.It appears the DC Load just returns the value without any indication of what was requested???Theupdate()requests both current and voltage values and then waits for a response from the DC Load of both values.Code:void MainWindow::update(){port.write(":MEAS:CURR?\r\n");port.write(":MEAS:VOLT?\r\n");}You really need to separate these two requests and keep track of each request.Request the current and wait for response. Then, request the voltage and wait for that response.Probably the best way would be to setup aflagof what the request was in theupdate()and then clear thatflagin thereadyRead().(i.e. Theupdate()function would alternate its requests and insuring a response.)Code:void MainWindow::readyRead(){if (tmp.at(tmp.length() - 1) == 'A')isCurrent = true;if (isCurrent){} elseif (value <= ui->sbCutVolts->value()){}}ReplaceisCurrentwith an enumerationflag(i.e. flag is 0=none, 1=current, 2=current_received, 3=voltage, 4=voltage_received).Code:void MainWindow::update(){if (flag==0 || flag==4) {port.write(":MEAS:CURR?\r\n");flag = 1;}else if (flag==2) {port.write(":MEAS:VOLT?\r\n");flag = 3;}}void MainWindow::readyRead(){QString tmp = port.readAll();switch (flag){case 1: // Currentflag = 2;break;case 3: // Voltageflag = 4;break;}timer->start();}

author: No.15
author_rank: Contributor
timestamp: August 23, 2020, 02:11:28 pm
content: MarkFThis was very helpful, thank you. My program is still not working correctly but I am learning a lot.Most of the issues now stem from the fact that when I query the load it always answers with an "OK", sometimes twice before it returns the value I want.Probably should try to redo this in Python, but even as a noob the whitespace thing drives me crazyI am going to contact Amrel and see if there is a way to suppress the OK message as it is really unnecessary. There is no way to do it listed in documentation.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 23, 2020, 03:36:14 pm
content: Quote from: MarkF on August 22, 2020, 03:32:38 pmThe LGPL licensed sources arehere.In essence, if you just use Qt without modifying it, you can rely on the LGPL license even in commercial projects. If you do modify Qt, you need to provide the sources to the modified version of Qt to your customers. In no case do you need to provide the sources of your own commercial project, because LGPL expressly allows incorporation into a proprietary product, as long as any modifications to the LGPL-licensed part are also provided per the LGPL license.What I would recommend here, is to use a separate thread (QThread) that communicates with the device in a simple loop. During each iteration, it would first query the current, ignore any superfluous OK responses, until it gets an answer; then query the voltage, ignore any superfluous OK responses, until it gets an answer.In C++, I would use a pair of globalQAtomicInts to represent the current and voltage, scaled by a suitable power of ten. (For example, you could have them represent the current in milliamps and voltage in millivolts, for three decimal digits when displaying in volts or amps.)That way you need no synchronization at all between the device communicating thread and the main thread, and the main thread can simply display the value whenever.In Python, I would use a Queue. (Well, in fact, I have used: a year or so ago I mentioned on this forum that I was thinking of writing a tutorial on how to use Python, Qt, and threads in Linux or Macs to create an user interface to control a microcontroller project using a graphical user interface. In Linux or Macs, because Python termios module can be used to talk to the device (if it has an USB serial interface) without any extra libraries. I don't particularly like libusb, as there really are two different versions, and one of them may actually just be a translation shim to the other version, making it actually quite fragile in real world use: if it works for you, it just works; but if there is an issue, it is hellishly annoying to find out which part causes it, due to libusb version differences.)I have not checked out the project, but if the UI can be used to send commands to the device, then you need aQMutexand twoQLists between the main thread and the device thread (that are accessed only when holding the QMutex). Instead of looping forever, the device thread checks if the command list has any commands in it. If it does, it sends that command to the device, and reads the response, and appends the response (or just a "command X sent") to the result list. You can also just append the voltage and current changes to the result list without using QAtomicInts; I would personally check if one of the last two values is a previous voltage (if appending voltage) or current (if appending current), and replace that value.The main thread update function must consume all results per invocation.It would be best if both command and response objects had an identifier, say a nonzero unsigned long that the main thread increments whenever it sends a new command; and perhaps an int or enum to describe the type of the result. Then, "idle" voltage and current readings would have a zero identifier.When the user pushes a button or something causing a command to be sent, the UI can show that the command is being processed, by comparing the last sent command ID to the response ID last received. As long as the response ID (is nonzero but) does not match, the device is busy processing commands. When the last nonzero ID matches the command ID last sent, the device has responded to all commands, and is now idle. So, in the Qt idle/update function, you just check the last sent command ID to the last received nonzero IDs (that the main thread must maintain itself), and if they do not match, it shows "busy" in the UI; otherwise "idle". This way the user has reliable feedback on whether the device has responded to a command yet or not.Instead of working all this out in a large existing project, you can work out the details by creating a test program that does this; it does not even have to have a window per se. Have each thread print what it is doing to standard output, but lock a dedicated QMutex before and unlock after each print; that way you ensure the output from the two threads stays nicely separate. The test program does not need to talk to any device, you can just fake the responses. You'll see that this is not at all scary, and actually a nicely modular way to do it.Really, the "tricky" bit is to decide what kind of objects to use for the commands and the responses. That depends highly on the device being talked to, but you'll want to use information objects – that is, describing theintent– rather than blindly following the command/response structure the device has. For example, I would use numbers/enums and not strings for the commands. This way, by replacing the device communication thread implementation, you can use the same UI for other similar devices also.

author: No.15
author_rank: Contributor
timestamp: August 23, 2020, 04:45:34 pm
content: That is a ot for me to digestThank you for your thoughts

author: MarkF
author_rank: Super Contributor
timestamp: August 23, 2020, 07:16:39 pm
content: Quote from: No.15 on August 23, 2020, 02:11:28 pmJust ignore the 'OK' response and continue waiting.Or check the response for 'isNumber()'. (This may not be the exact function name. It's been a long time since I've used Qt.)Code:void MainWindow::readyRead(){QString tmp = port.readAll();if (tmp != "OK") { // you might need to fix this syntax hereswitch (flag){case 1: // Currentflag = 2;break;case 3: // Voltageflag = 4;break;}}timer->start();}

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 24, 2020, 07:22:59 pm
content: I found the KEL103 command summary online, and I must agree with MarkF: This is such a simple protocol, there is no reason to add the complexity of a worker thread, just use the ReadyRead signal and the canReadLine() test to get all input lines already received by Qt.