44.1 在 RAM 中调试代码
把代码下载到 RAM 中调试有如下优点:
• 下载程序非常快。RAM 存储器的写入速度比在内部 FLASH 中要快得多,且没有擦除过程,因此在 RAM 上调试程序时程序几乎是秒下的,对于需要频繁改动代码的调试过程,能节约很多时间,省去了烦人的擦除与写入 FLASH 过程。另外,STM32 的内部 FLASH 可擦除次数为 1 万次,虽然一般的调试过程都不会擦除这么多次导致 FLASH 失效,但这确实也是一个考虑使用 RAM 的因素。
• 不改写内部 FLASH 的原有程序。
• 对于内部 FLASH 被锁定的芯片,可以把解锁程序下载到 RAM 上,进行解锁。相对地,把代码下载到 RAM 中调试有如下缺点:
• 存储在 RAM 上的程序掉电后会丢失,不能像 FLASH 那样保存。
• 若使用 STM32 的内部 SRAM 存储程序,程序的执行速度与在 FLASH 上执行速度无异,但SRAM 空间较小。
• 若使用外部扩展的 SRAM 存储程序,程序空间非常大,但 STM32 读取外部 SRAM 的速度比读取内部FLASH 慢,这会导致程序总执行时间增加,因此在外部 SRAM 中调试的程序无法完美仿真在内部 FLASH 运行时的环境。另外,由于 STM32 无法直接从外部 SRAM 中启动且应用程序复制到外部 SRAM 的过程比较复杂 (下载程序前需要使 STM32 能正常控制外部 SRAM),所以在很少会在 STM32 的外部 SRAM 中调试程序。
44.2 STM32 的启动方式
STM32 启动代码章节了解到 CM-3 内核在离开复位状态后的工作过程如下,见图复位序列 :
(1) 从地址 0x00000000 处取出栈指针 MSP 的初始值,该值就是栈顶的地址。
(2) 从地址 0x00000004 处取出程序指针 PC 的初始值,该值指向复位后应执行的第一条指令。
上述过程由内核自动设置运行环境并执行主体程序,因此它被称为自举过程。
虽然内核是固定访问 0x00000000 和 0x00000004 地址的,但实际上这两个地址可以被重映射到其它地址空间。以 STM32F103 为例,根据芯片引出的 BOOT0 及 BOOT1 引脚的电平情况,这两个地址可以被映射到内部 FLASH、内部 SRAM 以及系统存储器中,不同的映射配置见表 BOOT 引脚的不同设置对 0 地址的映射 。
内核在离开复位状态后会从映射的地址中取值给栈指针 MSP 及程序指针 PC,然后执行指令,我们一般以存储器的类型来区分自举过程。
(1) 内部 FLASH 启动方式
(2) 内部 SRAM 启动方式
(3) 系统存储器启动方式
44.3 内部 FLASH 的启动过程
我们以最常规的内部 FLASH 启动方式来分析自举过程,主要理解 MSP 和 PC 内容是怎样被存储到 0x08000000 和 0x08000004 这两个地址的。
在启动文件中把设置栈顶及****首条指令地址到了最前面的地址空间,但这并没有指定绝对地址,各种内容的绝对地址是由链接器根据分散加载文件 (*.sct) 分配的,STM32F103 的默认分散加载文件配置见代码清单:SRAM-1
分散加载文件把加载区和执行区的首地址都设置为 0x08000000,正好是内部 FLASH 的首地址,因此汇编文件中定义的栈顶及首条指令地址会被存储到 0x08000000 和 0x08000004 的地址空间。
类似地,如果我们修改分散加载文件,把加载区和执行区的首地址设置为内部 SRAM 的首地址0x20000000,那么栈顶和首条指令地址将会被存储到 0x20000000 和 0x20000004 的地址空间了。
为了进一步消除疑虑,我们可以查看反汇编代码及 map 文件信息来了解各个地址空间存储的内容,见图从反汇编代码及 map 文件查看存储器的内容
总结:若希望在内部 SRAM 中调试代码,需要设置启动方式为从内部 SRAM 启动,修改分散加载文件控制代码空间到内部 SRAM 地址以及把生成程序下载到芯片的内部 SRAM 中。
44.4 实验:在内部 SRAM 中调试代码
4.1 硬件设计
实验板左侧有引出 STM32 芯片的 BOOT0 和 BOOT1 引脚,可使用跳线帽设置它们的电平从而控制芯片的启动方式,它支持从内部 FLASH 启动、系统存储器启动以及内部 SRAM 启动方式。
实验在 SRAM 中调试代码,因此把BOOT0 和 BOOT1 引脚都用跳线帽连接到 3.3V,使芯片从 SRAM 中启动
4.2 软件设计
4.2.1 主要步骤
(1) 在原工程的基础上创建一个调试版本;
(2) 修改分散加载文件,使链接器把代码分配到内部 SRAM 空间;
(3) 添加宏修改 STM32 的向量表地址;
(4) 修改仿真器和下载器的配置,使程序能通过下载器存储到内部 SRAM;
(5) 根据使用情况选择是否需要使用仿真器命令脚本文件 *.ini;
(6) 尝试给 SRAM 下载程序或仿真调试。
4.2.2 创建工程的调试版本
在 SRAM 中运行的代码一般只是用于调试,调试完毕后,在实际生产环境中仍然使用在内部 FLASH 中运行的代码,因此我们希望能够便捷地在调试版和发布版代码之间切换。
当需要切换工程版本时,点击 MDK 工程名的下拉菜单可选择目标工程,在不同的工程中,所有配置都是独立的,例如芯片型号、下载配置等等,但如果两个工程共用了同一个文件,对该文件的修改会同时影响两个工程,例如这两个工程都使用同一个 main 文件,我们在 main 文件修改代码,两个工程都会被修改。
4.2.3 配置分散加载文件
本工程的分散加载只使用手动编辑的 sct 文件配置,不使用 MDK 的对话框选项配置,在“Options for Target->linker”的选项见图使用新建的 SRAM_ 调试 _sct 文件
在具体的应用中,虚拟 ROM 及 RW 区域的大小可根据自己的程序定制,配置完毕编译工程后可在 map 文件中查看具体的空间地址分配。
4.2.4 配置中断向量表
由于本工程的分散加载文件配置,在启动文件中定义的中断向量表会被分配到 SRAM 空间,所以我们要定义这个宏,使得 SystemInit 函数修改 VTOR 寄存器,向内核指示向量表被存储到内部SRAM 空间
配置完成后重新编译工程,即可生成存储到 SRAM 空间地址的代码指令。
4.2.5 修改 FLASH 下载配置
得到 SRAM 版本的代码指令后,为了把它下载到芯片的 SRAM 中,还需要修改下载器的配置,见图下载配置
这个配置对话框原本是用于设置芯片内部 FLASH 信息的,当我们点击 MDK 的下载(LOAD)或(调试)按钮时,它会从此处加载配置然后下载程序到 FLASH 中,而在上图中我们把它的配置修改成下载到内部 SRAM 了,各个配置的解释如下:
• 把“Download Function”中的擦除选项配置为“Do not Erase”。这是因为数据写入到内部SRAM 中不需要像 FLASH 那样先擦除后写入。在本工程中,如果我们不选择“Do not Erase”的话,会因为擦除过程导致下载出错。
•“RAM for Algorithm”一栏是指“编程算法”(Programming Algorithm) 可使用的 RAM 空间,下载程序到 FLASH 时运行的编程算法需要使用 RAM 空间,在默认配置中它的首地址为0x20000000,即内部 SRAM 的首地址,但由于我们的分散加载文件配置,0x20000000 地址开始的 32KB 实际为虚拟 ROM 空间,实际的 RAM 空间是从地址 0x20008000 开始的,所以这里把算法 RAM 首地址更改为本工程中实际作为 RAM 使用的地址。若编程算法使用的 RAM 地址与虚拟 ROM 空间地址重合的话,会导致下载出错。
•“Programming Algorithm”一栏中是设置内部 FLASH 的编程算法,编程算法主要描述了FLASH 的地址、大小以及扇区等信息,MDK 根据这些信息把程序下载到芯片的 FLASH中,不同的控制器芯片一般会有不同的编程算法。由于 MDK 没有内置 SRAM 的编程算法,所以我们直接在原来的基础上修改它的基地址和空间大小,把它改成虚拟 ROM 的空间信息。
注意: 非常遗憾的是,我们在各种平台做了大量测试,发现程序虽然被下载到 SRAM 了,但复位后 STM32 的程序 PC 指针和 SP 指针却莫名奇妙地指向非预设的 ResetHandler 及栈顶位置,导致程序无法正常运行(测试时,均有使用电压表直接测量 STM32 芯片 BOOT 引脚的电压确认它们都是高电平,后面小节有给出测试得的不正常情况下,PC 和 SP 指针的值)。另外,当使用 STM32F429 芯片时,根据前面介绍的理论作类似的配置,程序下载到 SRAM 后,完全能正常运行,而在 STM32F1 系列各型号的芯片上,均无法实现。
解决方案:由于直接下载到芯片上复位运行的方式无法正常工作,所以下面介绍另一种折衷的解决办法,即
使用仿真器强制设置 PC 指针及 SP 指针。
4.2.6 指定 PC 及 SP 指针值的仿真器配置
由于实际应用在 SRAM 启动方式时 PC 和 SP 指针加载不正常,因此需要使用仿真器辅助修改 PC 及 SP 指
针,然后在仿真器的控制下在 SRAM 中调试运行,即在 MDK 中使用调试按钮 debug时进行的硬件在线调试、单步运行等功能,该功能与在 FLASH 中的硬件调试一样,但针对本实验的在SRAM 运行环境,需要对配置进行修改。配置如下:
(1)添加“Download options”配置
(2)添加仿真器加载指令。
/******************************************************************************/
/* 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。
_WDWORD(0xE000ED08, 0x20000000); // Setup Vector Table Offset Register
}
LOAD %L INCREMENTAL // 下载axf文件到RAM
Setup(); //调用上面定义的setup函数设置运行环境
//g, main //跳转到main函数,本示例调试时不需要从main函数执行,注释掉了,程序从启动代码开始执行
4.2.7 关于复位后 PC 和 SP 指针的调试情况
为了更好地了解 RAM 调试的运行情况,在仿真时,可以点击 MDK仿真环境左栏底部的“Registers”按钮查看内核寄存器的情况。
当仿真器配置使用“Debug_RAM.ini”文件强制设置 SP 和 PC 寄存器的加载地址时,它们都获取到了正常的栈顶和 ResetHandler 的地址值,见图初始时的 SP 和 PC 指针正常 。
从图中的 map 文件可以了解到,Reset_Handler 程序存储的地址值为 0x20000145(PC 指针加载时会减 1,即从 0x20000144 可加载到正常的 ResetHandler 代码),栈顶指针 __initial_sp 的地址值为0x20008400。
查看图中左栏的 PC 与 SP 寄存器的值,正好是 0x20000144 和 0x20008400,也正因如此,使用这种方式调试时,SRAM 中的程序能正常运行。
反观后面的图初始时的 SP 和 PC 指针不正常 ,它呈现的是当仿真器不使用“Debug_RAM.ini”文件强制设置 SP 和 PC 寄存器的加载地址,或者点击复位键时的情况,这时 PC 寄存器的值为0x200001e0,经查询该地址存储的是 GPIO_Init 函数的部分指令,而 SP 寄存器的值为 0x20005000,指向未知的存储区域。由于 PC 寄存器存储的地址不是 ResetHandler,当程序运行时,就无法按照预定的设计运行,导致出错。
由于存在这个无法解决的问题,在 STM32F1 系列的芯片只能将就使用这种调试方式来使程序在SRAM 中运行
main.c
/**
******************************************************************************
* @file main.c
* @author fire
* @version V1.0
* @date 2013-xx-xx
* @brief 测试led
******************************************************************************
* @attention
*
* 实验平台:野火 F103-指南者 STM32 开发板
* 论坛 :http://www.firebbs.cn
* 淘宝 :https://fire-stm32.taobao.com
*
******************************************************************************
*/
#include "stm32f10x.h"
#include "bsp_led.h"
#define SOFT_DELAY Delay(0x0FFFFF);
void Delay(__IO u32 nCount);
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
while (1)
{
LED1_ON; // 亮
SOFT_DELAY;
LED1_OFF; // 灭
LED2_ON; // 亮
SOFT_DELAY;
LED2_OFF; // 灭
LED3_ON; // 亮
SOFT_DELAY;
LED3_OFF; // 灭
/*轮流显示 红绿蓝黄紫青白 颜色*/
LED_RED;
SOFT_DELAY;
LED_GREEN;
SOFT_DELAY;
LED_BLUE;
SOFT_DELAY;
LED_YELLOW;
SOFT_DELAY;
LED_PURPLE;
SOFT_DELAY;
LED_CYAN;
SOFT_DELAY;
LED_WHITE;
SOFT_DELAY;
LED_RGBOFF;
SOFT_DELAY;
}
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/
SRAM_调试.sct
;本文件用于备份sct文件的配置,要使用时把本文件名改为“SRAM_调试.sct”
;然后复制到Output目录即可。
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x20000000 0x00008000 { ; load region size_region
ER_IROM1 0x20000000 0x00008000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20008000 0x00008000 { ; RW data
.ANY (+RW +ZI)
}
}
其他配置: