[STM32 - 野火] - - - 固件库学习笔记 - - - 十六.在SRAM中调试代码

发布于:2025-02-20 ⋅ 阅读:(21) ⋅ 点赞:(0)

一、简介

在RAM中调试代码是一种常见的嵌入式开发技术,尤其适用于STM32等微控制器。它的核心思想是将程序代码和数据加载到微控制器的内部RAM(SRAM)中运行,而不是运行在Flash存储器中。这种方法在开发过程中具有显著的优势,但也有一些限制。

1.1 为什么要在RAM中调试代码?

  • 1、保护Flash存储器

    • Flash存储器的寿命有限:Flash存储器的擦写次数是有限的(例如,STM32的Flash通常支持10,000次擦写)。频繁的调试和代码烧录可能会加速Flash的磨损。

    • 减少擦写次数:将代码运行在RAM中可以避免对Flash的频繁擦写,从而延长Flash的使用寿命。

  • 2、提高调试效率

    • 快速修改和测试:在RAM中运行代码时,可以快速修改代码并重新加载,而无需擦除和重新烧录Flash。这大大加快了调试速度。

    • 动态调试:RAM中的代码可以动态修改,适合进行复杂的调试和测试,例如实时修改变量或函数逻辑。

  • 3、支持高级调试功能

    • 断点和单步调试:在RAM中运行代码时,调试器可以更灵活地设置断点和进行单步调试,而不会受到Flash存储器的限制。

    • 动态内存分配:某些调试功能(如动态内存分配和堆栈跟踪)在RAM中更容易实现。

1.2 在RAM中调试代码的优势

  • 1、在RAM上调试程序时,下载速度非常快

    与内部FLASH相比,RAM存储器的写入速度要快得多,且无需擦除过程。因此,程序几乎是秒下,这为需要频繁修改代码的调试过程节省了大量时间,省去了烦人的擦除与写入FLASH的步骤。此外,虽然STM32的内部FLASH可擦除次数通常为1万次,一般的调试过程不太可能达到这个次数导致FLASH失效,但这也确实是一个考虑使用RAM的因素。

  • 2、在RAM上调试程序时,不会改写内部FLASH的原有程序

  • 3、对于内部FLASH被锁定的芯片,还可以将解锁程序下载到RAM上,进行解锁操作。

1.3 在RAM中调试代码的缺陷

  • 1、存储在RAM中的程序在掉电后会丢失,无法像存储在FLASH中那样持久保存。

  • 2、如果使用STM32的内部SRAM存储程序,程序的执行速度与在FLASH上执行时基本相同,但内部SRAM的空间相对较小,可能会限制程序的大小

  • 3、如果使用外部扩展的SRAM存储程序,虽然程序空间可以非常大,但STM32读取外部SRAM的速度比读取内部FLASH慢,这会导致程序的总执行时间增加。因此,在外部SRAM中调试的程序无法完全模拟在内部FLASH中运行时的真实环境。

    • 此外,STM32无法直接从外部SRAM启动,且将应用程序复制到外部SRAM的过程较为复杂(在下载程序之前,需要确保STM32能够正常控制外部SRAM)。因此,实际开发中很少会在STM32的外部SRAM中调试程序。

二、STM32的启动方式

CM-3 内核在离开复位状态后的工作过程如下图:
在这里插入图片描述

  • 1、从地址 0x00000000 处取出栈指针 MSP 的初始值,该值就是栈顶的地址。

    • 程序局部变量存储在栈空间中,MSP指向栈顶,防止栈溢出。
  • 2、从地址 0x00000004 处取出程序指针 PC 的初始值,该值指向复位后应执行的第一条指令。

上述过程由内核自动设置运行环境并执行主体程序,因此它被称为自举过程
在这里插入图片描述

2.1 MSP指针

MSP(Main Stack Pointer,主堆栈指针)是用于管理堆栈的一个重要寄存器,系统复位后,MSP的值会被设置为向量表的第一个值(通常是堆栈的初始地址),用于初始化堆栈

在这里插入图片描述

Stack_Size      EQU     0x00000400			// 定义了堆栈的大小为 0x00000400 字节(1024 字节)
AREA    STACK, NOINIT, READWRITE, ALIGN=3	// 定义了一个名为 STACK 的内存区域,该区域未初始化(NOINIT),可读写(READWRITE),并且对齐到 2^3(即 8 字节)边界。
Stack_Mem       SPACE   Stack_Size			// 在堆栈区域中分配了 Stack_Size 大小的空间。
__initial_sp								// 表示堆栈指针的初始值将被设置为 Stack_Mem 的地址加上 Stack_Size 的大小,即堆栈空间的顶部。

2.2 PC指针

PC指针(程序计数器)是一个寄存器,用于存储下一条指令的地址
在这里插入图片描述

  • 1、系统复位后,首先调用 SystemInit 函数来初始化硬件;

  • 2、然后跳转到 __main 函数,由它完成C运行时环境的初始化(如全局变量的初始化);

  • 3、最终,程序会跳转到 main() 函数(由 __main 调用),开始执行用户程序。

PC指向Reset_Handler,跳转到Reset_Handler函数执行初始化时钟、调用main函数,最终进入main函数中执行程序。

2.3 STM32的三种启动方式

虽然内核默认访问的地址是 0x00000000 和 0x00000004,但这些地址实际上可以被重映射到其他地址空间。以 STM32F103 为例,根据芯片引脚 BOOT0BOOT1 的电平状态,这两个地址可以被映射到内部 FLASH、内部 SRAM 或系统存储器。具体的映射配置取决于 BOOT 引脚的不同设置,具体映射关系见表 BOOT 引脚设置对 0 地址的映射。

在这里插入图片描述
当内核离开复位状态后,会从映射的地址中获取初始值,分别赋给主堆栈指针(MSP)和程序计数器(PC),然后开始执行指令。通常,我们会根据这些地址所映射到的存储器类型(如内部FLASH、SRAM或系统存储器)来区分不同的自举过程。

2.3.1 内部 FLASH 启动方式

当芯片上电后,若采样到 BOOT0 引脚为低电平,则 0x00000000 和 0x00000004 地址会被映射到内部 FLASH 的首地址 0x08000000 和 0x08000004。因此,内核在离开复位状态后,会从内部 FLASH 的 0x08000000 地址读取内容并赋值给主堆栈指针(MSP),作为栈顶地址;再从内部 FLASH 的 0x08000004 地址读取内容并赋值给程序计数器(PC),作为第一条指令的地址。具备这两个条件后,内核便开始从 PC 指向的地址中读取并执行指令。

2.3.2 内部 SRAM 启动方式

当芯片上电后,若采样到 BOOT0BOOT1 引脚均为高电平,则 0x00000000 和 0x00000004 地址会被映射到内部 SRAM 的首地址 0x20000000 和 0x20000004。此时,内核会从 SRAM 空间获取内容以完成自举过程。

在实际应用中,0x00000000 和 0x00000004 地址存储的内容由启动文件(如 startup_stm32f10x.s)定义。而在链接阶段,分散加载文件(.sct 文件)会决定这些内容的最终存储位置,即它们会被分配到内部 FLASH 还是内部 SRAM。

2.3.3 内部 SRAM 启动方式

当芯片上电后,若采样到 BOOT0 引脚为高电平BOOT1 引脚为低电平时,内核将从系统存储器的 0x1FFFF000 和 0x1FFFF004 地址获取主堆栈指针(MSP)和程序计数器(PC)的值进行自举。

系统存储器是一段特殊的存储空间,用户无法直接访问。ST公司在芯片出厂前在系统存储器中固化了一段代码。当使用系统存储器启动时,内核会执行这段代码,该代码运行时会为 ISP(In System Program,系统内编程) 提供支持。具体来说,它会检测通过 USART1/2CAN2USB 通讯接口传输过来的信息,并根据这些信息更新内部 FLASH 的内容,从而实现产品应用程序的升级。因此,这种启动方式也被称为 ISP 启动方式

三、内部FLASH的启动过程

在启动代码的中间部分,通过汇编指令 DCD(Define Constant Data),将 __initial_spReset_Handler 的地址定义在了代码段的最前面,从而确保它们被放置在指定的地址空间。

在这里插入图片描述

在启动文件中,将栈顶地址和首条指令地址(__initial_spReset_Handler)放置在代码的最前面,但这并不直接指定它们的绝对地址。这些内容的绝对地址是由链接器根据分散加载文件**(*.sct)**分配的。

在这里插入图片描述

