按键和中断

按键

按键物理概念

按键是一个正常情况下断开,只有在按下才必合的物理器件,按键有一对常开触点和常闭触点.

按键电学特性

按键用于做输入设备,由人来操作向Soc发送按键信号,按键信号分按下信号和弹开信号

按键接法

  • SW5<->GPH0_2
  • SW6<->GPH0_3
  • SW7<->GPH2_0
  • SW8<->GPH2_1
  • SW9<->GPH2_2
  • SW10<->GPH2_3

按键处理

按键一旦按下,则这些引脚被接通,按键有两种处理方式,轮询和中断

  • 轮询:Soc主动每隔一段时间读取按键对应GPIO的电平,从而判断按键是否按下,
  • 中断:Soc事先设定好GPIO触发的中断对应的中断处理程序ISR,一旦GPIO的触发中断,则会执行该中断处理程序

综合来说,中断方式处理更合理。

轮询方式处理按键

寄存器配置

  • GPH0CON:0xE0200C00
    • 8-11位配置GPH0CON(2):配置为0000为输入方式
    • 12-15位配置GPH0CON(3):配置为0000为输入方式
  • GPH0DAT:0xE0200C04,数据寄存器
  • GPH2CON:0xE0200C40
    • 0-15位配置GPH0CON(0123):配置为16个0为输入方式
  • GPH0DAT:0xE0200C44,数据寄存器

代码

Key初始化
// 定义寄存器宏
#define GPH0CON 0xE0200C00
#define GPH0DAT 0xE0200C04
#define GPH2CON 0xE0200C40
#define GPH2DAT 0xE0200C44

#define rGPH0CON (*(volatile unsigned int *)GPH0CON)
#define rGPH0DAT (*(volatile unsigned int *)GPH0DAT)
#define rGPH2CON (*(volatile unsigned int *)GPH2CON)
#define rGPH2DAT (*(volatile unsigned int *)GPH2DAT)

//初始化按键
void key_init() {
    // 设置寄存器为输入模式
    rGPH0CON &= ~(0xFF<<8);
    rGPH2CON &= ~(0xFFFF<<0);
}
开启轮询
//轮询按键事件
void key_polling() {
    while(1) {
        // 依次读出每个GPIO值,判断为1则为弹起,0为按下
        // SW5
        if(rGPH0DAT & (1<<2)) {
            // SW5没有按下
            led_off();
            printf("closing LED\r\n");
        } else {
            // SW5按下
            led_light_3();
            printf("Key SW5 is pressed,It's name is LEFT\r\n");
        }

        // SW6
        if(rGPH0DAT & (1<<3)) {
            // SW6没有按下
            led_off();
            printf("closing LED\r\n");
        } else {
            // SW6按下
            led_light_4();
            printf("Key SW6 is pressed,It's name is DOWN\r\n");
        }

        // SW7
        if(rGPH2DAT & (1<<0)) {
            // SW7没有按下
            led_off();
            printf("closing LED\r\n");
        } else {
            // SW7按下
            led_light_5();
            printf("Key SW7 is pressed,It's name is UP\r\n");
        }

        // SW8
        if(rGPH2DAT & (1<<1)) {
            // SW8没有按下
            led_off();
            printf("closing LED\r\n");
        } else {
            // SW8按下
            led_light_6();
            printf("Key SW8 is pressed,It's name is RIGHT\r\n");
        }

        // SW9
        if(rGPH2DAT & (1<<2)) {
            // SW9没有按下
            led_off();
            printf("closing LED\r\n");
        } else {
            // SW9按下
            led_light_35();
            printf("Key SW9 is pressed,It's name is BACK\r\n");
        }

        // SW10
        if(rGPH2DAT & (1<<3)) {
            // SW10没有按下
            led_off();
            printf("closing LED\r\n");
        } else {
            // SW10按下
            led_light_46();
            printf("Key SW10 is pressed,It's name is MENU\r\n");
        }
    }
}

按键消抖

按键本身会有抖动信号,消抖思路是发现一次按键事件后,不处理,等待一段时间(根据具体产品而异)再次获取键值,如果两次键值一致,则确认该按键事件,进行处理,如果不一致,则再次获取键值继续判断。

