资讯详情

外设篇:按键和CPU的中断系统

什么是按键

按钮的物理特性 (1)平时没人按的时候,弹簧弹开按钮。此时内部断开。 (2)当有人按下时,手的力克服弹簧的弹性,按下按钮,保持内部打开(关闭);如果手打开,按钮在弹簧的作用下再次弹开,内部断开。 (3)一般按钮有四个引脚,这四个引脚成两对:其中一对是常开触点(如述不按则断,按则闭);一对是常闭触点(平时不按时关闭,按下后关闭)

按钮的电学原理(结合原理图分析) (1)硬件接法: SW5:GPH0_2 SW6:GPH0_3 SW78910:GPH2_0123 (2)按钮电路连接分析:平时按钮不按时,按钮内部断开,GPIO引脚处电压为高电平;当有人按下按钮时,按钮内部导通,外部导通VDD电阻和按钮连接到地面,形成电路GPIO引脚电压变低。此时VDD电压全部压在电阻上(这个电阻叫分压电阻,不能太小,因为电阻的功率是U*U/R) (3)总结:按键的工作方法:其实按键和弹开分别对应GPIO两种电平状态(按下)GPIO为低电平,弹开则GPIO为高电平)SoC这个内部可以通过检测来检测GPIO判断按钮是否按下电平高低,可作为判断结果SoC输入信号。

按钮属于输入设备 (1)按键一般用作输入设备(由人向)SoC由人向发送信息的设备称为输入设备)SoC发送按键信号(按键信号有两种:按下信号和弹开信号)。 (2)有些设备是简单的输入设备,如按钮、触摸屏等;有些设备是简单的输出设备,如LCD;有些设备可以输入输出,称为输入输出设备(IO),譬如串口。

按钮的两种响应方法

(1)SoC处理按钮有两种想法:轮询和中断。 (2)轮询法是SoC每隔一段时间主动读取(按钮对应)GPIO获取按键信息的电平;缺点是CPU注意按键事件会影响CPU做别的事。 (3)中断是SoC事先设定好GPIO触发中断对应的中断处理程序ISR,按下或弹开外部按钮时,会自动触发GPIO相应的外部中断导致相应的外部中断ISR执行,自动处理按键信息。

按钮轮询处理

X210开发板按键接法 (1)查找原理图,找到按钮对应的GPIO:SW5:GPH0_2 SW6:GPH0_3 SW78910:GPH2_0123 (2)原理图可以看出,按下时低电平,弹起时高电平

按键对应的GPIO模式设置 (1)按键接收GPIO在上面,按下按钮或弹外部电路是否连接,从而决定此GPIO引脚的电压是高是低;这个电压可以用作这个GPIO引脚的输入信号,此时GPIO可以将其配置为输入模式SoC读取引脚的内部电平为1或0(1对应高电平,0对应低电平)。 (2)GPH0CON(0xE0200C00) GPH2DAT(0xE0200C04) GPH2CON(0xE0200C40) GPH2DAT(0xE0200C44) (3)应该在CON寄存器中将GPIO设置为input然后读取模式DAT寄存器(读取的相应位值为1,表示外部为高电平(对应按钮弹起),读取的位值为0,表示外部为低电平(按键)

按钮程序流程的轮询处理 (1)第一步是初始化GPIO模式为input; (2)循环读取第二步GPIO然后判断是否有按钮

编写和调试代码

串口输出和按键消抖

按钮调试基于串口标准输出 (1)以前的串口stdio根据工程移植,添加轮询按钮处理。 (2)注意USB下载方式可能是错误的可能不下载,也可能下载执行错误),解决方案是使用SD卡启动替换。

按键是什么? (1)物理设备本身的按钮会有抖动信号,抖动信号是指在电平从高到低(即按键)或电平从低到高(即按键弹起)的过程中,电平的变化不是立即变化,而是经过一段时间的不稳定期完成变化,在这个不稳定期间,电平可能会反复变化,这个不稳定期称为抖动(抖动期获取按键信息不可靠,想办法消抖)。 (2)什么是消抖?抖动是使用硬件或软件来减少抖动期对按键获取的影响。抖动通常有两种想法:第一种是硬件抖动。抖动的想法是尽量减少抖动时间。方法是通过硬件添加电容器和其他元件来减少抖动;第二种是软件抖动。抖动的想法是在发现按钮按下/弹出事件后延迟一段时间(一般为10~20ms,如果按下/弹起,这就是抖动时间)后再次获得按键值,那就认为真的按下/弹起了。 (3)一般需要精确的时候,需要配合硬件抖动和软件抖动。

