搜索
bottom↓
回复: 25

AVR定时/计数器应用设计

[复制链接]

出0入0汤圆

发表于 2005-2-18 00:56:23 | 显示全部楼层 |阅读模式
定时/计数器(Timer/counter)是单片机芯片中最基本的外围接口,它的用途非常广泛,常用于测量时间、速度、频率、脉宽、提供定时脉冲信号等。相对于一般8位单片机而言,AVR不仅配备了更多的定时/计数器接口,而且还是增强型的,功能非常强大。ATmega128一共配置了2个8位和2个16位,共4个定时/计数器,本小节重点对它的一些增强功能的应用做基本的介绍。



5.9.1 预分频器

  定时/计数器最基本的功能就是对脉冲信号计数,当计数器计满后(8位为255,16位为65535),再来一个脉冲它就翻转到0,并产生中断信号。同其他单片机类似,AVR的定时/计数器的计数脉冲可以来自外部的引脚,也可以由从内部系统时钟获得;但AVR的定时/计数器在内部系统时钟和计数单元之间增加了一个预分频器,利用预分频器,定时/计数器可以从内部系统时钟获得不同频率的信号。表5-1为系统时钟为4MHz使用定时/计数器0的最高计时精度和时宽范围。



表5-1 T/C0计时精度和时宽(系统时钟4MHz)

分频系数  计时频率  最高计时精度(TCNT0=255)  最宽时宽(TCNT0=0)

1          4MHz      0.25us                    64us

8          500KHz    2us                       512us

32         125KHz    8us                       2.048ms

64         62.5KHz   16us                      4.096ms

128        31.25KHz  32us                      8.192ms

256        15.625KHz 64us                      16.384ms

1024       3906.25Hz 256us                     65.536ms



  从表中看出,在系统时钟为4MHz时,8位的T/C0最高计时精度为0.25us,最长的时宽可达到65.536ms。而使用16位的定时/计数器时,不需要辅助的软件计数器,就可以非常方便的设计一个时间长达16.777216秒(精度为256us)的定时器,这对于其它的8位单片机是做不到的。

  AVR单片机的每一个定时/计数器都配备独立的、多达10位的预分频器,由软件设定分频系数,与8/16位定时/计数器配合,可以提供多种档次的定时时间。使用时可选取最接近的定时档次,即选8/16位定时/计数器与分频系数的最优组合,减少了定时误差。所以,AVR定时/计数器的显著特点之一是:高精度和宽时范围,使得用户应用起来更加灵活和方便。此外,AVR的USART、SPI、I2C、WDT等都不占用这些定时/计数器。



5.9.2 输入捕捉功能

  ATmega128的两个16位定时/计数器(T/C1、T/C3)具有输入捕捉功能,它是AVR定时/计数器的又一个显著的特点。其基本作用是当一个事件发生时,立即将定时/计数器的值锁定在输入捕捉寄存器中(定时/计数器保持继续运行)。利用输入捕捉功能,可以对一个事件从发生到结束的时间进行更加精确,如下面的示例中精确测量一个脉冲的宽度。

  测量一个脉冲的宽度,就是测量脉冲上升沿到下降之间的时间。不使用输入捕捉功能,一般情况往往需要使用两个外围部件才能完成和实现。如使用1个定时/计数器加1个外部中断(或模拟比较器):定时/计数器用于计时;而外部中断方式设置成电平变化触发方式,用于检测脉冲的上升和下降沿。当外部中断输入电平由低变高,触发中断,读取时间1;等到输入电平由高变低时,再次触发中断,读取时间2;两次时间差既为脉冲宽度。这种实现方式不仅多占用了一个单片机的内部资源,而且精度也受到中断响应时间的限制。因为一旦中断发生,MCU响应中断需要时间,在中断中可能要进行适当的中断现场保护,才能读取时间值。而此时的时间值比中断发生的时间已经滞后了。

  而使用ATmega128的1个定时/计数器,再配合其输入捕捉功能来测量脉冲的宽度就非常方便,下面是实现的程序示例。



#include <mega128.h>



#define ICP1        PIND.4                        //脉冲输入由ICP1(Pind.4)输入



unsigned char ov_counter;

unsigned int rising_edge,falling_edge;

unsigned long pulse_clocks;