中断

中断是嵌入式系统模拟现实中的处理方式的一种工作机制,用于解决宏观上的并行需求,中断流程一般是当中断发生时,中断源发出中断信号通知CPU去处理该中断,CPU收到中断信号,会先保存好常规任务现场,然后执行中断处理程序ISR,执行完ISR就恢复常规任务现场,继续执行常规任务。
通过中断机制,单核CPU可以实现宏观上的并行。

异常向量表

异常向量表是CPU中某些特定地址的定义标识,当中断发生时,中断源需要通知CPU来处理中断,这时就需要异常向量表。
在CPU设计时,预先定义了一些特定地址作为异常入口地址,例如定义0x00000000为复位异常向量地址,则发生复位异常时CPU自动跳转到0x00000000地址执行该处的代码,如果外部中断的异常向量地址为0x00000008,则发生外部中断时,CPU跳转到0x00000008地址执行指令。
异常向量同时需要软件支持,软件需要把处理该异常的代码首地址填入该异常向量中,这样当CPU跳转到异常向量表中处理该地址时,就会执行我们写好的异常处理代码。
异常向量表中的异常向量不是绝对地址,通常都是相当于某个基地址的偏移。

异常和中断

对于Soc来说,中断是异常的一种,包括我们以前说的七种异常模式中就包含有中断和快速中断,一般中断指Soc中的内部外设产生的打算常规任务,或者是GPIO引脚传回来的中断信号,这种称作外部中断。

异常向量表编程

S5PV210的异常向量表可以改变,可以通过CP15协处理器设置异常向量表,以适应操作系统的需求。

异常向量寄存器定义

系统中刚启动时由于DRAM没有初始化,程序都在SRAM中运行,S5PV210在iRAM中为我们设置异常向量表以供使用,查表得知S5PV210iRAM中异常向量表起始地址为0xD0037400,则根据偏移量可以得到其余的异常处理入口:

  • Reset:+0x00 = 0xD0037400
  • Undefined Instruction:+0x04 = 0xD0037404
  • Software Interrupt:+0x08= 0xD0037408
  • Prefetch Abort:+0x0C= 0xD003740C
  • Data Abort:+0x10= 0xD0037410
  • (Reserved):+0x14= 0xD0037414
  • IRQ:+0x18= 0xD0037418
  • FIQ:+0x1C= 0xD003741C

定义在头文件中的代码如下:

#ifndef _INT_H_
#define _INT_H_

// 定义中断向量表基地址
#define EX_VECT_TAB_BASE_ADDR 0xD0037400
// 根据位移定义各个中断向量地址
#define EX_VECT_RESET_ADDR (EX_VECT_TAB_BASE_ADDR + 0x00)
#define EX_VECT_UNDEFINST_ADDR (EX_VECT_TAB_BASE_ADDR + 0x04)
#define EX_VECT_SOFTINT_ADDR (EX_VECT_TAB_BASE_ADDR + 0x08)
#define EX_VECT_PREFAB_ADDR (EX_VECT_TAB_BASE_ADDR + 0x0C)
#define EX_VECT_DATAAB_ADDR (EX_VECT_TAB_BASE_ADDR + 0x10)
#define EX_VECT_RESERVED_ADDR (EX_VECT_TAB_BASE_ADDR + 0x14)
#define EX_VECT_IRQ_ADDR (EX_VECT_TAB_BASE_ADDR + 0x18)
#define EX_VECT_FIQ_ADDR (EX_VECT_TAB_BASE_ADDR + 0x1C)
// 定义各个地址的值访问方式
#define rEX_VECT_RESET_ADDR (*(volatile unsigned int *)EX_VECT_RESET_ADDR)
#define rEX_VECT_UNDEFINST_ADDR (*(volatile unsigned int *)EX_VECT_UNDEFINST_ADDR)
#define rEX_VECT_SOFTINT_ADDR (*(volatile unsigned int *)EX_VECT_SOFTINT_ADDR)
#define rEX_VECT_PREFAB_ADDR (*(volatile unsigned int *)EX_VECT_PREFAB_ADDR)
#define rEX_VECT_DATAAB_ADDR (*(volatile unsigned int *)EX_VECT_DATAAB_ADDR)
#define rEX_VECT_IRQ_ADDR (*(volatile unsigned int *)EX_VECT_IRQ_ADDR)
#define rEX_VECT_FIQ_ADDR (*(volatile unsigned int *)EX_VECT_FIQ_ADDR)


