蓝桥杯单片机组经验分享之(三)各模块用法(4)定时器

定时器是单片机里非常重要的一个模块,必须熟练掌握,本篇按这样的顺序展开:

一、简单介绍定时器

二、定时器基本用法

三、定时器高级使用技巧(linux中常用的思想)

一、简单介绍定时器

在说定时器之前,先提一个比较重要的东西,传统51单片机是12T的,而15单片机是1T的,单片机复位后定时器默认处于12T模式,可以通过AUXR寄存器设置定时器工作在1T模式

12T单片机的机器周期为 12/晶振频率

1T  单片机的机器周期为 1  /晶振频率

显然对于1T单片机的处理速度会比12T单片机要高,不过上面的公式并不代表单片机的真实处理速度相差12倍,对于不同的指令速度差是不一样的

机器周期不同使得定时器的计数频率不同,因此需要格外注意

定时器相关寄存器主要有以下这些

需要重点掌握的是TCON、TMOD、TL0/1、TH0/1、IE、T2L、T2H、AUXR

STC15F2K60S2单片机上有定时器0、1、2、3、4,定时器3、4我也没怎么了解,有兴趣可以自己查阅手册

1、TCON:控制寄存器,一般记住每个位,通过位寻址单独设置每一位

TFx是溢出标志位,在溢出后必须清除该位,定时器才会重新计时,若定时器工作在自动重装载模式,该位自动清除

TRx是运行控制位,置1时使能定时器x,置0时失能定时器x

IEx、ITx使用较少,有兴趣可以自行查阅手册

2、TMOD:工作模式寄存器,高四位管理定时器1,低四位管理定时器0,由于不可位寻址,必须整体赋值

GATE:使用较少,有兴趣可以自行查阅手册

:置1用作计数器,置0用作定时器

M1、M0:两位有四种组合(00~11)表示四种工作方式,其中最常用的是工作方式0(M1M0 = 00)

3、

TLx、T2L是定时器x计数值的低8位

THx、T2H是定时器x计数值的高8位(当用作8位自动重装载模式,TH就是重装值,计数值只存在于TL中)

4、IE:中断允许寄存器,可以位寻址,一般进行位操作(IP是中断优先级控制寄存器,用得少)

EA:置1使能总中断,置0失能总中断

ETx(x为0/1):开启定时器x的中断

另外定时器2中断受IE2寄存器中的ET2位控制,该寄存器不能位寻址

5、AUXR:辅助寄存器,非常重要,能设置定时器0/1输入时钟是否进行12分频、选择串口1的波特率来源(定时器1/2),同时作为定时器2的控制、工作模式寄存器,另外由于这个寄存器不存在于51,如果使用51的头文件,必须自行定义该寄存器地址(对于T2L、T2H以及以后讲到的P4口也一样):

sfr AUXR = 0x8e;
sfr T2H = 0xd6;
sfr T2L = 0xd7;

定时器0、1功能几乎完全相同,定时器1和定时器2可以用作波特率发生器即作为串口的时钟,以定时器0为例,有四种工作方式,其中最常用的是方式0:16位自动重装载模式,工作原理图如下

时钟来源有2:SYSclk(可通过AUXR的T0x12位 选择不分频或12分频)、IO口中的T0引脚的计数序列;

计数序列输入后,只有在TR0 = 1,输入有效;

在工作模式0下,设置TL0、TH0并启动定时器,定时器会将TL0、TH0的值拷贝值RL_TL0、RL_TH0作为重装值

当TL0、TH0溢出,TF0将被硬件值1,如果开启了中断(EA=1、ET0=1),则程序指针指向中断服务程序,暂时离开主程序

在不开启中断时,需要在主程序中扫描TF0并置0

在开启中断时,如果工作在非自动重装载模式,需要在中断程序软件清除TF0;如果工作在自动重装载模式,中断发生后TF0会自动硬件清除

计数初值的计算:

        选择系统频率:如果使用到串口,希望串口有较好的工作可靠性,最好选择11.0592MHz,因为这是与波特率是整数倍的关系,能够整除;如果不使用串口,希望计时器工作尽可能精确,最好选择12MHz,尤其是12T模式下,系统时钟被12分频,如果用12MHz / 12 = 1MHz为整数,而11.0592MHz / 12不是整数,会出现误差

        初值计算:16位模式下,计数范围为0~65535,若选择11.0592MHz系统频率,定时器工作在12T模式下

每次计数间隔为t1 = 12 * 1 / 11.0592 us

最大定时时长为t2 = 65535 * t1 us

期望定时时长为t3,则计数初值 Count = (最大定时时长t2 - 定时时长t3)* 11.0592 / 12 = 65535 - t3 * 11.0592 / 12

高8位:TH1 = Count / 256;

低8位:TL1 = Count % 256;

二、定时器基本用法

定时器一般用于作为计时、任务调度,我比较喜欢将定时器0用作计时,定时器1用作任务调度,因为默认情况下定时器0的中断优先级高于定时器1,这样可以尽可能保证计时的准确性

1、定时器0配置为工作方式0,每2ms中断发生一次中断(系统频率12MHz)

void TimerInit(void){

	TMOD&=0xf0;
	TH0=(65535-2000)/256;
	TL0=(65535-2000)%256;
	ET0=1;
	EA=1;
	TR0=1;
}

2、中断程序框架

void Timer0(void)interrupt 1{

}

interrupt x表示这是一个中断服务程序,x是中断向量,0~4分别表示为外部中断0、定时器中断0、外部中断1、定时器中断1、串口中断的向量

3、基本用法

#define T_MAX 100
#define TASK1_TIME 5
#define TASK2_TIME 10

void Timer1()interrupt 3{

    static unsigned int uiCnt;

    uiCnt++;
    if(uiCnt >= T_MAX)
    {
        uiCnt = 0;
    }

    if(uiCnt % TASK1_TIME == 0)
    {
        Task1();
    }
    if(uiCnt % TASK2_TIME == 0)
    {
        Task2();
    }
}

1、定义计数变量,按照中断间隔递增计时(注意要限制最大值及时清除)

2、设置任务执行周期,按一定的间隔调度任务

三、定时器高级使用技巧(linux中常用的思想)

使用上面的方法,每添加一个任务,需要找到相应的中断服务程序进行修改,这样显然比较麻烦,理想的做法应该是,我们想添加任务,不需要找到该段程序,而是直接通过代码将任务添加到中断服务程序,那么有没有这样的办法呢?显然是有的,做法如下:

在初始化定时器的时候,定义一个定时器任务调度列表,然后在定时器中断服务程序中对这个列表循环扫描,检测到需要执行的任务调用相应子程序即可

为了将任务加入定时器任务列表中,需要额外编写一个注册函数TimerFunRegister

到这里已经可以将定时器完全独立为一个模块(对于按键等模块也可以这么做),在编写调试无误后,以后基本不需要再打开定时器相关的代码文件,只需要在应用程序相关文件编程任务程序,调用TimerFunRegister将其注册到定时器任务列表中即可,如果有需要,可以加入一个注销函数,方法类似注册,大家可以自己动手尝试

代码如下:

#include "stdlib.h"

#define FUNC_NUM_MAX	10
#define T_MAX           50

typedef void (*TimerFunc)(int uiTimes);

TimerFunc g_aTimerFun[FUNC_NUM_MAX] = {NULL};

int TimerFunRegister(void *pFun)
{
	unsigned int uiIndex;
	if(pFun == NULL)
	{
		return -1;
	}

	for(uiIndex = 0; uiIndex < FUNC_NUM_MAX; uiIndex++)
	{
		if(g_aTimerFun[uiIndex] == NULL)
		{
			g_aTimerFun[uiIndex] = (BTimerFunc)pFun;
			return 0;
		}
	}

	return -1;
}

void Timer1(void)interrupt 3
{
    static unsigned int uiTimes;
    unsigned int uiIndex;
   
    uiTimes++;
    if(uiTimes >= T_MAX)
        uiTimes = 0;

	for(uiIndex = 0; uiIndex < FUNC_NUM_MAX; uiIndex++)
	{
		if(g_aTimerFun[uiIndex] == NULL)
			continue ;
		g_aTimerFun[uiIndex](uiTimes);
	}
	
}


任务注册方法:

//包含头文件

void MyTask(void)
{

}

void TaskInit(void)
{
    /*
    ***任务相关初始化
    */
    TimerFunRegister(MyTask);
}

void Init(void)
{
    TimerInit();
    TaskInit();
}

void main(void)
{
    Init();

    while(1)
    {

    }
}
发布了71 篇原创文章 · 获赞 4 · 访问量 7195

猜你喜欢

转载自blog.csdn.net/floatinglong/article/details/103691636