RT-Thread進階筆記之FinSH組件

1、FinSH組件介紹

FinSH 是 RT-Thread 的命令行組件,提供一套供用戶在命令行調用的操作接口,主要用於調試或查看系統信息。它可以使用串口 / 以太網 / USB 等與 PC 機進行通信。
用戶在控制終端輸入命令,控制終端通過串口、USB、網絡等方式將命令傳給設備裏的 FinSH,FinSH 會讀取設備輸入命令,解析並自動掃描內部函數表,尋找對應函數名,執行函數後輸出迴應,迴應通過原路返回,將結果顯示在控制終端上。
當使用串口連接設備與控制終端時,FinSH 命令的執行流程,如下圖所示:
在這裏插入圖片描述

1.1 FinSH 支持的功能

FinSH 支持自動補全、查看歷史命令等功能,通過鍵盤上的按鍵可以很方便的使用這些功能,FinSH 支持的按鍵如下表所示:
在這裏插入圖片描述

1.2 FinSH 支持兩種輸入模式

FinSH 支持兩種輸入模式,分別是傳統命令行模式和 C 語言解釋器模式。
C 語言解釋器模式又稱爲C-Style 模式,C-Style 模式在運行腳本或者程序時不太方便,而使用傳統的 shell 方式則比較方便。另外,C-Style 模式下,FinSH 佔用體積比較大。RT-Thread默認只開啓傳統命令行模式。此文只介紹傳統命令行模式。
傳統命令行模式
此模式又稱爲 msh(module shell),msh 模式下,FinSH 與傳統shell(dos/bash)執行方式一致,例如,可以通過 cd / 命令將目錄切換至根目錄。
msh 通過解析,將輸入字符分解成以空格區分開的命令和參數。其命令執行格式如下所示:
command [arg1] [arg2] […]
其中 command 既可以是 RT-Thread 內置的命令,也可以是可執行的文件。

1.3 自定義 msh 命令

自定義的 msh 命令,可以在 msh 模式下被運行,將一個命令導出到 msh 模式可以使用如下宏接口:

MSH_CMD_EXPORT(name, desc);
參數 描述
name 要導出的命令
desc 導出命令的描述

導出無參數命令時,函數的入參爲 void,示例如下:

void hello(void)
{
    rt_kprintf("hello RT-Thread!\n");
}
MSH_CMD_EXPORT(hello , say hello to RT-Thread);

系統運行起來後,在 FinSH 控制檯按 tab 鍵可以看到導出的命令:

msh />
RT-Thread shell commands:
hello             - say hello to RT-Thread
version           - show RT-Thread version information
list_thread       - list thread
……

運行 hello 命令,運行結果如下所示:

msh />hello
hello RT_Thread!
msh />

導出有參數的命令時,函數的入參爲 int argc 和 char**argv。argc 表示參數的個數,argv 表示命令行參數字符串指針數組指針。導出有參數命令示例如下:

#include <rtthread.h>

static void atcmd(int argc, char**argv)
{
    if (argc < 2)
    {
        rt_kprintf("Please input'atcmd <server|client>'\n");
        return;
    }

    if (!rt_strcmp(argv[1], "server"))
    {
        rt_kprintf("AT server!\n");
    }
    else if (!rt_strcmp(argv[1], "client"))
    {
        rt_kprintf("AT client!\n");
    }
    else
    {
        rt_kprintf("Please input'atcmd <server|client>'\n");
    }
}

MSH_CMD_EXPORT(atcmd, atcmd sample: atcmd <server|client>);

系統運行起來後,在 FinSH 控制檯按 tab 鍵可以看到導出的命令:

msh />
RT-Thread shell commands:
hello             - say hello to RT-Thread
atcmd             - atcmd sample: atcmd <server|client>
version           - show RT-Thread version information
list_thread       - list thread
……

運行 atcmd 命令,運行結果如下所示:

msh />atcmd
Please input 'atcmd <server|client>'
msh />atcmd server
AT server!
msh />

1.3 FinSH 功能配置

在這裏插入圖片描述

2、FinSH組件原理介紹

FinSH 源碼位於 components/finsh 目錄下。FinSH組件不屬於內核層。要想實現FinSH組件,如果使能了RT_USING_POSIX,最少需要利用內核中的線程和設備兩個模塊,如果沒有使能RT_USING_POSIX,還需要內核的信號量模塊。FinSH線程用於維護shell,設備用於rt_kprintf() 輸出,信號量用於同步。
以使能RT_USING_POSIX爲例,不使用信號量。

2.1 finsh shell的結構

在這裏插入圖片描述

2.2 FinSH線程

FinSH線程的初始化使用的是RT-Thread 自動初始化機制,可以參考這篇文章瞭解https://blog.csdn.net/sinat_31039061/article/details/104127274
關於RT-Thread 的線程可以參考內核架構https://blog.csdn.net/sinat_31039061/article/details/104121771
創建FinSH線程:

    tid = rt_thread_create(FINSH_THREAD_NAME,
                           finsh_thread_entry, RT_NULL,
                           FINSH_THREAD_STACK_SIZE, FINSH_THREAD_PRIORITY, 10);

FinSH線程的入口函數爲:

void finsh_thread_entry(void *parameter)

