cancel
Showing results for 
Search instead for 
Did you mean: 

Timer to DMA to SPI STM32H723 using LL

ZacJFrank
Associate II

Hi,

I want a GPIO pin to trigger an SPI transfer. The way that I have figured this should work is:
PG3 -> TIM23 -> DMA2 -> SPI1

 

Here is basically what I have so far:

/* Includes --------------------------------------------------------------------------------------------------- */
#include "TypesDefinition.h"
#include "SpiHad.h"

#include <algorithm>

#include <stm32h7xx_ll_spi.h>
#include <stm32h7xx_ll_dma.h>
#include <stm32h7xx_ll_gpio.h>
#include <stm32h7xx_ll_bus.h>
#include <stm32h7xx_ll_system.h>
#include <stm32h7xx_ll_exti.h>
#include <stm32h7xx_ll_tim.h>


namespace mopf::sys
{

/* Static members --------------------------------------------------------------------------------------------- */
static SpiHad* g_SpiHadLocal;
SpiHad* const& g_SpiHad{g_SpiHadLocal};

SpiHad::SpiHad(std::function<void()> SpiCallback, std::function<void()> EotCallback) :
    ISpiHad{},
    m_SpiCallback{SpiCallback},
    m_EotCallback{EotCallback},
    m_Status{},
    m_RxPtr{nullptr},
    m_TransferLength{0U}
{
    g_SpiHadLocal = this;
    GpioConfiguration();
    SpiConfiguration();
}

void SpiHad::StartDmaTransfer(uint8_t const* const TxData, uint8_t* const RxData, uint16_t const Length)
{
    __DSB();
    LL_DMA_ClearFlag_HT0(DMA2); // half transfer flag
    LL_DMA_ClearFlag_TC0(DMA2); // transfer complete flag
    LL_DMA_ClearFlag_HT1(DMA2); // half transfer flag
    LL_DMA_ClearFlag_TC1(DMA2); // transfer complete flag
    LL_SPI_SetTransferSize(SPI1, Length);
    LL_SPI_SetDataWidth(SPI1, LL_SPI_DATAWIDTH_8BIT);

    __DSB();

    LL_SPI_ClearFlag_TXTF(SPI1);
    LL_SPI_ClearFlag_EOT(SPI1);

    __DSB();
    LL_SPI_EnableIT_EOT(SPI1);

    LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_0, (uint32_t)(TxData));
    LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_0, static_cast<uint16_t>(Length));
    LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_1, (uint32_t)(RxData));
    LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_1, static_cast<uint16_t>(Length));

    __DSB();
    LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_0);
    LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_1);

    LL_SPI_EnableDMAReq_TX(SPI1);
    LL_SPI_EnableDMAReq_RX(SPI1);
    __DSB();
    LL_SPI_Enable(SPI1);
    __DSB();
    LL_TIM_GenerateEvent_CC1(TIM23); // change later

    // LL_SPI_StartMasterTransfer(SPI1);
}

void SpiHad::DmaTxIsr()
{
    m_Status = Status::OK;
    SpiHad::m_SpiCallback();
}

void SpiHad::DmaRxIsr()
{
    m_Status = Status::OK;
    SpiHad::m_SpiCallback();
}

void SpiHad::SpiIsr()
{
    if (LL_SPI_IsActiveFlag_EOT(SPI1))
    {
        LL_SPI_ClearFlag_EOT(SPI1);
        SpiHad::m_SpiCallback();
    }
}
/* Internal Functions ----------------------------------------------------------------------------------------- */

