分类 AVR单片机 下的文章

通用I/O数字输入接口设计

I/O输入接口硬件设计要点

根据系统外围电路输入的电信号形式,可以把输入信号分为以下几种形式。

  • 模拟信号和数字信号
    传感器将某个外部参数的变化转换为电信号。如果传感器输出电信号的幅度变化特征代表了外部参数的变化,例如电压的升高/下降表示温度高/低变化,那么这个传感器就是模拟传感器,它产生的是模拟信号。由于MCU是数字化的,因此模拟信号要转换成数字信号才能由MCU处理。这个转换电路称为模/数转换。A/D转换是嵌入式系统重要的外围接口电路之一,用途广泛。在系统硬件设计中,可以选取专用的A/D转换芯片作为模拟传感器与单片机之间的接口,也可以选取片内A/D转换功能的单片机,以简化硬件电路的设计。大多数型号的AVR单片机在片内都集成有A/D接口。
  • 电压信号和电流信号
  • 单次信号和连续信号

I/O输入信号软件设计要点

  • 正确使用AVR的I/O口要注意:先正确设置DDRx方向寄存器,再进行I/O口的读写操作。
  • AVR的I/O口复位后的初始状态全部为输入工作方式,内部上拉电阻无效。因此,外部引脚呈现三态高阻输入状态。
  • 用户程序需要首先对要使用的I/O口进行初始化设置,根据实际需要设定使用I/O口的工作方式(输出还是输入),当设定为输入方式时,还要考虑是否使用内部上拉电阻。
  • 在硬件电路设计时,如果能利用AVR内部I/O口的上拉电阻,则可以节省对外部的上拉电阻。
  • I/O口用于输出时,应设置DDRx = 1或DDRx,n = 1,输出值写入PORTx或PORTx,n中。
  • I/O口用于输入时,应设置DDRx = 0或DDRx,n = 0,读取外部引脚电平时,应读取PINx,n的值,而不是PORTx,n的值,此时PORTx,n = 1表示该I/O内部的上拉电阻有效,而PORTx,n=0表示不使用内部上拉电阻,外部引脚呈现三态高阻输入状态。
  • 一旦将I/O口的工作方式由输出设置成输入方式后,必须等待1个时钟周期后才能正确读到外部引脚PINx,n的值。

P81206-141542(1).jpg
ATmega16的T/C1是一个16位多功能定时/计数器,上图为该16位定时/计数器的结构框图。主要特点如下:

  • 真正的16位设计
  • 2个独立的输出比较匹配单元
  • 双缓冲输出比较寄存器
  • 1个输入捕捉单元
  • 输入捕捉噪声抑制
  • 比较匹配清0计数器(自动重装特性)
  • 可产生无输出抖动(Glitch-free)的、相位可调的脉宽调制(PWM)信号输出
  • 周期可调的PWM波形输出
  • 频率发生器
  • 外部事件计数器
  • 10位时钟预分频
  • 4个独立的中断源(TOV1、OCF1A、OCF1B和ICF1)
    图中给出了MCU可操作的寄存器及相关的标志位,其中计数器寄存器TCNT1、输出比较寄存器OCR1A、OCR1B和输入捕捉寄存器ICR1都是16位寄存器。T/C1所有的中断请求信号TOV1、OCF1A、OCF1B和ICF1都可以在定时/计数器T/C1所有的中断标志寄存器TIFR找到,而在定时器中断屏蔽寄存器TIMSK中,可以找到与它们相对应的4个相互独立的中断屏蔽控制位TOIE1、OCIE1A、OCIE1B和TICIE1。TCCR1A和TCCR1B为2个8位寄存器,是T/C1的控制寄存器。

T/C1时钟源的选择由T/C1的控制寄存器TCCR1B中的3个标志位CS1[2:0]确定,共有8种选择。其中包括无时钟源(停止计数),外部引脚T1的上升沿或下降沿,以及内部系统时钟经过一个10位预定比例分频器分频的5种频率的时钟信号(1/1、1/8、1/64、1/256、1/1024)。
T/C1的基本工作原理和功能与8位定时/计数器相同,常规的使用方法也是类同的。但与8位的T/C0、T/C2相比,T/C1不仅位数增加到16位,而且功能也更加强大。

