前面我们利用纯粹的程序代码通过循环处理,产生PWM波。处理过程中由于代码的原因,会有一定的占空比误差。另外由于单片通常还有大量的其他处理,不可能把处理时间片完全用来生成PWM波,所以这个时候,利用定时器来生成PWM波就是一种很好的选择了。
在单片机中,定时器是非常重要的一个外设。利用定时器的特点,我们可以让定时器按照固定周期(频率)使自定义变量加1或者减1,实现自动计数。这个计数步进值可以是1,也可以是其它数值。
定时器控制自动加1或者减1的处理过程中,我们控制好两边的边界以及中间计数值的某一个点,就可以用来生成技术范围的两个段,这两个端就可以作为脉冲波的高/低电平的边界。如下如图所示,让计数变量在0-9之间进行计数,在计数值为0-4之间时,使某个IO口输出低电平;在计数值为5-9之间时输出高电平。
那么只要定时器一直工作下去,在输出的IO口上就有占空比为50%的PWM波。改变这个中间界限值,就可以改变占空比值。
接下来,我们就用STC32G12K128来做这个测试。完成这个过程,需要启用定时器,并允许定时器中断。在中断处理中,根据当前计数值,改变某个IO口的输出电平。代码如下:
#include "../../comm/STC32G.h" //包含此头文件后,不需要再包含"reg51.h"头文件 #define MAIN_Fosc 24000000UL //定义主时钟 typedef unsigned char u8; typedef unsigned int u16; typedef unsigned long u32; // 这个值决定定时器0的中断周期 #define Timer0_Reload (MAIN_Fosc / 1000) //Timer 0 中断频率, 1000次/秒 unsigned long cnt=0; // 计数变量 unsigned long jx=2; // 界限值 unsigned long max=9; // 计数值上限 void Timer0_init(void); //======================================================================== // 函数: void main(void) // 描述: 主函数. // 参数: none. // 返回: none. // 版本: V1.0, 2015-1-12 //======================================================================== void main(void) { WTST = 0; //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快 EAXFR = 1; //扩展寄存器(XFR)访问使能 CKCON = 0; //提高访问XRAM速度 P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口 P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口 P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口 P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口 P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口 P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口 P6M1 = 0x00; P6M0 = 0x00; //设置为准双向口 P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口 EA = 1; //打开总中断 // 初始化定时器0 Timer0_init(); while (1) { } } //======================================================================== // 函数: void Timer0_init(void) // 描述: 初始化timer0 // 参数: none. // 返回: none. //======================================================================== void Timer0_init(void) { TR0 = 0; //停止计数 #if (Timer0_Reload < 64) // 如果用户设置值不合适, 则不启动定时器 #error "Timer0设置的中断过快!" #elif ((Timer0_Reload/12) < 65536UL) // 如果用户设置值不合适, 则不启动定时器 ET0 = 1; //允许中断 TMOD &= ~0x03; TMOD |= 0; //工作模式, 0: 16位自动重装, 1: 16位定时/计数, 2: 8位自动重装, 3: 16位自动重装, 不可屏蔽中断 T0_CT = 0; // 定时模式 T0CLKO = 0; //不输出时钟 #if (Timer0_Reload < 65536UL) T0x12 = 1; //1T mode TH0 = (u8)((65536UL - Timer0_Reload) / 256); TL0 = (u8)((65536UL - Timer0_Reload) % 256); #else T0x12 = 0; //12T mode TH0 = (u8)((65536UL - Timer0_Reload/12) / 256); TL0 = (u8)((65536UL - Timer0_Reload/12) % 256); #endif TR0 = 1; //开始运行 #else #error "Timer0设置的中断过慢!" #endif } //======================================================================== // 函数: void timer0_int (void) interrupt TIMER0_VECTOR // 描述: timer0中断函数. //======================================================================== void timer0_int (void) interrupt 1 { P10 = ~P10; // 这是最简单的可以形成占空比为50%的方波 cnt=(cnt+1)%(max+1); if (cnt <= jx) { P11 = 0; // P11输出低电平 } else { P11 = 1; // P11输出高电平 } }
测试输出波形如下:
根据程序,在计数值为0-2时,P11输出低电平;在3-9时,P11输出高电平。PWM的占空比为7/10=70%,实测结果验证了这个结果。
在某些单片机中,能够用来产生PWM的定时器,配置了多种和PWM相关的寄存器,比如用来存贮预先设置界限值的寄存器、生成PWM用的目标通道(就是输出电平的IO口)。这样我们都不需要在定时器的在中断程序中做判断,根据当前计数值确定IO口输出什么电平了,而仅仅是在初始化时设置好对应的参数就行了,大大简化了处理过程。STC32G12K128中也有这样的寄存器设置,设计更为复杂的PWM生成处理。后面依旧会用STC32G12K128来进行说明。