S5PV介绍210中断系统

什么是中断 (1)中断发明是用来解决宏观并行需求的。总的来说,宏观并行意味着很多事情都完成了。 (2)微观平行是指真正的平行,即准确到每一秒甚至每一刻,同时进行多件事。宏观平行并不等于围观平行,有时宏观平行,微观串行。 (3)比如一个人在看电影,快递来了,暂停电影去收快递,收快递后继续回来看电影。这个例子是宏观平行和微观串行。一个人等同于一个例子SoC中1个CPU(即单核CPU),这个CPU看电影不能收快递,收快递不能看电影(也就是说不能真正平行)。单核心CPU它从微观角度串行,但因为CPU很快,所以在宏观上可以并行。 (4)上一个例子大部分时间看电影,中间少量时间收快递,所以类比CPU看电影应该是CPU常规任务,收快递应中断例程。也就是说CPU平时一直在看电影。快递员(类似于中断源)会打电话给人收快递(中断源会触发中断通知)CPU处理中断),人们接到电话(CPU收到中断信号后,将暂定电影(CPU保存常规任务的现场)跑去收快递(CPU执行中断处理程序ISR处理中断),收快递(执行完)ISR)回来继续看电影(CPU恢复常规任务现场,继续执行常规任务) (5)为什么要中断?因为单核CPU实际上不能并行,但假并行可以通过中断机制实现(宏观并行,微观实际串行)。

SoC实现中断的机制:异常向量表 (1)异常向量表是CPU一些特定地址的特定定义。当中断发生时,中断应找到通知的方法CPU去处理中断,怎么做到?这就要靠异常向量表。 (2)在CPU设计时,提前定义CPU中一些特定地址作为特定异常的入口地址(譬如定义0x复位异常向量地址为复位异常向量地址CPU会自动跳转到0x执行指令。例如,外部中断对应的异常向量地址为0x30000008,则发生外部中断后,CPU硬件会自动跳转到0x3000008地址执行指令。)如 (3)上面说的是CPU在硬件设计中支持异常向量表,需要软件支持。硬件决定了什么异常CPU自动跳转PC软件需要做的是填写处理此异常代码的第一个地址。

S5PV异常向量表210 (1)异常向量表1.2.说了14节,可以回去听听 (2)异常向量表中各向量的相对位置固定,但其起始地址不固定,各种SoC可以不同,复杂ARM还可以让用户来软件设置这个异常向量表的基地址。 (3)扩展到所有架构CPU中:所有架构(如51单片机)PIC单片机)的CPU通过异常向量表实现中断,机制不变;但不同CPU异常向量表的结构和位置不同。

异常与中断的区别与联系: (1)针对SoC就复位、软中断、中断、快速中断、指令异常、数据异常等而言,我们都称之为异常。因此,中断实际上是一种异常。 (2)异常的定义是突发事件,中断了CPU正常的常规业务,CPU必须跳转到异常向量表执行异常处理程序;中断是一种异常,一般指SoC内部外设产生的中断SoC常规业务或外部中断(SoC的GPIO引脚传回来的中断)。

异常向量表的编程处理 访问异常向量表就像内存一样 (1)S5PV可以改变210的异常向量表(在CP15协处理器),以满足操作系统的需要。但当系统刚刚启动时,此时DRAM尚未初始化,程序都在SRAM中运行。210在iRAM临时使用设置异常向量表。

(2)查210的iROM application note文档中iRAM地址分配,可见,iRAM异常向量表的起始地址为0xD0037400。知道异常向量表的起始地址后,就很容易知道每个异常对应的入口。

#include "stdio.h"   #define exception_vector_table_base  0xD0037400 #define exception_reset     (exception_vector_table_base   0x00) #define exception_undef     (exception_vector_table_base   0x04) #define exception_sotf_int    (exception_vector_table_base   0x08) #define exception_prefetch    (exception_vector_table_base   0x0C) #define exception_data     (exception_vector_table_base   0x10) #define exception_irq     (exception_vector_table_base   0x18) #define exception_fi					(exception_vector_table_base + 0x1C)

