写在前面:在上节我们介绍了51单片机的中断系统,并且学习了外部中断的使用。这节我们学习51单片机的定时器中断。51系列单片机一定含有基本的两个定时/计数器(定时/计数器1、定时/计数器2)。
本节的主要内容包括:定时/计数器的概念、相关原理、相关寄存器的讲解。以及使用定时/计数器实现流水灯、时钟等相关实验;
如果只需要相关代码,我将其放在博客末尾的百度网盘中!!!
在前面我们使用独立按键的功能时,需要进行消抖,消抖的原理就是按键按下后进行一定的延时,在对其状态进行检测。采用的延时方式称为软件方法;
软件延时是指执行一段循环的程序进行时间延时从而达到定时的目的;软件延时的优点是无需额外的硬件开销,时间比较精确,但是其缺点是消耗了CPU的时间,所以软件延时的时间不宜过长,对于实时控制等要求严格的场合也不适用;
定时/计数器是一种可以通过软件编程来实现定时时间改变的集成电路;其定时是通过对系统的时钟脉冲计数完成的,当然也可以对外部脉冲进行计数。
51 单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称 为定时器/计数器。
定时器/计数器和单片机的 CPU 是相互独立的。定时器/计数器工作的过程是自动完成的,不需要 CPU 的参与。
51 单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加 1。
有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加 1 的 工作可以交给定时器/计数器处理。CPU 转而处理一些复杂的事情。同时可以实现精确定时作用。
上面讲到51单片机有两个基本定时/计数器T0、T1。
定时计数器的原理是:一个(16位)加1计数器,由高8位与低8位的两个寄存器THx与TLx组成。 随着输入计数器的脉冲进行加1,也就是每来一个脉冲计数器就自动加1,当加到计数器全为1时,再输入一个脉冲就使计数器回0,且计数器的溢出使相应的中断标志位为1,向CPU发出中断请求。
如果工作在定时模式,表示定时时间已到;如果工作在计数模式,则表示计数值已满。可见溢出计数器的值减去计数初值才是加1计数器的计数值。
51单片机定时器/计数器内部结构如图所示:
上图中的T1、T0引脚与单片机的P3.4、P3.5管脚相连,用于接受外部脉冲。
51单片机的定时/计数器的工作受TCON与TMOD两个寄存器控制。
TCON:使控制寄存器,控制T0、T1的启动停止以及溢出标志的设置;
TMOD:工作方式寄存器,用于确定定时/计数器的工作方式与功能;
工作方式寄存器 TMOD 用于设置定时/计数器的工作方式,低四位用于 T0,高四位用于 T1。
GATE 是门控位,即此时定时器的启动条件,加上了 INT0/1 引脚为高电平这一条件。
GATE=0 时,用于控制定时器的启动是否受外部中断源信号的影响。只要用软件使 TCON 中的 TR0 或 TR1 为 1,就可以启动定时/计数器工作;
GATA=1 时,要用软件使 TR0 或 TR1 为 1,同时外部中断引脚 INT0/1 也为高电平时,才能启动定时/计数器工作。
C/T :定时/计数模式选择位。C/T =0 为定时模式;C/T =1 为计数模式。
M1M0:工作方式设置位。定时/计数器有四种工作方式。
M1M0 | 工作方式 | 说明 |
00 | 方式0 | 13位定时/计数器 |
01 | 方式1 | 16位定时/计数器 |
10 | 方式2 | 8位自动重装定时/计数器 |
11 | 方式3 | T0分为两个独立的8位定时/计数器;T1此方式地址计数 |
TCON(控制寄存器) 的低 4 位用于控制外部中断,已在前面介绍。TCON 的高 4 位用于控制定 时/计数器的启动和中断申请
TF1(TCON.7):T1 溢出中断请求标志位。T1 计数溢出时由硬件自动置 TF1 为 1。CPU 响应中断后 TF1 由硬件自动清 0。T1 工作时,CPU 可随时查询 TF1 的 状态。所以,TF1 可用作查询测试的标志。TF1 也可以用软件置 1 或清 0,同硬件置 1 或清 0 的效果一样。
TR1(TCON.6):T1 运行控制位。TR1 置 1 时,T1 开始工作;TR1 置 0 时, T1 停止工作。TR1 由软件置 1 或清 0。所以,用软件可控制定时/计数器的启动 与停止。
TF0(TCON.5):T0 溢出中断请求标志位,其功能与 TF1 类同。
TR0(TCON.4):T0 运行控制位,其功能与 TR1 类同。
方式 0 为 13 位计数,由 TL0 的低 5 位(高 3 位未用)和 TH0 的 8 位组成。 TL0 的低 5 位溢出时向 TH0 进位,TH0 溢出时,置位 TCON 中的 TF0 标志,向 CPU 发出中断请求。
门控位 GATE 具有特殊的作用。当 GATE=0 时,经反相后使或门输出为 1,此时仅由 TR0 控制与门的开启,与门输出 1 时,控制开关接通,计数开始;当 GATE=1 时,由外中断引脚信号控制或门的输出,此时控制与门的开启由外中断引脚信号 和 TR0 共同控制。当 TR0=1 时,外中断引脚信号引脚的高电平启动计数,外中断 引脚信号引脚的低电平停止计数。这种方式常用来测量外中断引脚上正脉冲的宽度。计数模式时,计数脉冲是 T0 引脚上的外部脉冲。计数初值与计数个数的关系为:X=2^13-N;N为计数初值;
1.5.2方式1
方式 1 的计数位数是 16 位,由 TL0 作为低 8 位,TH0 作为高 8 位,组成了 16 位加 1 计数器。计数初值与计数个数的关系为:X=2^16-N.
1.5.3方式2
方式 2 为自动重装初值的 8 位计数方式。工作方式 2 特别适合于用作较精确的脉冲信号发生器.计数初值与计数个数的关系为:X=2^8-N。
1.5.4方式3
方式 3 只适用于定时/计数器 T0,定时器 T1 处于方式 3 时相当于 TR1=0, 停止计数。工作方式 3 将 T0 分成为两个独立的 8 位计数器 TL0 和 TH0。
根据上述的原理概念等等;在使用定时/计数器时应该如何让配置使其工作呢?
具体步骤如下(无次序排列):
1、对TMOD赋值,确定定时/计数器(T0\1)的工作方式;
2、根据所需要的定时时间,对TH0、TL0(TH1、TL1)进行赋初值;
3、如果使用中断,对IE寄存器进行赋值(打开中断开关);
4、对TCON赋值,启动定时/计数器;
如何确定定时/计数器的初值计算?
引入时序等相关概念;
①振荡周期:为单片机提供定时信号的振荡源的周期(晶振周期或外加振荡周期)。
②状态周期:2 个振荡周期为 1 个状态周期,用 S 表示。
③机器周期:1 个机器周期含 6 个状态周期,12 个振荡周期;
④指令周期:完成 1 条指令所占用的全部时间,它以机器周期为单位。
例如:外接晶振为 12MHz 时,51 单片机相关周期的具体值;
振荡周期:1/12us;状态周期:1/6us;机器周期:1us;指令周期:1-4us;
51 单片机内部时钟频率是外部时钟的 12 分频,也就是说当外部晶振的频率输入到单片机里面的时候要进行 12 分频。比如说你用的是 12MHZ 晶振,那么单片机内部的时钟频率就是 12/12MHZ, 当你使用 12MHZ 的外部晶振的时候,机器周期=1/1M=1us。如果我们想定时 1ms 的初值是多少呢?1ms/1us=1000。也就是要计数 1000 个,初值=65535-1000+1 (因为实际上计数器计数到 65536(2 的 16 次方)才溢出,所以后面要加 1) =64536=FC18H,所以初值即为 THx=0XFC,TLx=0X18。
配置定时器0的工作方式,初值。开启定时器计数功能以及总中断;
CSDNhttps://mp.csdn.net/mp_blog/creation/editor/133904885
void time0_init()
{
TMOD=0X01;//选择定时计数器T0,工作方式为方式1;
THx=0XFC;//为定时器赋初值;
TLx=0X18;
ET0=1;//打开定时器9中断允许;
EA=1;//打开总中断;
TR0=1;//打开定时器,定时器开始工作;
TF0=0;//溢出标志位为0;无溢出;
}
本实验涉及的硬件资源包括:
1、定时器模块
2、独立按键模块
3、LED模块
电路如下所示:
本次所要实现的功能是:通过定时器控制LED流水灯,此外通过独立按键K3可以改变流水灯的方向;
源码:
/*************************************************
利用定时器控制LED亮灭。实现流水灯的效果;
通过按键实现外部中断,改变流水灯的方向;
************************************************/
#include //包含_crol_函数的头文件,_crol_为按位左/右移函数
#include //包含51头文件
unsigned int u, temp;
unsigned int v=0;
sbit key =P3^2; //定义按键
/*******************************************************************************
* 函 数 名 Delayms()
* 函数功能 : 进行延迟xms;
* 输 入 : xms
* 输 出 : 无
*******************************************************************************/
void Delayms(xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
//中断初始化
void time0_init()
{
TMOD=0X01;//定时/计数器0工作,工作方式为1
TR0=1;//打开计数,开始计数‘
TF=0;//溢出标志位清0
TH0=0XFC; //给定时器赋初值,定时 1ms
TL0=0X18;
ET0=1;//打开定时器 0 中断允许
EA=1;//打开总中断
IT0=1;//选择按键下降沿触发
EX0=1;//打开外部中断0中断允许
}
void main ()
{
time0_init();//调用中断初始化
temp = 0xfe;//为LED赋初值
P2 = temp;
while(1)
{}
}
void time0() interrupt 1//定时器0中断函数
{
TH0=0XFC; //给定时器赋初值,定时 1ms
TL0=0X18;
u++;
if(u==1000)
{
u=0;
if(v == 0) temp = _crol_(temp, 1);//根据按键控制流水灯的方向
if(v == 1) temp = _cror_(temp, 1);
P2 = temp;
}
}
void exti0() interrupt 0 //外部中断0中断函数
{
Delayms(10);//消斗
if(key==0)//再次判断K3键是否按下
v=!v; //交换值,用于控制左右移
}
定时器流水灯
本实验涉及的硬件资源包括
1、LCD1602液晶屏幕;
2、定时/计数器0;
本次实验实现的功能是,在LCD屏幕上显示时、分、秒的时钟计时,当秒满60进一分钟,当分钟满60进一小时;LCD1602相关驱动程序在之前的博客中已经介绍过了,此处直接调用即可。
CSDNhttps://mp.csdn.net/mp_blog/creation/editor/133749186源码:
main.c文件
/************************************************
利用定时器控显示始时钟
************************************************/
#include "lcd1602.h"
unsigned int s;
unsigned int min;
unsigned int hour;
//中断初始化
void time0_init()
{
TMOD=0X01;//选择定时/计数器0工作,工作方式1
TR0=1;//开始计数
TH0=0XFC; //给定时器赋初值,定时 1ms
TL0=0X18;
ET0=1;//打开定时器 0 中断允许
EA=1;//打开总中断
}
void main ()//定义主函数
{
LCD_Init();//LCD1602初始化
LCD_ShowString(1,1,"clock:");//第一行显示
time0_init();//中断初始化函数引用
while(1)
{
LCD_ShowNumber(2,1,hour,2);
LCD_ShowString(2,3,":");
LCD_ShowNumber(2,4,min,2);
LCD_ShowString(2,6,":");
LCD_ShowNumber(2,7,s,2);}
}
void time0() interrupt 1//定时/计数器0中断服务程序
{
static unsigned int u;
TH0=0XFC; //给定时器赋初值,定时 1ms
TL0=0X18;
u++;
if(u==1000)
{
u=0;
s++;
if(s>=60)//秒进分钟
{
s=0;
min++;
if(min>=60)//分钟进小时
{
min=0;
hour++;
if(hour>=24)//小时满24清零
hour=0;
}
}
}
}
LCD1602.h文件
#include "LCD1602.c"
void LCD_Init();
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNumber(unsigned char Line,unsigned char Column,unsigned int number,unsigned char length);
LCD1602.c文件
#include
//引脚配置:
sbit LCD_RS=P2^6; // RS引脚为数据/指令选择 1为数据,0为指令
sbit LCD_RW=P2^5; // RW引脚为读/写选择 1为读,0为写
sbit LCD_EN=P2^7; // EN引脚为使能 1为数据有效,下降沿执行命令
#define LCD_DataPort P0 //定义P0引脚为数据端口
//延迟函数的定义;LCD1602延时函数,12MHz调用可延时xms;
void LCD_Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
//写指令函数定义: LCD1602写指令函数
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;//选择为指令,1为数据,0为指令
LCD_RW=0;//选择为写, 1为读,0为写
LCD_DataPort=Command;//写入指令的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
// 写数据函数定义: LCD1602写数据函数
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1; //选择为数据,1为数据,0为指令
LCD_RW=0; //选择为写, 1为读,0为写
LCD_DataPort=Data;//写入指数据的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
//初始化函数定义: LCD1602屏幕初始化
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
//LCD1602 进行清屏
void LCD_clear()
{
LCD_WriteCommand(0x01);
}
//设置光标位置
void LCD_SetCursor(unsigned char Line,unsigned char Column)//(行数1-2,列数1-16)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
// 字符串函数定义: LCD1602显示字符串
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
// 字符数字函数定义: LCD1602显示数字
int Pow(int x,int y)
{
unsigned char i;
int result = 1;
for(i = 0; i < y; i++)
{
result *= x;
}
return result;
}
void LCD_ShowNumber(unsigned char Line,unsigned char Column,unsigned int number,unsigned char length)
{
unsigned char i;
unsigned char temp;
LCD_SetCursor(Line,Column);
for(i =length ; i > 0 ; i--)
{
temp = number/Pow(10,i-1)%10 + '0'; //循环将每一位都提取出来并转换为字符
LCD_WriteData(temp);
}
}
定时器时钟
链接:https://pan.baidu.com/s/1_4wYlddjJ1aMO4Ta9byrkw
提取码:1022
总结: 本节的主要内容包括:定时/计数器的概念、相关原理、相关寄存器的讲解。以及使用定时/计数器实现流水灯、时钟等相关实验;大家在学习完后一定一定要自己动手练习!!!有任何问题欢迎大家留言讨论。
创作不易还请大家多多点赞支持!!!感谢大家!!!