在上一章中我们学习了ATmega32单片机的定时/计数器0,对其结构和用法有了基本的了解。本章我们要学习的是ATmega32单片机片内的定时/计数器1,它具有比定时/计数器0更强大的功能,可以胜任精确的程序定时、波形生成以及信号测量等任务。
9.1
认识T/C1
ATmega32单片机片内的T/C1是16位的定时/计数器模块,具有2个独立的输出比较单元和1个输入捕捉单元,可以实现定时、对外部事件计数、PWM信号输出以及测量外部信号频率等功能。
9.1.1
T/C1的内部结构
T/C1的内部结构主要由计数器单元、两个独立的输出比较单元及输入捕捉单元构成,其内部结构如图9-1所示。
1.计数器单元
T/C1的计数单元核心部分是一个16位的计数器TCNT1,它既可以由系统时钟通过预分频器驱动,也可以由T1引脚输入的外部时钟信号驱动,时钟选择模块用于设定时钟的来源以及时钟的有效边沿。时钟选择模块输出的clk 2.输出比较单元
输出比较单元持续地将TCNT1的值与输出比较寄存器OCR1A和OCR1B的值作比较,一旦发现二者相等,比较器立即产生一个匹配信号,并在下一个定时器时钟到来时置位相应的标志位OCF1A或OCF1B,如果此中断得到允许,将引发输出比较中断。当中断执行后,标志位OCF1A或OCF1B自动硬件清零,也可以软件对这两位写1以清零标志位。
波形发生器用于在产生匹配时,按照软件的设置在输出比较引脚OC1A和OC1B上产生相应的电平逻辑信号。当T/C1工作在比较输出模式或PWM模式时,引脚OC1A和OC1B的功能由配置寄存器TCCR1A的COM1A1:0和COM1B1:0位使能,但引脚方向必须通过设定相应的数据方向寄存器将其设定为输出。
3.输入捕捉单元
T/C1的输入捕捉单元可以用来捕获外部事件,并为其赋予时间标记,以说明此事件的发生时刻或者两个间隔事件的时间关系。当T/C1工作在输入捕捉模式时,计数器TCNT1在输入时钟的驱动下持续计数。
当发生外部事件(ICP1引脚发生电平变化)时,触发信号由ICP1引脚进入可编程的噪声抑制器中。噪声抑制器对输入的触发信号进行4次采样,只有当4次采样的值全部相等时才将该信号送入边沿检测器中。噪声抑制器可以通过软件来使能,启用噪声抑制器使得从输入发生变化到捕捉单元动作存在4个系统时钟周期(CPU时钟)的延时。另外,模拟比较器产生的信号也可以作为捕捉功能的触发信号。
之后,触发信号送入边沿检测器,判定输入信号的有效性。一旦输入信号被判定为有效,输入捕捉即刻启动,持续计数的TCNT1内16位的数据被保存到输入捕捉寄存器ICR1中,同时输入捕捉标志位ICF1置位。如果此中断得到允许,单片机将产生输入捕捉中断并执行相应的中断服务程序。中断服务程序执行后,标志位ICF1自动清零,也可以通过软件向ICF1位写1来清零该位。另外,TCCR1A和TCCR1B寄存器用于对整个T/C1模块的功能设定和控制。
9.1.2
T/C1的工作模式
按照配置的不同,T/C1模块可以配置成以下几种模式。
1.普通模式(定时器或捕捉模式)
在此模式下,计数器TCNT1的值不停地累加,计数到最大值0xFFFF后,在下一个时钟到来时产生计数溢出,计数值返回到0x0000并重新开始计数。在计数器TCNT1为0x0000的同时,T/C1溢出标志位TOV1会置位,如果T/C1溢出中断得到使能,单片机将会执行中断服务程序,之后TOV1标志位会硬件清零。
在普通模式下,输入捕捉单元会被使能,如果ICP1引脚被配置成输入状态,ICP1引脚上电平的变化将会引发输入捕捉,当一个有效的捕捉产生后,计数器TCNT1的值会保存到输入捕捉寄存器ICR1中。
2.CTC模式(比较匹配清零定时器模式)
在此模式下,按照配置的不同,16位的输出比较寄存器OCR1A或输入捕捉寄存器ICR1用于保存此模式下的周期值。计数器TCNT1的值不断累加,当TCNT1的值等于OCR1A或ICR1时,二者产生匹配,随后计数器TCNT1的值会清零,并且相应的中断标志位OCF1A或ICF1会置位,如果此中断得到使能,即可执行中断服务程序。
当产生匹配时,如果引脚OC1A和OC1B的数据方向被设定为输出,两个引脚上均会产生电平变化,具体变化方式由T/C1的控制寄存器TCCR1A的COM1Ax位和COM1Bx位(x的值为1或0)设定。当COM1A1:0=1或COM1B1:0=1时,比较匹配时OC1A或OC1B引脚上会有电平取反变化,这种方式下的波形频率可由下式计算:
比较器输出频率=系统时钟/[2×N×(1+寄存器OCR1A值)]
在以上公式中,N为预分频因子,其值为1、8、64、256或1024。
3.快速PWM模式
快速PWM模式可以产生更高频率的PWM波形。在此模式下,计数器从BOTTOM计数到TOP,然后回到BOTTOM重新开始计数。快速PWM模式下TOP值可由以下三种方式定义。
方式1:TOP值可以通过WGM13:0位来设定,其值可以为0x00FF、0x01FF和0x03FF。而输出比较寄存器OCR1A和OCR1B则分别用来指定比较匹配的值。输出比较引脚OC1A可以定义成在计数器TCNT1与OCR1A匹配时置位,在达到TOP值时清零,当然也可以设定成在匹配时清零而在达到TOP值时置位。输出比较引脚OC1B的匹配值则由OCR1B指定,同样也可以定义其匹配或计数达到TOP时的电平变化方式。由于OCR1A和OCR1B的值可以不同,所以在OC1A和OC1B引脚上可以各自产生出占空比不同的PWM信号,但它们的基频是相同的(因为最大值已经固定)。
方式2:TOP值由输入捕捉寄存器ICR1来定义,输出比较寄存器OCR1A和OCR1B用来指定比较匹配的值,其引脚的输出情况与方式1相同。
方式3:TOP值由输出比较寄存器OCR1A来定义,而OCR1B用来指定比较匹配的值。在这种情况下,OC1B引脚可以正常输出PWM波,由于OCR1A寄存器用于定义TOP值而被占用,即比较单元OCR1A的TOP值与匹配值相同,所以引脚OC1A只能输出持续的高电平或低电平,具体由寄存器TCCR1A的COM1A1:0位控制,快速PWM模式的时序如图9-2所示。
在图9-2中,假定TOP值由OCR1A寄存器指定,比较匹配值由OCR1B指定。寄存器TCCR1A的COM1x1:0位(x可以是A或B)设置值为2,即比较匹配时清零引脚OC1A或OC1B,在计数达到TOP值时置位上述引脚。为了理解快速PWM的工作原理,我们结合以下关键点进行说明:
·A点:计数器TCNT1计数到达TOP值,其计数值随即清零,由OCR1A寄存器指定的TOP值会在此时更新,引脚OC1A或OC1B输出高电平。
·B点:输出比较寄存器OCR1B的值与计数器TCNT1的值产生匹配,引脚OC1B输出低电平。
·C点:计数器TCNT1计数到达TOP值,T/C1溢出标志位TOV1置位。如果TOP值是由寄存器OCR1A或ICR1定义的,则相应的中断标志位OCF1A或ICF1也将与TOV1在同一个时钟周期置位,如果中断使能,单片机即可执行中断服务程序。利用这一特点,可以在中断服务程序中更改TOP值以改变PWM的周期。
·D点:TCNT1计数器清零,引脚OC1A或OC1B输出高电平。
快速PWM模式下的频率可由下式计算:
PWM频率=系统时钟频率/[N×(1+TOP值)]
上式中,N为预分频因子,其值为1、8、64、256或1024。
在图9-2中,需要另外说明的是:
·快速PWM的周期1~8是不同的,其周期的改变可以通过改变TOP值的方法来实现,并且TOP值会在计数器TCNT1清零时得到更新。
·在快速PWM模式下,当产生比较匹配时,标志位OCF1A和OCF1B会置位,当TCNT1计数到TOP值后,T/C1溢出标志位TOV1也会置位。
注意:在T/C0的快速PWM模式下,计数器从BOTTOM计到MAX,输出比较寄存器OCR0保存匹配值,用于改变PWM的占空比。但T/C1就有所不同,在快速PWM模式下,计数器是从BOTTOM计到TOP,而TOP值可以为0x01FF等固定值,也可以由输出比较寄存器OCR1A或输入捕捉寄存器ICR1指定。这样,输出比较寄存器OCR1A或OCR1B则可用于保存匹配值,用于改变PWM的占空比。
4.相位修正PWM模式
相位修正PWM模式是基于双斜坡操作的,计数器TCNT1重复地从BOTTOM计数到TOP,然后再从TOP反向计数回到BOTTOM,具体时序如图9-3所示。
在图9-3所示的相位修正PWM模式中,同样假定由OCR1A指定TOP值,而由OCR1B指定匹配值,其工作方式如下:
·A点:计数器TCNT1达到TOP值,由OCR1A指定的新TOP值会在此时得到更新,这也是一个新PWM周期的开始(周期4)。
·B点:计数器TCNT1开始反向计数到OCR1B指定的匹配值,按照COM1x1:0位的设定,引脚OC1B输出高电平。
·C点:计数器TCNT1清零并重新开始正向计数。
·D点:计数器TCNT1正向计数到OCR1B指定的匹配值,引脚OC1B输出低电平。
·E点:计数器TCNT1正向计数到达TOP值,本次PWM周期结束。
5.相频修正PWM模式
与相位修正PWM模式类似,计数器重复地从BOTTOM计数到TOP,然后再从TOP反向计数回到BOTTOM,所不同的是在相位修正PWM模式下,更新TOP值在计数达到最大值(TOP)时,而相频修正PWM更新TOP值是在计数器清零(BOTTOM)时。相频修正PWM模式的时序如图9-4所示。
对比图9-3和图9-4我们不难发现,相位修正PWM模式下一个完整PWM周期是从计数器达到TOP值时开始算起的,而相频修正PWM模式的周期是在计数器到达最小值BOTTOM时算起的。在图9-4所示的相频修正PWM模式中,仍然假定由OCR1A指定TOP值,而由OCR1B指定匹配值,其工作方式如下:
·A点:计数器TCNT1的值反向计数到最小值,由OCR1A指定的TOP值会得到更新,一个新的PWM周期(周期2)开始。
·B点:计数器正向计数到达B点,TCNT1的值与输出比较寄存器OCR1B的值产生匹配,引脚OC1B输出低电平。
·C点:计数器TCNT1正向计数到TOP值,由于TOP值是由寄存器OCR1A定义的,所以中断标志位OCF1A会置位。如果TOP值由ICR1定义,则标志位ICF1会置位。
·D点:计数器TCNT1反向计数到达D点,产生二次匹配,引脚OC1B输出高电平。
·E点:计数器TCNT1反向计数到达最小值,计数器溢出标志位TOV1会在此时置位。
相频修正PWM模式的优点在于输出的波形在每一个PWM周期内都是对称的,确保了频率的正确性。
9.1.3
捕捉模式的应用
初学单片机的人对捕捉的功能往往不是十分理解。简单地说,捕捉功能主要是用于高精度地测量信号的频率或者脉冲的宽度。虽然我们可以通过使用外部中断的方法,配合定时器来对外部电平变化事件做时间的测量,但这与捕捉有本质的区别。在使用外部中断方式配合定时器进行脉冲宽度测量时,中断响应本身就会有延时,读取定时器又需要消耗时间,不可避免地会引入误差,而且这个误差有时是不可预见的。使用捕捉功能则可以最大限度地获取事件发生的实时时间值,当T/C1工作在捕捉模式时,在触发事件发生的同一时刻,连续计数的TCNT1中16位的计数值会一次性复制保存到ICR1寄存器中,在对时间值进行读取时,时间结果已经保存,不会影响测量的结果。捕捉功能可以对信号的频率进行精确测量,其分辨率由TCNT1的计数周期决定,最高精度可以达到一个时钟周期。
为了进一步说明捕捉的原理,我们假设一个周期为20000μs的方波由ICP1引脚输入,捕捉单元被设定为由上升沿触发,定时器预分频比为1∶1,而且方波的一个上升沿恰好于计数器TCNT1溢出时产生,这时捕捉的过程与TCNT1计数值的对应关系如图9-5所示。
16位的TCNT1计数范围是0~65535,一次事件的捕捉并不会使TCNT1复位归0。在捕捉的过程中,TCNT1是连续计数的,用户无需对它软件清零,只要将两次临近的捕捉时间值相减即可得到我们要的数据,但这也应当考虑到两次相减的数据中,在取值时没有发生计数溢出,这样的结果才是有效的。在图9-5中,计数溢出发生在第三次和第四次捕捉之间,所以对于第四次捕捉到的数据处理要格外小心。
9.2
T/C1的控制
9.2.1
T/C1的16位读写方式
我们以T/C1的计数器TCNT1为例来说明一下对16位寄存器的读写方式。
从图9-6中我们可以看出,16位的定时计数器TCNT1实际上是由两个8位的寄存器构成。低8位称为TCNT1L,高8位称为TCNT1H。其中低8位的寄存器TCNT1L可以被CPU直接访问,但高8位的寄存器TCNT1H不能被CPU直接访问。当CPU需要访问TCNT1H时,实际访问的是它的8位临时寄存器TEMP,具体的访问过程如下:
1)读TCNT1寄存器。当要读取寄存器TCNT1时,CPU要首先对TCNT1L进行读操作,该操作会将寄存器TCNT1H中的数据保存到TEMP寄存器中,之后再次读取TCNT1H寄存器,但实际上读回的数据是保存在TEMP寄存器中的内容。TCNT1的这种读写方式既可以确保CPU能够读取到精确的值,又可以避免计数器因进位等因素产生的误差。
2)写TCNT1寄存器。当写入TCNT1寄存器时,过程正好与读操作相反。CPU首先将数据写入TCNT1H中,这时写入的数据其实是保存在TEMP寄存器中,并没有真正更新TCNT1H寄存器。当CPU再次写入数据到TCNT1L时,写操作会触发单片机将TEMP寄存器中的内容一并写入TCNT1H中。
与TCNT1类似,寄存器OCR1A、OCR1B以及ICR1都是16位的,每个寄存器都是由相应的高8位和低8位两个寄存器组构成的,CPU在通过8位的数据总线访问这些寄存器时,都需要有临时寄存器TEMP的配合,其读写方式与TCNT1寄存器相同。这里需要特别说明的是,TEMP寄存器是所有T/C1的16位寄存器所共用的。在对这些16位的寄存器进行读写操作时,最好将系统的中断屏蔽,防止因使用两条指令对寄存器操作时产生中断而影响读写结果的正确性。
9.2.2
T/C1的控制寄存器
1.TCCR1A寄存器
该寄存器是T/C1的控制寄存器A,包含了多个T/C1的控制位。
TCCR1A寄存器:T/C1控制寄存器A bit 7:6COM1A1:COM1A0:通道A(OC1A引脚)的比较输出模式设定位,具体设置详见表9-1。
bit 5:4COM1B1:COM1B0:通道B(OC1B引脚)的比较输出模式设定位,具体设置详见表9-1。
bit 3FOC1A:通道A强制比较匹配控制位。该位置1时将强制OC1A引脚进入比较匹配的状态,引脚电平状态由COM1A1:COM1A0位设定。
bit 2FOC1B:通道B强制比较匹配控制位。该位置1时将强制OC1B引脚进入比较匹配的状态,引脚电平状态由COM1B1:COM1B0位设定。
bit 1:0WGM11:WGM10:波形产生模式设定位,这两位与TCCR1B寄存器的WGM13:WGM12位配合使用,用于设定T/C1的工作状态,具体设置详见表9-2。
注意:虽然引脚OC1A和OC1B的比较输出状态由寄存器TCCR1A的COM1A1:COM1B1和COM1A0:COM1B0位控制,但它同时也受引脚的方向寄存器DDRD的控制,使用时需要将相应位设定为输出,才能在OC1A和OC1B引脚上正常输出信号。
2.TCCR1B寄存器
该寄存器是T/C1的控制寄存器B,包含了多个比较匹配单元输出模式控制位和时钟源选择位。
TCCR1B寄存器:T/C1控制寄存器B bit 7ICNC1:输入捕捉噪声抑制器使能位。该位置1时使能输入捕捉的噪声抑制功能。
bit 6ICES1:输入捕捉触发沿选择位。该位置1时输入捕捉由上升沿触发,清零时输入捕捉由下降沿触发。
bit 5保留位。
bit 4:3WGM13:WGM12:波形产生模式设定位,这两位与TCCR1A寄存器的WGM11:WGM10位配合使用,用于设定T/C1的工作状态,具体设置详见表9-2。
bit 2:0CS12:CS10:T/C1时钟源选择位,用于选择T/C1的时钟源及设定预分频比,具体详见表9-3。
注意:当T/C1使用外部时钟源时,需要将T1引脚配置成输入。如果此时T1引脚被配置成输出,那么该引脚的输出电平变化仍会驱动T/C1计数,这一特性可以用于软件控制计数。
3.TCNT1H和TCNT1L寄存器
这两个寄存器组成了T/C1的数据寄存器TCNT1,用于保存定时或计数值。
TCNT1H寄存器:数据寄存器TCNT1的高字节
TCNT1L寄存器:数据寄存器TCNT1的低字节
4.OCR1AH和OCR1AL寄存器
这两个寄存器组成了T/C1的输出比较寄存器OCR1A。该寄存器中的16位数据与TCNT1寄存器中的计数值进行比较,一旦数据匹配,将产生一个输出比较中断,或改变OC1x引脚的电平。
OCR1AH寄存器:输出比较寄存器OCR1A的高字节
OCR1AL寄存器:输出比较寄存器OCR1A的低字节
5.OCR1BH和OCR1BL寄存器
这两个寄存器组成了T/C1的输出比较寄存器OCR1B。同样,该寄存器中的16位数据与TCNT1寄存器中的计数值进行连续的比较,一旦数据匹配,将产生一个输出比较中断,或改变OC1x引脚的电平。
OCR1BH寄存器:输出比较寄存器OCR1B的高字节
OCR1BL寄存器:输出比较寄存器OCR1B的低字节
6.ICR1H和ICR1L寄存器
这两个寄存器组成了T/C1的输入捕捉寄存器ICR1。当ICP1引脚有输入捕捉触发信号产生时,计数器TCNT1中的值会写入ICR1寄存器中,另外ICR1的设定值还可以作为计数器的TOP值。
ICR1H寄存器:输入捕捉寄存器ICR1的高字节
ICR1L寄存器:输入捕捉寄存器ICR1的低字节
7.TIMSK寄存器
该寄存器是定时/计数器的中断屏蔽寄存器,该寄存器包含了多个中断控制位。
TIMSK寄存器:T/C中断屏蔽寄存器
bit 7-6暂不介绍。
bit 5TICIE1:T/C1输入捕捉中断使能位。该位置1时,T/C1输入捕捉中断使能。
bit 4OCIE1A:T/C1输出比较A匹配中断使能位。该位置1时,T/C1输出比较A匹配中断使能。
bit 3OCIE1B:T/C1输出比较B匹配中断使能位。该位置1时,T/C1输出比较B匹配中断使能。
bit 2TOIE1:T/C1溢出中断使能位。该位置1时,T/C1溢出中断使能。
bit 1-0暂不介绍。
8.TIFR寄存器
该寄存器是T/C的中断标志寄存器,包含了多个T/C的中断标志位。
TIFR寄存器:T/C中断标志寄存器
bit 7-6:暂不介绍。
bit 5ICF1:T/C1输入捕捉标志位。当外部引脚ICP1出现捕捉事件时ICF1位置1,当ICR1作为计数器的TOP值时,一旦计数器达到TOP,ICF1也会置1。中断服务程序执行后,该位自动清零。软件对该位写1会清零此标志位。
bit 4OCF1A:T/C1输出比较A匹配标志位。当TCNT1与OCR1A匹配时该位置1,中断服务程序被执行后,该位自动清零。软件执行强制输出比较时(将TCCR1A寄存器的FOC1A位置1)此标志位不会置1。软件对该位写1会清零此标志位。
bit 3OCF1B:T/C1输出比较B匹配标志位。当TCNT1与OCR1B匹配时该位置1。中断服务程序执行后,该位自动清零。软件执行强制输出比较时(将TCCR1A寄存器的FOC1B位置1)此标志位不会置1。软件对该位写1会清零此标志位。
bit 2TOV1:T/C1溢出标志位。T/C1溢出时该标志位置1,当此中断服务程序被执行后,该位自动清零。软件对该位写1会清零此标志位。
bit 1-0暂不介绍。
9.3
T/C1的编程应用
T/C1是AVR单片机中比较复杂的模块之一,也是本书学习的难点。花时间仔细领会T/C1的功能会让你更好地理解AVR单片机的精妙所在,为此我们需要通过几个实例来掌握其功能。
9.3.1
基于T/C1的秒计时器
将T/C1设置成普通模式并使其产生时基信号,可以编写出一个秒计时器。打开Atmel Studio 6.1软件,新建名为TC1的项目并自动添加名为TC1.c的源文件,修改这个源文件,具体代码详见代码清单9-1。
代码清单9-1
基于T/C1的秒计时器
/*
*
TC1.c *
T/C1
秒计时器
*
Created: 2013/8/29
19:54:04
*
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 int MIN,SEC,MS; //
定义全局变量分,秒,毫秒
unsigned char RUN; //
定义运行标志位
void TC1_init(void); //T/C1
初始化函数声明
void key_init(void); //
按键初始化函数声明
void key_scan(void); //
按键扫描函数声明
void display(void); //
数码管显示函数声明
/**********
主函数**********/
int main(void)
{
DDRA=0xFF; //
设数码管段驱动端为输出
DDRB=0xF0; //
设数码管位驱动端为输出
key_init(); TC1_init(); while(1)
{
display(); //
显示时间
key_scan(); //
扫描按键
}
}
/**********
数码管显示函数**********/
void display(void)
{
unsigned char NUM4,NUM3,NUM2,NUM1; NUM1=SEC%10; NUM2=SEC%100/10; NUM3=MIN%10; NUM4=MIN%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 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 }
/**********
按键初始化函数**********/
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
按下
{
RUN=1; //
时钟运行
}
if(KEYNUM==0x01)
//S2
按下
{
RUN=0; //
时钟停止
}
}
/**********T/C1
初始化函数**********/
void TC1_init(void)
{
SREG=0x80; //
开全局中断
TIMSK=0x04; //
开T/C1
溢出中断
TCNT1H=(65536-16000)/256; //16000*0.0625us=1ms TCNT1L=(65536-16000)%256; TCCR1A=0x00; TCCR1B=0x01; //T/C1
无预分频
}
/**********T/C1
溢出中断服务函数**********/
ISR(TIMER1_OVF_vect)
{
TCNT1H=(65536-16000)/256; //16000*0.0625us=1ms TCNT1L=(65536-16000)%256; if(RUN==1)
{
MS++; }
if(MS==1000)
{
MS=0; SEC++; }
if(SEC>=60)
{
SEC=0; MIN++; }
if(MIN>=90)
{
RUN=0; //
时钟停止
}
}
/**********
结束**********/
程序经编译后下载到AVR系统板中,运行后如图9-7所示。按下按键S1,秒计时器开始计时,按下按键S2,秒计时器停止,按复位键秒计时器清零。
9.3.2
基于T/C1的2路快速PWM调光
将T/C1设置成快速PWM工作模式,使用ICR1寄存器指定TOP值,用OCR1A和OCR1B分别指定匹配值,这样可以在OC1A和OC1B引脚上得到两路不同占空比的PWM输出,这也是T/C1区别于其他两个定时器的特点之一。
在AVR系统板上,使用杜邦线将两个LED流水灯(LED1、LED2)的驱动端由PC0、PC1端跳接到OC1A(PD5)和OC1B(PD4)端上,让两路PWM信号分别驱动两个流水灯。在Atmel Studio 6.1中,新建名为T1PWM2的项目,并自动添加T1PWM2.c源文件,具体代码详见代码清单9-2。
代码清单9-2
T/C1的2路快速PWM /*
*
T1PWM2.c *
T/C1
的2
路快速PWM *
Created: 2013/9/4
21:51:53
*
Author: GAO */
/*
T/C1
的2
路快速PWM,ICR1
指定TOP 值,OCR1A 和OCR1B 指定匹配值
匹配时清零OC1A/B 引脚, 晶振16M,1:1
预分频,1000*0.0625
=62.5um,PWM 频率16kHz,OC1A 和
OC1B 分别输出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 PWM1,PWM2; //
定义变量保存PWM 周期1
,周期2
unsigned char DISP; //
定义显示状态标志位
void display(unsigned int NUM); //
数码管显示函数声明
void TC1_init(void); //T/C1
初始化函数声明
void pwm_init(void); //PWM 状态初始化函数声明
void key_init(void); //
按键初始化函数声明
void key_scan(void); //
按键扫描函数声明
/**********
主函数**********/
int main(void)
{
DDRA=0xFF; //
设数码管段驱动端为输出
DDRB=0xF0; //
设数码管位驱动端为输出
key_init(); TC1_init(); pwm_init(); while(1)
{
if(DISP==1)
{
display(PWM1); //
显示PWM1
的值
}
else {
display(PWM2); //
显示PWM2
的值
}
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/C1
初始化函数**********/
void TC1_init(void)
{
TCCR1A=0xA2; //
快速PWM 模式,匹配时清零OC1A/B 引脚,ICR1
指定TOP 值
TCCR1B=0x19; //OCR1A 和OCR1B 分别指定匹配值,1:1
预分频
}
/**********
按键初始化函数**********/
void key_init(void)
{
DDRD=0xF0; //
将PORTD 低四位设为输入
PORTD=0x03; //
开PD0
、PD1
端口上拉
}
/**********
按键扫描函数**********/
void key_scan(void)
{
unsigned char KEYNUM; KEYNUM=PIND; //
读PORTD 端口
KEYNUM=KEYNUM&0x03; //
保留PORTD 低两位
if(KEYNUM==0x02)
//S1
按下
{
DISP=1; //
显示PWM1
的值
PWM1++; if(PWM1>=1000)
{
PWM1=1; }
OCR1AH=PWM1/256; OCR1AL=PWM1%256; }
if(KEYNUM==0x01)
//S2
按下
{
DISP=0; //
显示PWM2
的值
PWM2++; if(PWM2>=1000)
{
PWM2=1; }
OCR1BH=PWM2/256; OCR1BL=PWM2%256; }
}
/**********PWM 状态初始化函数**********/
void pwm_init(void)
//
开机后写入基础PWM 值
{
ICR1H=999/256; //1000*0.0625
=62.5um ,PWM 频率16kHz ICR1L=999%256; PWM1=99; //
给PWM1
赋初值,占空比10%
PWM2=799; //
给PWM2
赋初值,占空比80%
OCR1AH=PWM1/256; //
将初值写入比较匹配寄存器A OCR1AL=PWM1%256; OCR1BH=PWM2/256; //
将初值写入比较匹配寄存器B OCR1BL=PWM2%256; }
/**********
结束**********/
程序经编译后下载到AVR系统板中,运行后如图9-8所示。两个LED灯会呈现出不同的亮度,这是因为两路PWM输出的占空比初始设置不同所致。按动两个按键,可以分别调整两路PWM输出的占空比,进而改变两个LED灯的亮度,数码管会分别显示两路PWM信号的占空比。
9.3.3
基于T/C1的频率计
利用T/C1的捕捉功能可以设计出高精度的频率计,用于信号频率的测量。在AVR系统板上,使用单片机的PD0引脚输出一个固定频率的方波信号,用杜邦线将PD0输出的信号导入到ICP(PD6)引脚中,用于T/C1捕捉并测量其信号的频率。在Atmel Studio 6.1中,新建名为CAPTURE1的项目,项目会自动添加名为CAPTURE1.c的源文件,具体代码详见代码清单9-3。
代码清单9-3
基于T/C1的频率计
/*
*
CAPTURE1.c *
基于T/C1
的频率计
*
Created: 2013/9/5
23:20:32
*
Author: GAO */
/*
PD0
输出振荡信号,ICP(PD6)
捕捉
晶振16M, 时钟周期0.0625us, T/C1
预分频比1024
计数器每计一次数耗时为:1024*0.0625
=64us */
#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 PERI,PERI1,PERI2; //
定义周期,奇次周期,偶次周期变量
unsigned char INTNUM,INTTYPE,PERIL,PERIH; //
定义中断次数,中断类型,周期高字节,周期低字节变量
void display(unsigned int NUM); //
显示函数声明
void TC1_init(void); //T/C1
初始化函数声明
#define SETPD0
(PORTD=PORTD|0x01)
//
置位PD0
#define CLRPD0
(PORTD=PORTD&0xFE)
//
清零PD0
/**********
主函数**********/
int main(void)
{
DDRA=0xFF; //
设数码管段驱动端为输出
DDRB=0xF0; //
设数码管位驱动端为输出
DDRD=0x01; //
设定PD0
引脚为输出,ICP 引脚为输入
TC1_init(); while(1)
{
display(PERI); //
显示周期值
SETPD0; //41Hz 频率发生器(周期24ms )
_delay_ms(12); //
延时12ms CLRPD0; //41Hz 频率发生器(周期24ms )
}
}
/**********
数码管显示函数**********/
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=table1[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/C1
初始化函数**********/
void TC1_init(void)
{
SREG=0x80; //
开全局中断
TIMSK=0x20; //
开T/C1
捕捉中断、关溢出中断
TCCR1A=0x00; //
T/C1
普通模式
TCCR1B=0xC5; //
开噪声抑制、上升沿捕捉、1024
预分频
}
/**********T/C1
捕捉中断服务函数**********/
ISR(TIMER1_CAPT_vect)
{
INTTYPE=TIFR&0x04; //
取出TOV1
位,判断T/C1
是否溢出
INTNUM++; //
记录中断次数
if(((INTNUM%2)==1)&&(INTTYPE==0x00))
//
产生奇次捕捉且T/C1
无溢出
{
PERIL=ICR1L; //
将CCPR1L 寄存器的值赋给变量FREQL PERIH=ICR1H; //
将CCPR1H 寄存器的值赋给变量FREQH PERI1=(PERIH<<8)|PERIL; //
将这两个变量合成一个FREQ1
}
if(((INTNUM%2)==0)&&(INTTYPE==0x00))
//
产生偶次捕捉且T/C1
无溢出
{
PERIL=ICR1L; //
将CCPR1L 寄存器的值赋给变量FREQL PERIH=ICR1H; //
将CCPR1H 寄存器的值赋给变量FREQH PERI2=(PERIH<<8)|PERIL; //
将这两个变量合成一个FREQ2
PERI=PERI2-PERI1; //
两次捕捉的值相减得出周期值:375
PERI=PERI*64/100; //
转换成毫秒并保留一位小数
}
if(INTTYPE==0x04)
//
产生T/C1
溢出
{
TIFR=TIFR|0x04; //
向TOV1
位写1
清零此标志位
}
}
/**********
结束**********/
将以上代码编译后烧写到AVR系统板中,程序运行后如图9-9所示。数码管显示值为:024.0,表示当前捕捉到的方波周期是24.0ms,修改主函数中的延时时间可以改变此周期值。
通过以上三个程序的编写,相信你已经对ATmega32单片机的T/C1模块有了更加深入的理解。灵活的使用T/C1模块,不仅可以使你获得更长的计时时间、更高分辨率的双路占空比可调PWM波,还可以精确地测量外部信号的频率,这些在单片机的应用中都占有举足轻重的作用。如果你现在已经能够熟练地驾驭T/C1模块了,那么恭喜你,你已经跨越了一个AVR单片机的学习难关,接下来的内容会变得更加得有趣和轻松。