#define r_exception_reset		(*(volatile unsigned int *)exception_reset)
#define r_exception_undef		(*(volatile unsigned int *)exception_undef)
#define r_exception_sotf_int	(*(volatile unsigned int *)exception_sotf_int)
#define r_exception_prefetch	(*(volatile unsigned int *)exception_prefetch)
#define r_exception_data		(*(volatile unsigned int *)exception_data)
#define r_exception_irq			(*(volatile unsigned int *)exception_irq)
#define r_exception_fiq			(*(volatile unsigned int *)exception_fiq)

void system_init_exception(void)
{
	r_exception_reset = (unsigned int)reset_exception;
	r_exception_undef = (unsigned int)undef_exception;
	r_exception_sotf_int = (unsigned int)sotf_int_exception;
	r_exception_prefetch = (unsigned int)prefetch_exception;
	r_exception_data = (unsigned int)data_exception;
	r_exception_irq = (unsigned int)IRQ_handle;//真正的中断处理程序在汇编中
	r_exception_fiq = (unsigned int)IRQ_handle;//c中也可以直接调用汇编中的函数
	

}

void reset_exception(void)
{
	printf("reset_exception.\n");
}

void undef_exception(void)
{
	printf("undef_exception.\n");
}

void sotf_int_exception(void)
{
	printf("sotf_int_exception.\n");
}

void prefetch_exception(void)
{
	printf("prefetch_exception.\n");
}

void data_exception(void)
{
	printf("data_exception.\n");
}



// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场
void irq_handler(void)
{
	//printf("irq_handler.\n");
	// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断
	// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来
	// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断
	// 对应的isr。

}

中断向量表要注意:

各中断的相对位置是固定的,所以只要确定了基地址,然后加上偏移量即可定位到相应的中断。

 

函数名的实质就是函数的首地址 (1)函数名在C语言中的理解方法和变量名其实没区别。编译器会把这个函数的函数体对应的代码段和这个函数的函数名(实质是符号)对应起来,等我们在使用这个函数名符号时,编译器会将函数的函数体实际上做替换。因为函数体都不止4字节,而函数名这个符号只能对应1个地址,所以实际对应的是函数体那一个代码段的首地址。 (2)拿C语言中的语法来讲,函数名就是这个函数的函数指针。

总结:当我们将异常处理程序的首地址和异常向量表绑定起来后,异常处理初步阶段就完成了。到目前可以保证相应异常发生后,硬件自动跳转到对应异常向量表入口去执行时,可以执行到我们事先绑定的函数。

用汇编进行中断处理

#define WTCON		0xE2700000

#define SVC_STACK	0xd0037d80
#define IRQ_STACK	0xd0037f80 //设置栈的时候,地址要从上到下,因为ARM使用的是满减栈

.global _start	
.global IRQ_handle	

// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]
	
	// 第2步:初始化时钟
	bl clock_init
	
	// 第3步:设置SVC栈
	ldr sp, =SVC_STACK
	
	// 第4步:开/关icache
	mrc p15,0,r0,c1,c0,0;			// 读出cp15的c1到r0中
	//bic r0, r0, #(1<<12)			// bit12 置0  关icache
	orr r0, r0, #(1<<12)			// bit12 置1  开icache
	mcr p15,0,r0,c1,c0,0;

	bl main
	
	// 从这里之后就可以开始调用C程序了
	//bl led_blink					// led_blink是C语言实现的一个函数
	
// 汇编最后的这个死循环不能丢
	b .
	
// 在这个汇编函数中,用来做中断模式下的现场保护和恢复,并且调用真正的中断处理程序

IRQ_handle:
	// 设置IRQ模式下的栈
	ldr sp, =IRQ_STACK
	// 保存LR
	// 因为ARM有流水线,所以PC的值会比真正执行的代码+8,
	sub lr, lr, #4
	// 保存r0-r12和lr到irq模式下的栈上面
	stmfd sp!, {r0-r12, lr}
	// 在此调用真正的isr来处理中断
	bl irq_handler
	// 处理完成开始恢复现场,其实就是做中断返回,关键是将r0-r12,pc,cpsr一起恢复
	ldmfd sp!, {r0-r12, pc}^

为什么中断处理要先在汇编中进行 (1)中断处理要注意保护现场(中断从SVC模式来,则保存SVC模式下的必要寄存器的值)和恢复现场(中断处理完成后,准备返回SVC模式前,要将保存的SVC模式下的必要寄存器的值恢复回去,不然到了SVC模式后寄存器的值乱了,SVC模式下原来正在进行的常规任务就被你搞坏了) (2)保存现场包括:第一:设置IRQ栈;第二,保存LR;第三,保存R0~R12 (3)为什么要保存LR寄存器?要考虑中断返回的问题。中断ISR执行完后如何返回SVC模式下去接着执行原来的代码。中断返回其实取决于我们进入中断时如何保存现场。中断返回时关键的2个寄存器就是PC和CPSR。所以我们在进入IRQ模式时,应该将SVC模式下的下一句指令的地址(中断返回地址)和CPSR保存起来,将来恢复时才可以将中断返回地址给PC,将保存的CPSR给CPSR。 (4)中断返回地址就保存在LR中,而CPSR(自动)保存在(IRQ模式下的)SPSR中

汇编保存现场和恢复现场 (1)保护现场关键是保存:中断处理程序的返回地址,r0-r12(cpsr是自动保存的) (2)恢复现场主要是恢复:r0-r12,pc,cpsr

**************************************************************************************

当中断发生时,就会从默认的SVC模式自动进入IRQ模式。

在跳入之前,pc已经被保存在了lr中,此时pc指向的是当前正在执行的下下一条指令。将lr减去4才是此前模式下要执行的下一条指令。中断处理完成后,再还给pc,就会继续执行之前的指令。

保存数据的实质是利用内存作为中间存储。

从一种模式到另一种模式时,各寄存器里的数据是不会变的,只不过目标寄存器的所有操作,会影响共用寄存器,而不会影响原来的影子寄存器。

************************************************************************************** S5PV210的向量中断控制器

异常处理的2个阶段 (1)可以将异常处理分为2个阶段来理解。第一个阶段是异常向量表跳转;第二个阶段就是进入了真正的异常处理程序irq_handler之后的部分。 1.8.6.2、回顾:中断处理的第一阶段(异常向量表阶段)处理。 (1)第一个阶段之所以能够进行,主要依赖于CPU设计时提供的异常向量表机制。第一个阶段的主要任务是从异常发生到响应异常并且保存/恢复现场、跳转到真正的异常处理程序处。 (2)第二个阶段的目的是识别多个中断源中究竟哪一个发生了中断,然后调用相应的中断处理程序来处理这个中断。

S3C2440的第二阶段处理过程 (1)第一个问题,怎么找到具体是哪个中断:S3C2440的中断控制器中有一个寄存器(32位的),寄存器的每一个位对应一个中断源(为了解决支持更多中断源,2440又设计了一个子中断机制。在一级中断寄存器中有一些中断是共用的一个bit位,譬如AC97和WDT。对于共用中断,用子中断来区分究竟是哪一个发生了中断) (2)第二个问题,怎么找到对应的isr的问题:首先给每个中断做了个编号,进入isr_handler之后先通过查阅中断源寄存器和子中断寄存器(中哪一位为1)确定中断的编号,然后用这个编号去isr数组(isr数组是中断初始化时事先设定好的,就是把各个中断的isr的函数名组成一个数组,用中断对应的编号作为索引来查询这个数组)中查阅得到isr地址。 评价:2440的中断处理设计不是特别优秀:第一个过程中使用子中断搞成2级的很麻烦;第二个过程中计算中断编号是个麻烦事,很耗费时间。而中断处理的时间是很宝贵的(系统有一个性能指标,叫实时性。实时性就是中断发生到响应的时间,这个时间越短越好。)

S5PV210的第二阶段处理过程 (1)第一个问题,怎么找到具体是哪个中断:S5PV210中因为支持的中断源很多,所以直接设计了4个中断寄存器,每个32位,每位对应一个中断源。(理论上210最多支持128个中断,实际支持不足128个,有些位是空的);210没有子中断寄存器,每个中断源都是并列的。当中断发生时,在irq_handler中依次去查询4个中断源寄存器,看哪一个的哪一位被置1,则这个位对应的寄存器就发生了中断,即找到了中断编号。

4个中断控制器VIC,每个VIC32位,用每一位来表示1个中断,共有128个中断:

搜索:

怎么来管理这128个中断呢?

ARM设计了很多寄存器,这里不怎么好理解。重点关注下。

128个中断分为4组,VIC0-VIC3

对应的每一组都有相应的寄存器,比如状态寄存器,这个状态寄存器是32位,每一位跟每一个中断相对应。比如:

VIC0[0]    表示一种中断    用1位二进制数表示状态
VIC0[1]    表示一种中断    用1位二进制数表示状态
VIC0[2]    表示一种中断    用1位二进制数表示状态
VIC0[3]    表示一种中断    用1位二进制数表示状态
VIC0[4]    表示一种中断    用1位二进制数表示状态
...        表示一种中断    用1位二进制数表示状态
VIC0[31]   表示一种中断    用1位二进制数表示状态

之后,将表示状态的32位二进制数构成一个寄存器,就能用寄存器的相应位来控制各中断的状态。

(2)第二个问题,怎么找到对应的isr的问题:210中支持的中断源多了很多,如果还使用2440的那一套来寻找isr地址就太慢了,太影响实时性了。于是210开拓了一种全新的寻找isr的机制。210提供了很多寄存器来解决每个中断源对应isr的寻找问题,具体寻找过程和建立过程见下节,实现的效果是当发生相应中断时,硬件会自动的将相应isr推入一定的寄存器中,我们软件只要去这个寄存器中执行函数就行了。

总结:第一阶段都相同,第二阶段各不同 (1)第一阶段(异常向量表阶段)2440和210几乎是完全相同的。实际上几乎所有的CPU在第一阶段都是相同的。 (2)第二阶段就彼此不同了。各个SoC根据自己对实时性的要求,和支持的中断源的多少,各自发明了各自处理中断,找到中断编号,进一步找到对应isr地址的方式。

S5PV210中断处理的主要寄存器

VICnINTENABLE和VICnINTENCLEAR (1)VICnINTENABLE 对应interrupt enable,INTENCLEAR对应interrupt enable clear (2)INTENABLE寄存器负责相应的中断的使能,INTENCLEAR寄存器负责相应的中断的禁止。 (3)当我们想使能(意思就是启用这个中断,意思就是当硬件产生中断时CPU能接收的到)某个中断时,只要在这个中断编号对应的VICnINTENABLE的相应bit位写1即可(注意这个位写1其他位写0对其他位没有影响);如果我们想禁止某个中断源时,只要向VICnINTENCLEAR中相应的位写1即可。 注意:这里的设计一共有2种:有些CPU是中断使能和禁止是一个寄存器位,写1就使能写0就进制(或者反过来写1就进制写0就使能),这样的中断使能设计就要非常小心,要使用我们之前说过的读改写三部曲来操作;另一种就是使能和禁止分开为2个寄存器,要使能就写使能寄存器,要禁止就写禁止寄存器。这样的好处是我们使能/禁止操作时不需要读改写,直接写即可。

VICnINTSELECT (1)设置各个中断的模式为irq还是fiq。一般都设置成irq (2)IRQ和FIQ究竟有何区别。210中支持2种中断,irq和fiq。irq是普通中断,fiq是快速中断。快速中断提供一种更快响应处理的中断通道,用于对实时性要求很高的中断源。fiq在CPU设计时预先提供了一些机制保证fiq可以被快速处理,从而保证实时性。fiq的限制就是只能有一个中断源被设置为fiq,其他都是irq。 (3)CPU如何保证fiq比irq快?有2个原因:第一,fiq模式有专用的r8~r12,因此在fiq的isr中可以直接使用r8-r12而不用保存,这就能节省时间;第二,异常向量表中fiq是最后一个异常向量入口。因此fiq的isr不需要跳转,可以直接写在原地,这样就比其他异常少跳转一次,省了些时间。

VICnIRQSTATUS和VICnFIQSTATUS (1)中断状态寄存器,是只读的。当发生了中断时,硬件会自动将该寄存器的对应位置为1,表示中断发生了。软件在处理中断第二阶段的第一阶段,就是靠查询这个寄存器来得到中断编号的。

另外,因为优先级也不能用1或者0来表示,理论上,有128种中断,所以需要0—127个数来表示优先级。不过,ARM中只支持16个层级的优先级,即0—15,所以用给每一个中断配备了一个寄存器来表示优先级,不过实际只用了4位二进制。

 

VICnVECTPRIORITY0~VICnVECTPRIORITY31 (1)中断优先级设置寄存器,设置多个中断同时发生时先处理谁后处理谁的问题。一般来说高优先级的中断可以打断低优先级的中断,从而嵌套处理中断。当然了有些硬件/软件可以设置不支持中断嵌套。

VICnVECTADDR0~VICnVECTADDR31、VICnADDRESS (1)这三个寄存器和210中断处理第二阶段的第二阶段有关。 (2)VICnVECTADDR0到31这32个寄存器分别用来存放真正的各个中断对应的isr的函数地址。相当于每一个中断源都有一个VECTADDR寄存器,程序员在设置中断的时候,把这个中断的isr地址直接放入这个中断对应的VECTADDR寄存器即可。 (3)VICnADDRESS这个寄存器是只需要读的,它里面的内容是由硬件自动设置的。当发生了相应中断时,硬件会自动识别中断编号,并且会自动找到这个中断的VECTADDR寄存器,然后将其读出复制到VICnADDR中,供我们使用。这样的设计避免了软件查找中断源和isr,节省了时间,提高了210的中断响应速度。(每个VIC下只有一个)

S5PV210中断处理的编程实践

中断控制器初始化 主要工作有:第一阶段绑定异常向量表到异常处理程序;禁止所有中断源;选择所有中断类型为IRQ;清理VICnADDR寄存器为0.

// 初始化中断控制器
void intc_init(void)
{
    // 禁止所有中断
	// 为什么在中断初始化之初要禁止所有中断?
	// 因为中断一旦打开,因为外部或者硬件自己的原因产生中断后一定就会寻找isr
	// 而我们可能认为自己用不到这个中断就没有提供isr,这时它自动拿到的就是乱码
	// 则程序很可能跑飞,所以不用的中断一定要关掉。
	// 一般的做法是先全部关掉,然后再逐一打开自己感兴趣的中断。一旦打开就必须
	// 给这个中断提供相应的isr并绑定好。
    VIC0INTENCLEAR = 0xffffffff;
    VIC1INTENCLEAR = 0xffffffff;
    VIC2INTENCLEAR = 0xffffffff;
    VIC3INTENCLEAR = 0xffffffff;

    // 选择中断类型为IRQ
    VIC0INTSELECT = 0x0;
    VIC1INTSELECT = 0x0;
    VIC2INTSELECT = 0x0;
    VIC3INTSELECT = 0x0;

    // 清VICxADDR
    intc_clearvectaddr();
}

void intc_clearvectaddr(void)
{
    // VICxADDR:当前正在处理的中断的中断处理函数的地址
    VIC0ADDR = 0;
    VIC1ADDR = 0;
    VIC2ADDR = 0;
    VIC3ADDR = 0;
}

中断的使能与禁止 思路是先根据中断号判断这个中断属于VIC几,然后在用中断源减去这个VIC的偏移量,得到这个中断号在本VIC中的偏移量,然后1<<x位,写入相应的VIC的INTENABLE/INTENCLEAR寄存器即可。

