查看: 737|回复: 1

【瑞萨RA4系列开发板体验】启动过程,中断,时钟初始化等分析

[复制链接]

116

主题

134

帖子

3778

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3778
发表于 2023-1-5 23:04:20 | 显示全部楼层 |阅读模式
作者jf_1137202360


前言
本文基于Demo程序,讲解程序的启动过程。对时钟初始化,中断等关键模块进行讲解分析,了解这些细节后,以便后续进行应用开发。


分散加载文件分析
一般分析程序的启动过程从链接脚本入手,MDK的ARMCC编译器用的是分散加载文件.scat。
在工程的Options for target ’xxx’的Linker选项卡下指定


打开该文件

先包含了
#include "memory_regions.scat"
该文件定义了存储区块划分的宏,和手册中描述是对应的

  1. /* generated memory regions file - do not edit */

  2.                 #define RAM_START  0x20000000

  3.                 #define RAM_LENGTH 0x20000

  4.                 #define FLASH_START  0x00000000

  5.                 #define FLASH_LENGTH 0x80000

  6.                 #define DATA_FLASH_START  0x08000000

  7.                 #define DATA_FLASH_LENGTH 0x2000

  8.                 #define OPTION_SETTING_START  0x0100A100

  9.                 #define OPTION_SETTING_LENGTH 0x100

  10.                 #define OPTION_SETTING_S_START  0x0100A200

  11.                 #define OPTION_SETTING_S_LENGTH 0x100

  12.                 #define ID_CODE_START  0x00000000

  13.                 #define ID_CODE_LENGTH 0x0

  14.                 #define SDRAM_START  0x00000000

  15.                 #define SDRAM_LENGTH 0x0

  16.                 #define QSPI_FLASH_START  0x60000000

  17.                 #define QSPI_FLASH_LENGTH 0x4000000

  18.                 #define OSPI_DEVICE_0_START  0x00000000

  19.                 #define OSPI_DEVICE_0_LENGTH 0x0

  20.                 #define OSPI_DEVICE_1_START  0x00000000

  21.                 #define OSPI_DEVICE_1_LENGTH 0x0
复制代码
比如SRAM0On-chip flash,其他的也可以去一一对应查看。

分散加载文件的语法可以参考MDK的帮助文档。
我们下面只讲看一些关键的地方

程序入口与栈设置
  1. LOAD_REGION_FLASH FLASH_ORIGIN ALIGN 0x80 LIMITED_FLASH_LENGTH

  2. {

  3.   __tz_FLASH_S +0 EMPTY 0

  4.   {

  5.   }



  6.   VECTORS +0 FIXED PADVALUE 0xFFFFFFFF   ; maximum of 256 exceptions (256*4 bytes == 0x400)

  7.   {

  8.     *(.fixed_vectors, +FIRST)

  9.     *(.application_vectors)

  10.   }
复制代码
如下语句将fixed_vectors段放在了FLASH_ORIGIN区域的开头,即片上flash的开头处0x00000000
根据CORTEX-M33内核的说明,程序从FLASH启动时,将最开始4字节加载到SP指针,接下来4字节加载到PC,然后跳转到PC执行。
我们搜索fixed_vectors处的代码
正式位于ra-fsp-examples\example_projects\ek_ra4m2\sci_uart\sci_uart_ek_ra4m2_ep\keil\ra\fsp\src\bsp\cmsis\Device\RENESAS\Source\startup.c处的

  1. /* Vector table. */

  2. BSP_DONT_REMOVE const exc_ptr_t __Vectors[BSP_CORTEX_VECTOR_TABLE_ENTRIES] BSP_PLACE_IN_SECTION(

  3.     BSP_SECTION_FIXED_VECTORS) =

  4. {

  5.     (exc_ptr_t) (&g_main_stack[0] + BSP_CFG_STACK_MAIN_BYTES), /*      Initial Stack Pointer     */

  6.     Reset_Handler,                                             /*      Reset Handler             */

  7.     NMI_Handler,                                               /*      NMI Handler               */

  8.     HardFault_Handler,                                         /*      Hard Fault Handler        */

  9.     MemManage_Handler,                                         /*      MPU Fault Handler         */

  10.     BusFault_Handler,                                          /*      Bus Fault Handler         */

  11.     UsageFault_Handler,                                        /*      Usage Fault Handler       */

  12.     SecureFault_Handler,                                       /*      Secure Fault Handler      */

  13.     0,                                                         /*      Reserved                  */

  14.     0,                                                         /*      Reserved                  */

  15.     0,                                                         /*      Reserved                  */

  16.     SVC_Handler,                                               /*      SVCall Handler            */

  17.     DebugMon_Handler,                                          /*      Debug Monitor Handler     */

  18.     0,                                                         /*      Reserved                  */

  19.     PendSV_Handler,                                            /*      PendSV Handler            */

  20.     SysTick_Handler,                                           /*      SysTick Handler           */

  21. };
复制代码
可以看出启动后&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-examples\example_projects\ek_ra4m2\sci_uart\sci_uart_ek_ra4m2_ep\keil\ra\fsp\src\bsp\cmsis\Device\RENESAS\Source\startup.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);

下面是调用,放在指定段的构造函数

  1. /* Initialize static constructors */

  2. #if defined(__ARMCC_VERSION)

  3.     int32_t count = Image$$INIT_ARRAY$$Limit - Image$$INIT_ARRAY$$Base;

  4.     for (int32_t i = 0; i < count; i++)

  5.     {

  6.         void (* p_init_func)(void) =

  7.             (void (*)(void))((uint32_t) &Image$$INIT_ARRAY$$Base + (uint32_t) Image$$INIT_ARRAY$$Base);

  8.         p_init_func();

  9. }
复制代码
然后是更新时钟SystemCoreClockUpdate

最后是bsp_irq_cfg中断初始化

bsp初始化bsp_init

时钟初始化分析
前面了解到启动代码中bsp_clock_init进行了时钟初始化
使能了CACHE
bsp_clock_freq_var_init根据宏定义的参数初始化参数
这些宏在ra-fsp-examples\example_projects\ek_ra4m2\sci_uart\sci_uart_ek_ra4m2_ep\keil\ra_cfg\fsp_cfg\bsp\bsp_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-examples\example_projects\ek_ra4m2\sci_uart\sci_uart_ek_ra4m2_ep\keil\ra_gen\vector_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-examples\example_projects\ek_ra4m2\sci_uart\sci_uart_ek_ra4m2_ep\keil\ra_gen\vector_data.h中定义了中断号和中断服务函数的申明。

事件号在ra-fsp-examples\example_projects\ek_ra4m2\sci_uart\sci_uart_ek_ra4m2_ep\keil\ra\fsp\src\bsp\mcu\ra4m2\bsp_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个人感觉层次过于复杂,尤其是外设操作各种回调嵌套,对于初用着不太友好,不过也可以借鉴其一些设计思想。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

3

主题

195

帖子

1540

积分

金牌会员

Rank: 6Rank: 6

积分
1540
发表于 2023-3-14 11:25:46 | 显示全部楼层

不错,学习一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

用户排行榜

RA助手

主题: 116帖子:134精华:0

RA_Lance

主题: 92帖子:132精华:9

lugl

主题: 45帖子:134精华:0

xujiwei263

主题: 16帖子:73精华:0

books咦

主题: 11帖子:11精华:2

Juggernaut

主题: 9帖子:95精华:0
快速回复 返回顶部 返回列表