S32K144芯片内存映射图与启动流程分析

1. s32k144为nxp公司采用arm内核(cortex-m4)ip和armv7-m架构集成的soc。
2. 内存映射图
2.1 s32k144芯片内存映射图(memory map)
2.2 cortex-m4内存映射图
code,sram,ram区域都能保存程序。arm系统推荐使用code段来保存执行程序。
3. 编程模型
3.1) cortex-m4两种工作模式:
线程模式(thread model),应用程序正常执行的时候所在的模式,处理器每次reset重启后进入这个模式;异常处理模式(handler model),cpu异常处理的时候进入这个模式,当cpu执行完异常处理程序后会退回到thread model。
3.2) cortex-m4两种特权级:
非特权级状态(unprivileged),软件限制使用msr和mrs指令,不能使用cps指令。不能访问系统定时器,nvic嵌套向量中断控制器和系统控制块。特权级状态(privileged),能访问所有资源,使用所有指令。
3.3) thread模式下,control寄存器控制软件执行在privileged或者unprivileged状态。unpriveleged 软件执行时可以通过 svc 指令进行supervisor call进入privilieged software。
3.4) 处理器使用降栈。thread mode下,control寄存器控制处理器使用main stack还是进程栈process stack。在handler模式下,处理器只使用main stack。
3.5) 核寄存器(core registers)
r0-r12为通用寄存器
特殊寄存器:
r13是堆栈寄存器(stack pointer),在thread mode下,cotrol寄存器的bit[1]控制stack pointer作为main stack pointer(msp 这个是reset 值)还是process stack pointer(psp)
note:reset后,处理器装载0x00000000地址处的四字节值到msp寄存器。也就是说系统启动的时候,0地址处存放的是msp寄存器的值。
r14是连接寄存器(link register),他存储子程序、函数调用、异常处理程序时的返回信息。reset重启时,处理器设置lr寄存器的默认值为0xffffffff。
r15是程序计数器(program counter pc)。存储当前程序地址。reset重启的时候,处理器装载0x00000004地址处的值到pc指针。
程序状态寄存器(program status register),包括应用程序状态寄存器(application program status register apsr)、中断程序状态寄存器(ipsr)、异常程序状态寄存器(epsr)。
4. startup_s32k144.s源代码分析:
4.1 上电启动
根据上面的分析,reset后处理器从0x00000000地址处取四字节值到msp寄存器,也就是取 __stacktop标号的值到msp寄存器。然后处理器装载0x00000004地址处的值(reset_handler标号代表的值)到pc指针,也就是程序跳转到reset_handler标号处开始运行。
__isr_vector: .long __stacktop /* top of stack */ .long reset_handler /* reset handler */  
4.2 关闭cpu全局中断
 通过汇编指令“cpsid i”,关闭 cpu 全局中断的目的是避免启动过程中中断的影响;因为此时中断向量表还未建立好,无法响应外设中断