上图为 STM32F103 的默认分散加载文件配置。

  • LR_IROM1:表示一个加载区域(Load Region),名称为 LR_IROM1。

    • 0x08000000:加载区域的起始地址(这里是内部FLASH的起始地址);0x00080000:加载区域的大小(这里是512KB)。
  • ER_IROM1(内部FLASH):表示一个执行区域(Execution Region),名称为 ER_IROM1。

    • 0x08000000:执行区域的起始地址(与加载地址相同,表示代码加载后直接在该地址执行);0x00080000:执行区域的大小(这里是 512KB)。
  • 代码段分配

    *.o (RESET, +First)	// 将所有对象文件中定义的 RESET 段(通常是中断向量表和复位处理程序)放在最前面。
    *(InRoot$$Sections)	// 将所有对象文件中定义的 InRoot 段(通常是启动代码和初始化代码)放在后面。
    .ANY (+RO)	        // 将所有只读(Read-Only)段分配到该区域。
    .ANY (+XO)          // 将所有可执行但不读取的段分配到该区域。
    

    如果把*.o (RESET, +First)放到RW_IRAM1中,那么MSP指针跟PC指针就会指向0x20000000跟0x20000004的地址。

  • RW_IRAM1(RAM空间):表示一个读写区域(Read-Write Region),名称为 RW_IRAM1。

    • 0x20000000:读写区域的起始地址(这里是内部 SRAM 的起始地址);0x00010000:读写区域的大小(这里是 64KB)。

    • .ANY (+RW +ZI):将所有读写(Read-Write)和零初始化(Zero-Initialized)的段分配到该区域。

在分散加载文件中,加载区和执行区的首地址都被设置为 0x08000000,这恰好是内部 FLASH 的起始地址。因此,汇编文件中定义的栈顶地址和首条指令地址会被存储到 0x08000000 和 0x08000004 的地址空间中。

类似地,如果修改分散加载文件,将加载区和执行区的首地址设置为内部 SRAM 的起始地址 0x20000000,那么栈顶地址和首条指令地址将会被存储到 0x20000000 和 0x20000004 的地址空间中。

四、将代码修改为RAM自举

  • 1、设置一个RAM调试的工程:

在这里插入图片描述

  • 2、在C/C++中添加宏VECT_TAB_SRAM:
    在这里插入图片描述

此宏在SystemInit函数中。
在这里插入图片描述
如果定义了 VECT_TAB_SRAM,则将中断向量表重定向到内部 SRAM 的指定位置;
如果未定义 VECT_TAB_SRAM,则将中断向量表重定向到内部 FLASH 的指定位置。

注意,两个宏之间用","分开。

  • 3、打开.sct文件:
    在这里插入图片描述

  • 4、修改.sct文件,把程序分配到SRAM:
    在这里插入图片描述

修改前的空间大小。

在这里插入图片描述

修改后的空间大小:原本的RAM空间为64K,现分一半用来做FLASH,一半用来做RAM

  • 5、修改下载配置,把程序下载到SRAM:
    在这里插入图片描述
    在这里插入图片描述

    • 选择Do not Erase是因为程序下载到RAM中,不需要修改FLASH,勾选Erase Full Chip或Erase Sectors的话会修改FLASH,导致程序下载失败。

    • RAM for Algorithm:烧录算法(Flash Programming Algorithm)预留的RAM空间。

      烧录过程中的临时存储:在将程序烧录到Flash时,烧录算法需要运行,而这个算法需要一定的RAM空间来存储其运行时的数据和代码。
      仅在烧录时使用:一旦烧录完成,这段RAM空间会被释放,可供应用程序(APP代码)使用。

  • 6、修改调试器配置,初始化SP和PC指针:

在这里插入图片描述

/******************************************************************************/
/* 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,把0x20000000地址中的内容赋值到PC。
  _WDWORD(0xE000ED08, 0x20000000);      // Setup Vector Table Offset Register
}

LOAD %L INCREMENTAL                    // 下载axf文件到RAM
Setup();							   //调用上面定义的setup函数设置运行环境		

//g, main							  //跳转到main函数,本示例调试时不需要从main函数执行,注释掉了,程序从启动代码开始执行

这里是强制将SP、PC指针强制指向了0x20000000与0x20000000。

  • 7、将工程更改为RAM调试的工程:

在这里插入图片描述

调试的时候不能点DOWNLOAD,要点DEBUG。

在DEBUG的时候,想要复位,不能点RST按键,要退出DEBUG后再重新点DEBUG。


网站公告

今日签到

点亮在社区的每一天
去签到