- Собираем плату
- Создаем приложение
- Собираем форму
- Добавляем обработчик нажатия на кнопку
- Выводим значение из txtPort
- Посылаем сообщение arduino
- Пробуем зажечь светодиод
- Правим красную кнопку
- Читаем значения датчика освещённости
- Обновляем значения датчика освещённости в режиме реального времени
- БОНУС
Тут я хочу показать как сделать простое GUI приложение чтобы передавать команды arduino и считывать ответ.
В качестве фреймворка для работы с интерфейсом я буду использовать Qt.
Более того Qt включает в себя огромное количество библиотечек для работы с чем только душе угодно. В частности для нас будет актуальна возможность реализация общения по COM порту.
Собираем плату
Значит сделал я такую схемку (да простят меня ардуинщики и физики и вообще…). Короче не будем вдаваться в подробности ее корректности. По крайне мере она работает.
Короче на ней есть два светодиода подключенный к цифровым портам и фоторезистор подключенный к аналоговому порту. Будем писать интерфейс который позволит переключать светодиоды и считывать значения фоторезистора.
Для управления ардуино я буду использовать следующую схему
- Если я отправил символ ‘r’ – значит я хочу чтобы зажегся красный светодиод (англ. r ~ red)
- Если я отправил символ ‘b’ – значит я хочу чтобы зажегся синий светодиод (англ. b ~ blue)
- Если я отправил символ ‘p’ – значит я хочу чтобы устройство сообщило мне текущее значение фоторезистора
Вот такая у меня получилась программа для ардуино
void setup() {
pinMode(2, OUTPUT); // устанавливаем 2 и 3 пин на отправку данных, [привет Игорю Федотову]
pinMode(3, OUTPUT);
Serial.begin(9600);
}
void loop() {
if (Serial.available() > 0) {
char a = Serial.read();
if (a == 'b') {
digitalWrite(3, LOW);
digitalWrite(2, HIGH);
Serial.print("Blue is on");
} else if (a == 'r') {
digitalWrite(2, LOW);
digitalWrite(3, HIGH);
Serial.print("Red is on");
} else if (a == 'p') {
int val = analogRead(0);
Serial.print(val);
}
}
}
Создаем приложение
И так открываем Qt Creator и создаем новый проект
выбираем тип приложения
даем имя
тут далее
и тут
ну и все собственно
увидим что-то такое
чтобы ошибки ушли. Надо один раз запустить приложение. Для этого кликаем на зеленый треугольник (либо жмем Ctrl+R)
запустится наше интерфейс, который пока выглядит вот так:
Собираем форму
закрываем его и начинаем править. Для этого переключимся на форму двойным щелчком на mainwindow.ui
собственно, увидим нашу форму:
Теперь начнем перетаскивать на нее элементы формы. Сначала я хочу, чтобы у меня было поле в котором можно ввести порт к которому я буду коннектиться для общения с ардуино.
Делается это так:
- Label – это просто метка, не редактируемый текст.
- LineEdit – поле для редактирования однострочного текста
Не смотрите что пока все криво, мы в конце добавим немного магии чтобы все выровнять.
Теперь сделаем две кнопки для включения красного и синего светодиодов
и еще поле где будем показывать показания с датчика освещённости
а, ну и еще лог, в который будем писать, что отвечает нам ардуинка
А теперь чуток магии чтобы все это выровнялось
если немного разъяснить, то тут я объединяю компоненты в горизонтальные группы (чтобы выбрать несколько элементов я удерживаю Ctrl)
А потом кликаю на последнюю кнопку, чтобы выровнять группы по сетке.
Можно запустить (Ctrl+R) и проверить как все работает
теперь нам присвоить имена нашим полям для ввода, чтобы была возможность обратится к ним из кода программы. Кликаем на поле для ввода порта, и справа в списке свойств находим в самом верху свойство objectName
меняем его на txtPort
у остальных полей поменяем потом.
Добавляем обработчик нажатия на кнопку
Теперь давайте добавим реакцию на клик кнопки “Включить синий” для этого кликаем на нее правой кнопкой мыши и выбираем “Перейти к слоту”
в Qt используется так называемый механизм сигналов (signal) и слотов (slot) это расширений C++, которое позволяет связывать событие с реакцией на событие более-менее естественный образом.
В общем открывается такое окно где виден список сигналов, которые может послать кнопка. Например, когда вы просто нажали на кнопку, но еще не отпустили кнопку мышь, генерируется сигнал pressed, а если же вы отпустил мышь, то сгенерируется сигнал clicked.
Короче суть в том, что мы к этому сигнал сейчас привяжем функцию (или если мыслить в рамках Qt – слот)
выбираем clicked и жмем OK:
нас кидает в редактор кода
#include "mainwindow.h"
#include "ui_mainwindow.h"
// ЭТО КОНСТУРУКТОР
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
// ЭТО ДЕСТРУКТОР
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
// вот эта функция только что добавилась
// сейчас в нее впишем реакцию на клик кнопки
}
не будем мудрствовать и для начала давайте просто выведем какое-нибудь сообщение, правим функцию
void MainWindow::on_pushButton_clicked()
{
QMessageBox::information(this, "Сообщение", "Ура! Нажали на кнопку!");
}
скорее всего вы увидите, что QMessageBox подчеркнуто и выдается ошибка:
это потому что мы не подключим заголовочный файл с этим классом, ставим курсор на QMessageBox и жмем Alt+Enter
и видим, как вверху добавился необходимый include
теперь давайте запустим приложение и проверим что кнопка действительно заработала.
Жмем Ctrl+R и кликаем на кнопку
Красота! =)
Выводим значение из txtPort
Давайте теперь попробуем сделать так чтобы по нажатию на кнопку выводилось содержимое поля для ввода порта. Подправим функцию:
void MainWindow::on_pushButton_clicked()
{
// У QT свой тип для работы со строками QString
// обращением к элементам на форме всегда идет через ui
// очевидно что ui->txtPort -- это указатель на поле для ввода порта
// ну а ui->txtPort->text() -- это вызов функции которая вернет текстовое содержимое поля
QString port = ui->txtPort->text();
// QString очень крутой класс, и позволяет нам склеивать строки на лету
// т.е. "Выбранный порт: " + port -- означает подклеить содержимое txtPort к фразе "Выбранный порт"
QMessageBox::information(this, "Сообщение", "Выбранный порт: " + port);
}
проверяем:
Замечательно! =)
Посылаем сообщение arduino
Для работы с COM портом у QT есть класс QSerialPort, давайте подправим функцию чтобы она отправил символ “b” в порт и тем самым зажгла светодиод:
void MainWindow::on_pushButton_clicked()
{
// создали экземпляр для общения по последовательному порту
QSerialPort serialPort;
// указали имя к какому порту будем подключаться
serialPort.setPortName(this->ui->txtPort->text());
// указали скорость
serialPort.setBaudRate(QSerialPort::Baud9600);
// пробуем подключится
if (!serialPort.open(QIODevice::ReadWrite)) {
// если подключится не получится, то покажем сообщение с ошибкой
QMessageBox::warning(this, "Ошибка", "Не удалось подключится к порту");
return;
}
// отправляем строку с b нашей арудинкой
serialPort.write("b"); // очень важно, что именно двойные кавычки
serialPort.waitForBytesWritten(); // ждем пока дойдет
// и не знаю с чем тут связано, но, чтобы сообщение дошло
// надо обязательно прочитать не пришло ли нам чего в ответ
//
// функция waitForReadyRead(10) проверят не появилось
// в ближайшие 10 миллисекунд чего-нибудь в ответ
while (serialPort.waitForReadyRead(10)) {
// и если появилось мы просто это читаем в пустоту
serialPort.readAll();
// сам while необходим для того что ответ приходит порциями
// и мы хотим считать все что отправилось
}
// ну и закрываем порт
serialPort.close();
}
он опять будет ругаться на QSerialPort (ставьте курсор на него, жмите Alt+Enter, выбирайте #include <QtSerialPort/QSerialPort>, ну либо руками сами добавляйте)
Пробуем запустить. Жмем Ctrl+R
И видим кучу ошибок:
дело в том, что QSerialPort специализированная библиотечка, и чтобы ее начать использовать надо попросить QT использовать ее dll файл.
Как это сделать. Для этого есть документация!) В ней указано что надо прописать класс заработал.
У QtCreator встроенная документация по Qt, так что ставим курсор на QSerialPort и жмем F1
собственно, тут видно какой заголовочный файл (Header) достаточно указать, и еще что-то про qmake
qmake – это такой себе компилятор QT, который делает из QT C++ кода обычный C++ код, его поведение настраивается в *.pro файле
Кликаем в левом меню Редактор
далее выбираем GuiController.pro – собственно pro файл, ну или проще говоря project файл
в самом верху в нем строчка:
QT += core gui
добавим к ней снизу
QT += core gui
QT += serialport # прям из документации скопировал
пробуем запустить снова (Ctrl+R)
уже по крайней мере запустилось!) Попробуем нажать на кнопку
Ну да мы ведь порт не указали, так что логично
Пробуем зажечь светодиод
Собираем и подключаем нашу ардуинку. Заливаем на нее программу, за одно проверяем какой порт использовать
запускаем нашу программу, вводим порт COM3 и смотрим:
Вроде работает! =)
Давайте теперь добавим то что вернет arduino в ответ на зажжение светодиода, как мы помним там у нас есть:
Serial.print("Blue is on");
а в нашем обработчике мы в холостую читаем данные:
void MainWindow::on_pushButton_clicked()
{
// ...
while (serialPort.waitForReadyRead(10)) {
serialPort.readAll();
}
serialPort.close();
}
попробуем сохранить результат в какую-нибудь переменную и вывести ее в наше большое поле для текста.
Только сначала дадим ему осознанное имя. Переключаемся на mainwindow.ui
теперь идем обратно в mainwindow.cpp и правим on_pushButton_clicked:
void MainWindow::on_pushButton_clicked()
{
QSerialPort serialPort;
serialPort.setPortName(this->ui->txtPort->text());
serialPort.setBaudRate(QSerialPort::Baud9600);
if (!serialPort.open(QIODevice::ReadWrite)) {
QMessageBox::warning(this, "Ошибка", "Не удалось подключится к порту");
return;
}
serialPort.write("b");
serialPort.waitForBytesWritten();
// ВСЕ ЧТО ВЫШЕ ЭТОГО НЕ ТРОГАЕМ
QByteArray data; // специальный тип QT для хранения последовательности байтов
while (serialPort.waitForReadyRead(10)) {
// вместо холостого чтения накапливаем результат в переменную data
data.append(serialPort.readAll());
}
// добавляем строку с содержимым data в поле txtOutput
ui->txtOutput->append(data);
serialPort.close();
}
проверяем
у нас в программе никаких “Blue is on” так что можно с чистой совестью заявить, что это отправило нам ардуино.
Правим красную кнопку
По аналогии с синей кнопкой добавим обработчик на красную.
выбираем сигнал
копипастим обработчик:
void MainWindow::on_pushButton_2_clicked()
{
QSerialPort serialPort;
serialPort.setPortName(this->ui->txtPort->text());
serialPort.setBaudRate(QSerialPort::Baud9600);
if (!serialPort.open(QIODevice::ReadWrite)) {
QMessageBox::warning(this, "Ошибка", "Не удалось подключится к порту");
return;
}
serialPort.write("r"); // меняем тут b на r
serialPort.waitForBytesWritten();
QByteArray data;
while (serialPort.waitForReadyRead(10)) {
data.append(serialPort.readAll());
}
ui->txtOutput->append(data);
serialPort.close();
}
запускаем и проверяем:
в общем показывать, что диоды у меня действительно переключаются мне чего-то лень, но вот сообщения по крайне мере точно приходят с ардуино =)
Читаем значения датчика освещённости
Дадим осознанное имя поля для значения фоторезистора
И добавим обработчик кнопке обновить:
выбрали сигнал
и пишем код (по сути копипастим с правкой в двух местах):
void MainWindow::on_pushButton_3_clicked()
{
QSerialPort serialPort;
serialPort.setPortName(this->ui->txtPort->text());
serialPort.setBaudRate(QSerialPort::Baud9600);
if (!serialPort.open(QIODevice::ReadWrite)) {
QMessageBox::warning(this, "Ошибка", "Не удалось подключится к порту");
return;
}
serialPort.write("p"); // меняем тут на p
serialPort.waitForBytesWritten();
QByteArray data;
while (serialPort.waitForReadyRead(10)) {
data.append(serialPort.readAll());
}
ui->txtLight->setText(data); // тут вставляем значение в txtLight
serialPort.close();
}
проверяем
Ура!
Обновляем значения датчика освещённости в режиме реального времени
Тыкать все время на кнопку обновить не очень интересно. Лучше, чтобы это делалось на автомате. При чем не именно кликанье на кнопку, а просто команда запроса значения и вывода его на форму. Допустим раз в секунду или чаще.
Для этого есть возможность добавить таймер, ему можно указать вызывать некоторую функцию с каким-то промежутком.
Делается это так. В конструкторе добавляем таймер
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this); // это не трогаем
// создали таймер, привязали его к форме
// если ругается на QTimer ставим на него курсор и нажимаем Alt+Enter
// чтоб на автомате добавить #include <QTimer>
QTimer *timer = new QTimer(this);
// подключили сигнал timeout, к слоту нажатия на кнопку
connect(timer, SIGNAL(timeout()), this, SLOT(on_pushButton_3_clicked()));
// запускаем со значением вызывать раз в 300мс
timer->start(300);
}
// ...
void MainWindow::on_pushButton_3_clicked()
{
QSerialPort serialPort;
serialPort.setPortName(this->ui->txtPort->text());
serialPort.setBaudRate(QSerialPort::Baud9600);
if (!serialPort.open(QIODevice::ReadWrite)) {
// закомментим вызовы QMessageBox, так как эта функция будет вызываться таймером
// и информационное сообщение будет блокировать работу программы
//QMessageBox::warning(this, "Ошибка", "Не удалось подключится к порту");
return; // просто оставляем ретёрн, типа не подключились ну и бог с ним
}
// ... остальное все также
}
Теперь как только мы укажем правильный порт, начнут приходить значения:
Вот собственно и все!
БОНУС
Писать порт все время лениво. Есть возможность получить список всех доступных портов. Делается это так.
Сначала идем на mainwindow.ui и удаляем txtPort, на место него ставим Combo Box
правим ему свойства
- objectName меняем на cmbPort
- кликаем дважды на sizePolicy и в свойстве Горизонтальная политика ставим Expanding, чтобы этот комбо бокс как и lineEdit занимал все свободное место
возвращаемся к коду mainwindow.cpp и в конструкторе добавляем:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(on_pushButton_3_clicked()));
timer->start(300);
// ВОТ ЭТОТ цикл добавляем
foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts())
{
ui->cmbPort->addItem(serialPortInfo.portName());
}
}
void MainWindow::on_pushButton_clicked()
{
QSerialPort serialPort;
// тут меняем чтобы бралось значение из cmbPort
serialPort.setPortName(this->ui->cmbPort->currentText());
// ...
}
void MainWindow::on_pushButton_2_clicked()
{
QSerialPort serialPort;
// и тут
serialPort.setPortName(this->ui->cmbPort->currentText());
// ...
}
void MainWindow::on_pushButton_3_clicked()
{
QSerialPort serialPort;
// и здесь
serialPort.setPortName(this->ui->cmbPort->currentText());
// ...
}
запускаем:
Прикольно, что прям сразу подключается! Такие дела =)