This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C129ENCPDT: TM4C129ENCPDT uDMA continuous transfer

Part Number: TM4C129ENCPDT
Other Parts Discussed in Thread: EK-TM4C1294XL

Hi,

I need to configure TM4C129ENCPDT microcontroller to transfer 16-bit words from RAM buffer to EPI via uDMA. Each word transfer must be triggered (timer DMA timeout event) with General-Purpose Timer (GPTM) running in periodic mode. The used General-Purpose Timer timer will generate the timeout events continuously and the uDMA channel must also move the words continuously to EPI and when the words transfer count reach maximum then the source address must wrap back to RAM buffer beginning automatically and continue repeating the buffer continuously. I know that C2000 series microcontrollers DMA has this operating mode but I do not know if it is possible to implement it with Tiva C uDMA.

I have implemented and tested the continues uDMA transfer using the Scatter-Gather Mem/per by setting up the DMA control table task list where the second task is to reload uDMA channel configuration for buffer transfer:

#define EPI_PORTA_PINS (GPIO_PIN_7 | GPIO_PIN_6)
#define EPI_PORTB_PINS (GPIO_PIN_3)
#define EPI_PORTC_PINS (GPIO_PIN_7 | GPIO_PIN_6 | GPIO_PIN_5 | GPIO_PIN_4)
#define EPI_PORTG_PINS (GPIO_PIN_1 | GPIO_PIN_0)
#define EPI_PORTK_PINS (GPIO_PIN_5 | GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0)
#define EPI_PORTL_PINS (GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0)
#define EPI_PORTM_PINS (GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0)
#define EPI_PORTQ_PINS (GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0)
#define EPI_PORTP_PINS (GPIO_PIN_3 | GPIO_PIN_2)


uint32_t sys_clk;


static uint16_t src_buf[1024];


#pragma DATA_ALIGN(udma_control_table, 1024)
tDMAControlTable udma_control_table[1024];



tDMAControlTable dma_task_list_preload;


tDMAControlTable dma_mem_tasks[] =
{

    {
    (((void*)&src_buf) + 0x0000007F),                 // src end addr
    (void*)0xA000007F,                                // dst end addr
    UDMA_CHCTL_SRCSIZE_16 | UDMA_CHCTL_SRCINC_16 |
    UDMA_CHCTL_DSTSIZE_16 | UDMA_CHCTL_DSTINC_16 |
    UDMA_CHCTL_ARBSIZE_1 |
    ((64 - 1) << UDMA_CHCTL_XFERSIZE_S) |
    UDMA_CHCTL_XFERMODE_PER_SGA
    },

    {
    &(dma_task_list_preload.ui32Control),           // src end addr
    &(udma_control_table[UDMA_CHANNEL_TMR0B].ui32Control), // dst end addr
    UDMA_CHCTL_SRCSIZE_32 | UDMA_CHCTL_SRCINC_NONE |
    UDMA_CHCTL_DSTSIZE_32 | UDMA_CHCTL_DSTINC_NONE |
    UDMA_CHCTL_ARBSIZE_1 |
    ((1 - 1) << UDMA_CHCTL_XFERSIZE_S) |
    UDMA_CHCTL_XFERMODE_MEM_SG
    }

};


tDMAControlTable dma_task_list_preload =
{
    &(dma_mem_tasks[1].ui32Spare),                                    // src end addr
    &(udma_control_table[UDMA_CHANNEL_TMR0B | UDMA_ALT_SELECT].ui32Spare),  // dst end addr
    UDMA_CHCTL_SRCSIZE_32 | UDMA_CHCTL_SRCINC_32 |
    UDMA_CHCTL_DSTSIZE_32 | UDMA_CHCTL_DSTINC_32 |
    UDMA_CHCTL_ARBSIZE_4 |
    ((8 - 1) << UDMA_CHCTL_XFERSIZE_S) |
    UDMA_CHCTL_XFERMODE_MEM_SG
};



void uDMAErrorHandler(void)
{

    uint32_t err;

    err = uDMAErrorStatusGet();


    uDMAErrorStatusClear();

}


void uDMAIntHandler(void)
{

    uint32_t int_status;

    int_status = uDMAIntStatus();


    uDMAIntClear(int_status);


}



int main(void)
{

    uint32_t i;

    init_sys(&sys_clk);

    IntRegister(INT_UDMA, uDMAIntHandler);
    IntRegister(INT_UDMAERR, uDMAErrorHandler);


    SysCtlPeripheralEnable(SYSCTL_PERIPH_EPI0);

    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOK);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOM);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOQ);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOP);


    GPIOPinConfigure(GPIO_PK0_EPI0S0);  // D0
    GPIOPinConfigure(GPIO_PK1_EPI0S1);  // D1
    GPIOPinConfigure(GPIO_PK2_EPI0S2);  // D2
    GPIOPinConfigure(GPIO_PK3_EPI0S3);  // D3
    GPIOPinConfigure(GPIO_PC7_EPI0S4);  // D4
    GPIOPinConfigure(GPIO_PC6_EPI0S5);  // D5
    GPIOPinConfigure(GPIO_PC5_EPI0S6);  // D6
    GPIOPinConfigure(GPIO_PC4_EPI0S7);  // D7
    GPIOPinConfigure(GPIO_PA6_EPI0S8);  // D8
    GPIOPinConfigure(GPIO_PA7_EPI0S9);  // D9
    GPIOPinConfigure(GPIO_PG1_EPI0S10); // D10
    GPIOPinConfigure(GPIO_PG0_EPI0S11); // D11
    GPIOPinConfigure(GPIO_PM3_EPI0S12); // D12
    GPIOPinConfigure(GPIO_PM2_EPI0S13); // D13
    GPIOPinConfigure(GPIO_PM1_EPI0S14); // D14
    GPIOPinConfigure(GPIO_PM0_EPI0S15); // D15
    GPIOPinConfigure(GPIO_PL0_EPI0S16); // A0
    GPIOPinConfigure(GPIO_PL1_EPI0S17); // A1
    GPIOPinConfigure(GPIO_PL2_EPI0S18); // A2
    GPIOPinConfigure(GPIO_PL3_EPI0S19); // A3
    GPIOPinConfigure(GPIO_PQ0_EPI0S20); // A4
    GPIOPinConfigure(GPIO_PQ1_EPI0S21); // A5
    GPIOPinConfigure(GPIO_PQ2_EPI0S22); // A6
    GPIOPinConfigure(GPIO_PQ3_EPI0S23); // A7
    GPIOPinConfigure(GPIO_PB3_EPI0S28); // /RD
    GPIOPinConfigure(GPIO_PP2_EPI0S29); // /WR
    GPIOPinConfigure(GPIO_PP3_EPI0S30); // /CS
    GPIOPinConfigure(GPIO_PK5_EPI0S31); // CLK

    GPIOPinTypeEPI(GPIO_PORTA_BASE, EPI_PORTA_PINS);
    GPIOPinTypeEPI(GPIO_PORTB_BASE, EPI_PORTB_PINS);
    GPIOPinTypeEPI(GPIO_PORTC_BASE, EPI_PORTC_PINS);
    GPIOPinTypeEPI(GPIO_PORTG_BASE, EPI_PORTG_PINS);
    GPIOPinTypeEPI(GPIO_PORTK_BASE, EPI_PORTK_PINS);
    GPIOPinTypeEPI(GPIO_PORTL_BASE, EPI_PORTL_PINS);
    GPIOPinTypeEPI(GPIO_PORTM_BASE, EPI_PORTM_PINS);
    GPIOPinTypeEPI(GPIO_PORTQ_BASE, EPI_PORTQ_PINS);
    GPIOPinTypeEPI(GPIO_PORTP_BASE, EPI_PORTP_PINS);

    EPIDividerSet(EPI0_BASE, 1);
    EPIModeSet(EPI0_BASE, EPI_MODE_HB16);
    EPIDividerCSSet(EPI0_BASE, 0, 1);
    EPIDMATxCount(EPI0_BASE, 128);
    EPIConfigHB16Set(EPI0_BASE, (EPI_HB16_MODE_ADDEMUX | EPI_HB16_WRWAIT_0 | EPI_HB16_RDWAIT_0 | EPI_HB16_CSCFG_CS), 0);
    EPIAddressMapSet(EPI0_BASE, EPI_ADDR_PER_SIZE_256B | EPI_ADDR_PER_BASE_A);  // Start address 0xA000.0000


    // Fill read buffer
    for(i = 0; i < 128; i++)
        {
        src_buf[i] = (i + 1) * 0x0200;
        }


    // uDMA
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
    IntEnable(INT_UDMAERR);
    IntEnable(INT_UDMA);
    uDMAEnable();
    uDMAControlBaseSet(udma_control_table);
    uDMAChannelAttributeDisable(UDMA_CHANNEL_TMR0B, UDMA_ATTR_ALL);

    udma_control_table[UDMA_CHANNEL_TMR0B].pvDstEndAddr = dma_task_list_preload.pvDstEndAddr;
    udma_control_table[UDMA_CHANNEL_TMR0B].pvSrcEndAddr = dma_task_list_preload.pvSrcEndAddr;
    udma_control_table[UDMA_CHANNEL_TMR0B].ui32Control = dma_task_list_preload.ui32Control;
    uDMAChannelEnable(UDMA_CHANNEL_TMR0B);

    // GPTimer0A/B - PL4/PL5
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    GPIOPinConfigure(GPIO_PL5_T0CCP1);
    GPIOPinTypeTimer(GPIO_PORTL_BASE, GPIO_PIN_5);
    GPIOPadConfigSet(GPIO_PORTL_BASE, GPIO_PIN_5, GPIO_STRENGTH_12MA, GPIO_PIN_TYPE_STD);

    TimerConfigure(TIMER0_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_B_PERIODIC));
    TimerControlStall(TIMER0_BASE, TIMER_BOTH, true);
    TimerPrescaleSet(TIMER0_BASE, TIMER_B, 0x00000000);
    TimerLoadSet(TIMER0_BASE, TIMER_B, 0x000007FF);


    TimerUpdateMode(TIMER0_BASE, TIMER_A, TIMER_UP_LOAD_IMMEDIATE);
    TimerDMAEventSet(TIMER0_BASE, TIMER_DMA_TIMEOUT_B); // TIMER_DMA_TIMEOUT_B assumes TIMER_CFG_B_PERIODIC mode
    TimerEnable(TIMER0_BASE, TIMER_BOTH);


    while(1);
    
}

