ARM裸机
笔记来源《朱老师物联网大讲堂》、《迅为电子提供的资料》、网上资料、基于itop4412 scp开发板
想搭建HomeAssistant环境,但迅为提供的uboot2020.10只有1G内存的版本,所以有了这篇笔记
需要代码资料可留言
1. ARM那些你得知道的事儿
1.1 ARM的成长史
ARM发展的里程碑
-
ARM的前身为艾康电脑(Acorn),于1978年,于英国剑桥创立
-
在1980年代晚期,苹果电脑开始与艾康电脑合作开发新版的ARM核心
-
1985年开发出全球第一款商用RISC处理器,即ARM1
-
1990年艾康电脑财务危机,受苹果和VLSI的投资,分割出独立子公司Advanced RISC Machines (ARM) ,ARM公司正式成立面世
-
1991年,ARM推出第一款嵌入式RISC处理器,即ARM6
-
1993年,发布ARM7
-
1997年,发布ARM9TDMI
-
1999年,发布ARM9E
-
2001年,发布ARMv6架构
-
2002年,发布ARM11微架构
-
2004年,发布ARMv7架构的Cortex系列处理器,同时推出Cortex-M3
-
2005年,发布Cortex-A8处理器
-
2007年,发布Cortex-M1和Cortex-A9
-
2009年,实现Cortex-A9、发布Cortex-M0
-
2010年,推出Cortex-M4、成立Linaro,推出Cortex-A15 MPcore高性能处理器
-
2011年,推出Cortex-A7,ARMv8发布
-
2012年,开始64位处理器进程
......
1.2 ARM的版本
内核(架构)版本 | 处理器版本 |
---|---|
ARMv1 | ARM1 |
ARMv2 | ARM2、ARM3 |
ARMv3 | ARM6、ARM7 |
ARMv4 | StrongARM、ARM7TDMI、ARM9TDMI |
ARMv5 | ARM7EJ、ARM9E、ARM10E、XScale |
ARMv6 | ARM11、ARM Cortex-M |
ARMv7 | ARM Cortex-A、ARM Cortex-M、ARM Cortex-R |
ARMv8 | ARM Cortex-A30、ARM Cortex-A50、ARM Cortex-A70 |
链接:汽车软件工程师基础知识 - ARM发展史及各时期内核(ARM1 ~ ARM11 / Cortex)介绍 - 知乎 (zhihu.com)
1.3 CPU、MPU、MCU、SOC
-
CPU:中央处理单元(Central Processing Unit),由运算器、控制器和寄存器及相应的总线构成。由CPU发展出来三个分支,一个是DSP(Digital Signal Processing/Processor,数字信号处理),另外两个是MCU(Micro Control Unit,微控制器单元)和MPU(Micro Processor Unit,微处理器单元)
-
MPU:微处理器(Micro Processor Unit),是由计算机中的CPU演变而来。 与CPU的区别在于,它只保留了与嵌入式应用紧密相关的功能硬件。目前主要的嵌入式处理器类型有ARM、MIPS、PowerPC、68000系列等。(摘自《ARM&Linux嵌入式系统教程》),我们经常说ARM,却忘了它是MPU。
-
MCU:微控制器(micro control unit),即平常所说的单片机(MCU一般以一种MPU为核心),是随着大规模集成电路的发展,将计算机CPU、RAM、ROM、定时器、计数器和多种I/O接口集成在一片芯片上,形成芯片级的芯片,比如51,AVR、STM这些芯片,相当于内部集成了整个计算机系统,可以加一些简单的外围器件(电阻、电容等)即可运行代码,其有处理器,有外围接口,基于已有的系统架构进行开发,应用者主要工作是添加外围设备和开发软件程序。我们说的ARM(Cortex-M、Cortex-A系列)直接放代码运行不了,因为本质上是cpu,必须添加响应RAM或ROM才能运行代码。
-
SOC:指的是片上系统(System on Chip)。可以这样对比来看:MCU只是芯片级的芯片,而SOC是系统级的芯片,它集成了MCU和MPU的优点,即拥有内置RAM和ROM的同时又像MPU那样强大,它可以存放并运行系统级别的代码,也就是说可以运行操作系统(以Linux OS为主)
2. ARM体系结构与汇编指令
2.1 指令集、编址方式、存储结构
2.1.1 RISC和CISC
-
CISC
-
complex instruction set computer复杂指令集CPU
-
CISC体系的设计理念是用最少的指令来完成任务(譬如计算乘法只需要一条MUL指令即可),因此CISC的CPU本身设计复杂、工艺复杂,但好处是编译器好设计。CISC出现较早,至今Intel还一直采用CISC设计
-
-
RISC
-
Reduced Instruction-Set Computer精简指令集CPU
-
RISC的设计理念是让软件来完成具体的任务,CPU本身仅提供基本功能指令集。因此RISC CPU的指令集中只有很少的指令,这种设计相对于CISC,CPU的设计和工艺简单了,但是编译器的设计变难了
-
2.1.2 统一编址&独立编址&哈佛结构&冯诺依曼结构
内存与IO
-
内存是程序的运行场所,内存和CPU之间通过总线连接,CPU通过一定的地址来访问具体内存单元。
-
IO(input and output)是输入输出接口,是CPU和其他外部设备(如串口、LCD、触摸屏、LED等)之间通信的道路。一般的,IO就是指CPU的各种内部或外部外设。
内存的访问方式
-
内存通过CPU的地址总线来寻址定位,然后通过CPU数据总线来读写。
-
CPU的地址总线的位数是CPU设计时确定的,因此一款CPU所能寻址的范围是一定的,而内存是需要占用CPU的寻址空间的。
-
内存与CPU的这种总线式连接方式是一种直接连接,优点是效率高访问快,缺点是资源有限,扩展性差。
IO的访问方式
-
IO指的是与CPU连接的各种外设
-
CPU访问各种外设有2种方式:一种是类似于访问内存的方式,即把外设的寄存器当作一个内存地址来读写,从而以访问内存相同的方式来操作外设,叫IO与内存统一编址方式;另一种是使用专用的CPU指令来访问某种特定外设,叫IO与内存独立编址。
冯诺依曼结构与哈佛结构
-
程序和数据都放在内存中,且不彼此分离的结构称为冯诺依曼结构。譬如Intel的CPU均采用冯诺依曼结构。
-
程序和数据分开独立放在不同的内存块中,彼此完全分离的结构称为哈佛结构。譬如大部分的单片机(MCS51、ARM9等)均采用哈佛结构。
-
冯诺依曼结构中程序和数据不区分的放在一起,因此安全和稳定性是个问题,好处是处理起来简单。
-
哈佛结构中程序(一般放在ROM、flash中)和数据(一般放在RAM中)独立分开存放,因此好处是安全和稳定性高,缺点是软件处理复杂一些(需要统一规划链接地址等)
2.2 寄存器
2.2.1 什么是寄存器
-
寄存器属于CPU外设的硬件组成部分
-
CPU可以像访问内存一样访问寄存器
-
寄存器是CPU的硬件设计者制定的,目的是留作外设被编程控制的“活动开关”
-
正如汇编指令集是CPU的编程接口API一样,寄存器是外设硬件的软件编程接口API。使用软件编程控制某一硬件,其实就是编程读写该硬件的寄存器。
2.2.2 两类寄存器
-
SoC中有2类寄存器:通用寄存器和SFR
-
通用寄存器(ARM中有37个)是CPU的组成部分,CPU的很多活动都需要通用寄存器的支持和参与。
-
SFR(special function register,特殊功能寄存器)不在CPU中,而存在于CPU的外设中,我们通过访问外设的SFR来编程操控这个外设,这就是硬件编程控制的方法。
2.3 ARM体系结构要点总结
2.3.1 ARM是RISC架构
-
常用ARM汇编指令只有二三十条
-
ARM是低功耗CPU
2.3.2 ARM是统一编址的
-
大部分ARM(M3 M4 M7 M0 ARM9 ARM11 A8 A9等)都是32位架构
-
32位ARM CPU支持的内存少于4G,通过CPU地址总线来访问
-
SoC中的各种内部外设通过各自的SFR编程访问,这些SFR的访问方式类似于访问普通内存,这叫IO与内存统一编址
2.3.3 ARM是哈佛结构的
-
常见ARM(除ARM7外)都是哈佛结构的
-
哈佛结构保证了ARM CPU运行的稳定性和安全性,因此ARM适用于嵌入式领域
-
哈佛结构也决定了ARM裸机程序(使用实地址即物理地址)的链接比较麻烦,必须使用复杂的链接脚本告知链接器如何组织程序;对于OS之上的应用(工作在虚拟地址之中)则不需考虑这么多
2.4 地址映射
2.4.1 什么是地址映射
-
Exynos4412属于ARM Cortex-A9架构,32位CPU,CPU设计时就有32根地址线&32根数据线。
-
32根地址线决定了CPU的地址空间为4G,那么这4G空间如何分配使用?
2.4.2 一些术语
-
ROM:read only memory 只读存储器
-
RAM:ramdom access memory 随机访问存储器
-
IROM:internal rom 内部ROM,指的是集成到SoC内部的ROM
-
IRAM:internal ram 内部RAM,指的是集成到SoC内部的RAM
-
DRAM:dynamic ram 动态RAM
-
SRAM:static ram 静态RAM
-
SROM:static rom
-
SFR:special function register
2.5 内存和外存
2.5.1 概念
-
内存:内部存储器,用来运行程序的,举例(DRAM SRAM DDR)
-
外存:外部存储器,用来存储东西的,例(硬盘 Flash(Nand iNand···· U盘、SSD) 光盘)
-
CPU连接内存和外存的连接方式不同。
-
内存需要直接地址访问,所以是通过地址总线&数据总线的总线式访问方式连接的(好处是直接访问,随机访问;坏处是占用CPU的地址空间,大小受限);
-
外存是通过CPU的外存接口来连接的(好处是不占用CPU的地址空间,坏处是访问速度没有总线式快,访问时序较复杂)
2.5.2 SoC常用外存
-
NorFlash:总线式访问,接到SROM bank,优点是可以直接总线访问,一般用来启动。
-
NandFlash:分为SLC和MLC
-
eMMC:embeded MMC
-
iNand:SanDisk公司出产的eMMC
-
moviNand:三星公司出产的eMMC
-
oneNand:三星公司出的一种Nand
-
SD卡/TF卡/MMC卡
-
eSSD
2.6 ARM的编程模式和内核工作模式
2.6.1 ARM的基本设定
-
ARM 采用的是32位架构.
-
ARM 约定:
-
Byte :8 bits
-
Halfword :16 bits (2 byte)
-
Word : 32 bits (4 byte)
-
-
大部分ARM core 提供:
-
ARM 指令集(32-bit)
-
Thumb 指令集(16-bit )
-
Thumb2指令集(16 & 32bit)
-
Jazelle cores 支持 Java bytecode
-
2.6.2 流水线
-
传统的单片机(如8051)中,处理器只有完成一条指令的读取和执行后,才会开始下一条指令的处理,所以PC(程序计数器)总是指向正在执行的指令。而ARM体系架构中则引入了流水线的概念。
-
到ARM7为止的ARM处理器使用了简单的三级流水线。三级流水线使用三个工位,将指令的处理分为三个阶段,分别为取指、译码和执行。取指:从存储器中装载;译码:识别将要被执行的指令;执行:处理指令并将结果写回寄存器。
-
流水线机制 (1)在第1个周期,PC指向指令1,此时指令1进入三级流水线的取指阶段。 (2)在第2个周期,PC指向指令2,此时指令1进入三级流水线的译码阶段,同时取出指令2。 (3)在第3个周期,PC指向指令3,此时指令1进入三级流水线的执行阶段,指令2进入译码阶段,取出指令3。 (4)在第4个周期,指令1执行完成,指令2和指令3流水线推进一级,同时开始指令4的取指处理
2.6.3 Processor modes
ARMv7内核共支持9种处理器模式。当前程序状态寄存器CPSR的控制位M[4:0]可指示处理器正在执行的模式
-
User : 非特权模式,大部分任务执行在这种模式
-
FIQ : 当一个高优先级(fast) 中断产生时将会进入这种模式
-
IRQ : 当一个低优先级(normal) 中断产生时将会进入这种模式
-
Supervisor(SVC) :当复位或软中断指令执行时将会进入这种模式
-
Monitor(Mon):进行操作系统的高级处理
-
Hypervisor(Hyp):虚拟内存和存储器保护
-
Abort : 当存取异常时将会进入这种模式
-
Undef : 当执行未定义指令时会进入这种模式
-
System (Sys): 使用和User模式相同寄存器集的特权模式
注意
-
除User(用户模式)是Normal(普通模式)外,其他8种都是Privilege(特权模式)。
-
Privilege中除Sys模式外,其余7种为异常模式。
-
各种模式的切换,可以是程序员通过代码主动切换(通过写CPSR寄存器);也可以是CPU在某些情况下自动切换。
-
各种模式下权限和可以访问的寄存器不同。
2.6.4 状态寄存器
在 armv7 中,状态寄存器为 CPSR,即 Current Program Status Register,该状态寄存器中保存了处理器运行时的状态信息:
CPSR 寄存器为 32 位,其中:
-
N:bit31,当运算结果为负且运算指令要求更新寄存器时,该位会被置位。
-
Z:bit30,当运算结果为0且运算指令要求更新寄存器时,该位会被置位。
-
C:bit29,当运算结果产生进位且指令要求更新寄存器时,该位会被置位,具体进位规则见下文。
-
V:bit28,当运算结果产生符号位溢出且指令要求更新寄存器时,该位会被置位,具体溢出规则见下文。
-
Q:bit27,累积饱和位,置为1表示某些指令中发生溢出或饱和,通常与数字信号处理(DSP)有关。
-
IT[1:0],bit[26:25],IT 位,同后续 IT[7:2] 相关。
-
J:bit24,指示处理器当前是否处于 Jazelle 状态。
-
bit[23:20],保留位。
-
GE[3:0]:bit[19:16],大于或者等于标志位,主要被 SIMD 指令使用,SIMD 全称为 single instruction multiple data,armv7 提供一条指令同时处理多个寄存器数据,属于扩展指令。
-
IT:bit[15:10],Thumb IT 指令集的 if-then 执行状态位。
-
E:bit9,指示当前处理器是运行于大端模式还是小端模式,同时,也可以通过设置该位来切换大小端模式。
-
mask bits,bit[8:6],屏蔽位 A、I、F,分别对应异步终止、快中断和中断,当对应的位为1时,相应的功能被屏蔽,当处理器需要屏蔽中断时,通常就是设置该屏蔽位。
-
T,bit5,指示处理器当前使用 thumb 指令集还是 arm 指令集,当前位和 J bit决定当前处理器的指令集,是 arm、Thumb、Jazelle 还是 ThumbEE 指令集。J、T的值对应指令集关系为:00-arm指令集,01-Thumb指令集,10-Jazelle指令集,11-ThumbEE指令集。
-
M,bit[4:0],模式位,指示处理器当前位于哪种运行模式下
条件位:
处理器状态:
模式位:
链接:armv7-A系列2-arm状态寄存器 - 知乎 (zhihu.com)
2.7 汇编指令集
2.7.1 关于汇编指令
-
(汇编)指令是CPU机器指令的助记符,经过编译后会得到一串10组成的机器码,可以由CPU读取执行。
-
(汇编)伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,经过编译后伪指令最终不会生成机器码。
-
ARM官方的ARM汇编风格:指令一般用大写、Windows中IDE开发环境(如ADS、MDK等)常用。如: LDR R0, [R1]
-
GNU风格的ARM汇编:指令一般用小写字母、linux中常用。如:ldr r0, [r1]
2.7.2 汇编特点
(1)LDR/STR架构
-
ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理。
-
ldr(load register)指令将内存内容加载入通用寄存器。
-
str(store register)指令将寄存器内容存入内存空间中。
-
ldr/str组合用来实现 ARM CPU和内存数据交换。
(2)8种寻址方式
-
寄存器寻址
@操作数在寄存器中,此句代码的功能就是将r2的数据加载到r1中。相当于C语言:r1 = r2
move r1, r2
-
立即数寻址
@直接给出操作数,但是要受到合法立即数的限制。相当于c语言:r0=0xFF00
mov r0, #0xFF00
-
寄存器移位寻址
@寄存器中的数进行移位后得到操作数。相当于C语言:r0=r1<<3
mov r0, r1, lsl #3
-
寄存器间接寻址
@操作数不是在寄存器中,而是操作数的所在内存地址在寄存器中。在C语言中,就是r2中存的是一个指针,指针指向的内存地址存的操作数:r1=*r2
ldr r1,[r2]
-
基址变址寻址
@操作数的地址等于寄存器中的值加上偏移量,访问该地址可以得到操作数。相当于C语言:r1=*(r2+4).
ldr r1, [r2, #4]
-
多寄存器寻址
/*
多寄存器寻址就是一次往多个寄存器里写数据,比如下面那条语句就是以r1里的数据为地址,读取地址里的数据依次写到后面的寄存器,每次操作完r1里的地址都+4,再去新的地址读取数据写到寄存器中。这对应于C语言里的数组,知道数组的首地址就可以每次数组首地址+4的方式来读取数组里的元素。常见用法:弹栈操作
*/
ldmia sp!, {r2-r7, r12}
-
堆栈寻址
@堆栈寻址其实和多寄存寻址很像,几乎就是特殊的多寄存寻址。sp是栈指针,上面的语句就是把栈里的内容依次写到r2-r7还有lr寄存器中。常见用法:压栈操作
stmfd sp!, {r2-r7, lr}
-
相对寻址
@pc当前值为基址,指令中值为偏移量,相加作为操作数的地址。地址分为相对地址和绝对地址,这里就是相对地址,跳转地址=当前地址+偏移量。此句是一个跳转指令,flag是汇编里的标号,相当于C语言中的函数名,eq是指令后缀,当上一句的执行结果为相等时才会执行本句,eq的作用相当于C语言的if条件语句。整句相当于c语言中的函数跳转,调用某个函数时,就会跳转到函数代码去执行,执行完毕后会返回。
beq flag
(3)指令后缀
-
同一指令经常附带不同后缀,变成不同的指令。经常使用的后缀有:
-
B(byte)功能不变,操作长度变为8位,如
ldrb
-
H(half word)功能不变,长度变为16位,如
ldrh
-
S(signed)功能不变,操作数变为有符号,如
ldrsb ldrsh
-
S(S标志)功能不变,影响CPSR标志位,如 mov和movs,
movs r0, #0
-
(4)条件执行后缀
(5)多级指令流水线
-
为增加处理器指令流的速度,ARM使用多级流水线.,下图为3级流水线工作原理示意图。(Exynos4412为先进的推测型八级流水线)
–允许多个操作同时处理,而非顺序执行。
-
PC指向正被取指的指令,而非正在执行的指令
2.7.3 常用ARM指令
2.7.3.1 数据处理指令
-
数据传输指令 mov mvn
-
算术指令 add sub rsb adc sbc rsc
-
逻辑指令 and orr eor bic
-
比较指令 cmp cmn tst teq
-
乘法指令 mvl mla umull umlal smull smlal
-
前导零计数 clz
/*
注:mov的两个操作数中不能有内存地址
mvn和mov用法一样,区别是mov是原封不动的传递,而mvn是按位取反后传递
按位取反的含义:譬如r1 = 0x000000ff,然后mov r0, r1 后,r0 = 0xff;但是我mvn r0, r1后,r0=0xffffff00。
*/
mov r1, r0 @ 两个寄存器之间数据传递
mov r1, #0xff @ 将立即数赋值给寄存器
/*
and逻辑与
orr逻辑或
eor逻辑异或
bic位清除指令
*/
bic r0,r1,#0x1f @ 将r1中的数的bit0到bit4清零后赋值给r0
/*
比较指令用来比较2个寄存器中的数
注意:比较指令不用后加s后缀就可以影响cpsr中的标志位。
*/
cmp r0, r1 @等价于 sub r2, r0, r1 (r2 = r0 - r1)
cmn r0, r1 @等价于 add r0, r1
tst r0, #0xf @测试r0的bit0~bit3是否全为0
teq x0, x1 @用于比较操作数1和操作数2是否相等
2.7.3.2 cpsr访问指令
-
mrs用来读psr,msr用来写psr(指cpsr/spsr)
-
CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr
-
cpsr和spsr的区别和联系:cpsr是程序状态寄存器,整个SoC中只有1个;而spsr有7个,分别在5种异常模式下,作用是当从普通模式进入异常模式时,用来保存之前普通模式下的cpsr的,以在返回普通模式时恢复原来的cpsr。
2.7.3.3 跳转(分支)指令
-
b & bl & bx
-
b: 直接跳转(就没打开算返回)
-
bl :branch and link,跳转前把返回地址放入lr中,以便返回,以便用于函数调用
-
bx :跳转同时切换到ARM模式,一般用于异常处理的跳转。
2.7.3.4 访存指令
-
单个字/半字/字节访问ldr/str
ldr,str 的第一操作数是目标寄存器,第二操作数是内存地址 ldrd 寄存器 <-- 内存 str 寄存器 --> 内存
-
多字批量访问 ldm/stm
ldm,stm 的第一操作数是内存,第二操作数是寄存器列表,
注意第一操作数是寄存器里地址指向的内存,如ldmia r1, {r2-r7, r12}
可以理解为ldmia [r1], {r2-r7, r12}
ldm 内存 --> 寄存器
stm 内存 <-- 寄存器
-
swp
@ 将[r0]内存地址的内容放入r1寄存器中,将r2寄存器的值放入[r0]内存地址中,这两步同时进行。
swp r1, r2, [r0]
@ 实现r1和[r0]内存地址中内容的互换
swp r1, r1, [r0]
-
A合法立即数与非法立即数
-
ARM指令都是32位,除了指令标记和操作标记外,本身只能附带很少位数的立即数。因此立即数有合法和非法之分。
-
合法立即数:经过任意位数的移位后非零部分可以用8位表示的即为合法立即数
-
合法立即数: 0x000000ff0x00ff0000 0xf000000f
-
非法立即数: 0x000001ff
-
2.7.3.5 软中断指令
-
swi(software interrupt)
-
软中断指令用来实现操作系统中系统调用
2.7.3.6 协处理器cp15操作指令
-
mcr & mrc
-
mrc用于读取CP15中的寄存器
-
mcr用于写入CP15中的寄存器
-
使用方法:
opcode_1:对于cp15永远为0 Rd:ARM的普通寄存器 Crn:cp15的寄存器,合法值是c0~c15 Crm:cp15的寄存器,一般均设为c0 opcode_2:一般省略或为0 mcr{<cond>} p15, <opcode_1>, <Rd>,<Crn>, <Crm>, {<opcode_2>} mrc{<cond>} p15, <Opcode_1>, <Rd>,<Crn>, <Crm>, {<Opcode_2>}
-
什么是协处理器?
-
SoC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。
-
ARM设计上支持多达16个协处理器,但是一般SoC只实现其中的CP15.(cp:coprocessor)
-
协处理器和MMU、cache、TLB等处理有关,功能上和操作系统的虚拟地址映射、cache管理等有关。
-
链接:Cortex-A 系列CP15协处理器简单解析 (ngui.cc)
2.7.3.7 ldm/stm与栈的处理
ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ldm
/*
ldm(load register mutiple)
stm(store register mutiple)
将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),将r2存入该地址······直到r12内容放入(0x3001030),指令完成。
一个访存周期同时完成13个寄存器的读写
*/
stmia sp,{r0 - r12}
8种后缀
ia(increaseafter)先传输,再地址+4 ib(increasebefore)先地址+4,再传输 da(decreaseafter)先传输,再地址-4 db(decreasebefore)先地址-4,再传输 fd(full decrease)满递减堆栈 ed(empty decrease)空递减堆栈 fa(·······) 满递增堆栈 ea(·······)空递增堆栈
四种栈
在ARM中,ATPCS(ARM-Thumb过程调用标准)要求使用满减栈
空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出 满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针 增栈:栈指针移动时向地址增加的方向移动的栈 减栈:栈指针移动时向地址减小的方向移动的栈
!的作用:就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说ldm时会改变r0的值。
^的作用:
ldmfd sp!,{r0 - r6, pc} ldmfd sp!,{r0 - r6, pc}^ ^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。
2.7.3.8 ARM汇编伪指令
-
伪指令的意义:
-
伪指令不是指令,伪指令和指令的根本区别是经过编译后会不会生成机器码。
-
伪指令的意义在于指导编译过程。
-
伪指令是和具体的编译器相关的,我们使用gnu工具链,因此学习gnu环境下的汇编伪指令。
-
-
gnu汇编中的一些符号:
-
@ 用来做注释。可以在行首也可以在代码后面同一行直接跟,和C语言中//类似
-
:以冒号结尾的是标号
-
. 点号在gnu汇编中表示当前指令的地址
-
立即数前面要加#或$,表示这是个立即数
-
-
常用gnu伪指令:
@ 指定当前段为代码段
.section .text
@ .ascii用于定义字符,.byte用于定义字节,.short用于定义半字,.word和.long是字
.ascii .byte .short .long .word
@ 定义数据 .quad用于定义双字,.float定义浮点型,.string用于定义字符串
.quad .float .string
@以16字节对齐
.align 4
@ 16字节对齐填充
.balignl 16 0xabcdefgh
@ 类似于C中宏定义
.equ
-
最重要的几个伪指令
-
ldr 大范围的地址加载指令
-
adr 小范围的地址加载指令
-
adrl 中等范围的地址加载指令
-
nop 空操作
-
ARM中有一个ldr指令,还有一个ldr伪指令
-
一般都使用ldr伪指令而不用ldr指令,因为ldr伪指令可以不用考虑立即数是否合法,编译器会帮你转换为合法立即数
-
-
adr与ldr:
-
adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理
-
adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址在哪里。
-
ldr加载的地址和链接时给定的地址有关,由链接脚本决定。
-
3.裸机实验说明
3.1 启动方式
(1)从哪里启动是由SoC的OM5:OM1这5个引脚的高低电平决定的。
(2)实际上在4412内部有一个寄存器(地址是0x10020000),这个寄存器中的值是硬件根据OM引脚的设置而自动设置值的。这个值反映的就是OM引脚的接法(电平高低),也就是真正的启动介质是谁。
(3)用户可以通过拨码开关来选择启动方式,itop4412支持上图红框两种启动方式,具体参考迅为的文档说明,也可以看iTop-4412 裸机教程(一)- 从启动方式开始4412教学Kilento的博客-CSDN博客
3.2 DNW方式进行裸机程序实验
(1)调整拨码开关,选择TF 卡启动模式,不插TF卡,让soc选择从USB启动,发现设备管理器中的连接总是在开机几秒后就丢失,原因应该是供电没锁存导致。
(2)没办法,只能用SD卡方式调试
3.3 SD卡方式进行裸机程序实验
(1)调整拨码开关,选择TF 卡启动模式,插入已烧录程序TF卡
4. GPIO和LED
源代码在代码目录下
(1)点亮led:1.leds_s
(2)流水灯:2.leds_s
注意
5.时钟
5.1 Exynos4412的时钟系统简介
5.1.1 时钟模块
(1)内部的时钟划分为5大模块 (2)CMU_CPU :CPU(Cortex-A9内核)、L2 cache controller、CoreSight (3)CMU_DMC:DMC、SSS、GIC (4)CMU_LEFTBUS 和CMU_RIGHTBUS: 全局数据总线、全局外设总线 (5)CMU_TOP :G3D, MFC, LCD0, ISP, CAM,TV, FSYS, MFC, GPS, MAUDIO ......
5.1.2 时钟来源
5.1.2.1 外部晶振
(1)Exynos4412外部有3个晶振接口:XRTCXTI、 XXTI、 XUSBXTI (2)itop4412使用24M外部晶振,接在XUSBXTI,不使用XXTI(被下拉接地),XRTCXTI由S5M8767A电源管理芯片提供
5.1.2.1 内部锁相环
内部四个PLL:APLL、MPLL、EPLL、VPLL
5.2 Exynos4412时钟详解
5.2.1 CMU_CPU
-
ARMCLK:给cpu内核工作的时钟,也就是所谓的主频
-
ACLK_COREM0:
-
ACLK_COREM1:
-
PERIPHCLK:
-
ATCLK:
-
PCLK_DBG:
5.2.2 CMU_DMC
-
SCLK_DMC:DMC时钟
-
ACLK_DMCD
-
ACLK_DMCP
-
ACLK_ACP
-
PCLK_ACP
-
SCLK_C2C
-
ACLK_C2C
5.2.3 CMU_LEFTBUS
-
ACLK_GDL
-
ACLK_GPL
5.2.4 CMU_RIGHTBUS
-
ACLK_GDR
-
ACLK_GPR
5.2.5 CMU_TOP
-
ACLK_400_MCUISP
-
ACLK_200
-
ACLK_100
-
ACLK_160
-
ACLK_133
-
SCLK_ONENAND
5.2.6 各时钟典型值(默认值,iROM中设置的值)
(1)刚上电时,默认是外部晶振+内部时钟发生器产生的24MHz频率的时钟直接给ARMCLK的,这时系统的主频就是24MHz,运行非常慢。 (2)iROM代码执行时初始化了时钟系统,这时给了系统一个默认推荐运行频率。这个时钟频率是三星推荐的4412工作性能和稳定性最佳的频率。 (3)高性能时钟典型值:
5.2.7 时钟体系框图
-
MUX:多路复用,即从多个输入源中选择一个
-
PLL:把低频率的输入时钟提高后输出
-
DIV:分频器,把高频率的输入时钟降频后输出
.global clock_init
clock_init:
push {lr} //保存返回地址,因为下面的代码还有函数调用
ldr r0, =ELFIN_CLOCK_BASE // 0x1003_0000
// 1 设置CMU_CPU,暂时不使用PLL
ldr r1, =0x0
// APLL、MPLL
ldr r2, =CLK_SRC_CPU_OFFSET
str r1, [r0, r2]
// EPLL、VPLL
ldr r2, =CLK_SRC_TOP0_OFFSET
str r1, [r0, r2]
// 2 设置锁定时间,使用默认值即可
// 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间
ldr r1, =PLL_LOCK_VAL // 默认值0xffff
// SET APLL LOCK TIME
ldr r2, =APLL_LOCK_OFFSET
str r1, [r0, r2]
// SET MPLL LOCK TIME
ldr r2, =MPLL_LOCK_OFFSET
str r1, [r0, r2]
// SET EPLL LOCK TIME
ldr r2, =EPLL_LOCK_OFFSET
str r1, [r0, r2]
// SET VPLL LOCK TIME
ldr r2, =VPLL_LOCK_OFFSET
str r1, [r0, r2]
// 3 设置PLL
// PDIV:VCO分频因子,PDIV,MDIV:VCO倍频因子,SDIV:系统时钟分频因子
// PDIV = 3, MDIV = 175, SDIV = 0, Fvco_out = Fref
// Fref = FIN / PDIV = 24MHz / 3 = 8MHz
// FAPLLOUT = MDIV * FIN / (PDIV + 2^SDIV) = 125 * 24Mhz / (3 * 2 ^ 0) = 1000MHz
ldr r1, =APLL_CON0_VAL
ldr r2, =APLL_CON0_OFFSET
str r1, [r0, r2]
// FMPLLOUT = 800MHz
ldr r1, =MPLL_CON0_VAL
ldr r2, =MPLL_CON0_OFFSET
str r1, [r0, r2]
// FEPLLOUT = 400MHz
ldr r1, =EPLL_CON0_VAL
ldr r2, =EPLL_CON0_OFFSET
str r1, [r0, r2]
// FVPLLOUT = 100MHz
ldr r1, =VPLL_CON0_VAL
ldr r2, =VPLL_CON0_OFFSET
str r1, [r0, r2]
// 4 设置时钟源和分频
// MUX_MPLL_USER_SEL_C = FOUTMPLL,MUX_HPM_SEL = MOUTAPLL,MUX_CORE_SEL = MOUTAPLL,MUX_APLL_SEL = MOUTAPLLFOUT
ldr r1, =0x01000001
ldr r2, =CLK_SRC_CPU_OFFSET
str r1, [r0, r2]
ldr r2, =CLK_MUX_STAT_CPU_OFFSET
ldr r3, =0x02110002
bl wait_mux_state
// MUX_PWI_SEL = XusbXTI,MUX_MPLL_SEL = MOUTMPLLFOUT
ldr r1, =0x00011000
ldr r2, =CLK_SRC_DMC_OFFSET
str r1, [r0, r2]
ldr r2, =CLK_MUX_STAT_DMC_OFFSET
ldr r3, =0x11102111
bl wait_mux_state
ldr r1, =CLK_DIV_DMC0_VAL
ldr r2, =CLK_DIV_DMC0_OFFSET
str r1, [r0, r2]
ldr r1, =CLK_DIV_DMC1_VAL
ldr r2, =CLK_DIV_DMC1_OFFSET
str r1, [r0, r2]
// MUX_ONENAND_SEL = ACLK_133, MUX_ACLK_133_SEL = SCLKMPLL, MUX_ACLK_160_SEL = SCLKMPLL, MUX_ACLK_100_SEL = SCLKMPLL
// MUX_ACLK_200_SEL = SCLKMPLL, MUX_EPLL_SEL = FOUTEPLL, MUX_EPLL_SEL = FOUTEPLL, MUX_ONENAND_1_SEL = MOUTONENAND
ldr r1, =0x00000110
ldr r2, =CLK_SRC_TOP0_OFFSET
str r1, [r0, r2]
ldr r2, =CLK_MUX_STAT_TOP_OFFSET
ldr r3, =0x11111221
bl wait_mux_state
// MUX_MPLL_USER_SEL_T = SCLKMPLLL, MUX_ACLK_266_GPS_SUB_SEL = DIVOUT_ACLK_266_GPS
ldr r1, =0x00011000
ldr r2, =CLK_SRC_TOP1_OFFSET
str r1, [r0, r2]
ldr r2, =CLK_MUX_STAT_TOP1_OFFSET
ldr r3, =0x01122110
bl wait_mux_state
ldr r1, =CLK_DIV_TOP_VAL
ldr r2, =CLK_DIV_TOP_OFFSET
str r1, [r0, r2]
// MUX_MPLL_USER_SEL_L = FOUTMPLL, MUX_GDL_SEL = SCLKMPLL
ldr r1, =0x00000010
ldr r2, =CLK_SRC_LEFTBUS_OFFSET
str r1, [r0, r2]
ldr r2, =CLK_MUX_STAT_LEFTBUS_OFFSET
ldr r3, =0x00000021
bl wait_mux_state
ldr r1, =CLK_DIV_LEFRBUS_VAL
ldr r2, =CLK_DIV_LEFTBUS_OFFSET
str r1, [r0, r2]
// MUX_MPLL_USER_SEL_R = FOUTMPLL, MUX_GDR_SEL = SCLKMPLL
ldr r1, =0x00000010
ldr r2, =CLK_SRC_RIGHTBUS_OFFSET
str r1, [r0, r2]
ldr r2, =CLK_MUX_STAT_RIGHTBUS_OFFSET
ldr r3, =0x00000021
bl wait_mux_state
ldr r1, =CLK_DIV_RIGHTBUS_VAL
ldr r2, =CLK_DIV_RIGHTBUS_OFFSET
str r1, [r0, r2]
pop {pc} // 返回
wait_mux_state:
ldr r1, [r0, r2]
cmp r1, r3
bne wait_mux_state
mov pc, lr
6. 重定位与SDRAM
6.1 Exynos4412的DMC
Exynos4412共有2个内存端口(就好像有2个内存插槽)。再结合查阅数据手册中内存映射部分,可知:两个内存端口分别叫DRAM0和DRAM1: DRAM0:内存地址范围:0x40000000~0x9FFFFFFF(1.5G),对应引脚是Xm1xxxx DRAM1: 内存地址范围:0xA0000000~0xFFFFFFFF(1.5G),对应引脚是Xm2xxxx
结论:
(1)Exynos4412最多支持内存为3GB,如果给)Exynos4412更多的内存CPU就无法识别。
(2))Exynos4412最多支持3GB内存,但是实际开发板不一定要这么多,itop4412_scp_2G开发板就只有2G内存,连接方法是在DRAM0端口分布1GB,在DRAM1端口分布了1GB。
(3)开发板上理论上内存合法地址是:0x40000000~0x7FFFFFFF(1GB) + 0xA0000000~0xDFFFFFFF(1GB),但实际的内存分布是0x40000000~0xBFFFFFFF(2GB),这里没搞明白。
6.2 itop4412_scp_2G的内存
-
使用的是K4B4G164EBYK0,规格是32Mbit x 16I/Os x 8banks ,也就是512M,共4片
-
DDR3-1600 (800MHz@CL=11, tRCD=11, tRP=11)
-
(2^15(Row Addr) * 2^11(Column Addr) * 2^3 (Bank) * 16(bits)) / 8 = 512MB
-
在逻辑上可以把这4颗内存芯片看成是2颗芯片,所以初始化的时候Number of Memory Chips选 1 chip
-
可以从片选引脚可以确定1 chip 和 2 chips,CSn0和CSn1都用的是2 chips
6.3 初始化代码
-
重点在DMC0_MEMCONTROL、DMC0_MEMCONFIG_0、DMC0_MEMCONFIG_1
-
其他时序相关可以查看对应的手册
#include "s5pc210.h"
#define MCLK_400
#define ENABLE_DRAM0 (1)
#define MDC0_CHIP_CNT (1)
#define ENABLE_DRAM1 (1)
#define MDC1_CHIP_CNT (1)
// DMC0
#define DMC0_MEMCONTROL 0x00302600 // MemControl BL=8, 1Chip, DDR3 Type, dynamic self refresh, force precharge, dynamic power down off
#define DMC0_MEMCONFIG_0 0x40801333 // MemConfig0 1GB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed
#define DMC0_MEMCONFIG_1 0x28F81312 // MemConfig1 默认值
#define DMC0_TIMINGA_REF 0x00000618 // TimingAref 7.8us*400MHz=3120(0xC30),小于3120就可以
//#define DMC0_TIMINGA_REF 0x000000BB // TimingAref
// DMC1
#define DMC1_MEMCONTROL 0x00302600 // MemControl BL=8, 1Chip, DDR3 Type, dynamic self refresh, force precharge, dynamic power down off
#define DMC1_MEMCONFIG_0 0x40801333 // MemConfig0 1GB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed
#define DMC1_MEMCONFIG_1 0x28F81312 // MemConfig1
#define DMC1_TIMINGA_REF 0x00000618
//#define DMC1_TIMINGA_REF 0x000000BB // TimingAref
#define CONFIG_IV_SIZE 0x1F
wait_phy_state:
ldr r1, [r0, #DMC_PHYSTATUS]
tst r1, #(1<<2)
beq wait_phy_state
mov pc, lr
dmc_delay:
push {lr}
1: subs r2, r2, #1
bne 1b
pop {pc}
.global sdram_asm_init
sdram_asm_init:
push {lr}
/*****************************************************************/
/*DREX0***********************************************************/
/*****************************************************************/
#if(ENABLE_DRAM0)
ldr r0, =APB_DMC_0_BASE
ldr r1, =0xe0000086
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0xE3854C03
str r1, [r0, #DMC_PHYZQCONTROL]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0xe000008e
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0xe0000086
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0x71101008
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0x7110100A
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0xe0000086
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0x7110100B
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0x00000000
str r1, [r0, #DMC_PHYCONTROL2]
ldr r1, =0x0FFF301A
str r1, [r0, #DMC_CONCONTROL]
ldr r1, =DMC0_MEMCONTROL
str r1, [r0, #DMC_MEMCONTROL]
ldr r1, =DMC0_MEMCONFIG_0
str r1, [r0, #DMC_MEMCONFIG0]
#if(MDC0_CHIP_CNT>1)
ldr r1, =DMC0_MEMCONFIG_1
str r1, [r0, #DMC_MEMCONFIG1]
#endif
#ifdef CONFIG_IV_SIZE
ldr r1, =(0x80000000 | CONFIG_IV_SIZE)
#else
ldr r1, =0x08
#endif
str r1, [r0, #DMC_IVCONTROL]
ldr r1, =0xff000000
str r1, [r0, #DMC_PRECHCONFIG]
ldr r1, =DMC0_TIMINGA_REF
str r1, [r0, #DMC_TIMINGAREF] @TimingAref
#ifdef MCLK_330
ldr r1, =0x3545548d
str r1, [r0, #DMC_TIMINGROW]
ldr r1, =0x45430506
str r1, [r0, #DMC_TIMINGDATA]
ldr r1, =0x46000A3c
str r1, [r0, #DMC_TIMINGPOWER]
#endif
#ifdef MCLK_400
ldr r1, =0x7846654F/*0x4046654F*/
str r1, [r0, #DMC_TIMINGROW] @TimingRow
ldr r1, =0x46400506
str r1, [r0, #DMC_TIMINGDATA] @TimingData
ldr r1, =0x52000a3c
str r1, [r0, #DMC_TIMINGPOWER] @TimingPower
#endif
/* chip 0 */
ldr r1, =0x07000000
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0x00020000
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00030000
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00010002
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00000328
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0x0a000000
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
#if(MDC0_CHIP_CNT>1)
/* chip 1 */
ldr r1, =0x07100000
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0x00120000
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00130000
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00110002
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00100328
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0x0a100000
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
#endif
ldr r1, =0xe000008e
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0xe0000086
str r1, [r0, #DMC_PHYCONTROL1]
mov r2, #0x100000
bl dmc_delay
#endif
/*****************************************************************/
/*DREX1***********************************************************/
/*****************************************************************/
#if(ENABLE_DRAM1)
ldr r0, =APB_DMC_1_BASE
ldr r1, =0xe0000086
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0xE3854C03
str r1, [r0, #DMC_PHYZQCONTROL]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0xe000008e
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0xe0000086
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0x71101008
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0x7110100A
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0xe0000086
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0x7110100B
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0x00000000
str r1, [r0, #DMC_PHYCONTROL2]
ldr r1, =0x0FFF301A
str r1, [r0, #DMC_CONCONTROL]
ldr r1, =DMC0_MEMCONTROL
str r1, [r0, #DMC_MEMCONTROL]
ldr r1, =DMC1_MEMCONFIG_0 @Interleaved?
str r1, [r0, #DMC_MEMCONFIG0]
#if(MDC1_CHIP_CNT>1)
ldr r1, =DMC1_MEMCONFIG_1
str r1, [r0, #DMC_MEMCONFIG1]
#endif
#ifdef CONFIG_IV_SIZE
ldr r1, =(0x80000000 | CONFIG_IV_SIZE)
#else
ldr r1, =0x08
#endif
str r1, [r0, #DMC_IVCONTROL]
ldr r1, =0xff000000
str r1, [r0, #DMC_PRECHCONFIG]
ldr r1, =DMC1_TIMINGA_REF
str r1, [r0, #DMC_TIMINGAREF] @TimingAref
#ifdef MCLK_330
ldr r1, =0x3545548d
str r1, [r0, #DMC_TIMINGROW]
ldr r1, =0x45430506
str r1, [r0, #DMC_TIMINGDATA]
ldr r1, =0x46000A3c
str r1, [r0, #DMC_TIMINGPOWER]
#endif
#ifdef MCLK_400
ldr r1, =0x7846654F/*0x4046654F*/
str r1, [r0, #DMC_TIMINGROW] @TimingRow
ldr r1, =0x46400506
str r1, [r0, #DMC_TIMINGDATA] @TimingData
ldr r1, =0x52000a3c
str r1, [r0, #DMC_TIMINGPOWER] @TimingPower
#endif
/* chip 0 */
ldr r1, =0x07000000
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0x00020000
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00030000
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00010002
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00000328
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0x0a000000
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
#if(MDC0_CHIP_CNT>1)
/* chip 1 */
ldr r1, =0x07100000
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0x00120000
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00130000
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00110002
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00100328
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
ldr r1, =0x0a100000
str r1, [r0, #DMC_DIRECTCMD]
mov r2, #0x100000
bl dmc_delay
#endif
ldr r1, =0xe000008e
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0xe0000086
str r1, [r0, #DMC_PHYCONTROL1]
mov r2, #0x100000
bl dmc_delay
#endif
/*****************************************************************/
/*Finalize********************************************************/
/*****************************************************************/
ldr r0, =APB_DMC_0_BASE
ldr r1, =0x0FFF303A
str r1, [r0, #DMC_CONCONTROL]
ldr r0, =APB_DMC_1_BASE
ldr r1, =0x0FFF303A
str r1, [r0, #DMC_CONCONTROL]
pop {pc}
6.4 关于DRAM初始化的疑问
测试条件:
DMC0_MEMCONTROL 0x00312600
DMC1_MEMCONTROL 0x00312600
启用内存交错
结论:好像只设置DMC1_MEMCONFIG_0就可以,设置DMC0_MEMCONFIG_0无意义?
#define rTest1 *((volatile unsigned int *)(0x78000000))
#define rTest2 *((volatile unsigned int *)(0x88000000))
#define rTest3 *((volatile unsigned int *)(0xA0000000))
#define rTest4 *((volatile unsigned int *)(0xB8000000))
int uart_main(void)
{
uart_init();
rTest1 = 145;
printf("rTest1= %ld\n",rTest1);
rTest2 = 11;
printf("rTest2= %ld\n",rTest2);
rTest3 = 45;
printf("rTest3= %ld\n",rTest3);
rTest4 = 35;
printf("rTest4= %ld\n",rTest4);
printf("hello world +++++\n");
while(1);
}