Software Programming/Qt2018. 3. 3. 02:38


AVR 시리즈의 칩을 사용하는 Arduino 보드와 STM32F 시리즈 칩을 사용하는 Nucleo 보드의 가장 큰 장점은, UART(Universal asynchronous receiver/transmitter) 통신을 이용하여 PC로부터 프로그래밍(업로드)과 디버깅이 가능하다는 것입니다. 이것이 가능한 이유는 USB를 경유한 UART 통신이 가능하도록 전용 칩을 별도로 보드에 내장하기 때문입니다.


따라서 Arduino와 Nucleo 보드에 작성한 임베디드(embedded) 프로그래밍과 소통하고 서로 데이터를 주고 받기 위해서는 PC 상에 인터페이스 프로그램이 필요하고, 이 간단한 Qt 프로그램은 Arduino나 Nucleo 보드와 UART(serial) 통신을 위한 예제입니다. 아이디어는 다음의 링크에서 참조하였습니다.

http://archive.fabacademy.org/2016/fablabhrw/students/162/week_16.html


mainwindow.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
#include <QDebug>
 
namespace Ui {
class MainWindow;
}
 
class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
 
public slots:
    void serial_connect();
    void serial_rescan();
    void widget_changed();
    void send_test();
    void serial_received();
 
private:
    Ui::MainWindow *ui;
};
 
#endif // MAINWINDOW_H
 
cs


main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "mainwindow.h"
#include <QApplication>
#include <qlabel.h>
 
int main(int argc, char* argv[])
{
   QApplication a(argc, argv);
 
   MainWindow w;
//   w.setWindowTitle(QString::fromUtf8("Qt Serial communication"));
//   w.setFixedSize(300, 150);
   w.show();
 
   return a.exec();
}
 
cs


mainwindow.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <qserialport.h>
#include <qserialportinfo.h>
#include <stdio.h>
 
QSerialPort *serial;
 
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
        ui->setupUi(this);
        ui->lcd_temp->setPalette(Qt::red);
        serial = new QSerialPort(this);
        connect(ui->rescan_Button, SIGNAL(clicked()), this, SLOT(serial_rescan()));
        connect(ui->connect_button, SIGNAL(clicked()), this, SLOT (serial_connect()));
        connect(ui->slider1, SIGNAL(valueChanged(int)), this, SLOT(widget_changed()));
        connect(ui->slider2, SIGNAL(valueChanged(int)), this, SLOT(widget_changed()));
        connect(ui->dial, SIGNAL(valueChanged(int)), this, SLOT(widget_changed()));
        connect(serial, SIGNAL(readyRead()), this, SLOT(serial_received()));
        serial_rescan();
}
 
MainWindow::~MainWindow()
{
    delete ui;
    serial->close();
}
 
void MainWindow::serial_connect()
{
        serial->setPortName(ui->port_box->currentText());
        serial->setBaudRate(QSerialPort::Baud115200);
        serial->setDataBits(QSerialPort::Data8);
        serial->setParity(QSerialPort::NoParity);
        serial->setStopBits(QSerialPort::OneStop);
        serial->setFlowControl(QSerialPort::NoFlowControl);
//        serial->write("Hello World!\n");
 
        if(!serial->open(QIODevice::ReadWrite)){
            qDebug() << "Serial port open error";
        }
}
 
void MainWindow::serial_rescan()
{
    ui->port_box->clear();
    foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()){
        ui->port_box->addItem(serialPortInfo.portName());
    }
}
 
void MainWindow::widget_changed()
{
    QString buffer;
    buffer.sprintf("A%03i;%03i;%03i\n",ui->slider1->value(),ui->slider2->value(),ui->dial->value());
    qDebug() << buffer;
    serial->write( buffer.toStdString().c_str(), buffer.size());
    serial->QSerialPort::waitForBytesWritten(-1);
    ui->lcd_led1->display(ui->slider1->value());
    ui->lcd_led2->display(ui->slider2->value());
    ui->lcd_pwm->display(ui->dial->value());
}
 
