RT-thread finsh組件工作流程


finsh是RT-Thread的命令行外殼(shell),提供一套供用戶在命令行的操作接口,主要用於調試、查看系統信息。在大部分嵌入式系統中,一般開發調試都使用硬件調試器和printf日志打印,在有些情況下,這兩種方式並不是那么好用。比如對於RT-Thread這個多線程系統,我們想知道某個時刻系統中的線程運行狀態、手動控制系統狀態。如果有一個shell,就可以輸入命令,直接相應的函數執行獲得需要的信息,或者控制程序的行為。這無疑會十分方便。

finsh支持兩種模式:

1. C語言解釋器模式, 為行文方便稱之為c-style;

2. 傳統命令行模式,此模式又稱為msh(module shell)。C語言表達式解釋模式下, finsh能夠解析執行大部分C語言的表達式,並使用類似C語言的函數調用方式訪問系統中的函數及全局變量,此外它也能夠通過命令行方式創建變量。在msh模式下,finsh運行方式類似於dos/bash等傳統shell。

大致工作流程

一、finsh組件初始化函數finsh_system_init(),並且添加了INIT_COMPONENT_EXPORT(finsh_system_init),支持組件初始化;

這個函數會初始化finsh組件,包括一些finsh變量以及相關數據結構。

然后它會創建一個線程,代碼如下:

result = rt_thread_init(&finsh_thread,
        "tshell",
        finsh_thread_entry, RT_NULL,
        &finsh_thread_stack[0], sizeof(finsh_thread_stack),
        FINSH_THREAD_PRIORITY, 10);

    if (result == RT_EOK)
        rt_thread_startup(&finsh_thread);

可以看到,線程函數是finsh_thread_entry,在下一節中我們將分析它具體工作流程。

 

二、void finsh_set_device(const char* device_name)函數為finsh設置終端設備,在stm32中主要設置串口設備為終端。該函數一般放在組件初始化函數rt_component_init()后面,因為要先完成finsh組件初始化才能設置終端設備。

void finsh_set_device(const char* device_name)
{
    rt_device_t dev = RT_NULL;

    RT_ASSERT(shell != RT_NULL);
    dev = rt_device_find(device_name);
    if (dev == RT_NULL)
    {
        rt_kprintf("finsh: can not find device: %s\n", device_name);
        return;
    }

    /* check whether it's a same device */
    if (dev == shell->device) return;
    /* open this device and set the new device in finsh shell */
    if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX |\
                       RT_DEVICE_FLAG_STREAM) == RT_EOK)
    {
        if (shell->device != RT_NULL)
        {
            /* close old finsh device */
            rt_device_close(shell->device);
            rt_device_set_rx_indicate(shell->device, RT_NULL);
        }

        shell->device = dev;
        rt_device_set_rx_indicate(dev, finsh_rx_ind);
    }
}

這個函數為finsh組件設置使用的串口,從這個函數中我們可以總結出,如何使用串口設備。

  1. 調用rt_device_find使用設備的字符串名字查找設備,得到設備數據結構指針
  2. 調用rt_devcie_open打開設備,open_flag為RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX |RT_DEVICE_FLAG_STREAM

* 對finsh來說,還使用了rt_device_set_rx_indicate函數設置了一個回調函數finsh_rx_ind,它的作用我們后面會討論

到這里設備就被打開了。

在serial.c中rt_hw_serial_isr()中有:

/* invoke callback */
            if (serial->parent.rx_indicate != RT_NULL)
            {
                rt_size_t rx_length;
            
                /* get rx length */
                level = rt_hw_interrupt_disable();
                rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index):
                    (serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index));
                rt_hw_interrupt_enable(level);

                serial->parent.rx_indicate(&serial->parent, rx_length);
            }

上面計算得到rx_length,然后觸發回調函數,也就是前面的finsh_rx_ind函數,即實際執行的是fins_rx_ind(device, rx_length)。

在shell.c中fins_rx_ind源碼為:

static rt_err_t finsh_rx_ind(rt_device_t dev, rt_size_t size)
{
    RT_ASSERT(shell != RT_NULL);

    /* release semaphore to let finsh thread rx data */
    rt_sem_release(&shell->rx_sem);

    return RT_EOK;
}

這個函數里只是簡單的釋放信號量。也就是說,當串口硬件上接收到一個字節,就會調用finsh_rx_ind函數來釋放一個信號量。

 

