脉冲宽度调制(Pulse width Modulation或 PWM) 是当今控制系统中使用的强大技术之一。它广泛应用于速度控制、功率控制、测量和通信。本文将带您了解脉冲宽度调制的基础知识及其在微控制器上的实现。PWM 的基本原理
脉冲宽度调制基本上是一个具有变化的高低时间的方波。下图显示了一个基本的 PWM 信号。

PWM 有各种相关术语:
开启时间(On-Time):信号为高电平的持续时间
关闭时间(Off-Time):信号为低电平的持续时间
周期(Period):表示为 PWM 信号开启时间和关闭时间的总和
占空比(Duty Cycle):表示为 PWM 信号周期内信号保持开启的时间百分比
周期
如图所示,Ton 表示信号的开启时间,Toff 表示信号的关闭时间。周期是开启和关闭时间的总和,计算公式如下:
占空比
占空比开启时间占整个周期的比例。使用上面计算的周期,占空比计算如下:

PWM:电压调节
PWM 信号在不同占空比下使用时,输出端的电压会发生变化。此方法用于各种应用领域,例如:
- 开关调节器
- LED 调光器
- 音频
- 模拟信号生成
- 等等…
电压调节是通过平均 PWM 信号来完成的。输出电压由以下公式表示:

从公式中可以看出,通过改变 Ton 值可以直接改变输出电压。
如果 Ton 为 0,则 Vout 也为 0。如果 Ton 为 Ttotal,则 Vout 为 Vin 或最大值。
在 8051 上实现 PWM
在 8051 上实现 PWM 的基本思想是使用定时器并以定义的间隔切换端口引脚高/低。正如我们在 PWM 简介中所讨论的那样,通过改变 Ton 时间,我们可以改变方波的宽度,同时保持方波的时间周期相同。
我们将在模式 0 中使用 8051 Timer0。高电平和低电平的值将以总延迟保持不变的方式加载。如果对于高电平,我们在 TH0 中加载一个值 X,那么对于低电平,TH0 将加载 255-X,这样总延迟仍为 255。
(注:有关单片机定时器的介绍可以查看这篇文章)
C程序代码
/* Global variables and definition */
#include <reg51.h> // Include 8051 register definitions
sbit PWMPIN = P1^0; // Define P1^0 as PWM output pin
unsigned char pwm_width; // PWM duty cycle width
bit pwm_flag = 0; // Flag to toggle high/low state
void pwm_setup()
{
TMOD = 0; // Set Timer 0 in Mode 0 (13-bit mode)
pwm_width = 128; // Set duty cycle to 50% (high time = low time)
EA = 1; // Enable global interrupts
ET0 = 1; // Enable Timer 0 interrupt
TR0 = 1; // Start Timer 0
}
/* Timer 0 Interrupt Service Routine */
void timer0() interrupt 1
{
if (!pwm_flag) { // Start of High level
pwm_flag = 1; // Set flag
PWMPIN = 1; // Set PWM output pin high
TH0 = pwm_width; // Load timer with high time
TF0 = 0; // Clear interrupt flag
} else { // Start of Low level
pwm_flag = 0; // Clear flag
PWMPIN = 0; // Set PWM output pin low
TH0 = 255 - pwm_width; // Load timer with low time
TF0 = 0; // Clear interrupt flag
}
}
void pwm_stop()
{
TR0 = 0; // Disable Timer 0 to stop PWM
}
/* Main function */
void main()
{
pwm_setup(); // Set up PWM
while (1) {
// Main loop can perform other tasks
}
}
先熟悉一下以下关键字:
TMOD:定时器模式寄存器,是一个8位宽的寄存器。低4位用于定时器0,高4位用于定时器1,TMOD=0表示启用定时器0模式0。
TH0: 定时器0寄存器高字节(8位)。
TF0: 定时器0控制寄存器(TCON)溢出标志位。当定时器溢出(从逻辑1过渡到逻辑0时)时,溢出标志TF将被设置为1。
TR0: 定时器0控制寄存器(TCON)启动控制位,0为停止,1为启动。
EA: 中断使能寄存器第7位(使能位),这个位必须设置为1以启用所有中断,0则会禁用所有中断。
ET0: 定时器0中断位, 1为启用,0为禁止。
再来看这段代码:
void pwm_setup()
{
TMOD = 0; // Set Timer 0 in Mode 0 (13-bit mode)
pwm_width = 128; // Set duty cycle to 50% (high time = low time)
EA = 1; // Enable global interrupts
ET0 = 1; // Enable Timer 0 interrupt
TR0 = 1; // Start Timer 0
}
所以以上代码的作用就是设置定时器0为模式0,设置好全局中断,设置好定时器0中断,启动定时器。
再来看这段中断服务代码(有关单片机中断的介绍可以查看这篇文章):
void timer0() interrupt 1
{
if (!pwm_flag) { // Start of High level
pwm_flag = 1; // Set flag
PWMPIN = 1; // Set PWM output pin high
TH0 = pwm_width; // Load timer with high time
TF0 = 0; // Clear interrupt flag
} else { // Start of Low level
pwm_flag = 0; // Clear flag
PWMPIN = 0; // Set PWM output pin low
TH0 = 255 - pwm_width; // Load timer with low time
TF0 = 0; // Clear interrupt flag
}
}
其中interrupt 1也是一个关键字,他是跟计时器0溢出相关联的中断服务interrupt service routine (ISR)。
8051具有以下中断源及其对应的编号:
Interrupt Name | Interrupt Number | Trigger Condition |
---|---|---|
External Interrupt 0 | 0 | Signal on INT0 pin (P3.2) |
Timer 0 Overflow | 1 | Timer 0 overflows |
External Interrupt 1 | 2 | Signal on INT1 pin (P3.3) |
Timer 1 Overflow | 3 | Timer 1 overflows |
Serial Communication | 4 | Serial receive or transmit event |
当单片机检测到中断(例如,定时器 0 溢出)。它将暂停当前程序的执行并跳转到与中断号关联的 ISR(例如,定时器 0 溢出的中断 1)。一旦 ISR 完成,单片机将恢复主程序。
定时器溢出
当定时器寄存器达到其最大值(例如,8 位定时器的最大值是 255)并收到另一个时钟脉冲时,它会“溢出”并返回到 0。此事件称为定时器溢出。
程序运行逻辑如下:
程序启动->计时器0溢出->触发中断1->设置PWM针脚高电平->重置计时器值为128并开始重新计时->清理溢出标志->计时器0溢出->触发中断1->设置PWM针脚低电平->重置计时器值为127并开始重新计时->清理溢出标志
Ton = 2^13-128 = 8192-128 = 8064 tick ≈ 8ms
Toff = 2^13-127 = 8192-127 = 8065 tick≈ 8ms
Ttotal(Period ) = Ton + Toff ≈ 16ms
在Proteus中建立如下简单电路图并运行程序:
示波器显示占空比为50%的PWM方波: