中断概念
CPU在工作的过程中,经常需要与外设进行交互,交互的方式包括”轮询方式”,”中断方式”。
1. 轮询方式:
CPU不断地查询设备的状态并作出相应的反应。该方式实现比较简单,但占用 CPU 资源过高,CPU利用率很低,不适合多任务的系统。
2. 中断方式:
当某件事发生了,硬件会设置某个寄存器;CPU在每执行完一个指令时,通过硬件查看这个寄存器,如果发现所关注的事件发生了,则中断当前程序流程,跳转到一个固定地址处理这件事,最后返回继续执行被中断的程序。它的实现复杂,但是效率很高,是常用的方法。
在中断的生命周期中,中断源的作用是负责产生中断信号。S3C6410支持64个中断源; (通过芯片手册浏览中断源)
从上图可以看出,S3C6410的中断分为VIC0和VIC1两组,每一组32个中断。
外部中断:
No |
Source |
Desciption |
Group |
0 |
INT_EINT0 |
External interrupt 0~ 3 |
VIC0 |
1 |
INT_EINT1 |
External interrupt 4 ~ 11 |
VIC0 |
32 |
INT_EINT2 |
External interrupt 12 ~ 19 |
VIC1 |
33 |
INT_EINT3 |
External interrupt 20 ~ 27 |
VIC1 |
53 |
INT_EINT4 |
External interrupt Group 1 ~ Group 9 |
VIC1 |
InterruptController in S3C6410X
S3C6410中断处理有向量模式和非向量方式。向量中断就是不同的中断有不同的入口地址,非向量中断就只有一个入口地址,进去了在判断中断标志来识别具体是哪个中断。向量中断实时性好,非向量中断简单。
向量中断------由硬件提供中断服务程序入口地址;
非向量中断------由软件提供中断服务程序入口地址;
非向量中断方式:
向量中断方式:
不论何种CPU,中断处理过程是相似的。
1) 中断控制器汇集各类外设发出的中断信号,然后告诉CPU。
2) CPU保存当前程序的运行环境(各个寄存器等),调用中断服务程序(ISR)来处理这些中断。
3) 在ISR中进行相应的处理。
4) 清除中断
5) 最后恢复被中断程序的运行环境,继续执行。
对于不同的CPU而言,中断的处理只是细节不同。
下面我们实现按键中断,这里采用的是向量中断的方式。
在ok6410的开发板上按键S2---S7对应的引脚为GPN0-----GPN5,LED1-LED4对应的引脚为GPM0----GPM3
一 设置GPN0引脚为外部中断
#define GPNCON (volatile unsigned long*)0x7f008830 void button_init() { *(GPNCON) = 0b10 ; //设置按键S2 }
二 初始化中断控制寄存器
定义各个寄存器的地址:
/*interrupt registes*/ #define EXT_INT_0_CON *((volatile unsigned int *)0x7f008900) #define EXT_INT_0_MASK *((volatile unsigned int *)0x7f008920) #define EXT_INT_0_PEND *((volatile unsigned int *)0x7f008924) #define VIC0INTENABLE *((volatile unsigned int *)0x71200010) #define EINT0_VECTADDR *((volatile unsigned int *)0x71200100) #define VIC0ADDRESS *((volatile unsigned int *)0x71200f00)
1 设置触发方式
外部中断配置寄存器:
设置按键S2下降沿触发方式:
EXT_INT_0_CON = 0b010;
EXT_INT_0_MASK = 0;
3 使能外部中断
VIC0INTENABLE |= (0b1); /* 使能外部中断*/
因为S3C6410是采用向量中断,所以就需要配置中断的入口地址。这个入口地址就是靠VICxVECTADDRy寄存器来配置的。
当按下S2时,CPU就会自动的将VIC0VECTADDR0的值赋给VIC0ADDRESS并跳转到这个地址去执行。
EINT0_VECTADDR = (int)key_handle; /*设置入口地址*/
Key_handle是中断处理程序的函数名。
4 使能向量中断和打开总中断
__asm__( //打开向量中断方式 "mrc p15,0,r0,c1,c0,0\n" "orr r0,r0,#(1<<24)\n" "mcr p15,0,r0,c1,c0,0\n" //打开总中断,将CPRS的I位清零 "mrs r0,cpsr\n" "bic r0, r0, #0x80\n" "msr cpsr_c, r0\n" : : );
三 编写中断处理函数
void key1_handle() { __asm__( "sub lr, lr, #4\n" "stmfd sp!, {r0-r12, lr}\n" : : ); led_on(); /* 清除中断 */ EXT_INT_0_PEND = ~0x0; VIC0ADDRESS = 0; __asm__( "ldmfd sp!, {r0-r12, pc}^ \n" : : ); }
"sub lr, lr, #4\n" 计算中断处理完毕后的返回地址
中断跳转的时候,会将pc的值给lr,pc的值为当前执行程序地址+4,lr的值就是pc的值。而返回的地址应该是执行阶段的下一条地址,也就是当前执行程序地址+4,所以直接返回lr的值就不对了,应该返回lr-4的值。
"stmfd sp!, {r0-r12, lr}\n" 保存被中断程序的运行环境,即各个寄存器。
"ldmfd sp!, {r0-r12, pc}^ \n" 恢复被中断程序的运行环境
Last but not least!!!
中断处理函数的代码是用c代码写的,需要栈来环境保护和恢复运行环境了。ARM有7中不同工作模式,不同的工作模式有自己的备份寄存器。其中,栈SP是每个模式都有自己的。所以需要设置下irq模式下的栈。将模式切换为irq模式,再设置sp。
init_stack: msr cpsr_c, #0xd2 ldr sp, =0x53000000 //初始化r13_irq msr cpsr_c, #0xd3 ldr sp, =0x54000000 //初始化R13_svc mov pc, lr
参考资料:《嵌入式开发完全手册》
http://comm.chinaaet.com/adi/blogdetail/40071.html