void SpiHad::SpiConfiguration()
{
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA2);
    LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM23);

    /* Common initialization */
    LL_GPIO_InitTypeDef GPIO_InitStruct{
        {},
        LL_GPIO_MODE_ALTERNATE,
        LL_GPIO_SPEED_FREQ_HIGH,
        LL_GPIO_OUTPUT_PUSHPULL,
        LL_GPIO_PULL_NO,
        LL_GPIO_AF_5,
    };

    GPIO_InitStruct.Pin = LL_GPIO_PIN_10; // Chip Select -> PG10
    LL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = LL_GPIO_PIN_11; // Clk  -> PG11
    LL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = LL_GPIO_PIN_9; // MISO -> PG9
    LL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = LL_GPIO_PIN_7; // MOSI -> PD7
    LL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    //lint -restore

    /* Configure the SPI1 parameters */
    LL_SPI_InitTypeDef SPI_InitStruct{};
    SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
    SPI_InitStruct.Mode              = LL_SPI_MODE_MASTER;
    SPI_InitStruct.DataWidth         = LL_SPI_DATAWIDTH_8BIT;
    SPI_InitStruct.ClockPolarity     = LL_SPI_POLARITY_LOW;
    SPI_InitStruct.ClockPhase        = LL_SPI_PHASE_1EDGE;
    SPI_InitStruct.NSS               = LL_SPI_NSS_HARD_OUTPUT;
    SPI_InitStruct.BaudRate          = LL_SPI_BAUDRATEPRESCALER_DIV4;
    SPI_InitStruct.BitOrder          = LL_SPI_MSB_FIRST;
    SPI_InitStruct.CRCCalculation    = LL_SPI_CRCCALCULATION_DISABLE;
    SPI_InitStruct.CRCPoly           = 0x0;

    LL_SPI_Init(SPI1, &SPI_InitStruct);

    LL_SPI_SetStandard(SPI1, LL_SPI_PROTOCOL_MOTOROLA);   // todo check
    LL_SPI_SetFIFOThreshold(SPI1, LL_SPI_FIFO_TH_03DATA); // todo check
    LL_SPI_DisableNSSPulseMgt(SPI1);


    LL_DMA_SetPeriphRequest(DMA2, LL_DMA_STREAM_0, LL_DMAMUX1_REQ_TIM23_CH1);
    LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_0, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
    LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_0, LL_DMA_PRIORITY_HIGH);
    LL_DMA_SetMode(DMA2, LL_DMA_STREAM_0, LL_DMA_MODE_NORMAL);
    LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_0, LL_DMA_PERIPH_NOINCREMENT);
    LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_0, LL_DMA_MEMORY_INCREMENT);
    LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_0, LL_DMA_PDATAALIGN_BYTE);
    LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_0, LL_DMA_PDATAALIGN_BYTE);
    LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_0);
    LL_DMA_SetFIFOThreshold(DMA2, LL_DMA_STREAM_0, LL_DMA_FIFOTHRESHOLD_1_4);
    // HalDmaInit();

    LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_0, LL_SPI_DMA_GetTxRegAddr(SPI1));


    LL_DMA_SetPeriphRequest(DMA2, LL_DMA_STREAM_1, LL_DMAMUX1_REQ_SPI1_RX);
    LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
    LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_1, LL_DMA_PRIORITY_HIGH);
    LL_DMA_SetMode(DMA2, LL_DMA_STREAM_1, LL_DMA_MODE_NORMAL);
    LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_1, LL_DMA_PERIPH_NOINCREMENT);
    LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_1, LL_DMA_MEMORY_INCREMENT);
    LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_1, LL_DMA_PDATAALIGN_BYTE);
    LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_1, LL_DMA_PDATAALIGN_BYTE);
    LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_1);

    LL_DMA_SetFIFOThreshold(DMA2, LL_DMA_STREAM_1, LL_DMA_FIFOTHRESHOLD_3_4);
    LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_1, LL_SPI_DMA_GetRxRegAddr(SPI1));

    // Enable DMA requests for TX and RX
    LL_SPI_EnableDMAReq_TX(SPI1);
    LL_SPI_EnableDMAReq_RX(SPI1);

    /* Lock GPIO for master to avoid glitches on the clock output */
    LL_SPI_EnableGPIOControl(SPI1);
    LL_SPI_DisableMasterRxAutoSuspend(SPI1);

    HAL_NVIC_SetPriority(SPI1_IRQn, 7U, 0U);
    NVIC_ClearPendingIRQ(SPI1_IRQn);
    HAL_NVIC_EnableIRQ(SPI1_IRQn);

    constexpr uint32_t period{80U}; // Set the period for TIM23
    // Enable clocks for TIM23
    // Configure TIM23 for one-shot mode
    LL_TIM_SetPrescaler(TIM23, 15 - 1);
    LL_TIM_SetCounterMode(TIM23, LL_TIM_COUNTERMODE_UP);
    LL_TIM_SetAutoReload(TIM23, period - 1);                   // Set the period
    LL_TIM_SetOnePulseMode(TIM23, LL_TIM_ONEPULSEMODE_SINGLE); // One-shot mode
    // Set the output polarity to active high (non-inverted)
    LL_TIM_OC_SetPolarity(TIM23, LL_TIM_CHANNEL_CH4, LL_TIM_OCPOLARITY_LOW);

    // Configure TRGO to be generated on enable event
    LL_TIM_EnableDMAReq_CC1(TIM23);
    // LL_TIM_SetTriggerOutput(TIM23, LL_TIM_TRGO_CC1IF); // change back to ENABLE or UPDATE

    // Configure TIM23_CH4 to output to PF3
    LL_TIM_OC_SetMode(TIM23, LL_TIM_CHANNEL_CH4, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH4(TIM23, period - 10); //
    LL_TIM_OC_EnablePreload(TIM23, LL_TIM_CHANNEL_CH4);
    LL_TIM_CC_EnableChannel(TIM23, LL_TIM_CHANNEL_CH4);

    // Generate an update event to apply the configuration
    LL_TIM_GenerateEvent_UPDATE(TIM23);
}

