Comunicação serial com Arduino e Qt5 Widgets Application – Parte 2

Qt5 Widgets Application

No post anterior vimos como criar um projeto no Qt Creator para interface gráfica. Que será nossa base para finalizar a aplicação de comunicação serial com uma GUI (Graphical User Interface).

Nesta segunda parte vamos ver como criar toda estrutura básica necessária para realizar comunicação serial e realizar interação com nos Widgets.

Para compreender este post é interessante ter lido o post anterior, e também o primeiro post dessa série, que mostro como preparar o ambiente para comunicação serial e um exemplo básico usando QSerialPort.

Configuração do ambiente host

Estou utilizando a distribuição Linux Mint 17 Qiana 64 bits, mas sem problemas em utilizar um Ubuntu ou Debian. A seguir são listados os seus pré-requisitos: 

  • Qt5 Instalado com o Qt Creator;
  • IDE Arduino >=1.6.2;
  • Arduino UNO;
  • Plugin Qt Serial.

Estruturando o código e criando classes

Baseado no projeto da primeira parte, com ele aberto, clique com o botão direito do mouse no nome do Projeto e vá em Add New…

Uma janela como da Figura 1 irá surgir, selecione C++ > C++ Class e clique em Choose.

Qt5 Widgets NewClass
Figura 1 – Criando classe C++ no Qt5

Na janela a seguir, como da Figura 2, devemos passar o nome da classe a ser criada, no caso, sera comserial, as demais opções são preenchidas e Base class não é importante agora, clique em Next.

Qt5 Widgets - Criando a classe
Figura 2 – Criando a classe comserial no Qt5

Na última janela como da Figura 3, clique em Finish.

Qt5 Widgets - Concluindo a criação da classe
Figura 3 – Concluindo a criação da classe

Como pode ser visto na Figura 4, foi criada a estrutura para nossa classe comserial (.cpp e .h).

Qt5 Widgets - Estrutura da classe criada
Figura 4 – Estrutura da classe criada

Para criar a estrutura de código vamos alterar o comunicacao_serial_qt_gui.pro como visto no primeiro post dessa série. 

Qt5

QT += serialport

Qt4

CONFIG += serialport
Qt5 Widgets - Editando o arquivo .pro para serialport
Figura 5 – Editando o arquivo .pro para serialport

Todo o processo para abrir o dispositivo serial (/dev/ttyXXXX) e realizar leitura/escrita sobre o dispositivo será o mesmo que vimos no post anterior com Qt Console. Para facilitar eu criei algumas funções que ficaram dentro da classe comserial que criamos, então agora copie o código abaixo referente ao comserial.h.

#ifndef COMSERIAL_H
#define COMSERIAL_H


/** 
 * Incluindo QDebug para enviar debug em background no terminal
 * QSerialPort e QSerialPortInfo para manipular o dispositivo serial
 */
#include <QDebug>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>

class comserial
{
public:
    comserial(QSerialPort *myDev);
    ~comserial();

    QStringList CarregarDispositivos();

    bool Conectar(QString Port, uint32_t bd);
    bool Desconectar(void);

    qint64 Write(const char *cmd);
    QString Read();
    QString Read(int TamanhoBuffer);

protected:
    QSerialPort *devSerial;
};

#endif // COMSERIAL_H

E agora o comserial.cpp, você pode copiar o código abaixo, ou clicar com o botão direito do mouse no protótipo da função em comserial.h e ir em Refactor > Add Definition in comserial.cpp e a estrutura da função é criada automaticamente no .cpp. Inseri o código referente à cada função abaixo.

#include "comserial.h"


/**
 * @brief CommSerial::CommSerial
 *
 * Função do metodo construtor, inicia variaveis e objetos privados para uso da classe
 *
 * @param
 * @return
 */
comserial::comserial(QSerialPort *myDev)
{
    devSerial = myDev;
}


/**
 * @brief CommSerial::CarregarDispositivos
 *
 * Função que verifica todas as portas disponiveis /dev/tty*
 * e na sequencia seta a porta com serial.setport e testar se é uma
 * porta serial
 *
 * @param void
 * @return QStringList com os devices do /dev que são portas seriais
 */
