Взаимодействие с Arduino через последовательный порт (COM) в Qt5. Часть 1. Настройка, подключение, чтение символов.

Россия
Взаимодействие с Arduino через последовательный порт (COM) в Qt5. Часть 1. Настройка, подключение, чтение символов.

В предыдущих статьях мы рассмотрели установку среды разработки Arduino и загрузку простых скетчей в устройство.

Сегодня мы рассмотрим работу с последовательным портом (COM) в QT5. Соединяться через COM-порт мы будем с Arduino UNO.

Для начала установим среду разработки и драйвера для Arduino.

Мы уже рассматривали, в предыдущих статьях процесс установки под Windows и Astra Linux.

Подключим Arduino к ПК. И загрузим на него тестовый скетч Blink.

Изменим скетч, таким образом, чтобы через COM-порт отправлялась информация. В нашем случае это будет ноль раз в одну секунду.

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
   Serial.begin(9600);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(500);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(500);                       // wait for a second

  Serial.println("0");  
}

Загрузим скетч на устройство. В среде разработки выберем Инструменты -> Монитор порта

2021-12-05_10-34-52.png

Как видите, в COM-порт передается некоторая информация.

Проверка отправки данных в COM-порт

Проверим, что операционная система имеет доступ к последовательному порту.

Обязательно закроем Монитор порта.

Запустим Putty

Укажем номер порта, в моем случае это COM4.

Настроем как указано на рисунке, дополнительных настроек на данном этапе не требуется:.

2021-12-05_10-37-51.png

Нажмем Open

2021-12-05_10-39-16.png

Как видите, в COM-порт с устройства передается информация.

Обратите внимание, в Windows к последовательному порту одновременно может обращаться только одна программа. Если вы сейчас, не закрывая Putty, попробуете загрузить новый скетч в Arduino, то получите сообщение об ошибке:

avrdude: ser_open(): can't open device "\\.\COM4": �������� � �������.

Problem uploading to board

Так что вам придется закрывать программу перед заливкой нового скетча или использовать отдельный шилд для подключения к последовательному порту.

Можно использовать виртуальный делитель последовательного порта, но об этом мы поговорим в одной из следующих статей.

Теперь, когда мы уверены, что устройство передает информацию, а операционная система имеет к ней доступ, мы можем приступит к написанию программы на QT5.

Создание проекта в QtCreator

Создадим новый проект Qt Windgets Application

Назовем его

BasicSerial

Откроем файл BasicSerial.pro

Изменим строку

QT       += core gui

На

QT       += core gui serialport

Обязательно запустим сборку проекта, чтобы инициализировать его с новыми библиотеками!

Добавим в начало файла mainwindows.h:

#include <QSerialPort>

Добавим в класс MainWindow приватное поле

QSerialPort serialPort;

 Добавим в конец конструктора 

MainWindow::MainWindow(QWidget *parent)

Код:

    const QString serialPortName = "COM4";
    serialPort.setPortName(serialPortName);

    serialPort.setBaudRate(QSerialPort::Baud9600);

    bool result = serialPort.open(QIODevice::ReadOnly);

    if (!result) {
        qDebug() << "Failed to open port: " << serialPortName << ", error: " << serialPort.errorString();
    } else {
        qDebug() << "Connected to COM4";
    }

    serialPort.close();

Здесь нет ничего сложного мы создаем экземпляр класса QSerialPort, используемого для работы с последовательным портом.

Задаем имя порта и скорость передачи. 

Connected to COM4

И открываем соединения в режиме только для чтения. 

Проверяем результат, если он False, то при открытии произошла ошибка, иначе выводим подтверждение подключения.

В конце вызываем метод close() чтобы освободить порт.

Если мы запустим этот код, при запущенном Putty, подключенном к порту COM4 мы получим сообщение

Failed to open port:  "COM4" , error:  "Отказано в доступе."

При свободном последовательном порте сообщение:

Connected to COM4

Это важно помнить, чтобы не тратить время на поиск причин невозможности подключиться к порту.

Мы успешно подключились к порту COM4, пришла пора прочитать из него данные.

Класс SerialPortManager

Для упрощения инициализации последовательного порта добавим новый класс:

SerialPortManager

Заголовок:

#ifndef SERIALPORTMANAGER_H
#define SERIALPORTMANAGER_H

#include <QSerialPort>


class SerialPortManager
{
public:
    SerialPortManager(QString portName,
                        qint32 baud = QSerialPort::Baud9600,
                        QSerialPort::DataBits bits = QSerialPort::Data8,
                        QSerialPort::StopBits sbits = QSerialPort::OneStop,
                        QSerialPort::Parity parity = QSerialPort::NoParity,
                        QSerialPort::FlowControl flow = QSerialPort::NoFlowControl);
    ~SerialPortManager();

    QSerialPort *getSerialPort() const;

private:
    QSerialPort *serialPort;
    QString portName;
};

#endif // SERIALPORTMANAGER_H

Реализация 

#include "serialportmanager.h"
#include <QDebug>

SerialPortManager::SerialPortManager(QString portName,
                                     qint32 baud,
                                     QSerialPort::DataBits bits,
                                     QSerialPort::StopBits sbits,
                                     QSerialPort::Parity parity,
                                     QSerialPort::FlowControl flow)
{
    this->serialPort = new QSerialPort();

    this->portName = portName;

    this->serialPort->setPortName(portName);
    this->serialPort->setBaudRate(baud);
    this->serialPort->setDataBits(bits);
    this->serialPort->setStopBits(sbits);
    this->serialPort->setParity(parity);
    this->serialPort->setFlowControl(flow);


    bool result = this->serialPort->open(QIODevice::ReadOnly);

    if (!result) {
        qDebug() << "Failed to open port: " << this->portName << ", error: " << this->serialPort->errorString();
    } else {
        qDebug() << "Connected to " << this->portName;
    }


}

SerialPortManager::~SerialPortManager()
{
    this->serialPort->close();
}

QSerialPort *SerialPortManager::getSerialPort() const
{
    return this->serialPort;
}

Я добавил дополнительные параметры для настройки соединения по умолчанию.

Чтобы соединиться с последовательным портом добавим в MainWindow:

private:
    SerialPortManager *sm;

Удалим из конструктора класса MainWindow весь добавленный код и добавим:

sm = new SerialPortManager("COM4");

Запустим, результат не изменится!

Читаем данные из COM-порта

Чтобы прочитать данные из последовательного порта, создадим новый класс:

QSerialIO

Заголовок:

#ifndef QSerialIO_H
#define QSerialIO_H

#include <QSerialPort>

class QSerialIO : public QObject
{
    Q_OBJECT
public:
    explicit QSerialIO(QSerialPort *serialPort, QObject *parent = nullptr);

public slots:
    void handleRead();
    void handleError(QSerialPort::SerialPortError serialPortError);
private:
  QByteArray data;
  QSerialPort *serialPort = nullptr;
};

#endif // QSerialIO_H

 Реализация:

#include "QSerialIO.h"
#include <QDebug>

QSerialIO::QSerialIO(QSerialPort *serialPort, QObject *parent) :
    QObject(parent)
{
    this->serialPort = serialPort;
     connect(serialPort, SIGNAL(readyRead()), this, SLOT(handleRead()));
     connect(serialPort, SIGNAL(errorOccurred(QSerialPort::SerialPortError)), this, SLOT(handleError(QSerialPort::SerialPortError)));

}

void QSerialIO::handleRead()
{
    QByteArray d = serialPort->readAll();
    QString ds = d;
    qDebug() << d << ds.simplified();
}

void QSerialIO::handleError(QSerialPort::SerialPortError serialPortError)
{
    if (serialPortError == QSerialPort::ReadError) {
        qDebug() << "I/O error on port" << serialPort->portName() << serialPort->errorString();
    }
}

Чтобы работа с портом не блокировала программу мы используем слоты, подключаемые к сигналам класса QSerialPort.

Таким образом, обработчики вызываются только в том случае, если из порта прочитаны данные.

Добавим в MainWindow:

QSerialIO *serialRdr;

Добавим в конец конструктора класса MainWindow строку:

 serialRdr = new QSerialIO(sm->getSerialPort());

Запустим:

Connected to  "COM4"
"0\r\n0\r\n" "0 0"
"0\r\n" "0"
"0\r\n" "0"
"0\r\n" "0"
"0\r\n" "0"
"0\r\n" "0"

 Обратите внимание, в зависимости от скорости запуска или загруженности системы в момент запуска, у вас в первой строке может быть выведено следующее: 

"0\r\n0\r\n" "0 0"

Это происходит из-за того, что между соединением к порту и запуском обработчика проходит больше 1 секунды. В результате класс QSerialPort успевает принять два символа через последовательный порт до того, как программа начинает считывать их из буфера.

Сегодня мы не будем рассматривать, как с этим бороться, в данным момент это не принципиально.

Вот и всё, мы написали программу, которая считывает данные из последовательного (COM)  порта.

Заключение

Сегодня мы рассмотрели подключение к последовательному порту в QT5 к Arduino.

Установили среду разработки Arduino.

Загрузили в Arduino скетч выводящий 0 в последовательный порт.

Проверили работу скетча с помощью putty.

Создали новый проект в QT5.

Добавили в него поддержку serialport.

Добавили код для инициализации соединения с COM-портом.

Проверили статус соединения.

Добавили класс SerialPortManager для облегчения работы с последовательным портом.

Добавили класс QSerialIO в котором реализовали чтение вывод в консоль строк, отправленных с Arduino.

Запустили и проверили, что программа действительно читает данные из последовательного порта.

В следующей части мы усовершенствуем класс QSerialIO – добавим поддержку сигналов и слотов.

Скачать исходный код вы можете с Github - https://github.com/vasiliyaltunin/articles.blog.altuninvv.ru/tree/master/qt5/Serial/BasicSerial

Прочитано 2750 раз Последнее изменение Вторник, 07 декабря 2021 10:57

Обсудить:

Сообщество ВКонтакте Группа в Telegram Сервер Discord Канал в ЯRUS
Топ-100