TA的每日心情 | 奋斗 16 分钟前 |
---|
签到天数: 2087 天 连续签到: 169 天 [LV.Master]伴坛终老
|
新冠肺炎疫情不容乐观,明天是计划复工的日子,不知道能不能如期恢复正常工作,很多公司采用了远程办公方式,“停工不停学”,今天跟大家来分享一下关于GPIO中断体会。
视频资料讲解的很详细,开发手册很具体。一路走来,了解了Cortex-A7 MPCore架构,I.MX6U启动方式,官方SDK移植步骤等,在学习视频中收货颇丰,数据存储在内存中,以字节对齐方式存储,访问必须得要考虑字节对齐,否则跨界访问会造成重要数据被用户应用程序覆盖,无法正常开机。关于中断,首先我们来复习一下STM32的中断处理系统。STM32的中断系统主要有中断向量表,内嵌向量中断控制器,中断使能,中断服务函数等重要组成部分。
①、中断向量表,是一系列中断服务程序入口地址组成的表,当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处,中断向量表在整个程序的最前面。中断向量表偏移可以将中断向量表存放到任意地址处,中断向量表偏移配置在函数SystemInit中完成,通过向SCB_VTOR寄存器写入新的中断向量表首地址完成中断服务。
②、内嵌向量中断控制器即NVIC,STM32是Cortex-M内核,中断管理机构也就是NVIC
③、中断使能,即需要哪个外设端口的中断,必须先使能这个外设的中断
④、中断服务函数,当中断发生以后中断服务函数就会被调用,我们将要处理的事件放到中断服务函数中,当中断响应后,中断服务函数会被立即调用,从而完成事件的处理。
I.MX6ULL是基于Cortex-A7内核架构,它与STM32有很大的相似之处, I.MX6ULL的中断控制器称为GIC,GIC控制着ARM内核的VFIQ(虚拟快速FIQ),VIRQ(虚拟快速IRQ),FIQ(快速中断IRQ),IRQ(外部中断IRQ)。GIC将众多的中断源分为三类:①.共享中断 ②.私有中断 ③.软件中断。然而I.MX6ULL的中断源共有160个。
GIC架构分为了两个逻辑块:分发器端和CPU接口端。
分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到CPU接口端。分发器端要做的主要工作如下:
①、全局中断使能控制。
②、控制每一个中断的使能或者关闭。
③、设置每个中断的优先级。
④、设置每个中断的目标处理器列表。
⑤、设置每个外部中断的触发模式:电平触发或边沿触发。
⑥、设置每个中断属于组0还是组1。
CPU接口端是分发器和CPU Core之间的连接桥梁。CPU接口端主要工作如下:
①、使能或者关闭发送到CPU Core的中断请求信号。
②、应答中断。
③、通知中断处理完成。
④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给CPU Core。
⑤、定义抢占策略。
⑥、当多个中断到来的时候,选择优先级最高的中断通知给CPU Core。
中断使能包括两部分,一个是IRQ或者FIQ总中断使能,另一个就是ID0~ID1019,共1020个中断源的使能。IRQ和FIQ分别是外部中断和快速中断的总开关,就类似家里面用户的总电闸。然而ID0~ID1019这1020个中断源就类似家里面的各个电器开关。要想打开电灯,那要保证用户的总电闸是打开的,因此要想使用I.MX6ULL上的外设中断就必须先打开IRQ中断。
关于中断优先级,在STM32的Cortex-M内核中,中断优先级分为抢占优先级和子优先级,两者可以配置。I.MX6ULL采用Cortex-A7内核,中断优先级与Cortex-M一样。Cortex-A7最多可以支持256个优先级,数字越小,优先级越高!I.MX6ULL选择了32个优先级。在使用中断的时候需要初始化GICC_PMR寄存器,此寄存器用来决定使用几级优先级。因为I.MX6ULL支持32个优先级,所以GICC_PMR要设置为0b11111000。优先级设置主要有三部分:
①、设置寄存器GICC_PMR,配置优先级个数,比如I.MX6ULL支持32级优先级。
②、设置抢占优先级和子优先级位数,一般为了简单起见,会将所有的位数都设置为抢占优先级。
③、设置指定中断ID的优先级,也就是设置外设优先级。
本次中断实验是通过按键中断,当按下按键KEY0以后就打开蜂鸣器,再次按下按键KEY0就关闭蜂鸣器。其底层中断程序如下:
- .global _start /* 全局标号 */
- /*
- * 描述: _start函数,首先是中断向量表的创建
- * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)
- * ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)
- */
- _start:
- ldr pc, =Reset_Handler /* 复位中断 */
- ldr pc, =Undefined_Handler /* 未定义中断 */
- ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */
- ldr pc, =PrefAbort_Handler /* 预取终止中断 */
- ldr pc, =DataAbort_Handler /* 数据终止中断 */
- ldr pc, =NotUsed_Handler /* 未使用中断 */
- ldr pc, =IRQ_Handler /* IRQ中断 */
- ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */
- /* 复位中断 */
- Reset_Handler:
- cpsid i /* 关闭全局中断 */
- /* 关闭I,DCache和MMU
- * 采取读-改-写的方式。
- */
- mrc p15, 0, r0, c1, c0, 0 /* 读取CP15的C1寄存器到R0中 */
- bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit12位(I位),关闭I Cache */
- bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),关闭D Cache */
- bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),关闭对齐 */
- bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),关闭分支预测 */
- bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),关闭MMU */
- mcr p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入到CP15的C1寄存器中 */
-
- /* 设置各个模式下的栈指针,
- * 注意:IMX6UL的堆栈是向下增长的!
- * 堆栈指针地址一定要是4字节地址对齐的!!!
- * DDR范围:0X80000000~0X9FFFFFFF
- */
- /* 进入IRQ模式 */
- mrs r0, cpsr
- bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
- orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */
- msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
- ldr sp, =0x80600000 /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */
- /* 进入SYS模式 */
- mrs r0, cpsr
- bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
- orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */
- msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
- ldr sp, =0x80400000 /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */
- /* 进入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 /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */
- cpsie i /* 打开全局中断 */
- b main /* 跳转到main函数 */
- /* 未定义中断 */
- Undefined_Handler:
- ldr r0, =Undefined_Handler
- bx r0
- /* SVC中断 */
- SVC_Handler:
- ldr r0, =SVC_Handler
- bx r0
- /* 预取终止中断 */
- PrefAbort_Handler:
- ldr r0, =PrefAbort_Handler
- bx r0
- /* 数据终止中断 */
- DataAbort_Handler:
- ldr r0, =DataAbort_Handler
- bx r0
- /* 未使用的中断 */
- NotUsed_Handler:
- ldr r0, =NotUsed_Handler
- bx r0
- /* IRQ中断 */
- IRQ_Handler:
- push {lr} /* 保存lr地址 */
- push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */
- mrs r0, spsr /* 读取spsr寄存器 */
- push {r0} /* 保存spsr寄存器 */
- mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
- * 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
- * Cortex-A7 Technical ReferenceManua.pdf P68 P138
- */
- add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
- ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
- * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
- * 这个中断号来绝对调用哪个中断服务函数 */
- push {r0, r1} /* 保存r0,r1 */
-
- cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */
-
- push {lr} /* 保存SVC模式的lr寄存器 */
- ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
- blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */
- pop {lr} /* 执行完C语言中断服务函数,lr出栈 */
- cps #0x12 /* 进入IRQ模式 */
- pop {r0, r1}
- str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
- pop {r0}
- msr spsr_cxsf, r0 /* 恢复spsr */
- pop {r0-r3, r12} /* r0-r3,r12出栈 */
- pop {lr} /* lr出栈 */
- subs pc, lr, #4 /* 将lr-4赋给pc */
-
- /* FIQ中断 */
- FIQ_Handler:
- ldr r0, =FIQ_Handler
- bx r0
复制代码- #ifndef _BSP_EXIT_H
- #define _BSP_EXIT_H
- #include "imx6ul.h"
- /* 函数声明 */
- void exit_init(void); /* 中断初始化 */
- void gpio1_io18_irqhandler(void); /* 中断处理函数 */
- #endif
复制代码- #include "bsp_exit.h"
- #include "bsp_gpio.h"
- #include "bsp_int.h"
- #include "bsp_delay.h"
- #include "bsp_beep.h"
- /*
- * @description : 初始化外部中断
- * @param : 无
- * @return : 无
- */
- void exit_init(void)
- {
- gpio_pin_config_t key_config;
- /* 1、设置IO复用 */
- IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 复用为GPIO1_IO18 */
- IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
- /* 2、初始化GPIO为中断模式 */
- key_config.direction = kGPIO_DigitalInput;
- key_config.interruptMode = kGPIO_IntFallingEdge;
- key_config.outputLogic = 1;
- gpio_init(GPIO1, 18, &key_config);
- GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */
- system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注册中断服务函数 */
- gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */
- }
- /*
- * @description : GPIO1_IO18最终的中断处理函数
- * @param : 无
- * @return : 无
- */
- void gpio1_io18_irqhandler(void)
- {
- static unsigned char state = 0;
- /*
- *采用延时消抖,中断服务函数中禁止使用延时函数!因为中断服务需要
- *快进快出!!这里为了演示所以采用了延时函数进行消抖,后面我们会讲解
- *定时器中断消抖法!!!
- */
- delay(10);
- if(gpio_pinread(GPIO1, 18) == 0) /* 按键按下了 */
- {
- state = !state;
- beep_switch(state);
- }
-
- gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */
- }
复制代码- #ifndef _BSP_INT_H
- #define _BSP_INT_H
- #include "imx6ul.h"
- /* 中断服务函数形式 */
- typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);
- /* 中断服务函数结构体*/
- typedef struct _sys_irq_handle
- {
- system_irq_handler_t irqHandler; /* 中断服务函数 */
- void *userParam; /* 中断服务函数参数 */
- } sys_irq_handle_t;
- /* 函数声明 */
- void int_init(void);
- void system_irqtable_init(void);
- void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam);
- void system_irqhandler(unsigned int giccIar);
- void default_irqhandler(unsigned int giccIar, void *userParam);
- #endif
复制代码- #include "bsp_int.h"
- /* 中断嵌套计数器 */
- static unsigned int irqNesting;
- /* 中断服务函数表 */
- static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
- /*
- * @description : 中断初始化函数
- * @param : 无
- * @return : 无
- */
- void int_init(void)
- {
- GIC_Init(); /* 初始化GIC */
- system_irqtable_init(); /* 初始化中断表 */
- __set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移,偏移到起始地址 */
- }
- /*
- * @description : 初始化中断服务函数表
- * @param : 无
- * @return : 无
- */
- void system_irqtable_init(void)
- {
- unsigned int i = 0;
- irqNesting = 0;
-
- /* 先将所有的中断服务函数设置为默认值 */
- for(i = 0; i < NUMBER_OF_INT_VECTORS; i++)
- {
- system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);
- }
- }
- /*
- * @description : 给指定的中断号注册中断服务函数
- * @param - irq : 要注册的中断号
- * @param - handler : 要注册的中断处理函数
- * @param - usrParam : 中断服务处理函数参数
- * @return : 无
- */
- void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
- {
- irqTable[irq].irqHandler = handler;
- irqTable[irq].userParam = userParam;
- }
- /*
- * @description : C语言中断服务函数,irq汇编中断服务函数会调用此函数,此函数通过在中断服务列表中查找指定中断号所对应的中断处理函数并执行。
- * @param - giccIar : 中断号
- * @return : 无
- */
- void system_irqhandler(unsigned int giccIar)
- {
- uint32_t intNum = giccIar & 0x3FFUL;
-
- /* 检查中断号是否符合要求 */
- if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
- {
- return;
- }
-
- irqNesting++; /* 中断嵌套计数器加一 */
- /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
- irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
-
- irqNesting--; /* 中断执行完成,中断嵌套寄存器减一 */
- }
- /*
- * @description : 默认中断服务函数
- * @param - giccIar : 中断号
- * @param - usrParam : 中断服务处理函数参数
- * @return : 无
- */
- void default_irqhandler(unsigned int giccIar, void *userParam)
- {
- while(1)
- {
- }
- }
复制代码- #ifndef _BSP_GPIO_H
- #define _BSP_GPIO_H
- #define _BSP_KEY_H
- #include "imx6ul.h"
- /*
- * 枚举类型和结构体定义
- */
- typedef enum _gpio_pin_direction
- {
- kGPIO_DigitalInput = 0U, /* 输入 */
- kGPIO_DigitalOutput = 1U, /* 输出 */
- } gpio_pin_direction_t;
- /*
- * GPIO中断触发类型枚举
- */
- typedef enum _gpio_interrupt_mode
- {
- kGPIO_NoIntmode = 0U, /* 无中断功能 */
- kGPIO_IntLowLevel = 1U, /* 低电平触发 */
- kGPIO_IntHighLevel = 2U, /* 高电平触发 */
- kGPIO_IntRisingEdge = 3U, /* 上升沿触发 */
- kGPIO_IntFallingEdge = 4U, /* 下降沿触发 */
- kGPIO_IntRisingOrFallingEdge = 5U, /* 上升沿和下降沿都触发 */
- } gpio_interrupt_mode_t;
- /*
- * GPIO配置结构体
- */
- typedef struct _gpio_pin_config
- {
- gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */
- uint8_t outputLogic; /* 如果是输出的话,默认输出电平 */
- gpio_interrupt_mode_t interruptMode; /* 中断方式 */
- } gpio_pin_config_t;
- /* 函数声明 */
- void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
- int gpio_pinread(GPIO_Type *base, int pin);
- void gpio_pinwrite(GPIO_Type *base, int pin, int value);
- void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pinInterruptMode);
- void gpio_enableint(GPIO_Type* base, unsigned int pin);
- void gpio_disableint(GPIO_Type* base, unsigned int pin);
- void gpio_clearintflags(GPIO_Type* base, unsigned int pin);
- #endif
复制代码- #include "bsp_gpio.h"
- /*
- * @description : GPIO初始化。
- * @param - base : 要初始化的GPIO组。
- * @param - pin : 要初始化GPIO在组内的编号。
- * @param - config : GPIO配置结构体。
- * @return : 无
- */
- void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
- {
- base->IMR &= ~(1U << 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); /* 中断功能配置 */
- }
- /*
- * @description : 读取指定GPIO的电平值 。
- * @param - base : 要读取的GPIO组。
- * @param - pin : 要读取的GPIO脚号。
- * @return : 无
- */
- int gpio_pinread(GPIO_Type *base, int pin)
- {
- return (((base->DR) >> pin) & 0x1);
- }
- /*
- * @description : 指定GPIO输出高或者低电平 。
- * @param - base : 要输出的的GPIO组。
- * @param - pin : 要输出的GPIO脚号。
- * @param - value : 要输出的电平,1 输出高电平, 0 输出低低电平
- * @return : 无
- */
- void gpio_pinwrite(GPIO_Type *base, int pin, int value)
- {
- if (value == 0U)
- {
- base->DR &= ~(1U << pin); /* 输出低电平 */
- }
- else
- {
- base->DR |= (1U << pin); /* 输出高电平 */
- }
- }
- /*
- * @description : 设置GPIO的中断配置功能
- * @param - base : 要配置的IO所在的GPIO组。
- * @param - pin : 要配置的GPIO脚号。
- * @param - pinInterruptMode: 中断模式,参考枚举类型gpio_interrupt_mode_t
- * @return : 无
- */
- void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode)
- {
- volatile uint32_t *icr;
- uint32_t icrShift;
- icrShift = pin;
-
- base->EDGE_SEL &= ~(1U << pin);
- if(pin < 16) /* 低16位 */
- {
- icr = &(base->ICR1);
- }
- else /* 高16位 */
- {
- icr = &(base->ICR2);
- icrShift -= 16;
- }
- switch(pin_int_mode)
- {
- case(kGPIO_IntLowLevel):
- *icr &= ~(3U << (2 * icrShift));
- break;
- case(kGPIO_IntHighLevel):
- *icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));
- break;
- case(kGPIO_IntRisingEdge):
- *icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));
- break;
- case(kGPIO_IntFallingEdge):
- *icr |= (3U << (2 * icrShift));
- break;
- case(kGPIO_IntRisingOrFallingEdge):
- base->EDGE_SEL |= (1U << pin);
- break;
- default:
- break;
- }
- }
- /*
- * @description : 使能GPIO的中断功能
- * @param - base : 要使能的IO所在的GPIO组。
- * @param - pin : 要使能的GPIO在组内的编号。
- * @return : 无
- */
- void gpio_enableint(GPIO_Type* base, unsigned int pin)
- {
- base->IMR |= (1 << pin);
- }
- /*
- * @description : 禁止GPIO的中断功能
- * @param - base : 要禁止的IO所在的GPIO组。
- * @param - pin : 要禁止的GPIO在组内的编号。
- * @return : 无
- */
- void gpio_disableint(GPIO_Type* base, unsigned int pin)
- {
- base->IMR &= ~(1 << pin);
- }
- /*
- * @description : 清除中断标志位(写1清除)
- * @param - base : 要清除的IO所在的GPIO组。
- * @param - pin : 要清除的GPIO掩码。
- * @return : 无
- */
- void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
- {
- base->ISR |= (1 << pin);
- }
复制代码- #include "bsp_clk.h"
- #include "bsp_delay.h"
- #include "bsp_led.h"
- #include "bsp_beep.h"
- #include "bsp_key.h"
- #include "bsp_int.h"
- #include "bsp_exit.h"
- /*
- * @description : main函数
- * @param : 无
- * @return : 无
- */
- int main(void)
- {
- unsigned char state = OFF;
- int_init(); /* 初始化中断(一定要最先调用!) */
- imx6u_clkinit(); /* 初始化系统时钟 */
- clk_enable(); /* 使能所有的时钟 */
- led_init(); /* 初始化led */
- beep_init(); /* 初始化beep */
- key_init(); /* 初始化key */
- exit_init(); /* 初始化按键中断 */
- while(1)
- {
- state = !state;
- led_switch(LED0, state);
- delay(500);
- }
-
- return 0;
- }
复制代码 编写好Makefile文件后,编译ok后如下图所示:
实验结果现象是按下KEY0会打开蜂鸣器,再次按下就会关闭蜂鸣器,LED0会不断闪烁,周期大约500ms。视频演示效果如下。此次中断学习体验就与大家分享到这里,后续持续更新,谢谢围观!
正点原子I.MX6U-ALPHA开发板测评文章
|
|