当前位置:   article > 正文

i.MX6ULL裸机开发 六:按键中断实验_寄存器cbar

寄存器cbar

一、中断系统

中断系统主要有以下几个关键点:

1、中断向量表(通过地址偏移找到对应中断服务函数入口地址

2、中断控制器(STM32 使用 NVIC 中断控制器,iMX6ULL 使用 GIC 中断控制器

3、中断使能(全局中断使能和指定中断使能)

4、中断服务函数

注:要注意配置中断向量表起始地址(也就是链接起始地址iMX6ULL 需要通过协处理器配置 VBAR 寄存器(向量表基地址寄存器)和 读取 CBAR 寄存器( GIC 基地址,此寄存器为只读寄存器))

二、Cortex-A7 中断系统

详细内容见《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition》中 B1.8 Exception handling 章节和 B1.9 Exception descriptions 章节。

1、中断向量表

在这里插入图片描述

  • 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 中断

2、中断控制器

详细信息见:GICv2 整理_OnlyLove_的博客-CSDN博客

3、中断使能

1、IRQ和 FIQ总中断使能

寄存器 CPSRI=1 禁止 IRQ,当 I=0 使 能 IRQF=1 禁止 FIQF=0 使能 FIQ

2、外设中断使能

GIC 寄存器 GICD_ISENABLERnGICD_ ICENABLERn 用来完成外部中断的使能和禁止。一个 bit 控制一个中断 ID 的使能。

3、中断优先级设置

GICC_PMR 寄存器配置中断优先级。
在这里插入图片描述
i.MX6ULLCortex-A7 内核,所以支持 32 个优先级,因此 GICC_PMR 要设置为 0b11111000

GICC_BPR 寄存器用来配置抢占优先级和子优先级各占多少位。GICC_BPR 结构如下图所示:
在这里插入图片描述
寄存器 GICC_BPR 只有低 3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同,配置如下图所示:
在这里插入图片描述

4、中断服务函数

具体内容见代码实现。

三、代码实现

1、中断实现设计思路

在这里插入图片描述

2、工程建立

1、工程配置

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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2、makefile

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)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

3、添加中断向量表

添加中断向量表,只实现基本架构。代码实现在 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

编译测试,编译日志如下:

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$
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

通过反汇编文件查看编译结果:


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

……
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

4、添加中断服务函数

1、添加 reset 中断服务函数

reset 中断服务函数为复位后执行第一个函数,主要完成系统运行前的一些初始化功能,具体功能包括:

  • 关闭全局中断
  • 关闭 ICacheDCache
  • 关闭 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函数 			 	*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

以上源码从正点原子例程中拷贝,在开发板验证测试没有问题。

2、添加 IRQ 中断服务函数

IRQ 中断服务函数主要用于处理 imx6ull 外设中断,处理逻辑如下:

  • 保护现场(包括通用寄存器(r0~r3r12)和状态寄存器(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 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

3、移植 SDK 包中中断相关文件

主要移植 core_ca7.h 文件,文件保存在 i.MX6ULL-SDK\CORTEXA\Include 目录下。

注:文件移植正点原子提供。

4、中断初始化相关处理

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--;	/* 中断执行完成,中断嵌套寄存器减一 */

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

5、GPIO 中断设置

1、设置中断触发方式

针对于按键中断实验,也就是设置 GPIO_ICR1GPIO_ICR2 寄存器。详细描述及具体触发方式包括:
在这里插入图片描述

  • 00:低电平
  • 01:高电平
  • 10:上升沿
  • 11:下降沿

2、GPIO 中断开发和关闭

通过设置 GPIOx_IMR 进行开启或关闭中断。
在这里插入图片描述

3、GPIO 边缘选择寄存器

GPIO_EDGE_SEL 可用于覆盖 ICR 寄存器的配置。如果设置了 GPIO_EDGE_SEL 位,则相应信号中的上升沿或下降沿将产生中断。重置时,所有位都被清除(ICR 不会被覆盖)。
在这里插入图片描述

4、中断状态寄存器

GPIO_ISR 用作中断状态指示器。每个位指示是否满足相应输入信号的中断条件。当满足中断条件(由相应的中断条件寄存器字段确定)时,将设置此寄存器中的相应位。同步的读取访问权限中需要两个等待状态。重置需要一个等待状态。
在这里插入图片描述
检测到中断,相应 bit 位会被置 1GPIOx_ISR 被置 1,和中断是否开启无关(和 GPIO_IMR 寄存器状态无关)。检测到活动中中断条件后,相应的位将保持设置状态,直到软件清除为止。通过将 1 写入相应的位位置来清除状态标志。

5、原理图分析

在这里插入图片描述
通过原理图分析可以得到,当按键按下KEY0低电平,当按键释放KEY0高电平。通过原理图可以确定 KEY0 连接在 UART1_CTS 引脚上。

6、代码实现

1、imx6ull_gpio.h
#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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
2、imx6ull_gpio.c
#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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

6、GIC 中断设置

使能 UART1_CTS 引脚对应中断配置。UART1_CTS 引脚为 GPIO_IO18
在这里插入图片描述
具体配置如下:

1、使能 67+32=99 中断 ID 位(前 32 个中断 ID 预留给 SPIPPI 中断)

2、设置中断优先级

3、注册 GPIO1_IO18 中断处理函数

7、key 相关代码实现

代码设计思路:

1、设置 GPIO1_IO18 引脚复用功能

2、设置 GPIO1_IO18 引脚电平属性

3、设置 GPIO1_IO18 引脚为输入

4、配置中断

  • 打开 GPIO1_IO18 引脚相应通道
  • 注册中断服务函数
  • 开启中断

5、中断服务函数功能是对 led 灯进行亮灭翻转。

1、key.h

#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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2、key.c

#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); /* 清除中断标志位 */
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

3、main.c

#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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

四、编译测试

编译过程如下:

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$
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在开发板上进行测试,按键工作正常。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/385611
推荐阅读
  

闽ICP备14008679号