PWM(Pulse Width Modulation)全称脉冲宽度调制,它通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。

面积等效原理

面积等效原理是PWM控制技术的重要理论基础。 原理内容:冲量相等而形状不同的窄脉冲加在具有惯性的环节上时,其效果基本相同。

  • 冲量即指窄脉冲的面积。
  • 效果基本相同,是指环节的输出响应波形基本相同。
  • 如果把各输出波形用傅里叶变换分析,则其低频段非常接近,仅在高频 段略有差异。
  • 将图1a、b、c、d所示的脉冲作为输入,加在图2a所示的R-L电路 上,设其电流i(t)为电路的输出,图2b给出了不同窄脉冲时i(t)的响应波形。


图1:形状不同而冲量相同的各种窄脉冲图2:冲量相同的各种窄脉冲的响应波形

用PWM波代替正弦半波

将正弦半波看成是由N个彼此相连的脉冲宽度为π/N,但幅值顶部是曲线且大小按正弦规律变化的脉冲序列组成的。
把上述脉冲序列利用相同数量的等幅而不等宽的矩形脉冲代替,使矩形脉冲的中点和相应正弦波部分的中点重合,
且使矩形脉冲和相应的正弦波部 分面积(冲量)相等,这就是PWM波形。
对于正弦波的负半周,也可以用同样的方法得到PWM波形。
脉冲的宽度按正弦规律变化而和正弦波等效的PWM波形,也称SPWM(Sinusoidal PWM)波形。
基于等效面积原理,PWM波形还可以等效成其他所需要的波形,如等效所需要的非正弦交流波形等。
PWM波形可分为等幅PWM波和不等幅PWM波两种,由直流电源产生的PWM波通常是等幅PWM波。

图3:用PWM波代替正弦半波

调制法生成PWM波形

把希望输出的波形作为调制信号,把接受调制的信号作为载波,通过信号波的调制得到所期望的PWM波形。
最简单可以产生一个脉冲宽度调制信号的方式是交集性方法(intersective method),
这个方法只需要使用锯齿波或三角波以及一个比较器。
当参考的信号值(红色波)比锯齿波(蓝色波) 大,则脉冲调制后的结果会在高状态,反之,则在低状态。

红色:调制信号;蓝色:载波信号;紫色:已调制信号

PWM信号参数

周期:信号变化的过程中,某段波形重复出现,其某一次开始至结束的这段时间就称为“周期“。
脉宽:在一个周期内正脉冲的持续时间。
占空比:是在一串理想的脉冲系列中,脉宽与周期的比值。例如在上图中t为正脉冲的持续时间,T为脉冲周期,占空比为t/T。


  • 使用PWM制作呼吸灯
  • 使用PWM对LED调光
  • 使用PWM制作电子琴
  • 使用PWM驱动舵机
  • 使用PWM驱动步进电机
  • 使用PWM驱动直流减速电机
  • 使用PWM实现DAC的功能
  • 使用PWM实现直流稳压

简而言之,PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。
PWM信号仍然是数字信号,而且幅值相等,在任意时刻,输出的信号就只有1(ON)或者0(OFF)两个状态,具体是0还是1,则由控制器来控制,也由控制器的精度决定。
只要带宽足够,任何模拟值都可以使用PWM进行编码。
既然PWM是数字信号,那么任何具有高低电平可控输出功能的数字芯片都可以生成PWM,比如最常用有微控制器(MCU),可编程逻辑FPGA,或是LED驱动控制芯片如PCA9685,以及各种电源用的控制芯片,以上都可以产生PWM波形,所以说PWM应用实在是太广了。

模拟器件搭建PWM电路

3.1 单片机(MCU)生成PWM

使用MCU生成PWM是最简单的方式,PWM发生功能和定时器功能是一起的,一种方法是使用软件设置定时器的定时时间,定时时间到翻转IO的高低电平,由于是软件来翻转电平,因此精度不能做到非常精确;另一种是直接使用定时器中的PWM功能,在定时器模块中已经集成了专用的PWM发生电路,用户只需要配置一下该模块的寄存器,在寄存器中配置好PWM的频率和占空比,软件使能该功能后,就可以输出精准的PWM波形。

微控制器通过定时器输出PWM,再经过模拟低通滤波器得到模拟信号

每一路可以产生不同的信号

