独立按键控制流水灯及定时器时钟(基于51单片机)

师从江科大

定时器

定时器介绍

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

定时器作用

1、计时功能,用于计时系统,可以实现软件计时,或者使程序每隔一固定时间完成一项操作

2、延时功能,代替长时间的Delay,提高CPU的运行效率和处理速度

3、中断功能,当定时器计数值达到预设值时,会产生硬件中断请求。CPU响应中断后将执行相应的中断服务程序,这样可以在固定的时间间隔内自动切换执行不同任务,提高了CPU的工作效率,实现了多任务的协同处理。

4、频率测量与计数脉冲,定时器也可以用作计数器,对外部输入的脉冲信号进行计数,从而可以测量外部事件发生的频率或次数。

STC89C52单片机中,定时器/计数器T0和T1都有4种不同的工作模式。以下是这四种工作模式的简要描述:

  1. 方式0(Mode 0):13位计数器

    • 在这种模式下,定时器是一个13位的计数器,由TLx(低8位,对于T0是TL0,对于T1是TL1)和THx(高5位)组成。
    • 计数器从0开始递增计数,当计数值达到2^13-1(即8191)时会自动重置并重新开始计数。
    • 方式0没有自动重装载功能,不适用于波特率发生器或其他需要周期性中断的应用。
  2. 方式1(Mode 1):16位计数器(常用)

    • 这是一种16位的定时器/计数器模式,使用THx作为高8位,TLx作为低8位。
    • 当计数值从0计数到65535(FFFFH)时溢出,并可以触发一个硬件中断(中断号为INT0或INT1,对应于T0和T1)。
    • 此模式下支持自动重装载初值的功能,常用于产生精确的定时间隔或者计算外部事件的频率。
  3. 方式2(Mode 2):8位自动重载模式

    • 这是一种自动重载的8位定时器/计数器,但不同于方式1,它主要用于波特率发生器。
    • TLx作为一个8位计数器,在计数溢出后,不仅会置1溢出标志(TFx),还会自动装入THx的内容到TLx中继续计数。
    • THx则用来存放预设的重载值。
  4. 方式3(Mode 3):两个8位计数器

    • 这种模式通常称为“捕捉/比较”模式,提供两个独立的8位定时器。
    • 每个定时器单元(TLx和THx)都可以独立工作,各自有自己的控制寄存器和中断功能。
    • 它们既可以用于捕捉外部信号的上升沿或下降沿时刻,也可以用于比较输出,但这种方式相对复杂,不常用在简单的定时任务中。

 工作模式1框图:

独立按键控制流水灯及定时器时钟(基于51单片机)_第1张图片

  1. 非门(NOT Gate): 非门用于实现逻辑反相操作,它有一个输入和一个输出。当输入为高电平(1)时,输出为低电平(0);(给1变0,给0变1)

  2. 与门(AND Gate): 与门有至少两个输入和一个输出,只有当所有输入均为高电平时,输出才为高电平。(有0即0)

  3. 或门(OR Gate): 或门同样具有至少两个输入和一个输出,只要有任何一个输入为高电平,输出就为高电平。(有1即1)

注:图中左下角从左到右第一个位为非门,第二个为

注:左上是定时系统,左下是计数系统,右边是中断系统

定时器时钟

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

中断系统

中断系统流程

独立按键控制流水灯及定时器时钟(基于51单片机)_第2张图片

STC89C52中断资源

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

2、中断优先级个数:4个

3、中断号:

独立按键控制流水灯及定时器时钟(基于51单片机)_第3张图片

中断系统图(传统51单片机)

独立按键控制流水灯及定时器时钟(基于51单片机)_第4张图片

定时器、计数器、中断相关寄存器

独立按键控制流水灯及定时器时钟(基于51单片机)_第5张图片

注:寄存器是连接软硬件的媒介

寄存器TCON

   独立按键控制流水灯及定时器时钟(基于51单片机)_第6张图片独立按键控制流水灯及定时器时钟(基于51单片机)_第7张图片

 独立按键控制流水灯及定时器时钟(基于51单片机)_第8张图片

独立按键控制流水灯及定时器时钟(基于51单片机)_第9张图片

寄存器TMOD

独立按键控制流水灯及定时器时钟(基于51单片机)_第10张图片

独立按键控制流水灯及定时器时钟(基于51单片机)_第11张图片

独立按键控制流水灯及定时器时钟(基于51单片机)_第12张图片

 

  1. 可位寻址: 可位寻址是指可以单独对某个寄存器或存储器中的某一位进行读取、设置或清除操作。这意味着CPU可以直接指定并操作内存或寄存器中的任何一个独立的位(bit)。例如,在一些单片机架构中,有一些特殊功能寄存器(SFRs)和内部RAM区域能够以位为单位进行操作,这些位置有专门的位地址。这对于实现诸如标志位检查、中断控制等逻辑控制非常有用。

  2. 不可位寻址: 不可位寻址则意味着只能以字节(8位)或者其他固定大小的数据单元(如半字、全字等)作为最小访问单位来操作寄存器或存储器。对于不可位寻址的寄存器,如果要改变其中的某个位状态,必须先读取整个寄存器的内容,修改该位后再将整个数据写回寄存器中。例如,某些定时器模式寄存器可能就是不可位寻址的,需要通过编程一次性更改整个寄存器的值。

按键控制LED流水灯代码

main.c

#include 
#include "Timer0.h"
#include "Key.h"
#include 

unsigned char KeyNum,LEDMode;

void main()
{
		P2=0xFE;//点亮最低位LED
		Timer0_Init();//定时器工作
		while(1)
		{
				KeyNum=Key();
				if(KeyNum)
				{
						if(KeyNum==1)
						{
								LEDMode++;
								if(LEDMode>=2)LEDMode=0;
						}
				}
		}
}

//中断处理程序
void Timer0_Rountine() interrupt 1 //1是中断序号
{
		static unsigned int TOCount;
		TH0 = 0xFC;	//高位
		TL0 = 0x18;	//低位  重新赋初值保证下一次也是1ms
		TOCount++;
		if(TOCount>=500)
		{
			TOCount=0;
			if(LEDMode==0)
				P2=_crol_(P2,1);
			if(LEDMode==1)
				P2=_cror_(P2,1);
		}
			
}

Timer0.c

#include 

/**
	* @brief 定时器初始化//1毫秒@12.000MHz
	* @param 无
	* @retval 无
   */
void Timer0_Init()		
{
        //TMOD=0x01;    //0000 0001
		TMOD &= 0xF0;			//设置定时器模式   把TMOD的低四位清零,高四位保持不变
		TMOD |= 0x01;			//设置定时器模式   把TMOD的把最低位清零,高四位保持不变

		TL0 = 0x18;				//设置定时初始值
		TH0 = 0xFC;				//设置定时初始值
		TF0 = 0;				//清除TF0标志
		TR0 = 1;				//定时器0开始计时
		ET0=1;  //将中断部分打通
		EA=1;//继续打通中断部分
		PT0=0;//定时器0中断为低优先级   PT0=1;意思是将定时器中断为高优先级
}
/*
//定时器中断模版
void Timer0_Rountine() interrupt 1 //1是中断序号
{
		static unsigned int TOCount;
		TH0 = 0xFC;	//高位
		TL0 = 0x18;	//低位  重新赋初值保证下一次也是1ms
		TOCount++;
		if(TOCount>=1000)
		{
			
			TOCount=0;
			P2_0=~P2_0;
		}
			
}
*/

Timer0.h

#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init();




#endif

Key.c

#include 
#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_1==0);Delay(20);KeyNumber=2;}
		if(P3_2==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=3;}
		if(P3_3==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=4;}
		
		return KeyNumber;
		
}

Key.h

#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key();

#endif

定时器时钟代码

main.c

#include 
#include "Delay.h"
#include "Timer0.h"
#include "LCD1602.h"
unsigned char sec=0,min=19,hour=10;
void main()
{
	LCD_Init();  //LCD初始化
	LCD_ShowString(1,1,"COLCK:");
	Timer0Init();  //定时器0初始化
	while(1)
	{
		LCD_ShowNum(2,1,hour,2); 
		LCD_ShowString(2,3,":");
		LCD_ShowNum(2,4,min,2);
		LCD_ShowString(2,6,":");
		LCD_ShowNum(2,7,sec,2);
	}
}


void TimerRoutine() interrupt 1
{
	static unsigned int T0Count;
	//当触发中断后,每次中断结束后,初始值还是为64535 即1ms
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)  //一次是1ms,*1000就是一秒    
	{
		T0Count=0;
		sec++;
		if(sec>=60)
		{
			sec=0;
			min++;
			if(min>=60)
			{
				min=0;
				hour++;
				if(hour>=24)
				{
					hour=0;
					sec=0;
					min=0;
				}
			}
		}
	}
}

Timer0.c

#include 

/**
  * @brief 定时器0初始化
  * @param  
  * @retval 
  */
void Timer0Init()
{
	TMOD&=0xF0;  //高四位不变
	TMOD|=0x01;  //设置定时器模式1 以及设置为定时方式 0
	
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	
	TF0=0;  //定时器0溢出标志位
	TR0=1;  //定时器0运行控制位
	
	TF0=1;	//设置外部中断
	ET0=1;
	EA=1;
	PT0=0;
}
/*
//定时器中断模版
void Timer0_Rountine() interrupt 1 //1是中断序号
{
		static unsigned int TOCount;
		TH0 = 0xFC;	//高位
		TL0 = 0x18;	//低位  重新赋初值保证下一次也是1ms
		TOCount++;
		if(TOCount>=1000)
		{
			
			TOCount=0;
			P2_0=~P2_0;
		}
			
}
*/

Timer0,h

#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init();




#endif

LCD1602.c

#include 

//引脚配置:
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;i0;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

若有侵权,请联系作者

你可能感兴趣的:(嵌入式硬件,c语言,51单片机)