Qt 實現串口終端控制檯,適配RT-Thread的FinSH控制檯功能(提供qt源碼)

開發環境:Window 10 64bit
開發工具:IAR Embedded Workbench
硬件:stm32f103c8t6


RT-Thread Nano 版本包含了 FinSH 組件,我們可以在reconfig.h配置使用它,使用之後我們可以在電腦上通過串口終端輸入命令調試系統。這功能用於調試或查看系統信息,在實際開發中可以帶來很多的方便,。效果如下圖:

效果圖
 

1.基於IAR,進行RT-Thread源碼移植

1.我使用的芯片是stm32f103c8t6,基於IAR移植RT-Thread Nano,點擊下載RT-Thread Nano源碼 。如果你沒有IAR,而是使用KEIL,點擊查看參考鏈接

2.下載源碼後,在rt-thread-3.1.3\bsp\stm32f103-msh路徑下,打開iar工作空間文件Project.eww,打開後是這樣子的:

Project->Options,General Optons->配置成自己使用的芯片、Debugger->配置調試工具:

 

3.配置rtconfig.h文件,在該文件里加入一下宏:

#define RT_USING_FINSH
#define FINSH_USING_HISTORY

 開啓後可對 FinSH 組件相關的參數進行配置修改(在rtconfig.h文件結尾的地方):

#if defined (RT_USING_FINSH)                    // 開關 FinSH 組件

    #define FINSH_USING_MSH                     // 使用 FinSH 組件 MSH 模式
    #define FINSH_USING_MSH_ONLY                // 僅使用 MSH 模式

    #define __FINSH_THREAD_PRIORITY     5       // 設置 FinSH 組件優先級,配置該值後通過下面的公式進行計算
    #define FINSH_THREAD_PRIORITY       (RT_THREAD_PRIORITY_MAX / 8 * __FINSH_THREAD_PRIORITY + 1)

    #define FINSH_THREAD_STACK_SIZE     1024     // 設置 FinSH 線程棧大小,範圍 1-4096

    #define FINSH_HISTORY_LINES         5       // 設置 FinSH 組件記錄歷史命令個數,值範圍 1-32

    #define FINSH_USING_SYMTAB                  // 使用符號表,需要打開,默認打開

#endif

4.在shell.c文件裏面找到rt_hw_console_getchar函數:

//修改點:函數返回值,增加清除RXNE標誌,去掉delay

int rt_hw_console_getchar(void)
{
    int ch = -1;

    if (__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_RXNE) != RESET)
    {
        ch = UartHandle.Instance->DR & 0xff;
        __HAL_UART_CLEAR_FLAG(&UartHandle, UART_FLAG_RXNE);
    }
    else
    {
        //rt_thread_mdelay(10);
    }
    return ch;
}

5.在shell.c文件裏面找到finsh_thread_entry 函數,註釋掉兩個回顯:

            
            /*.........*/
            else if (ch == 0x44) /* left key */
            {
                if (shell->line_curpos)
                {
                    //rt_kprintf("\b");
                    shell->line_curpos --;
                }

                continue;
            }
            else if (ch == 0x43) /* right key */
            {
                if (shell->line_curpos < shell->line_position)
                {
                    //rt_kprintf("%c", shell->line[shell->line_curpos]);
                    shell->line_curpos ++;
                }

                continue;
            }
             /*.........*/

2.基於QT,開發與finsh功能對接的PC終端

1.點擊下載源碼

2.我使用的qt版本是Qt Creator 5.14,如果下載我的源碼由於版本衝突,不能使用的話可以自己新建一個工程,把代碼複製過去;或者去qt官網下載該版本。

3.效果圖:

4.關鍵代碼:

 重寫鍵盤釋放事件函數,通過ev->text()可以獲取鍵盤輸入的字符(包括回退鍵,確認鍵),此外我們還需要獲取方向鍵,方向鍵的使用類似Linux終端,可通過按上鍵來翻閱之前的命令記錄,命令是在MCU裏記錄,當按了上鍵時,MCU就會返回上一次輸入的命令,命令記錄的條數最大值設置是在在rtconfig.h文件中的宏FINSH_HISTORY_LINES。

方向鍵需要發送的鍵值是不能直接通過ev->text()獲取到的,所以先通過ev->key(),不同的按鍵發送對應的控制碼,比如“上”鍵,發送的是:0x1b 0x5b 0x41即可。