微控制器的软件流程

更多与微控制器定时器生成PWM信号的信息参见技术文章:使用微控制器的PWM定时器作为DAC

3.2 用Verilog生成PWM

3.3 双PWM扩展转换频率和分辨率

采用多PWM输出扩展工作频率和分辨率的图示

为方便测试和验证,特用KiCad设计了一块测试板,电路原理图和PCB的3D效果图如下,图中的阻、容参数可以根据实际使用的位数以及转换频率、要生成的波形的频率进行调整。在此电路中的值为16位DAC设置。

双PWM的原理图,用KiCad绘制

双PWM的PCB 3D,用KiCad绘制

import time, _thread, sys
from machine import Pin, PWM
import math
import uarray
pwmA = PWM(Pin(1, Pin.OUT)) # upper 6 bits
pwmB = PWM(Pin(0, Pin.OUT)) # low 6 bits
 
# Set the PWM frequency.
pwmFreq= int(125_000_000/16) # 6 bit
pwmA.freq(pwmFreq)
pwmB.freq(pwmFreq)
pwmA.duty_u16(31<<10) # upper
pwmB.duty_u16(0<<10)
 
sineBufLen= 500 #for 1kHz and 500kHz update frequ
sineBuf=uarray.array("H",range(0,sineBufLen))
 
def setAmpl(amp):
    global sineBuf
    for x in range(0,sineBufLen):
        xr= x/sineBufLen*2*math.pi
        sineBuf[x]= 127 +int(amp*math.sin(xr)) # 12 bit resolution
        #print(sineBuf[x])
 
ddsCtrl = uarray.array('i',[
    0x40050000 + 0x0c, # 0 cc7, Counter compare values, Channel 7
    sineBufLen, # 4
    0x40054000 + 0x28, # 8 TIMERAWL Register 1 MHz S. 553, Raw read from bits 31:0 of time (no side effects)
    int((1<<16)*1.0) # 12 Index Step <>0 run, step in half words
    ])
 
@micropython.asm_thumb
def dds(r0, r1): # Buffer, ctrl-array
 
    mov(r2,r0) # Buffer Start Address
    ldr(r5, [r1,4]) # buffer length
    ldr(r4, [r1,0]) # pwm counter compare register
    mov(r3,0) # Buffer Index fine
 
    label(nextVal)
 
    label(waitLoop)
    ldr(r6, [r1,8]) # Timer address
    ldr(r6, [r6,0]) # 1Mhz Timer
    lsr(r6,r6,1)    # 500kHz
    cmp(r6,r7)
    beq(waitLoop)
    mov(r7,r6)      # store time slice
 
    lsr(r0,r3,16) # index coarse 
    lsl(r0,r0,1) # index coarse half words
    add(r0,r0,r2)
    ldrh(r0,[r0,0]) # read buffer     regPoke(0x40050000 + 0x98, ((a>>6)<<16) + (a&63))
    lsl(r6,r0,28) # get the lowest 6 bit8
    lsr(r0,r0,4) # shift bits right (upper)
    lsl(r0,r0,16)
 
    lsr(r6,r6,28) # lower 6 bits
    add(r0,r0,r6)
 
    str(r0,[r4,0])
    #b(retu)
 
    ldr(r6, [r1,12]) # reload step index repeat?
    cmp(r6,0)
    beq(retu)
 
    add(r3,r3,r6) # next buffer index fine
    lsr(r0,r3,16)
 
    cmp(r5,r0) # end not yet reached
    bhi(nextVal)
 
    lsl(r0,r5,16)
    sub(r3,r3,r0)    
    mov(r0,r3)
 
    b(nextVal)
 
    label(retu)
 
def setF(f):
    ddsCtrl[3]=int(f/1000*(1<<16))
 
#sineBuf[0]=1<<6
#print(dds(sineBuf, ddsCtrl))
def ddsWrap(sineBuf, ddsCtrl):
    dds(sineBuf, ddsCtrl)
    print("<<<End of DDS>>>")
    _thread.exit()
 
_thread.start_new_thread(ddsWrap,(sineBuf, ddsCtrl))
 
setAmpl(127)
setF(20000)

生成的5KHz的正弦波信号

生成的20KHz的正弦波信号

生成的50KHz的正弦波信号,低通滤波器截至频率太低

使用PWM制作呼吸灯