VirtualBox ja Qt - Sarjaportti Qt:hen

Artikkeli jatkuu sarjaportin liittämisellä Virtualbox/QT ympäristöön ja virtuaalisen sarjaportin luomisella. Pahoitteluni videoiden hieman huonosta laadusta, kaikista teksteistä ei aina saa kunnolla selvää.

[size=150]Qt ja sarjaportti[/size]
QT ei natiivina tue sarjaporttia. Sarjaportin käyttöön on olemassa muutamia valmiita kirjastoja, QextSerialPort ja QSerialDevice. Itse käytän QextSerialPort kirjastoa, koska se on tutumpi. Olen kokeillut myöskin QSerialDevice:ä mutta itselläni ilmeni hieman ongelmia käännöksessä (win XP) joten jätin kyseisen kirjaston väliin. Riittävällä kokemuksella molemmat ovat täysin toimivia ratkaisuja. Molemmat kirjastot periytyvät QIODevice luokasta.

[size=150]Virtuaalisen sarjaportin luonti ja sen asentaminen virtualboxiin[/size]
Jos koneessa ei ole sarjaporttia tai sitä ei haluta käyttää on mahdollista käyttää virtuaalista sarjaportti emulaattoria (VSPE) (älä sekoita esim usb-sarjaportti muuntimeen). Tällä tavoin on helppo emuloida sarjaportin toimintaa. Täältä löydät ohjelman, jolla voidaan luoda useita erilaisia virtuaalisia portteja. VSPE ohjeessa on selitetty eri moodien merkitys. tässä esimerkissä luodaan virtuaalinen liiitin, johon voidaan liittää monta laitetta ja siirtää dataa niiden välillä. Alla oleva video selventää luomista.

Youtube:RP-artikkeli,jatkoa:VSPE

Ainakaan Windows 7:ssä portin olemista ei huomaa ilman tiedonsiirto-ohjelman avaamista ja kokeilua. Windows 7:ssä ei ole hyperterminalia mutta vanhan version käyttö on mahdollista. Täältä löydät vaihtoehtoja, itse käytän XP:n aikaista hyperterminalia. Hyperterminalin vaatimien tiedostojen siirto onnistuu helposti jaetun kansion lävitse. Seuraavaksi virtuaalikoneeseen tarvitsee lisätä sarjaportti. Sarjaportti voi olla “irroitettu”, “isäntäkoneen putki”, “isäntäkoneen laite” tai “Raw file”.


Tarkempaa tietoa antaa Virtualboxin ohje kohdassa “3.9. Serialports”. Itse päädyin “isäntäkoneen laite” valintaan jolloin riville kirjoitetaan COM1 (tämä riippuu käytössä olevasta sarjaportista). Virtuaalikone auki (huomaathan että asetukset ovat lukossa, jos ko. kone on avoinna). XP ei ainakaan itselläni suoraan löytänyt sarjaporttia, se lisätään “lisää uusi laite” valinnan kautta. Sarjaportin löydyttyä on aika kokeilla virtuaaliportin toimintaa. Muista määrittää databitit, stoppibitit, parieteetit ja kättelyt samalla tavalla tai tieto ei siirry oikein. Alla olevasta videosta näkyy esimerkki virtuaaliportin toiminnasta.

Youtube:RP-artikkeli,jatkoa:Sarjaportin testaus

Seuraavaksi päästään QT:n koodaukseen.

[size=150]QextSerialPortin kääntö ja sen käyttöönotto[/size]
Varmuuden vuoksi loin “puhtaan” virtuaalisen XP:n johon asensin QT:n ja muut tarvittavat ohjelmat. Eli tämä on suoraa jatkoa siitä mihin artikkelin ensimmäisessä osassa jäätiin. QextSerialPort:n linkki. Download-> "Download qextserialport->“1.2win-alpha.zip (276.9 KB)” tai vaihtoehtoisesti Download->qextserialport alpha->1.2win-alpha->“qextserialport-1.2win-alpha.zip”. Seuraavaksi puretaan tiedostot ja pistetään kansion paikka muistiin. Itse purin tiedostot c:\qt-kansioon ja muutin kansion nimen “qextserialport” jotta sen muistaa helpommin.

Seuraavaksi muokkasin qextserialport.pro tiedostoa seuraavanlaiseksi
qextserialport.pro

[code]PROJECT = qextserialport
TEMPLATE = lib

#CONFIG += debug_and_release
#nämä on rivit lisätty
CONFIG += release
CONFIG += debug

CONFIG += qt
CONFIG += warn_on
CONFIG += thread

CONFIG += dll
#CONFIG += staticlib

QT -= gui

OBJECTS_DIR = build/obj
MOC_DIR = build/moc
DEPENDDIR = .
INCLUDEDIR = .
HEADERS = qextserialbase.h
qextserialport.h
qextserialenumerator.h
SOURCES = qextserialbase.cpp
qextserialport.cpp
qextserialenumerator.cpp

unix:HEADERS += posix_qextserialport.h
unix:SOURCES += posix_qextserialport.cpp
unix:DEFINES += TTY_POSIX

win32:HEADERS += win_qextserialport.h
win32:SOURCES += win_qextserialport.cpp
win32:DEFINES += TTY_WIN

win32:LIBS += -lsetupapi

DESTDIR = build
#DESTDIR = examples/enumerator/debug
#DESTDIR = examples/qespta/debug
#DESTDIR = examples/event/debug

CONFIG(debug) {
TARGET = qextserialportd
} else {
TARGET = qextserialport
}

unix:VERSION = 1.2.0

[/code]
Kääntämiseksi Käynnistetään “QT Command Prompt”

Etsitään qextserialport hakemisto ja ajetaan seuraavat komennot:

qmake qextserialport.pro mingw32-make Näytöllä pyörii hetken ajan tekstiä kun ohjelmaa käännetään. Komentorivi sammuu komennolla “exit”. Lopputuloksena löytyy qextserialport-hakemistosta “build”-hakemisto ja sieltä neljä tiedostoa (ja kaksi kansiota).


Käynnistetään QT ja luodaan uusi widget-projekti. Kun projekti on luotu kopioidaan qextserialport.h tiedosto oman projektin hakemistoon.

Ja lisätään oman projekti .pro-tiedostoon muutama rivi.
rstest.pro

#-------------------------------------------------
#
# Project created by QtCreator 2011-03-25T22:51:33
#
#-------------------------------------------------

QT       += core gui

TARGET = rsTestWidget
TEMPLATE = app

SOURCES += main.cpp\
        rstest.cpp

INCLUDEPATH += C:\Qt\qextserialport
QMAKE_LIBDIR += C:/Qt/qextserialport/build

#HEADERS  += rstest.h\
#tähän muotoon, tai projektin nimen päällä oikea klik-> "add existing files"
HEADERS  += rstest.h\
            qextserialport.h

FORMS    += rstest.ui

#Nämä lisätään
CONFIG(debug):LIBS  += -lqextserialportd
else:LIBS  += -lqextserialport

unix:DEFINES = _TTY_POSIX_
win32:DEFINES = _TTY_WIN_ QWT_DLL QT_DLL

Seuraavaksi lisätään polku qextserialport-kirjastoon. Nopein ja helpoin tapa windows puolella on siirtää qextserialportd.dll ja qextserialport.dll windows:n juureen. Vaihtoehtoinen tapa on lisätä QT:n path muuttujaan seuraava teksti ;C:\Qt\qextserialport\build;C:\Qt\qextserialport TÄMÄ PITÄÄ TEHDÄ SEKÄ “DEBUG” ETTÄ “RELEASE”-MOODISSA!

HUOM! hakemisto voi olla eri kuin tässä esimerkissä. Muokkaa hakemisto oman hakemistorakenteesi mukaiseksi.

[size=150]Käyttöliittymän luonti ja koodi[/size]
Luodaan käyttöliittymään kaksi nappulaa, yksi “text edit” ja yksi “line edit”. Kuva selventää asiaa, teksteistä ilmenee ko objektin nimi. Näitä nimiä voidaan muuttaa valitsemalla objekti ja muokkaamalla oikean reunan “object name”-kentän arvoa.

Kuvassa on valittu clrBtn objekti ja sen nimen muutos.

Seuraavaksi tehdään vähän koodia. Tavallisuudesta poiketen en piirtänyt nappien yhteyksiä, tekemällä ne suoraan koodilla ei tarvitse lähteä arvailemaan yhteyksiä. Eikä tarvitse muistaa ulkoa SLOT:n tarkkoja nimiä.

rsTest.h

#ifndef RSTEST_H
#define RSTEST_H

#include <QWidget>
#include "qextserialport.h"


namespace Ui {
    class rsTest;
}

class rsTest : public QWidget
{
    Q_OBJECT

public:
    explicit rsTest(QWidget *parent = 0);
    ~rsTest();

