摘要本文主要介紹RT-Thread剛剛入手,以及RTT的上電啟動過程分析。
系統移植
關於RTT的系統移植,我覺得還是很簡單的,之前我做過針對自己的
開發板
做了一款BSP。參考RTT官網提供的BSP製作教程,一次性成功,唯一的是要花點時間。
而本次我準備使用使用的正點原子的戰艦V3的板子來學習RTT,因為這個板子上的板載資源要豐富一些。當我準備按照之前的經驗,做一款戰艦V3的BSP的時候才發現,原來已經有成品了。那就本著拿來主義,先用用吧。
最終我在從官網下載的壓縮包裡面找到正點原子戰艦V3的BSP。
個人推薦,可以把用不到的BSP刪了,畢竟這個源碼壓縮包解壓之後有700多M,還是蠻佔用空間的。然後在rt-thread\bsp\stm32\libraries目錄下,把其他用不到的庫文件也刪掉,只保留了F1的庫文件夾即可。
最後強調一下:學習RTT,板子不是關鍵,不論是正點原子、野火、安富萊,還是F1、F4、F7都是可以的,不要糾結板子,最主要的了解RTT的使用方法。
程序下載進去之後,我使用的Xshell5軟體,將板載的USB串口與PC連接:
Main.C文件的代碼十分簡單,就一個閃燈程序。
RTT的系統初始化,以及空閒任務等啟動系統啟動,都不見蹤影,這不得不讓我想一探究竟。
代碼框架分析
首先,看了下啟動文件startup_stm32f103xe.s,這個文件還是使用的是hal庫提供的標準文件,RTT並沒在這個上面做文章。
啟動代碼還是沒有變,流程還是一樣:
166行:將SystemInit()函數的入口地址放到R0寄存器
167行:跳轉到R0地址的,開始執行SystemInit()函數,這個函數還是ST官方提供的函數。
168行,將
main函數的入口地址給到R0169行:跳轉到
main函數,開始執行。
需要注意的是__main()是編譯系統提供的一個函數,負責完成庫函數的初始化和初始化應用程式執行環境,最後自動跳轉到main()。
所以說,前者是庫函數,後者就是我們自己編寫的main()主函數;
但是RTT的啟動函數呢?
經過一番查找,RTT的啟動用到了MDK提供的$Super$$和$Sub$$「補丁」函數的功能,這個是在__CC_ARM編譯器環境中,編譯器提供的一個功能。下圖為MDK提供官方原文:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0377g/pge1362065967698.html
其意思是,以下文中的示例代碼為例:
int$Sub$$main(void)
{
補丁函數();
$Super$$main();
return0;
}
在進入main()之前,會先執行$Sub$$main(void)函數中的內容,
是以,補丁函數();得以優先運行。
最後當執行到$Super$$main()函數時,程序才開始跳轉到main()函數,去執行main()函數的內容。
而RTT也就是用的這種「補丁」的方式,也下面我將整個RTT的初始化流程做成了一個簡單的流程示意圖,方便大家更明白的了解RTT的啟動流程。
程序從啟動文件的__main函數執行完畢之後,在準備進入main()函數之前,會優先執行$Sub$$main()函數,該函數包含了RTT的初始化過程,如下圖所示。
在rtthread_startup()函數中完成對RTT的所有初始化。
另外需要額外的說明,在rt_application_init()函數中,定義了一個「main」線程。
這個main線程的入口函數是main_thread_entry(),就是我們的正主,main函數的跳轉入口,$Super$$main()。
程序執行到rt_application_init()時,並沒有真正意義上的執行到$Super$$main(),而是將含有$Super$$main()的這個任務線程放置到就緒狀態。
最後,當程序執行到rt_system_scheduler_start()函數後,此刻任務調度器已經啟動了,這個含有$Super$$main()的線程會立即得到執行,從而轉入到我們的main函數。
所以,這就是為什麼,我們看到這個main()函數這麼簡潔的原因。
不用我多說,相信大家也知道這樣設計的好處:盡最大的可能,保證hal庫,與RTT的文件保持獨立,又能很好的相互銜接。降低RTT的移植難度。
Shell串口初始化
這個串口也就是我使用Xshell5連接的調試串口,為什麼我要單獨的把它拿出來說?因為,在我們設計新的硬體的時候,由於布線方便的原因,有時候會使用其他的串口作為調試串口。
所以,針對這個串口的初始化,我重點了看了下RTT的調試串口初始化,方便以後項目中需要調整的時候,進行調整。
我也是找了很久才看明白RTT是如何初始化調試串口的。
在說RTT的調試串口初始化之前,我建議先看看單純使用HAL庫的時候,串口是如何進行初始化的,我做了截圖,如下圖所示:
在HAL庫中,先對UartHandle結構體進行賦值,在HAL_UART_Init(&UartHandle)函數中完成串口引腳的初始化,和串口外設的配置(波特率、數據位等等)。
好了,說到了這裡,在回頭RTT的代碼吧!根據上文,RTT是在rt_hw_board_init()函數中的rt_hw_usart_init()函數中進行串口初始化。
在rt_hw_usart_init()函數中,需要重點注意stm32_uart_ops結構體
這個stm32_configure就是我們調試串口的初始化,至於這個.configure=stm32_configure的寫法,意思是,將stm32_configure賦值給configure,這裡我不多做解釋。
繼續查看stm32_configure函數,就可以看如下圖所示:
這裡已經和單獨使用HAL庫,初始串口的流程一樣了,在HAL_UART_Init(&uart->handle)函數中,將會對調試串口的引腳、以及外設參數進行配置。
結語從RTT的代碼中,能看到很對linux嵌入式開發的影子,同時,其又能和ST的HAL進行兼容與配套,如果有興趣的朋友可以研究下,每當看了一段時間RTT的代碼,不禁發出感嘆「原來如此!」。總之,學習RTT一定會讓你有所收穫!