前言

上一篇我们学习了矩阵键盘的应用,这篇我们来学习定时器的知识,并利用定时器完成独立按键控制流水灯和定时器时钟两个实例。


定时器

定时器介绍

在这里插入图片描述
前面几节讲的按键、数码管、LCD1602都是单片机I/O口控制的外设,定时器是单片机内部完成的。

其他用途还可以进行操作系统任务切换,多任务同时执行。
在这里插入图片描述
定时器的内部资源一定要参考手册!

定时器原理

在这里插入图片描述
在这里插入图片描述

  • 左边为时钟,中间为计数系统(此处为16位), TL和TH两个字节最大只能存0-65535,溢出时会置一个标志位TF0,然后向中断系统申请中断,右边为中断系统。
  • TR0控制时钟的启动和暂停
    在这里插入图片描述
  • 我的开发板的晶振是11.0592hz
  • 默认12T的模式会分频,输出为1MHz,那么连此时的线路每隔一微秒计数一次;C/T是一个选择开关,配置为1时为计数功能(count),给0为定时器(time);
  • 本节内容配置为实现定时器功能(即配置0),而时钟也可以由系统提供,也可以由外部引脚来提供,如下图中位置
    在这里插入图片描述

中断系统

在这里插入图片描述
中断源有一个优先级别,CPU总是先响应优先级别高的中断请求。

在这里插入图片描述
如上图,表明中断程序可以同时完成两项任务:主程序和中断程序。
在这里插入图片描述
在这里插入图片描述
为了方便理解,图中只有两个优先级。

在这里插入图片描述
单片机通过配置寄存器来控制内部线路的连接,开关拨到哪个位置就是靠寄存器控制的。

一般就是结合咱们的系统原理图和手册上寄存器每一位的功能来给寄存器赋值。

7-1 按键控制LED流水灯模式

加入独立按键和流水灯联动起来,如果二者简单拼接会出现一些问题:LED流水灯在移动的时候会有一个很长时间Delay,如果直接连在一起的话按键检测会很不灵敏,为了解决灵敏度问题研究本节内容。

配置定时器0

  1. 首先我们来配置寄存器,#include <REGX52.H>文档中有寄存器声明,我们可以直接在代码中使用寄存器的名字。

在这里插入图片描述

  1. 配置工作模式寄存器TMOD,根据TMOD的功能描述,我们想要定时器0处于工作模式1,那么定时器1我们先不管,所以高(7、6、5、4)四位给0,低四位给0001,即TMOD=0x01。

在这里插入图片描述

  1. 下面再来配置控制寄存器TCON,其中TF1和TR1都是定时器1的,我们不管,主要管TF0和TR0,根据TCON的功能,TF0是溢出中断标志需要先清零,TR0是定时器0的运行控制位,我们给1。即TF0=0,TR0=1

我们发现TCON和TMOD一个是可位寻址,一个是不可位,区别如下:

  • 可位寻址:可以对每一位赋值
  • 不可位寻址:只能整体赋值

在这里插入图片描述
IE0和IT0由于我们GATE=0,所以这部分不用管。
在这里插入图片描述
4. TL0和TH0我们还需要配置,一开始我没听懂视频中这部分讲的内容,后面多听了几遍明白了,首先我们要清楚TL0和THO共八位,对应十进制0~65536,晶振12Mhz每隔1微秒计数加1,总共定时时间为65536微秒,总计1000次就能实现1s的间隔,所以只需要给TL0和TH0赋一个初值,也就是65536-初值=1000,就能完成1s的间隔。

而我的开发板是11.0592MHZ的,代入初值 = 65536 - ( 定时时间 * 晶振频率 / 12 ),得到初值=64614.4
TH0=64614/256=FC HEX
TL0=64614%256=66 HEX

  1. 还要配置中断寄存器部分,ET0=1、EA=1、PT0=0。
    在这里插入图片描述
    编写下中断函数,执行完中断函数后,跳回主函数。
/*定时器中断函数模板
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		P2_0=~P2_0;
	}	
}
*/

注意,这里还有个问题就是,TMOD是不可位寻址,如果程序既要定时器1又要定时器0,我们直接赋值会影响操作。
与或操作法 &与 |或

TMOD&=0xF0// 把TMOD的低四位清零,高四位保持不变。
TMOD|=0x01// 把TMOD的最低位置1,高四位保持不变。

其实STC-ISP软件中,有计算TL0和TH0的初值功能,以后不用再自己算了,只需要注意选择我们要的一些选项。
在这里插入图片描述
生成的代码和我们一开始配置的一致。

void Timer0Init()		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD&=0xF0; //把TMOD的低四位清零,高四位保持不变
	TMOD|=0x01; //把TMOD的最低位置1,高四位保持不变
	//初值 = 65536 - ( (1ms)定时时间 * 晶振频率 / 12 )
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
	PT0=0;
}

将定时器模块化,按照我们之前学的模块化编程的思想,生成Timer0.c和Timer0.h,这样以后直接调用就行。
Timer0.c代码:

#include <REGX52.H>
/**
  * @brief 定时器0初始化,1毫秒@11.0592MHz
  * @param  无
  * @retval 无
  */
void Timer0Init()		//1毫秒@11.0592MHz
{

	TMOD&=0xF0; //把TMOD的低四位清零,高四位保持不变
	TMOD|=0x01; //把TMOD的最低位置1,高四位保持不变
	//初值 = 65536 - ( (1ms)定时时间 * 晶振频率 / 12 )
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
	PT0=0;
}

/*定时器中断函数模板
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		P2_0=~P2_0;
	}	
}
*/

Timer0.h代码:

#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0Init();

#endif

按键控制流水灯模式

编写独立按键模块,之前说过P3_1和P3_0反了。
key.c文件

#include <REGX52.H>
#include "Delay.h"
/**
  * @brief 获取独立按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0~4,无按键按下返回值为0
  */
unsigned char Key()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
	if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
	if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
	if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
	
	return KeyNumber;
}

key.h文件

#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key();

#endif

测试功能

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"

unsigned char KeyNum;

void main()
{
//	Timer0Init();
	while(1)
	{
		KeyNum=Key();
		if(KeyNum)
		{
			if(KeyNum==1)P2_1=~P2_1;
			if(KeyNum==2)P2_2=~P2_2;
			if(KeyNum==3)P2_3=~P2_3;
			if(KeyNum==4)P2_4=~P2_4;
		}
	}
}
//void Timer0_Routine() interrupt 1
//{
//	static unsigned int T0Count;
//	TL0 = 0x18;		//设置定时初值
//	TH0 = 0xFC;		//设置定时初值
//	T0Count++;
//	if(T0Count>=1000)
//	{
//		T0Count=0;
//		P2_0=~P2_0;
//	}
//	
//}

我们发现按键模块可以正常使用
在这里插入图片描述
继续编写流水灯模式部分
对于实现流水灯,我们可以添加函数库头文件 #include <INTRINS.H>

// _crol_ 和 _cror_ 函数应用实现流水灯
unsigned char a = 0x01;
a= _crol_(a,1);   //0x02,如果a是0x08,调用后会变成0x01,与位运算不一样

完整代码如下

#include <REGX52.H>
#include "Timer0.h"
#include "key.h"
#include <INTRINS.H>

unsigned char KeyNum,LEDMode;

void main()
{
	P2=0xFE;
	Timer0Init();
	while(1)
	{
		KeyNum=Key();
		if(KeyNum)
		{
			if(KeyNum==1)
			{
				LEDMode++;
				if(LEDMode>=2)LEDMode=0;
			}

		}	
	}
	
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=500)
	{
		T0Count=0;
		if(LEDMode==0)
			P2=_crol_(P2,1);
		if(LEDMode==1)
			P2=_cror_(P2,1);
	}
	
}

编译下载后,成功实现流水灯。

7-2 定时器时钟

这部分需要用到LCD1602,所以我们把之前编写的模块直接复制粘贴到新工程文件中。
在这里插入图片描述
完整代码:

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec=55,Min=59,Hour=23;

void main()
{
	LCD_Init(); //LCD初始化
	Timer0Init(); //定时器0初始化
	LCD_ShowString(1,1,"Clock:"); //一行一列显示clock字符串
	LCD_ShowString(2,3,":  :"); 
	while(1)
	{
		LCD_ShowNum(2,1,Hour,2);
		LCD_ShowNum(2,4,Min,2);
		LCD_ShowNum(2,7,Sec,2);
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000) //每隔一秒
	{
		T0Count=0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
				Min=0;
				Hour++;
				if(Hour>=24)
				{
					Hour=0;
				}			
			}		
		}
	}	
}

现象如下:
在这里插入图片描述


Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