• 售前

  • 售后

热门帖子
入门百科

51单片机入门——定时器与外部制止

[复制链接]
123457376 显示全部楼层 发表于 2022-1-16 09:30:47 |阅读模式 打印 上一主题 下一主题
目 录



1. 定时器

1.1. 定时器的开端熟悉

在熟悉定时器之前我们先相识两个根本概念。
时钟周期:时钟周期 T 是时序中最小的时间单位,具体盘算方法就是 1 / 时钟源频率,一样平常环境下单片机的晶振都是 11.0592 MHz 的,对于这个单片机体系来说时钟周期就是 1 / 11059200 秒。
呆板周期:我们的单片机完成一个利用的最短的时间。呆板周期紧张针对于汇编语言,在汇编语言下步调的每一条语句所利用的时间都是呆板语言的整数倍,而且语句占用的时间是可以盘算出来的,而 C 语言一条语句所占用的时间是不确定的,受诸多因数的影响。51 单片机系列,在其标准架构下一个呆板语言是 12 个时钟周期,也就是 12 / 11059200 秒。如今有不少增强型的 51 单片机,其速率都比力快,有的 1 个 呆板周期便是 4 个时钟周期,有的 1 个呆板周期就便是 1 个时钟周期,也就是说大要上其速率可以到达标准 51 架构的 3 倍 或 12 倍。
上述概念相识后就可以开始我们的重头戏了,定时器和计数器。定时器计数器是单片机的同一个模块,通过设置 SFR(特别功能寄存器)可以实现的两种差异的功能,我们大多数环境下都利用的是定时器,故本文紧张陈诉定时器功能。
顾名思义,定时器就是用来定时的。定时器内有一个寄存器,我们让它开始计数后,这个寄存器的值每颠末过一个呆板周期就会主动加 1,因此,我们可以把呆板周期明白为定时器的计数周期。就像我们的钟表,每颠末一秒,数字主动加 1,而这个定时器就是每过一个呆板周期的时间,也就是 12/11059200 秒,数字主动加 1。另有一个特别注意的地方,就是钟表是加到 60 后,秒就主动酿成 0 了,这种环境在单片机或盘算机里我们称之为溢出。那定时器加到多少才会溢出呢?背面会讲到定时器有多种工作模式,分别利用差异的位宽(教唆用多少个二进制位),假如是 16 位的定时器,也就是 2 个字节,最大值就是 65535,那么加到 65535 后,再加 1 就算溢出,假如有其他位数的话,原理是一样的,对于 51 单片机来说,溢出后,这个值会直接酿成 0。从某一个初始值开始,颠末确定的时间后溢出,这个过程就是定时的寄义。
1.2. 定时器的寄存器

标准的 51 单片机内部有 T0 和 T1 这两个定时器,T 就是 Timer 的缩写,如今许多 51 系列单片机还会增长额外的定时器,在这里我们先讲定时器 0 和 1。前边提到过,对于单片机的每一个功能模块,都是由它的 SFR,也就是特别功能寄存器来控制。与定时器有关的特别功能寄存器,有以下几个,各人不必要去影象这些寄存器的名字和作用,你只要大概知道就行,用的时间,随时可以查手册,找到每个寄存器的名字和每个寄存器所起到的作用。
  1. <code>下图的寄存器是存储定时器的计数值的。TH0/TL0 用于 T0,TH1/TL1 用于 T1。
复制代码

  1. <code>下图是定时器控制寄存器 TCON 的位分配(地址 0x88、可位寻址)
复制代码

  1. <code>下图为定时器控制寄存器的位描述
复制代码

对于上图的形貌中,只要写到硬件置 1 大概清 0 的,就是指一旦符合条件,单片机将主动完成的动作,只要写软件置 1 大概清 0 的,是指我们必须用步调去完成这个动作,后续碰到此类形貌就不再另做阐明白。
对于 TCON 这个 SFR,此中有 TF1、TR1、TF0、TR0 这 4 位必要我们明白清晰,它们分别对应于 T1 和 T0,我们以定时器 1 为例教学,那么定时器 0 同理。先看 TR1,当我们步调中写 TR1 = 1 以后,定时器值就会每颠末一个呆板周期主动加 1,当我们步调中写 TR1 = 0以后,定时器就会制止加 1,其值会保持稳定化。TF1,这个是一个标记位,他的作用是告诉我们定时器溢出了。比如我们的定时器设置成 16 位的模式,那么每颠末一个呆板周期,TL1加 1 一次,当 TL1 加到 255 后,再加 1,TL1 酿成 0,TH1 会加 1 一次,云云不停加到 TH1和 TL1 都是 255(即 TH1 和 TL1 构成的 16 位整型数为 65535)以后,再加 1 一次,就会溢出了,TH1 和 TL1 同时都变为 0,只要一溢出,TF1 立刻主动酿成 1,告诉我们定时器溢出了,仅仅是提供给我们一个信号,让我们知道定时器溢出了,它不会对定时器是否继承运行产生任何影响。
定时器有多种工作模式,工作模式的选择就由 TMOD 来控制。
  1. <code>下图为定时器模式寄存器的位分配(地址 0x89、不可位寻址)
复制代码

  1. <code>下图为定时器模式寄存器的位描述
复制代码

  1. <code>下图为定时器模式寄存器 M1 / M0 工作模式
复制代码

在定时器控制寄存器 TCON 的位分配末了标注了“可位寻址”,而定时器模式寄存器的位分配标注的是“不可位寻址”。意思就是说:比如 TCON 有一个位叫 TR1,我们可以在步调中直接举行 TR1 = 1 如许的利用。但对 TMOD 里的位比如 ( T1 ) M1 = 1 如许的利用就是错误的。我们要利用就必须一次利用这整个字节,也就是必须一次性对 TMOD 全部位利用,不能对此中某一位单独举行利用,那么我们能不能只修改此中的一位而不影响别的位的值呢?固然可以,在后续文章中你就会学到方法的,如今就先不关心它了。
上图列出的就是定时器的 4 种工作模式,此中模式 0 是为了兼容老的 8048 系列单片机而计划的,如今的 51 险些不会用到这种模式,而模式 3 根据我的应用履历,它的功能用模式 2 完全可以取代,以是根本上也是不消的,那么我们就重点来学习模式 1 和模式 2。
模式 1,是 THn 和 TLn 构成了一个 16 位的定时器,计数范围是 0~65535,溢出后,只要不对 THn 和 TLn 重新赋值,则从 0 开始计数。模式 2,是 8 位主动重装载模式,只有 TLn做加 1 计数,计数范围 0~255,THn 的值并不发生变革,而是保持原值,TLn 溢出后,TFn就直接置 1 了,而且 THn 原先的值直接赋给 TLn,然后 TLn 重新赋值的这个数字开始计数。这个功能可以用来产生串口的通讯波特率,我们讲串口的时间要用到,本篇上半部分我们重点学习模式 1。为了加深各人明白定时器的原理,我们来看一下他的模式 1 的电路表示图。

OSC 框体现时钟频率,由于 1 个呆板周期便是 12 个时钟周期,以是谁人 d 就便是 12。下边 GATE 右边的谁人门是一个非门电路,再右侧是一个或门,再往右是一个与门电路。
图上可以看出来,下边部分电路是控制了上边部分,那我们先来看下边是怎样控制的,我们以定时器 0 为例。
1、TR0 和下边或门电路的效果要举行与运算,TR0 假如是 0 的话,与运算完了肯定是 0,以是假如要让定时器工作,那么 TR0 就必须置 1。
2、这里的与门效果要想得到 1,那么前面的或门出来的效果必须也得是 1 才行。在 GATE位为 1 的环境下,颠末一个非门酿成 0,或门电路效果要想是 1 的话,那 INT0 即 P3.2 引脚必须是 1 的环境下,这个时间定时器才会工作,而 INT0 引脚是 0 的环境下,定时器不工作,这就是 GATE 位的作用。
3、当 GATE 位为 0 的时间,颠末一个非门会酿成 1,那么不管 INT0 引脚是什么电平,颠末或门电路后都肯定是 1,定时器就会工作。
4、要想让定时器工作,就是主动加 1,从图上看有两种方式,第一种方式是谁人开关打到上边的箭头,就是 C/T = 0 的时间,一个呆板周期 TL 就会加 1 一次,当开关打到下边的箭头,即 C/T =1 的时间,T0 引脚即 P3.4 引脚来一个脉冲,TL 就加 1 一次,这也就是计数器功能。
1.3. 定时器的应用

相识了定时器干系的寄存器,那么我们下面就来做一个定时器的步调,巩固一下我们学到的内容。我们这节课的步调先利用定时器 0,在利用定时器的时间,必要以下几个步调:
第一步:设置特别功能寄存器 TMOD,设置好工作模式。
第二步:设置计数寄存器 TH0 和 TL0 的初值。
第三步:设置 TCON,通过 TR0 置 1 来让定时器开始计数。
第四步:判定 TCON 寄存器的 TF0 位,监测定时器溢出环境。
写步调之前,我们要先来学管帐算怎样用定时器定时时间。我们的晶振是 11.0592M,时钟周期就是 1/11059200,呆板周期是 12/11059200,假如要定时 20ms,就是 0.02 秒,要颠末x 个呆板周期得到 0.02 秒,我们来算一下 x*12/11059200=0.02,得到 x= 18432。16 位定时器溢出值是 65536(因 65535 再加 1 才是溢出),于是我们就可以如许利用,先给 TH0 和 TL0一个初始值,让它们颠末 18432 个呆板周期后刚好到达 65536,也就是溢出,溢出后可以通过检测 TF0 的值得知,就刚好是 0.02 秒。那么初值 y = 65536 - 18432 = 47104,转成 16 进制就是 0xB800,也就是 TH0 = 0xB8,TL0 = 0x00。
  1. #include <reg52.h>
  2. sbit ENLED = P1^4;
  3.                
  4. void main()
  5. {
  6.         unsigned char cnt = 0; //定义一个计数变量,记录 T0 溢出次数
  7.         ENLED = 0; //使能 U3,选择独立 LED
  8.         ADDR3 = 1;
  9.         ADDR2 = 1;
  10.         ADDR1 = 1;
  11.         ADDR0 = 0;
  12.         TMOD = 0x01; //设置 T0 为模式 1
  13.         TH0 = 0xB8; //为 T0 赋初值 0xB800
  14.         TL0 = 0x00;
  15.         TR0 = 1; //启动 T0
  16.        
  17.         while (1)
  18.         {
  19.                 if (TF0 == 1) //判断 T0 是否溢出
  20.                 {
  21.                         TF0 = 0; //T0 溢出后,清零中断标志
  22.                         TH0 = 0xB8; //并重新赋初值
  23.                         TL0 = 0x00;
  24.                         cnt++; //计数值自加 1
  25.                         if (cnt >= 50) //判断 T0 溢出是否达到 50 次
  26.                         {
  27.                                 cnt = 0; //达到 50 次后计数值清零
  28.                                 LED = ~LED; //LED 取反:0-->1、1-->0
  29.                         }
  30.                 }
  31.         }
  32. }
复制代码

2. 外部停止

2.1. 停止的配景

我们假想如许一个场景:现在我正在厨房用煤气烧一壶水,而烧开一壶水刚好必要 10 分钟,我是一个主体,烧水是一个目标,而且我只能时时间刻在这里烧水,由于一旦水开了,溢出来浇灭煤气的话,有大概引发一场劫难。但就在这个时间呢,我又听到了电视里传来《喜羊羊与灰太狼》的主题歌,立刻就要开演了,我真想夺门而出,去看我最喜好的动画片。然而,听到这个水壶发出的“咕嘟”的声音,我清晰:除非等水烧开了,否则我是无法享受我喜好的动画片的。
这里边主体只有一个我,而我要做的有两件变乱,一个是看电视,一个是烧水,而电视和烧水是两个独立的客体,它们是同时举行的。此中烧水必要 10 分钟,但不必要相识烧水的过程,只必要得到水烧开的如许一个效果就行了,提下水壶和关闭煤气只必要几秒的时间而已。以是我们采取的办法就是:烧水的时间,定上一个闹钟,定时 10 分钟,然后我就可以安心看电视了。当 10 分钟时间到了,闹钟响了,现在水也烧开了,我就已往把煤气灭掉,然后继承返来看电视就可以了。
这个场景和单片机有什么关系呢?
在单片机的步调处置惩罚过程中也有许多类似的场景,当单片机正在全心全意的做一件变乱(看电视)的时间,总会有一件大概多件告急大概不告急的变乱发生,必要我们去关注,有一些必要我们停动手头的工作去立刻行止置惩罚(比如水开了),只有处置惩罚完了,才华转头继承完成刚才的工作(看电视)。这种环境下单片机的停止体系就该发挥它的强盛作用了,公道奥妙的利用停止,不但可以使我们得随处置惩罚突发状态的本领,而且可以使单片机可以大概“同时”完成多项任务。
2.2. 定时器停止的应用

在上一节我们学过了定时器,而现实上定时器一样平常用法都是采取停止方式来做的,我是故意在上一节用查询法,就是利用 if(TF0==1)如许的语句先用定时器,由于定时器和停止不是一回事,定时器是单片机模块的一个资源,确确实实存在的一个模块,而停止,是单片机的一种运行机制。尤其是初学者们,许多人会误以为定时器和停止是一个东西,只有定时器才会触发停止,但现实上许多事故都会触发停止的,除了“烧水”,另有“有人按门铃”,“来电话了”等等。
标准 51 单片机中控制停止的寄存器有两个,一个是停止使能寄存器,另一个是停止优先级寄存器,这里先先容停止使能寄存器,如下表。随着一些增强型 51 单片机的问世,大概会有增长的寄存器,各人明白了我们这里所讲的,别的的通过自己研读数据手册就可以明白明白而且用起来了。
  1. <code>下表为:IE——中断使能寄存器的位分配(地址 0xA8、可位寻址)
复制代码

  1. <code>下表为:IE——中断使能寄存器的位描述
复制代码

停止使能寄存器 IE 的位 0~5 控制了 6 个停止使能,而第 6 位没有用到,第 7 位是总开关。总开关就相称于我们家里大概弟子宿舍里的谁人电源总闸门,而 0~5 位这 6 个位相称于每个分开关。那么也就是说,我们只要用到停止,就要写 EA = 1 这一句,打开停止总开关,然后用到哪个分停止,再打开相对应的控制位就可以了。
我们如今就把前面的数码管动态表现改用停止再实现出来。
  1. <code>下图为:数码管动态显示秒表程序流程图
