Tiny 6410 按键中断驱动笔记
1. 先查看《Tiny6410SDK-1103 底板原理图》,找到按键部分:
从上图可知,当按键按下时,相当于接地,即低电平,从而产生一个由高电平到低电平的跳变。
Tiny6410的底板有8个按键:
2. 查看《Tiny6410-1170 CPU核心板原理图》,找到EINT0的连接图:
从上图可知:
EINT0 接 GPN0
EINT1 接 GPN1
EINT2 接 GPN2
ENIT3 接 GPN3
EINT4 接 GPN4
EINT5 接 GPN5
EINT19 接 GPL11
EINT20 接 GPL12
知识点:
由s3c6410外部触发的中断就是外部中断,由s3c6410内部触发的是内部中断,像watch dog就是内部中断,像key,wm9717触发的就是外部中断。
外部中断从外设到cpu的具体流程:
外设------>GPIO------>VIC------>ARM1176
我们编写关于中断的程序也是这个过程:
配置外设------>配置GPIO------>配置VIC------>配置ARM协处理器等
其实就是要打造一个通路能够让中断的电平变化能顺顺利利的传送到ARM里!
3. 中断设置流程
外设都是连到GPIO上的。s3c6410具有187个多功能I/O端口,其实有127个用于外部中断。这127个引脚可以分为10个分组:
EINT0 GPN0--->GPN15 GPL8--->GPL14 GPM0--->GPM4
EINT1 GPA0--->GPA7 GPB0--->GPB6
EINT2 GPC0--->GPC7
EINT3 GPD0--->GPD5
EINT4 GPF0--->GPF14
EINT5 GPG0--->GPG7
EINT6 GPH0--->GPH9
EINT7 GPO0--->GPO15
EINT8 GPP0--->GPP14
EINT9 GPQ0--->GPQ9
每个引脚可以对应一个外部中断。那么当外部中断电平变化传GPIO里,除了对应端口的哪几个寄存器(CON,PUD,etc)GPIO里又有哪些寄存器会
对这个中断信号造成影响呢?看下面:
EINTXCON :配置触发方式,低电平,高电平,上升沿,下降沿。
EINTXPEND :这个现在用不到,一会儿中断处理程序会用到,这个是pending register.
EINTXMASK :这里可以屏蔽某个外部中断,要通过需要clear一下对应的中断位。默认是全屏蔽的。
EINTXFLTCON :这里设置滤波方式,可以去毛刺。
我们这里要设置的是EINTXCON,EINTXMASK,EINTXFLTCON。这样我们的中断信号就可以顺利通过GPIO了,然后就是VIC 向量中断控制器了。
上面说的这些外部中断在GPIO里分成了九组,具体反应到VIC里他们占用中断号如下:
No |
Sources |
Description |
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 |
这里我们使用VIC,当然要先开启VIC,这个是在协处理器里设置的 VE位
mrc p15,0,r0,c1,c0,0
orr r0,r0,#(1<<24)"
mcr p15,0,r0,c1,c0,0
主要是通过CP15协处理器特定的寄存器来控制VIC的使能:查看arm6410手册,arm1176JZF-S.pdf 第3-14页如下图所示,
因为op1=0,CRn=c1,CRm=c0,op2=0,跳转到3-44页图所示,查看对应寄存器。
首先,mrc p15,0,r0,c1,c0,0将协处理器cp15中op1=0,CRn=c1,CRm=c0,
op2=0所对应的寄存器的值传给r0寄存器。
其次,orr r0,r0,#(1<<24)将上面得到的r0寄存器的值与1左移24位得到的值进行按位或运算,将得到的结果放入r0中。
最后,mcr p15,0,r0,c1,c0,0将运算后的r0的值重新放回cp15协处理器对应的寄存器当中。
这样我们的VIC就能用了。
然后就是开启总中断:
mrs r0,cpsr
bic r0,r0,#0x80
msr cpsr_c,r0
然后是VIC的设置,几个重要的寄存器:
VICXINTSELECT:选择中断方式FIQ or IRQ。
VICXVECTADDR:设置中断处理程序的地址。
VICXINTENABLE:使能GPIO传过来的中断信号。
其实设置到这里外部中断就能正确运行了,但是还有许多别的寄存器,比如设置什么优先级的。
ARM得知来了个中断,就和VIC进行一系列的握手,得到VICADDRESS,就开始执行我们的中断处理程序了。最后要清除一下EINTXPEND和VICXADDRESS。
4. 程序实现:
inter.s :
.text .code 32 .global _start .global asm_handle_k1_irq .extern interrupt_test .extern handle_k1_irq _start: @disable watch dog ldr r0, =0x7E004000 mov r1, #0 str r1, [r0] @enable vic mrc p15,0,r0,c1,c0,0 orr r0,r0,#(1<<24) mcr p15,0,r0,c1,c0,0 @enable interrupt mrs r0,cpsr bic r0,r0,#0x80 msr cpsr_c,r0 ldr sp, = 0x0C001000 @sp_irq mode msr cpsr_cxsf,#0xd2 ldr sp, = 0x0C001000 @return back to svc mode msr cpsr_cxsf, #0x13 bl interrupt_test loop: b loop asm_handle_k1_irq: stmfd sp!, {r0-r3,r12,lr} ldr lr,=int_return bl handle_k1_irq int_return: ldmfd sp!, {r0-r3,r12,lr} subs pc,lr,#4
inter_func.c:
#define GPKCON0 *((volatile unsigned int*)0x7F008800) #define GPKDAT *((volatile unsigned int*)0x7F008808) #define GPKPUD *((volatile unsigned int*)0x7F00880C) #define GPNCON *((volatile unsigned int*)0x7F008830) #define GPNPUD *((volatile unsigned int*)0x7F008838) #define EINT0CON0 *((volatile unsigned int*)0x7F008900) #define EINT0MASK *((volatile unsigned int*)0x7F008920) #define EINT0PEND *((volatile unsigned int*)0x7F008924) #define EINT0FLTCON0 *((volatile unsigned int*)0x7F00891C) #define VIC0INTSELECT *((volatile unsigned int*)0x7120000C) #define VIC0VECTADDR *((volatile unsigned int*)0x71200100) #define VIC0INTENABLE *((volatile unsigned int*)0x71200010) #define VIC0INTENCLEAR *((volatile unsigned int*)0x71200014) #define VIC0ADDRESS *((volatile unsigned int*)0x71200F00) #define VIC1ADDRESS *((volatile unsigned int*)0x71300F00) typedef void (isr) (void); extern void asm_handle_k1_irq(); void led_init() { //init gpkcon GPKCON0 &= 0x0000ffff; GPKCON0 |= 0x11110000; //set output //light led1 GPKDAT = 0xffef; //set pull-up register //GPKPUD = 0x000aa00; } void handle_k1_irq() { //reverse led1 GPKDAT ^= 0x0010; // clear K1 irq EINT0PEND = 1; //clear irq VIC0ADDRESS = 0; VIC1ADDRESS = 0; } void key_io_init() { //configure k1 as Ext.Interrupt GPNCON &= (~0x03); GPNCON |= 0x02; GPNPUD &= ~(0x3); //configure k1 as falling edge trigged EINT0CON0 &= (~0x03); EINT0CON0 |= 0x3; //EINT0FLTCON0 |= 0x40; //Enable EINT0 irq EINT0MASK &= (~0x1); // Select INT_EINT0 mode as irq VIC0INTSELECT = 0; // init the isr addr isr** isr_array = (isr**)(0x71200100); isr_array[0] = (isr*)asm_handle_k1_irq; //EINT0PEND = 0xffffffff; //VIC0INTENCLEAR = 0xffffffff; //enable VIC0INTENABLE |= 0x01; } void interrupt_test() { led_init(); key_io_init(); }
makefile:
CC=arm-linux-gcc LD=arm-linux-ld OBJCOPY=arm-linux-objcopy CFLAG=-c LDFLAG=-e _start -Ttext 0x0c000000 inter.bin: inter $(OBJCOPY) -O binary $< $@ inter: inter.o inter_func.o $(LD) $(LDFLAG) $? -o $@ inter.o:inter.s $(CC) $(CFLAG) $< -o $@ inter_func.o:inter_func.c $(CC) $(CFLAG) $< -o $@ clean: rm *.o rm inter rm inter.bin
5. 问题记录
(1) 设置GPNCON时,网上的文章写成GPNCON &= (~0x2),实际上应该是GPNCON &= (~0x03),导致中断无反应。
(2) 直接设置VIC向量的地址为中断逻辑,忘记要在中断模式下进行堆栈的保存与恢复,导致程序只能触发一次中断。
asm_handle_k1_irq:
stmfd sp!, {r0-r3,r12,lr}
ldr lr,=int_return
bl handle_k1_irq @一开始直接作为VIC向量的地址
int_return:
ldmfd sp!, {r0-r3,r12,lr}
subs pc,lr,#4