cancel
Showing results for 
Search instead for 
Did you mean: 

SPI DMA - too much delay time between bytes

kecskesgery
Associate II
Posted on November 11, 2015 at 17:36

Hello there!

In my application, I have a 320x240 graphic display (IlI9341 disp dirver), which I'm communicating with on SPI interface; the SPI clock is 10MHz, this is the max supported speed for the display. Now, when I fill the entire screen with some color, the filling process is kinda slow, it takes about 1 sec. In my opinion, this could be done so much faster. This is when I thought, it'll be good idea to use DMA. Now, I use the HAL_SPI_Transmit_DMA() function, but for surprise it is a bit slower than without the DMA use. At this point, I wanted to check on scope, how the bytes transfer happens. What really hit me, is that when I use the pollingSPI functions, the delay between two bytes is around 6400 ns, but when I use DMA, it is doubled. Since I'm writing every pixel on the screen (320x240), this much delay is just unacceptable

. So again, I dig myself into the HAL library, and I found there theHAL_SPI_TxCpltCallback() function. My only problem is that I dont really know how to use it. I do understand that is get called, when the SPI is ready for another transfer, but how am I supposed to use it for sending data to the screen.

This is what I tried, but it does not seem to boost up the write process:

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
isILI9341Ready = SET;
}

And then

void ILI9341_SendData(uint8_t data)
{
/*Wait for the callback flag*/
while(isILI9341Ready!= SET);
ILI9341_DC(1);
ILI9341_SPI_nSS(0);
if(HAL_SPI_Transmit_DMA(SPI_HandleDef, &Value, size)!= HAL_OK)
{ 
ErrorHandler();
}
ILI9341_SPI_nSS(1);
 isReady = RESET;

}

Well, I'm guessing I'm using the calback function wrong. Can you guys please help me? I' sure, I'm missing something. There is no way, that the SPI communication has that much delay between each byte transfer. #txcallback #dma #spi
8 REPLIES 8
megahercas6
Senior
Posted on November 11, 2015 at 17:46

what kind of MCU you are using ?

I have similar task with DCMI and SRAM. I end up using double buffer DMA because DMA only can transfer 65536*16b at once. And i use TC interrupt to switch to new address. Very easy, and you are free to do other taks, and can push lot of data.

And yes, HAL does not work with me , only SPL

kecskesgery
Associate II
Posted on November 11, 2015 at 17:55

I have a F429. 

megahercas6
Senior
Posted on November 11, 2015 at 17:59

Can you show SPI and DMA init code. Maybe you are using TIM to trigger DMA,  not *free running* dma

kecskesgery
Associate II
Posted on November 11, 2015 at 18:12

Sure, here it is:

void SPI_Init_Config(SPI_Periph_t SPIx_type, uint32_t CLKPolarity, uint32_t CLKPhase, uint32_t MAX_SPI_Frequency)
{
HAL_StatusTypeDef SPI_Status;
SPI_HandleTypeDef *SPI_HandleDef;
SPI_TypeDef *SPIx;
if(SPIx_type == SPI_AD7792){
SPI_HandleDef = &SPI_HandleDef_AD7792; SPIx = SPI1;
}
else if(SPIx_type == SPI_RFM73){
SPI_HandleDef = &SPI_HandleDef_RFM73; SPIx = SPI2;
}
else if(SPIx_type == SPI_ILI9341){
SPI_HandleDef = &SPI_HandleDef_ILI9341; SPIx = SPI4;
}
else if(SPIx_type == SPI_AD7843){
SPI_HandleDef = &SPI_HandleDef_AD7843; SPIx = SPI4;
}
/* Enable clocks */
SPIx_ClockEnable(SPIx);
/* Configure SPIx interface for given device ------------------------------------*/
SPI_HandleDef->Instance = SPIx;
SPI_HandleDef->Init.Direction = SPI_DIRECTION_2LINES;
SPI_HandleDef->Init.Mode = SPI_MODE_MASTER;
SPI_HandleDef->Init.DataSize = SPI_DATASIZE_8BIT;
SPI_HandleDef->Init.CLKPolarity = CLKPolarity;
SPI_HandleDef->Init.CLKPhase = CLKPhase;
SPI_HandleDef->Init.NSS = SPI_NSS_SOFT;
SPI_HandleDef->Init.BaudRatePrescaler = SPI_GetPrescaleraxFreq(SPIx, MAX_SPI_Frequency);
SPI_HandleDef->Init.FirstBit = SPI_FIRSTBIT_MSB;
SPI_HandleDef->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
SPI_HandleDef->Init.CRCPolynomial = 7;
SPI_HandleDef->Init.TIMode = SPI_TIMODE_DISABLE;
SPI_Status = HAL_SPI_Init(SPI_HandleDef);
if(SPI_Status != HAL_OK)
{
ErrorHandler();
}
}

