【问题标题】:Periodic communication using threads and GUI使用线程和 GUI 进行定期通信
【发布时间】:2021-11-22 23:36:56
【问题描述】:

我需要使用 QT 制作一个 GUI,它将通过串行端口与嵌入式软件进行通信。用户将设置一个通信频率,当他们点击开始时,GUI将定期向嵌入式软件发送控制消息。

嵌入式 sw 使用包含其状态信息的响应来响应每条消息,该状态信息通过一些显示小部件显示在屏幕上。

大部分信息不是时间关键的(我的意思是屏幕不需要在每个传入和传出消息时更新)但是,需要从每条消息中获取命令和反馈位置信息并绘制在屏幕上以免丢失位置数据。

首先,我在不使用任何线程的情况下编写了整个应用程序,但是当我以 100 Hz 运行它时,GUI 开始冻结或丢失一些消息。所以我决定创建一个单独的线程来处理通信。这是我第一次尝试在 QT 中使用线程,我对线程只有一些理论知识。

我检查了所有使用线程的示例,但找不到一个同时执行周期性任务(发送消息)和触发任务(接收消息)。我不想在发送消息后等待响应,因为响应到达的时间有时比通信时间长。

我的想法是在线程的run函数中使用两个两个互斥体,线程在每个互斥体上调用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;
            
        }
    }
}

我将串口的readyRead信号连接到一个解锁receiveMutex的插槽。并且 sendMutex 在由 SendControlCommand_Timer 的超时信号触发的 SendControlCommand_Slot() 中被解锁。 当然我会使用互斥锁来保护共享数据,但同时使用这两个互斥锁是我唯一能想到的实现需要由同一个线程执行的周期性和触发任务的想法。

总结一下,我的问题是: 使用主线程也将使用的通信库结构的单个实例来构造这个将从串行端口读取数据和向串行端口写入数据的通信线程的正确方法是什么?

【问题讨论】:

  • GUI 冻结,因为您需要在等待时处理事件 (QCoreApplication::processEvents())。但是...您为什么不简单地使用QTimer 并将其timeout() 信号连接到发送控制消息的插槽?
  • 使用 QTimer 是我最初所做的,但它似乎不是很好。屏幕经常冻结,我的应用程序不时收到一些消息。我将收到的消息数量与嵌入式 sw 发送的消息数量进行了比较,结果显示大约 10% 的消息丢失。我尝试使用 readyRead 信号并使用具有 0 周期的计时器轮询串行端口,没有太大区别。
  • 我没有想到使用 QCoreApplication::processEvents(),我会试试的,谢谢。
  • 响应到达的时间比通信周期长如果轮询周期比可获得的答复更频繁,对我来说没有多大意义。如果没有收到上一条的回复,我不会发送新的投票消息。
  • @neko 我更多的是考虑使用QTimer 来处理定期发送。等待响应的轮询只是一个 while 循环,您可以在其中调用 QCoreApplication::processEvents() 以在等待有字节可读取时不会冻结 GUI。它应该可以解决问题。

标签: c++ qt qthread


【解决方案1】:

您确定客户端是在计算机上而不是在连接的设备上丢失数据包吗?

您可以使用 USB -> UART 适配器代替真实设备,方法是使用跳线连接 TX 和 RX 引脚进行测试。

我写了一个例子,没有单独的线程,也没有使用QCoreApplication :: processEvents ()

我运行了这个示例并且没有丢失任何包。

虽然读数明显落后于传输。

example project

您也可以查看我的this 项目。它使用Modbus通信协议。

【讨论】:

  • 我敢肯定,我们调试嵌入式设备并让它计算发送了多少条消息。结果证明它们等于我的软件发送的消息数。但是我的软件收到的消息数落后于这三个数字。
  • 非常感谢您的代码,我目前不在办公室,但我一回来就会调查它
  • 我无法进行环回测试,因为命令和响应消息彼此非常不同,而且我没有教我的软件解释控制消息。 (控制信息13字节,响应信息27字节)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-08
  • 2012-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多