0

I'm trying to trigger a SPI communication using a timer on the NXP LPC1788 microcontroller. In the features of the DMA user manual it's written that the controller can use a timer for triggering a DMA burst. But I can't find a hint where the trigger source is configured in the DMA controller.

I plan to send a high frequent SPI command and read the result SPI with the same frequency using the DMA Controller. The transfer should be triggered by a timer. Is this possible?

Of cause I can implement the transfer in a timer ISR. But the controller is already busy doing other more important things.

Edit:

Today I had time to retry this again. I Use timer 3 match 0 to count with 1 kHz. Then I configured DMA stream 3 with source peripheral type TIM3M0 and destination connection type SSP0 TX. The timer triggers the DMA transfer once but then the channel is disabled.

So the configuration seams to be correct as the data gets transferred once as expected. So I tried to configure circular mode for DMA. I found the linked list configuration within the DMA controller what seams perfect. I added one element link to itself to close the loop. Now If I run this example the timer triggers the DMA channel again and the communication starts in an endless loop running as fast as possible. That's also not what I wanted to archive. Why is it not waiting for the trigger condition of the source peripheral. At the moment I think this is not possible with this microcontroller.

I add my current register configuration here:

Timer configuration

Timer3 register contents

DMA source peripheral selection

DMA source peripheral register

DMA stream 3 configuration

DMA stream 3 registers part 1

DMA stream 3 register part 2

Edit 2:

I also think that there is a small chance that this is possible. You have to use 3 channels.

  1. Channel 4 is a memory to memory transfer. It gets triggered by timer 3 match 0. The task is to reconfigure and enable channel 3 and channel 5.
  2. Channel 3 is used for Memory to SPI Peripheral transfer
  3. Channel 5 is also a memory to memory transfer used to reconfigure Channel 4 finally. This one closes the loop

I added the code I'm testing at the moment. The chain runs once as expected. But reenabling the chain is not working yet. I added three test elements to the chains to see if the get executed. Do you see any obvious error? The problem is you have to reconfigure the entire DMA channel with all registers after channel transfer termination.

    GPDMA_LLI_Type linkedListEntry[15];
    uint32_t specialControlSPI = 0x0;
    uint32_t specialControlEnable = 0x0;
    uint32_t specialConfigSPI = 0x0;
    uint32_t specialConfigEnable = 0x0;
    uint32_t specialConfigReenab = 0x0;
    uint32_t specialCotrolReenab = 0x0;
    uint32_t linkedListEnab = 0x0;
    uint32_t linkedListReenab = 0x0;
    uint32_t myTest = 0x0;
    uint32_t myTest2 = 0x0;
    uint32_t srcDataRegisterSPI = 0x0;
    uint32_t srcDataRegisterEnab= 0x0;
    uint32_t srcDataRegisterReenab = 0x0;
    uint32_t destDataRegisterEnab= 0x0;
    uint32_t destDataRegisterReenab = 0x0;
    const uint32_t pattern1 = 0xAABBCCDD;
    const uint32_t pattern2 = 0xEEFF0088;

    #define MEMORY_TRANSFER_BURST (GPDMA_BSIZE_1)
    #define MEMORY_TRANSFER_WIDTH (GPDMA_WIDTH_WORD)

    void configureDMA_ForSPI_ADC_Tx(DMA dmaTx, void* data, uint32_t size)
    {
        //Disable all required channels 
        GPDMA_ChannelCmd(3, DISABLE); 
        GPDMA_ChannelCmd(4, DISABLE); 
        GPDMA_ChannelCmd(5, DISABLE); 

        //Form spcecial control for "enable" channel
        uint32_t specialControl = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;

        uint32_t j = 0;
        //Configure "enable" channel to be triggered by timer 3 match 0 resulting in a memory transfer
        GPDMA_Channel_CFG_Type conf;
        conf.ChannelNum = 4;
        conf.DstConn = 0;
        conf.SrcConn = GPDMA_CONN_MAT3_0;
        conf.TransferType = GPDMA_TRANSFER_M2M_CTRL_DMA;
        conf.SrcMemAddr = (uint32_t)&specialControlSPI;
        conf.TransferSize = 4;
        conf.DstMemAddr = (uint32_t)&LPC_GPDMACH3->CControl;
        conf.DMALLI = (uint32_t)&linkedListEntry[j],
        GPDMA_Setup(&conf, &specialControl);     

        //Prepare linked list to enable "enable" channel again
        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH3->CSrcAddr; 
        linkedListEntry[j].SrcAddr = (uint32_t)&srcDataRegisterSPI;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH3->CConfig;     ///Enable SPI channel
        linkedListEntry[j].SrcAddr = (uint32_t)&specialConfigSPI;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH5->CControl; 
        linkedListEntry[j].SrcAddr = (uint32_t)&specialCotrolReenab;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH5->CLLI; 
        linkedListEntry[j].SrcAddr = (uint32_t)&linkedListReenab;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH5->CDestAddr; 
        linkedListEntry[j].SrcAddr = (uint32_t)&destDataRegisterReenab;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;    

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH5->CSrcAddr; 
        linkedListEntry[j].SrcAddr = (uint32_t)&srcDataRegisterReenab;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH5->CConfig;    ///Enable "Reenable" channel
        linkedListEntry[j].SrcAddr = (uint32_t)&specialConfigReenab;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;        


        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH7->CControl;   
        linkedListEntry[j].SrcAddr = (uint32_t)&pattern1;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;        

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) 
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&myTest; 
        linkedListEntry[j].SrcAddr = (uint32_t)&pattern1;
        linkedListEntry[j].NextLLI = 0;        
        j++;


        //------------------------------------------------------------------------------------------------------------------

        //Form special control for "SPI" channel
        specialControl = GPDMA_DMACCxControl_TransferSize(size) \
                | GPDMA_DMACCxControl_SBSize((uint32_t)GPDMA_BSIZE_1)
                | GPDMA_DMACCxControl_DBSize((uint32_t)GPDMA_BSIZE_1)
                | GPDMA_DMACCxControl_SWidth((uint32_t)GPDMA_WIDTH_HALFWORD)
                | GPDMA_DMACCxControl_DWidth((uint32_t)GPDMA_WIDTH_HALFWORD)
                | GPDMA_DMACCxControl_SI;


        //Configure "SPI" channel to be triggered by software resulting in a peripheral transfer and a memory transfer in linked list
        conf.ChannelNum = 3;
        conf.DMALLI = 0,
        conf.DstConn = GPDMA_CONN_SSP0_Tx;
        conf.SrcConn = 0;
        conf.TransferType = GPDMA_TRANSFER_M2P_CTRL_DMA;
        conf.SrcMemAddr = (uint32_t)data;
        conf.TransferSize = size;
        conf.DstMemAddr = 0; 
        GPDMA_Setup(&conf, &specialControl);

        //------------------------------------------------------------------------------------------------------------------
        //Form special control for "reenab" channel
        specialControl = GPDMA_DMACCxControl_TransferSize(size) 
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;

        //Configure "SPI" channel to be triggered by software resulting in a peripheral transfer and a memory transfer in linked list
        conf.ChannelNum = 5;
        conf.DstConn = 0;
        conf.SrcConn = 0;
        conf.TransferType = GPDMA_TRANSFER_M2M_CTRL_DMA;
        conf.TransferSize = 4;
        conf.SrcMemAddr = (uint32_t)&specialControlEnable;
        conf.DstMemAddr = (uint32_t)&LPC_GPDMACH4->CControl;
        conf.DMALLI = (uint32_t)&linkedListEntry[j],
        GPDMA_Setup(&conf, &specialControl);

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) 
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH4->CLLI; 
        linkedListEntry[j].SrcAddr = (uint32_t)&linkedListEnab;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) 
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH4->CDestAddr; 
        linkedListEntry[j].SrcAddr = (uint32_t)&destDataRegisterEnab;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) 
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH4->CSrcAddr; 
        linkedListEntry[j].SrcAddr = (uint32_t)&srcDataRegisterEnab;
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        //Prepare linked list to enable "enable" channel again
        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) 
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&LPC_GPDMACH4->CConfig;
        linkedListEntry[j].SrcAddr = (uint32_t)&specialConfigEnable;   ///Enable trigger channel again
        linkedListEntry[j].NextLLI = (uint32_t)&linkedListEntry[j+1];
        j++;

        linkedListEntry[j].Control = GPDMA_DMACCxControl_TransferSize(4) 
                | GPDMA_DMACCxControl_SBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_DBSize((uint32_t)MEMORY_TRANSFER_BURST)
                | GPDMA_DMACCxControl_SWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_DWidth((uint32_t)MEMORY_TRANSFER_WIDTH)
                | GPDMA_DMACCxControl_SI
                | GPDMA_DMACCxControl_DI;
        linkedListEntry[j].DstAddr = (uint32_t)&myTest2; 
        linkedListEntry[j].SrcAddr = (uint32_t)&pattern2;
        linkedListEntry[j].NextLLI = 0;
        j++;

        //Store the configuration registers for DMA operation
        specialControlSPI    = LPC_GPDMACH3->CControl | GPDMA_DMACCxControl_TransferSize(size);
        specialControlEnable = LPC_GPDMACH4->CControl | GPDMA_DMACCxControl_TransferSize(4);
        specialCotrolReenab  = LPC_GPDMACH5->CControl | GPDMA_DMACCxControl_TransferSize(4);

        srcDataRegisterSPI =  LPC_GPDMACH3->CSrcAddr;
        srcDataRegisterEnab = LPC_GPDMACH4->CSrcAddr;
        srcDataRegisterReenab = LPC_GPDMACH5->CSrcAddr;

        destDataRegisterEnab = LPC_GPDMACH4->CDestAddr;
        destDataRegisterReenab = LPC_GPDMACH5->CDestAddr;

        specialConfigSPI    = (0x1) | LPC_GPDMACH3->CConfig; 
        specialConfigEnable = (0x1) | LPC_GPDMACH4->CConfig;   
        specialConfigReenab = (0x1) | LPC_GPDMACH5->CConfig;  

        linkedListReenab = LPC_GPDMACH5->CLLI;
        linkedListEnab = LPC_GPDMACH4->CLLI;

        //Enable the channel "enable" channel. Hopfully will be triggered by timer match soon. This should enable SPI transfer
        GPDMA_ChannelCmd(4, ENABLE);
    }
Jonny Schubert
  • 1,393
  • 2
  • 18
  • 39
  • Maybe Table 689 in the [User Manual](http://www.nxp.com/documents/user_manual/UM10470.pdf) has the information you're looking for. – kkrambo Mar 26 '15 at 12:26
  • Thank you for your answer. I have seen this table already but I still don't understand how to configure the DMA Controller. Do you mean I should map the Timer to SPI RX and SPI TX to my circular buffer? But in this configuration the SPI Rx gets the Timer Capture result and not the command I want to send. I'm really confused about this – Jonny Schubert Mar 26 '15 at 12:41
  • I'm not familiar with setting up a timer DMA request so I can't answer directly. But It looks like chapter 24.6.12 in the User Manual has more details. – kkrambo Mar 26 '15 at 12:56
  • Reading the user manual linked by @kkrambo, section 34.6.2.1 indicates that after a burst transfer the channel is left disabled. The last paragraph of section 34.6.5.1.2 seems to support this. So it looks like you might need to enable the terminal count interrupt and re-enable the channel after each burst. – Jeremy Mar 18 '19 at 09:12
  • 1
    A crazy thought: If you really want to avoid handling an interrupt, what if you configure a second DMA channel, at lower priority than the first and triggered off the same timer, to write to the first DMA control register to re-enable the channel? – Jeremy Mar 18 '19 at 09:15
  • I already thought about something like this but not exactly the way you are mentioning. I will give it a try. Thanks for sharing your idea! – Jonny Schubert Mar 18 '19 at 10:50
  • But one question remains. Who enables the channel of the enable channel after the time expired? The enable channel fires only once as well. – Jonny Schubert Mar 18 '19 at 10:57
  • 1
    Crikey, you're right. It's turtles all the way down! – Jeremy Mar 19 '19 at 10:42
  • OK then, more madness: the timer triggers a DMA channel that enables a second channel and then writes to the Software Single Request register, triggering that channel. That second channel uses linked descriptors to (a) perform the SPI transfer, and then (b) re-enable the first channel. – Jeremy Mar 19 '19 at 10:47
  • Ok I will give it a try. What a configuration horror... – Jonny Schubert Mar 19 '19 at 10:56
  • Table 709 DMA controller points out: Restarting the channel by setting the Channel Enable bit has unpredictable effects, the channel must be fully re-initialized... Does this only affect the channel configuration register? – Jonny Schubert Mar 19 '19 at 14:30
  • You'll need to suck it & see - I'm working purely from the user manual. But I guess if you need to completely reconfigure the channel I see no reason why this couldn't be done using one or more linked descriptors. – Jeremy Mar 20 '19 at 08:43
  • NXP support told me that this is not possible with this uC. Anyway I managed to implement this using an STM F7 uC. See https://electronics.stackexchange.com/questions/353152/stm32f-how-to-config-dma-transfer-to-spi-triggered-by-timer/429296#429296 – Jonny Schubert Apr 08 '19 at 07:23

0 Answers0