键盘控制LED——S3C44B0X的IRQ编程

《简单的S3C44B0X Bootloader》一文中我所描述的Bootloader没有任何异常处理,这显然是很不实用的,下面我将结合键盘控制LED的范例来在该Bootloader中实现对IRQ的处理。

     首先明确一些基本信息。根据原理图,在我的板子里,1x4键盘的4个按键分别接S3C44B0X的EXTINT4~7这4个引脚,我们可以通过这些外部中断信号来处理按键的按下。对PGIO的细节不再赘述,参看代码应该没什么问题了。LED的细节请参见《第一个跑马灯程序》一文。

     回想一下《简单的S3C44B0X Bootloader》中的head.s里,我预留了一个内核异常向量表,处理都是进入死循环。现在当然不能再那么搞了,我们需要给它填上实际内容,即ISRInterrupt Service Routine,中断服务例程)。此外,由于ARM7体系结构的特性,我们需要在陷入ISR之前先初始化好IRQ模式(irq mode)以及该模式下的堆栈(ISR多带有堆栈操作,比如调用C函数,因此一般此步不可少)。改进后的代码如下:

head.s

  1. .equ    KERNEL_STACK,   0x0c002000      @ 内核堆栈栈底(管理模式)  
  2. .equ    KERNEL_LIMIT,   0x0c001000      @ 内核堆栈栈限  
  3. .equ    IRQ_STACK,      0x0c002200      @ IRQ模式堆栈栈底  
  4.   
  5. .text  
  6.   
  7. vectors:  
  8. @ 这里是物理地址0x0c000000(RAM)  
  9.         b       undef_handler           @ 内核异常向量0  
  10.         b       swi_handler             @ 内核异常向量1  
  11.         b       pabort_handler          @ 内核异常向量2  
  12.         b       dabort_handler          @ 内核异常向量3  
  13.         b       irq_handler             @ 内核异常向量4  
  14.         b       fiq_handler             @ 内核异常向量5  
  15.   
  16. .space  0x100 - 6 * 4  
  17.   
  18. @ 这里是物理地址0x0c000100  
  19. start:  
  20.         @ 设置管理模式的堆栈  
  21.         ldr     sp, = KERNEL_STACK      @ 初始化svc模式的堆栈  
  22.         ldr     sl, = KERNEL_LIMIT      @ 设置svc模式的栈限  
  23.   
  24.         @ 初始化IRQ模式  
  25.         mrs     r0, cpsr                @ 保存现场(管理模式)  
  26.         msr     cpsr_all, #0x000000d2   @ 禁止IRQ和FIQ,ARM指令集状态,进入irq模式  
  27.         ldr     sp, = IRQ_STACK         @ 设置IRQ模式的堆栈  
  28.         msr     cpsr_all, r0            @ 恢复现场(返回管理模式)  
  29.   
  30.         @ 调用C入口函数  
  31.         bl      entry                   @ 跳转到C程序中执行  
  32.         mov     pc, #0                  @ 软复位  
  33.   
  34. undef_handler:  
  35. swi_handler:  
  36. pabort_handler:  
  37. dabort_handler:  
  38. fiq_handler:  
  39.         b       .                       @ 目前什么都不做  
  40.   
  41. irq_handler:  
  42.         stmfd   sp!, {r0-r12, lr}       @ 因为后面要调用C函数,因此需要保存当前状态到栈上  
  43.         bl      on_irq                  @ 调用ISR的C函数  
  44.         ldmfd   sp!, {r0-r12, lr}       @ 从栈上恢复原状态  
  45.         subs    pc, lr, #4              @ 从IRQ异常返回  

      如此一来整个ISR都可以放在on_irq这个C函数里处理了,真是方便啊。

      来看应用部分,都在main.c里:

main.c

  1. #define PORT(addr)      (*(volatile int *)(addr))  
  2.  
  3. #define PCONC           PORT(0x01d20010)  
  4. #define PDATC           PORT(0x01d20014)  
  5. #define PCONG           PORT(0x01d20040)  
  6. #define PDATG           PORT(0x01d20044)  
  7.  
  8. #define EXTINT          PORT(0x01d20050)  
  9. #define EXTINTPND       PORT(0x01d20054)  
  10.  
  11. #define INTCON          PORT(0x01e00000)  
  12. #define INTPND          PORT(0x01e00004)  
  13. #define INTMOD          PORT(0x01e00008)  
  14. #define INTMSK          PORT(0x01e0000c)  
  15. #define I_ISPR          PORT(0x01e00020)  
  16. #define I_ISPC          PORT(0x01e00024)  
  17.   
  18. static void init(void)  
  19. {  
  20.         PCONC = 0xaaaaaa56;     /* PC1~3=output */  
  21.         PCONG = 0xff00;         /* PG4~7=EINT4~7 */  
  22.         PDATC = 0x0000;         /* 熄灭所有LED */  
  23. #ifdef LOW_LEVEL  
  24.         EXTINT = 0x00000000;    /* EINT4~7=低电平中断信号 */  
  25. #else  
  26.         EXTINT = 0x22220000;    /* EINT4~7=下降边缘触发 */  
  27. #endif  
  28.         INTCON = 0x5;           /* 非向量终端模式,IRQ可用 */  
  29.         INTMOD = 0x000000;      /* EINT4~7=IRQ mode */  
  30.         INTMSK = 0x03dfffff;    /* EINT4~7=IRQ模式 */  
  31. }  
  32.   
  33. static void led(int num, int light)  
  34. {  
  35.         if (light)  
  36.                 PDATC |= 1 << num;  
  37.         else  
  38.                 PDATC &= ~(1 << num);  
  39. }  
  40.   
  41. static void key(void)           /* 按键处理函数 */  
  42. {  
  43.         static int stat[3];     /* 用来记录LED状态的静态数组 */  
  44.         int i;  
  45.   
  46.         for (i = 0; i < 3; i++) {  
  47.   
  48.                 if (!(EXTINTPND & (1 << i)))  
  49.                         continue;       /* 该按键没有中断发生,不处理 */  
  50.   
  51.                 if (!stat[i]) { /* 如果对应LED原先是灭的 */  
  52.                         led(i + 1, 1);  /* 点亮该LED */  
  53.                         stat[i] = 1;  
  54. #ifdef LOW_LEVEL  
  55.                         /* 将键盘对应外部中断切换为高电平触发方式 */  
  56.                         EXTINT &= ~(0x6 << (16 + (i << 2)));  
  57.                         EXTINT |= 0x1 << (16 + (i << 2));  
  58. #endif  
  59.                 } else {        /* 如果对应LED原先是亮的 */  
  60.                         led(i + 1, 0);  /* 熄灭该LED */  
  61.                         stat[i] = 0;  
  62. #ifdef LOW_LEVEL  
  63.                         /* 将键盘对应外部中断切换为低电平触发方式 */  
  64.                         EXTINT &= ~(0x7 << (16 + (i << 2)));  
  65. #endif  
  66.                 }  
  67.   
  68.                 EXTINTPND |= 1 << i;    /* 清除对应的外部中断挂起标志 */  
  69.         }  
  70. }  
  71.   
  72. void entry(void)  
  73. {  
  74.         init();  
  75.         while (1);              /* 因为剩下的工作都是ISR的事情了,主程序陷入死循环 */  
  76. }  
  77.   
  78. void on_irq(void)               /* ISR程序 */  
  79. {  
  80.         if (!(I_ISPR & 0x200000))  
  81.                 return;         /* 如果不是EXTINT4~7则不处理 */  
  82.   
  83.         if (!(INTPND & 0x200000))  
  84.                 return;         /* 如果没有EXTINT4~7的中断挂起标志则不处理 */  
  85.   
  86.         key();                  /* 按键处理 */  
  87.         I_ISPC |= 0x200000;     /* 清除EXTINT4~7的中断挂起标志 */  
  88. }  

     最后可别忘了要把svc模式下的CPSR控制位中的I位(IRQ中断控制位)打开,具体在boot.s的reset子程序开头:
    
          reset:
             @ initialize s3c44b0x
             mov     r0, #0x00000053                 @ enable IRQ, disable FIQ, ARM state, svc mode
             msr     cpsr_all, r0                    @ set CPSR

             ...

     没什么特别好说的,注释都写了。注意gcc编译选项定义了LOW_LEVEL则采用电平触发方式,否则采用下降边缘触发。按键0~2对应LED1~3。在电平触发模式下,按下则灯亮,松开则灯灭;边缘触发模式下,按一下灯亮,再按一下则灯灭。

     有了这个雏形,就可以对各种IRQ进行编程处理了。

posted on 2013-03-16 17:04  AI_JJ  阅读(190)  评论(0)    收藏  举报

导航