IMX6UL开发板的GPIO中断实验,编写中断向量表(一)

        那么就开始直接编写我们的中断向量表,中断向量表保存了各类中断处理程序入口地址的表格,通常包含处理器在发生中断或异常时跳转到的指令地址,它需要放在固定且易于访问的地方,处理器在发生中断或异常时,可以迅速找到对应的中断服务例程等原因
        然后先介绍一下,Cortex-A所有的中断向量表,Corterx-A7内核有8个异常中断,中断向量表中都是中断服务函数的入口地址

        1. 复位中断(Rest),CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面
做一些初始化工作,比如初始化 SP 指针、DDR 等等。
        2. 未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
        3. 软中断(Software Interrupt,SWI),由 SWI 指令引起的中断,Linux 的系统调用会用 SWI
指令来引起软中断,通过软中断来陷入到内核空间。
        4. 指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
        5. 数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
        6. IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此
中断的发生。
        7. FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。

        根据上面的表的内容创建中断向量表,中断向量表位于程序最开始的地方,在这里就是start.S文件的最前面,在ldr pc那里就是中断向量表,当指定的中断发生以后就会调用对应的中断复位函数,比如复位中断发生以后就会执行ldr pc, =Reset_Handler,也就是调用函数Reset_Handler,函数Reset_Handler就是复位中断的中断复位函数,对于其它的中断也是一样的
        而下面的代码则是,对应的中断服务函数,中断服务函数都是用汇编编写的,我们实际需要编写的只有复位中断服务函数Reset_Handler和IRQ中断服务函数IQR_Handler,其他的中断暂时我们也不会使用到

        首先,ldr pc, =Reset_Handler,是加载特定中断处理程序的入口地址到程序计数器pc,从而跳转到Reset_Handler函数执行复位处理过程,用于从中断向量表条目跳转到对应的中断处理函数,确保处理器能够处理各种中断。这种写法提供了一个基本的中断响应框架,同时允许开发者在有需要时替换掉默认的处理程序,添加实际的中断处理逻辑。
        

_start:

	ldr pc, =Reset_Handler
	ldr pc, =Undefined_Handler
	ldr pc,	=SVC_Handler
	ldr pc, =PreAbort_Handler
	ldr pc, =DataAbort_Handler
	ldr pc, =NotUsed_Handler
	ldr pc, =IRQ_Handler
	ldr pc, =FIQ_Handler	

	/*复位中断 */
	Reset_Handler:
		/*复位中断具体处理过程 */

	/*未定义中断 */
	Undefined_Handler:
		ldr r0, =Undefined_Handler
		bx  r0

	/*SVC中断 */
	SVC_Handler:
		ldr r0, =SVC_Handler
		bx  r0

	/*预取终止中断 */
	PreAbort_Handler:
		ldr r0, =PreAbort_Handler
		bx  r0

	/*数据终止中断 */
	DataAbort_Handler:
		ldr r0, =DataAbort_Handler
		bx  r0

	/*未使用的中断 */
	NotUsed_Handler:
		ldr r0, =NotUsed_Handler
		bx  r0

	/*IRQ中断 */
	IRQ_Handler:	
		/*复位中断具体处理过程 */

	/*FIQ中断 */
	FIQ_Handler:
		ldr r0, =FIQ_Handler
		bx  r0

        这几段代码的意思便是,因为目前我们主要会使用到的只有,复位中断和IRQ中断,对于其他的中断都不会使用到,,正常情况下不会进入那些中断处理函数(比如 Undefined_Handler)。这些函数通常是用来处理异常或未定义的行为的,所以这里不编写其它的中断类型,所以就写成死循环的形式
        但是之前这段代码有一个困扰博主的地方在于,如果我在中断服务函数中写入了死循环,那么程序在编译的过程当中,就无法继续往下编译执行,但是后面理解了一下,是博主对编译和程序的理解而不够
        程序在编译时,并不会因为在中断处理函数里有死循环而影响的编译。编译器在编译代码时,主要是将源代码翻译为机器代码(也就是可执行指令),并不会实际执行这些代码。只有在程序运行时,处理器才会真正进入这些中断服务程序,并执行其中的指令。
        例如只要不产生,Undefined_Handler类型的中断信号,便不会找到中断向量表中对应跳转的中断处理函数

        首先我们需要在复位中断服务函数中,清除Icache、Dcache、MMU,具体是什么目前不需要刻去了解,因为涉及到非常底层知识点,其实这三个在程序的内部BootRom已经将它关闭了,但是我们的目的就是让大家学会如何去关闭和打开他们
        要关闭这些,还需要引入一个名字叫做CP15的寄存器,也叫CP15协处理器,CP15一共有16个32的位的寄存器,虽然一般来说它的主要作用还是存储系统管理,但是对于中断还是可以使用,

        访问CP15下的协处理器下的寄存器也就是C0-C15,它都有特定的格式写法,CP15协处理器,使用MCR就是将CP15协处理器的寄存器的值读取ARM寄存器中,ARM寄存器也就是平时我们使用的r0到r15
        那么MCR则是刚好相反,用于读取CP15协处理器
        

        既然我们要关闭上面的Icache、Dcache、MMU,我们就需要知道在CP15协处理器中是谁在进行控制,这三个的控制寄存器
        首先找到此SCTLR,这个是系统控制寄存器,这个寄存器控制着他们的打开和关闭

        bit0就是控制MMU的开启和关闭,bit1待会儿我们也开始关闭,bit2负责Dcache的关闭和打开

        bit11是用来控制分支预测,也就是如果前面的MMU被打开过后,这个地方也会一定被打开所以也需要将bit11也关闭
        然后12位就是控制Icache的开启与关闭,目前我们就知道了SCTLR的哪些位负责控制他们的开启与关闭,但是现在我们需要知道如何对这个寄存器进行读写

        在这个寄存器下方有描述,<Rt>就是选择读取到的ARM寄存器,也就是r0到r15,在说明后面也有解释,Read System Control Register对应MRC也就是读取此寄存器,Write System Control Register,也就是MCR ,读取此寄存器

        在代码是如下体现的,那么在完成了Icache等的清楚过后,就需要设置中断向量表的地址偏移,对于IMX6ul的地址偏移要从0x87000000开始

