• 嵌入式Linux学习笔记(六) 上位机QT界面实现和通讯实现


    目录

    (1).参考资料

    (2).QT界面布局实现

    (3).数据和操作逻辑

      在上一章我们实现了下位机的协议制定,并通过串口通讯工具完成了对设备内外设(LED)的状态修改,下面就要进行上位机软件的实现了(事实上这部分不属于嵌入式Linux的内容,所以只在本章节讲述下上位机实现的流程和思路,后续维护更新不在进行详细说明,不过下位机界面实现肯定还会涉及这些技术),上位机的界面方案一般指在Windows平台的软件界面开发,如UWP,WINFORM/C#, WPF/C#, QT/C++等,如果说我的个人倾向的话,当然更喜欢的WINFORM/C#技术,一方面C#相对于C++更简单,不会因为复杂的模板和继承机制,导致出问题的报错比代码都长,另一方面网上的资料也多,遇到问题很容易找到解决办法,在我之前实现的应用中,也都是使用WINFORM技术,再加上对于QT/C++根本没有了解过,算是第一次接触(之前接触的都是无界面应用或者使用的Android/Java),不过对于嵌入式Linux来说,QT/C++是也是十分需要掌握的,既然都要学习,那么上位机选择QT/C++先来熟悉语法和基础,完成上位机QT界面和通讯协议的实现,这也是这篇文章耽误一段时间的原因,在QT还没有熟悉之前,参考例程写应用还可以,清晰的讲清楚还是很困难的,在应用接近大半个月后,也算有些心得,可以进行后续的进度了,下面开始本节的实现吧。

     

    参考资料

      1. 开源QT例程项目

      2. 《QT5开发和实例》 -- 参考这本书不是因为写的有深度,而是因为里面全是例程,适合初学者了解

      3. 《C++ Primer Plus》

     

    QT界面布局实现

      基于从Winform的界面开发经验,QT界面的布局也是类似,参考上面的例程项目,主要涉及的的窗体有:

      QFrame:基本控件的基类,用于将功能类似的结构整理在一起

      QLabel:标签控件,用于显示文字说明

      QPushButton:按键控件,执行按键动作

      QTextEdit:编辑文本框控件,用于输入或者显示文本

      QComboBox:选择框控件,支持下拉菜单的选择

      QLineEdit:行编辑框,用于行输入和显示文本

      在掌握基础的基本的布局编辑框后,就可以使用设计栏左边的控件框中,拖出如下的编辑框。

     

      在构建完成上述编辑框后,下面就要实现界面内容的填充,主要包含页面布局的显示,下拉框的完善,代码如下:

     1  //添加COM口
     2     QStringList comList;
     3     for (int i = 1; i <= 20; i++) {
     4         comList << QString("COM%1").arg(i);
     5     }
     6     ui->combo_box_com->addItems(comList);
     7 
     8     //波特率选项
     9     QStringList BaudList;
    10     BaudList <<"9600"<<"38400"<<"76800"<<"115200"<<"230400";
    11     ui->combo_box_baud->addItems(BaudList);
    12     ui->combo_box_baud->setCurrentIndex(3);
    13 
    14     //数据位选项
    15     QStringList dataBitsList;
    16     dataBitsList <<"6" << "7" << "8"<<"9";
    17     ui->combo_box_data->addItems(dataBitsList);
    18     ui->combo_box_data->setCurrentIndex(2);
    19 
    20     //停止位选项
    21     QStringList StopBitsList;
    22     StopBitsList<<"1"<<"2";
    23     ui->combo_box_stop->addItems(StopBitsList);
    24     ui->combo_box_stop->setCurrentIndex(0);
    25 
    26     //校验位
    27     QStringList ParityList;
    28     ParityList<<"N"<<"Odd"<<"Even";
    29     ui->combo_box_parity->addItems(ParityList);
    30     ui->combo_box_parity->setCurrentIndex(0);
    31 
    32     //设置协议类型
    33     QStringList SocketTypeList;
    34     SocketTypeList<<"TCP"<<"UDP";
    35     ui->combo_box_socket_type->addItems(SocketTypeList);
    36     ui->combo_box_parity->setCurrentIndex(0);
    37 
    38 //    //正则限制部分输入需要为数据
    39 //    QRegExp regx("[0-9]+$");
    40 //    QValidator *validator_time = new QRegExpValidator(regx,  ui->line_edit_time);
    41 //    ui->line_edit_time->setValidator( validator_time );
    42 //    QValidator *validator_id = new QRegExpValidator(regx,  ui->line_edit_dev_id);
    43 //    ui->line_edit_dev_id->setValidator( validator_id );
    44 
    45     //默认按键配置不可操作
    46     init_btn_disable(ui);
    47     ui->btn_uart_close->setDisabled(true);
    48     ui->btn_socket_close->setDisabled(true);
    View Code

       至此,我们就完成了布局相关的代码。

     

    数据和操作逻辑

      对于无界面的软件或者方案实现,我们主要关注的是数据在整个逻辑模型之间的流通,转移和处理,对于有界面的软件实现,其实这套逻辑也是存在的。除了涉及界面的处理,其它部分其实也是这套逻辑,不过是将部分数据的源头来自于界面的动作,并且将最后的输出结果从命令行转移到界面的窗口中,如果理解了这一点,就会发现其实带界面的应用实现并没有太困难,这也是我接触QT/C++很短时间就能将Winform和下位机经验快速转换的原因。对于这个项目来说,主要实现的背后数据逻辑包含以下三个方面:

      1.按键动作的信号和界面的输入信息处理

      2.硬件通讯相关的串口知识,socket通讯以及涉及的TCP和UDP协议传输

      3.协议相关的硬件实现和数据处理

      4.处理结果的界面输出显示

      其中按键部分的动作和界面输出显示都是QT界面背后的逻辑,包含信号槽的绑定和界面变量的操作方法,如下所示

     1 //获取设备ID信息
     2 pMainUartProtocolThreadInfo->SetId(ui->line_edit_dev_id->text().toShort());
     3 
     4 //界面显示的操作
     5 if(ui->text_edit_test->document()->lineCount() > 20)
     6 {
     7     qDebug()<<"lines do";
     8     ui->text_edit_test->setText(s);
     9 }
    10 else
    11 {
    12     ui->text_edit_test->append(s);
    13 }
    View Code

      这部分是涉及QT的基础知识,主要都是积累的技巧,难度不高,建议参考《QT5开发和实例》实例去学习。

      硬件串口知识和Socket知识就是应用实现需要的其它能力,包含对QextSerialPort和Socket接口的应用,此外为了满足多接口应用同时操作的需求,需要实现多线程的编程,其中串口的应用初始化配置主要包含的有

      flush:清空缓存区

      setBaudRate:设置波特率

      setDataBits:设置数据位

      setParity:设置奇偶校验位

      setStopBits:设置停止位

      setFlowControl:设置流量控制

      setTimeout:设置接收和发送超时时间

     1 pMainUartProtocolThreadInfo->m_pSerialPortCom = new QextSerialPort(ui->combo_box_com->currentText(), QextSerialPort::Polling);
     2 pMainUartProtocolThreadInfo->m_bComStatus = pMainUartProtocolThreadInfo->m_pSerialPortCom->open(QIODevice::ReadWrite);
     3 
     4 if(pMainUartProtocolThreadInfo->m_bComStatus)
     5 {
     6     //清除缓存区
     7     pMainUartProtocolThreadInfo->m_pSerialPortCom ->flush();
     8     //设置波特率
     9     pMainUartProtocolThreadInfo->m_pSerialPortCom ->setBaudRate((BaudRateType)ui->combo_box_baud->currentText().toInt());
    10     //设置数据位
    11     pMainUartProtocolThreadInfo->m_pSerialPortCom->setDataBits((DataBitsType)ui->combo_box_data->currentText().toInt());
    12     //设置校验位
    13     pMainUartProtocolThreadInfo->m_pSerialPortCom->setParity((ParityType)ui->combo_box_parity->currentText().toInt());
    14     //设置停止位
    15     pMainUartProtocolThreadInfo->m_pSerialPortCom->setStopBits((StopBitsType)ui->combo_box_stop->currentText().toInt());
    16     pMainUartProtocolThreadInfo->m_pSerialPortCom->setFlowControl(FLOW_OFF);
    17     pMainUartProtocolThreadInfo->m_pSerialPortCom ->setTimeout(10);
    18     init_btn_enable(ui);
    19     pMainUartProtocolThreadInfo->SetId(ui->line_edit_dev_id->text().toShort());
    20     ui->btn_uart_close->setEnabled(true);
    21     ui->btn_uart_open->setDisabled(true);
    22     ui->btn_socket_open->setDisabled(true);
    23     ui->btn_socket_close->setDisabled(true);
    24     ui->combo_box_com->setDisabled(true);
    25     ui->combo_box_baud->setDisabled(true);
    26     ui->combo_box_data->setDisabled(true);
    27     ui->combo_box_stop->setDisabled(true);
    28     ui->combo_box_parity->setDisabled(true);
    29     append_text_edit_test(QString::fromLocal8Bit("serial open success!"));
    30     protocol_flag = PROTOCOL_UART;
    31 }
    32 else
    33 {
    34     pMainUartProtocolThreadInfo->m_pSerialPortCom->deleteLater();
    35     pMainUartProtocolThreadInfo->m_bComStatus = false;
    36     append_text_edit_test(QString::fromLocal8Bit("serial open failed!"));
    37 }
    View Code

      串口的通讯读写接口主要包含

      Write:数据发送接口

      Read:数据读取接口

     1 //设备写数据
     2 int CUartProtocolThreadInfo::DeviceWrite(uint8_t *pStart, uint16_t nSize)
     3 {
     4     m_pSerialPortCom->write((char *)pStart, nSize);
     5     return nSize;
     6 }
     7 
     8 //设备读数据
     9 int CUartProtocolThreadInfo::DeviceRead(uint8_t *pStart, uint16_t nMaxSize)
    10 {
    11     return m_pSerialPortCom->read((char *)pStart, nMaxSize);
    12 }
    View Code

      socket通讯的的初始化配置包含

      abort:中断当前的所有连接

      connectToHost:指定连接到指定的IP地址和端口

      waitForConnect:等待服务器的连接

      waitForBytesWritten:等待数据发送完成

      waitForReadyRead:等待数据可以接收

      此外,还包含和Socket通讯相关的

      信号:connect <-> 槽函数:slotConnected

      信号:diconnect <-> 槽函数:slotDisConnected

      信号:readyRead <-> 槽函数:dataReceived

      具体代码实现如下:

     1 void CTcpSocketThreadInfo::run()
     2 {
     3     bool is_connect;
     4     int nLen;
     5     int nStatus;
     6 
     7     m_pTcpSocket = new QTcpSocket();
     8     m_pServerIp = new QHostAddress();
     9     connect(m_pTcpSocket, SIGNAL(connected()), this, SLOT(slotConnected()));
    10     connect(m_pTcpSocket, SIGNAL(disconnected()), this, SLOT(slotDisconnected()));
    11     connect(m_pTcpSocket, SIGNAL(readyRead()), this, SLOT(dataReceived()));
    12 
    13     for(;;)
    14     {
    15         if(m_nIsStop)
    16             return;
    17 
    18         nStatus  = m_pQueue->QueuePend(&SendBufferInfo);
    19         if(nStatus == QUEUE_INFO_OK)
    20         {
    21             m_pTcpSocket->abort();
    22             m_pTcpSocket->connectToHost(*m_pServerIp, m_nPort);
    23             nLen = this->CreateSendBuffer(this->GetId(), SendBufferInfo.m_nSize,
    24                                                            SendBufferInfo.m_pBuffer, SendBufferInfo.m_IsWriteThrough);
    25             is_connect = m_pTcpSocket->waitForConnected(300);
    26             if(is_connect)
    27             {
    28                 emit send_edit_test(QString("socket client ok"));
    29                 this->DeviceWrite(tx_buffer, nLen);
    30 
    31                 //通知主线程更新窗口
    32                 emit send_edit_test(byteArrayToHexString("Sendbuf:", tx_buffer, nLen, "
    "));
    33 
    34                 //等待发送和接收完成
    35                 m_pTcpSocket->waitForBytesWritten();
    36                 m_pTcpSocket->waitForReadyRead();
    37 
    38             }
    39             else
    40             {
    41                 emit send_edit_test(QString("socket client fail
    "));
    42             }
    43             qDebug()<<"thread queue test OK
    ";
    44         }
    45     }
    46 }
    View Code

      完成上述接口应用的实现,后续的逻辑就是涉及协议实现的部分,这部分的实现与协议相关的章节实现一致,具体如下,包含

      CreateSendBuffer:生成发送数据

      DeviceRead:数据接收

      DeviceWrite:数据发送

      CheckReceiveData:接收数据,并校验

      ExecuteCommand:执行指令的处理

      如此变完整实现了整个数据逻辑的框架,完成了从按键数据发送触发,协议数据发送和接收处理,接收界面显示的完整流程,最后实现如图所示的功能:

      至此,我们对于QT上位机界面的基本应用框架已经实现完毕,后续就是在该平台的基础上构建新的接口实现,满足不同应用的需求,因为本身这个系列是学习嵌入式Linux开发的,虽然后续肯定会在这份代码的基础上区完善上位机的应用,但对于上位机的说明目前就浅尝辄止了(毕竟本系列的实现并非嵌入式开发),不过在应用开发中,我对曾经学到的类的继承和派生,模板,lambda表达式都有了进一步的实践和应用,加深了相关的理解,也算是意义非凡了,至于代码的地址,参考第一章节的github开源地址中upper_app的内容,包含全部的代码实现。

  • 相关阅读:
    Goroutine被动调度之一(18)
    实战分析一个运行起来会卡死的Go程序
    Go语言调度器之盗取goroutine(17)
    第三章 Goroutine调度策略(16)
    非main goroutine的退出及调度循环(15)
    Go语言调度器之调度main goroutine(14)
    PHP经典面试题之 Redis 内存满了怎么办?
    【PHP】让新人快速理解ThinkPHP6中的事务操作
    面试官:说说swoole+PHP实现自动取消订单,还原库存等操作
    最新整理的PHP高级面试题来啦!【附答案】
  • 原文地址:https://www.cnblogs.com/zc110747/p/13057517.html
Copyright © 2020-2023  润新知