.extern main //.extern 表示main在另外的文件中定义,在这里要引用,至于为什么只声明了extern而没 //有声明别的,目前还不知道,不过都声明一下,应该没问题
.text //.text 表示后面的内容编译出来放在代码段
.global _start //.global 告诉编译器后面声明的是一个全局可见的名字,由于这是启动代码,所以cpu上电后 //必须要找到_start函数,所以_start必须声明为全局的,当然这个名字可以改,无所谓
_start: //查看反汇编地址就可以发现,_start的地址就是0x0
b Reset //不带返回的跳转到Reset中,为什么不用返回呢,因为没必要,reset中直接进入到主函数内了
HandleUndef: //查看反汇编地址可知HandleUndef的地址位0x04,这个很好理解,因为一条指令4个字节
b HandleUndef //在这里由于是精简的启动代码,所以一旦发生了未定义异常时,就让CPU自己玩死自己吧
HandleSWI: //反汇编地址0x08,其他分析同上
b HandleSWI
HandlePrefetchAbort: //反汇编地址0x0C,其他分析同上
b HandlePrefetchAbort
HandleDataAbort: //反汇编地址0x10,其他分析同上
b HandleDataAbort
HandleNotUsed: //反汇编地址0x14,其他分析同上
b HandleNotUsed
HandleIRQ:
b HandlerIRQ //反汇编地址0x18,看到这里相比就有点感觉了,0x18是普通中断向量地址,这个就有很 //多地方可说了,首先这里依然是使用B指令进行跳转,这个之所以不直接用BL的原因 ///是,中断还不太算是调用函数那么简单,发生中断的时候,要保存现场,而现场的数 //据有很多,具体为R0-R12、PC、CPSR的内容,这些都需要进栈保存,所以这些的内容不 //是BL一条命令能完成的,所以保存现场的内容就直接放到了中断程序中了。其次要特别 //注意这个里面跳转的程序时HandlerIRQ不是HandleIRQ,如果还像上面那样,那么发生中 //断时,CPU就会不停的循环来回跳了,所以这点需要特别留意,当然还有一种方法,就是 //不要这条命令的标号HandleIRQ,直接使用一条 b HandlerIRQ即可。
HandleFIQ:
b HandleFIQ //反汇编地址位0x1C,由于本程序只分析简单的普通中断,所以认为发生快速 //中断的时候,让CPU自己玩
Reset:
ldr sp,=4096 //由于要在汇编中调用C函数,所以要先设置堆栈指针,具体为什么,就 //不详细分析了,简单的说,C函数要使用很多的中间内存存放运算数据,所以 //要开辟一段堆栈用。这里还要特别提到一点,由于ARM工作模式的特点,此时 //设置的堆栈指针SP,即R14,对应的是系统模式下的堆栈指针寄存器,即R14_svc
bl disable_watch_dog //关闭看门狗
msr cpsr_c,# 0xd2 //设置CPU进入到中断模式
ldr sp,=3072 //由于发生中断时,要用到堆栈用于保存现场,所以需要先设置中断模式下的堆 //栈指针地址(设置堆栈指针就相当于开辟堆栈了,因为堆栈就是有堆栈指针的 //一片普通内存区域),即设置R14_irq,特别注意R14_irq和R14_svc是不一样 //的,只是同名,但是指向的物理地址是完全不同的。
msr cpsr_c,# 0xdf //从普通中断模式返回到系统模式
bl init_led //这个非常值得分析,首先这里使用了BL指令进行跳转,BL指令的特点就是,跳转 //的同时,会自动的将PC的值放到LR中,而且,如果后面跳转的是C函数的话,C函 //数中就不用设置返回命令了,而如果BL后面跟的是一个汇编子程序,那就需要程 //序员自己添加返回命令,这是为什么呢?如果用专业术语说,如果调用C函数, //函数运行结束后,编译器会自动的将上面保存的LR中的内容减去4重新传递给 //PC(为什么减4下面分析),也就实现了自动返回的功能,但是汇编函数不行,因 //为汇编的编译器没有这项功能,所以通俗的讲,为什么C语言是高级语言,除了语 //法高级人性化,配套的编译器也比较人性化,会帮程序员做一些通用的工作。而 //汇编语言就比较一根筋,所有的事儿,哪怕是最简单的事儿,都要程序员一条一 //条的把代码敲上去告诉它怎么干(这也是我为什么讨厌汇编的原因,比较懒,呵 //呵),至于调用汇编子程序怎么返回怎么做,分析在下面
bl init_irq //分析同上
msr cpsr_c,# 0x5f //开中断,即允许普通中断IRQ
ldr lr,=halt_loop //这个也很有意思,在这里为什么不直接用 bl main代替这两条命令呢,因 //为。。。。。想嘚瑟一下(玩笑了),主要是想学习另外一种常见常用的跳转方 //法。BL命令很好用,但是有缺点,就是跳转的范围,因为它只能跳转区区32MB, //换算成ARM的命令,也就只有8M的地址(因为一条命令4个字节),也就 //是-4MB~+4MB,所以范围有限,而嵌入式程序,尤其是有操作系统时,程序存放地 //址跨度比较大,所以呢,需要有一个长跳转指令,所以就引出了LDR命令,这个命 //令的跳转范围是4G,所以知道为什么LDR长用了吧,因为无论地址在哪,它都能跳 //到,跳转范围大是LDR命令的优点,但是它也有缺点,就是它负责跳转,不能保存 //PC,如果在跳转前不往LR中放返回地址,就会出现,跳到了子程序的位置,子程序 //执行完后,跳不回来了,所以如果使用ldr调用子程序,一定要在调用前给LR一个 //返回地址。
ldr pc,=main
halt_loop:
b halt_loop //main函数的返回地址,或者说是返回操作,让CPU原地踏步
HandlerIRQ:
sub lr,lr,# 4 //这条指令用于计算中断处理完毕后的返回地址,在这里有个隐含的地方,即当发生中 //断时,处理器会自动的向LR寄存器中存放被中断指令的地址加4,或者也可以理解成,
//存放的是PC的值,而PC的值是当前运行命令地址加8,虽然发生了中断,但是CPU仍然会 //运行完当前命令后才去处理中断,所以LR-4的值,正是被中断指令的下一条命令,当中 //断处理完毕以后,返回到被中断指令的下一条指令进行执行。
stmdb sp!,{r0-r12,lr} //入栈指令,表示将R0~R12,以及LR寄存器的内容入栈,这个命令格式暂不分析,具体的可以参考 //相关手册
ldr lr,=int_return //这两条命令的分析跟上面跳转到main函数原理是一样的,可以使用一条命令
//bl EINT_Handle代替,因为这个程序本身并不大,不会超过4Kb
ldr pc,=EINT_Handle
int_return:
ldmia sp!,{r0-r12,pc}^ //出栈指令,跟上面的入栈指令相呼应,表示将sp对应的地址内容递增(因为ARM的堆栈式满递减 //形式)的依次存放到r0~r12,以及pc中,而且这个很有意思,按照顺序,sp即r13肯定不包括在 //内,所以到pc的时候,赋值的内容寄存器正好是LR,而LR已经在中断开头处理了,而且^表示将 //spsr的值复制到cpsr中,至此完成了中断的返回