欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Part14: 按键中断与定时器

程序员文章站 2022-06-09 09:02:47
...

0 明确硬件中断处理

对于s3c2440开发版,查手册可获得如下图
Part14: 按键中断与定时器
上图不方便注释寄存器的用途,在此从中断源–>中断寄存器–>CPU详细说明
1)对于中断源:s3c2440有60种中断源。中断源分为两类:带sub-register和不带sub-register.
带sub-register是指某一个中断源有多种中断,需借助sub-register寄存器(即SUBSRCPND和SUBMASK共同作用)辨别。
如对于UART0串口发生中断时,在SRCPND对应位置1,但无法区分是接收/发送数据或发送/接收数据异常引起的中断,
因此可设置SUBSRCPND明确UART0的具体中断。
反之,不带sub-register的中断源,可直接到达SRCPND寄存器,即不需借助其它寄存器加以区分

2)可同时发生多个中断,此时,SRCPND寄存器对应位自动置位,表示有中断发生
3)此外,还可设置MASK屏蔽寄存器(INTMSK和INTSUBMSK)屏蔽中断
注意:发生中断时,SRCPND对应位肯定会置1,并不会受屏蔽寄存器的影响。
即中断源能否发生 与 是否屏蔽 没关系,换句话说,任你中断发生,但半路我可以选择屏蔽/封杀
4)接着会发现有两种中断可选择,一个FIQ(快中断),一个IRQ(正常中断)(这两者区别我前面博客有讲)
可通过INTMOD寄存器(32bit,每位对应一个/一组中断源)设置。
注意:一旦置位INTMOD某位,即设置某个中断源为快中断, INTOFFSET 和INTPND不受影响
5)对于中断优先级,可通过仲裁寄存器(即PRIORITY)设置中断源的优先级
6)对于IRQ中断,INTPND寄存器(32bit),某位置1表示对应的中断将会被送往CPU,即响应中断
需注意的是:INTPND某一时刻只能某位置1

7)最容易忘的一步,清中断,查看手册会发现,有三个寄存器需要在ISR(中断服务例程)中手动清零
记住既可:从源头开始清分别是:
EINTPEND——清除发生的外部中断对应位,避免多次响应
SRCPND——中断的源头,以让同一中断源可再次发出中断
INTPND——服务完了,记得清除

OK,理清硬件的中断处理后,在编程前需明确套路
1.开总中断,即cpsr的I位(bit 7)清零
2.中断源、中断控制器的初始化
3.编写中断处理函数

下面的代码根据这三步编写…

1 按键中断处理程序

1.0 设置中断入口
_start:
b reset
	ldr pc, halt
	ldr pc, halt
	ldr pc, halt
	ldr pc, halt
	ldr pc, halt
	ldr pc, addr_itr  // 中断入口
	ldr pc, halt
addr_itr:
	.word do_itr
.align 4
do_itr:
	ldr sp, =0x34000000
	/*保护现场*/
	sub lr, lr, #4
	stmdb sp!, {r0-r12, lr}
	/*调用中断处理函数*/
	 bl handle_irq
	/*恢复现场*/
	ldmia sp!, {r0-r12, pc}^
.align 4

reset:
	/*......*/
1.1 CPU使能总中断
mrs r0, cpsr          // r0 = cpsr
bic r0, r0, #0xf      // 设置为usr模式,cpsr低5bit:0b10000
bic r0, r0, #(1<<7)   // 清零cpsr的I位(bit 7), 使能中断
msr cpsr, r0         // cpsr = r0
1.2 中断源初始化

interrupt.c

void key_eint_init(void)
{
// s2--eint0  s3--eint2  s4--eint11 s5--eint19
// 配置四个按键为中断引脚
 GPFCON &= ~((3<<0) | (3<<4)); //s2
 GPFCON |= ((2<<0)|(2<<4));    //s3
 
 GPGCON &= ~((3<<6)|(3<<22));  //s4
 GPGCON |= ((2<<6) |(2<<22));  //s5
 // 设置中断触发方式:双边沿触发
 EXTINT0 |= (7<<0) | (7<<8);
 EXTINT1 |= (7<<12);
 EXTINT2 |= (7<<12);
 // 使能eint11, eint19
 EINTMASK &= ~((1<<11) | (1<<19));
}
1.3 中断控制器初始化

interrupt.c

void interrupt_init(void)
{
 INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
}
1.4 中断处理函数
void handle_irq_c(void)
{
	/* 分辨中断源 */
	int bit = INTOFFSET;
	/* 调用对应的处理函数 */
	if (bit == 0 || bit == 2 || bit == 5)  /* eint0,2,eint8_23 */
	{
		key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
	}
	/* 清中断 : 从源头开始清 */
	SRCPND = (1<<bit);
	INTPND = (1<<bit); 
}
void key_eint_irq(int irq)
{
	unsigned int val = EINTPEND;
	unsigned int val1 = GPFDAT;
	unsigned int val2 = GPGDAT;
	
	if (irq == 0) /* eint0 : s2 控制 D12 */
	{
  		if (val1 & (1<<0)) /* s2 --> gpf6 */
  		{
   			/* 松开 */
   			GPFDAT |= (1<<6);
 		}
  		else
  		{
   			/* 按下 */
   			GPFDAT &= ~(1<<6);
  		}
 	}
 	else if (irq == 2) /* eint2 : s3 控制 D11 */
 	{
  		if (val1 & (1<<2)) /* s3 --> gpf5 */
  		{
   			/* 松开 */
   			GPFDAT |= (1<<5);
  		}
  		else
  		{
   			/* 按下 */
   			GPFDAT &= ~(1<<5);
  		} 
 	}
 	else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
 	{
  		if (val & (1<<11)) /* eint11 */
  		{
   			if (val2 & (1<<3)) /* s4 --> gpf4 */
   			{
    				/* 松开 */
    				GPFDAT |= (1<<4);
   			}
   			else
   			{
    				/* 按下 */
    				GPFDAT &= ~(1<<4);
   			}
  		}
 		else if (val & (1<<19)) /* eint19 */
  		{
   			if (val2 & (1<<11))
   			{
    				/* 松开 */
    				/* 熄灭所有LED */
    				GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
   			}
   			else
   			{
    				/* 按下: 点亮所有LED */
    				GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
   			}
  		}
 	}
 	EINTPEND = val;
}

备注以下:贴出全部代码太占篇幅,完整代码可在我的一篇博文:“代码集合”获取
总结:
硬件中断的编写套路非常明朗,重点理解硬件的中断处理。看似要
操作很多寄存器,但是心里明确套路后非常简单,即既然要把中断发给CPU,那就沿着从中断源->
中断控制器–>CPU 一路设置下去,把这条路打通了,就可以响应中断了。

2 定时器实现LED计数

代码如下

2.1 时钟初始化
// 在 timer.c 文件中
void timer_init(void)
{
 /* 设置TIMER0的时钟 */
 /* Timer clk = PCLK / {prescaler value+1} / {divider value} 
              = 50000000/(99+1)/16
              = 31250
  */
 TCFG0 = 99;  /* Prescaler 0 = 99, 用于timer0,1 */
 TCFG1 &= ~0xf;
 TCFG1 |= 3;  /* MUX0 : 1/16 */
 /* 设置TIMER0的初值 */
 TCNTB0 = 15625;  /* 0.5s中断一次 */
 /* 加载初值, 启动timer0 */
 TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */
 /* 设置为自动加载并启动 */
 TCON &= ~(1<<1);
 TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */
 /* 设置中断 */
}
// 在interrupt.c文件中
/* 初始化中断控制器 */
void interrupt_init(void)
{
 INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
 INTMSK &= ~(1<<10);  /* enable timer0 int */
}
void handle_irq_c(void)
{
 /* 分辨中断源 */
 int bit = INTOFFSET;
 /* 调用对应的处理函数 */
 if (bit == 0 || bit == 2 || bit == 5)  /* eint0,2,eint8_23 */
 {
  key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
 }
 else if (bit == 10)  /* 记得,这里要修改 */
 {
  timer_irq();
 }
 /* 清中断 : 从源头开始清 */
 SRCPND = (1<<bit);
 INTPND = (1<<bit); 
}
// 在main.c文件中,添加下面这句
 timer_init();
2.2 时钟中断处理函数
void timer_irq(void)
{
 /* 点灯计数 */
 static int cnt = 0;
 int tmp;
 cnt++;
 tmp = ~cnt;
 tmp &= 7;
 GPFDAT &= ~(7<<4);
 GPFDAT |= (tmp<<4);
}

ok,以上代码经过测试,运行没问题

相关标签: ARM裸机基础编程