复制代码

  1. #include <reg52.h>
  2. sbit ADDR0 = P1^0;
  3. sbit ADDR1 = P1^1;
  4. sbit ADDR2 = P1^2;
  5. sbit ADDR3 = P1^3;
  6. sbit ENLED = P1^4;
  7. unsigned char code LedChar[] = { //数码管显示字符转换表
  8. 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
  9. 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
  10. };
  11. unsigned char LedBuff[6] = { //数码管显示缓冲区,初值 0xFF 确保启动时都不亮
  12. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
  13. };
  14. unsigned char i = 0; //动态扫描的索引
  15. unsigned int cnt = 0; //记录 T0 中断次数
  16. unsigned char flag1s = 0; //1 秒定时标志
  17. void main()
  18. {
  19.         unsigned long sec = 0; //记录经过的秒数
  20.         EA = 1; //使能总中断
  21.         ENLED = 0; //使能 U3,选择控制数码管
  22.         ADDR3 = 1; //因为需要动态改变 ADDR0-2 的值,所以不需要再初始化了
  23.         TMOD = 0x01; //设置 T0 为模式 1
  24.         TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms
  25.         TL0 = 0x67;
  26.         ET0 = 1; //使能 T0 中断
  27.         TR0 = 1; //启动 T0
  28.         while (1)
  29.         {
  30.                 if (flag1s == 1) //判断 1 秒定时标志
  31.                 {
  32.                         flag1s = 0; //1 秒定时标志清零
  33.                         sec++; //秒计数自加 1
  34. //以下代码将 sec 按十进制位从低到高依次提取并转为数码管显示字符
  35.                         LedBuff[0] = LedChar[sec%10];
  36.                         LedBuff[1] = LedChar[sec/10%10];
  37.                         LedBuff[2] = LedChar[sec/100%10];
  38.                         LedBuff[3] = LedChar[sec/1000%10];
  39.                         LedBuff[4] = LedChar[sec/10000%10];
  40.                         LedBuff[5] = LedChar[sec/100000%10];
  41.                 }
  42.         }
  43. }
  44. /* 定时器 0 中断服务函数 */
  45. void InterruptTimer0() interrupt 1
  46. {
  47.         TH0 = 0xFC; //重新加载初值
  48.         TL0 = 0x67;
  49.         cnt++; //中断次数计数值加 1
  50.         if (cnt >= 1000) //中断 1000 次即 1 秒
  51.         {
  52.                 cnt = 0; //清零计数值以重新开始下 1 秒计时
  53.                 flag1s = 1; //设置 1 秒定时标志为 1
  54.         }
  55. //以下代码完成数码管动态扫描刷新
  56.         P0 = 0xFF; //显示消隐
  57.          switch (i)
  58.         {
  59.                   case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
  60.                 case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
  61.                 case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
  62.                 case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
  63.                 case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
  64.                 case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
  65.                 default: break;
  66.         }
  67. }
复制代码
在这个步调中,有两个函数,一个是主函数,一个是停止服务函数。主函数 main()我们就不消说了,重点夸大一下停止服务函数,它的誊写格式是固定的,起首停止函数前边 void体现函数返回空,即停止函数不返回任何值,函数名是 InterruptTimer0(),这个函数名在符合函数定名规则的条件下可以任意取,我们取这个名字是为了方便区分和影象,而后是 interrupt这个关键字,肯定不能错,这是停止特有的关键字,别的后边另有个数字 1,这个数字 1 怎么来的呢?我们看看下面的表格
  1. <code>下表为:中断查询序列
复制代码

我们如今看第二行的 T0停止,要使能这个停止那么就要把它的停止使能位 ET0 置 1,当它的停止标记位 TF0 变为 1时,就会触发 T0 停止了,那么这时就应该来实行停止函数了,单片机又怎样找到这个停止函数呢?靠的就是停止向量所在,以是 interrupt 背面停止函数编号的数字 x 就是根据停止向量得出的,它的盘算方法是 x*8+3=向量所在。固然表中都已经给算好放在第一栏了,我们可以直接查出来用就行了。到此为止,停止函数的定名规则我们就都搞清晰了。停止函数写好后,每当满意停止条件而触发停止后,体系就会主动来调用停止函数。比如我们上面这个步调,平常不停在主步调 while(1)的循环中实行,假如步调有 100 行,当实行到 50 行时,定时器溢出了,那么单片机就会立即跑到停止函数中实行停止步调,停止步调实行完毕后再主动返回到刚才的第 50 行处继承实行下面的步调,如许就包管了动态表现隔断是固定的 1ms,不会由于步调实行时间不划一的缘故起因导致数码管表现的抖动了。
2.3. 停止的优先级

