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 0001TF0 = 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 0001TF0 = 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.mzph.cn/news/868730.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运算符和表达式

运算符 运算&#xff1a;对数据进行加工和处理。 运算符&#xff1a;表示各种运算的符号。 操作数&#xff1a;参与运算的数据。 根据操作数的个数&#xff0c;可以将运算符分为单目、双目和多目运算符。单目运算符只对1个操作数运算&#xff0c;双目运算符对2个操作数运算…

k8s中port,targetPort,nodePort,containerPort的区别

一、说明 在 Kubernetes 中&#xff0c;port、targetPort、nodePort 和 containerPort 是用于定义服务&#xff08;Service&#xff09;和容器之间网络通信的不同参数。 它们各自的作用和含义如下&#xff1a; 1. port 定义&#xff1a;这是服务对外暴露的端口号。作用&#x…

linux指令练习

二、touch、vi练习&#xff1a; 1、在root家目录下创建目录A1和B1 2、进入B1下同时创建三个文件m1, m2 , n1&#xff0c;单独创建目录N1 3、进入到A1目录中分别创建一个文件t1,k2&#xff0c;同时创建目录F1&#xff0c;F2 4、删除B1下的所有1结尾的文件或者目录 5、删除A1目录…

Python基础知识——(001)

文章目录 P4——3. 程序设计语言的分类 1. 程序设计语言 2. 编译与解释 P5——4. Python语言的简介与开发工具 1. Python语言的简介 2. Python语言的发展 3. Python语言的特点 4. Python的应用领域 5. Python的开发工具 P6——5. IPO编程方式 IPO程序编写方法 P7——6. print函…

案例精选 | 聚铭综合日志分析系统为江苏省电子口岸构建高效安全的贸易生态

江苏省电子口岸有限公司&#xff0c;成立于2009年&#xff0c;由江苏省贸促会携手南京海关、江苏检验检疫局及江苏海事局等部门共同出资组建。公司承载着推动江苏乃至长三角地区国际贸易便利化的重大使命&#xff0c;致力于打造一个集先进性、创新性、高效性于一体的电子口岸综…

STM32初识HAL库(下载和使用)

初识HAL库&#xff08;了解&#xff09; ST 为了方便用户开发 STM32芯片开发提供了三种库&#xff1a; 标准外设库 (Standard Peripheral Libraries)HAL库(硬件抽象层)&#xff1a;Hardware Abstraction LayerLL库&#xff1a;Low Layer 一、获取STM32Cube固件包 方式一&…

jQuery 笔记

一、什么是jQuery 框架&#xff1a;半成品软件 Jquery就是封装好的js 本质上还是js jQuery是一个快速、简洁的JavaScript**框架**&#xff0c;是继Prototype之后又一个优秀的**JavaScript代码库**&#xff08;*或JavaScript框架*&#xff09;。 JQuery:封装好的代码库。有一…

【Proteus】按键的实现『⒉种』

&#x1f6a9; WRITE IN FRONT &#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四" &#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…

Qt 进程间通信(一)——QSharedMemory共享内存

QSharedMemory共享内存 序言环境理论—逻辑理解实战—代码读取示例写入示例 序言 讲讲Qt的共享内存吧&#xff0c;巩固下 环境 msvc2022 Qt5.15 参考文档&#xff1a;https://doc.qt.io/qt-5/qsharedmemory.html 理论—逻辑理解 看下面前&#xff0c;你需要将共享内存看成…

JS数据类型检测的方式有哪些 (常用)

typeof 其中数组、对象、null都会被判断为object&#xff0c;其他判断都正确typeof返回的类型都是字符串形式 instanceof instanceof &#xff1a;用于检测一个实例是否属于某个类&#xff0c;通过验证当前类的原型 prototype 是否出现在实例的原型链 __proto__ 上。它不能检测…

如何在Excel中对一个或多个条件求和?

在Excel中&#xff0c;基于一个或多个条件的求和值是我们大多数人的常见任务&#xff0c;SUMIF函数可以帮助我们根据一个条件快速求和&#xff0c;而SUMIFS函数可以帮助我们对多个条件求和。 本文&#xff0c;我将描述如何在Excel中对一个或多个条件求和&#xff1f; 在Excel中…

DataExcelServer局域网文件共享服务器增加两个函数

1、PFSUM合并指定路径下单元格ID的值 PFSUM("/103采购/8月采购名细","amount") 第一个参数为路径&#xff0c;第二个参数为单元格的ID 2、PFQuery 查询路径下 单元格ID值的列表 PFQuery("/103采购/8月采购名细","amount") 查询/103采…

【vue】JSON数据导出excel

前言 导出方式有很多种&#xff0c;但是若只需要数据导出成.xlsx文件并下载的话&#xff0c;只用xlsx一个插件就行 目标 1 实现数据导出excel 2 如何设置表格列宽 3 如何在文件中创建工作表 准备工作 1 安装 npm i xlsx -S 2 引入 npm i xlsx -S 二、导出excel 创建文件 con…

LeetCode 算法:腐烂的橘子 c++

原题链接&#x1f517;&#xff1a;腐烂的橘子 难度&#xff1a;中等⭐️⭐️ 题目 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子&#xff1b;值 2 代表腐烂的橘子。 每分钟&#…

选择适合的220V转5V电源芯片,220V转5V非隔离降压电源ic

#### 问题&#xff1a; 在设计一个需要将220V交流电转换为5V直流电的电路时&#xff0c;我应该选择哪种型号的电源芯片&#xff1f;我需要输出电流在200mA以内&#xff0c;有没有推荐的型号&#xff1f; #### 答案&#xff1a; 在220V交流电转换为5V直流电的应用中&#xff0c…

【Spring Boot】Spring AOP中的环绕通知

目录 一、什么是AOP?二、AOP 的环绕通知2.1 切点以及切点表达式2.2 连接点2.3 通知&#xff08;Advice&#xff09;2.4 切面(Aspect)2.5 不同通知类型的区别2.5.1 正常情况下2.5.2异常情况下 2.6 统一管理切点PointCut 一、什么是AOP? Aspect Oriented Programming&#xff…

STM32学习历程(day5)

EXTI外部中断 中断 中断就是在主程序运行过程中 出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;CPU会暂停当前的程序&#xff0c;去处理中断程序 处理完会返回被暂停的位置 继续运行原来的程序。 中断优先级 当有多个中断源同时申请中断时 CPU会根据…

【面试八股总结】线程基本概念,线程、进程和协程区别,线程实现

一、什么是线程&#xff1f; 线程是“轻量级进程”&#xff0c;是进程中的⼀个实体&#xff0c;是程序执⾏的最小单元&#xff0c;也是被系统独立调度和分配的基本单位。 线程是进程当中的⼀条执行流程&#xff0c;同⼀个进程内多个线程之间可以共享代码段、数据段、打开的文件…

王老师 linux c++ 通信架构 笔记(二)配置服务器为固定的 ip 地址、远程登录、安装 gcc g++ 与虚拟机文件夹共享

&#xff08;7&#xff09;本条目开始配置 linux 的固定 ip 地址&#xff0c;以作为服务器使用&#xff1a; 首先解释 linux 的网口编号&#xff1a; linux 命令 cd &#xff1a; change directory 改变目录。 ls &#xff1a; list 列出某目录下的文件 根目录文件名 / etc &a…

JAVA基础-----final关键字

一、前言 final关键字的含义&#xff1a; final在Java中是一个保留的关键字&#xff0c;可以声明成员变量、方法、类以及本地变量。一旦你用final修饰&#xff0c;你将不能改变被修饰的代码&#xff0c;编译器会检查代码&#xff0c;如果你试图将变量再次初始化的话&#xff0…