注:以下内容学习于韦东山老师arm裸机第一期视频教程
一.中断的处理
1.1 中断初始化
1.1.1 设置中断源,让他能够发出中断信号
a.以按键中断为例,按键原理图如下,4个按键分别接到EINT0,EINT2,EINT11,ENIT19对应GPF0,GPF2,GPG3,GPG11
b.配置GPFCON,GPGCON使得GPF0,GPF2,GPG3,GPG11被配置为外部中断引脚
c.配置EXTINTX寄存器设置中断触发方式为双边沿触发
其中EXTINT0寄存器对应EINT0-EINT7
EXTINT1寄存器对应EINT8-EINT15
EXTINT2寄存器对应EINT16-EINT23
d.配置EINTMASK寄存器允许EINT0,EINT2,EINT11,ENIT19向中断控制器发生中断
其中EINT0-EINT3的中断信号不需要配置,可以直接到达中断控制器
相关码如下:
/* 初始化按键, 设为中断源 */ void key_eint_init(void) { /* 配置GPIO为中断引脚 */ GPFCON &= ~((3<<0) | (3<<4)); GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */ GPGCON &= ~((3<<6) | (3<<22)); GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */ /* 设置中断触发方式: 双边沿触发 */ EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */ EXTINT1 |= (7<<12); /* S4 */ EXTINT2 |= (7<<12); /* S5 */ /* 设置EINTMASK使能eint11,19 */ EINTMASK &= ~((1<<11) | (1<<19)); }
1.1.2 设置中断控制器,让他可以向CPU发出中断
根据下图进行相关的配置
a.多个中断产生经过优先级只会有一个通知CPU,可以读取INTPND寄存器来判断是哪一个中断产生了
bit0-eint0 1-中断产生,需要进行清除
bit2-eint2
bit5-eint8-23
b.Priority表示中断优先级,暂时不设置
c.MODE用来设置某个中断为快中断或者普通的中断,我们使用默认值,发出IRQ信号
d.需要设置MASK寄存器,某一位被设置为1,会屏蔽掉这个中断
bit0-eint0
bit2-eint2
bit5-eint8-23
e.对于中断源,有的中断源包括子中断源,例如串口中断包括接受中断,发送中断和出错的中断
假设串口0产生了TX0子中断SUBSRCPND寄存器的某一位就会被置1,然后进行过滤,通过SUBMASK进行处理,
SUBMASK寄存器每一位对应一子中断源,如果我们将这里面的某一位置1,就会过滤掉这一位对应的中断
f.如果是没有子中断源的中断,会直接进入SRCPND,用来显示某一个中断是否发生了,执行完成之后需要清除SRCPND寄存器
外部中断可以没有子中断,会直接到达SRCPND,对于外部中断其对应关系如下
bit0-eint0 1-中断产生,需要清除
bit2-eint2
bit5-eint8-23
某一位等于1时,表示中断发生了,对于eint8-23需要再次读取EINTPEND寄存器判断哪个中断发生了
g.INTOFFSET寄存器,显示哪一个中断正在等待处理,即用来显示INTPND中哪一位被设置为1,可以读取INTOFFSET寄存器或者INTPND来判断中断源,对应关系如下
这个寄存器不需要清除,在清除SRCPND和INTPND时会自动清除
相关代码如下:
/* SRCPND 用来显示哪个中断产生了, 需要清除对应位 * bit0-eint0 * bit2-eint2 * bit5-eint8_23 */ /* INTMSK 用来屏蔽中断, 1-masked * bit0-eint0 * bit2-eint2 * bit5-eint8_23 */ /* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位 * bit0-eint0 * bit2-eint2 * bit5-eint8_23 */ /* INTOFFSET : 用来显示INTPND中哪一位被设置为1 */ /* 初始化中断控制器 */ void interrupt_init(void) { INTMSK &= ~((1<<0) | (1<<2) | (1<<5)); }
1.1.3 设置CPU,CPSR中的I位是中断的总开关,需要清0才能打开中断
相关代码如下:
mrs r0, cpsr /* 读出cpsr */ bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */ bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */ msr cpsr, r0
1.2 处理中断
1.2.1 分辨中断源
读取INTOFFSET寄存器,表示INTPND中的值哪一位被设置为1
0-eint0
2-eint2
5-eint8-23
1.2.2 调用对应的处理函数,并且清除中断源
清中断时想EINTPND写入1清除对应的中断(读的时候1表示中断发生,写入1清除中断,很奇怪,2440手册上是这么描述的,见下图)
相关代码如下:
void key_eint_irq(int irq) { unsigned int val = EINTPEND; unsigned int val1 = GPFDAT; unsigned int val2 = GPGDAT; if (irq == 0) /* eint0 : s2 控制 D12 */ { ... } else if (irq == 2) /* eint2 : s3 控制 D11 */ { ... } else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */ { if (val & (1<<11)) /* eint11 */ { ... } else if (val & (1<<19)) /* eint19 */ { ... } } EINTPEND = val; }
1.3 处理完毕后要清中断
清除中断时从前向后清,即先清SRCPND,再清INTPND。否则前面的还会影响后面
相关代码如下:
void handle_irq_c(void) { /* 分辨中断源 */ int bit = INTOFFSET; /* 调用对应的处理函数 */ if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */ { key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */ } /* 清中断 : 从源头开始清 */ SRCPND = (1<<bit); INTPND = (1<<bit); }
interrupt.c文件代码如下
#include "s3c2440_soc.h" /* SRCPND 用来显示哪个中断产生了, 需要清除对应位 * bit0-eint0 * bit2-eint2 * bit5-eint8_23 */ /* INTMSK 用来屏蔽中断, 1-masked * bit0-eint0 * bit2-eint2 * bit5-eint8_23 */ /* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位 * bit0-eint0 * bit2-eint2 * bit5-eint8_23 */ /* INTOFFSET : 用来显示INTPND中哪一位被设置为1 */ /* 初始化中断控制器 */ void interrupt_init(void) { INTMSK &= ~((1<<0) | (1<<2) | (1<<5)); } /* 初始化按键, 设为中断源 */ void key_eint_init(void) { /* 配置GPIO为中断引脚 */ GPFCON &= ~((3<<0) | (3<<4)); GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */ GPGCON &= ~((3<<6) | (3<<22)); GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */ /* 设置中断触发方式: 双边沿触发 */ EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */ EXTINT1 |= (7<<12); /* S4 */ EXTINT2 |= (7<<12); /* S5 */ /* 设置EINTMASK使能eint11,19 */ EINTMASK &= ~((1<<11) | (1<<19)); } /* 读EINTPEND分辨率哪个EINT产生(eint4~23) * 清除中断时, 写EINTPEND的相应位 */ void key_eint_irq(int irq) { unsigned int val = EINTPEND; unsigned int val1 = GPFDAT; unsigned int val2 = GPGDAT; if (irq == 0) /* eint0 : s2 控制 D12 */ { if (val1 & (1<<0)) /* s2 --> gpf6 */ { /* 松开 */ GPFDAT |= (1<<6); } else { /* 按下 */ GPFDAT &= ~(1<<6); } } else if (irq == 2) /* eint2 : s3 控制 D11 */ { if (val1 & (1<<2)) /* s3 --> gpf5 */ { /* 松开 */ GPFDAT |= (1<<5); } else { /* 按下 */ GPFDAT &= ~(1<<5); } } else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */ { if (val & (1<<11)) /* eint11 */ { if (val2 & (1<<3)) /* s4 --> gpf4 */ { /* 松开 */ GPFDAT |= (1<<4); } else { /* 按下 */ GPFDAT &= ~(1<<4); } } else if (val & (1<<19)) /* eint19 */ { if (val2 & (1<<11)) { /* 松开 */ /* 熄灭所有LED */ GPFDAT |= ((1<<4) | (1<<5) | (1<<6)); } else { /* 按下: 点亮所有LED */ GPFDAT &= ~((1<<4) | (1<<5) | (1<<6)); } } } EINTPEND = val; } void handle_irq_c(void) { /* 分辨中断源 */ int bit = INTOFFSET; /* 调用对应的处理函数 */ if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */ { key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */ } /* 清中断 : 从源头开始清 */ SRCPND = (1<<bit); INTPND = (1<<bit); }
二.定时器(参考2440第10章)
2.1 工作原理如下图
2.1.1 每次来一个时钟信号,TCNTn减1
2.1.2 damh TCNTn == TCMPn时,不会产生中断,可以让对应的PWM引脚翻转
2.1.3 TCNTn继续减1,当TCNTn等于0时可以产生中断,PWM引脚再次翻转
TCNTn和TCMPn的初值来自于TCNTBn和TCMPn寄存器
2.1.4 TCNTn等于0时,可以自动加载初值
2.2 定时器的使用
2.2.1 设置时钟
2.2.2 设置初值
2.2.3 加载初值,启动Timer
2.2.4 设置为自动加载
2.2.5 设置中断
2.3 代码示例
/* timer.c */ #include "s3c2440_soc.h" void TimerFunc(int irq) { static int cnt = 3; #if 0 if (GPFDAT & (1 << 4)) GPFDAT &= ~(1 << 4); else GPFDAT |= (1 << 4); #endif cnt++; GPFDAT &= ~(1 << cnt); if (cnt == 7) { cnt = 3; GPFDAT |= (0x7 << 4); } } void TimerInit(void) { /* 1.Set clock */ /* * Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value} * = 50000000 / (99 + 1) / 16 * = 31250 */ TCFG0 = 99; /* Prescaler 0 = 99 */ TCFG1 |= 0x3; /* divider value = 16 */ /* 2.Set TCNTn init val */ TCNTB0 = 15625; /* 0.5s */ /* 3.Load init val*/ TCON |= (1 << 1); /* 4.Set Auto update, and start timer */ TCON &= ~(1 << 1); TCON |= (1 << 0) | (1 << 3); /* timer0的中断号是10 */ register_irq(10, TimerFunc); } /* interrupt.c */ #include "s3c2440_soc.h" #include "interrupt.h" void handle_irq(void) { /* 1.Resolved interrupt source */ int bit = INTOFFSET; /* 2.handle irq */ irq_arr[bit](bit); /* 3.clear irq */ SRCPND = (1 << bit); INTPND = (1 << bit); } /* 以中断号为下标放到irq_arr指针数组中去 */ void register_irq(int irq, irq_func func) { irq_arr[irq] = func; INTMSK &= ~(1 << irq); }