按键
按键物理概念
按键是一个正常情况下断开,只有在按下才必合的物理器件,按键有一对常开触点和常闭触点.
按键电学特性
按键用于做输入设备,由人来操作向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);
这样的话就可以使用中断方式处理按键的点击事件了。