前言
論壇里有人提出了一個疑問,說 STM32 系列 bsp 在初始化系統(tǒng)時鐘的過程中使用到了 tick ,而 tick 需要初始化并使能 SysTick 中斷。但是呢,SysTick 中斷中有 rtt 的 tick 以及硬定時器檢測,以及可能存在的系統(tǒng)任務(wù)調(diào)度。初始化時鐘是極其早期必須完成的工作,這個時候別說系統(tǒng)了,其它外圍設(shè)備也沒有被初始化。由此產(chǎn)生一個問題,極其早期使用了比較后期的資源,同時,因為需要使用 SysTick 中斷而簡單粗暴地使用 `__set_PRIMASK` 使能了總中斷?。?!這是萬萬不可取的。
有人發(fā)現(xiàn)了這一矛盾之處,在[論壇](https://club.rt-thread.org/ask/article/2857.html) 里和 gitee 的 [issue]( https://gitee.com/rtthread/rt-thread/pulls/246) 里也是各執(zhí)己見,熱鬧非凡。
下面我講講我的處理方案。
理論前提
1. rtt 標(biāo)榜的是實時操作系統(tǒng),對于系統(tǒng)中頻繁執(zhí)行的代碼需要嚴格審查,保證沒有一行多余代碼。(非實時操作系統(tǒng)也不希望自己多數(shù)時候空跑一條無用的代碼)
2. 中斷是個很棘手的東西,在不確定開中斷會引起什么后果的時候,堅決不能開中斷。
3. rtt 系統(tǒng)啟動是有它自己的流程的。分階段的,任何資源的初始化都有個階段以及先后順序。
4. 板級初始化,如非必要,如果可以延遲盡量延遲,先保證 rtt 內(nèi)核調(diào)度運行起來。
基于以上幾點,我的修改方案如下。
預(yù)初始化 SysTick
上電后,MCU 默認使用的內(nèi)部晶振,時鐘也是默認值,這個時候使用默認值初始化 SysTick,但是**不開啟中斷**!?。?br />
在 STM32 的 bsp 里,因為有如下調(diào)用關(guān)系 `rt_hw_board_init` -> `HAL_Init` -> `HAL_InitTick`。
`HAL_InitTick` 在 hal 里是弱實現(xiàn),'drv_common.c' 重新實現(xiàn)并且是個空函數(shù),這里可以借用一下。
/* re-implement tick interface for STM32 HAL */
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);
/* Return function status */
return HAL_OK;
}
初始化系統(tǒng)時鐘
第二步配置時鐘頻率,切換內(nèi)外時鐘或者倍頻等等操作。中間如果有延時等待的需求,可以使用 SysTick 實現(xiàn) udelay 短延時,進而通過 while 循環(huán)累積達到等待超時的效果。
首先,在 'drv_common.c' 文件里添加 `HAL_uDelay` 微秒延時實現(xiàn),其實就是調(diào)用 `rt_hw_us_delay` 。
然后,在 'stm32xxx_hal_conf.h' 頭文件末尾添加一個通用宏定義:
#define HAL_WAITFOR_CONDITION(condition, ms) do { \
uint32_t cnt = 0; \
while((condition)) { \
if (cnt > (ms) * 1000 / 10) \
{ \
return HAL_TIMEOUT; \
} \
HAL_uDelay(10); \
cnt++; \
}\
} while(0)
其中,condition 是等待條件,ms 是等待超時時間。
因為 udelay 了 10us ,所以整體的精度就是 10us ,時間損失可以控制在 10us 內(nèi)。
涉及到的函數(shù)有 `HAL_RCC_OscConfig` `HAL_PWREx_EnableOverDrive` `HAL_RCC_ClockConfig` `HAL_RCCEx_PeriphCLKConfig` 等。
其中一處修改前后
/* Get Start Tick */
tickstart = HAL_GetTick();
/* Wait till HSE is ready */
while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
{
if((HAL_GetTick() - tickstart ) > HSE_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
/* Wait till HSE is ready */
HAL_WAITFOR_CONDITION((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET), HSE_TIMEOUT_VALUE);
其它地方如法炮制,只是把等待條件和等待時間添加到宏函數(shù)里。
rt_hw_board_init 調(diào)整
調(diào)整后的代碼如下:
1. 去掉開啟關(guān)閉全局中斷操作;
2. 提前控制臺串口初始化;
3. 堆棧的初始化并不需要那么著急,但是必須在 rt_components_board_init 之前;
4. gpio 初始化也在 rt_components_board_init 之前。
其中 2-4 的調(diào)整是調(diào)試需求控制臺。
最終結(jié)果如下。
RT_WEAK void rt_hw_board_init()
{
#ifdef SCB_EnableICache
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
#endif
#ifdef SCB_EnableDCache
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
#endif
/* HAL_Init() function is called at the beginning of the program */
HAL_Init();
/* System clock initialization */
SystemClock_Config();
rt_hw_systick_init();
/* USART driver initialization is open by default */
#ifdef RT_USING_SERIAL
rt_hw_usart_init();
#endif
/* Set the shell console output device */
#ifdef RT_USING_CONSOLE
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
/* Pin driver initialization is open by default */
#ifdef RT_USING_PIN
rt_hw_pin_init();
#endif
/* Heap initialization */
#if defined(RT_USING_HEAP)
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
#endif
/* Board underlying hardware initialization */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
}
修改 `rt_hw_systick_init`
去掉 `rt_hw_systick_init` 函數(shù)中使能 SysTick 中斷的操作。添加使能 SysTick 中斷函數(shù)。
void rt_hw_systick_irq_enable(void)
{
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
修改 `rtthread_startup`
添加使能 SysTick 中斷處理。
rt_hw_systick_irq_enable();
/* start scheduler */
rt_system_scheduler_start();
SysTick_Handler 異常響應(yīng)
目前,`SysTick_Handler` 函數(shù)就下面這么清爽,不需要考慮并行增加 hal 的tick值。
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
運行測試
修改后系統(tǒng)啟動正常,運行正常。
RCC 初始化過程如果失敗,等待超時也能正常超時返回。
經(jīng)過初步驗證,不使用 SysTick 前提下初始化配置硬件的可行性還是有的。
其它可行性
這次嘗試僅限于最小范圍,僅僅考慮了系統(tǒng)時鐘配置過程,未考慮其它板級外設(shè)配置過程??紤]到 hal 是比較龐大的,每次 hal 升級都把所有的 hal 文件修改一遍工作量也是不小的。這樣也不方便。如果降低修改范圍,只修改 RCC 相關(guān)部分,其它外設(shè)配置仍然想正常使用 SysTick ,有一種方法就是繼續(xù)提前系統(tǒng)調(diào)度的啟動時間點。
鑒于此,系統(tǒng)啟動流程大致如下
1. 配置系統(tǒng)時鐘,同時配置 SysTick(同前);
2. 初始化 rtt 系統(tǒng)調(diào)度器;
3. 初始化 idle 線程;
4. 啟動 SysTick 中斷;
5. 啟動 idle 線程并啟動 rtt 系統(tǒng)調(diào)度;
6. 由 idle 線程初始化調(diào)試串口;
7. 由 idle 線程初始化內(nèi)存堆;
8. 由 idle 線程調(diào)用執(zhí)行 `rt_components_board_init` 初始化板級外設(shè)配置;
9. 由 idle 線程創(chuàng)建 main 和 soft timer 線程。
10. main 線程進行組件初始化配置,以及創(chuàng)建其它應(yīng)用線程。
由此,可以做到以下幾點:
1. 保障所有操作都在是線程中進行的。省去很多 `if (rt_thread_self() != RT_NULL)` 的操作;
2. 保障中斷可以及早放心打開;
3. 明確啟動流程,確定啟動流程每一階段的工作重點以及必須完成的任務(wù)。
缺點,因 idle 線程工作量變多,idle 線程??赡軙容^大。一個空閑線程占用過多的內(nèi)存也是浪費。另外,多核處理器的 idle 線程數(shù)量和核心數(shù)量一樣,由哪個 idle 線程完成上面的工作也需要做個抉擇。
> 本優(yōu)化系列所有提到的更改已經(jīng)提交到 gitee ,歡迎大家測試
https://gitee.com/thewon/rt_thread_repo
審核編輯:湯梓紅
-
STM32
+關(guān)注
關(guān)注
2270文章
10917瀏覽量
356788 -
Systick
+關(guān)注
關(guān)注
0文章
62瀏覽量
13124 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1302瀏覽量
40268
發(fā)布評論請先 登錄
相關(guān)推薦
評論