新闻详情

新闻详情

首页 / 资讯中心 / 详情

STM32的一些知识补充

发布时间:2026/10/1 21:46:44来源:尧图网络
STM32的一些知识补充
读写内部Flash1.STM32 的内部 FLASH 简介:在 STM32 芯片内部有一个 FLASH 存储器它主要用于存储代码我们在电脑上编写好应用程序后使用下载器把编译后的代码文件烧录到该内部 FLASH 中由于 FLASH 存储器的内容在掉电后不会丢失芯片重新上电复位后内核可从内部 FLASH 中加载代码并运行见图 STM32 的内部框架图 。除了使用外部的工具如下载器读写内部 FLASH 外STM32 芯片在运行的时候也能对自身的内部 FLASH 进行读写因此若内部 FLASH 存储了应用程序后还有剩余的空间我们可以把它像外部 SPI-FLASH 那样利用起来存储一些程序运行时产生的需要掉电保存的数据。由于访问内部 FLASH 的速度要比外部的 SPI-FLASH 快得多所以在紧急状态下常常会使用内部FLASH 存储关键记录为了防止应用程序被抄袭有的应用会禁止读写内部 FLASH 中的内容或者在第一次运行时计算加密信息并记录到某些区域然后删除自身的部分加密代码这些应用都涉及到内部 FLASH 的操作。2.内部 FLASH 的构成STM32 的内部 FLASH 包含主存储器、系统存储器、OTP 区域以及选项字节区域它们的地址分布及大小见表 STM32 内部 FLASH 的构成 。各个存储区域的说明如下• 主存储器一般我们说 STM32 内部 FLASH 的时候都是指这个主存储器区域它是存储用户应用程序的空间芯片型号说明中的 1M FLASH、2M FLASH 都是指这个区域的大小。**主存储器分为两块共 2MB每块内分 12 个扇区其中包含 4 个 16KB 扇区、1 个64KB 扇区和 7 个 128KB 的扇区。如我们实验板中使用的 STM32F407ZGT6 型号芯片它的主存储区域大小为 1MB所以它只包含有表中的扇区 0-扇区 11。**与其它 FLASH 一样在写入数据前要先按扇区擦除而有的时候我们希望能以小规格操纵存储单元所以 STM32F42x/43x 针对 1MB FLASH 的产品还提供了一种双块的存储格式见表 1MB 产品的双块存储格式 。(2M 的产品按表 STM32 内部 FLASH的构成 的格式)通过配置 FLASH 选项控制寄存器 FLASH_OPTCR 的 DB1M 位可以切换这两种格式切换成双块模式后扇区 8-11 的空间被转移到扇区 12-19 中扇区细分了总容量不变。要强调的是本实验板采用的是 STM32F40x 系列的芯片它没有双块存储格式也不存在扇区 12-23仅 STM32F42x/43x 系列产品才支持扇区 12-23。• 系统存储区系统存储区是用户不能访问的区域它在芯片出厂时已经固化了启动代码它负责实现串口、USB 以及 CAN 等 ISP 烧录功能。• OTP 区域OTP(One Time Program)指的是只能写入一次的存储区域容量为 512 字节写入后数据就无法再更改OTP 常用于存储应用程序的加密密钥。• 选项字节选项字节用于配置 FLASH 的读写保护、电源管理中的 BOR 级别、软件/硬件看门狗等功能这部分共 32 字节。可以通过修改 FLASH 的选项控制寄存器修改。3.对内部 FLASH 的写入过程1解锁由于内部 FLASH 空间主要存储的是应用程序是非常关键的数据为了防止误操作修改了这些内容芯片复位后默认会给 FLASH 上锁这个时候不允许设置 FLASH 的控制寄存器并且不能对修改 FLASH 中的内容。所以对 FLASH 写入数据前需要先给它解锁。解锁的操作步骤如下(1) 往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY1 0x45670123(2) 再往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY2 0xCDEF89AB数据操作位数在内部 FLASH 进行擦除及写入操作时电源电压会影响数据的最大操作位数该电源电压可通过配置 FLASH_CR 寄存器中的 PSIZE 位改变见表数据操作位数 。最大操作位数会影响擦除和写入的速度其中 64 位宽度的操作除了配置寄存器位外还需要在Vpp 引脚外加一个 8-9V 的电压源且其供电时间不得超过一小时否则 FLASH 可能损坏所以64 位宽度的操作一般是在量产时对 FLASH 写入应用程序时才使用大部分应用场合都是用 32位的宽度。2擦除扇区在写入新的数据前需要先擦除存储区域STM32 提供了扇区擦除指令和整个 FLASH 擦除 (批量擦除) 的指令批量擦除指令仅针对主存储区。扇区擦除的过程如下(1) 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”以确认当前未执行任何 Flash 操作(2) 在 FLASH_CR 寄存器中将“激活扇区擦除寄存器位 SER ”置 1并设置“扇区编号寄存器位 SNB”选择要擦除的扇区(3) 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1开始擦除(4) 等待 BSY 位被清零时表示擦除完成。3查看工程的空间分布由于内部 FLASH 本身存储有程序数据若不是有意删除某段程序代码一般不应修改程序空间的内容所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生的“*.map”后缀文件可以了解程序存储到了哪些区域它在工程中的打开方式见图打开工程的 map 文件 也可以到工程目录中的“Listing”文件夹中找到。如下图可以看到ROM Size的大小就是2636这远远没达到第一块扇区但是我们要注意的是在进行扇区擦除的时候不能擦除我们存储我们程序的扇区所以保险起见我们可以选择第二进行擦除和读写操作这一段是某工程的 ROM 存储器分布映像在 STM32 芯片中ROM 区域的内容就是指存储到内部 FLASH 的代码。其中Code是代码指令Data是数据程序 ROM 的加载与执行空间上述说明中有两段分别以“Load Region LR_ROM1”及“Execution RegionER_IROM1”开头的内容它们分别描述程序的加载及执行空间在芯片刚上电运行时会加载程序及数据例如它会从程序的存储区域加载到程序的执行区域还把一些已初始化的全局变量从 ROM 复制到 RAM空间以便程序运行时可以修改变量的内容。加载完成后程序开始从执行区域开始执行。在上面 map 文件的描述中我们了解到加载及执行空间的基地址 (Base) 都是 0x08000000它正好是 STM32 内部 FLASH 的首地址即 STM32 的程序存储空间就直接是执行空间它们的大小(Size) 分别为 0x00000a4c 及 0x00000a20执行空间的 ROM 比较小的原因就是因为部分 RW-data类型的变量被拷贝到 RAM 空间了它们的最大空间 (Max) 均为 0x00100000即 1M 字节它指的是内部 FLASH 的最大空间。计算程序占用的空间时需要使用加载区域的大小进行计算本例子中应用程序使用的内部FLASH 是从 0x0800000至(0x080000000x00000aa4) 地址的空间区域。4ROM 空间分布表在加载及执行空间总体描述之后紧接着一个 ROM 详细地址分布表它列出了工程中的各个段(如函数、常量数据) 所在的地址 BaseAddr 及占用的空间 Size列表中的 Type 说明了该段的类型CODE 表示代码DATA 表示数据而 PAD 表示段之间的填充区域它是无效的内容PAD 区域往往是为了解决地址对齐的问题。观察表中的最后一项它的基地址是 0x08000a70大小为 0x00000020可知它占用的最高的地址空间为 0x08000a20跟执行区域的最高地址 0x00000a90 一样但它们比加载区域说明中的最高地址 0x8000a4c 要小所以我们以加载区域的大小为准。对比表 STM32 内部 FLASH 的构成 的内部 FLASH 扇区地址分布表可知仅使用扇区 0 就可以完全存储本应用程序所以从扇区 1(地址 0x08004000) 后的存储空间都可以作其它用途使用这些存储空间时不会篡改应用程序空间的数据。5操作内部 FLASH 的库函数为简化编程STM32 标准库提供了一些库函数它们封装了对内部 FLASH 写入数据操作寄存器的过程。/*开锁函数*/voidFLASH_Unlock(void);/*扇区擦除函数*//*参数一是选择擦除的扇区参数二是选择以多少字节来擦除我们一般选择32位这样擦除的快点*/FLASH_StatusFLASH_EraseSector(uint32_tFLASH_Sector,uint8_tVoltageRange);/*这三个分别是字半字字节写入函数*//*参数一是选择要写入数据的地址参数二就是要写入的数据*/FLASH_StatusFLASH_ProgramWord(uint32_tAddress,uint32_tData);FLASH_StatusFLASH_ProgramHalfWord(uint32_tAddress,uint16_tData);FLASH_StatusFLASH_ProgramByte(uint32_tAddress,uint8_tData);/*上锁函数*/voidFLASH_Lock(void);4.下面我们来编写内部Flash读写程序inter_flash.c#includeinter_flash.h#defineW_ADDRESS0x08008000voidIniter_Flash(void){char*p(char*)W_ADDRESS;FLASH_Status status;/*开锁*/FLASH_Unlock();/*扇区擦除*/statusFLASH_EraseSector(FLASH_Sector_2,VoltageRange_3);if(statusFLASH_COMPLETE){printf(\r\n擦除成功);}else{printf(\r\n擦除失败);}/*写入数据*/statusFLASH_ProgramByte(W_ADDRESS,0x12);if(statusFLASH_COMPLETE){printf(\r\n写入成功);}else{printf(\r\n写入失败);}printf(\r\n读出数据的地址:0x%x,读出的数据%d,W_ADDRESS,*p);/*上锁*/FLASH_Lock();}main.cintmain(void){Systick_Init();LED_Init();USARTConfig_Init();printf(内部Flash读写程序实验\r\n);Initer_Flash();while(1){}}可以看到也是读写成功了设置 FLASH 的读写保护及解除1.选项字节与读写保护选项字节是一段特殊的 FLASH 空间STM32 芯片会根据它的内容进行读写保护、复位电压等配置选项字节的构成见表选项字节的构成 。选项字节具体的数据位配置说明见表选项字节具体的数据位配置说明 。我们主要讲解选项字节配置中的 RDP 位和 PCROP 位它们分别用于配置读保护级别及代码读出保护。2.RDP 读保护级别修改选项字节的 RDP 位的值可设置内部 FLASH 为以下保护级别• 0xAA级别 0无保护这是 STM32 的默认保护级别它没有任何读保护读取内部 FLASH 及“备份 SRAM”的内容都没有任何限制。注意这里说的“备份 SRAM”是指 STM32 备份域的 SRAM 空间不是指主 SRAM下同• 其它值级别 1使能读保护把 RDP 配置成除 0xAA 或 0xCC 外的任意数值都会使能级别 1 的读保护。在这种保护下若使用调试功能使用下载器、仿真器或者从内部 SRAM 自举时都不能对内部 FLASH 及备份 SRAM 作任何访问读写、擦除都被禁止而如果 STM32 是从内部 FLASH 自举时它允许对内部 FLASH 及备份 SRAM 的任意访问。也就是说在级别 1 模式下任何尝试从外部访问内部 FLASH 内容的操作都被禁止例如无法通过下载器读取它的内容或编写一个从内部 SRAM 启动的程序若该程序读取内部 FLASH会被禁止。而如果是芯片自己访问内部 FLASH是完全没有问题的例如前面的“读写内部 FLASH”实验中的代码自己擦写内部 FLASH 空间的内容即使处于级别 1 的读保护也能正常擦写。**当芯片处于级别 1 的时候可以把选项字节的 RDP 位重新设置为 0xAA恢复级别 0。在恢复到级别 0 前芯片会自动擦除内部 FLASH 及备份 SRAM 的内容即降级后原内部 FLASH 的代码会丢失。**在级别 1 时使用 SRAM 自举的程序也可以访问选项字节进行修改所以如果原内部 FLASH 的代码没有解除读保护的操作时可以给它加载一个 SRAM 自举的程序进行保护降级后面我们将会进行这样的实验。• 0xCC级别 2禁止调试把 RDP 配置成 0xCC 值时会进入最高级别的读保护且设置后无法再降级它会永久禁止用于调试的 JTAG 接口相当于熔断。在该级别中除了具有级别 1 的所有保护功能外进一步禁止了从 SRAM 或系统存储器的自举即平时使用的串口 ISP 下载功能也失效JTAG 调试相关的功能被禁止选项字节也不能被修改。它仅支持从内部 FLASH 自举时对内部 FLASH 及 SRAM 的访问读写、擦除。由于设置了级别 2 后无法降级也无法通过 JTAG、串口 ISP 等方式更新程序所以使用这个级别的保护时一般会在程序中预留“后门”以更新应用程序若程序中没有预留后门芯片就无法再更新应用程序了。所谓的“后门”是一种 IAP 程序In Application Program它通过某个通讯接口获取将要更新的程序内容然后利用内部 FLASH 擦写操作把这些内容烧录到自己的内部 FLASH 中实现应用程序的更新。不同级别下的访问限制见图不同级别下的访问限制。不同保护级别之间的状态转换见图不同级别间的状态转换 。3.修改选项字节的过程修改选项字节的内容可修改各种配置但是当应用程序运行时无法直接通过选项字节的地址改写它们的内容例如接使用指针操作地址 0x1FFFC0000 的修改是无效的。要改写其内容时必须设置寄存器 FLASH_OPTCR 及 FLASH_OPTCR1 中的对应数据位寄存器的与选项字节对应位置见图 FLASH_OPTCR 寄存器说明 及图 FLASH_OPTCR1 寄存器说明 详细说明请查阅《STM32参考手册》。注意Flash 选项控制寄存器位于 RAM 内存中也就是说直接配置该寄存器并不能实现对 Flash 内部内容的操控。不过只要配置完该寄存器后将 bit1 位置 1就可以将该寄存器的配置存储到 Flash 内部。默认情况下FLASH_OPTCR 寄存器中的第 0 位 OPTLOCK 值为 1它表示选项字节被上锁需要解锁后才能进行修改当寄存器的值设置完成后对 FLASH_OPTCR 寄存器中的第 1 位 OPTSTRT值设置为 1硬件就会擦除选项字节扇区的内容并把 FLASH_OPTCR/1 寄存器中包含的值写入到选项字节。所以修改选项字节的配置步骤如下(1) 解锁在 Flash 选项密钥寄存器 (FLASH_OPTKEYR) 中写入 OPTKEY1 0x0819 2A3B接着在 Flash 选项密钥寄存(FLASH_OPTKEYR) 中写入 OPTKEY2 0x4C5D 6E7F。(2) 检查 FLASH_SR 寄存器中的 BSY 位以确认当前未执行其它 Flash 操作。(3) 在 FLASH_OPTCR 和/或 FLASH_OPTCR1 寄存器中写入选项字节值。(4) 将 FLASH_OPTCR 寄存器中的选项启动位 (OPTSTRT) 置 1。(5) 等待 BSY 位清零即写入完成。4.设置Flash读写保护代码如下voidRead_Protect(void){FLASH_OB_Unlock();/*判断是否开启读保护没有就进入该if语句*/if(FLASH_OB_GetRDP()RESET){printf(\r\n准备开启读保护);/*开启读保护*/FLASH_OB_RDPConfig(OB_RDP_Level_1);}/*启动选项将选项字节的配置存入到Flash*/FLASH_OB_Launch();if(FLASH_OB_Launch()FLASH_COMPLETE){printf(\r\n开启读保护成功);}FLASH_OB_Lock();while(1){printf(\r\n按下key1按键取消读保护);if(Key_State()1){FLASH_OB_Unlock();/*判断是否开启读保护有就进入该if语句*/if(FLASH_OB_GetRDP()SET){printf(\r\n准备取消读保护);/*开启读保护*/FLASH_OB_RDPConfig(OB_RDP_Level_0);}/*读保护等级降级会删除Flash区的程序代码所以以下程序不会执行*//*启动选项将选项字节的配置存入到Flash*/FLASH_OB_Launch();if(FLASH_OB_Launch()FLASH_COMPLETE){printf(\r\n取消读保护成功);}FLASH_OB_Lock();}}}voidWrite_Protect(void){FLASH_OB_Unlock();/*判断是否开启写保护没有就进入该if语句*/if((FLASH_OB_GetWRP()0xFFF)0xFFF){printf(\r\n准备开启写保护);/*开启扇区2写保护*/FLASH_OB_WRPConfig(OB_WRP_Sector_2,ENABLE);}/*启动选项将选项字节的配置存入到Flash*/if(FLASH_OB_Launch()FLASH_COMPLETE){printf(\r\n开启写保护成功);}FLASH_OB_Lock();while(1){if(Key_State()1){FLASH_OB_Unlock();/*判断是否开启2扇区写保护有就进入该if语句*/if((FLASH_OB_GetWRP()0xFFB)0xFFB){printf(\r\n取消2扇区写保护);/*开启扇区2写保护*/FLASH_OB_WRPConfig(OB_WRP_Sector_2,DISABLE);}/*启动选项将选项字节的配置存入到Flash*/if(FLASH_OB_Launch()FLASH_COMPLETE){printf(\r\n写保护取消保护成功);}FLASH_OB_Lock();}}}注意事项如果你的应用程序代码中没有编写解除读写保护的逻辑那么一旦芯片进入读写保护状态你将无法通过常规方式如 JTAG、串口 ISP 等直接烧录程序来解除保护。此时唯一的解决办法是将一段专门用于解除读写保护的程序烧录到 SRAM 内存中运行通过该程序修改选项字节从而解除读写保护。在SRAM中调试代码重点前言一般情况下我们在 MDK 中编写工程应用后调试时都是把程序下载到芯片的内部 FLASH 运行测试的代码的 CODE 及 RW-data 的内容被写入到内部 FLASH 中存储。但在某些应用场合下却不希望或不能修改内部 FLASH 的内容这时就可以使用 RAM 调试功能了**它的本质是把原来存储在内部 FLASH 的代码 (CODE 及 RW-data 的内容) 改为存储到 SRAM 中 (内部 SRAM 或外部 SRAM 均可)芯片复位后从内部 SRAM 中加载代码并运行。**把代码下载到 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 中调试程序1.STM32 的启动方式在前面讲解的 STM32 启动代码章节了解到 CM-3 内核在离开复位状态后的工作过程如下见图复位序列 (1) 从地址 0x00000000 处取出栈指针 MSP 的初始值该值就是栈顶的地址。(2) 从地址 0x00000004 处取出程序指针 PC 的初始值该值指向复位后应执行的第一条指令。上述过程由内核自动设置运行环境并执行主体程序因此它被称为自举过程。虽然内核是固定访问 0x00000000 和 0x00000004 地址的但实际上这两个地址可以被重映射到其它地址空间。以 STM32F407 为例根据芯片引出的 BOOT0 及 BOOT1 引脚的电平情况这两个地址可以被映射到内部 FLASH、内部 SRAM 以及系统存储器中不同的映射配置见表 BOOT 引脚的不同设置对 0 地址的映射。内核在离开复位状态后会从映射的地址中取值给栈指针 MSP 及程序指针 PC然后执行指令我们一般以存储器的类型来区分自举过程例如内部 FLASH 启动方式、内部 SRAM 启动方式以及系统存储器启动方式。(1) 内部 FLASH 启动方式当芯片上电后采样到 BOOT0 引脚为低电平时0x00000000 和 0x00000004 地址被映射到内部FLASH 的首地址 0x08000000 和0x08000004。因此内核离开复位状态后读取内部 FLASH 的0x08000000 地址空间存储的内容赋值给栈指针 MSP作为栈顶地址再读取内部 FLASH 的0x08000004 地址空间存储的内容赋值给程序指针 PC作为将要执行的第一条指令所在的地址。具备这两个条件后内核就可以开始从 PC 指向的地址中读取指令执行了。(2) 内部 SRAM 启动方式类似地当芯片上电后采样到 BOOT0 和 BOOT1 引脚均为高电平时0x00000000 和 0x00000004地址被映射到内部 SRAM 的首地址 0x20000000 和 0x20000004内核从 SRAM 空间获取内容进行自举。在实际应用中由启动文件 starttup_stm32f40xx.s 决定了 0x00000000 和 0x00000004 地址存储什么内容链接时由分散加载文件 (sct) 决定这些内容的绝对地址即分配到内部 FLASH 还是内部 SRAM。(3) 系统存储器启动方式当芯片上电后采样到 BOOT0 引脚为高电平BOOT1 为低电平时内核将从系统存储器的0x1FFF0000 及 0x1FFF0004 获取 MSP 及 PC 值进行自举。系统存储器是一段特殊的空间用户不能访问ST 公司在芯片出厂前就在系统存储器中固化了一段代码。因而使用系统存储器启动方式时内核会执行该代码该代码运行时会为 ISP 提供支持 (In System Program)如检测USART1/2、CAN2 及 USB 通讯接口传输过来的信息并根据这些信息更新自己内部 FLASH 的内容达到升级产品应用程序的目的因此这种启动方式也称为 ISP 启动方式。2.内部 FLASH 的启动过程下面我们以最常规的内部 FLASH 启动方式来分析自举过程主要理解 MSP 和 PC 内容是怎样被存储到 0x08000000 和 0x08000004 这两个地址的。见图启动代码中存储的 MSP 及 PC 指针内容 这是 STM32F407 默认的启动文件的代码启动文件的开头定义了一个大小为 0x400 的栈空间且栈顶的地址使用标号“__initial_sp”来表示**在图下方定义了一个名为“Reset_Handler”的子程序它就是我们总是提到的在芯片启动后第一个执行的代码。**在汇编语法中程序的名字和标号都包含它所在的地址因此我们的目标是把“__initial_sp”和“Reset_Handler”赋值到 0x08000000 和 0x08000004 地址空间存储这样内核自举的时候就可以获得栈顶地址以及第一条要执行的指令了。在启动代码的中间部分使用了汇编关键字“DCD”把“__initial_sp”和“Reset_Handler”定义到了最前面的地址空间。在启动文件中把设置栈顶及首条指令地址到了最前面的地址空间但这并没有指定绝对地址各种内容的绝对地址是由链接器根据分散加载文件 (*.sct) 分配的STM32F407ZGT6 型号的默认分散加载文件配置如下图**分散加载文件把加载区和执行区的首地址都设置为 0x08000000正好是内部 FLASH 的首地址因此汇编文件中定义的栈顶及首条指令地址会被存储到 0x08000000 和 0x08000004 的地址空间。**类似地如果我们修改分散加载文件把加载区和执行区的首地址设置为内部 SRAM 的首地址0x20000000那么栈顶和首条指令地址将会被存储到 0x20000000 和 0x20000004 的地址空间了。为了进一步消除疑虑我们可以查看反汇编代码及 map 文件信息来了解各个地址空间存储的内容见图从反汇编代码及 map 文件查看存储器的内容 这是多彩流水灯工程编译后的信息它的启动文件及分散加载文件都按默认配置。其中反汇编代码是使用 fromelf 工具从 axf 文件生成的具体过程可参考前面的章节了解。从反汇编代码可了解到这个工程的0x08000000地址存储的值为0x200004000x08000004地址存储的值为0x0800019d。查看 map 文件这两个值正好是栈顶地址__initial_sp以及首条指令Reset_Handler的地址。下载器会根据 axf 文件bin、hex 类似存储相应的内容到内部 FLASH 中。由此可知当 BOOT0 为低电平时内核复位后从0x08000000读取到栈顶地址为0x20000400从而了解到子程序的栈空间范围再从0x08000004读取到第一条指令的存储地址为0x0800019d于是跳转到该地址执行代码即从Reset_Handler开始运行依次执行SystemInit、__main包含分散加载代码最后跳转到 C 语言的main函数。不过我们到实际的 map 文件可以看到这个__initial_sp并不是0x20010400因为在它前面还有0x30字节大小的全局变量。我们到 sct 文件可以看到.ANY(RW ZI)选择剩余所有节区 RW 及 ZI 属性的内容都分配到执行域RW_IRAM1中。所以我们的__initial_sp 栈段起始 Stack_Size。可以这样理解SRAM 是一个巨大的存储空间而__initial_sp是一个0x400字节大小的存储空间的顶部地址值它不一定为0x400大小这个可以通过更改Stack_Size调节分配的。这也是为什么我们定义一个数组的时候不要定义成局部变量因为如果这个数组太大超过这个栈大小就会导致栈溢出。对比在内部 FLASH 中运行代码的过程可了解到若希望在内部 SRAM 中调试代码需要设置启动方式为从内部 SRAM 启动修改分散加载文件控制代码空间到内部 SRAM 地址以及把生成程序下载到芯片的内部 SRAM 中。实验在内部 SRAM 中调试代码硬件首先我们要将BOOT0和BOOT1引脚把 BOOT0 和 BOOT1 引脚都使用跳线帽连接到 3.3V使芯片从 SRAM 中启动。软件设计(1) 在原工程的基础上创建一个调试版本(2) 修改分散加载sct文件使链接器把代码分配到内部 SRAM 空间(3) 添加宏修改 STM32 的向量表地址(4) 修改仿真器和下载器的配置使程序能通过下载器存储到内部 SRAM(5) 尝试给 SRAM 下载程序或仿真调试3.创建工程的调试版本由于在 SRAM 中运行的代码一般仅用于调试调试完毕后在实际生产环境中仍然使用在内部 FLASH 中运行的代码因此我们希望能够便捷地在调试版和发布版代码之间切换。MDK 的“Manage Project Items”功能可实现这一点使用它可管理多个不同配置的工程见图使用 Manage_Project_Items 添加一个工程配置。点击“Manage Project Items”按钮在弹出对话框左侧的“Project Target”一栏包含了原工程的名字如图中的原工程名为“多彩流水灯”右侧是该工程包含的文件。为了便于调试我们在左侧的“Project Target”一栏添加一个工程名如图中输入“SRAM_调试”输入后点击 OK 即可。这个“SRAM_调试”版本的工程会复制原“多彩流水灯”工程的配置后面我们再进行修改。当需要切换工程版本时点击 MDK 工程名的下拉菜单可选择目标工程在不同的工程中所有配置都是独立的例如芯片型号、下载配置等等但如果两个工程共用了同一个文件对该文件的修改会同时影响两个工程例如这两个工程都使用同一个 main 文件我们在 main 文件修改代码两个工程都会被修改。在下面的教程中我们将切换到“SRAM_ 调试”版本的工程配置出一个代码会被存储到 SRAM的多彩流水灯工程。(1)配置分散加载文件随后我们就配置好新的sct文件路径了因此在修改分散加载文件后务必在 MDK 的“Options for Target”中为当前工程重新指定新的 sct 文件路径否则 MDK 会使用默认的 sct 文件即分配到内部 FLASH 的配置导致我们辛苦修改好的 SRAM 分配配置在下次编译或调试时被覆盖回 Flash 分配SRAM 调试也就无法生效了。下面我们修改sct文件可以看到默认的分散加载文件把加载区Load Region LR_IROM1和执行区Execution Region ER_IROM1的首地址都设置为0x08000000正好是内部 FLASH 的首地址。因此汇编启动文件中用DCD定义的栈顶地址__initial_sp和首条指令Reset_Handler会被链接器分配到0x08000000和0x08000004这两个地址空间存储。当 BOOT0 为低电平、芯片从内部 FLASH 启动时内核复位后就会到0x08000000取出栈顶地址赋给 MSP再到0x08000004取出首条指令地址赋给 PC从而开始执行程序。而我们要在 SRAM 中调试就需要把这里的加载区、执行区首地址都改成内部 SRAM 的首地址0x20000000这样栈顶和首条指令地址就会被存储到0x20000000和0x20000004配合 BOOT0/BOOT1 都拉高从 SRAM 启动内核复位后就会从 SRAM 中取值自举了。配置完毕编译工程后可在 map 文件中查看具体的空间地址分配。可以看到修改后的分散加载文件把加载区Load Region LR_IROM1和执行区Execution Region ER_IROM1的首地址都改成了内部 SRAM 的首地址0x20000000。因此链接器把启动文件中用DCD定义的栈顶地址__initial_sp和首条指令Reset_Handler分配到了0x20000000和0x20000004这两个地址空间存储。从反汇编代码可以看到0x20000000地址存储的值0x20010430为栈顶地址0x20000004地址存储的值为0x200001a1这正是Reset_Handler的地址。也就是说Reset_Handler这条首条指令被烧录到了 SRAM 中而不是内部 FLASH。当 BOOT0 和 BOOT1 引脚都拉高、芯片从内部 SRAM 启动时内核复位后就会到0x20000000取出栈顶地址0x20010430赋给 MSP再到0x20000004取出0x200001a1赋给 PC从而跳转到 SRAM 中的Reset_Handler开始执行程序。这也验证了我们的配置生效了——代码确实被分配到了 SRAM 空间。(2)配置中断向量表由于 startup_stm32f40xx.s 文件中的启动代码不是指定到绝对地址的经过它由链接器决定应存储到内部 FLASH 还是 SRAM所以 SRAM 版本工程中的启动文件不需要作任何修改。重点在于启动文件定义的中断向量表被存储到内部 FLASH 和内部 SRAM 时这两种情况对内核的影响是不同的内核会根据它的“向量表偏移寄存器 VTOR”配置来获取向量表即中断服务函数的入口。VTOR 寄存器是由启动文件中 Reset_Handle 中调用的库函数 SystemInit 配置的也就是说SystemInit函数中通过条件编译#ifdef VECT_TAB_SRAM来决定向量表的存放位置定义了VECT_TAB_SRAM向量表被重定位到 SRAM 的首地址0x20000000即把中断向量表存储到 SRAM 中未定义VECT_TAB_SRAM向量表保持默认位置即存储到内部 FLASH 的首地址0x08000000。由于我们在 SRAM 中调试代码代码本身已经被链接器分配到了 SRAM 空间中断向量表也必须跟着放到 SRAM 中否则内核通过 VTOR 寄存器获取中断服务函数入口时会去 FLASH 中找向量表而 FLASH 中并没有我们 SRAM 版本的向量表就会导致中断无法正常响应。因此在 SRAM 调试工程中我们需要在编译选项中定义VECT_TAB_SRAM宏让SystemInit把向量表重定位到 SRAM从而保证中断向量表与代码所在位置一致。见编译选项中加入宏 VECT_TAB_SRAM 在“Options for Target- c/c±Define”框中输入宏 VECT_TAB_SRAM注意它与其它宏之间要使用英文逗号分隔开。配置完成后重新编译工程即可生成存储到 SRAM 空间地址的代码指令。(3)修改 FLASH 下载配置这个配置对话框原本是用于设置芯片内部 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 地址开始的 64KB 实际为虚拟 ROM 空间实际的 RAM 空间是从地址 0x20010000 开始的所以这里把算法 RAM 首地址更改为本工程中实际作为 RAM 使用的地址。若编程算法使用的 RAM 地址与虚拟 ROM 空间地址重合的话会导致下载出错。•“Programming Algorithm”一栏中是设置内部 FLASH 的编程算法编程算法主要描述了FLASH 的地址、大小以及扇区等信息MDK 根据这些信息把程序下载到芯片的 FLASH中不同的控制器芯片一般会有不同的编程算法。由于 MDK 没有内置 SRAM 的编程算法所以我们直接在原来的基础上修改它的基地址和空间大小把它改成虚拟 ROM 的空间信息。从这个例子可了解到这里的配置是跟我们的分散加载文件的实际 RAM 空间和虚拟 ROM 空间信息是一致的若您的分散加载文件采用不同的配置这个下载选项也要作出相应的修改不能照抄本例子的空间信息。这个配置是针对程序下载的配置完成后点击 MDK 的下载按钮LOAD程序会被下载到 STM32的内部 SRAM 中。复位后程序会正常运行 (前提是 BOOT0 和 BOOT 要被设置为 SRAM 启动) 。芯片掉电后这个存储在 SRAM 的程序会丢失想恢复的话必须要重新下载程序。(4)仿真器配置上面的下载配置使得程序能够加载到 SRAM 中全速运行但作为 SRAM 版本的程序其功能更着重于调试也就是说我们希望它能支持平时使用按钮 (调试、debug) 时进行的硬件在线调试、单步运行等功能。要实现调试功能还要在“Options for Target-Debug-Settings”中进行配置见图设置仿真前检查代码并下载程序到 FLASH 中 。在图中我们需要勾选“Verify Code Download”及“Download to FLASH”选项也就是说点击调试按钮后本工程的程序会被下载到内部 SRAM 中只有勾选了这两个选项才能正常仿真。经过这样的配置后硬件仿真时与平时内部 FLASH 版本的程序无异支持软件复位、单步运行、全速运行以及查看各种变量值等 (同样地前提是 BOOT0 和 BOOT 要被设置为 SRAM 启动)4.总结经过这样的配置后硬件仿真时与平时内部 FLASH 版本的程序无异支持软件复位、单步运行、全速运行以及查看各种变量值等同样地前提是 BOOT0 和 BOOT1 要接 3.3V设置为 SRAM 启动。第一步硬件配置。将 BOOT0 和 BOOT1 引脚都接 3.3V让芯片从内部 SRAM 启动。内核复位后会到 SRAM 的 0x20000000 地址取出栈顶地址赋给 MSP再到 0x20000004 地址取出 Reset_Handler 的地址赋给 PC作为下一个要执行的程序。第二步软件配置。那么 0x20000000 和 0x20000004 处的内容是从哪里来的呢分两个来源位置由 sct 定(RESET, First) 把启动文件里 RESET 节区钉在 0x20000000 最前面。RESET 是启动文件里 AREA RESET 定义的节区名节区里装的就是向量表向量表必须位于启动地址的最前面前两项依次是栈顶、Reset_Handler。内容由链接器填第一项 __initial_sp 栈段起始 Stack_Size第二项 Reset_Handler 在代码段里的排位地址。另外RAM 调试还需在 system_stm32f4xx.c 里定义宏 VECT_TAB_SRAMSRAM 启动后 SystemInit 默认把 VTOR 设回 Flash 基址定义该宏后改设 SRAM 基址中断向量才不会查错表Debug_RAM.ini 只在进入调试那一刻设一次 VTOR软件复位后由 SystemInit 兜底。整个流程如下编译将 .c 源文件编译成 .o 目标文件此时还没有绝对地址地址不固定链接链接器根据 sct 文件分散加载文件的地址分配规则把各个 .o 文件链接生成 .axf 文件此时所有符号都有了确定的地址格式转换按需只有走串口 ISP、IAP 或第三方烧录工具时才用 fromelf 把 axf 转 bin/hex注意 bin 是纯裸数据、不含地址hex 才带地址。KeilDAP 下载——无论 SRAM 调试还是烧 Flash——都直接用 axf用不到这一步加载调试器读 axf 里的地址信息把程序加载进 SRAM。核心理解sct 里 LR_IROM1 定义加载地址程序装到哪个地址ER_IROM1 定义运行地址代码指令和常量运行时所在的地址。烧录/加载跟随加载地址本实验两者重合都从 0x20000000 起。Flash 配置下两者分离代码加载运行Flash 就地执行RW 数据加载在 Flash初值副本、运行在 RAM——不重合的部分由链接器记账Region 表、启动代码搬运scatterload搬完才进 main。一句话重合 就地不重合 搬家。LR_IROM1 → 加载地址 装在哪下载/烧录时内容写到哪个地址ER_IROM1 → 运行地址 指令和常量在哪CPU 从这取指令、读常量RW_IRAM1 → 运行地址 变量在哪CPU 从这读写变量
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