interrupt [TIM1_OVF] void timer1_ovf_isr(void)                // T/C1溢出中断

{

        ov_counter++;                                //记录溢出次数

}



interrupt [TIM1_CAPT] void timer1_capt_isr(void)        // T/C1捕捉中断

{

        if (ICP1)       

        {                                                        //上升沿中断

                rising_edge = ICR1;                //记录上升沿开始时间

                TCCR1B = TCCR1B & 0xBF;                //设置T/C1为下降沿触发捕捉

                ov_counter = 0;                        //清零溢出计数器

        }

        else

        {                                                        //下降沿中断

                falling_edge = ICR1;                //记录下降沿时间

                TCCR1B = TCCR1B | 0x40;                //设置T/C1为上升沿触发捕捉

                pulse_clocks = (unsigned long)falling_edge - (unsigned long)rising_edge

+ (unsigned long)ov_counter * 0x10000 / 500;        //计算脉冲宽度

        }

}



void main(void)

{

TCCR1B=0x42;                //初始化T/C1,1/8分频,上升沿触发捕捉

        TIMSK=0x24;                //允许T/C1溢出和捕捉中断

#asm("sei")

       

while (1)

     {………

     };

}

  这段程序是在CVAVR中实现的。在T/C1的捕捉中断中,先检查ICP1的实际状态,以确定是出现了上升沿还是下降沿信号。如果中断是由上升沿触发的(ICP1为高电平),程序便开始一次脉冲宽度的测量:记录下上升沿出现的时间,把T/C1的捕捉触发方式改为下降沿触发,并清空溢出计数器。如果中断由下降沿触发(ICP1为低电平),表示到达脉冲的未端,程序记录下降沿出现时间,计算出脉冲的宽度,再将T/C1的捕捉触发方式改为上升沿触发,以开始下一次的测量。

  脉冲的实际宽度(毫秒格式)是根据T/C1的计数时钟个数来计算的。本例中T/C1的计数时钟是系统时钟(4MHz)的8分频,即500KHz,相应的计数脉冲宽度为2us。因此计算出从上升沿和下降沿之间总的计数脉冲个数,除以500个脉冲(为1ms)即得到以毫秒为单位的被测脉冲宽度了。

  可以看到,使用定时/计数器以及配合它的捕捉功能测量脉冲宽度,不仅节省系统的硬件资源,编写程序简单,而且精度也高,因为读到的上升沿和下降沿的时间就是其实际发生的时间。



5.9.3 比较匹配输出和脉冲宽度调制PWM

(见已发表的主题)

阿莫论坛20周年了!感谢大家的支持与爱护!!

知道什么是神吗?其实神本来也是人,只不过神做了人做不到的事情 所以才成了神。 (头文字D, 杜汶泽)

出0入0汤圆

发表于 2005-2-18 01:05:52 | 显示全部楼层
好好学习,争当“三好学生”。



先给老师上根烟,敬杯酒后再泡壶茶。。。
头像被屏蔽

出0入0汤圆

发表于 2005-2-18 08:38:55 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽

出0入0汤圆

发表于 2006-2-24 09:42:54 | 显示全部楼层
pulse_clocks = (unsigned long)falling_edge - (unsigned long)rising_edge

+ (unsigned long)ov_counter * 0x10000 / 500;   //计算脉冲宽度

是不是少了个括号

出0入0汤圆

发表于 2008-1-26 14:41:34 | 显示全部楼层
如何改为gcc的程序 啊?

出0入0汤圆

发表于 2008-1-26 16:34:57 | 显示全部楼层
#include<avr/io.h>
#include<avr/delay.h>
#include<stdio.h>
#include<avr/interrupt.h>
#include<avr/signal.h>
#include<avr/pgmspace.h>
//注: 内部函数_delay_ms() 最高延时  262.144mS@1MHz 即 32.768ms@8MHz
//    该函数可以实现较精确的定时for()/while()指令很难计算延时时间
//    为了使 _delay_ms()函数的延时正确,须在makefile中设定F_CPU为实际的系统时钟频
//时钟定为8MHz,F_CPU=8000000

//#define ICP1        PINB 0                      //脉冲输入由ICP1(Pind.4)输入
#define ICP1      PB0         //ICP1  PB0 按键模拟ICP输入
unsigned char ov_counter;
unsigned int rising_edge,falling_edge;
volatile unsigned long pulse_clocks;



