前言
本篇文档介绍 Nano 移植原理,针对的是不同 MCU 的移植,如 Cortex M,RISC-V,或者是其他 MCU 的移植。移植过程主要分为两个部分:libcpu 移植与板级移植,在讲解移植之前,本文档对 RT-Thread Nano 的启动流程与移植目录结构先进行说明。
启动流程
rtthread_startup()
,芯片启动文件在完成必要工作(如初始化时钟、配置中断向量表、初始化堆栈等)后,最终会在程序跳转时,跳转至 RT-Thread 的启动入口中。RT-Thread 的启动流程如下:
-
全局关中断,初始化与系统相关的硬件。 -
打印系统版本信息,初始化系统内核对象(如定时器、调度器)。 -
初始化用户 main 线程(同时会初始化线程栈),在 main 线程中对各类模块依次进行初始化。 -
初始化软件定时器线程、初始化空闲线程。 -
启动调度器,系统切换到第一个线程开始运行(如 main 线程),并打开全局中断。
移植目录结构
rtthread-nano
源码中,与移植相关的文件位于下图中有颜色标记的路径下(黄色表示 libcpu 移植相关的文件,绿色部分表示板级移植相关的文件):
libcpu 移植
libcpu
文件夹下。
启动文件 startup.s
rtthread_startup()
。GCC 下的启动文件需要修改,让其跳转到 RT-Thread 提供的 entry() 函数,其中 entry() 函数调用了 RT-Thread 系统启动函数
rtthread_startup()
。
1//修改前:
2 bl SystemInit
3 bl main
4
5//修改后:
6 bl SystemInit
7 bl entry /* 修改此处,由 main 改为 entry */
RT-Thread 在 entry 函数中实现了 GCC 环境下的 RT-Thread 启动:
1int entry(void)
2{
3 rtthread_startup();
4 return 0;
5}
最终调用 main() 函数进入用户 main()。
上下文切换 context_xx.s
线程栈初始化 cpuport.c
板级移植 board.c
rt_hw_board_init()
函数内容的实现,该函数在板级配置文件 board.c 中,函数中做了许多系统启动必要的工作,其中包含:
-
配置系统时钟。 -
实现 OS 节拍。 -
初始化外设:如 GPIO/UART 等等。 -
初始化系统内存堆,实现动态堆内存管理。 -
板级自动初始化,使用 INIT_BOARD_EXPORT() 自动初始化的函数会在此处被初始化。 -
其他必要的初始化,如 MMU 配置(需要时请自行在 rt_hw_board_init 函数中调用应用函数实现)。
1/* board.c */
2void rt_hw_board_init(void)
3{
4 /* System Clock Update */
5 SystemCoreClockUpdate();
6
7 /* System Tick Configuration */
8 _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
9
10#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
11 rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
12#endif
13
14 /* Call components board initial (use INIT_BOARD_EXPORT()) */
15#ifdef RT_USING_COMPONENTS_INIT
16 rt_components_board_init();
17#endif
18}
配置系统时钟
rt_hw_board_init()
函数中完成,可以调用库函数实现配置,也可以自行实现。
1/* board.c */
2void rt_hw_board_init()
3{
4 SystemCoreClockUpdate(); // 在无库函数使用时,一般使用 rt_hw_clock_init() 配置,函数名不做要求,函数自行实现
5 ...
6}
实现 OS 节拍
rt_tick_increase()
函数实现全局变量 rt_tick 自加,从而实现时钟节拍。一般地,在 Cortex M 上直接使用内部的滴答定时器 Systick 实现。
rt_tick_increase()
。
1/* board.c */
2void rt_hw_board_init()
3{
4 ...
5 _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); // 使用 SysTick 实现时钟节拍
6 ...
7}
8
9/* 中断服务例程 */
10void SysTick_Handler(void)
11{
12 /* enter interrupt */
13 rt_interrupt_enter();
14
15 rt_tick_increase();
16
17 /* leave interrupt */
18 rt_interrupt_leave();
19}
对于使用了 RT-Thread 中断管理的 CPU 架构,中断服务例程需要通过 rt_hw_interrupt_install()
进行装载(关于中断及其装载,详见本文档的” 中断管理 “ 小节),如下示例:
1/* board.c */
2void rt_hw_board_init()
3{
4 ...
5 rt_hw_timer_init(); // 使用 硬件定时器 实现时钟节拍,一般命名为 rt_hw_timer_init()
6 ...
7}
8
9int rt_hw_timer_init(void) // 函数自行实现,并需要装载中断服务例程
10{
11 ...
12 rt_hw_interrupt_install(IRQ_PBA8_TIMER2_3, rt_hw_timer_isr, RT_NULL, "tick");
13 rt_hw_interrupt_umask(IRQ_PBA8_TIMER2_3);
14}
15
16/* 中断服务例程 */
17static void rt_hw_timer_isr(int vector, void *param)
18{
19 rt_interrupt_enter();
20 rt_tick_increase();
21 rt_interrupt_leave();
22}
硬件外设初始化
1/* board.c */
2void rt_hw_board_init(void)
3{
4 ....
5 uart_init();
6 ....
7}
实现动态内存堆
1void rt_system_heap_init(void *begin_addr, void *end_addr)
1#define RT_HEAP_SIZE 1024
2static uint32_t rt_heap[RT_HEAP_SIZE];
3RT_WEAK void *rt_heap_begin_get(void)
4{
5 return rt_heap;
6}
7
8RT_WEAK void *rt_heap_end_get(void)
9{
10 return rt_heap + RT_HEAP_SIZE;
11}
12
13void rt_hw_board_init(void)
14{
15 ....
16#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
17 rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get()); //传入 heap 的起始地址与结束地址
18#endif
19 ....
20}
如果不想使用数组作为动态内存堆,则可以重新指定系统 HEAP 的大小,例如使用 RAM ZI 段结尾处作为 HEAP 的起始地址(这里需检查与链接脚本是否对应),使用 RAM 的结尾地址作为 HEAP 的结尾地址,这样可以将空余RAM 全部作为动态内存 heap 使用。如下示例重新定义了 HEAP 的起始地址与结尾地址,并作为初始化参数进行系统 HEAP 初始化。
1#define STM32_SRAM1_START (0x20000000)
2#define STM32_SRAM1_END (STM32_SRAM1_START + 20 * 1024) // 结束地址 = 0x20000000(基址) + 20K(RAM大小)
3
4#if defined(__CC_ARM) || defined(__CLANG_ARM)
5extern int Image$$RW_IRAM1$$ZI$$Limit; // RW_IRAM1,需与链接脚本中运行时域名相对应
6#define HEAP_BEGIN ((void *)&Image$$RW_IRAM1$$ZI$$Limit)
7#endif
8
9#define HEAP_END STM32_SRAM1_END
1void rt_hw_board_init(void)
2{
3 ....
4#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
5 rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
6#endif
7 ....
8}
链接脚本
1LR_IROM1 0x08000000 0x00020000 { ; load region size_region
2 ER_IROM1 0x08000000 0x00020000 { ; load address = execution address
3 *.o (RESET, +First)
4 *(InRoot$$Sections)
5 .ANY (+RO)
6 }
7 RW_IRAM1 0x20000000 0x00005000 { ; RW data
8 .ANY (+RW +ZI)
9 }
10}
其中 RW_IRAM1 0x20000000 0x00005000
表示定义一个运行时域 RW_IRAM1(默认域名),域基址为 0x20000000,域大小为 0x00005000(即 20K ),对应实际 RAM 大小。.ANY (+RW +ZI)
表示加载所有匹配目标文件的可读写数据 RW-Data、清零数据 ZI-Data。所以运行时所占内存的结尾处就是 ZI 段结尾处,可以将 ZI 结尾处之后的内存空间作为系统动态内存堆使用。
2.一站式开发工具:RT-Thread Studio 正式发布
3.熊大:致社区小伙伴们的信
4.STM32 上使用 USB Host 读写 U 盘
5.智能家居 DIY 教程连载4——手把手教你连云
6.社区专访|王君杰:软件包制作经验及使用体验分享
你可以添加微信17775982065为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群!
RT-Thread
让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。
长按二维码,关注我们
本文分享自微信公众号 - RTThread物联网操作系统(RTThread)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4428324/blog/4565688