0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

按键中断实验是什么

汽车电子技术 来源:程序猿搬砖 作者:坏人 2023-03-02 16:21 次阅读

前面的按键实验是通过死循环一直读取按钮电平值来判断是否有按下按钮,接下来将使用另外一个更优雅的方式实验按键按下功能-中断。

CPU在正常处理指令的时候会遇到外设打断当前执行逻辑,我们称为异常中断。一系列中断处理集中在一起管理,我们称为异常中断向量表。

中断向量表

Coretex-A系列的中断向量表就是存放在程序起始位置(链接起始地址)的一组由4字节组成的一组数据,Coretex-A 32位处理器每一条指令长度就是4个字节,所以本质上这个中断向量表是一组固定地址的指令。Coretext-A系统CPU总共支持8个中断:

图片图片

这8个中断里面需要特别注意也是需要开发的主要是复位中断IRQ中断复位中断在上电或者按下Reset按钮后硬件加载程序同时PC寄存器位置重置为0x0或者链接起始地址时触发,IRQ中断则是外设触发。每一个中断发生时PC寄存器会被设置成一个固定的地址,而这个地址则对应中断向量表中一条指令。

中断向量表添加到汇编最开始的位置:

/* 从链接起始地址开始,8条4字节的指令组成了ARM的中断向量表 */
/* 中断向量表放在最开始的位置,每一条指令对应了具体的中断处理 */
/* 当发生对应中断时,硬件会把对应的地址设置到pc寄存器,从而执行对应的中断服务函数 */
ldr pc, =Reset_Handler                          /* 0x00: 复位中断 */
ldr pc, =Undefine_Instruction_Handler           /* 0x04: 未定义中断指令 */
ldr pc, =Software_Interrupt_Handler             /* 0x08: 软中断, SVC特权模式 */
ldr pc, =Prefetch_Abort_Handler                 /* 0x0c: 指令预取中止中断 */
ldr pc, =Data_Abort_Handler                     /* 0x10: 数据访问中止中断 */
ldr pc, =Not_Used_Handler                       /* 0x14: 未使用的中断 */
ldr pc, =IRQ_Handler                            /* 0x18: 外部设备中断 */
ldr pc, =FIQ_Handler                            /* 0x1c: 快速中断 */

复位中断服务函数

上电后第一个要触发的则是复位中断,通过向量表中定义的指令可以将程序切换到Reset_Handler处开始执行

  • 关闭IRQ
  • 关闭I,D Cache,以及MMU
  • 设置中断的起始地址,即设置成链接起始地址(因为程序是从链接起始地址开始运行的)
  • 设置IRQ,SVC以及SYS模式下C语言的运行环境(C语言的SP指针栈顶)
  • 打开IRQ
  • 调转到C语言的main函数开始运行
cpsid i                             /* 关闭IRQ,  此时IRQ还没有配置完成,所以关闭*/

/* 
    在设备上电启动时,执行的代码访问的外设都是实际地址,
    mmu与cache此时的意义不大,
    这个时候为了防止cache与mmu可能导致的问题会先将mmu与cache关闭 
*/
/* CP15: SCTLR */
/* 关闭 I-Cache, D-Cache, MMU */
MRC p15, 0, r0, c1, c0, 0            /* 将SCTLR寄存器读取到r0寄存器 */
bic r0, r0, #(1 << 0)                /* 关闭MMU */
bic r0, r0, #(1 << 1)                /* 关闭对齐 */
bic r0, r0, #(1 << 11)               /* 关闭分支预测 */
bic r0, r0, #(1 << 12)               /* 关闭i-cache */
bic r0, r0, #(1 << 2)                /* 关闭d-cache */
MCR p15, 0, r0, c1, c0, 0            /* 将r0寄存器数据写入到SCTLR寄存器 */

/* 设置中断向量偏移,在发生中断之前设置即可,也可以在C语言中设置 */

ldr r0, =0x87800000                 /* 将0x87800000这个立即数写入到 r0寄存器, 也就是链接起始地址*/
dsb                                 /* 这里涉及到了改变读取内存的地址起始地址,需要使用内存屏障指令进隔断,保证前后读取指令都是正常的地址 */
isb                                  /* 这里涉及到了改变读取内存的地址起始地址,需要使用内存屏障指令进隔断,保证前后读取指令都是正常的地址 */
MCR p15, 0, r0, c12, c0, 0          /* 将r0的数据写入到VBAR寄存器中,表示向量偏移地址是0x87800000 */
dsb                                 /* 这里涉及到了改变读取内存的地址起始地址,需要使用内存屏障指令进隔断,保证前后读取指令都是正常的地址 */
isb                                 /* 这里涉及到了改变读取内存的地址起始地址,需要使用内存屏障指令进隔断,保证前后读取指令都是正常的地址 */

