Using DMA between Two State Machines, RP2040 Pico #15958
-
I am trying to use a DMA channel to move data from one state machine to the next state machine, but I am having trouble getting the DMA channel to function. I have created two simple state machines which function as intended. Could you please help by showing my error with DMA? # RP2040 Pico
# Expected result: single bits are fed to state machine 0,
# then DMA moves RX FIFO of state machine 0 to TX FIFO state machine 1,
# then state machine 1 builds a 32-bit array one bit at a time.
# Actual result: DMA does not function as intended
import time
from rp2 import PIO, asm_pio, StateMachine
### State Machine 0
@rp2.asm_pio(in_shiftdir=0, out_shiftdir=1, autopull=True, pull_thresh=1, autopush=True, push_thresh=1)
def bounce(): # move a single bit from TX FIFO to RX FIFO
wrap_target()
out(x, 1) # shift in one bit from OSR to scratch x
in_(x, 1) # shift out one bit from scratch x to ISR
irq(rel(0))
wrap()
def sm0_handler(sm0):
print("sm0", sm0.get())
sm0 = rp2.StateMachine(0, bounce)
#sm0.irq(sm0_handler) # uncomment to debug sm0
sm0.active(1)
### State Machine 1
@rp2.asm_pio(in_shiftdir=0, out_shiftdir=1, autopull=True, pull_thresh=1, autopush=True, push_thresh=32) # set the bit order direction of OSR and ISR as well as autopull threshold to allow a single bit into the state machine while storing these bits in the ISR until full (32-bits)
def build_bitstream(): # accumulate 32-bits to the RX FIFO one bit at a time from the TX FIFO
wrap_target()
set(y, 31) # set scratch y to 31, this will count down for each bit to be added to ISR
label("loop")
pull(block) # wait (block) for a 32-bit word to be added to the TX FIFO, then move the word to the OSR
out(x, 1) # move one bit from OSR to scratch x
in_(x, 1) # move one bit from scratch x to ISR
jmp(y_dec, "loop") # loop back unill 32 bits (bit stream) have filled the ISR
irq(rel(0)) # set IRQ flag which will trigger the IRQ handler to get the 32-bit word from the RX FIFO
wrap()
def sm1_handler(sm1):
SM1_RX_FIFO_Receive = sm1.get()
print(bin(SM1_RX_FIFO_Receive))
print("SM1_RX_FIFO_Receive")
sm1 = rp2.StateMachine(1, build_bitstream) # build array of bit at clock speed
sm1.irq(sm1_handler, trigger=1, hard=False)
sm1.active(1) # Start state machine
### DMA 0
RXF0_addr = const(0x50200020) # address of RX FIFO register for State Machine 0
TXF1_addr = const(0x50200014) # address of TX FIFO register for State Machine 1
dma0 = rp2.DMA()
dma0_ctrl = dma0.pack_ctrl(
enable = True, # enable DMA channel
high_pri = True, # set DMA bus traffic priority as high
size = 2, # Transfer size: 0=byte, 1=half word, 2=word (default: 2)
inc_read = False, # do not increment to read address
inc_write = False, # do not increment the write address
ring_size = 0, # increment size is zero
ring_sel = False, # use ring_size
treq_sel = 1, # select transfer rate of PIO0 TX FIFO, DREQ_PIO0_TX1
irq_quiet = True, # do not generate an interrupt after transfer is complete
bswap = False, # do not reverse the order of the word
sniff_en = False # do not allow access to debug
)
dma0_config = dma0.config(read=RXF0_addr, write=TXF1_addr, count=1, ctrl=dma0_ctrl, trigger=True)
dma0.active(1)
test_bitstream = 0b1111111111010100100101100010111000
# bit shift function
def get_bit(value, n):
return ((value >> n & 1) != 0)
# write test_bitstream one bit at a time to state machine 0
for k in range(0, 4):
for i in range(31,-1,-1):
j = get_bit(test_bitstream, i)
if j == 1:
sm0.put(0b1)
if j == 0:
sm0.put(0b0)
time.sleep_us(100)
time.sleep(1)
sm0.active(0)
sm1.active(0)
dma0.active(0)
machine.soft_reset()
|
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 6 replies
-
The state machine and DMA controller should be synchronized correctly. If the state machine is not producing data fast enough, or if there is a delay between transfers, the DMA may not behave as expected. Try to add some status checking or debug printouts to verify if the FIFO contents are being consumed correctly. |
Beta Was this translation helpful? Give feedback.
-
I dug into the RP2040 Datasheet and found I have to use the state machine interrupt to call a function which sets the DMA trigger bit to 1 every time the RX FIFO is ready for data to be transferred to the next state machine. Here is a typical MicroPython memory function that can be used to change a bit to the DMA register. def trigger_DMA(sm0):
machine.mem32[0x50000430] |= 0b00001 # triggers DMA chan0, reference 2.5.7 of the RP2040 Datasheet for DMA: MULTI_CHAN_TRIGGER Register Then set state machine 0 interrupt as follows: sm0.irq(trigger_DMA) # Trigger DMA channel 0 to move data from sm0 RX FIFO to sm1 TX FIFO While this does cause the DMA to transfer data, the MicroPython code is too slow and causes some of the bits to be dropped on the next interrupt call. Therefore, I had to use Viper code, which is faster. # RP2040 Pico demo of DMA use between state machines
# Expected result:
# 1) single bits are fed to state machine 0,
# 2) then DMA moves RX FIFO of state machine 0 to TX FIFO state machine 1,
# 3) then state machine 1 builds a 32-bit array one bit at a time.
# 4) then print the result bitstream from state machine 1 RX FIFO.
import time
from rp2 import PIO, asm_pio, StateMachine
@micropython.viper
def trigger_DMA(sm0):
ptr32(0x50000430)[0] |= 0b00001 # triggers DMA chan0, reference 2.5.7 of the RP2040 Datasheet for DMA: MULTI_CHAN_TRIGGER Register
### State Machine 0
@rp2.asm_pio(in_shiftdir=0, out_shiftdir=1, autopull=True, pull_thresh=1, autopush=True, push_thresh=1)
def bounce(): # move a single bit from TX FIFO to RX FIFO
wrap_target()
out(x, 1) # shift in one bit from OSR to scratch x
in_(x, 1) # shift out one bit from scratch x to ISR
irq(rel(0))
wrap()
def sm0_handler(sm0):
print("sm0", sm0.get()) # used for debugging state machine 0
sm0 = rp2.StateMachine(0, bounce)
sm0.irq(trigger_DMA) # Trigger DMA channel 0 to move data from sm0 RX FIFO to sm1 TX FIFO
sm0.active(1)
### State Machine 1
@rp2.asm_pio(in_shiftdir=0, out_shiftdir=1, autopull=True, pull_thresh=1, autopush=True, push_thresh=32) # set the bit order direction of OSR and ISR as well as autopull threshold to allow a single bit into the state machine while storing these bits in the ISR until full (32-bits)
def build_bitstream(): # accumulate 32-bits to the RX FIFO one bit at a time from the TX FIFO
wrap_target()
set(y, 31) # set scratch y to 31, this will count down for each bit to be added to ISR
label("loop")
pull(block) # wait (block) for a 32-bit word to be added to the TX FIFO, then move the word to the OSR
out(x, 1) # move one bit from OSR to scratch x
in_(x, 1) # move one bit from scratch x to OSR
jmp(y_dec, "loop") # loop back unill 32 bits (bit stream) have filled the ISR
# push(noblock) # move the 32-bits from the ISR to the RX FIFO
irq(rel(0)) # set IRQ flag which will trigger the IRQ handler to get the 32-bit word from the RX FIFO
wrap()
def sm1_handler(sm1):
SM1_RX_FIFO_Receive = sm1.get()
print(bin(SM1_RX_FIFO_Receive))
print("SM1_RX_FIFO_Receive")
sm1 = rp2.StateMachine(1, build_bitstream) # build array of bit at clock speed
#sm1.irq(sm1_handler, trigger=1, hard=False) # uncomment to debug state machine 1
sm1.active(1) # Start state machine
@micropython.viper
def configure_DMA():
RXF0_addr = uint(0x50200020) # address of RX FIFO register for State Machine 0
TXF1_addr = uint(0x50200014) # address of TX FIFO register for State Machine 1
EN = 1 # Start channel upon setting the trigger register
HIGH_PRIORITY = 1 # set DMA bus traffic priority as high
DATA_SIZE = 2 # Transfer size: 0=byte, 1=half word, 2=word (default: 2)
INCR_READ = 0 # No increment while reading
INCR_WRITE = 0 # Non increment while writing
RING_SIZE = 0 # No wrapping
RING_SEL = 0 # No wrapping
TREQ_SEL = 4 # select transfer rate of PIO0 RX FIFO, DREQ_PIO0_RX0
CHAIN_TO = 0 # chain to DMA channel 1
IRQ_QUIET = 1 # do not generate an interrupt
BSWAP = 0 # do not reverse the order of the word
SNIFF_EN = 0 # do not allow access to debug
DMA_control_word = ((SNIFF_EN << 23) | (BSWAP << 22) | (IRQ_QUIET << 21) | (TREQ_SEL << 15) |
(CHAIN_TO << 11) | (RING_SEL << 10) |(RING_SIZE << 6) | (INCR_WRITE << 5) |
(INCR_READ << 4) | (DATA_SIZE << 2) |(HIGH_PRIORITY << 1) | (EN << 0))
# DMA channel 0 Alias 3, reference 2.5.2 of the RP2040 Datasheet regarding Aliases
ptr32(0x5000003c)[0] = RXF0_addr # DMA Channel 0 Read Address pointer <- RX FIFO register for State Machine 0, DMA0 read_adress alias register 3 (CH0_AL3_READ_ADDR_TRIG ) - trigger the DMA0 start
ptr32(0x50000034)[0] = TXF1_addr # DMA Channel 0 Write Address pointer -> TX FIFO register for State Machine 1
ptr32(0x50000038)[0] = 1 # DMA Channel 0 Transfer Count <- Just one data (long) array to transfer continuously
ptr32(0x50000030)[0] = DMA_control_word # DMA Channel 0 Control and Status (using alias to not start immediately - will be started by DMA trigger register)
@micropython.viper
def enable_StateMachines():
ptr32(0x50200000)[0] |= 0b0011 # Enable PIO0 SM 0 and 1
@micropython.viper
def disable_StateMachines():
ptr32(0x50000444)[0] |= 0b000011 # Aborts DMA chan0 and 1
ptr32(0x50200000)[0] &= 0b111111110000 # Disable all PIO0 state machines
configure_DMA()
enable_StateMachines()
test_bitstream = 0b1111111111010100100101100010111000
# bit shift function
def get_bit(value, n):
return ((value >> n & 1) != 0)
# write test_bitstream one bit at a time to state machine 0
for k in range(0, 4):
for i in range(31,-1,-1):
j = get_bit(test_bitstream, i)
if j == 1:
sm0.put(0b1)
if j == 0:
sm0.put(0b0)
time.sleep_us(100)
#print("write bit to sm0", i) # uncomment to debug
#print("write loop", k) # uncomment to debug
print(bin(sm1.get()))
print(bin(sm1.get()))
time.sleep(1)
disable_StateMachines()
machine.soft_reset() |
Beta Was this translation helpful? Give feedback.
-
I was thinking of a solution for this without interrupts. Using my DMA class (which is somewhat outdated as it does not integrate the newer rp2.DMA functionality) I changed example 2 there to do a transfer of data from StateMachine 0 to StateMachine 4: This is to be able to try out the functionality easily.
which works quite well. It is essential to set the dreq parameter to the right value. You may try other settings. @sk8board You mentioned the problem of re-triggering the DMA channel: If your overall number of words is smaller than 2**32-1 then you may set the count parameter of the DMA to that value and be fine. Alternatively, you might set up two such channels in identical manner and set them to re-trigger each other in ping-pong style. Unfortunately a DMA channel cannot re-trigger itself, so there seems no other solution for this case that does not use a second channel or interrupts?!? |
Beta Was this translation helpful? Give feedback.
-
I appreciate your help. I made a demo of DMA with two PIO state machines. One DMA between two state machines. The other DMA from the second state machine to a data variable. |
Beta Was this translation helpful? Give feedback.
-
I rewrote my DMA interrupt example with Viper and the use of pointers showed how to use DMA the chaining ping-pong method. I created a demo of it here. |
Beta Was this translation helpful? Give feedback.
I rewrote my DMA interrupt example with Viper and the use of pointers showed how to use DMA the chaining ping-pong method. I created a demo of it here.