10 years, 6 months ago.

DMA with Freescale KL25z

Anyone get this going?

In particular, I am trying to drive an SPI exchange ...with an SD card.

I have studied the reference manual and have tried innumerable combinations of things with no luck so far. And now, to make life easier, I cannot even read or write the DMAMUX registers without the board freezing on me. :(

Hello Steve,

I advice you to share your code. Somebody can take your code as a starting point or at least correct the code. Please share your progress here.

Regards,
0xc0170

posted by Martin Kojtal 18 Oct 2013

2 Answers

10 years, 6 months ago.

Answer 1: Yes

I am working on it, to drive an SPI exchange ...with an SD card. :P

So we apparantly got kinda the same idea. I haven't actually tested DMA code with the SPI, only with memory and UART, since they are easy for me to observe. I can currently send data with DMA, either on full-speed (for memory to memory), or depending on trigger from a peripheral. It is still missing an interrupt when it is finished, and I probably want to restructure some of the program.

Considering your board freezes when you try to do something with DMAMUX, did you enable the peripheral clock to the correct locations?

    //Enable DMA
    SIM->SCGC6 |= 1<<1;     //Enable clock to DMA mux
    SIM->SCGC7 |= 1<<8;     //Enable clock to DMA

For memory to memory you don't actually need the DMAMUX, so you can also try that first. Although in the library I do everything via the DMAMUX for simplicity (and use an always-on channel for memory to memory). And for example my complete code for setting the DMAMUX channel:

int SimpleDMA::trigger(SimpleDMA_Trigger trig){ 
    DMAMUX0->CHCFG[_channel] = 0;
    DMAMUX0->CHCFG[_channel] = trig;
    return 0;
}

10 years, 6 months ago.

ok, will share code. Bear in mind, I have tried a million combinations of things, so this is just a snapshot of one attempt: And it's very bare bones, so it's hard to read without the ref. manual next to you - I'll add in some commentary to help...

// Memory addresses for things
uint8_t *spi = (uint8_t *)0x40076000u;//SPI0
int *dma = (int*)0x40008120u;//DMA0
int *dma2 = (int*)0x40008130u;//DMA1
uint8_t *dmamux = (uint8_t*)0x40021000u;
unsigned int *SIM_SCGC6 = (unsigned int*)0x4004803Cu;
.
.
.
SIM_SCGC6[0] |= 0x0020;
SIM_SCGC6[1] |= 0x0100;

// Will halt here if I dont comment out these lines
dmamux[0] = 0x00;// Clear before setting up DMA channel
dmamux[1] = 0x00;// Clear before setting up DMA channel

dma[2] = 0x1000000;// clear DMA0 status reg, reset w/ DONE flag
dma2[2] = 0x1000000;// clear DMA1 status reg, reset w/ DONE flag

// Configure READ DMA
dma[0] = (unsigned int)(spi+5);// set source address: SPI0_Data register
dma[1] = (unsigned int)buffer; // set dest address: memory buffer
dma[2] = 0xffffff & length; // length of transfer
dma[3] = 0xE09B00A4;  // That is: EINT|ERQ|CS|EADREQ|SSIZE=8-bit|DINC|DSIZE=8-bit|START

// Configure WRITE DMA
dma2[0] = (unsigned int)buf2;// for eg, a 512byte buffer filled with 0xff - have tried various other options as well
dma2[1] = (unsigned int)(spi+5); // dest address - SPI0 data reg
dma2[2] = 0xffffff & (length); // length of transfer
dma2[3] = 0xE0D300A0; // That is: EINT|ERQ|CS|EADREQ|SINC|SSIZE=8-bit|DSIZE=8-bit|START

dmamux[0] = 0xC0 | 0x10;// ENABLE, TRIG, SPI0 receive chnl
dmamux[1] = 0xC0 | 0x11;// ENABLE, TRIG, SPI0 transmit chnl


// Config SPI0 to work w/ DMA for both read/write
spi[0] |= 0xE0;// Enable interrupts for DMA transmit and receive
spi[1] |= 0x24;// Enable DMA for transmit and receive

I have also tried with and without the initial write to SPI data, as per ref. manual. I have tried various flavours of DMA DCR flags, various ordering of these lines of code. I'd hate to say I've tried it all, but it feels like it. Hopefully someone can see the folly of my ways and point out what I am doing wrong.

Also, to be clear, I am using the SDFileSystem library - and just injecting this code into the _read, and only when the request is for a 512-byte block - so I know the SD card is all set up properly. I've also made it conditional upon pressing a button (tied to a digitial IO pin) - so I can easily swap original code with the DMA code, and ensure that the only diff is this one. etc.

Thanks.

It is probably easier to start making sure your DMA works with simpler situations.

Aditionally you do know about the register defines? For the KL25 it is here: http://mbed.org/users/mbed_official/code/mbed-src/file/a3c7023e45de/targets/cmsis/TARGET_Freescale/TARGET_KL25Z/MKL25Z4.h. This file is automatically included if you include the mbed library, so then you can use registers as I got in my code, without risking errors in the addresses.

Quick look at your code shows that your DMAMUX clock enable in the SIM part is shifted 4 bits compared to mine.

posted by Erik - 18 Oct 2013

Thanks again!

I think I will try to simplify a bit. I did try to keep the DMA bit very localized, and I do disable it once the transfer is complete (well, we don't reach that code, but the intent is to keep it self-contained). However, even if I got a memory to memory transfer working, for example, it may not translate well to SPI. And I cannot think of what other hardware to attach to the SPI port to help simulate a real transfer of some kind - maybe a shift-register like a 74HC595? tie it's serial out to MISO? At one point during testing, I did have the SPI DMA read side fill my 512-byte buffer (CS turned off, no Write DMA config'd, etc) - it was done in practically no time at all, which was amazing, but it was all the same value (no data was being shifted through, it would seem)

I did notice the SIM shifting - so there is at least one mistake in that code. I just checked against the ref. manual as well, fwiw. For most of my testing I did not have those, and do not understand why they are needed - I found a sample DMA app that had them so I put them in on a whim. Ref. manual is lean on explaining those. I had assumed it was related to using clocks to trigger DMA transfers, whereas I am hoping the SPI flags will do this (SPRF, SPTEF). I guess they are needed regardless, and will fix and retry asap.

I did become aware of the #defines and typedefs and such things at one point, but was too lazy to put them in - which is funny because it's not trivial making sure all those hex values are correct either. I guess I'll create yet another test app, simplify, use defines as per the MKL25Z4.h, etc....

Thank you.

posted by Steve Marmer 18 Oct 2013

I believe below I have a fairly faithful translation, I'll try running it (and playing with it to make it work) later...

That's a lot more typing than my original, but less guesswork there as well.

My c/c++ is a bit rusty; hopefully I haven't messed up anything at that level (it does compile at least :) ). I also could not find any defines for the DMA MUX Sources, so I translated some of them from the ref. man.

This also does not show any additional SPI setup that may be required.

We have a 3k limit on posts so I'll break this up, sorry:

#include "mbed.h"

// DMA MUX Sources (partial list) - cannot find these defines elsewhere in mbed
#define DMA_MUX_SRC_SPI0_Receive (16)
#define DMA_MUX_SRC_SPI0_Transmit (17)
#define DMA_MUX_SRC_SPI1_Receive (18)
#define DMA_MUX_SRC_SPI1_Transmit (19)
#define DMA_MUX_SRC_I2C0 (22)
#define DMA_MUX_SRC_I2C1 (23)
#define DMA_MUX_SRC_TPM0_Channel_0 (24)
#define DMA_MUX_SRC_TPM0_Channel_1 (25)
#define DMA_MUX_SRC_TPM0_Channel_2 (26)
#define DMA_MUX_SRC_TPM0_Channel_3 (27)
#define DMA_MUX_SRC_TPM0_Channel_4 (28)
#define DMA_MUX_SRC_TPM0_Channel_5 (29)
#define DMA_MUX_SRC_TPM1_Channel_0 (32)
#define DMA_MUX_SRC_TPM1_Channel_1 (33)
#define DMA_MUX_SRC_TPM2_Channel_0 (34)
#define DMA_MUX_SRC_TPM2_Channel_1 (35)
#define DMA_MUX_SRC_ADC0 (40)
#define DMA_MUX_SRC_CMP0 (42)
#define DMA_MUX_SRC_DAC0 (45)

/**
 * Run a DMA Test using SPI0.  Attempts to read and write a fixed number of bytes, to/from provided buffers.
 *
 * @param dmaReadCh The DMA channel to configure for the SPI READ operation.
 * @param dmaWriteCh The DMA channel to configure for the SPI WRITE operation.
 * @param srcBuffer Data buffer of values to write out to SPI
 * @param destBuffer Data buffer in which to store values read from the SPI
 * @param length The length of both buffers
 */
void dma_test(const int dmaReadCh, const int dmaWriteCh, char *srcBuffer, char *destBuffer, int length) {
    //Enable DMA clocking
    SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK;    // Enable clock to DMA mux
    SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;       // Enable clock to DMA

    // reset DMAMUX
    DMAMUX0->CHCFG[dmaReadCh] = 0;
    DMAMUX0->CHCFG[dmaWriteCh] = 0;

    DMA0->DMA[dmaReadCh].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
    DMA0->DMA[dmaWriteCh].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
//to be continued...
posted by Steve Marmer 18 Oct 2013

continued....

    // Set up one of the DMA channels to read from SPI0, and link to the other DMA channel to trigger the next write
    DMA0->DMA[dmaReadCh].SAR = (uint32_t)&(SPI0->D);// set source address: SPI0_Data register
    DMA0->DMA[dmaReadCh].DAR = (unsigned int)destBuffer; // set dest address: memory buffer
    DMA0->DMA[dmaReadCh].DSR_BCR |= DMA_DSR_BCR_BCR_MASK & length; // length of transfer
    DMA0->DMA[dmaReadCh].DCR = DMA_DCR_EINT_MASK | DMA_DCR_ERQ_MASK | DMA_DCR_CS_MASK | /*DMA_DCR_EADREQ_MASK | DMA_DCR_SINC_MASK | */
        DMA_DCR_SSIZE(0x01) | DMA_DCR_DINC_MASK | DMA_DCR_DSIZE(0x01) | DMA_DCR_START_MASK | DMA_DCR_D_REQ_MASK |
        DMA_DCR_LINKCC(0x10) | DMA_DCR_LCH1(dmaWriteCh);
        // 8-bit transfers, link to DMA1, etc

    // Set up another DMA channel to write to the SPI, in order to force Reads, and link to above
    DMA0->DMA[dmaWriteCh].SAR = (unsigned int)srcBuffer;// set source address: dummy memory buffer for writes
    DMA0->DMA[dmaWriteCh].DAR = (uint32_t)&(SPI0->D); // set dest address: SPI0_Data register
    DMA0->DMA[dmaWriteCh].DSR_BCR |= DMA_DSR_BCR_BCR_MASK & length; // length of transfer
    DMA0->DMA[dmaWriteCh].DCR = DMA_DCR_EINT_MASK | DMA_DCR_ERQ_MASK | DMA_DCR_CS_MASK | /*DMA_DCR_EADREQ_MASK | */ DMA_DCR_SINC_MASK |
        DMA_DCR_SSIZE(0x01) | /*DMA_DCR_DINC_MASK |*/ DMA_DCR_DSIZE(0x01) | DMA_DCR_START_MASK | DMA_DCR_D_REQ_MASK |
        DMA_DCR_LINKCC(0x10) | DMA_DCR_LCH1(dmaReadCh);
        // 8-bit transfers, link to DMA0, etc

    // Configure DMAMUX0
    DMAMUX0->CHCFG[dmaReadCh] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_TRIG_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_SPI0_Receive);
    DMAMUX0->CHCFG[dmaWriteCh] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_TRIG_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_SPI0_Transmit);
 
    // Enable DMA features within the SPI registers   
    SPI0->C1 |= SPI_C1_SPIE_MASK | SPI_C1_SPE_MASK | SPI_C1_SPTIE_MASK;
    SPI0->C2 |= SPI_C2_TXDMAE_MASK | SPI_C2_RXDMAE_MASK;
}

posted by Steve Marmer 18 Oct 2013

I seem to have nailed it - tested with SPI1 pushing data through a series of four 74hc595's and then back in via MISO. Going to try it with SD card now.

posted by Steve Marmer 19 Oct 2013

Hi,

I'm having a hard time understanding how you set this up. Which pins are you using for SPI, and where are they specified? How do you initiate a read or write? How should the buffers be set up? Thanks!

posted by Braxtron Advance 21 Nov 2013