【AS32X601驱动系列教程】MCU启动详解
在嵌入式开发领域,掌握MCU(微控制单元)的启动流程是工程师们迈向深入开发的关键一步。本文将带您深入了解MCU启动的奥秘,从编译过程到启动文件,再到链接脚本和系统时钟配置,全方位解析MCU启动流程。
在实际编程开发之前,我们首先对芯片的启动流程进行一下介绍,对于绝大多数嵌入式工程师而言,在MCU开发过程中,通常只需要完成C语言代码功能,即可利用集成开发环境编译出来芯片的可执行文件,但在此过程中,编译器进行了一系列编译操作来保证MCU可以支持C程序的运行,因此,实际的完整工程代码中,总共包含了三类文件:汇编文件、C程序代码以及链接脚本。
首先,需要明确概念,程序的编译整体包含预处理、编译、汇编、链接四个步骤。经过这四个过程之后,编译器产生MCU可识别的可执行文件,在编译的过程中,上述所提供的汇编指令代码文件同样被编译,同时依照链接脚本所提供的规则,穿插到 C 语言功能代码之间,如图所示:
在此过程中,芯片厂商会提供基础的链接文件和启动文件,以便集成开发工程管理。一般来说,芯片的启动文件需要使用汇编指令编程,用于完成上电到用户程序之前的启动操作。链接脚本则需要根据不同的编译工具链解析要求进行编写。
启动文件介绍
以AS32x601启动文件为例进行具体流程介绍,为了便于理解,将启动流程划分为三个阶段:第一阶段为系统启动必备阶段,此阶段主要完成指针初始化操作,配置全局指针GP以及栈指针SP;第二阶段为数据搬移阶段,此过程主要完成两部分操作,搬移data段数据到 RAM,清空 bss 数据段;第三阶段与平台相关,主要完成的中断配置,系统时钟初始化等,此阶段需要根据不同MCU设计自行完成,属于非必需过程。
代码详解
随便打开一个demo的IAR工程,找到如下文件双击打开:
该文件即为AS32x601的启动文件,全部由汇编指令编写,为方便讲解,截取其中有效代码如下:
定义地址段
首先,第18行开始,利用SECTION `.init`:CODE指令定义.init段,声明以下内容均包含在init段中,接下来定义全局标签_start,声明此全局标签后,链接文件中即可分配.init段地址来指定_start为处理器上电执行的第一个操作,经过以上两个步骤,即可保证此部分内容先于所有指令,从而完成上电配置。
配置全局指针GP
第40行,通过la命令,将__iar_static_base$$GPREL写入寄存器gp。
其中,__iar_static_base$$GPREL是由IAR提供的全局指针初始值,编译器会合理分配。此处需要注意,通常情况下,gp 指针定义在 data 区,有时候为了优化代码密度,可以根据实际情况修改 gp 指针的位置,如工程中定义了大量的初始化为 0 或未初始化的全局数组作为缓冲区,可以将gp指针的位置定义到bss段。
配置栈指针SP
第41行,通过la命令,将CSTACK$$Limit写入寄存器sp。
其中,_sp 是由链接脚本提供的栈指针位置,开发者应根据数据存储器大小和程序调用层次合理分配,栈指针必须保持 4 字节对齐,否则会发生加载/存储对齐错误。
加载data段
第44-54行,将data段从程序存储器搬运至数据存储器,作为可读可写的变量。
第44-46行,向寄存器a0、a1、a2加载必要数据,地址由链接脚本生成:
第47行,如果a1大于等于a2,表示没有需要搬运的数据,跳过以下循环,执行下面的工作。
第48-53 行,是一个循环结构。读出a0 指向的地址,数据写入t0 暂存。t0的数据写入 a1 指向的地址。a0+4,指向下一存储单元,因为 32bit 是4 字节。a1+4,指向下一存储单元,因为 32bit是4 字节。如果 a1 小于 a2,表示未搬运完,跳转至 49 行,进入下一次循环。如果a1等于a2,表示已经搬完了最后一个数据,退出循环,执行下面的工作。
需要说明的是,程序正常运行,还需要链接脚本的配合,对于启动文件来说,链接脚本除定义上电入口函数外,还对数据搬移过程产生影响,对于自定义的启动地址_start,则需要在链接文件中定义initialize manually,才可以在启动文件中实现数据的搬运。
清空bss段
第57-68 行,工作与data段有点类似,但是只需要清空指定位置的数据。
第57-58 行,向寄存器a0、a1加载必要数据,地址由链接脚本生成:
第59行,如果a0大于等于a1,表示没有需要清0的空间,跳过以下循环,执行下面的工作。
第61-63 行,是一个循环结构。清空a0指向的地址a0+4,指向 下一存储单元,因为32bit是4字节。如果a0小于a1,表示清0未 结束,跳转至32行,进入下一次循环。如果a0等于a1,表示已经清 0 了最后一个存储单元,退出循环,执行下面的工作。
中断初始化
从64行开始,启动文件进入流程第三阶段,配置中断,该过程主要操作RISC-V内核的中断寄存器。
mstatus:Machine Status Register,RISC-V架构中的一个重要控制和状态寄存器,管理和反映机器模式下的状态和控制信息。
mstatus.MIE:Machine Interrupt Enable,机器模式全局中断使能位
mstatus.MPIE:Machine Previous Interrupt Enable,机器模式先前中断使能位
mstatus.MPP:Machine Previous Privilege,机器模式之前的特权级别
mstatus.SPP:Supervisor Previous Privilege,超级模式之前的特权级别
第66-68行,设置mstatus寄存器,开启CPU全局中断;
MEIE:M模式外部中断使能位
SEIE:S模式外部中断使能位
MTIE:M模式timer中断使能位
STIE:S模式timer中断使能位
MSIE:M模式软中断使能位
SSIE:S模式软中断使能位
第71-73行,配置MIE寄存器,开启外部中断;
Direct模式:所有的中断和异常使用同一个中断入口地址,一般都会设置为这种模式。
Vectored模式:所有异常使用同一个入口地址,但是不同的中断使用不同的入口地址。
第76-77行,使用Direct模式,设置中断向量表入口地址TrapEntry,再由as32x601_trapentry.S找到PLIC_TrapHandler。
链接配置文件说明
AS32x601芯片提供了最大2MB的P-Flash和512kb的D-flash及512kb的SRAM。链接配置文件(.icf)作用等同于GCC工具链下的链接文件(.ld),用来描述系统内存分区,定义内存大小,分配数据段存储位置,设置堆栈等,以本芯片为例,编写如下:
第10行,保留符号__iar_cstart_init_gp,该符号在启动代码cstartup.s定义,保留符号意味着即使它未被直接引用,也不会被链接器优化掉。
第12行,定义总体内存空间,包含数据存储区,程序存储区,最大为4G,可直接设置为最大,不影响实际效果;
第14-15行,定义RAM、ROM区间地址以及大小;
第17行,手动初始化.data段。这意味着.data段的内容需要在程序启动时从其他位置(如ROM)复制到RAM中;
第18行,不对以.noinit结尾的段进行初始化。这些段的内容在程序启动时保持不变;
第20-21行,定义堆栈大小以及对齐方式,此处CSTACK_SIZE和HEAP_SIZE代表此处大小由iar软件获取,也可直接进行固定设置,单位为字节;
第23-29行,定义了RW_DATA、RW_DATA_INIT、RW_BSS和RW_DATA_ALL,可供启动文件调用;
第30行,将只读的初始化代码段.init放置在ROM_region32的起始位置。这个段包含启动代码,负责初始化程序的运行环境;
第30行,将只读段(包括代码和常量数据)以及RW_DATA_INIT块放置在ROM_region32中。这意味着初始化数据将与代码一起存储在ROM中。
第31行,将RW_DATA_ALL块(包含.data和.bss段)、HEAP块和CSTACK块放置在RAM_region32中。这确保了可读写数据、堆和栈在RAM中进行操作,以实现快速访问和修改。