1. 要做什么MINI2440上有6个按键我们要做一个测试程序当按下按键的时候串口打印对应的按键按下的消息。要包含中断的知识就要用中断的方法去实现为了使用中断实现我们需要搭建一个完整的中断流程框架中断异常的现场保护、返回地址计算、中断分发、基础操作API实现等2. 具体思路测试程序和中断框架的两条流程设计代码框图3. 怎么实现3.1 框架层面3.1.1 irq_handler.c创建common/irq_handler.c文件实现irq框架初始化/// brief 中断控制器初始化 /// 设置IRQ模式的SP, 禁用IRQ/FIQ void irq_handler_init() { unsigned long cpsr; __asm__ volatile ( // 读取cpsr mrs %0, cpsr\n\t // 禁用IRQ/FIQ, 切换至IRQ模式 msr cpsr_c, #0b11010010\n\t // 设置IRQ模式的SP ldr sp, 0x40001000\n\t // 恢复原来模式和原始中断状态 msr cpsr, %0\n\t : r (cpsr) : : sp, cc, memory ); // 清除可能的中断挂起 SUBSRCPND SUBSRCPND; SRCPND SRCPND; INTPND INTPND; // 屏蔽所有中断 INTMSK 0xFFFFFFFF; INTSUBMSK 0xFFFFFFFF; }实现irq处理函数作用是中断分发// 默认中断处理函数 (弱实现用户可重写) void __attribute__((weak)) irq_eint0_handler(void) { } void __attribute__((weak)) irq_eint1_handler(void) { } void __attribute__((weak)) irq_eint2_handler(void) { } void __attribute__((weak)) irq_eint3_handler(void) { } void __attribute__((weak)) irq_eint4_7_handler(void) { } void __attribute__((weak)) irq_eint8_23_handler(void) { } // ...略... // 中断处理函数表 static irq_handler_t irq_handlers[32] { [IRQ_EINT0] irq_eint0_handler, [IRQ_EINT1] irq_eint1_handler, [IRQ_EINT2] irq_eint2_handler, [IRQ_EINT3] irq_eint3_handler, [IRQ_EINT4_7] irq_eint4_7_handler, [IRQ_EINT8_23] irq_eint8_23_handler, // ...略... }; /// brief IRQ中断处理函数 /// 中断分发 void __irq_handler() { // 获取中断源偏移 uint32_t offset INTOFFSET; if (offset 32 || NULL irq_handlers[offset]) return ; // 清除对应的中断标志位 SRCPND (1 offset); INTPND (1 offset); // 分发 irq_handlers[offset](); }我们使用了weak函数技巧实现了所有中断处理函数的存根然后应用层根据需要覆盖实现对应的处理函数~还要实现一些公共的API/// brief CPU中断使能控制 void irq_enable(bool enable) { unsigned long old_cpsr disable_irq_save(); if (!enable) old_cpsr | (1 7); else old_cpsr ~(1 7); restore_irq_mask(old_cpsr); } /// brief 恢复IRQ Mask void restore_irq_mask(unsigned long old_cpsr) { __asm__ volatile ( msr cpsr, %0\n\t : : r (old_cpsr) : cc, memory ); } /// brief 禁用IRQ并保存当前cpsr unsigned long disable_irq_save() { unsigned long old_cpsr; __asm__ volatile ( /* 读取cpsr */ mrs %0, cpsr\n\t /* 禁止IRQ */ orr r1, %0, #(1 7)\n\t msr cpsr_c, r1\n\t : r (old_cpsr) : // 这里损坏部声明了r1, 表示我弄脏了r1, 这样编译器会将r1自动压栈保护 : cc, memory, r1 ); return old_cpsr; } /// brief 中断源使能控制 void irq_src_enable(irq_src_t src, bool enable) { unsigned long _save disable_irq_save(); SRCPND (1 src); INTPND (1 src); if (enable) { INTMSK ~(1 src); } else { INTMSK | (1 src); } restore_irq_mask(_save); }我们用了大量的的内联汇编技巧可以参考章节007.GNU C内联汇编杂谈|千篇笔记实现嵌入式全栈/裸机篇3.1.2 start.s回到common/start.s进行修改注释掉原来的irq: b ., 重新实现irq段并且设置cpsr确保处于SVC模式然后调整SVC模式下的栈顶为IRQ模式预留512字节栈空间irq: b . fiq: b . irq现场保护和恢复 irq: 修正返回地址 ARM9流水线取指 (Fetch) - 译码 (Decode) - 执行 (Execute) - 访存 (Memory) - 回写 (Write) LR(异常触发时PC) 触发点PC 8 那么目标返回点PC 触发点PC 4 (LR - 8) 4 LR - 4 sub lr, lr, #4 压栈保护通用寄存器和lr stmdb sp!, {r0-r12, lr} 跳转至C分发函数 bl __irq_handler 恢复现场退出中断 ^表示将SPSRCPSR, 实现模式切换 ldmia sp!, {r0-r12, pc}^ .section .text .global _start _start: 关闭看门狗 ldr r0, WTCON ldr r1, 0 str r1, [r0] 关中断且确保处于SVC模式 msr cpsr_c, 0b11010011 设置栈顶(给IRQ SP预留512空间) ldr sp, 0x40000E003.2 应用方面看原理图为了与常规轮询方式对比我们选择两路按键使用轮询的方式四路按键使用外部中断的方式/** * file key.c * brief 按键测试 * 部分采用轮询, 部分采用中断 * pins: * KEY1(轮询): EINT8/GPG0 * KEY2(轮询): EINT11/GPG3 * KEY3(中断): EINT13/GPG5 * KEY4(中断): EINT14/GPG6 * KEY5(中断): EINT15/GPG7 * KEY6(中断): EINT19/GPG11 */新建key/main.c,实现main函数用于配置和逻辑处理int main() { // 配置为INPUT GPIO_SET_MODE(GPIOGCON, 0, 0b00, 2); GPIO_SET_MODE(GPIOGCON, 3, 0b00, 2); // 配置为EINT GPIO_SET_MODE(GPIOGCON, 5, 0b10, 2); GPIO_SET_MODE(GPIOGCON, 6, 0b10, 2); GPIO_SET_MODE(GPIOGCON, 7, 0b10, 2); GPIO_SET_MODE(GPIOGCON, 11, 0b10, 2); // 上拉 GPIO_SET_PULLUP(GPIOGUP, 0, 0); GPIO_SET_PULLUP(GPIOGUP, 3, 0); GPIO_SET_PULLUP(GPIOGUP, 5, 0); GPIO_SET_PULLUP(GPIOGUP, 6, 0); GPIO_SET_PULLUP(GPIOGUP, 7, 0); GPIO_SET_PULLUP(GPIOGUP, 11, 0); // 外部中断配置 EINT13/14/15/19 // 下降沿触发 EXTINT1 ~((7 20) | (7 24) | (7 28)); EXTINT2 ~(7 12); EXTINT1 | (0b010 20) | (0b010 24) | (0b010 28); EXTINT2 | (0b010 12); // 外部中断屏蔽 // 屏蔽其它所有外部中断 EINTMASK ~((1 13) | (1 14) | (1 15) | (1 19)); // 使能EINT8_23中断 irq_src_enable(IRQ_EINT8_23, true); // 使能CPU中断 irq_enable(true); bool _key1_st_old false; bool _key2_st_old false; int count_for_key1 0; int count_for_key2 0; while (1) { // 10ms周期 easy_delay_ms(10); count_ref ; if (event_key3) { uart0_printf(KEY3: pressed\n); event_key3 false; } if (event_key4) { uart0_printf(KEY4: pressed\n); event_key4 false; } if (event_key5) { uart0_printf(KEY5: pressed\n); event_key5 false; } if (event_key6) { uart0_printf(KEY6: pressed\n); event_key6 false; } // 轮询检测KEY1/KEY2 // 100ms去抖 bool key1_st GPIO_GET_VALUE(GPIOGDAT, 0) ? true : false; bool key2_st GPIO_GET_VALUE(GPIOGDAT, 3) ? true : false; if (key1_st ! _key1_st_old) { if (count_for_key1 DEBOUNCE_COUNT) { if (!(_key1_st_old key1_st)) uart0_printf(KEY1: pressed\n); count_for_key1 0; } } else { count_for_key1 0; } if (key2_st ! _key2_st_old) { if (count_for_key2 DEBOUNCE_COUNT) { if (!(_key2_st_old key2_st)) uart0_printf(KEY2: pressed\n); count_for_key2 0; } } else { count_for_key2 0; } } return 0; }然后覆盖实现中断源处理函数就是通过外部中断挂起寄存器来判断是哪里按键触发///brief EINT8_23 中断处理函数 void irq_eint8_23_handler(void) { unsigned long _pend EINTPEND; EINTPEND _pend; // 清除中断标志 if (_pend (1 13)) { if ((count_ref - count_for_key3) DEBOUNCE_COUNT_EINT) { event_key3 true; count_for_key3 count_ref; } } if (_pend (1 14)) { if ((count_ref - count_for_key4) DEBOUNCE_COUNT_EINT) { event_key4 true; count_for_key4 count_ref; } } if (_pend (1 15)) { if ((count_ref - count_for_key5) DEBOUNCE_COUNT_EINT) { event_key5 true; count_for_key5 count_ref; } } if (_pend (1 19)) { if ((count_ref - count_for_key6) DEBOUNCE_COUNT_EINT) { event_key6 true; count_for_key6 count_ref; } } }完整的代码见当前TAG9-isr-key4. 效果编译make key生成key.bin烧录运行依次按下所有按键