第11讲 定时/计数器0

单片机在片内集成定时/计数器,使其既可以精确地测量时间,又可以对外部脉冲进行计数。定时/计数器从电路本质上来讲就是一个脉冲计数器,当计数脉冲来自于单片机内部时钟时,我们习惯称其为定时器,而当计数脉冲来自于片外时,则称其为计数器。本章主要介绍的是ATmega32单片机定时/计数器0的结构和使用方法。

8.1

认识T/C0

ATmega32单片机片内有三个定时/计数器,分别称为T/C0、T/C1和T/C2,与其他单片机相比,AVR单片机定时/计数器的功能则更加强大,它不仅可以完成定时和计数功能,还具有数字捕捉、比较和PWM功能。T/C0是一个8位的定时/计数器模块,具有10位的时钟预分频器,计数溢出和比较匹配时可以触发中断。

8.1.1

T/C0的内部结构

T/C0的内部是由计数器单元和输出比较单元两部分构成的,其结构如图8-1所示。

1.计数器单元

T/C0的核心部分是一个可编程8位双向计数单元TCNT0,它既可以由系统时钟通过预分频器驱动,也可以由T0引脚输入的外部时钟信号驱动,时钟选择模块用于设定时钟的来源以及时钟的有效边沿。

时钟选择模块输出的clk 注意:T/C0没有专用的启动和停止位,在时钟选项中时钟选择为无时钟源时,计数器会停止工作;当时钟选择为无预分频时,系统时钟直接驱动计数器计数,在每个时钟周期,计数器TCNT0的值都会加1。例如,当使用16M晶体作为时钟源时,计数器TCNT0每计一个数需要的时间是1/16MHz=0.0625μs。

2.输出比较单元

输出比较单元用于持续地将计数器TCNT0的值与输出比较寄存器OCR0的值作比较,一旦二者相等,比较器立即产生一个匹配信号,并在下一个定时器时钟到来时置位OCF0标志位,如果此中断得到允许,将会引发输出比较中断。中断服务程序执行后,标志位OCF0硬件清零,软件对OCF0位写1可以清零此标志位。

波形发生器用于在产生匹配时,按照软件设定的方式在输出比较引脚OC0上产生相应的电平逻辑信号。当T/C0工作在比较输出模式或PWM模式时,引脚OC0的功能由配置寄存器使能(由TCCR0寄存器的COM01:COM00位控制),但引脚的方向必须通过数据方向寄存器将其设定为输出。

8.1.2

认识PWM PWM是Pulse Width Modulation的缩写,中文意思是脉冲宽度调制。PWM提供了一种用数字方式产生模拟信号的方法,用于控制模拟器件的运行。PWM的应用非常广泛,通过PWM方式可以对电灯调光、对电机调速或者是产生连续可调的电压值等。要想用好PWM功能,就要首先明确几个与PWM相关的概念:

·周期:一个完整的PWM波形所占用的时间。

·频率:1秒钟内PWM波形重复的次数。如果周期是T,那么频率就是1/T。

·占空比:在输出的PWM波形中,高电平保持的时间与该PWM周期的时间之比。

·分辨率:它表示PWM信号的控制精度,即占空比的最小数值。

PWM的占空比原理如图8-2所示。当PWM信号的频率是1kHz时,其周期为1000μs。在一个PWM周期中,如果高电平出现的时间是200μs,那么占空比就是200/1000,即为20%。

在一个高电平为5V的PWM信号中,如果将一个PWM波形10等分,那么它的最小占空比是1/10,即分辨率是1/10,当其工作在最小占空比时,其输出的电压相当于0.5V;如果我们将这个PWM波形100等分,那么它的最小占空比是1/100,即分辨率是1/100,当其同样工作在最小占空比时,其输出的电压相当于0.05V。由此可见,PWM信号的分辨率越高,其输出模拟信号的精度也越高,通过控制PWM信号的占空比,即可改变其输出的等效电压,从而实现使用数字信号对模拟器件的控制功能。

8.1.3

T/C0的工作模式

按照配置的不同,T/C0可以配置成以下几种模式。

1.普通模式(定时器模式)