    private slots:
    void clearText();
    void openComPort();

private:
    Ui::rsTest *ui;
    QextSerialPort *myCom;
    void initComPort();
   
};

#endif // RSTEST_H

rsTest.cpp

#include "rstest.h"
#include "ui_rstest.h"

rsTest::rsTest(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::rsTest)
{
    ui->setupUi(this);
    connect(ui->clrBtn,SIGNAL(clicked()),this,SLOT(clearText()));
    connect(ui->openBtn,SIGNAL(clicked()),this,SLOT(openComPort()));
    initComPort(); //portin määritykset
    ui->inText->setText("ohjelma käynnistetty");
}

rsTest::~rsTest()
{
    delete ui;

}

void rsTest::clearText(){
ui->inText->clear(); //tyhjennä näyttö
ui->debugLine->setText("näyttö tyhjennetty");
}

void rsTest::initComPort(){
 /* Määritykset löydät täältä 

http://qextserialport.sourceforge.net/qextserialport-1.1.x/
    Muokkaa määritykset omien tarpeidesi mukaan. En käytä timeout määrettä jotta portti jää
    auki aina kun nappulaa on painettu.
*/
myCom = new QextSerialPort("COM1");
myCom->setBaudRate(BAUD9600);
myCom->setDataBits(DATA_8);
myCom->setFlowControl(FLOW_OFF);
myCom->setParity(PAR_NONE);
myCom->setStopBits(STOP_1);
ui->debugLine->setText("Määritykset 9600,8N1,no flow");
}

void rsTest::openComPort(){
    myCom->open(QIODevice::ReadOnly);
    ui->debugLine->setText("portti avattu");
    QString inText;
    char buff[1024];
    int numBytes;
    numBytes = myCom->bytesAvailable();
    if(numBytes > 0){
        if(numBytes > 1024){
            numBytes = 1024;
            }
     int i = myCom->read(buff, numBytes);
     buff[i] = '\0';
     inText = buff;
    }else{
       ui->debugLine->setText("Ei luettavaa");
       }
    QString tmpStr;
    tmpStr=ui->inText->toPlainText()+inText;
    ui->inText->setText(tmpStr);
    }

Jos ohjelma antaa käännöksessä virheen Starting C:\Qt\rsTestWidget-build-desktop\debug\rsTestWidget.exe... C:\Qt\rsTestWidget-build-desktop\debug\rsTestWidget.exe exited with code -1073741515
niin tällä viitataan dll-tiedoston puuttumiseen. Tämän virheen korjaus vei (ainakin omalla kohdalla) suurimman osan debuggaamiseen kuluvasta ajasta. Ohjelmassa portista luetaan aina tietoa kun nappulaa painetaan. Muistiin mahtuu 1023 merkkiä, joten se pitää tyhjentää aika ajoin. Tähän toimii esimerkiksi QTimer. Toinen vaihtoehto on lukea porttia aina kun dataa on saatavilla, tämä perustuu “readyReady”-tapahtuman (event) käyttöön. Alla olevasta linkistä löytyy video ensimmäisen ohjelma version toiminnasta.

Youtube:RP-artikkeli,jatkoa: Ensimmäinen ohjelma versio

[size=150] Event ohjattu portin luku sekä kirjoitus sarjaporttiin[/size]
Periaatteessa eventin käyttö on helppoa, lisätään yksi määrite lisää portin määrityksiin, avataan portti valmiiksi ja luodaan yhteys readyRead()-tapahtuman ja luku-funktion välille. Pari sivustoa asian tiimoilta, QT interest threadi ja KDE-api artikkeli Muutin koodia event-ohjattuun muotoon ja samalla lisäsin nappulan, joka on elektroniikka termein OFF-ON toiminnallinen, normaalin OFF-MOM tilalla. Koska koodaus tuntui sujuvan mukavasti niin tein vielä lähetä nappulankin niin saadaan kaksi suuntainen tiedonsiirto aikaiseksi. Alla kuva käyttöliittymästä ja koodit.


rstest.h

#ifndef RSTEST_H
#define RSTEST_H

#include <QWidget>
#include "qextserialport.h"


namespace Ui {
    class rsTest;
}

class rsTest : public QWidget
{
    Q_OBJECT

public:
    explicit rsTest(QWidget *parent = 0);
    ~rsTest();

    private slots:
    void clearText();
    void openComPort();
    void eventOn();
    void sendTxt();

private:
    Ui::rsTest *ui;
    QextSerialPort *myCom;
    void initComPort();
    bool eventBool;

};

