前言
最近在听江科大51单片机的课程,刚开始刷课嗖嗖的,到了定时器/计数器,强度陡增
开始听不懂了,所以开始各种摸索、试图理解
感觉这部分也挺难挺重要,虽然电脑上在onenote上记了一些笔记
但是感觉理解的还是很牵强,所以就打算写一篇博客试图总结一下
尽管可能漏洞百出罢了。
参考的视频、资料有:
b站江科大51单片机视频:
【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】 https://www.bilibili.com/video/BV1Mb411e7re/?p=18&share_source=copy_web&vd_source=8256f7cbd3f693ab14fe6d725298a46f
b站其他一个up的视频:
【【51单片机入门教程】中断优先级及嵌套,包括对按键去抖的处理】 https://www.bilibili.com/video/BV1pc411P75U/?share_source=copy_web&vd_source=8256f7cbd3f693ab14fe6d725298a46f
这个up的这个系列的视频都很赞,今天就主要看他这个系列的几个相关视频了
还有就是STC89C52的芯片手册
定时器简介
STC89C52芯片有三个计时器,分别为0,1,2(但是我不太清楚为什么要这么多)
定时器由三部分组成,分别是时钟,计数单元和中断(从左到右分别对应下图三个框)
51单片机定时器有四种工作模式,如下图,其中第二第三种最为常见
定时器相关寄存器
单片机控制寄存器从而控制其内部电路通断,从而实现相应功能
定时器控制寄存器
TR0,TR1:控制定时器T0,T1是否计数,由软件置位和清零,详细控制规则不表
TF0,TF1:对应的T0,T1计数器溢出后自动置1,置1后CPU响应中断,然后置0,也可由程序查询清零,常见的清零代码如下:while(TF0==0);(但是说实话我没搞懂查询清零和这段代码的意思)
IE0,IE1:控制外部中断的请求
IT0,IT1:控制外部中断的触发方式,有两种方式:低电平触发和下降沿触发,在中断系统再慢慢说这个
定时器模式寄存器
以T0为例,M0,M1组合起来通过二进制对应四种计数方式
C/T的0/1对应定时/计数功能
GATE的功能见上图左下角。
定时器置工作模式1
该工作模式下有两个计数器,分别是TH0和TL0,前者为高八位,后者为低八位
每过一个机器周期或系统时间,计数器加一,所以该模式下计数器经过65536个周期后溢出
溢出就会导致TF0/1置零,从而导致一次中断
由于一个周期对应1us,所以65536us即65.536ms后溢出
因为无法改变该工作模式下计数器在65536时发生溢出
所以需要通过更改计数器的初始值的方式,来更改发生溢出的时间
初值的计算方法如下
但是如果所需的溢出时长超过了65536us,那么就需要在程序中设置循环
比如1s=50ms*20,循环20次即可
代码实现
定时器工作模式2
与工作模式1不同的地方在于,模式2的技术长度为256,仅需8位就能表示
模式1的TH0,TL0两个加起来总共16位,所以换到模式2时,就有一个可以不用干活
我们让高位不用干活,但是赋初值的时候,高八位和低八位都进行赋值
这样就可以在第八位溢出的时候,将高八位的值再次赋值给低八位
这样就不用重置初值了。有部分代码如下
中断系统
中断系统触发方式
中断寄存器
因为TCON在上面介绍过,而且目前就用过几个,所以这里不一一介绍,只介绍我搞懂的几个(哈哈哈)
IE寄存器
这张图讲的很清楚了,首先EA是总的中断允许位,别的分别对应图中文字。只有EA和所想要的位均为1时,这个地方才能允许向CPU提供中断信号。
IP寄存器
IP寄存器用于修改优先级,从而确保程序在运行时更能符合恰当的工作顺序和逻辑
内部中断的配置
配置好中断后,还需要写好相应的中断程序
要注意配置方法有两种,一种是默认0,把需要变为1的引脚单独写出
一种是转化为16进制,从而对整个寄存器赋值
不会在函数体中看到有语句调用中断程序
而是如果一旦满足触发中断的条件
程序就自动停止执行当前任务,转而执行中断程序
有代码如下
外部中断的配置
这时就需要对EX1,EX2赋值,并写出相应的中断程序。但是要注意外部中断的触发条件
请看,INT1和INT2的触发条件有两种:低电平和下降沿
低电平触发:低电平,即为0。按键按下,对应的引脚即为低电平
下降沿触发如下图,与其对应的是上升沿
对于低电平触发,要慎用。因为系统检测低电平的频率很快
按一次按键,很有可能在很短时间内就已经输出了很多个低电平信号
这样就会导致中断程序运行好多次,这样可能会出问题,所以要慎用。
下降沿触发:一个下降沿只触发一次。下面是数据手册对这两种触发方式的表述
低电平触发时,需要手动使IE0=0。但是我没在数据手册上找到依据
低电平触发与下降沿触发的选择:
有示例代码如下
中断优先级嵌套
有时候需要更改一些程序的运行顺序,从而使得最终产品的运行达到自己想要的效果,所以有了优先级的修改这么一说
优先级的修改,需要用到IP寄存器
修改优先级后,高、低优先级内,还会按照自然优先级排序
如果想要避免优先级的嵌套,有两种方式:
设置优先级,或在中断服务程序中关闭其他中断允许
有示例代码如下
结语
到这里才发现单片机是大坑
前两天点灯点的多高兴,现在就有多头疼
没办法咯,冲冲冲
案例一:按键控制流水灯
先上代码
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>
unsigned char Keynum,LEDmode;
void main(){
P2=0xFE;
Timer0_Init();
while(1){
Keynum=Key();
if(Keynum){
if(Keynum==1){
LEDmode++;
if(LEDmode>=2) LEDmode=0;
}
}
}
}
void Timer0_Rountine() interrupt 1{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=500){
T0Count=0;
if(LEDmode==0){
P2=_crol_(P2,1);
}
if(LEDmode==1){
P2=_cror_(P2,1);
}
}
}
以下为key.cpp
#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;}
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;
}
以下为Timer0.cpp
#include <REGX52.H>
void Timer0_Init(){
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
插一句,可位寻址,也就是说寄存器中的每一位可以单独赋值
不可位寻址,也就是说寄存器只能整体赋值
由上述工作原理图和表格可知,配置寄存器的代码如下
4为配置TMOD,定时器/计数器模式寄存器,不可位寻址
5-9行为配置TCON,定时器/计数器控制寄存器,可位寻址
10,11行为配置IE,中断允许寄存器
12行为配置IP,中断优先级寄存器
用下面两行代码替换掉注释掉的那一行
就可以达到不更改T1的配置,但是重新配置好T0的效果
循环左移函数:_crol_();
循环右移函数:_cror_();
上述两者包含在INTRINS.H头文件中