赞
踩
中断系统主要有以下几个关键点:
1、中断向量表(通过地址偏移找到对应中断服务函数入口地址)
2、中断控制器(STM32
使用 NVIC
中断控制器,iMX6ULL
使用 GIC
中断控制器)
3、中断使能(全局中断使能和指定中断使能)
4、中断服务函数
注:要注意配置中断向量表起始地址(也就是链接起始地址,iMX6ULL
需要通过协处理器配置 VBAR
寄存器(向量表基地址寄存器)和 读取 CBAR
寄存器( GIC
基地址,此寄存器为只读寄存器))
详细内容见《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition》中 B1.8 Exception handling 章节和 B1.9 Exception descriptions 章节。
Rest
- 复位中断:CPU
复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP
指针、DDR
等等。Undefined Instruction
- 未定义指令中断:如果指令不能识别的话就会产生此中断。Software Interrupt
- 软中断:由 SWI
指令引起的中断,Linux
的系统调用会用 SWI
指令来引起软中断,通过软中断来陷入到内核空间。Prefetch Abort
- 指令预取中止中断:预取指令的出错的时候会产生此中断。Data Abort
- 数据访问中止中断:访问数据出错的时候会产生此中断。IRQ Interrupt
- 外部中断:芯片内部的外设中断都会引起此中断的发生。FIQ Interrupt
- 快速中断:如果需要快速处理中断的话就可以使用此中断。中断向量表里面都是中断服务函数的入口地址,从上图中可以看出,Cortex-A7
一共有 8
个中断,而且还有一个中断向量未使用,实际只有 7
个中断。
iMX6ULL
共有 128
个中断,7
个中断向量表该如何管理 128
中断?
上图展示了 Cortex-A7
中断管理,所有外设中断都触发 IRQ
中断。
详细信息见:GICv2 整理_OnlyLove_的博客-CSDN博客
寄存器 CPSR
的 I=1
禁止 IRQ
,当 I=0
使 能 IRQ
;F=1
禁止 FIQ
,F=0
使能 FIQ
。
GIC
寄存器 GICD_ISENABLERn
和 GICD_ ICENABLERn
用来完成外部中断的使能和禁止。一个 bit
控制一个中断 ID
的使能。
GICC_PMR
寄存器配置中断优先级。
i.MX6ULL
是 Cortex-A7
内核,所以支持 32
个优先级,因此 GICC_PMR
要设置为 0b11111000
。
GICC_BPR
寄存器用来配置抢占优先级和子优先级各占多少位。GICC_BPR
结构如下图所示:
寄存器 GICC_BPR
只有低 3
位有效,其值不同,抢占优先级和子优先级占用的位数也不同,配置如下图所示:
具体内容见代码实现。
在 vscode
中进行开发,根据 vscode
相关规则进行配置。
打开 VSCode
,按下 “Crtl+Shift+P”
打开 VSCode
的控制台,然后输入 “C/C++: Edit configurations(JSON) ”
,打开 C/C++
编辑配置文件。
打开以后会自动在 .vscode
目录下生成一个名为 c_cpp_properties.json
的文件,此文件默认内容如下所示:
{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**" ], "defines": [], "compilerPath": "/usr/bin/gcc", "cStandard": "gnu17", "cppStandard": "gnu++14", "intelliSenseMode": "linux-gcc-x64" } ], "version": 4 }
includePath
表示头文件路径,需要将包含头文件的目录都列举出来。修改后 c_cpp_properties.json
的文件内容如下:
{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**", "${workspaceFolder}/imx6ull/inc", /* 针对 imx6ull 实现的库函数 */ "${workspaceFolder}/user", /* 启动文件 start.S 和 main.c */ "${workspaceFolder}/bsp/clock", /* 时钟配置 */ "${workspaceFolder}/bsp/delay", /* 延时函数 */ "${workspaceFolder}/bsp/led", /* led 灯相关函数 */ "${workspaceFolder}/bsp/key" /* 按键相关函数 */ ], "defines": [], "compilerPath": "/usr/bin/gcc", "cStandard": "gnu17", "cppStandard": "gnu++14", "intelliSenseMode": "linux-gcc-x64" } ], "version": 4 }
CROSS_COMPILE ?= arm-linux-gnueabihf- TARGET ?= int CC := $(CROSS_COMPILE)gcc LD := $(CROSS_COMPILE)ld OBJCOPY := $(CROSS_COMPILE)objcopy OBJDUMP := $(CROSS_COMPILE)objdump INCDIRS := imx6ull/inc \ bsp/clock \ bsp/delay \ bsp/led \ bsp/key \ bsp/int \ user SRCDIRS := imx6ull/src \ bsp/clock \ bsp/delay \ bsp/led \ bsp/key \ bsp/int \ user INCLUDE := $(patsubst %, -I %, $(INCDIRS)) SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.s)) CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c)) SFILENDIR := $(notdir $(SFILES)) CFILENDIR := $(notdir $(CFILES)) SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.s=.o)) COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o)) OBJS := $(SOBJS) $(COBJS) VPATH := $(SRCDIRS) .PHONY: clean $(TARGET).bin : $(OBJS) $(LD) -Timx6ul.lds -o $(TARGET).elf $^ $(OBJCOPY) -O binary -S $(TARGET).elf $@ $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis $(SOBJS) : obj/%.o : %.s $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $< $(COBJS) : obj/%.o : %.c $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $< clean: rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
添加中断向量表,只实现基本架构。代码实现在 start.s
中,具体代码如下:
.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: ldr r0, =Reset_Handler bx r0 Undefined_Handler: ldr r0, =Undefined_Handler bx r0 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_Handler: ldr r0, =IRQ_Handler bx r0 FIQ_Handler: ldr r0, =FIQ_Handler bx r0
编译测试,编译日志如下:
onlylove@ubuntu:~/linux/driver/board_driver/6_int$ make
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/start.o user/start.s
user/start.s: Assembler messages:
user/start.s: Warning: end of file not at end of a line; newline inserted
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/imx6ull_gpio.o imx6ull/src/imx6ull_gpio.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/clock.o bsp/clock/clock.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/delay.o bsp/delay/delay.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/led.o bsp/led/led.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/key.o bsp/key/key.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/main.o user/main.c
arm-linux-gnueabihf-ld -Timx6ul.lds -o int.elf obj/start.o obj/imx6ull_gpio.o obj/clock.o obj/delay.o obj/led.o obj/key.o obj/main.o
arm-linux-gnueabihf-objcopy -O binary -S int.elf int.bin
arm-linux-gnueabihf-objdump -D -m arm int.elf > int.dis
onlylove@ubuntu:~/linux/driver/board_driver/6_int$
通过反汇编文件查看编译结果:
int.elf: file format elf32-littlearm Disassembly of section .text: 87800000 <_start>: 87800000: e59ff058 ldr pc, [pc, #88] ; 87800060 <FIQ_Handler+0x8> 87800004: e59ff058 ldr pc, [pc, #88] ; 87800064 <FIQ_Handler+0xc> 87800008: e59ff058 ldr pc, [pc, #88] ; 87800068 <FIQ_Handler+0x10> 8780000c: e59ff058 ldr pc, [pc, #88] ; 8780006c <FIQ_Handler+0x14> 87800010: e59ff058 ldr pc, [pc, #88] ; 87800070 <FIQ_Handler+0x18> 87800014: e59ff058 ldr pc, [pc, #88] ; 87800074 <FIQ_Handler+0x1c> 87800018: e59ff058 ldr pc, [pc, #88] ; 87800078 <FIQ_Handler+0x20> 8780001c: e59ff058 ldr pc, [pc, #88] ; 8780007c <FIQ_Handler+0x24> 87800020 <Reset_Handler>: 87800020: e59f0038 ldr r0, [pc, #56] ; 87800060 <FIQ_Handler+0x8> 87800024: e12fff10 bx r0 87800028 <Undefined_Handler>: 87800028: e59f0034 ldr r0, [pc, #52] ; 87800064 <FIQ_Handler+0xc> 8780002c: e12fff10 bx r0 87800030 <SVC_Handler>: 87800030: e59f0030 ldr r0, [pc, #48] ; 87800068 <FIQ_Handler+0x10> 87800034: e12fff10 bx r0 87800038 <PrefAbort_Handler>: 87800038: e59f002c ldr r0, [pc, #44] ; 8780006c <FIQ_Handler+0x14> 8780003c: e12fff10 bx r0 87800040 <DataAbort_Handler>: 87800040: e59f0028 ldr r0, [pc, #40] ; 87800070 <FIQ_Handler+0x18> 87800044: e12fff10 bx r0 87800048 <NotUsed_Handler>: 87800048: e59f0024 ldr r0, [pc, #36] ; 87800074 <FIQ_Handler+0x1c> 8780004c: e12fff10 bx r0 87800050 <IRQ_Handler>: 87800050: e59f0020 ldr r0, [pc, #32] ; 87800078 <FIQ_Handler+0x20> 87800054: e12fff10 bx r0 87800058 <FIQ_Handler>: 87800058: e59f001c ldr r0, [pc, #28] ; 8780007c <FIQ_Handler+0x24> 8780005c: e12fff10 bx r0 ……
reset
中断服务函数为复位后执行第一个函数,主要完成系统运行前的一些初始化功能,具体功能包括:
ICache
,DCache
MMU
VBAR
)IRQ
中断main
函数/* 复位中断 */ 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寄存器中 */ /* 汇编版本设置中断向量表偏移 */ ldr r0, =0X87800000 dsb isb mcr p15, 0, r0, c12, c0, 0 dsb isb /* 设置各个模式下的栈指针, * 注意: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 /* 打开全局中断 */ /* 使能IRQ中断 */ mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */ bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */ msr cpsr, r0 /* 将r0重新写入到cpsr中 */ b main /* 跳转到main函数 */
以上源码从正点原子例程中拷贝,在开发板验证测试没有问题。
IRQ
中断服务函数主要用于处理 imx6ull
外设中断,处理逻辑如下:
r0~r3
、r12
)和状态寄存器(spsr
)GIC
基址ID
号(GICC_IAR.Interrupt ID
)SVC
模式,允许再次中断imx6ull_irq
函数处理相应外设中断IRQ
模式,等待中断处理完成EOIR
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, =imx6ull_irq /* 加载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 */
主要移植 core_ca7.h
文件,文件保存在 i.MX6ULL-SDK\CORTEXA\Include
目录下。
注:文件移植正点原子提供。
1、调用 SDK
提供的 GIC_Init( )
进行初始化 GIC
。
2、初始化 imx6ull
中断向量表,将 imx6ull
中断向量表每一项设置为默认中断处理(函数逻辑:死循环)。
3、根据需要设置中断向量表起始地址。
#include "imx6ull.h" #include "int.h" /* 中断嵌套计数器 */ static unsigned int irqNesting; /* 中断服务函数表 */ static imx6ull_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS]; /* * @description : 默认中断服务函数 * @param - giccIar : 中断号 * @param - usrParam : 中断服务处理函数参数 * @return : 无 */ void im6ull_default_irqhandler(unsigned int giccIar, void *userParam) { while(1) { } } /* * @description : 给指定的中断号注册中断服务函数 * @param - irq : 要注册的中断号 * @param - handler : 要注册的中断处理函数 * @param - usrParam : 中断服务处理函数参数 * @return : 无 */ void imx6ull_register_irqhandler(IRQn_Type irq, imx6ull_irq_handler_t handler, void *userParam) { irqTable[irq].irqHandler = handler; irqTable[irq].userParam = userParam; } /* * @description : 初始化中断服务函数表 * @param : 无 * @return : 无 */ void im6ull_irq_table_init(void) { unsigned int i = 0; irqNesting = 0; /* 先将所有的中断服务函数设置为默认值 */ for(i = 0; i < NUMBER_OF_INT_VECTORS; i++) { imx6ull_register_irqhandler((IRQn_Type)i,im6ull_default_irqhandler, NULL); } } /* * @description : 中断初始化函数 * @param : 无 * @return : 无 */ void int_init(void) { GIC_Init(); /* 初始化 GIC */ im6ull_irq_table_init(); /* 初始化中断表 */ __set_VBAR((uint32_t)0x87800000); /* 配置中断向量表起始地址 */ } /* * @description : C语言中断服务函数,irq汇编中断服务函数会 调用此函数,此函数通过在中断服务列表中查 找指定中断号所对应的中断处理函数并执行。 * @param - giccIar : 中断号 * @return : 无 */ void imx6ull_irq(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--; /* 中断执行完成,中断嵌套寄存器减一 */ }
针对于按键中断实验,也就是设置 GPIO_ICR1
或 GPIO_ICR2
寄存器。详细描述及具体触发方式包括:
00
:低电平01
:高电平10
:上升沿11
:下降沿通过设置 GPIOx_IMR
进行开启或关闭中断。
GPIO_EDGE_SEL
可用于覆盖 ICR
寄存器的配置。如果设置了 GPIO_EDGE_SEL
位,则相应信号中的上升沿或下降沿将产生中断。重置时,所有位都被清除(ICR
不会被覆盖)。
GPIO_ISR
用作中断状态指示器。每个位指示是否满足相应输入信号的中断条件。当满足中断条件(由相应的中断条件寄存器字段确定)时,将设置此寄存器中的相应位。同步的读取访问权限中需要两个等待状态。重置需要一个等待状态。
检测到中断,相应 bit
位会被置 1
。GPIOx_ISR
被置 1
,和中断是否开启无关(和 GPIO_IMR
寄存器状态无关)。检测到活动中中断条件后,相应的位将保持设置状态,直到软件清除为止。通过将 1 写入相应的位位置来清除状态标志。
通过原理图分析可以得到,当按键按下后 KEY0
为低电平,当按键释放后 KEY0
为高电平。通过原理图可以确定 KEY0
连接在 UART1_CTS
引脚上。
#ifndef __IMX6ULL_GPIO_H__ #define __IMX6ULL_GPIO_H__ #include "imx6ull.h" /* * GPIO 输入输出枚举配置 */ typedef enum{ GPIO_INPUT = 0, /* 输入 */ GPIO_OUTPUT = 1 /* 输出 */ }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_direction_t direction; /* GPIO方向:输入还是输出 */ uint8_t output_logic; /* 如果是输出的话,默认输出电平 */ gpio_interrupt_mode_t interruptMode; /* 中断方式 */ }gpio_pin_config_t; int gpio_pinread(GPIO_Type *base, int pin); void gpio_pinwrite(GPIO_Type *base, int pin, int value); void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config); void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode); 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 "imx6ull_gpio.h" /* * @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 : 要初始化的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 == GPIO_INPUT){ /* GPIO作为输入 */ base->GDIR &= ~( 1 << pin); }else if(config->direction == GPIO_OUTPUT){ /* GPIO作为输出 */ base->GDIR |= 1 << pin; gpio_pinwrite(base,pin, config->output_logic); /* 设置默认输出电平 */ } gpio_intconfig(base, pin, config->interruptMode); /* 中断功能配置 */ } /* * @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); }
使能 UART1_CTS
引脚对应中断配置。UART1_CTS
引脚为 GPIO_IO18
。
具体配置如下:
1、使能 67+32=99
中断 ID
位(前 32
个中断 ID
预留给 SPI
和 PPI
中断)
2、设置中断优先级
3、注册 GPIO1_IO18
中断处理函数
代码设计思路:
1、设置 GPIO1_IO18
引脚复用功能
2、设置 GPIO1_IO18
引脚电平属性
3、设置 GPIO1_IO18
引脚为输入
4、配置中断
GPIO1_IO18
引脚相应通道5、中断服务函数功能是对 led
灯进行亮灭翻转。
#ifndef __KEY_H__
#define __KEY_H__
void key_init(void);
int key_get_value(void);
int key_get_status(void);
void gpio1_io18_irqhandler(void);
#endif
#include "key.h" #include "imx6ull.h" #include "delay.h" #include "imx6ull_gpio.h" #include "int.h" #include "led.h" void key_init(void) { gpio_pin_config_t pin_config; pin_config.direction = GPIO_INPUT; pin_config.interruptMode = kGPIO_IntFallingEdge; /* 1、初始化 IO 复用, 复用为 GPIO1_IO18 */ IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0); /* 2、、配置 UART1_CTS_B 的 IO 属性 * bit 16:0 HYS 关闭 * bit [15:14]: 11 默认 22K 上拉 * bit [13]: 1 pull功能 * bit [12]: 1 pull/keeper 使能 * bit [11]: 0 关闭开路输出 * bit [7:6]: 10 速度 100Mhz * bit [5:3]: 000 关闭输出 * bit [0]: 0 低转换率 */ IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080); /* 3、初始化 GPIO GPIO1_IO18 设置为输入*/ gpio_init(GPIO1,18,&pin_config); /* 4、中断配置 */ GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */ imx6ull_register_irqhandler(GPIO1_Combined_16_31_IRQn, (imx6ull_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注册中断服务函数 */ gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */ } int key_get_value(void) { return gpio_pinread(GPIO1,18); } int key_get_status(void) { static unsigned char key_status = 1; // 默认状态,按键松开,KEY0 为高电平 if((key_status == 1)&&(key_get_value() == 0)){ // 按键按下 delay(10); // 消抖 if(key_get_value() == 0){ key_status = 0; // 按键按下 return 1; } }else{ key_status = 1; return 2; } return 0; } /* * @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; if(state == 0){ led_off(); }else if(state == 1){ led_on(); } } gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */ }
#include "imx6ull.h" #include "clock.h" #include "delay.h" #include "led.h" #include "key.h" #include "int.h" int main(void) { int_init(); clk_enable(); led_init(); key_init(); while(1){ } return 0; }
编译过程如下:
onlylove@ubuntu:~/linux/driver/board_driver/6_int$ make
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/start.o user/start.s
user/start.s: Assembler messages:
user/start.s: Warning: end of file not at end of a line; newline inserted
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/imx6ull_gpio.o imx6ull/src/imx6ull_gpio.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/clock.o bsp/clock/clock.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/delay.o bsp/delay/delay.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/led.o bsp/led/led.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/key.o bsp/key/key.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/int.o bsp/int/int.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ull/inc -I bsp/clock -I bsp/delay -I bsp/led -I bsp/key -I bsp/int -I user -o obj/main.o user/main.c
arm-linux-gnueabihf-ld -Timx6ul.lds -o int.elf obj/start.o obj/imx6ull_gpio.o obj/clock.o obj/delay.o obj/led.o obj/key.o obj/int.o obj/main.o
arm-linux-gnueabihf-objcopy -O binary -S int.elf int.bin
arm-linux-gnueabihf-objdump -D -m arm int.elf > int.dis
onlylove@ubuntu:~/linux/driver/board_driver/6_int$
在开发板上进行测试,按键工作正常。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。