13.ARM定时器中断实例

目录

1.原理框图

2.如何使用定时器

3.思路

4.例程

5.定时器的改进

源代码:


此例程接上一篇博客:点我查看


1.原理框图

其中核心部分如下图所示:

解析:

1).在外部有一个时钟源,CLK

2).每次时钟上,TCNTn减去1(n代表的是哪个计数器),当TCNTn的数值减小到和TCMPn相等时有个标志位进行提示(用于产生PWM信号),当数值减小到0时,可以产生一个定时器中断。

3)TCMPn和TCNTn的数值来自寄存器,TCMPBn和TCNTBn寄存器,当TCNTn的数值减到0之后可以设置自动重装载TCNTBn寄存器的值。


2.如何使用定时器

①设置时钟源

②设置自动重装载值(TCNTBn寄存器)。

③自动加载初值,使能启动定时器

④写中断服务函数。

芯片手册初始化流程:

步骤:

1).写初始值到TCNTBn和TCMPn寄存器

2).设置对应定时器的手动更新位。

3).设置启动位,启动定时器


3.思路

1).设置定时器时钟来源:

查看数据手册,定时器时钟来源给出了一个计算公式:

公式如下:

Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}

在这里设置PCLK为50MHz,prescaler value为99,divider value设置 16这样子得到的Timer input clock Frequency为:

CLK=50000000/(99+1)/16 = 31250 Hz

即:每经过  1/31250s的时间,TCNTn的数值就减去1,如果TCNTn的数值从31250减到0的时间就是1s钟。

注意:Prescaler 0 用于定时器0和定时器1的分频

设置divider value 的数值,设置TCFG1寄存器,设置5选1多路选择器

2).写初始值到TCNTBn和TCMPn寄存器

此处未使用到PWM,所以不需要设置TCMPB0

3).设置对应定时器的手动更新位和启动定时器

  • 手动更新:

  • 自动装载,启动定时器: 

注意:这这里要先清除手动更新位: 

4).设置中断控制器,使能INT_TIMER0

代码如下:

 5)编写中断服务函数


4.例程

1).编写定时器初始化函数

void timer_init(void)
{
	/*设置Timter0的时钟*/
	/*Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
									50000000/100/16 = 31250 Hz =  */
	TCFG0 = 99;
	    /*设置5选1多路选择器*/
	TCFG1 &= ~0xf;
		/*16分频*/
	TCFG1 |= 3<<0;
	
	/*设置timer0的初值*/
	TCNTB0 = 31250; //1s中断间隔
	
	/*手动加载初值*/
	TCON |=(1<<1) ;
	/*设置自动加载,启动定时器*/
	TCON &= ~(1<<1); //清除手动更新位
	TCON |= ((1<<0) | (1<<3));
}

2).设置中断控制器,不屏蔽Timer0中断

/*设置中断控制器*/
void Interrupt_Init(void)
{
	/*设置中断屏蔽寄存器,不屏蔽EINT0/2/11/19*/

	INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
	/*设置中断屏蔽寄存器,不屏蔽Timer0*/
	INTMSK &= ~(1<<10);

}

3).编写产生中断处理函数

注意:

定时器中断属于IRQ异常,产生异常时,程序跳转到0x18的位置,执行,然后执行我们预设好的函数,void IRQ_handle(void),在这个函数中需要分辨中断源,接着根据不同的中断源,调用不同的处理函数。注意需要清除中断标志位。

代码如下:

/*中断服务函数*/
void IRQ_handle(void)
{ 
	int eint_bit = INTOFFSET;  //读取INTOFFSET寄存器的值
	/*分辨中断源:使用INTOFFSET寄存器判断发生哪个中断*/
	/*外部中断0,或者外部中断2,或者外部中断8-23发生*/
	if(eint_bit == 0 || eint_bit==2 || eint_bit==5)   
	{
		/*调用按键中断处理函数,清除中断*/
		key_eint_irq(eint_bit);
		
	}
	else if(eint_bit == 10)
	{	
		timer_irq(eint_bit);
	}
	
	/*清除中断标志位,
	 *1.首先清除具体哪一个中断,在上面执行了
	 *2.清除SRCPEND
	 *3.清除INTPEND
	 */
	 SRCPND = (1<<eint_bit);  //写1清除
	 INTPND = (1<<eint_bit);  //写1清除

}

4).编写timer_irq(eint_bit) 函数,如下所示

/*定时器中断服务函数*/
void timer_irq(int eint_bit)
{
	static int cnt=0;
	cnt++;
	if(cnt==1)
	{
		GPFDAT &= ~((1<<4)|(1<<5)
|(1<<6));
	}
	else if(cnt==2)
	{
		cnt =0;
		GPFDAT |= ((1<<4)|(1<<5)
|(1<<6));
	}

}

5).修改Makefile,增加timer.c

all: start.o led.o uart.o sdram_init.o main.o execption.o interrupt.o timer.o
	arm-linux-ld -T sdram.lds  $^ -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin	
	arm-linux-objdump -D sdram.elf > sdram.dis

%.o : %.c
	arm-linux-gcc -c -o $@ $<

%.o : %.S
	arm-linux-gcc -c -o $@ $<
clean:
	rm *.bin *.o *.elf *.dis

6).上传编译,下载

打开串口,每隔1s打印一次

开发版的灯也是1s闪烁一次。



5.定时器的改进

思路:

使用一个结构体数组来保存已经注册的定时器。

然后提供一个函数给外部调用,如果需要使用定时器就需要进行注册,不需要使用就注销定时器。

1.新建一个文件timer.h

新建一个结构体用于保存定时器的参数:

2.修改tiemr.c函数

注册定时器函数:

注销定时器函数

中断定时器函数:

3.设置定时器0的中断周期为10ms

并使用上一节的register_irq( )函数注册,定时器0

4.在led.c中调用注册函数,使用定时器0

5.在led_timer_irq中实现循环点灯

/*定时器中断服务函数*/
void led_timer_irq(int eint_bit)
{
	static int cnt=0;
	cnt++;
	if(cnt==1)
	{
		GPFDAT &= ~((1<<4)|(1<<5)
|(1<<6));
	}
	else if(cnt==2)
	{
		cnt =0;
		GPFDAT |= ((1<<4)|(1<<5)
|(1<<6));
	}

}

6.在main函数中初始化led和定时器

7.下载烧录,效果同上。

这样一来定时器中断就会变得很灵活。

例如:当需要使用定时器的时候。

1.在定时器初始化函数中,调用register_irq(INT_TIMER0,Timer_Handler);  注册定时器0,允许产生定时器0中断。

含义:如果有INT_TIMER0中断产生,执行Timer_Handler函数

2.如果此时想在led中使用定时器,只需在led初始化函数中,调用register_timer("led",led_timer_irq); 函数即可

含义:注册名为“led”的函数led_timer_irq,在Timer_Handler 中就会寻找已经注册的函数执行,最后执行的就是

led_timer_irq .

而在led_timer_irq 执行什么就由你自己决定了。


源代码:

定时器中断实验:https://download.csdn.net/download/qq_36243942/10932444

定时器改进例子:https://download.csdn.net/download/qq_36243942/11123177

发布了91 篇原创文章 · 获赞 247 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_36243942/article/details/86589872