11_GPIO中断

发布时间:2022-08-18 18:25

第十一章 GPIO中断

1.1 GPIO中断介绍(通用的概念)

​ 假设你现在正在写作业,突然电话响起,你需要停下写作业接电话,挂电话后继续写作业。突然由人按门铃,你需要先去开门,然后继续回来写作业。电话和门铃打断了写作业,能中断写作业的事情有很多,比如身体不舒服,口渴等。被打断后怎么做?身体不舒服就停下写作业休息一会,身体好了继续写作业。口渴就停下写作业喝水,喝完水继续写作业。如果你正在接一个很重要的电话,突然门铃响了,这是会优先处理其中一件事,比如先让按门铃的人等一下,挂电话后再去开门,或者先挂电话,等开门后再打电话过去。这就存在一个中断优先级的问题。

​ 当有事件产生,处理事件之前我们需要记住现在作业写到第几页了,或者在作业上记一个标记,然后取处理事件,电话铃响了需要到放电话的地方去,门铃响了需要到门口去,口渴需要到放饮水机地方去,也就是说,不同的突发事件需要到不同的地方去处理。

​ 嵌入式系统中也有类似的情况。CPU在运行过程中,也会被各种异常打断。这些异常

​ ① 指令未定义

​ ② 指令、数据访问有问题

​ ③ SWI(软中断)

​ ④ 快中断

​ ⑤ 中断

​ 中断也属于一种异常,导致中断发生的中断源有很多,比如:

​ ① 按键

​ ② 定时器

​ ③ ADC转换完成

​ ④ UART发生完数据、接收数据

​ ⑤ 等等

​ 这些众多的中断源,汇集中中断管理器,由中断管理器选择优先级最高的中断并通知CPU。CPU会根据中断的类型到跳转到不同的地址处理中断。发生中断后,CPU并不是随便跳到一个地址处理中断,而是根据异常向量表,跳转到对应的地址处理中断。

1.2.1 GPIO中断

​ GPIO中断,指有GPIO模块产生的中断,有边沿触发中断或者电平翻转中断。GPIO模块能检测到引脚上的值是0还是1,并能通过外部拓展将电平从变为1或是从1变到0。CPU接收外部的中断请求,并进行处理,其实是一个被动接受的过程,这样的好处是己能保证主任务的执行效率,又能及时获取外部请求,从而处理重要的设备请求中断。

​ 当GPIO模块检测到管脚电平变化且满足中断触发条件,就会触发中断,CPU会跳转到中断处理地址进行中断处理,为了避免破坏主任务数据,CPU会处理保存当前相关寄存器(保存现场)并进入中断服务函数,执行完中断服务函数后,CPU会恢复相关寄存器(恢复现场),回到主任务继续执行程序。

​ 程序发生GPIO中断后会根据异常向量表强制跳转到0x18(IRQ中断地址)。如下图:
11_GPIO中断_第1张图片

​ 异常向量表并不总是从0地址开始,IMX6ULL可以设置vector base寄存器,指定向量表在其他位置,比如设置 vector base 为 0x80000000,指定为 DDR 的某个地
址。但是表中的各个异常向量的偏移地址,是固定的:复位向量偏移地址是 0,中断是 0x18。

​ 本次实验使用GPIO中断方式实现按键控制LED亮灭,并通过串口把中断ID打印出来。

​ 中断控制器和CP15协处理器

​ 操作系统中,中断系统是很重要的一部分。有了中断系统,才不用一直轮询是否有事件发生,系统效率得以提高。中断系统一般分为三个部分:模块、中断管理器和处理器。模块通常有寄存器设置是否使能中断和中断触发方式。中断控制器可以管理中断优先级等。处理器则设置寄存器响应中断。

11_GPIO中断_第2张图片

​ 如上图所示,硬件中断信号发送GIC(Generic Interrupt Controller),GIC产生一个FIQ或IRQ信号给CPU。GPIO模块、UART模块均能产生硬件中断。在初始化中断时,要初始化GIC中断控制器,如果时GPIO中断则还要设置GPIO模块内相关的寄存器,如果时串口中断则还要设置UART模块内相关的寄存器。

1.2 GIC中断控制器介绍

1.2.1 IMX6ULL GIC中断控制器

​ IMX6ULL是Cortex-A7内核,采用GIC V2(Generic Interrupt Controller)中断控制器。在这里只简单的介绍一下GIC,具体可以参考arm文档。

​ GIC的主要作用可以归结为接受硬件中断信号,并进行简单的处理,按照一定的设置策略,分给对应的CPU处理。如下图:

11_GPIO中断_第3张图片

​ ARM内核只提供了四个信号给GIC汇报中断情况:VIRQ(虚拟快速IRQ)、VFIQ(虚拟快速FIQ)、IRQ、FIQ。VIRQ、VFIQ是针对虚拟化,剩下就是IRQ和FIQ。GPIO中断属于IRQ中断,所以在本次实验中GIC上报IRQ信号给ARM内核。

​ 接下来看一下GIC内部过程,如下图:

11_GPIO中断_第4张图片

​ 中断源分为SPI(Shared Peripheral Interrupt)、PPI(Private Peripheral Interrupt)、SGI request(Software-generated Interrupt)。外部中断都属于SPI中断源。

​ GIC控制器包括分发器(Distributor)和CPU接口端(CPU interface)。

​ 分发器(Distributor)主要完成对整个中断控制器使能,设置中断优先级,设置中断触发方式,决定每个中断信号发送到哪一个具体的CPU上执行。

​ CPU接口端(CPU interface)主要完成使能和发送一个具体的中断信号到特定的CPU上,确认中断已被CPU接受、处理以及处理完成,设置CPU能接受中断的优先级以及基于级别的中断抢占。

​ 中断信号先到分发器,根据设定CPU,发送到CPU对应的interface上,在这里判断是否优先级足够高,能否抢断或打断当前的终端处理,如果可以,CPU interface就会发送一个物理的signa到CPU的IRQ线上,CPU接收到中断信号,转到中断处理模式进行处理。

1.2.2 IMX6ULL GIC中断寄存器

​ GIC寄存器分为Distributor register和CPU interface register。寄存器数目较多,这里介绍本次实验中需要我们设置的寄存器。

1.2.2.1 GICC_IAR寄存器

​ GICC_IAR寄存器属于CPU interface register,作用是:保存中断ID,读取GICC_IAR寄存器可以获得中断ID,这个过程可以当作对中断的确认。

1.2.2.2 GICC_EOIR寄存器

11_GPIO中断_第5张图片

​ GICC_EOIR寄存器属于CPU interface register,作用是:中断完成时,向GICC_EOIR写入中断ID,表示IRQ处理结束。

1.2.3 CP15协处理器

1.2.3.1 CP15协处理器介绍

​ 在基于ARM的嵌入式系统中,存储系统通常是系统控制协处理器CP15完成的。ARM处理器使用协处理器指令MCR和MRC来读写寄存器,控制cache、MMU、配置时钟(在bootloader时钟初始化时会用到)等。CP15包含16个32位寄存器,编号为0~15。

​ 在本次实验中,需要设置的寄存器有:SCTLR(System Control Register)寄存器,VBAR(Vector Base Address)寄存器。

1.2.3.2 SCTLR(System Control Register)寄存器

​ 设置SCTLR寄存器可以控制cache、MMU等。

11_GPIO中断_第6张图片

11_GPIO中断_第7张图片

11_GPIO中断_第8张图片

11_GPIO中断_第9张图片

11_GPIO中断_第10张图片

​ Bit[13]: 异常向量表地址设置位。我们设置为0,默认0x00000000地址,可以通过设置vector base寄存器映射到设置地址。

​ Bit[12]、Bit[2]: 指令cache、数据cache使能位。刚上电时,CPU还不能管理cache,指令cache可关闭也可不关闭,但数据cache一定要关闭,否争可能导致刚开始的代码里,去读取数据时到cache里读取,而这时候RAM数据还没有cache过来,导致数据预取错误。

​ Bit[11]: 分支预测使能位。分支预测技术是用来提高执行流水线指令效率。在本次实验中关闭分支预测技术。

​ Bit[1]: 字节对齐设置位。打开字节对齐,可以提高CPU访问效率,但会损失一部分内存空间。在本次实验中 CPU并不会做太多复杂的工作,所以关闭字节对齐。

​ Bit[0]: MMU使能位。上电后系统没有配置MMU,所以要先关掉MMU。

​ MRC p15, 0, < Rt >, c1, c0, 0: 把SCTLR寄存器的值读到ARM寄存器Rt中。

​ MRC p15, 0, < Rt >, c1, c0, 0: 把ARM寄存器Rt的值写入SCTLR寄存器。

1.2.3.3 VBAR(Vector Base Address)寄存器

​ 设置VBAR寄存器,可以设置异常向量表的映射地址。如果不把异常向量表的映射地址告诉CPU,在发生异常时,CPU就找不到异常向量表,就无法处理异常。

11_GPIO中断_第11张图片

11_GPIO中断_第12张图片

​ MRC p15, 0, < Rt >, c12, c0, 0: 把VBAR寄存器的值读到ARM寄存器Rt中。

​ MRC p15, 0, < Rt >, c12, c0, 0: 把ARM寄存器Rt的值写入VBAR寄存器。

1.3 IMX6ULL的GPIO中断寄存器介绍

1.3.1 GPIO interrupt configuration register1 (GPIOx_ICR1)

​ GPIO中断配置寄存器1

11_GPIO中断_第13张图片

​ ICRn[1:0]决定中断类型:

​ 00 低电平触发

​ 01 高电平触发

