OK6410开发板学习之外部中断(按键点亮led和蜂鸣器)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014754841/article/details/80377676

中断在嵌入式里面是很常见的一个功能了。通过这个功能,可以让CPU减轻很多负担,不用不断的查询设备的状态。提高了CPU的效率。

中断的大体过程如下:

    

中断源检测中断信号产生,然后将中断信号发送给中断控制器,中断控制器判断该中断是否被屏蔽,从而决定该中断信号是否要发送给CPU。中断信号发送给CPU后,CPU对中断进行处理,也就是调用中断函数。上述过程,基本上是嵌入式的通过中断处理过程,只是不同的嵌入式在这三部分配置有区别而已。

 S3C6410共有64个中断源。

 上图是S3C6410的中断控制器,这里就关心红色框部分。这两个是中断控制器,分别管理各自的32个中断。 如下图:




上图红框中的就是今天我们需要关心的中断部分(外部中断)。

S3C6410共有127个外部中断,被分成10组,分组及引脚对应情况如下:


分组对应中断号如下:


从上图我们知道,并不是每一个外部中断引脚都分配了中断号,因此,在中断服务程序中,为了知道具体是哪一个中断,还需要去查询寄存器以知道是哪一个中断产生。

学习过2440的朋友可能知道,2440的中断的处理采用的是非向量方式,就是当中断产生时,都跳转到中断异常去,然后这个中断异常中,编写程序,判断是哪一个中断产生,然后去执行对应的中断处理程序。如下图:


为了向下兼容,S3C6410中断处理有向量模式和非向量方式。在向量模式中,提前设定每个中断对应的入口地址,这样当中断产生的时候,就不用跳转到中断异常去了,直接跳转到对应的中断程序去了。这样中断处理的效率就提高了。向量中断模式如下图:


明显看出,中断向量方式的效率要高。因此,在学习ok6410开发板的中断时,我职无旁贷的选择了向量中断模式。

综上,可以总结出S3C6410的外部中断程序设计基本步骤如下

1、  设置外部管脚为中断;

2、  设置中断触发方式;

3、  取消中断屏蔽,使外部中断不屏蔽;

4、  设置中断滤波(可不设置,这里忽略);

5、  使能外部中断;

6、  设置中断号的入口地址;

7、  设置中断号的中断选择,是irq还是fiq,默认为是irq;

8、  开启向量中断方式并打开全局中断;

9、  编写中断处理函数,中断函数前和后要使用嵌入汇编,保存环境和恢复环境。中断处理后,要清除中断挂起位和中断执行地址。

接下来我们就以OK6410开发板为载体来逐步的分析外部中断的实现步骤。OK6410开发板上共有6个用户按键,我们接下来就通过这6个按键完成外部中断,实现按键控制led和蜂鸣器的运行情况。

要想实现上述的控制功能,就得知道这些按键、led和蜂鸣器对应的引脚情况,这些可以通过查看OK6410开发板原理图得到。原理图部分截图如下:

用户按键与芯片部分

led

     

蜂鸣器

    

从截图上可以看出来按键对应的引脚是GPN0~5、led对应的引脚是GPM0~3、蜂鸣器对应的引脚是GPF15。需要用到的引脚已经确认,下面开始按步骤配置外部中断。

1.配置外部引脚位中断

通过查看s3c6410手册GPIO章节,我们找到GPN相关配置寄存器如下:


这三个寄存器我们只需设置GPNCON,详细说明如下:


从上图可知,要想将开发板用户按键设置成外部中断,只需要将GPNCON寄存器的GPN0~5这几个位设为10即可。实现代码如下:

#define  GPNCON *((volatile unsigned long*)0x7f008830)           /* GPN控制寄存器 */
#define  GPNDAT *((volatile unsigned long*)0x7f008834)           /* GPN数据寄存器 此处未使用*/
#define  GPNPUD *((volatile unsigned long*)0x7f008838)           /* GPN上下拉配置寄存器 此处未使用*/

/* 按键初始化 
*  设置按键对应引脚为外部中断模式
*/
void button_init(void)
{
	/*	方式1  移位
	GPNCON = (0b10 << 0) | (0b10 << 2) | (0b10 << 4) | (0b10 << 6) | (0b10 << 8) | (0b10 << 10);	
	*/
	/* 方式2 逻辑运算 */
	GPNCON &= (~0x00000aaa);	
	GPNCON |= 0x00000aaa;
}

2.设置中断触发方式

通过原理图我们发现,按键部分是作了上拉处理的,所以这里设定外部中断触发方式为下降沿触发。接着在s3c6410手册的GPIO章节中找到外部中断相关寄存器说明部分,如下:



由GPNCON寄存器我们知道GPN0~5对应的是外部中断分组0 ,所以这里我们只需要配置EINT0CON0寄存器即可,EINT0CON0说明如下:


红框中的位就是我们需要设置,从图中可知,只需将对应为设置位010即可,代码如下:

/*interrupt registes*/
#define EXT_INT_0_CON       *((volatile unsigned int *)0x7f008900)        /* 外部中断0~27配置寄存器 */

    EXT_INT_0_CON &= ~(0x00000222);   
    EXT_INT_0_CON |= 0x00000222;                                   /* 配置为下降沿触发 */  

3.取消中断屏蔽,使外部中断不屏蔽

既然想要取消中断屏蔽,那就得配置中断屏蔽寄存器,该寄存器在s3c6410手册的GPIO章节中找到外部中断相关寄存器说明部分。因为这里是外部中断分组0的0~5,所以只需配置EINT0MASK寄存器(0x7f008920)的bit0~5,如下红框部分:


从上图可以知道,需要将EINT0MASK寄存器的bit0~5配置为0,代码如下:

#define EXT_INT_0_MASK      *((volatile unsigned int *)0x7f008920)        /* 外部中断0~27屏蔽寄存器 */  

EXT_INT_0_MASK &= 0xffffffc0;                                   /* 取消屏蔽外部中断 */

4.设置中断滤波

滤波主要是消除毛刺干扰,需要配置滤波寄存器,如下图:


代码如下“

#define EXT_INT_0_FLTCON0   *((volatile unsigned int *)0x7f008910)        /* 外部中断组0滤波寄存器 */ 

EXT_INT_0_FLTCON0 = (0xff) | (0xff << 8) | (0xff << 16);        /* 设置外部中断0~5的滤波*/

5.使能外部中断

要想使能外部中断,就得配置中断使能寄存器VICxINTENABLE,又因为这里是外部中断分组0的0~5,根据s3c6410手册中断章节中断源介绍部分可知,这里只需要配置VIC0INTENABLE,如下红框:




中断使能寄存器VIC0INTENABLE地址及说明如下:



从上图可知,只需将中断使能寄存器VIC0INTENABLE的bit0~1设置为1即可。配置代码如下:

#define VIC0INTENABLE       *((volatile unsigned int *)0x71200010)        /* 中断使能寄存器 */

 VIC0INTENABLE &= ~(0x00000003);                                /* 使能外部中断*/  
 VIC0INTENABLE |= (0x00000003);

6.设置中断号的入口地址

要想设置中断的入口地址,就得找到中断向量地址寄存器。通过上面的讲解可以知道外部中断组0的0~5中断由中断源0、1产生,隶属于VIC0,所以这里的中断向量地址寄存器就是VIC0VECRADDR0和VIC0VECRADDR1,地址如下:


中断向量地址寄存器说明如下:


所以,这里只需要将自定义的中断处理函数的地址赋给VIC0VECRADDR0和VIC0VECRADDR1即可,代码如下:

#define EINT0_3_VECTADDR    *((volatile unsigned int *)0x71200100)        /* 外部中断0~3向量地址寄存器 */  
#define EINT4_7_VECTADDR    *((volatile unsigned int *)0x71200104)        /* 外部中断4~7向量地址寄存器 */

EINT0_3_VECTADDR = (int)INT_TINT0_ISR;                         /* 中断产生时,CPU就会自动的将VIC0VECTADDR0的值赋给VIC0ADDRESS并跳转到这个地址去执 */  
EINT4_7_VECTADDR = (int)INT_EINT1_ISR;                         /* INT_TINT0_ISR  INT_TINT1_ISR即为中断处理函数 */          

7.设置中断号的中断选择,是irq还是fiq,默认为是irq

既然这里说了默认是irq,那我们就是使用默认的中断模式,不去进行设置。如果想要更改,配置中断选择寄存器即可,如下:


8.开启向量中断方式并打开全局中断

开启向量中断方式需要操作系统控制协处理器(P15),而打开全局中断需要操作状态寄存器(CPSR)。别问我是怎么知道的,我不会告诉你是uboot告诉我这么做的。

开启向量中断方式:首先从arm11内核技术参考手册中找到系统控制协处理器章节(3.2节),找到与开启向量中断方式相关的协处理器控制寄存器,如下:




根据协处理器控制寄存器介绍得知,只需将控制寄存器的bit24置1就行了,代码如下:

/* 开启向量中断方式 */
__asm__(
    "mrc p15,0,r0,c1,c0,0\n"
    "orr r0,r0,#(1<<24)\n"
    "mcr p15,0,r0,c1,c0,0\n"          
    : 
    :
     );

开全局中断:从arm架构参考手册中找到程序状态寄存器相关章节,如下:


红框中这两个位就是关于开启中断的操作为,具体说明如下:

arm11内核技术参考手册中也有相关的介绍,如下:

设置代码如下:

    /*
    打开全局中断(开总中断)
    */
	__asm__(
    "mrs r0,cpsr\n"
    "bic r0, r0, #0x80\n"
    "msr cpsr_c, r0\n"            
    : 
    :
);

至此,有关外部中断的初始化就完成,下面就可以进行中断服务函数的设计了。

9.编写中断处理函数,中断函数前和后要使用嵌入汇编,保存环境和恢复环境。中断处理后,要清除中断挂起位和中断执行地址

保存环境:这里需要嵌入汇编,代码如下:

/* 保存坏境 */  
    __asm__(
    "sub lr, lr, #4\n"  
    "stmfd sp!, {r0-r12, lr}\n"       
    : 
    :
);

这是一个固定的代码,目的是为了保存环境,将r0-r12,lr寄存器的值给压入栈中。这里有sub lr,lr,#4。将lr的值减去4。这个原因就要从ARM的流水线说起了。ARM采用流水线,取址,译码,执行。所以pc的值永远是当前执行指令的地址+8。中断跳转的时候,会将pc的值给lr,pc的值为当前执行程序地址+8,lr的值就是pc的值。而返回的地址应该是执行阶段的下一条地址,也就是当前执行程序地址+4,所以直接返回lr的值就不对了,应该返回lr-4的值。

恢复环境:这里也需要嵌入汇编代码,如下:

    __asm__( 
    "ldmfd sp!, {r0-r12, pc}^ \n"       
    : 
    : 
);

这段代码也是固定的,目的是为了中断执行完后,在将这些值返回给r0-r12,pc寄存器。这样r0-r12寄存器的内容就恢复了,同时pc得到返回地址,就返回到中断前的程序地址去了。

有关于保存环境和恢复环境的代码。参考arm架构参考手册的A2,6章节,如下:


中断处理代码主要实现按键控制led和蜂鸣器运行状态,按下按键s2,点亮led1,再按一次s2,关闭led1;依次类推,详见代码:

/* 外部中断0~3处理函数 */ 
	if((EXT_INT_0_PEND & (1<<0)) == (1 << 0))
    	    led_Toggle(1);
	if((EXT_INT_0_PEND & (1<<1)) == (1 << 1))
    	    led_Toggle(2);
	if((EXT_INT_0_PEND & (1<<2)) == (1 << 2))
    	    led_Toggle(3);
	if((EXT_INT_0_PEND & (1<<3)) == (1 << 3))
          led_Toggle(4);
/* 外部中断4~7处理函数 */ if((EXT_INT_0_PEND & (1<<4)) == (1 << 4))     beep_Toggle();if((EXT_INT_0_PEND & (1<<5)) == (1 << 5)){     led_Toggle(0);     beep_Toggle();}

中断处理完后,需要将中断挂起位给清零。这里,很简单的将所有中断位都给清零。然后再将中断执行地址给清0

中断位挂起寄存器如下:


中断向量0地址寄存器如下:


代码如下:

    /* 清除中断 */
    EXT_INT_0_PEND = ~0x0; 
    VIC0ADDRESS = 0; 

这样,也就完成了S3C6410的外部中断程序设计了。但是,此时的代码下载至Ok6410开发板并不能正常工作,那是因为中断处理函数的代码是用c代码写的,c代码需要什么?需要栈啊。不然怎么时间环境保护和恢复环境了。可能有人会问之前不是设置过栈吗?怎么这里还需要设置了?那是因为之前设置的栈是SVC模式下的栈,而不是irq模式下的栈。不同模式,有自己的备份寄存器,其中,栈SP是每个模式都有自己的。所以需要设置下irq模式下的栈。代码也是比较简单的。在之前的设置栈的汇编代码中,将模式切换为irq模式,再设置sp。

代码如下:

@栈初始化 64M内存用于栈
init_stack:
    msr cpsr_c, #0xd2
    ldr sp, =0x53000000    @ 初始化r13_irq
  
    msr cpsr_c, #0xd3
    ldr sp, =0x54000000    @ 将0x54000000写入sp寄存器中,栈大小64M    0x5400000000-0x500000000
    mov pc, lr	           @ 返回调用处继续往下执行

这样再将代码编译后下载至开发板里就可以正常工作了。

代码下载地址:OK6410实现外部中断控制led与蜂鸣器




5 、    设置中断号的中断选择,是irq 还是fiq,默认为是irq;

猜你喜欢

转载自blog.csdn.net/u014754841/article/details/80377676