十.ARM裸机学习之中断系统1(S5PV210按键外部中断及中断处理)

it2022-07-04  105

版权声明:本文为博主原创文章,允许转载请注明。谢谢! <a class="copy-right-url" href=" https://blog.csdn.net/wangweijundeqq/article/details/78632424"> https://blog.csdn.net/wangweijundeqq/article/details/78632424</a> </div> <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-3019150162.css"> <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-3019150162.css"> <div class="htmledit_views" id="content_views"> 上接: http://blog.csdn.net/wangweijundeqq/article/details/78634146 ARM裸机学习之中断系统1(S5PV210的中断系统详解) 六 、 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、 171819,设置为下降沿触发 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 最后,上面的理解在结合下图 一起理解,就显得很简单了。 中断处理的编程模型: (参考 http://blog.csdn.net/kiny_anderson/article/details/46884893 ) 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; }

最新回复(0)