- Comunicação serial com Arduino utilizando Qt5 Console Application
- Comunicação serial com Arduino e Qt5 Widgets Application – Parte 1
- Comunicação serial com Arduino e Qt5 Widgets Application – Parte 2
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.
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.
Na última janela como da Figura 3, clique em Finish.
Como pode ser visto na Figura 4, foi criada a estrutura para nossa classe comserial (.cpp e .h).
Para criar a estrutura de código vamos alterar o comunicacao_serial_qt_gui.pro como visto no primeiro post dessa série.
Qt5
1 |
QT += serialport |
Qt4
1 |
CONFIG += 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.
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 |
#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.
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
#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.
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.
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 |
#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:
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
#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.
comunicacao_serial_qt_gui.tar.gz
6.94 KBCódigo do Arduino
O código que iremos usar no Arduino é o mesmo do post anterior.
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 |
#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.
Conectando o Arduino na porta USB e executando a aplicação novamente, vamos ver algo como da Figura 8.
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.
1 2 3 4 5 6 7 8 9 10 |
$ 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.
E no Application Output da IDE do Qt5 podemos ver as saídas do uso do qDebug(), como na Figura 10.
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.
Referências
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
Olá Manuel, já entrei em contato com o pessoal do site e me informaram que o mesmo passou por atualizações e novas tecnologias instaladas, e o desenvolvedor esta reparando os posts que sofreram problemas, eu já reportei este artigo como problema e esta no radar para resolver.
Obrigado por avisar.
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
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.
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?
Muito top, muito dez!!! Tá de parabéns, Cleiton!
Obrigado André. Que bom que gostou!
Abraço.
Cleiton, parabéns!
Utilizei seu artigo. Ele me ajudou muito!
Excelente!
Olá Francesco. Fico feliz que tenha aplicado o artigo e dado certo.
Obrigado pelo feedback.