51 单片机[7]:计时器

一、定时器

1. 定时器介绍

51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。

定时器作用:
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)替代长时间的Delay,提高CPU的运行效率和处理速度
……

定时器个数:3个(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源

注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的

定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔一段时间,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行。

2. 定时器工作原理

STC89C52的T0和T1均有四种工作模式:
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器

img

时钟给计数器 TL0 和 TH0 提供脉冲。每收到一个脉冲,TL0 和 TH0 就加 1 。当加到最大值(65535)时,计数器就会产生溢出,溢出之后,计数器回到 0 。计数器产生溢出后,会产生一个标志位 TF0 告诉中断系统,申请终端。

时钟有 2 个来源,一个是外部引脚T0 pin,一个是系统时钟SYSclk。

SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHz

晶振是由压电陶瓷震动产生的固定频率。

C / T ‾ C/\overline{T} C/T 取 1 时是计数器(Counter),取 0 时是定时器(Timer)

3. 中断系统

中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。

当中央处理器CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示CPU中断的请求源称为中断源。微型机的中断系统一般允许多个中断源,当几个中断源同时向CPU请求中断,要求为它服务的时候,这就存在CPU优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。

当CPU正在处理一个中断源请求的时候(执行相应的中断服务程序),发生了另外一个优先级比它还高的中断源请求。如果CPU能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,这样的过程称为中断嵌套。这样的中断系统称为多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。

中断流程

img

STC89C52中断资源

中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)

中断优先级个数:4个

中断号:
img

定时器相关寄存器

img

img

二、按键控制LED流水灯模式

1. 初始化定时器0

(1)工作模式寄存器TMOD

img

想让定时器0以模式1(16位定时/计数)工作,得通过M1=0和M0=1选择工作模式,并通过 C / T ‾ C/\overline{T} C/T=0选择定时模式。然后让GATE=0,即TR0单独控制定时器工作。

所以TMOD应该等于0000 0001,16进制为0x01

注意:可位寻址:可以单独赋值;不可位寻址:不可单独赋值,需要整体赋值。)

(2)控制寄存器TCON

img

TF0:是中断标志位,要置0
TR0:是运行控制位,要置1\

其他的位不管

(3)定时器TH0和TL0

定时器每隔1微秒+1,最大值为65535,总共定时时间为65535微妙。当设定为64535时,差值为1000,距离溢出还有1000微秒,即定时1毫秒。

所以TH0=64535/256(取出高8位),TL0=64535%256(取出低8位)

img

(4)中断器中的寄存器ET0、EA、PT0

要初始化中断允许控制寄存器、中断优先级控制寄存器。

为了接收到定时器的中断请求,需要ET0=1EA=1
若选择低优先级,则需要PT0=0

初始化代码如下:

void Timer0_Init()
{
	TMOD = 0x01;	// 0000 0001
	TF0 = 0;
	TR0 = 1;
	TH0=64535/256;
	TL0=64535%256;
	ET0=1;
	EA=1;
	PT0=0;
}

2. 定时器0的中断函数

在中断号中可以看到定时器0的中断函数
img

在函数体内部编写中断后执行什么操作。

void Timer0_Rountine() interrupt 1
{

}

3. 中断后执行:D1每隔一秒闪烁

(1)完整代码
#include <REGX52.H>


void Timer0_Init()
{
	TMOD = 0x01;	// 0000 0001
	TF0 = 0;
	TR0 = 1;
	TH0=64535/256;
	TL0=64535%256;
	ET0=1;
	EA=1;
	PT0=0;
}
void main()
{
	Timer0_Init();
	while(1)
	{
		
	}
}

unsigned int T0Count;
void Timer0_Rountine() interrupt 1
{
	T0Count++;
	TH0=64535/256;	//重新赋初值,防止溢出后从0开始计数
	TL0=64535%256;
	if(T0Count>=1000)	//每隔一秒执行一次
	{
		T0Count = 0;
		P2_0 = ~P2_0;
	}
}

编译后可以发现,主函数中并没有什么内容,但是D1却闪烁了。

(2)代码进化

TMOD = TMOD & 0xf0; //高四位不变,低四位清零
例:1010 0011 & 1111 0000 = 1010 0011(按位与)
TMOD = TMOD | 0X01; //高四位不变,最低位置1
例:1010 0000 | 0000 0001 = 1010 0001(按位或)

所以TMOD = 0x01; // 0000 0001可以替换为:

	TMOD &= 0xf0;	//高四位不变,低四位清零
	TMOD |= 0X01;	//高四位不变,最低位置1

在STC-ISP中点击“定时器计算器”,系统频率选择12MHz,选择“定时器0”,定时长度设为“1毫秒”,定时器模式选择“16位”,定时器时钟选择“12T (FOSC/12)”。最后点击“生成C代码”。复制粘贴到Keil中。

89C52单片机没有16位自动重载模式,只有16位和8位自动重载。选择12T模式是因为下图:
img
若想选6T,则需在STC-ISP中给6T打勾。一般都用12T模式。
img

void Timer0Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}

98C52没有AUXR寄存器,所以需要把AUXR &= 0x7F;删掉。
我们发现,生成的代码没有中断系统的配置,需要我们手动加上。

void Timer0Init(void)		//1毫秒@12.000MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
	PT0=0;
}

现在用计算器验证一下,刚刚我们自己设定的

	TH0=64535/256;	//重新赋初值,防止溢出后从0开始计数
	TL0=64535%256;

和自动生成的代码是否一致

	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值

64535 ÷ 256 = 252…23
商数为252,余数为23
252的16进制是FC
23的16进制是17

发现TL0差一位,即差了一微秒65535-64535正好等于1000,差一位才溢出,但还没溢出。需要再+1

4. 将定时器模块化

新建Timer.c和Timer.h文件。

在Timer.c文件中写

#include <REGX52.H>

/**
  * @brief	定时器0初始化,1毫秒@12.000MHz
  * @param	无参数传入
  * @retval	无返回值
  */
void Timer0Init(void)		//1毫秒@12.000MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
	PT0=0;
}

/*
定时器中断函数模板
void Timer0_Rountine() interrupt 1
{
	static unsigned int T0Count;	//静态变量,退出函数后不丢失值
	T0Count++;
	TL0 = 0x18;		//重新赋初值,防止溢出后从0开始计数
	TH0 = 0xFC;
	if(T0Count>=1000)	//每隔一秒执行一次
	{
		T0Count = 0;
		
	}
}
*/

在Timer.h文件中写

#ifndef __TIMER0__H__
#define __TIMER0__H__

void Timer0Init(void);

#endif

在main.c中写

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

void main()
{
	Timer0Init();
	while(1)
	{
		
	}
}


void Timer0_Rountine() interrupt 1
{
	static unsigned int T0Count;	//静态变量,退出函数后不丢失值
	T0Count++;
	TL0 = 0x18;		//重新赋初值,防止溢出后从0开始计数
	TH0 = 0xFC;
	if(T0Count>=1000)	//每隔一秒执行一次
	{
		T0Count = 0;
		P2_0 = ~P2_0;
	}
}

编译一下,发现D1一秒闪一下。

5. 按键控制流水灯

之前都是计时器的基础应用,现在进入正题。

新建Key.c和Key.h文件。去“6-1矩阵键盘”项目中把Delay.c和Delay.h文件复制到本项目。

img

然后把Delay.c和Delay.h文件添加到左侧边栏

img

在Delay.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;
}

在Delay.h里先写上

#ifndef __KEY__H__
#define __KEY__H__

unsigned char Key();

#endif

然后,去main.c文件里加上#include "Key.h"
现在就可以调用刚刚在Delay.c里定义的函数Key()了。

首先定义一个全局变量unsigned char KeyNum;,然后再main()函数里调用Key(),即KeyNum = Key();

下面测试一下返回值是否正确(Key()函数定义是否正确)。
注意要把刚才写的和定时器有关的代码全都注释掉,单独测试Key()函数。

在main()函数里写

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_Rountine() interrupt 1注释掉。
编译一下,可以看到按下K1后D2灯亮,再按一下D2灯灭;可以看到按下K2后D3灯亮,再按一下D3灯灭……

在main.c中写入#include "INTRINS.h",是为了调用循环左移_crol_和循环右移_cror_函数。

#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_Rountine() interrupt 1
{
	static unsigned int T0Count;	//静态变量,退出函数后不丢失值
	T0Count++;
	TL0 = 0x18;		//重新赋初值,防止溢出后从0开始计数
	TH0 = 0xFC;
	if(T0Count>=1000)	//每隔一秒执行一次
	{
		T0Count = 0;
		if(LEDMode==0)P2=_crol_(P2, 1);
		if(LEDMode==1)P2=_cror_(P2, 1);
	}
}

编译一下,可以看到,按下K1后,LED模块开始循环向左移(D8->D1),按下K2后,LED模块开始循环向右移(D1->D8)。

三、定时器时钟

1. 复制粘贴之前的模块化文件

新建项目“7-2 定时器时钟”和main.c。
把“5-2 LCD1602调试工具”项目文件夹的Delay.c, Delay.h, LCD1602.c, LCD1602.h文件复制粘贴到“7-2 定时器时钟”项目路径中。
把“7-1 按键控制LED流水灯模式”路径中的Timer0.c和Timer0.h文件复制粘贴到“7-2 定时器时钟”项目路径中。

别忘了要在main.c中加上相应的头文件

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

调用LCD1602中的函数

void main()
{
	LCD_Init();
	LCD_ShowString(1, 1, "Clock: ");
	while(1)
	{
		
	}
}

先编译一下,看看有没有错误。
img
可以看到没有错误。

2. 先做个秒钟

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

unsigned char Sec;

void main()
{
	LCD_Init();	//LCD初始化
	Timer0Init();	//定时器初始化
	
	LCD_ShowString(1, 1, "Clock: ");
	
	while(1)
	{
		LCD_ShowNum(2, 1, Sec, 2);
	}
}


void Timer0_Rountine() interrupt 1
{
	static unsigned int T0Count;	//静态变量,退出函数后不丢失值
	T0Count++;
	TL0 = 0x18;		//重新赋初值,防止溢出后从0开始计数
	TH0 = 0xFC;
	if(T0Count>=1000)	//每隔一秒执行一次
	{
		T0Count = 0;
		Sec++;
	}
}

编译一下,发现没有问题。

3. 加入分钟和小时

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

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

void main()
{
	LCD_Init();	//LCD初始化
	Timer0Init();	//定时器初始化
	
	LCD_ShowString(1, 1, "Clock: ");
	LCD_ShowString(2, 3, ":");
	LCD_ShowString(2, 6, ":");
	
	while(1)
	{
		LCD_ShowNum(2, 1, Hour, 2);
		LCD_ShowNum(2, 4, Min, 2);
		LCD_ShowNum(2, 7, Sec, 2);
	}
}


void Timer0_Rountine() interrupt 1
{
	static unsigned int T0Count;	//静态变量,退出函数后不丢失值
	T0Count++;
	TL0 = 0x18;		//重新赋初值,防止溢出后从0开始计数
	TH0 = 0xFC;
	if(T0Count>=1000)	//每隔一秒执行一次
	{
		T0Count = 0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
				Min=0;
				Hour++;
				if(Hour>=24)
				{
					Hour=0;
				}
			}
		}
	}
}

编译一下,可以发现时分秒都有了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/784784.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【零基础】学JS之APIS(基于黑马)

喝下这碗鸡汤 披盔戴甲,一路勇往直前! 1. 什么是事件 事件是在编程时系统内发生的动作或者发生的事情 比如用户在网页上单击一个按钮 2. 什么是事件监听? 就是让程序检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函数做出响应&#xff0c;也称为 注…

【人工智能】—基于成都市各区(市)县租房价格预测建模研究

引言 随着城市化进程的加速&#xff0c;人口流动日益频繁&#xff0c;租房市场作为城市生活的重要组成部分&#xff0c;其价格波动对居民生活质量和城市经济发展具有显著影响。成都市&#xff0c;作为中国西部地区的经济、文化、交通和科技中心&#xff0c;近年来吸引了大量人…

5.Python学习:面向对象

1.面向对象和面向过程的区别 以下五子棋为例&#xff1a; 2.类和实例 &#xff08;1&#xff09;类是抽象的模板&#xff0c;实例是根据模板创建出来的具体的对象 &#xff08;2&#xff09;比如人类就是一个类&#xff0c;刘亦菲就是人类的一个实例 2.1 新建类和类的实例…

王老师 linux c++ 通信架构 笔记(三)安装 xftp、

&#xff08;11&#xff09;调整 xshell 终端的字体大小&#xff0c;默认字体大小是 9 &#xff1a; &#xff08;12&#xff09; 共享文件夹 hgfs 的含义&#xff1a; &#xff08;13&#xff09;安装 xftp &#xff0c; 傻瓜式安装&#xff0c;出了修改下默认安装位置。 操作…

上位机图像处理和嵌入式模块部署(mcu项目2:串口日志记录器)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 淘宝上面有一个商品蛮好玩的&#xff0c;那就是日志记录器。说是记录器&#xff0c;其实就是一个模块&#xff0c;这个模块的输入是一个ttl串口&am…

18.动态规划之斐波那契数列模型1

1.第N个斐波那契数 1137. 第 N 个泰波那契数 - 力扣&#xff08;LeetCode&#xff09; 做题流程 1. 状态表示&#xff1a; 这道题可以【根据题目的要求】直接定义出状态表示&#xff1a; dp[i] 表示&#xff1a;第 i 个泰波那契数的值。 2. 状态转移方程&#xff1a; …

Social to Sales全链路,数说故事专享会开启出海新视角

————瞎出海&#xff0c;必出局 TikTok&#xff0c;这个充满活力的短视频平台&#xff0c;已经成为全球范围内不可忽视的电商巨头。就在6月8日&#xff0c;TikTok美区带货直播诞生了首个“百万大场”。在此之前&#xff0c;百万GMV被视为一道难以逾越的高墙。以TikTok为首的…

Zabbix分布式监控

目录 分布式监控架构 实现分布式监控的步骤 优点和应用场景 安装Zabbix_Proxy Server端Web页面配置 测试 Zabbix 的分布式监控架构允许在大规模和地理上分散的环境中进行高效的监控。通过分布式监控&#xff0c;Zabbix 可以扩展其监控能力&#xff0c;支持大量主机和设备…

Android - 云游戏本地悬浮输入框实现

一、简述 云游戏输入法分两种情况,以云化原神为例,分为 云端输入法 和 本地输入法,运行效果如下: 云端输入法本地输入法云端输入法 就是运行在云端设备上的输入法,对于不同客户端来说(Android、iPhone),运行效果一致。 本地输入法 则是运行在用户侧设备上的输入法,对…

WordPress开发进群V2主题源码,多种引流方法,引私域二次变现

WordPress开发进群V2主题源码&#xff0c;多种引流方法&#xff0c;引私域二次变现 全新前端UI界面&#xff0c;多种前端交互特效让页面不再单调&#xff0c;进群页面群成员数&#xff0c;群成员头像名称&#xff0c;每次刷新页面随机更新不重复&#xff0c;最下面评论和点赞也…

C语言编程3:运算符,运算符的基本用法

C语言3&#x1f525;&#xff1a;运算符&#xff0c;运算符的基本用法 一、运算符&#x1f33f; &#x1f387;1.1 定义 运算符是指进行运算的动作&#xff0c;比如加法运算符"“&#xff0c;减法运算符”-" 算子是指参与运算的值&#xff0c;这个值可能是常数&a…

4.动态SQL(if,choose,where,set,trim,foreach遍历)的使用+$和#的区别

文章目录 动态sql一、动态sql1.if条件判断2、choose、when、otherwise3、where标签4、set标签5、trim标签1)替代where标签效果2) 生成set标签效果 6、foreach迭代遍历1)属性 7.SQL标签-提取重用的SQL代码片段8、bind标签9.MyBatis中${}和#{}的区别: 动态sql 一、动态sql 常见…

React -- useState状态更新异步特性——导致获取值为旧值的问题

useState状态异步更新 问题导致的原因解决办法进一步分析后续遇到的新问题 问题 const [isSelecting, setIsSelecting] useState(false);useEffect(() > {const handleKeyDown (event) > {if (event.key Escape) {if(isSelectingRef){//.......setIsSelecting(!isSele…

js使用proxy代理监听控制事件

本文为proxy代理的实例应用&#xff0c;有关代理的内容可以参考&#xff1a; js语法---理解反射Reflect对象和代理Proxy对象 监听事件 要监听dom元素的事件&#xff0c;我们会采用回调触发的方式来执行操作&#xff0c; 而触发事件的过程很明显是一个异步操作&#xff0c;异…

Oracle中EXIT Statement用于终止循环语句的关键字

Oracle的EXIT Statement是PL/SQL编程语言中用于终止循环语句的关键字。它有两种主要形式&#xff1a;无条件EXIT和条件EXIT WHEN。以下是对Oracle EXIT Statement的详细解释&#xff1a; 1. 无条件EXIT 语法&#xff1a;EXIT; 作用&#xff1a;无条件地终止当前循环。当程序执…

【咨询】企业数字档案馆(室)建设方案-模版范例

导读&#xff1a;本模版来源某国有大型医药行业集团企业数字档案馆&#xff08;室&#xff09;建设方案&#xff08;一期300W、二期250W&#xff09;&#xff0c;本人作为方案的主要参与者&#xff0c;总结其中要点给大家参考。 目录 1、一级提纲总览 2、项目概述 3、总体规…

办公必备——ONLYOFFICE8.1版本桌面编辑器

一、介绍ONLYOFFICE ONLYOFFICE是一款免费的开源办公软件&#xff0c;它可以让你创建、编辑和分享文档、表格和演示文稿。就像微软的Office一样&#xff0c;但它是完全免费的&#xff0c;而且可以在多种设备上使用&#xff0c;包括电脑和手机。它还支持多人同时在线编辑文档&am…

SpringCloud 负载均衡

目录 一、负载均衡 1、问题 2、什么是负载均衡 服务端负载均衡 客户端负载均衡 二、Spring Cloud LoadBalance 1、使用 Spring Cloud LoadBalance 2、负载均衡策略 3、LoadBalancer 原理 一、负载均衡 1、问题 我们来看一下前面写的代码&#xff1a; List<Serv…

抖音矩阵云剪系统saas源码 短视频矩阵获客管理系统

2024抖音矩阵云混剪系统是一款专业的短视频营销管理工具。该系统支持多平台多账号的集中式管理&#xff0c;并实现一键式作品发布功能。它配备了智能标题生成和关键词优化工具&#xff0c;以及排名查询机制&#xff0c;帮助用户提升内容在平台上更好的矩阵管理. 智能剪辑 托管发…

java中 使用数组实现需求小案例

Date: 2024.04.08 18:32:57 author: lijianzhan 需求实现&#xff1a; 设计一个java类&#xff0c;java方法&#xff0c;根据用户手动输入的绩点&#xff0c;从而获取到绩点最高的成绩。 实现业务逻辑的代码块 import java.util.Scanner;public class PointDemo {/*** 需求&…