Wrong/ too few output from Qt serial write function - c++

I am trying to send 7 variables that I read before from the GUI with the serial->write... function to my microcontroller.
I wrote a littile program on my microcontroller that blinks a led if there was an input. But it only shows 5 inputs.
I thougt it might be that my microcontroller is not fast enough and I stalled the Qt programm but that didn´t work.
So I don´t understand why there are too few inputs.
Further I used the qDebug() << function to print out in the console what I send to my microcontroller but this shows only the number of characters I send ( f.e. if I send 500 qDebug() shows "3").
So i guess something with my conversion isn´t correct too.
here is my code:
//conversion from QString into const char*
q_bauteillaenge = (ui->Bauteillaenge_e->text());
q_messintervall_vert = (ui->Messintervall_vert_e->text());
q_anz_inkrem_vert = (ui->Anzahl_Inkremente_vert_e->text());
q_inkrem_laenge_vert = (ui->Inkrementlaenge_vert_e->text());
q_anz_messungen_vert = (ui->Anzahl_Messungen_vert_e->text());
q_abs_messungen_vert = (ui->Abstand_Messungen_vert_e->text());
a_bauteillaenge = q_bauteillaenge.toUtf8();
a_messintervall_vert = q_messintervall_vert.toUtf8() ;
a_anz_inkrem_vert = q_anz_inkrem_vert.toUtf8() ;
a_inkrem_laenge_vert = q_inkrem_laenge_vert.toUtf8() ;
a_anz_messungen_vert = q_anz_messungen_vert.toUtf8();
a_abs_messungen_vert = q_abs_messungen_vert.toUtf8();
bauteillaenge = a_bauteillaenge.constData();
messintervall_vert = a_messintervall_vert.constData() ;
anz_inkrem_vert = a_anz_inkrem_vert.constData() ;
inkrem_laenge_vert = a_inkrem_laenge_vert.constData() ;
anz_messungen_vert = a_anz_messungen_vert.constData();
abs_messungen_vert = a_abs_messungen_vert.constData();
//Sending
qDebug() << serial->write("1");
serial->write("\n");
qDebug() << serial->write(bauteillaenge);
serial->write("\n");
qDebug() << serial->write(messintervall_vert);
serial->write("\n");
qDebug() << serial->write(anz_inkrem_vert);
serial->write("\n");
qDebug() << serial->write(inkrem_laenge_vert);
serial->write("\n");
qDebug() << serial->write(anz_messungen_vert);
serial->write("\n");
qDebug() << serial->write(abs_messungen_vert);
serial->write("\n");
Where are my faults ?
Regards

I would look into the UTF-8 encoding. Are you sure your microcontroller code excepts UTF-8 bytes?
How the led is responding to the incoming data depends on the code of your microcontroller. Maybe you can add some microcontroller code.
The serial->write function returns the count of the actual written bytes. So it works as it should, but you expected another return value.
Read the docs about this one:
http://doc.qt.io/qt-5/qiodevice.html#write

Related

QT 5.7 Serial Port reads very slow

