移植一個實時OS很難?那就手把手教你如何快速移植一個RT-Thread Nano吧!
最近在學(xué)習(xí)RT-Thread的使用,同時也相當(dāng)于在拿它評估做產(chǎn)品的軟件開發(fā)周期,最終學(xué)習(xí)的目的也就是希望能在未來的項目上用起來,STM32CubeMX其實已經(jīng)支持了RT-Thread Nano的配置了,但我還是希望手動移植一下,沒想到移植RT-Thread Nano如此簡單,必須分享出來,哈哈哈!
之前也分享過很多RT-Thread的學(xué)習(xí)筆記,鏈接如下:
RT-Thread PIN設(shè)備學(xué)習(xí)筆記
RT-Thread ADC設(shè)備學(xué)習(xí)筆記
RT-Thread I2C總線設(shè)備學(xué)習(xí)筆記
RT-Thread RTC設(shè)備學(xué)習(xí)筆記
RT-Thread UART設(shè)備驅(qū)動框架初體驗(中斷方式接收帶\r\n的數(shù)據(jù))
1、RT-Thread Nano版是啥?
RT-Thread Nano 是一個極簡版的硬實時內(nèi)核,它是由 C 語言開發(fā),采用面向?qū)ο蟮木幊趟季S,具有良好的代碼風(fēng)格,是一款可裁剪的、搶占式實時多任務(wù)的 RTOS。其內(nèi)存資源占用極小,功能包括任務(wù)處理、軟件定時器、信號量、郵箱和實時調(diào)度等相對完整的實時操作系統(tǒng)特性。適用于家電、消費電子、醫(yī)療設(shè)備、工控等領(lǐng)域大量使用的 32 位 ARM 入門級 MCU 的場合。
對比RT-Thread完整版:
以上內(nèi)容摘自RTT官網(wǎng)文檔中心。
詳情查看網(wǎng)址:
https://www.rt-thread.org/document/site/tutorial/nano/an0038-nano-introduction/
很明顯Nano版本已經(jīng)裁剪了很多東西,比如設(shè)備驅(qū)動及很多組件還有軟件包,Nano版本更適合給客戶做自由定制,客戶在開發(fā)上自由度更大一些,但如果是重新開發(fā)一個新產(chǎn)品,我還是建議使用完整版,這么多輪子都造好了,而且RT-Thread的社區(qū)如此活躍,大佬們隨時隨地的線上支持,不用難道不可惜么?
文檔中心也提供了如何移植RT-Thread Nano的筆記,但筆記歸筆記,畢竟要自己動手去做才能了解這個過程,方便以后分析調(diào)試。
2、移植RT-Thread Nano到小熊派
2.1 在官網(wǎng)上下載RT-Thread Nano
解壓后得到如下文件:
2.2 使用stm32CubeMX生成一個基礎(chǔ)工程
由于之前已經(jīng)寫了很多CubeMX配置的文章,所以這里不詳細(xì)寫了,只寫我配置了哪些東西,具體看下面這篇鏈接,寫得非常詳細(xì):
超輕量級網(wǎng)紅軟件定時器multi_timer(51+stm32雙平臺實戰(zhàn))
2.2.1 時鐘配置
2.2.2 調(diào)試接口配置
2.2.3 調(diào)試串口配置
2.2.4 配置調(diào)試LED燈
2.2.5 去掉這三個處理函數(shù),以防止重復(fù)定義報錯
因為RT-Thread已經(jīng)有相關(guān)定義和基于RT-Thread內(nèi)核特性進(jìn)行實現(xiàn)了,這里如果勾選相當(dāng)于重復(fù)定義,所以不需要,這點在官網(wǎng)的開發(fā)文檔里也有介紹,可以去瞧一瞧。
2.2.6 工程生成配置
這里選擇不生成main函數(shù),后面我們自己寫,然后點Generate Code生成基礎(chǔ)裸機工程代碼。
2.3、移植RT-Thread Nano到基礎(chǔ)工程
2.3.1 往工程中添加RT-Thread Nano代碼
2.3.2 對RT-Thread Nano進(jìn)行裁剪以及工程配置
(1)將BSP目錄下除board.c、rtconfig.h以外的所有文件刪除
(2)刪除無關(guān)內(nèi)核適配
由于小熊派的CPU是基于ARM Cortex M4架構(gòu),所以在arm下只保留cortex-m4文件夾,其余都刪除。
(3)刪除工程自帶的finsh組件
由于finsh需要RT-Thread的串口設(shè)備驅(qū)動支持,這里我們只需要一個最小的RT-Thread工程,所以這里把finsh組件去掉。
(4)在Keil MDK中添加裁剪過后的RT-Thread源代碼
(5)根據(jù)需求配置rtconfig.h文件
rtconfig.h
/* RT-Thread config file */
#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#include "RTE_Components.h"
#if defined(RTE_USING_FINSH)
#define RT_USING_FINSH
#endif //RTE_USING_FINSH
#endif //(__CC_ARM) || (__CLANG_ARM)
// <<< Use Configuration Wizard in Context Menu >>>
// <h>Basic Configuration
// <o>Maximal level of thread priority <8-256>
// <i>Default: 32
/*線程優(yōu)先級數(shù)*/
#define RT_THREAD_PRIORITY_MAX /**8**/ 32
// <o>OS tick per second
// <i>Default: 1000 (1ms)
/*定義時鐘節(jié)拍 1個tick為1ms 1000個tick每秒 1個tick為1ms*/
#define RT_TICK_PER_SECOND 1000
// <o>Alignment size for CPU architecture data access
// <i>Default: 4
/*設(shè)定字節(jié)對齊個數(shù)為4字節(jié)*/
#define RT_ALIGN_SIZE 4
// <o>the max length of object name<2-16>
// <i>Default: 8
/*內(nèi)核對象名稱長度,大于該長度會被內(nèi)核裁剪*/
#define RT_NAME_MAX 8
// <c1>Using RT-Thread components initialization
// <i>Using RT-Thread components initialization
/*如果定義該宏,則會開啟自動初始化機制,不定義的話就會關(guān)閉*/
#define RT_USING_COMPONENTS_INIT
// </c>
/*如果定義該宏,則設(shè)置應(yīng)用入口為main函數(shù)*/
#define RT_USING_USER_MAIN
// <o>the stack size of main thread<1-4086>
// <i>Default: 512
/*設(shè)置main線程的大小*/
#define RT_MAIN_THREAD_STACK_SIZE 256
// </h>
// <h>Debug Configuration
// <c1>enable kernel debug configuration
// <i>Default: enable kernel debug configuration
//#define RT_DEBUG
// </c>
// <o>enable components initialization debug configuration<0-1>
// <i>Default: 0
#define RT_DEBUG_INIT 0
// <c1>thread stack over flow detect
// <i> Diable Thread stack over flow detect
//#define RT_USING_OVERFLOW_CHECK
// </c>
// </h>
// <h>Hook Configuration
// <c1>using hook
// <i>using hook
//#define RT_USING_HOOK
// </c>
// <c1>using idle hook
// <i>using idle hook
//#define RT_USING_IDLE_HOOK
// </c>
// </h>
// <e>Software timers Configuration
// <i> Enables user timers
#define RT_USING_TIMER_SOFT 0
#if RT_USING_TIMER_SOFT == 0
#undef RT_USING_TIMER_SOFT
#endif
// <o>The priority level of timer thread <0-31>
// <i>Default: 4
#define RT_TIMER_THREAD_PRIO 4
// <o>The stack size of timer thread <0-8192>
// <i>Default: 512
#define RT_TIMER_THREAD_STACK_SIZE 512
// </e>
// <h>IPC(Inter-process communication) Configuration
// <c1>Using Semaphore
// <i>Using Semaphore
#define RT_USING_SEMAPHORE
// </c>
// <c1>Using Mutex
// <i>Using Mutex
//#define RT_USING_MUTEX
// </c>
// <c1>Using Event
// <i>Using Event
//#define RT_USING_EVENT
// </c>
// <c1>Using MailBox
// <i>Using MailBox
#define RT_USING_MAILBOX
// </c>
// <c1>Using Message Queue
// <i>Using Message Queue
//#define RT_USING_MESSAGEQUEUE
// </c>
// </h>
// <h>Memory Management Configuration
// <c1>Dynamic Heap Management
// <i>Dynamic Heap Management
//#define RT_USING_HEAP
// </c>
// <c1>using small memory
// <i>using small memory
#define RT_USING_SMALL_MEM
// </c>
// <c1>using tiny size of memory
// <i>using tiny size of memory
//#define RT_USING_TINY_SIZE
// </c>
// </h>
// <h>Console Configuration
// <c1>Using console
// <i>Using console
#define RT_USING_CONSOLE
// </c>
// <o>the buffer size of console <1-1024>
// <i>the buffer size of console
// <i>Default: 128 (128Byte)
#define RT_CONSOLEBUF_SIZE 128
// </h>
#if defined(RT_USING_FINSH)
#define FINSH_USING_MSH
#define FINSH_USING_MSH_ONLY
// <h>Finsh Configuration
// <o>the priority of finsh thread <1-7>
// <i>the priority of finsh thread
// <i>Default: 6
#define __FINSH_THREAD_PRIORITY 5
#define FINSH_THREAD_PRIORITY (RT_THREAD_PRIORITY_MAX / 8 * __FINSH_THREAD_PRIORITY + 1)
// <o>the stack of finsh thread <1-4096>
// <i>the stack of finsh thread
// <i>Default: 4096 (4096Byte)
#define FINSH_THREAD_STACK_SIZE 512
// <o>the history lines of finsh thread <1-32>
// <i>the history lines of finsh thread
// <i>Default: 5
#define FINSH_HISTORY_LINES 1
#define FINSH_USING_SYMTAB
// </h>
#endif
// <<< end of configuration section >>>
#endif
2.4 驗證RT-Thread Nano是否移植成功
2.4.1 在board.c中添加GPIO以及USART等初始化函數(shù)
board.c
#include "gpio.h"
#include "usart.h"
/**
* This function will initial your board.
*/
void rt_hw_board_init()
{
//添加HAL初始化函數(shù)
HAL_Init();
/* System Clock Update */
SystemCoreClockUpdate();
/* System Tick Configuration */
_SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
//添加初始化函數(shù)
MX_GPIO_Init();
MX_USART1_UART_Init();
}
2.4.2 添加fputc函數(shù),以支持printf
int fputc(int ch,FILE *fp)
{
return HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,1000);
}
2.4.3 實現(xiàn)rt_hw_console_output函數(shù)
//重新實現(xiàn)rt_hw_console_output打印函數(shù)
void rt_hw_console_output(const char *str)
{
/* empty console output */
printf("%s",str);
}
只要實現(xiàn)了這個函數(shù),在工程中才可以調(diào)用rt_kprintf來打印,這一點通過閱讀分析rt_kprintf函數(shù)源代碼得知:
/**
* This function will print a formatted string on system console
*
* @param fmt the format
*/
void rt_kprintf(const char *fmt, ...)
{
va_list args;
rt_size_t length;
static char rt_log_buf[RT_CONSOLEBUF_SIZE];
va_start(args, fmt);
/* the return value of vsnprintf is the number of bytes that would be
* written to buffer had if the size of the buffer been sufficiently
* large excluding the terminating null byte. If the output string
* would be larger than the rt_log_buf, we have to adjust the output
* length. */
length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args);
if (length > RT_CONSOLEBUF_SIZE - 1)
length = RT_CONSOLEBUF_SIZE - 1;
#ifdef RT_USING_DEVICE
if (_console_device == RT_NULL)
{
rt_hw_console_output(rt_log_buf);
}
else
{
rt_uint16_t old_flag = _console_device->open_flag;
_console_device->open_flag |= RT_DEVICE_FLAG_STREAM;
rt_device_write(_console_device, 0, rt_log_buf, length);
_console_device->open_flag = old_flag;
}
#else
rt_hw_console_output(rt_log_buf);
#endif
va_end(args);
}
RTM_EXPORT(rt_kprintf);
#endif
如上代碼所示,當(dāng)定義了RT_USING_DEVICE宏時,這里是調(diào)用了串口設(shè)備驅(qū)動,而我們這個Nano沒有移植串口設(shè)備驅(qū)動,所以直接調(diào)用的rt_hw_console_output
,而rt_hw_console_output
是一個弱函數(shù),本身并沒有在內(nèi)核里實現(xiàn),所以我們要重新去實現(xiàn)它。
2.4.4 編寫main函數(shù)
RT-Thread此時已經(jīng)移植好了,接下來我們要編寫main函數(shù),實現(xiàn)以500ms的頻率翻轉(zhuǎn)LED燈以及通過打印Hello RTT_NANO
字符串,通過這個例子,驗證移植是否成功!
main.c
int main(void)
{
while(1)
{
rt_kprintf("Hello RTT_NANO\n");
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
rt_thread_mdelay(500);
}
}
3、運行結(jié)果
對剛剛編寫的程序編譯然后下載到小熊派上:
如果采用-O0優(yōu)化等級進(jìn)行編譯,通過編譯生成的RT-Thread_Nano_For_BearPi.map
查看:
本工程移植的代碼編譯采用默認(rèn)的-O3最大優(yōu)化等級:
能夠看到,采用-O3等級優(yōu)化后的代碼體積:通過編譯生成的RT-Thread_Nano_For_BearPi.map查看:
優(yōu)化等級越高,則相應(yīng)的編譯時間就越長,一般采用-O0進(jìn)行驗證,沒有問題后再選擇-O3編譯,如果出現(xiàn)變量被優(yōu)化;導(dǎo)致系統(tǒng)明明邏輯正確卻有一些莫名其妙的問題也能夠根據(jù)實際調(diào)試情況進(jìn)行修改。
這里順便說說這里各個字段的含義:
-
Code:是我們編寫代碼占用的空間。 -
RO data(Read Only) :只讀常量的大小,如const。 -
RW data(Read Write):初始化了的可讀寫變量的大小。 -
ZI data(Zero Initialize):沒有初始化的可讀寫變量的大小,ZI-data不會被算做代碼里因為不會被初始化。
FLASH中的被占用的空間為:Code+RO Data + RW Data
芯片內(nèi)部RAM使用的空間為:RW Data + ZI Data
足夠輕量了吧!接下來下載到小熊派上可以看到:
不少朋友可能第一次移植RTT后會有疑問:為啥main函數(shù)里沒看到其它硬件外設(shè)、時鐘的初始化的東西呢?
那是因為RT-Thread拓展了main函數(shù),在main函數(shù)之前就把這些工作都做好啦!還記得前面為什么要在board.c中添加外設(shè)等初始化函數(shù)的步驟吧?如下圖所示,這是RT-Thread的啟動流程圖:
關(guān)于RT-Thread初始化流程描述文檔如下:
https://www.rt-thread.org/document/site/programming-manual/basic/basic/
官網(wǎng)上描述啟動流程的文檔非常詳細(xì),而且我認(rèn)為RT-Thread已經(jīng)做得足夠傻瓜化了,什么文檔、例程啥都給你弄好了,如果認(rèn)真花點時間好好學(xué)習(xí)研究是可以學(xué)習(xí)得非常透徹的,不懂隨時論壇和RT-Thread的QQ群及微信群都可以提問,有眾多的開發(fā)者一起解決問題。
4、移植工程案例下載
公眾號后臺回復(fù):rttnano
即可獲取移植案例的下載鏈接。
5、題外話
看到公眾號后臺有人問:C語言#和##連接符在項目中的應(yīng)用(漂亮) 這篇文章里老外代碼的出處,這是我很早之前在Github上看到的,這是來自Github上的Tilen Majerle
大佬寫的,代碼質(zhì)量非常優(yōu)秀,值得我們學(xué)習(xí),他本人就職于ST意法半導(dǎo)體公司。
鏈接:https://github.com/MaJerle/stm32fxxx-hal-libraries
Github鏈接:https://github.com/MaJerle
往期精彩
C語言#和##連接符在項目中的應(yīng)用(漂亮)
MCU SPI屏也能跑這么炫酷的特效?來,移植起來秀一秀
超輕量級網(wǎng)紅軟件定時器multi_timer(51+stm32雙平臺實戰(zhàn))
覺得本次分享的文章對您有幫助,隨手點[在看]
并轉(zhuǎn)發(fā)分享,也是對我的支持。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!