这将是我们PIC教程系列中的第五篇教程,它将帮助您学习和使用PIC16F877A中的定时器。在以前的教程中,我们从PIC和MPLABX IDE简介开始,然后编写了第一个PIC程序,以使用PIC使LED闪烁,然后通过使用PIC微控制器中的延迟功能来制作LED闪烁序列。现在,让我们使用与之前的教程硬件相同的LED闪烁序列,并以此学习我们如何在PIC MCU中使用定时器。在本教程中,我们刚刚在LED板上添加了一个按钮。浏览本教程以了解更多信息。
计时器是嵌入式程序员的重要工作之一。我们设计的每个应用程序都将以某种方式涉及定时应用程序,例如在指定的时间间隔后打开或关闭某些内容。好的,但是为什么当我们已经有延迟宏(__delay_ms())做同样的事情时,为什么需要计时器呢!
为什么在有Delay()时使用计时器?
延迟宏称为“转储”延迟。因为在执行“延迟”功能期间,MCU只是通过创建一个延迟来进行转储。在此过程中,MCU无法监听其ADC值或从其寄存器中读取任何内容。因此,建议不要使用“延迟”功能,除非“ LED闪烁”之类的应用中的时间延迟不需要准确或较长,否则建议不要使用“延迟”功能。
延迟宏还具有以下缺点,
- 对于延迟宏,延迟值必须为常数;在程序执行期间无法更改。因此,它仍然是程序员定义的。
- 与使用计时器相比,延迟将不准确。
- 不能使用宏创建较大的延迟值,例如,延迟宏无法创建半小时的延迟。可以使用的最大延迟取决于所使用的晶体振荡器。
PIC微控制器定时器:
从物理上讲,计时器是一个寄存器,其值不断增加到255,然后重新从头开始:0、1、2、3、4… 255…. 0、1、2、3…..等等。
该PIC16F877A PIC单片机有3个定时器模块。它们的名称为Timer0,Timer1和Timer2。定时器0和定时器2是8位定时器,定时器1是16位定时器。在本教程中,我们将为应用程序使用计时器0。一旦我们了解了定时器0,就可以轻松地对定时器1和定时器2进行操作。
Timer0模块定时器/计数器具有以下功能:
- 8位定时器/计数器
- 可读可写
- 8位软件可编程预分频器
- 内部或外部时钟选择
- 从FFh溢出到00h时中断
- 外部时钟的边沿选择
要开始使用定时器,我们应该了解一些花哨的术语,例如8位/ 16位定时器,预分频器,定时器中断和Focs。现在,让我们看看每个人的真正含义。如前所述,我们的PIC MCU中既有8位定时器又有16位定时器,它们之间的主要区别在于16位定时器的分辨率比8位定时器好得多。
预分频器是微控制器部分的名称,该部分在达到可增加定时器状态的逻辑之前对振荡器时钟进行分频。预分频器ID的范围是1到256,可以使用OPTION寄存器(与上拉电阻相同的值)来设置预分频器的值。例如,如果预分频器的值为64,则对于第64个脉冲,计时器将增加1。
随着定时器的递增,当其达到最大值255时,它将触发中断并将自身重新初始化为0。该中断称为定时器中断。该中断通知MCU该特定时间已耗尽。
该的Fosc代表振荡器的频率,它是用水晶的频率。定时器寄存器所用的时间取决于预分频器的值和Fosc的值。
编程和工作说明:
在本教程中,我们将两个按钮设置为两个输入,将8个LED设置为8个输出。第一个按钮将用于设置时间延迟(每次按下500毫秒),第二个按钮将用于启动计时器序列闪烁。例如,如果第一个按钮被按下三次(500 * 3 = 1500ms),则延迟将设置为1.5秒,而当按下两个按钮时,每个LED将以预定义的时间延迟打开和关闭。查看本教程末尾的演示视频。
现在,牢记这些基础知识,让我们看一下代码部分最后给出的程序。
如果您没有获得程序,那是可以的,但是如果您没有,那就可以了!给自己一个cookie并转储该程序以享受您的输出。对于其他人,我会将程序分成有意义的部分,并向您解释每个块中发生的情况。
由于代码的前几行始终是Configuration设置和头文件,因此,由于在之前的教程中已经做过,所以不再赘述。
接下来,让我们跳过所有行,直接跳到void main函数,在其中,我们具有Timer0的PORT配置。
void main(){/ *****定时器的端口配置****** / OPTION_REG = 0b00000101; //具有外部频率和64作为预分频器的Timer0 //也使能PULL UP TMR0 = 100; //加载时间值0.0019968s; delayValue只能在0-256之间,只有TMR0IE = 1;//使能PIE1寄存器GIE = 1的定时器中断位;//启用全局中断PEIE = 1; //启用外围中断/ *********** ______ *********** /
要了解这一点,我们必须查看PIC数据手册中的OPTION寄存器。
如先前教程中所讨论的,位7用于为PORTB启用弱上拉电阻。查看上图,将位3设为0,以指示MCU应该将以下已设置的预分频器用于定时器,而不用于WatchDogTimer(WDT)。通过将位5 T0CS清零来选择定时器模式
(OPTION_REG <5>)
现在,位2-0用于设置定时器的预分频器值。如上表所示,将预分频器值设置为64,必须将这些位设置为101。
接下来,让我们看一下与Timer0相关的寄存器
设置后,定时器将开始递增,并在达到256值后溢出,以在该点使能定时器中断,必须将寄存器TMR0IE设置为高电平。由于定时器0本身是外设,我们必须通过使PEIE = 1来使能外设中断。最后,我们必须启用全局中断,以便在任何操作期间都会向MCU通知中断,这是通过使GIE = 1来完成的。
延迟=((256-REG_val)*(Prescal * 4))/ Fosc
上面的公式用于计算Delay的值。
哪里
REG_val = 100;
预分频= 64
Fosc = 20000000
经计算得出,
延迟= 0.0019968s
下一行是设置I / O端口。
/ ***** I / O的端口配置****** / TRISB0 = 1; //指示MCU PORTB引脚0用作按钮1的输入。TRISB1 = 1; //指示MCU:PORTB引脚1用作按钮1的输入。TRISD = 0x00; //指示MCU PORT D上的所有引脚都输出PORTD = 0x00; //将所有引脚初始化为0 / *********** ______ *********** /
这与我们之前的教程相同,因为我们使用的是相同的硬件。除了我们添加了另一个按钮作为输入。这是通过TRISB1 = 1行完成的。
接下来,在无限 while 循环内部,我们有两个代码块。一种用于从用户处获取定时器输入,另一种用于在LED上执行延迟序列。我已经通过对每一行使用注释来解释了它们。
while(1){count = 0; //在主循环中不要运行计时器// *******如果(RB0 == 0 && flag == 0)//从用户那里获得数字延迟**** //////输入给定{get_scnds + = 1; // get_scnds = get_scnds + http:// Increment variable flag = 1; } if(RB0 == 1)//防止连续递增flag = 0; / *********** ______ *********** /
每次用户按下按钮1时,都会递增一个名为get_scnds的变量。标志(软件定义)变量用于保持递增过程,直到用户从按钮上移开手指为止。
// *******延迟执行序列**** //////而(RB1 == 0){PORTD = 0b00000001 <
如果按下两个按钮,则下一个块开始生效。由于用户已经使用按钮1定义了所需的时间延迟,并且已将其保存在变量get_scnds中。我们使用一个名为hscnd的变量,该变量由ISR(中断服务程序)控制。
在中断服务程序的中断,将被每次定时器0溢出调用。让我们看看下一个块中ISR如何控制它,就像我们想在每次按下按钮时将延迟时间增加半秒(0.5s),然后需要每半秒增加变量 hscnd一样 。由于我们已将计时器编程为每0.0019968s(〜2ms)溢出一次,因此计数半秒 计数 变量应为250,因为250 * 2ms = 0.5秒。因此,当count变为250(250 * 2ms = 0.5秒)时,意味着它已经过半秒,因此我们将 hscnd 递增1并将count初始化为零。
无效中断timer_isr(){if(TMR0IF == 1)//由于定时器溢出{TMR0 = 100; //加载定时器值TMR0IF = 0; //清除计时器中断标志计数++; } if(count == 250){hscnd + = 1; // hscnd将每半秒计数= 0递增一次;}}
因此,我们使用此值并将其与 hscnd 进行比较,并根据用户定义的时间对LED进行移位。它也与上一教程非常相似。
就是这样,我们对我们的程序有了理解并可以正常工作。
电路图和Proteus仿真:
通常,首先使用Proteus验证输出,我在这里链接了Proteus的原理图文件。
在我们之前的LED板上添加一个按钮,我们的硬件就可以使用了。它看起来应该像这样:
连接完成后,上传代码并验证输出。如果您有任何问题,请使用评论部分。另外,请查看下面的视频以了解整个过程。