前言 本文基于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个人感觉层次过于复杂,尤其是外设操作各种回调嵌套,对于初用着不太友好,不过也可以借鉴其一些设计思想。
|