QStringList comserial::CarregarDispositivos()
{
    QStringList devs;

    foreach (const QSerialPortInfo info, QSerialPortInfo::availablePorts()) {

        devSerial->setPort(info);


        if (devSerial->open(QIODevice::ReadWrite)) {
            devSerial->close();
            devs << info.portName();
        }

    }
    return devs;
}



/**
 * @brief CommSerial::Conectar
 *
 * Função que recebe os parametros Porta, BaudRate e FlowControl e realiza conexão
 *
 * @param QString Port(Porta Serial), uint32_t bd(BaudRate)
 * @return
 */
bool comserial::Conectar(QString Port, uint32_t bd)
{
    /* Device Serial Port */
    devSerial->setPortName(Port);
    qDebug() << "Dispositivo Porta Serial: " << Port;


     /* Conectar SerialPort */

        /* BaudRate */
        switch (bd) {
        case 2400:
            qDebug() << "Baudrate: 2400";
            devSerial->setBaudRate(QSerialPort::Baud2400);
            break;
        case 4800:
            qDebug() << "Baudrate: 4800";
            devSerial->setBaudRate(QSerialPort::Baud4800);
            break;
        case 9600:
            qDebug() << "Baudrate: 9600";
            devSerial->setBaudRate(QSerialPort::Baud9600);
            break;
        case 19200:
            qDebug() << "Baudrate: 19200";
            devSerial->setBaudRate(QSerialPort::Baud19200);
            break;
        case 115200:
            qDebug() << "Baudrate: 115200";
            devSerial->setBaudRate(QSerialPort::Baud115200);
            break;
        }

        /* FlowControl */
        devSerial->setFlowControl(QSerialPort::NoFlowControl);


        /* Configurações adicionais */
        devSerial->setDataBits(QSerialPort::Data8);
        devSerial->setParity(QSerialPort::NoParity);
        devSerial->setStopBits(QSerialPort::OneStop);


        if(devSerial->open(QIODevice::ReadWrite)) {
            qDebug() << "Porta serial aberta com sucesso!";
            return true;
        }
        else {
            qDebug() << "Falha ao abrir porta serial!";
            qDebug() << "Erro: " << devSerial->error();
            return false;
        }


}


/**
 * @brief CommSerial::Desconectar
 *
 * Função Desconectar, realiza a limpeza do componente e fecha
 *
 * @param
 * @return
 */
bool comserial::Desconectar()
{
    devSerial->clear();
    devSerial->close();


    if(devSerial->error() == 0 || !devSerial->isOpen()) {
        qDebug() << "Porta serial fechada com sucesso!";
        return true;
    }
    else {
        qDebug() << "Falha ao fechar a porta serial! ERRO: " << devSerial->error();
        return false;
    }

}


/**
 * @brief CommSerial::Write
 *
 * Função para escrever na serial, recebe um ponteiro de caracteres já no formato do const char* do write
 *
 * @param const char *cmd
 * @return void
 */
qint64 comserial::Write(const char *cmd)
{
    qint64 tamanhoEscrito;
    tamanhoEscrito = devSerial->write(cmd,qstrlen(cmd));

    return tamanhoEscrito;
}


/**
 * @brief CommSerial::Read
 *
 * Função Realiza a leitura do que chegar pela serial apos escrever na mesma, e devolve um QString
 *
 * @param
 * @return QString
 */
QString comserial::Read()
{
     QString bufRxSerial;

     /* Awaits read all the data before continuing */
     while (devSerial->waitForReadyRead(20)) {
         bufRxSerial += devSerial->readAll();
     }
     return bufRxSerial;
}


/**
 * @brief CommSerial::Read
 *
 * Função Realiza a leitura do que chegar pela serial apos escrever na mesma, e devolve uma QString
 * neste caso é enviado um inteiro com o numero de caracteres do buffer de recepção
 *
 * @param int
 * @return QString
 */
