SAME70 UART RX DMA Circular Linked List

Go To Last Post
3 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

 

My SAME70 receives data via the UART0 interface. I've confirmed successful implementation of the ASF UART drivers, and can see the correct data being received with both polling and UART0_Handler() interrupt approaches. What I'm now trying to do is introduce a DMA process to minimise the CPU utilisation in receiving this UART data.

 

The UART data is received in bursts of around 250 bytes every 20ms. The exact size of each burst is not known, but the maximum it can be is <256 bytes. Below is a diagrammatic representation of my envisaged approach:

 

 

I'll have a 256 byte buffer (readBuffer), into which the DMA data can be written. At the moment I've got 4 linked list descriptors, with blocks of 64 bytes each. The intent here is to allow the application to read and process a section of readBuffer memory (e.g. Block2), with no risk of the DMA process writing to that same memory area. In practise I can probably use two linked list descriptors of 128 bytes each to achieve this same functionality, and I will definitely test this once I've got the process working.

 

The application maintains a blockCount variable in the main loop, so it knows which section of readBuffer has just been updated, and can process those 64 bytes accordingly. I've made an attempt at putting this approach into code, and it has elements which appear to work, but the main issue is that the linked list isn't circular. I believe I've set the final linked list Next Descriptor Address (NDA) to the first descriptor (as per the diagram above), but readBuffer is not updated with new data at the end of the fourth block on the first iteration. I.e. the data read and processed by the application when blockCount = 0, 1, 2, 3 is correct, but when blockCount resets to 0, the data in readBuffer[0:63] has not been updated. This suggests that my linked list configuration is not circular.

 

Any guidance on the below would be greatly appreciated.

 

//XDMAC Defines
#define DMA_BLOCK_LEN      4
#define DMA_MICROBLOCK_LEN 64
#define DMA_CHANNEL        0
#define DMA_INT_NUMBER     21

//readBuffer Size
#define READ_BUFFER_SIZE   256

//readBuffer
COMPILER_ALIGNED(8)
static uint8_t readBuffer[READ_BUFFER_SIZE] = {0xFF};

//XMDAC Configuration
static xdmac_channel_config_t xdmac_channel_cfg;

//Linked List Descriptors
COMPILER_WORD_ALIGNED
static lld_view0 linkedList[DMA_BLOCK_LEN];

//DMA Completion Flag - used for polling/testing
volatile uint8_t dmaFlag = 0;

//DMA Block Counter
volatile uint8_t blockCount = 0;

//XDMAC interrupt - triggered in block write completion
void XDMAC_Handler(void)
{
    uint32_t status;

    status = xdmac_channel_get_interrupt_status(XDMAC, DMA_CHANNEL);

    if (status & XDMAC_CIS_BIS) {

        dmaFlag = 1;

    }
}

int main(void)
{
    //General setup of UART0, XDMAC and printf() serial output excluded for brevity

    //XDMAC Channel Configuration
    xdmac_channel_cfg.mbr_ubc = DMA_MICROBLOCK_LEN;
    xdmac_channel_cfg.mbr_sa  = (uint32_t)&UART0->UART_RHR;
    xdmac_channel_cfg.mbr_da  = (uint32_t)readBuffer;
    xdmac_channel_cfg.mbr_cfg =
        XDMAC_CC_TYPE_PER_TRAN        |
        XDMAC_CC_MBSIZE_SINGLE        |
        XDMAC_CC_DSYNC_PER2MEM        |
        XDMAC_CC_SWREQ_HWR_CONNECTED  |
        XDMAC_CC_MEMSET_NORMAL_MODE   |
        XDMAC_CC_CSIZE_CHK_1          |
        XDMAC_CC_DWIDTH_BYTE          |
        XDMAC_CC_SIF_AHB_IF1          |
        XDMAC_CC_DIF_AHB_IF1          |
        XDMAC_CC_SAM_FIXED_AM         |
        XDMAC_CC_DAM_INCREMENTED_AM   |
        XDMAC_CC_PERID(DMA_INT_NUMBER)
    ;
    xdmac_channel_cfg.mbr_bc  = DMA_BLOCK_LEN - 1;
    xdmac_channel_cfg.mbr_ds  = 0;
    xdmac_channel_cfg.mbr_sus = 0;
    xdmac_channel_cfg.mbr_dus = 0;

    xdmac_configure_transfer(XDMAC, DMA_CHANNEL, &xdmac_channel_cfg);

    //Linked List Descriptor Setup
    for (int i = 0; i < DMA_BLOCK_LEN; i++) {

        if (i == (DMA_BLOCK_LEN - 1)) {

            linkedList[i].mbr_nda = (uint32_t)&linkedList[0];

        } else {

            linkedList[i].mbr_nda = (uint32_t)&linkedList[i + 1];

        }

        linkedList[i].mbr_ubc =
            XDMAC_UBC_UBLEN(DMA_MICROBLOCK_LEN) |
            XDMAC_UBC_NDE_FETCH_EN              |
            XDMAC_UBC_NSEN_UNCHANGED            |
            XDMAC_UBC_NDEN_UPDATED              |
            XDMAC_UBC_NVIEW_NDV0
        ;

        linkedList[i].mbr_da = (uint32_t)readBuffer + (i * DMA_BLOCK_LEN * DMA_MICROBLOCK_LEN);
    }

    xdmac_channel_set_descriptor_control(
        XDMAC,
        DMA_CHANNEL,
        XDMAC_CNDC_NDVIEW_NDV0                |
        XDMAC_CNDC_NDE_DSCR_FETCH_EN          |
        XDMAC_CNDC_NDSUP_SRC_PARAMS_UNCHANGED |
        XDMAC_CNDC_NDDUP_DST_PARAMS_UPDATED
    );

    xdmac_channel_set_descriptor_addr(XDMAC, DMA_CHANNEL, (uint32_t)(&linkedList[0]), 0);

    xdmac_enable_interrupt(XDMAC, DMA_CHANNEL);

    xdmac_channel_enable_interrupt(XDMAC, DMA_CHANNEL, XDMAC_CIE_BIE);

    SCB_CleanDCache();

    xdmac_channel_enable(XDMAC, DMA_CHANNEL);

    //Poll functionality for testing/confirming DMA circular linked list process
    while (1) {

        if (dmaFlag) {

            printf("blockCount: %d - ", blockCount);

            for (int i = 0; i < DMA_MICROBLOCK_LEN; i++) {

                printf("0x%X", readBuffer[(blockCount * DMA_MICROBLOCK_LEN) + 1]);

                readBuffer[(blockCount * DMA_MICROBLOCK_LEN) + 1] = 0xFF;

            }

            blockCount++;

            if (blockCount == DMA_BLOCK_LEN) {

                blockCount = 0;

            }
            
            dmaFlag = 0;

        }
    }
}

Per the above, what I expect is a continuous output of the following:

blockCount: 0 - [Valid data]
blockCount: 1 - [Valid data]
blockCount: 2 - [Valid data]
blockCount: 3 - [Valid data]
blockCount: 0 - [Valid data]
blockCount: 1 - [Valid data]
blockCount: 2 - [Valid data]
blockCount: 3 - [Valid data]
etc

However, the following is what I'm actually getting:

blockCount: 0 - [Valid data]
blockCount: 1 - [Valid data]
blockCount: 2 - [Valid data]
blockCount: 3 - [Valid data]
blockCount: 0 - {0xFF}
blockCount: 1 - {0xFF}
blockCount: 2 - {0xFF}
blockCount: 3 - {0xFF}
etc

So it seems that the process of reading the readBuffer address section depending on blockCount, and then overwriting the data with 0xFF works as intended, but the linked list pointer isn't wrapping back to the start of the linkedList memory region at the end of linkedList[3].

 

Any ideas?

Last Edited: Thu. Jul 9, 2020 - 07:14 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've found a couple of instances in previous threads with similar issues regarding circular linked list functionality. I found one or two people who implemented an XDMAC restart process on interrupt of the final linked list descriptor as follows:

 

//XDMAC interrupt - triggered in block write completion
void XDMAC_Handler(void)
{
    uint32_t status;

    status = xdmac_channel_get_interrupt_status(XDMAC, DMA_CHANNEL);

    if (status & XDMAC_CIS_BIS) {

        dmaFlag = 1;

    }
    
    if (blockCount == 3) {
        
        //Final linked list descriptor is finished
        //Disable the XDMAC channel
        xdmac_channel_disable(XDMAC, DMA_CHANNEL);
        
        //Reset the channel descriptor address - I don't know why this would need to be done again...
        xdmac_channel_cfg.mbr_da = (uint32_t)&linkedList[0];
        
        //Reset the XDMAC channel transfer configuration - again, not sure why we're needing to do this again
        xdmac_configure_transfer(XDMAC, DMA_CHANNEL, &xdmac_channel_cfg);
        
        //Restart the XDMAC channel
        xdmac_channel_enable(XDMAC, DMA_CHANNEL);
        
    }
}

I've tried this approach, and have found that it doesn't work, I'm still getting 0xFF across linkedList[0, 1, 2, 3] after the first reset. Additionally, disabling, reconfiguring the XDMAC channel and enabling again seems to add considerable overhead and wouldn't be at all suitable to high throughput applications.

 

Surely I'm not trying to do anything particularly exotic here with a circular linked list approach?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've not had any luck on progressing this issue. I don't suppose anyone has any ideas? As per my earlier posts, the application will happily run, but the new data never overwrites the buffer after the first cycle/iteration.