前言
了解程序从上电开始如何运行第一条代码,运行应用程序之前的代码做了什么等等,
对于今后的开发会大有帮助,我们一定要理解底层的细节,知其然知其所以然,才能在遇到问题时能快速解决问题。
分析流程
对于任何一款芯片,都可以按照如下流程方法分析,举一反三,一通百通。
从以下两个方面进行分析:
芯片手册-> 上电-> 启动过程
以上信息要从对应的芯片架构手册我们这里是CORTEX-M33去查找。
工程配置->连接脚本->中断向量->启动代码->底层初始化代码->系统初始化代码->main
以上过程从工程中,借助仿真器分析。
启动过程了解
从手册RA6M4 Group User’s Manual: Hardware第三章可以看到观其boot的描述
首先有一个引脚MD用于配置启动模式,当复位后检测到MD引脚电平为:
MD=1 则为Single-chip mode即,直接从on-chip flash开始运行用户代码。
而MD=0时则为SCI / USB boot mode,即,先运行芯片内部的boot代码,可以通过SCI/USB下载用户代码。
如下所示
正常我们使用前者运行用户程序,只有在没有仿真器的情况下需要更新程序时才使用后者。
所以这里只介绍前者,后者有时间可以单独介绍。
我们也可以从原理图可以看到,有跳线J8进行MD的电平配置的。
另外可以通过第4章看下存储的地址空间
本开发板使用的是R7FA6M4AF3CFB,1MB FLASH,256kB RAM。
按照前者配置启动后,即是CORTEX-M33的启动流程了,所以我们需要参考CORTEX-M33的手册。
复位时根据INITNSVTOR设定默认的中断向量地址,INITNSVTOR是IP核提供的可配参数,芯片实现时定义,一般是0x0000000,对应on chip flash地址。
根据LOCKNSVTOR引脚可决定,中断向量是否可配置到0x00000000 to 0xFFFFFF80,这也是IP提供的可配参数,具体芯片根据实际实现。中断向量的大小必须是2的指数倍字对齐。
我们看到中断向量的最开始4字节就是SP的初始化值,接下来4字节就是Reset后执行的第一条代码的地址,也就是将该4字节内容的值赋值给PC寄存器,相当于跳转到该处执行。
也就是芯片从on chip flash执行时,先将最开始4字节内容加载到SP寄存器(由于栈位于寄存器中所以内容肯定是0x20xxxxxx),然后从接下来4字节读取指令地址加载到PC寄存器进行执行。后面我们会通过仿真器进行验证。
启动过程代码分析
要查找启动代码一般从连接脚本入手
按照如下可以找到使用的链接脚本
打开对应的ld文件
可以看到先包含了INCLUDE memory_regions.ld
该文件定义了存储区域和芯片手册对应
从以下可以看出将.fixed_vectors代码段放在了FLASH_START处即0x00000000处。根据前面描述正是从这个地方开始加载SP和第一条指令。
我们搜索fixed_vectors
找到如下定义
继续搜索BSP_SECTION_FIXED_VECTORS
找到如下位置
可以看到
最开始4字节为
(exc_ptr_t) (&g_main_stack[0] + BSP_CFG_STACK_MAIN_BYTES),
即SP初始化为该值其中 #define BSP_CFG_STACK_MAIN_BYTES (0x400)
然后接下来在4字节为Reset_Handler,即加载Reset_Handler的地址到PC执行。
相当于跳转到Reset_Handler执行。
接下就可以继续查看执行过程
为了方便跟踪,我们使用仿真器跟踪执行过程
在Reset_Handler处打断点,再进入仿真
此时我们可以查看
0x00000000内存处的值
由于是小端模式,所以最开始4字节为0x20001400,接下来4字节为0x00009729
0x00009729处正是Reset_Handler
这时看SP寄存器的值,为0x200013f8
与之前代码中的g_main_stack+1024少了8字节,这是因为
是从c代码中打的断点,实际是打开函数Reset_Handler里面,由于进入函数编译器会自动添加代码保存LR和使用的寄存器,多了指令 push {r7, lr}压栈了两个字,所以SP减少了8字节(满递减栈)
至此我们已经了解芯片复位到执行第一条代码的过程,我们继续
首先调用了SystemInit进行底层初始化
开始先进性FPU使能配置,栈设置
中断向量基地址寄存器VTOR设置
然后是时钟初始化
bsp_clock_init
设置栈检测__set_MSPLIM(BSP_PRV_STACK_LIMIT);
初始化bss区域(清0)
memset(&bss_start, 0U, ((uint32_t) &bss_end - (uint32_t) &bss_start));
初始化data区域,从flash中复制初始化值到ram中
memcpy(&data_start, &__etext, ((uint32_t) &data_end - (uint32_t) &data_start));
调用初始化函数
int32_t count = __init_array_end - __init_array_start;
for (int32_t i = 0; i < count; i++)
{
__init_array_starti;
}
更新获取时钟
SystemCoreClockUpdate();
其他
PMSAR初始化
bsp_irq_cfg();
bsp_init(NULL); 弱定义用户代码可以覆盖
然后进入(如果是gcc编译器)
entry();
否则直接进入main。
继续看entry()调用了rtthread_startup();
继续看就是系统初始化了
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initialization
- NOTE: please initialize heap inside board initialization.
/
rt_hw_board_init();
/ show RT-Thread version /
rt_show_version();
/ timer system initialization /
rt_system_timer_init();
/ scheduler system initialization /
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
/ signal system initialization /
rt_system_signal_init();
#endif / RT_USING_SIGNALS /
/ create init_thread /
rt_application_init();
/ timer thread initialization /
rt_system_timer_thread_init();
/ idle thread initialization /
rt_thread_idle_init();
#ifdef RT_USING_SMP
rt_hw_spin_lock(&_cpus_lock);
#endif / RT_USING_SMP /
/ start scheduler /
rt_system_scheduler_start();
/ never reach here */
return 0;
}
到开始进行调度执行第一个任务,至此函数不再回来。
rt_application_init创建了main_thread_entry任务
启动调度之后会执行该任务,
调用rt_components_init初始化组件
最终花式进入main函数
void main_thread_entry(void parameter)
{
extern int main(void);
#ifdef RT_USING_COMPONENTS_INIT
/ RT-Thread components initialization /
rt_components_init();
#endif / RT_USING_COMPONENTS_INIT /
#ifdef RT_USING_SMP
rt_hw_secondary_cpu_up();
#endif / RT_USING_SMP /
/ invoke system main function /
#ifdef __ARMCC_VERSION
{
extern int Super$main(void);
Super$main(); / for ARMCC. /
}
#elif defined(ICCARM) || defined(GNUC) || defined(TASKING)
main();
#endif
}
#include "hal_data.h"
int main(void) {
hal_entry();
return 0;
}
我们看到main_thread_entry并不是一个死循环
main会return 0
rt_thread_create调用rt_hw_stack_init时
stack_frame->exception_stack_frame.lr = (unsigned long)texit; / lr */
所以最后任务return 时会返回texit执行
默认传入的是_thread_exit
会将该任务状态修改为不再调度,(是否释放动态内存后续再看代码确认)。
static void _thread_exit(void)
{
struct rt_thread thread;
register rt_base_t level;
/ get current thread /
thread = rt_thread_self();
/ disable interrupt /
level = rt_hw_interrupt_disable();
/ remove from schedule /
rt_schedule_remove_thread(thread);
/ remove it from timer list /
rt_timer_detach(&thread->thread_timer);
/ change stat /
thread->stat = RT_THREAD_CLOSE;
/ insert to defunct thread list /
rt_thread_defunct_enqueue(thread);
/ enable interrupt /
rt_hw_interrupt_enable(level);
/ switch to next task */
rt_schedule();
}
总结
通过以上分析,我们了解了芯片从上电到运行到用户代码的整个流程,相信对于任何一个全新的芯片平台也能很快就进行分析了解。
原作者:qinyunti