十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)

时间:2021-05-08 01:03:51
上接: http://blog.csdn.net/wangweijundeqq/article/details/78634146
ARM裸机学习之中断系统1(S5PV210的中断系统详解)
S5PV210中断处理
1.S5PV210的外部中断通过外部中断对应的GPIO产生
外部中断寄存器位于文档中GPIO部分。
外部中断的主要寄存器:
EXT_INT_N_CON:设置外部中断的触发方式
EXT_INT_N_PEND:中断挂起寄存器,32位,每位对应一个外部中断源,
EXT_INT_N_MASK:中断掩码控制寄存器,各个外部中断的使能/禁止开关。
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
分析X210开发板的按键对应的EINT编号:
GPIO :SW5:GPH0_2 SW6:GPH0_3 SW78910:GPH2_0123
EINT :EINT2、EINT3、EINT16、EINT17、EINT18、EINT19

2.中断方式处理按键编程
  • 按键的初始化(包括外部中断的GPIO,触发模式等设置,分开说明)
2.1. 外部中断对应的GPIO模式设置
rGPH0CON |= 0xFF<<8;		// GPH0_2 GPH0_3设置为外部中断模式
rGPH2CON |= 0xFFFF<<0;// GPH2_0123共4个引脚设置为外部中断模式
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)

. 十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
2.2. 中断触发模式设置
	rEXT_INT_0_CON &= ~(0xFF<<8);	// bit8~bit15全部清零
	rEXT_INT_0_CON |= ((2<<8)|(2<<12));		// EXT_INT2和EXT_INT3设置为下降沿触发
	rEXT_INT_2_CON &= ~(0xFFFF<<0);       //EINT16、17、18、19,设置为下降沿触发
        rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12));	
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
按键的初始化(包括外部中断的GPIO,触发模式等设置)的整合如下:
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
2.3. 中断允许
	rEXT_INT_0_MASK &= ~(3<<2);			// 外部中断允许
	rEXT_INT_2_MASK &= ~(0x0f<<0);
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
2.4. 清挂起,清除是写1,不是写0
	rEXT_INT_0_PEND |= (3<<2);
	rEXT_INT_2_PEND |= (0x0F<<0);
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
按键的初始化(包括外部中断的GPIO,触发模式等设置)的整合如下:
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
3.中断处理程序isr编写及其整体调用
3.1.中断ISR处理函数的编写,即(类似于STM32的中断服务函数)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
3.2.绑定isr到中断控制器硬件
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
3.3.使能中断(不是使能外部中断(因前面的中断初始化已经使能),是使能中断控制器)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
3.4.整体调用如下:
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
3.5 最后,上面的理解在结合下图 一起理解,就显得很简单了。
中断处理的编程模型:
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)
十.ARM裸机学习之中断系统2(S5PV210按键外部中断及中断处理)



int.c:
#include "int.h"
#include "stdio.h"


void reset_exception(void)
{
	printf("reset_exception.\n");
}

void undef_exception(void)
{
	printf("undef_exception.\n");
}

void sotf_int_exception(void)
{
	printf("sotf_int_exception.\n");
}

void prefetch_exception(void)
{
	printf("prefetch_exception.\n");
}

void data_exception(void)
{
	printf("data_exception.\n");
}

// 主要功能:绑定第一阶段异常向量表;禁止所有中断;选择所有中断类型为IRQ;
// 清除VICnADDR为0
void system_init_exception(void)
{
	// 第一阶段处理,绑定异常向量表
	r_exception_reset = (unsigned int)reset_exception;
	r_exception_undef = (unsigned int)undef_exception;
	r_exception_sotf_int = (unsigned int)sotf_int_exception;
	r_exception_prefetch = (unsigned int)prefetch_exception;
	r_exception_data = (unsigned int)data_exception;
	r_exception_irq = (unsigned int)IRQ_handle;
	r_exception_fiq = (unsigned int)IRQ_handle;
	
	// 初始化中断控制器的基本寄存器
	intc_init();
}

// 清除需要处理的中断的中断处理函数的地址
void intc_clearvectaddr(void)
{
    // VICxADDR:当前正在处理的中断的中断处理函数的地址
    VIC0ADDR = 0;
    VIC1ADDR = 0;
    VIC2ADDR = 0;
    VIC3ADDR = 0;
}

// 初始化中断控制器
void intc_init(void)
{
    // 禁止所有中断
	// 为什么在中断初始化之初要禁止所有中断?
	// 因为中断一旦打开,因为外部或者硬件自己的原因产生中断后一定就会寻找isr
	// 而我们可能认为自己用不到这个中断就没有提供isr,这时它自动拿到的就是乱码
	// 则程序很可能跑飞,所以不用的中断一定要关掉。
	// 一般的做法是先全部关掉,然后再逐一打开自己感兴趣的中断。一旦打开就必须
	// 给这个中断提供相应的isr并绑定好。
    VIC0INTENCLEAR = 0xffffffff;
    VIC1INTENCLEAR = 0xffffffff;
    VIC2INTENCLEAR = 0xffffffff;
    VIC3INTENCLEAR = 0xffffffff;

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

    // 清VICxADDR
    intc_clearvectaddr();
}


