STM32F407在RAM中执行程序

STM32F407在flash中执行代码的速度比在ram中执行代码的速度快。因为STM32F407有一颗“自适应实时存储器加速器”,这里不讨论ART Accelerator的加速方案。
把代码放在RAM中执行纯粹是为了学习。

将个别函数的代码放到RAM中运行

使用自己编写的链接脚本(sct文件)。
在这里插入图片描述
再来看自己编写的分散加载文件内容:

LR_IROM1 0x08000000 0x00004000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00004000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00100000  {  ; RW data
   .ANY (+RW +ZI)
   *.o (RAM_SPEED)
  }
}

LR_ROM1后边的0x08000000是加载地址(代码存储时的地址),0x00004000是加载域的大小。
ER_IROM1 0x08000000是链接地址(代码执行时的地址,PC寻址的地址),0x00004000是执行域的大小。
在链接脚本中定义了一个代码段RAM_SPEED。
程序中将函数指定到代码段RAM_SPEED。

#pragma arm section code = "RAM_SPEED"
void my_delay(void)
{
	volatile uint16_t i,j;
	for(i=0;i<1000;i++)
		for(j=0;j<1000;j++);
}
#pragma arm section

编译链接后的加载地址和链接地址如下:
在这里插入图片描述
运行时PC指针如下图:
在这里插入图片描述
PC指针确实指向了SRAM区域,而不是FLASH区域。
通过打印发现my_delay在SRAM中运行时,耗时89453us。my_delay在FLASH中运行时,耗时71542us。也证实了在F407芯片上SRAM运行程序比FLASH运行程序慢。

F407有一块CCM,我们把程序放到CCM中是否可行?
修改链接脚本

LR_IROM1 0x08000000 0x00004000  {    ; load region size_region
	ER_IROM1 0x08000000 0x00004000  {  ; load address = execution address
		*.o (RESET, +First)
		*(InRoot$$Sections)
		.ANY (+RO)
		.ANY (+XO)
	}
	RW_IRAM1 0x20000000 0x00100000  {  ; RW data
		.ANY (+RW +ZI)
	}
	RW_IRAM2 0x10000000 0x00010000  {  ; RW data
		*.o (RAM_SPEED)
	}
}

答案是不行,程序执行到my_delay的时候就hardfault了,原因是CCM呢M4内核只通过D-BUS总线相连,M4内核是通过I-BUS执行取指令的,所以不行。
还有一种方式是通过__attribute__声明函数,将函数声明在RAM_SPEED代码段,sct文件和上边那种方式一样。

__attribute__((section("RAM_SPEED"))) void my_delay(void)
{
	volatile uint16_t i,j;
	for(i=0;i<1000;i++)
		for(j=0;j<1000;j++);
}

将整个工程代码放到RAM中运行

这种方案思路是写两套代码,一套是Boot代码,一套是APP代码。Boot代码在flash中存储在flash中执行,Boot上电将APP的代码拷贝到SRAM中,再跳转到SRAM中执行APP代码。APP代码是在flash中存储,在SRAM中执行。

Boot程序:使用默认的分散加载文件,target选项卡如下:
在这里插入图片描述

	/*其它必要操作*/
	/*从flash中把代码搬运到RAM中  共60K*/
	memcpy(p,(uint8_t *)0x08004000,60*1024);

	jump2app = (IapFun)*(vu32*)(0x20000800 + 4);
	MSR_MSP(*(vu32*)0x20000800);//存0x20000800地址处开始存放程序
	jump2app();  //跳转到APP

由上图和代码可以看出,我们给Boot分配了16KByte flash,2Kbyte RAM,这个根据个人实际情况分配。
APP程序:
先看一下分散加载文件

LR_IROM1 0x08004000   0x000FC000{    ; load region size_region
  ER_IROM1 0x20000800 0x0000F000{  
   *.o (RESET, +First)  
;  *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 +0  {  ; RW data
   .ANY (+RW +ZI)
  }
}

Boot程序占用了16K Byte flash,那么我们应用程序就从0x08004000开始存储,也就是APP 分散加载文件的加载地址是0x08004000,给APP分配了0x000FC000个Bytes flash空间。APP的执行域(链接地址)从0x20000800开始,这里想一下能不能从0x20000000开始,肯定不行的呀,如果我们改成了0x20000000,那么Boot程序拷贝的时候就往0x20000000开始的地址处拷贝数据,但是拷贝的那一刻是在Boot中完成的,Boot会使用0x20000000到0x20000800这一段RAM,所以不行。
ER_ROM1中,把RESET链接到执行域(0x20000800)最前边,RO(只读数据段)和XO(执行段)也链接到执行域。
RW_IRAM1中,把初始化不为0的数据段和BSS段放到执行域RW_IRAM1中。
到这里理论上就可以了,我们需要做的核心工作:
1.在Boot程序从Flash中拷贝APP代码到SRAM。
2.跳转到SRAM,运行APP。
3.编写APP的分散加载文件。

但是实际上由于ST官方的启动文件中调用了__main,导致我们修改APP的分散加载文件后会链接报错。如下图
在这里插入图片描述
报错的意思就是entry那些数据段不能放到非启动区域,
由于__main是库文件,所以我们无法修改,也不知道如何修改。所以我放弃了使用__main,我大概知道__main库函数做了那些工作,__main函数就是把RW数据段从flash中搬运到RAM中,把RAM中的BSS数据段清零,最后跳转到main函数,那我们就自己在启动文件中实现这些工作。
汇编文件修改成如下代码:

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

                PRESERVE8
                THUMB


; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     0x20000800               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

                ; External Interrupts
                DCD     WWDG_IRQHandler                   ; Window WatchDog                                        
				;省略***
                DCD     FPU_IRQHandler                    ; FPU
                                         
__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors

                AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
					 
					 
				IMPORT	mymain
				IMPORT 	my_memcpy
				IMPORT	my_memset
                IMPORT  SystemInit
					
				IMPORT	|Image$$RW_IRAM1$$RW$$Base|
				IMPORT	|Load$$RW_IRAM1$$RW$$Base|
				IMPORT	|Image$$RW_IRAM1$$RW$$Length|
					
				IMPORT	|Image$$RW_IRAM1$$ZI$$Base|
				IMPORT	|Image$$RW_IRAM1$$ZI$$Length|					 
					 
				;重定位RW段
				LDR     R0, =|Image$$RW_IRAM1$$RW$$Base|
				LDR     R1, =|Load$$RW_IRAM1$$RW$$Base|
				LDR     R2, =|Image$$RW_IRAM1$$RW$$Length|
				BL		my_memcpy
				
				;清除BSS段
				LDR     R0, =|Image$$RW_IRAM1$$ZI$$Base|
				LDR     R1, =0
				LDR     R2, =|Image$$RW_IRAM1$$ZI$$Length|
				BL		my_memset			 
					 
;				BL	SystemInit
                LDR     R0, =SystemInit
                BLX     R0 

;				BL	mymain
                LDR     R0, =mymain
                BX      R0					 
					 
					 
					 
				;IMPORT  SystemInit
				;IMPORT  __main

                 ;LDR     R0, =SystemInit
                 ;BLX     R0
                 ;LDR     R0, =__main
                 ;BX      R0
                 ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP
MemManage_Handler\
                PROC
                EXPORT  MemManage_Handler          [WEAK]
                B       .
                ENDP
BusFault_Handler\
                PROC
                EXPORT  BusFault_Handler           [WEAK]
                B       .
                ENDP
UsageFault_Handler\
                PROC
                EXPORT  UsageFault_Handler         [WEAK]
                B       .
                ENDP
SVC_Handler     PROC
                EXPORT  SVC_Handler                [WEAK]
                B       .
                ENDP
DebugMon_Handler\
                PROC
                EXPORT  DebugMon_Handler           [WEAK]
                B       .
                ENDP
PendSV_Handler  PROC
                EXPORT  PendSV_Handler             [WEAK]
                B       .
                ENDP
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP

Default_Handler PROC

                EXPORT  WWDG_IRQHandler                   [WEAK]                                        
                ;省略
                EXPORT  FPU_IRQHandler                    [WEAK]

WWDG_IRQHandler                                                       
 ;省略
FPU_IRQHandler
   
                B       .

                ENDP

                ALIGN

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
                 IF      :DEF:__MICROLIB
                
                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit
                
                 ELSE
                
                 IMPORT  __use_two_region_memory
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap

                 LDR     R0, =  Heap_Mem
                 LDR     R1, =(Stack_Mem + Stack_Size)
                 LDR     R2, = (Heap_Mem +  Heap_Size)
                 LDR     R3, = Stack_Mem
                 BX      LR

                 ALIGN

                 ENDIF

                 END

;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****

main函数的名字我修改了mymain。在汇编中调用了my_memcpy和my_memset函数,代码实现如下

void my_memcpy(void *pcdest,void *pcsrc,int len)
{
	uint8_t *dest = pcdest;
	uint8_t *src = pcsrc;
	while(len--)
	{
		*dest++ = *src++;
	}
}
void my_memset(void *pcdest,int val,int len)
{
	uint8_t *dest = pcdest;
	while(len--)
	{
		*dest++ = 0;
	}
}

我栈顶指针故意写成0x20000800,因为此时此刻已经来到了APP的世界,0x20000000到0x20000800属于Boot,我们在APP中又把这2K Bytes空间利用起来了,分配给了APP的栈空间。

这个APP程序虽然可以在RAM运行,但是有个问题,就是链接的时候把工程中的所有代码都链接到映像文件了,导致映像文件特别大,我们需要的是只链接那些调用到的代码,把所有都链接进去的原因是程序缺少指定一个入口点,这是在ARM LINK手册中看到的,如下图
在这里插入图片描述
我不知道如何解决,有知道的告诉一下。

<think>好的,用户现在想了解关于C51程序代码的内容。让我先回想一下之前的对话历史。之前用户询问了关于Arduino头文件的相关内容,我详细介绍了头文件的作用、结构、使用规范以及在Arduino项目中的特殊处理。现在用户转向了C51,这是一个8051单片机的开发环境,常用于嵌入式系统开发。 首先,我需要确定用户的需求。他们可能刚开始学习单片机开发,或者从Arduino转向更底层的C51编程。用户可能想了解C51程序的基本结构、语法特点、与标准C的不同之处,以及如何开始编写和调试C51代码。 接下来,我要考虑用户可能的背景。如果他们已经熟悉Arduino,那么C51的底层操作可能会有些挑战,比如直接操作寄存器和中断处理。需要比较两者的不同,帮助用户过渡。同时,用户可能需要实际的代码示例来理解如何控制硬件,比如LED闪烁、定时器中断等常见用例。 然后,我要确保回答结构清晰,分点说明,符合用户之前提到的结构要求。包括C51的特点、程序结构、语法要点、开发流程、示例代码以及常见问题。这样用户能逐步深入了解C51编程的各个方面。 另外,用户可能遇到的一些常见问题,比如头文件包含、硬件资源操作、代码优化等,也需要在回答中提及解决方案。例如,提到使用sfr和sbit来访问特殊功能寄存器,或者如何通过定时器中断实现精确延时,这些都是在实际开发中常见的问题点。 还要注意使用中文,避免专业术语过于晦涩,必要时进行解释。例如,在解释“编译模式”时,提到SMALL、COMPACT、LARGE的区别,以及它们对内存分配的影响,帮助用户理解如何根据项目需求选择合适的模式。 最后,提供示例代码,并详细注释每个部分的功能,这样用户可以直接参考并动手实践。同时,提醒常见的错误和解决方法,比如未定义寄存器的错误,或者中断服务函数的编写注意事项,帮助用户避免常见的陷阱。 总结来说,用户需要的是一个全面的C51入门指南,涵盖基本概念、代码结构、开发流程和实际示例,同时解答可能遇到的问题。需要确保内容准确、结构清晰,并且易于理解,特别是针对从Arduino转向C51的用户,强调底层操作和资源管理的重要性。</think>以下是关于C51单片机程序开发的详细指南,包含代码规范、架构设计和实用技巧: --- ### 一、C51程序核心特点 1. **哈佛架构**:代码存储器(ROM)与数据存储器(RAM)独立编址 2. **存储类型**:通过关键字指定存储区域 ```c data // 直接寻址片内RAM(128B) bdata // 可位寻址区(16B@20H-2FH) idata // 间接寻址片内RAM xdata // 外部扩展RAM(最大64KB) code // 程序存储器 ``` 3. **特殊功能寄存器(SFR)**:通过`sfr`关键字访问硬件模块 ```c sfr P0 = 0x80; // P0端口寄存器 sbit P0_0 = P0^0; // P0.0引脚定义 ``` --- ### 二、典型程序结构 #### 1. 最小工程框架 ```c #include <reg52.h> // 包含SFR定义 void delay_ms(unsigned int ms) { // 软件延时函数 unsigned int i, j; for(i=0; i<ms; i++) for(j=0; j<114; j++); } void main() { // 主函数 while(1) { // 死循环 P1 = 0x00; // 点亮所有LED delay_ms(500); P1 = 0xFF; // 熄灭所有LED delay_ms(500); } } ``` #### 2. 带中断的完整结构 ```c #include <reg52.h> bit flag_10ms = 0; // 10ms定时标志 void Timer0_Init() { // 定时器0初始化 TMOD &= 0xF0; // 设置模式1(16位定时器) TMOD |= 0x01; TH0 = 0xDC; // 10ms定时初值(11.0592MHz) TL0 = 0x00; ET0 = 1; // 允许定时器0中断 EA = 1; // 总中断允许 TR0 = 1; // 启动定时器 } void main() { Timer0_Init(); while(1) { if(flag_10ms) { flag_10ms = 0; // 执行周期任务... } } } void Timer0_ISR() interrupt 1 { // 定时器0中断服务程序 TH0 = 0xDC; // 重装初值 TL0 = 0x00; flag_10ms = 1; // 设置标志位 } ``` --- ### 三、关键编程技术 #### 1. 精确延时实现 ```c void Delay10us() { // 误差0us @11.0592MHz unsigned char i; _nop_(); // 单周期空操作 i = 2; while(--i); } ``` #### 2. 位操作优化 ```c #define LED P1_7 // 定义LED控制位 void Led_Blink() { LED = ~LED; // 位取反操作 delay_ms(200); } ``` #### 3. EEPROM操作 ```c #include <intrins.h> void EEPROM_Write(unsigned char addr, unsigned char dat) { IAP_CONTR = 0x80; // 使能IAP IAP_CMD = 0x02; // 写命令 IAP_ADDRL = addr; // 地址低字节 IAP_ADDRH = 0x00; IAP_DATA = dat; // 写入数据 _nop_(); IAP_TRIG = 0x5A; // 触发写入 _nop_(); IAP_CONTR = 0x00; // 关闭IAP } ``` --- ### 四、工程优化技巧 1. **内存管理策略** ```c xdata unsigned char buffer[512]; // 大数组声明在外部RAM idata unsigned char stack_ptr; // 高频变量放在内部RAM ``` 2. **混合编程接口** ```c #pragma ASM // 插入汇编代码 MOV A, #55H ADD A, R0 #pragma ENDASM ``` 3. **看门狗配置** ```c void Watchdog_Init() { WDT_CONTR = 0x3C; // 预分频128,使能看门狗 } void Feed_Dog() { // 喂狗函数 WDT_CONTR |= 0x10; } ``` --- ### 五、常见问题解决方案 1. **程序跑飞** - 现象:代码执行异常,无法进入中断 - 检查: - 堆栈溢出(51默认堆栈区在内部RAM) - 中断向量地址错误 - 未正确初始化SP寄存器 2. **变量值异常** ```c volatile unsigned int counter; // 多线程访问需加volatile ``` 3. **代码体积优化** - 使用`small`编译模式 - 启用`OMF2`格式生成优化 - 合并相似功能函数 --- ### 六、Keil开发环境配置要点 1. **目标设置** - Memory Model:根据RAM需求选择 - Code Rom Size:选择适当编译模式 - SMALL: 所有函数在2KB内 - COMPACT: 使用页寻址 - LARGE: 完整64KB空间 2. **输出文件生成** ```properties CREATE_HEX_FILE(1) // 生成HEX文件 DEBUG_INFORMATION(2) // 包含调试信息 ``` 3. **调试技巧** - 使用逻辑分析仪视图观察IO变化 - 在Watch窗口监控关键变量 - 利用Performance Analyzer优化代码 --- 通过规范的C51代码开发,可有效提升: - 代码可维护性(模块化设计) - 执行效率(合理选择存储类型) - 硬件可靠性(精确时序控制) - 可移植性(硬件抽象层设计) 建议开发时遵循以下原则: 1. 关键函数注释执行时间(机器周期数) 2. 高频变量优先使用data区 3. 中断服务程序保持简洁 4. 重要端口操作添加保护锁存
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值