目录
此例程接上一篇博客:点我查看
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