深度学习全栈实战:PINN、Transformer、GNN、强化学习与扩散模型串联指南 2026/10/1 23:45:39

深度学习全栈实战:PINN、Transformer、GNN、强化学习与扩散模型串联指南

1. 为什么这五个方向值得放在一起学1.1 从“单点突破”到“全栈串联”的动机2026年做深度学习,如果还停留在“会调一个Transformer分类模型”或者“跑通一个DQN打游戏”的阶段,竞争力会非常有限。我这两年接触了不少工业界和学术界的项目,发现…

阅读更多 →
从单体到多Agent:复杂任务架构演进与Python实战 2026/10/1 23:45:38

从单体到多Agent:复杂任务架构演进与Python实战

1. 从一次上下文溢出报错说起如果你正在做 Agent 相关的开发,大概率见过这个报错:api error: 400 this models maximum context length is 1048576 tokens。一百多万 token 的上下文窗口,听起来已经大得离谱了,但真正跑起复杂任务…

阅读更多 →
Linux modprobe驱动加载失败的七步诊断与修复 2026/10/1 23:45:30

Linux modprobe驱动加载失败的七步诊断与修复

1. 项目概述:为什么一个简单的 modprobe 命令会卡住整个嵌入式开发流程?“modprobe 加载驱动失败”——这行报错,我过去三年在客户现场、实验室调试台、远程支持工单里见过不下两百次。它不像内核 panic 那样直接黑屏,也不像段错误…

