【体验开源项目PicoAWG树莓派pico任意波形发生器AWG,最高125MSPS,可编程高速数字信号发生器】

概要

研究目的:实现高速可编程任意数字信号发生器

高速数字信号发生器可以理解为逻辑分析仪的逆设备,通过上位机写入数据缓存器,发生器便可源源不断产生数字码,根据项目需求,可以设置只发送一次还是不间断循环发送。本文体验大佬的开源项目PicoAWG,简单测试运行效果,方便后续开发与改进。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a1f982dd54d34a96873353f895b795c7.png在这里插入图片描述
在这里插入图片描述

项目地址

硬件
https://github.com/leidawt/PicoAWG-Firmware
软件
https://github.com/leidawt/PicoAWG-Software

快速体验

只需要一块树莓派pico开发板,一台逻辑分析仪,剩下的全靠软件。
在这里插入图片描述
先在树莓派pico中文站下载micropython最新板固件
https://micropython.org/download/rp2-pico/rp2-pico-latest.uf2
树莓派pico上电前按住boot按钮,上电后可以看到虚拟磁盘RPI-RP2,直接把这个uf2文件拖进去即可,简直不要太方便。
在这里插入图片描述
建议先简单体验一下micropython,再开始开发。

固件开发

先下载开发树莓派micropython的IDE软件:https://thonny.org/
在这里插入图片描述
这是一款用于python开发的IDE,本质上就是用来写代码,然后通过串口把代码上传到开发板内。开源代码是通过DMA将数据从buffer中搬运到IO口外设中,因此不会受到micropython脚本运行慢的限制,纯后台运行。同时需要指出,项目所配置的系统时钟频率为250M,这是在给系统超频,虽然也能工作,但实际测试下来运行很不稳定,输出波形有毛刺,建议把 fclock = const(250000000) 改成 fclock = const(125000000)。虽然达不到标题中最高125MSPS的输出速率,但稳定也很重要。

原版代码如下:

# PicoAWG v1.0
# ref: Rolf Oldeman's Arbitrary waveform generator
# https://www.instructables.com/Arbitrary-Wave-Generator-With-the-Raspberry-Pi-Pic/
# tested with rp2-pico-20230426-v1.20.0.uf2

from machine import Pin, mem32, PWM
from rp2 import PIO, StateMachine, asm_pio
from array import array
from math import pi, sin, exp, sqrt, floor
from uctypes import addressof
from random import random
from micropython import const
#################################SETTINGS#######################################

fclock = const(250000000)  # clock frequency of the pico 建议改为fclock = const(125000000)
dac_clock = int(fclock/2)
# make buffers for the waveform.
# large buffers give better results but are slower to fill
maxnsamp = const(4096)  # must be a multiple of 4. miximum size is 65536
wavbuf = {}
wavbuf[0] = bytearray(maxnsamp*2)
wavbuf[1] = bytearray(maxnsamp*2)
ibuf = 0
temp = None
################################################################################


DMA_BASE = const(0x50000000)
CH0_READ_ADDR = const(DMA_BASE+0x000)
CH0_WRITE_ADDR = const(DMA_BASE+0x004)
CH0_TRANS_COUNT = const(DMA_BASE+0x008)
CH0_CTRL_TRIG = const(DMA_BASE+0x00c)
CH0_AL1_CTRL = const(DMA_BASE+0x010)
CH1_READ_ADDR = const(DMA_BASE+0x040)
CH1_WRITE_ADDR = const(DMA_BASE+0x044)
CH1_TRANS_COUNT = const(DMA_BASE+0x048)
CH1_CTRL_TRIG = const(DMA_BASE+0x04c)
CH1_AL1_CTRL = const(DMA_BASE+0x050)

PIO0_BASE = const(0x50200000)
PIO0_TXF0 = const(PIO0_BASE+0x10)
PIO0_SM0_CLKDIV = const(PIO0_BASE+0xc8)

# set desired clock frequency
machine.freq(fclock)

# set default DAC current to 10mA
dac904_bias = PWM(Pin(14))
dac904_bias.freq(100000)
dac904_bias.duty_u16(32768)

# state machine that just pushes bytes to the pins


@asm_pio(sideset_init=(PIO.OUT_HIGH,),
         out_init=(PIO.OUT_HIGH,)*14,
         out_shiftdir=PIO.SHIFT_RIGHT,
         fifo_join=PIO.JOIN_TX,
         autopull=True,
         pull_thresh=28)
def stream():
    wrap_target()
    out(pins, 14).side(0)
    nop().side(1)
    wrap()


sm = StateMachine(0, stream, freq=fclock,
                  sideset_base=Pin(15), out_base=Pin(0))
sm.active(1)

# 2-channel chained DMA. channel 0 does the transfer, channel 1 reconfigures
p = array('I', [0])  # global 1-element array


def startDMA(ar, nword):
    # first disable the DMAs to prevent corruption while writing
    mem32[CH0_AL1_CTRL] = 0
    mem32[CH1_AL1_CTRL] = 0
    # setup first DMA which does the actual transfer
    mem32[CH0_READ_ADDR] = addressof(ar)
    mem32[CH0_WRITE_ADDR] = PIO0_TXF0
    mem32[CH0_TRANS_COUNT] = nword
    IRQ_QUIET = 0x1  # do not generate an interrupt
    TREQ_SEL = 0x00  # wait for PIO0_TX0
    CHAIN_TO = 1  # start channel 1 when done
    RING_SEL = 0
    RING_SIZE = 0  # no wrapping
    INCR_WRITE = 0  # for write to array
    INCR_READ = 1  # for read from array
    DATA_SIZE = 2  # 32-bit word transfer
    HIGH_PRIORITY = 1
    EN = 1
    CTRL0 = (IRQ_QUIET << 21) | (TREQ_SEL << 15) | (CHAIN_TO << 11) | (RING_SEL << 10) | (RING_SIZE << 9) | (
        INCR_WRITE << 5) | (INCR_READ << 4) | (DATA_SIZE << 2) | (HIGH_PRIORITY << 1) | (EN << 0)
    mem32[CH0_AL1_CTRL] = CTRL0
    # setup second DMA which reconfigures the first channel
    p[0] = addressof(ar)
    mem32[CH1_READ_ADDR] = addressof(p)
    mem32[CH1_WRITE_ADDR] = CH0_READ_ADDR
    mem32[CH1_TRANS_COUNT] = 1
    IRQ_QUIET = 0x1  # do not generate an interrupt
    TREQ_SEL = 0x3f  # no pacing
    CHAIN_TO = 0  # start channel 0 when done
    RING_SEL = 0
    RING_SIZE = 0  # no wrapping
    INCR_WRITE = 0  # single write
    INCR_READ = 0  # single read
    DATA_SIZE = 2  # 32-bit word transfer
    HIGH_PRIORITY = 1
    EN = 1
    CTRL1 = (IRQ_QUIET << 21) | (TREQ_SEL << 15) | (CHAIN_TO << 11) | (RING_SEL << 10) | (RING_SIZE << 9) | (
        INCR_WRITE << 5) | (INCR_READ << 4) | (DATA_SIZE << 2) | (HIGH_PRIORITY << 1) | (EN << 0)
    mem32[CH1_CTRL_TRIG] = CTRL1


def setupwave(buf, f, w):
    # required clock division for maximum buffer size
    div = dac_clock/(f*maxnsamp)
    if div < 1.0:  # can't speed up clock, duplicate wave instead
        dup = int(1.0/div)
        nsamp = int((maxnsamp*div*dup+0.5)/4)*4  # force multiple of 4
        clkdiv = 1
    else:  # stick with integer clock division only
        clkdiv = int(div)+1
        nsamp = int((maxnsamp*div/clkdiv+0.5)/4)*4  # force multiple of 4
        dup = 1
    # gain set
    if w.amplitude >= 2:
        dac904_bias.duty_u16(65535)
        w.amplitude = 0.5
        w.offset = 0
    elif w.amplitude <= 0.2:
        if w.offset == 0:
            dac904_bias.duty_u16(6554)
            w.amplitude = w.amplitude/0.2*0.5
        else:
            dac904_bias.duty_u16(65535)
            w.amplitude = w.amplitude/2*0.5
            w.offset = w.offset/2
    else:
        k = 1-w.offset/w.amplitude
        dac904_bias.duty_u16(int(w.amplitude/2/k*65535))
        w.amplitude = 0.5*k
        w.offset = 0.5-0.5*k

    # fill the buffer
    # for isamp in range(nsamp):
    #     buf[isamp] = max(0, min(255, int(255*eval(w, dup*(isamp+0.5)/nsamp))))

    # print([dup, clkdiv, nsamp, int(nsamp/2)])
    if w.func is not None:
        for iword in range(int(nsamp/2)):
            val1 = int(16383*eval(w, dup*(iword*2+0)/nsamp))+8192
            val2 = int(16383*eval(w, dup*(iword*2+1)/nsamp))+8192

            word = val1 + (val2 << 14)
            buf[iword*4+0] = (word & (255 << 0)) >> 0
            buf[iword*4+1] = (word & (255 << 8)) >> 8
            buf[iword*4+2] = (word & (255 << 16)) >> 16
            buf[iword*4+3] = (word & (255 << 24)) >> 24
    # set the clock divider
    clkdiv_int = min(clkdiv, 65535)
    clkdiv_frac = 0  # fractional clock division results in jitter
    mem32[PIO0_SM0_CLKDIV] = (clkdiv_int << 16) | (clkdiv_frac << 8)

    # start DMA
    startDMA(buf, int(nsamp/2))


# evaluate the content of a wave
def eval(w, x):
    m, s, p = 1.0, 0.0, 0.0
    if 'phasemod' in w.__dict__:
        p = eval(w.phasemod, x)
    if 'mult' in w.__dict__:
        m = eval(w.mult, x)
    if 'sum' in w.__dict__:
        s = eval(w.sum, x)
    x = x*w.replicate-w.phase-p
    x = x-floor(x)  # reduce x to 0.0-1.0 range
    v = w.func(x, w.pars)
    v = v*w.amplitude*m
    v = v+w.offset+s
    return v