​ 10 上升沿触发

​ 11 下降沿触发

​ ICR0~ICR15对应GPIO interrupt 0-15

1.3.2 GPIO interrupt configuration register2 (GPIOx_ICR2)

​ GPIO中断配置寄存器2

11_GPIO中断_第14张图片

​ 与GPIOx_ICR1类似

​ ICR0~ICR15对应GPIO interrupt 16-31

1.3.3 GPIO interrupt mask register (GPIOx_IMR)

​ GPIO中断屏蔽寄存器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzczoE5q-1642060200367)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/11_GPIO_interrupt_image_018.png)]

​ Bit[n]对应interrupt n

​ 0 interrupt n屏蔽

​ 1 interrupt n 打开

1.3.4 GPIO interrupt status register (GPIOx_ISR)

​ GPIO中断状态寄存器

11_GPIO中断_第15张图片

​ 中断状态位-当在GPIO输入上检测到有效状态(由相应的ICR位确定)时,该寄存器的位n置为有效(高电平有效)。该寄存器的值与GPIO_IMR中的值无关。

​ 当检测到活动状态时,相应的位将保持置位状态,直到被软件清除为止。通过将1写入相应的位位置来清除状态标志。

1.3.5 GPIO edge select register (GPIOx_EDGE_SEL)

​ GPIO中断边沿选择寄存器

​ 设置GPIO_EDGE_SEL [n]时,GPIO会忽略ICR [n]设置,同时检测对应输入信号的上升沿和下降沿。

1.4 按键中断程序编程示例一

1.4.1 管脚设置和查询中断号

11_GPIO中断_第16张图片

​ 从上面的电路图可见KEY1接在GPIO5_1(SNVS_TAMPER1 pad,ALT5)上,KEY4接在GPIO4_14(NAND_CE1_B pad,ALT5)上。使用IOMUXC_SetPinMux设置这两个引脚为GPIO模式。如何获取这两个GPIO的中断号呢?查阅数据手册的chapter3,CORTEX A7interrupts章节,这两个GPIO的中断号如下表所示。对应到GIC的SPI中断号需要在此编号基础上加上32,所以KEY1对应的GIC interrupt ID为(74 + 32 = 106),KEY2对应的GIC interrupt ID为(72 + 32 = 104)。

11_GPIO中断_第17张图片

1.4.2 GIC控制器基地址的获取方法

​ 直接查数据手册 Table 2-1. System memory map

​ 可以知道gic的基地址是0xA0000

​ 对于gic控制器还有另一种方法,通过 CP15查询:

​ mrc p15, 4, r0, c15, c0, 0

​ 将gic的基地址通过mrc指令读取到r0寄存器。

1.4.3 GIC的初始化

​ 通过CP15获取GIC的基地址,读取GICD_TYPER寄存器获得中断的数目,往GICD_ ICENABLERn寄存器写入0xFFFFFFFF禁用所有的SGI,PPI和SPI。通过GICC_PMR设置优先级等级,设置为0xF8;将GICC_BPR设置为2,这允许各个优先级进行抢占。 最后使能group0的distributor和CPU interface。

​ 代码在**裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception/gic.c)**目录内:

void gic_init(void)
{
	u32 i, irq_num;

	GIC_Type *gic = get_gic_base();

	/* the maximum number of interrupt IDs that the GIC supports */
	irq_num = (gic->D_TYPER & 0x1F) + 1;

	/* On POR, all SPI is in group 0, level-sensitive and using 1-N model */
	
	/* Disable all PPI, SGI and SPI */
	for (i = 0; i < irq_num; i++)
	  gic->D_ICENABLER[i] = 0xFFFFFFFFUL;

	/* The priority mask level for the CPU interface. If the priority of an 
	 * interrupt is higher than the value indicated by this field, 
	 * the interface signals the interrupt to the processor.
	 */
	gic->C_PMR = (0xFFUL << (8 - 5)) & 0xFFUL;

	/* No subpriority, all priority level allows preemption */
	gic->C_BPR = 7 - 5;
	
	/* Enables the forwarding of pending interrupts from the Distributor to the CPU interfaces.
	 * Enable group0 distribution
	 */
	gic->D_CTLR = 1UL;
	
	/* Enables the signaling of interrupts by the CPU interface to the connected processor
	 * Enable group0 signaling 
	 */
	gic->C_CTLR = 1UL;
}

1.4.4 中断异常处理汇编部分

​ 在异常向量表偏移为0x18的地方将pc设置为IRQ_Handler标号的位置,跳转到IRQ_Handler标号位置执行,处理器处于中断模式,lr_irq保存了被中断模式中的下一条即将执行的指令的地址,将lr减去4,将r0-r12和lr保存在栈上,用bl指令调用C函数handle_irq_c,C函数返回来后将r0-r12从栈上弹出,栈上的lr弹出到PC,并将SPSR拷贝到CPSR,返回被打断的指令继续执行。在reset handler里需要设置好irq模式的栈,这样在中断模式里才可以调用C函数,同时调用cpsie i打开中断。使用如下两条指令设置异常向量的基地址