This configuration seems to work ok but the problem is that in Scatter-Gather mode the configuration reload event will add an extra pause to continues data stream to EPI, especially if I need to stream the data to EPI fast as possible and without pauses. Same time the word transfer period must be controllable via General-Purpose Timer. So I need to know if there is any other uDMA configuration that enables continues data transfer where the atomic source address wrap and transfer count reset happens in DMA hardware automatically?

  • Hello Mark,

    I am not sure about the transfer count but the address wrap sounds like it would be possible to handle this way:

    The μDMA controller alternates between using the primary control structure to copy the next transfer instruction from the list and then executing the new transfer instruction. The end of the list is marked by programming the control word for the last entry to use Auto transfer mode. Once the last transfer is performed using Auto mode, the μDMA controller stops. A completion interrupt is generated only after the last transfer. It is possible to loop the list by having the last entry copy the primary control structure to point back to the beginning of the list (or to a new list).

    Have you tried that approach yet? I haven't tested that functionality myself before, but to me it reads like what you are trying to do there.

    The other possibility would be if you could use Ping-Pong mode. I am not certain this will fit your use case, but I wanted to highlight it just in case.

    In Ping-Pong mode, you typically would have two buffers and the DMA would bounce between them seamlessly, and after it bounces from one to another you'd get an interrupt to indicate you need to load the buffer that is not being transferred with the next batch data. This ensues an indefinite, consistent flow of data. In a case like this one where it sounds like you are reading data from RAM, I think this could still be valid because instead of providing a buffer address, you'd just provide a RAM address that updates with each cycle. Also you could keep a transfer count because each interrupt would mark a certain number of transfers completed as you swap the next channel to your new RAM address.

    Let me know what you think of the viability of these possible solutions.

    Best Regards,

    Ralph Jacobi

  • t is possible to loop the list by having the last entry copy the primary control structure to point back to the beginning of the list (or to a new list).


    Yes, I have tried this solution and it works but my problem is that the DMA configuration reload causes extra pauses. Perhaps you can give me example code how I should initialize the task list of tDMAControlTable where last entry copy the primary control structure points back to the beginning of the list. It might be that in my code the task list is not optimal?
    I also tried the Ping-pong mode but each time the primary or secondary buffer transfer completes the DMA channel stops with ISR call where I should reconfigure DMA for next transfer (referring to the Figure 9-2 at pg. 686 in M4C1294NCPDT Microcontroller datasheet and referring to example code in TivaWare example udma_demo.c). I didn't managed to get DMA channel to work continuously alternating between primary and alternate buffers - each time the buffer transfer completed DMA would stop. Can you give me Ping-pong mode example how I should configure DMA channel so that it would continuously alternate between two buffers?

  • Hello Mark,

    I don't believe I have a ready made example for the memory scatter gather with looping, so if you could post what you've done so far, that would give me a head start actually. I've not done a memory scatter gather example with that kind of loop implemented before.

    Though that said as I read the description again, I am not sure that looping the list will actually cause it to loop without stopping. I'll dig into that more tomorrow as well.

    As for the Ping-Pong example, you should be able to reference this one: [Install Path]\TivaWare_C_Series-2.2.0.295\examples\boards\ek-tm4c1294xl\adc_udma_pingpong

    This example sets up both primary and alternate transfer channels. Then after the ADC is started, it begins filling the primary channel. An interrupt fires once that buffer is full - and the alternate starts filling at that time while the primary is in STOP mode. As the alternate fills, the buffer data is processed, and then the buffer is considered empty again. At this time the next transfer for the primary is configured. Then when the alternate is fill, now the primary has been re-enabled and so it starts getting the data streamed in while the same data processing then occurs with the alternate.

    That should result in the continuous stream you are looking for with Ping-Pong mode at least.

    Best Regards,

    Ralph Jacobi

  • Hi Ralph,

    Form TivaWare expmaple ADC uDMA Ping-Pong I see that the uDMA channels must be re-enabled again in main loop after primary or secondary transfer has completed. So for me it seems that uDMA can't continuously alternate between buffers unless the CPU re-enables the next transfer by calling uDMAChannelTransferSet() and uDMAChannelEnable()?

    I have also tried the uDMA Ping-Pong mode but the main problem is that the uDMA channel always stops if one of the primary or alternate transfer completes - if the uDMA channel can make the switch to alt/primary transfer in hardware automatically then it would work perfectly.

    The memory scatter gather with looping method I have implemented in the example I provided with my initial post, you can try with that.

  • Hello Mark,

    Form TivaWare expmaple ADC uDMA Ping-Pong I see that the uDMA channels must be re-enabled again in main loop after primary or secondary transfer has completed. So for me it seems that uDMA can't continuously alternate between buffers unless the CPU re-enables the next transfer by calling uDMAChannelTransferSet() and uDMAChannelEnable()?

    So I'll try and explain in detail what the example is doing.

    First we configure the uDMA so both the primary and alternate control structures have a buffer assigned to them and they are set to receive data from the ADC0 Sample Sequence 0 FIFO. The uDMA and ADC are then enabled.

    As the ADC takes samples and fills the FIFO, it flags the uDMA to transfer the data as the FIFO fills. The uDMA does until one of the buffers is full.

    At that point, three things happen:

    1. The Primary control structure indicates a UDMA_STOP condition on it
    2. An interrupt is fired to alert the MCU that the uDMA has filled the buffer and has no free space for more data
    3. Because the Alternate control structure has been initialized and is prepared for transfer, the uDMA controller automatically switches over to the Alternate control structure to continue filling ADC data

    Now in the application, while the Alternate structure is being filled, the interrupt can be serviced and the main application loop is informed the Primary control structure is full of ADC data.

    The application processes that data, and then configures the Primary control structure to be prepared to receive more data and re-enables it.

    As long as this is done before the Alternate buffer is full, then when the Alternate control structure fills the buffer, the same three steps will occur, but for the Alternate control structure, and the uDMA will see the Primary is ready for transfers to occur, so it automatically switches to that one.

    So while you do have to re-enable the channels after a buffer is full, the switch is always done automatically unless your buffer fills before you had the chance to re-enable the next uDMA transfer.

    Best Regards,

    Ralph Jacobi

  • Hello Mark,

    To circle back on the Scatter-Gather piece, after reviewing the datasheet with my colleague, we have jointly assessed that the uDMA controller is designed to always stop in scatter-gather mode regardless of if the loopback is in place. This would be because there isn't any way to indicate to the uDMA controller that because the end address is the same as the start address, it can proceed endlessly. So the controller needs to be told by the micro its okay to continue transferring data.

    Hopefully the Ping-Pong method ends up being viable for you, because otherwise I don't believe the uDMA will be able to meet your exact need.

    Best Regards,

    Ralph Jacobi

  • Hi Ralph,

    Thank you for the explanations. I tried to modify my example according to the example adc_udma_pingpong but noticed that I have to use the peripheral ISR not uDMA ISR. My purpose is to use periodic timer to to trigger uDMA data word copy from RAM to EPI (currently using UDMA_CHANNEL_TMR0B DMA channel) - Can you suggest what would be the best solution to implement this with pingpong mode?

  • Hello Mark,

    I would expect the same approach as what was done with the ADC ping-pong example would work with one tweak.

    The EPI interface has interrupts for uDMA writes / read transfers so that's what should be enabled and used to flag that a Control Structure needs to be re-enabled. Meanwhile the Timer can run independently to trigger the data copies.

    The ADC example we provide works basically like that. The ADC is set up to fire the interrupt and we actually service the  ADC Sample Sequence 0 interrupt to process that the uDMA has filled a buffer, so there isn't a dedicated uDMA ISR running. Meanwhile the timer is configured to constantly sample the ADC at a set frequency.

    The difference for your use case is that the timer in the ADC case is tired directly into the ADC peripheral as part of the peripheral configuration. In your case the Timer doesn't automatically plug into the uDMA to just run in the background but instead you'll need a Timer ISR as well which triggers the uDMA word copies. Once triggered, then the EPI interrupt would fire after a copy with one buffer is completed and you can determine which Control Structure needs to be re-enabled to keep the continuous flow until that set of transfers is concluded.

    Best Regards,

    Ralph Jacobi

  • Hi Ralph,

    I implemented the code according to your suggestions and got it work in continues pingpong mode, Timer0B will trigger ISR with frequency of 125 kHz where the uDMAChannelRequest() is issued for EPI0 DMA channel, then after 64 words have been transferred then the EPI0IntHandler() ISR is invoked where next pri/alt transfer is re-enabled:

    #define EPI_PORTA_PINS (GPIO_PIN_7 | GPIO_PIN_6)
    #define EPI_PORTB_PINS (GPIO_PIN_3)
    #define EPI_PORTC_PINS (GPIO_PIN_7 | GPIO_PIN_6 | GPIO_PIN_5 | GPIO_PIN_4)
    #define EPI_PORTG_PINS (GPIO_PIN_1 | GPIO_PIN_0)
    #define EPI_PORTK_PINS (GPIO_PIN_5 | GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0)
    #define EPI_PORTL_PINS (GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0)
    #define EPI_PORTM_PINS (GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0)
    #define EPI_PORTQ_PINS (GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0)
    #define EPI_PORTP_PINS (GPIO_PIN_3 | GPIO_PIN_2)
    
    
    uint32_t sys_clk;
    
    
    static volatile uint16_t *g_par_port_ptr = (uint16_t *)0xA0000000;
    static uint16_t src_buf[1024];
    
    
    #pragma DATA_ALIGN(udma_control_table, 1024)
    tDMAControlTable udma_control_table[1024];
    
    
    
    
    
    
    
    void Timer0BIntHandler(void)
    {
    
        uint32_t int_stat;
    
    
        int_stat = TimerIntStatus(TIMER0_BASE, true);
    
        uDMAChannelRequest(UDMA_CH21_EPI0TX);
    
        TimerIntClear(TIMER0_BASE, int_stat);
    
    }
    
    
    void EPI0IntHandler(void)
    {
    
        uint32_t int_stat;
    
        int_stat = EPIIntStatus(EPI0_BASE, true);
    
        if(uDMAChannelModeGet(UDMA_CH21_EPI0TX | UDMA_PRI_SELECT) == UDMA_MODE_STOP)
            {
            uDMAChannelControlSet(UDMA_CH21_EPI0TX | UDMA_PRI_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_16 | UDMA_DST_INC_16 | UDMA_ARB_1);
            uDMAChannelTransferSet(UDMA_CH21_EPI0TX | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG, src_buf, (void *)g_par_port_ptr, 64);
            uDMAChannelEnable(UDMA_CH21_EPI0TX | UDMA_PRI_SELECT);
            }
        else if(uDMAChannelModeGet(UDMA_CH21_EPI0TX | UDMA_ALT_SELECT) == UDMA_MODE_STOP)
            {
            uDMAChannelControlSet(UDMA_CH21_EPI0TX | UDMA_ALT_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_16 | UDMA_DST_INC_16 | UDMA_ARB_1);
            uDMAChannelTransferSet(UDMA_CH21_EPI0TX | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG, src_buf, (void *)g_par_port_ptr, 64);
            uDMAChannelEnable(UDMA_CH21_EPI0TX | UDMA_ALT_SELECT);
            }
    
        EPIIntErrorClear(EPI0_BASE, int_stat);
    
    }
    
    
    int main(void)
    {
    
        uint32_t i;
    
        init_sys(&sys_clk);
    
    
        IntRegister(INT_TIMER0B, Timer0BIntHandler);
    
        SysCtlPeripheralEnable(SYSCTL_PERIPH_EPI0);
    
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOK);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOM);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOQ);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOP);
    
    
        GPIOPinConfigure(GPIO_PK0_EPI0S0);  // D0
        GPIOPinConfigure(GPIO_PK1_EPI0S1);  // D1
        GPIOPinConfigure(GPIO_PK2_EPI0S2);  // D2
        GPIOPinConfigure(GPIO_PK3_EPI0S3);  // D3
        GPIOPinConfigure(GPIO_PC7_EPI0S4);  // D4
        GPIOPinConfigure(GPIO_PC6_EPI0S5);  // D5
        GPIOPinConfigure(GPIO_PC5_EPI0S6);  // D6
        GPIOPinConfigure(GPIO_PC4_EPI0S7);  // D7
        GPIOPinConfigure(GPIO_PA6_EPI0S8);  // D8
        GPIOPinConfigure(GPIO_PA7_EPI0S9);  // D9
        GPIOPinConfigure(GPIO_PG1_EPI0S10); // D10
        GPIOPinConfigure(GPIO_PG0_EPI0S11); // D11
        GPIOPinConfigure(GPIO_PM3_EPI0S12); // D12
        GPIOPinConfigure(GPIO_PM2_EPI0S13); // D13
        GPIOPinConfigure(GPIO_PM1_EPI0S14); // D14
        GPIOPinConfigure(GPIO_PM0_EPI0S15); // D15
        GPIOPinConfigure(GPIO_PL0_EPI0S16); // A0
        GPIOPinConfigure(GPIO_PL1_EPI0S17); // A1
        GPIOPinConfigure(GPIO_PL2_EPI0S18); // A2
        GPIOPinConfigure(GPIO_PL3_EPI0S19); // A3
        GPIOPinConfigure(GPIO_PQ0_EPI0S20); // A4
        GPIOPinConfigure(GPIO_PQ1_EPI0S21); // A5
        GPIOPinConfigure(GPIO_PQ2_EPI0S22); // A6
        GPIOPinConfigure(GPIO_PQ3_EPI0S23); // A7
        GPIOPinConfigure(GPIO_PB3_EPI0S28); // /RD
        GPIOPinConfigure(GPIO_PP2_EPI0S29); // /WR
        GPIOPinConfigure(GPIO_PP3_EPI0S30); // /CS
        GPIOPinConfigure(GPIO_PK5_EPI0S31); // CLK
    
        GPIOPinTypeEPI(GPIO_PORTA_BASE, EPI_PORTA_PINS);
        GPIOPinTypeEPI(GPIO_PORTB_BASE, EPI_PORTB_PINS);
        GPIOPinTypeEPI(GPIO_PORTC_BASE, EPI_PORTC_PINS);
        GPIOPinTypeEPI(GPIO_PORTG_BASE, EPI_PORTG_PINS);
        GPIOPinTypeEPI(GPIO_PORTK_BASE, EPI_PORTK_PINS);
        GPIOPinTypeEPI(GPIO_PORTL_BASE, EPI_PORTL_PINS);
        GPIOPinTypeEPI(GPIO_PORTM_BASE, EPI_PORTM_PINS);
        GPIOPinTypeEPI(GPIO_PORTQ_BASE, EPI_PORTQ_PINS);
        GPIOPinTypeEPI(GPIO_PORTP_BASE, EPI_PORTP_PINS);
    
        EPIDividerSet(EPI0_BASE, 1);
        EPIModeSet(EPI0_BASE, EPI_MODE_HB16);
        EPIDividerCSSet(EPI0_BASE, 0, 1);
        EPIDMATxCount(EPI0_BASE, 1);
        EPIConfigHB16Set(EPI0_BASE, (EPI_HB16_MODE_ADDEMUX | EPI_HB16_WRWAIT_0 | EPI_HB16_RDWAIT_0 | EPI_HB16_CSCFG_CS), 0);
        EPIAddressMapSet(EPI0_BASE, EPI_ADDR_PER_SIZE_256B | EPI_ADDR_PER_BASE_A);  // Start address 0xA000.0000
        EPIIntEnable(EPI0_BASE, EPI_INT_DMA_TX_DONE);
        EPIIntRegister(EPI0_BASE, EPI0IntHandler);
    
    
    
        // Fill read buffer
        for(i = 0; i < 128; i++)
            {
            src_buf[i] = (i + 1) * 0x0200;
            }
    
    
        // uDMA
        SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
        uDMAEnable();
        uDMAControlBaseSet(udma_control_table);
        uDMAChannelAttributeDisable(UDMA_CHANNEL_TMR0B, UDMA_ATTR_ALL);
    
        uDMAChannelAssign(UDMA_CH21_EPI0TX);
        uDMAChannelControlSet(UDMA_CH21_EPI0TX | UDMA_PRI_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_16 | UDMA_DST_INC_16 | UDMA_ARB_1);
        uDMAChannelControlSet(UDMA_CH21_EPI0TX | UDMA_ALT_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_16 | UDMA_DST_INC_16 | UDMA_ARB_1);
        uDMAChannelTransferSet(UDMA_CH21_EPI0TX | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG, src_buf, (void *)g_par_port_ptr, 64);
        uDMAChannelTransferSet(UDMA_CH21_EPI0TX | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG, src_buf, (void *)g_par_port_ptr, 64);
        uDMAChannelEnable(UDMA_CH21_EPI0TX);
    
    
        // GPTimer0B
        TimerConfigure(TIMER0_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_B_PERIODIC));
        TimerPrescaleSet(TIMER0_BASE, TIMER_B, 0x00000000);
        TimerLoadSet(TIMER0_BASE, TIMER_B, 0x00001DF);
        //TimerDMAEventSet(TIMER0_BASE, TIMER_DMA_TIMEOUT_B); // TIMER_DMA_TIMEOUT_B if DMA channel UDMA_CHANNEL_TMR0B is used
        IntEnable(INT_TIMER0B);
        TimerIntEnable(TIMER0_BASE, TIMER_TIMB_TIMEOUT);
        TimerEnable(TIMER0_BASE, TIMER_B);
    
    
    
    
        while(1);
    }
    
     

    With this implementation I have couple of concerns:

    1) The uDMA transfer is triggered through the Timer0B ISR and when I need to increase timer frequency from 125kHz to 2 MHz then the CPU must service too many timer ISR's which will limit the DMA words transfer speed. Is there any other option to pass the Timer0B periodic interrupts to this DMA channel without ISR, like using GPIO uDMA trigger where timer output is connected externally to GPIO input that triggers uDMA transfers?
    2) The words transfer is not periodically uniform around the event when the pri/alt transfer switchover happens, you can see that after last word transfer and at the beginning first word transfer the periods are different.

    On the image first diagram is DATA(16bit), ADDRESS(8bit), EPI-CLK, CS, RD, WR

  • Hi Mark,

    2) The words transfer is not periodically uniform around the event when the pri/alt transfer switchover happens, you can see that after last word transfer and at the beginning first word transfer the periods are different.

    That isn't surprising as the DMA controller both issues the interrupt and makes the swap which takes a handful of clock cycles, there won't be a way around that.

    1) The uDMA transfer is triggered through the Timer0B ISR and when I need to increase timer frequency from 125kHz to 2 MHz then the CPU must service too many timer ISR's which will limit the DMA words transfer speed. Is there any other option to pass the Timer0B periodic interrupts to this DMA channel without ISR, like using GPIO uDMA trigger where timer output is connected externally to GPIO input that triggers uDMA transfers?

    I'm not sure what transfer speeds you are getting but 2MHz seems to be very fast to be able to transfer data over DMA depending how much you are sending. The device is running at 120MHz, so you'd be interrupting every 60 clock cycles at that point. Maybe there is a piece of your use case I am not understanding but I was thinking you'd be sending a buffer like say a 256 byte buffer that's now split into 128 bytes for Primary / Alternate channels and prompting that transfer to occur as needed. So you'd run the timer at a speed where you can just trigger the next transfer. I don't see how something like that would scale up to trying to do so at a 2MHz timer rate.

    That deviated some from the fundamental answer but wanted to express what my understanding is about the use case.

    The answer itself is that in ping-pong mode I don't see a way to do what you described. DMA transfers are effectively one of three types: memory-to-memory, memory-to-peripheral, and peripheral-to-memory. This is important to understand for why it may not work

    With Scatter-Gather mode, you can do something where you have multiple peripherals to memory which is the point of that mode, but even with that, I am not sure that scatter gather would support Timer --> DMA --> trigger burst write into Buffer --> DMA --> EPI. I haven't heard of something like that working but I haven't tried it to prove it doesn't. Typically its like ADC0 to Buffer[0], ADC1 to Buffer[1],... ADCx to Buffer[X] and not using one peripheral to trigger a data send from memory to another peripheral. But as established, you'd lose the continuous data transfers at that point.

    For ping-pong it wouldn't be possible because you are either doing single peripheral to memory, or memory* to single peripheral. And since you are asking to tie together two peripherals and a memory buffer, there no means to support all three with ping-pong mode. This is also illustrated in Figure 9-2. Example of Ping-Pong μDMA Transaction where it's focused on a single peripheral control structure so there isn't another control structure that can be included to support both Timer and EPI.

    Best Regards,

    Ralph Jacobi

  • Hi Ralph

    Thank you for your help.

    I was looking for a solution where I can periodically write data over EPI interface to parallel DAC which requires that the updates are done with uniform periods. I have done similar successful implementation with C2000 series microcontroller using DMA+EXTIF+DAC
    Since I have now tested two DMA modes for continues DMA transfers I think we can close this thread.