Периодическая связь с использованием потоков и графического интерфейса

#c #qt #qthread

Вопрос:

Мне нужно создать графический интерфейс с использованием QT, который будет взаимодействовать со встроенным программным обеспечением через последовательный порт. Пользователь установит частоту связи, и когда он нажмет кнопку Пуск, графический интерфейс будет периодически отправлять управляющие сообщения встроенному sw.

Встроенный sw отвечает на каждое сообщение ответом, содержащим информацию о его состоянии, которая отображается на экране с помощью некоторых виджетов отображения.

Большая часть информации не имеет критического значения по времени (я имею в виду, что экран не нужно обновлять при каждом входящем и исходящем сообщении). Однако информация о местоположении команды и обратной связи должна извлекаться из каждого сообщения и выводиться на экран, чтобы данные о местоположении не были потеряны.

Во-первых, я закодировал все приложение без использования каких-либо потоков, но, например, когда я запускаю его на частоте 100 Гц, графический интерфейс начинает зависать или пропускать некоторые сообщения. Поэтому я решил создать отдельный поток, который будет обрабатывать сообщение. Я впервые пытаюсь использовать потоки в QT, и у меня есть только некоторые теоретические знания о потоковой передаче.

Я проверил все примеры, в которых используются потоки, но не смог найти ни одного, который выполнял бы как периодическую задачу (отправка сообщений), так и запускаемую задачу (получение сообщений). Я не хочу ждать ответа после отправки сообщения, так как иногда для получения ответа требуется больше времени, чем период общения.

Моя идея состояла в том, чтобы использовать два двух мьютекса в функции запуска потока, и поток вызывает tryLock для каждого мьютекса, чтобы отправить или получить сообщение.

 class CommunicationThread : public QThread
{
    Q_OBJECT
public:
    explicit CommunicationThread(QObject *parent = nullptr);
    
    QMutex sendMutex;
    
    QMutex receiveMutex;
    
    QTimer *SendControlCommand_Timer;
    
    QSerialPort *serialPort;
    
    Communication_Packets_st *commManager_s; // the communication library struct 
    
    double SendAngle_d;
    
    double ReceiveAngle_d;
    
    uint32 LastSendIndex_u32 = 0;
    
    double periodSec_d;

private slots:

    void MessageReceived_Slot();

    void SendControlCommand_Slot();
    
}


void CommunicationThread::run()
{
    forever {
        if(sendMutex.tryLock())
        {

            //calculate the sendAngle here and add it to the graph, the other fields of the structure will be filled by the main thread with a slower frequency
            //...
            
            CommandGraph->addData(LastSendIndex_u32*periodSec_d,SendAngle_d);
            LastSendIndex_u32  ;
            
            commManager_s->ControlCommandPacket_s.CommandAngle_i32 =
                    static_cast<int32>(SendAngle_d * COMMAND_ANGLE_MULTIPLIER);
            
            Comm_SendControlCommand(commManager_s); //Function that serializes data and sends the message
            sendMutex.lock();
        }
        if(receiveMutex.tryLock())
        {
            QByteArray ReceiveBuffer_ba;
            quint32 length_u32;

            if(serialPort->bytesAvailable())
            {
                ReceiveBuffer_ba.append(serialPort->readAll());
            }
            Comm_PacketParser(commManager_s); //Function that parses the serial message and fills the commManager_s struct.
            //Put the feedback angle in the plot here so no data is lost, the other fields of the received message will be read and displayed by the main thread with a slower frequency
            ReceiveAngle_d = static_cast<double>
                (commManager_s->ControlCommandResponsePacket_s.AxisAngle_i32) * FEEDBACK_ANGLE_MULTIPLIER;
            
        }
    }
}
 

Я подключил сигнал готовности последовательного порта к разъему, который разблокирует модуль приема. И sendMutex разблокирован в SendControlCommand_Slot (), инициируемом сигналом тайм-аута SendControlCommand_Timer.
Конечно, я буду использовать мьютексы, которые защитят общие данные, но также использование этих двух мьютексов-единственная идея, которую я мог бы придумать для реализации этих периодических и запускаемых задач, которые должны выполняться одним и тем же потоком.

Подводя итог, мой вопрос заключается в следующем: каков правильный способ структурирования этого потока связи, который будет считывать и записывать данные из/в последовательный порт, используя один экземпляр структуры библиотеки связи, которая также будет использоваться основным потоком?

Комментарии:

1. Графический интерфейс застыл, потому что вам нужно обработать события ( QCoreApplication::processEvents() ), пока вы ждете. Но… почему бы вам просто не использовать a QTimer и не подключить его timeout() сигнал к разъему, который отправляет ваши управляющие сообщения ?

2. Использование QTimer-это то, что я сделал в первую очередь, но, похоже, это работает не очень хорошо. Экран часто зависает, и мое приложение время от времени не получает некоторых сообщений. Я сравнил количество сообщений, которые я получил, с количеством сообщений, отправленных встроенным sw, в результате оказалось, что около 10% сообщений было потеряно. Я попытался использовать как сигнал readyRead, так и опрос последовательного порта с таймером с периодом 0, особой разницы не было.

3. Использование QCoreApplication::processEvents() мне не пришло в голову, хотя я попробую, спасибо.

4. для получения ответа требуется больше времени, чем период общения , для меня это не имеет особого смысла, если период опроса чаще, чем доступные ответы. Я бы не стал отправлять новое сообщение для опроса, не получив ответа от предыдущего.

5. @neko я больше думал об использовании QTimer для обработки периодических отправок. Опрос для ожидания ответа-это просто while цикл, в котором вы вызываете QCoreApplication::processEvents() , чтобы графический интерфейс не зависал, пока вы ждете, пока не появятся доступные для чтения байты. И это должно сработать.

Ответ №1:

Вы уверены, что клиент теряет пакеты на компьютере, а не на подключенном устройстве?

Вы можете использовать адаптер USB -> UART вместо реального устройства, подключив контакты TX и RX с помощью перемычки для тестирования.

Я написал пример без отдельной темы и без использования QCoreApplication :: processEvents () .

Я запустил этот пример и не потерял ни одной посылки.

Хотя показания заметно отставали от передачи.

пример проекта

Вы также можете посмотреть на этот мой проект. Он использует протокол связи Modbus.

Комментарии:

1. Я уверен, что мы отлаживаем встроенное устройство и заставляем его подсчитывать, сколько сообщений было отправлено. Они оказываются равными количеству отправленных сообщений моего программного обеспечения. Но количество полученных сообщений моего программного обеспечения отстает от этих трех цифр.

2. И большое спасибо за код, в настоящее время я не работаю, но рассмотрю его, как только вернусь

3. Однако я не могу выполнить тест с обратной связью, так как командные и ответные сообщения сильно отличаются друг от друга, и я не учил свое программное обеспечение интерпретировать управляющее сообщение. (Управляющее сообщение составляет 13 байт, ответное сообщение-27 байт)