Skip to main content
Visitor II
October 1, 2024
Solved

STM32F415 DMA SPI transfer after stop mode

  • October 1, 2024
  • 4 replies
  • 1801 views

I am using SPI2 and SPI3 with DMA1 to write and read to/from peripheral chips. This works fine most of the time. At the same time I am using the stop mode to save power. Now in some cases just after leaving the stop mode, the next DMA transfer does not work. The transfer is started but the complete-interrupt-routine is never called. And when checking the registers the half transfer bit is not set. A reset of the DMA(LL_DMA_DeInit(DMA1, LL_DMA_STREAM_ALL)) in this case seems to not resolve the problem. I have to reset the whole device to resolve it.

DMA and SPI transfers are setup before each transfer.

LL_SPI_InitTypeDef SPI_InitStruct; //Structure containing the settings for SPI2
LL_DMA_InitTypeDef DMA_InitStruct; //Structure containing the settings for DMA1
NVIC_InitTypeDef NVIC_InitStructure;
LL_DMA_DisableIT_TC(DMA1, LL_DMA_STREAM_2);
LL_DMA_DisableIT_TC(DMA1, LL_DMA_STREAM_5);
LL_SPI_Disable(SPI3);
 
LL_I2S_DeInit(SPI3);
LL_SPI_StructInit(&SPI_InitStruct);
SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_HIGH;
SPI_InitStruct.ClockPhase = LL_SPI_PHASE_2EDGE;
SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV32;
SPI_InitStruct.CRCPoly = 0;
SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;
SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
LL_SPI_Init(SPI3, &SPI_InitStruct);
 

//--------------- DMA1 Channel 0, Stream 2: Rx--------------//
// Deinitialise
LL_DMA_DeInit(DMA1, LL_DMA_STREAM_2);
LL_DMA_StructInit(&DMA_InitStruct);default value.
DMA_InitStruct.Channel = LL_DMA_CHANNEL_0;
DMA_InitStruct.NbData = 0;
DMA_InitStruct.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
DMA_InitStruct.FIFOMode = LL_DMA_FIFOMODE_DISABLE;
DMA_InitStruct.MemoryOrM2MDstAddress = (uint32_t)bleUbtagRWCom.spi3_RWVal.dataIn;
DMA_InitStruct.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
DMA_InitStruct.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
DMA_InitStruct.Mode = LL_DMA_MODE_NORMAL;
DMA_InitStruct.PeriphOrM2MSrcAddress = (uint32_t)&SPI3->DR;
DMA_InitStruct.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
DMA_InitStruct.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
DMA_InitStruct.Priority = LL_DMA_PRIORITY_HIGH;
LL_DMA_Init(DMA1, LL_DMA_STREAM_2, &DMA_InitStruct);

//--------------- DMA1 Channel 0, Stream 5: Tx------------------//
LL_DMA_DeInit(DMA1, LL_DMA_STREAM_5);
DMA_InitStruct.Channel = LL_DMA_CHANNEL_0;
DMA_InitStruct.NbData = 0;
DMA_InitStruct.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
DMA_InitStruct.FIFOMode = LL_DMA_FIFOMODE_DISABLE;
DMA_InitStruct.MemoryOrM2MDstAddress = (uint32_t)bleUbtagRWCom.spi3_RWVal.dataOut;
DMA_InitStruct.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
DMA_InitStruct.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
DMA_InitStruct.Mode = LL_DMA_MODE_NORMAL;
DMA_InitStruct.PeriphOrM2MSrcAddress = (uint32_t)&SPI3->DR;
DMA_InitStruct.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
DMA_InitStruct.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
DMA_InitStruct.Priority = LL_DMA_PRIORITY_HIGH;
LL_DMA_Init(DMA1, LL_DMA_STREAM_5, &DMA_InitStruct);

// Enable DMA Interrupts
LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_2);
LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_5);

/*---------- End configuration DMA and SPI ------------------------*/
//Inititialize NVIC Struct or DMA1 Channel 0 Stream 2
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = IRQPreemptionPriorityMedium;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = IRQSubPriorityMax;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

//Inititialize NVIC Struct or DMA1 Channel 0 Stream 5
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = IRQPreemptionPriorityMedium;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = IRQSubPriorityMax;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_2);
LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_5);

ENABLE_CHIP_SELECT;

LL_SPI_DisableDMAReq_RX(SPI3);
LL_SPI_DisableDMAReq_TX(SPI3);
 
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_2, bytesToRead);
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_5, bytesToRead);
LL_DMA_ClearFlag_TC2(DMA1);
LL_DMA_ClearFlag_TC5(DMA1);
// Start transfer
LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_2);
LL_SPI_EnableDMAReq_RX(SPI3);
LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_5);
LL_SPI_Enable(SPI3);
LL_SPI_EnableDMAReq_TX(SPI3);
// Wait for transfer to complete
OS_RESULT res = os_evt_wait_or (EVENT_SPI3, SPI_TRANSFER_TIMEOUT);
if(res == OS_R_TMO) {
 // A timeout occured. This means that the end of the transfer
 // could not be detected.
 LL_DMA_DeInit(DMA1, LL_DMA_STREAM_ALL);
}

DISABLE_CHIP_SELECT;

And of course there is the interrupt routine:

// BLE SPI RX, CH0 Stream2
void DMA1_Stream2_IRQHandler(void){
 if (LL_DMA_IsActiveFlag_TC2(DMA1)) {
 LL_DMA_ClearFlag_TC2(DMA1);
 LL_I2S_DisableDMAReq_RX(SPI3);
 LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_2);
 isr_evt_set(EVENT_SPI3, callerIdSpi3); // signal task ready
 }
 if (LL_DMA_IsActiveFlag_TE2(DMA1)) {
 LL_DMA_ClearFlag_TE2(DMA1); // Clear transfer error flag
 _bleTransferError |= TRANSFER_ERROR_RX;
 // Handle DMA transfer error
 }
}

The stop mode is done as shown in the following simplified code segment. All running tasks are halted before entering the stop mode and the entering of the stop mode is done from the idle task.

// Enter stop mode
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// Init system clocks
SystemInit();
// Resume task scheduler and set time the system was in stop mode
os_resume(TdS);

Additionally I tried the following to approaches to find a possible workaround but which did not work:

  • Start the task executing the DMA SPI with a delay of up to 10ms after stop mode
  • Increase the priority to very high of the failing DMA stream.

Is there maybe any known issue with DMA, SPI and stop mode?

Or can the setup of stream 3 and 4 influence the operation of stream 2 and 5 of the same DMA?

    This topic has been closed for replies.
    Best answer by rkoehli

    The solution is the following. There are two aspects working together to cause the observed problem.

    1) From the errata:


    Slowing down APB clock during a DMA transfer
    Description
    When the CPU modifies the APB clock (slows down the clock by changing the AHB/APB prescaler from 1 to 2, 1
    to 4, 1 to 8 or 1 to 16) while the DMA is performing a write access to the same APB peripherals, the current DMA
    transfer is blocked. Only a system reset recovers.
    Workaround
    Before slowing down the APB clock, wait until the end of the DMA transfer on this APB.

    2) When using Keil OS there are two functions to be called when leaving stop mode

    • SystemInit() // Setup system clocks and PLL
    • os_resume() // Resume task scheduler

    The system has to wait for both functions to complete to be sure that system clocks aren't modified anymore. I added another 1ms delay to be sure before starting any DMA resp. SPI transfers.

    4 replies

    Super User
    October 1, 2024

    > All running tasks are halted before entering the stop mode

    What does that mean exactly, in terms of the DMA? Can the DMA be running when stop is entered?

     

    [...]the next DMA transfer does not work. The transfer is started[...]

    What does that mean, exactly? When failure occurs, are there SPI clocks from the started transfer (observed using oscilloscope/LA), and if yes, how many of the total expected?

     

    Read out and check/post GPIO and SPI registers at the moment of "failure". In debugger, in that state, try to write to SPI_DR manually and observe clocks.

    JW

    rkoehliAuthor
    Visitor II
    October 2, 2024

    The error is not so easy to reproduce. I tested on a device overnight but had no luck. So I will provide some of the information later on. But for now here some answers:

    > What does that mean exactly, in terms of the DMA? Can the DMA be running when stop is entered?

    The DMA is not running in stop mode, which I think isn't even possible. The DMA/SPI transfer is started in normal mode. But the observed problems always occured just after leaving the stop mode, which is why I was asking if there is any known issue or link between them. 

    I was not able to observe the signals with the oscilloscope. Again because it happens rarely.

    And I can't use the debugger since the debugger is disconnected when the processor enters the stop mode.

    I will try to record the register contents of SPI and DMA and come back with this information soon.

    Super User
    October 2, 2024

    @rkoehli wrote:

    And I can't use the debugger since the debugger is disconnected when the processor enters the stop mode.


    You should be able to configure the debugger for 'Debug in low power modes'

    (This may or may not help, as it does affect the behaviour in the low-power modes)

     


    @rkoehli wrote:

    I was not able to observe the signals with the oscilloscope. Again because it happens rarely.


    Could you generate a trigger signal on a GPIO when the problem occurs, and use that to trigger the scope to capture up to that point?

    rkoehliAuthor
    Visitor II
    December 10, 2024

    Ok, it took me a while to advance on this problem. I redesigned my project so that all access to DMA1 are made from a timer interrupt and therefore never at the same time. The problem still occurs.

    I recorded the signals when the DMA access is interrupted (1: Test-Signal 2: MOSI 3: SCK 4: Chip Select). The first picture shows the complete SPI access. The second shows a close up of the moment the SPI access stops. One can see that just at that moment the clock signal frequency changes.

    scope_0.pngscope_2.png

     

    I then tried to reset one by one the DMA, the SPI and then the Clock through the RCC AHB1 peripheral reset, resp. RCC APB1 peripheral reset. But I could not get the SPI running again. I again had to reset the complete board.

    One note on the system clocks. After leaving the stop mode the software has to reset and setup the clock registers. This is library code from system_stm32f4xx.c, function SystemInit(). This happens before the first SPI access is started.

    rkoehliAuthor
    Visitor II
    December 10, 2024

    I also wrote out the registers after the error occurs. 

    10.12. 12:50:51.501 || i || DMA1:LISR=04100000,HISR=00000410,S3CR=00020411,S4CR=00020451,S3NDTR=008C,S4NDTR=008A || 0008 || hw_spi.c 1145
    10.12. 12:50:51.513 || i || SPI2:CR1=0357,CR2=03,SR=0043 || 0008 || hw_spi.c 1157
    10.12. 12:50:51.524 || i || DMA1:LISR=04100000,HISR=00000410,S2CR=00020410,S5CR=00020450,S2NDTR=0000,S5NDTR=0000 || 0008 || hw_spi.c 1145
    10.12. 12:50:51.530 || i || SPI3:CR1=0367,CR2=00,SR=0002 || 0009 || hw_spi.c 1157
    10.12. 12:50:51.634 || i || RCC CSR:00000003,CFGR:0000100A,PLLCFGR:08433008,CIR:00000000,CSR:00000003 || 0008 || hw_spi.c 1167
    10.12. 12:50:51.645 || i || RCC AHB1RSTR:00000000,APB1RSTR:00000000,AHB1ENR:0070101F,APB1ENR:160AC02B || 2000008 || hw_spi.c 1173

    The SPI status register reports an "Overrun flag", which indicates that the data have been read from the SPI data lines but the register has not yet been read. The reading of the register would be the DMAs task. Does anyone have a clue how this could happen?

    rkoehliAuthorAnswer
    Visitor II
    February 11, 2025

    The solution is the following. There are two aspects working together to cause the observed problem.

    1) From the errata:


    Slowing down APB clock during a DMA transfer
    Description
    When the CPU modifies the APB clock (slows down the clock by changing the AHB/APB prescaler from 1 to 2, 1
    to 4, 1 to 8 or 1 to 16) while the DMA is performing a write access to the same APB peripherals, the current DMA
    transfer is blocked. Only a system reset recovers.
    Workaround
    Before slowing down the APB clock, wait until the end of the DMA transfer on this APB.

    2) When using Keil OS there are two functions to be called when leaving stop mode

    • SystemInit() // Setup system clocks and PLL
    • os_resume() // Resume task scheduler

    The system has to wait for both functions to complete to be sure that system clocks aren't modified anymore. I added another 1ms delay to be sure before starting any DMA resp. SPI transfers.

    Super User
    February 11, 2025

    Interesting.

    So, if I understand it properly, the problem was that RTOS took over and started SPI+DMA before the clocks in RCC were set up (including APB divider)? Because you've shown us above that the SPI clock frequency has halved on the fly.

    I also don't understand, why would clocks in RCC need to be set up at all. AFAIK, Stop mode which you've used does not change the registers, so the APB divider wouldn't need to be changed, would it?

    JW

    rkoehliAuthor
    Visitor II
    February 12, 2025

    It's the other way round. The RTOS is manipulating clocks while my software starts a SPI/DMA transfer. I was also astonished about the RTOS doing this, especially in this late phase. But once I waited until the RTOS commands were done, the problem disappeared.