普通模式是最简单的工作模式。在此模式下,计数器TCNT0的值不停累加,计数到最大值0xFF后,在下一个时钟到来时产生计数溢出,计数值返回到0x00并重新开始计数。当计数器TCNT0的值为0x00时,T/C0溢出标志位TOV0置位。如果T/C0溢出中断得到使能,单片机会执行相应的中断服务程序,中断服务程序执行后,TOV0标志位会硬件清零。在普通模式下,可以随时软件写入寄存器TCNT0。

2.CTC模式(比较匹配时清零定时器模式)

在此模式下,输出比较寄存器OCR0用于保存匹配值,计数器TCNT0的值持续累加,当TCNT0的值等于OCR0时,二者产生匹配,随后计数器TCNT0自动清零,中断标志位OCF0置位,如果此中断得到使能,即可执行相应的中断服务程序。

如果引脚OC0的数据方向被设定为输出,引脚上将会产生电平变化,具体变化方式由T/C0控制寄存器TCCR0的COM01:COM00位设定。为了使OC0引脚能输出正确的波形,通常的做法是将引脚电平变化的方式设定为比较匹配时电平取反,这时其产生的波形频率可由下式计算:

比较器输出频率=系统时钟/[2×N×(1+寄存器OCR0值)]

在上面的公式中,N为预分频因子,其值为1、8、64、256或1024。

3.快速PWM模式

快速PWM模式是基于单斜坡操作的,这有助于产生更高频率的PWM波形。在此模式下,计数器从BOTTOM开始计数到MAX,然后立即返回到BOTTOM重新开始计数。关于计数器的MAX、TOP和BOTTOM值的定义如图8-3所示。

掌握计数器的空间定义对于理解AVR单片机定时/计数器功能十分重要。当计数器为空时,其值为BOTTOM,随着计数值的增长,达到某一个约定值时,称其为TOP,当计数值达到计数器的最大容量时,称其为MAX。在T/C0的快速PWM模式下,计数器TCNT0的值从BOTTOM开始递增,当其值与OCR0寄存器所指定的TOP值匹配时,引脚OC0上的电平将会按照TCCR0寄存器COM01:COM00位所设定的方式动作。快速PWM模式的时序如图8-4所示。

在图8-4所示的快速PWM方式下,由于计数器TCNT0的计数值是从BOTTOM计到MAX,所以PWM的周期是一个固定的值,另外图中还有以下关键点需要说明:

·A点:计数器的值为最小值BOTTOM,此时OC0引脚输出高电平。

·B点:当计数器TCNT0的值不断累加到与OCR0的值匹配时,OC0引脚电平翻转,输出低电平,并且将输出比较中断标志位OCF0置位。

·C点:计数器TCNT0的计数值达到最大值MAX,T/C0的溢出标志位TOV0置位,此时OCR0寄存器中保存的值会进行一次更新,此更新可以用于改变PWM波形的占空比。

·D点:计数器TCNT0由于溢出而清零到BOTTOM,OC0引脚电平翻转,输出高电平。

快速PWM模式下的输出频率可由下式计算:

PWM频率=系统时钟频率/(N×256)

在上面的公式中,N为预分频因子,其值为1、8、64、256或1024。

另外,在快速PWM模式下,通过将引脚OC0设定为在比较匹配时电平取反(COM01:0=1),可以得到占空比为50%的PWM信号。

4.相位修正PWM模式

相位修正PWM模式是基于双斜坡操作的。在此模式下,计数器TCNT0正向地从BOTTOM计数到MAX,然后再从MAX倒退计数到BOTTOM,其时序如图8-5所示。

与快速PWM模式不同的是,相位修正PWM模式的周期是由TCNT0的两次MAX计数周期组合而成的,其时序的关键点如下:

·A点:计数器TCNT0的值已经达到MAX,此时上一个PWM周期结束,新的PWM周期开始,OC0引脚电平翻转,按照COM01:0位的设定输出低电平。比较匹配寄存器OCR0的值得到更新,这个更新用于改变PWM波形的占空比。

·B点:计数器TCNT0反向计数到OCR0指定的匹配值,这时输出比较标志位OCF0置位,按照COM01:0位的设定,引脚OC0电平再次翻转,输出高电平。

·C点:计数器TCNT0清零并重新开始正向计数,计数器溢出中断标志位TOV0置位。

