PDA

View Full Version : First attempt to display serial port data on GUI



ShaChris23
2nd May 2007, 02:56
Hi guys,

I created this Qt Application project under VS2005, and tried to display the GPS data onto GUI---real simple. I can read the data from the serial port fine (using QExtSerialPort class) but I cant display it on the GUI for some reason. The GUI would just be non-responsive and I have to kill it. If you guys could help me out here, that'd be great. I tried to compare this to other existing tutorials I have been working on, but couldnt find anything. In particular, I simply call label->setText() to display the string :



#ifndef QTAPP_H
#define QTAPP_H

#include <QtGui/QMainWindow>
#include "ui_qtapp.h"
#include <QLabel>
#include <QIODevice>
#include <QString>
#include <QApplication>
#include <QStringList>
#include <QDebug>
#include <QVariant>
#include <QWidget>
#include "qextserialport.h"

class QtApp : public QMainWindow
{
Q_OBJECT

public:
QtApp(QWidget *parent = 0, Qt::WFlags flags = 0);
~QtApp();
void Run();

private:
Ui::QtAppClass ui;
QString* str;
QStringList* list;
QextSerialPort* port;
};

#endif // QTAPP_H




#include "qtapp.h"

QtApp::QtApp(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);

str = new QString;
list = new QStringList;
port = new QextSerialPort( "COM1" );
port->setBaudRate(BAUD115200);
port->setFlowControl(FLOW_OFF);
port->setParity(PAR_NONE);
port->setDataBits(DATA_8);
port->setStopBits(STOP_1);

// Open port
port->open( QIODevice::ReadOnly );
}

QtApp::~QtApp()
{

}

void QtApp::Run()
{
char data[100];

QLabel* label = new QLabel;
while(1)
{
if( !port->bytesAvailable() ) continue;

int charIdx = 0;
int bytesRead;

//
// Find sync word
do
{
data[charIdx] = 'A';
port->read( data,
1 );
} while( data[charIdx] != '$' );
charIdx++;

//
// Read the rest of the data
do
{
charIdx += port->read( &data[charIdx],
1 );
} while( data[charIdx-1] != '*' );


// Read check sum
charIdx += port->read( &data[charIdx],
2 );
data[charIdx] = '\0';


// Copy buffer to QString
*str = data;

//
// Diplay this on GUI
label->setText( *str );
}
}

marcel
2nd May 2007, 04:47
Hey, where do you call the Run method?
How often do you receive data from the serial port( at what intervals )?

Best to do this is to use a thread - to handle the incoming data from the serial port. Or maybe even a QTimer will work.

Your interface block because of the while(1) loop in Run.

Regards

ShaChris23
2nd May 2007, 17:55
Hi,

Here's where I call the Run()



#include <QtGui/QApplication>
#include "qtapp.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QtApp w;
w.show();
w.Run();
a.connect(&a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()));
return a.exec();
}


The serial port data comes in at about 1 second an interval. When I used the debugger, I could see the data getting read fine, the problem is just the display part.

My concept of label->setText(str); is just like cout or printf. At least I thought that's how it worked, but maybe not?

marcel
2nd May 2007, 17:59
This is not how I would have done it, but you can make it work.

Just add QApplication::processEvents() as the first line in the while(1) loop. This will make your GUI responsive and you will see data changing in the label.

But this is not the best way to poll a serial port.

Regards

ShaChris23
2nd May 2007, 18:43
Hi Marcel,

Yup. That worked perfectly. Though I found the load on my simple application was quite high. This is a testing module only to see if I can finally display something on the GUI. The way I really design this is as follows:

1) GUI: using Designer to create the GUI that I want, then create a MyWidget class that has this ui as its member. This class will have its slot tied to SerialPortReader thread class' signal.

2) SerialPortReader: is a thread that emits a signal whenever the blocking port->read() gets a new value. The signal is going to indicate that its value (a QString) changed.

Do you find the while( 1 ) a bad design? The port->read() is a blocking call, so it shouldn't consume any CLK cycles.

Let me know what you think about this...Thanks a whole bunch. You've been extremely helpful in my first Qt adventure.

