主机平台:Linux CentOS 6.5
arm平台:粤嵌GEC210开发板(S5PV210)
中断是arm里面很重要的一个部分,但S5PV210数据手册中中断那一章里讲的基本是一些概述性的内容,没有具体的中断实现流程,如果没有参考例程的话,完全不知到如何入手写一个中断程序。(或者其它地方有,但是我没有找到,哪位知道的麻烦告诉我一下)
下面把一些我认为比较重要的归纳下。因为是个人归纳的,而且有些地方涉及到芯片内部的操作,所以在某些细节方面可能会有不严谨的地方,这些地方我会加一些注释的,但在整体的流程上应该是没有问题的。
此外,下面所有的内容都以IRQ中断为例。
首先是:
中断系统接收到一个由外设来的中断信号时,会执行:
1.检测全局的IRQ中断是否打开。(注:这里也有可能是:若全局的IRQ中断没有打开,中断系统根本不会接收到中断信号)
2.若全局IRQ中断以打开,检查对应外设的中断在中断系统对应寄存器是否使能(注:同上,也可能并非检测)
3.若使能,将对应中断入口地址加载到其所在中断组的VIC0ADDRESS寄存器中,并置位对应的中断状态标志位。(注:这一步的位置不太确定)
4.跳转到异常向量表中,查找IRQ中断的对应入口地址,并进入此地址中执行通用中断服务程序,这个中断服务程序是所有IRQ中断共用的。
5.建立中断所需的环境(注:这里不太清楚具体做了那些工作,至少在我调试过程中,会自动将SP设置为IRQ模式下的对应值)
5.5.这步与中断系统无关,是在程序中通过中断系统的相关寄存器,从通用的中断服务程序跳转到与外设相关的特定中断服务程序。
6.当从特定中断服务程序返回时,会再次进入通用的中断服务程序,
7.从中断服务程序返回,恢复环境,此次中断结束
下面说一下当要写一个外设的中断驱动时的具体步骤:
1.关闭IRQ全局中断
2.编写通用服务程序
3.设置异常向量表
4.清除中断系统中所有中断的使能位(置位VICxINTENCLEAR)
5.设置所有中断的模式为IRQ(默认就是IRQ模式,可省略)
6清除VICxADDR(置0)
7.使能IRQ全局中断
上面是系统复位后,或者需要重置中断系统的状态时必须的步骤,下面就是针对某个特定中断的步骤
8.编写外设对应的中断服务程序(第8、9步是中断驱动的重点)
9.设置外设寄存器初始化外设(包括清除中断标志位,使能中断等操作)
10.根据外设查找中断源表获得其中断号
11.根据中断号设置对应的中断服务程序入口地址寄存器(VICxVECTADDRx)
12.根据中断号使能对应中断
只是讲也不太清除要怎么做,下面就贴一下代码,看一下具体的实现过程(代码是参考网上的一段中断程序修改的,但不知到出处,总之感谢一下原作者)
main.c文件
/******************************************
*main.c
******************************************/
extern void IRQ_handle();
extern void irq_handler();
void exceptionundef(void)
{
//printf("undefined instruction exception.\n");
while(1);
}
void exceptionswi(void)
{
//printf("swi exception.\n");
while(1);
}
void exceptionpabort(void)
{
//printf("pabort exception.\n");
while(1);
}
void exceptiondabort(void)
{
//printf("dabort exception.\n");
while(1);
}
void system_vector_init( void) //第3步:初始化异常向量表
{
pExceptionUNDEF = (unsigned long)exceptionundef;
pExceptionSWI = (unsigned long)exceptionswi;
pExceptionPABORT = (unsigned long)exceptionpabort;
pExceptionDABORT = (unsigned long)exceptiondabort;
pExceptionIRQ = (unsigned long)IRQ_handle; //IRQ通用中断服务程序入口
pExceptionFIQ = (unsigned long)IRQ_handle;
}
void clear_vicaddress()
{
//清除VECTADDRx寄存器
<span style="font-size:14px;"> </span>VIC0ADDRESS=0X00000000;
VIC1ADDRESS=0X00000000;
VIC2ADDRESS=0X00000000;
VIC3ADDRESS=0X00000000;
}
void interrupt_init()
{
//关闭所有中断
VIC0INTENCLEAR=0xFFFFFFFF;
VIC1INTENCLEAR=0xFFFFFFFF;
VIC2INTENCLEAR=0xFFFFFFFF;
VIC3INTENCLEAR=0xFFFFFFFF;
//设置中断模式为IRQ模式
VIC0INTMOD=0x00000000;
VIC1INTMOD=0x00000000;
VIC2INTMOD=0x00000000;
VIC3INTMOD=0x00000000;
//清除VECTADDRx寄存器
clear_vicaddress();
}
void interrupt_enable(int int_num) //使能特定中断号对应的中断
{
unsigned long temp;
if(int_num<32)
{
temp = VIC0INTENABLE;
temp |= (1<<int_num);
VIC0INTENABLE=temp;
}
else if(int_num<64)
{
temp = VIC1INTENABLE;
temp |= (1<<(int_num-32));
VIC1INTENABLE=temp;
}
else if(int_num<96)
{
temp = VIC2INTENABLE;
temp |= (1<<(int_num-64));
VIC2INTENABLE=temp;
}
else if(int_num<128)
{
temp = VIC0INTENABLE;
temp |= (1<<(int_num-96));
VIC3INTENABLE=temp;
}
}
void interrupt_disable(int int_num) //除能特定中断号对应的中断
{
unsigned long temp;
if(int_num<32)
{
temp = VIC0INTENCLEAR;
temp |= (1<<int_num);
VIC0INTENCLEAR=temp;
}
else if(int_num<64)
{
temp = VIC1INTENCLEAR;
temp |= (1<<(int_num-32));
VIC1INTENCLEAR=temp;
}
else if(int_num<96)
{
temp = VIC2INTENCLEAR;
temp |= (1<<(int_num-64));
VIC2INTENCLEAR=temp;
}
else if(int_num<128)
{
temp = VIC3INTENCLEAR;
temp |= (1<<(int_num-96));
VIC3INTENCLEAR=temp;
}
}
void set_int_vectaddr(unsigned long int_num,void(*func)(void)) //设置特定中断号的服务程序入口地址
{
if(int_num<32)
*(volatile unsigned long *)(VIC0VECTADDR+int_num*4)=(unsigned long)func;
else if(int_num<64)
*(volatile unsigned long *)(VIC1VECTADDR+(int_num-32)*4)=(unsigned long)func;
else if(int_num<96)
*(volatile unsigned long *)(VIC2VECTADDR+(int_num-64)*4)=(unsigned long)func;
else if(int_num<128)
*(volatile unsigned long *)(VIC3VECTADDR+(int_num-96)*4)=(unsigned long)func;
}
void enable_global_IRQ() //打开全局IRQ中断
{
asm("stmfd sp!,{r0}\n"
"mrs r0, cpsr\n"
"bic r0,r0,#0x80\n"
"msr cpsr, r0\n"
"ldmfd sp!,{r0}\n");
}
void disable_global_IRQ() //关闭全局IRQ中断
{
asm("stmfd sp!,{r0}\n"
"mrs r0, cpsr\n"
"and r0,r0,#0x80\n"
"msr cpsr, r0\n"
"ldmfd sp!,{r0}\n");
}
unsigned long get_irq_status(unsigned long vic_num) //检测那个中断发生了,在irq_handler中调用
{
unsigned long irq_status_addr[4]={VIC0IRQSTATUS,VIC1IRQSTATUS,VIC2IRQSTATUS,VIC3IRQSTATUS};
if(vic_num<4)
return irq_status_addr[vic_num];
return 0;
}
void irq_handler()//第2步:编写通用中断服务程序 汇编部分
<span style="font-size:14px;">{
unsigned long vicaddr[4]={VIC0ADDRESS,VIC1ADDRESS,VIC2ADDRESS,VIC3ADDRESS};
unsigned long i=0,temp=0,status=0;
void (*isr)(void)=(void (*)(void))0;
//下面的for循环就是选出真正发生中断的入口地址,将其赋值给isr
for(i=0;i<4;i++)
{
status=get_irq_status(i);
if(status)
{
isr=(void (*)(void))vicaddr[i];
break;
}
}
//如果isr为0则出错,否则进入特定的中断服务程序
if((unsigned long)isr==0)
{
disable_global_IRQ();
error();
}
(*isr)();
}
void timer0_int_func() //第9步:编写外设对应的中断服务程序
{
TINT_CSTAT |= 0x3e0;
clear_vicaddress();
enable_global_IRQ();
mputs("in timer0\r\n");
}
void timer0_init()
{
//第8步:初始化外设寄存器
TCFG0_REG = 0xffff;
TCFG1_REG = 0x4;
TCNTB0_REG = 16113;
TCON_REG |= 0x2;
TCON_REG &=~0x2;
TCON_REG |= 0x8;
TCON_REG |= 0x1;
TINT_CSTAT |= 0x3e1;
set_int_vectaddr(21,timer0_int_func); //第11步:设置中断入口地址
interrupt_enable(21); //第12步:使能中断系统中的中断
}
int main()
{
disable_global_IRQ()关闭全局中断;
system_vector_init(); //第3步:初始化异常向量表
interrupt_init(); //第4、5、6步:初始化中断寄存器
enable_global_IRQ(); //第7步:打开IRQ全局中断
//下面是根据相应的外设初始化各个寄存器
timer0_init();
while(1);
}
start.S文件
/******************************************
*start.S
******************************************/
IRQ_handle:
ldr sp,=0xD0037F80
sub lr,lr,#4
stmfd sp!,{r0-r12,lr}
bl irq_handler
ldmfd sp!,{r0-r12,PC}
好了,这次主要写的是裸机中断驱动的编写思路,也可以说是一个框架,以后写某个中断驱动时,最主要修改的是上面所讲的第8、9步,其它地方基本上是不需要修改的。
后面会针对各个外设编写出相应的驱动的,这次就到这里了。