一、移植RT-Thread准備
-
RT-Thread源碼
源碼版本和下載方式,可以參考RT-Thread移植入門學習。 -
keil軟件
-
STM32工程項目模板
因為每一廠家提供的庫文件可能有一些區別,在移植時可能會出現各種不同的問題,對於剛了解RT-Thread的小伙伴不友好,所以我已經將之前創建好的項目模板放在百度網盤了,當然也可以參考STM32新建模板之庫文件,百度的下載連接是:https://pan.baidu.com/s/1_H3l4Dy5aZHfZ_FirBjgtA ,提取碼是:vbzt -
STM32F103C8T6開發版
想要購買通關開發版的我也提供了賣家的連接,需要的小伙伴可以參考STM32零基礎入門教程
二、RT-Thread 幫助文檔
這里我移植的是標准版,當然需要移植的組件也是很多的,我們從內核開始移植,然后在進行外設的移植。學習RT-Thread 過程中有什么不明白的可以參考官方提供的幫助文檔。
RT-Thread標准版文檔:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic?id=rt-thread-內核配置示例
移植RT-Thread的內核有兩種方法,一種是通過keil提供的插件進行一起,一種是通過下載官方的源碼進行移植,這里主要是了解通過源碼的方式進行移植,這樣在后面進行外設的移植時比較方便。
三、使用keil提供的工具進行移植
-
打開模板工程
-
通過keil下載RT-Thread內核接口
-
添加RT-Thread
-
添加完成后項目工程中會增加一個RTOS路徑
-
編譯,編譯完成后會發現兩個錯誤
注意:這里主要的錯誤是在board.c文件中,聲明了SystemCoreClockUpdate(void)方法和SystemCoreClock變量,但是沒有對進進行定義導致的錯誤。 -
函數SystemCoreClockUpdate()
這個函數主要是用於獲取SystemCoreClock 定時器和處理器的頻率,這一我們只是想測試一下移植的效果我們可以不用對其進行實現,我們會在下次筆記中進行分析。當然有在system_stm32fqox.c文件中已經實現了,這里就不會出現這樣的錯誤,現在的解決辦法比較簡單,在文件最后定義一下這個函數即可。
-
SystemCoreClock變量
SystemCoreClock是當前系統時鍾評率,並且是通過函數SystemCoreClockUpdate()中獲取的,這里我們也不用過多了解,因為不同的庫文件對系統時鍾頻率的命名是不一樣的,比如我現在使用庫文件的命名如下圖所示:
從圖中可知,SystemFrequency的時鍾頻率是通過變量SYSCLK_FREQ_72MHz的賦值,所有我們只需要知道當前系統的頻率賦值給SystemCoreClock變量即可。
- 解決辦法
- 在board.c文件中引入頭文件stm32f10x.h
- 在函數rt_hw_board_init()中定義SystemCoreClock變量並賦值。
- 在board.c文件中引入頭文件stm32f10x.h
-
開啟堆內存
打開的方式比較簡單,只需要在rtconfig.h文件中取消RT_USING_HEAP宏的注釋即可
-
在次編譯,這次編譯即便會發現沒有錯誤了,其中的警告我們先忽視。
-
編寫主程序,在線程中進行led燈閃爍,main.c文件如下所示
#include "stm32f10x.h"
#include "led.h"
#include <rtthread.h>
int main(void)
{
LED_GPIO_Config();
while (1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
rt_thread_delay(1000); // 延時1000 ms
GPIO_SetBits(GPIOB, GPIO_Pin_12 );
rt_thread_delay(1000); // 延時1000 ms
}
}
到這里我們便可以簡單的使用RT-Thread的延時函數進行led的閃爍試驗了。
四、通過官方源碼移植
-
在模板工程中創建RT_Thread、RT_Thread/kernel、board文件
-
將源碼路徑下的include和src文件拷貝到創建的RT_Thread/kernel文件中
-
將路徑libcpu\arm中的cortex-m3文件拷貝到創建的RT_Thread文件中
注意:這里拷貝的是項目架構文件,因為我這里使用的是M3的芯片,小伙們需要根據自己的芯片類型拷貝相應的文件 -
在board文件中創建board.c和rtconfig.h文件
-
添加源碼到工程組文件
-
board.c文件,相信這個文件已經不陌生了,沒錯我們將上一流程更改的內容拷貝過來
/*
* Copyright (c) 2006-2019, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2017-07-24 Tanek the first version
* 2018-11-12 Ernest Chen modify copyright
*/
#include <stdint.h>
#include <rthw.h>
#include <rtthread.h>
#include "stm32f10x.h"
#define _SCB_BASE (0xE000E010UL)
#define _SYSTICK_CTRL (*(rt_uint32_t *)(_SCB_BASE + 0x0))
#define _SYSTICK_LOAD (*(rt_uint32_t *)(_SCB_BASE + 0x4))
#define _SYSTICK_VAL (*(rt_uint32_t *)(_SCB_BASE + 0x8))
#define _SYSTICK_CALIB (*(rt_uint32_t *)(_SCB_BASE + 0xC))
#define _SYSTICK_PRI (*(rt_uint8_t *)(0xE000ED23UL))
// Updates the variable SystemCoreClock and must be called
// whenever the core clock is changed during program execution.
extern void SystemCoreClockUpdate(void);
// Holds the system core clock, which is the system clock
// frequency supplied to the SysTick timer and the processor
// core clock.
extern uint32_t SystemCoreClock;
static uint32_t _SysTick_Config(rt_uint32_t ticks)
{
if ((ticks - 1) > 0xFFFFFF)
{
return 1;
}
_SYSTICK_LOAD = ticks - 1;
_SYSTICK_PRI = 0xFF;
_SYSTICK_VAL = 0;
_SYSTICK_CTRL = 0x07;
return 0;
}
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE 1024
static uint32_t rt_heap[RT_HEAP_SIZE]; // heap default size: 4K(1024 * 4)
RT_WEAK void *rt_heap_begin_get(void)
{
return rt_heap;
}
RT_WEAK void *rt_heap_end_get(void)
{
return rt_heap + RT_HEAP_SIZE;
}
#endif
/**
* This function will initial your board.
*/
void rt_hw_board_init()
{
uint32_t SystemCoreClock = 72000000;
/* 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
}
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
void SystemCoreClockUpdate(void)
{
}
- 拷貝rtconfig.h文件,同樣是將上一流程中更改的內容拷貝過來
/* RT-Thread config file */
#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#if defined(RTE_USING_FINSH)
#define RT_USING_FINSH
#endif //RTE_USING_FINSH
#endif //(__CC_ARM) || (__CLANG_ARM)
/**
* RT-Thread 內核部分
*/
/* 表示內核對象的名稱的最大長度,若代碼中對象名稱的最大長度大於宏定義的長度,
* 多余的部分將被截掉。*/
#define RT_NAME_MAX 8
/* 字節對齊時設定對齊的字節個數。常使用 ALIGN(RT_ALIGN_SIZE) 進行字節對齊。*/
#define RT_ALIGN_SIZE 4
/* 定義系統線程優先級數;通常用 RT_THREAD_PRIORITY_MAX-1 定義空閑線程的優先級 */
#define RT_THREAD_PRIORITY_MAX 32
/* 定義時鍾節拍,為 100 時表示 100 個 tick 每秒,一個 tick 為 10ms */
#define RT_TICK_PER_SECOND 1000
/* 檢查棧是否溢出,未定義則關閉 */
#define RT_USING_OVERFLOW_CHECK
/* 定義該宏開啟 debug 模式,未定義則關閉 */
#define RT_DEBUG
/* 開啟 debug 模式時:該宏定義為 0 時表示關閉打印組件初始化信息,定義為 1 時表示啟用 */
#define RT_DEBUG_INIT 1
/* 開啟 debug 模式時:該宏定義為 0 時表示關閉打印線程切換信息,定義為 1 時表示啟用 */
#define RT_DEBUG_THREAD 0
/* 定義該宏表示開啟鈎子函數的使用,未定義則關閉 */
#define RT_USING_HOOK
/* 定義了空閑線程的棧大小 */
#define IDLE_THREAD_STACK_SIZE 256
/**
* 線程間同步與通信部分,該部分會使用到的對象有信號量、互斥量、事件、郵箱、消息隊列、信號等。
*/
/* 定義該宏可開啟信號量的使用,未定義則關閉 */
#define RT_USING_SEMAPHORE
/* 定義該宏可開啟互斥量的使用,未定義則關閉 */
//#define RT_USING_MUTEX
/* 定義該宏可開啟事件集的使用,未定義則關閉 */
//#define RT_USING_EVENT
/* 定義該宏可開啟郵箱的使用,未定義則關閉 */
#define RT_USING_MAILBOX
/* 定義該宏可開啟消息隊列的使用,未定義則關閉 */
//#define RT_USING_MESSAGEQUEUE
/* 定義該宏可開啟信號的使用,未定義則關閉 */
//#define RT_USING_SIGNALS
/**
* 內存管理部分
*/
/* 開啟靜態內存池的使用 */
// RT_USING_MEMPOOL
/* 定義該宏可開啟兩個或以上內存堆拼接的使用,未定義則關閉 */
//#define RT_USING_MEMHEAP
/* 開啟小內存管理算法 */
#define RT_USING_SMALL_MEM
/* 關閉 SLAB 內存管理算法 */
//#define RT_USING_SLAB
/* 開啟堆的使用 */
#define RT_USING_HEAP
/**
* 內核設備對象
*/
/* 表示開啟了系統設備的使用 */
//#define RT_USING_DEVICE
/* 定義該宏可開啟系統控制台設備的使用,未定義則關閉 */
#define RT_USING_CONSOLE
/* 定義控制台設備的緩沖區大小 */
#define RT_CONSOLEBUF_SIZE 128
/* 控制台設備的名稱 */
//#define RT_CONSOLE_DEVICE_NAME "uart1"
/**
* 自動初始化方式
*/
/* 定義該宏開啟自動初始化機制,未定義則關閉 */
#define RT_USING_COMPONENTS_INIT
/* 定義該宏開啟設置應用入口為 main 函數 */
#define RT_USING_USER_MAIN
/* 定義 main 線程的棧大小 */
#define RT_MAIN_THREAD_STACK_SIZE 2048
/**
* FinSH
*/
/* 定義該宏可開啟系統 FinSH 調試工具的使用,未定義則關閉 */
//#define RT_USING_FINSH
/* 開啟系統 FinSH 時:將該線程名稱定義為 tshell */
//#define FINSH_THREAD_NAME "tshell"
/* 開啟系統 FinSH 時:使用歷史命令 */
//#define FINSH_USING_HISTORY
/* 開啟系統 FinSH 時:對歷史命令行數的定義 */
//#define FINSH_HISTORY_LINES 5
/* 開啟系統 FinSH 時:定義該宏開啟使用 Tab 鍵,未定義則關閉 */
//#define FINSH_USING_SYMTAB
/* 開啟描述功能 */
//#define FINSH_USING_DESCRIPTION
/* 開啟系統 FinSH 時:定義該線程的優先級 */
//#define FINSH_THREAD_PRIORITY 20
/* 開啟系統 FinSH 時:定義該線程的棧大小 */
//#define FINSH_THREAD_STACK_SIZE 4096
/* 開啟系統 FinSH 時:定義命令字符長度 */
//#define FINSH_CMD_SIZE 80
/* 開啟系統 FinSH 時:定義該宏開啟 MSH 功能 */
//#define FINSH_USING_MSH
/* 開啟系統 FinSH 時:開啟 MSH 功能時,定義該宏默認使用 MSH 功能 */
//#define FINSH_USING_MSH_DEFAULT
/* 開啟系統 FinSH 時:定義該宏,僅使用 MSH 功能 */
//#define FINSH_USING_MSH_ONLY
/**
* 關於 MCU
*/
/* 定義該工程使用的 MCU 為 STM32F103ZE;系統通過對芯片類型的定義,來定義芯片的管腳 */
//#define STM32F103ZE
/* 定義時鍾源頻率 */
//#define RT_HSE_VALUE 8000000
/* 定義該宏開啟 UART1 的使用 */
//#define RT_USING_UART1
#endif
-
編譯,這是會發現找不到RTE_Components.h文件
因為這個是之前keil軟件中的頭文件,編譯的時候會自動生成,這里我們不需要,直接刪除這個引用即可。
-
main.c文件
#include "stm32f10x.h"
#include "led.h"
#include <rtthread.h>
int main(void)
{
LED_GPIO_Config();
while (1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
rt_thread_delay(1000); // 延時1000 ms
GPIO_SetBits(GPIOB, GPIO_Pin_12 );
rt_thread_delay(1000); // 延時1000 ms
}
}
五、文件保護
防止在開發中不小心更改到內核文件,導致新的錯誤產生,所以我們需要進行文件保護。
處理辦法也比較簡單,在項目文件中把需要保護的文件改為只讀即可
設置為只讀模式后,在項目文件上就可以看到一把鑰匙的存在,這樣就可以避免在更改程序時,意外改動導致新的錯誤產生,如下圖所示:
到此移植RT-Thread的內核也算基本完成了,當然還存在一些問題,接下來只需要哦邊學習邊修改即可,感興趣的小伙伴可以看我之后的文章,哪里寫得不好望小伙本們指出。
六、常見問題
- HardFault_Handler、PendSV_Handler、SysTick_Handler三個函數重復定義
- 解決辦法,這個問題是因為在工程中導入了stm32f10x_it.c文件,而這個文件主要是提供了一些模板,這里我們不需要,所以解決方法有兩種,
- 方式一: 直接將stm32f10x_it.c文件重項目中刪除即可。
- 方式二: 在stm32f10x_it.c文件中將重復定義的函數屏蔽即可。
參考文獻
stm32 移植 rt-thread:https://blog.csdn.net/qq_36958104/article/details/111604665