S32K DMA and SPI

取消
显示结果 
显示  仅  | 搜索替代 
您的意思是: 
已解决

S32K DMA and SPI

跳至解决方案
6,558 次查看
rudolphriedel
Contributor II

Hello,

I am new to S32K144 and I am trying to send a  buffer with DMA over SPI.

This is on a S32K144EVB and except for DMA I got it to work so far.

LPSPI0 is setup for 1 byte transfers.

Now I am stuck at this point:

#if defined (EVE_DMA)
uint32_t EVE_dma_buffer[1025];
volatile uint16_t EVE_dma_buffer_index;
volatile uint8_t EVE_dma_busy = 0;

void EVE_init_dma(void)
{
DMAMUX->CHCFG[EVE_DMA_CHANNEL] = 0;
DMAMUX->CHCFG[EVE_DMA_CHANNEL] |= DMAMUX_CHCFG_SOURCE(15); /* EDMA_REQ_LPSPI0_TX */
DMAMUX->CHCFG[EVE_DMA_CHANNEL] |= DMAMUX_CHCFG_ENBL_MASK;

DMA->TCD[EVE_DMA_CHANNEL].CSR = 0U;
DMA->TCD[EVE_DMA_CHANNEL].SADDR = DMA_TCD_SADDR_SADDR((uint32_t) ((&EVE_dma_buffer[1])));
DMA->TCD[EVE_DMA_CHANNEL].SOFF = DMA_TCD_SOFF_SOFF(1); /* add 1 after each transfer */
DMA->TCD[EVE_DMA_CHANNEL].ATTR = 0U; /* 8-bit source and destination transfer size */
DMA->TCD[EVE_DMA_CHANNEL].SLAST = 0U;
DMA->TCD[EVE_DMA_CHANNEL].DADDR = DMA_TCD_DADDR_DADDR((uint32_t) &EVE_SPI->TDR);
DMA->TCD[EVE_DMA_CHANNEL].DOFF = 0U; /* do not increase address after each transfer */
DMA->TCD[EVE_DMA_CHANNEL].DLASTSGA = 0U;
DMA->TCD[EVE_DMA_CHANNEL].NBYTES.MLNO = DMA_TCD_NBYTES_MLNO_NBYTES(1); /* transfer 1 byte per minor loop */
DMA->TCD[EVE_DMA_CHANNEL].CITER.ELINKNO = 0U;
DMA->TCD[EVE_DMA_CHANNEL].BITER.ELINKNO = 0U;
}

void EVE_start_dma_transfer(void)
{
uint8_t *bytes = ((uint8_t *) &EVE_dma_buffer[0])+1;

#if 0
uint16_t length = (EVE_dma_buffer_index*4)-1;

EVE_SPI -> TCR |= LPSPI_TCR_RXMSK_MASK; /* disable LPSPI receive */
EVE_cs_set();
for(uint16_t index = 0; index < length; index++)
{
EVE_SPI->SR |= LPSPI_SR_TDF_MASK; /* clear transmit data flag */
EVE_SPI->TDR = bytes[index]; /* transmit data */
while((EVE_SPI->SR & LPSPI_SR_TDF_MASK) == 0);
}
while((EVE_SPI->SR & LPSPI_SR_TCF_MASK) == 0);
EVE_cs_clear();
EVE_SPI -> TCR &= ~LPSPI_TCR_RXMSK_MASK; /* enable LPSPI receive */
#endif

#if 1
uint16_t length = (EVE_dma_buffer_index-1)*4;

EVE_SPI -> TCR |= LPSPI_TCR_RXMSK_MASK; /* disable LPSPI receive */
EVE_cs_set();

for(uint16_t index = 0; index < 3; index++)
{
EVE_SPI->SR |= LPSPI_SR_TDF_MASK; /* clear transmit data flag */
EVE_SPI->TDR = bytes[index]; /* transmit data */
while((EVE_SPI->SR & LPSPI_SR_TDF_MASK) == 0);
}

DMA->TCD[EVE_DMA_CHANNEL].SADDR = DMA_TCD_SADDR_SADDR((uint32_t) ((&EVE_dma_buffer[1])));
DMA->TCD[EVE_DMA_CHANNEL].CITER.ELINKNO = DMA_TCD_CITER_ELINKNO_CITER(length) | DMA_TCD_CITER_ELINKNO_ELINK(0);
DMA->TCD[EVE_DMA_CHANNEL].BITER.ELINKNO = DMA_TCD_BITER_ELINKNO_BITER(length) | DMA_TCD_BITER_ELINKNO_ELINK(0);
DMA->TCD[EVE_DMA_CHANNEL].CSR |= DMA_TCD_CSR_START(1);
while((DMA->TCD[EVE_DMA_CHANNEL].CSR & DMA_TCD_CSR_START_MASK) != 0); /* wait for channel to start */
while((DMA->TCD[EVE_DMA_CHANNEL].CSR & DMA_TCD_CSR_ACTIVE_MASK) != 0); /* wait for channel to end execution */
while((EVE_SPI->SR & LPSPI_SR_TCF_MASK) == 0);
// EVE_dma_busy = 42;
EVE_cs_clear();
EVE_SPI -> TCR &= ~LPSPI_TCR_RXMSK_MASK; /* enable LPSPI receive */
#endif
}

So the intention is to send everything from &EVE_dma_buffer[1] over the SPI with DMA and sending it byte by byte.

The commented out code where the buffer is send per loop works just fine.

In the not working code the first three bytes are send with a direct loop and again this works.

But the DMA transfer is not working, it stops after only 1 byte transferred.

