'callback'에 해당되는 글 2건

  1. 2018.03.03 UART(Serial) 통신 예제 2
  2. 2017.05.08 인터럽트의 처리(2)
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
Embedded Lecture/Arduino2017. 5. 8. 20:34


인터럽트(interrupt)란 지정된 핀의 신호가 원하는 조건과 일치하면 미리 등록한 인터럽트 callback 함수(ISR; Interrupt Service Routines)를 자동으로 호출해주는 기능입니다. 이 ISR 함수는 사용자가 만든 함수이며, 이때 실행 중이던 loop() 함수 내부의 루틴은 인터럽트 callback 함수가 끝날 때까지 멈추게 됩니다.


즉, 특정 핀의 입력 상태가 바뀔 때 Arduino는 이를 자동으로 감지해서 모든 동작을 잠시 멈춘 다음, ISR(Interrupt Service Routines) 이라 불리는 미리 지정된 함수를 실행하고, 다시 원래 작업으로 복귀한다는 것입니다. 이를 '하드웨어 인터럽트'라 부릅니다. 이 외에도 비슷한 타이머(timer) 인터럽트가 있는데 이는 사용법이 전혀 다르므로 이 글에서 다루진 않습니다.

♧ 타이머 인터럽트 - AVR 칩을 비롯한 임베디드 시스템에서는 시간을 재기 위해서 타이머(Timer)/카운터(Counter)를 내장합니다. 만일 타이머/카운터가 없다면, 소프트웨어적으로 시간지연 함수를 사용해야 함으로써 MCU는 동작을 멈추게 되고, 이로 인해 MCU의 효율을 떨어뜨리게 된다는 것입니다.

뿐만 아니라 이와 같은 내장 타이머/카운터는 정확한 시간을 잴 수 있으며, MCU가 다른 작업과 병행할 수 있고, 미리 설정한 조건에서 타이머 인터럽트도 발생하게 할 수 있다는 것입니다. 이러한 각각 인터럽트들은 동시에 발생할 수도 있으므로 우선 순위를 갖습니다.


Arduino의 인터럽트 핀

Arduino Uno 기준으로 2개의 인터럽트 핀이 할당되어 있습니다. 즉, Number 0 (D2), Number 1 (D3). 몇개의 보드별 지원되는 인터럽트 핀은 다음과 같습니다. 인터럽트 callback(ISR)을 등록할 때 유의할 점은 핀 번호가 아니라 반드시 인터럽트 넘버를 사용한다는 것입니다. 예를 들어, 다음 표에서 Leonardo 보드는 Uno 보다 많은 5개의 인터럽트 핀을 제공합니다. 그리고 다섯번째 인터럽트 핀(int4)은 Number 7이라는 것입니다. 참고로 Arduino Due 보드는 모든 핀에 인터럽트가 지원됩니다.


 보드

 int0

 int1

 int2

 int3

 int4

 int5

 Uno/Ethernet

 2

 3

 x

 x

 x

 x

 Mega2560

 2

 3

 21

 20

 19

 18

 Leonardo

 3

 2

 0

 1

 7

 x


인터럽트 callback 함수(ISR)는 입력 인자가 없고 반환값이 없습니다. 즉 파라미터를 전달하거나 리턴할 수가 없다는 것입니다. 게다가 ISR 함수 내에서는 delay() 함수를 사용할 수 없습니다. 또한 milli second의 시각을 가져오는 함수인 millis()를 사용하더라도 값이 증가하지는 않습니다. delayMicroseconds()의 경우에는 인터럽트에 독립적이므로 정상 동작합니다.


뿐만 아니라 ISR 함수 내에서는 Serial data를 읽을 경우 값이 소실되며, 이전 글에서 언급했던 것처럼 ISR 함수 내에서 업데이트 되는 전역 변수는 volatile로 반드시 선언되어야 합니다. ISR 함수를 만드는 요령은 최대한 짧고 빠르게 수행되도록 간결하게 작성해야 합니다. 왜냐면 이 코드가 길어지는 만큼 CPU는 멈추어 메인 작업을 수행하지 않아 효율적이지 않다는 것입니다.


또한 여러 개의 ISR 함수가 등록되어 있더라도 동시에 수행되지는 않습니다. 블루투스 모듈과의 통신을 위해 주로 사용되는 Software Serial 라이브러리의 경우, 내부적으로 인터럽트를 사용하는 것으로 알려져 있습니다. 따라서 Uno 보드의 경우 D2, D3 에 연결해야만 정상 동작 가능합니다. 따라서 이를 회피하기 위해서 D0, D1 핀에 연결해서 Hardware Serial로 동작시킬 수 있습니다.



Posted by Nature & Life