定时器
定时器介绍:
51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成
定时器作用:
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2) 替代长时间的Delay,提高CPU的运行效率和处理速度
STC89C52定时器资源
定时器个数:3个 (TO、T1、T2),TO和T1与传统的51单片机兼容T2是此型号单片机增加的资源
不同的型号注意:定时器的资源和单片机的型号是关联在一起的,可能会有不同的定时器个数和操作方式,但一般来说,TO和T1的操作方式是所有51单片机所共有的
定时器框图
定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请产生“响铃提醒”,使程序跳转到中断服务函数中执行
定时器工作模式
STC89C52的TO和T1均有四种工作模式模式0: 13位定时器/计数器
模式1: 16位定时器/计数器 (常用)
模式2: 8位自动重装模式
模式3: 两个8位计数器
工作模式1框图SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHZ
这是定时器
计数器
在单片机上看是T0处,用于接外界的计时器
中断系统
P2口作为地址复用,P3口有第二功能引脚(这里面就有中断的相关引脚)
IAP15中断源有21个
IAP里面的5个中断源
分时的操作(我在服务你的同时被中断后可以服务别人,这样就实现了服务对象的多样化);
实现实时处理(可以在编译的时候,中断的子程序);
进行故障的处理(例如:看门狗程序,进入死循环后,可以跳出并进行复位)
(下面有可能是考点)
中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的.当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示CPU中断的请求源称为中断源。
微型机的中断系统一般允许多个中断源,当几个中断源同时向CPU请求中断,要求为它服务的时候,这就存在CPU优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。
当CPU正在处理一个中断源请求的时候(执行相应的中断服务程序) ,发生了另外一个优先级比它还高的中断源请求。如果CPU能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,这样的过程称为中断嵌套。这样的中断系统称为多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。
中断程序流程
STC89C52中断资源
中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器中断源个数:8个1中断、串口中断、外部中断2、外部中断3)
中断优先级个数:4个
中断号:注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,例如中断源个数不同、中断优先级个数不同等等
定时器和中断系统
IAP15和89c52的中断系统图相同
IAP15的内部中断接口
外部中断0(连P3.2):IT0触发方式(外部请求)
IT0=0 电平触发 低电平触发
IT0=1 边沿触发分为 上升沿(低电平转高电平)
下降沿(高电平转低电平)
IE0为中断标志位:是用来存储前面的触发源是否有效
(如果触发成功IE0=1,否则的等于0)
EX0 中断允许:外部中断0的中断允许
EA为总的中断:集体关闭中断的允许,更加方便
PX0优先级:当PX0=1的时候为高优先级
(其中里面还有自然优先级的概念)
TCON控制中断请求的控制寄存器
IE,IP也都同理
T0是靠内部的定时器进行溢出来触发请求
TF0是T0的中断标志位
TR0是控制定时器启动的
相关程序书写:
1、将定时器0工作在模式1中:
想要让定时器0在模式1下工作,那么就需要将M0打开,所以M0的值为1,其余的都为0,所以得出二进制的数为0000 0001,
GATE:为门控端,表示控制启动方、方式
GATE=0(为软启动)GATE=1(为硬启动)
C/T:为0时,为定时器模式;为1的时候,为计数器模式
M0和M1:用于设置定时器的工作方式
00 01都是16位的计数器,方式00具有重复计数的功能,而00只能自己再写初值(写到中断函数中)
10 为一个8位的计数器(也具有自动重复计数功能)最多计数256个数
11 只有T0才有方式3,TL1和TH1就各自干各自的事情(这时候T1:可以继续计数,不能发送中断请求(被TH1借走了)了,这时候就辅助串口工作(给串口产生计时脉冲(波特率(是应该脉冲信号))))
定时器结束后,就会向单片机发送中断请求(就会让TF0=1或TF1=1)
中断撤销:是自动撤销(TF0=0或TF1=0)
不可位寻址:表示不能控制位
可位寻址:可以控制每一位
TMOD=0;//选择定时器 TF=0;//选0是因为防止中断,(TF0为中断艺术标志位) TR0=1;//TR0是判断定时器是否开启,如果想要让电机开始工作,就赋值为1。
计数器是8个为1位的,8个只能存256个
//控制中断器的开关 ET0=1;//打开中断的第一个开关 EA=1;//打开第二个开关 PT0=0;//打开第三个开关
中断函数
//闹钟响了以后要调到这个指函数中,起到中断的作用 //初始化后大概一ms后会发生中断 //后面跟的小尾巴就是中断子程序 void Timer0_Routine() interrupt 1 { }
这个中断系统可以用STC-ISP来进行配置(需要注意配置的时候选的频率等因素)
复制过来的代码中会少一个中断定时器(中断是是需要自己开的),开头的那个12T因为是新的版本,89C52的旧版是用不到的所以可以直接删掉
(太重要了,我听了3遍才知道的技巧)
中断系统的(完整代码)
中断子程序是不能够被调用的,
中断子程序有固定的地址入口
#include <REGX51.H> /******************************* 初始化 *******************************/ void Timer0_Init(){ // TMOD=0;//选择定时器//0000 0001 //(利用“与或”的方法) //利用下面这两句代码来配置上面的那一句代码,可以保证在配置第四位的时候不影响高四位 TMOD&=0xF0;//把TMOD低四位清零,高四位保持不变(“与”清零) TMOD|=0x01;//把TMOD低位置1,高四位保持不变(“或”置1)(任何数或零都为任何数) TF0=0;//选0是因为防止中断,(TF0为中断艺术标志位) TR0=1;//TR0是判断定时器是否开启,如果想要让定时器开始工作,就赋值为1。 TH0=64535/256;//表示高位 TL0=64535%256;//表示存下了低位(高位和地位,相当于用两个盒子把数装进去) /******上面可以用stc-isp生成,下面的中断还是需要自己打开(其中如果单片机不是12T的可以将生成的第一条删除)*********/ //控制中断器的开关 ET0=1;//打开中断的第一个开关 EA=1;//打开第二个开关 PT0=0;//打开第三个开关 } /******************************* 中断函数 *******************************/ //闹钟响了以后要调到这个指函数中,起到中断的作用 //初始化后大概一ms后会发生中断 //后面跟的小尾巴就是中断子程序 unsigned int T0Count;//定义了一个新的函数,用来尝试计时一分钟 void Timer0_Routine() interrupt 1 { //计数器是同步计数,起始值是64536计时才是1ms。 TH0=64535/256; TL0=64535%256;//为了防止溢出过后,初始值会发生变化,所以在这里需要重置一下 T0Count++;//来计中断的次数 if(T0Count>=1000){ T0Count=0;//将T0Count恢复原值 P2_0=~P2_0;//表示在执行Timer0_Init()的时候,主函数中虽然没有写Timer0_Routine()但还是会点亮灯,这就说明中断起到了作用 } } void main(){ Timer0_Init(); while(1){ } }
static unsigned int T0Count;//定义静态变量,这样定义的话就不会丢失数字
流水灯
用_crol_和_cror_来实现循环左移和循环右移
完整代码如下
//main.c #include <REGX51.H> #include "Timer0.h" #include "Key.h" #include <INTRINS.H> unsigned char KeyNum,LEDMode;//LEDMode为流水灯模式 void main(){ P2=0xFE;//点亮最低位LED Timer0Init(); while(1){ KeyNum=Key();//给 KeyNumber赋值 if(KeyNum) { if(KeyNum==1) { LEDMode++; if(LEDMode>=2)LEDMode=0; } } } } ///******************************* //初始化 //*******************************/ //void Timer0_Init(){ TMOD=0;//选择定时器//0000 0001 // //(利用“与或”的方法) // //利用下面这两句代码来配置上面的那一句代码,可以保证在配置第四位的时候不影响高四位 // TMOD&=0xF0;//把TMOD低四位清零,高四位保持不变(“与”清零) // TMOD|=0x01;//把TMOD低位置1,高四位保持不变(“或”置1)(任何数或零都为任何数) // // TF0=0;//选0是因为防止中断,(TF0为中断艺术标志位) // TR0=1;//TR0是判断定时器是否开启,如果想要让定时器开始工作,就赋值为1。 // TH0=64535/256;//表示高位 // TL0=64535%256;//表示存下了低位(高位和地位,相当于用两个盒子把数装进去) // // /******上面可以用stc-isp生成,下面的中断还是需要自己打开(其中如果单片机不是12T的可以将生成的第一条删除)*********/ // 控制中断器的开关 // ET0=1;//打开中断的第一个开关 // EA=1;//打开第二个开关 // PT0=0;//打开第三个开关 // //} /******************************* 中断函数//一般都放在主函数中 *******************************/ //闹钟响了以后要调到这个指函数中,起到中断的作用 //初始化后大概一ms后会发生中断 //后面跟的小尾巴就是中断子程序 void Timer0_Routine() interrupt 1 { static unsigned int T0Count;//定义静态变量//定义了一个新的函数,用来尝试计时一分钟 //计数器是同步计数,起始值是64536计时才是1ms。 TH0=0x18; TL0=0xfc;//为了防止溢出过后,初始值会发生变化,所以在这里需要重置一下 T0Count++;//来计中断的次数 if(T0Count>=500){//每个500毫秒进来一次 T0Count=0;//将T0Count恢复原值 if(LEDMode==0){ P2=_crol_(P2,1); } if(LEDMode==1){ P2=_cror_(P2,1); } // P2_0=~P2_0;//表示在执行Timer0_Init()的时候,主函数中虽然没有写Timer0_Routine()但还是会点亮灯,这就说明中断起到了作用 } }
//Timer0.c #include <REGX51.H> /** * @brief 定时器初始化,1毫秒@12.000MHz * @param 无 * @retval 无 */ //Timer0.c void Timer0Init(void) //1毫秒@12.000MHz { // AUXR &= 0x7F; //定时器时钟12T模式//这句话在旧版的89C52中是可以删去的 TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; //设置定时器模式 TL0 = 0x18; //设置定时初值 TH0 = 0xFC; //设置定时初值 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0=1;//打开中断的第一个开关 EA=1;//打开第二个开关 PT0=0;//打开第三个开关 } /*定时器中断函数模板 void Timer0_Routine() interrupt 1 { static unsigned int T0Count;//定义静态变量//定义了一个新的函数,用来尝试计时一分钟 //计数器是同步计数,起始值是64536计时才是1ms。 TH0=64535/256; TL0=64535%256;//为了防止溢出过后,初始值会发生变化,所以在这里需要重置一下 T0Count++;//来计中断的次数 if(T0Count>=1000){ T0Count=0;//将T0Count恢复原值 P2_0=~P2_0;//表示在执行Timer0_Init()的时候,主函数中虽然没有写Timer0_Routine()但还是会点亮灯,这就说明中断起到了作用 } } */
//Timer0.h #ifndef __TIMER0_H__ #define __TIMER0_H__ void Timer0Init(void); //1毫秒@12.000MHz #endif
//Key.c #include <REGX51.H> #include "Delay.h" /** * @brief 获取独立按键键码 * @param 无 * @retval 按下独立按键的键码,范围:0~4,无按键按下时值为0 */ unsigned char KeyNumber=0;//char的初值是不确定的 unsigned char Key(){ if(P3_1==0){Delay(20);while(P3_1);Delay(20);KeyNumber=1;} if(P3_0==0){Delay(20);while(P3_0);Delay(20);KeyNumber=2;} if(P3_2==0){Delay(20);while(P3_2);Delay(20);KeyNumber=3;} if(P3_3==0){Delay(20);while(P3_3);Delay(20);KeyNumber=4;} return KeyNumber; }
//Key.h #ifndef __KEY_H__ #define __KEY_H__ unsigned char Key(); #endif
中断思路
1、触发中断:选择触发方式
开总中断
开对应的中断
选择设置自然优先级、
2、定义中断子程序函数
(中断关键词 interrupt 0~4)
3、主函数
while(1);(之后等着子程序被触发)
换灯的亮的案例
时钟的代码
//main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char Sec=55,Min=59,Hour=23;//定义一个变量秒
void main(){
LCD_Init();//LCD1602初始化
Timer0Init();//对时钟进行初始化
LCD_ShowString(1,1,"Clock:");//初始化显示
LCD_ShowString(2,1," : :");
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;//定义静态变量//定义了一个新的函数,用来尝试计时一分钟
//计数器是同步计数,起始值是64536计时才是1ms。
TL0 = 0x18; //设置定时初值
TH0 = 0xFC;
// TH0=64535/256;
// TL0=64535%256;//为了防止溢出过后,初始值会发生变化,所以在这里需要重置一下
T0Count++;//来计中断的次数
if(T0Count>=1000){ //每隔1s
T0Count=0;
Sec++;
if(Sec>=60){
Sec=0;
Min++;
if(Min>=60){
Min=0;
Hour++;
if(Hour>=24){
Hour=0;
}
}
}
}
}
//LCE1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__
//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
#endif
//LCE1602.c
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
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]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
//Timer0.c
#include <REGX51.H>
/**
* @brief 定时器初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0Init(void) //1毫秒@12.000MHz
{
// AUXR &= 0x7F; //定时器时钟12T模式//这句话在旧版的89C52中是可以删去的
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
//上面的可以用ISP进行生成
//但下面的需要自己书写
ET0=1;//打开中断的第一个开关
EA=1;//打开第二个开关
PT0=0;//打开第三个开关
}
/*定时器中断函数模板
void Timer0_Routine() interrupt 1 //在中断函数中不要执行过长的任务
{
static unsigned int T0Count;//定义静态变量//定义了一个新的函数,用来尝试计时一分钟
//计数器是同步计数,起始值是64536计时才是1ms。
TL0 = 0x18; //设置定时初值
TH0 = 0xFC;
// TH0=64535/256;
// TL0=64535%256;//为了防止溢出过后,初始值会发生变化,所以在这里需要重置一下
T0Count++;//来计中断的次数
if(T0Count>=1000){ //每隔1s
T0Count=0;//将T0Count恢复原值
P2_0=~P2_0;//表示在执行Timer0_Init()的时候,主函数中虽然没有写Timer0_Routine()但还是会点亮灯,这就说明中断起到了作用
}
}
*/
//Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0Init(void); //1毫秒@12.000MHz
#endif