基本信息

芯片基本组成:
在这里插入图片描述

  • 电源部分: DC/DC LDO 晶振电路;
  • 处理器部分: MCU采用双核Cortex-M4F,包含Flash和外部存储电路; Flash大小为4MB,
  • 蓝牙和音频:包含数字音频CODEC,双模蓝牙(蓝牙协议栈 2.1+5.0 )

芯片内部架构
在这里插入图片描述

  • MCU部分:Cortex-M4F是支持浮点和硬件DSP指令计算的,包含指令和数据的Cache; 支持安全启动、包含片上的ROM,用于量产是代码的烧录; 有一个看门狗用于系统崩溃时的恢复。
  • 内存部分:
    • 内部ROM大小为850KB,用于系统最关键的常量和代码,起始地址为0x00000000
    • 内部SRAM大小为830KB,加上64KB的大小用于蓝牙,作用为运行时产生的临时数据,比如变量、堆栈等;起始地址为0x20000000;
    • 串行Flash大小为4MB,用于存储用户应用程序代码;
  • 系统外设:
    • Clock Management Unit (CMU)
    • 串口/I2C/GPIO/PWM/TIMER/USB/RTC
    • Sony/Philips Digital Interface Format (SPDIF)in/out
  • 电源管理PMU
    • 芯片的几种低功耗模式:待机模式、掉电模式、睡眠模式;
    • 包括7路LDO,分别是LDO_MEM、LDO_USB、LDO_VIO用于通用IO接口、LDO_CODEC、LDO_ANA用于模拟电路、LDO_CORE用于数字逻辑;
    • 多路独立降压结构:一路用于ARM Cortex-M4F ,一路用于射频、一路用于音频;
  • 音频部分:包括ADC通道、DAC通道、低功耗语音检测单元、I2S接口、SPDIF接口;

固件代码分布

通过连接脚本可以将相同的代码段连接到一起,通过查阅对应的连接脚本,可以大致分析出代码在flash中的分布情况,比如有什么段,这个段放在什么位置,也可以查出在RAM中栈空间的大小和范围。

在链接脚本中定义的空间分布:

MEMORY
{
 ROM (rx) : ORIGIN = 0x00000000, LENGTH = 0x0000C000
 FLASH (r) : ORIGIN = (0x3C000000 + 0), LENGTH = (0x400000 - ((0x3C000000 + 0) - 0x3C000000))  大小4MB
 FLASH_NC (r) : ORIGIN = (((0x3C000000 + 0)) - 0x3C000000 + 0x3C000000), LENGTH = (0x400000 - ((0x3C000000 + 0) - 0x3C000000))
 FLASHX (rx) : ORIGIN = (((0x3C000000 + 0)) - 0x3C000000 + 0x0C000000), LENGTH = (0x400000 - ((0x3C000000 + 0) - 0x3C000000))
 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x000C0000 - 0x16000
 RAMX (rx) : ORIGIN = 0x00200000, LENGTH = 0x000C0000 - 0x16000
 FRAMX (rwx) : ORIGIN = 0x00200000 + 0x000C0000 - 0x16000, LENGTH = 0x16000
}

存储内存映射:
在这里插入图片描述

  • BES2300芯片内存有SRAM,FLASH,BOOT ROM三种器件,其中FLASH用于存储代码,类似于电脑中的硬盘;SRAM在代码执行时存储数据,相当于电脑中的内存;FLASH在芯片中对应的地址空间为0x3C000000-0X3C400000,SRAM在对应的地址空间为0x20000000-0x200AA000;BOOT ROM属于内部的存储器件,对外不开放。
  • FLASH中存储的信息大致可以划分为5部分,分别是:
    • boot:包括文本、数据等信息;
    • sram段:包括对应的文本和数据信息,这部分代码在执行的过程中会拷贝至SRAM中;
    • overlay。包括数据和文本;
    • 主程序:有数据和文本,只读数据,这部分是代码的主体;
    • 其他:用户数据,定制化的参数等信息;
    • 注意上述信息中虽然有bss段的信息,但是不占用flash的内存空间,占用flash空间的只有data和text;
  • SRAM中信息基本上与flash相对应,主要有以下几部分:
    • 中断向量表+用户数据+重启数据;
    • boot、sram的bss段,data段和textx段,说明sram、boot相关的代码是运行在SRAM中的;
    • 主程序段的bss/data;
    • 关键的堆栈信息,堆用于malloc的内存分别,起始地址在主程序bss段之后,地址由下向上增长;栈位于SRAM的顶部,地址从高向下增长;
  • 为了实现程序文本段的取址执行,flash中的文本段通过地址映射的方式映射到的CODE区域;这样CPU再CODE区域取地址时,实际上取得是存储在flash中的代码,这几个区域分别是:
    • boot_text_flash
    • boot_text_sram
    • sram_text
    • fast_text_sram
    • text
  • 地址映射:这里映射的代码主要是文本段的代码,在SRAM运行的代码映射到RAMX,在flash中运行的代码映射到FLASHX;
  • 上述中具体的程序函数地址每次编译后可能不同,仅供位置排序参考。

启动流程