16位T/C1增强功能介绍

16计数器

由于T/C1是16计数器,因此它的计数器宽度、计时长度大大增加。配合一个独立的10位预定比例分频器,在系统时钟为4MHz条件下,16位T/C1最高计时精度为0.25μs,而最长的时宽可达到16.777216s(精度为256μs),这是其他8位单片机所做不到的。

16位寄存器的读/写操作步骤

由于AVR内部数据总线为8位,因此读/写16位寄存器需要分两次操作。为了能够同步读/写16位寄存器,每一个16位寄存器分别配有一个8位临时辅助寄存器,读/写操作应遵循以下步骤:

  1. 16位寄存器的读操作:当MCU读取16位寄存器的低字节(低8位)时,16位寄存器低字节内容被送到MCU,而高位字节(高8位)内容在读低字节操作的同时被置于临时辅助寄存器(TEMP)中。当MCU读取高字节时,读到的是TEMP寄存器中的内容。因此,要同步读取16位寄存器,应先读取寄存器的低位字节,再立即读取其高位字节。
  2. 16位寄存器的写入操作:当MCU写入数据到16位寄存器的高位字节时,数据是写入到TEMP寄存器中。当MCU写入数据到16位寄存器的低位字节时,写入的8位数据与TEMP寄存器中的8位数据组合成一个16位数据,同步写入到16位寄存器中。因此,要同步写16位寄存器,应先写入该寄存器的高位字节,再立即写入它的低位字节。

用户编写汇编程序时,如果要对16位寄存器进行读/写操作,则应遵循以上特定的步骤。此外,再对16位寄存器操作时,最好将中断响应屏蔽,防止在主程序读/写16位寄存器的两条指令之间插入一个含有对该寄存器操作的中断服务。如果这种情况发生,那么中断返回后,寄存器中的内容已经发生改变,会造成主程序中对16位寄存器的读写失/误。

更加强大和完善的PWM功能

输入捕捉功能

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

  • 对一个序列的脉冲信号进行计数,而且计数过程由硬件自己完成,不需要软件干预。
  • 一旦计数值到达某个值,通常是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的利用率等。

ATmega16配置了2个8位和1个16位共3个定时/计数器,它们是8位定时/计数器T/C0、T/C2和16位定时/计数器T/C1。

定时计数器结构

在单片机内部,一般都会集成专门硬件电路构成的可编程定时/计数器。定时/计数器最基本的功能就是自动进行计数。这里所谓的“自动”,是指计数的过程由硬件完成,不需要MCU的干预。但MCU可以通过指令设置定时/计数器的工作方式,以及根据定时/计数器的计数值或工作状态做必要的处理和响应。
定时/计数器的基本要素:

  • 定时/计数器的长度。定时/计数器的长度是指计数单元的位长度,一般为8位(1字节)或16位(2字节)。
  • 脉冲信号源。脉冲信号源是指输入到定时/计数器的计数脉冲信号,可以由外部输入引脚提供,也可以由单片机内部提供。
  • 计数器类型。计数器类型是指计数器的计数运行方式,可以分为加1(减1)计数器,单向计数或双向计数。
  • 计数器的上下限。指计数单元的最小值和最大值,一般情况下,计数器的下限值为0,上限为255(8位)或65535(16位)。需要注意的是,当计数器工作在不同模式时,计数器的上限值取决于用户的配置和设定。
  • 计数器的事件。指计数器处于某种状态时的输出信号,该信号通常可以向MCU申请中断。例如,当计数器计数达到计数上限值255时,产生溢出信号,向MCU申请中断。

8位定时/计数器T/C0的结构