void MainWindow::keyReleaseEvent(QKeyEvent *ev)
{
    int code = 0xFF;
    unsigned char controlkey[5]={0x1b,0x5b,0x00};

    //unsigned int key = ev->key();
    //qDebug("鍵值:0x%x",ev->key());

    if(!ui->textEdit->hasFocus()){//判斷光標是否在textEdit控件上
        return;
    }

    /*
     * handle control key
     * up key  : 0x1b 0x5b 0x41
     * down key: 0x1b 0x5b 0x42
     * right key:0x1b 0x5b 0x43
     * left key: 0x1b 0x5b 0x44
     */
controlkey[2] = 0x00;
    switch (ev->key()) {
    case Qt::Key_Left:
        controlkey[2] = 0x44;
        ui->textEdit->moveCursor(QTextCursor::Left);
        break;
    case Qt::Key_Right:
        controlkey[2] = 0x43;
        ui->textEdit->moveCursor(QTextCursor::Right);
        break;
    case Qt::Key_Up:
        controlkey[2] = 0x41;
        break;
    case Qt::Key_Down:
        controlkey[2] = 0x42;
        break;
    default:
        QString text = ev->text();
        if(text.size() > 0){
            code = text.at(0).toLatin1();
            if((code >= 0) && (code <= 0x7e)){//ASCII
                if(code == '\n'||code == '\r'){//回車按鍵按下時,將光標移動到末尾
                    ui->textEdit->moveCursor(QTextCursor::End);
                }

                if(port != NULL && port->isOpen()){//發送字符到串口
                    port->write((const char*)&code,1);
                }
            }
        }
        break;
    }

    if(controlkey[2] != 0x00){
        port->write((const char*)&controlkey[0],3);
    }

    //qDebug("0x%x  %c",(char)code,(char)code);
    //QWidget::keyPressEvent(ev);
}

窗口接收函數,當PC端串口接收到數據就會進入下面的函數,接受到的數據會先保存到MainSerialRecvData數組裏,然後再一個一個的追加到顯示輸入控件textEdit上,這裏我同了temp數組來過度,如果有更好的辦法可以優化一下;當CP端按下了“backspace”鍵時,MCU會往PC回發‘/b’,那麼當PC端接收到‘/b’就要刪除關標後一個字符。

void MainWindow::SerialRecvMsgEvent()
{
    QByteArray MainSerialRecvData;
    char temp[10]={0};
    if(port->bytesAvailable()>0)
    {
         MainSerialRecvData = port->readAll();
         //qDebug("rec data");
         int count = MainSerialRecvData.count();
         for(int i = 0;i<count;i++){
             //qDebug("0x%x",MainSerialRecvData.at(i));
             temp[0] = MainSerialRecvData[i];
             if((temp[0] >= 0) && (temp[0] <= 0x7e)){//ASCII
                 if(temp[0] == 0x08){// '/b':backspace鍵
                     ui->textEdit->textCursor().deletePreviousChar();//刪除光標後面一個字符
                 }else{
                    ui->textEdit->textCursor().insertText(temp);//把字符顯示到textedit裏
                 }
                 //移動滾動條到底部
                 QScrollBar *scrollbar = ui->textEdit->verticalScrollBar();
                 if (scrollbar)
                 {
                       scrollbar->setSliderPosition(scrollbar->maximum());
                 }
             }
         }
     }
 }

把qt的源碼下載解壓,就可以直接編譯運行,選擇串口,設置波特率,輸入命令是記得把輸入法切換到英文,否則輸入無效的。 

3.遇到的問題

  • 還沒用qt做的終端時,我在PC端用串口助手發送命令到MCU,發現MCU會丟失很多數據,因爲MCU端用輪詢的方式從UART裏獲取數據,如果處理不及時必然會丟失數據。解決這個問題,一種方法是,可以加長PC端發送兩個字符之間的時間;另一種方法是,MCU端利用中斷接收數據,先把數據發進隊列裏面(RT-Thread源碼有隊列功能),然後再while循環裏取出隊列裏的數據進行處理,這樣就不會丟失UART數據了。
  • 在函數rt_hw_console_getchar裏,我註釋掉了rt_thread_mdelay(10),因爲不註釋掉,按方向鍵是沒用的,因爲MCU沒及時處理導致PC端發送的數據丟失了;但是,註釋掉也會導致比finsh線程優先級低的其他線程永遠得不到調用。

 

 

 

《路漫漫其修遠兮,吾將上下而求索。———屈原》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章