芯片的启动主要分为三个阶段,分别为Bootload阶段、系统阶段和应用阶段。这三个阶段是逐步完成的,Bootload执行完毕后会进入系统层,将使用的操作系统启动,最后操作系统启动后,进入应用层,创建一系列的用于程序。下面简单了解下这三个阶段的差异和所做的事情。

Bootloader
  • msp初始化

​ 主堆栈寄存器初始化,指向RAM区域的顶部_StackTop。_StackTop在链接脚本中定义:

251     __rom_StackTop = ORIGIN(RAM) + LENGTH(RAM);
252     __rom_StackLimit = __rom_StackTop - SIZEOF(.rom_stack_dummy);
253     PROVIDE(__rom_stack = __rom_StackTop);
254 #ifndef NOSTD
255     __StackTop = __rom_StackTop;
256     PROVIDE(__stack = __rom_StackTop);
257 #endif

​ 链接脚本中定义的LENGTH(RAM)信息如下:

​ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x000C0000 - 0x16000

​ RAM区域的起始地址为0x20000000,这一点在ARM Cortex核心中有定义,LENGTH的大小为RAM的总容量768KB减去88KB,__StackTop的数据实际为0x200AA000,保留的88KB可能为特殊用途设计。

  • 中断向量初始化

    对中断向量表进行初始化,这里的中断数量一共是16+48个中断,其中16个中断为Cortex核心定义的中断,48个中断是MCU外设的中断。初始化过程中会将Cortex核心定义的中断处理函数定义为fault_handlers数组中的函数。其他48个中断定义为NVIC_default_handler函数,最后将中断向量表的地址写入到VTOR寄存器。

    74 static const uint32_t BOOT_RODATA_LOC fault_handlers[NVIC_USER_IRQ_OFFSET] = {
     75 #ifdef ROM_BUILD
     76     (uint32_t)__rom_stack,
     77 #else
     78     (uint32_t)__stack,
     79 #endif
     80     (uint32_t)Reset_Handler,
     81     (uint32_t)NMI_Handler,
     82     (uint32_t)HardFault_Handler,
     83     (uint32_t)MemManage_Handler,
     84     (uint32_t)BusFault_Handler,
     85     (uint32_t)UsageFault_Handler,
     86     (uint32_t)NVIC_default_handler,
     87     (uint32_t)NVIC_default_handler,
     88     (uint32_t)NVIC_default_handler,
     89     (uint32_t)NVIC_default_handler,
     90     (uint32_t)SVC_Handler,
     91     (uint32_t)DebugMon_Handler,
     92     (uint32_t)NVIC_default_handler,
     93     (uint32_t)PendSV_Handler,
     94     (uint32_t)SysTick_Handler,
     95 };
    
     56 static void NAKED BOOT_TEXT_FLASH_LOC NVIC_default_handler(void)
     57 {
     58     do { asm volatile("nop \n nop \n nop \n nop"); } while (1);
     59 }
    
  • 启动初始化BootInit

    首先是使能Cache Buffer,包括I-Cache D-cache。操作以下寄存器实现,然后是初始化全局偏移量表(GOT, Global Offset Table)的基址寄存器;

    #define ICACHE_CTRL_BASE            0x07FFE000
    
    void BOOT_TEXT_FLASH_LOC GotBaseInit(void)
    {
        asm volatile("ldr r9, =__got_info_start");
    }
    
    

    __got_info_start在编译后实际的数值为0x3c000c60。GOT 的作用:当程序以位置无关方式编译时,访问全局变量和函数需要先通过 GOT 查找其绝对地址。GOT 是一张表,记录了所有全局符号的运行时地址。

  • 启动代码搬运和bss段清零

    • 代码搬运实际上是将存储在flash上的一段代码搬运到ram上,这么做的好处是可以加快代码的执行。搬运的源地址为__boot_sram_start_flash__,目的地址为__boot_sram_start__,大小为SIZEOF(.boot_text_sram) + SIZEOF(.boot_data_sram)。

    • 具体搬运过去的代码有hal_timer.o hal_cmu_common.o hal_chipid.o hal_cache.o hal_cmu_best2300.o pmu_best2300.o等,除此之外,还搬运了启动的数据,也就是boot_data。

    • bss段清零指的是将RAM中bss的段置0;

  • cmu配置

    void BOOT_TEXT_FLASH_LOC hal_cmu_setup(void)
    {
        hal_iomux_set_default_config();  /*复用关系初始化 */
        hal_cmu_module_init_state();
        hal_cmu_timer_select_slow();
        hal_sys_timer_open();			//TIMER1_IRQn
        hal_hw_bootmode_init();			//启动模式初始化
        //sys flash mem ispi时钟配置,这三个函数闭源
        hal_cmu_sys_set_freq(HAL_CMU_FREQ_26M);
        hal_cmu_flash_set_freq(HAL_CMU_FREQ_26M);
        hal_cmu_mem_set_freq(HAL_CMU_FREQ_26M);
        hal_cmu_ispi_set_freq(HAL_CMU_PERIPH_FREQ_26M);
        //与芯片和时钟相关
        hal_analogif_open();
        hal_chipid_init();
        hal_cmu_osc_x2_enable();
        hal_cmu_osc_x4_enable();
        hal_cmu_pll_enable(HAL_CMU_PLL_AUD, HAL_CMU_PLL_USER_SYS);
        hal_cmu_flash_select_pll(HAL_CMU_PLL_AUD);
        hal_cmu_sys_select_pll(HAL_CMU_PLL_AUD);
        hal_cmu_uart0_set_freq(HAL_CMU_PERIPH_FREQ_26M);
        hal_cmu_i2c_set_freq(HAL_CMU_PERIPH_FREQ_26M);
        hal_cmu_lpu_init(HAL_CMU_LPU_CLK_26M, LPU_TIMER_US(3000), 0)hal_sysfreq_req(HAL_SYSFREQ_USER_APP_0, freq);
        hal_norflash_init(); 
    }
    

    上面函数主要做的事情是对外设的时钟进行配置;

  • 将主程序中data段拷贝到SRAM空间下

        ldr    r1, =__etext					// 0x3c0d2c98
        ldr    r2, =__data_start__			//0x2000a23c	
        ldr    r3, =__data_end__
    
    .L_loop1:
        cmp    r2, r3
        ittt    lt
        ldrlt    r0, [r1], #4
        strlt    r0, [r2], #4
        blt    .L_loop1
    
  • SystemInit函数

    void BOOT_TEXT_FLASH_LOC SystemInit (void)
    {
        SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk; /* 设置为1时,执行除零操作会触发 HardFault;*/
        SCB->CCR |= SCB_CCR_UNALIGN_TRP_Msk;/*设置为1时,任何未对齐的内存访问都会触发 HardFault */
        SCB->SHCSR &= ~SCB_SHCSR_USGFAULTENA_Msk;/*禁用 Usage Fault */		
        SCB->SHCSR &= ~SCB_SHCSR_BUSFAULTENA_Msk;/*禁用 Bus Fault*/
        SCB->SHCSR &= ~SCB_SHCSR_MEMFAULTENA_Msk; /*禁用 Memory Management Fault */
    }
    

    这部分主要是操作Cortex-M的SCB空间寄存器,分别为CCR和SHCSR;DIV_0_TRPUNALIGN_TRP 被启用,意味着除零和非对齐访问会被视为错误;而 SHCSR 的三个 ENA 位都被清零,则意味着所有 Bus Fault、MemManage Fault、Usage Fault 都将“跳过”自己专属的处理,直接上溢给 HardFault 处理。
    在这里插入图片描述
    在这里插入图片描述

  • 跳转到_start函数

    _start 是整个程序的入口点。这个符号在 C 运行时库(crt0.o)定义,源码不对外开放。

    arm-none-eabi/lib/armv7e-m/fpu/crt0.o,在 main() 函数执行前,搭建好 C 语言运行所需的基础环境。