/* 设置不同模式下的sp指针,每一个模式的sp对应不同的物理地址,当进入不同工作模式时C语言会在不同的物理sp指针指向的栈内存上工作 */
/* 进入IRQ模式 */
mrs r0, cpsr
bic r0, r0, #0x1f                  /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4  */
orr r0, r0, #0x12                  /* r0或上0x12,表示使用IRQ模式     */
msr cpsr, r0                  /* 将r0 的数据写入到cpsr_c中      */
ldr sp, =0x80600000                 /* 设置栈指针    */

/* 进入SYS模式 */
mrs r0, cpsr
bic r0, r0, #0x1f                  /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4  */
orr r0, r0, #0x1f                  /* r0或上0x1f,表示使用SYS模式     */
msr cpsr, r0                  /* 将r0 的数据写入到cpsr_c中      */
ldr sp, =0x80400000                 /* 设置栈指针    */

/* 进入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                 /* 设置栈指针    */

cpsie i                             /* 打开IRQ */
b main                    /* 跳转到C语言main函数    */

IRQ外设中断服务函数

当一个外设触发中断(比如按键按下后)会执行IRQ_Handler函数。

  • 中断发生是首先保护现场(lr, r0-r12寄存器, 保存spsr寄存器数据)
  • 读取GIC寄存器组的起始地址
  • 通过对GIC寄存器组基地址偏移得到CPU Interface寄存器组
  • 通过对CPU Interface基地址进行偏移得到GICC_IAR寄存器,它保存了触发中断的CPU号与中断号
  • 读取中断号(目前只有一个CPU内核,可以不管CPU号)放入r0寄存器,调用对应的C语言函数执行中断
  • 在执行中断前,首先需要将模式切换到SVC,这样在执行中断的时候可以允许新的IRQ中断触发
  • 执行C语言的中断逻辑后切换到IRQ模式,继续完成中断收尾工作
  • 恢复spsr寄存器数据
  • 恢复中断执行前的现场(lr, r0-r12)
  • 将lr地址减4字节再给到pc寄存器,恢复中断前的执行指令
/* 
    中断发生时, IRQ模式下的lr(LR_svc物理)寄存器保存中断时刻的PC寄存器
    通过使用push命令将lr的值压入栈,这样的目的是为了在执行完当前中断服务函数
    后可以顺利的返回到中断前的执行位置,因为在执行中断服务函数的时候lr里面的值可能发生变化
    比如: 在内部使用了blx调用其它函数,新的IRQ中断进入
*/
push {lr}    
/*
    保存中断发生时的执行现场(r0-r12)
    从User/Sys模式切换到IRQ模式,r0-r12寄存器是通用的,所以需要将这些寄存器都压入栈保存起来,
    由于在执行IRQ中断函数时模式已经切换,此时的sp指针已经是IRQ模式下的栈地址了,所以r0-r12保存到了
    IRQ对应的栈空间中,恢复现场的时候只需要入栈即可
 */   
push {r0-r12}
// push {r0-r3, r12}
/* 
    将spsr(SPSR_irq)寄存器的值压入栈,spsr保存了中断发生时的cpsr寄存器的值,
    中断执行完成之后需要恢复
*/
mrs r0, spsr        
push {r0}

/* GIC寄存器组的基地址(起始地址,通过起始地址可以访问得所有的GIC寄存器) */
/* 将GIC基地址读取到r1寄存器中 */
MRC p15, 4, r1, c15, c0, 0          // Read Configuration Base Address Register

/* 0x2000 - 0x3FFF 是GIC中CPU Interface的范围 */
/* 将r1中保存的GIC基地址偏移0x2000再保存到r1中 */
/* 此时r1中保存的是CPU Interface的基地址 */
add r1, r1, #0x2000
/*
    r1(CPU Interface基地址)偏移0x0c得到GICC_IAR寄存器地址,
    将GICC_IAR寄存器的值读取到r0中,
    GICC_IAR保存了IRQ中断的中断号与CPU号(多核时使用),
    通过中断号即可明确具体的中断来源并对中断进行响应
 */
