C51单片机:利用定时器控制 LED 的闪烁

目录

0 引言

1 定时的原理

1.1 生活中的定时

1.2单片机中的定时器

2 C语言程序设计

2.1 定时器的内部功能

2.2 设置寄存器

2.3 实验程序

3 有两个小问题(算是延伸吧)

3.1 只能0.05s闪一次吗?

3.2 每次都要在草稿纸手算初始值好麻烦

4 最后


0 引言

其实,这个是可以通过软件延时计算器来生成自己需要的延时程序,而且效果非常好,但是既然要学习单片机,我们的目的就是要搞清楚硬件系统的知识,所以必须要学会如何用软件(程序)去控制硬件(处理器)。

1 定时的原理

1.1 生活中的定时

我们每个人都用过“定时”这个功能。比如说闹钟,我们需要学习1小时,然后定个1小时的闹钟,然后闹钟从59:59:59开始倒计时,直到00:00:00结束,闹钟响铃,学习结束,这个从我们的角度看来它是倒计时。单片机里的定时器原理也是这个,但是用的是正计时哈。

1.2单片机中的定时器

单片机的定时器,它是从某个值(你可以设定)加到某个值后停止。

这里要提到两个概念:①时钟周期;②机器周期。(下面用人话讲一下,可能不太严谨)

①时钟周期T:单片机上面有一个晶振,通电后它会产生一个工作频率f(我用的C51是11.0592MHz,板子不同频率可能不同,可以用放大镜看一下,他的参数刻在晶振的上面了),这个评率的倒数就是你用的这个板子的时钟周期,单位:s。

T = 1/f

②机器周期:一个机器周期 = n×时间周期;不同的板子,n的取值不同,C51系列的n是12(那我就以12说明了),单位也是:s。

说完这两个,开头说单片机的定时器的工作就是不停地加,而且是加1(计一个数),而每次+1,就要消耗一个机器周期(所有这就是我为啥要先解释①②),或者讲每隔一个机器周期就寄存器就会+1。

知道这个有啥用呢?因为我们要算出来,我们打算让LED隔多长时间闪一次啊,这就是目的。

现在我们打算让灯泡每隔0.05秒闪一次,即:

0.05 (s) = m × n/f (s)

n和f刚才提到了,我用我的板子的参数代进去举个例子吧。

 0.05 (s) = m × 12/11059200(s)

m =  46080(次)

 也就是说,寄存器加了四万多个1,所用时长大约0.05s。

闹钟是从你设定的某个值倒计时至00:00后停止计时,单片机中寄存计时是你设置某个值后不断+1,直到加到某个值后停止,那加到哪个数值才能停止呢?

51系列标准的单片机内部是有两个寄定时器T0和T1,每一个定时器(也就是寄存器)有16bit,最大可以存储2^16bit的数据,换成16进制就是可以储存0x00到0xFF,超过这个数值,就会发生溢出(overflow),从0xFF进位后再次变成0x00,所以如果我们可以检测到有进位的情况,说明我们设置的初值经过不断+1,到了最大值(是不是很像闹钟计时到了终值00:00呢),此时LED闪一次。

这就是所需的原理部分。下面讲一下如何将原理通过c程序的方式实现目标。

2 C语言程序设计

2.1 定时器的内部功能

注意:这块很繁琐,也牵涉很多东西,但是不要强记,先了解就好,数据手册里面的内容是用来查的,遇到需要的或者不会的翻翻就行了!而且这一块内容我暂时只讲用的着的哈,见谅,以后的blog会遇到一个讲一个,慢慢来,哈哈。

C51单片机内部的定时器有两个,即T0、T1。这两个定时器有基本相同功能的寄存器(大部分功能都是相同的),下面我用大白话讲方便快速理解,且我只讲暂时用的到的,见谅哈)。

  1. 定时器控制寄存器(TCON):
    可位寻址就是说你可以直接赋值到某一位,举个栗子:比如我想把TCON的TR0置1。
    TR0 = 1;//可位寻址
    

    像TMOD这种不可位寻址的,如我想把TMOD的定时器0.3置1,怎么写到?

    GATE = 1;//错
    TMOD = 00001000;//只能这样写,不能具体地给某一位赋值。
    TMOD = 0x04;//这样也行。

  2. 定时器模式寄存器(TMOD):

    模式寄存器TMOD是用来选择模式的,每个定时器都有4个模式(模式0、1、2、3),除了模式3功能不同,其他的是一样。
    功能大家可以自查手册,我们在这次只用模式1(为啥只用模式1?)∵模式1的功能是我们想要的。
     
  3. 定时值储存寄存器(TL、TH):用来存放我们初始值的,你把初始值放着后,开始慢慢地+1计数。
    定时器0的值储存寄存器是16bit的,分两块:TL0(低8bit)、TH0(高8bit)

2.2 设置寄存器

图源:《手把手教你学习单片机》

书作者:宋雪松(侵删)

 上图,是定时器模式1示意图。

分析:OSC是时钟频率源,也就是你板子上的晶振,d*就是之前讲的n,一般是12,Tn是外接振荡源,一般用计数器才接Tn,图上写的很清楚,当C/~T = 0时,我们接的是OSC,这也是我们要的。我们看后面的控制框,如果想闭合,即下面的组件需要输入1,TRn就是TCON中的一位,我们用的是T0的,所以就是TR0,功能见下图。

只有TR0 = 1时或者TR0 = 0且~INT0输入高电平时,哪一个情况都行,T0才开始工作(计数), 那我直接把TR0 = 1不就好了,不用考虑另一个了,多好。因为是与门,所以下面的~INT0和GATE通过并门后必须得出1,那只有GATE = 0,经过反相器后出1,此时不用考虑~INT0的情况了(因为或门的性质)啦。

看一下后面的TF0。

当从初始值经过不断+1后,到达最大值(也就是16bit值储存器的最大容量0xFF) ,会产生进位(溢出),会被TF0检测到,一旦检测到,TF0就会自动置1。

整理一下:
也就是说,我需要设置一下参数

  1. TMOD = 0x01//使定时器工作在模式1状态下。
  2. TH0 =0x4B ;
  3. TL0 = 0xFF;
  4. TR0 = 1//置1使定时器计数。

我们之前打算设置0.05s闪烁,通过计算需要加46080次1可以实现,用65535(0xFFFF)-46080=19455(这个就是初值)→0x4BFF,把0x4B、0xFF分别放入高8位,低8bit,也就是TH0 =0x4B、TL0 = 0xFF。

2.3 实验程序

#include<reg52.h>

sbit LED = P2^7;//select LED you want to flash.

void main()
{
	TMOD = 0X01;//select mode1 of Timer0
	TH0 = 0X4B;//input start.
	TL0 = 0XFF;
	TR0 = 1;//make T0 start to count.

	while(1)
	{
		if(TF0==1)//examine overflow of value 
		{
	
			TF0 = 0;
			TH0 = 0X4B;
			TL0 = 0XFF;
			LED =! LED;
		}
		
	}
}

3 有两个小问题(算是延伸吧)

3.1 只能0.05s闪一次吗?

0.05s闪一次是可以看出来的,但是我想延时的时间长一点怎么办?

有个小问题,就是我们把初值放在TL0、TH0这个存储器里,最大也就只能放0xFFFF(65535)个数据,换句话说,我就是把初值设为0x0000,让他+1,直到0xFFFF,但是折合过来也只是有71ms(0.071s),那我想设置每隔1s(1000ms)闪一次咋办?那就用下面这个程序,稍微加一点东西。

#include<reg52.h>

sbit LED = P2^7;//select LED you want to flash.

void main()
{
	unsigned int i;//define a counter.
	TMOD = 0X01;//select mode1 of Timer0
	TH0 = 0X4B;//input start.
	TL0 = 0XFF;
	TR0 = 1;//make T0 start to count.


	while(1)
	{
		if(TF0==1)//examine overflow of value 
		{
	
			TF0 = 0;
			TH0 = 0X4B;
			TL0 = 0XFF;
			i++;
			if(i>=20)//examine if it is 20 enough.
			{
				LED =! LED;
				i = 0;//counter is back 0.
			}		
		}
		
	}
}

 因为从我们设定的初值0x4BFF到0xFFFF溢出是0.05s,我们希望变成1s,那么我们可以设置到溢出时并不让LED取反(闪一次),而是攒够20次才执行。

20×0.05s = 1s

当攒够20次(我们用一个if语句判断),我们才让他取反一次,并且同时让次数清零,反复这样搞,就实现了隔1s闪一次。如果是想两秒闪一次,就是40×0.05s = 2s,可以先算好在设置参数。

3.2 每次都要在草稿纸手算初始值好麻烦

我不知道这个blog发布之后还可不可以改,如果可以改,我在补充,要不让就另写一篇补一下这个没解决的点。

4 最后

单片机确实好难啊,要涉猎的知识要很多,要不然到处都是洞,要有恒心学下去。文章内如有错误,请猛烈地抨击我吧~跪蟹各位看官老爷们了。

补充-关于不用再草稿纸每次算初值是多少的方法。

#include<reg52.h>

sbit LED = P2^0;//小灯泡D1位选
sbit LED1 = P2^1;//小灯泡D2位选

unsigned THL0,TLL0,THL1,TLL1;//全局变量

void config_t0(float s)//configure T0
{
	unsigned int tmp;
	tmp = 11059200*s/12;
	tmp = 65536 - tmp;
	THL0 = (unsigned char)(tmp>>8);//数据类型强制转换,因为TH0只能容下4bit,也就是0.5Byte,所以选择char,原本的高四位直接补零(看做被舍弃)
	TLL0 = (unsigned char)tmp;

	TR0 = 1;
	TH0 = THL0;
	TL0 = TLL0;
}
void config_t1(float s1)//configure T1
{
	unsigned int tmp;
	tmp = 11059200*(s1)/12;
	tmp = 65536 - tmp;
	THL1 = (unsigned char)(tmp>>8);
	TLL1 = (unsigned char)tmp;

	TR1 = 1;
	TH1 = THL1;
	TL1 = TLL1;
}

void main()
{
	config_t0(0.05);//你自己设置的闪烁时间间隔,单位是s,所以上面那个s就是秒的意思。
	config_t1(0.06);
	TMOD = 0x11;//T0、T1 同时选中(打开)
	while(1)
	{
		if(TF0 == 1)//溢出符号位
		{
			TF0 = 0;//溢出清零。
			TH0 = THL0;
			TL0 = TLL0;
			LED =! LED;//闪烁	
		}
	
		if(TF1 == 1)
		{
			TF1 = 0;
			TH1 = THL1;
			TL1 = TLL1;
			LED1 =! LED1;
		}
	}
}

 部分说明和代码如上,另外使用定时器控制闪烁间隔,有条件限制。前面也提到了,就是因为储存初值的寄存器最大只能存储65536,所以时间间隔最大也就是0.071s,0.072s都不行,所以要使用if循环来补充这个弊端。所以时间间隔最好取一个好算的,比如0.05s,我想变成1s闪烁一次,那就if循环20,如果你设置0.03sTF0置1,我想变成1s闪烁就不好设置if循环了。

物联沃分享整理
物联沃-IOTWORD物联网 » C51单片机:利用定时器控制 LED 的闪烁

发表评论