QString comserial::Read(int TamanhoBuffer)
{
    char buf[TamanhoBuffer];

    if (devSerial->canReadLine()) {
        devSerial->read(buf, sizeof(buf));
    }

    return buf;

}

Agora vamos criar algumas conexões entre sinais e slots, ou seja, quando clicar em um botão quem irá atender ou o que será feito com esta ação, através de sinais.

Então na guia Design, clique com o botão direito do mouse no botão Conectar, vá até Go to Slot… e na janela que surgir selecione clicked() e clique em OK, como na Figura 6.

Qt5 Widgets - Adicionando ação nos botões do Qt5
Figura 6 – Adicionando sinais nos botões do Qt5

Repita o processo para os botões Desconectar e Enviar, e já vamos dar ações logo mais.

Agora o mesmo vamos fazer com widget.h, os slots que criamos acima já estarão no widget.h.

#ifndef WIDGET_H
#define WIDGET_H

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

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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


private slots:
    void on_pbCloseSerial_clicked();
    void on_pbOpenSerial_clicked();
    void on_pbSendCmd_clicked();

    void WriteData(const QByteArray data);
    void ReadData();



private:
    Ui::Widget *ui;

    QSerialPort *devserial;
    comserial *procSerial;

    void CarregarInfoDispSerial(void);
};

#endif // WIDGET_H

E no widget.cpp, já haverá as funções dos sinais/slots que criamos, apenas adicione o código nas funções:

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);


    /* Create Object the Class QSerialPort*/
    devserial = new QSerialPort(this);

    /* Create Object the Class comserial to manipulate read/write of the my way */
    procSerial = new comserial(devserial);

    /* Load Device PortSerial available */
    QStringList DispSeriais = procSerial->CarregarDispositivos();

    /* Inserting in ComboxBox the Devices */
    ui->cbDevice->addItems(DispSeriais);

    /* Enable PushButton "Conectar" if any port is found.
     * If an error occurs, it is reported in the Log
     */
    if(DispSeriais.length() > 0) {
        ui->pbOpenSerial->setEnabled(true);
        ui->teLog->append("### Porta serial pronta para ser utilizada.");
    }
    else { ui->teLog->append("### Nenhuma porta serial foi detectada!"); }


    /* Connect Objects -> Signal and Slots
     * DevSerial with Read Data Serial
     * TextEdit "teLog" with getData() after send data WriteData() [Not apply here in version 5.X]
     */
    connect(devserial, SIGNAL(readyRead()), this, SLOT(ReadData()));
    //connect(ui->teLog, SIGNAL(getData(QByteArray)), this, SLOT(WriteData(QByteArray)));
}

Widget::~Widget()
{
    delete ui;
}



/**
 * @brief Widget::CarregarInfoDispSerial()
 *
 * Função que cria uma QStringList e baseado no device selecionado na Porta em Porta Serial
 * carrega algumas informações como Fabricante e Descrição e já envia para o EditText no
 * frame Porta Serial
 *
 * @param void
 * @return void
 */
/*
void Widget::CarregarInfoDispSerial()
{
    QStringList InfoDisp = procSerial->Info(ui->cbDevice->currentText());
}
*/

void Widget::on_pbCloseSerial_clicked()
{
    bool statusCloseSerial;


    statusCloseSerial = procSerial->Desconectar();

    /* BUG: Existe um bug no close do QtSerialPort, já conhecido pela comunidade onde
     * quando usado com waitForReadyRead, da um erro 12 Timeout no SerialPort e não encerra a conexão
     * porém é reportado o erro mas o device é encerrado.
     * Para contornar o problema no Desconectar eu verifiquei com isOpen logo apos fechar a conexão
     * serial.
     */

    if (statusCloseSerial) {
        ui->pbCloseSerial->setEnabled(false);
        ui->pbOpenSerial->setEnabled(true);
        ui->teLog->append("### Porta serial fechada com sucesso!");
    }
    else {
        ui->teLog->append("### Falha ao fechar conexão serial.");
    }
}

void Widget::on_pbOpenSerial_clicked()
{
    bool statusOpenSerial;

    statusOpenSerial = procSerial->Conectar(ui->cbDevice->currentText(),
                                             ui->cbBaudRate->currentText().toInt()
                                             );
    if (statusOpenSerial) {
        ui->pbCloseSerial->setEnabled(true);
        ui->pbOpenSerial->setEnabled(false);
        ui->teLog->append("### Porta serial aberta com sucesso!");
    }
    else {
        ui->teLog->append("Falha ao abrir conexão serial.");
    }
}

void Widget::WriteData(const QByteArray data)
{
    procSerial->Write(data);
}

void Widget::ReadData()
{
    QString data = procSerial->Read();
    qDebug() << "Input: " << data << endl;
    ui->teLog->append(data);

}

void Widget::on_pbSendCmd_clicked()
{
    QString Cmd = ui->leCmd->text()+"\n";
    qDebug() << "Output: " << Cmd << endl;
    WriteData(Cmd.toUtf8());
}

Chegando aqui, sua aplicação já está apta a ser construída e executada. Eu comentei cada função e etapa que achei importante. Se tiver qualquer problema, ao construir, não se preocupe, também estou disponibilizando o projeto inteiro, que pode ser baixado, descompactado e aberto no Qt5 e irá compilar. Segue o link abaixo.

[wpdm_asset id=’25’]

Código do Arduino

O código que iremos usar no Arduino é o mesmo do post anterior.

#define FW_VERSAO  1.0
#define HW_MODELO "ARDUINO UNO"

String serialCmd = "";
bool flagControlRxSerial = false;


void setup() {
    Serial.begin(9600);
    /* Limitando a 10 bytes para nossos comandos */
    serialCmd.reserve(10);
}


void loop() {

  /* Estarou sempre verificando se acabou a recepcao da serial,
     esta variavel flagControlRxSerial se tornara true para isso.
  */
    if (flagControlRxSerial) {
        if( serialCmd == "versao\n" ) {
            Serial.print("\tFirmware: ");
            Serial.print(FW_VERSAO);
        }
        else if ( serialCmd == "hardware\n") {
            Serial.print("\tPlaca : ");
            Serial.print(HW_MODELO);
        }
        else if ( serialCmd == "help\n" || serialCmd == "?\n") {
            Serial.print("\tComandos : \n\thardware - Modelo atual de placa\n\tversao - Versao atual do firmware");
        }
        else if (serialCmd == "\n") {
            Serial.print("\n");
        }
        else {
            Serial.print("\t* Comando invalido *");
        }
        /* Resetando valores para nova recepcao */
        serialCmd = "";
        flagControlRxSerial = false;
  }
}
void serialEvent() {
    /* Loop verificando se algum byte esta disponivel na serial */
    while (Serial.available()) {
      /* O que chegar pela serial  feito um typecast de char para a variavel caractere */
        char caractere = (char)Serial.read();
        /* Nossa variavel caractere eh concatenada a nossa variavel serialCmd que eh uma String */
        serialCmd += caractere;
        /* Se chegar um CR, nova linha, nossa flag de controle (flagsControlRxSerial) passa para true
           e no loop principal ja podera ser utilizada
        */
        if (caractere == '\n') {
            flagControlRxSerial = true;
        }
    }
}

Executando a aplicação Qt5 Widgets

Vamos construir nossa aplicação (Build Project Ctrl + B) e Executá-la (Run Ctrl + R). Na Figura 7 vamos executar nossa aplicação sem conectar o Arduino.

Qt5 Widgets - Aplicação Qt5 executando sem porta serial detectada
Figura 7 – Aplicação Qt5 executando sem porta serial detectada

Conectando o Arduino na porta USB e executando a aplicação novamente, vamos ver algo como da Figura 8.

Qt5 Widgets - Executando Aplicação Qt5 com porta serial detectada
Figura 8 – Aplicação Qt5 com porta serial detectada

Caso ao conectar o Arduino não apareça a porta ttyXXXX, existe uma grande chance de você não estar no grupo dialout ou não possuir uma regra no udev para enquadrar a criação deste dispositivo na permissão para usuário comum, o seu no caso.