marcel
2nd May 2007, 19:02
Do you find the while( 1 ) a bad design? The port->read() is a blocking call, so it shouldn't consume any CLK cycles

yes, the while(1) loop is not a good solution. The processor load will be almost 1.0 all the time your app runs. Although, you will not notice any lag in the interface, because of processEvents.

A simpler solution, that doesn't require any fancy synchronization would be:


QtApp::QtApp(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);

str = new QString;
list = new QStringList;
port = new QextSerialPort( "COM1" );
port->setBaudRate(BAUD115200);
port->setFlowControl(FLOW_OFF);
port->setParity(PAR_NONE);
port->setDataBits(DATA_8);
port->setStopBits(STOP_1);

// Open port
port->open( QIODevice::ReadOnly );
mPollTimer = new QTimer( this );
mPollTimer->setInterval( 1000 ); //poll every 1 second
connect( mPollTimer, SIGNAL( timeout() ), this, SLOT( updateSerialData() ) );
mPollTimer->start();
}

void QtApp::updateSerialData()
{
char data[100];
QLabel* label = new QLabel;
if( !port->bytesAvailable() )
retrun;

int charIdx = 0;
int bytesRead;

//
// Find sync word
do
{
data[charIdx] = 'A';
port->read( data, 1 );
} while( data[charIdx] != '$' );
charIdx++;

//
// Read the rest of the data
do
{
charIdx += port->read( &data[charIdx], 1 );
} while( data[charIdx-1] != '*' );


// Read check sum
charIdx += port->read( &data[charIdx], 2 );
data[charIdx] = '\0';


// Copy buffer to QString
*str = data;

//
// Diplay this on GUI
label->setText( *str );
}


You'll have to declare the updateSerialPort slot in your class.
Also, declare a private member QTimer* mPollTimer, and don't forget to delete in the constructor.

This way, you read the serial port every 1 second ( you can modify the interval ).
This solution has one major drawback - you can loose some data. As far as I know, the serial port buffer is overwritten as soon as neew data comes in, regardless if the old data was read or not.

This draw back can be overcome by using a thread, and storing all incoming data in a buffer. The GUI just needs to "consume" the data in the buffer.

Regards

ShaChris23
2nd May 2007, 19:27
Also, if I used signal/slot, whenever I call something like:




// Somewhere in my class declaration
signals:
stringChanged( QString string );

// ...

// Somewhere in my code
QString str;

...

emit stringChanged( str );


Does that string gets copied or does Qt actually use a pointer? I'm just concerned about performance because if it's a copy, and the string is really large, wouldnt that be kind of wasteful?

marcel
2nd May 2007, 19:33
No, because you pass the QString parameter by value. If you're connecting to a slot in the same thread, you can pass a reference to your QString.

Otherwise, if you connect to another thread, you must pass a pointer and also create the QString class member on the heap.

ShaChris23
2nd May 2007, 20:26
Hi Marcel,

For the case of 2 different threads, did you mean that each time the SerialPort class reads a new string, we need to "new" a QString, use signal to pass the pointer, then the consumer (GUI) would display then "delete" that Qstring?

marcel
2nd May 2007, 20:33
No, you just pass the same string, reinitialized. You don't delete it, just empty it. You will delete it just once, in the destructor of the worker thread.

You only have to be careful that you don't use the pointer to the QString after you delete the worker thread.

Regards

ShaChris23
2nd May 2007, 22:58
Here's my "final" implementation. It uses a thread to read data from the serial port, and sends a signal to a GUI class to slot update the value to the screen. The CPU load on this is 0-1% as the Serial Port Read is a true blocking function call.


There are 2 remarks about this:

1) I notice that as time goes on, the program's memory keeps increasing (I checked from Windows task manager. Not a lot, about 4 KB per minute. I couldnt think of anything that could cause this. I tried taking out the serial port Read code, and the program's size still kept increasing. Can it be because of signal/slot?

2) I'm currently passing signal/slot by value. If somebody knows a better way of passing the QString and could show it to me, that'd be great.

That was fun though, now I start to get a hang of it, and I'm going to start adding features.

SerialPortReader.hpp


#pragma once

#include "CSerial.hpp"
#include <QString>
#include <QStringList>
#include <QThread>

class SerialPortReader : public QThread
{
Q_OBJECT

public:
SerialPortReader();
void run();

signals:
void newString( QString string );

private:
QString* str;
QStringList* list;
CSerial* port;
};


SerialPortReader.cpp


#include "SerialPortReader.hpp"

#include <tchar.h>
#include <assert.h>

SerialPortReader::SerialPortReader()
{
str = new QString;
list = new QStringList;
port = new CSerial;

LONG stat;

stat = port->Open( _T("COM1"), 0, 0, true );
assert( stat == ERROR_SUCCESS );

stat = port->Setup( CSerial::EBaud115200,
CSerial::EData8,
CSerial::EParNone,
CSerial::EStop1 );
assert( stat == ERROR_SUCCESS );

stat = port->SetupHandshaking( CSerial::EHandshakeOff );
assert( stat == ERROR_SUCCESS );

stat = port->SetupReadTimeouts( CSerial::EReadTimeoutBlocking );
assert( stat == ERROR_SUCCESS );

}

void SerialPortReader::run()
{
char data[100] = "Hey man how are you?";

while(1)
{
int charIdx = 0;
DWORD bytesRead;

//
// Find sync word
do
{
data[charIdx] = 'A';
port->Read( &data[charIdx],
1,
&bytesRead );
} while( data[charIdx] != '$' );
charIdx++;

//
// Read the rest of the data
do
{
port->Read( &data[charIdx],
1,
&bytesRead );
charIdx++;
} while( data[charIdx-1] != '*' );


//
// Read check sum
port->Read( &data[charIdx],
2,
&bytesRead );
charIdx += 2;
data[charIdx] = '\0';

//
// Copy buffer to QString
*str = data;

//
// Indicate there's a new string
emit newString( *str );
}
}


GUI.hpp


#ifndef QTAPP_H
#define QTAPP_H

#include <QtGui/QMainWindow>
#include "ui_qtapp.h"
#include <QLabel>
#include <QString>
#include "SerialPortReader.hpp"

class QtApp : public QMainWindow
{
Q_OBJECT

public:
QtApp(QWidget *parent = 0, Qt::WFlags flags = 0);

private:
Ui::QtAppClass ui;
SerialPortReader* portThread;

public slots:
void DisplayString( QString string );
};

#endif // QTAPP_H



GUI.cpp


#include "qtapp.h"

QtApp::QtApp(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);

// Start a new thread
portThread = new SerialPortReader;

connect( portThread, SIGNAL(newString(QString)),
this, SLOT(DisplayString(QString)) );

portThread->start();
}

void QtApp::DisplayString( QString string )
{
ui.mLabel->setText( string );
}


UI Generated by Designer


class Ui_QtAppClass
{
public:
QWidget *centralWidget;
QLabel *mLabel;
QStatusBar *statusBar;

//....

}

marcel
3rd May 2007, 05:10
I can't see anywhere why you get those mem leaks... Maybe I'm missing something. Anyway, you should really add destructors to your classes and delete all the pointers you allocate. You will have some leaks in the thread class.

Anyway, why don't you just use a queue of qstrings instead of the single qstring (QQueue. You will pass a pointer to this QQueue to the main GUI, in the rest it is a standard producer/consumer scenario. The GUI thread takse QStrings of the top of the Queue, the worker thread appends them as soon as it gets them.

I realize now you can't pass a single QString pointer because it could get overwritten in the mean time, before it is displayed in the GUI.

Regards

sar_van81
4th May 2007, 09:14
ShaChris23:

can you say me how did you link the qextserial package to qt ? actually i'm trying to read the data's coming from the serial port.i found in the some forum posts that we can use qextserial package. i downloaded and compiled it for my linux PC also .but i could not figure how to use it with the qt. when i compiled it, i found only the libraries where created. In your code i found that you had added the header file "#include "qextserialport.h"" and created an object for qextserial. so i my code if i simply add the header will it work ? or should i place the library in some location in qt-embedded