#endif

异常向量表初始化

#include "interrupt.h"

// 初始化异常向量表
void ex_vector_tab_init() {
    // 函数名其实就是该函数的首地址,就是该函数的指针
    rEX_VECT_RESET_ADDR = handle_reset_exception;
    rEX_VECT_UNDEFINST_ADDR = handle_undefinst_exception;
    rEX_VECT_SOFTINT_ADDR = handle_softint_exception;
    rEX_VECT_PREFAB_ADDR = handle_prefab_exception;
    rEX_VECT_DATAAB_ADDR = handle_dataab_exception;
    rEX_VECT_IRQ_ADDR = IRQ_handle;
    rEX_VECT_FIQ_ADDR = handle_fiqt_exception;
}

中断处理的现场保护和恢复

中断需要先在汇编中进行处理,以IRQ异常为例,大致步骤如下:

  • 设置IRQ栈
  • 保存LR,其实是保存原来模式中的PC到IRQ的LR中,这里要注意流水线的问题
  • 保存R0-R12
  • 调用真正的异常处理代码
  • 恢复现场,和保存现场相仿

汇编代码如下:

// 在汇编中做中断模式下的现场保护和恢复,并且调用真正的中断处理程序
IRQ_handle:
    ldr sp,=IRQ_STACK   // 设置IRQ栈
    sub lr,lr,#4        // 由于ARM流水线,所以PC的值会比真正的正在执行的语句的值+8,所以需要-4得到真正的PC的值
    stmfd sp! {r0-r12,lr}   // 保存r0-r12和lr到栈

    bl handle_irq_exception // 调用中断处理程序并返回
    ldrfd sp! {r0-r12,pc}^  // 恢复现场

中断处理程序

S5PV210的中断处理使用了四组寄存器,这四组寄存器的每一个寄存器的每个位都有不同功能:

  • VICnINTENABLE:interrupt enable,中断使能寄存器,如果要使能某个中断,在该中断对应的VICnINTENABLE寄存器的对应位上写1即可
  • VICnINTENCLEAR:interrupt enable clear,中断禁止寄存器,如果要使能某个中断,在该中断对应的VICnINTENCLEAR寄存器的对应位上写1即可
  • VICnINTSELECT:中断模式寄存器,可以设置为IRQ或者FIQ模式
    • IRQ:普通中断,中断速度处理一般
    • FIQ:快速中断,提供了一种更快响应处理中断的通道,用于实时性高的场景,由在CPU设计时预先提供的机制来实现,但是只能有一个中断源被设置为FIQ模式
    • 为什么FIQ比IRQ的速度快?在ARM工作模式的37个寄存器中,只有在FIQ模式中的r8-r12是专用的,所以现场的保存和恢复比较快,如果只用r8-r12,甚至可以不用保存恢复现场,第二,FIQ在异常向量表的末尾,所以FIQ异常向量之后的区域可以直接放置中断处理程序,不用放置函数指针来跳转,所以速度较快
  • VICnIRQSTATUS:中断状态寄存器,可以根据该寄存器中的位来判断中断是否发生
  • VICnVECTADDR0-VICnVECTADDR31 :中断的ISR地址存放在这些寄存器中,在初始化中断时,把中断ISR的地址直接放到对应的VICnVECTADDRn中即可
  • VICnADDRESS:当中断发生时,这个寄存器中存放的就是处理该中断的ISR地址,我们只需要去读取这个地址执行程序即可,这个地址就是VICnVECTADDR0-VICnVECTADDR31其中的一个值,
  • VICnVECTPRIORITY0-VICnVECTPRIORITY31:中断优先级寄存器,设置当多个中断发生时被处理的优先级,高优先级的中断可以打断低优先级的中断的处理

中断控制器代码

寄存器宏定义

首先我们要根据上一节的分析,查找所有中断寄存器的地址并进行宏定义

//// Interrupt
#define VIC0_BASE                   (0xF2000000)
#define VIC1_BASE                   (0xF2100000)
#define VIC2_BASE                   (0xF2200000)
#define VIC3_BASE                   (0xF2300000)

// VIC0
#define     VIC0IRQSTATUS           ( *((volatile unsigned long *)(VIC0_BASE + 0x00)) )
#define     VIC0FIQSTATUS           ( *((volatile unsigned long *)(VIC0_BASE + 0x04)) )
#define     VIC0INTSELECT           ( *((volatile unsigned long *)(VIC0_BASE + 0x0c)) )
#define     VIC0INTENABLE           ( *((volatile unsigned long *)(VIC0_BASE + 0x10)) )
#define     VIC0INTENCLEAR          ( *((volatile unsigned long *)(VIC0_BASE + 0x14)) )
#define     VIC0VECTADDR            (VIC0_BASE + 0x100)
#define     VIC0ADDR                ( *((volatile unsigned long *)(VIC0_BASE + 0xf00)) )

// VIC1
#define     VIC1IRQSTATUS           ( *((volatile unsigned long *)(VIC1_BASE + 0x00)) )
#define     VIC1FIQSTATUS           ( *((volatile unsigned long *)(VIC1_BASE + 0x04)) )
#define     VIC1INTSELECT           ( *((volatile unsigned long *)(VIC1_BASE + 0x0c)) )
#define     VIC1INTENABLE           ( *((volatile unsigned long *)(VIC1_BASE + 0x10)) )
#define     VIC1INTENCLEAR          ( *((volatile unsigned long *)(VIC1_BASE + 0x14)) )
#define     VIC1VECTADDR            (VIC1_BASE + 0x100)
#define     VIC1ADDR                ( *((volatile unsigned long *)(VIC1_BASE + 0xf00)) )

// VIC2
#define     VIC2IRQSTATUS           ( *((volatile unsigned long *)(VIC2_BASE + 0x00)) )
#define     VIC2FIQSTATUS           ( *((volatile unsigned long *)(VIC2_BASE + 0x04)) )
#define     VIC2INTSELECT           ( *((volatile unsigned long *)(VIC2_BASE + 0x0c)) )
#define     VIC2INTENABLE           ( *((volatile unsigned long *)(VIC2_BASE + 0x10)) )
#define     VIC2INTENCLEAR          ( *((volatile unsigned long *)(VIC2_BASE + 0x14)) )
#define         VIC2VECTADDR            (VIC2_BASE + 0x100)
#define         VIC2ADDR                ( *((volatile unsigned long *)(VIC2_BASE + 0xf00)) )

// VIC3
#define     VIC3IRQSTATUS           ( *((volatile unsigned long *)(VIC3_BASE + 0x00)) )
#define     VIC3FIQSTATUS           ( *((volatile unsigned long *)(VIC3_BASE + 0x04)) )
#define     VIC3INTSELECT           ( *((volatile unsigned long *)(VIC3_BASE + 0x0c)) )
#define     VIC3INTENABLE           ( *((volatile unsigned long *)(VIC3_BASE + 0x10)) )
#define     VIC3INTENCLEAR          ( *((volatile unsigned long *)(VIC3_BASE + 0x14)) )
#define         VIC3VECTADDR            (VIC3_BASE + 0x100)
#define         VIC3ADDR                ( *((volatile unsigned long *)(VIC3_BASE + 0xf00)) )
中断编号定义

根据数据手册,给所有相关的中断源编号

// 中断源编号
#define INT_LIMIT               (96)

//INT NUM - VIC0
#define NUM_EINT0               (0)
#define NUM_EINT1               (1)
#define NUM_EINT2               (2)
#define NUM_EINT3               (3)
#define NUM_EINT4               (4)
#define NUM_EINT5               (5)
#define NUM_EINT6               (6)
#define NUM_EINT7               (7)
#define NUM_EINT8               (8)
#define NUM_EINT9               (9)
#define NUM_EINT10              (10)
#define NUM_EINT11              (11)
#define NUM_EINT12              (12)
#define NUM_EINT13              (13)
#define NUM_EINT14              (14)
#define NUM_EINT15              (15)
#define NUM_EINT16_31           (16)
#define NUM_Reserved17          (17) 
#define NUM_MDMA                (18)
#define NUM_PDMA0               (19)
#define NUM_PDMA1               (20)
#define NUM_TIMER0              (21)
#define NUM_TIMER1              (22)
#define NUM_TIMER2              (23)
#define NUM_TIMER3              (24)
#define NUM_TIMER4              (25)
#define NUM_SYSTIMER            (26)
#define NUM_WDT                 (27)
#define NUM_RTC_ALARM           (28)
#define NUM_RTC_TICK            (29)
#define NUM_GPIOINT             (30)
#define NUM_FIMC3               (31)

//INT NUM - VIC1
#define NUM_CORTEX0             (32+0)
#define NUM_CORTEX1             (32+1)
#define NUM_CORTEX2             (32+2)
#define NUM_CORTEX3             (32+3)
#define NUM_CORTEX4             (32+4)
#define NUM_IEM_APC             (32+5)
#define NUM_IEM_IEC             (32+6)
#define NUM_Reserved39          (32+7)
#define NUM_NFC                 (32+8)
#define NUM_CFC                 (32+9)
#define NUM_UART0               (32+10)
#define NUM_UART1               (32+11)
#define NUM_UART2               (32+12)
#define NUM_UART3               (32+13)
#define NUM_I2C                 (32+14)
#define NUM_SPI0                (32+15)
#define NUM_SPI1                (32+16)
#define NUM_SPI2                (32+17)
#define NUM_AUDIO               (32+18)
#define NUM_I2C_PMIC            (32+19)
#define NUM_I2C_HDMI            (32+20)
#define NUM_HSIRX               (32+21)
#define NUM_HSITX               (32+22)
#define NUM_UHOST               (32+23)
#define NUM_OTG                 (32+24)
#define NUM_MSM                 (32+25)
#define NUM_HSMMC0              (32+26)
#define NUM_HSMMC1              (32+27)
#define NUM_HSMMC2              (32+28)
#define NUM_MIPI_CSI            (32+29)
#define NUM_MIPI_DSI            (32+30)
#define NUM_ONENAND_AUDI        (32+31)

//INT NUM - VIC2
#define NUM_LCD0                (64+0)
#define NUM_LCD1                (64+1)
#define NUM_LCD2                (64+2)
#define NUM_LCD3                (64+3)
#define NUM_ROTATOR             (64+4)
#define NUM_FIMC_A              (64+5)
#define NUM_FIMC_B              (64+6)
#define NUM_FIMC_C              (64+7)
#define NUM_JPEG                (64+8)
#define NUM_2D                  (64+9)
#define NUM_3D                  (64+10)
#define NUM_MIXER               (64+11)
#define NUM_HDMI                (64+12)
#define NUM_HDMI_I2C            (64+13)
#define NUM_MFC                 (64+14)
#define NUM_TVENC               (64+15)
#define NUM_I2S0                (64+16)
#define NUM_I2S1                (64+17)
#define NUM_I2S2                (64+18)
#define NUM_AC97                (64+19)
#define NUM_PCM0                (64+20)
#define NUM_PCM1                (64+21)
#define NUM_SPDIF               (64+22)
#define NUM_ADC                 (64+23)
#define NUM_PENDN               (64+24)
#define NUM_KEYPAD              (64+25)
#define NUM_Reserved90          (64+26) 
#define NUM_HASH                (64+27) 
#define NUM_FEEDCTRL            (64+28) 
#define NUM_PCM2                (64+29)
#define NUM_SDM_IRQ             (64+30)
#define NUM_SMD_FIQ             (64+31)

//INT NUM - VIC3
#define NUM_IPC                 (96+0)
#define NUM_HOSTIF              (96+1)
#define NUM_HSMMC3              (96+2)
#define NUM_CEC                 (96+3)
#define NUM_TSI                 (96+4)
#define NUM_MDNIE0              (96+5)
#define NUM_MDNIE1              (96+6)
#define NUM_MDNIE2              (96+7)
#define NUM_MDNIE3              (96+8)
#define NUM_ADC1                (96+9)
#define NUM_PENDN1              (96+10)
#define NUM_ALL                 (200)
中断控制器初始化

初始化中断控制器,由从汇编跳转过来的C函数中添加:

// 初始化异常向量表
void ex_vector_tab_init() {
    // 函数名其实就是该函数的首地址,就是该函数的指针
    rEX_VECT_RESET_ADDR = handle_reset_exception;
    rEX_VECT_UNDEFINST_ADDR = handle_undefinst_exception;
    rEX_VECT_SOFTINT_ADDR = handle_softint_exception;
    rEX_VECT_PREFAB_ADDR = handle_prefab_exception;
    rEX_VECT_DATAAB_ADDR = handle_dataab_exception;
    // 调用汇编进行现场处理
    rEX_VECT_IRQ_ADDR = IRQ_handle;
    rEX_VECT_FIQ_ADDR = IRQ_handle;

    // 初始化中断控制器基本寄存器
    intc_init();
}

intc_init中做的工作有:

  • 禁止中断控制器,防止外部发生中断之后来寻找我们根本就没设定好的isr,从而导致无法处理中断而跑飞,所以我们先关掉所有中断,只开启自己感兴趣的中断并为它设置isr
  • 选择中断控制器,选择要处理的中断源,我们在这里全部设置为IRQ
  • 清除VICnADDR寄存器,防止其打乱返回的isr
// 初始化中断控制器
void intc_init(void)
{
    // 禁止所有中断
    VIC0INTENCLEAR = 0xffffffff;
    VIC1INTENCLEAR = 0xffffffff;
    VIC2INTENCLEAR = 0xffffffff;
    VIC3INTENCLEAR = 0xffffffff;

    // 选择中断类型为IRQ
    VIC0INTSELECT = 0x0;
    VIC1INTSELECT = 0x0;
    VIC2INTSELECT = 0x0;
    VIC3INTSELECT = 0x0;

    // 清VICxADDR
    intc_clearvectaddr();
}

// 清除需要处理的中断的中断处理函数的地址
void intc_clearvectaddr(void)
{
    // VICxADDR:当前正在处理的中断的中断处理函数的地址
    VIC0ADDR = 0;
    VIC1ADDR = 0;
    VIC2ADDR = 0;
    VIC3ADDR = 0;
}
中断的使能和禁止
// 使能中断
// 通过传参的intnum来使能某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_enable(unsigned long intnum)
{
    unsigned long temp;
    // 确定intnum在哪个寄存器的哪一位
    // <32就是0~31,必然在VIC0
    if(intnum<32)
    {
        temp = VIC0INTENABLE;
        temp |= (1<<intnum);        // 如果是第一种设计则必须位操作,第二种设计可以
                                    // 直接写。
        VIC0INTENABLE = temp;
    }
    else if(intnum<64)
    {
        temp = VIC1INTENABLE;
        temp |= (1<<(intnum-32));
        VIC1INTENABLE = temp;
    }
    else if(intnum<96)
    {
        temp = VIC2INTENABLE;
        temp |= (1<<(intnum-64));
        VIC2INTENABLE = temp;
    }
    else if(intnum<NUM_ALL)
    {
        temp = VIC3INTENABLE;
        temp |= (1<<(intnum-96));
        VIC3INTENABLE = temp;
    }
    // NUM_ALL : enable all interrupt
    else
    {
        VIC0INTENABLE = 0xFFFFFFFF;
        VIC1INTENABLE = 0xFFFFFFFF;
        VIC2INTENABLE = 0xFFFFFFFF;
        VIC3INTENABLE = 0xFFFFFFFF;
    }

}

