基于树莓派RP2040制作的任意波形发生器
使用树莓派微控制器RP2040,通过双路PWM + LPF生成DC-50KHz之间的任意波形,能够精准调节输出信号幅度和的频率。
标签
嵌入式系统
任意信号发生器
PWM
RP2040
adking
更新2022-02-22
3570

本项目使用了硬禾学堂针对"2022年寒假在家一起练"开发的"基于RP2040的嵌入式系统学习平台",通过该平台上的两个扩展引脚,连接自制的低通滤波器板,从而通过软件编程(使用MicroPython),在125MHz的时钟频率下,通过双路PWM得到DC-50KHz之间的模拟信号,模拟信号的波形由算法自动生成。

本项目的硬件设计和MicroPython软件代码都参考了第4部分参考文章中的内容,只是在本项目中做了移植和简单的修改。

 

先简述一下PWM的工作原理:

1. PWM的原理

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

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

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

 

PWM信号参数和相互之间的关系

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

从上图可以看出,无论是用MCU还是FPGA来产生PWM信号,能够生成的PWM信号的频率(相当于DAC的转换率)和占空比可以调节的分辨率(相当于DAC的精度)会受到主时钟的限制,例如48MHz的主时钟,如果要获得8位精度的占空比调节,PWM的频率最高只能到48MHz/256 ~ 187.5KHz,如果要获得12位精度的占空比调节,PWM的频率最高只能到11.7KHz,用于生成任意波形时,根据奈奎斯特定律,只能生成11.7Khz/2以内的波形,当然要让波形好看,一个周期内要有10个点的话,生成的模拟信号的频率只能在18KHz(8位精度)、1KHz(12位精度)了。

PWM在音频信号的产生以及直流偏置电压的产生方面非常普遍,这两种场合都是对频率要求不太高的情况,通过单片机的编程能够满足系统的设计需求。

理解上述几个参数之间的关系,对于我们灵活使用PWM会非常有帮助。

2. PWM的应用

一根数据线通过占空比的变化来传递信息,应该是最简单的载体了,因此它的应用非常的广泛:

  • 使用PWM制作呼吸灯,用于表达系统的工作状态,当然呼吸灯也是初学嵌入式系统或FPGA的基本训练。
  • 使用PWM对LED调光,无论家用照明的LED、各种显示器的背光以及用颜色和亮度进行信息指示的LED,都是通过PWM的占空比调节来控制发光的亮度。
  • 使用PWM制作电子琴,通过PWM控制音调的输出
  • 使用PWM驱动舵机
  • 使用PWM驱动步进电机
  • 使用PWM驱动直流减速电机
  • 使用PWM实现DAC的功能
  • 使用PWM实现直流稳压

3. PWM的实现方式

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

3.1 单片机(MCU)生成PWM

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

微控制器通过定时器输出PWM,再经过模拟低通滤波器得到模拟信号(以MSP430微控制器为例)

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

微控制器的软件流程

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

 

3.2 用Verilog生成PWM

 

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

给定了系统主时钟,比如RP2040的时钟位125MHz,也就限定了PWM能够产生的信号的频率和占空比(二者之间下互相制约),是否有能够产生更高频率、同时更高调节精度的方法呢?答案是肯定的,可以采用2个或3个PWM合成的方式,如下图所示:

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

以左侧的2路PWM合成为例,在PWM1的基础上通过增加一个PWM输出管脚和一个电阻R2,就可以在给定主时钟的前提下扩展输出信号的上限频率和分辨率。假如我们需要一个8位精度的DAC,可以通过两路4位的PWM DAC的道,没一路负责4位,合在一起构成8位,在上图中n=4,R2的值为R1的16倍。内部的8位波表中的高4位通过PWM1送出来,低4位通过PWM2送出来。

这种做法的优势在于在同样8位精度的情况下,PWM的频率可以提高16倍,也就是从原来的主时钟/256变成了主时钟/16,如果精度是12位,则PWM的频率可以提高64倍,从而能够生成的信号的最高频率相应提升。

要注意的是R2如果太大,就很难保证精度,我们常用的电阻的精度为5%、1%。

如果我们有多余的可以用PWM的管脚也可以通过3个PWM的组合进一步提高性能,但原理都是一样的。

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

双PWM的原理图,用KiCad绘制

双PWM的PCB 3D,用KiCad绘制

这个电路在使用双PWM的结构的同时还通过一颗斯密特反相器74HC14来实现高频纹波抵消的功能,关于这个电路的设计细节可以参考文章:

 

使用我们的嵌入式系统平台,选择扩展IO中的IO14、IO15(为内部第7组PWM)连接扩展板,软件代码如下(用MicroPython和汇编语言编写),使用了RP2040的第二个内核:

import time, _thread, sys
from machine import Pin, PWM
import math
import uarray

pwmA = PWM(Pin(14, Pin.OUT)) # upper 4 bits
pwmB = PWM(Pin(15, Pin.OUT)) # low 4 bits


# Set the PWM frequency.
pwmFreq= int(125_000_000/16) # 4 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)) # 8 bit resolution
        #print(sineBuf[x])
        
setAmpl(127)

ddsCtrl = uarray.array('i',[
    0x40050000 + 0x98, # 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 4 bits
    lsr(r0,r0,4) # shift bits right (upper)
    lsl(r0,r0,16)
    
    lsr(r6,r6,28) # lower 4 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))

def ddsWrap(sineBuf, ddsCtrl):
    dds(sineBuf, ddsCtrl)
    _thread.exit()

_thread.start_new_thread(ddsWrap,(sineBuf, ddsCtrl))

setF(20000)

在例程中波表用算法生成正弦波,每个周期500个点,精度为8位,信号的幅度设定为127(+/-),其中正弦波的高4位发送到管脚IO14对应的PWM的占空比控制参数中,第4位发送到管教IO15对应的PWM的占空比控制参数中。

setF()函数可以设置要输出的信号的频率。

下面几张图就是用这种方法生成的不同频率的正弦波信号:

生成的5KHz的正弦波信号

生成的20KHz的正弦波信号

生成的50KHz的正弦波信号,低通滤波器截止频率设置的比较高,能够看出样点之间保持的状态。

 

4. 参考文章

附件下载
dual_pwmsine.py
MicroPython代码
dualpwm.pdf
团队介绍
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号