赞
踩
原文 : http://www.elecfans.com/d/651468.html
存储器映射是指把芯片中或芯片外的FLASH,RAM,外设,BOOT,BLOCK等进行统一编址。即用地址来表示对象。
这个地址绝大多数是由厂家规定好的,用户只能用而不能改。用户只能在挂外部RAM或FLASH的情况下可进行自定义。
Cortex-M3支持4GB的存储空间,它的存储系统采用统一编址的方式; 程序存储器、数据存储器、寄存器被组织在4GB的线性地址空间内,以小端格式(little-endian)存放。由于Cortex-M3是32位的内核,因此其PC指针可以指向2^32=4G的地址空间,也就是0x0000_0000——0xFFFF_FFFF这一大块空间。见图1:
图1:Cortex-M3的存储器映射
Cortex-M3内核将0x0000_0000——0xFFFF_FFFF这块4G大小的空间分成8大块:代码、SRAM、外设、外部RAM、外部设备、专用外设总线-内部、专用外设总线-外部、特定厂商(见图1)。这就导致了,使用该内核的芯片厂家必须按照这个进行各自芯片的存储器结构设计,如stm32。
图2:Cortex-M3与中密度stm32的存储器映射对比
图三:
图2中可以很清晰的看到,STM32的存储器结构和Cortex-M3的很相似(这是因为stm32本来就是按照cortex_m3内核来设计硬件的),不同的是,STM32加入了很多实际的东西,如:Flash、SRAM等。只有加入了这些东西,才能成为一个拥有实际意义的、可以工作的处理芯片——STM32。STM32的存储器地址空间被划分为大小相等的8块区域,每块区域大小为512MB(如:0x20000000~0x40000000)。对STM32存储器知识的掌握,实际上就是对Flash和SRAM这两个区域知识的掌握。
不同类型的STM32单片机的SRAM大小是不一样的,但是他们的起始地址都是0x2000 0000,终止地址都是0x2000 0000+其固定的容量大小。SRAM的理解比较简单,其作用是用来存取各种动态的输入输出数据、中间计算结果以及与外部存储器交换的数据和暂存数据。设备断电后,SRAM中存储的数据就会丢失。
STM32的Flash,严格说,应该是Flash模块。三个分区的称呼与datasheet保持一致。该Flash模块包括:
Flash主存储区(Main memory)Flash:存放代码的地方,如图2中的FLASH区域:128KB(有的是1M)(0x08000000~0x0801ffff)(不同容量的Flash终止地址不同);
Flash信息区(Information block),该区域又可以分为Option Bytes和System Memory区域;
System Memory:STM32在出厂时,已经固化了一段程序在System memory(medium-density devices的地址为:0x1FFF_F000,大小为2KB)存储器中。这段程序就是一个固定好的,并且没法修改的Boot Loader(见编程手册PM0042这种描述)。
Option Bytes:可以按照用户的需要进行配置(如配置看门狗为硬件实现还是软件实现);该区域除了互联型所用型号地址都一样:(0x1fff_f000~0x1fff_f80f)图中终止地址有误:应为0x1fff_f80f,正好16个字节。
Flash存储接口寄存器区(Flash memory interface),用于片上外设。是图2中从0x40000000开始的PERIPHERALS区域。也称作外设存储器映射,对该区域操作,就是对相应的外设进行操作。
-----------------------------
原文 : http://www.eeworld.com.cn/mcu/2018/ic-news101041642.html
STM32采用的Crotex_M3内核存储使用哈佛结构,程序存储器,数据存储器,寄存器,外设都被分配在4G_bytes(32bits总线宽度)的线性空间上,但并不是所有的空间都是有效的。
存储器采用小端模式。
小端模式和大端模式
定义:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端
32位的数据0x12345678从地址0x20000000开始存储,小端和大端的存储区别如下图
SRAM
SRAM的起始地址从0x2000 0000地址开始,可以进行字(32-bits),半字(16-bits),字节(8-bits)访问
bit-band
STM32的SRAM和外设寄存器并不能进行bit访问,但STM32对所有的SRAM和外设寄存器映射了两个被称为bit-band的区域,对这个区域的字写对应到想用SRAM的bit写。
区域映射算法
bit_word_addr = bit_band_base + (byte_offset x 32) + (bit_number × 4)
bit_band_base为bit-band的起始地址,bit-band有两个区域0x2200 0000和0x4200 0000分别对应SRAM和外设
byte_offset SRAM对应起始地0x20000000的偏移地址,
bit_number 需要访问的bit号
例如需要访问0x2000 0300内存bit-2,公式应该是
0x22006008 = 0x22000000 + (0x300*32) + (2*4).
Flash
flash的读写接口(FLITF)包含读预期缓存,选项字节,Flash写/擦除,读/写保护
读:读flash需要编写等待数,不同的SYSCLK等待数不同
指令预期,这个功能在系统复位后自动开启
半周期,用于节约功耗
写和擦除
flash只能进行半字的写,要进行擦除和写操作时,内部RC振荡器(HSI)必须打开
flash的擦除操作能进行页和全flash擦除
STM32的页被分为1kbyte或者2Kbyte
Flash,SRAM寄存器和输入输出端口被组织在同一个4GB的线性地址空间内。可访问的存储器空间被分成8个主要块,每个块为512MB。
Flash 与SRAM的区别
见 本人: https://blog.csdn.net/lizhoufan/article/details/84841540
总的来说就是,Flash可以像硬盘一样存数据,也可以直接像RAM运行,一般在里面放bootload ,程序执行代码。
1. STM32中的堆栈。
这个我产生过混淆,导致了很多逻辑上的混乱。首先要说明的是单片机是一种集成电路芯片,集成CPU、RAM、ROM、多种I/O口和中断系统、定时器/计数器等功能。CPU中包括了各种总线电路,计算电路,逻辑电路,还有各种寄存器。Stm32有通用寄存器 R0‐ R15 以及一些特殊功能寄存器,其中包括了堆栈指针寄存器。当stm32正常运行程序的时候,来了一个中断,CPU就需要将寄存器中的值压栈到RAM里,然后将数据所在的地址存放在堆栈寄存器中。等中断处理完成退出时,再将数据出栈到之前的寄存器中,这个在C语言里是自动完成的。
2. 编程中的堆栈。
在编程中很多时候会提到堆栈这个东西,准确的说这个就是RAM中的一个区域。我们先来了解几个说明:
(1) 程序中的所有内容最终只会出现在flash,ram里(不外扩)。
(2) 段的划分,是将类似数据种类存储在一个区域里,方便管理,但正如上面所说,不管什么段的数据,都是最终在flash和ram里面。
C语言上分为栈、堆、bss、data、code段。具体每个段具体是存储什么数据的,直接百度吧。重点分析一下STM32以及在MDK里面段的划分。
MDK下Code, RO-data,RW-data,ZI-data这几个段:
Code是存储程序代码的。
RO-data是存储const常量和指令。
RW-data是存储初始化值不为0的全局变量。
ZI-data是存储未初始化的全局变量或初始化值为0的全局变量。
Flash=Code + RO Data + RW Data;
RAM= RW-data+ZI-data;
这个是MDK编译之后能够得到的每个段的大小,也就能得到占用相应的FLASH和RAM的大小,但是还有两个数据段也会占用RAM,但是是在程序运行的时候,才会占用,那就是堆和栈。在stm32的启动文件.s文件里面,就有堆栈的设置,其实这个堆栈的内存占用就是在上面RAM分配给RW-data+ZI-data之后的地址开始分配的。
堆:是编译器调用动态内存分配的内存区域。
栈:是程序运行的时候局部变量的地方,所以局部变量用数组太大了都有可能造成栈溢出。
堆栈的大小在编译器编译之后是不知道的,只有运行的时候才知道,所以需要注意一点,就是别造成堆栈溢出了。。。不然就等着hardfault找你吧。
3. OS中的堆栈及其内存管理。
嵌入式系统的堆栈,不管是用什么方法来得到内存,感觉他的方式都和编程中的堆差不多。目前我知道两种获得内存情况:
(1)用庞大的全局变量数组来圈住一块内存,然后将这个内存拿来进行内存管理和分配。这种情况下,堆栈占用的内存就是上面说的:如果没有初始化数组,或者数组的初始化值为0,堆栈就是占用的RAM的ZI-data部分;如果数组初始化值不为0,堆栈就占用的RAM的RW-data部分。这种方式的好处是容易从逻辑上知道数据的来由和去向。
(2)就是把编译器没有用掉的RAM部分拿来做内存分配,也就是除掉RW-data+ZI-data+编译器堆+编译器栈后剩下的RAM内存中的一部分或者全部进行内存管理和分配。这样的情况下就只需要知道内存剩下部分的首地址和内存的尾地址,然后要用多少内存,就用首地址开始挖,做一个链表,把内存获取和释放相关信息链接起来,就能及时的对内存进行管理了。内存管理的算法多种多样,不详说,这样的情况下:OS的内存分配和自身局部变量或者全局变量不冲突,之前我就在这上面纠结了很久,以为函数里面的变量也是从系统的动态内存中得来的。这种方式感觉更加能够明白自己地址的开始和结束。
这两种方法我感觉没有谁更高明,因为只是一个内存的获取方式,高明的在于内存的管理和分配。
keil编译后code,RO-data,RW-data,ZI-data含义及mcu的flash实际存储数据
keil编译后会有一行:Program Size:Code=xxxRO-data=xxx RW-data=xxx ZI-data=xxx
Code 代表执行的代码,程序中所有的函数都位于此处。
RO-data 代表只读数据,程序中所定义的全局常量数据和字符串都位于此处。
RW-data 代表已初始化的读写数据,程序中定义并且初始化的全局变量和静态变量位于此处。
ZI-data 代表未初始化的读写数据,程序中定义了但没有初始化的全局变量和静态变量位于此处。ZI英语是zero initial,就是程序中用到的变量并且被系统初始化为0的变量的字节数,keil编译器默认是把你没有初始化的变量都赋值一个0,这些变量在程序运行时是保存在RAM中的。
2.如果你查看.map文件,如下例子:
==============================================================================
Total RO Size (Code + RO Data) 2980 ( 2.91kB)
Total RW Size (RW Data + ZI Data) 104 ( 0.10kB)
Total ROM Size (Code + RO Data + RW Data) 2988 ( 2.92kB)
==============================================================================
Total ROM Size (Code + RO Data + RW Data)这样所写的程序占用的ROM的字节总数,也就是说程序所下载到ROM flash 中的大小。为什么Rom中还要存RW,因为掉电后RAM中所有数据都丢失了,每次上电RAM中的数据是被重新赋值的,每次这些固定的值就是存储在Rom中的,为什么不包含ZI段呢,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可,包含进去反而浪费存储空间。
实际上,ROM中的指令至少应该有这样的功能:
1. 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。
2. 将ZI所在的RAM区域全部清零,因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中。
在程序运行的最初阶段,RO中的指令完成了这两项工作后C程序才能正常访问变量。否则只能运行不含变量的代码。
一、已经了解的
如图是MDK编译之后的消息
从中知道
占用的FLASH = Code + RO-data + RW-data
stm32 FLASH的起始地址是0x08000000,当然也可以自定义起始地址,不过记得在main函数中定义变量后加一句SCB->VTOR=FLASH_BASE | OFFSET;OFFSET是想要偏移的量,可宏定义或直接0xXX。
当然也可以调用库函数 NVIC_SetVectorTable()进行偏移,效果一样。IAP升级这样用的多。
占用的SRAM = RW-data + ZI-data
stm32 SRAM的起始地址是0x20000000。看MDK的魔法棒设置target选项也是可以设置RAM起始地址和大小的,不过一般我不动RAM。
我一般是这样记忆的,前面3个是给FLASH,后面2个是给SRAM。
含义:
Code :代码
RO-data :只读数据,字符串常量(const修饰的)
RW-data:可读可写数据,已经初始化的全局变量 或者 static修饰的变量(不管局部变量还是全局变量,没有显示地初始化的话会初始化为0)
ZI-data :没有进行初始化的全局变量
二、基于.map文件的分析,我逐个试出来的结果如下,右边是.map文件的内容。
PAD是垫子、卫生巾的意思,占用两个字节,姑且理解为隔开各个段吧。
宏定义、结构体、联合体、枚举类型等都属于代码。RAM只存放变量。但FLASH不只是存放代码,还有变量,就是和RAM都有的交集,即MDK生成的 RW-data (全局变量、static变量)
这么多的变量,他们是怎么排列在RAM中的呢,看上面.map文件中的地址就知道了。就是已初始化的变量在前面,然后跟着是未初始化的全局变量,然后是堆,最后是栈。
也就是说栈和堆的起始地址是不可控的,是根据我们程序的全局变量多少计算出来的,一句话,吃剩下的。
三、裁剪栈和堆的大小
1.当我们程序运行到一个函数中莫名其妙的就崩了,进入HardFault_Handler,数组也没有越界啊。
其中可能的一个原因就是局部变量过多,栈溢出,不够用了,需要我们Tailor(裁剪)一下RAM。ps:数组越界也属于栈溢出。在启动文件中进行裁剪。HardFault_Handler网上也有很多分析方法。
2.例如,如下启动文件我们可以吧Stack_Size从0x400(1KB)改到0x800(2KB)一般就不会出现崩溃了,如果还崩,那要找找其他原因(是不是函数使用了非法的指针(即非法地址,0xE0000000之类的MCU不认,不能访问的地址))等等。
3.当然有时候栈加上其他的,大小超过了RAM总大小,编译器就会提示不够,就要一点一点加着试。
这时大小超过了RAM总大小,也能从一定程度说明程序设计存在不合理,几乎用光了RAM。比如全局变量设置的过大,实际没用这么多,也设置这么多,一般多实际最大使用量加2个字节都没问题。大型的全局变量(即结构体全局变量、联合体全局变量)过多,重复繁杂,共用率不高。就要逐个文件删繁就简,砍掉能砍的全局变量,缩小能缩的结构体,字节对齐,各种看家本领使出来。砍到不能再砍只能考虑砍功能或者换芯片了。
1,首先来看:栈(STACK)的问题.
函数的局部变量,都是存放在"栈"里面,栈的英文是:STACK.STACK的大小,我们可以在stm32的启动文件里面设置,以战舰stm32开发板为例,在startup_stm32f10x_hd.s里面,开头就有:
Stack_Size EQU 0x00000800
表示栈大小是0X800,也就是2048字节.这样,CPU处理任务的时候,函数局部变量做多可占用的大小就是:2048字节,注意:是所有在处理的函数,包括函数嵌套,递归,等等,都是从这个"栈"里面,来分配的.
所以,如果一个函数的局部变量过多,比如在函数里面定义一个u8 buf[512],这一下就占了1/4的栈大小了,再在其他函数里面来搞两下,程序崩溃是很容易的事情,这时候,一般你会进入到hardfault....
这是初学者非常容易犯的一个错误.切记不要在函数里面放N多局部变量,尤其有大数组的时候!
对于栈区,一般栈顶,也就是MSP,在程序刚运行的时候,指向程序所占用内存的最高地址.比如附件里面的这个程序序,内存占用如下图:
图中,我们可以看到,程序总共占用内存:20+2348字节=2368=0X940
那么程序刚开始运行的时候:MSP=0X2000 0000+0X940=0X2000 0940.
事实上,也是如此,如图:
图中,MSP就是:0X2000 0940.
程序运行后,MSP就是从这个地址开始,往下给函数的局部变量分配地址.
再说说栈的增长方向,我们可以用如下代码测试:
//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;
//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
static u8 *addr=NULL; //用于存放第一个dummy的地址。
u8 dummy; //用于获取栈地址
if(addr==NULL) //第一次进入
{
addr=&dummy; //保存dummy的地址
find_stack_direction (); //递归
}else //第二次进入
{
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
else stack_dir=0; //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.
}
}
这个代码不是我写的,网上抄来的,思路很巧妙,利用递归,判断两次分配给dummy的地址,来比较栈是向下生长,还是向上生长.
如果你在STM32测试这个函数,你会发现,STM32的栈,是向下生长的.事实上,一般CPU的栈增长方向,都是向下的.
2,再来说说,堆(HEAP)的问题.
全局变量,静态变量,以及内存管理所用的内存,都是属于"堆"区,英文名:"HEAP"
与栈区不同,堆区,则从内存区域的起始地址,开始分配给各个全局变量和静态变量.
堆的生长方向,都是向上的.在程序里面,所有的内存分为:堆+栈. 只是他们各自的起始地址和增长方向不同,他们没有一个固定的界限,所以一旦堆栈冲突,系统就到了崩溃的时候了.
同样,我们用附件里面的例程测试:
stack_dir的地址是0X20000004,也就是STM32的内存起始端的地址.
这里本来应该是从0X2000 0000开始分配的,但是,我仿真发现0X2000 0000总是存放:0X2000 0398,这个值,貌似是MSP,但是又不变化,还请高手帮忙解释下.
其他的,全局变量,则依次递增,地址肯定大于0X20000004,比如cpu_endian的地址就是0X20000005.
这就是STM32内部堆的分配规则.
3,再说说,大小端的问题.
大端模式:低位字节存在高地址上,高位字节存在低地址上
小端模式:高位字节存在高地址上,低位字节存在低地址上
STM32属于小端模式,简单的说,比如u32 temp=0X12345678;
假设temp地址在0X2000 0010.
那么在内存里面,存放就变成了:
地址 | HEX |
0X2000 0010 | 78 56 43 12 |
CPU到底是大端还是小端,可以通过如下代码测试:
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;
//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1; //大端模式
}
以上测试,在STM32上,你会得到cpu_endian=0,也就是小端模式.
3,最后说说,STM32内存的问题.
还是以附件工程为例,在前面第一个图,程序总共占用内存:20+2348字节,这么多内存,到底是怎么得来的呢?
我们可以双击Project侧边栏的:Targt1,会弹出test.map,在这个里面,我们就可以清楚的知道这些内存到底是怎么来的了.在这个test.map最后,Image 部分有:
==============================================================================
Image component sizes
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
172 10 0 4 0 995 delay.o//delay.c里面,fac_us和fac_ms,共占用4字节
112 12 0 0 0 427 led.o
72 26 304 0 2048 828 startup_stm32f10x_hd.o //启动文件,里面定义了Stack_Size为0X800,所以这里是2048.
712 52 0 0 0 2715 sys.o
348 154 0 6 0 208720 test.o//test.c里面,stack_dir和cpu_endian 以及*addr ,占用6字节.
384 24 0 8 200 3050 usart.o//usart.c定义了一个串口接收数组buffer,占用200字节.
----------------------------------------------------------------------
1800 278 336 20 2248 216735 Object Totals //总共2248+20字节
0 0 32 0 0 0 (incl. Generated)
0 0 0 2 0 0 (incl. Padding)//2字节用于对其
----------------------------------------------------------------------
Code (inc. data) RO Data RW Data ZI Data Debug Library Member Name
8 0 0 0 0 68 __main.o
104 0 0 0 0 84 __printf.o
52 8 0 0 0 0 __scatter.o
26 0 0 0 0 0 __scatter_copy.o
28 0 0 0 0 0 __scatter_zi.o
48 6 0 0 0 96 _printf_char_common.o
36 4 0 0 0 80 _printf_char_file.o
92 4 40 0 0 88 _printf_hex_int.o
184 0 0 0 0 88 _printf_intcommon.o
0 0 0 0 0 0 _printf_percent.o
4 0 0 0 0 0 _printf_percent_end.o
6 0 0 0 0 0 _printf_x.o
12 0 0 0 0 72 exit.o
8 0 0 0 0 68 ferror.o
6 0 0 0 0 152 heapauxi.o
2 0 0 0 0 0 libinit.o
2 0 0 0 0 0 libinit2.o
2 0 0 0 0 0 libshutdown.o
2 0 0 0 0 0 libshutdown2.o
8 4 0 0 96 68 libspace.o //库文件(printf使用),占用了96字节
24 4 0 0 0 84 noretval__2printf.o
0 0 0 0 0 0 rtentry.o
12 0 0 0 0 0 rtentry2.o
6 0 0 0 0 0 rtentry4.o
2 0 0 0 0 0 rtexit.o
10 0 0 0 0 0 rtexit2.o
74 0 0 0 0 80 sys_stackheap_outer.o
2 0 0 0 0 68 use_no_semi.o
2 0 0 0 0 68 use_no_semi_2.o
450 8 0 0 0 236 faddsub_clz.o
388 76 0 0 0 96 fdiv.o
62 4 0 0 0 84 ffixu.o
38 0 0 0 0 68 fflt_clz.o
258 4 0 0 0 84 fmul.o
140 4 0 0 0 84 fnaninf.o
10 0 0 0 0 68 fretinf.o
0 0 0 0 0 0 usenofp.o
----------------------------------------------------------------------
2118 126 42 0 100 1884 Library Totals //调用的库用了100字节.
10 0 2 0 4 0 (incl. Padding) //用于对其多占用了4个字节
----------------------------------------------------------------------
Code (inc. data) RO Data RW Data ZI Data Debug Library Name
762 30 40 0 96 1164 c_w.l
1346 96 0 0 0 720 fz_ws.l
----------------------------------------------------------------------
2118 126 42 0 100 1884 Library Totals
----------------------------------------------------------------------
==============================================================================
Code (inc. data) RO Data RW Data ZI Data Debug
3918 404 378 20 2348 217111 Grand Totals
3918 404 378 20 2348 217111 ELF Image Totals
3918 404 378 20 0 0 ROM Totals
==============================================================================
Total RO Size (Code + RO Data) 4296 ( 4.20kB)
Total RW Size (RW Data + ZI Data) 2368 ( 2.31kB) //总共占用:2248+20+100=2368.
Total ROM Size (Code + RO Data + RW Data) 4316 ( 4.21kB)
==============================================================================
通过这个文件,我们就可以分析整个内存,是怎么被占用的,具体到每个文件,占用多少.一目了然了.
4,最后,看看整个测试代码:
main.c代码如下,工程见附件.
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
//ALIENTEK战舰STM32开发板堆栈增长方向以及CPU大小端测试
//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;
//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
static u8 *addr=NULL; //用于存放第一个dummy的地址。
u8 dummy; //用于获取栈地址
if(addr==NULL) //第一次进入
{
addr=&dummy; //保存dummy的地址
find_stack_direction (); //递归
}else //第二次进入
{
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
else stack_dir=0; //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.
}
}
//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1; //大端模式
}
int main(void)
{
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
printf("stack_dir:%x\r\n",&stack_dir);
printf("cpu_endian:%x\r\n",&cpu_endian);
find_stack_direction(); //获取栈增长方式
find_cpu_endian(); //获取CPU大小端模式
while(1)
{
if(stack_dir)printf("STACK DIRCTION:向上生长\r\n\r\n");
else printf("STACK DIRCTION:向下生长\r\n\r\n");
if(cpu_endian)printf("CPU ENDIAN:大端模式\r\n\r\n");
else printf("CPU ENDIAN:小端模式\r\n\r\n");
delay_ms(500);
LED0=!LED0;
}
}
测试结果如图:
42.52 KB
根据STM32的内存映射图,在代码区,0x00000000地址为启动区,上电以后,CPU从这个地址开始执行代码。0x08000000是用户FLASH的起始地址,0x20000000是SRAM的起始地址。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。