Using DMA as a beginner

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
Post Reply
ndclarkehall
Posts: 4
Joined: Fri Jun 18, 2021 3:29 pm

Using DMA as a beginner

Post by ndclarkehall » Fri Jun 18, 2021 3:46 pm

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!!!

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Using DMA as a beginner

Post by hippy » Fri Jun 18, 2021 6:06 pm

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:]))
Last edited by hippy on Thu Jun 24, 2021 9:14 am, edited 1 time in total.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Using DMA as a beginner

Post by Roberthh » Fri Jun 18, 2021 6:49 pm


ndclarkehall
Posts: 4
Joined: Fri Jun 18, 2021 3:29 pm

Re: Using DMA as a beginner

Post by ndclarkehall » Sat Jun 19, 2021 12:04 pm

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!

ndclarkehall
Posts: 4
Joined: Fri Jun 18, 2021 3:29 pm

Re: Using DMA as a beginner

Post by ndclarkehall » 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.

I assume PioDac is the key but it doesn't behave like any function I've come across before.

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Using DMA as a beginner

Post by hippy » Wed Jun 23, 2021 3:19 pm

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()".

ndclarkehall
Posts: 4
Joined: Fri Jun 18, 2021 3:29 pm

Re: Using DMA as a beginner

Post by ndclarkehall » Sun Jun 27, 2021 11:00 am

That's working now! Thanks :D

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.

hippy
Posts: 130
Joined: Sat Feb 20, 2021 2:46 pm
Location: UK

Re: Using DMA as a beginner

Post by hippy » Sun Jun 27, 2021 1:19 pm

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()

Post Reply