--作者:燕十三(flyingcys)
-- blog:http://blog.csdn.net/flyingcys
--QQ:294102238
何爲RT-Thread Nano?大家知道,Keil5以後採用pack形式管理芯片及各種相關組件的。RT-Thread Nano就是通過Keil pack方式發佈,在保持原有RT-Thread基本功能的情況下,實現了極小的Flash和Ram佔用。默認配置下,Flash可小至2.5K,Ram可以小至1K。
目前pack包含有kernel、shell(msh)、device drivers三部分功能,這3個功能可按實際使用情況按需加載。
本次使用的主芯片爲GD32F150C8T6,資源爲Flash:64K,Ram:8K。
一、RT-Thread Nano Pack下載安裝
1. 在Keil5主界面上點擊“Pack Install”按鈕,即可進入Pack Install界面
圖1:Keil5主界面
2. 在Pack Install界面下,RT-Thread Pack在右邊欄中。如未下載,可點擊“Install”下載;如已安裝,版本有更新,將提示“Update”可更新。
圖2:RT-Thread Pack下載
3. 如在圖2界面“Packs”欄中未發現“RT-Thread”,可先在菜單“Packs”下點擊“Check for Updates”。Update完成後,將可看到RT-Thread Pack。
圖3:Pack Update
4. Pack下載完成後,Keil將自動彈出Pack安裝界面,按步驟依次完成安裝。
二、裸機最小系統工程建立
1. 本次工程使用的是芯片是GD32F150C8T6,64KFlash、8KRam。Keil5下開發須先在官網下載Keil Pack (GigaDevice.GD32F1x0_DFP.pack),並正確安裝。
圖4:GD32F150 Device選擇
2. 先按照裸機Keil工程流程搭建工程,爲 測試Flash及Ram大小,最小工程只包含必須的Libraries文件,main函數也未作任何多餘處理。
圖5:GD32F150C8T6最小工程
3. 編譯完成後,默認配置Flash:1112字節、Ram:2144字節
4. 修改默認啓動文件startup_gd32f1x0.s定義堆和棧大小:默認堆爲0x400,棧爲0x400。後續我們將採用RT-Thread管理內存堆,所有堆設置爲0;棧可按照main函數應用需求調整爲0x100或以上。
圖6:啓動文件棧和堆修改
啓動文件修改後,Ram大小爲352字節
圖7:修改堆和棧後Flash和棧佔用大小
三、kernel加載與應用
1. 加載RT-Thread Kernel:
在主界面點擊“Manage Run-Time Environment”按鈕即可進入加載頁。
圖8:Manage Run-Time Environment
在“RTOS”一欄中選中“RT-Thread”,並在列表中選中“kernel”,當前版本爲2.1.2。
圖9:RT-Thread kernel選擇
2. 確定後,keil界面上會加載RT-Thread的kernel文件,更根據當前選擇芯片類型加入已移植完成的M3芯片內核代碼、配置文件等。
圖10:RT-Thread kernel文件
其中:
l 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
l Cortex-m3芯片內核移植代碼:
cpuport.c
context_rvds.s
l 應用代碼及配置文件:
board.c
rtconfig.h
3. 此時再次編譯工程,編譯器會提示有函數被重複定義了。需按照如下方式做一些修改:
a) 修改gd32f10x_it.c文件,刪除如下函數:
void HardFault_Handler(void)
void PendSV_Handler(void)
void SysTick_Handler(void)
b) 按照board.c上的說明,依次完成如下操作:
圖11:board.c修改流程說明
修改24行:#include “gd32f1x0.h”
修改48行:在rt_hw_board_init()函數內開啓
SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
修改66行:void SysTick_Handler(void)
4. 修改main.c:
加入#include <rtthread.h>
在while循環中加入rt_thread_dealy(100);
5. 再次編譯順利通過,下載至芯片運行可看到main函數中每1s可中斷一次。RT-Thread任務調度器已經正常運行。
圖12:RT-Thread正常運行
通過查看.map文件可獲取當前各文件資源佔用情況。在未開啓任何優化的情況下,可以看到RT-Thread內核各文件資源佔用情況。
圖13:資源佔用表
6. 可在main函數內添加RT-Thread支持的任務、定時器、信號量等功能。Nano默認rtconfig.h配置只支持靜態任務、信號量創建。在靜態模式下,不能使用rt_thread_create/rt_thread_delete/rt_sem_create/rt_sem_delete/rt_malloc/rt_free與動態創建、刪除有關的接口。如需動態創建,需開啓RT_USING_HEAP項,詳見本篇第五部分:《RT-Thread配置》
四、RT-Thrad啓動流程分析
這次創建的keil工程雖然應用了RT-Thread嵌入式操作系統,但開發流程無不帶os開發幾乎沒有差別。都是將main作爲入口,完成硬件初始化、應用代碼添加,而且可以直接應用RT-Thread的各種功能完成產品開發。但是我們沒有添加RT-Thread相關初始化、啓動等代碼到我們的工程裏面,但實際情況是調度器已經正常運行了,這是怎麼實現的呢?
1. RT-Thread入口
我們可以在components.c文件的140行看到#ifdef RT_USING_USER_MAIN宏定義判斷,這個宏是定義在rtconfig.h文件內的,而且處於開啓狀態。同時我們可以在146行看到#if defined (__CC_ARM)的宏定義判斷,__CC_ARM就是指keil的交叉編譯器名稱。
我們可以在這裏看到定義了2個函數:$$Sub$$main()和$$Super$$main()函數;這裏通過$$Sub$$main()函數在程序就如主程序之前插入一個例程,實現在不改變源代碼的情況下擴展函數功能。鏈接器通過調用$$Sub$$Main()函數取代main(),然後通過$$Super$$main再次回到main()
#if defined (__CC_ARM)
extern int $Super$$main(void);
/* re-define main function */
int $Sub$$main(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
在$$Sub$$main函數內調用了rt_hw_interrutp_disable()和rtthread_startup()兩個函數。熟悉RT-Thread開發流程的,一看就知道這是標準的RT-Thread的啓動入口。
其中:
- rt_hw_interrupt_disable():關中斷操作,
- rtthread_startup():完成systick配置、timer初始化/啓動、idle任務創建、應用線程初始化、調度器啓動等工作。
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initalization
* NOTE: please initialize heap inside board initialization.
*/
rt_hw_board_init();
/* show RT-Thread version */
rt_show_version();
/* timer system initialization */
rt_system_timer_init();
/* scheduler system initialization */
rt_system_scheduler_init();
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
- rt_hw_board_init():該函數定義在board.c文件內,需要修改systick配置
- rt_system_timer_init()/rt_system_timer_thread_init():timer初始化/啓動
- rt_thread_idle_init():idle任務創建
- rt_application_init():應用線程初始化
- rt_system_scheduler_start():調度器啓動
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initalization
* NOTE: please initialize heap inside board initialization.
*/
rt_hw_board_init();
/* show RT-Thread version */
rt_show_version();
/* timer system initialization */
rt_system_timer_init();
/* scheduler system initialization */
rt_system_scheduler_init();
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
2. 應用線程入口rt_application_init()
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3, 20);
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main_thread;
result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack), RT_THREAD_PRIORITY_MAX / 3, 20);
RT_ASSERT(result == RT_EOK);
#endif
rt_thread_startup(tid);
}
在這裏,我們可以看到應用線程創建了一個名爲main_thread_entry的任務,並且已經啓動了該任務。我們再次來看一下man_thread_entry任務。
/* the system main thread */
void main_thread_entry(void *parameter)
{
extern int main(void);
extern int $Super$$main(void);
/* RT-Thread components initialization */
rt_components_init();
/* invoke system main function */
#if defined (__CC_ARM)
$Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
main();
#endif
}
man_thread_entry任務完成了2個工作:調用rt_components_init()、進入應用代碼真正的main函數。
在這裏我們看到了$$Super$$main()的調用,在前面我們講了調用該函數可用來回到main()的。
圖14:RT-Thread初始化及啓動流程
從以上分析可以,正是由於在rtconfig.h內開啓了RT_USING_USER_MAIN選項,編譯器在main之前插入了$$Sub$$main(),完成了RT-Thread初始化及調度器啓動工作。並且通過創建main_thread_entry任務,並通過$$Super$$main()回到main()函數。這樣看來main()函數其實只是RT-Thread的一個任務,該任務的優先級爲RT_THREAD_PRIORITY_MAX / 3,任務棧爲RT_MAIN_THREAD_STACK_SIZE。
圖15:RT_USING_USER_MAIN選項
五、RT-Thread配置(rtconfig.h)
RT-Thread是一個高度可配置的嵌入式實時操作系統,配置通過rtconfig.h文件實現。Nano就是在rtconfig.h配置下實現了2.5KFlash,1KRam的內核應用,但是由於Nano未開啓RT_USING_HEAP選項,故只支持靜態方式創建任務及信號量。下面分步開啓rtconfig.h配置常用選項。
1. RT_USING_HEAP:開啓heap
根據芯片型號在board.c第37行,修改SARM_SIZE大小,默認爲8,GD32F150C8T6正好也爲8K。
圖16:SRAM_SIZE配置
開啓RT_USING_HEAP選項後,在board.c的rt_hw_board_init()內將調用rt_system_heap_init()
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init((void*)HEAP_BEGIN, (void*)SRAM_END);
#endif
其中:
SRAM_END:根據宏定義爲0x20000000 + SRAM_SIZE * 1024
HEAP_BEGIN:
圖17:HEAP_BEGIN定義
其中Image$$RW_IRAM1$$ZI$$Limit是鏈接器導出符號,表示ZI段的結束地址。
配置完成後,就可通過動態創建任務、信號量等方式開發軟件了。
2. RT_USING_TIMER_SOFT:開啓軟件定時器
Nano默認配置未開啓軟件定時器功能。開啓軟件定時器功能後,可創建多個軟件定時器,定時器精度爲Systick觸發精度。
圖18:軟件定時器開啓