// 禁止中断
// 通过传参的intnum来禁止某个具体的中断源,中断号在int.h中定义,是物理中断号
void intc_disable(unsigned long intnum)
{
    unsigned long temp;

    if(intnum<32)
    {
        temp = VIC0INTENCLEAR;
        temp |= (1<<intnum);
        VIC0INTENCLEAR = temp;
    }
    else if(intnum<64)
    {
        temp = VIC1INTENCLEAR;
        temp |= (1<<(intnum-32));
        VIC1INTENCLEAR = temp;
    }
    else if(intnum<96)
    {
        temp = VIC2INTENCLEAR;
        temp |= (1<<(intnum-64));
        VIC2INTENCLEAR = temp;
    }
    else if(intnum<NUM_ALL)
    {
        temp = VIC3INTENCLEAR;
        temp |= (1<<(intnum-96));
        VIC3INTENCLEAR = temp;
    }
    // NUM_ALL : disable all interrupt
    else
    {
        VIC0INTENCLEAR = 0xFFFFFFFF;
        VIC1INTENCLEAR = 0xFFFFFFFF;
        VIC2INTENCLEAR = 0xFFFFFFFF;
        VIC3INTENCLEAR = 0xFFFFFFFF;
    }

    return;
}
绑定自己实现的ISR到VICnVECTADDR

VICnVECTADDR寄存器一共有4*32个,每个中断源都有一个VICnVECTADDR寄存器,我们将自己写的isr的地址放入到对应的中断源的VICnVECTADDR寄存器中即可

// 等发生相应中断的时候,我们直接到相应的VICnADDR中去取isr地址即可。
// 参数:intnum是int.h定义的物理中断号,handler是函数指针,就是我们写的isr