Reset_Handler:


		MRC p15, 0, r0, c1, c0, 0

		bic	r0 , r0, #(0x1 << 12)	/*关闭Icache*/

		bic r0 , r0 ,#(0x1 << 11)	/*关闭分支预测 */

		bic r0 , r0 ,#(0x1 << 2)	/*关闭Dcache */

		bic r0 , r0 ,#( 1 <<  1)	/*关闭对齐 */

		bic r0 , r0 ,#( 1 <<  0)	/*关闭MMU */

		MCR p15, 0 , r0, c1, c0, 0 


        设置向量偏移,没有具体的位置,只要保证在产生中断之前,能够偏移向量就可以。
        将新的中断向量表首地址写入到CP15协处理器的VBAR寄存器,翻译过来名字就是向量基址寄存器,至于如何去设置这个寄存器


        还是按照上面的公式,首先在r0中存入基地址,dsb(Data Synchronization Barrier)指令用于确保所有在dsb之前的内存访问操作都已完成,并且它们的结果对所有观察者都是可见的。这通常用于确保在修改系统控制寄存器之前,所有相关的数据都已正确写入内存。
        mcr指令用于将数据从通用寄存器移动到协处理器寄存器。将r0寄存器中的值(即0x87800000)写入到协处理器p15的寄存器中。具体地,它写入到协处理器p15c12(性能监视和控制寄存器组)的c0(通常是该组中的第一个寄存器)中,偏移量为0
        isb(Instruction Synchronization Barrier)指令用于确保在isb之后的指令在执行前,所有之前的指令(包括那些可能影响指令流或内存访问的指令)都已完成。这通常用于在修改系统控制寄存器后,确保处理器正确地重新解释后续指令。
        在修改系统控制寄存器之后,再次使用dsbisb指令来确保所有之前的操作都已完成,并且处理器将基于新的配置来重新解释和执行后续指令。

	ldr r0, =0X87800000
	dsb
	isb
	mcr	p15, 0,r0, c12, c0, 0
	dsb
	isb

        然后,这里需要提到一个概念,在ARM架构中,处理器支持多种操作模式,每种模式都有其特定的用途和限制。这些模式包括用户模式(User)、快速中断模式(FIQ)、中断请求模式(IRQ)、系统模式(SYS)、超级用户模式(Supervisor,也称为SVC模式)、未定义指令模式(Undefined)和终止模式(Abort)

这里做三个模式的说明
用户模式:正常程序执行的模式,具有最低的权限
中断模式(IRQ/FIQ):用于处理外部中断或快速中断
SVC模式:操作模式内核使用的模式,用于系统调用和异常处理

        为什么我们需要反复切换模式呢,原本普遍设置的模式都是SVC模式,当处理器进入中断模式时,它会自动保存当前执行状态的上下文(如程序计数器、状态寄存器等),并切换到中断模式的堆栈。这样,当中断处理完成后,处理器可以轻松地恢复到中断发生前的状态,继续执行被中断的代码。
        中断模式通常具有比用户模式更高的权限,这允许中断服务例程执行一些特权操作,如直接访问硬件寄存器。同时,由于中断处理代码在中断模式下运行,它与其他任务或程序在逻辑上是隔离的,这有助于减少冲突和错误。将中断处理代码放在特定的中断模式中,还可以针对该模式进行优化。例如,可以优化中断向量的布局以减少跳转延迟,或者优化中断服务例程的代码以减少执行时间。
        在复杂的嵌入式系统或多任务操作系统中,不同的任务或程序可能需要同时运行。为了确保它们不会相互干扰,系统需要提供一种机制来隔离这些任务或程序。所以,通过多种模式的切换,来实现隔离
        但隔离是如何实现的呢,通过在不同的模式下运行不同的任务或程序,可以实现逻辑上的隔离。每个模式都有自己的堆栈、寄存器和权限级别,这有助于防止任务或程序之间的数据冲突和权限越界。中断和异常处理机制也可以用于实现隔离。当发生中断或异常时,处理器会切换到特定的中断或异常模式来处理这些事件。这有助于将中断处理代码与主程序代码隔离开来,减少它们之间的干扰和冲突。
        

        那么为什么都要设置堆栈指针呢,堆栈管理在ARM架构(以及许多其他处理器架构)中扮演着至关重要的角色,尤其是在处理中断、函数调用和确保系统稳定性方面。
        当程序执行到函数调用时,处理器需要保存当前函数的执行环境(包括返回地址、参数、局部变量等),以便在函数返回时能够恢复到调用前的状态。这些数据被存储在调用堆栈上,每个函数调用都会在其上分配一定的空间。如果堆栈指针不正确,可能会导致数据被错误地保存到无效的内存位置,或者在函数返回时无法正确恢复执行环境。
        当中断发生时,处理器同样需要保存当前任务的执行环境,以便在中断处理完成后能够恢复到中断前的状态。这包括保存程序计数器(PC)、程序状态寄存器(PSR,在ARM中可能是CPSR或SPSR)以及所有或部分通用寄存器的内容。这些数据也被存储在堆栈上,但通常是在与主程序堆栈不同的中断堆栈上。如果中断堆栈指针不正确,可能会导致中断处理过程中数据丢失或损坏,进而影响系统的稳定性和可靠性。
        并且在ARM架构中,每个模式(如用户模式、系统模式、中断模式等)通常都有自己的堆栈指针。这是因为不同的模式可能需要同时运行或交替运行,而它们的执行环境和数据是相互独立的。如果所有模式都使用相同的堆栈指针,那么它们可能会相互覆盖堆栈上的数据,导致数据损坏和不可预测的行为。通过为每个模式分配独立的堆栈空间,并设置正确的堆栈指针,可以确保每个模式都有自己独立的堆栈区域,从而避免数据冲突和损坏。

        所以,为了实现多模式切换并确保每个模式在执行时都有独立的执行环境和堆栈空间,系统需要为每个模式分配一个独立的堆栈,并且每个模式都需要一个对应的堆栈指针(SP, Stack Pointer)来指向该模式的堆栈顶部。每种模式在执行时都可能需要保存和恢复其特定的执行环境,包括寄存器值、程序计数器和堆栈内容等。
        为了确保这些模式之间不会相互干扰,特别是在中断处理和上下文切换时,每个模式都需要有自己的堆栈空间。这样,当从一种模式切换到另一种模式时,处理器可以使用相应模式的堆栈指针来访问和修改该模式的堆栈内容,而不会影响到其他模式的堆栈。
        在操作系统或嵌入式系统的初始化阶段,通常会为每个模式设置堆栈指针的初始值,并分配足够的堆栈空间。这些堆栈空间可以是静态分配的(在编译时确定大小和位置),也可以是动态分配的(在运行时根据需要分配)。无论哪种方式,都需要确保堆栈空间足够大,以容纳该模式在执行过程中可能产生的所有数据。

        所以模式切换代码如下,因为还是需要进入SVC模式,所以将SVC的堆栈指针放在最前面,然后在SVC模式结束后,就直接跳转到C语言,也就是main函数

	/*设置处理器进入IRQ模式 */

	mrs r0, cpsr

	bic r0, r0, #0x1f

	orr r0, r0, #0x12

	msr cpsr, r0

	ldr sp, =0x80600000



	/*设置处理器进入SYS模式 */

	mrs r0, cpsr

	bic r0, r0, #0x1f

	orr r0, r0, #0x1f

	msr cpsr, r0

	ldr sp, =0x80400000



	/* 进入SVC模式 */

	mrs r0, cpsr

	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/

	orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/

	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/

	ldr sp,=0X80200000	/* 设置用户模式下的栈首地址为0X80200000,大小为2MB */



	b main				/* 跳转到main函数 		

        所以具体代码就是如上,能够在各个模式之间反复切换,这一节就是对于中断向量表的具体编写,处于篇幅长度的考虑,这一节先完成Start.s的中断向量表的编写,下一节就开始具体在代码中完成中断的具体外设实验体现

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值