void MainWindow::send_test()
{
    serial->write("test!\n");
}
 
 
void MainWindow::serial_received()
{
//    usleep(10000);
//    QString received = serial->readAll();
    QString received;
    while (serial->canReadLine()){
        received = serial->readLine();  //reads in data line by line, separated by \n or \r characters
    }
 
    QStringList splitted = received.split(";");
    if (splitted.value(0== "A"){
        ui->lcd_temp->display(splitted.value(1));
    }
}
 
cs


Qt 프로그램의 가장 큰 특징은 signal/slot 시스템입니다. 이는 오브젝트간 통신을 위해 사용되는데 예를 들어, GUI 프로그래밍에서 버튼을 눌렀을 때, 그 이벤트를 받아서 label에 텍스트를 출력한다거나 할 때에 사용됩니다. 이는 굳이 GUI가 없는 console 프로그래밍에서 비동기 호출이 필요한 pipe/socket/serial port 등에서도 유용하게 사용될 수 있습니다. 여기서 signal이란 Win32 프로그래밍에서 message 처리와 같은 개념입니다.


Qt의 signal/slot 시스템은 QObject를 상속받은 모든 class에서 사용할 수 있으며, 사용자에게 일관된 이벤트 인터페이스를 제공합니다. 사실 오브젝트간 통신에서 쓸 수 있는 가장 보편적인 방법으로 callback 또는 인터페이스가 존재하고 있지만, 첫째 함수를 처리할 때 정확한 매개변수 타입으로 호출하게 될 것인지 알 수 없다는 것으로 타입 안정성이 낮고, 둘째로 객체 지향 프로그램(Object Oriented Programming)을 지향하는 언어에서 흔히 쓰이는 인터페이스를 사용하는 방법은 오브젝트간에 강한 커플링으로 상호 참조가 생기거나 래퍼런스 카운팅에 문제가 생길 수 있습니다.


Qt는 이런 방식의 대안으로 타입 안정성을 갖고 있으며 이벤트를 발생시키는 오브젝트와 이벤트를 처리하는 핸들러 오브젝트간에 아무런 연관 관계가 없어, 서로 참조하지 않으면서도 사용이 가능하다는 것입니다.



위 그림에서와 같이 Qt에서 signal과 slot이라는 멤버 함수는 connect() 함수로 서로 연결되며, slot이 처리해야 할 signal을 감시하고 있다가 signal이 발생하면 slot 함수가 실행되게 됩니다. signal과 slot 함수는 1:1 연결 뿐만 아니라 1:N으로도 연결 가능합니다.


이 프로그램에서는 'mainwindow.h' 파일의 13번 라인에서 Q_OBJECT로 상속받았고 'mainwindow.cpp'의 16~21번 라인에서 다음과 같이 5개의 connect() 함수로써 signal과 slot 함수를 연결시켰습니다.


1
2
3
4
5
6
connect(ui->rescan_Button, SIGNAL(clicked()), this, SLOT(serial_rescan()));
connect(ui->connect_button, SIGNAL(clicked()), this, SLOT (serial_connect()));
connect(ui->slider1, SIGNAL(valueChanged(int)), this, SLOT(widget_changed()));
connect(ui->slider2, SIGNAL(valueChanged(int)), this, SLOT(widget_changed()));
connect(ui->dial, SIGNAL(valueChanged(int)), this, SLOT(widget_changed()));
connect(serial, SIGNAL(readyRead()), this, SLOT(serial_received()));
cs


1번 라인의 의미는 'rescan_Button'이 클릭(clicked())되어 signal이 발생하면 'serial_rescan()'이라는 slot 함수를 실행시킴을 의미하고, 3번 라인은 'slider1'이 값이 변동(valueChanged)하였다는 signal이 발생하면 'widget_change()'이라는 slot 함수를 실행시킴을 의미하고, 마지막으로 6번 라인에 'serial' 포트가 준비가 되었다(readyRead())는 signal이 발생하면 'serial_received()'이라는 slot 함수가 실행됨을 의미합니다.


다음은 예를 들어 push button의 경우에 발생할 수 있는 signal을 Qt Designer로 본 것입니다. 다만 모든 signal들이 Qt Designer에서 보여지지는 않으므로 라이브러리를 참고하시길 바랍니다.



serialport.pro

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#-------------------------------------------------
#
# Project created by QtCreator 2016-05-22T13:08:09
#
#-------------------------------------------------
 
QT       += core gui serialport
 
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 
TARGET = serialport
TEMPLATE = app
 
 
SOURCES += main.cpp\
        mainwindow.cpp
 
HEADERS  += mainwindow.h
 
FORMS    += mainwindow.ui
 
cs


Qt Designer의 화면입입니다.


다음은 Qt로 작성한 serialport.exe의 실행화면입니다.


USB를 이용하여 PC와 연결하고자 하는 Nucleo 보드나 기타 보드와 송수신 데이터의 패킷 구성은 사용자가 임의로 정할 수 있습니다. 이 프로그램의 경우에는 송수신 데이터를 'A', ';' 그리고 '\n' 문자를 식별자로 구분하였습니다. 따라서 사용자는 해당 보드의 임베디드 프로그램에서 이와 같은 식별자를 이용하여 합니다.


Serialport.zip



'Software Programming > Qt' 카테고리의 다른 글

Hello world 예제  (0) 2018.03.02
Qt 프로그램 소개  (0) 2017.12.24
Posted by Nature & Life