# some common waveforms. combine with sum,mult,phasemod


def sine(x, pars):
    return sin(x*2*pi)


def pulse(x, pars):  # risetime,uptime,falltime
    if x < pars[0]:
        return -1+2*x/pars[0]
    if x < pars[0]+pars[1]:
        return 1.0
    if x < pars[0]+pars[1]+pars[2]:
        return -1+2*(1.0-(x-pars[0]-pars[1])/pars[2])
    return -1


def gaussian(x, pars):
    return exp(-((x-0.5)/pars[0])**2)


def sinc(x, pars):
    if x == 0.5:
        return 1.0
    else:
        return sin((x-0.5)/pars[0])/((x-0.5)/pars[0])


def exponential(x, pars):
    return exp(-x/pars[0])


def noise(x, pars):  # p0=quality: 1=uniform >10=gaussian
    return sum([random()-0.5 for _ in range(pars[0])])*sqrt(12/pars[0])


# empty class just to attach properties to


class wave:
    pass


def data_mov(start, num):
    for i in range(start, start+num):
        wavbuf[ibuf][i] = temp[i-start]


wave1 = wave()
wave1.replicate = 1
wave1.phase = 0.0

# sine
wave1.func = sine
wave1.amplitude = 0.5  # Vpp
wave1.offset = 0.0  # DC bias(V)
wave1.pars = []

# white noise
# wave1.func = noise
# wave1.amplitude = 1
# wave1.offset = 0
# wave1.pars = [1]

# sinc
# wave1.func = sinc
# wave1.amplitude = 0.5
# wave1.offset = 0
# wave1.pars = [0.01]

# pulse
# wave1.func = pulse
# wave1.amplitude = 1.0
# wave1.offset = 0.0
# # risetime(percent),uptime(percent),falltime(percent)
# # sum(risetime,uptime,falltime) should be 1
# wave1.pars = [0.1, 0.8, 0.1]

# triginal(special case of pulse)
# wave1.func = pulse
# wave1.amplitude = 1.0
# wave1.offset = 0.0
# # risetime(percent),uptime(percent),falltime(percent)
# # sum(risetime,uptime,falltime) should be 1
# wave1.pars = [0.5, 0, 0.5]

setupwave(wavbuf[ibuf], 1e5, wave1)
ibuf = (ibuf+1) % 2

配置一下解释器,就可以上传代码到开发板了。
在这里插入图片描述
将文件另存为main.py,要保存到开发板中,当然此电脑也可以备份一份。
在这里插入图片描述
!](https://img-blog.csdnimg.cn/direct/8631d2a6bdb349428efc60fed5b0872a.png)
在这里插入图片描述
保存为main.py
在这里插入图片描述
先断开连接,再断电重启一下就可以运行。
用逻辑分析仪看看波形。逻辑分析仪检测到P15端口输出了125M的时钟,也就是系统时钟250M二分频的结果。
在这里插入图片描述
用P15端口作为逻辑分析仪同步采样的时钟端口,
在这里插入图片描述
采样后的数据波形
在这里插入图片描述
如果排除逻辑分析仪的问题,看来超频后不太行,毛刺这么多。
系统时钟频率改成200M,再来,还是有一点毛刺。
在这里插入图片描述
系统时钟频率改成125M。
在这里插入图片描述
系统时钟频率62.5M。
在这里插入图片描述

上位机下载

直接下载已经编译好的上位机软件
https://github.com/leidawt/PicoAWG-Software/releases/download/V2.0/PicoAWG.dist.7z
在这里插入图片描述
解压后找到PicoAWG.exe直接运行即可。
在这里插入图片描述
上位机选择相应串口,发送波形即可
在这里插入图片描述

上位机二次开发

把项目克隆到本地

git clone https://github.com/leidawt/PicoAWG-Software.git

在这里插入图片描述
安装需要的python依赖库,前提是电脑已经安装好了python环境和相应的pip包管理器

pip install -r requirements.txt 

先试试编译打包
在这里插入图片描述
发现缺少模块nuitka
那就pip安装一个
在这里插入图片描述
再重新编译下,运行build.bat
在这里插入图片描述
编译成功,二次开发需要怎么修改代码看心情。
首次编译会提示需要从github下载工具链,网络问题自行解决。

写在后面

本文只是简单的看看运行效果,实测发现超频运行多次就寄了,波形长这样。一堆毛刺,再也回不到之前的完美状态。所以建议不要轻易超频。
在这里插入图片描述
这个项目原本是驱动DAC904芯片的,通过上文逻辑分析仪可以看到,用P15引脚作为DAC时钟,P14引脚产生PWM调节参考电流,P0-P13就是14位的数字码输出了。
在这里插入图片描述
需要继续完善的地方:
上位机稳定性,经常卡死。
如何开发实时数据流数字信号发生器,而不是buffer模式,这样这个数字信号发生器就可以直接作为电脑外设使用,不需要高成本的FPGA。可以作为超高速声卡,SPI控制器等等。pico性能有限,最终还是需要FPGA方案。

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

简单|纯粹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值