ATmega16中有两个8位定时/计数器:T/C0和T/C2,它们都是通用的多功能定时/计数器,其主要特点如下:

  • 单通道计数器
  • 比较匹配清0计数器(自动重装特性)
  • 可产生无输出抖动(glitch-free)的、相位可调的脉宽调制(PWM)信号输出
  • 概率发生器
  • 外部事件计数器(仅T/C0)
  • 10位时钟预分频器
  • 溢出和比较匹配中断源(TOV0、OCF0和TOV2、OCF2)
    -允许使用外部引脚的32768Hz晶体作为独立的计数时钟源(仅T/C2)

T/C0的组成结构

  1. 计数寄存器TCNT0
  2. 输出比较寄存器OCR0
  3. 控制寄存器TCCR0
  4. 中断标志寄存器TIFR
  5. 定时器中断屏蔽寄存器TIMSK

T/C0的计数器事件输出信号:

  1. 计数器计数溢出TOV0
  2. 比较匹配相等OCF0

这两个信号都可以申请中断,中断请求信号TOV0、OFC0可以在定时器中断标志寄存器TIFR中找到,同时在定时器中断屏蔽寄存器TIMSK中可以找到与TVO0、OCF0对应的、两个相互独立的中断屏蔽控制位TOIE0、OCIE0。

  • T/C0的时钟源

(1)T/C0计数时钟源的选择
T/C0计数时钟源的选择由T/C0的控制寄存器TCCR0中的3个标志位CS0[2:0]确定,共有8种选择。其中包括无时钟源(停止计数),外部引脚T0的上升沿或下降沿,以及内部系统时钟经过一个10位预定比例分频器分频的5种频率的时钟信号(1/1、1/8、1/64、1/265和1/1024)。T/C0与T/C1共享一个预定比例分频器,但它们时钟源的选择是独立的。

(2)使用系统内部时钟源
当定时/计数器使用内部系统时钟作为计数源时,通常作为定时器和波形发生器使用。因为系统时钟的频率是已知的,所以通过计数器的计数值就可以知道时间值

(3)使用外部时钟源
当定时/计数器使用外部时钟作为计数源时,通常作为计数器使用,用于记录外部脉冲的个数。

外部引脚T0(PB0)上的脉冲信号可以作为T/C0的集数时钟源。PB0引脚内部有一个同步采样电路,它在每个系统时钟周期都对T0引脚上的电平进行同步采样,然后将同步采样信号送到边沿检测器中。同步采样电路在系统时钟的上升沿将引脚信号电平打入寄存器,因此当系统的时钟频率大大高于外部引脚电平变化的频率时,同步采样寄存器可以看做是透明的。边沿检测电路对同步采样的输出信号进行边沿检测,当检测到一个上升沿(CS0[2:0=7])或下降沿(CS0[2:0]=6)时,产生一个计数脉冲clkT0

由于引脚T0内部的同步采样和边沿检测电路的存在,引脚电平的变化需要经过2.5~3.5个系统时钟才能在边沿检测的输出端反映出来。因此,要使外部时钟源能正确地被引脚T0检测采样,外部时钟源的最高频率不能大于fclkT0/2.5,脉冲宽度也要大于1个系统时钟周期。另外,外部时钟源是不进入预定比例分频器进行分频的。

  • T/C0的计数单元

T/C0的计数单元是一个可编程的8位双向计数器
P81202-121649(1).jpg
图中符号所代表的意义如下:
计数(count) TCNT0加1或减1
方向(direction) 加或减的控制
清除(clear) 清0 TCNT0
计数时钟源(clkT0) C/T0时钟源
顶部值(TOP) 表示TCNT0计数值达到上边界
底部值(BOTTOM) 表示TCNT0计数值达到下边界(0)

T/C0根据计数器的工作模式,在每一个clkT0时钟到来时,计数器进行加1,减1或清0操作。clkT0的来源由标志位CS0[2:0]设定。当CS0[2:0]=0时,计数器停止计数(无计数时钟源)

T/C0的计数值保存在8位寄存器TCNT0中,MCU可以在任何时间访问(读/写)TCNT0。MCU写入TCNT0的值将覆盖其中原有的内容,同时也会影响到计数器的运行。

