上一期我们学习了外部中断的相关内容,现在我接着来学习定时器。
定时器/计数器是一种能够对内部时钟信号或者外部输入信号进行计数,当计数值达到设定要求时,向CPU提出中断请求,从而实现定时或计数功能的外设。定时器的基本工作原理是进行计数。
举个栗子:你可以把定时器比喻成一个装了水的瓶子,每一次计数理解成向瓶子里面丢一个石子,当丢的石子足够多时,瓶子里面的水就会溢出,产生中断请求。
当作为定时器使用时,计数信号的来源是周期性的内部时钟频率,在单片机的内部,有一个频率为12MHZ的晶振,可以稳定的产生的产生一个周期为12MHZ的时钟信号,那么定时器就可以实现在每隔一段时间加一,实现定时。我们可以来简单的计算一下一个定时器的溢出周期:
单片机的时钟周期:T=1/f(sys) = 1/12MHZ = 0.083us 这是一个时钟周期
单片机的指令周期:单片机的指令周期 = 12*时钟周期=12 * 0.083 = 1us;即定时器在工作的状态下每1us计数器就会加一(每1us向瓶子里面丢一个石子)。
那么最大可以加到多少呢?我们这里以定时器0为例:在单片机内部有两个寄存器TH0和TL0,用来给定时器0计数,那么计数的最大值就是2^16-1 = 65535(最多丢65535个石子,瓶子里的水就会溢出),也就是说定时器可以对时钟信号从0开始计数一直到65535,然后溢出产生中断请求。那么最大的定时周期就是1us*65535 =65.535ms;
当然:65.535ms的定时时间对于我们也许有一些鸡肋,但是我们可以认为的设置定时器初始的值(即可以设置这个瓶子里原来有多少石子),来控制定时的时间。
举个栗子:我们需要定时1ms,则我们可以设置:
TH0 = (65535-1000)/256 = 0xFC;
Tl0 = (65535-1000)%256 = 0x18;
这样,我们就可以控制定时器每1ms溢出一次
PS:STC15系列单片机有两种计数模式,一种是12T模式,每12个时钟周期加一,与传统的8051单片机相同;另一种时1T模式,每一个时钟信号加一,速度时12T模式的12倍,但一般默认是12T模式。
作为计数器使用时计数信号的来源是非周期性的外部输入信号。外部引号可以有对应的引脚的脉冲信号输入,其对应关系如下:
定时器0—>P3.4; 定时器1—>P3.5;定时器2—>P3.1;
TR0/TR1分别为定时器0/1的运行控制位,TR0/TR1为1时表示打开定时器0/1,为0时表示关闭
TF0/TF1为定时器0/1溢出中断标志,当定时器0/1溢出时由硬件置1,在CPU处理中断请求后自动清0,也可以由软件清0。(知道就行,不用管)
该寄存器的前四位控制定时器1,后四位控制定时器0。
GATE控制定时器打开方式。
C/T可以选择定时器的工作方式:为0时工作为定时器,为1时工作为计数器。
M1/M0控制定时器的工作模式:一共有四种。
3.辅助寄存器AUXR(不可位寻址)
主要负责对定时器0/1的速度控制,以及定时器2的相关配置
T0x12/T1x12/T2x12:控制定时器0/1/2的速度模式,为0时为12T模式,为1时为1T模式,默认是12T模式。
T2R::定时器2允许控制位,为1时表示打开定时器2,反之则关闭。
T2_C/T :为0表示工作为定时器,为1表示工作为计数器。
4.定时器T0heT1的中断控制寄存器IE和IP(可位寻址)
ET0/ET1:定时器T0/T1的中断允许位,为1时表示允许中断,反之则不允许
PT0/PT1:设置定时器T0/T1的中断优先级,为1设置为高优先级,为0设置低优先级。
5.定时器0/1/2高八位/低八位寄存器TLH0/TL0/TH1/TL1/T2H/T2L
前面讲过,定时器的本质是一个加法计数器,定时器的高八位/低八位寄存器的作用就是用来存放这个计数器的值,当计数器的值到达最大值溢出时,产生中断请求。
在M1 = M0 = 0时,定时器工作在工作模式0(16位自动重装载)。
在定时器溢出之后,会自动装载为原来设置的TL0/TH0。
在M1 =0; M0 = 1时,定时器工作在工作模式1(16位不可重装载),
在定时器溢出之后,需要在中断服务函数中设置TL0和TH0。不可自动重装载。
当M1 = M0 = 1时,定时器工作在工作模式2(八位自动重装载)
此时定时器的计数范围位0~255,在定时器溢出时,TL0=TH0,将Th0的值装载的TL0中,实现八位自动重装载。可以通过改变TH0的值去控制定时器溢出的时间。
4.不可屏蔽中断16位自动重装载
由于这个模式几乎用不到,所以我们不讲。
PS:定时器2的工作模式固定位16位自动重装载,不可更改。
配置定时器中断,一般有一下几个步骤:
初始化函数
1.设置定时器工作模式
2.设置定时器高八位/低八位寄存器的值(设置定时器溢出时间)
3.开启中断允许
4.设置定时器中断优先级(定时器2不可以设置优先级)
5.开启定时器
具体代码如下:
//定时器0初始化函数,1ms进来一次
void Timer0_Init(void)
{
TMOD |= 0x00; //设置定时器0工作模式0,16位自动重装载
// TMOD |= 0x01; //设置定时器0工作模式1
// TMOD |= 0x02; //设置定时器0工作模式2,8位自动重装载
TL0 = 0x18; //设置定时器溢出时间
TH0 = 0xFC;
ET0 = 1; //开启定时器0中断允许
EA=1; //开启总中断允许
TR0 = 1; //开启定时器0
PT0 = 0; //设置优先级为低优先级,为1时设置为高优先级
}
//定时器0初始化函数,1ms进来一次
void Timer1_Init(void)
{
TMOD |= 0x00; //设置定时器1工作模式0,16位自动重装载
// TMOD |= 0x10; //设置定时器1工作模式1
// TMOD |= 0x20; //设置定时器1工作模式2,8位自动重装载
TL1 = 0x18; //设置定时器溢出时间
TH1 = 0xFC;
ET1 = 1; //开启定时器0中断允许
EA=1; //开启总中断允许
TR1 = 1; //开启定时器0
PT1 = 0; //设置优先级为低优先级,为1时设置为高优先级
}
//定时器2初始化函数,1ms进来一次
//定时器2工作模式固定为16位自动重装载模式
void Timer2_Init(void)
{
AUXR |= 0x10; //将T2R设置为1,打开定时器2
IE2 |= 0x04; //开启定时器2中断允许
T2H = 0xFC; //设置定时器2中断溢出时间
T2L = 0x18;
EA = 1; //开启中断总允许
}
中断服务函数
6.编写定时器中断服务函数
具体中断号与定时器对于关系如下:
定时器0 interrupt 1
定时器1 interrupt 3
定时器2 interrupt 12
具体代码如下:
//定时器0服务函数
void External_Hander0() interrupt 1
{
//函数内容
}
//定时器1服务函数
void External_Hander2() interrupt 3
{
//函数内容
}
//定时器2服务函数
void External_Hander2() interrupt 12
{
//函数内容
}
首先我们有一个Tick值,可以通过Tick得到数码管需要显示的数字,每50msTick++,数码管需要显示的数字也随之改变,就可以实现模拟秒表的走时功能,
然后通过外部中断0/S5按下,将Tick清0,数码管上的数字也随之清0.
通过外部中断1/S4按下,控制定时器的开启和关闭,实现暂停和启动的功能
1.mian.c
#include
#include "Interrupt.h"
#include "LS138.h"
unsigned char arr_NixieTube[8]; //用于存放数码管显示的内容
unsigned long int Tick = 0; //用于计算数码管所需要显示的内容
//通过数码管显示的内容计数的到Tick的初始值
void arr_NixieTube_Init(void)
{
arr_NixieTube[0]=0;
arr_NixieTube[1]=8;
arr_NixieTube[2]=21;
arr_NixieTube[3]=2;
arr_NixieTube[4]=6;
arr_NixieTube[5]=21;
arr_NixieTube[6]=1;
arr_NixieTube[7]=8;
Tick = arr_NixieTube[7] + 10*arr_NixieTube[6] + arr_NixieTube[4]*60 +
arr_NixieTube[3]*600 + arr_NixieTube[1]*3600 +arr_NixieTube[0]*360000;
}
//通过Tick的值,得到数码管需要显示的内容
void Tick_To_arr(void)
{
arr_NixieTube[0] = Tick/12000;
arr_NixieTube[1] = Tick/1200%10;
arr_NixieTube[3] = Tick/200%10;
arr_NixieTube[4] = Tick/20%10;
arr_NixieTube[6] = Tick/10%2;
arr_NixieTube[7] = Tick%10;
}
//初始化函数
void Init(void)
{
Timer0_Init();
LS138_Init();
IT0_Init();
IT1_Init();
arr_NixieTube_Init();
}
void main()
{
Init();
while(1)
{
Tick_To_arr(); //在数码管显示之前,通过Tick的值,得到数码管需要显示的内容
SEG_Write(arr_NixieTube); //数码管显示内容
}
}
//外部中断0服务函数
void External_Hander0() interrupt 0
{
Tick = 0; //将Tick清0
}
//外部中断1服务函数
void External_Hander2() interrupt 2
{
TR0 = ~TR0; //开启/关闭定时器
}
void External_Hander1() interrupt 1
{
static unsigned char Timer0_Count=0;
// TL0 = 0x18;
// TH0 = 0xFC;
Timer0_Count++;
if(Timer0_Count>=50)
{
Timer0_Count=0; //每50msTick++
Tick++;
}
}
PS: void SEG_Write(unsigned char* p) 这个函数出现在“蓝桥杯单片机学习——数码管静态显示”,
2.Interrupt.c
#include "Interrupt.h"
//外部中断0初始化函数
void IT0_Init(void)
{
EX0 = 1; //开启外部中断0允许
EA=1; //开启中断总允许
IT0=1; //设置触发方式为下降沿触发,为0时触发方式为下降沿和上升沿都触发
PX0 = 0; //设置低优先级,为1时为高优先级
}
//外部中断1初始化函数
void IT1_Init(void)
{
EX1 = 1; //开启外部中断0允许
EA=1; //开启中断总允许
IT1=1; //设置触发方式为下降沿触发,为0时触发方式为下降沿和上升沿都触发
PX1 = 0; //设置低优先级,为1时为高优先级
}
//定时器0初始化函数,1ms进来一次
void Timer0_Init(void)
{
TMOD |= 0x00; //设置定时器0工作模式0,16位自动重装载
// TMOD |= 0x01; //设置定时器0工作模式1
// TMOD |= 0x02; //设置定时器0工作模式2,8位自动重装载
TL0 = 0x18; //设置定时器溢出时间
TH0 = 0xFC;
ET0 = 1; //开启定时器0中断允许
EA=1; //开启总中断允许
TR0 = 1; //开启定时器0
PT0 = 0; //设置优先级为低优先级,为1时设置为高优先级
}
3.Interrupt.h
#ifndef __INTERRUPT_H_
#define __INTERRUPT_H_
#include
//外部中断0初始化函数
void IT0_Init(void);
//外部中断1初始化函数
void IT1_Init(void);
//定时器0初始化函数,1ms进来一次
void Timer0_Init(void);
外部中断0服务函数
//void External_Hander0() interrupt 0
//{
//
//}
外部中断1服务函数
//void External_Hander2() interrupt 2
//{
//
//}
定时器0服务函数
//void External_Hander0() interrupt 1
//{
//
//}
#endif /*__INTERRUPT_H_*/
定时器在我们的程序编写工程中应用比较广泛,其中定时器0/1的使用频率最高,工作模式中16位自动重装载使用较多,需要重点掌握,使用方法并不难,但需要理解清楚定时器的工作原理,其他的就没有了,好好学习吧。