ldr r0, =_vector_table
	mcr p15, 0, r0, c12, c0, 0  /* set VBAR, Vector Base Address Register*/

​ 汇编部分代码如下所示代码如下**裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception/**008_exception\start.S)文件:

.text
.global  _start, _vector_table
_start:
_vector_table:
	ldr 	pc, =Reset_Handler			 /* Reset				   */
	ldr 	pc, =Undefined_Handler		 /* Undefined instructions */
	ldr 	pc, =SVC_Handler			 /* Supervisor Call 	   */
	b halt//ldr 	pc, =PrefAbort_Handler		 /* Prefetch abort		   */
	b halt//ldr 	pc, =DataAbort_Handler		 /* Data abort			   */
	.word	0							 /* RESERVED			   */
	ldr 	pc, =IRQ_Handler			 /* IRQ interrupt		   */
	b halt//ldr 	pc, =FIQ_Handler			 /* FIQ interrupt		   */
………
.align 2
IRQ_Handler:
	/* 执行到这里之前:
	 * 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
 * 2. SPSR_irq保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
	 * 4. 跳到0x18的地方执行程序 
	 */

	/* 保存现场 */
	/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr-4是异常处理完后的返回地址, 也要保存 */
	sub lr, lr, #4
	stmdb sp!, {r0-r12, lr}  
	
	/* 处理irq异常 */
	bl handle_irq_c
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr_irq的值恢复到cpsr里 */	
.align 2
Reset_Handler:
	/* Reset SCTlr Settings */
	mrc 	p15, 0, r0, c1, c0, 0	  /* read SCTRL, Read CP15 System Control register		*/
	bic 	r0,  r0, #(0x1 << 13)	  /* Clear V bit 13 to use normal exception vectors  	*/
	bic 	r0,  r0, #(0x1 << 12)	  /* Clear I bit 12 to disable I Cache					*/
	bic 	r0,  r0, #(0x1 <<  2)	  /* Clear C bit  2 to disable D Cache					*/
	bic 	r0,  r0, #(0x1 << 2)	  /* Clear A bit  1 to disable strict alignment 		*/
	bic 	r0,  r0, #(0x1 << 11)	  /* Clear Z bit 11 to disable branch prediction		*/
	bic 	r0,  r0, #0x1			  /* Clear M bit  0 to disable MMU						*/
	mcr 	p15, 0, r0, c1, c0, 0	  /* write SCTRL, Write to CP15 System Control register	*/

    cps     #0x1B                /* Enter undef mode                */
    ldr     sp, =0x80300000     /* Set up undef mode stack      */

    cps     #0x12                /* Enter irq mode                */
    ldr     sp, =0x80400000     /* Set up irq mode stack      */

    cps     #0x13                /* Enter Supervisor mode         */
    ldr     sp, =0x80200000     /* Set up Supervisor Mode stack  */

	ldr r0, =_vector_table
	mcr p15, 0, r0, c12, c0, 0  /* set VBAR, Vector Base Address Register*/
	//mrc p15, 0, r0, c12, c0, 0  //read VBAR

	bl clean_bss

	bl system_init
	cpsie	i					 /* Unmask interrupts			  */

	bl main

halt:
	b  halt


clean_bss:
	/* 清除BSS段 */
	ldr r1, =__bss_start
	ldr r2, =__bss_end
	mov r3, #0
clean:
	cmp r1, r2
	strlt r3, [r1]
	add r1, r1, #4
	blt clean
	
	mov pc, lr

1.4.5 中断异常处理C函数部分

​ 获取到gic的基地址后,读取GICC_IAR获得中断号,根据中断号调用对应中断号的irq_handler函数,该函数是用户通过request_irq注册的中断处理函数,然后往GICC_EOIR写入中断号清除掉中断。

​ 代码在裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception\gic.c)

void handle_irq_c(void)
{
	int nr;

	GIC_Type *gic = get_gic_base();
	/* The processor reads GICC_IAR to obtain the interrupt ID of the
	 * signaled interrupt. This read acts as an acknowledge for the interrupt
	 */
	nr = gic-> C_IAR;
	printf("irq %d is happened\r\n", nr);
irq_table[nr].irq_handler(nr, irq_table[nr].param);

	/* write GICC_EOIR inform the CPU interface that it has completed 
	 * the processing of the specified interrupt 
	 */
	gic->C_EOIR = nr;
}

1.4.6 GPIO中断初始化和安装中断处理程序

