从零开始带你玩转单片机----------【第六期】51单片机定时器与中断

❤️第一话——定时器基础知识

🤡1.1什么是定时器

上图为单片机内部定时系统,51单片机的定时器属于内部的资源,电路连接与运转都在单片机内部完成;单片机内部定时器与计数器实际工作原理相同,通过配置相应寄存器来确定是计数还是定时,他们的存储范围为0~65535大小,超过后被称为溢出,配合中断操作,让主程序停下来去执行中断(中断就类似于你在写作业,突然接到了电话让你拿外卖,这时候你停下来去拿外卖,拿完外卖接着写作业,拿外卖的这个过程就是中断,写作业就是主程序)

🤖1.2定时器工作原理

在这里插入图片描述

由上图,左上角为时钟部分,中间为定时器/计数器,最右边为中断。单片机的时钟系统,是经过12或者6分频,产生一个时钟周期,每一个时钟周期,定时器加1,一直加到65535,超出后就会向系统单元发出中断请求,使程序跳至中断函数执行。

1. 定时器工作模式:模式0——13位定时器/计数器
模式1——16位定时器计数器
模式2——8位自动重装模式
模式3——两个8位计数器
2.中断流程:
在这里插入图片描述

在这里插入图片描述
这个就是判断中断的优先级

❤️第二话——补充知识

😃 2.1机器周期

以12分频为例,一个机器周期=12/主振频率,假设主振频率为12MHZ,一个机器周期=12/12x10^6 us=1us,也就是每隔1us定时器/计数器就加1,因为定时器可以装下65535,假设我们需要定时10us,那就用65535减去10us,得到的这个数赋值给定时器作为初值,定时器从这个数开始运行时,再过10us是不是就溢出产生中断了,就达到了我们定时10us的效果了。
1ms=1000次机器周期
振荡周期: 为单片机提供定时信号的振荡源的周期(晶振周期或外加振荡周期)
状态周期:2个振荡周期为1个状态周期,用S表示。振荡周期又称S周期或时钟周期。
机器周期:1个机器周期含6个状态周期,12个振荡周期。
指令周期:完成1条指令所占用的全部时间,它以机器周期为单位。

例如:外接晶振为12MHz时,51单片机相关周期的具体值为:
振荡周期=1/12us;
状态周期=1/6us;
机器周期=1us;
指令周期=1~4us;

🤣 2.2 补充一个50MHZ,波特率为115200推计数个数的(后面用的到)

在这里插入图片描述

❤️第三话——代码实操部分

软件配置步骤:
配置工作方式寄存器——TMOD
配置控制寄存器——TCON
给定时器赋初值——THX\TLX
配置中断——开中断

🏀 3.1 最简单的中断操作写法

#include <regx52.h>

void Timer0_Init()     //定时器初始化
{
	TMOD=(TMOD&0XF0|0X01;    //配置TMOD寄存器,TMOD赋值为0000 0001(16位定时器模式)
	TF0=0;            //配置TCON寄存器,中断标志位置0
	TR0=1;            //配置TCON寄存器,定时器开始计数
	
	TH0=64535/256;    //给定时器赋初值,存16进制高位
	TL0=64535%256;    //存16进制低位
	
	EA=1;              //中断配置,打开中断总开关
	ET0=1;             //中断配置,打开T0的中断
	PT0=0;             //中断优先级设置
	
}

/*用STC软件也可直接生成如下定时器初始化函数
void Timer0Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}
**********************************************/

void main()
{
	Timer0_Init();    //定时器初始化函数
	while(1)
	{
	}	
}


unsigned int T0Cout;       //T0Cout用于传参,记录中断次数
void Timer0_Routine() interrupt 1    //中断函数,interrupt 1为中断先后次序标志号
{
	TH0=64535/256;    //重新给定时器赋初值,因为范围满了,需要重新刷新下
	TL0=64535%256;
	T0Cout++;              //每产生一个机器周期,计数器加1
	if(T0Cout>=1000)       //定时1000us
	{
		T0Cout=0;          //T0Cout重新归0
		P2_0=~P2_0;        //为观察现象,P2.0口的LED灯状态取反,每隔1000us状态反转一次
	}
}

🏐 3.2 按键控制流水灯反转

/************main.c主函数************/
#include <regx52.h>       //包含52单片机相关寄存器的头文件
#include <Timer0.h>       //定时器函数的头文件
#include <Key.h>          //按键函数的头文件
#include <intrins.h>     //移位操作头文件,左边的高位移出后,右边的补位,(_crol_ 左移;_cror_ 右移)

unsigned char KeyNum,LEDMode;      //定义了按键值,LED模式


void main()
{
	P2=0XFE;          //给P2口赋值,让一个LED亮,因为后面要用循环移位
	Timer0_Init();    //定时器初始化
	while(1)
	{
		KeyNum=Key();    //键值赋给KeyNum
		if(KeyNum)           
		{
			if(KeyNum==1)          //判断按键是否按下
			{
				LEDMode++;         //按键每按下一次,LED模式加一
				if(LEDMode>=2)LEDMode=0;	 //让LEDMode随着按键按下,进行0 1 循环
			}
		}
	}
	
}


void Timer0_Routine() interrupt 1        //中断函数
{
	static unsigned int T0count;   //定义计数器,用于判断中断次数;static用于局部静态变量,程序运行结束,不销毁
	TH0=64535/256;             //再一次给定时器赋初值,因为定时器已经溢出产生了中断
	TL0=64535%256;
	T0count++;                 //让计数器加1
	if(T0count>=1000)           //每隔1000次,执行一次条件
	{
	T0count=0;                  //先让T0Count归0
	if(LEDMode==0)              //对LEDMode状态判断,为0时,P2当前状态左移一次
	{
		P2=_crol_(P2,1);
	}
	if(LEDMode==1)               //对LEDMode状态判断,为1时,P2当前状态右移一次
	{
		P2=_cror_(P2,1);
	}
	}
}
/*****************Timer0.c************************/
#include <regx52.h>

void Timer0_Init()
{
	//TMOD=0X01;  //0000 0001
	TMOD=TMOD&0xF0;  //把TMOD的低四位清零,高四位保持不变
	TMOD=TMOD|0X01;  //把TMOD的最低位置1,高四位保持不变
	TF0=0;
	TR0=1;
	TH0=64535/256;
	TL0=64535%256;
	ET0=1;
	EA=1;
	PT0=0;
}
/*****************key.c***********************/
#include <regx52.h>
#include "Delay.h"

unsigned char key()
{
	unsigned char KeyNumber=0;
	if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}    //剪纸判定后,给KEYNUMBER赋值
	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.c*******************/
void Delay(unsigned int xms)
{
	while(xms)
	{
	 unsigned char i, j;
     i = 2;
	 j = 239;
	 do
	 {
	  	while (--j);
	  } while (--i);
	 xms--;
    }
}

使用时需要把.c和.h文件放进去,这里将代码模块化了,不会的可以看之前的教程

⚽ 3.2 用定时器做一个简易时钟

#include <regx52.h>
#include <Delay.h>
#include <LCD1602.h>
#include <Timer0.h>

unsigned char Sec,Min,Hour;   //定义秒,分钟,小时

void main()
{
	LCD_Init();     //LCD初始化
	Timer0_Init();   //定时器初始化
	
	LCD_ShowString(1,1,"clock:");    //LCD的1行1列显示clock
	LCD_ShowString(2,3,":");    //LCD的2行3列显示冒号
	LCD_ShowString(2,6,":");    //LCD的2行6列显示冒号
	
	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= 0x18;
	TH0= 0xFC;
	T0Count++;
	if(T0Count>=1000)        //定时了1000毫秒,即1秒
	{
		T0Count=0;
		Sec++;        //与定时器协同,每秒累加
		if(Sec>=60)     //秒计满了60,秒清0,向分钟进1
		{
			Sec=0;
			Min++;
			if(Min>=60)      //分钟满60,分钟清0,向小时进1
			{
				Min=0;
				Hour++;
				if(Hour>=24)     //小时满24,小时清0
				{
					Hour=0;
				}
			}
			
		}
	}
	
}
/**************************LCD1602.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');
	}
}

/*****************LCD1602.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

Timer0和Delay的.c和.h直接复制上面就行。。。。定时器和中断要多看看STC官方手册的描述领悟下,才容易理解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值