ldr r0, [r1, #0x0c]
/*
    由于要进入到SVC模式了,需要将r0, r1两个通用寄存器的数据保存到栈里,
    防止在SVC模式下后r0,r1数据丢失
    此时r0, r1保存到的是IRQ模式下的栈空间,
 */
push {r0, r1}
/*
    将CPSR寄存器的M[4:0]值改成10011, 让CPU进入到SVC模式,
    进行SVC模式之后,当我们处理当前中断时,
    系统可以再次响应IRQ中断
 */
cps #0x13                       // 进入到SVC
/*
    进入到svc模式后先将lr的数据压入栈,执行完后再恢复
    因为接下来要使用blx调用C语言函数,会改变lr寄存器的数据
 */
push {lr} 
ldr r2, =system_irq_handler     // 将C语言写的中断服务函数的地址加载到r2寄存器
blx r2                          // 调用C语言的中断处理函数, r0为函数参数
pop {lr}                        // 调用完具体中断处理函数后,lr恢复
cps #0x12                       // 进入到IRQ,执行完中断服务函数后进入IRQ不能再次响应IRQ中断,直到当前的IRQ中断完成
pop {r0, r1}                    // 恢复IRQ模式下r0,r1寄存器的数据
/*
    此时r0保存的是GICC_IAR寄存器的数据,
    将GICC_IAR的数据写入到GICC_EOIR寄存器,表示当前IRQ中断处理完成
 */
str r0, [r1, #0x10]

pop {r0}                        // 将栈顶的数据(此时栈顶保存的是spsr寄存器的值)出栈到r0寄存器
/// spsr_cxsf其中(cxsf表示4个不同的8bit位数据,后续表示此次命令影响的数据位), spsr_cxsf等于spsr
msr spsr_cxsf, r0               // 恢复spsr寄存器数据
pop {r0-r12}                    // 恢复r0-r12寄存器的数据
pop {lr}                        // 恢复lr寄存器的数据
subs pc, lr, #4                 // 将lr - 4字节赋值给lc, 恢复中断前的执行命令继续执行

IRQ中断服务通用逻辑处理函数

我们需要编写一个通用的中断处理函数,从参数(r0寄存器中的GICC_IAR寄存器的数据)中提取中断号,根据对应的中断号再调用注册进来的具体的中断函数,比如: 按键中断函数

void system_irq_handler(unsigned int gicciar)
{
    uint32_t irqNum = gicciar & 0x3FF;
    if (irqNum >= NUMBER_OF_INT_VECTORS)
        return;
    Interrupt_Irq_Count++;
    Interrupt_Irq_Data iid = _irqInterruptTables[irqNum];
    iid.handler(irqNum, iid.context);
    Interrupt_Irq_Count--;
}

外设中断驱动

  • GPIO复用以及配置电气属性
  • 配置GPIO的输入与输出
  • 初始化GPIO中断
void GPIO_Init_Interrupt(GPIO_Type *base, int pin, GPIO_INTERRUPT_MODE mode)
{
    /// 首先将GPIO的edge_sel寄存器对应pin位清0,如果为1则会使ICR寄存器的配置无效
    base->EDGE_SEL &= ~(1 << pin);
    /// 对应ICR的索引(按2位为一个单元)
    int icrOffset = pin;
    /// 具体的icr寄存器地址
    __IO uint32_t *p_icr = NULL;
    if (pin < 16)
    {
        p_icr = &(base->ICR1);
    }
    else
    {
        p_icr = &(base->ICR2);
        icrOffset -= 16;
    }

    switch (mode)
    {
    case GPIO_INTERRUPT_MODE_NO_INTERRUPT:
        break;
    case GPIO_INTERRUPT_MODE_LOW:
        *p_icr &= ~(3 << (2 * icrOffset));
        break;
    case GPIO_INTERRUPT_MODE_HIGH:
        *p_icr &= ~(3 << (2 * icrOffset));
        *p_icr |= 1 << (2 * icrOffset);
        break;
    case GPIO_INTERRUPT_MODE_RISING_EDGE:
        *p_icr &= ~(3 << (2 * icrOffset));
        *p_icr |= 2 << (2 * icrOffset);
        break;
    case GPIO_INTERRUPT_MODE_FALLING_EDGE:
        *p_icr &= ~(3 << (2 * icrOffset));
        *p_icr |= 3 << (2 * icrOffset);
        break;
    case GPIO_INTERRUPT_MODE_RISING_AND_RALLING_EDGE:
        *p_icr &= ~(3 << (2 * icrOffset));
        base->EDGE_SEL |= (1 << pin);
        break;
    }
}
  • I.MX6ULL的GIC使能对应中断ID的中断
/// 使用GPIO1的IO18对应的IRQ中断ID(GPIO1_Combined_16_31_IRQn)
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
  • 注册对应中断ID的中断服务处理函数
/// 注册对应IRQ中断号的中断服务函数
Interrupt_Irq_Handler_Register(GPIO1_Combined_16_31_IRQn, (Interrupt_Irq_Handler)Key0_Interrupt_Irq_Handler, NULL);
/// 使用GPIO01_IO18中断
  • GPIO使能中断
/// 使用GPIO01_IO18中断
GPIO_Enable_Interrupt(GPIO1, 18);
  • 在中断服务处理函数中处理中断
void Key0_Interrupt_Irq_Handler(unsigned int gicciar, void *context)
{
    /// 中断服务函数要求快进快出,这里没有定时器
    /// 为了处理抖动暂时使用Delay来解决
    /// 以后使用定时器来处理
    Delay(10);
    if (GPIO_ReadValue(GPIO1, 18) == 0)
    {
        Beep_On();
        Led_On();
        Delay(350);
        Beep_Off();
        Led_Off();
    }
    /// 中断处理完成后,清楚中断标志位
    GPIO_Clean_Interrupt_Flag(GPIO1, 18);
}
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
收藏 人收藏

    评论

    相关推荐

    基于RoboMasterC板的RT-Thread使用分享—按键中断实验

    说起中断,我们常常就会提到一个经典的例子,就是我们在家里处理手头上事情的时候,热水煮开了,这时候我们就需要放下手头的事情,去关掉煤气炉。这个就是中断
    的头像 发表于 10-16 15:31 917次阅读
    基于RoboMasterC板的RT-Thread使用分享—<b class='flag-5'>按键</b><b class='flag-5'>中断</b><b class='flag-5'>实验</b>

    #硬声创作季 #Linux 学Linux-4.13.4 按键中断实验驱动编写1-1

    Linux
    水管工
    发布于 :2022年11月10日 20:52:33

    #硬声创作季 #Linux 学Linux-4.13.4 按键中断实验驱动编写1-2

    Linux
    水管工
    发布于 :2022年11月10日 20:52:54

    #硬声创作季 #Linux 学Linux-4.13.5 按键中断实验驱动编写2-1

    Linux
    水管工
    发布于 :2022年11月10日 20:53:22

    #硬声创作季 #Linux 学Linux-4.13.5 按键中断实验驱动编写2-2

    Linux
    水管工
    发布于 :2022年11月10日 20:53:57

    第13.4讲 Linux中断实验 按键中断实验驱动编写上 - 第1节 #硬声创作季

    电路电容LINUX内核
    充八万
    发布于 :2023年08月14日 19:52:14

    为什么我把按键改成key2就不起作用了?

    按键中断实验里,我想把key2改为按键扫描模式,key0。1还为中断模式,但是我下面这样改了以后key2就不起作用了/*u16 n=0;v
    发表于 07-15 03:46

    【正点原子FPGA连载】第四章GPIO之MIO按键中断实验-领航者 ZYNQ 之嵌入式开发指南

    原子公众号,获取最新资料第四章GPIO之MIO按键中断实验中断是一种当满足要求的突发事件发生时通知处理器进行处理的信号。中断可以由硬件处理单
    发表于 08-29 16:21

    【正点原子FPGA连载】第四章按键中断实验--摘自【正点原子】达芬奇之Microblaze 开发指南

    官方B站:https://space.bilibili.com/3946208905)对正点原子FPGA感兴趣的同学可以加群讨论:9056247396)关注正点原子公众号,获取最新资料第四章按键中断实验
    发表于 10-17 18:24

    STM32按键中断实验

    按键中断实验实验2是按键查询一、实验原理1、按键使
    发表于 08-13 06:05

    按键中断实验概述

    按键中断实验概述1.1 资源概述开发板:正点原子STM32F103zet6精英开发板控芯片型号:STM32F103ZET6开发板资料1.2实现功能key1:红灯亮,再按一下红灯灭key2:绿灯亮
    发表于 01-11 08:08

    键盘与按键中断实验相关资料推荐

    这里写自定义目录标题键盘与按键中断实验代码图像使用控件键盘与按键中断实验4X4键盘(
    发表于 01-13 06:18

    记录外部中断函数的学习情况

    本章主要记录外部中断函数的学习情况,实验过程首先完成按键扫描实验,使用外部中断完成了按键
    发表于 02-24 07:42

    INT0中断实验

    INT0中断实验。 1、按键中断实验。低电平中断,在
    发表于 06-30 11:22 2888次阅读

    STM32——中断、EXTI、按键中断实验

    STM32中断——总结及实操一、中断是什么?1.1 中断的含义1.2 中断的作用(了解即可)1.3 中断的流程二、
    发表于 01-14 15:48 4次下载
    STM32——<b class='flag-5'>中断</b>、EXTI、<b class='flag-5'>按键</b><b class='flag-5'>中断</b><b class='flag-5'>实验</b>