SAMD21 SPI DMA

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

Has anyone managed to create demo code making use of page transfer between FLASH device into SAMD21 memory and vice versa?

 

May I try out this code, alternatively send me SPI transaction waveform to show how close between two SCLK 8 bit frame while running on 4MHz SCLK and 8MHz MCU/Perpherial. 

 

Based on a direct register (not using ASF 3.44 which is two time slower), It shown transfer of 6 byte while /CS is LOW (1 byte command, 3 byte address and two byte read data) which take 46uSec. This is based on 4MHz SCLK with 8MHz MCU and Perpherial (low power). Would DMA-SPI transfer improve this any more (without changing the clock). 

 

I hear it not well documented and lack demo code for this. I have done DMA before for other projects (NXP/TI) which has demo code and better documentation on how to use DMA. 

 

 

This topic has a solution.
Last Edited: Tue. Jan 22, 2019 - 08:12 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I never used DMA with SPI so can't help there. I did improve some SPI code by ignoring the RX side when not needed. Not sure if it would improve your transaction (the 8 MHz CPU speed could be limiting, quite a few registers to write/check after all).
 

static inline void spiSend(const uint8_t data)
{
    while(SERCOM1->SPI.INTFLAG.bit.DRE == 0);
    SERCOM1->SPI.DATA.reg = data;
}

static inline void spiSendWait(const uint8_t data)
{
    while(SERCOM1->SPI.INTFLAG.bit.DRE == 0);
    SERCOM1->SPI.DATA.reg = data;
    while(SERCOM1->SPI.INTFLAG.bit.TXC == 0);
}

static inline uint8_t spiSendRec(const uint8_t data)
{
    while(SERCOM1->SPI.INTFLAG.bit.DRE == 0);
    SERCOM1->SPI.DATA.reg = data;
    while(SERCOM1->SPI.INTFLAG.bit.RXC == 0);
    return SERCOM1->SPI.DATA.reg;
}

One issue with that when mixing write and read is the need to clear out the rx buffering which I did like this:

        while(SERCOM1->SPI.INTFLAG.bit.RXC) {
            (void)SERCOM1->SPI.DATA.reg;
        }		

I think it could be used like this for your 6 byte transaction:

        spiSend(cmd);
        spiSend(addr0);
        spiSend(addr1);
        spiSendWait(addr2);
        while(SERCOM1->SPI.INTFLAG.bit.RXC) {
            (void)SERCOM1->SPI.DATA.reg;
        }		
        data0 = spiSendRec(0);
        data1 = spiSendRec(0);					

/Lars

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

Yes that what I did exactly.

 

Have you done a similar thing for USART for UART comm purpose?

 

 

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

No I have not, is it really needed? I mean the RX and TX on UART are not dependent like they are in SPI.

/Lars

 

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

I have used the DMA with SPI before.  However I was not interested in increasing the transfer rate as much as I was doing other work while transfer happened, as such I never timed it.

 

I also refuse to use the ASF as that it full of bugs, so I always write my own drivers. Yes I often have my own bugs in the drivers but after a few years they are pretty solid and a lot easier to understand than ASF.  

 

Also for low power (battery devices) I have found that taking the time to test various clock options worth it. That is sometimes running processor faster and getting work done quicker and thus getting back to sleep faster improves the battery life.  However this really depends on your application.  Therefore I will often run tests to find the sweet spot for my application. 

 

Trampas

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

Trampas wrote:

I have used the DMA with SPI before.  However I was not interested in increasing the transfer rate as much as I was doing other work while transfer happened, as such I never timed it.

 

I'm having a bear of a time getting bidirectional SPI data to work using DMA. If you've done this successfully without ASF, would you be willing to share the code that makes it work?

 

I've tried a variety of things, and I suspect some descriptor chaining may be necessary, but I'm not sure. The closest I've got functionally is this:

 

  1. Set up two separate DMA channels (one for TX, one for RX) on SERCOM1 (my SPI peripheral)
  2. Each has its own descriptor:
    1. TX descriptor (trigger DMAC_ID_TX) uses uint8 buffer for source w/increment enabled, SPI.DATA.reg as destination w/increment disabled
    2. RX descriptor (trigger DMAC_ID_RX) uses SPI.DATA.reg as source w/increment disabled, uint8 buffer as destination w/increment enabled
  3. Both descriptors have the same data length (beat count), i.e. the length of the buffer
  4. Start RX job first, and it will sit indefinitely in this state
  5. Start TX job next, which causes both to start running

 

This seems logical, but it doesn't work correctly. For a simple test, I have a 3-byte buffer that is pre-filled with [0x00, 0x01, 0x02]. Capturing the SPI traffic with a logic analyzer, I can see that the data actually transmitted is [0x00, 0x00, 0x02], and although all three bytes show up on MISO ([0xEF, 0xAB, 0x21] in this case), only the first two are stored in the buffer, while the last byte is overwritten with 0x00 for a final content of [0xEF, 0xAB, 0x00].

 

It's like the two jobs are not synced in any meaningful way, and so they're stepping on each other. I'm sure this is a bone-headed facepalm mistake, but any tips would be greatly appreciated.