前面我们利用纯粹的程序代码通过循环处理,产生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来进行说明。
我要赚赏金