系统
__attribute__((naked)) void software_init_hook (void) {
  __asm (
    ".syntax unified\n"
    ".thumb\n"
    "movs r0,#0\n"
    "movs r1,#0\n"
    "mov  r4,r0\n"
    "mov  r5,r1\n"
    "ldr  r0,= __libc_fini_array\n"
    "bl   atexit\n"
    "bl   __libc_init_array\n"
    "mov  r0,r4\n"
    "mov  r1,r5\n"
    "bl   osKernelInitialize\n"
    "bl   set_main_stack\n"
    "ldr  r0,=os_thread_def_main\n"
    "movs r1,#0\n"
    "bl   osThreadCreate\n"
    "bl   osKernelStart\n"
    "bl   exit\n"
  );
}

software_init_hooknewlib C 库中定义的一个弱符号 (Weak Symbol),它是一个在 C/C++ 程序启动过程中、main 函数执行之前被调用的初始化钩子 (Hook) 函数。所做的事情就是将RTX操作系统启动,创建主线程os_thread_def_main,RTX开始调度,此时CPU转为去执行主线程。主线程的定义如下:

extern int main (void);
osThreadDef_t os_thread_def_main = {(os_pthread)main, osPriorityNormal, 0, NULL};

这里的main函数对应platform/main/main.cpp中的main函数:

int main(int argc, char *argv[])

之后便是应用层的逻辑了。

应用

应用层主要会创建一些与蓝牙、音频等相关的线程,线程之间相互调用,具体创建的什么线程,线程之间通过什么方式通信放在后面专门的章节介绍。这部分的逻辑是先创建一个主线程,又这个主线程完成其他部分的初始化操作。

总结

以上就是基本的章节内容,首先是介绍了BES2300芯片的组成,这款芯片内部的组成,包含什么外设,使用的CPU是什么内核这样;然后详细分析了在flash和ram中关于代码是如何进行分布的,这里有一点不同的是在代码中为了保证访问的速率或者低功耗的涉及,创建了一个boot和sram的代码段。最后是分析了芯片的启动流程,主要是Bootload层和系统层。Bootload会去初始化一些外设,最主要的是时钟,这部分的代码位于boot段,在执行之前,已经拷贝到了sram中,系统层应用的是RTX实时操作系统。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