├── .gitignore ├── README.md ├── main.py └── rp2-pico-20230426-v1.20.0.uf2 /.gitignore: -------------------------------------------------------------------------------- 1 | old/ 2 | temp.py 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PicoAWG任意波形发生器RP2040固件。 2 | 3 | RP2040 firmware for PicoAWG wave generator. 4 | 5 | Tested with MicroPython runtime: rp2-pico-20230426-v1.20.0.uf2 6 | 7 | [Hardware](https://oshwhub.com/32478yf4780gf72r49fg4/picoawg) 8 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # PicoAWG v1.0 2 | # ref: Rolf Oldeman's Arbitrary waveform generator 3 | # https://www.instructables.com/Arbitrary-Wave-Generator-With-the-Raspberry-Pi-Pic/ 4 | # tested with rp2-pico-20230426-v1.20.0.uf2 5 | 6 | from machine import Pin, mem32, PWM 7 | from rp2 import PIO, StateMachine, asm_pio 8 | from array import array 9 | from math import pi, sin, exp, sqrt, floor 10 | from uctypes import addressof 11 | from random import random 12 | from micropython import const 13 | #################################SETTINGS####################################### 14 | 15 | fclock = const(250000000) # clock frequency of the pico 16 | dac_clock = int(fclock/2) 17 | # make buffers for the waveform. 18 | # large buffers give better results but are slower to fill 19 | maxnsamp = const(4096) # must be a multiple of 4. miximum size is 65536 20 | wavbuf = {} 21 | wavbuf[0] = bytearray(maxnsamp*2) 22 | wavbuf[1] = bytearray(maxnsamp*2) 23 | ibuf = 0 24 | temp = None 25 | ################################################################################ 26 | 27 | 28 | DMA_BASE = const(0x50000000) 29 | CH0_READ_ADDR = const(DMA_BASE+0x000) 30 | CH0_WRITE_ADDR = const(DMA_BASE+0x004) 31 | CH0_TRANS_COUNT = const(DMA_BASE+0x008) 32 | CH0_CTRL_TRIG = const(DMA_BASE+0x00c) 33 | CH0_AL1_CTRL = const(DMA_BASE+0x010) 34 | CH1_READ_ADDR = const(DMA_BASE+0x040) 35 | CH1_WRITE_ADDR = const(DMA_BASE+0x044) 36 | CH1_TRANS_COUNT = const(DMA_BASE+0x048) 37 | CH1_CTRL_TRIG = const(DMA_BASE+0x04c) 38 | CH1_AL1_CTRL = const(DMA_BASE+0x050) 39 | 40 | PIO0_BASE = const(0x50200000) 41 | PIO0_TXF0 = const(PIO0_BASE+0x10) 42 | PIO0_SM0_CLKDIV = const(PIO0_BASE+0xc8) 43 | 44 | # set desired clock frequency 45 | machine.freq(fclock) 46 | 47 | # set default DAC current to 10mA 48 | dac904_bias = PWM(Pin(14)) 49 | dac904_bias.freq(100000) 50 | dac904_bias.duty_u16(32768) 51 | 52 | # state machine that just pushes bytes to the pins 53 | 54 | 55 | @asm_pio(sideset_init=(PIO.OUT_HIGH,), 56 | out_init=(PIO.OUT_HIGH,)*14, 57 | out_shiftdir=PIO.SHIFT_RIGHT, 58 | fifo_join=PIO.JOIN_TX, 59 | autopull=True, 60 | pull_thresh=28) 61 | def stream(): 62 | wrap_target() 63 | out(pins, 14) .side(0) 64 | nop() .side(1) 65 | wrap() 66 | 67 | 68 | sm = StateMachine(0, stream, freq=fclock, 69 | sideset_base=Pin(15), out_base=Pin(0)) 70 | sm.active(1) 71 | 72 | # 2-channel chained DMA. channel 0 does the transfer, channel 1 reconfigures 73 | p = array('I', [0]) # global 1-element array 74 | 75 | 76 | def startDMA(ar, nword): 77 | # first disable the DMAs to prevent corruption while writing 78 | mem32[CH0_AL1_CTRL] = 0 79 | mem32[CH1_AL1_CTRL] = 0 80 | # setup first DMA which does the actual transfer 81 | mem32[CH0_READ_ADDR] = addressof(ar) 82 | mem32[CH0_WRITE_ADDR] = PIO0_TXF0 83 | mem32[CH0_TRANS_COUNT] = nword 84 | IRQ_QUIET = 0x1 # do not generate an interrupt 85 | TREQ_SEL = 0x00 # wait for PIO0_TX0 86 | CHAIN_TO = 1 # start channel 1 when done 87 | RING_SEL = 0 88 | RING_SIZE = 0 # no wrapping 89 | INCR_WRITE = 0 # for write to array 90 | INCR_READ = 1 # for read from array 91 | DATA_SIZE = 2 # 32-bit word transfer 92 | HIGH_PRIORITY = 1 93 | EN = 1 94 | CTRL0 = (IRQ_QUIET << 21) | (TREQ_SEL << 15) | (CHAIN_TO << 11) | (RING_SEL << 10) | (RING_SIZE << 9) | ( 95 | INCR_WRITE << 5) | (INCR_READ << 4) | (DATA_SIZE << 2) | (HIGH_PRIORITY << 1) | (EN << 0) 96 | mem32[CH0_AL1_CTRL] = CTRL0 97 | # setup second DMA which reconfigures the first channel 98 | p[0] = addressof(ar) 99 | mem32[CH1_READ_ADDR] = addressof(p) 100 | mem32[CH1_WRITE_ADDR] = CH0_READ_ADDR 101 | mem32[CH1_TRANS_COUNT] = 1 102 | IRQ_QUIET = 0x1 # do not generate an interrupt 103 | TREQ_SEL = 0x3f # no pacing 104 | CHAIN_TO = 0 # start channel 0 when done 105 | RING_SEL = 0 106 | RING_SIZE = 0 # no wrapping 107 | INCR_WRITE = 0 # single write 108 | INCR_READ = 0 # single read 109 | DATA_SIZE = 2 # 32-bit word transfer 110 | HIGH_PRIORITY = 1 111 | EN = 1 112 | CTRL1 = (IRQ_QUIET << 21) | (TREQ_SEL << 15) | (CHAIN_TO << 11) | (RING_SEL << 10) | (RING_SIZE << 9) | ( 113 | INCR_WRITE << 5) | (INCR_READ << 4) | (DATA_SIZE << 2) | (HIGH_PRIORITY << 1) | (EN << 0) 114 | mem32[CH1_CTRL_TRIG] = CTRL1 115 | 116 | 117 | def setupwave(buf, f, w): 118 | # required clock division for maximum buffer size 119 | div = dac_clock/(f*maxnsamp) 120 | if div < 1.0: # can't speed up clock, duplicate wave instead 121 | dup = int(1.0/div) 122 | nsamp = int((maxnsamp*div*dup+0.5)/4)*4 # force multiple of 4 123 | clkdiv = 1 124 | else: # stick with integer clock division only 125 | clkdiv = int(div)+1 126 | nsamp = int((maxnsamp*div/clkdiv+0.5)/4)*4 # force multiple of 4 127 | dup = 1 128 | # gain set 129 | if w.amplitude >= 2: 130 | dac904_bias.duty_u16(65535) 131 | w.amplitude = 0.5 132 | w.offset = 0 133 | elif w.amplitude <= 0.2: 134 | if w.offset == 0: 135 | dac904_bias.duty_u16(6554) 136 | w.amplitude = w.amplitude/0.2*0.5 137 | else: 138 | dac904_bias.duty_u16(65535) 139 | w.amplitude = w.amplitude/2*0.5 140 | w.offset = w.offset/2 141 | else: 142 | k = 1-w.offset/w.amplitude 143 | dac904_bias.duty_u16(int(w.amplitude/2/k*65535)) 144 | w.amplitude = 0.5*k 145 | w.offset = 0.5-0.5*k 146 | 147 | # fill the buffer 148 | # for isamp in range(nsamp): 149 | # buf[isamp] = max(0, min(255, int(255*eval(w, dup*(isamp+0.5)/nsamp)))) 150 | 151 | # print([dup, clkdiv, nsamp, int(nsamp/2)]) 152 | if w.func is not None: 153 | for iword in range(int(nsamp/2)): 154 | val1 = int(16383*eval(w, dup*(iword*2+0)/nsamp))+8192 155 | val2 = int(16383*eval(w, dup*(iword*2+1)/nsamp))+8192 156 | 157 | word = val1 + (val2 << 14) 158 | buf[iword*4+0] = (word & (255 << 0)) >> 0 159 | buf[iword*4+1] = (word & (255 << 8)) >> 8 160 | buf[iword*4+2] = (word & (255 << 16)) >> 16 161 | buf[iword*4+3] = (word & (255 << 24)) >> 24 162 | # set the clock divider 163 | clkdiv_int = min(clkdiv, 65535) 164 | clkdiv_frac = 0 # fractional clock division results in jitter 165 | mem32[PIO0_SM0_CLKDIV] = (clkdiv_int << 16) | (clkdiv_frac << 8) 166 | 167 | # start DMA 168 | startDMA(buf, int(nsamp/2)) 169 | 170 | 171 | # evaluate the content of a wave 172 | def eval(w, x): 173 | m, s, p = 1.0, 0.0, 0.0 174 | if 'phasemod' in w.__dict__: 175 | p = eval(w.phasemod, x) 176 | if 'mult' in w.__dict__: 177 | m = eval(w.mult, x) 178 | if 'sum' in w.__dict__: 179 | s = eval(w.sum, x) 180 | x = x*w.replicate-w.phase-p 181 | x = x-floor(x) # reduce x to 0.0-1.0 range 182 | v = w.func(x, w.pars) 183 | v = v*w.amplitude*m 184 | v = v+w.offset+s 185 | return v 186 | 187 | # some common waveforms. combine with sum,mult,phasemod 188 | 189 | 190 | def sine(x, pars): 191 | return sin(x*2*pi) 192 | 193 | 194 | def pulse(x, pars): # risetime,uptime,falltime 195 | if x < pars[0]: 196 | return -1+2*x/pars[0] 197 | if x < pars[0]+pars[1]: 198 | return 1.0 199 | if x < pars[0]+pars[1]+pars[2]: 200 | return -1+2*(1.0-(x-pars[0]-pars[1])/pars[2]) 201 | return -1 202 | 203 | 204 | def gaussian(x, pars): 205 | return exp(-((x-0.5)/pars[0])**2) 206 | 207 | 208 | def sinc(x, pars): 209 | if x == 0.5: 210 | return 1.0 211 | else: 212 | return sin((x-0.5)/pars[0])/((x-0.5)/pars[0]) 213 | 214 | 215 | def exponential(x, pars): 216 | return exp(-x/pars[0]) 217 | 218 | 219 | def noise(x, pars): # p0=quality: 1=uniform >10=gaussian 220 | return sum([random()-0.5 for _ in range(pars[0])])*sqrt(12/pars[0]) 221 | 222 | 223 | # empty class just to attach properties to 224 | 225 | 226 | class wave: 227 | pass 228 | 229 | 230 | def data_mov(start, num): 231 | for i in range(start, start+num): 232 | wavbuf[ibuf][i] = temp[i-start] 233 | 234 | 235 | wave1 = wave() 236 | wave1.replicate = 1 237 | wave1.phase = 0.0 238 | 239 | # sine 240 | wave1.func = sine 241 | wave1.amplitude = 0.5 # Vpp 242 | wave1.offset = 0.0 # DC bias(V) 243 | wave1.pars = [] 244 | 245 | # white noise 246 | # wave1.func = noise 247 | # wave1.amplitude = 1 248 | # wave1.offset = 0 249 | # wave1.pars = [1] 250 | 251 | # sinc 252 | # wave1.func = sinc 253 | # wave1.amplitude = 0.5 254 | # wave1.offset = 0 255 | # wave1.pars = [0.01] 256 | 257 | # pulse 258 | # wave1.func = pulse 259 | # wave1.amplitude = 1.0 260 | # wave1.offset = 0.0 261 | # # risetime(percent),uptime(percent),falltime(percent) 262 | # # sum(risetime,uptime,falltime) should be 1 263 | # wave1.pars = [0.1, 0.8, 0.1] 264 | 265 | # triginal(special case of pulse) 266 | # wave1.func = pulse 267 | # wave1.amplitude = 1.0 268 | # wave1.offset = 0.0 269 | # # risetime(percent),uptime(percent),falltime(percent) 270 | # # sum(risetime,uptime,falltime) should be 1 271 | # wave1.pars = [0.5, 0, 0.5] 272 | 273 | setupwave(wavbuf[ibuf], 1e5, wave1) 274 | ibuf = (ibuf+1) % 2 275 | -------------------------------------------------------------------------------- /rp2-pico-20230426-v1.20.0.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leidawt/PicoAWG-Firmware/08350e677dc10aea23224ca5b25cd215eca785b3/rp2-pico-20230426-v1.20.0.uf2 --------------------------------------------------------------------------------