SIGNAL(SIG_OUTPUT_COMPARE1A)                // T/C1溢出中断
{
        ov_counter++;                                //记录溢出次数
}

SIGNAL(SIG_INPUT_CAPTURE1)       // T/C1捕捉中断
{
        if(ICP1)         
        {                                                        //上升沿中断
                rising_edge = ICR1;                //记录上升沿开始时间
                TCCR1B = TCCR1B & 0xBF;                //设置T/C1为下降沿触发捕捉
                ov_counter = 0;                        //清零溢出计数器
        }
        else
        {                                                        //下降沿中断
                falling_edge = ICR1;                //记录下降沿时间
                TCCR1B = TCCR1B | 0x40;                //设置T/C1为上升沿触发捕捉
                pulse_clocks = ((unsigned long)falling_edge - (unsigned long)rising_edge + (unsigned long)ov_counter * 0x10000 ) / 500;        //计算脉冲宽度

                }
}
//



void main(void)
{
PORTD=0xff;//D口启用上拉
DDRD=0xFB;//PD2输入
//PORTD=_BV(PD7);//上拉

TCCR1B=0x42;                //初始化T/C1,1/8分频,上升沿触发捕捉
TIMSK=0x24;                //允许T/C1溢出和捕捉中断
sei();
pulse_clocks=0X0;
while (1)
     {
      
         if(pulse_clocks >= 0x21)
         PORTD=_BV(PD7);//
         else
         PORTD=~_BV(PD7);
     } ;
}

我改了gcc后,为什么运行不正常。我给信号是2ms,大于21ms亮,现在led是闪亮状态。

出0入0汤圆

发表于 2008-4-25 09:06:51 | 显示全部楼层
好贴
谢谢

出0入0汤圆

发表于 2008-8-4 14:49:02 | 显示全部楼层
BIAOB标记一个。学习。。。。。

出0入0汤圆

发表于 2009-3-25 08:52:00 | 显示全部楼层
我看到资料上说,改变一次捕捉中断的触发沿就需要清中断响应标志位ICF,我看上面的程序没有这部,不清没有关系吗?

出0入0汤圆

发表于 2009-4-29 16:51:15 | 显示全部楼层
书上还有这一句,”如果仅仅需要测量频率,且使用了中断,则需要对ICF进行软件清0“,应该是这个了....

出0入0汤圆

发表于 2009-11-7 19:20:57 | 显示全部楼层
ji

出0入0汤圆

发表于 2009-11-8 09:37:38 | 显示全部楼层
经你这么一说,比我看书效果好多了,谢谢

出0入0汤圆

发表于 2009-12-25 13:42:29 | 显示全部楼层
ji

出0入0汤圆

发表于 2010-4-28 15:37:20 | 显示全部楼层
试试

出0入0汤圆

发表于 2010-4-28 15:49:39 | 显示全部楼层
马老师辛苦,呵呵

出0入0汤圆

发表于 2010-7-23 11:22:22 | 显示全部楼层
挺好资料

出0入0汤圆

发表于 2011-3-6 22:18:56 | 显示全部楼层
学习一下

出0入0汤圆

发表于 2011-6-23 19:29:36 | 显示全部楼层
谢谢,好贴!

出0入0汤圆

发表于 2011-7-10 06:38:42 | 显示全部楼层
学习了  嘿嘿

出0入0汤圆

发表于 2011-7-10 15:19:40 | 显示全部楼层
学习一下

出0入0汤圆

发表于 2011-7-11 22:27:24 | 显示全部楼层
学习中

出0入0汤圆

发表于 2011-8-17 10:54:16 | 显示全部楼层
学习了,谢谢~

出0入0汤圆

发表于 2012-2-2 16:42:05 | 显示全部楼层
刚开始自学单片机正琢磨定时器计数器呢 谢了 楼主

出0入0汤圆

发表于 2013-1-15 13:28:57 | 显示全部楼层
是否应该改成这样子:pulse_clocks = (unsigned long)((signed long)falling_edge - (signed long)rising_edge

+ (signed long)ov_counter * 0x10000 / 500);        //计算脉冲宽度

出0入0汤圆

发表于 2013-3-6 20:34:21 来自手机 | 显示全部楼层
支持下!!!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-7-24 09:20

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表