​ 对于KEY1,将GPIO5_01通过EDGE_SEL设置成双边沿触发,通过IMR对应位设置为1打开中断,为了防止误触发将ISR对应位写1清除掉中断。然后调用request_irq注册对应中断的中断处理函数,对于GPIO5_01是key_gpio5_handle_irq,中断处理函数里根据按键按下和松开分别在串口打印,并且按下时绿灯点亮,松开时绿灯熄灭,并且往ISR对应位写1清掉中断,否则会一直触发中断。对于KEY2,和KEY1类似,按下和松开只会在串口进行打印。

​ 代码在裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception\main.c)

void key_gpio5_handle_irq(void)
{
	/* read GPIO5_DR to get GPIO5_IO01 status*/
	if((GPIO5->DR >> 1 ) & 0x1) {
		printf("key 1 is release\r\n");
		/* led off, set GPIO5_DR to configure GPIO5_IO03 output 1 */
		GPIO5->DR |= (1<<3); //led on
	} else {
		printf("key 1 is press\r\n");
		/* led on, set GPIO5_DR to configure GPIO5_IO03 output 0 */
		GPIO5->DR &= ~(1<<3); //led off
	}
	/* write 1 to clear GPIO5_IO03 interrput status*/
	GPIO5->ISR |= (1 << 1);
}
void key_gpio4_handle_irq(void)
{
	/* read GPIO4_DR to get GPIO4_IO014 status*/
	if((GPIO4->DR >> 14 ) & 0x1)
		printf("key 2 is release\r\n");
	else
		printf("key 2 is press\r\n");
	/* write 1 to clear GPIO4_IO014 interrput status*/
	GPIO4->ISR |= (1 << 14);
}

void key_irq_init(void)
{
	/* if set detects any edge on the corresponding input signal*/
	GPIO5->EDGE_SEL |= (1 << 1);
	/* if set 1, unmasked, Interrupt n is enabled */
	GPIO5->IMR |= (1 << 1);
	/* clear interrupt first to avoid unexpected event */
	GPIO5->ISR |= (1 << 1);

	GPIO4->EDGE_SEL |= (1 << 14);
	GPIO4->IMR |= (1 << 14);
	GPIO4->ISR |= (1 << 14);

	request_irq(GPIO5_Combined_0_15_IRQn, (irq_handler_t)key_gpio5_handle_irq, NULL);
	request_irq(GPIO4_Combined_0_15_IRQn, (irq_handler_t)key_gpio4_handle_irq, NULL);
}

1.4.7 特定中断号的中断使能和禁止

​ 以中断号调用gic_enable_irq,对应的中断在GIC中打开,通过往GICD_ISENABLERn对应的位写入1打开。以中断号调用gic_ disable _irq,对应的中断在GIC中关闭,通过往GICD_ICENABLERn对应的位写入1关闭。

​ 代码裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception/gic.c)

void gic_enable_irq(IRQn_Type nr)
{
	GIC_Type *gic = get_gic_base();

	/* The GICD_ISENABLERs provide a Set-enable bit for each interrupt supported by the GIC.
	 * Writing 1 to a Set-enable bit enables forwarding of the corresponding interrupt from the
	 * Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled.
	 */
	gic->D_ISENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));

}

void gic_disable_irq(IRQn_Type nr)
{
	GIC_Type *gic = get_gic_base();

	/* The GICD_ICENABLERs provide a Clear-enable bit for each interrupt supported by the
	 * GIC. Writing 1 to a Clear-enable bit disables forwarding of the corresponding interrupt from
     * the Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled. 
	 */
	gic->D_ICENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));
}

1.4.8 主函数调用

​ 调用system_init_irq_table初始化中断跳转表,key_irq_init初始化按键中断的GPIO配置和注册中断处理函数,通过gic_init初始化GIC控制器,最后通过gic_enable_irq使能按键对应GIC中断号使用的中断。

​ 代码裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception/main.c)

void system_init()
{
	init_pins();
	led_gpio_init();
	led_ctl(0);//turn off led
	boot_clk_gate_init();
	boot_clk_init();
	uart1_init();
	puts("hello world\r\n");
	system_init_irq_table();
	key_irq_init();
	gic_init();
	gic_enable_irq(GPIO5_Combined_0_15_IRQn);
	gic_enable_irq(GPIO4_Combined_0_15_IRQn);
}

1.4.9 参考章节《4-1.4编译程序》编译程序

​ 进入 **裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception)**源码目录

1.4.10 参考章节《4-1.4映像文件烧写、运行》烧写、运行程序

​ 此时观察串口打印

​ 按下KEY1,绿灯点亮,松开,绿灯熄灭,同时串口会打印按下松开的信息。按下或者松开KEY2,串口会打印出KEY2按下松开的信息。串口打印如下所示:

11_GPIO中断_第18张图片

1.5 按键中断编程示例二

1.5.1 按键中断程序编程

​ 此节代码在**裸机Git仓库 NoosProgramProject/(11_GPIO中断/011_gpio_eint)**目录内。

