按键中断 KEY0 (UART1_CTS 引脚)触发蜂鸣器
1 修改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(快速中断)未定义中断 */
2 定义对应的中断函数
2.1 复位函数
CP15协处理器
1 关闭I,DCache和MMU CP15协处理器 附加 (上电之初,我们的内存初始化比较慢一拍,当cpu初始化了,但内存还没准备好之后,就对内存进行数据读,那么势必会造成了指令取址异常,系统就会挂了。)
mmu在设备上电之初是没有任何作用的,也就是说,在u-boot的初始化之初执行汇编的那一段代码中,包括后面的初始化一些具体的外设时,访问的都是实际的地址,mmu的打开起不到任何的意义
2 设置处理器9中模式(按需)对应SP指针,要使用中断必须设置 IRQ模式下的SP指针 C12
3 清除bss
4 跳转main函数
Cotex-A7 参考手册 107页面 关闭I,DCache和MMU SCTLR寄存器 系统控制寄存器
中断向量偏移设置
将新的中断向量表首地址写入到CP15 的VBAR 寄存器
mcr p15, 0, r0, c12, c0, 0
注意 读改写顺序
分支预测(Branch Prediction): 解决处理分支指令导致流水线失败的数据处理方法 。从P5时代开始的一种先进的,解决处理分支指令(if-then-else)导致流水线失败的数据处理方法,由CPU来判断程序分支的进行方向,能够加快运算速度。
/* 复位中断 */
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寄存器中 */#if 0/* 汇编版本设置中断向量表偏移 */ldr r0, =0X87800000dsb //数据同步指令 isb //指令同步mcr p15, 0, r0, c12, c0, 0dsbisb
#endif/* 设置各个模式下的栈指针,* 注意:IMX6UL的堆栈是向下增长的!* 堆栈指针地址一定要是4字节地址对齐的!!!* DDR范围:0X80000000~0X9FFFFFFF*//* 进入IRQ模式 */mrs r0, cpsrbic 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, cpsrbic 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, cpsrbic 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 /* 打开全局中断 */
#if 0/* 使能IRQ中断 */mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */msr cpsr, r0 /* 将r0重新写入到cpsr中 */
#endifb main /* 跳转到main函数 */
2.2 未定义 \SVC\预取终止、数据终止、未使用等中断
以下代码未实现 中断服务函数 死循环处理
/* 未定义中断 */
Undefined_Handler:ldr r0, =Undefined_Handlerbx r0/* SVC中断 */
SVC_Handler:ldr r0, =SVC_Handlerbx r0/* 预取终止中断 */
PrefAbort_Handler:ldr r0, =PrefAbort_Handler bx r0/* 数据终止中断 */
DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0/* 未使用的中断 */
NotUsed_Handler:ldr r0, =NotUsed_Handlerbx r0/* FIQ中断 */
FIQ_Handler:ldr r0, =FIQ_Handler bx r0
2.3 IRQ 编写
2.3.1 流程
复位后使能了IRQ cpsie i
1 保存中断上下文 push 相关寄存器
2 初始化 GIC控制器
3 设置IRQ中断服务处理函数 system_irqhandler() 此时已经切换到SVC模式(??)
4 进入IRQ模式后恢复现场
寄存器R0-R12属于通用寄存器,除了FIQ工作模式,在其他工作模式下这些寄存器都是共用、共享的。R0-R3通常用来传递函数的参数,R4-R11用来保存程序运算的中间结果或者函数的局部变量等,R12常用来作为函数调用过程中的临时寄存器。ARM处理器有多种工作模式,除了这些在各个模式下通用的寄存器,还有一些寄存器在各自的工作模式下独立存在,如R13,R14,R15,CPSR,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
*/
/* IRQ中断!重点!!!!! */
IRQ_Handler:push {lr} /* 保存lr地址 PC 指针*/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 */
当中断处理程序执行完毕后,需要恢复现场并跳转到原始程序中的下一条指令地址,即将r0-r12、pc和cpsr的值回复到栈中。在ARM架构中,lr(返回地址寄存器)存储了当前指令执行完成后的返回地址,因为指令在执行过程中会将pc(程序计数器)的值加上8(取指+译指+执 行)),因此需要在lr的基础上减4才能得到正确的返回地址。 lr = pc +8
2.3.2 system_irqhandler 通用中断驱动文件编写
system_irqhandler 此函数有一个中断号 参数, 不同的中断源对应不同的中断处 理函数,I.MX6U 有 160 个中断源,所以需要 160 个中断处理函数,我们可以将这些中断处理 函数放到一个数组里面,中断处理函数在数组中的标号就是其对应的中断号。当中断发生以后 函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。
编写前先移植bsp内api 有关中断相关文件 (imx6ull目录)
#ifndef __IMX6UL_H#define __IMX6UL_H/***************************************************************Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.文件名 : imx6ul.h作者 : 左忠凯版本 : V1.0描述 : 包含一些常用的头文件。其他 : 无论坛 : www.wtmembed.com日志 : 初版V1.0 2019/1/3 左忠凯创建***************************************************************/#include "cc.h"#include "MCIMX6Y2.h"#include "fsl_common.h"#i
nclude "fsl_iomuxc.h"#include "core_ca7.h"#endif
在 int目录下 实现 system_irqhandler 函数
1 bsp.init.h 声明中断服务函数形式 注册中断 初始化等 函数
#ifndef _BSP_INT_H
#define _BSP_INT_H
#include "imx6ul.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_int.c
作者 : 左忠凯
版本 : V1.0
描述 : 中断驱动头文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************//* 中断服务函数形式 */
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);
2 bsp_int.c
结构体 sys_irq_handle_t 包含一个中断处理函数和中断处 理函数的用户参数。一个中断源就需要一个 sys_irq_handle_t 变量,I.MX6U 有 160 个中断源, 因此需要 160 个 sys_irq_handle_t 组成中断处理数组。并注册
#include "bsp_int.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_int.c
作者 : 左忠凯
版本 : V1.0
描述 : 中断驱动文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************//* 中断嵌套计数器 */
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) {
//todo}
}
,代码中 默认中断处理函数为循环 未实现
3 GPIO 按键中断
3.1 修改 GPIO 驱动文件 加入GPIO中断处理
1 设置触发方式 GPIO_ICR1/GPIO_ICR2
2 使能GPIO对应的中断确定 中断ID 设置 GPIO_IMR寄存器
3 处理完中断后 清除中断标志位 清除 GPIO_ISR寄存器对应位
4 GIC配置 a 使能中断ID GPIO1_IO18对应 ID 67+32 = 99 b设置中断优先级 c 注册GPIO1_io18中断处理函数
.h
#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#define _BSP_KEY_H
#include "imx6ul.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_gpio.h
作者 : 左忠凯
版本 : V1.0
描述 : GPIO操作文件头文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建V2.0 2019/1/4 左忠凯修改添加GPIO中断相关定义***************************************************************//* * 枚举类型和结构体定义 */
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
bsp.gpio.c
#include "bsp_gpio.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_gpio.h
作者 : 左忠凯
版本 : V1.0
描述 : GPIO操作文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建V2.0 2019/1/4 左忠凯修改:修改gpio_init()函数,支持中断配置.添加gpio_intconfig()函数,初始化中断添加gpio_enableint()函数,使能中断添加gpio_clearintflags()函数,清除中断标志位***************************************************************//** @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);
}
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config) 函数初始化GPIO 输入输出
gpio_intconfig函数 根据gpio位确定 控制寄存器 低16 GPIO_ICR1 然后设置触发方式
3.2 编写按键中断业务逻辑
设置 GPIO 电器属性
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_exit.c
作者 : 左忠凯
版本 : V1.0
描述 : 外部中断驱动。
其他 : 配置按键对应的GPIP为中断模式
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************/
#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 */
/* 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);/* 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); /* 清除中断标志位 */
}
按键消抖 GPIO一般采用施密特触发器硬件消抖。按键中断出发后 打开蜂鸣器 清除中断标志位 至此 中断服务函数结束
注意 bss 清除 代码段的位置 将影响_start 复位后 执行地址,中断向量表偏移地址 不一致 0x87800000