DMA, adresses

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
rgcoldeman
Posts: 14
Joined: Sat Jan 30, 2021 11:18 pm

Re: DMA, adresses

Post by rgcoldeman » Sun Feb 07, 2021 9:19 pm

The code is complete. It should work out of the box. I used the original 1.13 RP2 micropython version. Where would you expect a 'SetChannelData' method/function?

User avatar
marfis
Posts: 206
Joined: Fri Oct 31, 2014 10:29 am
Location: Zurich / Switzerland

Re: DMA, adresses

Post by marfis » Sun Feb 07, 2021 10:24 pm

I think here (from your snippet above):

DmaChan0.SetChannelData(uctypes.addressof(screen), uctypes.addressof(screen1), 30, True)

rgcoldeman
Posts: 14
Joined: Sat Jan 30, 2021 11:18 pm

Re: DMA, adresses

Post by rgcoldeman » Sun Feb 07, 2021 10:58 pm

Ah, you refer to the DMA classes posted by mw66 on this list?
It looks awesome and I hope those classes or similar will eventually be included with the RP2 micropython.
(actually I don't find the implemation of 'SetChannelData' method used in the examples)

The code I posted directly writes the DMA registers with mem32[] commands.

Not pretty and should be modified, but it works for now.

User avatar
marfis
Posts: 206
Joined: Fri Oct 31, 2014 10:29 am
Location: Zurich / Switzerland

Re: DMA, adresses

Post by marfis » Sun Feb 07, 2021 11:14 pm

ups, yes that was @mw66 snippet sorry..

mw66
Posts: 3
Joined: Sun Feb 07, 2021 11:45 am

Re: DMA, adresses

Post by mw66 » Mon Feb 08, 2021 12:36 am

Sorry, I don't know how I missed that out.
Here it is

Code: Select all

   @micropython.viper
    def SetChannelData(self, readAddress : uint , writeAddress : uint, count: uint, trigger : bool):
        ptr= ptr32(self.ReadRegister)
        ptr2= ptr32(self.WriteRegister)
        ptr3= ptr32(self.TransferCountRegister)
        ptr[0] = readAddress
        ptr2[0] = writeAddress
        ptr3[0] = count
        if trigger:
            ptr4= ptr32(self.TriggerControlRegister)
            ptr4[0] = uint(self.ControlValue)

Last edited by mw66 on Mon Feb 08, 2021 9:02 am, edited 1 time in total.

rgcoldeman
Posts: 14
Joined: Sat Jan 30, 2021 11:18 pm

Re: DMA, adresses

Post by rgcoldeman » Mon Feb 08, 2021 8:04 am

Thanks, it looks very neat!

I've posted a more detailed description of the project on https://www.instructables.com/Arbitrary ... ry-Pi-Pic/

mw66
Posts: 3
Joined: Sun Feb 07, 2021 11:45 am

Re: DMA, adresses

Post by mw66 » Mon Feb 08, 2021 8:53 am

rgcoldeman wrote:
Mon Feb 08, 2021 8:04 am
Thanks, it looks very neat!

I've posted a more detailed description of the project on https://www.instructables.com/Arbitrary ... ry-Pi-Pic/
Thanks. Your code supports a lot more of the various features than mine though. As I haven't put in any functions to change things like the ring values yet.
One thing I did note about your code is that you are using mem32, even though you are using viper. According to my tests and the following thread, using a direct viper pointer is much faster than mem32. Although it seems in your case you are getting the max throughput you could get anyway.

viewtopic.php?t=6994

rgcoldeman
Posts: 14
Joined: Sat Jan 30, 2021 11:18 pm

Re: DMA, adresses

Post by rgcoldeman » Mon Feb 08, 2021 9:10 am

Indeed mem32 is slow. I used viper to get the address of the array to pass on to the registers, not for speed, since it's only for configuration.
In another test I compared blink speed between mem32 and ptr (both viper). ptr could get me up to 15.625MHz, (4 clock cycles per loop), mem32 well below 100kHz.
Ring wrapping seemed interesting at first, but it seems to have many limitations/complications (power-of-2 blocks, no infinite transfers, alignment of the buffer with the memory map)
Your code is very useful. First thing I tried out with DMA was memory-to-memory block transfer, and realized that it could be very useful to access from micropython, so it's definitely worth pushing for getting something like that into the nominal RP2 micropython.

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

Re: DMA, adresses

Post by Roberthh » Wed Feb 17, 2021 7:26 pm

@rgcoldeman Thank you for the good example on using DMA. It took for me some time today to tell, how to write to 8 pins at the same time. I had seen somewhere the way to do ot, but could not recall where. Finally I looked into the MP source code.
I agree that the setting looks a little bit strange, but found a shorter way to write it as:

out_init=(PIO.OUT_HIGH,) * 8,

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

Re: DMA, adresses

Post by Roberthh » Thu Feb 18, 2021 11:22 am

@rgcoldeman. Thanks again. I have now a first version of a 8080 style bus transmit, which works well. The bus uses three control signals, so I used three bit sideset. The PIO code is just two instructions. There would be similar functions of command_write() and data_read(), two instructions each. I streamlined DMASetup a bit avoiding mem32, supplying the DMA number and avoiding temporary stores by using const. The data is transferred bytewise, since not all data sets are a multiple of 4 bytes. Sample code below:

Code: Select all

# Example using PIO to drive a 8080 type bus

from machine import Pin, mem32
from time import sleep_us
import rp2
import array
import uctypes

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 = 0x50200000
PIO0_BASE_TXF0 = PIO0_BASE+0x10

NUM_WORDS = 259

@rp2.asm_pio(
    sideset_init=(rp2.PIO.OUT_HIGH,) * 3,
    out_init=(rp2.PIO.OUT_HIGH,) * 8,
    out_shiftdir=rp2.PIO.SHIFT_RIGHT,
    autopull=True,
    pull_thresh=8)

def data_write():
    # fmt: off
    out(pins, 8)            .side(0b111)
    nop()                   .side(0b110)
    # fmt: on

@micropython.viper
def startDMA(chan:uint, ar, nword:uint):
    dma=ptr32(uint(DMA_BASE) + chan * 0x40)
    dma[READ_ADDR] = uint(ptr32(ar))
    dma[WRITE_ADDR] = uint(PIO0_BASE_TXF0)
    dma[TRANS_COUNT] = nword
    IRQ_QUIET = const(0x1) # do not generate an 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(0) # 8-bit word transfer
    HIGH_PRIORITY = const(1)
    EN = const(1)
    CTRL0 = 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) | (EN << 0))
    dma[CTRL_TRIG] = uint(CTRL0)


def run():
    ar = array.array("B", [_ for _ in range(NUM_WORDS)])

    # Create the StateMachine with the data_write program, WR on Pin(5), data on pin 8++
    sm = rp2.StateMachine(0, data_write, freq=125_000_000,
                          sideset_base=Pin(2), out_base=Pin(6))
    sm.active(1)

    startDMA(0, ar, len(ar) - 3)

Post Reply