SPI and DMA

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

SPI and DMA

4,214 Views
john71
Senior Contributor I

I need to transfer a big block of data (4096 bytes) on SPI using DMA.

I found an example. The example is for K20 and I use K10 but I think the DMA registers are the same.

I have several questions

void vfnInitDMA_SPITxMstr(void)
{
  SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
  SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;
  /**********************************************************************************************************************************/
  /**********************************************************************************************************************************/
  /* Look for the DMA request sources table on your RM for a complete list of available sources */
  DMAMUX->CHCFG[0] |= DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(17); //SPI0 TX
   /*start the sequence*/
  //DMA_ERQ |= DMA_ERQ_ERQ0_MASK;
  /* This example has no table of data to                                                */
    /* transfer, making only a single minor loop necessary to complete a major loop        */
  DMA0->TCD[0].SADDR =  (uint32_t)&MstrDataSend_Buff;    //Source address
  /* Destination address */
  DMA0->TCD[0].DADDR = (unsigned long)(&SPI0->PUSHR); //
    /* Source offset disabled */
  DMA0->TCD[0].SOFF = 0x04;
    /* Source and Destination Modulo off, source and destination size 2 = 32 bits */
  DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(2) | DMA_ATTR_DSIZE(2);
    /* Transfer 4 bytes per transaction */
  DMA0->TCD[0].NBYTES_MLNO = 0x04;
    /* No adjust needed */
  DMA0->TCD[0].SLAST = -32;
    /* Destination offset disabled */
  DMA0->TCD[0].DOFF = 0x00;
    /* No link channel to channel, 1 transaction */
  DMA0->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(8);
    /* No adjustment to    destination address */
  DMA0->TCD[0].DLAST_SGA = 0;
    
  DMA0->TCD[0].BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(8);

  DMA0->TCD[0].CSR = DMA_CSR_INTMAJOR_MASK | DMA_CSR_DREQ_MASK;
}

I didn't find the DMA request sources table - what source corresponds to what module.
The destination data transfer size - is it total size to transfer? how do I set total size to transfer?
In the example no CS pin handling. Hwo operates the CS pin?

11 Replies

2,402 Views
john71
Senior Contributor I

I still can not get it running.

I configure DMA

DMA_SPI_TxMstr();

EnableInterrupts;

NVICISER0 |= (1<<DMA0_IRQn);

set_irq_priority (DMA0_IRQn, 1);

And I set an interrupt routine

void DMA0_isr(void)
{
    GPIOB_PCOR = (1<<18);
    dma_active_flag = 1;
    DMA_CINT |= DMA_CINT_CINT(0);
    if(DMA_TCD0_CSR & DMA_CSR_DONE_MASK)
    {
        dma_active_flag = 2;
        SPI2_CS0_ON;
        DMA_CDNE |= DMA_CDNE_CDNE(0);
        SPI2_SR  |= SPI_SR_EOQF_MASK; //clear EOQF flag
        //SPI0_RSER &= ~SPI_RSER_TFFF_RE_MASK;    //Stop the request to the DMA
    }
}

Then I put it in a vectortable

(tIsrFunc)& DMA0_isr,          /* 0x10  0x00000040   -   ivINT_DMA0_DMA16               unused by PE */

And in the code when I want to write a block of data

void SPI2_MasterSend_DMA (void)
{
    DMA_ERQ |= DMA_ERQ_ERQ0_MASK;       //Enable the requests of the channel 0

    while(!(SPI2_SR & SPI_SR_EOQF_MASK)) {} //wait till the last entry has been transmitted

    SPI2_SR  |= SPI_SR_EOQF_MASK; //clear EOQF flag
}

I wonder why I should wait in  while(!(SPI2_SR & SPI_SR_EOQF_MASK)) {} - what's the point of DMA then? But this is another question.

I set a break point in DMA0_isrt, and I'm not getting there.

0 Kudos

2,402 Views
mahmoudsherrah
Contributor III

Hello Evgeny,

The basic idea is to enable interrupt only on DONE signal. The interrupt just clears the DONE bit and disables the DMA channel.

Your send function just needs to setup the source address, the number of bytes to send and then enable the DMA channel.

The DMA will communicate independantly with SPI in the background (no need for polling/while loops) and fire the DONE interrupt once the number of bytes are sent.

Don't forget to enable cycle steal mode since you need a single transfer per trigger, since you are limited by the SPI baudrate (unlike super fast memory to memory transfers for example)

 

Could you please refer to my post here? It is discussing DMA UART but I guess it is also applicable to SPI.

FRDM-KL25Z: Bare Metal DMA Basics?

Here I am using DMAMUX to start/stop the transfers. You can use this as a working starting point and then explore your options without DMAMUX.

Hope this helps.

2,402 Views
john71
Senior Contributor I

Thank you.

I have different registers.

The code I represented is official example.

0 Kudos

2,402 Views
mjbcswitzerland
Specialist V

Hi Evgeny

Here is a reference from the uTasker project:

        static const unsigned long ulSPI_TX[8] = {                       // fixed SPI transmission (0x01, 0x02,.. 0x08) with CS0 asserted throughout the frame
                (0x01 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
                (0x02 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
                (0x03 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
                (0x04 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
                (0x05 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
                (0x06 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
                (0x07 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
                (0x08 | SPI_PUSHR_EOQ  | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0), // final byte negates CS after transmission
        };

fnConfigDMA_buffer(3, DMAMUX_CHCFG_SOURCE_SPI0_TX, sizeof(ulSPI_TX), ulSPI_TX, (void *)SPI0_PUSHR_ADDR, (DMA_LONG_WORDS | DMA_DIRECTION_OUTPUT | DMA_SINGLE_CYCLE), _spi_tx_dma_Interrupt, SPI_DMA_TX_INT_PRIORITY);

It sends 8 data bytes of 1,2,3,4,5,6,7,8 and asserts the CS0 during the transmission (as controlled by the SPI_PUSHR_CONT and SPI_PUSHR_EOQ flags in each command/data.
DMA channel is 3 and the SPI Tx trigger is selected by DMAMUX_CHCFG_SOURCE_SPI0_TX, which is 17 (0x11) for K20/K10 (see the DMAMUX section in the user's manual for a list of trigger channel numbers).
_spi_tx_dma_Interrupt is an optional end of transfer interrupt call-back (otherwise set to 0).
To your questions:
- The TCD_NBYTES_ML value matches the number of physical bytes to be transferred, as does TCD_DLASTSGA, but TCD_BITER_ELINK and TCD_CITER_ELINK need to be set to the number of "service requests" (number of physical bytes/4 for long word transfers).

The CS line control is automated in the reference but you could also just set the CS line low (as GPIO) before starting a transmission and set it high again once the transfer has completed [DMA complete interrupt] (assuming it needs to stay low throughout the frame) - the automated method ensures accurate timing since you will need to otherwise wait for the final transfer to physically complete (poll the Tx busy flag) to ensure to not negate it too early.

If in doubt just use the uTasker Open Source project as reference where DMA interfaces are compatible with all processors and types (eg. KL with different DMA controller) and don't need the numbers to be looked up and plugged in. It simulates the DMA operation for simple verification in Visual Studio and then you can copy the values over to complete or improve the reference code that you are using.

Regards

Mark


uTasker developer and supporter (+5'000 hours experience on +60 Kinetis derivatives in +80 product developments)
Kinetis: http://www.utasker.com/kinetis.html

2,402 Views
john71
Senior Contributor I

Thank you Mark.

Something I failed to understand

1. Why should I add to the data all these flags SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0? (actually this one I understood)

2. So I should write TCD_BITER_ELINK0 = 4096 / 4;

                     TCD_CITER_ELINK0 = 4096 / 4 ?

3. I still failed to find the sources table in reference manual.

One more question. In my project (unfortunately generated with ProjectExpert) there is no core_cm4.h included and I don't have

__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{
/*  NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F));  enable interrupt */
  NVIC->ISER[(uint32_t)((int32_t)IRQn) >> 5] = (uint32_t)(1 << ((uint32_t)((int32_t)IRQn) & (uint32_t)0x1F)); /* enable interrupt */
}

So I try to enable DMA interrupt manually - is it correct NVICISER0 |= (1<<0);?

0 Kudos

2,402 Views
mjbcswitzerland
Specialist V

Evgeny

The interrupt enable will work for for DMA channel 0, but for other channels you need to set (1 << DMA channel number).
|= is not entirely correct since it is a set register, where only writing '1' has an effect so you can just do = (1 << DMA channel number).

You should also add a priority to the interrupt too.

See

extern void fnEnterInterrupt(int iInterruptID, unsigned char ucPriority, void (*InterruptFunc)(void));
in the uTasker open source project for a reference of how to do it efficiently and simply. It also simulates interrupt operation and peripherals so that you can then test/develop efficiently - you can quickly get everything working and tested and then copy the parts back to improve the more basic library that you are using.

Regards

Mark

0 Kudos

2,402 Views
john71
Senior Contributor I

Thank you Mark.

Couple of issues remaining

I don't have void fnEnterInterrup - so how should I set interrupt priority for DMA channel 0 - NVICIP0 = (1<<0) ?

I don't understand - what register should I set for the data size (4096 bytes)?

0 Kudos

2,402 Views
mjbcswitzerland
Specialist V

Hi Evgeny

NVIC: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0439b/Cihfihfe.html
Code from https://github.com/uTasker/uTasker-Kinetis
Routine (stripped down to Cortex-M4 only):

// Function used to enter processor interrupts
//
extern void fnEnterInterrupt(int iInterruptID, unsigned char ucPriority, void (*InterruptFunc)(void))
{
    volatile unsigned long *ptrIntSet = IRQ0_31_SER_ADD;
    volatile unsigned char *ptrPriority = IRQ0_3_PRIORITY_REGISTER_ADD;
#if !defined INTERRUPT_VECTORS_IN_FLASH
    VECTOR_TABLE *ptrVect = (VECTOR_TABLE *)VECTOR_TABLE_OFFSET_REG;
    void (**processor_ints)(void);
    processor_ints = (void (**)(void))&ptrVect->processor_interrupts;    // first processor interrupt location in the vector table
    processor_ints += iInterruptID;                                      // move the pointer to the location used by this interrupt number
    *processor_ints = InterruptFunc;                                     // enter the interrupt handler into the vector table
#endif
    ptrPriority += iInterruptID;                                         // move to the priority location used by this interrupt
    *ptrPriority = (ucPriority << __NVIC_PRIORITY_SHIFT);                // define the interrupt's priority (16 levels for Cortex-m4 and 4 levels for Cortex-m0+)
    ptrIntSet += (iInterruptID/32);                                      // move to the interrupt enable register in which this interrupt is controlled
    *ptrIntSet = (0x01 << (iInterruptID % 32));                          // enable the interrupt
}

where __NVIC_PRIORITY_SHIFT is 4 for Cortex-M4.


TCD_BITER_ELINK and TCD_CITER_ELINK are used for the transfer length.

Regards

Mark

0 Kudos

2,402 Views
mahmoudsherrah
Contributor III

Dear Evgeny,

I just wanted to tell you where to exactly find the DMA MUX sources table, because I also spent a lot of time looking for it everywhere and it was a bit frustrating :smileygrin:

In the reference manual, go to section 3.3.9.1 DMA MUX request sources

There you will find all 64 DMA MUX sources. The SPI sources are from 16 to 19 for example.

Cheers

0 Kudos

2,402 Views
john71
Senior Contributor I

Dear Mahmoud thank you very much. Finally! :smileyhappy:

0 Kudos

2,402 Views
mahmoudsherrah
Contributor III

You're welcome :smileygrin:

It's funny coz somehow I opened up the KL25z reference manual instead of the K10. The section name is the same but number is different. I guess you searched for the name "DMA MUX Request Sources" after you found out it was the wrong section number! Haha

Anyway, I updated my reply. Cheers!

0 Kudos