Skip to content

Instantly share code, notes, and snippets.

@batica81
Created May 8, 2022 20:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save batica81/1ad65ef8cbdea8d80d22b80ff4ca2830 to your computer and use it in GitHub Desktop.
Save batica81/1ad65ef8cbdea8d80d22b80ff4ca2830 to your computer and use it in GitHub Desktop.
# Arbitrary waveform generator for Rasberry Pi Pico
# Requires 8-bit R2R DAC on pins 0-7. Works for R=1kOhm
# Achieves 125Msps when running 125MHz clock
# Rolf Oldeman, 13/2/2021. CC BY-NC-SA 4.0 licence
# tested with rp2-pico-20210205-unstable-v1.14-8-g1f800cac3.uf2
#### This is an abriged version that plays only a sine wave. Early proof of concept. by YU4HAK
from machine import Pin, mem32
from rp2 import PIO, StateMachine, asm_pio
from array import array
from utime import sleep
from math import pi, sin, exp, sqrt, floor
from uctypes import addressof
from random import random
keyerButton = Pin(12, Pin.IN, Pin.PULL_UP) # Keyer input
ledState = False
led = Pin(25, Pin.OUT) # the LED on the Pico is pin 25
fclock = 125000000 # clock frequency of the pico
DMA_BASE = 0x50000000
CH0_READ_ADDR = DMA_BASE + 0x000
CH0_WRITE_ADDR = DMA_BASE + 0x004
CH0_TRANS_COUNT = DMA_BASE + 0x008
CH0_CTRL_TRIG = DMA_BASE + 0x00c
CH0_AL1_CTRL = DMA_BASE + 0x010
CH1_READ_ADDR = DMA_BASE + 0x040
CH1_WRITE_ADDR = DMA_BASE + 0x044
CH1_TRANS_COUNT = DMA_BASE + 0x048
CH1_CTRL_TRIG = DMA_BASE + 0x04c
CH1_AL1_CTRL = DMA_BASE + 0x050
PIO0_BASE = 0x50200000
PIO0_TXF0 = PIO0_BASE + 0x10
PIO0_SM0_CLKDIV = PIO0_BASE + 0xc8
# state machine that just pushes bytes to the pins
@asm_pio(out_init=(
PIO.OUT_HIGH, PIO.OUT_HIGH, PIO.OUT_HIGH, PIO.OUT_HIGH, PIO.OUT_HIGH, PIO.OUT_HIGH, PIO.OUT_HIGH, PIO.OUT_HIGH),
out_shiftdir=PIO.SHIFT_RIGHT, autopull=True, pull_thresh=32)
def stream():
out(pins, 8)
sm = StateMachine(0, stream, freq=125000000, 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):
div = fclock / (f * maxnsamp) # required clock division for maximum buffer size
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
# fill the buffer
for isamp in range(nsamp):
buf[isamp] = max(0, min(255, int(256 * eval(w, dup * (isamp + 0.5) / nsamp))))
# 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 / 4))
# 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)
# make buffers for the waveform.
# large buffers give better results but are slower to fill
maxnsamp = 4096 # must be a multiple of 4. miximum size is 65536
wavbuf = {}
wavbuf[0] = bytearray(maxnsamp)
wavbuf[1] = bytearray(maxnsamp)
ibuf = 0
# empty class just to attach properties to
class wave:
pass
wave1 = wave()
wave1.amplitude = 0.5
wave1.offset = 0.5
wave1.phase = 0.0
wave1.replicate = 1
wave1.func = sine
wave1.pars = []
#freq = 7e6
freq = 800
amp = 0.5
offs = 0.5
wave1.amplitude = amp
wave1.offset = offs
ibuf = (ibuf + 1) % 2
print(wavbuf[0])
setupwave(wavbuf[ibuf], freq, wave1);
sm.active(0)
#machine.reset()
# TODO: CHANGE TO IRQ !!!!
while True:
keyerButtonValue = not keyerButton.value()
print(keyerButtonValue)
if keyerButtonValue == True:
ledState = True
sm.active(ledState)
led(ledState)
print("Led is:" + str(ledState))
while keyerButton.value() == True:
pass
elif (ledState == True):
ledState = False
sm.active(ledState)
led(ledState)
print("Led is:" + str(ledState))
sleep(0.01) #Debounce timer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment