1
\$\begingroup\$

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()
\$\endgroup\$
2
\$\begingroup\$

SPI will not create the waveforms for you. It is full duplex and master side receives at the same as it transmits, only.

The DMA does not request the receiver to get data, it is the other way around - when receiver happens to have received something, it requests the DMA to transfer it.

You probably have to set both transmit and receive arrays to length of 4 and ignore the first received.

I got it to work once, feel free to ask for more.

\$\endgroup\$
  • \$\begingroup\$ Ah, I see. I wonder where my incorrect assumption came from... I'll give it a try, and see what happens :) Thanks! \$\endgroup\$ – bitsmack Sep 2 '14 at 16:34
  • \$\begingroup\$ Thanks venny, I was able to make it work now. You're a life saver! \$\endgroup\$ – bitsmack Sep 2 '14 at 19:16
3
\$\begingroup\$

I faced to the same issue when I was trying to make the SPI full duplex transfer using the DMA in both directions on the STM32F103 (Maple Mini).

The receive buffer contained one unexpected byte at the beginning. I had to ignore it and increase the buffer size by one byte for the Rx DMA channel to get all received bytes. However, I managed to find the solution.

In the RM0008 Reference manual I found the section 25.3.9 SPI communication using DMA (direct memory addressing) saying:

In reception, a DMA request is issued each time RXNE is set to 1. The DMA then reads the SPI_DR register (this clears the RXNE flag).

So, I tried to clean the RXNE flag on the SPI before the DMA transfer started:

Rx buffer not empty (RXNE)

When set, this flag indicates that there are valid received data in the Rx buffer. It is cleared when SPI_DR is read.

Fortunately, this helped and I was able to get clean data without any garbage.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.