嵌入式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, "\n"));
33 
34                 //等待發送和接收完成
35                 m_pTcpSocket->waitForBytesWritten();
36                 m_pTcpSocket->waitForReadyRead();
37 
38             }
39             else
40             {
41                 emit send_edit_test(QString("socket client fail\n"));
42             }
43             qDebug()<<"thread queue test OK\n";
44         }
45     }
46 }
View Code

  完成上述接口應用的實現,后續的邏輯就是涉及協議實現的部分,這部分的實現與協議相關的章節實現一致,具體如下,包含

  CreateSendBuffer:生成發送數據

  DeviceRead:數據接收

  DeviceWrite:數據發送

  CheckReceiveData:接收數據,並校驗

  ExecuteCommand:執行指令的處理

  如此變完整實現了整個數據邏輯的框架,完成了從按鍵數據發送觸發,協議數據發送和接收處理,接收界面顯示的完整流程,最后實現如圖所示的功能:

  至此,我們對於QT上位機界面的基本應用框架已經實現完畢,后續就是在該平台的基礎上構建新的接口實現,滿足不同應用的需求,因為本身這個系列是學習嵌入式Linux開發的,雖然后續肯定會在這份代碼的基礎上區完善上位機的應用,但對於上位機的說明目前就淺嘗輒止了(畢竟本系列的實現並非嵌入式開發),不過在應用開發中,我對曾經學到的類的繼承和派生,模板,lambda表達式都有了進一步的實踐和應用,加深了相關的理解,也算是意義非凡了,至於代碼的地址,參考第一章節的github開源地址中upper_app的內容,包含全部的代碼實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM