2016-05-30 22:45:39 zhangren_ham 阅读数 2332
  • 单片机有很多-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2473 人正在学习 去看看 朱有鹏

单片机软件定时器的使用方法

特别声明:文章是原创但是本文讲述的思想是在国外的开源代码中借鉴的

初学者在编写单片机程序时经常会用到延时函数,但是当系统逐步复杂以后(没有复杂到使用操作系统)延时会因为延时降低MCU的利用率,更严重的会影响系统中的“并行”操作例如一个既有按键又有蜂鸣器的系统中,如果要求按下按键发出不同的声音,每次发声时间在1秒-2秒之间, 如果用延时来做代码很简单:

//蜂鸣器发出“哔-哔-哔”声音时间约1s
void BeepFuction(void)
{
  unsigned char i;
  for(i=0;i<3;i=++)
  {
    BeepEn(); //开启蜂鸣器
    Delayms(220);//延时220ms
    BeepDis();//关闭蜂鸣器  
    Delayms(110);//延时110ms  
  }
}

当这段代码执行时MCU不可能同时处理按键检查程序因为它大部分时间在执行Delayms()函数中的nop指令,这样就不可能去执行检查按键了(不使用中断时),如果把程序改成流程形式的写法则结果会大为不同,下面先介绍一下基本原理。

我们都知道一般的定时器为16位或8位循环计数,例如对于16位的计数器当计数器数值从0增加到65535时再加一就会回到0那么我们来比较下面两种情况(不考虑计数器在记录当前时刻T后再次回到或超过T这种情况我暂且称它为“压圈”):
情况1:
T1时刻计数器数值为300
T2时刻计数器数值为400
则T1时刻到T2为100个计数单位。
这段时间差也为100个计数单位。
情况2:
T1时刻计数器数值为65535
T2时刻计数器数值为99
则T1到T2 可以算出为65535到0的1个计数单位再加上 0到99的99个计数单位总共为100个计数单位。
所以时间差还是100个计数单位。
在C语言中如果使用两个无符号数作减法会得到如下结果:99-65535=100,这个很好理解就和10进制的借位一样只不过借位后不用管高位了也就相当于99+65536-65535结果是100了,当然这些前提条件都是计数器不会出现“压圈”。
有了上面对定时器的了解就可以从新写这个Beep函数了

//蜂鸣器发出“哔-哔-哔”声音时间约1s
bit BeepFlag = 0;//蜂鸣流程忙标志位
bit BeepCtrl = 0;//蜂鸣器流程控制标志位
void BeepProc(void)
{
  static unsigned int BeepTimer;
  static unsigned char BeepStatus = 0;
  static unsigned char i;
  switch(BeepStatus)
  {
    case 0://
       if(BeepCtrl)
       {
        i = 3;//蜂鸣次数
        BeepFlag = 1;//置位忙标志位
        BeepCtrl = 0;//清除控制标志位
        BeepTimer = TIMER;//这里TIMER为系统定时器计数时钟为1ms
        BeepEn(); //开启蜂鸣器
        BeepStatus = 1;//进入下一个状态
       }
    break;
    case 1://蜂鸣状态
       if(TIMER-BeepTimer>220)//220ms
       {
         BeepDis(); //关闭蜂鸣器
         BeepTimer = TIMER;//记录时刻
         BeepStatus = 2;//进入下一个状态
       }
    break;
    case 2://停止蜂鸣状态
       if(TIMER-BeepTimer>110)//110ms
       {
         if(i!=0)
         {
           i--;
           BeepTimer = TIMER;//记录时刻
           BeepEn(); //开启蜂鸣器
           BeepStatus = 2;//回到蜂鸣状态
         }
         else
         {
           BeepStatus = 0;//回到初始状态
           BeepFlag = 0;//清除忙标志位
         }
       }    
    break;
    default:
      BeepFlag = 0;//清除忙标志位
      BeepStatus = 0;//回到初始状态
    break;
  }
}

用这样的方法实现的蜂鸣程序在使用时也有不同的地方,因为使用的switch状态所有在主循环中要一直调用:

void main()
{
  SystemInitial();//系统初始化
  ...............

  //主循环
  while(1)
  {
     Fun1Proc();//功能1流程
     Fun2Proc();//功能2流程
     ....     
     BeepProc();//蜂鸣流程
     ....
  }

}

在别的函数中需要使蜂鸣器工作时只需要下面代码即可:

if(!BeepFlag)//检查是否忙
 BeepCtrl = 1;//启动蜂鸣器

用这种方法能充分利用MCU,在蜂鸣器发声或发声间隔的等待时间MCU可以处理别的函数,但是还要有几点需要注意

第一,主循环while(1)的循环周期最好小于定时器计数时钟周期
第二,主循环中尽量不要使用硬延时Delayms
第三,代码中如果存在多个地方需要控制一个流程时一定要先读取标志位再控制

2015-12-12 16:14:06 snyanglq 阅读数 3359
  • 单片机有很多-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2473 人正在学习 去看看 朱有鹏
一、定时器介绍
51单片机有2个16位的定时/计数器T0、T1,52单片机有3个16位定时/计数器T0、T1、T2。
定时/计数器顾名思义就是有定时和计数的功能,下面主要介绍定时器,对计数器不进行论述单片机的定时功能在日常的单片机编程中应用十分的广泛,同时定时中断也是单片机常用的中断之一,所以学好定时控制也是非常重要的,如果不理解中断的概念请查我的文章嵌入式中断的理解 http://blog.csdn.net/snyanglq/article/details/50238205

二、单片机的内部框图与定时器工作原理分析


大家请看上面的图,这是定时器T0和T1内部结构图,图中用不同颜色的线条标注了相互之间的工作关系,
从上面的图中红线可以看出由TMOD选择由那个定时器工作,工作于什么方式;
从上面的图中蓝线可以看出由TCON决定定时器是否启动;
从上面的图中黄线可以看出外部技术输入由TH和TL进行累计;
从上面的图中紫线可以看出当TH和TL计数溢出时会向TCON进行申请报告;
从上面的图中绿线可以看出所有的定时中断都由TCON向CPU进行中断申请;
从上面的图中黑线可以看出外部中断直接向CPU进行中断申请;

通过上面的图大家已经对CPU的定时中断内部结构图有一个清晰的理解,下面再看看单片机的工作原理图


从上图可以看出当作为定时器使用时,计时脉冲来自于外部晶振振荡频率后的12分频,当设置为计数器时则是直接外部脉冲作为计时脉冲,控制选择定时还是计数由C/T位控制,而控制定时器是否进行计时则由B控制,B是一个与门的输出端,与门的工作原理是有0出0,全1出1,可见定时器要启动TRx和A必须要为1,而A是或门的输出,或门的工作原理是有1出1,全0出0,所以需要输入端要任意一位为1方可,当所有条件符合,TH和TL开始定时计算,当两个计数器计数满溢出时,会令TF置位,最后由TF向CPU发出中断请求,处理中断程序,注意此时,单片机的计数器会清零,这是计数器内部设计决定的,所以定时需要再次使用时需要重载计数器初值

三、寄存器介绍


TCON:
TF0和TF1:定时器/计数器溢出标志位。

                    当定时器/计数器0(或定时器/计数器1)溢出时,由硬件自动使TF0(或TF1)置1,并向CPU申请中断。

                    CPU响应中断后,自动对TF1清零。TF1也可以用软件清零。 

TR0和TR1:定时器/计数起运行控制位。  
                     TR0(或TR1)=0,停止定时器/计数器0(或定时器/计数器1)工作。
                     TR0(或TR1)=1,启动定时器/计数器0(或定时器/计数器1)工作。

外部中断暂时不介绍

TMOD:
GATE:门控位。  
             GATE=0,只要用软件使TR0(或TR1)置1就能启动定时器/计数器0(或定时器/计数器1);
             GATE=1,只有在(或)引脚为高电平的情况下,且由软件使TR0(或TR1)置1时,才能启动定时器/计数器0(或定时器/计数器1)工作。不管GATE处于什么状态,只要TR0(或TR1)=0定时器/计数器便停止工作。
C/T: 定时器/计数器工作方式选择位。
           C/T =0,为定时工作方式;
           C/T=1,为计数工作方式。  
M0、M1:工作方式选择位,确定4种工作方式。


IE:
EA = 1, CPU开放总中断;     
ET0 = 1,允许T0中断;
TR TL:定时器寄存器
注意:由于reg52.h的库定义了大部分的寄存器所以可以直接给TCON TMOD IE赋值操作或者是单独寄存器操作。


四、中断程序执行方法和定时计数公式
中断程序执行方法有直接中断和查询两种方法,查询即查询TF位是否置位为1
定时初值计算公式为:
定时时间=(计数最大值 – 计数初值)×机器周期 
机器周期T = 12/晶振频率


或者是用取模的方式
TH0=(计数最大值 – 定时时间)/256;
TL0=(计数最大值 – 定时时间)%256;


首先解析为什么要设定初值?
当设定定时器为16为计数器时,则计数最大值为2^16 = 65536;
TH和TL分别为8位,即TL每逢256进一位,所以除掉256就能看出TH进了多少位,取模则是余下多少位
举个例子,2位计数器最大计数为4,要将初值6分到两个2位计数器,由于低位逢四进1所以剩下2

即TL = 2然后高位是1即TH=1,按照上面的方法:2^4 = 16,2^2 = 4; TH = (16-(16-6))/4=1(整型不算小数);

TL = (16-(16-6))%4=2;可见结果是一样的。


五、程序设计
例:利用数码管静态显示0到9,每1S钟加一,到9再加1回到0,使用中断程序实现。


分析:一秒钟加一即需要定时1S后才能动作,由于单片机最大定时也只是65536ms,

所以我们需要累加,为了计算方便我们累加20次定时50ms

#include<reg52.h>
#define uchar unsigned char 
#define uint unsigned int 

uchar code smg_du[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00};
uchar num,a;

void main()
{
 TMOD=0x01;             //设置定时器0为工作方式1[直接赋值]
 TH0=(65536-50000)/256; //利用取模方式设置初值
 TL0=(65536-50000)%256;
 EA=1;      //打开总中断开关[直接操作寄存器]
 ET0=1;     //打开定时器中断
 a = 0;     //初始化a、num为了程序严谨,也可以去掉
 num = 0;
 TR0=1;     //启动定时器
 
 while(1)
 {
  if(a==20)
  {
   a=0; 
   P1=smg_du[num];
   num++;
   if(num==10)
   {
     num=0;
   }
  }
 }
}

void timer0 () interrupt 1  
{
 TH0=(65536-50000)/256; //初值重载,否则下次定时就是65535us而不是50ms
 TL0=(65536-50000)%256;
 a++;                   //50ms中断累加
}


2005-02-16 17:31:00 czeus 阅读数 1661
  • 单片机有很多-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2473 人正在学习 去看看 朱有鹏
单片机定时中断的精确定时编程方法种种

作 者:■ 南京农业大学 蹇兴亮

引 言

  MCS-51单片机的中断响应延迟时间,取决于其它中断服务程序是否在进行,或取决于正在执行的是什么样的指令。单中断系统中的中断响应时间为3~8个机器周期[1]。无论是哪一种原因引起的误差,在精确定时的应用场合,必须考虑它们的影响,以确保精确的定时控制。根据定时中断的不同应用情况,应选择不同的精确定时编程方法。

  文中以定时器T1工作在定时方式1为例,晶振频率为12MHz 。

1 方法1

  在定时器溢出中断得到响应时,停止定时器计数,读出计数值(反映了中断响应的延迟时间),根据此计数值算出到下一次中断时,需多长时间,由此来重装载和启动定时器。例如定时周期为1ms,则通常定时器重装载值为-1000(0FC18H)。下面的程序在计算每个定时周期的精确重装载值时,考虑了由停止计数(CLR TR1)到重新启动计数(SETB TR1)之间的7个机器周期时间。程序中#LOW(-1000+7)和#HIGH(-1000+7)是汇编符号,分别表示-1000+7=0FC1FH这个立即数的低位字节(1FH)和高位字节(0FCH)。
……
CLR EA ;禁止所有中断
CLR TR1 ;停止定时器T1
MOV A,#LOW(-1000+7) ;期望数的低位字节
ADD A,TL1 ;进行修正
MOV TL1,A ;重装载低位字节
MOV A,#HIGH(-1000+7) ;对高位字节处理
ADDC A,TH1
MOV TH1,A
SETB TR1 ;重启动定时器
SETB EA ;重开中断
……

  此方法适用于各种原因造成的定时误差的情况,为通用方法。

2 方法2

  假如定时周期为10ms,通常定时器重装载值为0D8F0H,中断子程序如下[2]:
ORL TL1,#0F0H
MOV TH1,#0D8H
……

  这里用ORL TL1,#0F0H代替MOV TL1,#0F0H 可提高定时精度。此方法只适用于重装载值低位字节的低4位为零,且中断响应的延迟时间小于16个机器周期的情况。类似的定时器重装载值有0FFF0H,0FFE0H等。

3 方法3

  假如定时周期为1ms,通常定时器重装载值为0FC18H,中断子程序如下:
MOV A,#LOW(-1000+4) ;期望数的低位字节
ADD A,TL1
MOV TL1,A
MOV A,#HIGH(-1000+4) ;对高位字节处理
ADDC A,TH1
MOV TH1,A
DEC TL1 ;恢复提前了的2个机器周期
……

  这种方法中不停止定时器计数过程,若在执行指令ADDC A,TH1 或MOV TH1,A时,恰好产生TL1溢出向TH1进位的情况,则TH1的值就不对了,会产生更大的误差。为此,程序段开头为重装载值加4,若有溢出进位,则可提前发生,其中2个机器周期是考虑到为TL1重装载占用的时间。

  此方法适用于系统中无其它更高优先级中断源的情况。若类似方法1,在程序段开头和结尾分别加上禁止所有中断(CLR EA)和开中断(SETB EA)指令,则将适用于所有情况。

4 方法4

  假如定时周期不确定,只知道定时器重装载值存放在寄存器R3、R2中,中断子程序如下:
MOV A,#05H ;3个机器周期装载TL1,2个周期提前
ADD A,TL1
ADD A,R2
MOV TL1,A
MOV A,R3 ;处理高位字节
ADDC A,TH1
MOV TH1,A
DEC TL1 ;恢复提前了的2个机器周期
……

  此法适用于定时周期不确定的情况,其它同方法3。

5 方法5

  当定时中断发生的位置可预知时,通常出现在主程序的AJMP $ (或SJMP $)等待指令处,中断延迟时间为3个或4个机器周期。取固定值4可简化补偿程序。以定时周期1ms为例,中断子程序如下:
ORG 001BH
MOV TL1,#LOW(-1000+4)
MOV TH1,#HIGH(-1000+4)
……

  此方法适用于定时中断总发生在同一条指令位置,且无其它中断源的情况。
结 语

  上述5种方法误差均不超过1个机器周期,其中方法1、3、4较为通用,适用于任何情况,但程序较长;方法2、5简单,但必须注意满足对应条件,才能使用。当然,也还有其它方法[3],但比较烦琐,并不理想,这里不一一介绍。

                 参考文献
1 孙涵芳,徐爱卿. MCS-51/96系列单片机原理及应用[M]. 北京:北京航空航天大学出版社,1988
2 周航慈. 单片机应用程序设计技术. [M]. 修订版. 北京:北京:北京航空航天大学出版社,2002
3 聂毅. 单片机定时器中断时间误差的分析及补偿[J]. 微计算机信息,2002,18(4):37~38

蹇兴亮:讲师,主要从事测试计量技术及单片机的教学。
                               (收稿日期:2004-03-30)
2016-03-15 09:02:22 a1314521531 阅读数 1272
  • 单片机有很多-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2473 人正在学习 去看看 朱有鹏

单片机——定时器

 

宗旨:技术的学习是有限的,分享的精神是无限的。

 

1、定时器和计数器

时钟周期:单片机时序中的最小单位,集体计算的放法就是时钟源分之一

机器周期:单片机完成一个操作的最短时间,=12个时钟周期

定时器:打开定时器后,定时器“存储寄存器”的值经过一个机器周期自动加1,也就是说,机器周期是定时器的计数周期。

 

2、定时器的寄存器

 

 

TMOD —— 工作模式选择寄存器——常用模式1和模式2.

TCON —— 控制寄存器(TRn定时器开关)

TH/TL —— 定时计数器

 

3、定时器应用

第一步:设置特殊功能寄存器 TMOD,配置好工作模式。
第二步:设置计数寄存器TH0TL0的初值。
第三步:设置TCON,通过TR0 1来让定时器开始计数。
第四步:判断TCON 寄存器的TF0 位,监测定时器溢出情况。

写程序之前,我们要先来学会计算如何用定时器定时时间。我们的晶振是 11.0592M,时钟周期就是 1/11059200,机器周期是12/11059200,时器定时值最大也就是 71ms 左右。

(65536-N)*12/11.0592M = time ---计算出N转换成十六进制

1ms的例子:

TMOD = 0X01;

TH0 = 0XFC;

TL0 = 0X67;

TR0 = 1; 

#include<reg52.h>

typedef unsigned char uchar;

sbit LED = P0^0;

int  main(void)
{
  uchar cnt = 0;  //定义一个计数变量,记录T0溢出次数

  TMOD = 0x01;  //设置T0为模式1
  TH0 = 0xFC;  //为T0赋初值0xB800
  TL0 = 0x67;
  TR0 = 1;     //启动T0

  while (1)
  {
    if (1 == TF0)        //判断T0是否溢出
    {
      TF0 = 0;         //T0溢出后,清零中断标志
      TH0 = 0xFC;      //并重新赋初值
      TL0 = 0x67;
      cnt++;           //计数值自加1
      if (cnt >= 1000)   //判断T0溢出是否达到50次
      {
        cnt = 0;     //达到50次后计数值清零
        LED = ~LED;  //LED取反:0-->1、1-->0
      }
    }
  }

  return 0;
}

 

4、数码管

 