// 绑定我们写的isr到VICnVECTADDR寄存器
// 绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理
// 等发生相应中断的时候,我们直接到相应的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;
}


// 使能中断
// 通过传参的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;
}


// 通过读取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 irq_handler(void)
{
	//printf("irq_handler.\n");
	// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断
	// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来
	// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断
	// 对应的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)();		// 通过函数指针来调用函数
}







key.c:
#include "stdio.h"
#include "main.h"

// 定义操作寄存器的宏
#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)

#define EXT_INT_0_CON	0xE0200E00
#define EXT_INT_2_CON	0xE0200E08
#define EXT_INT_0_PEND	0xE0200F40
#define EXT_INT_2_PEND	0xE0200F48
#define EXT_INT_0_MASK	0xE0200F00
#define EXT_INT_2_MASK	0xE0200F08

#define rEXT_INT_0_CON	(*(volatile unsigned int *)EXT_INT_0_CON)
#define rEXT_INT_2_CON	(*(volatile unsigned int *)EXT_INT_2_CON)
#define rEXT_INT_0_PEND	(*(volatile unsigned int *)EXT_INT_0_PEND)
#define rEXT_INT_2_PEND	(*(volatile unsigned int *)EXT_INT_2_PEND)
#define rEXT_INT_0_MASK	(*(volatile unsigned int *)EXT_INT_0_MASK)
#define rEXT_INT_2_MASK	(*(volatile unsigned int *)EXT_INT_2_MASK)



//------------------------轮询方式处理按键---------------------------
// 初始化按键
void key_init(void)
{
	// 设置GPHxCON寄存器,设置为输入模式
	// GPH0CON的bit8~15全部设置为0,即可
	rGPH0CON &= ~(0xFF<<8);
	// GPH2CON的bit0~15全部设置为0,即可
	rGPH2CON &= ~(0xFFFF<<0);
}

static void delay20ms(void)
{
	// 这个函数作用是延时20ms
	// 因为我们这里是裸机程序,且重点不是真的要消抖,而是教学
	// 所以我这里这个程序只是象征性的,并没有实体
	// 如果是研发,那就要花时间真的调试出延时20ms的程序
	int i, j;
	
	for (i=0; i<100; i++)
	{
		for (j=0; j<1000; j++)
		{
			i * j;
		}
	}
}

void key_polling(void)
{
	// 依次,挨个去读出每个GPIO的值,判断其值为1还是0.如果为1则按键按下,为0则弹起
	
	// 轮询的意思就是反复循环判断有无按键,隔很短时间
	while (1)
	{
		// 对应开发板上标着LEFT的那个按键
		if (rGPH0DAT & (1<<2))
		{
			// 为1,说明没有按键
			led_off();
		}
		else
		{
			// 添加消抖
			// 第一步,延时
			delay20ms();
			// 第二步,再次检验按键状态
			if (!(rGPH0DAT & (1<<2)))
			{
				// 为0,说明有按键
				led1();
				printf("key left.\n");
			}
		}
		
		// 对应开发板上标着DOWN的那个按键
		if (rGPH0DAT & (1<<3))
		{
			// 为1,说明没有按键
			led_off();
		}
		else
		{
			// 为0,说明有按键
			led2();
			printf("key down.\n");
		}
		
		// 对应开发板上标着UP的那个按键
		if (rGPH2DAT & (1<<0))
		{
			// 为1,说明没有按键
			led_off();
		}
		else
		{
			// 为0,说明有按键
			led3();
		}
	}
}


//-----------------------中断方式处理按键-----------------------------------
// 以中断方式来处理按键的初始化 
void key_init_interrupt(void)
{
	// 1. 外部中断对应的GPIO模式设置
	rGPH0CON |= 0xFF<<8;		// GPH0_2 GPH0_3设置为外部中断模式
	rGPH2CON |= 0xFFFF<<0;		// GPH2_0123共4个引脚设置为外部中断模式
	
	// 2. 中断触发模式设置
	rEXT_INT_0_CON &= ~(0xFF<<8);	// bit8~bit15全部清零
	rEXT_INT_0_CON |= ((2<<8)|(2<<12));		// EXT_INT2和EXT_INT3设置为下降沿触发
	rEXT_INT_2_CON &= ~(0xFFFF<<0);
	rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12));	
	
	// 3. 中断允许
	rEXT_INT_0_MASK &= ~(3<<2);			// 外部中断允许
	rEXT_INT_2_MASK &= ~(0x0f<<0);
	
	// 4. 清挂起,清除是写1,不是写0
	rEXT_INT_0_PEND |= (3<<2);
	rEXT_INT_2_PEND |= (0x0F<<0);
}

