中断源按照硬件位置分为外部中断源和内部中断源,外部中断源和内部中断源又包含子外部中断源和子内部中断源,如图所示(画了一整天)。
1. 子内部中断源的产生
以UART0接收数据产生INT_RXD0中断为例,INT_RXD0产生后进入SUBSRCPND子中断源暂存寄存器,设置INT_RXD0对应的中断位,中断信号经过INTSUBMSK子中断屏蔽寄存器,如果INT_RXD0信号对应位没有被置位(屏蔽掉),中断信号继续向前传递,经过子内部中断源聚合器,将INT_RXD0聚合成对应的中断源信号INT_UART0,设置SRCPND中断源暂存寄存器里INT_UART0位,经过INTMSK中断屏蔽寄存器,如果INT_UART0信号没有被屏蔽掉,中断信号进入INTMOD中断模式寄存器判断是否为快速中断,如果被编程为快速中断,直接打断ARM内核,进入中断处理,如果中断信号为一般中断,进入中断优先级仲裁器进入优先级仲裁,如果INT_UART0信号为最高优先级或只有INT_UART0中断信号产生,则该中断信号被记录到INTPND最高优先级中断暂存寄存器,同时设置INTOFFSET的值为中断号28,最终将中断信号打断ARM内核进行中断处理。如果同时产生多个中断且INT_UART0不是最高优先级,则该中断信号不会被处理,等最高优先级信号处理完后,再次进行优先级仲裁,也就是说中断信号不消失,一直保存在SRCPND里,只到被处理为止。
2. 内部中断源的产生
该过程在子内部中断处理过程中已经包含,中断信号产生后直接进入SRCPND里,然后经历上述子内部中断后期处理过程。
3. 子外部中断的产生
外部中断源共有24个,其中EINT0~EINT3为外部中断源,EINT4_7,EINT8_23为复合中断源,他们包含有子外部中断源。
由于外部硬件直接挂接到I/O Ports(详见S3C2440A硬件手册第9章)上的,我们要想让外设硬件中断得到处理,要先从EINT0~EINT23里选择中断信号,我们以EINT11为例,介绍子外部中断处理过程。
通常CPU内部引出引脚都是复用的,也就是说一根CPU引脚可以有多种功能,可以设置其为输入信号线,输出信号线或中断信号线,要想让硬件产生中断,首先要对可以产生中断的引脚进行编程,设置该引脚为中断信号线。EINT11中断信号对应CPU引脚为GPG3,通过设置GPGCON[7:6] = 0b10,可以设置该引脚为中断信号线。
表3-14 GPGCON寄存器
设置了CPU管脚为中断信号线之后,还要通过设置EXTINT0寄存器来指定中断信号的触发方式:高电平触发,低电平触发,电平上升沿,下除沿,双沿触发。
图3-9 电平信号触发示意图
由于按键按下时让它产生中断,也就是从高电平变为低电平时产生(上节按键中断原理),因此我们设置EINT11中断信号的触发方式为下降沿触发,EXTINT1[14:12] = 0b01x
表3-15 EXTINT1寄存器
设置完触发方式之后,当外设中断信号线上的电平达到触发条件时,通过外部中断产生器产生中断信号,然后将子外部中断暂存寄存器EINTPND中对应的EINT11位置1,中断信号再进入EINTMSK子外部中断屏蔽寄存器,如果EINT11中断源信号没有被屏蔽,则EINT11中断信号进入子外部中断聚合器,复合成EINT8_23中断信号,然后再经历与前面子内部中断信号一样的处理机制。
(1)EINTPEND外部中断暂存寄存器
表3-16外部中断暂存寄存器(EINTPEND)
寄存器名地址是否读写描述复位默认值
EINTPEND0x560000A8R/W外部中断信号暂存寄存器
0:没有中断请求信号
1:中断请求信号产生0x0000000
EINTPEND位描述初始值
EINT23[23]0 = 未产生中断 1 = 产生中断0
…………
EINT4[4]0 = 未产生中断 1 = 产生中断0
保留位[3:0]无0000
(2)EINTMASK外部中断屏蔽寄存器
表3-17外部中断屏蔽寄存器(EINTMASK)
寄存器名地址是否读写描述复位默认值
EINTMASK0x560000A4R/W外部中断信号屏蔽寄存器
0:未屏蔽,中断可用
1:屏蔽中断信号0x000FFFFF
EINTMASK位描述初始值
EINT23[23]0 = 未屏蔽1 = 屏蔽中断1
…………
EINT4[4]0 = 未屏蔽1 = 屏蔽中断1
保留位[3:0]无1111
4. 外部中断源的产生
外部中断产生过程读者可以根据上面中断图自行分析。
按键控制LED灯实验
本实验分三个版本,分别针对三种开发板:友善之臂QQ2440,友善之臂MINI2440,天嵌TQ2440。每种开发板对应工程在:“sys_irq_开发板名”目录下。下面实验内容为针对MINI2440开发板。
head.s:
主要实现安装异常向量表,处理复位异常,初始化必要硬件,中断入口处理等功能。
;**********************************************************************
; 系统中断实验(MINI2440)
;**********************************************************************
GPBCON EQU 0x56000010
GPBDAT EQU 0x56000014
EXPORT SYS_IRQ
AREA SYS_IRQ,CODE,READONLY
ENTRY
;**********************************************************************
; 设置中断向量,除Reset和HandleIRQ外,其它异常都没有使用(如果不幸发生了,
; 将导致死机)
;**********************************************************************
; 0x00: 复位Reset异常
b Reset
; 0x04: 未定义异常(未处理)
HandleUndef
b HandleUndef
; 0x08: 软件中断异常(未处理)
HandleSWI
b HandleSWI
; 0x0c: 指令预取异常(未处理)
HandlePrefetchAbt
b HandlePrefetchAbt
; 0x10: 数据访问中止异常(未处理)
HandleDataAbt
b HandleDataAbt
; 0x14: 未使用异常(未处理)
HandleNotUsed
b HandleNotUsed
; 0x18: 一般中断异常,跳往HandleIRQ
b HandleIRQ
; 0x1c: 快速中断异常(未处理)
HandleFIQ
b HandleFIQ
Reset ; 复位异常处理入口
; 关闭看门狗
ldr r0, = 0x53000000
mov r1, #0
str r1, [r0]
bl initmem
ldr sp, =0x32000000 ; 设置管理模式栈指针
IMPORT uart_init
bl uart_init ; UART串口初始化
IMPORT irq_init
bl irq_init ; 系统中断初始化
IMPORT key_init
bl key_init ; 按键初始化
IMPORT led_init
bl led_init ; LED灯初始化
msr cpsr_cxsf, #0xd2 ; 切换到中断模式下
ldr sp, =0x31000000 ; 设置中断模式栈指针
msr cpsr_cxsf, #0x13 ; 返回管理模式
ldr lr, =halt_loop ; 设置管理模式下返回地址
IMPORT main
ldr pc, =main ; 跳入主函数main里执行
;***********************************************************************
; 中断处理
;***********************************************************************
HandleIRQ
sub lr,lr,#4 ; 修正返回地址
stmdb sp!,{r0-r12,lr} ; 保存程序执行现场
ldr lr,=int_return ; 设置中断处理程序返回地址
IMPORT handle_irq
ldr pc,=handle_irq ; 跳入中断处理程序
int_return ; 中断处理返回标签
ldmia sp!,{r0-r12,pc}^ 恢复程序执行现场,返回继续执行
halt_loop
b halt_loop
initmem
ldr r0, =0x48000000
ldr r1, =0x48000034
;ldr r2, =memdata
adr r2, memdata
initmemloop
ldr r3, [r2], #4
str r3, [r0], #4
teq r0, r1
bne initmemloop
mov pc,lr
memdata
DCD 0x22000000 ;BWSCON
DCD 0x00000700 ;BANKCON0
DCD 0x00000700 ;BANKCON1
DCD 0x00000700 ;BANKCON2
DCD 0x00000700 ;BANKCON3
DCD 0x00000700 ;BANKCON4
DCD 0x00000700 ;BANKCON5
DCD 0x00018005 ;BANKCON6
DCD 0x00018005 ;BANKCON7
DCD 0x008e07a3 ;REFRESH
DCD 0x000000b1 ;BANKSIZE
DCD 0x00000030 ;MRSRB6
DCD 0x00000030 ;MRSRB7
END ; 代码结束
该程序主要设置异常向量表,除了Reset异常和中断处理被处理以外,其它异常都未被处理,如果发生时,会产生死循环,Reset异常里主要实现了硬件的基本初始化,如:按键,LED灯等,设置栈指针,用于执行C程序,最后跳入C程序的main函数。在中断处理异常处理中首先修正返回地址,保存用户执行现场,跳入到中断处理例程中执行。
sys_init.c:
硬件初始化文件,里面包含LED,KEY的初始化函数。
#include “register.h”
#include “comm_fun.h”
#define TXD0READY (1《《2) //发送数据状态OK
#define RXD0READY (1) //接收数据状态OK
/* UART串口初始化 */
void uart_init()
{
GPHCON |= 0xa0; //GPH2,GPH3 used as TXD0,RXD0
GPHUP = 0x0; //GPH2,GPH3内部上拉
ULCON0 = 0x03; //8N1
UCON0 = 0x05; //查询方式为轮询或中断;时钟选择为PCLK
UFCON0 = 0x00; //不使用FIFO
UMCON0 = 0x00; //不使用流控
UBRDIV0 = 12; //波特率为57600,PCLK=12Mhz
}
/* UART串口单个字符打印函数 */
extern void putc(unsigned char c)
{
while( ! (UTRSTAT0 & TXD0READY) );
UTXH0 = c;
}
/* UART串口接受单个字符函数 */
extern unsigned char getc(void)
{
while( ! (UTRSTAT0 & RXD0READY) );
return URXH0;
}
/* UART串口字符串打印函数 */
extern int printk(const char* str)
{
int i = 0;
while( str[i] ){
utc( (unsigned char) str[i++] );
}
return i;
}
/* 按键初始化 */
int key_init()
{
// 设置K1,K2,K3,K4,K5,K6对应控制寄存器为中断模式
GPGCON = (2《《0) | (2《《6) | (2《《10) | (2《《12) | (2《《14) | (2《《22);
/*
01x falling edge triggered下降沿触发
10x Rising edge triggered上升沿触发
11x Both edge triggered双沿触发
*/
// 设置K1,K2,K3,K4,K5按键中断触发方式为上升沿触发
EXTINT1 = (3《《0) | (3《《12) | (3《《20) | (3《《24) | (3《《28);
EXTINT2 = (3《《12); // 设置K6按键中断触发方式为上升沿触
printk(“按键初始化OK/r/n”);
return 0;
}
/* Led1~Led4初始化*/
#define LED1 (1《《5) //LED1 GPBDAT[5]
#define LED2 (1《《6) //LED2 GPBDAT[6]
#define LED3 (1《《7) //LED3 GPBDAT[7]
#define LED4 (1《《8) //LED4 GPBDAT[8]
/* 点亮对应num号led灯 */
extern int led_on(int num)
{
switch(num)
{
case 1:
GPBDAT = GPBDAT & ~LED1; break;
case 2:
GPBDAT = GPBDAT & ~LED2; break;
case 3:
GPBDAT = GPBDAT & ~LED3; break;
case 4:
GPBDAT = GPBDAT & ~LED4; break;
default:
return 0;
}
return num;
}
/* 关闭num号led灯 */
extern int led_off(int num)
{
switch(num)
{
case 1:
GPBDAT = GPBDAT | LED1; break;
case 2:
GPBDAT = GPBDAT | LED2; break;
case 3:
GPBDAT = GPBDAT | LED3; break;
case 4:
GPBDAT = GPBDAT | LED4; break;
default:
return 0;
}
return num;
}
/* 关闭全部led灯 */
extern int all_led_off(void)
{
GPBDAT = GPBDAT | LED1 | LED2 | LED3 | LED4;
return 0;
}
/* led灯初始化 */
int led_init(void)
{
GPBCON = 0x15400; //设置GPB7为输出口
all_led_off();
printk(“led初始化OK/r/n”);
return 0;
}
/* 中断初始化 */
void irq_init(void)
{
// 打开KEY1~KEY6的屏蔽位
INTMSK &= ~(1《《5);
EINTMASK &= ~((1《《8) | (1《《11) | (1《《13) | (1《《14) | (1《《15) | (1《《19));
printk(“中断初始化OK/r/n”);
}
该文件是相关硬件初始化程序,主要包含了看门狗驱动,按键驱动,系统中断驱动,LED驱动。
handle_irq.c:
中断处理函数,查出中断源,中断处理,清除中断源。
#include “register.h”
#include “comm_fun.h”
#define EINT_Key_REQUEST 5 // Key中断源中断号(6个按键全部使用外部子中断)
#define K1_EINT_BIT (1《《8) // K1外部子中断位
#define K2_EINT_BIT (1《《11) // K2外部子中断位
#define K3_EINT_BIT (1《《13) // K3外部子中断位
#define K4_EINT_BIT (1《《14) // K4外部子中断位
#define K5_EINT_BIT (1《《15) // K5外部子中断位
#define K6_EINT_BIT (1《《19) // K6外部子中断位
/* 系统中断处理函数 */
void handle_irq()
{
unsigned long irqOffSet = INTOFFSET; // 取得中断号
all_led_off(); // 关闭全部Led灯
if(EINT_Key_REQUEST==irqOffSet){ // Key中断产生(6个按键使用一个总中断号)
if(K1_EINT_BIT & EINTPEND){
led_on(1); // 点亮Led1
printk(“Key1 pressed/r/n”);
EINTPEND &= K1_EINT_BIT; // 清除外部子中断源
}else if(K2_EINT_BIT & EINTPEND){
led_on(2); // 点亮Led2
printk(“Key2 pressed/r/n”);
EINTPEND &= K2_EINT_BIT; // 清除外部子中断源
}else if(K3_EINT_BIT & EINTPEND){
led_on(3); // 点亮Led3
printk(“Key3 pressed/r/n”);
EINTPEND &= K3_EINT_BIT; // 清除外部子中断源
}else if(K4_EINT_BIT & EINTPEND){
led_on(4); // 点亮Led4
printk(“Key4 pressed/r/n”);
EINTPEND &= K4_EINT_BIT; // 清除外部子中断源
}else if(K5_EINT_BIT & EINTPEND){
all_led_off(1); // 熄灭全部Led
printk(“Key5 pressed/r/n”);
EINTPEND &= K5_EINT_BIT; // 清除外部子中断源
}else if(K6_EINT_BIT & EINTPEND){
all_led_on(); // 点亮全部Led
printk(“Key6 pressed/r/n”);
EINTPEND &= K6_EINT_BIT; // 清除外部子中断源
}
}
SRCPND &= (1《《irqOffSet); // 清除中断源
INTPND = INTPND; // 清除中断结果
}
main.c:
包含主函数和延时函数,主要实现字符串的循环打印。
#include “register.h”
#include “comm_fun.h”
/* 延时 */
void delay(int msec)
{
int i, j;
for(i = 1000; i 》 0; i--)
for(j = msec*10; j 》 0; j--)
/* do nothing */;
}
/* 主函数 */
int main()
{
while(1)
{
printk(“main函数在运行。。。/r/n”);
delay(5); //delay
}
return 0;
}