8位定时/计数器T/C0的应用

December 3, 2018 · AVR单片机 · 94次阅读

定时/计数器基本工作特点如下:

  • 对一个序列的脉冲信号进行计数,而且计数过程由硬件自己完成,不需要软件干预。
  • 一旦计数值到达某个值,通常是MAX(0xFF)、BOTTOM(0x00)或TOP时,可以产生中断申请,通知MCU进行处理。
  • 仔细确定使用哪个定时/计数器。ATmega16一共配置了2个8位和1个16位共3个定时/计数器,不仅长度不同而且功能也不同。要选择合适的定时/计数器使用。
  • 脉冲信号源。脉冲信号源是指输入到定时.计数器的计数脉冲信号。用于定时/计数器计数的脉冲信号可以由外部输入引脚提供,也可以由单片机内部提供。当使用内部计数脉冲信号时,应选择合适的分频比例与计数值的配合。
  • 计数器的工作模式和触发方式的选择。
  • 中断服务程序的正确设计。定时/计数器的使用通常是与中断程序相结合在一起使用的,因此要非常清楚中断产生的条件,以及在中断服务程序中正确的进行中断处理及其相关的设置。

外部事件计数器

T/C0作为外部事件计数器使用时,是指其计数脉冲信号来自外部引脚的T0(PB0)。
通常,对外部输入脉冲信号的基本处理有以下两种:

  • 对外部脉冲信号进行计数,即记录脉冲的个数,一旦记录的脉冲个数达到一个设定值时,就进行必要的处理。
  • 对外部脉冲信号的频率(周期)进行测定。

2N分频系统设计(一)

1)硬件电路
2N分频系统要实现的功能是对T0引脚输入的方波信号进行偶数次的分频,以获得频率低于T0输入的方波信号。
本设计的硬件电路非常简单,将实验板上的250Hz的方波信号输出与ATmega16的T0引脚连接,作为T/C0计数器的外部输入。另外将PA0作为分频后的脉冲输出引脚,用PA0控制一个LED的显示,通过LED的亮暗变化可以简单地观察方波的频率,当然最好的方法是用示波器观察PA0的输出。
2)软件设计
首先考虑使用T/C0的普通模式(WGM0[0:1]=0),采用T0上升沿触发(CS0[2:0]=111),并设置TCNT0的初值为0xFF。当T0引脚输入电平出现一个上跳变时,T/C0的TCNT0回到0x00,并产生溢出中断,在溢出中断服务程序重新设置TCNT0位0xFF,并改变PA0口的输出电平(取反输出)。当T0引脚输入电平出现第二个上跳变时,又会如此,这样PA0上就得到T0的2分频输出信号。同理,如果将TCNT0的初值设定为0xFE,则在PA0上得到T0的4分频输出信号,0xFD→6分频,0xFC→8分频...而当TCNT0的初值设置为0x00时,可实现最大512分频的输出。
下面给出在PA0上输出1Hz方波(LED亮0.5s,暗0.5s)的设计和程序。由于T0输入的频率为250Hz,所以分频洗系数为250,因此TCNT0的初值=255-124(0x83),即T/C0计数125次时PA0的电平改变一次。
P81204-133109(1).jpg
利用CVAVR的程序生成向导生成一个程序框架后,再加入自己的程序并进行必要的修改。程序代码如下:

/******-
File name            :demo_8_1.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>

//Timer 0溢出中断服务
interrrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
    TCNT0 = 0x83;
    PORTA.0 = ~PORTA.0;
}

void main(void)
{
    PORTA = 0x01;
    DDRA = 0x01;        //设置PA0输出方式
    
    PORTB = 0x01;
    DDRB = 0x00;        //设置PB0(T0)为输入方式
    
    //T/C0初始化
    TCCR0 = 0x07;        //T/C0工作于普通模式,T0上升沿触发
    TCNT0 = 0x83;
    OCR0 = 0x00;
    TIMSK = 0x01;        //允许T0溢出中断
    #asm("sei");        //使能全局中断
    while(1)
    {
        //palce your code here
    };
}

思考与实践:

  • 为什么在中断服务程序中要重新设置TCNT0的初值?
  • 如何计算TCNT0的初值,使得PA0输出0.5Hz的方波?

2N分频系统设计(二)

1)硬件电路
同2N分频系统设计(一)
2)软件设计
2N分频系统设计(一)中使用了T/C0的普通模式,因此在中断服务程序中必须重新对TCNT0进行初始化。其实更方便的方法是使用T/C0的CTC模式,利用T/C0的自动重装特性。当T/C0工作在CTC模式时,计数器TCNT0的值与OCR0的值比较,一旦相等,就在下一次计数脉冲序列到来时清0 TCNT0,并产生T/C0的比较匹配中断。此时在比较匹配中断服务程序中改变PA0的输出即可。代码如下:

/******-
File name            :demo_8_2.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>

//Timer 0溢出中断服务
interrrupt [TIM0_COMP] void timer0_comp_isr(void)
{
    PORTA.0 = ~PORTA.0;        //PA0取反输出
}

void main(void)
{
    PORTA = 0x01;
    DDRA = 0x01;
    
    PORTB = 0x01;
    DDRB = 0x00;
    
    //T/C0初始化
    TCCR0 = 0x0F;        //T/C0工作于CTC模式,T0上升沿触发
    TCNT0 = 0x00;
    OCR0 = 0x7C;        //设置OCR0的比较值为124(0x7C)
    TIMSK = 0x02;        //允许T0比较匹配中断
    #asm("sei");        //使能全局中断
    while(1)
    {
        //palce your code here
    };
}

思考与实践:

  • 比较demo_8_1.c和demo_8_2.c中T/C0两种方式的特点。
  • 如何利用T/C0实现N分频?

N分频系统设计

1)硬件电路
同2N分频系统设计,但在PA0上得到N分频的输出。
2)软件设计
实际上,利用T/C0的比较匹配的特点,可以实现N分频的系统。代码如下:

/******-
File name            :demo_8_3.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>

//Timer 0溢出中断服务
interrrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
    TCNT0 = 0xFB;            //重新设置TCNT0的初值
    PORTA = ~PORTA;            //PA0取反输出
}

//Timer 0比较匹配中断服务
interrrupt [TIM0_COMP] void timer0_comp_isr(void)
{
    PORTA = ~PORTA;            //PA0取反输出
}

void main(void)
{
    PORTA = 0x00;
    DDRA = 0x01;
    
    PORTB = 0x01;
    DDRB = 0x00;
    
    //T/C0初始化
    TCCR0 = 0x07;            //T/C0工作于普通模式,T0上升沿触发
    TCNT0 = 0xFB;
    OCR0 = 0xFD;            //设置OCR0的比较值,大于TCNT0的初始值,小于0xFF
    TIMSK = 0x03;            //允许T/C0溢出和比较匹配中断
    #asm("sei");            //使能全局中断
    while(1)
    {
        //palce your code here
    };
}

定时器应用设计

采用T/C0硬件定时器的时钟系统。

1)硬件电路
在之前例子(6.5)中,采用调用CVAVR中软件延时函数的方法给出了一个使用6个数码管组成的时钟系统。采用软件延时,时钟是不准确的,因为一旦系统中使用了中断,就可能打断延时程序的执行,使延时时间发生变化。另外,使用软件延时的方法,也降低了MCU的效率。
而7.2中,系统时钟的基准信号来自外部的标准方波信号源,这样尽管定时时间比采用软件延时方式要准确得多,但由于采用外部标准方波信号源而增加了系统的成本。
实际上,更加方便和简单的方式是采用系统本身的时钟信号,并配合T/C0产生时钟系统的定时信号。下面给出采用T/C0硬件定时器实现的时钟系统设计。时钟系统的硬件与图6-15相同。
2)软件设计
C语言代码如下:

/******-
File name            :demo_8_4.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>
flash unsigned char led_7[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7E,0x6F};//段选码,对应0~9
flash unsigned char position[6]={0x6e,0xfd,0xfb,0xf7,0xef,0xdf};//位选码,确定哪一个LED导通
unsigned char time[3];//时分秒计数单元
unsigned char dis_buff[6];//缓冲区,保存要显示的6个字符的段码值
int time_counter;//中断次数计数单元
unsigned char posit;
bit point_on, time_1s_ok;

void display(void)//LED灯动态扫描函数
{
    PORTC = 0xff;
    PORTA = led_7[dis_buff[posit]];
    if (point_on && (posit == 2 || posit == 4)) PORTA |= 0x80;
    PORTC = position[posit];
    if (++posit >= 6) posit = 0;
}

//Timer 0比较匹配中断服务
interrrupt [TIM0_COMP] void timer0_comp_isr(void)
{
    display();                        //调用LED扫描显示
    if (++time_counter >= 500)
    {
        time_counter = 0;
        time_1s_ok = 1;
    }
}

void time_to_disbuffer(void)        //时钟时间送显示缓冲区
{
    unsigned char i,j = 0;
    for (i=0;i<=2;i++)
    {
        dis_buff[j++] = time[i] % 10;
        dis_buff[j++] = time[i] / 10;
    }
}

void main(void)
{
    PORTA = 0x00;//显示控制I/O口初始化
    DDRA = 0xFF;
    PORTC = 0x3F;
    DDRC = 0x3F;
    //T/C0初始化
    TCCR0 = 0x0B;            //内部时钟,64分频(4MHz/64 = 62.5kHz) CTC模式
    
    TCNT0 = 0x00;
    OCR0 = 0x7C;            //OCR0 = 0x7C(124),(124+1)/62.5kHz=2ms
    TIMSK = 0x02;            //允许T/C0比较匹配中断
    
    time[2] = 23;time[1]=58;time[0]=55;//设定初始的时间
    posit = 0;
    time_to_disbuffer();//时间送缓冲区
    #asm("sei")            //使能全局中断
    while(1)
    {
        if (time_1s_ok)            //1s到
        {
            time_1s_ok = 0;
            point_on = ~point_on;//亮一下点,表明时间过了一秒
            if (++time[0] >= 60)//下面是对时间增加1s后,时分秒的处理
            {
                time[0] = 0;//过了60s则秒数又从0开始
                if (++time[1] >= 60)//分钟先自增1,判断有没有到60分钟
                {
                    time[1] = 0;//分钟数到了60则重置为0
                    if (++time[2] >= 24) time[2] = 0;//类似上方,略...
                }
            }
            time_to_disbuffer();//新调整好的时间送显示缓冲区
        }
    };
}

该程序中的LED动态扫描,时间调整与例7.2相同,所不同的是使用了T/C0硬件定时。T/C0工作在CTC模式,采用系统时钟经过64分频的信号作为计数器的计数脉冲。4MHz系统时钟经过64分频后62.5KHz,周期为16μs。T/C0的比较寄存器OCR0的值为124(0x7C),因此T/C0每计数125次产生一次比较匹配中断,中断的间隔时间为16*125=2ms。
在T/C0的比较匹配中断服务中,中断服务的内容同例7.2,首先进行LED的扫描,即每位LED的扫描间隔(点亮时间)为2ms,然后中断次数计数器time_counter加1.当time_counter加到500时,置位秒标志time_1s_ok,表示1s时间到。
主程序中循环检测秒标志time_1s_ok,当秒标志置位时,进行时间的调整,然后将新的时间值送到显示缓冲区中。
思考与实践:

  • 将该程序与6.5和7.2进行分析比较,比如它们各自的优点、缺点及MCU的利用率等。

标签:none

最后编辑于:2018/12/04 10:04

添加新评论