Page 1 of 1
Using DMA as a beginner
Posted: Fri Jun 18, 2021 3:46 pm
by ndclarkehall
Hello!
Am attempting to construct a triggerable function (envelope for synthesisers) generator using a Pi Pico and Micropython. The Pico has a 7bit R2R DAC across GPIO 16-22.
Have been successful using while loops and utime.sleep to play back lists of voltages but I find that there is an annoyingly low limit to the sample rate I can achieve, even though the unit is clocked at 125 MHz? I would like to get close to that value.
Have seen some posts that suggest using DMA is the answer but my Python is pretty basic and I don't know where to start.
Any help would be hugely appreciated!!!
Re: Using DMA as a beginner
Posted: Fri Jun 18, 2021 6:06 pm
by hippy
I have been investigating DMA myself for a couple of days and Roberthh's code is what got me going -
viewtopic.php?f=21&t=9697&start=10#p55104
This is my proof of concept version of that which outputs as a 7-bit DAC using 32-bit data. It's cobbled together from more generic code intended for something else so you can optimise out the "len(pioPins)", and use Roberthh's code to figure out how to use 8-bit data -
Edit : Changed 'set_init' and 'set_base' to 'out_init' and 'out_base'.
Code: Select all
from machine import Pin
from rp2 import asm_pio, PIO, StateMachine
import array
pioPins = []
pioPins.append(Pin(16, Pin.OUT))
pioPins.append(Pin(17, Pin.OUT))
pioPins.append(Pin(18, Pin.OUT))
pioPins.append(Pin(19, Pin.OUT))
pioPins.append(Pin(20, Pin.OUT))
pioPins.append(Pin(21, Pin.OUT))
pioPins.append(Pin(22, Pin.OUT))
@asm_pio(out_init = [PIO.OUT_LOW] * len(pioPins),
autopull = True,
pull_thresh = (32 - ((32 // len(pioPins)) * len(pioPins))),
out_shiftdir = PIO.SHIFT_RIGHT
)
def PioDac(LEN=len(pioPins)):
out(x, LEN) # Get the bits to output, lsb's of pulled 32-bit word
mov(pins, x) # Output the bits
if True:
mov(isr, x) # Report what was sent for debugging
push(block)
sm = StateMachine(0, PioDac, out_base=pioPins[0])
sm.active(1)
# Credit : Roberthh
# https://forum.micropython.org/viewtopic.php?f=21&t=9697&start=10#p55104
import uctypes
@micropython.viper
def Dma():
channel = uint(0)
DMA_BASE = const(0x50000000)
READ_ADDR = const(0)
WRITE_ADDR = const(1)
TRANS_COUNT = const(2)
CTRL_TRIG = const(3)
AL1_CTRL = const(4)
PIO0_BASE = const(0x50200000)
PIO0_BASE_TXF0 = const(PIO0_BASE + 0x10)
dma = ptr32(uint(DMA_BASE) + channel * 0x40)
dma[READ_ADDR] = uint(ptr32(wave32)) # Buffer to write
dma[WRITE_ADDR] = uint(PIO0_BASE_TXF0) # Where to write to
dma[TRANS_COUNT] = uint(len(wave32)) # Items to transfer
IRQ_QUIET = const(0x1) # No interrupt
TREQ_SEL = const(0x00) # Wait for PIO0_TX0
CHAIN_TO = const(0) # Do not chain
RING_SEL = const(0)
RING_SIZE = const(0) # No wrapping
INCR_WRITE = const(0) # For write to array
INCR_READ = const(1) # For read from array
DATA_SIZE = const(2) # 32-bit transfer
HIGH_PRIORITY = const(1) # High priority
ENABLE = const(1) # Enabled
TRIG = const((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) |
(ENABLE << 0))
dma[CTRL_TRIG] = uint(TRIG)
wave32 = array.array("L")
wave32.append(0b0000000)
wave32.append(0b0000001)
wave32.append(0b0000011)
wave32.append(0b0000111)
wave32.append(0b0001111)
wave32.append(0b0011111)
wave32.append(0b0111111)
wave32.append(0b1111111)
Dma()
count = 0
while sm.rx_fifo() > 0:
count += 1
print("{:>2} : {:0>7}".format(count, bin(sm.get())[2:]))
Re: Using DMA as a beginner
Posted: Fri Jun 18, 2021 6:49 pm
by Roberthh
Re: Using DMA as a beginner
Posted: Sat Jun 19, 2021 12:04 pm
by ndclarkehall
V helpful thanks!
I'll try and get my head around it
I was aware of that post, my design is partially based off the instructable!
Re: Using DMA as a beginner
Posted: Wed Jun 23, 2021 12:27 pm
by ndclarkehall
Been going through this for a while and am still unsure how to get it to actually send the voltages to the pins in hippy's code- it just seems to print the values.
I assume PioDac is the key but it doesn't behave like any function I've come across before.
Re: Using DMA as a beginner
Posted: Wed Jun 23, 2021 3:19 pm
by hippy
ndclarkehall wrote: ↑Wed Jun 23, 2021 12:27 pm
Been going through this for a while and am still unsure how to get it to actually send the voltages to the pins in hippy's code- it just seems to print the values.
If it is printing the values then that is good news because it means PIO is running and being driven by DMA, and the "mov(pins, x)" should be setting the pins to the desired values before it pushes those values for MicroPython to print.
I don't have a Pico configured to verify what's actually being set on the output pins. I would have expected it to work but maybe it doesn't.
Update : Seems it doesn't - Please accept my apologies. Have I mentioned that I love the idea of PIO, hate the way they are implemented, and configured using '@asm_pio' and 'StateMachine' ?
Change 'set_init=' and 'set_base=' to 'out_init=' and 'out_base=' and I think everything should work ...
Code: Select all
@asm_pio(out_init = [PIO.OUT_LOW] * len(pioPins),
Code: Select all
sm = StateMachine(0, PioDac, out_base=pioPins[0])
I figured that out by reading the GPIO output pins back in and seeing that my "mov(pins, x)" was not doing anything ...
Code: Select all
from machine import mem32
SIO_BASE = 0xD0000000
GPIO_IN = SIO_BASE + 0x004
inputBits = mem32[GPIO_IN]
You can add a "time.sleep()" after the "print" to delay the PIO execution, give you longer to see what the physical pins are actually doing. The PIO will scream through the first four values but should block after that and be paced by that "time.sleep()".
Re: Using DMA as a beginner
Posted: Sun Jun 27, 2021 11:00 am
by ndclarkehall
That's working now! Thanks
Is there a way to reload the DMA without rerunning the script?
The brief needs the program to run as a standalone where triggers on a GPIO pin play back wave32 from the beginning.
Re: Using DMA as a beginner
Posted: Sun Jun 27, 2021 1:19 pm
by hippy
Untested but I imagine you could do something like ...
Code: Select all
btn = Pin(PIN_NUMBER, Pin.IN, Pin.PULL_UP) # Short to 0V when pushed button
while True:
while btn.value() == 0 : pass # Wait for button to be released
while btn.value() != 0 : pass # Wait for button to be pushed
Dma()