void SpiHad::GpioConfiguration()
{
    /* Common initialization */

    LL_GPIO_InitTypeDef GPIO_InitStruct{
        {},
        LL_GPIO_MODE_OUTPUT,
        LL_GPIO_SPEED_FREQ_HIGH,
        LL_GPIO_OUTPUT_PUSHPULL,
        LL_GPIO_PULL_NO,
        LL_GPIO_AF_0,
    };

    GPIO_InitStruct.Pin = LL_GPIO_PIN_12;
    LL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    LL_GPIO_SetOutputPin(GPIOC, LL_GPIO_PIN_12);

    GPIO_InitStruct.Mode      = LL_GPIO_MODE_ALTERNATE;
    GPIO_InitStruct.Alternate = LL_GPIO_AF_13;
    GPIO_InitStruct.Pin       = LL_GPIO_PIN_3;
    LL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    // Configure PC14 as input with interrupt
    GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
    GPIO_InitStruct.Pin  = LL_GPIO_PIN_14;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; // Adjust pull-up/down as needed
    LL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    // Configure PG3 as input with link to start TIM23
    GPIO_InitStruct.Mode      = LL_GPIO_MODE_ALTERNATE;
    GPIO_InitStruct.Pin       = LL_GPIO_PIN_3;
    GPIO_InitStruct.Pull      = LL_GPIO_PULL_NO; // Adjust pull-up/down as needed
    GPIO_InitStruct.Alternate = LL_GPIO_AF_13;   // TIM23_ETR
    LL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    // Clear any pending interrupts and enable the NVIC IRQ
    __HAL_GPIO_EXTI_CLEAR_IT(LL_GPIO_PIN_14);
    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 7U, 0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}


}

I initially got the SPI working with DMA by directly by having LL_DMAMUX1_REQ_SPI1_RX as the peripheral request for both streams and starting the transfer with LL_SPI_StartMasterTransfer(SPI1);

So the SPI and DMA work in general. With the current setup, I'm skipping the pin triggering the timer, and just generating a TIM23_CH1 event manually. Once that works I'll get to triggering the timer.

When I call the function LL_TIM_GenerateEvent_CC1(TIM23), the value of DMA2->S0NDTR (for the TxSpi) decrements by one. I would  expect it to transfer all bytes (4 in the first case). The SPI does nothing. DMA2->S1NDTR (for the RxSpi) doesn't budge. If I call LL_TIM_GenerateEvent_CC1 a few more times, it continues decrementing, and the TC flags are set, but again, nothing happens with the SPI.

I played around with the peripheral source (I had the RxDma (Stream1) as coming from LL_DMAMUX1_REQ_TIM23_CH1 as well (as the TxDma does)), but it didn't seem to help.

 

Any ideas? Cheers

 

1 ACCEPTED SOLUTION

Accepted Solutions
ZacJFrank
Associate II

Ok, I managed to get it working through EXTI0. It'd be nice if that weren't necessary though. 

View solution in original post

5 REPLIES 5
ZacJFrank
Associate II

Ok, apparently I still needed to call LL_SPI_StartMasterTransfer(SPI1);

 

Now the SPI at least sends out the one byte it receives. But the DMA still only transfers 1 byte per DMA request. I want it to send as many bytes as in NDTR. I tried playing with the burst feature but it doesn't seem to want to do that.

Hello @ZacJFrank 

Whay you are configuring a TIM to trigger DMA SPI transfer from GPIO pin? It is easier to use EXTI.

If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar
ZacJFrank
Associate II

As far as I can see, the only EXTI that can trigger a DMA SPI transfer are EXTI0 through DMAMUX1, or EXTI0 or EXTI2 through DMAMUX2. That's easier said than done and requires a hardware re-design, which is not impossible but not desired.

I've done some soldering and connected PF0 to the signal, trying with EXTI0 through the DMAMUX Request Generator, but at the moment this seems to be doing absolutely nothing. 

@ZacJFrank 

Could you please share the new configuration with EXTI and DMAMUX?

If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar
ZacJFrank
Associate II

Ok, I managed to get it working through EXTI0. It'd be nice if that weren't necessary though.