计数器的计数序列取决于寄存器TCCR0中标志位WGM0[1:0]的设置,WGM0[1:0]的设置直接影响到计数器的计数方式和OC0的输出,同时也影响和涉及T/C0的溢出标志位TOV0的置位。标志位TOV0可用于产生中断申请。

  • 比较匹配单元

P81202-164800(1).jpg
图为T/C0的比较匹配单元逻辑功能图。在T/C0运行期间,比较匹配单元一直将寄存器TCNT0的计数值与寄存器OCR0的内容进行比较。一旦两者相等,在下一个计数时钟脉冲到达时将置位OCF0标志位。标志位OCF0也可用于产生中断申请。根据WGM0[1:0]和COM0[1:0]的不同设置,可以控制比较匹配单元产生和输出不同类型的脉冲波形。

寄存器OCR0实际上配置有一个辅助缓存器。当T/C0工作在非PWM模式下时,该辅助缓存器处于禁止使用状态,MCU直接访问和操作寄存器OCR0。当T/C0工作在PWM模式时,该辅助缓存器投入使用,这时MCU对OCR0的访问操作实际上是对OCR0的辅助缓存器操作。一旦计数器TCNT0的计数值达到设定的最大值或最小值,则辅助缓存器中的内容将同步更新比较寄存器OCR0的值。这将有效防止产生奇边非对称的PWM脉冲信号,使输出的PWM波中没有杂散脉冲。
(1)强制输出比较
在非PWM波形发生模式下,写“1”到强制输出比较位(FOC0)时,将强制比较器产生一个比较匹配输出信号。强制比较匹配信号不会置OCF0标志位或重新装载/清0计数器,但会像真的发生了比较匹配事件一样更新OC0输出引脚输出。
(2)通过写TCNT0寄存器屏蔽比较匹配事件。
任何MCU对TCNT0寄存器的写操作都会屏蔽在一个定时器时钟周期中发生的比较匹配事件,即使在定时器暂停时。这一特性使OCR0可以被初始化为与TCNT0相同的值,而不会在定时/计数器使能时触发中断。
(3)使用输出比较单元
由于在任何工作模式下写TCNT0寄存器都会使输出比较匹配事件被屏蔽1个定时器时钟周期,因此可能会影响比较匹配输出的正确性。例如,当写一个与OCR0相同的值到TCNT0时,将丢失一次比较匹配事件,从而引起发生不正确的波形。同样定时计数器向下计数时,不要将下边界的值写入TCNT0。

外部引脚OC0的设置必须在设置该端口引脚(PB3)为输出之前。设置OC0的值,最简单的方法是在通常模式下使用FOC0来设置。这是因为在改变工作模式时,OC0寄存器保持其原来的值。
注意:COM0[1:0]是无缓冲的,改变COM0[1:0]位的设置,会立即影响T/C0的工作方式。

  • 比较匹配输出单元

标志位COM0[1:0]有两个作用:定义OC0的输出状态,以及控制外部引脚OC0是否输出OC0寄存器的值。图为比较匹配输出单元的逻辑图。
P81202-164353(1).jpg

当标志位COM0[1:0]中任何一位为“1”时,波形发生器的输出OC0取代引脚PB3原来的I/O功能,但引脚的方向寄存器DDRB3仍然控制OC0引脚的输入/输出方向。如果要在外部引脚PB3上输出OC0的逻辑电平,则应设定DDRB3定义该引脚为输出脚。采用这种结构,用户可以先初始化OC0的状态,然后再允许其由引脚PB3输出。

  • 比较输出模式和波形发生器

T/C0有4种工作模式,根据COM0[1:0]的不同设定,波形发生器将产生各种不同的脉冲波形,如PWM波形的产生和输出。但只要COM0[1:0]=0,波形发生器对OC0寄存器就没有任何作用。

与8位T/C0相关的寄存器

  • T/C0计数寄存器TCNT0
    TCNT0各点定义如下:

P81202-170707(1).jpg
TCNT0是T/C0的计数值寄存器,可以直接被MCU读/写访问,写TCNT0寄存器将在下一个定时器时钟周期中阻断比较匹配。因此,在计数器运行期间修改TCNT0的内容,有可能将丢失一次TCNT0与OCR0的匹配比较操作。

  • 输出比较寄存器OCR0

寄存器OCR0各位的定义如下:
P81202-170716(1).jpg
8位寄存器OCR0中的数据用于与寄存器TCNT0中的计数值进行匹配比较。在T/C0运行期间,比较匹配单元一直将寄存器TCNT0中的计数值与寄存器OCR0的内容进行比较。一旦TCNT0的计数值与OCR0的数据匹配相等,将产生一个输出比较匹配相等的中断申请,或改变OC0的输出逻辑电平。

  • 定时/计数器中断屏蔽寄存器TIMSK

寄存器TIMSK各位的定义如下:
P81202-170724(1).jpg
位7(位1)——OCIE2(OCIE0):T/C2(T/C0)输出比较匹配中断允许标志位。当OCIE2(OCIE0)被设为“1”,且状态寄存器中的I位被设为“1”时,将使能T/C2(T/C0)的输出比较匹配中断。若在T/C2(T/C0)中发生输出比较匹配,即OCF2=1(OCF0=1)时,则执行T/C2(T/C0)输出比较匹配中断服务程序。

位6(位0)——TOIE2(TOIE0):T/C2(T/C0)溢出中断允许标志位。当TOIE2(TOIE0)被设为“1”,且状态寄存器中的I位被设为“1”时,将使能T/C2(T/C0)。若在T/C2(T/C0)发生溢出,即TOV2=1(TOV0=1)时,则执行T/C2(T/C0)溢出中断服务程序。

  • 定时/计数器中断标志寄存器TIFR

寄存器TIFR各位的定义如下:
P81202-170737(1).jpg
位7(位1)——OCF2(OCF0):T/C2(T/C0)比较匹配输出的中断标志位。当T/C2(T/C0)输出比较匹配成功,即TCNT2=OCR2(TCNT0=OCR0)时,OCF2(OCF0)位被设为“1”。当转入T/C2(T/C0)输出比较匹配中断向量执行中断处理程序时,OCF2(OCF0)由硬件自动清0.写入一个逻辑“1”到OCF2(OCF0)标志位,将清除该标志位。当寄存器SREG中的I位,OCIE2(OCIE0)和OCF2(OCF0)均为“1”时,T/C2(T/C0)的输出比较匹配中断被执行。
位6(位0)——TOV2(TOV0):T/C2(T/C0)溢出中断标志位。当T/C2(T/C0)产生溢出时,TOV2(TOV0)位被设为“1”。当转入T/C2(T/C0)溢出中断向量执行中断处理程序时,TOV2(TOV0)由硬件自动清0.写入一个逻辑“1”到TOV2(TOV0)标志位,将清除该标志位。当寄存器SREG中的I位,TOIE2(TOIE0)和TOV2(TOV0)均为“1”时,T/C2(T/C0)的溢出中断被执行。在PWM模式中,当T/C2(T/C0)计数器的值为0x00并改变计数方向时,TOV2(TOV0)自动置1。

  • T/C0控制寄存器TCCR0

寄存器TCCR0各位定义如下:
P81202-170750(1).jpg
8位寄存器TCCR0是T/C0的控制寄存器,它用于选择计数器的计数源、工作模式和比较输出的方式等。

位7——FOC0:强制输出比较位。FOC0位只在T/C0被设置为非PWM模式下工作时才有效,但为了保证同以后的器件兼容,在PWM模式下写TCCR0寄存器时,该位必须清0.当将一个逻辑“1”写到FOC0位时,会强加在波形发生器上一个比较匹配成功信号,使波形发生器依据COM0[1:0]位的设置而改变OC0输出状态。
位6[3:6]——WGM0[1:0]:波形发生模式位。这两个标志位控制T/C0的计数和工作方式、计数器计数的上限值及确定波形发生器的工作模式(见下表)。T/C0支持的工作方式有:普通模式、比较匹配时定时器清0(CTC)模式和两种脉宽调制(PWM)模式。