// VIC0VECTADDR定义为VIC0VECTADDR0寄存器的地址,就相当于是VIC0VECTADDR0~31这个
// 数组(这个数组就是一个函数指针数组)的首地址,然后具体计算每一个中断的时候
// 只需要首地址+偏移量即可。
void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{
    //VIC0
    if(intnum<32)
    {
        *( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;
    }
    //VIC1
    else if(intnum<64)
    {
        *( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
    }
    //VIC2
    else if(intnum<96)
    {
        *( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;
    }
    //VIC3
    else
    {
        *( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;
    }
    return;
}
中断发生时的ISR获取

当中断发生时,硬件会把相应的中断源的ISR地址放置到VICnADDR寄存器中,我们需要获取该ISR并调用

// 通过读取VICnIRQSTATUS寄存器,判断其中哪个有一位为1,来得知哪个VIC发生中断了
unsigned long intc_getvicirqstatus(unsigned long ucontroller)
{
    if(ucontroller == 0)
        return  VIC0IRQSTATUS;
    else if(ucontroller == 1)
        return  VIC1IRQSTATUS;
    else if(ucontroller == 2)
        return  VIC2IRQSTATUS;
    else if(ucontroller == 3)
        return  VIC3IRQSTATUS;
    else
    {}
    return 0;
}

// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场
void handle_irq_exception(void)
{
    printf("irq exception occor.\r\n");
    // SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断
    // 这么多中断irq在第一个阶段走的是一条路,都会进入到handle_irq_exception来
    // 我们在handle_irq_exception中要去区分究竟是哪个中断发生了,然后再去调用该中断
    // 对应的isr。


    // 虽然硬件已经自动帮我们把isr放入了VICnADDR中,但是因为有4个,所以我们必须
    // 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICADDR寄存器中
    unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
    int i=0;
    void (*isr)(void) = NULL;

    for(i=0; i<4; i++)
    {
        // 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0
        if(intc_getvicirqstatus(i) != 0)
        {
            isr = (void (*)(void)) vicaddr[i];
            break;
        }
    }
    (*isr)();       // 通过函数指针来调用函数
}

外部中断

内部中断指的是中断来自于Soc内部,例如串口,LCD,I2C,Timer等内部外设,Soc外部设备产生通过外部中断GPIO引脚产生的中断叫做外部中断,按键中断就是一个外部中断,外部中断在S5PV210中以EINT开头,在数据手册中的2.2.60.中有外部中断的配置详情。

外部中断主要寄存器
  • EXT_CON:配置外部中断的触发方式,描述外部电平如何变化才能触发该中断
  • EXT_PEND:中断挂起寄存器,当中断发生后,该寄存器中的对应中断源的位置1,表示该对应的中断源发生了中断,切记中断处理完毕需要将该位置0
  • EXT_MASK:中断开关,控制外部中断的使能和禁止。

    外部中断触发模式
  • 电平触发:只要GPIO电平满足条件就会不停触发中断,分为高电平触发和低电平触发

  • 边沿触发,在电平变化瞬间方向触发中断,分为上升沿触发,下降沿触发和双边沿触发,
210开发板按键中断分析

按键对应的中断源编号为:

  • EINT2:NUM_EINT2 LEFT按钮
  • EINT3:NUM_EINT3 DOWN按钮
  • EINT16-19:NUM_EINT16_31 其余4个按钮

中断方式处理按键

外部中断初始化
// 以中断方式初始化按键
void key_init_interrupt(void) {
    // 设置GPIO的外部中断模式,我们要设置为1111EXT_INT外部中断模式
    rGPH0CON |= 0xFF<<8;
    rGPH2CON |= 0xFFFF<<8;
    //设置中断触发模式,我们设置为010为下降沿为触发
    rEXT_INT_0_CON &= ~(0xFF<<8); //清0 8-15位
    rEXT_INT_0_CON |= ((2<<8)|(2<<12)); // 设置为下降沿触发
    rEXT_INT_2_CON &= ~(0xFFFF<<0); 
    rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12));

    //设置中断允许
    rEXT_INT_0_MASK &= ~(3<<2);
    rEXT_INT_2_MASK &= ~(0x0f<<0);
    //清理挂起,写1可以清0
    rEXT_INT_0_PEND |= (3<<2);
    rEXT_INT_2_PEND |= (0x0f<<0);
}
中断ISR的编写
// EINT2中断处理,LEFT按键,
void isr_eint2(void) {
    // 中断处理代码
    printf("Key SW5 is pressed,It's name is LEFT\r\n");
    // 清除中断挂起
    rEXT_INT_0_PEND |= (1<<2);
    intc_clearvectaddr();
}

// EINT3中断处理,LEFT按键,
void isr_eint3(void) {
    // 中断处理代码
    printf("Key SW6 is pressed,It's name is DOWN\r\n");
    // 清除中断挂起
    rEXT_INT_0_PEND |= (1<<3);
    intc_clearvectaddr();
}

// EINT16-19中断处理,UP,RIGHT,BACK,MENU按键
void isr_eint16t19(void) {
    if(rEXT_INT_2_PEND & (1<<0)) {
        // EINT16中断处理,UP按键,
        printf("Key SW7 is pressed,It's name is UP\r\n");
    }

    if(rEXT_INT_2_PEND & (1<<1)) {
        // EINT17中断处理,RIGHT按键,
        printf("Key SW8 is pressed,It's name is RIGHT\r\n");
    }

    if(rEXT_INT_2_PEND & (1<<2)) {
        // EINT18中断处理,BACK按键,
        printf("Key SW9 is pressed,It's name is BACK\r\n");
    }

    if(rEXT_INT_2_PEND & (1<<3)) {
        // EINT19中断处理,MENU按键,
        printf("Key SW10 is pressed,It's name is MENU\r\n");
    }
    //清除中断挂起
    rEXT_INT_2_PEND |= (0x0f<<0);
    intc_clearvectaddr();
}
代码调用
#define KEY_EINT2 NUM_EINT2
#define KEY_EINT3 NUM_EINT3
#define KEY_EINT16T19 NUM_EINT16_31

// 初始化按键
key_init_interrupt();
// 初步初始化中断控制器
ex_vector_tab_init();
// 绑定ISR到中断控制器
intc_setvectaddr(KEY_EINT2,isr_eint2);
intc_setvectaddr(KEY_EINT3,isr_eint3);
intc_setvectaddr(KEY_EINT16T19,isr_eint16t19);

//使能中断
intc_enable(KEY_EINT2);
intc_enable(KEY_EINT3);
intc_enable(KEY_EINT16T19);

这样的话就可以使用中断方式处理按键的点击事件了。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值