停止优先级的内容,各人先通过我的先容大概相识一下即可,后边现实应用的时间我们再具体明白。
在讲停止产生配景的时间,我们仅仅讲了看电视和烧水的例子,但是现实生存当中另有更复杂的,比如我正在看电视,这个时间来电话了,我要进入接电话的“停止”步调当中去,就在接电话的同时,听到了水开的声音,水开的“停止”也发生了,我们就必须要放动手上的电话,先把煤气关掉,然后再返来听电话,末了听完了电话再看电视,这里就产生了一个优先级的题目。
另有一种环境,我们在看电视的时间,这个时间听到水开的声音,水开的“停止”发生了,我们要进入关煤气的“停止”步调当中,而在关煤气的同时,电话声音响了,而这个时间,我们的处置惩罚方式是先把煤气关闭,再去接听电话,末了再看电视。
从这两个过程中,我们可以得到一个结论,就是最最告急的变乱,一旦发生后,我们不管其时处在哪个“步调”当中,我们必须先行止置惩罚最最告急的变乱,处置惩罚完毕后再去办理别的变乱。在我们的单片机步调当中偶然候也是如许的,有一样平常告急的停止,有特别告急的停止,这取决于具体的体系计划,这就涉及到停止优先级和停止嵌套的概念,在本节我们先简朴先容一下干系寄存器,不做例程阐明。
停止优先级有两种,一种是抢占优先级,一种是固有优先级,先先容抢占优先级。
  1. <code>下表为:IP——中断优先级寄存器的位分配(地址 0xB8、可位寻址)
复制代码

  1. <code>下表为:IP——中断优先级寄存器的位描述
复制代码

IP 这个寄存器的每一位,体现对应停止的抢占优先级,每一位的复位值都是 0,当我们把某一位设置为 1 的时间,这一位的优先级就比别的位的优先级高了。比如我们设置了 PT0位为 1 后,当单片机在主循环大概任何别的停止步调中实行时,一旦定时器 T0 发生停止,作为更高的优先级,步调立刻就会跑到 T0 的停止步调中来实行。反过来,当单片机正在 T0停止步调中实行时,假如有别的停止发生了,照旧会继承实行 T0 停止步调,直到把 T0 中的停止步调实行完毕以后,才会去实行别的停止步调。
当进入低优先级停止中实行时,如又发生了高优先级的停止,则立即进入高优先级停止实行,处置惩罚完高优先级级停止后,再返回处置惩罚低优先级停止,这个过程就叫做停止嵌套,也称为抢占。以是抢占优先级的概念就是,优先级高的停止可以打断优先级低的停止的实行,从而形成嵌套。固然反过来,优先级低的停止是不能打断优先级高的停止的。那么既然有抢占优先级,天然就也有非抢占优先级了,也称为固有优先级。在表停止查询序列中的末了一列给出的就是固有优先级,请注意,在停止优先级的编号中,一样平常都是数字越小优先级越高。从表中可以看到一共有 1~6 共 6 级的优先级,这里的优先级与抢占优先级的一个差异点就是,它不具有抢占的特性,也就是说纵然在低优先级停止实行过程中又发生了高优先级的停止,那么这个高优先级的停止也只能比及低优先级停止实行完后才华得到相应。既然不能抢占,那么这个优先级有什么用呢?
答案是多个停止同时存在时的仲裁。比如说有多个停止同时发生了,固然现实上发生这种环境的概率很低,但别的一种环境就常见的多了,那就是出于某种缘故起因我们暂时关闭了总停止,即 EA=0,实行完一段代码后又重新使能了总停止,即 EA=1,那么在这段时间里就很大概有多个停止都发生了,但由于总停止是关闭的,以是它们其时都得不到相应,而当总停止再次使能后,它们就会在同时哀求相应了,很显着,这时也必须有个先后序次才行,这就黑白抢占优先级的作用了——如表停止查询序列 中,谁优先级最高先相应谁,然后按编号列队,依次得到相应。
抢占优先级和非抢占优先级的协同,可以使单片机停止体系井井有条的工作,既不会无休止的嵌套,又可以包管须要时告急任务得到优先处置惩罚。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作