·D点:计数器TCNT0正向计数到OCR0指定的匹配值,引脚OC0输出低电平,这时输出比较标志位OCF0置位。

·E点:计数器TCNT0正向计数到达最大值MAX,引脚OC0电平翻转,比较匹配寄存器OCR0的值再次得到更新,本次PWM周期结束。

与快速PWM模式的单斜坡操作相比,双斜坡的相位修正PWM所获得的最大频率比快速PWM模式要小,但其波形对称性很好,非常适合于电机的控制。相位修正PWM方式的输出频率可由下式计算:

PWM频率=系统时钟频率/(N×510)

8.2

T/C0的控制

8.2.1

T/C0的相关寄存器

1.TCCR0寄存器

该寄存器是T/C0的控制寄存器,包含了多个T/C0的工作模式和比较匹配单元输出模式控制位。

TCCR0寄存器:T/C0控制寄存器

bit 7FOC0:强制输出比较控制位。该位置1时将强制OC0引脚进入比较匹配状态,具体工作方式由COM01:COM00位设定。强制输出比较匹配状态不会引发中断,并且只在非PWM模式时有效,在PWM模式下,不可将该位置1。

bit 6,3WGM01:WGM00:波形发生模式设定位,用于设定T/C0的工作状态,具体详见表8-1。

bit 5:4COM01:COM00:OC0引脚比较匹配输出模式设定位,具体详见表8-2。

bit 2:0CS02:CS00:T/C0时钟源选择位,用于选择T/C0的时钟源或设定预分频比,具体详见表8-3。

注意:当T/C0使用外部时钟源时,需要将T0引脚配置成输入。如果此时T0引脚被配置成输出,那么该引脚的输出电平变化会驱动T/C0计数,这一特性可以用于软件控制计数。另外,虽然引脚OC0的比较输出状态由寄存器TCCR0的COM01:COM00位控制,但它也同时受引脚方向寄存器的控制,使用时需要将相应位设定为输出,才能在OC0引脚上得到正确的信号。

2.TCNT0寄存器

该寄存器是T/C0的数据寄存器,用于保存定时或计数数据,TCNT0寄存器可以随时读写。

TCNT0寄存器:T/C0的数据寄存器

3.OCR0寄存器

该寄存器是T/C0的输出比较寄存器,用于保存匹配值。当其值与TCNT0的计数值产生匹配时,将产生输出比较中断,并且可以改变OC0引脚的电平状态。

OCR0寄存器:输出比较寄存器

4.TIMSK寄存器

该寄存器是T/C(定时/计数器)的中断屏蔽寄存器,该寄存器包含了多个中断控制位。

TIMSK寄存器:T/C中断屏蔽寄存器

bit 7-bit 2暂不介绍。

bit 1OCIE0:T/C0输出比较匹配中断使能位。该位置1时,T/C0输出比较匹配中断使能。

bit 0TOIE0:T/C0溢出中断使能位。该位置1时,T/C0溢出中断使能。

5.TIFR寄存器

该寄存器是T/C(定时/计数器)的中断标志寄存器,该寄存器包含了多个中断标志位。

TIFR寄存器:T/C中断标志寄存器

bit 7-2:暂不介绍。

bit 1OCF0:T/C0输出比较匹配标志位。当TCNT0与OCR0匹配时该位置1。当此中断服务程序被执行后,该位自动清零。另外软件对该位写1会清零此标志位。

bit 0TOV0:T/C0溢出标志位。T/C0溢出时该标志位置1,当此中断服务程序被执行后,该位自动清零。软件对该位写1会清零此标志位。

8.2.2

T/C0的预分频器

ATmega32单片机的T/C0模块与T/C1模块共用一个预分频器,但是两个模块可以有不同的分频比设置,预分频器的结构如图8-6所示。

系统时钟clkI/O经过10位的T/C预分频器后,输出4种不同频率的时钟信号,分别是clk SFIOR寄存器。该寄存器是特殊功能IO寄存器,包含了多个A/D转换、模拟比较器等功能控制位。

SFIOR寄存器:特殊功能I/O寄存器

bit 7-1:暂不介绍。

bit 0PSR10:T/C0与T/C1的预分频器复位控制位。该位置1时T/C0与T/C1的预分频器复位,操作完成后该位硬件清零。