模式WGM01WGM00T/C0工作模式计数上限值OCR0更新TOV0置位
000普通模式0xFF立即0xFF
101PWM,相位可调0xFF0xFF0xFF
210CTC模式OCR0立即0xFF
311快速PWM模式0xFF0xFF0xFF

位[5:4]——COM0[1:0]:比较匹配输出方式位。这两个位用于控制比较输出引脚OC0的输出方式。如果COM0[1:0]中任何一位或两位置1,则OC0的输出将覆盖PB3引脚的通用I/O端口功能,但此时PB3作为OC0引脚的数据方向寄存器DDRB3位必须置为输出方式。当引脚PB3作为OC0输出引脚时,其输出方式取决于COM0[1:0]和WGM0[1:0]设定。
下表给出了在WGM0[1:0]设置为普通模式和CTC模式(非PWM模式)时,COM0[1:0]位功能定义。

COM01COM00说明
00PB3为通用I/O引脚(OC0与引脚不连接)
01比较匹配时,触发OC0(OC0为原OC0的取反)
10比较匹配时,清零OC0
11比较匹配时,置位OC0

下表给出了在WGM0[1:0]设置为快速PWM模式时,COM0[1:0]位功能定义。

COM01COM00说明
00PB3为通用I/O引脚(OC0与引脚不连接)
01保留
10比较匹配时,清零OC0;计数值为0xFF时,置位OC0
11比较匹配时,置位OC0;计数值为0xFF时,清零OC0

下表给出了在WGM0[1:0]设置为相位可调PWM模式时,COM0[1:0]位功能定义。

COM01COM00说明
00PB3为通用I/O引脚(OC0与引脚不连接)
01保留
10向上计数过程中比较匹配时,清零OC0,向下计数过程中比较匹配时,置位OC0
11向上计数过程中比较匹配时,置位OC0,向下计数过程中比较匹配时,清零OC0

位[2:0]——CS0[2:0]:T/C0时钟源选择。这3个标志位用于选择设定T/C0的时钟源,如下表所示:

CS2CS1CS0说明
000无时钟源(停止T/C0)
001clkT0S(不经过分频器)
010clkT0S/8(来自预分频器)
011clkT0S/64(来自预分频器)
100clkT0S/256(来自预分频器)
101clkT0S/1024(来自预分频器)
110外部T0引脚,下降沿驱动
111外部T0引脚,上升沿驱动

8位T/C0的工作模式

T/C0的控制寄存器TCCR0的标志位WGM0[1:0]和COM0[1:0]的组合构成了T/C0的4种工作模式以及OC0不同方式的输出

普通模式(WGM0[1:0]=0)

普通模式时T/C0最简单、最基本的一种工作方式。当T/C0工作在普通模式下时,计数器为单向加1计数器,一旦寄存器TCNT0的值达到0xFF(上限值),在下一个脉冲到来时便恢复为0x00,并继续单向加1计数。在TCNT0由0xFF转变为0x00的同时,溢出标志位TOV0置1,用于申请T/C0溢出中断。一旦MCU响应T/C0的溢出中断,硬件则将自动把TOC0清0。

考虑到T/C0在正常的计数过程中,当TCNT0由0xFF返回0x00时,能将标志位TOV0置1(注意:不能清0):而当MCU响应T/C0溢出中断时,硬件会自动把TOV0清0.因此溢出标志位TOV0也可以作为计数器的第9位使用,使T/C0变成9位计数器。但这种提高定时器分辨率的方法,需要通过软件配合实现。

与其他工作模式相比,T/C0工作在普通模式时。不会产生任何其他的特殊状态,用户可以随时改变计数器TCNT0的数值。
在普通模式中,同样可以使用比较匹配功能产生定时中断,但是最好不要在普通模式下使用输出比较单元来产生PWM波形输出,因为这将占用过多的MCU的时间。