My first idea was to only use the minor loop and set TCD[x].NBYTES.MLNO to the number of bytes in the buffer plus CITER and BITER to 1.

But his is not working either, this way only a fraction of bytes is send and the ones that are send are not korrect, this looks like there are bytes skipped.

For example when I set the minor loop to 122 I see 11 bytes on the SPI.

What did I do wrong?

And the next task when this works is to setup an interrupt for when the DMA transfer is done.

I would apreciate a cookbook example for something like this. Memory to SPI with IRQ at the end.

My application is driving a display but annother setup would be writing pages of 4k each to an external SPI flash.

0 项奖励
回复
1 解答
6,519 次查看
rudolphriedel
Contributor II

The linked example really helped a lot, I got it to work now.

Most of all I forgot to set the TDDE bit in SPI->DER.

And then I tried to trigger the DMA transfer by using the START bit in TCD.CSR but somehow this does not do much.

I switched over to using DMA->SERQ = channel and also set the DREQ bit in TCD.CSR to make the DMA stop after one major loop.

Some cleanup, adding the end-of-dma handler and it is ready for release.

Thank you.

在原帖中查看解决方案

0 项奖励
回复
4 回复数
6,538 次查看
danielmartynek
NXP TechSupport
NXP TechSupport

Hello @rudolphriedel,

You can use the lpspi_transfer_s32k144 SDK example and change it from the interrupt mode to DMA.

There also is this non-SDK example, but it uses LPIT to trigger a few transfer periodically:

https://community.nxp.com/t5/S32K-Knowledge-Base/Example-S32K144-LPIT-DMA-LPSPI/ta-p/1108628

Please share the whole project so that I can test it.

 

Thank you,

BR, Daniel

 

0 项奖励
回复
6,535 次查看
rudolphriedel
Contributor II

The thing with using the SDK is that early on it was clear that using the SDK is not working for this application with at this point pauses between bytes send over the SPI of 31µs.

The SDK function took way more time to do something else than actually sending the data.

In the first iteration of my own SPI send function I got that down to 21µs for the whole transfer of four bytes from chip-select down to chip-select up, with the SDK that would be ~150µs.

The next issue I ran into is that there are no standard CMSIS headers provided with functions like SysTick_Config() and when I added my own SysTick_Handler() I had to learn that the SDK SPI driver also is adding some RTOS files that they are depending upon.

I went along with the pins and clock driver. But only for the configuration, I am not driving the pins thru SDK functions.

Then I also checked out the "lpspi_dma_s32k144" example but it does not explain at all what is going on, it for the most part obfuscates the action with lots of unnecessary code. Not on purpose I presume but just because it is just a generic driver and is not on point of what is actually needed and what needs to be done.

Also I found the reference manual irritating since the way registers and bits in these registers are descriped does not match the actual syntax from the SDK.

I have been using quite a number of controllers so far but I had no prior experience with NXP controllers. At least not directly since the Teensy 4.1 also is using a NXP controller but the supplied SPI functions with DMA support just worked out of the box.

The S32K functions are in EVE_target.h line 1034ff and in EVE_target.c line 383ff. And of course the main.c is setup for only this project.

The rest of the files in /src  are from my standard small demo for these displays and not part of the issue that the DMA is not looping thru the major loops.

 

Edit: forgot about one important detail, without the display attached the software will never get to the point of actually using the DMA to transfer a buffer since it is only used to refresh the frames.

Well, adding

EVE_init_dma();

EVE_dma_buffer_index = 32;

EVE_start_dma_transfer();

to main() compiles so it should behave the same, only the buffer is of course not filled with anything meaningfull.

 

Ok, I checked with the logic analyser and modified main() a little:

int main(void)
{
uint8_t led_delay = 0;
uint8_t display_delay = 0;

/* Initialize and configure clocks */
CLOCK_SYS_Init(g_clockManConfigsArr, CLOCK_MANAGER_CONFIG_CNT, g_clockManCallbacksArr, CLOCK_MANAGER_CALLBACK_CNT);
CLOCK_SYS_UpdateConfiguration(0U, CLOCK_MANAGER_POLICY_AGREEMENT);
PINS_DRV_Init(NUM_OF_CONFIGURED_PINS0, g_pin_mux_InitConfigArr0); /* initialize pins */

SysTick_Init(48000000 / 200); /* configure and enable Systick for 5ms ticks */

EVE_init_spi();
// TFT_init();

EVE_init_dma();
EVE_dma_buffer_index = 32;
EVE_start_dma_transfer();


while(1)
{
if(system_tick)
{
system_tick = 0;

led_delay++;
if(led_delay > 49)
{
led_delay = 0;
LED0_PORT->PTOR = (1<<LED0_PIN);
}

// TFT_touch();

display_delay++;
if(display_delay > 3)
{
display_delay = 0;
// TFT_display();
}
}
}
}

And the result is four bytes on the SPI instead of the expected 127 for EVE_dma_buffer_index = 32.

0 项奖励
回复
6,520 次查看
rudolphriedel
Contributor II

The linked example really helped a lot, I got it to work now.

Most of all I forgot to set the TDDE bit in SPI->DER.

And then I tried to trigger the DMA transfer by using the START bit in TCD.CSR but somehow this does not do much.

I switched over to using DMA->SERQ = channel and also set the DREQ bit in TCD.CSR to make the DMA stop after one major loop.

Some cleanup, adding the end-of-dma handler and it is ready for release.

Thank you.

0 项奖励
回复
6,515 次查看
rudolphriedel
Contributor II

And to round this up, attached is the project that is also using the DMA-done interrupt.