阅读更多 →
YOLO目标检测实战全景:从版本选型到部署落地与进阶改进 2026/10/1 23:45:30

YOLO目标检测实战全景:从版本选型到部署落地与进阶改进

做目标检测这些年,我见过太多人一上来就纠结“YOLOv8和YOLOv5到底哪个好”,结果环境配了一周,训练跑起来又遇到BatchNorm崩溃、Loss直接变NaN,最后连数据集都懒得好好标。网上讲YOLO的PPT和教程视频一大把,但真踩过坑的…

阅读更多 →
腾讯WorkBuddy实战:从安装避坑到Agent智能工作流配置 2026/10/1 23:45:30

腾讯WorkBuddy实战:从安装避坑到Agent智能工作流配置

先说个结论:WorkBuddy 这东西,腾讯定位是“AI 工作台”,不是单纯给你补全代码的插件,而是一个能让 AI Agent 替你干活的完整环境。我重度用了几个星期,从安装、改缓存目录、配置自定义指令、折腾 Skill,到拿…

阅读更多 →
从零规划马德拉七日环岛:水渠步道、云海与酒庄全复盘 2026/10/1 23:45:29

从零规划马德拉七日环岛:水渠步道、云海与酒庄全复盘

两年前我在整理旅行清单时,无意中点进了一张大西洋中部的卫星图,屏幕上一小块布满沟壑的绿色土地让我愣住了。查资料发现它叫马德拉群岛(Madeira),葡萄牙的海外领地,以“大西洋明珠”闻名。越看越觉得这不是…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