1.5.1.1 编写start.S

1.编写异常向量表

.text 	
.global  _start, _vector_table 	
_start: 	
_vector_table: 		
	ldr pc, =Reset_Handler			     
    /* Reset				   */ 		
    b halt							
    /* Undefined instructions */ 		
    b halt			 				 
    /* Supervisor Call 	      */ 		
    b halt		 					 
    /* Prefetch abort		  */ 		
    b halt		 					 
    /* Data abort			  */ 		
    .word	0						
    /* RESERVED			      */ 		
    ldr pc, =IRQ_Handler			     
    /* IRQ interrupt		  */ 		
    b halt			 				 
    /* FIQ interrupt		  */  	
.align 2 
IRQ_Handler: 		
	b halt  	
.align 2 	
    Reset_Handler: 		
	b halt  	
halt: 		
	b halt

​ 上电后,程序从_start地址开始执行,05行代码对应的是0x00地址,06行代码对应的是0x4,依次类推,11行代码对应的是0x18,与11.1.2章节介绍的异常向量表对应。在编程时,通常在异常向量表中放一条跳转指令,跳转去执行更复杂的操作。比如在IRQ_Handler函数中需要保存现场,等处理完异常后又需要恢复现场。

​ 程序在0x00地址通过ldr指令把Reset Handler的地址赋给pc,CPU跳转到Reset Handler运行,发生中断时,CPU跳转到IRQ_Handler运行。

2.编写复位中断函数

	Reset_Handler:
		/* Reset SCTlr Settings */
		mrc 	p15, 0, r0, c1, c0, 0	  	/* read SCTRL, Read CP15 System Control register	*/
		bic 	r0,  r0, #(0x1 << 13)	  	/* Clear V bit 13 to use normal exception vectors  	*/
		bic 	r0,  r0, #(0x1 << 12)	  	/* Clear I bit 12 to disable I Cache					*/
		bic 	r0,  r0, #(0x1 <<  2)	  	/* Clear C bit  2 to disable D Cache				*/
		bic 	r0,  r0, #(0x1 << 1)	  	  	/* Clear A bit  1 to disable strict alignment 		*/
		bic 	r0,  r0, #(0x1 << 11)	  	/* Clear Z bit 11 to disable branch prediction		*/
		bic 	r0,  r0, #0x1			  	/* Clear M bit  0 to disable MMU					*/
		mcr 	p15, 0, r0, c1, c0, 0	  	/* write SCTRL, Write to CP15 System Control register	*/

		cps     #0x1B				/* Enter undef mode               */
		ldr     sp, =0x80300000     	/* Set up undef mode stack      	*/

		cps     #0x12				/* Enter irq mode                	*/
		ldr     sp, =0x80400000	    /* Set up irq mode stack      		*/

		cps     #0x13				/* Enter Supervisor mode         	*/
		ldr     sp, =0x80200000     	/* Set up Supervisor Mode stack  	*/

		ldr r0, =_vector_table
		mcr p15, 0, r0, c12, c0, 0  		/* set VBAR, Vector Base Address Register*/
		//mrc p15, 0, r0, c12, c0, 0  		//read VBAR

		bl clean_bss
		
		bl system_init
		cpsie	i					 /* Unmask interrupts			  	*/

		bl main

	halt:
		b  halt


	clean_bss:
		/* 清除BSS段 */
		ldr r1, =__bss_start
		ldr r2, =__bss_end
		mov r3, #0
	clean:
		cmp r1, r2
		strlt r3, [r1]
		add r1, r1, #4
		blt clean
		
		mov pc, lr

​ 在Reset_Handler中,需要完成关闭Icache、Dcache、MMU等操作。通过CPS指令改变处理器状态,比如cps #0x1B进入undef mode,然后设置undef mode状态下的栈地址。CPS #0x12进入irq mode,设置irq mode状态下的栈地址。CPS #0x13进入Supervisor mode,设置Supervisor mode状态下的栈地址。设置不同模式下栈地址的目的是在调用C函数时,总有一些寄存器的值需要保存下来,如果直接跳转到子函数里去执行,很有可能就被破坏,因为子函数可能也会用到这些寄存器。

3.编写IRQ服务函数

IRQ_Handler: 	
/* 执行到这里之前: 	 
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址 	 
* 2. SPSR_irq保存有被中断模式的CPSR 	 
* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式 	 
* 4. 跳到0x18的地方执行程序  	 */  	
/* 保存现场 */ 	
/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */ 	
/* lr-4是异常处理完后的返回地址, 也要保存 */ 	
sub lr, lr, #4 	
stmdb sp!, {r0-r12, lr}   	 	

/* 处理irq异常 */ 	
bl handle_irq_c 	 	

/* 恢复现场 */ 	
ldmia sp!, {r0-r12, pc}^  /* ^会把spsr_irq的值恢复到cpsr里 */	