// EINT2通道对应的按键,就是GPH0_2引脚对应的按键,就是开发板上标了LEFT的那个按键
void isr_eint2(void)
{
	// 真正的isr应该做2件事情。
	// 第一,中断处理代码,就是真正干活的代码
	printf("isr_eint2_LEFT.\n");
	// 第二,清除中断挂起(中断处理后,必须清除中断挂起)
	rEXT_INT_0_PEND |= (1<<2);
	intc_clearvectaddr();//清除需要处理的中断的中断处理函数的地址
}

void isr_eint3(void)
{
	// 真正的isr应该做2件事情。
	// 第一,中断处理代码,就是真正干活的代码
	printf("isr_eint3_DOWN.\n");
	// 第二,清除中断挂起
	rEXT_INT_0_PEND |= (1<<3);
	intc_clearvectaddr();// 清除需要处理的中断的中断处理函数的地址
}

void isr_eint16171819(void)
{
	// 真正的isr应该做2件事情。
	// 第一,中断处理代码,就是真正干活的代码
	// 因为EINT16~31是共享中断,所以要在这里再次去区分具体是哪个子中断
	if (rEXT_INT_2_PEND & (1<<0))
	{
		printf("eint16\n");
	}
	if (rEXT_INT_2_PEND & (1<<1))
	{
		printf("eint17\n");
	}
	if (rEXT_INT_2_PEND & (1<<2))
	{
		printf("eint18\n");
	}
	if (rEXT_INT_2_PEND & (1<<3))
	{
		printf("eint19\n");
	}
  // 第二,清除中断挂起
	rEXT_INT_2_PEND |= (0x0f<<0);
	intc_clearvectaddr();
}















start.S:
#define WTCON		0xE2700000

#define SVC_STACK	0xd0037d80
#define IRQ_STACK	0xd0037f80

.global _start	
.global IRQ_handle	

// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]
	
	// 第2步:初始化时钟
	bl clock_init
	
	// 第3步:设置SVC栈
	ldr sp, =SVC_STACK
	
	// 第4步:开/关icache
	mrc p15,0,r0,c1,c0,0;			// 读出cp15的c1到r0中
	//bic r0, r0, #(1<<12)			// bit12 置0  关icache
	orr r0, r0, #(1<<12)			// bit12 置1  开icache
	mcr p15,0,r0,c1,c0,0;

	bl main
	
	// 从这里之后就可以开始调用C程序了
	//bl led_blink					// led_blink是C语言实现的一个函数
	
// 汇编最后的这个死循环不能丢
	b .
	
// 在这个汇编函数中,用来做中断模式下的现场保护和恢复,并且调用真正的中断处理程序

IRQ_handle:
	// 设置IRQ模式下的栈
	ldr sp, =IRQ_STACK
	// 保存LR
	// 因为ARM有流水线,所以PC的值会比真正执行的代码+8,
	sub lr, lr, #4
	// 保存r0-r12和lr到irq模式下的栈上面
	stmfd sp!, {r0-r12, lr}
	// 在此调用真正的isr来处理中断
	bl irq_handler
	// 处理完成开始恢复现场,其实就是做中断返回,关键是将r0-r12,pc,cpsr一起回复
	ldmfd sp!, {r0-r12, pc}^
	

main.c:
#include "stdio.h"
#include "int.h"
#include "main.h"

void uart_init(void);

#define KEY_EINT2		NUM_EINT2		// left NUM_EINT2为按键对应中断源编号
#define KEY_EINT3		NUM_EINT3		// down
#define KEY_EINT16_19	NUM_EINT16_31	// 其余4个共用的

void delay(int i)
{
	volatile int j = 10000;
	while (i--)
		while(j--);
}


int main(void)
{
	uart_init();
	
	key_init_interrupt();//中断方式处理按键的初始化
	
	// 如果程序中要使用中断,就要调用中断初始化来初步初始化中断控制器
	system_init_exception();
	
	printf("-------------key interrypt test--------------");
	
	// 绑定isr到中断控制器硬件
	intc_setvectaddr(KEY_EINT2, isr_eint2);
	intc_setvectaddr(KEY_EINT3, isr_eint3);
	intc_setvectaddr(KEY_EINT16_19, isr_eint16171819);
	
	// 使能中断
	intc_enable(KEY_EINT2);
	intc_enable(KEY_EINT3);
	intc_enable(KEY_EINT16_19);
	
	// 在这里加个心跳
	while (1)
	{
		printf("A ");
		delay(10000);
	}

	return 0;
}