Skip to main content
Explorer
September 30, 2024
Solved

DMA to SPI triggered by Timer STM32H723

  • September 30, 2024
  • 1 reply
  • 1236 views

Hi All,

I am trying to send data to SPI1 via DMA1 Stream 0, using a timer to trigger DMA.

Currently, I have used TIM4 Channel2 and Enabled DMA Update with DIER register (UDE bit).

DMAMUX1 Channel 0 Request is set to 32.

So far, this part seems to work: I have used a LED to monitor HT and TC flags of DMA, and it toggles at the speed it should.

But the SPI does not send anything (I checked the MOSI and SCK lines with an oscilloscope)... as if TXDR register was not filled by the DMA.

Do you have an idea of what is happening?

I have checked that my buffer xyDacData is correct (not null), and also I monitored SPI CR1 as well as SPI SR with SWD debug, all seems OK (SR is null, CR1 shows SPE and CSTART at 1).

Thank you!

(About SSM and SSI bits, I have set these to test, but they should be useless, since I am in master transmitter mode..

 

/*
 * FBx_XY.c
 *
 * Created on: Sep 10, 2024
 * Author: Thomas
 */


#include "main.h"

#define XY_SYNC_BITS 	(24 + 8) 	// Take some margin
#define DURATION_SPI_SEQ_CLK 	(TIMERS_CLK / 25000000 * XY_SYNC_BITS) 	// 25MHz is the SPI Speed

static void XY_Init_DMA();
static void XY_Init_Sync();
static void XY_Init_SPI();

__IO uint32_t xyDacData[BASE_POINT_LOAD * 2 * 2] = {0};
//__IO uint32_t testDacData[BASE_POINT_LOAD * 2 * 2] = {0};

/*
 * ===================
 * ===================
 */
void XY_Init()
{
	XY_Init_SPI();
	XY_Init_DMA();
	XY_Init_Sync();

	TIM4->CR1 |= TIM_CR1_CEN;
}

/*
 * ======================
 * DMA1 Stream0
 * Channel 32
 * ======================
 */
void XY_Init_DMA()
{
	__HAL_RCC_DMA1_CLK_ENABLE();
	// Circular mode. HT and TC are used to fill the buffer safely

	DMA1_Stream0->CR |= (0b10 << DMA_SxCR_PL_Pos); 			// High Priority Channel
	DMA1_Stream0->CR |= (0b10 << DMA_SxCR_MSIZE_Pos); 		// 32-bits Memory Size
	DMA1_Stream0->CR |= (0b10 << DMA_SxCR_PSIZE_Pos); 		// 32-bits Peripheral Size
	DMA1_Stream0->CR |= DMA_SxCR_CIRC;						//
	DMA1_Stream0->CR |= DMA_SxCR_MINC;						// Memory Auto-Increment
	DMA1_Stream0->CR |= (0b01 << DMA_SxCR_DIR_Pos);			// Read from Memory to Peripheral
	//DMA1_Stream0->CR |= DMA_SxCR_HTIE;						// Interrupt enable
	//DMA1_Stream0->CR |= DMA_SxCR_TCIE;						// Interrupt enable

	DMA1_Stream0->NDTR = BASE_POINT_LOAD * 2 * 2; 				// Number of points in the frame. x2 because of X and Y.

	DMA1_Stream0->M0AR = (uint32_t) xyDacData; 				// Initial Address for Memory
	DMA1_Stream0->PAR = (uint32_t) &(SPI1->TXDR); 		//

	DMAMUX1_Channel0->CCR |= 32 << DMAMUX_CxCR_DMAREQ_ID_Pos; 	// TIM4_UP = 32

	DMA1_Stream0->CR |= DMA_SxCR_EN;						// Enable, waits for TIM trigger

	// DMAMUX32 = TIM4 UP
	// DMAMUX30 = TIM4 CH2
}

/*
 * ======================
 * PG11 SPI1 SCK
 * PB5 SPI1 MOSI
 * SPI Speed Up to 30MHz
 * ======================
 */
void XY_Init_SPI()
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	__HAL_RCC_SPI1_CLK_ENABLE();

	GPIO_InitStruct.Pin = GPIO_PIN_11;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
	HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

	// SPI CLK 200MHz
	SPI1->CR1 = 0;
	SPI1->CR2 = 0;
	SPI1->CFG1 = 0;
	SPI1->CFG2 = 0;
	//SPI1->CFG1 |= SPI_CFG1_TXDMAEN; 	// ?? No need, DMA trigger is generated by TIM4
	SPI1->CFG1 |= (24 - 1) << SPI_CFG1_DSIZE_Pos; 	// 24 bits data
	SPI1->CFG1 |= 0b10 << SPI_CFG1_MBR_Pos; 	// Master clock / 8 = 25MHz
	SPI1->CFG2 |= SPI_CFG2_MASTER;
	SPI1->CFG2 |= 0b01 << SPI_CFG2_COMM_Pos; 	// Simplex transmitter
	//SPI1->CFG2 |= SPI_CFG2_SSIOP; 				// Software manageemnt (to disable SS pin)
	SPI1->CFG2 |= SPI_CFG2_SSM; 				// Software manageemnt (to disable SS pin)
	SPI1->CFG2 |= 0b10 << SPI_CFG2_MSSI_Pos;
	SPI1->IFCR = SPI_IFCR_MODFC;

	SPI1->CR1 |= SPI_CR1_SPE; 	// SPI Enable, waiting for data
	SPI1->CR1 |= SPI_CR1_CSTART;

	SPI1->CR1 |= SPI_CR1_SSI; 				// Software manageemnt (to disable SS pin)
}

/*
 * ===================
 * PB7 TIM4 CH2
 * ===================
 */