由于T/C0与T/C1共用同一个预分频器,因此当预分频器复位后,对这两个定时器都会有影响,这一特性也可以用于同步两个定时器的运行。

8.3

T/C0的编程应用

8.3.1

基于T/C0的时钟

使用T/C0产生时基信号驱动数码管,制作一款数显电子钟,能帮助我们学习和理解定时器的功能。打开Atmel Studio 6.1软件,新建名为“TC0”的项目文件,保存在chapter8文件夹下,软件会将名为TC0.c的源文件自动添加到新建的项目中。编辑TC0.c源文件,具体代码详见代码清单8-1。

代码清单8-1

基于T/C0的时钟

/*

*

TC0.c *

T/C0

时钟

*

Created: 2013/8/26

21:06:44

*

Author: GAO */

#include 
//
包含AVR 头文件
#include 
//
包含AVR 中断控制头文件
#define F_CPU 16000000UL //
定义系统时钟
#include 
//
包含延时函数头文件
unsigned char table0[]={0x3f,0x06,0x5b,0x4f,0x66, 0x6d,0x7d,0x07,0x7f,0x6f }; //
共阴无点
unsigned char table1[]={0xbf,0x86,0xdb,0xcf,0xe6, 0xed,0xfd,0x87,0xff,0xef }; //
共阴有点
unsigned char TEMP; //
定义临时变量
unsigned char SEC,MIN,HOU; //
定义秒、分、时变量
void display(void); //
数码管显示函数声明
void TC0_init(void); //
定时器0
初始化函数声明
int main(void)
{
DDRA=0xFF; //
设数码管段驱动端为输出
DDRB=0xF0; //
设数码管位驱动端为输出
TC0_init(); //
定时器0
初始化
HOU=12; //
时钟初始时间12:00
while(1)
{
display(); }
}
/**********
数码管显示函数**********/
void display(void)
{
unsigned char NUM4,NUM3,NUM2,NUM1; NUM1=MIN%10; NUM2=MIN%100/10; NUM3=HOU%10; NUM4=HOU%100/10; PORTA=table0[NUM1]; PORTB=0x10; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM2]; PORTB=0x20; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms if(SEC%2==1)
//
点闪烁
{
PORTA=table0[NUM3]; PORTB=0x40; _delay_ms(2); //
延时2ms }
else {
PORTA=table1[NUM3]; PORTB=0x40; _delay_ms(2); //
延时2ms }
PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM4]; PORTB=0x80; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms }
/**********T/C0
初始化函数**********/
void TC0_init(void)
{
SREG=0x80; //
开全局中断
TIMSK=0x01; //
开T/C0
溢出中断
TCNT0=(256-157); //
初值99
(157*0.0625us*1024=10.048ms )
TCCR0=0x05; //T/C0
预分频比1/1024
}
/**********T/C0
中断服务函数**********/
ISR(TIMER0_OVF_vect)
{
TCNT0=(256-157); //
初值99
(157*0.0625us*1024=10.048ms )
TEMP++; //10ms 中断产生并自加一次
if(TEMP==100)
{
TEMP=0; SEC++; //
秒自加
}
if(SEC>=60)
{
SEC=0; MIN++; //
分自加
}
if(MIN>=60)
{
MIN=0; HOU++; //
时自加
}
if(HOU>=24)
{
HOU=0; }
}
/**********
结束**********/
将以上代码编译后,下载到AVR系统板中。程序运行后如图8-7所示。
图中数码管显示当前时间为“12.00”,时与分之间的小数点以秒为周期闪烁,显示时钟正在运行。在此程序的基础上,你只要稍加修改,就可以将其扩充成可调时、有闹钟的真正意义上的时钟了。
8.3.2
基于T/C0的PWM调光灯
T/C0可以产生占空比可调的PWM信号,用它来驱动LED灯时,改变PWM信号的占空比,就可以调节LED灯的亮度。实现的方法也很简单,在AVR系统板上,使用杜邦线将OC0引脚连接至LAMP的驱动端(J1),打开Atmel Studio 6.1软件,新建名为“TC0PWM”的项目,同样保存在chapter8文件夹下,软件会自动将名为TC0PWM.c的源文件添加到新建的项目中。编辑TC0PWM.c源文件,具体代码见代码清单8-2。
代码清单8-2
基于T/C0的PWM调光灯
/*
*
TC0PWM.c *
T/C0
PWM 调光
*
Created: 2013/8/27
21:00:11
*
Author: GAO */
/*OC0
输出PWM 驱动LED 、按键连接于PD0
、PD1
开端口上拉*/
#include 
//
包含AVR 头文件
#include 
//
包含AVR 中断控制头文件
#define F_CPU 16000000UL //
定义系统时钟
#include 
//
包含延时函数头文件
unsigned char table0[]={0x3f,0x06,0x5b,0x4f,0x66, 0x6d,0x7d,0x07,0x7f,0x6f }; //
共阴无点
unsigned char table1[]={0xbf,0x86,0xdb,0xcf,0xe6, 0xed,0xfd,0x87,0xff,0xef }; //
共阴有点
unsigned int PWM; //
定义变量保存PWM 占空比
void display(unsigned int NUM); //
数码管显示函数声明
void TC0_init(void); //T/C0
初始化函数声明
void pwm_init(void); //PWM 输出初始化函数声明
void key_init(void); //
按键初始化函数声明
void key_scan(void); //
按键扫描函数声明
/**********
主函数**********/
int main(void)
{
DDRA=0xFF; //
设数码管段驱动端为输出
DDRB=0xF0; //
设数码管位驱动端为输出
DDRB|=0x08; //
置OC0
为输出
key_init(); TC0_init(); pwm_init(); while(1)
{
display(PWM); //
显示PWM 占空比的值
key_scan(); //
扫描按键
}
}
/**********
数码管显示函数**********/
void display(unsigned int NUM)
{
unsigned char NUM4,NUM3,NUM2,NUM1; NUM1=NUM%10; NUM2=NUM%100/10; NUM3=NUM%1000/100; NUM4=NUM/1000; PORTA=table0[NUM1]; PORTB=0x10; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM2]; PORTB=0x20; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM3]; PORTB=0x40; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM4]; PORTB=0x80; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms }
/**********T/C0
初始化函数**********/
void TC0_init(void)
{
TCCR0=0x61; //
相位修正PWM 模式(升序匹配时清零OC0
引脚,1:1
预分频)
OCR0=128; //OCR0
指定匹配值(PWM 频率=16M/1*510=31kHz )
}
/**********
按键初始化函数**********/
void key_init(void)
{
DDRD=0x00; //
将PORTD 设为输入
PORTD=0x03; //
开PD0
、PD1
端口上拉
}
/**********
按键扫描函数**********/
void key_scan(void)
{
unsigned char KEYNUM; KEYNUM=PIND; //
读PORTD 端口
KEYNUM=KEYNUM&0x03; //
保留PORTD 低两位
if(KEYNUM==0x02)
//S1
按下
{
PWM++; if(PWM>=255)
{
PWM=255; }
OCR0=PWM; //
将占空比值赋予OCR0
}
if(KEYNUM==0x01)
//S2
按下
{
PWM--; if(PWM<=1)
{
PWM=1; }
OCR0=PWM; //
将占空比值赋予OCR0
}
}
/**********PWM 输出初始化函数**********/
void pwm_init(void)
{
PWM=128; //
初始占空比50%
}
/**********
结束**********/
程序编写完毕经编译后烧写到系统板上的ATmega32单片机中,程序运行后如图8-8所示。
数码管初始显示为“128”,表明当前PWM的占空比为128/256,即50%。按下按键S1,占空比会增加,按下按键S2,占空比会减小,白色的LED灯会随着占空比的变化而改变亮度。如果你对此有足够的兴趣,你可以做些更加复杂的应用尝试,比如给直流电机调速、改变可控硅的导通角等。总之,理解PWM的意义会给你以后的研发工作带来很大帮助。

 


评论:

AVR单片机基础教程

作者:高显生   共18讲

AVR系列单片机是8位单片机中第一个真正的RISC结构单片机,它采用了大型快速存取寄存器组、快速的单周期指令系统以及单级流水线等诸多先进技术,使得AVR单片机具备了高达1MIPS/MHz的运行处理能力。