我們知道,在寫裸機(jī)程序時,當(dāng)我們完成硬件初始化后,就需要在主函數(shù)中進(jìn)行調(diào)用。當(dāng)我們使用RT-Thread后,完全不需要這樣做了,我們可以將硬件等自動初始化。RT-Thread自動初始化機(jī)制是指初始化函數(shù)不需要被顯式調(diào)用,只需要在函數(shù)定義處通過宏定義的方式進(jìn)行申明,就會在系統(tǒng)啟動過程中被執(zhí)行,非常的方便。
1 普通初始化
前面也講了,我們在寫單片機(jī)的程序時,需要對硬件進(jìn)行初始化操作,我們這里還是以LED為例。需要對LED的GPIO進(jìn)行初始化后才能進(jìn)一步操作。
int main(void)
{
rt_err_t rst;
/* LED初始化 */
LED_GPIO_Config();
rst = rt_thread_init(&led_thread,
"ledshine",
led_thread_entry,
RT_NULL,
&led_thread_stack[0],
sizeof(led_thread_stack),
RT_THREAD_PRIORITY_MAX-2,
20);
if(rst == RT_EOK)
{
rt_thread_startup(&led_thread);
}
}
上述代碼很簡單,就是在main()函數(shù)中對LED的GPIO進(jìn)行初始化,也就是調(diào)用了LED_GPIO_Config() 函數(shù),而針對RT-Thread系統(tǒng),我們在需要初始化的地方進(jìn)行初始化即可,無需在main()函數(shù)或者board.c中初始化了。
2 RT-Thread初始化流程
要想搞清楚RT-Thread的自動初始化流程,那么必須的了解RT-Thread初始化流程,這一部分前文也就有講,官方也有,我們還是再來復(fù)習(xí)下。
RT-Thread支持多種平臺和多種編譯器。RT-Thread啟動代碼統(tǒng)一入口為 rtthread_startup(),芯片啟動文件在完成必要工作(如初始化時鐘、配置中斷向量表、初始化堆棧等)后,跳轉(zhuǎn)至 RT-Thread的啟動入口中,最后進(jìn)入用戶入口 main()。RT-Thread的啟動流程如下:
1.全局關(guān)中斷,初始化與系統(tǒng)相關(guān)的硬件。
2.打印系統(tǒng)版本信息,初始化系統(tǒng)內(nèi)核對象(如定時器、調(diào)度器)。
3.初始化用戶 main線程(同時會初始化線程棧),在 main線程中對各類模塊依次進(jìn)行初始化。
4.初始化軟件定時器線程、初始化空閑線程。
5.啟動調(diào)度器,系統(tǒng)切換到第一個線程開始運(yùn)行(如 main線程),并打開全局中斷。
在圖中標(biāo)出顏色的部分需要用戶特別注意(黃色表示 libcpu移植相關(guān)的內(nèi)容,綠色部分表示板級移植相關(guān)的內(nèi)容)。
3 RT-Thread自動初始化原理
既然是初始化,我們這里中的重點(diǎn)關(guān)注初始化過程,重新整理RT-Thread初始化如下圖所示:
在系統(tǒng)啟動流程圖中,有兩個函數(shù):rt_components_board_init() 與 rt_components_init(),其后的帶底色方框內(nèi)部的函數(shù)表示被自動初始化的函數(shù),其中:
“board init functions”為所有通過 INIT_BOARD_EXPORT(fn)申明的初始化函數(shù)。
“pre-initialization functions”為所有通過 INIT_PREV_EXPORT(fn)申明的初始化函數(shù)。
“device init functions”為所有通過 INIT_DEVICE_EXPORT(fn)申明的初始化函數(shù)。
“components init functions”為所有通過 INIT_COMPONENT_EXPORT(fn)申明的初始化函數(shù)。
“enviroment init functions”為所有通過 INIT_ENV_EXPORT(fn)申明的初始化函數(shù)。
“application init functions”為所有通過 INIT_APP_EXPORT(fn)申明的初始化函數(shù)。
rt_components_board_init() 函數(shù)執(zhí)行的比較早,主要初始化相關(guān)硬件環(huán)境,執(zhí)行這個函數(shù)時將會遍歷通過 INIT_BOARD_EXPORT(fn)申明的初始化函數(shù)表,并調(diào)用各個函數(shù)。
rt_components_init() 函數(shù)會在操作系統(tǒng)運(yùn)行起來之后創(chuàng)建的 main線程里被調(diào)用執(zhí)行,這個時候硬件環(huán)境和操作系統(tǒng)已經(jīng)初始化完成,可以執(zhí)行應(yīng)用相關(guān)代碼。rt_components_init() 函數(shù)會遍歷通過剩下的其他幾個宏申明的初始化函數(shù)表。
RT-Thread的自動初始化機(jī)制使用了自定義 RTI符號段,將需要在啟動時進(jìn)行初始化的函數(shù)指針放到了該段中,形成一張初始化函數(shù)表,在系統(tǒng)啟動過程中會遍歷該表,并調(diào)用表中的函數(shù),達(dá)到自動初始化的目的。
自動初始化功能的宏接口定義詳細(xì)描述如下表所示:
初始化順序 | 宏接口 | 描述 |
1 | INIT_BOARD_EXPORT(fn) | 非常早期的初始化,此時調(diào)度器還未啟動 |
2 | INIT_PREV_EXPORT(fn) | 主要是用于純軟件的初始化、沒有太多依賴的函數(shù) |
3 | INIT_DEVICE_EXPORT(fn) | 外設(shè)驅(qū)動初始化相關(guān),比如網(wǎng)卡設(shè)備 |
4 | INIT_COMPONENT_EXPORT(fn) | 組件初始化,比如文件系統(tǒng)或者 LWIP |
5 | INIT_ENV_EXPORT(fn) | 系統(tǒng)環(huán)境初始化,比如掛載文件系統(tǒng) |
6 | INIT_APP_EXPORT(fn) | 應(yīng)用初始化,比如 GUI應(yīng)用 |
初始化函數(shù)主動通過這些宏接口進(jìn)行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),鏈接器會自動收集所有被申明的初始化函數(shù),放到 RTI符號段中,該符號段位于內(nèi)存分布的 RO段中,該 RTI符號段中的所有函數(shù)在系統(tǒng)初始化時會被自動調(diào)用。
好了,介紹性文字我就不貼了,下面直接看源代碼進(jìn)一步分析。前文說過,在RT-Thread的啟動流程中,調(diào)用了兩個函數(shù) rt_components_board_init()與 rt_components_init()就完成了上述6個部分的初始化工作。從初始化啟動流程圖中我們可以看出: rt_components_board_init()完成了第 1部分工作, rt_components_init()完成了第2-6部分的工作。那么接下來我們先看這兩個函數(shù)源代碼。
/**
* RT-Thread Components Initialization for board
*/
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;
for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++)
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d done\n", result);
}
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
#endif
}
/**
* RT-Thread Components Initialization
*/
void rt_components_init(void)
{
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;
rt_kprintf("do components initialization.\n");
for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++)
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d done\n", result);
}
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
{
(*fn_ptr)();
}
#endif
}
【注】rt_components_board_init() 與 rt_components_init()函數(shù)在components.c文件中實(shí)現(xiàn)。
可以看到兩個函數(shù)在非調(diào)試模式下都是通過for循環(huán)會遍歷位于__rt_init_rti_board_start 到 __rt_init_rti_board_end以及__rt_init_rti_board_end 到 __rt_init_rti_end之間保存的函數(shù)指針,然后依次執(zhí)行這些函數(shù)。那么接下來我們看看上述函數(shù)指針。我們先編譯下,找到.map文件,我們在在文件中搜索上述的函數(shù)指針。
又問會問,找到又能怎樣呢?怎么和上述的6個宏定義對應(yīng)起來呢?不急哈,慢慢來,我們先找到上述6個宏定義又是如何實(shí)現(xiàn)的,找到啦,在rtdef.h中。
宏定義又是通過INIT_EXPORT宏函數(shù)定義的,那么INIT_EXPORT又是如何實(shí)現(xiàn)的呢?
#ifdef _MSC_VER /* we do not support MS VC++ compiler */
#define INIT_EXPORT(fn, level)
#else
#if RT_DEBUG_INIT
struct rt_init_desc
{
const char* fn_name;
const init_fn_t fn;
};
#define INIT_EXPORT(fn, level) \
const char __rti_##fn##_name[] = #fn; \
RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \
{ __rti_##fn##_name, fn};
#else
#define INIT_EXPORT(fn, level) \
RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
#endif
#endif
【注】上述代碼在rtdef.h中實(shí)現(xiàn)。
上述代碼最關(guān)鍵的代碼是:
RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
這又是啥,看不懂啊,不急哈,我們先看看init_fn_t是啥?其定義如下:
#ifdef RT_USING_COMPONENTS_INIT
typedef int (*init_fn_t)(void);
這就是一個函數(shù)指針類型,其實(shí)就是個指針。那么SECTION又是啥呢?其定義如下:
__attribute__((used))表示這個標(biāo)記這個東西是使用過的,避免出現(xiàn)如: warning: #177-D: variable "a" was declared but never referenced的警告。
在GCC的宏中,##后面跟變量名。__attribute__((section(x)))則表示fn被放置于指定段中。是不是還是一頭霧水,不急,我們再看看一些代碼或許你就明白了。
/*
* Components Initialization will initialize some driver and components as following
* order:
* rti_start --> 0
* BOARD_EXPORT --> 1
* rti_board_end --> 1.end
*
* DEVICE_EXPORT --> 2
* COMPONENT_EXPORT --> 3
* FS_EXPORT --> 4
* ENV_EXPORT --> 5
* APP_EXPORT --> 6
*
* rti_end --> 6.end
*
* These automatically initialization, the driver or component initial function must
* be defined with:
* INIT_BOARD_EXPORT(fn);
* INIT_DEVICE_EXPORT(fn);
* ...
* INIT_APP_EXPORT(fn);
* etc.
*/
static int rti_start(void)
{
return 0;
}
INIT_EXPORT(rti_start, "0");
static int rti_board_start(void)
{
return 0;
}
INIT_EXPORT(rti_board_start, "0.end");
static int rti_board_end(void)
{
return 0;
}
INIT_EXPORT(rti_board_end, "1.end");
static int rti_end(void)
{
return 0;
}
INIT_EXPORT(rti_end, "6.end");
【注】以上代碼再在components.c文件中實(shí)現(xiàn)。
好了,我們結(jié)合上述代碼和6個宏定義,以及.map文件。
上圖中框選的第一行是一個Section,叫做.rti_fn.0,這個內(nèi)容實(shí)際是我們通過INIT_EXPORT(rti_start, "0");完成的,我們把函數(shù)rti_start改名為__rt_init_rti_start,存入.rti_fn.0這個地方。同樣的,INIT_EXPORT(rti_board_start, "0.end");、INIT_EXPORT(rti_board_end, "1.end");、INIT_EXPORT(rti_end, "6.end");也是這里插入的。下圖就是我們插入的位置。
這下是不是很明白,當(dāng)然啦,多看幾遍,還是很好理解的。
4 RT-Thread自動初始化實(shí)例
前文理論講了很多,源代碼也分析,估計初學(xué)者還很蒙,沒關(guān)系,慢慢理解,我們先看個例子,先用起來,后面再慢慢理解。還會是LED的例子,前文已近給出了普通初始化方式,下面我們看看如何使用自動化初始化。
/**
* @brief 初始化LED的GPIO
* @param None
* @retval None
*/
int LED_GPIO_Config(void)
{
/*定義一個GPIO_InitTypeDef類型的結(jié)構(gòu)體*/
GPIO_InitTypeDef GPIO_InitStructure;
/*開啟LED的外設(shè)時鐘*/
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG, ENABLE);
/*設(shè)置IO口*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //設(shè)置引腳模式為通用推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //設(shè)置引腳速率為50MHz
/*調(diào)用庫函數(shù),初始化GPIOB0*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //選擇要控制的GPIOB引腳
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;/*選擇要控制的引腳*/
GPIO_Init(GPIOG, &GPIO_InitStructure);
/* 開啟所有l(wèi)ed燈 */
GPIO_SetBits(GPIOB, GPIO_Pin_0);
GPIO_SetBits(GPIOG, GPIO_Pin_6|GPIO_Pin_7);
return 0;
}
/* LED初始化 */
INIT_BOARD_EXPORT(LED_GPIO_Config);
我們注意最后一行代碼,這里使用INIT_BOARD_EXPORT宏定義進(jìn)行初始化,其初始化時間最早,值得注意的是,上述6個宏定義修飾的函數(shù)返回值都是int,最好將返回值改為int。
如果LED配置正確,其實(shí)驗(yàn)現(xiàn)象和普通初始化方式?jīng)]有任何區(qū)別,接下來我們再來看看.map文件有何變化,當(dāng)然我說的是自動初始化部分。
我們可以看到多了LED初始化的信息,也說明我們前文分析的是合理的。
5 總結(jié)
好了,關(guān)于自動出初始化就講完了,我估計初學(xué)者很蒙,沒關(guān)系,我們再來總結(jié)下,還是先看看RT-Thread初始化過程,如下圖所示:
rt_components_board_init() 與 rt_components_init()負(fù)責(zé)初始化,其中帶底色方框內(nèi)部的函數(shù)表示被自動初始化的函數(shù)。這部分應(yīng)該沒是啥問題。關(guān)鍵是如何將初始化函數(shù)和6個自動初始化宏定義聯(lián)系起來這就有點(diǎn)燒腦子,我整理了一張關(guān)系圖,如下圖所示:
結(jié)合前文的講解,再結(jié)合上述兩張圖,我不想在贅述了,自行理解去吧。
參考地址:
https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic
-
初始化
+關(guān)注
關(guān)注
0文章
50瀏覽量
11850 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1285瀏覽量
40078
發(fā)布評論請先 登錄
相關(guān)推薦
評論