六
、
S5PV210中断处理
1.S5PV210的外部中断通过外部中断对应的GPIO产生
外部中断寄存器位于文档中GPIO部分。
外部中断的主要寄存器:
EXT_INT_N_CON:设置外部中断的触发方式
EXT_INT_N_PEND:中断挂起寄存器,32位,每位对应一个外部中断源,
EXT_INT_N_MASK:中断掩码控制寄存器,各个外部中断的使能/禁止开关。
分析X210开发板的按键对应的EINT编号:
GPIO :SW5:GPH0_2 SW6:GPH0_3 SW78910:GPH2_0123
EINT :EINT2、EINT3、EINT16、EINT17、EINT18、EINT19
2.中断方式处理按键编程
- 按键的初始化(包括外部中断的GPIO,触发模式等设置,分开说明)
2.1. 外部中断对应的GPIO模式设置
rGPH0CON |= 0xFF<<8; // GPH0_2 GPH0_3设置为外部中断模式 rGPH2CON |= 0xFFFF<<0;// GPH2_0123共4个引脚设置为外部中断模式
.
2.2. 中断触发模式设置
rEXT_INT_0_CON &= ~(0xFF<<8); // bit8~bit15全部清零 rEXT_INT_0_CON |= ((2<<8)|(2<<12)); // EXT_INT2和EXT_INT3设置为下降沿触发 rEXT_INT_2_CON &= ~(0xFFFF<<0); //EINT16、17、18、19,设置为下降沿触发 rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12));
按键的初始化(包括外部中断的GPIO,触发模式等设置)的整合如下:
2.3. 中断允许
rEXT_INT_0_MASK &= ~(3<<2); // 外部中断允许 rEXT_INT_2_MASK &= ~(0x0f<<0);
2.4. 清挂起,清除是写1,不是写0
rEXT_INT_0_PEND |= (3<<2); rEXT_INT_2_PEND |= (0x0F<<0);
按键的初始化(包括外部中断的GPIO,触发模式等设置)的整合如下:
3.中断处理程序isr编写及其整体调用
3.1.中断ISR处理函数的编写,即(类似于STM32的中断服务函数)
3.2.绑定isr到中断控制器硬件
3.3.使能中断(不是使能外部中断(因前面的中断初始化已经使能),是使能中断控制器)
3.4.整体调用如下:
3.5
最后,上面的理解在结合下图 一起理解,就显得很简单了。
中断处理的编程模型:
int.c:
#include "int.h" #include "stdio.h" void reset_exception(void) { printf("reset_exception.\n"); } void undef_exception(void) { printf("undef_exception.\n"); } void sotf_int_exception(void) { printf("sotf_int_exception.\n"); } void prefetch_exception(void) { printf("prefetch_exception.\n"); } void data_exception(void) { printf("data_exception.\n"); } // 主要功能:绑定第一阶段异常向量表;禁止所有中断;选择所有中断类型为IRQ; // 清除VICnADDR为0 void system_init_exception(void) { // 第一阶段处理,绑定异常向量表 r_exception_reset = (unsigned int)reset_exception; r_exception_undef = (unsigned int)undef_exception; r_exception_sotf_int = (unsigned int)sotf_int_exception; r_exception_prefetch = (unsigned int)prefetch_exception; r_exception_data = (unsigned int)data_exception; r_exception_irq = (unsigned int)IRQ_handle; r_exception_fiq = (unsigned int)IRQ_handle; // 初始化中断控制器的基本寄存器 intc_init(); } // 清除需要处理的中断的中断处理函数的地址 void intc_clearvectaddr(void) { // VICxADDR:当前正在处理的中断的中断处理函数的地址 VIC0ADDR = 0; VIC1ADDR = 0; VIC2ADDR = 0; VIC3ADDR = 0; } // 初始化中断控制器 void intc_init(void) { // 禁止所有中断 // 为什么在中断初始化之初要禁止所有中断? // 因为中断一旦打开,因为外部或者硬件自己的原因产生中断后一定就会寻找isr // 而我们可能认为自己用不到这个中断就没有提供isr,这时它自动拿到的就是乱码 // 则程序很可能跑飞,所以不用的中断一定要关掉。 // 一般的做法是先全部关掉,然后再逐一打开自己感兴趣的中断。一旦打开就必须 // 给这个中断提供相应的isr并绑定好。 VIC0INTENCLEAR = 0xffffffff; VIC1INTENCLEAR = 0xffffffff; VIC2INTENCLEAR = 0xffffffff; VIC3INTENCLEAR = 0xffffffff; // 选择中断类型为IRQ VIC0INTSELECT = 0x0; VIC1INTSELECT = 0x0; VIC2INTSELECT = 0x0; VIC3INTSELECT = 0x0; // 清VICxADDR intc_clearvectaddr(); } // 绑定我们写的isr到VICnVECTADDR寄存器 // 绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理 // 等发生相应中断的时候,我们直接到相应的VICnADDR中去取isr地址即可。 // 参数:intnum是int.h定义的物理中断号,handler是函数指针,就是我们写的isr // VIC0VECTADDR定义为VIC0VECTADDR0寄存器的地址,就相当于是VIC0VECTADDR0~31这个 // 数组(这个数组就是一个函数指针数组)的首地址,然后具体计算每一个中断的时候 // 只需要首地址+偏移量即可。 void intc_setvectaddr(unsigned long intnum, void (*handler)(void)) { //VIC0 if(intnum<32) { *( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler; } //VIC1 else if(intnum<64) { *( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler; } //VIC2 else if(intnum<96) { *( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler; } //VIC3 else { *( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler; } return; } // 使能中断 // 通过传参的intnum来使能某个具体的中断源,中断号在int.h中定义,是物理中断号 void intc_enable(unsigned long intnum) { unsigned long temp; // 确定intnum在哪个寄存器的哪一位 // <32就是0~31,必然在VIC0 if(intnum<32) { temp = VIC0INTENABLE; temp |= (1<<intnum); // 如果是第一种设计则必须位操作,第二种设计可以 // 直接写。 VIC0INTENABLE = temp; } else if(intnum<64) { temp = VIC1INTENABLE; temp |= (1<<(intnum-32)); VIC1INTENABLE = temp; } else if(intnum<96) { temp = VIC2INTENABLE; temp |= (1<<(intnum-64)); VIC2INTENABLE = temp; } else if(intnum<NUM_ALL) { temp = VIC3INTENABLE; temp |= (1<<(intnum-96)); VIC3INTENABLE = temp; } // NUM_ALL : enable all interrupt else { VIC0INTENABLE = 0xFFFFFFFF; VIC1INTENABLE = 0xFFFFFFFF; VIC2INTENABLE = 0xFFFFFFFF; VIC3INTENABLE = 0xFFFFFFFF; } } // 禁止中断 // 通过传参的intnum来禁止某个具体的中断源,中断号在int.h中定义,是物理中断号 void intc_disable(unsigned long intnum) { unsigned long temp; if(intnum<32) { temp = VIC0INTENCLEAR; temp |= (1<<intnum); VIC0INTENCLEAR = temp; } else if(intnum<64) { temp = VIC1INTENCLEAR; temp |= (1<<(intnum-32)); VIC1INTENCLEAR = temp; } else if(intnum<96) { temp = VIC2INTENCLEAR; temp |= (1<<(intnum-64)); VIC2INTENCLEAR = temp; } else if(intnum<NUM_ALL) { temp = VIC3INTENCLEAR; temp |= (1<<(intnum-96)); VIC3INTENCLEAR = temp; } // NUM_ALL : disable all interrupt else { VIC0INTENCLEAR = 0xFFFFFFFF; VIC1INTENCLEAR = 0xFFFFFFFF; VIC2INTENCLEAR = 0xFFFFFFFF; VIC3INTENCLEAR = 0xFFFFFFFF; } return; } // 通过读取VICnIRQSTATUS寄存器,判断其中哪个有一位为1,来得知哪个VIC发生中断了 unsigned long intc_getvicirqstatus(unsigned long ucontroller) { if(ucontroller == 0) return VIC0IRQSTATUS; else if(ucontroller == 1) return VIC1IRQSTATUS; else if(ucontroller == 2) return VIC2IRQSTATUS; else if(ucontroller == 3) return VIC3IRQSTATUS; else {} return 0; } // 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场 void irq_handler(void) { //printf("irq_handler.\n"); // SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断 // 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来 // 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断 // 对应的isr。 // 虽然硬件已经自动帮我们把isr放入了VICnADDR中,但是因为有4个,所以我们必须 // 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICADDR寄存器中 unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR}; int i=0; void (*isr)(void) = NULL; for(i=0; i<4; i++) { // 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0 if(intc_getvicirqstatus(i) != 0) { isr = (void (*)(void)) vicaddr[i]; break; } } (*isr)(); // 通过函数指针来调用函数 }
key.c:
#include "stdio.h" #include "main.h" // 定义操作寄存器的宏 #define GPH0CON 0xE0200C00 #define GPH0DAT 0xE0200C04 #define GPH2CON 0xE0200C40 #define GPH2DAT 0xE0200C44 #define rGPH0CON (*(volatile unsigned int *)GPH0CON) #define rGPH0DAT (*(volatile unsigned int *)GPH0DAT) #define rGPH2CON (*(volatile unsigned int *)GPH2CON) #define rGPH2DAT (*(volatile unsigned int *)GPH2DAT) #define EXT_INT_0_CON 0xE0200E00 #define EXT_INT_2_CON 0xE0200E08 #define EXT_INT_0_PEND 0xE0200F40 #define EXT_INT_2_PEND 0xE0200F48 #define EXT_INT_0_MASK 0xE0200F00 #define EXT_INT_2_MASK 0xE0200F08 #define rEXT_INT_0_CON (*(volatile unsigned int *)EXT_INT_0_CON) #define rEXT_INT_2_CON (*(volatile unsigned int *)EXT_INT_2_CON) #define rEXT_INT_0_PEND (*(volatile unsigned int *)EXT_INT_0_PEND) #define rEXT_INT_2_PEND (*(volatile unsigned int *)EXT_INT_2_PEND) #define rEXT_INT_0_MASK (*(volatile unsigned int *)EXT_INT_0_MASK) #define rEXT_INT_2_MASK (*(volatile unsigned int *)EXT_INT_2_MASK) //------------------------轮询方式处理按键--------------------------- // 初始化按键 void key_init(void) { // 设置GPHxCON寄存器,设置为输入模式 // GPH0CON的bit8~15全部设置为0,即可 rGPH0CON &= ~(0xFF<<8); // GPH2CON的bit0~15全部设置为0,即可 rGPH2CON &= ~(0xFFFF<<0); } static void delay20ms(void) { // 这个函数作用是延时20ms // 因为我们这里是裸机程序,且重点不是真的要消抖,而是教学 // 所以我这里这个程序只是象征性的,并没有实体 // 如果是研发,那就要花时间真的调试出延时20ms的程序 int i, j; for (i=0; i<100; i++) { for (j=0; j<1000; j++) { i * j; } } } void key_polling(void) { // 依次,挨个去读出每个GPIO的值,判断其值为1还是0.如果为1则按键按下,为0则弹起 // 轮询的意思就是反复循环判断有无按键,隔很短时间 while (1) { // 对应开发板上标着LEFT的那个按键 if (rGPH0DAT & (1<<2)) { // 为1,说明没有按键 led_off(); } else { // 添加消抖 // 第一步,延时 delay20ms(); // 第二步,再次检验按键状态 if (!(rGPH0DAT & (1<<2))) { // 为0,说明有按键 led1(); printf("key left.\n"); } } // 对应开发板上标着DOWN的那个按键 if (rGPH0DAT & (1<<3)) { // 为1,说明没有按键 led_off(); } else { // 为0,说明有按键 led2(); printf("key down.\n"); } // 对应开发板上标着UP的那个按键 if (rGPH2DAT & (1<<0)) { // 为1,说明没有按键 led_off(); } else { // 为0,说明有按键 led3(); } } } //-----------------------中断方式处理按键----------------------------------- // 以中断方式来处理按键的初始化 void key_init_interrupt(void) { // 1. 外部中断对应的GPIO模式设置 rGPH0CON |= 0xFF<<8; // GPH0_2 GPH0_3设置为外部中断模式 rGPH2CON |= 0xFFFF<<0; // GPH2_0123共4个引脚设置为外部中断模式 // 2. 中断触发模式设置 rEXT_INT_0_CON &= ~(0xFF<<8); // bit8~bit15全部清零 rEXT_INT_0_CON |= ((2<<8)|(2<<12)); // EXT_INT2和EXT_INT3设置为下降沿触发 rEXT_INT_2_CON &= ~(0xFFFF<<0); rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12)); // 3. 中断允许 rEXT_INT_0_MASK &= ~(3<<2); // 外部中断允许 rEXT_INT_2_MASK &= ~(0x0f<<0); // 4. 清挂起,清除是写1,不是写0 rEXT_INT_0_PEND |= (3<<2); rEXT_INT_2_PEND |= (0x0F<<0); } // EINT2通道对应的按键,就是GPH0_2引脚对应的按键,就是开发板上标了LEFT的那个按键 void isr_eint2(void) { // 真正的isr应该做2件事情。 // 第一,中断处理代码,就是真正干活的代码 printf("isr_eint2_LEFT.\n"); // 第二,清除中断挂起(中断处理后,必须清除中断挂起) rEXT_INT_0_PEND |= (1<<2); intc_clearvectaddr();//清除需要处理的中断的中断处理函数的地址 } void isr_eint3(void) { // 真正的isr应该做2件事情。 // 第一,中断处理代码,就是真正干活的代码 printf("isr_eint3_DOWN.\n"); // 第二,清除中断挂起 rEXT_INT_0_PEND |= (1<<3); intc_clearvectaddr();// 清除需要处理的中断的中断处理函数的地址 } void isr_eint16171819(void) { // 真正的isr应该做2件事情。 // 第一,中断处理代码,就是真正干活的代码 // 因为EINT16~31是共享中断,所以要在这里再次去区分具体是哪个子中断 if (rEXT_INT_2_PEND & (1<<0)) { printf("eint16\n"); } if (rEXT_INT_2_PEND & (1<<1)) { printf("eint17\n"); } if (rEXT_INT_2_PEND & (1<<2)) { printf("eint18\n"); } if (rEXT_INT_2_PEND & (1<<3)) { printf("eint19\n"); } // 第二,清除中断挂起 rEXT_INT_2_PEND |= (0x0f<<0); intc_clearvectaddr(); }
start.S:
#define WTCON 0xE2700000 #define SVC_STACK 0xd0037d80 #define IRQ_STACK 0xd0037f80 .global _start .global IRQ_handle // 把_start链接属性改为外部,这样其他文件就可以看见_start了 _start: // 第1步:关看门狗(向WTCON的bit5写入0即可) ldr r0, =WTCON ldr r1, =0x0 str r1, [r0] // 第2步:初始化时钟 bl clock_init // 第3步:设置SVC栈 ldr sp, =SVC_STACK // 第4步:开/关icache mrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中 //bic r0, r0, #(1<<12) // bit12 置0 关icache orr r0, r0, #(1<<12) // bit12 置1 开icache mcr p15,0,r0,c1,c0,0; bl main // 从这里之后就可以开始调用C程序了 //bl led_blink // led_blink是C语言实现的一个函数 // 汇编最后的这个死循环不能丢 b . // 在这个汇编函数中,用来做中断模式下的现场保护和恢复,并且调用真正的中断处理程序 IRQ_handle: // 设置IRQ模式下的栈 ldr sp, =IRQ_STACK // 保存LR // 因为ARM有流水线,所以PC的值会比真正执行的代码+8, sub lr, lr, #4 // 保存r0-r12和lr到irq模式下的栈上面 stmfd sp!, {r0-r12, lr} // 在此调用真正的isr来处理中断 bl irq_handler // 处理完成开始恢复现场,其实就是做中断返回,关键是将r0-r12,pc,cpsr一起回复 ldmfd sp!, {r0-r12, pc}^
main.c:
#include "stdio.h" #include "int.h" #include "main.h" void uart_init(void); #define KEY_EINT2 NUM_EINT2 // left NUM_EINT2为按键对应中断源编号 #define KEY_EINT3 NUM_EINT3 // down #define KEY_EINT16_19 NUM_EINT16_31 // 其余4个共用的 void delay(int i) { volatile int j = 10000; while (i--) while(j--); } int main(void) { uart_init(); key_init_interrupt();//中断方式处理按键的初始化 // 如果程序中要使用中断,就要调用中断初始化来初步初始化中断控制器 system_init_exception(); printf("-------------key interrypt test--------------"); // 绑定isr到中断控制器硬件 intc_setvectaddr(KEY_EINT2, isr_eint2); intc_setvectaddr(KEY_EINT3, isr_eint3); intc_setvectaddr(KEY_EINT16_19, isr_eint16171819); // 使能中断 intc_enable(KEY_EINT2); intc_enable(KEY_EINT3); intc_enable(KEY_EINT16_19); // 在这里加个心跳 while (1) { printf("A "); delay(10000); } return 0; }