天天看點

RT-Thread Nano移植FinSH控制台1. 實作Nano控制台的序列槽輸出函數2. 實作指令輸入

RT-Thread Nano 提供了shell 控制台元件 FinSH ,對于 Nano 版本的 FinSH 元件,沒有使用 RT-Thread 的裝置驅動架構,是以移植起來比較簡單,隻要提供控制台的列印輸出函數

rt_hw_console_output

和輸入函數

rt_hw_console_getchar

即可。

1. 實作Nano控制台的序列槽輸出函數

我使用的硬體平台是 STM32F407ZGT6 ,移植前先準備可以正常運作 RT-Thread Nano 的工程源碼。

1.1 初始化序列槽外設

對于控制台的輸入輸出硬體接口,我使用的是序列槽,是以需要對序列槽外設進行基本的初始化,并且開啟序列槽外設的接收中斷(輸入使用序列槽中斷方式)。

對于序列槽外設的初始化的代碼,我使用的是CubeMX生成的初始化代碼。我發現 CubeMX 生成的初始化代碼,隻是開啟了使用的那個序列槽的總中斷,還要自己單獨寫代碼開啟序列槽的接收中斷的,不知道是不是我配置 CubeMX 的問題,反正花了點時間才發現問題。

另外,實作控制台列印需要确認 rtconfig.h 檔案中已經定義了

RT_USING_CONSOLE

這個宏。

序列槽外設初始化代碼如下:

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if(uartHandle->Instance==USART1)
    {
        /* 初始化序列槽接收資料的信号量 */
        rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0);

        /* USER CODE END USART1_MspInit 0 */
        /* USART1 clock enable */
        __HAL_RCC_USART1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        /**USART1 GPIO Configuration
    	   PA9     ------> USART1_TX
           PA10     ------> USART1_RX
        */
        GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        /* USART1 interrupt Init */
        HAL_NVIC_SetPriority(USART1_IRQn, 4, 0);		// 中斷優先級配置
        HAL_NVIC_EnableIRQ(USART1_IRQn);				// 使能序列槽1中斷
        huart1.Instance->SR &= ~(USART_SR_RXNE);		// 清除RXNE标志位
        __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);    // 使能序列槽接收中斷
    }
}
           

1.2 實作控制台輸出函數 rt_hw_console_output

rt_hw_console_output

是控制台輸出的一個函數,函數名需要保持不變。

UART_HandleTypeDef huart1;

void rt_hw_console_output(const char *str)
{
    unsigned int i = 0, size = 0;
    char a = '\r';

    __HAL_UNLOCK(&huart1);

    size = strlen(str);
    for (i = 0; i < size; i++)
    {
        if (*(str + i) == '\n')
        {
            /* 當輸出\n字元時,在\n字元之前先輸出\r */
            HAL_UART_Transmit(&huart1, (uint8_t *)&a, 1, 1);
        }
        HAL_UART_Transmit(&huart1, (uint8_t *)(str + i), 1, 1);
    }
}
           

當實作了這個函數之後,啟動 RT-Thread Nano 時,就可以在序列槽終端列印出啟動是的 logo 和版本資訊等内容了。如下圖:

RT-Thread Nano移植FinSH控制台1. 實作Nano控制台的序列槽輸出函數2. 實作指令輸入

2. 實作指令輸入

2.1 添加 FinSH 源碼到 keil

把 RT-Thread Nano 目錄下的 components/finsh 目錄,複制到 keil 工程目錄中,然後把下面4個 C 檔案添加到 keil 工程。

RT-Thread Nano移植FinSH控制台1. 實作Nano控制台的序列槽輸出函數2. 實作指令輸入

另外,rtconfig.h 配置檔案中,需要使能

#define RT_USING_FINSH

宏定義,才能使用 FinSH 元件。

2.2 實作控制台輸入函數rt_hw_console_getchar

rt_hw_console_getchar

函數功能,就是控制台從序列槽中擷取一個字元。

擷取一個字元的方式可以采用查詢方式。當然查詢方式不能死等了(查詢不到可讀字元時,調用延時函數休眠10ms),因為需要讓出CPU給其他線程運作的機會。

也可以采用中斷方式(我這裡采用中斷方式),可以更有效的利用CPU。

中斷方式實作原理:

當序列槽接收到資料産生接收中斷時,在中斷中把資料存入 ringbuffer 緩沖區中,然後釋放信号量。而 shell 線程去擷取信号量,可以擷取到信号量則讀取 ringbuffer 中的資料,沒有資料則一直阻塞等待擷取序列槽接收中斷釋放的信号量。

2.2.1 實作ringbuffer讀寫

先把環形緩沖區的讀寫代碼先實作了。

#define BUFFER_SIZE 1024        /* 環形緩沖區的大小 */
typedef struct
{
	volatile unsigned int pR;           /* 讀位址 */
	volatile unsigned int pW;           /* 寫位址 */   
    unsigned char buffer[BUFFER_SIZE];  /* 緩沖區空間 */    
} ring_buffer;

/*
 *  函數名:void ring_buffer_init(ring_buffer *dst_buf)
 *  輸入參數:dst_buf --> 指向目标緩沖區
 *  輸出參數:無
 *  傳回值:無
 *  函數作用:初始化緩沖區
*/
void ring_buffer_init(ring_buffer *dst_buf)
{
    dst_buf->pW = 0;
    dst_buf->pR = 0;
}

/*
 *  函數名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
 *  輸入參數:c --> 要寫入的資料
 *            dst_buf --> 指向目标緩沖區
 *  輸出參數:無
 *  傳回值:無
 *  函數作用:向目标緩沖區寫入一個位元組的資料,如果緩沖區滿了就丢掉此資料
*/
void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
{
    int i = (dst_buf->pW + 1) % BUFFER_SIZE;
    if(i != dst_buf->pR)    // 環形緩沖區沒有寫滿
    {
        dst_buf->buffer[dst_buf->pW] = c;
        dst_buf->pW = i;
    }
}

/*
 *  函數名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
 *  輸入參數:c --> 指向将讀到的資料儲存到記憶體中的位址
 *            dst_buf --> 指向目标緩沖區
 *  輸出參數:無
 *  傳回值:讀到資料傳回0,否則傳回-1
 *  函數作用:從目标緩沖區讀取一個位元組的資料,如果緩沖區空了傳回-1表明讀取失敗
*/
int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
{
    if(dst_buf->pR == dst_buf->pW)
    {
        return -1;
    }
    else
    {
        *c = dst_buf->buffer[dst_buf->pR];
        dst_buf->pR = (dst_buf->pR + 1) % BUFFER_SIZE;
        return 0;
    }
}
           

2.2.2 序列槽接收中斷

序列槽接收中斷要做的事情就是:把讀取到的資料存放進 ringbuffer 中,然後釋放信号量喚醒 shell 線程。

static ring_buffer uart1_rx_buf = {0, 0, {0}};		/* 序列槽接收 ringbuffer */
static struct rt_semaphore shell_rx_sem; 			/* 定義一個信号量 */

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

    /* enter interrupt */
    rt_interrupt_enter();          //在中斷中一定要調用這對函數,進入中斷

    if ((__HAL_UART_GET_FLAG(&(huart1), UART_FLAG_RXNE) != RESET) &&
        (__HAL_UART_GET_IT_SOURCE(&(huart1), UART_IT_RXNE) != RESET))
    {
        while (1)
        {
            ch = -1;
            if (__HAL_UART_GET_FLAG(&(huart1), UART_FLAG_RXNE) != RESET)
            {
                ch =  huart1.Instance->DR & 0xff;
            }
            if (ch == -1)
            {
                break;
            }
            /* 讀取到資料,将資料存入 ringbuffer */
            ring_buffer_write(ch, &uart1_rx_buf);
        }
        rt_sem_release(&shell_rx_sem);				// 釋放信号量喚醒 shell 線程
    }

    /* leave interrupt */
    rt_interrupt_leave();    //在中斷中一定要調用這對函數,離開中斷
}
           

2.2.3 實作rt_hw_console_getchar

rt_hw_console_getchar

函數是 shell 線程接收一個字元函數,進而實作指令行互動功能的。代碼如下:

char rt_hw_console_getchar(void)
{
    char ch = 0;
	
    /* 從 ringbuffer 中拿出資料 */
    while (ring_buffer_read((unsigned char *)&ch, &uart1_rx_buf) != 0)
    {	
        /* 沒有讀取到資料,則一直阻塞等待 */
        rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER);
    }	
    return ch;
}
           

實作完上面的代碼之後,就可以實作指令行互動功能了。自己可以在序列槽終端軟體輸入 help 指令,可以檢視到列出了系統支援的所有指令,說明移植 shell 成功了。