比较匹配清0计数器CTC模式(WGM0[1:0]=2)

T/C0工作在CTC模式下时,计数器为单向加1计数器,一旦寄存器TCNT0的值与OCR0的设定值相等(此时寄存器OCR0的值为计数上限值),就将计数器TCNT0清0为0x00,然后继续向上加1计数。通过设置OCR0的值,可以方便地控制比较匹配输出的频率,也方便了外部事件计数的应用。下图为CTC模式的计数时序图:
P81203-151739(1).jpg
在TCNT0与OCR0匹配的同时,置比较匹配标志位OCF0为“1”。标志位OCF0可用于申请中断。一旦MCU响应比较匹配中断,用户在中断服务程序中就可以修改OCR0的值。修改OCR0的值时需要注意,当T/C0的计数时钟频率比较高时,如果写入OCR0的值与0x00接近,则可能会丢失一次比较匹配成立条件。

例如:当TCNT0的值与OCR0匹配相等时,TCNT0被硬件清0并申请中断;在中断服务中重新改变设置OCR0为0x05;但中断返回后TCNT0的计数值已经为0x10了。因此便丢失了一次比较匹配成立条件。此时计数器将继续加1计数到0xFF,然后返回0x00。再次计数到0x05时,才能产生比较匹配成功。

在CTC模式下利用比较匹配输出单元产生波形输出时,应设置OC0的输出方式为触发方式(COM0[1:0]=1)。OC0输出波形的最高频率为fOC0=fclkI/O/2(OCR0=0x00)。其他的输出频率由下式确定:

f_{OC0} = \frac{{f_{clk}}_{I/O}}{2N(1 + OCR0)}

式中,N的取值为1、8、64、256、1024。
除此之外,与普通模式相同,当TCNT0计数值由0xFF转到0x00时,标志位TOV0置位。当OC0的输出方式为触发方式时(COM0[1:0]=1),T/C0将产生占空比为50%的方波。此时设置OCR0的值为0x00时,T/C0将产生占空比为50%的最高频率方波,频率为fOC0=fclkI/O/2

快速PWM模式(WGM0[1:0]=3)

T/C0工作在快速PWM模式时可以产生较高频率的PWM波形。当T/C0工作在此模式下时,计数器为单程向上的加1计数器,从0x00一直加到0xFF(上限值),在下一个计数脉冲到来时便恢复为0x00,然后再从0x00开始加1计数。在设置正向比较匹配输出(COM0[1:0]=2)方式中,当TCNT0的计数值与OCR0的值比较匹配时,清0 OC0;当计数器的值由0xFF返回0x00时,置位OC0。而在设置反向比较匹配输出(COM0[1:0]=3)方式中,当TCNT0的计数值与OCR0的值比较匹配时,置位OC0;当计数器的值由0xFF返回0x00时,清0 OC0。图为快速PWM工作时序图:
P81203-164243(1).jpg
由于快速PWM模式采用单程计数方式,所以它可以产生比相位可调PWM模式高1倍频率的PWM波。因此快速PWM模式适用于电源调整、DAC等应用。
当TCNT0的计数值达到0xFF时,溢出标志位TOV0置1。标志位TOV0可用于中断申请。一旦MCU响应TOV0中断,用户就可以在中断服务程序中修改OCR0的值。OC0输出的PWM波形的评论输出由下式确定:

f_{OC0 PWM} = \frac{{f_{clk}}_{I/O}}{256N}

式中,N的取值为1、8、64、256、1024。
通过设置寄存器OCR0的值,可以获得不同占空比的脉冲波形。OCR0的一些特殊值,会产生极端的PWM波形。当OCR0的设置值为0x00时,会产生周期为MAX+1的窄脉冲序列;而设置OCR0的值为0xFF时,OC0的输出为恒定的高(低)电平。

相位可调PWM模式(WGM0[1:0]=1)

