嵌入式系统的移植是指将嵌入式软件或操作系统从一个硬件平台移植到另一个硬件平台的过程。这通常需要适应目标硬件的架构、外设和资源,以确保嵌入式系统能够在新的目标系统上运行,该过程涉及处理器体系结构、外设、硬件抽象层、驱动程序和其他系统组件的变化。
本节的要求如下:
➲ 了解RT-Thread的启动流程。
➲ 学习libcpu和BSP。
➲ 掌握RT-Thread移植的基本方法。
1)RT-Thread的启动流程
RT-Thread的启动入口函数为rtthread_startup()。当芯片的启动文件完成必要工作(如初始化时钟、配置中断向量表、初始化内存等)后,会跳转到RT-Thread的启动入口函数。RT-Thread的启动流程如下:
(1)关闭全局中断,初始化与系统相关的硬件。
(2)显示系统版本信息,初始化系统内核对象(如定时器、线程调度器等)。
(3)初始化用户main线程(同时会初始化线程栈),在main线程中对各模块依次进行初始化。
(4)初始化软件定时器线程、初始化空闲线程。
(5)启动线程调度器,切换到第一个线程开始运行(如main线程),并打开全局中断。
2)启动文件(zonesion/common/LIB/Libraries/startup_stm32f407xx.S)
启动文件(startup_xx.S)由芯片厂商提供,位于芯片固件库中。每款芯片都有对应的启动文件,在不同开发环境下启动文件也不相同。当系统加入RT-Thread后,会将RT-Thread的启动放在调用main()函数之前,如图1.41所示。
图1.41 RT-Thread启动在调用main()之前
启动文件的主要工作包括初始化时钟、配置中断向量表、初始化全局/静态变量、初始化内存、初始化库函数、跳转程序等内容。
3)libcpu(rt-thread/libcpu)
RT-Thread的libcpu向下提供了一套统一的CPU架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache函数等内容,RT-Thread支持的CPU架构在源码的libcpu文件夹下。
4)上下文切换(rt-thread/libcpu/arm/cortex-m4/context_gcc.S)
上下文切换文件(context_xx.s)的主要工作是实现CPU在线程之间的切换或者线程与中断之间的切换等。在上下文切换中,CPU一般会停止当前运行的代码,并保存当前程序运行的具体位置以便后续运行。上下文切换的接口如表1.4所示。
表1.4 上下文切换的接口
5)线程栈初始化(rt-thread/libcpu/arm/cortex-m4/cpuport.c)
在RT-Thread中,线程具有独立的栈。在进行线程切换时,RT-Thread会将当前线程的上下文保存在栈中;在线程要恢复运行时,再从栈中读取上下文信息。rt_hw_hard_fault_exception()为故障异常处理函数,在发生硬件错误时,会触发HardFault_Handler中断,从而调用该函数。
线程栈初始化文件(cpuport.c)的主要工作是实现线程栈的初始化和硬件错误的处理。线程栈初始化涉及的接口如表1.5所示。
表1.5 线程栈初始化的接口
6)中断与异常
在基于Cortex-M内核的微控制器中,中断是通过中断向量表来进行处理的。当某个中断被触发时,微控制器直接判定该中断属于哪个中断源,然后直接跳转到相应的固定位置进行处理,不需要再自行实现中断管理。
7)板级文件(zonesion/common/BSP/drivers/board.c和zonesion/common/BSP/drivers/board.h)
板级文件(board.c和board.h)的主要工作是实现rt_hw_board_init()函数。该函数在board.c中,完成了系统启动必要的工作,主要包含:
(1)配置系统时钟。
(2)实现操作系统节拍。
(3)初始化外设(如GPIO、UART等)。
(4)设置控制台的串口设备。
(5)初始化系统内存,实现动态内存的管理。
(6)板级初始化,使用INIT_BOARD_EXPORT()进行自动初始化的函数会在此处被初始化。
(7)其他必要的初始化。
板级初始化的代码如下:
在rt_hw_board_init()函数中还会调用hw_board_init()函数。hw_board_init()函数的主要工作是初始化HAL库、配置系统时钟、初始化PIN设备和串口设备等,代码如下:
RT-Thread尽可能地编写了驱动层的代码,将系统级的配置统一放在rtconfig.h中进行,将板级独有的配置统一放在board.h中进行,因此只需要在rtconfig.h和board.h中配置相应的宏就能够配置相应的驱动,如ADC、PWM、USART、SPI等。在RT-Thread Studio中,rtconfig.h中的配置项由RT-Thread Settings生成,board.h中的配置只包含了一些示例,需要用户查阅相关资料。配置错误将导致驱动无法正常工作。
8)配置系统时钟(zonesion/common/BSP/drivers/drv_clk.c)
系统时钟的配置函数是drv_clk.c中clk_init()函数,该函数为各个硬件模块提供了工作时钟的基础。一般clk_init()函数是在hw_board_init()函数中调用的,hw_board_init()函数位于drv_common.c中。RT-Thread Studio生成的代码默认使用的是内部时钟,由于各家厂商的开发板使用的外部晶振频率不一定相同,因此需要根据具体的开发板来修改clk_init()函数,从而启动外部晶振。下面的代码给出了clk_init()函数的一种调用方式,这里使用的外部晶振频率为8 MHz。
9)实现操作系统节拍(zonesion/common/BSP/drivers/drv_common.c)
操作系统节拍也称为时钟节拍或滴答(Tick)时钟节拍。任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件。时钟节拍的实现过程是:通过硬件定时器(Timer)实现周期性中断,在定时器中断时调用rt_tick_increase()函数实现全局变量rt_tick的自加,从而实现时钟节拍。一般地,基于Cortex-M内核的微控制器直接使用内部的滴答定时器Systick实现时钟节拍。
时钟节拍由配置为中断触发模式的硬件定时器产生。当中断到来时,将调用一次rt_tick_increase()函数,通知操作系统已经过去一个系统时钟。不同硬件定时器的中断实现往往不同,下面的代码以STM32系列微控制器为例介绍时钟节拍的配置。在初始化时钟节拍后,直接在SysTick_Handler()函数(中断服务程序)中调用rt_tick_increase()。rt_hw_systick_init()函数由hw_board_init()函数调用,rt_hw_systick_init()函数使用HAL库完成系统时钟的配置,并设置系统时钟的中断优先级,这里的优先级是最高的。
本节在ZI-ARMEmbed上完成RT-Thread的移植。
1.4.3.1 硬件部署
同1.2.3.1节。
1.4.3.2 系统移植
由于02-Template工程是由RT-Thread Studio自动生成的,具有一定的参考意义,因此本节在02-Template工程的基础上,针对实际的硬件进行BSP移植,从而构成一个完整的实例工程,便于在后文建立各个项目的代码。读者也可以直接导入已经创建好的03-init工程。
1)创建工程
(1)复制02-Template工程并命名为03-init,将其复制到RT-ThreadStudio/workspace下。
(2)修改.cproject第215行的目录路径,将workspacePath中02-Template修改为03-init。.cproject的代码请参考本书的配套资源(目录为“CH01-RT-Thread开发基础/03-init/.cproject”)。.cproject的部分代码如下:
(3)修改.project第3行和倒数第3行的工程名,将name中02-Template修改为03-init。.project的代码请参考本书的配套资源(目录为“CH01-RT-Thread开发基础/03-init/.project”)。.project的部分代码如下:
(4)修改.settings目录下的projcfg.ini最后一行中的工程名,将project_name中的02-Template修改为03-init。projcfg.ini的代码请参考本书的配套资源(目录为“CH01-RT-Thread开发基础/03-init/.settings/projcfg.ini”)。projcfg.ini的部分代码如下:
(5)将.settings目录中4个配置文件头修改为新的工程名,修改前后的工程名如图1.42所示。
图1.42 修改前后的工程名
2)导入工程
(1)运行RT-Thread Studio,选择菜单“文件”→“导入”,在弹出的“导入”对话框中选择“RT-Thread Studio项目到工作空间中”,单击“下一步”按钮,在“导入项目”视图中将“选择根目录”设置为“RT-ThreadStudio\workspace\03-init”,“项目”下会显示所有的工程,勾选“03-init”,如图1.43和图1.44所示。
图1.43 导入工程(一)
图1.44 导入工程(二)
(2)导入工程后的界面如图1.45所示。如果存在多个工程,则可通过双击工程名进行工程切换。
图1.45 导入工程后的界面
3)调整工程结构
(1)在03-init工程根目录下,创建zonesion目录。
(2)在zonesion目录下,创建app目录、common目录。在app目录下,创建board目录;在common目录下,创建APL、BSP、DEV、DRV、LIB目录。在RT-Thread Studio中,右键单击“03-init”,在弹出的右键菜单中选择“刷新”即可看到建立的目录,如图1.46所示。
图1.46 建立的目录
(3)工程视图。工程栏有两个选项卡:“项目资源管理器”和“C/C++项目”。在“C/C++项目”选项卡中可以看到除编译文件之外的目录,如图1.47所示。
图1.47 “C/C++项目”选项卡
当需要使用RT-Thread Studio中的RT-Thread Settings来配置RT-Thread时,可以切换到“项目资源管理器”选项卡,如图1.48所示。
图1.48 “项目资源管理器”选项卡
(4)将03-init工程根目录下的drivers目录移动到common/BSP目录下,将libraries目录移动到common/LIB目录下,如图1.49所示。
图1.49 移动drivers目录和libraries目录
(5)将common/BSP/drivers目录下的board.c、board.h、stm32f4xx_hal_conf.h移动到app/board目录下。这三个文件的作用是配置RT-Thread和STM32系列微控制器的HAL库,因此不同的工程会有所不同,属于每个工程的独有文件。board.c、board.h、stm32f4xx_hal_conf.h的位置如图1.50所示。
图1.50 board.c、board.h、stm32f4xx_hal_conf.h的位置
(6)将applications/main.c移动到app/main.c,然后删除空的applications目录。
(7)在zonesion目录下创建readme目录,并在readme目录下新建readme.txt文件,该文件用于保存本工程的介绍和软件配置过程。
zonesion目录的作用如下:
4)修改头文件路径
由于前文对工程目录进行了调整,因此需要重新配置工程,将刚刚创建的目录都加入头文件搜索路径(如zonesion/app、zonesion/app/board、zonesion/common/APL、zonesion/common/BSP、zonesion/common/DEV、zonesion/common/DRV、zonesion/common/LIB),下面介绍具体的操作步骤。
(1)右键单击工程名“03-init”,在弹出的右键菜单中选择“属性”,可弹出“03-init的属性”对话框,在该对话框中选择“C/C++构建”→“设置”,可以看到使用的C的交叉编译器GNU ARM Cross C Compiler,在includes中修改原来的drivers和libraries目录的相对路径(见图1.51),代码如下:
图1.51 修改原来的drivers和libraries目录的相对路径
(2)增加应用程序和板级配置文件夹的搜索路径zonesion/app、zonesion/app/board。增加搜索路径如图1.52和图1.53所示。
图1.52 增加搜索路径(一)
图1.53 增加搜索路径(二)
(3)增加驱动相关的头文件路径,即zonesion/common/APL、zonesion/common/DEV、zonesion/common/DRV的搜索路径,如图1.54所示。
图1.54 增加驱动相关的头文件路径
(4)删除多余的头文件搜索路径,即删除“"${workspace_loc://${ProjName}/applications}"”。
(5)再次编译,可发现编译通过,说明头文件路径配置正确。
5)修改系统时钟
(1)由于RT-Thread生成的工程在默认情况下使用的是内部晶振,因此需要修改代码来使用外部晶振,并替换本工程中的“03-init/zonesion/common/BSP/drivers/drv_clk.c”。
(2)由于clk_init()函数是在hw_board_init()函数中引用的,hw_board_init()函数是在rt_hw_board_init()函数引用的,而hw_board_init()函数中的入口参数为BSP_CLOCK_SOURCE、BSP_CLOCK_SOURCE_FREQ_MHZ、BSP_CLOCK_SYSTEM_FREQ_MHZ(见图1.55),这三个宏的定义在“zonesion/app/board.h”中。
图1.55 hw_board_init()函数中的入口参数
按照实际的外部晶振修改参数即可,修改前的BSP_CLOCK_SOURCE如图1.56所示。
图1.56 修改前的BSP_CLOCK_SOURCE
修改前的BSP_CLOCK_SOURCE如图1.57所示。
图1.57 修改后的BSP_CLOCK_SOURCE
6)修改调试配置
单击工具栏中的“ ”(调试配置)按钮,可打开“配置工程”对话框,选择“SVD Path”选项卡,将“File path”中的0.2.0改为0.2.2,如图1.58所示。
图1.58 将“File path”中的0.2.0改为0.2.2
1.4.3.3 程序调试
1)导入工程
(1)前文已经完成了工程的创建,读者可直接将03-init工程复制到RT-Thread Studio。(2)如果RT-Thread Studio中存在多个工程,可通过双击工程名来切换工程。
2)编译工程
(1)单击工具栏中的“ ”(构建)按钮完成工程的编译。
(2)如果控制台窗口提示编译完成且没有报错,则表示工程编译成功。
3)下载工程
(1)单击工具栏中的“ ”(下载)按钮可将编译后的工程下载到ZI-ARMEmbed。
(2)如果控制台窗口提示下载完成且没有报错,则表示工程下载成功。
(3)工程下载完成后,按下ZI-ARMEmbed上的复位键即可运行新的工程,并在MobaXterm串口终端FinSH控制台中看到工程启动时的信息。
4)调试工程
(1)单击工具栏中的“ ”(调试)按钮可启动调试功能。
(2)在程序合适的位置设置断点,单步对程序进行调试,观察变量的变化和寄存器的变化,可加深对程序的理解。
(3)调试完后,单击工具栏中的“ ”(停止)按钮可退出调试模式。
1.4.3.4 验证效果
(1)关闭RT-Thread Studio,拔掉仿真器,按下ZI-ARMEmbed上的电源按键重新上电。
(2)本工程根据实际的硬件对工程结构进行了调整,完成了RT-Thread的移植,MobaXterm串口终端FinSH控制台会默认每秒显示一次“Hello RT-Thread!”消息。
本节介绍了RT-Thread的移植,涉及的内容包括系统的启动流程、上下文的切换、线程的初始化等。通过本节的学习,读者可完成RT-Thread的移植。