晶振,也就是晶体振荡器,主要作用是产生稳定的振动频率用于单片机时钟电路。
压电效应:
如图所示,晶体振荡器主要由引线构成的电极和晶体片构成。
当我们在晶体两电极外加电压后,晶体会发生形变,反过来,如果外力使得晶体变形,两极上又会产生电压。
因此当我们在晶体两端施加一个交变电压时,随着电压正负的改变,晶体交替形变,进而以一定的频率发生谐振–单频振荡(振荡频率固定不变)。
我们知道,脉冲信号是一个按一定电压幅度、一定时间间隔连续发出的信号。 而我们的晶体振荡器,既有电压幅度–晶体两端施加的电压,又有时间间隔–振荡频率 f 的倒数作为周期。因此我们可以通过我们的晶体振荡器产生一个稳定的时钟脉冲信号来为我们的单片机提供精准计时。
现在我们有了由晶体振荡器产生的精准时钟脉冲,可以给我们的单片机计时了。但对于单片机来讲,用我们所熟知的时分秒等计时单位来计时,并不合适。因此我们根据单片机的工作特性和原理,引入了时钟周期、状态周期、机器周期、指令周期几个概念来作为我们单片机的时间单位。
1、时钟周期
也叫振荡周期或者晶振周期,即晶振的单位时间发出的脉冲数。如12Mhz晶振的时钟周期为1/ f =1/12Mhz,也就是1/12微秒。
时钟周期是计算机中最基本、最小的时间单位。
在8051单片机中把一个时钟周期定义为一个节拍(用P表示),二个节拍定义为一个状态周期(用S表示)
2、、机器周期
计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。51单片机中一个机器周期等于12个时钟周期。
在标准的51单片机中,一般情况下,一个机器周期等于12个时钟周期,也就是机器周期=12*时钟周期,(上面讲到的原因)如果是12MHZ,那么机器周期=1微秒。单片机工作时,是一条一条地从RoM中取指令,然后一步一步地执行。单片机访问一次存储器的时间,称之为一个机器周期,这是一个时间基准。
机器周期不仅对于指令执行有着重要的意义,而且机器周期也是单片机定时器和计数器的时间基准。例如一个单片机选择了12MHZ晶振,那么当定时器的数值加1时,实际经过的时间就是1us,这就是单片机的定时原理。
3、指令周期
指令周期是执行一条指令所需要的时间,一般由若干个机器周期组成。指令不同,所需的机器周期数也不同。
对于一些简单的的单字节指令,在取指令周期中,指令取出到指令寄存器后,立即译码执行,不再需要其它的机器周期。
对于一些比较复杂的指令,例如转移指令、乘法指令,则需要两个或者两个以上的机器周期
在我们的单片机中,说到时间就离不开上面所提的时钟脉冲。而我们的定时计数器就是对这些脉冲进行计数,来进行时间计算的,本质上我们的定时计数器是一个加1计数器,也就是每输入一个脉冲,计数加一。
但要注意的一点是,定时器和计数器是两个不同的功能单元。
定时器是由单片机自身提供的一个非常稳定的计数器(晶振),接受的是内部脉冲;
而计数器则不同,记录的是单片机外部发生的事情,接受的是外部脉冲。
举一个计数器的例子,方便大家理解,我现在让我的计数器记录我的一个独立按键按下的次数,那它所接受的脉冲便是我们按键按下过程中产生外部脉冲。
这里需要注意的是,计数器自动加1需要检测从1到0的下降沿,因此需要两个计数周期,最高计数频率为24晶振频率。
由于不同型号的51单片机的定时器个数不同,下面以最基础的8951单片机为例进行阐述
说了这么多,那单片机具体应该怎么使用呢,别急,咱们还得从最基本的寄存器开始看起。
首先我们来看一下定时计数器的定时方式寄存器–TMOD GATE :定时操作开关控制位,当GATE=1时,INT0或INT1引脚为高电平,同时TCON中的TR0或TR1控制位为1时,计时/计数器0或1才开始工作。若GATE=0,则只要将TR0或TR1控制位设为1,计时/计数器0或1就开始工作。
C/T :定时器或计数器功能的选择位。C/T=1为计数器,通过外部引脚T0或T1输入计数脉冲。C/T=0时为定时器,由内部系统时钟提供计时工作脉冲。
M1 M0:T0、T1工作模式选择位
设置好方式,当然还有具体的定时器控制寄存器–TCON TF1:定时器T1溢出标志,可由程序查询和清零,TF1也是中断请求源,当CPU响应T1中断时由硬件清零。
TF0:定时器T0溢出标志,可由程序查询和清零,TF0也是中断请求源,当CPU响应T0中断时由硬件清零。
TR1:T1充许计数控制位,为1时充许T1计数(定时)。
TR0:T0充许计数控制位,为1时充许T0计数(定时)。
下面四个属于中断部分,这里不多做阐述。
IE1:外部中断1请示源(INT1,P3.3)标志。IE1=1,外部中断1正在向CPU请求中断,当CPU响应该中断时由硬件清“0”。
IT1:外部中断源1触发方式控制位。此位为1设置为底电平触发,为0设置为下降沿触发。
IE0:外部中断0请示源(INT0,P3.2)标志。IE0=1,外部中断1正在向CPU请求中断,当CPU响应该中断时由硬件清“0”。
IT0:外部中断源0触发方式控制位。此位为1设置为底电平触发,为0设置为下降沿触发。
1、工作模式0:
由TL0的低5位和TH0的全部8位共同构成一个13位的定时器/计数器,定时器/计数器启动后,定时或计数脉冲个数加到TL0上,从预先设置的初值(时间常数)开始累加,不断递增1,当 TL0计满后,向TH0进位,直到13位寄存器计满溢出,TH0溢出时,置位TCON中的TF0标志,向CPU发出中断请求。并且定时器/计数器硬件会自动地把13位的寄存器值清0,如果需要进一步定时/计数,需要使用相关指令重置时间常数,并把定时器/计数器的中断标记TF0置0。
2、工作模式1:最常用的定时器工作模式
模式1与模式0几乎完全相同,唯一的区别就是,模式1中的寄存器TH0和TL0共同构成的是一个16位定时器/计数器来参与操作,因此比模式0中的定时/计数范围更大
3、工作模式2: 工作方式2特别适合于用作较精确的脉冲信号发生器。
这种模式又称为自动再装入预置数模式。有时候,我们的定时/计数操作是需要多次重复定时/计数的,如果溢出时不做任何处理,那么,在第二轮定时/计数时就是从0开始定时/计数了,而这并不是我们想要的。所以,要保证每次溢出之后,再重新开始定时/计数的操作是我们想要的,那就要把预置数(时间常数)重新装入某个地方,而重新装入预置数的操作是硬件设备自动完成的,不需要人工干预,所以这种工作模式就叫自动再装入预置数方式。在工作模式2中,把自动重装入的预置数存放在定时器/计数器的寄存器的高8位中,也就是存放在TH0中,而只留下TL0参与定时/计数操作。
这个工作模式常用于波特率发生器(串口通信),T1工作在串口模式2;用于这种方式时,定时器就是为了提供一个时间基准;计数溢出之后,不需要做太多的事情,只做一件事就可以,就是重新装入预置数,再开始重新计数,而且中间不需要任何延时。
4、工作模式3:
方式3只适用于定时/计数器T0,定时器T1处于方式3时相当于TR1=0,停止计数由于定时器/计数器T1没有工作模式3,如果把定时器/计数器T0设置为工作模式3,那么TL0和TH0将被分割成两个相互独立的8位定时器/计数器。
1、计数器计数初值确定
计数初值C=模-X 注:模为选用定时方式的最大数值,如方式一的模为65536
2、定时器定时初值
定时初值C=[t/MC] 注: MC-- 机器周期,t预定时间
3、定时器不同方式初值
工作方式0:13位定时器/计数器工作模式,最多可计数2的13次方次,即:8192次
工作方式1:16位定时器/计数器工作模式,最多可计数2的16次方次,即:65536次
工作方式2:8位定时器/计数器工作模式,最多可计数2的8次方次,即:256次,
工作方式3:8位定时器/计数器工作模式,最多可计数2的8次方次,即:256次
4、定时计数器初值载入寄存器 TH0=(65536-9216)/256
TL0=(65536-9216)%6
定时:定时计数器作为定时器使用,配置步骤如下:
1.模式设置,配置TMOD寄存器
2.定时器初值设置 假设10ms中断
3.开定时器中断
4.开总中断
5.打开定时器
#include<reg51.h> sbit led=P1^0; unsigned int flag; void main() { TMOD=0x01;//1.模式设置,00000001,采用的是定时器0,工作与式1(M1=0,M0=1)。 TH0=(65536-10000)/256; //2.定时器设置,每隔10毫秒发起一次中断。 TL0=(65536-10000)%256; ET0=1; //3.开定时器0中断 EA = 1; //4.开总中断 TR0=1; //5.打开定时器 while(1) { if(flag==100) { led=~led; flag=0; } } } void TIM0() interrupt 1 //中断服务程序 { TH0=(65536-10000)/256; //进入中断要重新设置定时器处置,要注意。 TL0=(65536-10000)%256; flag++; }计数:定时计数器作为计数器使用,配置步骤如下:
1.模式设置,配置TMOD寄存器。
2.开计数器中断
3.开总中断
4.打开计数器
通过这简单的四步,我们就打开了一个计数器,可以对P3.4或者P3.5进行下降沿的脉冲计数,这里有一点要注意就是计数器可以不开中断,这样溢出时只是不会进去中断服务程序。
计数器示例程序
#include<reg52.h> sbit led=P1^0; sbit s=P3^4; unsigned int count; void main() { TMOD=0x05; //1.模式设置,00000101,采用的是计数器0,工作模式1(M1=0,M0=1)。 TH0=0; //计数器清零 TL0=0; ET0=1; //2.开计数器0中断 EA=1; //3.开总中断 TR0=1; //4.打开计数器 led=1; while(1) { count=(TH0<<8)|TL0; if((count*10000)==50000)//按5下按键led状态取反 { led=0; TH0=0XFF; TL0=0XFF; //人为的让计数器进入中断 } } } void TIM0() interrupt 1 //中断服务程序 { led=1; TH0=0; TL0=0; }