三、finsh線程函數的工作流程概述

void finsh_thread_entry(void* parameter)
{
    char ch;

    /* normal is echo mode */
    shell->echo_mode = 1;

#ifndef FINSH_USING_MSH_ONLY
    finsh_init(&shell->parser);
#endif
    rt_kprintf(FINSH_PROMPT);

    /* set console device as shell device */
    if (shell->device == RT_NULL)
    {
#ifdef RT_USING_CONSOLE
        shell->device = rt_console_get_device();
        RT_ASSERT(shell->device);
        rt_device_set_rx_indicate(shell->device, finsh_rx_ind);
        rt_device_open(shell->device, (RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM | RT_DEVICE_FLAG_INT_RX));
#else
        RT_ASSERT(shell->device);
#endif
    }

    while (1)
    {
        /* wait receive */
        if (rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER) != RT_EOK) continue;

        /* read one character from device */
        while (rt_device_read(shell->device, 0, &ch, 1) == 1)
        {
            /*
             * handle control key
             * up key  : 0x1b 0x5b 0x41
             * down key: 0x1b 0x5b 0x42
             * right key:0x1b 0x5b 0x43
             * left key: 0x1b 0x5b 0x44
             */
            if (ch == 0x1b)
            {
                shell->stat = WAIT_SPEC_KEY;
                continue;
            }
            else if (shell->stat == WAIT_SPEC_KEY)
            {
                if (ch == 0x5b)
                {
                    shell->stat = WAIT_FUNC_KEY;
                    continue;
                }

                shell->stat = WAIT_NORMAL;
            }
            else if (shell->stat == WAIT_FUNC_KEY)
            {
                shell->stat = WAIT_NORMAL;

                if (ch == 0x41) /* up key */
                {
#ifdef FINSH_USING_HISTORY
                    /* prev history */
                    if (shell->current_history > 0)
                        shell->current_history --;
                    else
                    {
                        shell->current_history = 0;
                        continue;
                    }

                    /* copy the history command */
                    memcpy(shell->line, &shell->cmd_history[shell->current_history][0],
                           FINSH_CMD_SIZE);
                    shell->line_curpos = shell->line_position = strlen(shell->line);
                    shell_handle_history(shell);
#endif
                    continue;
                }
                else if (ch == 0x42) /* down key */
                {
#ifdef FINSH_USING_HISTORY
                    /* next history */
                    if (shell->current_history < shell->history_count - 1)
                        shell->current_history ++;
                    else
                    {
                        /* set to the end of history */
                        if (shell->history_count != 0)
                            shell->current_history = shell->history_count - 1;
                        else
                            continue;
                    }

                    memcpy(shell->line, &shell->cmd_history[shell->current_history][0],
                           FINSH_CMD_SIZE);
                    shell->line_curpos = shell->line_position = strlen(shell->line);
                    shell_handle_history(shell);
#endif
                    continue;
                }
                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;
                }

            }

            /* handle CR key */
            if (ch == '\r')
            {
                char next;

                if (rt_device_read(shell->device, 0, &next, 1) == 1)
                    ch = next;
                else ch = '\r';
            }
            /* handle tab key */
            else if (ch == '\t')
            {
                int i;
                /* move the cursor to the beginning of line */
                for (i = 0; i < shell->line_curpos; i++)
                    rt_kprintf("\b");

                /* auto complete */
                shell_auto_complete(&shell->line[0]);
                /* re-calculate position */
                shell->line_curpos = shell->line_position = strlen(shell->line);

                continue;
            }
            /* handle backspace key */
            else if (ch == 0x7f || ch == 0x08)
            {
                /* note that shell->line_curpos >= 0 */
                if (shell->line_curpos == 0)
                    continue;

                shell->line_position--;
                shell->line_curpos--;

                if (shell->line_position > shell->line_curpos)
                {
                    int i;

                    rt_memmove(&shell->line[shell->line_curpos],
                               &shell->line[shell->line_curpos + 1],
                               shell->line_position - shell->line_curpos);
                    shell->line[shell->line_position] = 0;

                    rt_kprintf("\b%s  \b", &shell->line[shell->line_curpos]);

                    /* move the cursor to the origin position */
                    for (i = shell->line_curpos; i <= shell->line_position; i++)
                        rt_kprintf("\b");
                }
                else
                {
                    rt_kprintf("\b \b");
                    shell->line[shell->line_position] = 0;
                }

                continue;
            }

            /* handle end of line, break */
            if (ch == '\r' || ch == '\n')
            {
                #ifdef FINSH_USING_HISTORY
                shell_push_history(shell);
                #endif

                #ifdef FINSH_USING_MSH
                if (msh_is_used() == RT_TRUE)
                {
                    rt_kprintf("\n");
                    msh_exec(shell->line, shell->line_position);
                }
                else
                #endif
                {
                #ifndef FINSH_USING_MSH_ONLY                
                    /* add ';' and run the command line */
                    shell->line[shell->line_position] = ';';

                    if (shell->line_position != 0) finsh_run_line(&shell->parser, shell->line);
                    else rt_kprintf("\n");
                #endif                  
                }

                rt_kprintf(FINSH_PROMPT);
                memset(shell->line, 0, sizeof(shell->line));
                shell->line_curpos = shell->line_position = 0;
                break;
            }

            /* it's a large line, discard it */
            if (shell->line_position >= FINSH_CMD_SIZE)
                shell->line_position = 0;

            /* normal character */
            if (shell->line_curpos < shell->line_position)
            {
                int i;

                rt_memmove(&shell->line[shell->line_curpos + 1],
                           &shell->line[shell->line_curpos],
                           shell->line_position - shell->line_curpos);
                shell->line[shell->line_curpos] = ch;
                if (shell->echo_mode)
                    rt_kprintf("%s", &shell->line[shell->line_curpos]);

                /* move the cursor to new position */
                for (i = shell->line_curpos; i < shell->line_position; i++)
                    rt_kprintf("\b");
            }
            else
            {
                shell->line[shell->line_position] = ch;
                if (shell->echo_mode)
                    rt_kprintf("%c", ch);
            }

            ch = 0;
            shell->line_position ++;
            shell->line_curpos++;
            if (shell->line_position >= 80) 
            {
                /* clear command line */
                shell->line_position = 0;
                shell->line_curpos = 0;
            }
        } /* end of device read */
    }
}

