2025-04-10 9:47 AM
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
2025-04-11 2:45 AM
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.