void XY_Init_Sync()
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	__HAL_RCC_TIM4_CLK_ENABLE();

	GPIO_InitStruct.Pin = GPIO_PIN_7;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate = GPIO_AF2_TIM4;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

	TIM4->CR1 = 0;
	TIM4->CR1 &= ~TIM_CR1_CKD; 					// Clock division 1
	TIM4->PSC = 0; 								// Time base full speed for high accuracy on scan rate
	TIM4->ARR = (TIMERS_CLK / (SCAN_RATE * 2)) - 1; 	// Period. Scan rate x2 because we have to transfer X and Y

	//TIM4->BDTR |= TIM_BDTR_MOE; 		// Main Output Enable (used for output on Pin)

	//TIM4->CR2 &= ~TIM_CR2_OIS2; 		// Idle State RESET
	TIM4->CCMR1 |= (TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2); 	// PWM1
	TIM4->CCMR1 |= TIM_CCMR1_OC2PE; 	// OC CCR2 preload enable (must be enabled for PWM mode).
	TIM4->CCMR1 &= ~TIM_CCMR1_CC2S; 	// Channel configured as Output

	TIM4->DIER |= TIM_DIER_UDE;

	TIM4->CCR2 = DURATION_SPI_SEQ_CLK; 		// Set Period
	TIM4->CCER |= TIM_CCER_CC2P; 			// Inverted polarity
	TIM4->CCER |= TIM_CCER_CC2E; 	// Enable OC2
}

 

 

    This topic has been closed for replies.
    Best answer by waclawek.jan

    The 'H7 SPI is an overcomplicated beast (I don't use it but have had a look at the SPI chapter in RM and didn't like what I saw there).

    Polling (i.e. writing to SPI using processor rather than DMA) with these settings works?

    > About SSM and SSI bits, I have set these to test, but they should be useless, since I am in master transmitter mode..

    You shall set them before enabling SPI, or maybe even before setting the master mode, otherwise, if given pin is low (or even unconfigured, which is internally low), SPI will immediately switch to slave mode - you can check this by observing the respective bit in status register.

    JW

    1 reply

    Super User
    October 1, 2024

    The 'H7 SPI is an overcomplicated beast (I don't use it but have had a look at the SPI chapter in RM and didn't like what I saw there).

    Polling (i.e. writing to SPI using processor rather than DMA) with these settings works?

    > About SSM and SSI bits, I have set these to test, but they should be useless, since I am in master transmitter mode..

    You shall set them before enabling SPI, or maybe even before setting the master mode, otherwise, if given pin is low (or even unconfigured, which is internally low), SPI will immediately switch to slave mode - you can check this by observing the respective bit in status register.

    JW

    thomas23Author
    Explorer
    October 1, 2024

    Thank you very much!

    I enabled SSM and SSI bits before MASTER, and it solved the issue indeed!

    (Before SPE and CSTART is not enough, FYI).

    Thomas

    Explorer II
    December 17, 2025

    for all that are also struggling with transmit only SPI, triggered by Timer+DMA on H723ZG, I post the solution I found based on the above posts and "instructions":

    In order to use the code generated by CubeMX as much as possible, I came up with the following solution:

    - Setup timer, DMA and SPI in CubeMX as described at many locations and create code

    - in main.c, in "User Code" of  "static void MX_SPI3_Init(void)" (I was using SPI3) paste

    /* USER CODE BEGIN SPI3_Init 2 */
    SPI3->CR1 = 0;
    SPI3->CR2 = 0;
    SPI3->CFG1 = 0;
    SPI3->CFG2 = 0;
    SPI3->CR1 |= SPI_CR1_SSI; // Software management (to disable SS pin)
    SPI3->CFG2 |= SPI_CFG2_SSM; // SSM bit =1: SlaveSelect driven by SSI-bit = SS-pin is free
    SPI3->CFG2 |= SPI_CFG2_MASTER; // Set as master
    //SPI1->CFG1 |= SPI_CFG1_TXDMAEN; // ?? No need, DMA trigger is generated by TIM4
    SPI3->CFG1 |= (16 - 1) << SPI_CFG1_DSIZE_Pos; // N Bits in a single data frame => 15 = 111.. = 16 bit (max 32)
    SPI3->CFG1 |= 0b10 << SPI_CFG1_MBR_Pos; // Master clock: 000= div2,4,8,16,32,64,128,256
    SPI3->CFG2 |= 0b01 << SPI_CFG2_COMM_Pos; // CommMode: 01= transmit only (10 Receive-only)
    SPI3->CFG2 |= 0b10 << SPI_CFG2_MSSI_Pos; // Master SS Idleness
    SPI3->IFCR = SPI_IFCR_MODFC; // Interrupt Status Flag needed
    SPI3->CR1 |= SPI_CR1_SPE; // SPI Enable, waiting for data
    SPI3->CR1 |= SPI_CR1_CSTART;

    /* USER CODE END SPI3_Init 2 */

    Then, in "int main(void)"  /* USER CODE BEGIN 2 */ start SPI and DMA as with other STM32 MCUs:

    HAL_SPI_ENABLE(&hspi3);
    HAL_DMA_Start_IT(&hdma_tim1_up, (uint32_t)Scaled_Sine, (uint32_t)&hspi3.Instance -> TXDR, N_Sine);
    __HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE);

     

    The critical code is thus the setup of the SPI-registers, where the order of the instructions matter crucially. I found this solution by starting with code of Thomas. Then, observing the SPI-register changes in debug mode/single step and shifting commands forward and backward.

     

    The solution presented has the advantage, that as a beginner, you do not need to figure out how to assign the correct pins to the targeted SPI or even dig more into directly configuring registers.

    At least for me, CubeMX and HAL are a great help.

     

    Thanks to Thomas and JW, although it was still quite tough for a beginner.

    Herbert