2016-05-05 02:59 PM
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):
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.
2016-05-05 05:18 PM
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.2016-05-06 08:07 AM
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.