前面文章代码可以放到SRAM中来跑,但是这样就非常不方便我们去仿真了。但是keil支持从SRAM仿真。
假如您使用的硬件平台中 BOOT0 和 BOOT1 引脚电平已被固定,设置为内部 FLASH启动,不方便改成 SRAM 方式,可以使用如下方法配置调试选项实现在 SRAM 调试:
(1)、勾选“Verify Code Download”及“Download to FLASH”选项。
(2)见 下图 ,在“ Options for Target->Debug ” 对 话 框 中 取 消 勾 选 “ Load Application at startup”选项。点击“Initialization File”文本框右侧的文件浏览按钮,在弹出的对话框中新建一个名为“Debug_RAM.ini”的文件;
(3) 在 Debug_RAM.ini 文件中输入如代码清单 52-4 中的内容。
/* Debug_RAM.ini: Initialization File for Debugging from Internal RAM */
/******************************************************/
/* This file is part of the uVision/ARM development tools. */
/* Copyright (c) 2005-2014 Keil Software. All rights reserved. */
/* This software may only be used under the terms of a valid, current, */
/* end user licence from KEIL for a compatible version of KEIL software */
/*development tools. Nothing else gives you the right to use this software */
/***************************************************/
FUNC void Setup (void) {
SP = _RDWORD(0x20000000); // 设置栈指针 SP,把 0x20000000 地址中的内容赋值到 SP。
PC = _RDWORD(0x20000004); // 设置程序指针 PC,把 0x20000004 地址中的内容赋值到 PC。
XPSR = 0x01000000; // 设置状态寄存器指针 xPSR
_WDWORD(0xE000ED08, 0x20000000); // Setup Vector Table Offset Register
}
LOAD %L INCREMENTAL // 下载 axf 文件到 RAM
Setup(); //调用上面定义的 setup 函数设置运行环境
//g, main //跳转到 main 函数,本示例调试时不需要从 main 函数执行,注释掉了,程序从启动代码开始执行
(4)Utilities->Setting里修改调试地址
把“Download Function”中的擦除选项配置为“Do not Erase”。这是因为数据写入到内部 SRAM 中不需要像 FLASH 那样先擦除后写入。在本工程中,如果我们不选择“Do not Erase”的话,会因为擦除过程导致下载出错。
“RAM for Algorithm”一栏是指“编程算法”(Programming Algorithm)可使用的RAM 空间,下载程序到 FLASH 时运行的编程算法需要使用 RAM 空间,在默认配置中它的首地址为 0x20000000,即内部 SRAM 的首地址,但由于我们的分散加载文件配置,0x20000000 地址开始的 64KB 实际为虚拟 ROM 空间,实际的 RAM空间是从地址 0x20010000 开始的,所以这里把算法 RAM 首地址更改为本工程中实际作为 RAM 使用的地址。若编程算法使用的 RAM 地址与虚拟 ROM 空间地址重合的话,会导致下载出错。
“Programming Algorithm”一栏中是设置内部 FLASH 的编程算法,编程算法主要描述了 FLASH 的地址、大小以及扇区等信息,MDK 根据这些信息把程序下载到芯片的 FLASH 中,不同的控制器芯片一般会有不同的编程算法。由于 MDK 没有内置 SRAM 的编程算法,所以我们直接在原来的基础上修改它的基地址和空间大小,把它改成虚拟 ROM 的空间信息。
4处RAM for Algorithm是用来在IRAM1区域划分一段空间,用来运行flash下载算法(可理解为一个程序),从而给MCU下载代码。但是这个空间只在下载代码的时候有用,下载完了代码以后,这段空间就可以被你的APP代码(你下载的代码)占用的,相当于释放了。
确认后,代码重新编译就可以了在SRAM里仿真了。
在前面讲解的 STM32 启动代码章节了解到 CM-4 内核在离开复位状态后的工作过程如下,见图 52-1:
(1) 从地址 0x00000000 处取出栈指针 MSP 的初始值,该值就是栈顶的地址。
(2) 从地址 0x00000004 处取出程序指针 PC 的初始值,该值指向复位后应执行的第一条指令。
上述过程由内核自动设置运行环境并执行主体程序,因此它被称为自举过程。
虽然内核是固定访问 0x00000000 和 0x00000004 地址的,但实际上这两个地址可以被重映射到其它地址空间。以 STM32F429 为例,根据芯片引出的 BOOT0 及 BOOT1 引脚的电平情况,这两个地址可以被映射到内部 FLASH、内部 SRAM 以及系统存储器中,不同的映射配置见表 52-1。
内核在离开复位状态后会从映射的地址中取值给栈指针 MSP 及程序指针 PC,然后执行指令,我们一般以存储器的类型来区分自举过程,例如内部 FLASH 启动方式、内部SRAM 启动方式以及系统存储器启动方式。
当芯片上电后采样到 BOOT0 引脚为低电平时, 0x00000000 和 0x00000004 地址被映射到内部 FLASH 的首地址 0x08000000 和 0x08000004。因此,内核离开复位状态
后,读取内部 FLASH 的 0x08000000 地址空间存储的内容,赋值给栈指针 MSP,作为栈顶地址,再读取内部 FLASH 的 0x08000004 地址空间存储的内容,赋值给程序指针PC,作为将要执行的第一条指令所在的地址。具备这两个条件后,内核就可以开始从PC 指向的地址中读取指令执行了。
类似地,当芯片上电后采样到 BOOT0 和 BOOT1 引脚均为高电平时,0x00000000和 0x00000004 地址被映射到内部 SRAM 的首地址 0x20000000 和 0x20000004,内核从SRAM 空间获取内容进行自举。
在实际应用中,由启动文件 starttup_stm32f429_439xx.s 决定了 0x00000000 和0x00000004 地址存储什么内容,链接时,由分散加载文件(sct)决定这些内容的绝对地址,即分配到内部 FLASH 还是内部 SRAM。(下一小节将以实例讲解)
当芯片上电后采样到 BOOT0 引脚为高电平,BOOT1 为低电平时,内核将从系统存储器的 0x1FFF0000 及 0x1FFF0004 获取 MSP 及 PC 值进行自举。系统存储器是一段特殊的空间,用户不能访问,ST 公司在芯片出厂前就在系统存储器中固化了一段代码。因而使用系统存储器启动方式时,内核会执行该代码,该代码运行时,会为 ISP 提供支持(In System Program),如检测 USART1/3、CAN2 及 USB 通讯接口传输过来的信息,并根据这些信息更新自己内部 FLASH 的内容,达到升级产品应用程序的目的,因此这种启动方式也称为 ISP 启动方式。
下面我们以最常规的内部 FLASH 启动方式来分析自举过程,主要理解 MSP 和 PC 内容是怎样被存储到 0x08000000 和 0x08000004 这两个地址的。
见图 52-2 ,这是 STM32F4 默认的启动文件的代码,启动文件的开头定义了一个大小为 0x400 的栈空间,且栈顶的地址使用标号“__initial_sp”来表示;在图下方定义了一个名为“Reset_Handler”的子程序,它就是我们总是提到的在芯片启动后第一个执行的代码。在汇编语法中,程序的名字和标号都包含它所在的地址,因此,我们的目标是把“__initial_sp”和“Reset_Handler”赋值到 0x08000000 和 0x08000004 地址空间存储,这样内核自举的时候就可以获得栈顶地址以及第一条要执行的指令了。在启动代码的中间部分,使用了汇编关键字“DCD” 把“__initial_sp”和“Reset_Handler”定义到了最前面的
地址空间。
在启动文件中把设置栈顶及首条指令地址到了最前面的地址空间,但这并没有指定绝对地址,各种内容的绝对地址是由链接器根据分散加载文件(*.sct)分配的,STM32F429IGT6 型号的默认分散加载文件配置见代码清单 52-1。
//代码清单 52-1 默认分散加载文件的空间配置
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 {
; load region size_region
ER_IROM1 0x08000000 0x00100000 {
; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 UNINIT 0x00030000 {
; RW data
.ANY (+RW +ZI)
}
}
分散加载文件把加载区和执行区的首地址都设置为 0x08000000,正好是内部 FLASH的首地址,因此汇编文件中定义的栈顶及首条指令地址会被存储到 0x08000000 和0x08000004 的地址空间。
类似地,如果我们修改分散加载文件,把加载区和执行区的首地址设置为内部 SRAM的首地址 0x20000000,那么栈顶和首条指令地址将会被存储到 0x20000000 和 0x20000004的地址空间了。