本文旨在展示一些设置STM32项目固件的方法,以便能够在嵌入式SRAM存储器中执行部分或整个固件。通常,STM32s直接从其嵌入式闪存执行用户代码。
得益于ART加速器和预取缓冲区的机制,该机制允许在最大cpu和外围频率下以最小的闪存等待状态延迟执行代码。
但在某些情况下,在SRAM中执行部分或完整固件是有用的。
这可以激励嵌入式系统开发人员,原因如下:
  • 提高被认为是关键的代码或中断的执行时间。
  • 通过将闪存置于低功耗模式来降低功耗。
  • 当CPU在SRAM中执行代码时,对闪存执行其他并发读/写操作。
  • 在进行调试会话时加快代码上传速度。

以下所有示例均使用:
  • 核-U575ZI-Q
  • STM32CubeIDE和STM32Cube_FW_U5_V1.2.0。
  • STM32管式编程器

案例1:在SRAM中放置和执行一个函数。

本部分描述了使用STM32CubeIDE在SRAM中链接和执行C函数的步骤。在这个例子中,我们将创建一个名为Prime_Calc_SRAM()的基本函数,负责计算给定区间内的素数。
 
1.运行STM32CubIDE,使用NUCLEO-U575ZI-Q板创建一个新的STM32项目(请参阅STM32 MCU Wiki中的STM32逐步入门)
 
2.将其命名为示例“Nucleo-U575ZI_SRAM_Exec”并生成项目。
3.将所有STM32CubeMX配置保留为默认值,以下情况除外:
  • MSI和PLL的时钟配置,以获得HCLK=160MHz
4.不要在“项目经理”选项卡中生成MX_USART1_UART_Init函数调用。

5.保存项目并生成代码。
6.构建项目以检查是否没有错误,您应该只看到以下警告:
../Core/Src/main.c:382:13:警告:已定义但未使用“MX_USART1_UART_Init”[-Wunused函数]

