现在正在看韦东山的《嵌入式Linux应用开发完全手册》,看到了中断部分。书上是将程序烧录到NANDFlash中,由于当mini2440板子从NANDFlash启动的时候,会自动将其中的前4KB程序拷贝到CPU内部的SRAM(4KB大小)中,并且将该SRAM的起始地址映射为0x0。所以当中断发生的时候,PC指针指向0x0+offset(如:IRQ就是0x18),可以找到中断服务程序的入口。
但是有4KB的限制,加上U-BOOT一般都烧录到NORFlash中,再将程序搬移到SDRAM中再运行的,恰好手底下有JLINK设备,所以决定写一个烧录到NORFlash,运行在SDRAM中的中断测试程序。
先说一下整体思路:将系统启动时寻找的八个跳转指令写入一个文件,将其加载地址和运行地址都设为0x0,即加载和运行都在NORFlash的0x0。而其余的程序部分:关闭看门狗,设置堆栈,初始化SDRAM,搬移程序到SDRAM中,中断服务函数,按键和LED初始化,main函数等加载地址设为0x40,而运行地址设为SDRAM的起始地址:0x30000000。
这个程序分为head1.S, head2.S, init.c, interrupt.c, mian.c, Makefile, int.lds, s3c24xx.h八个文件组成。
一。head1.S文件:
上面程序中含有八条相对跳转指令b,和两条绝对跳转指令ldr pc,=xx。其中b为位置无关指令,即加载地址与运行地址不同时,仍可以跳转到正确的地方,但是其跳转范围有限,从NORFlash跳到SDRAM中是不可能的。ldr pc, =xx为绝对跳转,是位置相关指令,它是跳转到xx的运行地址处,如HandleIRQ的运行地址为0x3000009c,程序会跳转到0x3000009c处执行。以上两条语句均不会自动保存返回地址。值得注意的是:上面的0x40就是第二段程序(head2.S开始)的起始加载地址,它存放在NORFlash0x40处,所以跳转到0x40处就可以接着执行下面的程序。若是此处写_Reset(初始化程序入口标号,在head2.S文件中),那么程序会跳转到这个标号的运行地址处,即SDRAM中的0x30000008,而此时还没有将程序拷贝到SDRAM中,所以如果这么做,程序必然跑飞。而ldr pc,=HandleIRQ就可以正常跳转到中断服务程序处,这是因为中断发生时,SDRAM初始化和代码搬移已经完成,SDRAM的0x3000009c处存放的就是中断服务程序。
二。head2.S文件:
上面的程序中先定义了几个全局标号,如果不定义HandleIRQ,那么head1.S文件中的跳转是不会成功的,因为在链接之前,该标号没有被定义。其中值得注意的是sdram_start 和sdram_end这两个标号,他们定义的两个.word变量,这两个变量分别为需要搬移的第二段代码的起始地址与终止地址,这两个变量在int.lds链接文件中被定义,下文中有这两个变量的详细介绍。
在_Reset处开始为板子的一些初始化工作,包括关闭看门狗,设置堆栈,初始化SDRAM,将NOR Flash中的代码拷贝到SDRAM中,它们以调用子函数的方式实现。这里用到了跳转指令bl,这个指令和b类似,均为位置无关指令,而且当前pc指针在0x3000xxxx处,需要跳转到的这几个标号的运行地址也在0x3000xxxx处,所以不会出现超过跳转最大范围的问题,所以可以正常跳转。他们两个的不同之处是bl在执行时,返回地址的地址自动保存入lr(r14)寄存器中,在返回时,只用一句:mov pc, lr 即可返回到跳转之前的位置。
ldr pc,=on_sdram这一条绝对跳转指令就可以使程序跳转到SDRAM中去执行,所以on_sdram标号处以后的程序均在SDRAM上运行。上述初始化操作在NOR Flash上运行。然后跳转到了SDRAM中继续执行剩下的程序。
msr cpsr_c, #0x53这一条语句设置CPSR寄存器的低八位(cpsr_c表示CPSR寄存器的低八位),通过设置这个寄存器,将系统设为管理模式,允许IRQ中断,ARM模式。这里不明白的可以查看芯片手册中关于CPSR寄存器的解释部分。
上面的程序是关看门狗,设置堆栈的子程序。
关闭看门狗是为了省去一直“喂狗”的麻烦(如果不一直向系统写入特定的数据,看门狗会认为系统死掉,而重启系统)。关闭的方法就是向控制看门狗的寄存器WTCON(地址为0x53000000)写入0x0。
设置堆栈是为了后面C程序的运行和中断保存现场。其方法是:先将系统设置为IRQ中断模式,在此模式下设置中断指针为0x33500000(这个数可以更改,首先,堆栈是向低地址生长的,其次,SDRAM的截止地址为0x34000000,要留有一部分给C程序,所以这里设置为0x33500000);然后将系统再设为管理模式,设置该模式下的堆栈指针为0x34000000.然后返回即可。
上面的程序是初始化内存(主要是SDRAM)。初始化内存即配置控制内存的寄存器,需要配置的一共是13个,它们的地址是连贯的,初始地址为0x48000000。
s3c2440中一共有八个bank(bank0~bank7)每个bank128M(bank6和bank7的大小及bank7的起始地址可以自由配置,这样使得SDRAM地址的连贯得到保证),其中只有bank6和bank7可以使用SDRAM,mini2440中使用两块16位32M的SDRAM组成一个32位64M的SDRAM放在了bank6上。这里不明白的可以去看s3c2440芯片手册中关于内存的部分。
根据芯片手册,结合《嵌入式linux应用开发完全手册》将13个寄存器配置成上图中的数据。配置方式为将第一个寄存器地址给r0,将要配置的数据初始地址给r1,然后用ldr指令将r1地址内的数据传入r3,再用str指令将r3中的数据传入r0地址处的寄存器中,如此循环。r2为第十三个寄存器的地址,当r0与r2相同时,说明13个寄存器全部配置完成,跳出循环并返回。
上面的程序是将第二段程序(sdram_start到最后)从NORFlash拷贝到SDRAM中。
adr这个指令是位置无关指令,它会获取标号的加载地址(即当前实际运行在什么地方),所以可以用这个指令来判断程序运行在什么地方。这里将sdram_start这个标号的实际地址0x40传入了r0中,此时r0中即为需要拷贝的程序的起始地址。
r1为SDRAM的起始地址,也就是我们要拷贝到的目标地址。
sdram_start和sdram_end这两个标号处在head2.S文件的开头处分别定义了两个.word变量,且在链接文件lds中赋值(看下文中的int.lds文件),这两个变量的差值即为第二段程序的总长度,所以最终r2代表了第二段程序的结束地址。
后面就是进行循环,将代码从0x40拷贝到0x30000000处的过程,当拷贝完成,子程序返回。
上面程序为IRQ的服务程序,完成的功能是:保存现场,保存此时的pc值,进入下一级中断服务程序,当下一级服务程序返回时,恢复现场,并将CPSR还原。
由于ARM的指令流水线及PC始终指向将要运行的地址,需要先计算中断返回的地址,sub lr, lr, #4即为这个操作,有兴趣的可以去查找一下ARM指令流水线。
stmdb ,ldmia为多个寄存器与内存之间的操作指令。此处的stmdb将寄存器r0-r12,lr这些寄存器的值保存入IRQ的堆栈中,这是入栈操作。此处ldmia将刚才保存入堆栈的数据再传入r0-r12,中,将刚才的lr换为了pc,既是将刚才计算的返回地址,传给pc,即可返回中断发生时的地方继续执行。此处的^表示:将SPSR_irq中的数据传入CPSR中,而SPSR_irq在进入中断时自动将CPSR保存进去,所以这个操作,可以使系统回到进入中断前的状态。
EINT_Handle在interrupt.c中可以看到。
三。init.c文件
上面程序实现的功能为:初始化led和按键,写了几个led开关的子程序,打开外部中断。
这里的程序很简单,只说明一下init_irq这个函数。这个函数先是将连接到板子上按键的I/O口设置为外部中断模式,然后将相对应的几个外部中断的屏蔽取消,然后取消了这几个外部中断上一级的屏蔽。但此时中断并没有完全打开,再结合上文中msr cpsr_c, #53才将这几个外部中断完全打开。这里不理解的可以去看一下s3c2440芯片手册中关于中断的讲解。
四。interrupt.c文件
上面的程序就是上文中说到的下一级中断处理函数,它实现的功能十分简单,将每个中断都对应了一种led的变化形式,以此来观察中断程序是否正常运行。
EINTPEND这个寄存器中反映出具体是哪个外部中断产生了,它的每一位对应一个外部中断,该位被置1,说明中断发生,且会被CPU响应(没有被屏蔽),通过这个寄存器中的值即可知道哪个中断发生了。
当处理完中断后,需要将各个中断寄存器清空,清空方法为相对应寄存器的位(刚才置1的位)写1(注意不是0)。至于SRCPND和INTPND寄存器,大家可以在芯片手册上查到,它们分别表示通过一级屏蔽(子中断屏蔽)和二级屏蔽之后的对应的中断。
五。main.c文件
这个文件调用了一些初始化子程序,他们分别在init.c和interrupt.c中,然后进入死循环,死循环中始终让led1进行间隔亮灭。此时如果中断发生,则会进入中断服务程序。这里十分简单,就不详细地说了。
六。int.lds文件
该文件就是整个程序的链接文件。它将整个程序分为两段,第一段加载地址和运行地址均为0x0,第二段加载地址(由AT()确定)为0x40,运行地址为0x30000000。
.{空格} ={空格}0x30000000说明此处的运行地址为0x30000000。此处注意空格,没有的话会报错。
_sdram_start{空格}={空格}. 说明_sdram_start这个变量的值为0x30000000。
当second段语句执行完,定位符(.)变成了当前地址(也就是所有程序的结束地址)。所以_sdram_end变量的值即为所有程序的结束地址。它与_sdram_atart相结合就可以得出第二段程序(需要搬移的程序)的大小。
七。Makefile文件
makefile文件是整个工程的编译链接命令。由上面的程序可以看出,首先对.o文件进行链接,但是此时.o文件尚未生成,所以会先去生成这些.o文件。
那么根据命令:arm-linux-gcc -Wall -O2 -c -o $@ $< 即可逐个生成.o文件。该条命令是只编译不链接。$@代表目标文件,$<代表第一个依赖文件。
然后用链接命令:arm-linux-ld -Tint.lds -o int_elf $^ 来生成可执行文件int_elf。其中-Tint.lds为以int.lds文件所定义的地址分布来链接文件。$^代表全部的依赖文件。
arm-linux-objcopy -O binary -S int_elf $@ 用elf文件生成一个二进制文件int.bin。
arm-linux-objdump -D -m arm int_elf > int.dis 是将int_elf这个可执行文件进行反汇编生成int.dis反汇编文件。我们可以通过这个文件来查看程序的反汇编代码及每条语句的运行地址。
八。编写调试程序中的注意事项
1.第二段程序的实际的运行地址与lds中的运行地址一定要一致,因为其中大多为位置有关指令,若是不一致就不能跳转到正确的位置。所以要保证sdram_start标号的运行地址就是0x30000000,因为后面是以sdram_start标号为起始位置进行代码搬移的。本人在编写的时候,忽略了这一点,将sdram_end标号及_sdram_end变量的定义写到了sdram_start之前,所以经过搬移,sdram_start标号的运行地址为0x30000000,但是系统根据lds文件会认为它的运行地址是0x30000004。所以导致了程序一直不通。
2.注意跳转指令b、bl、ldr pc,=xx的使用,前两个是位置无关指令,有跳转范围,后一个是位置相关指令,无跳转范围。这三个中只有bl可以自动保存返回地址。
3.结合芯片手册搞懂各个寄存器的配置方式及其对应的地址,写错了地址,配置是不可能成功的。还要注意板子上按键和led分别对应的I/O接口,不要搞错了。
上一篇:S3C2440 SDRAM寄存器初始化设置
下一篇:S3c2440代码重定位详解
推荐阅读最新更新时间:2024-03-16 16:09