中断系统主要有以下几个关键点:
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
:下降沿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$
在开发板上进行测试,按键工作正常。