7.打开main.c文件,创建Prime_Calc_SRAM()函数的原型,该函数负责计算数组中的素数。
注:此函数定义为
__属性__((第(“.RamFunc”)节))关键字。
/*用户代码开始PFP*/static void __attribute__((部分(“.RamFunc”))Prime_Calc_SRAM(无效);/*用户代码端PFP*/
8.创建以下Private常量来定义素数数组的大小。
/*用户代码开始PD*/#定义PRIM_NUM 64U/*素数数组的大小*/*用户代码结束PD*/
9.创建以下Private变量。
注:此变量数组定义为__属性__((对齐(32))关键字,以确保数据与内存对齐。
/*用户代码开始PV*/static __attribute__((对齐(32)))uint32_t primes_ram[PRIM_NUM];/*用户代码端PV*/
10.最后创建Prime_Calc_SRAM()函数声明。
这个代码负责计算从1到64的素数(原始_编号)并将它们存储到primes_ram数组变量中。
注:至于原型的定义
__属性__((对齐(32))关键字。
/*用户代码BEGIN 0*/**@brief计算SRAM中的素数数量*@param None*@retval None*/static void __attribute__((部分(“.RamFunc”))Prime_Calc_SRAM(void){/*计算数组元素大小中的素数*/*初始条件*/primes_ram[0]=1;对于(uint32_t i=1;i<PRIM_NUM;){对于(uint32_t j=primes_ram[i-1]+1;j++){for(uint32_t k=2;k<=j;k++){primes_ram[i]=j;转到nexti;}}nexti:i++;}/*用户代码结束0*/
11.主要调用此函数。
/*用户代码开始2*/Prime_Calc_SRAM();/*用户代码端2*/
12.打开链接器文件STM32U575ZITXQ_FLASH.ld,查看以下内存定义:
/*内存定义*/MEMORY{RAM(xrw):ORIGIN=0x20000000,LENGTH=768K SRAM4(xrw
STM32U5嵌入了几个存储器,这些定义被用作物理位置,以便通知链接器放置每个固件符号、代码和数据的部分。
请参阅《STM32U5参考手册》内存组织章节(0456令吉)以获得设备中的存储器组织的完整描述。
然后链接器文件包含预定义的部分。用户可以创建自己的分区。
接下来,我们可以看到中断向量专用的部分(.isr_vector),默认情况下放在闪存的开头,然后程序代码(.text)也放在闪存中,变量、bss堆和RAM中的堆栈(.data、.bss、_user_heap_stack)。
对我们来说,我们的目标是使用在section.data中定义的节位置,它的名称是:.RamFunc,请参阅以下内容:
/*已将数据段初始化为“RAM”RAM类型内存*/.data:{_sdata=.;/*在数据开始处创建全局符号*/*(.data)/*.data节*/*(.data*)/*.data*节*/*
命令>RAM AT>FLASH用于通知链接器在启动初始化时通过从闪存复制到RAM来解决,所有符号都由这些放置部分配置定义。
在我们的情况下,函数Prime_Calc_SRAM()将在重置后从闪存复制到SRAM1。
此任务由startup_stm32u575zitxq.s程序集文件中的Reset_Handler:LoopCopyDataInit管理。

13.建设项目。
14
在Build Analyzer中找到Prime_Calc_SRAM符号,这是链接器阶段生成的映射文件的分层表示。

这里的要点如步骤12中所述,链接器从闪存地址0x0800317C解析函数,并在启动时将其复制到地址0x2000000C的SRAM1中。
第15页
放置两个断点:
  • 在startup_stm32u575zitxq.s中bl SystemInit行的Reset_Handler中
  • 在Prime_Calc_SRAM()函数中,在行Prime_ram[0]=1;
16启动STM32CubeIDE调试器。第一个断点使CPU停止重置句柄(_H):在里面 启动_stm32u575zitxq
 
17.打开Windows->显示视图->内存,并监视Prime_Calc_SRAM()函数在闪存和SRAM1中的地址位置。
18
我们可以看到,链接器将从0x0800317C开始、大小为96字节的函数放置在闪存部分。

19而SRAM1包含0x2000000C地址处的随机数据(或未分配)。
20.检查寄存器->通用寄存器监视表达式,PC计数器在闪存中。
这意味着代码是在闪存中执行的。

 
21.继续(F8)执行。第二个断点在main.c中Prime_Calc_SRAM()函数的开头再次停止CPU。
此外,与Prime_Calc_SRAM()函数相关的96字节代码从0x0800317C的闪存复制到0x2000000C的SRAM。
复印人循环复制数据初始化:在startup_stm32u575zitxq.s中
22.现在,再次检查电脑计数器。该功能现在在SRAM1中执行。
23.选择运行->删除所有断点并停止调试会话。
结论:使用gcc__attribute__((section(“.RamFunc”))关键字与链接器子节定义相结合,它可以帮助我们在SRAM内存中轻松地放置和执行函数或代码的一部分。
另一种方法是使用现有的__冲压(_FUNC)define(在stm32u5xx_hal_def.h中)来声明要在SRAM中执行的函数。
#定义__RAM_FUNC HAL_StatusTypeDef __attribute__((节(“.RamFunc”))

案例2:在SRAM中放置并执行中断。

第二部分描述了使用STM32CubeIDE在SRAM中链接和执行中断的步骤。
它基于前面步骤中使用的相同项目。
1.打开Nucleo-U575ZI_SRAM_Exec.oc,为Nucleo-U575ZI-Q板上的用户按钮配置EXTI中断。

 
2.打开NVIC类别,只需检查EXTI Line 13 interrupt Enabled(EXTI线路13中断已启用)。保留所有其他现有配置。
3.保存项目并重新生成代码。
 
4.构建项目并运行调试器。
5.在stm32u5xx_it.c中的第行放置一个断点:
HAL_GPIO_EXTI_IRQ处理器(USER_BUTTON_Pin);
 
6.按下Nucleo板上的用户按钮(蓝色按钮),CPU停止进入stm32u5xx_it.c中的EXTI13_IRQHandler()。
7.
检查寄存器->通用寄存器监视表达式,PC计数器在闪存中。
这意味着中断处理程序函数是在闪存中执行的。

8.停止调试器。
9.通过应用与前一种情况相同的属性关键字来更改EXTI13_IRQHandler名称。
添加
__属性__((第(“.RamFunc”)节))关键字。
/***@brief此函数处理EXTI Line13中断。*/void __attribute__((部分(“.RamFunc”))EXTI13_IRQ句柄(void){/*用户代码开始EXTI13_IRQn 0*/*用户代码结束EXTI13_IRCn 0*/HAL_GPIO_EXTI_IRQ句柄
 
10.重新生成项目,并运行调试器。将断点与步骤5保持在同一行。
11.再次按下Nucleo板上的用户按钮(蓝色按钮),CPU停止进入EXTI13_IRQHandler()。
12.检查寄存器->通用寄存器监视表达式,PC计数器位于地址0x20000070。
这意味着中断处理程序函数是在SRAM1中执行的。

13.选择运行->删除所有断点并停止调试会话。
结论:这个例子相当于前一章。它可以用于在嵌入式SRAM中放置和执行单个或多个中断的处理程序。
案例3:在SRAM中放置和执行整个项目(代码和中断)。

第三部分描述了使用STM32CubeIDE在SRAM中链接和执行完整的STM32项目的步骤。
它仍然基于前面步骤中使用的相同项目。
 

1.拆卸__属性__((第(“.RamFunc”)节))在Prime_Calc_SRAM()和EXTI13_IRQHandler()中。
注:对于Prime_Calc_SRAM(),您必须更改原型和函数定义。
2.打开system_stm32u5xx.c并搜索#定义VECT_TAB_SRAM象征
取消对相应行的注释(见下文)
/*************************其他配置************************//*!<如果需要在内部SRAM中重新定位矢量表,请取消注释以下行。*/#define VECT_TAB_SRAM#define VETC_TAB_OFFSET 0x00000000UL/*!<矢量表基偏移字段。此值必须是0x200的倍数。*//******************************************************************************/
3.在main while(1)循环中添加以下代码,稍后会有用:
/*无限循环*/*用户代码开始时*/WHILE(1){HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin);HAL_Delay(250);/*用户代码结束时*/
4.检查并在Project Explorer中找到CubeMX生成的链接器文件。

5.如第一种情况所述,CubeMX生成两种类型的链接器文件。
  • STM32U575ZITXQ_FLASH.ld涉及定义设备的所有物理存储器,如闪存和所有SRAM组,并将所有符号放置在这些部分中。
  • STM32U575ZITXQ_RAM.ld仅定义设备的RAM内存(见下文)
/*内存定义*/MEMORY{RAM(xrw):ORIGIN=0x20000000,LENGTH=768K SRAM4(xrw
在这个链接器配置中,所有符号都被放置在内存定义中,在这种情况下是在SRAM1(0x2000 0000)中,您可以检查每个SECTIONS的链接器命令>RAM。
.isr_vector(中断)和.text(代码)部分的示例。
/*将启动代码放入“RAM”RAM型内存*/.Irs_vector:{KEEP(*(.irs_vector))/*启动代码*/}>RAM/*将程序代码和其他数据放入“RAM)RAM型内存[/.text:{*(.text)/*.text节(代码)*/*(.text*)/*.text*节(代码
6.打开“项目属性”,在项目资源管理器中右键单击Nucleo-U575ZI_SRAM_Exec。
7.转到C/C++构建->设置,然后选择工具设置选项卡。
8.选择MCU GCC链接器->常规(见下文)
9.使用以下内容更改链接器脚本(-T)路径:${workspace_lc:/${ProjName}/STM32U575ZITXQ_SRAM.ld}

10.应用并关闭以保存新配置。
11.清理项目,然后重建。
12.检查生成分析器,该分析器提供对链接器配置生成的新映射文件的分析。

闪存部分不存在,所有符号都放在SRAM1中(中断矢量、代码和数据)
13.为了确保新项目将从SRAM1执行,我们使用STM32CubeProgrammer对闪存进行全芯片擦除。
14.打开STM32CubeProgrammer,连接到板,然后选择左下角的Full-chip erase Icon(全芯片擦除图标)。
 
15.闪存被擦除至0xFFFFFFFF值。
16.断开Nucleo板与STM32Cube编程器的连接。
17.在STM32CubeIDE中,启动调试器并检查与情况1和2相同的功能,如下所示:
  • EXTI13_IRQ按下用户按钮时的手柄。
  • Prime_Calc_RAM()函数结果的结果。
  • 蓝色LED闪烁
所有代码都是从SRAM1中放置和执行的,只要NUCLEO-U575ZI-Q通电,就可以进行调试。
如果Nucleo板断电,SRAM将丢失所有数据,您需要启动新的调试会话来再次加载和执行代码。
18.停止调试器。只需按下板上的重置按钮(黑色按钮)即可重新启动固件:蓝色LED2闪烁。
19.蓝色LED不闪烁,代码未执行?电路板没有断电,为什么代码没有重新启动?
20.即使我们将所有项目配置为在SRAM中放置和执行完整代码,所有STM32都有一个启动模式,可以选择处理器在启动时使用的内存地址。
对于STM32U5系列,必须参考第4章引导模式中的RM0456。
所有其他STM32在其各自的参考手册中也有相同的章节。
RM0456中的表25解释了TZ=0时的引导模式(非安全模式)

默认情况下,在全新的STM32U5上,用于引导模式的内存是闪存。
它取决于BOOT0 PH3引脚电平,在我们的情况下,该引脚通过电阻器连接到VSS。
我们只需要更改由用户选项字节定义的NSBOOTADD0[24:0],并在SRAM1地址中配置引导模式,以与我们的链接器配置对齐。
21.打开STM32CubeProgrammer,并与板建立连接。
22.在左侧,选择选项字节图标。
23.选择引导配置,您应该看到NSBOOTADD0字段(见下文)

24.将NSBOOTADD0地址的值更改为0x20000000。然后单击Apply(应用),将新的选项字节值写入STM32的闪存中。

25.观察蓝色LED2,它再次闪烁。这是因为在NSBOOTADD0的写入序列之后已经执行了STM32U5的复位。由于在该序列期间板仍然通电,因此SRAM1的内容没有丢失,固件可以再次开始在SRAM1中执行。

结论

本教程以SRAM1为例,展示了放置和运行项目及其整个代码的重要步骤:
  • 一种应用程序内编程固件。
  • 一种低功耗应用程序,可关闭闪存以降低功耗。
这种做法需要对STM32CubeIDE的配置进行一些小的更改。
任何其他IDE环境,如IAR-EWARM或KEIL-ARM,都可能处理相同的项目配置,请参阅它们各自的文档以了解链接器的语法。