reset_handler: cpsid i /* mask interrupts */
4.3 清零r1-r12通用寄存器
每次复位后, cpu 内核寄存器的值是随机不确定的,所以需要将其清零。
note 1: 为什么清零r1-r7寄存器用ldr伪指令,而清除r8-r12寄存器是要用mov指令 ?
r1-r7是low registers,r8-r12是hight registers,参考armv7-m architecture手册:
大多数16位指令指令只能访问r0-r7这8个通用寄存器(low registers);只有小部分的指令能访问r8-r15寄存器(high registers)
note2: ldr r,label 和 ldr r,=label的区别
ldr 是arm中的指令,也是伪指令。当用 ldr r, =imd ;r 为寄存器, imd为立即数ldr 是一条伪指令。编译器会根据 立即数的大小,决定用 ldr 指令或者是mov或mvn指令。当imd能用mov或者mvn操作时,就将它翻译成一条mov或mvn指令。当imd大于mov或mvn能够操作的数时,编译器会将imd存在一个内存单元中,然后再用一条ldr指令加载这个内存单元的的值到寄存器中。
ldr r, label 和 ldr r, =label的区别:
ldr r, =label 会把label表示的值加载到寄存器中,而ldr r, label会把label当做地址,把label指向的地址中的值加载到寄存器中。譬如 label的值是 0x8000, ldr r, =label会将 0x8000加载到寄存器中,而ldr r, label则会将内存0x8000处的值加载到寄存器中。
/* init the rest of the registers */ ldr r1,=0 ldr r2,=0 ldr r3,=0 ldr r4,=0 ldr r5,=0 ldr r6,=0 ldr r7,=0 mov r8,r7 mov r9,r7 mov r10,r7 mov r11,r7 mov r12,r7  
4.4 初始化堆栈
arm cortex m 系列 cpu 内核有 msp 和 psp 两个 32-bit 的堆栈,由于中断和异常处理时使用 msp 所以必须在发生中断/异常之前将其初始化,其初始化值来自默认向量表的 0 地址偏移,即 0x0000 地址存放的 4 个字节。
note: __stacktop是s32k144_64_flash.ld链接器脚本中定义标号,也就是0x20007000地址处,在sram中
/* initialize the stack pointer */ ldr r0,=__stacktop mov r13,r0
/* s32k144_64_flash.ld *//* specify the memory areas */memory{ /* flash */ m_interrupts (rx) : origin = 0x00005000, length = 0x00000400 m_flash_config (rx) : origin = 0x00005400, length = 0x00000010 m_text (rx) : origin = 0x00005410, length = 0x0007fbf0 /* sram_l */ m_data (rw) : origin = 0x1fff8000, length = 0x00008000 /* sram_u */ m_data_2 (rw) : origin = 0x20000000, length = 0x00007000}//.../* initializes stack on the end of block */ __stacktop = origin(m_data_2) + length(m_data_2); __stacklimit = __stacktop - stack_size; provide(__stack = __stacktop);  
4.5 系统初始化
在完成了以上堆栈初始化之后, cpu 就可以运行 c 代码了,所以此时通过调用定义在工程 sdk->platform->device->s32k144->startup 目录下的 system_s32k144.c 中的系统初始化函数--systeminit():根据工程配置完成:1)cpu 内核 fpu 配置和使能(如果创建应用工程时选择浮点数运算使用硬件 fpu)
2)关闭看门狗(默认配置)
3)使能 cpu 内核指令缓冲(i-cache)等 mcu 硬件平台配置。
#ifndef __no_system_init /* call the system init routine */ ldr r0,=systeminit blx r0#endif  
我们么有选择使用fpu和cpu内核指令缓冲,所以主要介绍关闭看门狗操作:
4.5.1 配置cnt(watchdog counter register)寄存器
往看门狗模块的cnt(watchdog counter register)寄存器写入0xd928c520(feature_wdog_unlock_value)。为什么要写入这个值?
查看s32芯片手册:
unlock sequence of writing 0xc520 and then 0xd928 for allowing updates to write-once configuration bits .
意思就是看门狗的默认配置是lock的,如果要改变看门狗的配置首先需要解锁:往cnt寄存器中写入
0xd928c520,然后再通过cs(watchdog control and status register)来配置看门狗模块。
4.5.2 配置cs(watchdog control and status register)
主要配置cs寄存器的四个功能
4.5.3 配置看门狗toval寄存器
toval是一个16位的超时寄存器(65535),也就是说是timeout为65535个时钟周期,到点没有喂狗则产生看门狗复位。
/* system_s32k144.c *//*function********************************************************************** * * function name : systeminit * description : this function disables the watchdog, enables fpu * and the power mode protection if the corresponding feature macro * is enabled. systeminit is called from startup_device file. * * implements : systeminit_activity *end**************************************************************************/void systeminit(void){/**************************************************************************/ /* fpu enable*//**************************************************************************/#ifdef enable_fpu /* enable cp10 and cp11 coprocessors */ s32_scb->cpacr |= (s32_scb_cpacr_cp10_mask | s32_scb_cpacr_cp11_mask);#ifdef errata_e6940 /* disable lazy context save of floating point state by clearing lspen bit * workaround for errata e6940 */ s32_scb->fpccr &= ~(s32_scb_fpccr_lspen_mask);#endif#endif /* enable_fpu *//**************************************************************************/ /* wdog disable*//**************************************************************************/#if (disable_wdog) /* write of the wdog unlock key to cnt register, must be done in order to allow any modifications*/ wdog->cnt = (uint32_t ) feature_wdog_unlock_value; /* the dummy read is used in order to make sure that the wdog registers will be configured only * after the write of the unlock value was completed. */ (void)wdog->cnt; /* initial write of wdog configuration register: * enables support for 32-bit refresh/unlock command write words, * clock select from lpo, update enable, watchdog disabled */ wdog->cs = (uint32_t ) ( (1ul << wdog_cs_cmd32en_shift) | (feature_wdog_clk_from_lpo << wdog_cs_clk_shift) | (0u << wdog_cs_en_shift) | (1u pmprot = system_smc_pmprot_value;#endif}  
4.6 ram初始化
接下来,启动文件会调用定义在 sdk->platform->devices 目录下 startup.c 中的init_data_bss()函数完成应用工程运行所需的 ram 初始化。
/* init .data and .bss sections */ ldr r0,=init_data_bss blx r0  
在 startup.c 中通过申明外部变量(extern)的方式,可以引用定义在工程链接文件中的__data_rom、__data_ram、__data_end、__code_ram、__code_rom、__code_end、__bss_start 和__bss_end 符号,获得工程链接结果中.data 段(有初始化值)、 .bss 段(未初始化和初始化值为 0)的全局变量以及重定向到 ram 中运行的.code 段代码/函数在 flash 和ram 中的起始地址和长度(结束地址-开始地址)。 
然后再通过数据指针的方式实现全局变量初始化值和重映射代码从 flash 到 ram 中的拷贝以及.bss 段的清零:具体包括:
1)初始化.data 段
2)初始化.code 段
3)初始化.bss 段
4)将中断向量表从 flash 拷贝到 ram 中并
5)初始化 cpu 系统中断向量偏移地址,使其指向 ram 中新的中断向量表(如果编译目标为 debug,编译结果存储在 flash 中)。 
note 1:  .code段不是代码段,.code段属于m_data域(ram)用来存放重映射到ram中的代码。
note 2:  .text段才是代码段,.text段属于m_text域(rom),存放的就是代码。
note 3:  为什么要将.data .bss 中断向量表拷贝到m_data域(ram),因为.data中保存的是初始化过后的全局变量/静态局部变量.bss段中保存的是未初始化(初始化为0)的全局变量/静态局部变量,在系统运行的时候是需要改变的,而rom是只读的,中断向量表同理。
note 4:  代码是不需要改变的,为什么也有部分的代码需要重映射到ram中执行?-- 因为效率,因为相对于rom来说,ram的数据宽度较大,速度较快。
note 5: 怎么将代码重映射到ram中?
通过上面的分析可知,在 s32k1xx 系列 mcu 的启动过程,会自动将定义在.code 段中的代码/函数从其 flash 储存地址拷贝到 ram 中的运行时地址。 
只有将用户代码分配到 flash 中的编译目标,即使用 s32k1xx_xx_flash.ld 链接文件的编译目标才存在代码重映射。若是将应用工程编译结果代码分配到 ram 的编译目标(使用 s32k1xx_xx_ram.ld 链接文件),其编译的函数/代码本身就是储存在 ram 中的,所以无需重映射 。
将 想 要 重 映 射 的 代 码 / 函 数 通 过 __attribute__((section(.code_ram)))指定到.code_ram 段 由于在应用工程链接文件中已经将用户段.code_ram 放置在了.code 段中,所以,我们只需要在 c 代码中,将想要重映射的代码/函数通过__attribute__ ((section(.code_ram)))指定到.code_ram 段即可。比如下面就是将 main()函数指定到.code_ram 段的具体实现:int __attribute__ ((section(.code_ram))) main(void) 。
在 s32ds ide 应用工程中,一个函数若没有特别指定,其将分配到.text 代码段。
需要注意是关键词-- __attribute__ ((section(.code_ram))) 添加的位置,每个需要指定的函数都要添加这个关键词,因此,可以将其定义为一个宏比如 code_ram 使用:#define code_ram __attribute__ ((section(.code_ram)))然后,再将 code_ram 放在定义的函数名前即可。
/*startup.c*/void init_data_bss(void){ uint32_t n; /* declare pointers for various data sections. these pointers * are initialized using values pulled in from the linker file */ uint8_t * data_ram; uint8_t * code_ram; uint8_t * bss_start; const uint8_t * data_rom, * data_rom_end; const uint8_t * code_rom, * code_rom_end; const uint8_t * bss_end; /* addresses for vector_table and vector_ram come from the linker file */ extern uint32_t __ram_vector_table_size[]; extern uint32_t __vector_table[]; extern uint32_t __vector_ram[]; /* get section information from linker files */#if defined(__iccarm__) /* data */ data_ram = __section_begin(.data); data_rom = __section_begin(.data_init); data_rom_end = __section_end(.data_init); /* code ram */ #pragma section = __code_rom #pragma section = __code_ram code_ram = __section_begin(__code_ram); code_rom = __section_begin(__code_rom); code_rom_end = __section_end(__code_rom); /* bss */ bss_start = __section_begin(.bss); bss_end = __section_end(.bss);#else extern uint32_t __data_rom[]; extern uint32_t __data_ram[]; extern uint32_t __data_end[]; extern uint32_t __code_ram[]; extern uint32_t __code_rom[]; extern uint32_t __code_end[]; extern uint32_t __bss_start[]; extern uint32_t __bss_end[]; /* data */ data_ram = (uint8_t *)__data_ram; data_rom = (uint8_t *)__data_rom; data_rom_end = (uint8_t *)__data_end; /* code ram */ code_ram = (uint8_t *)__code_ram; code_rom = (uint8_t *)__code_rom; code_rom_end = (uint8_t *)__code_end; /* bss */ bss_start = (uint8_t *)__bss_start; bss_end = (uint8_t *)__bss_end;#endif /* check if vector_table copy is needed */ if (__vector_ram != __vector_table) { /* copy the vector table from rom to ram */ for (n = 0; n vtor = (uint32_t)__vector_ram; } else { /* point the vtor to the position of vector table */ s32_scb->vtor = (uint32_t)__vector_table; } /* copy initialized data from rom to ram */ while (data_rom_end != data_rom) { *data_ram = *data_rom; data_ram++; data_rom++; } /* copy functions from rom to ram */ while (code_rom_end != code_rom) { *code_ram = *code_rom; code_ram++; code_rom++; } /* clear the zero-initialized data section */ while(bss_end != bss_start) { *bss_start = 0; bss_start++; }}  
4.7 打开cpu全局中断
在完成 ram 初始化和中断向量表初始化后,就可以打开 cpu 全局中断,响应外设中断了;打开 arm cortex m 系列 cpu 内核的全局中断通过汇编语句--“cpsie i”完成。
cpsie i /* unmask interrupts */  
4.8 跳转到应用程序 main()函数在完成以上准备工作之后,启动过程的最后一步是跳转到应用程序 main()函数.
bl main  
5. 相关应用
从bootloader跳转到application的时候,要设置应用中r13寄存器中的堆栈地址,然后直接跳转到app中的reset_handler执行:bootup_application(0x5000, 0x5004)
同时需要修改app程序中链接器脚本中的连接地址
memory{ /* flash */ m_interrupts (rx) : origin = 0x00005000, length = 0x00000400 m_flash_config (rx) : origin = 0x00005400, length = 0x00000010 m_text (rx) : origin = 0x00005410, length = 0x0007fbf0 /* sram_l */ m_data (rw) : origin = 0x1fff8000, length = 0x00008000 /* sram_u */ m_data_2 (rw) : origin = 0x20000000, length = 0x00007000}
void bootup_application(uint32_t appentry, uint32_t appstack){ static void (*jump_to_application)(void); static uint32_t stack_pointer; //shutdown_drivers(); jump_to_application = (void (*)(void))appentry; stack_pointer = appstack; s32_scb->vtor = app_image_start; //__set_msp(stack_pointer); __asm volatile (msr msp, %0 : : r (stack_pointer) : sp); //__set_psp(stack_pointer); __asm volatile (msr psp, %0 : : r (stack_pointer) : sp); jump_to_application();}


如何对状态指示灯进行编程
如何运用大数据的优势推进社会治理现代化进程?
关于人脸识别技术的讨论,它是否真的足够安全
高性能与低功耗兼具的ADC产品汇聚
澎湃微PT32F003/F005的详细介绍
S32K144芯片内存映射图与启动流程分析
Nordic联手ACS 加快智能家居和其它无线产品的开发
第四届“深圳光电显示周”五月将在深举行
AMC96L-E4/KC多功能三相可编程电力测控仪表
晶丰明源 品牌焕新——元启新程,焕芯启航
iphone8什么时候上市?iphone8最新消息:十周年良心巨制iPhone8,预测将达到史上最贵
vivo的首款全面屏手机vivox20今晚正式发布:vivox20外观、配置、价格提前看,鹿晗邀请你一起看发布会
组织研磨仪的具体操作步骤是怎样的
除工业机器人和纺织机械产业外 史陶比尔还提供先进的连接器解决方案
IGBT激励电路
什么是分层架构的依据与原则?本文告诉你答案!
声波电动牙刷哪个牌子的最好?品牌电动牙刷排行推荐
基于C语言实现环形缓冲区/循环队列
如何采用2MHz单芯片降压-升压DC-DC转换器和LED驱动器消除PCB空间受限的困扰
2大目标10大任务 智造规划引领产业新风尚