前言:
本文是根据哔哩哔哩网站上“正点原子[第二期]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 所示:
IRQ | ID | 中断源 | 描述 |
0 | 32 | boot | 用于启动异常的时候通知内核 |
1 | 33 | ca7_paltform | DAP中断,调试端口访问请求中断 |
2 | 34 | sdma | SDMA中断 |
3 | 35 | tcs | TSC(触摸)中断 |
4 | 36 | snvs_lp_wrapper snvs_hp_wrapper | SNVS中断 |
...... | |||
125 | 157 | 无 | 保留 |
126 | 158 | 无 | 保留 |
127 | 159 | pmu | PMU中断 |
在裸机例程 “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处理函数。
本节实验没有问题记录。