在写这篇blog之前,不得不感慨一句:纸上得来终觉浅,绝知此事要躬行.作为EE出身的,虽然好久好久没用汇编写单片机的中断了,但自我感觉对中断的理解还是比较深入的,本以为在GNU ARM汇编下搞个中断会很容易,谁知道断断续续花了我几周.完全用汇编写中断和用c中的_irq写中断还是有区别的,谁用谁知道.还是那句话:深入细节是必须的,也是值得的.
这一篇blog的理论知识主要来源于:《ARM System Developer's Guide》.
ARM的异常和相应的模式之间的对应关系见下表:
当一个异常导致模式的改变时,内核自动地:
1、把cpsr保存到相应模式下的spsr
2、把pc保存到相应模式下的lr
3、设置cpsr为相应异常模式
4、设置pc为相应异常处理程序的入口地址
从异常中断处理程序返回包含下面两个操作:
1、从spsr_mode中恢复内容到cpsr中
2、从lr_mode中恢复内容到pc中,返回到异常中断的指令的下一条政令处执行.
上面刚提到了异常发生时内核的一些动作,那对与IRQ或者FIQ而言,还多一项变化:禁用相关的中断IRQ或FIQ,禁止同类型的其他中断被触发.
对于最简单的非嵌套中断处理的处理流程如下:
下面给出汇编代码:
-
-
-
-
-
- .equ NOINT, 0xc0
- .equ WTCON, 0x53000000
- .equ GPBCON, 0x56000010 @led
- .equ GPBDAT, 0x56000014 @led
- .equ GPBUP, 0x56000018 @led
- .equ GPFCON, 0x56000050 @interrupt config
- .equ EINTMASK, 0x560000a4
- .equ EXTINT0, 0x56000088
- .equ EXTINT1, 0x5600008c
- .equ EXTINT2, 0x56000090
- .equ INTMSK, 0x4A000008
- .equ EINTPEND, 0x560000a8
-
- .equ INTSUBMSK, 0X4A00001C
-
- .equ SRCPND, 0X4A000000
- .equ INTPND, 0X4A000010
-
- .global _start
- _start: b reset
- ldr pc, _undefined_instruction
- ldr pc, _software_interrupt
- ldr pc, _prefetch_abort
- ldr pc, _data_abort
- ldr pc, _not_used
- @b irq
- ldr pc, _irq
- ldr pc, _fiq
-
-
-
- _undefined_instruction: .word undefined_instruction
- _software_interrupt: .word software_interrupt
- _prefetch_abort: .word prefetch_abort
- _data_abort: .word data_abort
- _not_used: .word not_used
- _irq: .word irq
- _fiq: .word fiq
-
- .balignl 16,0xdeadbeef
-
- reset:
-
-
- ldr r3, =WTCON
- mov r4, #0x0
- str r4, [r3] @ disable watchdog
-
- ldr r0, =GPBCON
- ldr r1, =0x15400
- str r1, [r0]
-
- ldr r2, =GPBDAT
- ldr r1, =0x160
- str r1, [r2]
-
- bl delay
-
-
- msr cpsr_c, #0xd2 @进入中断模式
- ldr sp, =3072 @中断模式的栈指针定义
-
- msr cpsr_c, #0xdf @进入系统模式
- ldr sp, =4096 @设置系统模式的栈指针
-
-
-
-
- @--------------------------------------------
-
- ldr r0, =GPBUP
- ldr r1, =0x03f0
- str r1, [r0]
-
-
-
- ldr r0, =GPFCON
- ldr r1, =0x2ea@0x2
- str r1, [r0]
-
- ldr r0, =EXTINT0
- ldr r1, =0x8f888@0x0@0x8f888 @~(7|(7<<4)|(7<<8)|(7<<16))
- str r1, [r0]
-
- ldr r0, =EINTPEND
- ldr r1, =0xf0@0b10000
- str r1, [r0]
-
- ldr r0, =EINTMASK
- ldr r1, =0x00@0b00000
- str r1, [r0]
-
-
-
- ldr r0, =SRCPND
- ldr r1, =0xff@0x1@0b11111
- str r1, [r0]
-
- ldr r0, =INTPND
- ldr r1, =0xff@0x1@0b11111
- str r1, [r0]
-
- ldr r0, =INTMSK
- ldr r1, =0xffffff00@0b00000
- str r1, [r0]
-
- MRS r1, cpsr
- BIC r1, r1, #0x80
- MSR cpsr_c, r1
-
-
- bl main
-
- irq:
- sub lr,lr,#4
- stmfd sp!,{r0-r12,lr}
- bl irq_isr
- ldmfd sp!,{r0-r12,pc}^
-
-
-
- irq_isr:
-
-
-
- ldr r2, =GPBDAT
- ldr r1, =0x0e0
- str r1, [r2]
-
-
- ldr r0,=EINTPEND
- ldr r1,=0xf0
- str r1,[r0]
-
- ldr r0, =SRCPND
- ldr r1, =0x3f@0b11111
- str r1, [r0]
-
-
-
- ldr r0, =INTPND
- ldr r1, =0x3f@0b11111
- str r1, [r0]
-
-
-
- mov pc,lr
-
-
- delay:
-
- ldr r3,=0xffff
-
- delay1:
- sub r3,r3,#1
-
- cmp r3,#0x0
-
- bne delay1
-
- mov pc,lr
-
-
-
-
- main:
- ledloop:
-
- ldr r1,=0x1c0
- str r1,[r2]
- bl delay
-
- ldr r1,=0x1a0
- str r1,[r2]
- bl delay
-
- ldr r1,=0x160
- str r1,[r2]
- bl delay
-
- ldr r1,=0x0e0
- str r1,[r2]
- bl delay
-
-
- b ledloop
-
-
-
-
- undefined_instruction:
- nop
- software_interrupt:
- nop
- prefetch_abort:
- nop
- data_abort:
- nop
- not_used:
- nop
- fiq:
- nop
lds文件:
- OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
- OUTPUT_ARCH(arm)
- ENTRY(_start)
-
- SECTIONS{
- . = 0x00000000;
- .text : {
- *(.text)
- *(.rodata)
- }
-
- .data ALIGN(4): {
- *(.data)
- }
-
- .bss ALIGN(4): {
- *(.bss)
- }
- }
makefile:
- CROSS = arm-linux-
- CFLAGS = -nostdlib
-
- int.bin: start.S
- ${CROSS}gcc $(CFLAGS) -c -o start.o start.S
- ${CROSS}ld -Tint.lds start.o -o int.elf
- # ${CROSS}ld -Ttext-segment 0x30000000 start.o -o int.elf
- ${CROSS}objcopy -O binary -S int.elf int.bin
- # rm -f *.o
-
-
- clean:
- rm -f *.elf *.o
- rm -f int.bin
该程序实现的流水灯,然后四个按键可以实现外部中断.
代码中值得注意的地方有几点:
1、lds文件中的地址配为0x00000000,因为程序是download到nandflash中运行的.最开始这里写的是0x30000000,那在异常向量表中:
@b irq
ldr pc, _irq
就出现了一个问题:只能用b irq跳转,无法用ldr pc, _irq跳转.当时就觉得奇怪,找了半天原因.后来才知道b跳转和用ldr伪指令只有区别的:
b是位置无关的,ldr不是位置无关的
b的范围只能是前后16M,总共32M,而ldr是4G
ldr的跳转是根据_irq: .word irq的值,这个值是链接的时候确定的,也就是与链接地址相关.
所以在lds中链接地址改为0x00000000后,b和ldr都是正确的.
具体可以用dump看一下实际效果:
当lds中是0x30000000时,arm-linux-objdump -d int.elf结果如下:
30000000 <_start>:
30000000: ea00000e b 30000040 <reset>
30000004: e59ff014 ldr pc, [pc, #20] ; 30000020 <_undefined_instruction>
30000008: e59ff014 ldr pc, [pc, #20] ; 30000024 <_software_interrupt>
3000000c: e59ff014 ldr pc, [pc, #20] ; 30000028 <_prefetch_abort>
30000010: e59ff014 ldr pc, [pc, #20] ; 3000002c <_data_abort>
30000014: e59ff014 ldr pc, [pc, #20] ; 30000030 <_not_used>
30000018: e59ff014 ldr pc, [pc, #20] ; 30000034 <_irq>
3000001c: e59ff014 ldr pc, [pc, #20] ; 30000038 <_fiq>
而lds中是0x00000000时,dump的结果如下:
00000000 <_start>:
0: ea00000e b 40 <reset>
4: e59ff014 ldr pc, [pc, #20] ; 20 <_undefined_instruction>
8: e59ff014 ldr pc, [pc, #20] ; 24 <_software_interrupt>
c: e59ff014 ldr pc, [pc, #20] ; 28 <_prefetch_abort>
10: e59ff014 ldr pc, [pc, #20] ; 2c <_data_abort>
14: e59ff014 ldr pc, [pc, #20] ; 30 <_not_used>
18: e59ff014 ldr pc, [pc, #20] ; 34 <_irq>
1c: e59ff014 ldr pc, [pc, #20] ; 38 <_fiq>
解决了这第一个问题,总算可以用ldr跳入中断向量了.
2、中断处理程序的写法:
- irq:
- sub lr,lr,#4
- stmfd sp!,{r0-r12,lr}
- bl irq_isr
- ldmfd sp!,{r0-r12,pc}^
值得注意的是ldmfd sp!,{r0-r12,pc}^ 会自动的从spsr_irq中恢复到cpsr中.
stmfd等价于stmdb,ldmfd等价于ldmia.因为arm使用FD(向低地址整长的满栈),所以堆栈处理都用fd的后缀即可.
3、记得在中断处理程序中清除中断,不然的话会一直响应那个中断.
转载于:blog.csdn.net/dndxhej