Vamos resolver da maneira mais simples, adicionar o usuário ao grupo dialout. No meu caso o usuário é cleiton.

$ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 Jun 5 11:54 /dev/ttyACM0
$ groups $USER
cleiton : cleiton
$ sudo adduser $USER dialout
Adicionando o usuário 'cleiton' ao grupo 'dialout' ...
Adicionando usuário cleiton ao grupo dialout
Concluído.
$ groups $USER
cleiton : cleiton dialout

Com isso, não só com o Arduino mas qualquer placa que conecta via serial no Linux, você não terá mais problemas para ler/escrever no dispositivo com seu usuário. No meu caso não foi necessário, mas se tiver em dúvida reinicie o computador.

Feche a aplicação e abra novamente pra ver se lista o dispositivo serial como na Figura 8.

Na Figura 9, veja a seleção do BaudRate, conexão, envio dos comandos, verificação do Log e desconexão da porta serial do Arduino.

Qt5 Widgets - Comunicação Aplicação Qt5 e Arduino
Figura 9 – Comunicação Aplicação Qt5 e Arduino

E no Application Output da IDE do Qt5 podemos ver as saídas do uso do qDebug(), como na Figura 10.

Qt5 Widgets Application
Figura 10 – Qt5 Application Output saida do qDebug()

Se você acessar o diretório /tmp/build-comunicao_serial_qt_gui-… irá ter, além dos objetos gerados, o binário final, que se executar no terminal irá abrir a aplicação e no seu stdout as mensagens em vermelho da Figura 10.

O que vem por aí…

Na continuação deste post, desenvolveremos a aplicação da Figura 11.

Qt5 Widgets Application
Figura 11 – Qt5 Aplicação Completa

Referências

https://doc.qt.io/qt-5/gettingstartedqt.html
https://doc.qt.io/qt-5/qtserialport-terminal-example.html

Comunicação serial com Arduino utilizando Qt5

Comunicação serial com Arduino e Qt5 Widgets Application – Parte 1
Comentários:
Notificações
Notificar
9 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Manuel Junior
Manuel Junior
13/05/2020 19:55

Boa tarde, consegue habilitar o link? [wpfilebase tag=fileurl path=’posts/comunicacao_serial_qt_gui.tar.gz’ linktext=’Download do Projeto’ /] acho que houve um erro

Manuel Junior
Manuel Junior
06/05/2020 02:28

Olá caro, fiz em windows, existe um erro na programação, no comserial.h deve ser uint32_t. Compilei e apenas está mostrando 4 dígitos no Qtextedit e no console, poderia me dizer onde está o erro? o link para ver o projeto está deshabilitado. desde já obrigado

Cleiton Bueno
Reply to  Manuel Junior
07/05/2020 11:17

Olá Manual, você tem razão no metodo da função Conectar esta como u_int32_t e o correto é uint32_t, eu acredito que algo na formação do site alterou isso, pois o Doxygen gerado no comentário acima da funcao esta certo uint32_t, alterando para isso deva resolver.

Irei encaminhar a alteração para o pessoal do editorial.

Obrigado pelo comentario.

Manuel Junior
Manuel Junior
Reply to  Cleiton Bueno
08/05/2020 12:23

boa tarde, mesmo alterando o uint32 apenas plota 4 valores na serial, algo está faltando no código, você teria o link do projeto completo?

André Curvello
15/06/2015 09:17

Muito top, muito dez!!! Tá de parabéns, Cleiton!

Cleiton Bueno
Reply to  André Curvello
18/06/2015 08:51

Obrigado André. Que bom que gostou!

Abraço.

Francesco Sacco
12/05/2016 15:24

Cleiton, parabéns!
Utilizei seu artigo. Ele me ajudou muito!
Excelente!

Cleiton Bueno
Reply to  Francesco Sacco
15/05/2016 14:20

Olá Francesco. Fico feliz que tenha aplicado o artigo e dado certo.

Obrigado pelo feedback.

Home » Software » Sistemas Operacionais » Comunicação serial com Arduino e Qt5 Widgets Application – Parte 2

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: