学ARM已经41天了,中间断断续续总有点收获,今天把我写的启动代码贴上来,注释很详细,希望
启动代码是几乎是每个arm程序程序必备的,刚开始看的时候看别人的启动代码时感觉云里雾里,所以懒惰的想法浮现脑中:别人都写好了我还写什么,直接拿来用不就行了,对在我懂得情况下,我一定会拿来就用,但是现在我还不懂,一切就要从头开始,经过几天的努力,现在的感觉是启动代码不过如此 :) ,呵呵。;---------------------------------------------------------------------
;startup.s
;系统启动代码
;起始时间 : 2009.5.7 ----->2009.5.11
;---------------------------------------------------------------------
;---------------------------------------------------------------------
GET ./Include/s3c2440.inc ;寄存器地址信息
GET ./Include/memcfg.inc ;内存控制器配置信息
;处理器模式
USERMODE EQU 0x10
FIQMODE EQU 0x11
IRQMODE EQU 0x12
SVCMODE EQU 0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
SYSMODE EQU 0x1f
;相关掩码
MODEMASK EQU 0x1f
NOINT EQU 0xc0
;各个处理器模式下堆栈设置
_STACK_BASEADDRESS EQU 0x33ff8000 ;BANK6 64MB顶部
UserStack EQU (_STACK_BASEADDRESS-0x3800) ;0x33ff4800 ~
SVCStack EQU (_STACK_BASEADDRESS-0x2800) ;0x33ff5800 ~
UndefStack EQU (_STACK_BASEADDRESS-0x2400) ;0x33ff5c00 ~
AbortStack EQU (_STACK_BASEADDRESS-0x2000) ;0x33ff6000 ~
IRQStack EQU (_STACK_BASEADDRESS-0x1000) ;0x33ff7000 ~
FIQStack EQU (_STACK_BASEADDRESS-0x0) ;0x33ff8000 ~
;导入操作系统入口函数
IMPORT OSEntry
;导入外部C语言编写的异常与中断处理函数
IMPORT vectorUNDEF
IMPORT vectorSWI
IMPORT vectorPABT
IMPORT vectorDABT
IMPORT vectorIRQ
IMPORT vectorFIQ
;导入镜像装载域段起始地址
IMPORT|Image$$RO$$Limit|; End of ROM code (=start of ROM data)
IMPORT|Image$$RW$$Base| ; Base of RAM to initialise
IMPORT|Image$$ZI$$Base| ; Base and limit of area
IMPORT|Image$$ZI$$Limit|; to zero initialise
;--------------------------------------------------------------------
;------------------------------------------------------
AREA startup, CODE, READONLY
ENTRY
;系统向量表
b vectorRESET ;复位向量
b vectorUNDEF ;未定义指令
b vectorSWI ;软中断
b vectorPABT ;预取指终止
b vectorDABT ;数据终止
b . ;系统保留
b vectorIRQ ;外部中断
b vectorFIQ ;快速中断
;-------------------------------------------------------
;--------------------------------------------------------------------------
;复位向量
;复位向量是ARM处理器上电后第一个被执行的异常
;此时系统处理管理(SVC)模式
vectorRESET
;复位向量有以下六件事要做
;第一步 : 关闭看门狗定时器屏蔽所有中断
;第二步 : 配置系统时钟
;第三步 : 配置内存控制器
;第四步 : 配置每种处理器模式下堆栈指针
;第五步 : 初始化镜像运行域
;第六步 : 跳转到操作系统入口
;------------------------------------------
;第一步 : 关闭看门狗定时器
;具体内容请参看s3c2440a数据手册的第18章
ldr r0, =WTCON
ldr r1, =0x0
str r1,
;屏蔽所有中断
ldr r0, =INTMSK
ldr r1, =0xffffffff
str r1,
;------------------------------------------
;------------------------------------------
;第二步 : 配置系统时钟
;具体内容请看手册第7章
;先减少锁相环锁定时间,s3c2440a要求PLL
;锁定时间>300us,在上电时s3c2440a预设值
;mpll为晶体频率,我用的晶体频率为12MHz
;300us*12M = 3600设置LOCKTIME = 0xfff
;足够了
ldr r0, =LOCKTIME
ldr r1, =0xfff0fff0 ;高16为对应UPLL
;低16为对应MPLL
str r1,
;根据器件手册我们还有以下几个事要做
;step1.配置UPLL
;step2.配置MPLL
;注:手册要求先配置UPLL后MPLL
; 且之间要间隔7NOP
; 详请看手册第7-21.
;step3.配置分频系数
;step1:
ldr r0, =UPLLCON
ldr r1, =((56<<12) + (2<<4) + 2)
ldr r1,
;按手册要求插入7个NOP
nop
nop
nop
nop
nop
nop
nop
;step2:
ldr r0, =MPLLCON
ldr r1, =((127<<12) + (2<<4) + 1)
ldr r1,
;step3:
ldr r0, =CLKDIVN
ldr r1, =((0<<3) + (2 << 2) + 1)
ldr r1,
;------------------------------------------
;------------------------------------------
;第三步 : 配置内存控制器
;内存控制内的寄存器器地址是连续分布的
;从0x4800_0000 -- 0x4800_0030,所以可以
;通过一个循环依次填入各个寄存器的内容
ldr r0, =SMRDATA ;装入配置值的地址
ldr r1, =BWSCON ;装入起始寄存器地址
add r2, r0, #0x34 ;计算结束地址
;下面是用于向内存控制器
;装入配置信息的循环
0
ldr r3, , #4 ;装入配置值到r3,后变址
str r3, , #4 ;把r3内包含的配置值写入
;内存控制器的寄存器
cmp r2, r0 ;结束否?
bne %B0 ;没结束则继续
;------------------------------------------
;------------------------------------------
;第四步 : 配置每种处理器模式下堆栈指针
;方法与原则:
;1: 通过CPSR寄存器切换处理器模式
;2: 对CPSR的操作方式为 读-修改-写回
;3: 绝对不要跳到用户模式,跳过去容易
; 回来就难了
;4: 切到新处理器模式后要屏蔽IRQ和FIQ
; 防止在未设置好堆栈前进入中断处理
; 程序,但是在启动代码的最先我们已
; 经屏蔽了所有的32个中断源,所以感
; 觉是否屏蔽都可以
;step1: 先把程序状态寄存器读到r0
mrs r0, cpsr
;step2: 清除处理器模式位(最前面5位)
bic r0, r0, #MODEMASK
;step3: 设置未定义状态下的堆栈指针
orr r1, r0, #UNDEFMODE|NOINT
msr cpsr_cxsf, r1 ;UndefMode
ldr sp, =UndefStack ;UndefStack=0x33FF_5C00
;step4: 设置终止状态下的堆栈指针
orr r1, r0, #ABORTMODE|NOINT
msr cpsr_cxsf, r1 ;AbortMode
ldr sp, =AbortStack ;AbortStack=0x33FF_6000
;step5: 设置中断模式下的堆栈指针
orr r1, r0, #IRQMODE|NOINT
msr cpsr_cxsf, r1 ;IRQMode
ldr sp, =IRQStack ;IRQStack=0x33FF_7000
;step6: 设置快速中断模式下的堆栈指针
orr r1, r0, #FIQMODE|NOINT
msr cpsr_cxsf, r1 ;FIQMode
ldr sp, =FIQStack ;FIQStack=0x33FF_8000
;step7: 设置管理模式下的堆栈指针
orr r1, r0, #SVCMODE|NOINT
msr cpsr_cxsf, r1 ;SVCMode
ldr sp, =SVCStack ;SVCStack=0x33FF_5800
;step8: 因为管理模式与用户模式共用
; 堆栈指针,所以借着系统模式
; 来设置用户模式的堆栈指针
orr r1, r0, #SYSMODE|NOINT
msr cpsr_cxsf, r1 ;SYSMode
ldr sp, =UserStack ;SVCStack & USERMode=0x33ff4800
;现在处理器处于系统模式
;------------------------------------------
;------------------------------------------
;第五步 : 初始化镜像运行域
;复制RW段和ZI段到SDRAM指定地址
LDR r0, =|Image$$RO$$Limit| ; 装入RO段结束地址
LDR r1, =|Image$$RW$$Base|; 装入RW段起始地址
LDR r3, =|Image$$ZI$$Base|; 装入ZI段起始地址
;|Image$$RO$$Limit| == |Image$$RW$$Base| ? 跳过RW段复制 : 复制RW段
CMP r0, r1
BEQ %F2
;复制RW段
1
CMP r1, r3
LDRCC r2, , #4
STRCC r2, , #4
BCC %B1
2
LDR r1, =|Image$$ZI$$Limit|
MOV r2, #0
;构造ZI段
3
CMP r3, r1
STRCC r2, , #4
BCC %B3
;------------------------------------------
;------------------------------------------
;第六步 : 跳转到操作系统入口
b OSEntry ;不要使用main,因为如果使用main
;ads还会调用_main()初始化RW和ZI
;段,但是那里的数据和本程序不同
b .
;------------------------------------------
;---------------------------------------------------------------------------
SMRDATA DATA
;这里是内存控制器的配置数据
;配置数据需要根据你使用的存储器修改
;在第三步时会将以下数据写入
;内存控制器的相关寄存器中
;共13个寄存器的配置值
DCD (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
DCD ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) ;GCS0
DCD ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) ;GCS1
DCD ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) ;GCS2
DCD ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) ;GCS3
DCD ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) ;GCS4
DCD ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) ;GCS5
DCD ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) ;GCS6
DCD ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) ;GCS7
DCD ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
DCD 0x32 ;SCLK power saving mode, BANKSIZE 128M/128M
DCD 0x30 ;MRSR6 CL=3clk
DCD 0x30 ;MRSR7 CL=3clk
ALIGN ;数据边界对齐
END
我在写一个arm920T的微型OS,主要是想借着写OS的过程学习ARM的底层编程,然后跳到Linux。启动代码是固件的一部分,最经学校要搞个设计,不知OS什么时候能写好,反正搞定后立即发帖。
下边是完整的工程
运行平台:mini2440
启动代码ourdev_444408.rar(文件大小:61K) (原文件名:MicroOS.rar) 谢谢 记号 我也在学 标记一下 bei ni qiangxian 回【4楼】 kedadiaobing
I am very sorry man 呵呵 谢谢,mark 向你学习ING mark 记号 mark,顺便说下,外部中断不初始化? 请问楼主怎样理解镜像运行域的,比如这段BOOT代码,让其最后跳到MIAN中,在MAIN中再运行一个点灯程序,在ADS的LINK选项中,将|Image$$RO$$Limit| 设为0X0C000000,用JINK仿真程序正常,但是把这个工程的BIN文件下载到FLASH中发现开发板不能启动;
另外;
|Image$$RO$$Limit| 设为0X00000000,下载BIN文件到FLASH中才能启动是为什么? 回【10楼】 deadleon
中断的初始化代码还没写,只是在开始的时候屏蔽了
回【11楼】 henalhs
镜像文件是对整个程序在SDRAM里存放状态的copy,因为内存中的数据段不是连续的,且有很多数据初始化为零的.所以如果严格按照在内存里的状态保存就会浪费rom空间所以在rom里的镜像状态称为装载状态,在sdram里的镜像状态称为运行状态,初始化镜像运行域就是要把在rom里处于装载状态的镜像文件还原到处于sdram里的运行状态,实现的方法是把RO RW ZI 段copy到编译器设置sdram 地址上去.我也是刚学,这只是我的想法,仅供参考 :)
如果要下载到Flash,只要把|Image$$RO$$Base|设置到0x0就可以了,同时修改下
"第二步 :配置系统时钟 "(程序时钟的配置有点问题),系数在s3c2440手册的7-21页,这段代码是固件的一部分,我正在写从Nand flash引导的代码,现在只能引导 <=4K的程序. 记号 谢谢楼主的讲解,期待楼主基于从Nand flash引导的代码! 值得学习!都没研究过启动代码,就只会用别人做好的模板! 同是新手,不过LZ比我强多了,呵呵
MASK一下。 mark 楼主真认真,建议阿莫加酷 发现注释里有点小bug:
;第六步 : 跳转到操作系统入口
b OSEntry ;不要使用main,因为如果使用main
;ads还会调用_main()初始化RW和ZI
;段,但是那里的数据和本程序不同
b .
应该是不要写成这样IMPORT __main
.
.
.
b__main
__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()函数。
写成 IMPORT main
.
.
.
bmain
是可以的。
当然楼主写成OSEntry没有问题,main只是个符号而已
期待楼主更多的学习资料。 会L19
谢谢指点
有点问题请教如果不使用_main()初始化C库好像也可以调用C库(我只使用了一部分C库),是不是不使用_main()也可以使用C库,_main()到底干什么了,等我改下代码,看看汇编代码再说。 楼上使用同学号回的 :) 楼主这个代码根本不是在SDRAM里运行的 不好意思,我也不清楚调用_main()系统干什么去了,我也是看了别人写的代码注释里这么说
我看友善的光盘里的启动代码也是直接跳到.c文件里的main函数执行了,这个__main()有待验证... 大师,野性的一比! 期待有大大详细说一下__main()的功能。 学习中 mark~ b OSEntry ;不要使用main,因为如果使用main
;ads还会调用_main()初始化RW和ZI
;段,但是那里的数据和本程序不同
这个B后面要是跳到MAIN话,ADS会根据
|Image$$RO$$Limit|
|Image$$RW$$Base|
算出你程序的代码段地址(也就是程序的运行段)和读写段地址(也就是已初始的变量段)
说白一点,如果你想把程序在FLASH里面运行,这时要把|Image$$RO$$Limit|设为0X00000000(FLASH地址),
|Image$$RW$$Base| 设为0X0C000000(RAM地址)
想用UBOOT引导后在RAM运行,这时要把|Image$$RO$$Limit|设为0X0C000000(RAM地址),
|Image$$RW$$Base| 设为0X0C000000(RAM地址)
这是我这几天的理解,我上面的方法在恒丰的44B0开发板上已验正通过,希望给初学的朋友有点帮助。 回【28楼】 henalhs
谢谢分享学习成果 :)
大家好像都对地址,装载很感兴趣,我也来唠唠:)
我认为之所以要指定镜像的各段的起始位置是因为程序中的标号,函数,变量的位置是在编译时根据镜像运行时(注意)计算出来的,所以镜像的各段的位置设置在哪里就一定要把镜像的各段放在那里,否者程序就会由于地址对不上而跑飞。
但是究竟位置应该设定在哪呢 ?
主要有两种情况:
1。 程序放在Nor里
程序的代码段可知放在Nor里运行,为什么?想想看,你的程序应该不会在运行时动态改变程序的指令吧,也就意味着只会读Nor,cpu(在有内存控制器的时候)读Nor和读RAM除了速度慢其他的没什么区别。
但是对于RW ZI 不只有读还有写,应为没法向写RAM一样的写Nor,所以RW ZI 一定要放到 RAM里(不管是SRAM还是DRAM),只有在那里程序才能写RW ZI
如何装载?
编译好的镜像处于一种“压缩”的状态。这么压缩的? 比如镜像运行时 RO从0x0 - 0x10RW从Ox3000_0000 - 0x3000_0004ZI 0x3000_0008 - 0x3000_000C 如果直接原样镜像,镜像文件肯定会有很大空隙,且ZI全是零完全没必要镜像下来,只要记住起始 结束地址就行了。所以镜像文件在运行前RO RW ZI 是连载一起的,且RO在最前边
所以就以上分析装载分两大步
step1:由bootloader完成
(1).bootloader直接把整个镜像copy到RAM里,从哪读镜像无所谓,ROM,uart,usb,SD卡,以太,甚至是无线都可以,但是目的位置一定是RO$$Base
(2).然后PC = Ro$$Base
step2:由镜像自己干
上边过后由于Ro段在镜像的最前边且RO的起始位置正好就在Ro$$base所以镜像Ro顺利运行,但是RW ZI还不一定在正确位置上,所以有了启动代码的第五步
2.程序在Nand上
这是的区别就是与nor相比想直接读nand都难了,所以这是要想让程序顺利运行就必须把Ro段也搬到RAM里,这时要有两个东西,一个小程序 《4k 在NAND最前边,负责装载,就是bootloader。把正真想完成任务的程序放在后边,上电时小程序最先被自动copy到sram,sram是定位在0x0的,所以bootloader 的 Ro起始地址必须设置在0x0,然后bootloader就和上边的没什么区别了,先把nand里的大程序整个copy到DRAM,然后大程序执行把自己的RW ZI copy 到正确位置,所以大程序的RO$$Base可以是DRAM的地址。
“这个B后面要是跳到MAIN话,ADS会根据
|Image$$RO$$Limit|
|Image$$RW$$Base|
算出你程序的代码段地址(也就是程序的运行段)和读写段地址(也就是已初始的变量段) "
我觉得bootloader完全没必要分析镜像,只要直接copy就行了,只要镜像自己保证自己的Ro在最前边就行了,这是编译器的事,后边的RW zi 镜像自己就搞定了。
”说白一点,如果你想把程序在FLASH里面运行,这时要把|Image$$RO$$Limit|设为0X00000000(FLASH地址),
|Image$$RW$$Base| 设为0X0C000000(RAM地址) “
这个|Image$$RO$$Limit| =0x0 那Base在哪?没看见ADS可以设置Limit啊.
"想用UBOOT引导后在RAM运行,这时要把|Image$$RO$$Limit|设为0X0C000000(RAM地址),
|Image$$RW$$Base| 设为0X0C000000(RAM地址)”
在ads里如果只设置 ro$$base 那后边的段是接起来的
请看
http://cache.amobbs.com/bbs_upload782111/files_15/ourdev_446201.JPG
(原文件名:Capture.JPG)
我想自己设置肯定可以,如果开MMU的话具体放在哪应该还是要仔细考虑的。
具体那个_main()干了什么,我现在也没看,大家反汇编看下吧,这几天搞学校的设计,学校给钱,不玩白不玩 :) mark!!!! Cool mark 眼泪阿
居然是2440的
先顶下。 mark 楼主比我强百倍
我都学三年了
哈哈 谢谢
刚拿到开发板,想研究一下启动代码
好好学习,天天向上! mark 高手啊,学习了 mark mark 楼上
你是个MM么
我郑重的要求你上PP
不然我会一直在猜想你到底是GG还是MM
严重影响我的正常工作 标记!,有时间再看,楼主不错夸奖一下! mark,很详细,楼主费心了! mark! 好,厉害学习一下 注释很详细,去掉了一些无关紧要的部分,很适合学习启动代码!
楼主厉害! 楼上
你是个青蛙吗?
我郑重的要求你上PP
不然我会一直在猜想你到底是青蛙还是MM
严重影响我的正常工作 标记一下 re: 【47楼】 csformat
晕咯,想不到随便灌水都会被人惦记上。
很遗憾,我是永远都变不成王子的青蛙GG。
而且,我想没有哪个MM会喜欢用爬虫做logo吧?嘿嘿 MARK __main 是keil的库入口函数,我感觉主要就是会根据你设置的heap地址给你初始化heap空间。 __main 是一个初始化函数...
编译器里有一大堆的__开头的隐含函数,(可以找找编译器的帮助文档)
需要设置相关内容时,可以重载这些函数进行参数的配置.
比如全局变量赋初值(非零),内存执行函数的加载,及一些系统默认函数等..
B __main;就是先执行这些函数后,再转到main()函运行.
比如以下代码即为重载__函数:
__user_initial_stackheap
LDR R0,=bottom_of_heap
BX LR
__rt_div0
B __rt_div0 mark 这么好的资料,真是好!! mark. 标记一下
等有时间好好看看 标记一下 有时间好好看看 记号。。 cool 初学和好的东西 谢谢楼主的分享 mark 好贴!!!! mark 哈哈,终于找到了,感谢楼主! 最近在初学AT91RM9200,,,向楼主学习!!! 看源代码看不懂,还是LZ这篇文章详细啊! 楼主用的是S3C2440,我刚买了个哈哈,顶啊 先标记! 你的头文件是哪儿来的?不会自己写的吧。。。。 感谢楼主 很好 顶一个 高手啊,学习 嗯 很好 标记下值得借鉴 哈哈,标记 mark 好东西 先标记一下,之前看启动代码看的都晕掉了 学习 很不错,谢谢。
不过发现给问题啊,你step1:
step1:
ldr r0, =UPLLCON
ldr r1, =((56<<12) + (2<<4) + 2)
ldr r1,
最好应该str r1, 吧 mark mark! 好东西,帮你顶! mark 记号 mark 顶. 赞 我买的板子中初始化程序没有;
step8: 因为管理模式与用户模式共用
; 堆栈指针,所以借着系统模式
; 来设置用户模式的堆栈指针
orr r1, r0, #SYSMODE|NOINT
msr cpsr_cxsf, r1 ;SYSMode
ldr sp, =UserStack ;SVCStack & USERMode=0x33ff4800
没有对用户栈的初始化? 为何?
还有下面处于系统态也可以运行? ;导入外部C语言编写的异常与中断处理函数
IMPORT vectorUNDEF
IMPORT vectorSWI
IMPORT vectorPABT
IMPORT vectorDABT
IMPORT vectorIRQ
IMPORT vectorFIQ
这样直接定义编译的时候不会提示找不到么?
在头文件中有没有指定地址? 好详细 mark 启动代码我都是直接用keil的,从来没研究过这是怎么回事。 MARK 学习 好 学习 mark 留爪!! 标记 footprint 顶