進入finsh線程首先運行 rt_kprintf(FINSH_PROMPT);,輸出msh />,然後進入while (1)死循環等待鍵盤的輸入:

rt_kprintf(FINSH_PROMPT);
while (1)
{
    ch = finsh_getchar();
    if (ch < 0)
    {
        continue;
    }
    ...
}

此線程根據鍵盤的輸入命令,解析並自動掃描內部函數表,尋找對應函數名,執行函數後輸出迴應,迴應通過原路返回,將結果顯示在控制終端上。

2.3 FinSH 的輸入

RT-Thread中FinSH 的輸入使用的是finsh_getchar(),進入finsh_getchar()爲stdio.h中的庫函數getchar(),查看getchar()的百度百科解釋:

getchar由宏實現:#define getchar() getc(stdin)。getchar有一個int型的返回值。當程序調用getchar時,程序就等着用戶按鍵。用戶輸入的字符被存放在鍵盤緩衝區中。直到用戶按回車爲止。當用戶鍵入回車之後,getchar纔開始從stdin流中每次讀入一個字符。getchar函數的返回值是用戶輸入的字符的ASCII碼,若文件結尾則返回-1(EOF),且將用戶輸入的字符回顯到屏幕。如用戶在按回車之前輸入了不止一個字符,其他字符會保留在鍵盤緩存區中,等待後續getchar調用讀取。也就是說,後續的getchar調用不會等待用戶按鍵,而直接讀取緩衝區中的字符,直到緩衝區中的字符讀完後,纔等待用戶按鍵。

getchar()函數的執行模式是阻塞式的,當需要接收字符流的時候,當前線程就會被掛起,其後的所有代碼均要等待用戶輸入回車表示輸入完畢後,線程纔會被調度進入CPU時鐘內執行其餘的代碼。

2.4 FinSH 的輸出

RT-Thread中FinSH 的輸出使用的是rt_kprintf,rt_kprintf使用的是RT-Thread的串口設備。關於RT-Thread設備框架的使用方法請參考這篇文章https://blog.csdn.net/sinat_31039061/article/details/104135728

2.4.1 創建並註冊串口設備:

int rt_hw_usart_init(void)
{
    rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart);
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
    rt_err_t result = 0;

    stm32_uart_get_dma_config();

    for (int i = 0; i < obj_num; i++)
    {
        uart_obj[i].config = &uart_config[i];
        uart_obj[i].serial.ops    = &stm32_uart_ops;
        uart_obj[i].serial.config = config;
        /* register UART device */
        result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
                                       RT_DEVICE_FLAG_RDWR
                                       | RT_DEVICE_FLAG_INT_RX
                                       | RT_DEVICE_FLAG_INT_TX
                                       | uart_obj[i].uart_dma_flag
                                       , NULL);
        RT_ASSERT(result == RT_EOK);
    }

    return result;
}

我在board.h中打開了串口1和串口3,如下:

#define BSP_USING_UART1
#define BSP_USING_UART3

所以在終端運行 list_device命令,運行結果如下所示:

device           type         ref count
-------- -------------------- ----------
uart3    Character Device     1
uart1    Character Device     2
pin      Miscellaneous Device 0
msh />

2.4.2 打開串口設備:

控制檯配置的是使用uart1,所以打開串口1設備:

#define RT_CONSOLE_DEVICE_NAME "uart1"
.
.
.
    /* Set the shell console output device */
#if defined(RT_USING_DEVICE) && defined(RT_USING_CONSOLE)
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
rt_device_t rt_console_set_device(const char *name)
{
    rt_device_t new_device, old_device;

    /* save old device */
    old_device = _console_device;

    /* find new console device */
    new_device = rt_device_find(name);
    if (new_device != RT_NULL)
    {
        if (_console_device != RT_NULL)
        {
            /* close old console device */
            rt_device_close(_console_device);
        }

        /* set new console device */
        rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM);
        _console_device = new_device;
    }

    return old_device;
}

把uart1這個串口設備句柄賦值給下邊這個全局變量,以備rt_kprintf使用。

static rt_device_t _console_device

rt_kprintf的實現:

void rt_kprintf(const char *fmt, ...)
{
    va_list args;
    rt_size_t length;
    static char rt_log_buf[RT_CONSOLEBUF_SIZE];

    va_start(args, fmt);
    /* the return value of vsnprintf is the number of bytes that would be
     * written to buffer had if the size of the buffer been sufficiently
     * large excluding the terminating null byte. If the output string
     * would be larger than the rt_log_buf, we have to adjust the output
     * length. */
    length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args);
    if (length > RT_CONSOLEBUF_SIZE - 1)
        length = RT_CONSOLEBUF_SIZE - 1;
#ifdef RT_USING_DEVICE
    if (_console_device == RT_NULL)
    {
        rt_hw_console_output(rt_log_buf);
    }
    else
    {
        rt_uint16_t old_flag = _console_device->open_flag;

        _console_device->open_flag |= RT_DEVICE_FLAG_STREAM;
        rt_device_write(_console_device, 0, rt_log_buf, length);
        _console_device->open_flag = old_flag;
    }
#else
    rt_hw_console_output(rt_log_buf);
#endif
    va_end(args);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章