// 使能中断
// 通过传参的intnum来使能某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_enable(unsigned long intnum)
{
    unsigned long temp;
	// 确定intnum在哪个寄存器的哪一位
	// <32就是0~31,必然在VIC0
    if(intnum<32)
    {
        temp = VIC0INTENABLE;
        temp |= (1<<intnum);		// 如果是第一种设计则必须位操作,第二种设计可以
									// 直接写。
        VIC0INTENABLE = temp;
    }
    else if(intnum<64)
    {
        temp = VIC1INTENABLE;
        temp |= (1<<(intnum-32));
        VIC1INTENABLE = temp;
    }
    else if(intnum<96)
    {
        temp = VIC2INTENABLE;
        temp |= (1<<(intnum-64));
        VIC2INTENABLE = temp;
    }
    else if(intnum<NUM_ALL)
    {
        temp = VIC3INTENABLE;
        temp |= (1<<(intnum-96));
        VIC3INTENABLE = temp;
    }
    // NUM_ALL : enable all interrupt
    else
    {
        VIC0INTENABLE = 0xFFFFFFFF;
        VIC1INTENABLE = 0xFFFFFFFF;
        VIC2INTENABLE = 0xFFFFFFFF;
        VIC3INTENABLE = 0xFFFFFFFF;
    }

}

// 禁止中断
// 通过传参的intnum来禁止某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_disable(unsigned long intnum)
{
    unsigned long temp;

    if(intnum<32)
    {
        temp = VIC0INTENCLEAR;
        temp |= (1<<intnum);
        VIC0INTENCLEAR = temp;
    }
    else if(intnum<64)
    {
        temp = VIC1INTENCLEAR;
        temp |= (1<<(intnum-32));
        VIC1INTENCLEAR = temp;
    }
    else if(intnum<96)
    {
        temp = VIC2INTENCLEAR;
        temp |= (1<<(intnum-64));
        VIC2INTENCLEAR = temp;
    }
    else if(intnum<NUM_ALL)
    {
        temp = VIC3INTENCLEAR;
        temp |= (1<<(intnum-96));
        VIC3INTENCLEAR = temp;
    }
    // NUM_ALL : disable all interrupt
    else
    {
        VIC0INTENCLEAR = 0xFFFFFFFF;
        VIC1INTENCLEAR = 0xFFFFFFFF;
        VIC2INTENCLEAR = 0xFFFFFFFF;
        VIC3INTENCLEAR = 0xFFFFFFFF;
    }

    return;
}


// 通过读取VICnIRQSTATUS寄存器,判断其中哪个有一位为1,来得知哪个VIC发生中断了
unsigned long intc_getvicirqstatus(unsigned long ucontroller)
{
    if(ucontroller == 0)
        return	VIC0IRQSTATUS;
    else if(ucontroller == 1)
        return 	VIC1IRQSTATUS;
    else if(ucontroller == 2)
        return 	VIC2IRQSTATUS;
    else if(ucontroller == 3)
        return 	VIC3IRQSTATUS;
    else
    {}
    return 0;
}

绑定自己实现的isr到VICnVECTADDR (1)搞清楚2个寄存器的区别:VICnVECTADDR和VICnADDR (1)VICVECTADDR寄存器一共有4×32个,每个中断源都有一个VECTADDR寄存器,我们应该将自己为这个中断源写的isr地址丢到这个中断源对应的VECTADDR寄存器中即可。

// 绑定我们写的isr到VICnVECTADDR寄存器
// 绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理
// 等发生相应中断的时候,我们直接到相应的VICnADDR中去取isr地址即可。
// 参数:intnum是int.h定义的物理中断号,handler是函数指针,就是我们写的isr

// VIC0VECTADDR定义为VIC0VECTADDR0寄存器的地址,就相当于是VIC0VECTADDR0~31这个
// 数组(这个数组就是一个函数指针数组)的首地址,然后具体计算每一个中断的时候
// 只需要首地址+偏移量即可。
void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{
    //VIC0
    if(intnum<32)
    {
        *( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;
    }
    //VIC1
    else if(intnum<64)
    {
        *( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
    }
    //VIC2
    else if(intnum<96)
    {
        *( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;
    }
    //VIC3
    else
    {
        *( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;
    }
    return;
}

真正的中断处理程序如何获取isr (1)当发生中断时,硬件会自动把相应中断源的isr地址从VICnVECTADDR寄存器中推入VICnADDR寄存器中,所以我们第二阶段的第二阶段isr_handler中,只需要到相应的VICnADDR中去拿出isr地址,调用执行即可。

// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场
void irq_handler(void)
{
	printf("irq_handler.\n");
	// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断
	// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来
	// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断
	// 对应的isr。
	
	
	// 虽然硬件已经自动帮我们把isr放入了VICnADDR中,但是因为有4个,所以我们必须
	// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICADDR寄存器中
	unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
    int i=0;
    void (*isr)(void) = NULL;

    for(i=0; i<4; i++)
    {
		// 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0
        if(intc_getvicirqstatus(i) != 0)
        {
            isr = (void (*)(void)) vicaddr[i];
            break;
        }
    }
    (*isr)();		// 通过函数指针来调用函数
}

总结:第4步绑定isr地址到VICnVECTADDR和第5步中断发生时第二阶段的第二阶段如何获取isr地址,这两步是相关的。这两个的结合技术,就是我们一直在说的210的硬件自动寻找isr的机制。

整个中断的工作分为2部分: 第一部分是我们为中断响应而做的预备工作: 1. 初始化中断控制器 2. 绑定写好的isr到中断控制器 3. 相应中断的所有条件使能 第二部分是当硬件产生中断后如何自动执行isr: 1. 第一步,经过异常向量表跳转入IRQ/FIQ的入口 2. 第二步,做中断现场保护(在start.S中),然后跳入isr_handler 3. 第三步,在isr_handler中先去搞清楚是哪个VIC中断了,然后直接去这个VIC的ADDR寄存器中取isr来执行即可。 4. 第四步,isr执行完,中断现场恢复,直接返回继续做常规任务。

外部中断

什么是外部中断?数据手册在哪里? (1)SoC支持的中断类型中有一类叫外部中断。内部中断就是指的中断源来自于SoC内部(一般是内部外设),譬如串口、定时器等部件产生的中断;外部中断是SoC外部的设备,通过外部中断对应的GPIO引脚产生的中断。 (2)按键在SoC中就使用外部中断来实现。具体实现方法是:将按键电路接在外部中断的GPIO上,然后将GPIO配置为外部中断模式。此时人通过按按键改变按键电路的电压高低,这个电压高低会触发GPIO对应的外部中断,通过引脚传进去给CPU处理。 (3)外部中断相关的介绍和寄存器都在2.2.6章节(属于GPIO部分)

搜索:

GPIO某些端口设置成1111时,就会进入外部中断模式。

之后,找到对应的中断控制寄存器,进行配置。

 

电平触发和边沿触发 (1)外部中断的触发模式主要有2种:电平触发和边沿触发。 (1)电平触发就是说GPIO上的电平只要满足条件,就会不停触发中断。电平触发分为高电平触发和低电平触发。电平触发的特点是,只要电平满足条件就会不停触发中断。 (2)边沿触发分为上升沿触发、下降沿触发和双边沿触发三种。边沿触发不关心电平常规状态,只关心电平变化的瞬间(边沿触发不关心电平本身是高还是低,只关心变化是从高到低还是从低到高的这个过程)。 分析按键的工作:如果我们关注的是按键按下和弹起这两个事件本身,那么应该用边沿触发来处理按键;如果我们关心的是按键按下/弹起的那一段时间,那么应该用电平触发。

关键寄存器:CON、PEND、MASK (1)外部中断的主要配置寄存器有3个:EXT_CON、EXT_PEND、EXT_MASK (2)EXT_CON配置外部中断的触发方式。触发方式就是说外部电平怎么变化就能触发中断,也就是说这个外部中断产生的条件是什么

(3)EXT_PEND寄存器是中断挂起寄存器。这个寄存器中每一位对应一个外部中断,平时没有中断时值为0。当发生了中断后,硬件会自动将这个寄存器中该中断对应的位置1,我们去处理完这个中断后应该手工将该位置0。这个PEND寄存器的位就相当于是一个标志,如果发生了中断但是我们暂时忙来不及去处理时,这个位一直是1(这就是挂起),直到我有空了去处理了这个中断才会手工清除(写代码清除)这个挂起位表示这个中断被我处理了。

(4)EXT_MASK寄存器就是各个外部中断的使能/禁止开关

 

分析X210开发板的按键对应的EINT编号: EINT2、EINT3、EINT16、EINT17、EINT18、EINT19

外部中断对应的GPIO模式设置 中断触发模式设置 中断允许、清挂起 中断处理程序isr编写 总结对比:轮询方式处理按键和中断方式的差异

标签: 四线电阻触摸屏控制器usb1r8电容

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台