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线程优先级低的其他线程永远得不到调用。

 

 

 

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

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