#endif // RSTEST_H

rstest.cpp

#include "rstest.h"
#include "ui_rstest.h"

rsTest::rsTest(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::rsTest)
{
    ui->setupUi(this);
    connect(ui->clrBtn,SIGNAL(clicked()),this,SLOT(clearText()));
    connect(ui->openBtn,SIGNAL(clicked()),this,SLOT(openComPort()));
    connect(ui->eventBtn,SIGNAL(clicked()),this,SLOT(eventOn()));
    connect(ui->sendBtn,SIGNAL(clicked()),this,SLOT(sendTxt()));
    initComPort(); //portin määritykset
    ui->inText->setText("ohjelma käynnistetty");
    ui->eventBtn->setCheckable(true);
    eventBool=false;
}

rsTest::~rsTest()
{
    delete ui;

}

void rsTest::clearText(){
ui->inText->clear(); //tyhjennä näyttö
ui->debugLine->setText("näyttö tyhjennetty");
}

void rsTest::initComPort(){
 /* Määritykset löydät täältä 

http://qextserialport.sourceforge.net/qextserialport-1.1.x/
    Muokkaa määritykset omien tarpeidesi mukaan. En käytä timeout määrettä jotta portti jää
    auki aina kun nappulaa on painettu.
*/
myCom = new QextSerialPort("COM1");
myCom->setBaudRate(BAUD9600);
myCom->setDataBits(DATA_8);
myCom->setFlowControl(FLOW_OFF);
myCom->setParity(PAR_NONE);
myCom->setStopBits(STOP_1);
myCom->setQueryMode(QextSerialBase::EventDriven); //tämä rivi lisätty
ui->debugLine->setText("Määritykset 9600,8N1,no flow");
}

void rsTest::openComPort(){
    //nämä kolme riviä lisätty/muokattu
    if(!myCom->isOpen()){ //jos portti ei ole auki niin avataan
        //huomaa määre READWRITE jotta kirjoitus onnistuu!
        myCom->open(QIODevice::ReadWrite);
        ui->debugLine->setText("portti avattu");
   }
    QString inText;
    char buff[1024];
    int numBytes;
    numBytes = myCom->bytesAvailable();
    if(numBytes > 0){
        if(numBytes > 1024){
            numBytes = 1024;
            }
     int i = myCom->read(buff, numBytes);
     buff[i] = '\0';
     inText = buff;
    }else{
       ui->debugLine->setText("Ei luettavaa");
       }
    QString tmpStr;
    tmpStr=ui->inText->toPlainText()+inText;
    ui->inText->setText(tmpStr);
    }

// Tämä funktio on lisätty kokonaan uutena
void rsTest::eventOn(){
    if(!myCom->isOpen()){ //jos portti ei ole auki niin avataan
        myCom->open(QIODevice::ReadWrite);
        ui->debugLine->setText("portti avattu");
       }

    if(!eventBool){
        ui->eventBtn->setChecked(true); //nappi pohjaan
        eventBool=true; //merkki että event on päällä
        connect(myCom,SIGNAL(readyRead()),this,SLOT(openComPort())); //luodaan yhteys
    }else if(eventBool){
        ui->eventBtn->setChecked(false); //nappi ylös
        eventBool=false;//merkki että event on pois päältä
        disconnect(myCom,SIGNAL(readyRead()),this,SLOT(openComPort())); 
	//poistetaan yhteys
    }
}

void rsTest::sendTxt(){
    //lähetetään myCom porttiin debugLinen teksti, määritteet teksti asciina ja pituus
    myCom->write((ui->debugLine->text()).toAscii(),(ui->debugLine->text()).length());
}

Ja tietysti video toimivasta ohjelmasta.

Youtube: RP-artikkeli,jatkoa: Toinen versio ohjelmasta

Kaikki lähetys tapahtuu siis todellisen käyttöjärjestelmän ja virtuaalikoneen välillä. Porttiin tullut tieto voidaan käsitellä esim QString tyyppisenä, jolloin parserointi/käsittely vaihtoehtoja on paljon. Toivottavasti nämä muutama esimerkki helpottaa siirtymistä esim VB maailmasta QT:n puolelle. QT on monipuolinen eikä rajoitu käyttöjärjestelmän osalta. Qt on saatavilla myös ARM-puolella. Täältä löydät laajan projektin, jossa käytetään ARM-prosessoria ja Qt:ta.

Kommentoikaa, varsinkin jos koodista löytyy virheitä…