Electronic – Full duplex SPI Master using DMA – STM32F105

dmamicrocontrollerspistm32

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()

Best Answer

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.