​ 在IRQ_Handler中,需要保存现场,因为在handle_irq_c函数中可能会有修改r0-r12寄存器。等异常处理完后,再恢复现场。保存现场、恢复现场都离不开栈,保存现场需要把寄存器的值一个个放入栈中,恢复现场则从栈中一个个读取寄存器的值。所以再Reset_Handler中提前设置IRQ mode下的栈地址,当然,在IRQ_Handler中在设置栈也可以,看个人习惯吧。

1.5.1.2 编写interrupt.c

​ 1.初始化GIC、使能中断并设置中断触发方式

void key_exit_init(void)
	{
		GPIO5_IMR								= (volatile unsigned int *)(0x20AC014);
		GPIO5_EDGE_SEL							= (volatile unsigned int *)(0x20AC01C);
		GPIO5_ISR								= (volatile unsigned int *)(0x20AC018);
		GPIO5_DR                                = (volatile unsigned int *)(0x20AC000);
		
		GPIO4_IMR								= (volatile unsigned int *)(0x20A8014);
		GPIO4_EDGE_SEL							= (volatile unsigned int *)(0x20A801C);
		GPIO4_ISR								= (volatile unsigned int *)(0x20A8018);
		GPIO4_DR								= (volatile unsigned int *)(0x20A8000);

		gic_init();
		gic_enable_irq(GPIO5_Combined_0_15_IRQn);
		gic_enable_irq(GPIO4_Combined_0_15_IRQn);

		/* 设置GPIOx_EDGE_SEL寄存器
		 * GPIO_EDGE_SEL bit is set, then a rising edge or falling edge in the corresponding
		 *      signal generates an interrupt.
		 * GPIO5_EDGE_SEL  0x20AC01C
		 * bit[1] = 0b1
		 * GPIO4_EDGE_SEL  0x20A801C
		 * bit[14] = 0b1
		 */
		*GPIO5_EDGE_SEL |= (1<<1);
		*GPIO4_EDGE_SEL |= (1<<14);

		/* 设置GPIOx_IMR寄存器
		 * GPIO_IMR contains masking bits for each interrupt line.
		 * GPIO5_IMR  0x20AC014
		 * bit[1] = 0b1
		 * GPIO4_IMR  0x20A8014
		 * bit[14] = 0b1
		 */
		*GPIO5_IMR |= (1<<1);
		*GPIO4_IMR |= (1<<14);
	}

​ 在key_exit_init函数中,首先调用gic_init函数对GIC中断控制器初始化,然后调用gic_enable_irq函数允许GPIO5_00GPIO5_15和GPIO4_00GPIO4_15管脚中断,这两个函数均根据官方SDK修改,具体的实现就是设置GIC寄存器,在这里不在详细分析。

​ 设置GPIOx_EDGE_SEL选择双边沿触发中断,再设置GPIOx_IMR寄存器,使能GPIO5_1(key1)、GPIO4_14(key2)管脚中断。

​ 2、编写中断服务C语言中断服务函数

void handle_irq_c(void)
	{
		int nr;

		GIC_Type *gic = get_gic_base();
		/* The processor reads GICC_IAR to obtain the interrupt ID of the
		 * signaled interrupt. This read acts as an acknowledge for the interrupt
		 */
		nr = gic->C_IAR;
		printf("irq %d is happened\r\n", nr);

		switch(nr)
		{
			case GPIO5_Combined_0_15_IRQn:
			{
				/* read GPIO5_DR to get GPIO5_IO01 status*/
				if((*GPIO5_DR >> 1 ) & 0x1) {
					printf("key 1 is release\r\n");
					/* led off, set GPIO5_DR to configure GPIO5_IO03 output 1 */
					led_ctl(0);
				} else {
					printf("key 1 is press\r\n");
					/* led on, set GPIO5_DR to configure GPIO5_IO03 output 0 */
					led_ctl(1);
				}
				/* write 1 to clear GPIO5_IO03 interrput status*/
				*GPIO5_ISR |= (1 << 1);
				break;
			}
			
			case GPIO4_Combined_0_15_IRQn:
			{
				/* read GPIO4_DR to get GPIO4_IO014 status*/
				if((*GPIO4_DR >> 14 ) & 0x1)
				{
					printf("key 2 is release\r\n");
					led_ctl(0);
				}
				else
				{
					printf("key 2 is press\r\n");
					led_ctl(1);
				}
				/* write 1 to clear GPIO4_IO014 interrput status*/
				*GPIO4_ISR |= (1 << 14);
				break;
			}

			default:
				break;
		}

		/* write GICC_EOIR inform the CPU interface that it has completed 
		 * the processing of the specified interrupt 
		 */
		gic->C_EOIR = nr;
	}