相位可调PWM模式可以产生高精度相位可调的PWM波形。当T/C0工作在此模式下时,计数器为双程计数器:从0x00一直加到0xFF,在下一个计数脉冲到达时,改变计数方向,从0xFF开始减1计数到0x00。设置正向比较匹配输出(COM0[1:0]=2)方式:在正向加1过程中,当TCNTO的计数值与OCR0的值比较匹配时,清0 OC0;在反向减1过程中,当计数器TCNT0的值与OCR0相同时,置位OC0。设置反向比较匹配输出(COM0[1:0]=3)方式:在正向加1过程中,当TCNTO的计数值与OCR0的值比较匹配时,置位OC0;在反向减1过程中,当计数器TCNT0的值与OCR0相同时,清0 OC0。下图为相位可调PWM工作时序图:
P81203-170900(1).jpg
由于相位可调PWM模式采用双程计数方式,所以它产生的PWM波的频率比快速PWM低。其相位可调的特性(即OC0逻辑电平的改变不是固定在TCNT0=0x00处),适用于电机控制一类的应用。当TCNT0的计数值达到0x00时,置溢出标志位TOV0为“1”。标志位TOV0可用于申请中断。在相位可调PWM模式下,OC0输出的PWM波形频率由下式确定:

f_{OC0 PCPWM} = \frac{{f_{clk}}_{I/O}}{510N}

式中,N的取值为1、8、64、256、1024。
通过设置寄存器OCR0的值,可以获得不同占空比的脉冲波形。OCR0的一些特殊值,会产生极端的PWM波形。当COOM0[1:0]=2且OCR0的值为0xFF时,OC0的输出为恒定的高电平;而OCR0的值为0x00时,OCR0的输出为恒定的低电平。

8位T/C0的计数工作时序

略。

硬件设计

在外部中断应用实例中,采用调用CVAVR中软件延时函数的方法给出了一个使用6个数码管组成的时钟系统。采用软件延时,时钟是不准确的,因为一旦系统中使用了中断,就可能打断延时程序的执行,使得延时时间发生变化。下面给出以外部振荡源为基准,采用外部中断方式实现的时钟系统的设计。
在AVR-51多功能实验开发板上,有一个采用CD4060和2.048MHz晶体构成的50%占空比、0~5V的标准方波振荡源。将其10个标准频率中500Hz的输出端与ATmega16的PD3引脚连接,作为外部输入信号。如果INT1采用下降沿方式触发中断,那么500次中断就是1s。此时,外部500Hz的振荡源就是时钟系统的计时基准,这样的时钟系统比使用软件延时的方法要准确得多。
时钟系统显示部分的硬件电路与动态扫描数码管图里的一样,只需要使用一根连线,将板上标准方波振荡源的500Hz输出端与ATmega16的PD3引脚连接在一起即可。

软件设计

实现程序如下:

/******-
File name            :demo_7_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>
flash unsigned char led_7[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
flash unsigned char position[6] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf};
unsigned char time[3];
unsigned char dis_buff[6];
int time_counter;
unsigned char posit;
bit point_on,time_1s_ok;
void display(void)
{
    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;
}

//外部中断INT1服务函数
interrupt [EXT_INT1] void ext_int1_isr(void)
{
    display();
    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;
    time[2] = 23;time[1]=58;time[0]=55;
    posit = 0;
    time_to_disbuffer();
    GICR |= 0x80;                //INT1中断允许
    MCUCR = 0x80;                //INT1下降沿触发
    GIFR = 0x80;                //清INT1中断标志
    #asm("sei")                    //全局中断允许
    
    while(1)
    {
        if (time_1s_ok)//1s到
        {
            time_1s_ok = 0;
            point_on = ~point_on;
            if (++time[0] >= 60)//调整时间
            {
                time[0] = 0;
                if (++time[1] >= 60)
                {
                    time[1] = 0;
                    if (++time[2] >= 24) time[2] = 0;
                }
            }
            time_to_disbuffer();//新调整好的时间送显示缓冲区
        }
    }
}

思考与实践

//略 应该不会摘抄上来