键盘控制LED——S3C44B0X的IRQ编程
在《简单的S3C44B0X Bootloader》一文中我所描述的Bootloader没有任何异常处理,这显然是很不实用的,下面我将结合键盘控制LED的范例来在该Bootloader中实现对IRQ的处理。
首先明确一些基本信息。根据原理图,在我的板子里,1x4键盘的4个按键分别接S3C44B0X的EXTINT4~7这4个引脚,我们可以通过这些外部中断信号来处理按键的按下。对PGIO的细节不再赘述,参看代码应该没什么问题了。LED的细节请参见《第一个跑马灯程序》一文。
回想一下《简单的S3C44B0X Bootloader》中的head.s里,我预留了一个内核异常向量表,处理都是进入死循环。现在当然不能再那么搞了,我们需要给它填上实际内容,即ISR(Interrupt Service Routine,中断服务例程)。此外,由于ARM7体系结构的特性,我们需要在陷入ISR之前先初始化好IRQ模式(irq mode)以及该模式下的堆栈(ISR多带有堆栈操作,比如调用C函数,因此一般此步不可少)。改进后的代码如下:
head.s
- .equ KERNEL_STACK, 0x0c002000 @ 内核堆栈栈底(管理模式)
- .equ KERNEL_LIMIT, 0x0c001000 @ 内核堆栈栈限
- .equ IRQ_STACK, 0x0c002200 @ IRQ模式堆栈栈底
- .text
- vectors:
- @ 这里是物理地址0x0c000000(RAM)
- b undef_handler @ 内核异常向量0
- b swi_handler @ 内核异常向量1
- b pabort_handler @ 内核异常向量2
- b dabort_handler @ 内核异常向量3
- b irq_handler @ 内核异常向量4
- b fiq_handler @ 内核异常向量5
- .space 0x100 - 6 * 4
- @ 这里是物理地址0x0c000100
- start:
- @ 设置管理模式的堆栈
- ldr sp, = KERNEL_STACK @ 初始化svc模式的堆栈
- ldr sl, = KERNEL_LIMIT @ 设置svc模式的栈限
- @ 初始化IRQ模式
- mrs r0, cpsr @ 保存现场(管理模式)
- msr cpsr_all, #0x000000d2 @ 禁止IRQ和FIQ,ARM指令集状态,进入irq模式
- ldr sp, = IRQ_STACK @ 设置IRQ模式的堆栈
- msr cpsr_all, r0 @ 恢复现场(返回管理模式)
- @ 调用C入口函数
- bl entry @ 跳转到C程序中执行
- mov pc, #0 @ 软复位
- undef_handler:
- swi_handler:
- pabort_handler:
- dabort_handler:
- fiq_handler:
- b . @ 目前什么都不做
- irq_handler:
- stmfd sp!, {r0-r12, lr} @ 因为后面要调用C函数,因此需要保存当前状态到栈上
- bl on_irq @ 调用ISR的C函数
- ldmfd sp!, {r0-r12, lr} @ 从栈上恢复原状态
- subs pc, lr, #4 @ 从IRQ异常返回
如此一来整个ISR都可以放在on_irq这个C函数里处理了,真是方便啊。
来看应用部分,都在main.c里:
main.c
- #define PORT(addr) (*(volatile int *)(addr))
- #define PCONC PORT(0x01d20010)
- #define PDATC PORT(0x01d20014)
- #define PCONG PORT(0x01d20040)
- #define PDATG PORT(0x01d20044)
- #define EXTINT PORT(0x01d20050)
- #define EXTINTPND PORT(0x01d20054)
- #define INTCON PORT(0x01e00000)
- #define INTPND PORT(0x01e00004)
- #define INTMOD PORT(0x01e00008)
- #define INTMSK PORT(0x01e0000c)
- #define I_ISPR PORT(0x01e00020)
- #define I_ISPC PORT(0x01e00024)
- static void init(void)
- {
- PCONC = 0xaaaaaa56; /* PC1~3=output */
- PCONG = 0xff00; /* PG4~7=EINT4~7 */
- PDATC = 0x0000; /* 熄灭所有LED */
- #ifdef LOW_LEVEL
- EXTINT = 0x00000000; /* EINT4~7=低电平中断信号 */
- #else
- EXTINT = 0x22220000; /* EINT4~7=下降边缘触发 */
- #endif
- INTCON = 0x5; /* 非向量终端模式,IRQ可用 */
- INTMOD = 0x000000; /* EINT4~7=IRQ mode */
- INTMSK = 0x03dfffff; /* EINT4~7=IRQ模式 */
- }
- static void led(int num, int light)
- {
- if (light)
- PDATC |= 1 << num;
- else
- PDATC &= ~(1 << num);
- }
- static void key(void) /* 按键处理函数 */
- {
- static int stat[3]; /* 用来记录LED状态的静态数组 */
- int i;
- for (i = 0; i < 3; i++) {
- if (!(EXTINTPND & (1 << i)))
- continue; /* 该按键没有中断发生,不处理 */
- if (!stat[i]) { /* 如果对应LED原先是灭的 */
- led(i + 1, 1); /* 点亮该LED */
- stat[i] = 1;
- #ifdef LOW_LEVEL
- /* 将键盘对应外部中断切换为高电平触发方式 */
- EXTINT &= ~(0x6 << (16 + (i << 2)));
- EXTINT |= 0x1 << (16 + (i << 2));
- #endif
- } else { /* 如果对应LED原先是亮的 */
- led(i + 1, 0); /* 熄灭该LED */
- stat[i] = 0;
- #ifdef LOW_LEVEL
- /* 将键盘对应外部中断切换为低电平触发方式 */
- EXTINT &= ~(0x7 << (16 + (i << 2)));
- #endif
- }
- EXTINTPND |= 1 << i; /* 清除对应的外部中断挂起标志 */
- }
- }
- void entry(void)
- {
- init();
- while (1); /* 因为剩下的工作都是ISR的事情了,主程序陷入死循环 */
- }
- void on_irq(void) /* ISR程序 */
- {
- if (!(I_ISPR & 0x200000))
- return; /* 如果不是EXTINT4~7则不处理 */
- if (!(INTPND & 0x200000))
- return; /* 如果没有EXTINT4~7的中断挂起标志则不处理 */
- key(); /* 按键处理 */
- I_ISPC |= 0x200000; /* 清除EXTINT4~7的中断挂起标志 */
- }
最后可别忘了要把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进行编程处理了。
浙公网安备 33010602011771号