Search code examples
dmabare-metalrp2040

rp2040 DMA channel hung, baremetal


I'm trying to trigger a DMA channel to perform a memory-to-memory transfer within the SRAM. The channel throws up the busy flag, but there is no change to the transfer count.

I have the following code:

    ;reset dma
    ldr r0, *resets_clr ;clear reset
    mov r1, 4
    str r1, (r0, 0)
    
    ldr r0, *resets_rw ;confirm reset clear
*rst_dma
    ldr r2, (r0, 2)
    and r2, r1
    beq *rst_dma
    
    ;start dma channel
    ldr r0, *dma_rw
    adr r1, *var
    str r1, (r0, 0) ;read addr
    
    ldr r1, *sram_addr
    str r1, (r0, 1) ;write addr
    
    mov r1, 8
    str r1, (r0, 2) ;trans count
    
    mov r1, 47 ;incr_write | size_word | high_priority | en
    str r1, (r0, 3) ;ctrl trig
    
    ;wait for transfer marked complete
    mov r2, 1
    lsl r2, r2, 24
*busy_dma
    ldr r1, (r0, 3)
    and r1, r2
    bne *busy_dma
    
    ;test
    ldr r0, *sram_addr
    ldr r1, (r0, 0)
    mov lr, pc
    cmp r1, 7
    beq  *led_on

With the following word-sized values:

resets_rw 0x4000c000

resets_clr 0x4000f000

var 7

dma_rw 0x50000000

sram_addr 0x20001000

The chip hangs up in the rst_dma loop, which waits until the channel drops its busy flag. If I run without the loop, the memory test fails. The channel throws no AHB error.


Solution

  • I already typed the question out so might as well answer it.

    The channel control register has a field to select a transfer request signal (TREQ_SEL), which resets to the first data request channel, DREQ_PIO0_TX0. If you're not using the DMA with a peripheral, and don't care about timing the transfer pace, you should set the field to 0x3f for unpaced transfers.

        mov r1, 63 ;treq_sel unpaced transfer
        lsl r1, r1, 15
        add r1, 47 ;inc_write | size_word | en
        str r1, (r0, 3) ;ctrl trig
    

    Without this, the DMA will just sit there, waiting for a non-existent signal.