cancel
Showing results for 
Search instead for 
Did you mean: 

Issues with interrupt driven SPI DMA Rx

banks2
Associate
Posted on May 05, 2016 at 23:59

Hi, I'm trying to get an STM32F0 read incoming data packets as a SPI slave using the DMA. I've been reading the DataExchangeDMA peripheral example code and trying to convert some of it to an interrupt driven model, but I have trouble getting the DMA to work correctly. I've boiled down my code to a simple program which should just move incoming SPI data to a buffer in RAM.

To debug, I'm running the code on an STM32F051 discovery board, and using the LED (PC8) to turn on between when the DMA completes a transfer and whenever the next byte comes in over SPI. Here's the code I'm working with:


#include ''stm32f0xx.h''


#define BUFFER_LEN 16


GPIO_InitTypeDef GPIO_InitStruct;

SPI_InitTypeDef SPI_InitStruct;

NVIC_InitTypeDef NVIC_InitStruct;

DMA_InitTypeDef DMA_InitStruct;


void
init(
void
);


uint8_t DataBuffer[BUFFER_LEN];


int
main(
void
)

{

init();


for
(;;)

{

}

}


void
init()

{

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_DMA1, ENABLE);

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);


// Discovery LED

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;

GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;

GPIO_Init(GPIOC, &GPIO_InitStruct);


GPIO_SetBits(GPIOC, GPIO_Pin_8);


// SPI pins

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;

GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;

GPIO_Init(GPIOA, &GPIO_InitStruct);


GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_0);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_0);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_0);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_0);


// SPI peripheral setup

SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

SPI_InitStruct.SPI_Mode = SPI_Mode_Slave;

SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;

SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;

SPI_InitStruct.SPI_NSS = SPI_NSS_Hard;

SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;

SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;

SPI_InitStruct.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStruct);


SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF);

SPI_Cmd(SPI1, ENABLE);


// SPI interrupt config

NVIC_InitStruct.NVIC_IRQChannel = SPI1_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPriority = 1;

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStruct);


SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);


// DMA setup

DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;

DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;

DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;

DMA_InitStruct.DMA_BufferSize = BUFFER_LEN;

DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)DataBuffer;

DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;

DMA_InitStruct.DMA_Priority = DMA_Priority_High;

DMA_InitStruct.DMA_PeripheralBaseAddr = 0x4001300C; 
// SPI1 peripheral DR register address

DMA_Init(DMA1_Channel2, &DMA_InitStruct);


// DMA interrupt config

NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel2_3_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPriority = 3;

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStruct);


SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);

DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);

}


void
DMA1_Channel2_3_IRQHandler()

{

GPIO_SetBits(GPIOC, GPIO_Pin_8);

if
(DMA_GetFlagStatus(DMA1_FLAG_TC2))

{

SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);

DMA_Cmd(DMA1_Channel2, DISABLE);

DMA_SetCurrDataCounter(DMA1_Channel2, BUFFER_LEN);

DMA_ClearFlag(DMA1_FLAG_TC2);

}

}


void
SPI1_IRQHandler()

{

GPIO_ResetBits(GPIOC, GPIO_Pin_8);

if
(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE))

{

SPI_I2S_ClearFlag(SPI1, SPI_I2S_FLAG_RXNE);

SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, DISABLE);


DMA_Cmd(DMA1_Channel2, ENABLE);

}

}


void
assert_failed(
const
char
* file, 
const
int
line)

{

for
(;;);

}

And here's an image of what I'm seeing when I run it (I have a separate SPI master providing data): 0690X00000605NsQAI.png As you can see, the SPI interrupt is firing correctly after the first byte comes into the Rx buffer, but the DMA interrupt never fires. Did I miss something with the DMA interrupt configuration? I have similar code driving DMA interrupts on a different application and it's working with no issues. Also, I'm not sure if I need to make the DMA wait and move one byte at a time instead of the whole buffer to prevent it trying to move data that hasn't yet made it to the SPI data register. This would certainly use more CPU cycles which is unfortunate in my application, I just don't know whether it's necessary or not. Either way, I'd still expect the TC interrupt to fire in the code I gave above. Thanks for any help, and let me know if any additional details would be helpful.
2 REPLIES 2
knielsen
Associate II
Posted on May 06, 2016 at 02:18

You write ''to prevent it trying to move data that hasn't yet made it to the SPI data register''. But the DMA will never do that. It only moves data as requested by the SPI peripheral, each time one byte is received. The DMA completion interrupt will only trigger once the requested number of bytes have been received, ie. 16 in your code.

In your SPI1_IRQHandler, you clear the RXNE flag. I think that is wrong, as the RXNE flag is what triggers the DMA, so you will lose the first byte. In fact, there should be no need to have an SPI1_IRQHandler at all. Just enable the DMA in init(), and wait for the transfer to complete and invoke the DMA handler.

You do not seem to enable the clock for the DMA? With something like

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE). This might be why your DMA interrupt does not trigger.

Hope this helps,

 - Kristian.

banks2
Associate
Posted on May 06, 2016 at 17:07

Thanks so much! I simply commented out all the code relating to SPI interrupts and added DMA_Cmd(DMA1_Channel2, ENABLE); to the end of the DMA interrupt routine, and now everything's working smoothly. I believe I could also just configure the DMA in circular mode to achieve the same result.

Also, I had enabled the DMA clock in line 25 of the code above.

Thanks again for your help.