I am very new to programming and I am teaching myself. I wrote a application to poll a number of requests from a control unit . I basically continuously send various read commands to the Control unit and read back the response .My program works and i successfully send commands and receive answers . But the reading is very slow ( i have a 100 ms timeout in my code to ensure i get the complete reply )
I have a program for the same control unit that was written by a professional coder in C++ , in his program he polls every 30 ms and i always receive the complete answer in that time frame . I have the same settings 57K baud 8 bits 1 stop bit and no parity . However my QT code takes almost 100 ms to receive the answer.
In my code i read the first 2 bytes ( first byte is the message identifier and second byte is the remainder of the message length) then i read in a loop until the total message length is equal to the message length byte +1 (the +1 is there to include the first byte ) . I am a wits end as to why my code is so slow in QT when i know its know the Hardware that's the limiting factor . The requests are always 3 bytes and the reply varies from 3 to 61 bytes . Please help me to point me to my error. If i remove the timeout i always have short reads . So far i also tried read(all) but with the same result .
Below is the extract from my code where i read the response . The full code is at https://github.com/MarkusIppy/PowerTune
//Error handling
QTime startTime = QTime::currentTime();
int timeOut = 100; // timeout in milisec.
QByteArray recvData = m_serialport->read(2); // reading first two bytes of received message to determine lenght of ecpected message
int msgLen = recvData[1]; //Total message Lenght excluding the first byte
while ( recvData.size() <= (msgLen+1) )
{
if ( startTime.msecsTo(QTime::currentTime()) > timeOut ) break;
recvData += m_serialport->read(msgLen+1-recvData.size());
}
if (msgLen +1 == recvData.length()) //if the received data lenght equals the message lenght from lenght byte + identifier byte (correct message lenght received )
{
qDebug() << "Received data OK"<<msgLen +1;
if(requestIndex <= 61){requestIndex++;}
else{requestIndex = 58;}
readData(recvData);
}
else //if the lenght of the received message does not correspond with the expected lenght repeat the request
{
qDebug() << "Received data lenght NIO";
readData(recvData);
qDebug() << "Request Message again"<< requestIndex;
}
I am sorry, I don't have enough time to go through your project and from the code you've provided I cannot be 100% sure what the cause is. My best guess though is that the problem in this case is that you wait explicitly for the data to be received and the events processing is somehow delayed or does not take place at all.
Anyway, here you have a couple of suggestions:
Use QTimer for timeouts instead of QTime.
Learn about the Qt5's signals and slots and use them to read from the serial port asynchronously.
I use the QSerialPort by connecting its bytesWritten(qint64 bytes) and readyRead() signals to slots of my program, let's say on_bytesWritten(qint64 bytes) and on_readyRead(). Then I send request to the target device and in the on_readyRead() slot I process the result. With each send command I start a QTimer with its timeout() signal connected to a on_timeout() slot of my application. This way I could monitor whether the device responds in time or not, as well as to have the data as soon as it comes. You may also use the errorOccurred(QSerialPort::SerialPortError error) signal of the QSerialPort to check if there is a problem with the transmission.
Changed my code slightly again and now it works perfectly on the actual Hardware
Below is my ready to read Slot :
void Serial::readyToRead()
{
qDebug() << "ready read";
if(ecu == 0)
{
m_readData.append(m_serialport->readAll());
Bytesexpected = m_readData[1]+1;
qDebug() << "readdata current" <<m_readData.toHex();
if (Bytesexpected == m_readData.size())
{
m_timer.stop();
if(requestIndex <= 62){requestIndex++;}
else{requestIndex = 59;}
readData(m_readData);
Serial::clear();
m_readData.clear();
Serial::sendRequest(requestIndex);
}
if (Bytesexpected != m_readData.size())
{
qDebug() << "starting timer";
m_timer.start(5000);
}
}
This is what i have so far ( just posting the important parts of my cpp file )
This code works now almost perfectly with my message emulator. It polls now as expected but the timeout gets always triggered after 5 seconds ( i need to change it to only trigger if there is no message comming ) . I will only be able to test it on the actual hardware end of next week .
This is what i have so far :
void Serial::initSerialPort()
{
if (m_serialport)
delete m_serialport;
m_serialport = new SerialPort(this);
connect(this->m_serialport,SIGNAL(readyRead()),this,SLOT(readyToRead()));
connect(m_serialport, static_cast<void (QSerialPort::*) (QSerialPort::SerialPortError)>(&QSerialPort::error),
this, &Serial::handleError);
connect(&m_timer, &QTimer::timeout, this, &Serial::handleTimeout);
m_timer.start(5000);
}
void Serial::readyToRead()
{
if(ecu == 0)
{
m_readData.append(m_serialport->readAll());
Bytesexpected = m_readData[1]+1;
if (Bytesexpected == m_readData.size())
{
if(requestIndex <= 62){requestIndex++;}
else{requestIndex = 59;}
readData(m_readData); // message for processing
Serial::clear();
m_readData.clear();
}
//Timeout
if (!m_timer.isActive())
m_timer.start(5000);
}
}
void Serial::handleTimeout()
{
qDebug() << "Timeout";
//request Data Again
QString fileName = "Errors.txt";
QFile mFile(fileName);
if(!mFile.open(QFile::Append | QFile::Text)){
qDebug() << "Could not open file for writing";
}
QTextStream out(&mFile);
out << "Timeout Request Index " << int(requestIndex)<< " lenght received "<< int(m_readData.length())<< " Bytes "<< " Expected Bytes "<< int(Bytesexpected)<< " bytes " <<" Message "<< QByteArray(m_readData.toHex()) <<endl;
mFile.close();
Serial::clear();
m_readData.clear();
Serial::sendRequest(requestIndex);
}
void Serial::handleError(QSerialPort::SerialPortError serialPortError)
{
if (serialPortError == QSerialPort::ReadError) {
QString fileName = "Errors.txt";
QFile mFile(fileName);
if(!mFile.open(QFile::Append | QFile::Text)){
qDebug() << "Could not open file for writing";
}
QTextStream out(&mFile);
out << "Serial Error " << (m_serialport->errorString()) <<endl;
mFile.close();
qDebug() <<"Serialport Error" <<(m_serialport->errorString());
}
}

How to read data from the serial port in QT?

I am creating a script in QT for reading the format packages (AA), (BB), etc from serial port. I open the serial port, but when I go to check inside the QByteArray values, comes back that I could not read any value.
This is my code
...
QSerialPort *serialPort = new QSerialPort();
serialPort->setPortName("ttyUSB0");
serialPort->setParity(QSerialPort::NoParity);
serialPort->setBaudRate(QSerialPort::Baud9600, QSerialPort::AllDirections);
serialPort->setStopBits(QSerialPort::OneStop);
serialPort->setFlowControl(QSerialPort::NoFlowControl);
serialPort->open(QIODevice::ReadOnly);
if (serialPort->isOpen()) {
qDebug() << "Serial port is open...";
QByteArray datas = serialPort->readAll();
if (datas.size() == 0) {
qDebug() << "Arrived data: 0";
} else {
for (int i = 0; i < datas.size(); i++){
if (datas.at(i)) {
qDebug() << datas[i];
}
}
}
} else {
qDebug() << "OPEN ERROR: " << serialPort->errorString();
}
serialPort->close();
qDebug() << "...serial port is closed!";
return 0;
...
You called readAll() immediately after open(). It probably took the computer a few nanoseconds to get from one to the other.
At 9600 baud, each byte of data takes slightly more than one millisecond to transfer. It would be absolutely impossible for any data to have arrived in that short an interval, so that's why you got no data.
Serial ports don't begin buffering incoming data until you open them (how could they, what baud rate and other settings would be used for receiving and buffering when no program has the port open?)
Use either a blocking read function of some sort (such as readLine()) or an event loop that reacts to data when it arrives.

libserial error: cannot set baud rate as 115200

I'm trying to communicate with an USB-uart module using Libserial.
Following is my code for initial part:
serial_port.Open("/dev/ttyUSB0");
if ( ! serial_port.good() )
{
std::cerr << "[" << __FILE__ << ":" << __LINE__ << "] "
<< "Error: Could not open serial port."
<< std::endl ;
exit(1) ;
}
serial_port.SetBaudRate( SerialStreamBuf::BAUD_115200 ) ;
if ( ! serial_port.good() )
{
std::cerr << "Error: Could not set the baud rate." <<
std::endl ;
exit(1) ;
}
When I run it on Ubuntu 12.04 and 13.04 with the same USB module, they all say
Error: Could not set the baud rate.
I did some tests and finally found this error would occur if I set the baud rate as or higher than 115200. It works well on 57600 and 19200.
But I'm wondering is there any possible way for me to set the baud rate as 115200?
I downloaded a serial test tool, it can work as the 115200(but I didn't checked the msg content, I just notice the transmit led is flash).
Or is it the hardware limit so I need to buy another module if I want a higher baud rate?
Thanks
===========
UPDATE:
There is no problem with the hardware. I tested it in Windows VS using 115200 and it works well. But it failed on two Ubuntu desktop(12.04 and 13.04).
I print the baudrate out after I set it
serial_port.SetBaudRate( SerialStreamBuf::BAUD_115200) ;
int rate = serial_port.BaudRate();
cout << SerialStreamBuf::BAUD_115200 << endl;
cout << rate << endl;
the result shows their values are the same, both are 4098.
Then I tried to comment all the .good() part with and after the SetBaudRate part, the program start successfully but the transmit LED doesn't flash. So I think there is really something wrong with the baudrate set so the serial initial failed, although the baudrate it returns is correct.
Now I have no idea what to do next...
in case you need to see all my
code
I'm guessing it's this bug, but haven't verified it.
http://ehc.ac/p/libserial/bugs/10/
Now in SerialStreamBuf.h
enum BaudRateEnum {
BAUD_50 = SerialPort::BAUD_50,
BAUD_75 = SerialPort::BAUD_75,
BAUD_110 = SerialPort::BAUD_110,
BAUD_134 = SerialPort::BAUD_134,
BAUD_150 = SerialPort::BAUD_150,
BAUD_200 = SerialPort::BAUD_200,
BAUD_300 = SerialPort::BAUD_300,
BAUD_600 = SerialPort::BAUD_600,
BAUD_1200 = SerialPort::BAUD_1200,
BAUD_1800 = SerialPort::BAUD_1800,
BAUD_2400 = SerialPort::BAUD_2400,
BAUD_4800 = SerialPort::BAUD_4800,
BAUD_9600 = SerialPort::BAUD_9600,
BAUD_19200 = SerialPort::BAUD_19200,
BAUD_38400 = SerialPort::BAUD_38400,
BAUD_57600 = SerialPort::BAUD_57600,
BAUD_115200 = SerialPort::BAUD_115200, // 4098
BAUD_230400 = SerialPort::BAUD_230400,
#ifdef __linux__
BAUD_460800 = SerialPort::BAUD_460800,
#endif
BAUD_DEFAULT = SerialPort::BAUD_DEFAULT, // 4097
BAUD_INVALID
} ;
So BAUD_INVALID will be 4098, exactly the same as BAUD_115200. That's why you get error.
hello i had the same problem and even i tried everything using c++ API for libSerial couldn't solve until i used the bellow code in my serial initialization!!
I used the system call once at the initialization and that worked GREAT!!
NOTE instead of /dev/ttyACM0 use the name of your serial device /dev/ttyXXX
LibSerial::SerialStream serial;
//serial.SetBaudRate(LibSerial::SerialStreamBuf::BAUD_9600);//THAT DOESNT WORKS
serial.SetCharSize( LibSerial::SerialStreamBuf::CHAR_SIZE_8);
serial.Open("/dev/ttyACM0");
system("sudo stty -F /dev/ttyACM0 115200");//YOU HAVE TO RUN THE EXCECUTABLE FROM COMMAND LINE WITH SU PRIVILEGES

win32 C++ print string to printer

After a few days of searching the net on how exactly I can go about printing an arbitrary string to an arbitrary printer on windows, I finally came up with this code.
LPBYTE pPrinterEnum;
DWORD pcbNeeded, pcbReturned;
PRINTER_INFO_2 *piTwo = NULL;
HDC printer;
EnumPrinters(PRINTER_ENUM_LOCAL,NULL,2,NULL,0,&pcbNeeded,&pcbReturned);
pPrinterEnum = new BYTE[pcbNeeded];
if (!EnumPrinters(PRINTER_ENUM_LOCAL,NULL,2,pPrinterEnum,pcbNeeded,&pcbNeeded,&pcbReturned)) {
qDebug() << "In Print, could not enumerate printers";
} else {
piTwo = ((PRINTER_INFO_2*)pPrinterEnum);
for (int i = 0; i < pcbReturned; i++) {
QString name = QString::fromWCharArray(piTwo[i].pPrinterName);
if (this->m_printer_path == name) {
const WCHAR * driver = L"WINSPOOL\0";
printer = CreateDC(NULL,piTwo[i].pPrinterName,NULL,NULL);
}
}
}
if (printer == 0) {
qDebug() << "No Printer HDC";
return;
} else {
qDebug() << "Printer seems okay!";
}
qDebug() << "Starting Document";
DOCINFO di;
memset( &di, 0, sizeof( di ) );
di.cbSize = sizeof( di );
WCHAR * text = new WCHAR[ba.length()];
QString(ba).toWCharArray(text);
StartDoc(printer,&di);
qDebug() << "Writing text";
TextOut(printer,0, 0, text, ba.length());
qDebug() << "Text Written";
EndPage(printer);
qDebug() << "Page ended";
DeleteDC(printer);
qDebug() << "DC Deleted";
Some basic caveats:
1) I cannot use QPrinter. I need to write raw text, no postscript.
2) I do not know the name of the printer until the user sets it, and I do not know the size of the string to print until the user creates it.
Additional information:
a) The printer works, I can print from Notepad, Chrome, just about everything to the printer that I want.
b) I am willing to implement just about any hack. Ones like write it to a text file and issue the copy command don't seem to work, that is, I get a failed to initialize device error.
This works:
notepad /P Documents/test_print.txt
This does not work:
copy Documents\test_print.txt /D:EPSON_TM_T20
copy Documents\test_print.txt /D \MYCOMPUTER\epson_tm_t20 (leads to access denied, printer is shared)
print Documents\test_print.txt (Unable to initialize device)
I have tried just about every recommended way to print a text file from the command line, just doesn't work. I have installed, reinstalled driver, added printer, mucked with ports and done it all again.
Obviously there is something simple about windows printing that I am missing due to inexperience.
What I want to accomplish is:
1) Best Scenario( Directly write text to the printer)
2) Second best scenario (Write text to a file, then execute some program to print it for me) Notepad adds an annoying amount of space to the bottom of the printout wasting paper.
Since the program is for end users, I have to find a way to do this automagically for them, so I can't expect them to click checkbox a in tab 36 after running command obscure_configuration from a powershell.
Any help would be greatly appreciated.
/Jason
UPDATE
This is the working code, before I go through an spruce it up a bit, which prints the contents of a QByteArray to a thermal printer.
qDebug() << "Executing windows code";
BOOL bStatus = FALSE;
DOC_INFO_1 DocInfo;
DWORD dwJob = 0L;
DWORD dwBytesWritten = 0L;
HANDLE hPrinter;
wchar_t * name = new wchar_t[this->m_printer_path.length()+1];
this->m_printer_path.toWCharArray(name);
name[this->m_printer_path.length() + 1] = 0;
qDebug() << "opening printer";
bStatus = OpenPrinter(name,&hPrinter, NULL);
if (bStatus) {
qDebug() << "Printer opened";
DocInfo.pDocName = L"My Document";
DocInfo.pOutputFile = NULL;
DocInfo.pDatatype = L"RAW";
dwJob = StartDocPrinter( hPrinter, 1, (LPBYTE)&DocInfo );
if (dwJob > 0) {
qDebug() << "Job is set.";
bStatus = StartPagePrinter(hPrinter);
if (bStatus) {
qDebug() << "Writing text to printer";
bStatus = WritePrinter(hPrinter,ba.data(),ba.length(),&dwBytesWritten);
EndPagePrinter(hPrinter);
} else {
qDebug() << "could not start printer";
}
EndDocPrinter(hPrinter);
qDebug() << "closing doc";
} else {
qDebug() << "Couldn't create job";
}
ClosePrinter(hPrinter);
qDebug() << "closing printer";
} else {
qDebug() << "Could not open printer";
}
if (dwBytesWritten != ba.length()) {
qDebug() << "Wrong number of bytes";
} else {
qDebug() << "bytes written is correct " << QString::number(ba.length()) ;
}
Note: I do owe an apology to Skizz, what he wrote was actually helpful in debugging the fundamental issue. The characters in the QByteArray are preformatted specifically for the printer, the problem is, they contain several NULL bytes. When trying to send them to the printer, this causes TextOut to truncate the text, only printing the first few lines. Using WritePrinter, as suggested in the answer ignores null bytes and accepts a void * and a length, and just puts it all there.
Further, his response recommending the use of PrintDlg did work to fectch the correct printer HDC, the issus is that, the user first chooses a printer once, and then doesn't need to choose it each time they print, because they will be printing alot (It's a Point of Sale).
The problem with getting the printer HDC from the string name was due to not adding the all important NULL byte to wchar_* which was solved this way:
wchar_t * name = new wchar_t[this->m_printer_path.length()+1];
this->m_printer_path.toWCharArray(name);
name[this->m_printer_path.length() + 1] = 0;
In the above, m_printer_path is a string representation of the name of the printer taken from Print Manager.
Because the string has all the formatting necessary for the printer, there's no need to worry about new lines, or any formatting.
All three answers to this question were actually very helpful in implementing the final working solution, and I have voted up each answer, and I appreciate the time each person took in responding.
Most modern printers don't perform any form of layout processing of the data they are given. Thus, sending a sequence of characters to the printer would, at best, just print a line of text running off the side of the page in some default font. Carriage returns may work too.
What modern printers usually do is print pages using preprocessed data that the printer understands and defines what to print where and how to print it. All this preprocessing is done on the host PC and the results sent to the printer. This is why you usually install printer drivers - these drivers take the user data (whether it's a simple text file or a DTP page) and converts it into a language the printer understands.
The upshot of this is that sending raw text to the printer probably won't work.
Then you've got the problem of having multiple printers with different properties and languages.
So, in Windows, all this is abstracted into the printer device context object. This has the same interface as a graphics device context but you create it differently.
The Win32 API has a common dialog to let the user choose the printer. Use the PrintDlgEx function to allow the user to choose a printer. Then use the returned DC to draw text to the page.
There are a couple of MSDN articles describing how to send raw data (printer control codes, etc.) to a printer.
How To: Send Data Directly to a GDI Printer
How To: Send Data Directly to an XPS Printer
You have the right idea (though you should have StartPage and EndDoc calls to match up). The problem is that TextOut draws only a line of text. It won't break long strings into multiple lines, etc. You need to do that (or find code to do it).
If you know that the text will always fit on a single page, you could probably replace your TextOut with a DrawTextEx call, which can do basic line breaking, tab expansion, etc.
Why not try QPrint.. it prints raw text using a Generic Text Only driver
QString prn("^XA^FO121,41^A0N,19,15^FDABC DEFGHIJKLMNOPQRSTUVWXYZ^FS^XZ");
QPrinter printer(QPrinterInfo::defaultPrinter()); //the default printer is "Generic / Text Only"
QTextDocument doc(prn);
doc.print(&printer);
MTry the following code in C++:
#include<fstream>
Class PrinterDriver{
Private:
fstream print("PRN")
Public:
Void Print(char a[]){
print >>a;}
Char GetPrinterStatus[](){
char c[];
print<<c;
return c;}};
understand it(key)

Problems capturing audio from the second sound card

I have written a program that captures sound via waveInOpen() in Wuindows. It works great on the michrophone-device on the main-board, but when I try to capture from the second sound-card, I get only [static] noise. Recording with SoundRecorder works great on both cards. Does any1 know if there are any known problems with waveInOpen() and multiple input-devices?
The code that opens the input-device looks like this:
void Audio::OpenDevice(const int device,
const Audio::SamplingRate samplingRate)
throw (Exception, std::exception)
{
switch(samplingRate)
{
...
case AUDIO_16BIT_44KHZ_STEREO:
bits_per_sample_ = 16;
hertz_ = 44100;
channels_ = 2;
break;
...
default:
throw Exception("Audio::OpenDevice(): Invalid enum value");
}
// Open the device
const UINT_PTR dev = (-1 == device) ? (UINT_PTR)WAVE_MAPPER : (UINT_PTR)device;
WAVEFORMATEX wf = {0};
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nChannels = channels_;
wf.wBitsPerSample = bits_per_sample_;
wf.nSamplesPerSec = hertz_;
wf.nBlockAlign = wf.nChannels * wf.wBitsPerSample / 8;
`
const MMRESULT result = waveInOpen(&hwi_, dev, &wf,
(DWORD_PTR)OnWaveEvent, (DWORD_PTR)this, CALLBACK_FUNCTION);
if (MMSYSERR_NOERROR != result)
throw Exception("waveInOpen()");
std::cout << "Audio: Sampling at " << hertz_ << " hertz from "
<< channels_ << " channel(s) with " << bits_per_sample_
<< " bits per sample. "
<< std::endl;
}
Did you check the microphone gain settings, mixer settings, that the microphone hardware you're using is compatible with the input you have it hooked to, etc? Hooking most microphones to a line in connection does not work well. The microphone doesn't have enough output voltage to drive that kind of input.
My guess (purely a guess) is that the bit depth or sample rate is somehow incorrect. If you are using 16/44100, then I would assume it is supported (pretty common). But maybe the sound card is not set for those rates. I have an external Edirol sound card that I have to physically turn on and off when I change bit depth (and adjust a separate switch on it).