函數主體依然是一個while(1)循環,這是顯然的,因為finsh要不停的監聽終端上輸入。

 if (rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER) != RT_EOK) continue;

即,如果串口上沒有收到任何數據,並且串口緩沖區中也無數據,即shell→rx_sem信號量的值為0,那么這個函數會使finsh線程休眠,RTT內核會執行其他線程。

當串口收到數據,串口終端調用回調函數finsh_rx_ind函數來釋放信號量,這會喚醒finsh線程,rt_sem_take函數會執行完畢,繼續執行接下來的代碼。

接下來的代碼調用rt_device_read函數從串口數據緩沖池中讀取一個字節。

然后判斷所讀取的到這個字節(判斷上下左右四個按鍵所代表的字節)。

(1) 如果是'\r',即表示用戶按下了回車鍵,再調用rt_device_read函數來讀取一個字節,如果讀到,則這將更新讀到的字節,一般情況下,這個函數會返回0,即沒有讀到新的字節。

(2) 如果是'\t',即表示用戶按下了TAB鍵,則調用finsh_auto_complete函數,這個函數做自動補全操作,也就是根據當前已輸入的字符串,從finsh內部已注冊的函數/變量中查找匹配字符串,如果找到則會在終端上自動補全。

(3) 如果是0x7f或者0x08 說明:查ascii碼表可知,0x08 表示按下了backspace鍵,【0x7f表示按下了DEL鍵,這個不對勁,如何知道當我們按下了鍵盤按鍵時,串口都收到了什么數據呢?】 這表示用戶期望刪除已經輸入的字符串,根據測試結果,發送”\0x08 \0x08”,可以實現退格。

(4) 如果收到了'\r'或者'\n',則表示用戶按下了回車,希望處理這個命令,那么finsh_run_line函數被執行,這個函數會從從finsh已注冊的函數/變量中匹配當前從終端里獲取的字符串,如果匹配到,則執行對應的函數(若字符串為函數名)或者打印變量的值(若字符串為已變量)。

(5) 回顯字符,也就是將剛才從串口接收到終端發送的字符發送到終端軟件上顯示出來。這就是說,我們在終端軟件上輸入字符,並且可以看到我們輸入的字符,實際上是板子上的串口重新發回來顯示的。在上面finsh的線程代碼中,rt_device_write函數是在rt_kprintf中調用的。

然后回到(1),重復這個過程。


免責聲明!

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



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