目錄
背景:移植RT-Thread nano,並基於 nano 添加 FinSH/shell
背景:移植RT-Thread nano,並基於 nano 添加 FinSH/shell
在nano上添加finsh可以有兩種方法:
1、移植finsh基於device框架【這個官方文檔中心有相關的文章了,鏈接:https://www.rt-thread.org/document/site/tutorial/nano/nano_finsh/an0033-nano-finsh/】
2、移植finsh不基於device框架【本文講解這個不基於device框架的,從頭講解,如果移植rtt nano,然後基於這個nano 再移植finsh】【本文基於 rtt nano 3.1.2 /3.1.1】
前提及準備工作
- 安裝了mdk
- 一個stm32 mdk裸機工程:要一份能運行的基本工程,如一個f103的可以跑led的裸機工程即可。(這裏基於hal庫做的mdk工程)
- 一個nano源碼:我們直接從keil中下載nano的pack包
- 注意,本文基於 rtt nano 3.1.2 版本,3.1.1也行
step1:添加rt-thread nano到裸機工程
注意:step1 是參考了RTT文檔中心的教程。
1.1、Nano Pack 安裝
Nano Pack 可以通過 MDK 聯網安裝,也可以手動安裝。下面開始介紹兩種安裝方式。
方法一:Pack Installer 安裝
打開 MDK 軟件,點擊工具欄的 Pack Installer 圖標:
點擊右側的 Pack,展開 Generic,可以找到 RealThread::RT-Thread,點擊 Action 欄對應的 Install ,就可以在線安裝 Nano Pack 了。另外,如果需要安裝其他版本,則需要展開 RealThread::RT-Thread,進行選擇。
方法二:手動安裝
我們也可以從官網下載安裝文件,RT-Thread Nano 離線安裝包下載,下載結束後雙擊文件進行安裝。
RT-Thread Nano 離線安裝包下載路徑:https://www.rt-thread.org/download/mdk/RealThread.RT-Thread.3.0.3.pack
1.2、基礎工程準備
在開始創建 RT-Thread 小系統之前,我們需要準備一個能正常運行的裸機工程。作爲示例,本文使用的是基於 STM32L475-Pandora 和 HAL 庫的一個 LED 閃爍程序。程序的主要截圖如下:
在我們的例程中主要做了系統初始化與LED閃爍功能,編譯下載程序後,就可以看到 LED 閃爍了。讀者可以根據自己的需要使用其他芯片,完成一個簡單的類似裸機工程。
1.3、開始移植rtt nano到裸機工程
打開已經準備好的可以運行的裸機程序,將 RT-Thread 添加到工程。如下圖,點擊 Manage Run-Time Environment。、
在 Manage Rum-Time Environment 裏”Software Component” 欄找到 RTOS,Variant 欄選擇 RT-Thread,然後勾選 kernel,點擊”OK” 就添加 RT-Thread 內核到工程了。
現在可以在 Project 看到 RT-Thread RTOS 已經添加進來了,展開 RTOS,可以看到添加到工程的文件:
Cortex-M 芯片內核移植代碼:
context_rvds.s
cpuport.c
kernel文件
clock.c
components.c
device.c
idle.c
ipc.c
irq.c
kservice.c
mem.c
object.c
scheduler.c
thread.c
timer.c
應用代碼及配置文件:
board.c
rtconfig.h
1.4、適配 RT-Thread nano
RT-Thread 會用到了異常處理函數 HardFault_Handler()
和懸掛處理函數 PendSV_Handler()
,以及 Systick 中斷服務函數 SysTick_Handler()
,所以用戶代碼需要保證這幾個函數沒有被使用,若編譯提示函數重複定義,請刪除自己定義的函數。
RT-Thread Nano 在 board.c 中默認完成了 systick 的配置,用戶可以修改宏 RT_TICK_PER_SECOND 的值配置每秒 systick 數。
RT-Thread Nano 默認是使用數組作爲 heap。
替換例程中的 delay
函數:
1). 包含 RT-Thread 的相關頭文件 <rtthread.h>
。
2). 刪除之前在裸機工程中做的系統配置(如hal初始化、時鐘初始化等),這是因爲RT-Thread在系統啓動時已經配置完成,否則會重複配置。
3). 將 delay()
函數替換成 rt_thread_mdelay()
,如 rt_thread_mdelay(500)
將延時 500ms。
下面是完成修改的代碼:
編譯程序之後下載到芯片就可以看到基於 RT-Thread 的程序運行起來了,LED 正常閃爍。
注意事項:當添加 RT-Thread 之後,裸機中的 main() 函數會自動變成 RT-Thread 系統中 main 線程 的入口函數。由於線程不能一直獨佔 CPU,所以此時在 main() 中使用 while(1) 時,需要有讓出 CPU 的動作,比如使用 rt_thread_mdelay()
系列的函數讓出 CPU。另外3.0.3版本中還沒有 rt_thread_mdelay()
接口,可以使用 rt_thread_delay()
。
1.5、RT-Thread Nano 配置(選配)
用戶可以根據自己的需要通過修改 rtconfig.h 文件裏面的宏定義配置相應功能。
RT-Thread Nano 默認未開啓宏 RT_USING_HEAP,故只支持靜態方式創建任務及信號量。若要通過動態方式創建對象則需要在 rtconfig.h 文件裏開啓 RT_USING_HEAP 宏定義。
MDK 的配置嚮導 configuration Wizard 可以很方便的對工程進行配置,Value 一欄可以選中對應功能及修改相關值,等同於直接修改配置文件 rtconfig.h。
1.6 常見問題
Q1:如何升級 pack?
A:Pack 升級步驟基本如同軟件包,展開 RealThread::RT-Thread 後,選擇比較新的 Nano 版本,點擊 Install 進行安裝。如下圖所示,點擊紅色框中的 Install 進行升級,即可將 3.1.1 版本升級到 3.1.2。
step2:添加finsh到工程
2.1、添加finsh源碼
點擊Manage Run-Environment:
勾選device drivers與shell,這將自動把FinSH組件的源碼到工程,並自動添加 #define RTE_USING_DEVICE
與#define RTE_USING_FINSH
宏:
2.2、實現uart驅動
實現uart驅動,主要實現初始化與讀寫接口,並藉助了device註冊接口,將uart註冊到系統中,使其更方便的對接shell。需要實現的函數原型如下:
rt_err_t (*init) (rt_device_t dev);
rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);
rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
//外加一個註冊的函數,註冊一個 rt_device 的實例,方便對接 FinSH 組件。
需要添加的代碼如下所示:
rt_err_t uart_init(rt_device_t dev)
{
...
}
rt_err_t uart_open(rt_device_t dev, rt_uint16_t oflag)
{
return uart_init(dev);
}
rt_size_t uart_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
...
}
rt_size_t uart_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
...
}
struct rt_device uart_dev;
static int uart_register(void)
{
uart_dev.init = uart_init;
uart_dev.open = uart_open;
uart_dev.read = uart_read;
uart_dev.write = uart_write;
rt_device_register(&uart_dev,"uart1",0);
return 0;
}
INIT_BOARD_EXPORT(uart_register);
- uart_init():初始化串口,包括硬件引腳的初始化與串口傳輸模式及波特率等的設置。
- uart_open():打開串口,使用該接口僅僅是爲了對接 FinSH,沒有實際意義,直接返回串口初始化即可。
- uart_read():讀入字符,長度size。
- uart_write():輸出字符,長度size。注意輸出打印時,在遇到
\n
時,就在輸出\n
之前先輸出一個\r
。 - uart_register():註冊函數,將uart設備註冊到系統中,務必使用INIT_BOARD_EXPORT()初始化該函數。
注意:RT-Thread 系統中已有的打印均以 “\n” 結尾,而並非 “\r\n”,所以在字符輸出時,需要在輸出“\n”之前輸出“\r”完成回車與換行,否則系統打印出來的信息將只有換行。
2.3、修改console名稱
打開rtconfig.h文件:將console的名稱修改爲剛註冊到系統中的uart名稱,這樣FinSH就對接到了UART端口上。如上面註冊名爲“uart1”,則在rtconfig.h中修改 RT_CONSOLE_DEVICE_NAME 的定義爲 uart1:
#define RT_CONSOLE_DEVICE_NAME "uart1"
2.4、下載驗證
下載到開發板進行驗證,移植成功
2.5 附錄一份uart驅動供參考
static UART_HandleTypeDef UartHandle;
rt_err_t uart_init(rt_device_t dev)
{
UartHandle.Instance = USART1;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
if (HAL_UART_Init(&UartHandle) != HAL_OK)
{
while (1);
}
return 0;
}
rt_err_t uart_open(rt_device_t dev, rt_uint16_t oflag)
{
return uart_init(dev);
}
rt_size_t uart_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
{
while (1)
{
if (HAL_UART_Receive(&UartHandle, buffer, size, 20) == 0)
{
return size;
}
}
}
rt_size_t uart_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
rt_size_t i = 0;
char a = '\r';
const char *val = 0;
val = (const char *)(buffer);
__HAL_UNLOCK(&UartHandle);
for (i = 0; i < size; i++)
{
if (*(val + i) == '\n')
{
HAL_UART_Transmit(&UartHandle, (uint8_t *)&a, 1, 20);
}
HAL_UART_Transmit(&UartHandle, (uint8_t *)(val + i), 1, 20);
}
return 1;
}
struct rt_device uart_dev;
static int uart_register(void)
{
uart_dev.init = uart_init;
uart_dev.open = uart_open;
uart_dev.read = uart_read;
uart_dev.write = uart_write;
rt_device_register(&uart_dev, "uart1", 0);
return 0;
}
INIT_BOARD_EXPORT(uart_register);
2.6 常見問題
Q1:輸出打印正常,卻沒有打印 msh >
,也不能輸入。
A:這是由於FinSH 沒打開,所以只有打印功能,需要在rtconfig.h中打開 #define RTE_USING_FINSH
宏定義。