DMA, adresses
-
- Posts: 14
- Joined: Sat Jan 30, 2021 11:18 pm
DMA, adresses
I would like to use DMA on the pico to continuously stream data every clock cycle to the pins (either directly or indirectly).
I think there is no explicit support for DMA yet in micropython, but I don't mind configuring it through the registers with mem32[] writes.
To do this, I need to pass the address of an array to the DMA registers, so that it knows where to start reading from.
Is there anyway to get access to the address of an array? uctypes is supposed to have a 'getaddressof' function but I get the error "ImportError: no module named 'uctypes'" when doing "'from uctypes import addressof". Any ideas or hints?
I think there is no explicit support for DMA yet in micropython, but I don't mind configuring it through the registers with mem32[] writes.
To do this, I need to pass the address of an array to the DMA registers, so that it knows where to start reading from.
Is there anyway to get access to the address of an array? uctypes is supposed to have a 'getaddressof' function but I get the error "ImportError: no module named 'uctypes'" when doing "'from uctypes import addressof". Any ideas or hints?
-
- Posts: 14
- Joined: Sat Jan 30, 2021 11:18 pm
Re: DMA, adresses
So far, viper seems to work, as least it returns an address in the SRAM area:
.....
ar=array("I",[0 for _ in range(nsamp)])
@micropython.viper
def run(freq: uint):
GPIO_OUT_ptr=ptr32(GPIO_OUT)
ar_ptr=ptr32(ar)
print(hex(ar_ptr))
.....
returns "0x2000c3d0"
another nice thing would be to 'reserve' a full bank exclusively for the samples so that the DMA access to that bank does not compete with processor access to that bank. Is that possible at all in micropython?
.....
ar=array("I",[0 for _ in range(nsamp)])
@micropython.viper
def run(freq: uint):
GPIO_OUT_ptr=ptr32(GPIO_OUT)
ar_ptr=ptr32(ar)
print(hex(ar_ptr))
.....
returns "0x2000c3d0"
another nice thing would be to 'reserve' a full bank exclusively for the samples so that the DMA access to that bank does not compete with processor access to that bank. Is that possible at all in micropython?
Re: DMA, adresses
Can this be done using PIO? My understanding is that you can run the SM with a DMA buffer?rgcoldeman wrote: ↑Sun Jan 31, 2021 7:50 pmI would like to use DMA on the pico to continuously stream data every clock cycle to the pins (either directly or indirectly).
-
- Posts: 14
- Joined: Sat Jan 30, 2021 11:18 pm
Re: DMA, adresses
Yes, from what I read in the datasheet it should be possible to do DMA->PIO->GPIO. That's probably the best solution, it gives some extra flexibility on frequency from the PIO clock divider and on the choice of pins. Just not easy for a newbie trying to stick to micropython...
Re: DMA, adresses
FWIW, you can control the PIO from MicroPython. See https://github.com/micropython/micropyt ... _ws2812.py for an example that uses data from memory to control a pin.rgcoldeman wrote: ↑Fri Feb 05, 2021 7:34 amJust not easy for a newbie trying to stick to micropython...
-
- Posts: 14
- Joined: Sat Jan 30, 2021 11:18 pm
Re: DMA, adresses
Thanks! yes it's very powerful. Anyway the pieces are coming together.jimmo wrote: ↑Sat Feb 06, 2021 4:28 amFWIW, you can control the PIO from MicroPython. See https://github.com/micropython/micropyt ... _ws2812.py for an example that uses data from memory to control a pin.rgcoldeman wrote: ↑Fri Feb 05, 2021 7:34 amJust not easy for a newbie trying to stick to micropython...
The syntax for telling the PIO to output to 8 pins seems a little clunky but works.
Amazing how the PIO can do sensible things with a single statement (wrapping seems implied in the micropython setup)
@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)
Re: DMA, adresses
> when doing "'from uctypes import addressof". Any ideas or hints?
I'm using a local build and this works (uctypes is enabled in mpconfigport.h).
There was an issue in official releases not including some libs (array, possibly others too..) but this has been resolved meanwhile. So you might just try flashing an updated micropython version and try again?
Keep us updated if you make any progress on using DMA + PIO. I would be interested.
I'm using a local build and this works (uctypes is enabled in mpconfigport.h).
There was an issue in official releases not including some libs (array, possibly others too..) but this has been resolved meanwhile. So you might just try flashing an updated micropython version and try again?
Keep us updated if you make any progress on using DMA + PIO. I would be interested.
Re: DMA, adresses
I have only just started looking into DMA, but have created a quick helper class, so thought I would post it here in case it is of any help to anyone. It has limited function at the moment, but can be used to do basic DMA transfers.
A couple of quick snippets on how to use it:
1 A simple transfer from one array to another
2: Chain two Channels together, so first channel triggers second channel when it completes its transfer.
As I said there are a lot of features that would require manually setting the control value for. Although there are helper functions for a few features, like changing the size of transfer. Default is set to byte transfers.
Code: Select all
class DmaChannel:
def __init__(self, channelNumber):
offset = channelNumber * 0x40
self.ChannelNumber = channelNumber
self.ReadRegister = 0x50000000 + offset
self.WriteRegister = 0x50000004 + offset
self.TransferCountRegister = 0x50000008 + offset
self.TriggerControlRegister = 0x5000000C + offset
self.ControlRegister = 0x50000010 + offset
self.ControlValue = 0x3F8033 + (channelNumber << 11) #so that the chain value is set to itself
@micropython.viper
def SetWriteAddress(self, address: uint):
ptr= ptr32(self.WriteRegister)
ptr[0] = address
#self.WriteAddress = address
@micropython.viper
def SetReadAddress(self, address: uint):
ptr= ptr32(self.ReadRegister)
ptr[0] = address
#self.ReadAddress = address
@micropython.viper
def SetTransferCount(self, count: uint):
ptr= ptr32(self.TransferCountRegister)
ptr[0] = count
#self.TransferCount = count
@micropython.viper
def SetControlRegister(self, controlValue: uint):
ptr= ptr32(self.ControlRegister)
ptr[0] = controlValue
self.ControlValue = controlValue
@micropython.viper
def SetTriggerControlRegister(self, controlValue: uint):
ptr= ptr32(self.TriggerControlRegister)
ptr[0] = controlValue
self.ControlValue = controlValue
@micropython.viper
def TriggerChannel(self):
ptr= ptr32(self.TriggerControlRegister)
ptr[0] = uint(self.ControlValue)
#@micropython.viper
def SetChainTo(self, chainNumber : uint):
self.ControlValue &= ~ 0x7800
self.ControlValue |= (chainNumber <<11)
#ptr= ptr32(self.ControlRegister)
#ptr[0] = uint(self.controlValue)
def SetByteTransfer(self):
self.ControlValue &= ~ 0xC
def SetHalfWordTransfer(self):
self.ControlValue &= ~ 0xC
self.ControlValue |= 0x4
def SetWordTransfer(self):
#self.ControlValue &= ~ 0xC
self.ControlValue |= 0xC
@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)
1 A simple transfer from one array to another
Code: Select all
screen = bytearray(100)
screen1 = bytearray(100)
for x in range (50):
screen[x]= x
DmaChan0 = DmaChannel(0) #dma channel number to use
DmaChan0.SetChannelData(uctypes.addressof(screen), uctypes.addressof(screen1), 30, True) # address of read location, address of write location, number of transfers to do, Trigger transfer?
Code: Select all
screen = bytearray(100)
screen1 = bytearray(100)
screen2 = bytearray(100)
for x in range (50):
screen[x]= x
DmaChan0 = DmaChannel(0)
DmaChan1 = DmaChannel(1)
DmaChan0.SetChannelData(uctypes.addressof(screen), uctypes.addressof(screen1), 30, False)
DmaChan1.SetChannelData(uctypes.addressof(screen1), uctypes.addressof(screen2), 30, False)
DmaChan0.SetChainTo(1) # channel number to chain to
DmaChan0.TriggerChannel() # start transfer
Last edited by mw66 on Mon Feb 08, 2021 9:03 am, edited 2 times in total.
-
- Posts: 14
- Joined: Sat Jan 30, 2021 11:18 pm
Re: DMA, adresses
OK it works, it is amazing: true 125Msps AWG without even overclocking, and without CPU!!
Trick was to use a second DMA channel to reconfigure with a single write to the READ register the first.
The DMA sends 32-bit words but the PIO only consumes 8 bits a time, so there is no delay in the reconfiguration.
I soldered an 8-bit R2R DAC to GPIO pins 0-7 and get very neat sine, triangle, saw-tooth and ladder shapes. Each wave consists of 100 samples, and the resulting output is exactly 1.25MHz. Parasitic capacitance or inductance with a characteristic time of ~20ns deform sharp edges a bit, but overall analog bandwidth appears to be at least 20MHz if not more. I have no idea how to make a decent front-end for this, my previous Arduino-based AWG had 381ksps and could be buffered and amplified by a sluggish LM358 opamp.
The code is still a mess but it's a proof of principle, next step would be a nice interface for 20+ shapes, amplitude control, sweeping etc
Trick was to use a second DMA channel to reconfigure with a single write to the READ register the first.
The DMA sends 32-bit words but the PIO only consumes 8 bits a time, so there is no delay in the reconfiguration.
I soldered an 8-bit R2R DAC to GPIO pins 0-7 and get very neat sine, triangle, saw-tooth and ladder shapes. Each wave consists of 100 samples, and the resulting output is exactly 1.25MHz. Parasitic capacitance or inductance with a characteristic time of ~20ns deform sharp edges a bit, but overall analog bandwidth appears to be at least 20MHz if not more. I have no idea how to make a decent front-end for this, my previous Arduino-based AWG had 381ksps and could be buffered and amplified by a sluggish LM358 opamp.
The code is still a mess but it's a proof of principle, next step would be a nice interface for 20+ shapes, amplitude control, sweeping etc
Code: Select all
# 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, 7/2/2021. CC BY-NC-SA 4.0 licence
from machine import Pin,mem32
from rp2 import PIO, StateMachine, asm_pio
from array import array
from utime import sleep
from math import sin,pi
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
PIO0_BASE =0x50200000
PIO0_BASE_TXF0=PIO0_BASE+0x10
#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_ar=array('I',[0]) #global 1-element array
@micropython.viper
def startDMA(ar,nword):
p=ptr32(ar)
mem32[CH0_READ_ADDR]=p
mem32[CH0_WRITE_ADDR]=PIO0_BASE_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
p_ar[0]=p
mem32[CH1_READ_ADDR]=ptr(p_ar)
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
#setup waveform. frequency is 125MHz/nsamp
nsamp=100 #must be a multiple of 4
wave=array("I",[0]*nsamp)
for isamp in range(nsamp):
val=128+127*sin((isamp+0.5)*2*pi/nsamp) #sine wave
#val=isamp*255/nsamp #sawtooth
#val=abs(255-isamp*510/nsamp) #triangle
#val=int(isamp/20)*20*255/nsamp #stairs
wave[int(isamp/4)]+=(int(val)<<((isamp%4)*8))
#start
startDMA(wave,int(nsamp/4))
#processor free to do anything else
- Attachments
-
- Screenshot 2021-02-07 at 19.19.15.jpg (91.43 KiB) Viewed 15541 times
-
- DS1Z_QuickPrint14.png (51.78 KiB) Viewed 15547 times
Re: DMA, adresses
cool, looks really good! The PIO's capabilities are pretty awesome.
A small note: I think the snippet is missing the `SetChannelData`method?
A small note: I think the snippet is missing the `SetChannelData`method?