Here is the DMA init:

void ILI9341_SPI_DMA_Init()
{
SPI_HandleTypeDef *hspi = &SPI_HandleDef_ILI9341;
/* Enable DMA2 clock */
__HAL_RCC_DMA2_CLK_ENABLE();
/* Configure the DMA handler for Transmission process */
DMA_HandleDef_TX.Instance = DMA2_Stream1;
DMA_HandleDef_TX.Init.Channel = DMA_CHANNEL_4;
DMA_HandleDef_TX.Init.Direction = DMA_MEMORY_TO_PERIPH;
DMA_HandleDef_TX.Init.PeriphInc = DMA_PINC_DISABLE;
DMA_HandleDef_TX.Init.MemInc = DMA_MINC_DISABLE;
DMA_HandleDef_TX.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
DMA_HandleDef_TX.Init.MemDataAlignment = DMA_PDATAALIGN_BYTE;
DMA_HandleDef_TX.Init.Mode = DMA_NORMAL;
DMA_HandleDef_TX.Init.Priority = DMA_PRIORITY_LOW;
DMA_HandleDef_TX.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
DMA_HandleDef_TX.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
DMA_HandleDef_TX.Init.MemBurst = DMA_MBURST_INC4;
DMA_HandleDef_TX.Init.PeriphBurst = DMA_MBURST_INC4;
HAL_DMA_Init(&DMA_HandleDef_TX);
/* Associate the initialized DMA handle to the the SPI handle */
__HAL_LINKDMA(hspi, hdmatx, DMA_HandleDef_TX);
/* Configure the DMA handler for Recieving process */
DMA_HandleDef_RX.Instance = DMA2_Stream0;
DMA_HandleDef_RX.Init.Channel = DMA_CHANNEL_4;
DMA_HandleDef_RX.Init.Direction = DMA_PERIPH_TO_MEMORY;
DMA_HandleDef_RX.Init.PeriphInc = DMA_PINC_DISABLE;
DMA_HandleDef_RX.Init.MemInc = DMA_MINC_ENABLE;
DMA_HandleDef_RX.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
DMA_HandleDef_RX.Init.MemDataAlignment = DMA_PDATAALIGN_BYTE;
DMA_HandleDef_RX.Init.Mode = DMA_NORMAL;
DMA_HandleDef_RX.Init.Priority = DMA_PRIORITY_HIGH;
DMA_HandleDef_RX.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
DMA_HandleDef_RX.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
DMA_HandleDef_RX.Init.MemBurst = DMA_MBURST_INC4;
DMA_HandleDef_RX.Init.PeriphBurst = DMA_MBURST_INC4;
HAL_DMA_Init(&DMA_HandleDef_RX);
/* Associate the initialized DMA handle to the the SPI handle */
__HAL_LINKDMA(hspi, hdmarx, DMA_HandleDef_RX);
/* NVIC configuration for DMA transfer complete interrupt (SPI4_TX) */
HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
/* NVIC configuration for DMA transfer complete interrupt (SPI4_RX) */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

And here are the IT functions:

#define SPIx_DMA_TX_IRQHandler DMA2_Stream1_IRQHandler
#define SPIx_DMA_RX_IRQHandler DMA2_Stream0_IRQHandler
...
/*--------------------[ILI9341 DMA IRQHandle]-----------------------*/
void SPIx_DMA_TX_IRQHandler(void)
{
HAL_DMA_IRQHandler(SPI_HandleDef_ILI9hdmatx);
}
void SPIx_DMA_RX_IRQHandler(void)
{
HAL_DMA_IRQHandler(SPI_HandleDef_ILI9hdmarx);
}

As you see, everything is configured properly, in my opinion.
megahercas6
Senior
Posted on November 11, 2015 at 18:18

DMA_MBURST_INC4

Don't know about this, i would use minimal DMA_PeripheralBurst_Single, this is what works for me
kecskesgery
Associate II
Posted on November 11, 2015 at 18:33

Well, that does not change anything, sadly.

jpeacock
Associate III
Posted on November 11, 2015 at 19:29

You might consider better use of the DMA FIFO to avoid bus cycles to memory.  If aligned right you can do a 4 word (16 byte) burst with only one bus arbitration, and then empty the FIFO to the SPI and LCD controller.  Byte at a time DMA it takes 16 bus arbitrations instead of one with the FIFO.

If you are transferring multiples of 2 or 4 bytes in total try setting memory size to word or half word to move more data per bus access, and make sure it's aligned.  Same idea, cut down on bus contention.  The DMA FIFO is your friend when it comes to fast transfers.

What memory bank are you using, SRAM1, SRAM2, Flash?  DMA can run in parallel if you are careful about bank selection.  If code and stack are coming from the same bus master you will have contention problems.

  Jack Peacock
megahercas6
Senior
Posted on November 11, 2015 at 19:36

void SPI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
SPIx_CLK_INIT(SPIx_CLK, ENABLE);
/* Enable GPIO clocks */
RCC_AHB1PeriphClockCmd(SPIx_SCK_GPIO_CLK | SPIx_MISO_GPIO_CLK | SPIx_MOSI_GPIO_CLK, ENABLE);
/* Enable DMA clock */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
/* Connect SPI pins to AF5 */ 
GPIO_PinAFConfig(SPIx_SCK_GPIO_PORT, SPIx_SCK_SOURCE, SPIx_SCK_AF );
GPIO_PinAFConfig(SPIx_MISO_GPIO_PORT, SPIx_MISO_SOURCE, SPIx_MISO_AF); 
GPIO_PinAFConfig(SPIx_MOSI_GPIO_PORT, SPIx_MOSI_SOURCE, SPIx_MOSI_AF);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
/* SPI SCK pin configuration */
GPIO_InitStructure.GPIO_Pin = SPIx_SCK_PIN;
GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStructure);
/* SPI MISO pin configuration */
GPIO_InitStructure.GPIO_Pin = SPIx_MISO_PIN;
GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStructure); 
/* SPI MOSI pin configuration */
GPIO_InitStructure.GPIO_Pin = SPIx_MOSI_PIN;
GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStructure);
SPI_I2S_DeInit(SPIx);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
/* DMA configuration -------------------------------------------------------*/
/* Deinitialize DMA Streams */
DMA_DeInit(SPIx_TX_DMA_STREAM);
DMA_DeInit(SPIx_RX_DMA_STREAM);
/* Configure DMA Initialization Structure */
DMA_InitStructure.DMA_BufferSize = BUFFERSIZE ;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable ;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull ;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single ;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t) (&(SPIx->DR)) ;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
/* Configure TX DMA */
DMA_InitStructure.DMA_Channel = SPIx_TX_DMA_CHANNEL ;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral ;
DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)DataTx ;
DMA_Init(SPIx_TX_DMA_STREAM, &DMA_InitStructure);
/* Configure RX DMA */
DMA_InitStructure.DMA_Channel = SPIx_RX_DMA_CHANNEL ;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory ;
DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)DataRx ; 
DMA_Init(SPIx_RX_DMA_STREAM, &DMA_InitStructure);
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
SPI_Init(SPIx, &SPI_InitStructure);
/* Enable DMA SPI TX Stream */
DMA_Cmd(SPIx_TX_DMA_STREAM,ENABLE);
/* Enable DMA SPI RX Stream */
DMA_Cmd(SPIx_RX_DMA_STREAM,ENABLE); 
/* Enable SPI DMA TX Requsts */
SPI_I2S_DMACmd(SPIx, SPI_I2S_DMAReq_Tx, ENABLE);
/* Enable SPI DMA RX Requsts */
SPI_I2S_DMACmd(SPIx, SPI_I2S_DMAReq_Rx, ENABLE);
/* Enable the SPI peripheral */
SPI_Cmd(SPIx, ENABLE);
}

This is for SPI SLAVE. It will clock data with 30MHz clock, no problem. If you select mode Normal, and use TC as dma address switchover (like in dual buffer mode) You can get 40MHz data stream with no gaps in data.