共阴与共阳数码管 ——8个LED灯

位选——控制选择多个数码管中的哪一个

段选——选择数码管显示的值

 

// 秒定时器:
#include<reg52.h>

typedef unsigned int uint;
typedef unsigned char uchar;

uchar code count[] =
{
  0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
  0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

int main(void)
{
  uchar cnt = 0;  //记录T0中断次数
  uchar sec = 0;  //记录经过的秒数

  TMOD = 0x01;  //设置T0为模式1
  TH0 = 0xFC;  //为T0赋初值0xB800
  TL0 = 0x67;
  TR0 = 1;     //启动T0

  while (1)
  {
    if (TF0 == 1)        //判断T0是否溢出
    {
      TF0 = 0;         //T0溢出后,清零中断标志
      TH0 = 0xFC;      //并重新赋初值
      TL0 = 0x67;
      cnt++;           //计数值自加1
      if (cnt >= 1000)   //判断T0溢出是否达到1000次
      {
        cnt = 0;            //达到1000次后计数值清零
        P0 = count[sec];  //当前秒数对应的真值表中的值送到P0口
        sec++;              //秒数记录自加1
        if (sec >= 16)      //当秒数超过0x0F(15)后,重新从0开始
        {
          sec = 0;
        }
      }
    }
  }

  return 0;
}

 

 

 

2018-05-19 00:38:28 qrsxtkf 阅读数 1217
  • 单片机有很多-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2473 人正在学习 去看看 朱有鹏

单片机的定时器是作为片外资源,开启定时器需要设置相关的寄存器,当然,这些寄存器是可以位寻址的,怎么位寻址?在头文件中reg52.h中已经预定义好了。不用担心,你只需要学会设置就好了:

TMOD=0X01;    //定时器0工作在方式1(16位计数器)

TH0=0xfe;

TL0=0x33;

EA=1;

ET0=1;

TR0=1;

以上6条语句,已经将定时器设置好了,现在协议个led的项目来时间一下:

#include<reg52.h>

#define uint unsigned int
#define uchar unsigned char
uint count=0;     //5000微秒的次数,用于在中断中加1,count有多少,定时count*5ms
sbit led=P1^0;


int main()

{
TMOD=0X01;    //定时器0工作在方式1(16位计数器)
TH0=0xfe;    //赋初值
//TH0=(65536-5000)/256;   //假设赋初值为60536,从60536开始,每隔1微秒,计数器加1,(高8位),计满为              5000微秒(5ms),之后产生中断
TL0=0x33;
////TH0=(65536-5000)%256;   //赋初值为60536,从60536开始,每隔1微秒,计数器加1,(低8位)
EA=1;     //开总中断
ET0=1;    //开T0中断
TR0=1;    //启动T0开始计数

while(1)//大循环,循环执行里面的程序
{
if(count==20)//判断:如果定时20*0.5=10ms,10ms亮灭一次led
{
count=0;  //重新赋成0
led=~led; //led状态取反一次
}
}

void timer0() interrupt 1  //中断函数
{
TH0=0xfe;    //,重新赋值
//TH0=(65536-5000)/256;   //赋初值为60536,从60536开始,每隔1微秒,计数器加1,(高8位)
TL0=0x33;
////TH0=(65536-5000)%256;   //赋初值为60536,从60536开始,每隔1微秒,计数器加1,(低8位)
count++;//0.5ms的次数加1

}

注意:这里使用到了中断,有时无需使用到中断:

#include<reg52.h>

#define uint unsigned int
#define uchar unsigned char
uint count=0;     //5000微秒的次数,用于在中断中加1,count有多少,定时count*5ms
sbit led=P1^0;


int main()

{
TMOD=0X01;    //定时器0工作在方式1(16位计数器)
TH0=0xfe;    //赋初值
//TH0=(65536-5000)/256;   //假设赋初值为60536,从60536开始,每隔1微秒,计数器加1,(高8位),计满为              5000微秒(5ms),之后产生中断
TL0=0x33;
////TH0=(65536-5000)%256;   //赋初值为60536,从60536开始,每隔1微秒,计数器加1,(低8位)
EA=1;     //开总中断
ET0=1;    //开T0中断
TR0=1;    //启动T0开始计数

while(1)//大循环,循环执行里面的程序

{

          if(TF0==1)//每次产生中断的时候,TFx(0或1)都会置位

           {

                 TF0=0;

                 count++;

           }

          if(count==20)//判断:如果定时20*0.5=10ms,10ms亮灭一次led

{
count=0;  //重新赋成0
led=~led; //led状态取反一次
}
}




没有更多推荐了,返回首页