imx6ul 编写按键中断程序步骤和自学感受

①、启动文件 start.s

需要添加一级中断向量表和中断处理函数的框架两部分的内容。

一级中断向量表如下:

3 _start:
4 ldr pc, =Reset_Handler /* 复位中断 */
5 ldr pc, =Undefined_Handler /* 未定义指令中断 */ 
6 ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */ 
7 ldr pc, =PrefAbort_Handler /* 预取终止中断 */ 
8 ldr pc, =DataAbort_Handler /* 数据终止中断 */ 
9 ldr pc, =NotUsed_Handler /* 未使用中断 */ 
10 ldr pc, =IRQ_Handler /* IRQ中断 */ 
11 ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */

 中断处理函数的框架(以SVC_Handler 为例)如下:

22 /* SVC中断 */ 
23 SVC_Handler: 
24 ldr r0, =SVC_Handler 
25 bx r0

其中 ,进入C语言环境需要 Reset_Handler,中断实验用到了 IRQ_Handler,这两个处理函数有所不同。

Reset_Handler 是 start.s 中要进行的第一个程序,主要用于关闭 I/D Cache,[设置中断向量表偏移],设置堆栈指针,[使能IRQ中断],跳转到 main 函数。(包含方括号的为可选)。

IRQ_Handler 将程序跳转到 system_irqhandler() 函数中(该函数在bsp_int.c由用户中定义),并带一个参数,用于表示是当前的 IRQ 中断号(从 CP15 的C15保存当前中断号, CP15 的结构体在 core_ca7.h 中定义)。

另外,中断号由 NXP 官方在文件 MCIMX6Y2C.h 中定义,范围0~159共计160个,包含了 imx6ul 所有的中断。形如:

3 typedef enum IRQn { 
4 /* Auxiliary constants */ 
5 NotAvail_IRQn = -128, 
6 
7 /* Core interrupts */ 
8 Software0_IRQn = 0, 
9 Software1_IRQn = 1,
...................................
/* Device specific interrupts */ 
33 IOMUXC_IRQn = 32, 
34 DAP_IRQn = 33,
35 SDMA_IRQn = 34,
....................................
157 PMU_IRQ2_IRQn = 159
} IRQn_Type;

这些中断可分为:

  • ID0~ID15:这 16个 ID分配给 SGI。(软件中断,由软件触发的中断)
  • ID16~ID31:这 16个 ID分配给 PPI。(私有中断,需要指定核心的中断)
  • ID32~ID159:这 128个 ID分配给 SPI。(共享中断)
    所有核心都共享所有外部中断(IRQ)中断(SPI中断)都属于 IRQ 中断(不过其实 SGI、PPI也都属于 IRQ 中断,所以这是一句废话)。

②、中断驱动文件:bsp_int.c、bsp_int.h

在该文件中,设置二级中断向量函数表,承接上文 start.s IRQ中断中跳转的 system_irqhandler() 。

IRQ_Handler 将程序跳转到 system_irqhandler() 函数。

该函数内容如下:

68 void system_irqhandler(unsigned int giccIar) 
69 { 
70 
71 uint32_t intNum = giccIar & 0x3FFUL; 
72
73 /* 检查中断号是否符合要求 */ 
74 if ((intNum == 1020) || (intNum >= NUMBER_OF_INT_VECTORS)) 
75 { 
76    return; 
77 } 
78 
79 irqNesting++; /* 中断嵌套计数器加一 */ 
80 
81 /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/ 
82 irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam); 
83 
84 irqNesting--; /* 中断执行完成,中断嵌套寄存器减一 */ 
85 
86 }

在该函数体中,用到了中断函数向量表

irqTable[intNum]

因此应该在 bsp_int.h 中定义相关结构体:

typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);
19 typedef struct _sys_irq_handle 
20 { 
21     system_irq_handler_t irqHandler; /* 中断处理函数 */ 
22     void *userParam; /* 中断处理函数参数 */ 
23 } sys_irq_handle_t;

然后在 bsp_int.c 中定义一个以上面结构体为单元的数组:

static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

③、使能中断、设置中断模式(边沿触发类型)函数:bsp_gpio.c

主要编写3个函数:

gpio_intconfig() 用于配置中断相关的设置(边沿触发类型)

gpio_enableint() 用于使能中断

gpio_clearintflags()  用于中断标志位清除函数。

gpio_intconfig() 如下:

/* 
73 * @description : 设置GPIO的中断配置功能
74 * @param - base : 要配置的IO所在的GPIO组。
75 * @param - pin : 要配置的GPIO脚号。
76 * @param – pinInterruptMode: 中断模式,参考gpio_interrupt_mode_t
78 */

void
gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode)

其中的中断模式结构体形如:

 gpio_enableint()和 gpio_clearintflags()  如下:

125 void gpio_enableint(GPIO_Type* base, unsigned int pin) 
126 { 
127     base->IMR |= (1 << pin); 
128 }
147 void gpio_clearintflags(GPIO_Type* base, unsigned int pin) 
148 { 
149     base->ISR |= (1 << pin); 
150 }

这些函数再封装进 gpio_init()中去

void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
    base->IMR &= ~(1U << pin);
  //相当于不使能中断,void gpio_disableint(GPIO_Type* base, unsigned int pin)
if(config->direction == kGPIO_DigitalInput) /* GPIO作为输入 */ { base->GDIR &= ~( 1 << pin); } else /* 输出 */ { base->GDIR |= 1 << pin; gpio_pinwrite(base,pin, config->outputLogic); /* 设置默认输出电平 */ } gpio_intconfig(base, pin, config->interruptMode); /* 中断功能配置 */ }

 

④、用户中断函数文件:bsp_exti.c、bsp_exti.h

这部分有3个任务:

1. 编写用户中断函数 gpio1_io18_irqhandler(),

2. 在函数向量表中注册  gpio1_io18_irqhandler(),使其相应的中断号相互匹配。

3. 使能相关 gpio,利用上一节中的 gpio_enableint()和 gpio_intconfig()。(bsp_key.c 中相同的内容)

内容如下:

 该函数名应该在  bsp_int.c 中定义的

static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

的对应的第GPIO1_Combined_16_31_IRQn 个中断服务函数向量相同。

 

 

自我感觉:

首先,难理解的是各种中断的分类

文中,将中断分为多种类型:

首先,按照第一层的函数向量表,将中断分为如下8个中断

 

接着,按官方 MCIMX6Y2C.h 的文件,将中断分为 SPI\SGI\PPI 三种类型,

这之间有什么联系呢?

我的猜想是: 

SPI\SGI\PPI  三种类型的中断(MCIMX6Y2C.h 中所有中断号代表的中断)

均属于第一层的函数向量表的 IRQ 中断(外部中断)。

书中是这样写的,应该没错。

 

 

本章最难的部分,就是理解函数向量表的部分。

用户(自己)编写了第一层的函数向量表,(这好像很自由,具有自定义的感觉,但其实官方也已经定义好,只是没有文档)

但是 NXP 官方已经给各种中断编好号,所以第二层的函数向量表本质上用户无权修改。

这种第一层的自由和第二层的官方限制的不自由的矛盾,给人很奇怪的感觉。

为什么官方不直接连第一层的函数向量表做好,而需要用户自己实现呢?(很没必要的感觉)

 

本章的另一个难点在于类似与 STM32 的库函数部分。

在使能中断的时候,要使能两次,一次是调用 GIC_EnableIRQ 函数,

FORCEDINLINE __STATIC_INLINE void GIC_EnableIRQ(IRQn_Type IRQn)

其有关的寄存器为 gic 的寄存器,如下所示:

 第二次使能调用  gpio_enableint()

 void gpio_enableint(GPIO_Type* base, unsigned int pin) 

其有关的寄存器为 GPIO 的寄存器,如下所示:

 就像打开一个的出口,也要打开另一个的入口,同时打开,才能通过(中断使能)。

posted @ 2023-04-18 10:45  FBshark  阅读(107)  评论(0编辑  收藏  举报