cancel
Showing results for 
Search instead for 
Did you mean: 

SPI transmission with DMA and a timer sending the DMA request

Tommino
Senior

Hello,

I would be glad to doublecheck with you how to implement the following operation with the STM32L476RG uC:

I would like to transmit a set of data to a DAC (AD5541) with SPI and the SPI data shall be moved from meomory to the SPI peripheral with the DMA. A timer sends the DMA request in order to set the output data rate with the timer threshold value.

I am not sure about a couple of things....so I recap here a todo list to review the procedure:

1) check the system architecture to see which channel of the DMA can receive the request from a timer and which DMA is connected to the different SPIs (If using Cube to init I think this is unnecessary... ).

2) set the Clock, the SPI1 and the Timer1 CH1 with cube to have the initialization code. First question: In cube configuration, shall I add DMA in the SPI tab or in the TIM tab? I think in the timer tab since it sends the request but I would like to be sure

3) then set the DMA registers:

a) DMA1_Channel3 -> CNDTR1 = 0x00000001; // one sample each transfer

b) DMA1 -> CPAR1 = (uint32_t)&SPI1->DR; // set SPI data register as destination

c) DMA1 -> CMAR1= (uint32_t)data; // set the data to transmit as source

4) finally set the TIM register:

a) TIM1 -> CR2 |= 0<<3 // set CCDS=0 to send a DMA when compare event occurs

b) TIM1 -> DIER |= 1<<9 // enable the DMA request when compared

c) not sure how to manage TIM1_CNT and TIM1_CCR1 to set the value be compared. Can you clarify this issue?

I think all the other registers of the SPI, Timer and DMA are written by the cube and/or are not relevant for my purpose. Do you agree?

Can we doublecheck together if am in the right direction? :)

Attached you can find a section of the manual that I found useful.

Regards

12 REPLIES 12

> DMA1_Channel3 -> CNDTR1 = 0x00000001; // one sample each transfer

There is no point in setting 1 as DMA transfer length.

Also, I don't see point in combining Cube and register access. Cube just will get into way.

But whatever - set up what you intend to, then read out content of the relevant registers and check/post.

JW

Tommino
Senior

@Community member​ 

"There is no point in setting 1 as DMA transfer length"

Why? The aim was to send one data each time the timer sends the DMA request.

"Also, I don't see point in combining Cube and register access. Cube just will get into way"

As I said, I thought in this way you can avoid the initialization of the peripheral

@Community member​ what about all the other bullets on the TIM registers?

With any NDTR, there is one transfer per request from timer.

> what about all the other bullets on the TIM registers?

Go ahead, try it.

JW

Tommino
Senior

> With any NDTR, there is one transfer per request from timer.

Pls check the attached screenshoot from the manual

>Go ahead, try it

Thanks for your replies Jan but honestly I was looking for a member more skilled than me to review my idea(In order to be sure about the concept...)I did not want to follow the trial and error way as a first attempt

I meant, NDTR > 1.

The idea of DMA is, that you prepare N data (say N halfwords) into memory, set N into NDTR, set DMA to transfer halfwords, start DMA and start timer. Then upon every request one halfword is transmitted, automatically, without any processor action, until all N halfwords are transmitted, when DMA throws the Transfer complete interrupt.

What you've written with TIM is okay, set CCRx to half of ARR. I don't know how the rest of it, which you intend to leave to Cube will interact, do go ahead and try.

JW

Tommino
Senior
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
 
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
 
/* USER CODE END Includes */
 
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
 
/* USER CODE END PTD */
 
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
 
/* USER CODE END PD */
 
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
 
/* USER CODE END PM */
 
/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
 
/* USER CODE BEGIN PV */
 
/* USER CODE END PV */
 
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);
/* USER CODE BEGIN PFP */
 
/* USER CODE END PFP */
 
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void TIM6Init (void){
	 // Enable the timer clock
	 RCC->APB1ENR1 |= (1<<4); // Enable the tim6 clock
	 // Set the prescaler and the ARR
	 TIM6 -> PSC = 79; // 80 MHz/80= 1MHz clock --> 1us count
	 TIM6-> ARR = 0xffff; // Max ARR value --> set the us total counts before sending the DMA request
	 // Enable the timer and wait for the update flag
	 TIM6 -> CR1 |= (1<<0); // Enable the timer6
	 //while (! (TIM6->SR & (1<<0))); // wait until bit 0 of SR becomes 1
     TIM6 -> DIER |= (1<<8); // Enable the DMA request when update
}
 
void DMA1Init(void){
	// Enable the DMA Clock
	RCC-> AHB1ENR |= (1<<0); // Enable DMA1 Clock
	// DMA Configuration register
	DMA1_Channel3 -> CCR  |= (1<<10)| (0<<11); // memory size 16 bits
	DMA1_Channel3 -> CCR  |= (1<<8)| (0<<9); // peripheral size 16 bits
	DMA1_Channel3 -> CCR  |= (1<<7)| (1<<5)| (1<<4); // memory increment mode, circular mode and DIR(read from memory)
	DMA1_CSELR -> CSELR |= (6<<8); // set TIM6 as DMA_channel3 request sender
	DMA1_Channel3 -> CPAR = (uint32_t)&SPI1->DR; // set the spi1 data register as destination address
}
 
 
/* USER CODE END 0 */
 
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
 
  /* USER CODE END 1 */
 
  /* MCU Configuration--------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  /* USER CODE BEGIN 2 */
  TIM6Init();
  DMA1Init();
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint16_t DMAlength=3;
  DMA1_Channel3 -> CNDTR= DMAlength;
  uint16_t SPIdata[DMAlength]={1,256,1024};
  DMA1_Channel3 ->CMAR= (uint32_t)SPIdata; // set the DMA source address
  DMA1_Channel3 -> CCR|= (1<<0); // Enable DMA
  TIM6 -> CR1|= (1<<0); // Enable Timer
 
  while (1)
  {
 
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
 
/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
  /** Configure the main internal regulator output voltage
  */
  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 10;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
 
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}
 
/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{
 
  /* USER CODE BEGIN SPI1_Init 0 */
 
  /* USER CODE END SPI1_Init 0 */
 
  /* USER CODE BEGIN SPI1_Init 1 */
 
  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 7;
  hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */
 
  /* USER CODE END SPI1_Init 2 */
 
}
 
/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
 
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
 
  /*Configure GPIO pin : B1_Pin */
  GPIO_InitStruct.Pin = B1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
 
  /*Configure GPIO pins : USART_TX_Pin USART_RX_Pin */
  GPIO_InitStruct.Pin = USART_TX_Pin|USART_RX_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
}
 
/* USER CODE BEGIN 4 */
 
/* USER CODE END 4 */
 
/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}
 
#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
 
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

Hi Jan,

I take advantage of cube just to initialize the system clock and the SPI interface. Then I used only the register to set the timer and the DMA

The relevant lines of the code are 43 to 64 and 105-110.

In this way I think the ARR register (now set at maximum value) has the value that sets the data rate of the SPI. Do you agree?

Then the goal is to have an SPI transfer that does not stop. Do you think setting the circular mode of the DMA is fine? Is it correct to enable the timer outside the while loop?

Thanks a lot

Regards

Yes, yes and yes (although you've enabled it in TIM6Init(), too).

Does it work?

JW

PS. make the data array global and volatile

Tommino
Senior

No, unfortunately It doesn't work :(

Any idea?

What exactly "it doesn't work" means? What are the symptoms?

Read out and check/post content of TIM, DMA and SPI registers.

JW