​ 在handle_irq_c函数中,通过读取GICC_IAR寄存器确定中断ID,得到中断ID后再判断此时管脚的电平状态,最后通过串口把中断ID打印出来,中断处理完成后不要忘了设置GPIOx_ISR寄存器,清除中断标志位,注意这是GPIO模块内的中断标志位寄存器,然后把中断ID写入GICC_EOIR寄存器,表示中断处理完成,这里时GIC中断控制器内的寄存器,清中断标志位一定要清除干净,注意分清楚GPIO内的中断标志位和GIC中断控制器内的中断标志寄存器。

1.5.2 上机实验

1.5.2.1 修改官方SDK文件

#include "gic.h"
#include "my_printf.h"

GIC_Type * get_gic_base(void)
{
	GIC_Type *dst;

	__asm volatile ("mrc p15, 4, %0, c15, c0, 0" : "=r" (dst)); 

	return dst;
}

void gic_init(void)
{
	u32 i, irq_num;

	GIC_Type *gic = get_gic_base();

	/* the maximum number of interrupt IDs that the GIC supports */
	irq_num = (gic->D_TYPER & 0x1F) + 1;

	/* On POR, all SPI is in group 0, level-sensitive and using 1-N model */
	
	/* Disable all PPI, SGI and SPI */
	for (i = 0; i < irq_num; i++)
	  gic->D_ICENABLER[i] = 0xFFFFFFFFUL;

	/* The priority mask level for the CPU interface. If the priority of an 
	 * interrupt is higher than the value indicated by this field, 
	 * the interface signals the interrupt to the processor.
	 */
	gic->C_PMR = (0xFFUL << (8 - 5)) & 0xFFUL;
	
	/* No subpriority, all priority level allows preemption */
	gic->C_BPR = 7 - 5;
	
	/* Enables the forwarding of pending interrupts from the Distributor to the CPU interfaces.
	 * Enable group0 distribution
	 */
	gic->D_CTLR = 1UL;
	
	/* Enables the signaling of interrupts by the CPU interface to the connected processor
	 * Enable group0 signaling 
	 */
	gic->C_CTLR = 1UL;
}

void gic_enable_irq(IRQn_Type nr)
{
	GIC_Type *gic = get_gic_base();

	/* The GICD_ISENABLERs provide a Set-enable bit for each interrupt supported by the GIC.
	 * Writing 1 to a Set-enable bit enables forwarding of the corresponding interrupt from the
	 * Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled.
	 */
	gic->D_ISENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));

}

void gic_disable_irq(IRQn_Type nr)
{
	GIC_Type *gic = get_gic_base();

	/* The GICD_ICENABLERs provide a Clear-enable bit for each interrupt supported by the
	 * GIC. Writing 1 to a Clear-enable bit disables forwarding of the corresponding interrupt from
	 * the Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled. 
	 */
	gic->D_ICENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));
}

​ gic_init函数作用是初始化GIC控制器

​ gic_enable_irq函数作用是使能GPIO管脚中断

​ gic_disable_irq函数作用是屏蔽GPIO管脚中断

​ get_gic_base函数作用是得到GIC寄存器地址,通过GIC寄存器地址就可以访问GIC内部的寄存器。

1.5.2.2 修改Makefile

PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump

INCLUDEDIR 	:= $(shell pwd)/include
CFLAGS 		:= -Wall
CPPFLAGS   	:= -nostdinc -fno-builtin -I$(INCLUDEDIR)
LDFLAGS         := -L /usr/arm/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/6.2.1 -lgcc
objs :=  start.o main.o led.o key.o interrupt.o uart.o eabi_compat.o my_printf.o gic.o

TARGET := eint

$(TARGET).img : $(objs)
	$(LD) -T imx6ull.lds -o $(TARGET).elf $^ $(LDFLAGS)
	$(OBJCOPY) -O binary -S $(TARGET).elf  $(TARGET).bin
	$(OBJDUMP) -D -m arm  $(TARGET).elf  > $(TARGET).dis	
	./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d $(TARGET).bin $(TARGET).imx
	dd if=/dev/zero of=1k.bin bs=1024 count=1
	cat 1k.bin $(TARGET).imx > $(TARGET).img

%.o:%.c
	${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

%.o:%.S
	${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

clean:
	rm -f $(TARGET).dis  $(TARGET).bin $(TARGET).elf $(TARGET).imx $(TARGET).img *.o

1.5.3 参考章节《4-1.4编译程序》编译程序

​ 进入裸机Git仓库 NoosProgramProject/(11_GPIO中断/011_gpio_eint) 源码目录进行编译。

1.5.3.1 参考章节《4-1.4映像文件烧写、运行》烧写、运行程序

​ 此时观察串口打印

​ 分别按下松开key1、key2,中断ID打印出来,对应的Led也亮灭,在MCIMX6Y2.h文件中查询GPIO5_Combined_0_15_IRQn、GPIO4_Combined_0_15_IRQn,分别对应106、104,实验成功。

11_GPIO中断_第19张图片

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号