基于S3C6410的ARM11学习(十六) 外部中断
0赞中断在嵌入式里面是很常见的一个功能了。通过这个功能,可以让CPU减轻很多负担,不用不断的查询设备的状态。提高了CPU的效率。
中断的过程如下:
中断源检测中断信号产生,然后将中断信号发送给中断控制器,中断控制器判断该中断是否被屏蔽,从而决定该中断信号是否要发送给CPU。中断信号发送给CPU后,CPU对中断进行处理,也就是调用中断函数。
上述过程,基本上是嵌入式的通过中断处理过程,只是不同的嵌入式在这三部分配置有区别而已。
S3C6410共有64个中断源。
上图是S3C6410的中断控制器,这里就关心红色框部分。这两个是中断控制器,分别管理各自的32个中断。
这里,就截取了一部分的图。总共有64个中断,每个中断有自己的标号,以及自己的所属组,也是属于哪个中断控制器控制。标号是指在对应的中断控制器寄存器的哪一位或者是哪一个寄存器对应自己。如INT_EINT0,这个是外部中断0,属于VIC0,标号是0。说明VIC0的寄存器的第0位或者是第0个寄存器对应外部中断0这个中断。
本章,是关心外部中断的部分。S3C6410共有127个外部中断,其I0引脚及分组如下:
S3C6410将外部中断分成了10组。每一组有相应的管脚。对于外部中断组0,可以使用GPN0-GPN15这16个管脚。
除了分组,还对组分配了中断号:
属于外部中断组0的27个中断占用了VIC里的4个中断号,分别是外部中断0-3,外部中断4-11,外部中断12-19,外部中断20-27。外部中断组1-9只占用了1个中断号。
为了节约中断,不可能为每个外部管脚都分配一个中断号,所以就会把某几个外部中断给合并成一个中断号。例如,对于外部中断0-3,就合并成了一个中断号,INT_EINT0。当这4个中断有任意一个产生中断时,INT_EINT0会挂起,CPU就会知道产生了外部中断0-3中断,然后去执行中断处理,在中断服务程序中,为了知道具体是哪一个中断,还需要去查询寄存器以知道是哪一个中断产生。
S3C6410中断处理有向量模式和非向量方式。因为要与之前的ARM系列兼容,所以保存了非向量方式。非向量方式,就是当中断产生时,都跳转到中断异常去,然后这个中断异常中,编写程序,判断是哪一个中断产生,然后去执行对应的中断处理程序。但是在非向量模式中,提前设定每个中断对应的入口地址,这样当中断产生的时候,就不用跳转到中断异常去了,直接跳转到对应的中断程序去了。这样中断处理的效率就提高了。
在我的设计中,使用的就是中断向量方式。推荐用中断向量方式。
上图是中断非向量方式。
上图是中断向量方式。
明显看出,中断向量方式的效率要高。
中断程序编写流程,大致就只有3步:
1、初始化中断源,也就是设置中断源是什么,以及中断的触发方式(高电平,低电平,上边沿,下降沿,双边沿)
2、初始化中断控制器,也就是使能对应的中断,让中断信号能够传递到CPU。在这里还需要设置中断优先级,中断滤波等与中断的相关设置
3、编写中断处理函数。也就是中断到了,该干什么。干完后,还要把中断挂起给清除掉,以免进入第二次中断下面就进行外部中断程序的编写了。这里要控制一些寄存器。OK6410的开发板有6个按键,接到了GPN0-5的六个外部中断脚上。
第一步,中断源设置
1、设置外部管脚为中断。
将对应管脚的值设置为10,就是外部中断了。这里,没有说明是哪个组,就是默认的第0组。
上面,就说明GPH0是外部中断第6组的外部中断0。
所以代码就有了
首先定义寄存器的地址,然后将这个寄存器的后12位设置为AAA。表示这几个管脚作为外部中断。
2、设置触发方式
设置外部中断中,有以上一堆寄存器。说明下这些寄存器有什么用
EINTxCONy:外部中断组x的第y个控制器。这个就是设置中断的触发方式。有5种触发方式。
EINTxFLTCON:外部中断组x的第y个滤波功能控制器。S3C6410对于外部中断有滤波功能,这个就是用来设置滤波功能。有延迟滤波,有数字滤波。滤波,可以用来滤掉毛刺信号。在以前51做按键外部中断,按键是有抖动的,就需要一个去抖动的一个东西。但是有了这个滤波的话,直接设定滤波的时间,就可以滤掉这一段时间的毛刺了。就不用再加去抖动的程序了。
EINTxMASK:外部中断组x的屏蔽寄存器。用来使能中断的。当要使用某个中断的时候,就需要让这个中断使能,就需要在这个寄存器中的对应位写入0,来打开中断。默认为中断都是屏蔽的。
EINTxPEND:外部中断组x的中断挂起寄存器。这个寄存器就是用来保存中断的状态的。当对应的中断产生时,对应的位为1。当中断处理完毕后,需要往对应对写入1进行清除。
在这里,就只需要设置触发方式即可了。将外部管脚的触发方式都设置为下降沿触发。因为在开发板上,按键按下的时候是低电平,没有按下的时候是高电平。
首先是定义寄存器地址
配置IO代码就如下了,对EINT0CON0配置即可。
第二步:中断控制器配置
1、去掉外部中断屏蔽
就是配置EINT0MASK寄存器。让对应的位为0.
2、设置滤波
就是配置EINT0FLTCON寄存器。打开滤波,设置为数字滤波,滤波宽度为6‘h3F。
3、使能中断号。
外部中断0-3属于INT_EINT0中断号,外部中断4-11属于INT_EINT1中断号,这里用到了外部中断0-5。所以用到了这两个中断号。就要是能这两个中断号。
这个VICxENABLE寄存器用来使能中断号。每一位就对应各个中断号的标号。我们这要打开INT_EINT0和INT_EINT1.这两个的标号是
所以要将VIC0ENABLE的最后两位设置为1.
因为S3C6410是采用向量中断,所以就需要配置中断的入口地址。这个入口地址就是靠VICxVECTADDRy寄存器来配置的。
对于VIC0和VIC1有各自的寄存器。每个寄存器保存的是对应中断的入口地址。例如,VIC0VECRADDR0就是VIC0下的第0个中断号的入口地址。
我们这用到VIC0下的第0个中断号和第一个中断号。
isr_INT_EINT0,isr_INT_EINT1是中断函数的名字。将函数地址地址转换为unsigned long型。在传递给寄存器。这样,就有了中断号的入口地址。当外部中断0-3产生的时候,0号中断号就有效,然后就跳转到isr_INT_EINT0函数去执行了。
最后两步,就是使能向量中断和打开总中断。在之前的qboot设计中,是将中断全部关掉的。
使用嵌入汇编,这里代码就不分析了。去看看CP15的c1寄存器和CPSR寄存器就知道这段代码有什么用了。
第三步:编写中断处理函数
这个就是写中断产生的时候,要做什么。这里就将灯翻转就行了。当有外部中断,就将灯状态翻转一次。
总共两个中断函数。在中断函数前和中断函数末尾都有使用嵌入汇编。这段代码是固定的。目的是为了保存环境,将r0-r12,lr寄存器的值给压入栈中,然后执行完后,在将这些值返回给r0-r12,pc寄存器。这样r0-r12寄存器的内容就恢复了,同时pc得到返回地址,就返回到中断前的程序地址去了。
这里有sub lr,lr,#4。将lr的值减去4。这个原因就要从ARM的流水线说起了。ARM采用流水线,取址,译码,执行。所以pc的值永远是当前执行指令的地址+8.
中断跳转的时候,会将pc的值给lr,pc的值为当前执行程序地址+8,lr的值就是pc的值。而返回的地址应该是执行阶段的下一条地址,也就是当前执行程序地址+4,所以直接返回lr的值就不对了,应该返回lr-4的值。
中断处理完后,需要将中断挂起位给清零。这里,很简单的将所有中断位都给清零。然后再将中断执行地址给清0。
这个寄存器,当执行中断函数的时候,值是中断函数的入口地址。往这个寄存器写入任意的值,就清除这个中断了。所以最后需要将这个寄存器给清0。
这样,就完成了S3C6410的外部中断程序设计了。总结下来就几步:
1、设置外部管脚为中断
2、设置中断触发方式
3、取消中断屏蔽,使外部中断不屏蔽
4、设置中断滤波
5、设置中断号的中断选择,是irq还是fiq,默认为是irq。
6、使能中断号
7、设置中断号的入口地址
8、开启向量中断方式并打开全局中断
9、编写中断处理函数,中断函数前和后要使用嵌入汇编,保存环境和恢复环境。中断处理后,要清除中断挂起位和中断执行地址
这样下来,就能实现外部中断了吗?当然,是不可以的。因为还差了一步。中断处理函数的代码是用c代码写的,c代码需要什么?需要栈啊。不然怎么时间环境保护和恢复环境了。之前不是设置过栈吗?怎么这样还需要设置了?
那是因为之前设置的栈是SVC模式下的栈,而不是irq模式下的栈。不同模式,有自己的备份寄存器,其中,栈SP是每个模式都有自己的。所以需要设置下irq模式下的栈。代码也是比较简单的。在之前的设置栈的汇编代码中,将模式切换为irq模式,再设置sp。
init_stack: msr cpsr_c, #0xd2 ldr sp, =0x53000000 //初始化r13_irq msr cpsr_c, #0xd3 ldr sp, =0x54000000 //初始化R13_svc mov pc, lr
对比STM32:
STM32直接就是采用的中断向量模式。而且中断的入口地址是相对固定的。所以就不像S3C6410那样,需要配置中断的入口地址。
在手册中,就定义了各个中断函数的入口地址。
所以在启动代码中,才有如下的程序,去定义中断函数的入口地址。
在后面还将各个中断函数导出
导出的中断函数用[weak]修饰,说明这个函数是弱函数,外部程序是可以改写这个函数的。在启动代码中,就将各个中断函数的名字给固定了,所以当我们要编写中断函数的时候,函数名可是不能乱取的,要取这里定义的函数,不然就跳转不到中断函数去了。
STM32的中断上电默认就是关闭的。所以要使用中断的话,就得要配置中断。过程和S3C6410的一样。
1、配置中断源
2、中断控制器配置
3、编写中断处理函数
STM32将中断分成了中断和事件两种。其框图:
中断和事件有什么区别了?中断,是一定要到CPU中去处理的。但是事件就不用,事件可以通知一些外设进行一些操作。例如使用DMA发送串口数据,当串口发送完一个数据后,就产生一个事件通知DMA,表示数据发送完毕,DMA接收到这个事件后,发送一个新的数据给串口进行发送。整个过程不需要CPU参与。效率就提高了。
从图中,也可以看出,配置外部中断,需要配置5个寄存器。
STM32将外部中断分成了16组线,对应每一组IO的一根线。
所以当要用外部管脚的中断,要开放来自对应线的请求。
另外,STM32对外部中断也采用的分组管理,也就是不是每一组外部线都有对应的中断。
STM32对外部共有6个中断号。
中断号 |
说明 |
EXTI0 |
EXTI线0中断 |
EXTI1 |
EXTI线1中断 |
EXTI2 |
EXTI线2中断 |
EXTI3 |
EXTI线3中断 |
EXTI4 |
EXTI线4中断 |
EXTI9_5 |
EXTI线[9:5]中断 |
EXTI15_10 |
EXTI线[15:10]中断 |
每个中断号对应自己的中断函数。
当外部中部9-5有一个产生时,就会跳转到EXTI9_5_IRQHandler这个中断处理函数中去,当然还需要判断到底是哪一个中断产生,通过查中断挂起寄存器就知道了。
以下,假设将外部管脚PA0-5设置为外部中断,触发方式都为下降沿。
第一步、中断源配置
1、IO管脚配置
首先是配置PA0-5这6个管脚为上拉输入,并且开启GPIOA的时钟。这里在这里配置就不说了,可以参考之前的点亮led。这个和S3C6410不一样,没有外部中断这一项,但是外部中断其实是属于输入的一种,所以就配置为输入就好了。上拉下拉就看IO的外部是怎么接的。
开启时钟要多一个AFIO时钟,当IO作为其他功能使用,要开启辅助功能时钟。这个在APB2外设时钟使能寄存器(RCC_APB2ENR)中设置。
2、设置中断的触发方式
配置中断触发方式,配置为下降沿。
所以最低6位也都设置为1。有下降沿寄存器,当然就有上升沿寄存器,但是这里不用上升沿,所以就不用配置了。
第二步:中断控制器配置
1、打开中断线请求
PA0-5对应中断线的0-5。这个寄存器的低6位就都要写入1。因为这里就只用到中断,事件屏蔽寄存器就不用管了。
2、配置中断线选择哪一个IO组。
在中断线是将每个组的IO管脚通过选择器到外部中断线上。所以就需要配置具体是哪一组的管脚链接到外部中断线上了。
外部中断0-3选择的都是PA管脚,这个寄存器的低16位值为0x0000。同理,外部中断4,外部中断5也是一样的配置。
第三步,编写中断处理函数
STM32不向S3C6410那样,在进入前要保存下环境,退出要保存环境。这些都不用做了。直接写中断处理即可。中断处理完毕后,要将中断挂起位给清零掉,避免再次进入中断。
这个寄存器就保存了各个中断线的请求,当处理完毕后,要将对应的位给清零。
这样,就将STM32的外部中断给搞定了,但是真的搞定了吗?
中断的框图中,中断最终是到NVIC中断控制器的,并没有到CPU。这说明什么,还需要配置NVIC中断控制器了。
NVIC中断控制器已经属于cortex-M3内核的部分了。从官方库文件的core_cm3.h文件中有NVIC的结构体。
以下是摘录STM32不完全手册-寄存器版本中的内容。
ISER[8]:ISER全称是:Interrupt Set-Enable Registers,这是一个中断使能寄存器组。CM3内核支持256个中断,所以这里用8个32位寄存器来控制,每个位控制一个中断。但是STM32的可屏蔽中断最多只有68个(互联型),所以有用的就是三个(ISER[0~2]]),总共可以表示96个中断。而STM32只用了其中的前68位。ISER[0]的bit0~31分别对应中断0~31;ISER[1]的bit0~32对应中断32~63;ISER[2]的bit0~3对应中断64~67;这样总共68个中断就分别对应上了。你要使能某个中断,必须设置相应的ISER位为1,使该中断被使能。
ICER[8]:全称是:Interrupt Clear-Enable Registers,是一个中断除能寄存器组。该寄存器组与ISER的作用恰好相反,是用来清除某个中断的使能的。向对应位写1,就清除了该中断。
ISPR[8]:全称是:Interrupt Set-Pending Registers,是一个中断挂起控制寄存器组。每个位对应的中断和ISER是一样的。通过置1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写0是无效的。
ICPR[8]:全称是:Interrupt Clear-Pending Registers,是一个中断解挂控制寄存器组。其作用与ISPR相反,对应位也和ISER是一样的。通过设置1,可以将挂起的中断接挂。写0无效。
IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位所代表的中断和ISER一样,如果为1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
IP[240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32的中断分组与这个寄存器组密切相关。IP寄存器组由240个8bit的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示240个可屏蔽中断。而STM32只用到了其中的68个。IP[67]~IP[0]分别对应中断67~0。而每个可屏蔽中断占用的8bit并没有全部使用,而是只用了高4位。这4位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据SCB->AIRCR中的中断分组设置来决定。
STM32对中断分成了5组,组0-组4。该分组设置是由SCB->AIRCH的bit10~8寄存器来定义的。
在使用中断前。必须要设置一个中断分组。而且一个系统,只能有一个中断分组。抢占优先级值越小,其中断的优先级越高,抢占优先级相同的话,响应优先级值越低的,优先级越高。
假设中断分组设置为2。那么就要往SCB->AIRCH的[10:4]写入101,也就是将2的取反的最后三位写入。但是写入这个寄存器,需要填入钥匙才能写进去。钥匙就是0x05FA。写入的值最高16位为0x05FA,才能将[10:8]的值写入。
书中也提供了参考代码
下面就是要设置各个中断的开启以及设置优先级了。这个也提供了参考代码:
其实就是设置NVIC下的ISER和IP寄存器。对中断使能及设置优先级。这里要填入中断的编号。怎么样得知中断的编号是多少了,从STM32的手册就可以得到。
最前面一列就是中断号。EXTI线0中断编号就是6。
假设,设置外部中断0-5的抢占优先级都是0,相应优先级从0-5。中断组为1。所以有1位抢占优先级,3位响应优先级。
那么代码就是
这部分的配置相对比较麻烦,因为这个是属于内核的一部分。不过呢,我们开发的时候,都是使用库开发的,库将这些都已经封装好了,只需要调用库函数即可了。
在库文件的misc.h文件中,就提供了函数。
这里,就用到前两个
第一个设置中断分组,第二个对中断进行设置。具体,大家可以去看看代码就知道了。其实也不难。
这样下来,才将STM32的外部中断给设置好了。
对比下来,STM32的中断配置比S3C6410要复杂一些,因为要配置内核的NVIC。不过其他都是差不多一样的。
最后来一个图比较