cancel
Showing results for 
Search instead for 
Did you mean: 

DMA to SPI triggered by Timer STM32H723

thomas23
Associate II

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
}

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
waclawek.jan
Super User

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

View solution in original post

3 REPLIES 3
waclawek.jan
Super User

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

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

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