前言本文基于Demo程序,讲解程序的启动过程。对时钟初始化,中断等关键模块进行讲解分析,了解这些细节后,以便后续进行应用开发。
分散加载文件分析一般分析程序的启动过程从链接脚本入手,MDK的ARMCC编译器用的是分散加载文件.scat。 在工程的Options for target ’xxx’的Linker选项卡下指定
打开该文件
先包含了
#include "memory_regions.scat"
该文件定义了存储区块划分的宏,和手册中描述是对应的
引用: /* generated memory regions file - do not edit */
#define RAM_START 0x20000000
#define RAM_LENGTH 0x20000
#define FLASH_START 0x00000000
#define FLASH_LENGTH 0x80000
#define DATA_FLASH_START 0x08000000
#define DATA_FLASH_LENGTH 0x2000
#define OPTION_SETTING_START 0x0100A100
#define OPTION_SETTING_LENGTH 0x100
#define OPTION_SETTING_S_START 0x0100A200
#define OPTION_SETTING_S_LENGTH 0x100
#define ID_CODE_START 0x00000000
#define ID_CODE_LENGTH 0x0
#define SDRAM_START 0x00000000
#define SDRAM_LENGTH 0x0
#define QSPI_FLASH_START 0x60000000
#define QSPI_FLASH_LENGTH 0x4000000
#define OSPI_DEVICE_0_START 0x00000000
#define OSPI_DEVICE_0_LENGTH 0x0
#define OSPI_DEVICE_1_START 0x00000000
#define OSPI_DEVICE_1_LENGTH 0x0
比如SRAM0和On-chip flash,其他的也可以去一一对应查看。
分散加载文件的语法可以参考MDK的帮助文档。
我们下面只讲看一些关键的地方
程序入口与栈设置引用: LOAD_REGION_FLASH FLASH_ORIGIN ALIGN 0x80 LIMITED_FLASH_LENGTH
{
__tz_FLASH_S +0 EMPTY 0
{
}
VECTORS +0 FIXED PADVALUE 0xFFFFFFFF ; maximum of 256 exceptions (256*4 bytes == 0x400)
{
*(.fixed_vectors, +FIRST)
*(.application_vectors)
}
如下语句将fixed_vectors段放在了FLASH_ORIGIN区域的开头,即片上flash的开头处0x00000000
根据CORTEX-M33内核的说明,程序从FLASH启动时,将最开始4字节加载到SP指针,接下来4字节加载到PC,然后跳转到PC执行。
我们搜索fixed_vectors处的代码
正式位于ra-fsp-examplesexample_projectsek_ra4m2sci_uartsci_uart_ek_ra4m2_epkeilrafspsrcbspcmsisDeviceRENESASSourcestartup.c处的
引用: /* Vector table. */
BSP_DONT_REMOVE const exc_ptr_t __Vectors[BSP_CORTEX_VECTOR_TABLE_ENTRIES] BSP_PLACE_IN_SECTION(
BSP_SECTION_FIXED_VECTORS) =
{
(exc_ptr_t) (&g_main_stack[0] + BSP_CFG_STACK_MAIN_BYTES), /* Initial Stack Pointer */
Reset_Handler, /* Reset Handler */
NMI_Handler, /* NMI Handler */
HardFault_Handler, /* Hard Fault Handler */
MemManage_Handler, /* MPU Fault Handler */
BusFault_Handler, /* Bus Fault Handler */
UsageFault_Handler, /* Usage Fault Handler */
SecureFault_Handler, /* Secure Fault Handler */
0, /* Reserved */
0, /* Reserved */
0, /* Reserved */
SVC_Handler, /* SVCall Handler */
DebugMon_Handler, /* Debug Monitor Handler */
0, /* Reserved */
PendSV_Handler, /* PendSV Handler */
SysTick_Handler, /* SysTick Handler */
};
可以看出启动后&g_main_stack[0] + BSP_CFG_STACK_MAIN_BYTES的值会加载到SP,即
&g_main_stack[0] ~&g_main_stack[0] + BSP_CFG_STACK_MAIN_BYTES这一部分就设置为了栈空间,开始时指向栈顶(满递减栈),栈大小是BSP_CFG_STACK_MAIN_BYTES。栈使用后就往低地址使用。
然后加载Reset_Handler到PC跳转到PC即Reset_Handler执行。
所以芯片复位后最开始执行的代码就是Reset_Handler。
这样我们就找到了函数的入口,和栈的设置。
相应的__Vector就是异常向量表。
启动过程分析上面我们找到了代码入口
ra-fsp-examplesexample_projectsek_ra4m2sci_uartsci_uart_ek_ra4m2_epkeilrafspsrcbspcmsisDeviceRENESASSourcestartup.c中的Reset_Handler
我们继续往下看
SystemInit中先进行了使能FPU和设置中断向量表地址VTOR的操作。
后面有很多宏控制的操作,细节可以参考相关资料,下面只讲解关键的。
bsp_clock_init进行了时钟初始化,后面再分析
宏BSP_CFG_C_RUNTIME_INIT的值是1,下面就是根据不同编译器,进行了c运行环境的初始化。
下面是BSS段初始化为0
#if defined(__ARMCC_VERSION)
memset((uint8_t *) &Image$$BSS$$ZI$$Base, 0U, (uint32_t) &Image$$BSS$$ZI$$Length);
下面是DATA段初始化,从加载段复制到运行段
#if defined(__ARMCC_VERSION)
memcpy((uint8_t *) &Image$$DATA$$Base, (uint8_t *) &Load$$DATA$$Base, (uint32_t) &Image$$DATA$$Length);
下面是调用,放在指定段的构造函数
引用: /* Initialize static constructors */
#if defined(__ARMCC_VERSION)
int32_t count = Image$$INIT_ARRAY$$Limit - Image$$INIT_ARRAY$$Base;
for (int32_t i = 0; i < count; i++)
{
void (* p_init_func)(void) =
(void (*)(void))((uint32_t) &Image$$INIT_ARRAY$$Base + (uint32_t) Image$$INIT_ARRAY$$Base);
p_init_func();
}
然后是更新时钟SystemCoreClockUpdate
最后是bsp_irq_cfg中断初始化
bsp初始化bsp_init
时钟初始化分析前面了解到启动代码中bsp_clock_init进行了时钟初始化
使能了CACHE
bsp_clock_freq_var_init根据宏定义的参数初始化参数
这些宏在ra-fsp-examplesexample_projectsek_ra4m2sci_uartsci_uart_ek_ra4m2_epkeilra_cfgfsp_cfgbspbsp_mcu_family_cfg.h中定义
从原理图可以看到外部晶振是24MHz
与#define BSP_CFG_XTAL_HZ (24000000)对应
我们再根据手册的时钟树来看PLL的来源,上面的SCKSCR 就是选择了PLL,往前看
MOSC->PLLCCR.PLSRCSEL选择MOSC进PLL
->PLLCCR.PLIDIV分频
->PLLCCR.PLLMUL倍频
以上都是通过PLLCCR寄存器配置,所以我们搜索PLLCCR
可以看到对应的宏定义如下
#define BSP_CFG_PLL_SOURCE (BSP_CLOCKS_SOURCE_CLOCK_MAIN_OSC) /* PLL Src: XTAL */
#define BSP_CFG_PLL_DIV (BSP_CLOCKS_PLL_DIV_3) /* PLL Div /3 */
#define BSP_CFG_PLL_MUL BSP_CLOCKS_PLL_MUL_25_0 /* PLL Mul x25.0 */
找到对应代码在bsp_clock_init
/* Configure the PLL registers. */
#if 1U == BSP_FEATURE_CGC_PLLCCR_TYPE
R_SYSTEM->PLLCCR = (uint16_t) BSP_PRV_PLLCCR;
那么PLL输出时钟应该是24MHz*25/3 = 200MHz
在手册规定的范围内取了最大值
然后调用bsp_prv_clock_set_hard_reset进行时钟选择和时钟分频设置
这里要根据时钟频率设置flash等待周期
R_SYSTEM->SCKSCR = BSP_CFG_CLOCK_SOURCE; 选择时钟源
#define BSP_CFG_CLOCK_SOURCE (BSP_CLOCKS_SOURCE_CLOCK_PLL) /* Clock Src: PLL */ 配置为来源于PLL
R_SYSTEM->SCKDIVCR = BSP_PRV_STARTUP_SCKDIVCR; 设置各时钟分频值。
各分频值宏定义如下
#define BSP_CFG_ICLK_DIV (BSP_CLOCKS_SYS_CLOCK_DIV_2) /* ICLK Div /2 */
#define BSP_CFG_PCLKA_DIV (BSP_CLOCKS_SYS_CLOCK_DIV_2) /* PCLKA Div /2 */
#define BSP_CFG_PCLKB_DIV (BSP_CLOCKS_SYS_CLOCK_DIV_4) /* PCLKB Div /4 */
#define BSP_CFG_PCLKC_DIV (BSP_CLOCKS_SYS_CLOCK_DIV_4) /* PCLKC Div /4 */
#define BSP_CFG_PCLKD_DIV (BSP_CLOCKS_SYS_CLOCK_DIV_2) /* PCLKD Div /2 */
#define BSP_CFG_FCLK_DIV (BSP_CLOCKS_SYS_CLOCK_DIV_4) /* FCLK Div /4 */
#define BSP_CFG_CLKOUT_DIV (BSP_CLOCKS_SYS_CLOCK_DIV_1) /* CLKOUT Div /1 */
#define BSP_CFG_UCK_DIV (BSP_CLOCKS_USB_CLOCK_DIV_5) /* UCLK Div /5 */
所以ICLK 2分频,即100MHz
也设置为了最大值
最后更新下系统时钟
SystemCoreClockUpdate
void SystemCoreClockUpdate (void)
{
uint32_t clock_index = R_SYSTEM->SCKSCR;
SystemCoreClock = g_clock_freq[clock_index] >> R_SYSTEM->SCKDIVCR_b.ICK;
}
中断处理分析前面已经从分散加载脚本和启动代码分析,VTOR的设置为__Vectors
即中断向量表为__Vectors
上述表定义了CORTEX-M33对应的各种异常。
那么用户中断向量在哪呢
再回过头来看分散加载文件
VECTORS +0 FIXED PADVALUE 0xFFFFFFFF ; maximum of 256 exceptions (256*4 bytes == 0x400)
{
*(.fixed_vectors, +FIRST)
*(.application_vectors)
}
紧挨着fixed_vectors的是application_vectors,那么异常向量后面的就是用户中断,即application_vectors段对应的代码。
搜索application_vectors
#define BSP_SECTION_APPLICATION_VECTORS ".application_vectors"
继续搜索
找到ra-fsp-examplesexample_projectsek_ra4m2sci_uartsci_uart_ek_ra4m2_epkeilra_genvector_data.c
即用户的中断向量表
BSP_DONT_REMOVE const fsp_vector_t g_vector_table[BSP_ICU_VECTOR_MAX_ENTRIES] BSP_PLACE_IN_SECTION(BSP_SECTION_APPLICATION_VECTORS) =
{
[0] = sci_uart_rxi_isr, /* SCI9 RXI (Received data full) */
[1] = sci_uart_txi_isr, /* SCI9 TXI (Transmit data empty) */
[2] = sci_uart_tei_isr, /* SCI9 TEI (Transmit end) */
[3] = sci_uart_eri_isr, /* SCI9 ERI (Receive error) */
};
这个文件是RASC自动生成,当然也可以手动修改。
ra-fsp-examplesexample_projectsek_ra4m2sci_uartsci_uart_ek_ra4m2_epkeilra_genvector_data.h中定义了中断号和中断服务函数的申明。
事件号在ra-fsp-examplesexample_projectsek_ra4m2sci_uartsci_uart_ek_ra4m2_epkeilrafspsrcbspmcura4m2bsp_elc.h定义
使用了宏BSP_PRV_IELS_ENUM进行名字转换。
那么中断是如何初始化化的呢
之前启动代码看到了bsp_irq_cfg
即在该函数实现的
for (uint32_t i = 0U; i < BSP_ICU_VECTOR_MAX_ENTRIES; i++)
{
R_ICU->IELSR = (uint32_t) g_interrupt_event_link_select;
}
即通过IELSR选择哪一个中断对应哪一个中断号
所以g_vector_table和g_interrupt_event_link_select的索引要意义对应。
也就是g_interrupt_event_link_select中定义了事件号,这个是bsp_elc.h中定义的和手册对应,某一事件号可以对应到某个中断号,这个是IELSR寄存器配置的。
IELSR[0]即配置中断号0对应哪个事件(中断源)。
这里和其他STM32等不一样,STM32等厂家芯片中断号和中断源是固定绑定的,而这里是中断号是可以配置绑定某个中断源。 具体可以参考手册的<<13. Interrupt Controller Unit (ICU)>>
中断优先级设置调用的是R_BSP_IrqCfg->NVIC_SetPriority
使能R_BSP_IrqEnable->R_BSP_IrqEnableNoClear
就是调用CMSIS的中断操作接口,与其他CORTEX-M芯片无异。
总结以上对关键的中断,启动过程,时钟配置等进行了讲解,只有了解这些才能更好的进行应用开发。相对于STM32等标准外设库来说,瑞萨的FSP个人感觉层次过于复杂,尤其是外设操作各种回调嵌套,对于初用着不太友好,不过也可以借鉴其一些设计思想。