1.理解ARM启动原理
除了通用原理还有各个厂商的独特的启动方式
ARM里边的程序永远冲0地址开始运行,这个内存0地址是ARM的0~4G的内存地址空间的0地址
ARM是32位最大寻址能力就是4个G,就决定了内存地址空间是4个G,这个空间是CPU的访问能力,CPU从0地址取指译码执行,储存器(内存DRAM)是实际上地址范围是0x20008000~40000000是512M的大小,指令的执行从寻址空间的0地址开始执行与实际内存没有关系,nandflash没有地址线,只能通过8个IO管脚来读取,但是一开机不能直接运行nandflash中的程序,一开机只能从内存中开始运行,然后再读出nandflash中的东西到内存,CPU再运行。0地址处存储着内置的ROM(几十K),IROM中固化好了一部分代码,是只读的,所以起始代码是在IROM中开始执行;不是210的处理器,起始代码不一定是在0地址中开始执行
其他ARM可以在0地址就可以访问norflash或者直接访问DRAM中的代码
IROM运行后怎么跑到Nandflash/USB/SD卡启动的?
在datasheet中有描述启动的过程
a.关看门狗(超时重启,在超时前重置看门狗(喂狗),这个的作用就是防止死锁或者跑飞了,因为如果出现死锁或者跑飞了,没办法喂狗,就会重启,这样就避免了程序跑飞或者死锁)
b.I cache 初始化,是由协处理器来初始化指令cache,cache可以提高效率
c.判断唤醒的原因,是休眠状态下唤醒(回复休眠前的状态)还是关机状态下唤醒
d.栈以及变量的初始化
e.设置时钟初始化相关的参数,设置完之后,各个控制器的主频就可以动了
f.OM Pin,判断一组管脚的高低电平,这组管脚的值不同就意味着从不同的存储器取程序,修改OM Pin就可以决定从哪个存储器来取程序
g.是否支持校验,不支持校验,就从一种存储设备开始执行第二段代码
第二段程序运行:
根据OM Pin决定的启动的存储器,来执行其中的代码,我选的OM Pin是nandflash上启动第二段代码
OM pin是根据拨码开关来决定的
2.启动代码基本步骤
a.硬件满足条件已经哪些硬件必须初始化
①时钟的初始化,处理器内部,所有的控制器都需要时钟
时钟如果不初始化,处理器有默认的时钟频率,这时候时钟频率特别低,告诉器件无法使用,所以这里要做的就是提高频率
传递时每秒跳变的速率,输入时外部晶振,经过时钟控制器,输出给处理器或者其他器件,控制器的作用就是保证工作频率是想要的值
锁相环(PLL):作用就是可以升频
相关寄存器:APLL_CON0,APLL的控制寄存器,配置这个寄存器,来达到升频的目的
bit31,ENABLE位:使能位
bit25~16,MDIV
bit13~8,PDIV
bit2~0,SDIV
计算公式:FOUT = MDIV X FIN / (PDIV × 2SDIV-1),这里边FOUT是已知的,是我们想要的值,FIN是输入值,这里边随意带入值,算出一组MDIV,PDIV,SDIV的值分别设定到寄存器中
多路选择器(MUX):有0,1两路与多路选择器连接,输出是一路管脚,选择0,1中额一路通过
CLK_SRC0:多路选择器的寄存器
第0位就是APLL锁相环后边的多路选择器,然后根据管脚的接通到0还是1上,如果是1上,则把CLK_SRC0的第0位写1就可以了
分频器(DIV):是减低频率的,例如输入时1G频率,采用8路分频的话,输出就变成128M
CLK_DIV0:分频寄存器
bit0~2,APLL_RATIO:是收到多路选择器的输出结果后,进行分频
计算公式:ARMCLK = MOUT_MSYS / (APLL_RATIO + 1),MOUT_MSYS是输入端的名字,如果想要1分频,就将APLL_RATIO赋值为0就可以了
②DRAM的初始化,处理器的内存
b.软件环境,初始化保证程序运行环境没有问题
①栈初始化,mov sp,#xxx,ARM中栈指针有6种sp指针,修改sp指针需要先切换模式再对sp赋值
②bss段的清空,以上两点都是C语言需要的,bss段如果不清0,会导致,未初始化的全局变量是一个随机值,这个时候就可能引发问题,所以必须清0
C语言内存:BSS段(未初始化与初始化为0的全局变量,编译器知道BSS段在内存中的地址,如果想知道BSS段的起始/结束地址,需要链接脚本),代码段,堆栈
3.链接脚本(链接脚本是在gnu中的,通常gcc中会有默认的连接脚本,但是在嵌入式开发中根据不同芯片需要配置连接脚本)
编译的过程:预处理,汇编,编译,链接
链接(把.o文件合并成一个可执行程序,这个过程叫做链接)
.o目标文件中已经包含代码段,数据段,BSS段,但是不同的.o文件可能存在依赖关系,并且.o文件没有具体的地址,给代码地址是在链接的时候给出的
编译器默认会将摆在前面先连接到内存,就是在内存的开头,把不同的.o代码段,数据段,BSS段分配到内存中
作用:就是给代码的组成各个部分链接到内存中,给链接器一个配置,让链接器去按照我们配置去链接
链接脚本写法:
main.lds
ENTRY(start) //关键字ENTRY():就是指定程序入口,start就是入口函数
SECTIONS //段(包含代码段,数据段,BSS段),在这里边确定各个段的地址
{
. = 0x20004000; //这种方法对各个段赋值,这句话的意思就是代码段就是从0x20004000开始存储
.text :{ //代码段
start.o(.text) //先链接start.o
*.o(.text); //写*,就会按照字母表来决定链接顺序
}
加关键字空出一段内存,什么都不加就是代码段与数据段与BSS段是连续分配的内存
.data :{ //数据段
*.o(.data);
}
bss_start = .; //bss_start这个就是常量,这个值在汇编中可以直接使用,在C语言中,则需要先声明外部变量,然后取地址值赋值给指针再使用
.bss:{ //BSS段
*.o(.bss);
}
bss_end = .; //bss_end这个也是常量,根据bss_end与bss_start就可以知道这个bss段的起始结束都在哪里
}
很多处理器0地址上没有东西
start.s要初始化栈,bss段,时钟,DROM
ARM学习笔记--day10
最新推荐文章于 2023-01-04 17:02:25 发布