I'm trying to write a SPI driver for an STM32F105 using DMA functionality. I'm using ST's Standard Peripheral Library. I'm not using interrupts. I can talk to the destination device if I use standard SPI routines, but I haven't figured out the DMA... The device is the Spansion S25FL164K flash memory IC.
As a test, I'm trying to read out the device identifiers. I should send a single byte then receive three bytes in response. Since the STM32 is the SPI master, it needs to provide three bytes worth of clock pulses to get the response. Generally, you have the master device send three dummy bytes to create this clock. With DMA, you tell it how many bytes to receive and it is supposed to create the waveforms for you.
With my code, the first byte is getting transmitted but no clock pulses are being created for the returned values. This implies that the DMA and SPI peripheral clocks are being configured correctly. I've looked online, and (surprisingly) can't find any examples, tutorials, or app notes that talk about transmit-then-receive sequencing.
I expect I'm simply getting the sequencing wrong.
For TX, I can:
- Enable the DMA device
- Disable the DMA device.
- Enable the SPI device to send signals to the DMA,
- Disable this signalling
- Clear DMA flags, and
- Clear SPI flags.
I can do the same, separately, for RX.
I've tried different permutations of enabling/disabling these different functions. Instead of trying to list them all here, I am hoping someone will tell me the proper method :) Any help is appreciated; I've been banging my head against this one.
Here's my DMA configuration, followed by one of the failed iterations of the SPI Read function:
void ConfigureDMA(void)
{
DMA_InitTypeDef DMA_InitStructure;
// Enable DMA1 Peripheral Clock (SPI_DECAWAVE and SPI_BUS)
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// Configure SPI_BUS RX Channel
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // From SPI to memory
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr = 0; // To be set later
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_BufferSize = 1; // To be set later
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
// Configure SPI_BUS TX Channel
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // From memory to SPI
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr = 0; // To be set later
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_BufferSize = 1; // To be set later
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
} // end ConfigureDMA()
In the function call, CommandBuffer and DataBuffer are pointers to arrays. This function gets caught waiting in the "Wait until the data is received" section, but even if I omit this check, the clock pulses are still never created.
void spiFlashRead(uint8_t CommandLength, const uint8_t *CommandBuffer,
uint16_t DataLength, uint8_t *DataBuffer)
{
// Prepare the DMA
DMA1_Channel5->CNDTR = CommandLength;
DMA1_Channel5->CMAR = (uint32_t)CommandBuffer;
DMA1_Channel4->CNDTR = DataLength;
DMA1_Channel4->CMAR = (uint32_t)DataBuffer;
// Enable the DMAs - They will await signals from the SPI hardware
DMA_Cmd(DMA1_Channel5, ENABLE); // TX
DMA_Cmd(DMA1_Channel4, ENABLE); // RX
// Activate the Flash CS
GPIO_ResetBits(SPI_MEM_CS_GPIO, SPI_MEM_CS);
// Enable the SPI communication to the TX DMA, which will send the command
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);
// Wait until the command is sent to the DR
while (!DMA_GetFlagStatus(DMA1_FLAG_TC5));
// Wait until the transmission is completed
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) == RESET);
// Disable the TX DMA and clear DMA flags
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, DISABLE);
DMA_Cmd(DMA1_Channel5, DISABLE);
DMA_ClearFlag(DMA1_FLAG_GL4 | DMA1_FLAG_HT4 | DMA1_FLAG_TC4 | DMA1_FLAG_GL5 | DMA1_FLAG_HT5 | DMA1_FLAG_TC5);
//NOTE: I checked the SPI OVR flag here, and it wasn't set...
// Enable SPI communication to the RX DMA, which should receive the data
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx, ENABLE);
// Wait until the data is received
while (!DMA_GetFlagStatus(DMA1_FLAG_TC4));
// Disable the DMAs
DMA_Cmd(DMA1_Channel4, DISABLE); // RX
DMA_Cmd(DMA1_Channel5, DISABLE); // TX
// Release the Flash CS
GPIO_SetBits(SPI_MEM_CS_GPIO, SPI_MEM_CS);
} // end spiFlashRead()