Bootstrap

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15.5讲 GPIO中断实验-通用中断驱动编写

前言:

本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》

正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档

正文:

本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第15.5 讲” 的读书笔记。第15讲主要是介绍I.MX6U处理器GPIO中断控制实验。本节将参考正点原子的视频教程第15讲和配套的正点原子开发指南文档进行学习。在第15.5讲视频教程中,正点原子会讲解如何实现自己的通用中断驱动编写。

0. 概述

本章实验的功能和之前按键控制蜂鸣器的实验一样,只是按键采用中断的方式来处理。当按下KEY0以后就打开蜂鸣器,再次按下蜂鸣器KEY0就关闭蜂鸣器。

1. 移植SDK

将NXP SDK里的文件 core_ca7.h 拷贝到本章实验目录中的 "imx6ul' 文件夹中,参考正点原子提供的示例源码"9_int"中的 core_ca7.h 进行修改。主要留下和GIC相关的内容,我们重点是需要使用 core_ca7.h 中的的10个API函数,这10个函数如下表所示:

函数描述
GIC_Init初始化GIC。
GIC_EnableIRQ使能指定外设中断。
GIC_DisableIRQ关闭指定的外设中断
GIC_AcknowledgeIRQ返回中断号。
GIC_DeactivateIRQ无效化指定中断。
GIC_GetRunningPriority获得当前正在运行的中断优先级。
GIC_SetPriorityGrouping设置抢占中断优先级位数。
GIC_GetPriorityGrougping获取抢占中断优先级位数。
GIC_SetPriority设置指定中断的优先级。
GIC_GetPriority获取指定中断的优先级。

移植好以后,吸怪 imx6ul.h ,在里面加上一行代码。

#include "core_ca7.h" 

在正点原子的哔哩哔哩网站第15.5 视频教程里,正点原子哥说自己移植NXP SDK里面的 core_ca7.h 花了一天时间,因为里面勾连引用了很多NXP SDK里的符号声明和定义等,我这里就直接偷懒从正点原子提供的示例源码里拷贝已经移植好的 core_ca7.h 来使用了。

core_ca7.h 的主要内容如下,我们关心的是前面列出来的10个GIC相关的 GIC_xxx 函数,这10个函数可以对GIC中断控制器进行配置,GIC中断控制器在之前的15.3,15.4两讲的博文里,已经分析过GIC中断控制器分为两个逻辑模块,GIC Distributor 分发器端和 GIC CPU Interface 内核接口端。GIC中断控制函数通过写对应的GIC寄存器(寄存器可以从ARM GICV2.0 手册中查到),来实现对中断的使能,关闭,应答,中断优先级配置。

  • 初始化GIC
  • 使能指定外设中断
  • 关闭指定外设中断
  • 返回中断号
  • 无效化指定中断
  • 获取当前正在运行的中断优先级
  • 设置抢占中断优先级位数
  • 获取抢占中断优先级位数
  • 设置指定中断的优先级
  • 获取指定中断的优先级

Struct GIC_Type 中定义的GIC分发器端寄存器和GIC CPU接口端寄存器,和《ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf》中介绍的GIC 内存映射分布是对应的。

然后是NXP SDK 在 core_ca7.h 文件里定义的 ‘GIC_xx()’ API 接口函数,通过上面的C语言嵌入式汇编语言的写法 '__MRC()' , '__MCR()' 来写相应的 GIC 寄存器来配置:GIC初始化,使能指定外设中断,关闭指定外设中断,获取中断ID号,获取指定中断优先级,设置指定中断优先级。

以下面一个函数为例分析NXP SDK 里的 core_ca7.h 里的函数宏展开之后如何读取GIC中断控制的的寄存器:

2. 编写bsp_int通用中断处理驱动

2.1 中断ID

在处理器正在正常执行指令时,  Cortex-A7内核收到GIC中断控制器送来的 IRQ 中断,内核根据中断向量表跳转到 IRQ_Handler 中断服务函数中执行,在IRQ中断服务函数处理完中单中断之后返回到被中断的现场继续执行。

在之间的博文里我们已经学习到所有的外设中断源都连接到GIC中断控制器,然后对所有的外设中断源,GIC中断控制器向内核都上报为IRQ中断,那么内核进入IRQ中断服务函数的时候如何知道是哪个具体的外部设备发生了中断哪?

中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一的ID,这些ID就是中断ID。每一个CPU最多支持1020个中断ID,中断ID号为0~1019。这1020个ID包含了PPI,SPI,和SGI,那么这三类中断是如何分配这1020个中断ID的呢?这1020个ID分配如下:

ID0~ID15:这个16个ID分配给SGI

ID16~ID31:这16个ID分配给PPI。

ID31~ID1019:这998个ID分配给SPI,像GPIO中断,串口中断灯这些外部中断,至于具体到某个ID对应按个中断那就有半导体厂商根据实际情况去定义了。

比如I.MX6U总共使用了128个中断ID,加上前面的PPI和SGI的32个ID,I.MX6U的中断源总共有 128 + 32 = 160 个,这128个中断ID对应的中断在《I.MX6ULL 参考手册》的“3.2 Cortex A7 interrupts”小节,中断源如表 17.1.3.1 所示:

IRQID中断源描述
032boot用于启动异常的时候通知内核
133ca7_paltformDAP中断,调试端口访问请求中断
234sdmaSDMA中断
335tcsTSC(触摸)中断
436snvs_lp_wrapper
snvs_hp_wrapper
SNVS中断
......
125157保留
126158保留
127159pmuPMU中断

在裸机例程 “9_int”,我们在前面移植了 NXP SDK 中的文件  MCIMX6Y2C.h ,在此文件中定义了一个枚举类型 IRQn_Type ,此枚举类型就枚举出了 I.MX6U 的所有中断,代码如下所示:

2.2 中断ID处理函数表

通过上面的分析可知,所有的外部设备中断源连接GIC中断控制器,对所有的外部设备中断源GIC控制器都通过一个IRQ中断信号来通知Cortex-A7内核。内核收到IRQ中断时,查中断向量表跳转到IRQ中断服务函数执行。为了区分出中断具体发生在哪一个外设源上,定义了中断ID号,内核可以在收到IRQ中断信号通知后通过从GIC控制器读取中断ID号来分辨具体是哪一个外设源发生了中断。外设中断ID (IRQ ID)最多1020的中断ID号,从0到1019。

所以进入IRQ中断服务函数中,只需要读取到中断ID,然后针对中断ID调用对应的外设中断处理函数就可以了。一般的做法是定义一个中断ID处理函数数组表,然后使用中断ID进行查表,根据中断ID执行对应的中断处理函数。这也就是通用的中断驱动处理函数。

定义中断ID处理函数表:

#ifndef __BSP_INT_H__
#define __BSP_INT_H__

#include "imx6u.h"

typedef void (* system_irq_handler_t)(IRQn_Type irq, void *userparam);

typedef struct _sys_irq_handler {
	system_irq_handler_t irqHandler;
	void *userParam;
} sys_irq_handler_t;

/* 中断初始化 */
void int_init(void);

/* 中断处理函数入口 */
void system_irqhandler(unsigned int gicc_iar);

/* 注册中断处理函数 */
void system_irqhandler_register(IRQn_Type irq, system_irq_handler_t handler, void *userparam);

/* 默认中断处理函数 */
void default_irqhandler(IRQn_Type irq, void *userParam);

/* 中断处理函数表初始化 */
void system_irqtable_init(void);

#endif

2.3 中断处理函数表源码分析

下面具体分析一下代码,在 'bsp/int.h' 中使用'typedef' 定义了中断ID函数指针类型

typedef void (* system_irq_handler_t)(IRQn_Type irq, void *userparam);

然后定义中断处理函数数组表的类型,数组表中的每一个成员都指向对应的中断ID对应的中断处理函数。在IRQ中断服务函数,值需要根据中断ID号从中断处理函数表中找到对应的中断处理函数执行,就能处理具体的外设中断源。

typedef struct _sys_irq_handler {
    system_irq_handler_t irqHandler;
    void *userParam;
} sys_irq_handler_t;

在 'bsp/int.c' 中定义了具体的中断处理函数表,I.MX6ULL 处理器支持160个中断ID号,所以 'bsp/int.c' 中定义的中断处理函数表数组的程度就是160。

当在 'bsp/init.hc' 中执行初始化函数 'void int_init(void)' 时将中断处理函数表初始化为 'default_irqhandler()' ,这样中断ID函数表就初始化完成。

/* 初始化irq_table表 */
void system_irqtable_init(void)
{
	int i = 0;

	for(i=0; i<NUMBER_OF_INT_VECTORS; i++)
	{
		irq_table[i].irqHandler = default_irqhandler;
		irq_table[i].userParam = NULL;
	}
}

'default_irqhandler()' 函数体的内容是一个空的死循环什么也不做,这样当还没有注册具体中断处理函数的中断ID发生时,能够保证该中断ID有中断处理函数可以执行,虽然是让它进入了一个死循环但是还是比让程序跑飞要强。


/* 默认irq处理函数,函数体中没有内容之后一个空循环 */
void default_irqhandler(IRQn_Type irq, void *userparam)
{
	int state = 0;
	
	while(1){
	}
}

外设可以调用'system_irqhandler_register()' 注册指定中断ID号的中断处理函数,对应中断ID处理函数就被更新到中断处理函数表中。当对应的中断ID发生时,IRQ中断服务函数根据中断ID查表就可以执行到注册的中断ID处理函数。这样具体外设中断源触发的中断ID号,就可以调用到具体外设注册的中断ID处理函数了。

/* 注册IRQ中断id的中断处理函数 */
void system_irqhandler_register(IRQn_Type irq, system_irq_handler_t handler, void *userparam)
{
	if(irq>=0 && irq <160)
	{
		irq_table[irq].irqHandler = handler;
		irq_table[irq].userParam = userparam;
	}
}
2.4 总结

通用IRQ中断处理驱动处理,通过两级查表的方式找到对应的中断ID处理函数

  • 第一级查表是,处理器收到IRQ中断信号后根据中断向量表进入IRQ中断服务函数。
  • 第二级查表是,在IRQ中断服务函数中读取GIC中断处理器的中断ID号,根据中断ID号查表(中断ID处理函数表),指定注册的中断ID处理函数。

3. 通用IRQ中断驱动处理源码

bsp/int.h源码

#ifndef __BSP_INT_H__
#define __BSP_INT_H__

#include "imx6u.h"

typedef void (* system_irq_handler_t)(IRQn_Type irq, void *userparam);

typedef struct _sys_irq_handler {
	system_irq_handler_t irqHandler;
	void *userParam;
} sys_irq_handler_t;

/* 中断初始化 */
void int_init(void);

/* 中断处理函数入口 */
void system_irqhandler(unsigned int gicc_iar);

/* 注册中断处理函数 */
void system_irqhandler_register(IRQn_Type irq, system_irq_handler_t handler, void *userparam);

/* 默认中断处理函数 */
void default_irqhandler(IRQn_Type irq, void *userParam);

/* 中断处理函数表初始化 */
void system_irqtable_init(void);

#endif

bsp/int.c源码

#include "bsp_int.h"
#include "bsp_beep.h"
#include "bsp_delay.h"

sys_irq_handler_t irq_table[NUMBER_OF_INT_VECTORS];


void int_init(void)
{
	/* 初始化GIC */
	GIC_Init();

	/* 设置中断向量表基地址VBAR */
	__set_VBAR(0x87800000U);

	/* 初始化irq_table为默认irq处理函数 */
	system_irqtable_init();
}

/* IRQ通用中断驱动函数处理入口 */
void system_irqhandler(unsigned int gicc_iar)
{
	unsigned int irq = gicc_iar & 0x3ff;	/* GICC_IAR bit[9:0]是中断ID */

	if(irq>=0 && irq <160){
		irq_table[irq].irqHandler(irq, irq_table[irq].userParam);
	}
}

/* 注册IRQ中断id的中断处理函数 */
void system_irqhandler_register(IRQn_Type irq, system_irq_handler_t handler, void *userparam)
{
	if(irq>=0 && irq <160)
	{
		irq_table[irq].irqHandler = handler;
		irq_table[irq].userParam = userparam;
	}
}

/* 默认irq处理函数,函数体中没有内容之后一个空循环 */
void default_irqhandler(IRQn_Type irq, void *userparam)
{
	int state = 0;
	
	while(1){
		state = !state;
		beep_switch(state);
		delay(500);
	}
}

/* 初始化irq_table表 */
void system_irqtable_init(void)
{
	int i = 0;

	for(i=0; i<NUMBER_OF_INT_VECTORS; i++)
	{
		irq_table[i].irqHandler = default_irqhandler;
		irq_table[i].userParam = NULL;
	}
}

4. 总结和实验过程中的问题记录

Cortex-A7内核通过中断ID区分不同的外设中断源,并定义了中断ID服务函数数组表,通过中断ID查表来调用注册的中断ID处理函数。

本节实验没有问题记录。

;