Author Topic: STM32F4 DMA Mem->GPIO triggered by timer  (Read 5277 times)

0 Members and 1 Guest are viewing this topic.

Offline MattHollandsTopic starter

  • Frequent Contributor
  • **
  • Posts: 313
  • Country: gb
    • Matt's Projects
STM32F4 DMA Mem->GPIO triggered by timer
« on: April 17, 2022, 03:16:58 am »
Hi all,

I am trying to generate an 8-bit bit pattern with approximately an 4MHz update rate using an STM32F446 microcontroller. My general idea is to configure a timer to trigger a DMA update from memory to GPIO in order to generate a certain bit pattern.

Creating the timer is easy, but I am unable to actually get the DMA to trigger. I have been following a number of sources online, in particular this YouTube video () which is accompanied by this GitHub code https://github.com/mnemocron/STM32_PatternDriver/blob/main/Core/Src/main.c

I think I have matched his code almost exactly (other than using a different chip, different timer and different GPIOs) but I cannot get the DMA to actually run. Here is my code and see attached images for STM32Cube configuration. The expected outcome is a square wave on PA8 (generated by the OC comparison) and then a similar square wave on PA0 generated by the DMA writes to the GPIOA->BSRR register.

I've tried all sorts of things, and to be honest I don't quite understand what some of this code is meant to do (for example why he is using the timer update to trigger DMA instead of the OC, or why he calls both HAL_TIM_Base_Start and HAL_TIM_OC_Start). At this point I've tried a bunch of variations and different online code examples, but none of them actually seem to trigger the DMA. I can see the timer is running and generating the pulse on PA8, but nothing is happening on PA0.

Any fresh eyes or experience very much appreciated!

Thanks,

Matt

Code: [Select]
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_TIM1_Init();
  MX_DMA_Init();
  /* USER CODE BEGIN 2 */
//  GPIOA->BSRR = 0x00000001;
  uint32_t pixelclock[4] = {0x00000001, 0x00010000, 0x00000001, 0x00010000 };
  HAL_DMA_Start(&hdma_tim1_up,  (uint32_t)pixelclock, (uint32_t)&(GPIOA->BSRR), 4);
  HAL_TIM_Base_Start(&htim1);
  HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);
  TIM1->DIER |= (1 << 8);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
HAL_Delay(500);
// HAL_GPIO_TogglePin(GPIOA, 1);
  }
  /* USER CODE END 3 */
}
« Last Edit: April 17, 2022, 03:18:34 am by MattHollands »
Read about my stuff at: projects.matthollands.com
 

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4014
  • Country: nl
Re: STM32F4 DMA Mem->GPIO triggered by timer
« Reply #1 on: April 17, 2022, 04:51:09 am »
Forget the HAL shit and use direct setting of the peripheral registers.

Take a look at the code in this repository https://github.com/pecostm32/STM32F303_Sine_Square_Generator

It is using two DMA channels controlled by timers. One is for making a sine by writing data to a DAC and the other one is making square waves on GPIO pins by writing to a port register.

For it all to work the GPIO pins have to be in the same port. The peripherals between the families are not to different. Just get the reference and programming manuals from here https://www.st.com/en/microcontrollers-microprocessors/stm32f446.html#documentation

Online DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6093
  • Country: es
Re: STM32F4 DMA Mem->GPIO triggered by timer
« Reply #2 on: April 17, 2022, 01:54:08 pm »
Use the timer Update DMA, everytime the timer updates, it triggers a DMA transfer.
https://www.cnblogs.com/shangdawei/p/4753284.html

Something like:
Code: [Select]
HAL_DMA_Start(&hdma_tim3_up,  (uint32_t*)SourceBuffer, (uint32_t)&GPIOA->ODR, bufferElements);
__HAL_TIM_ENABLE_DMA(&htim3, TIM_DMA_UPDATE);
HAL_TIM_Base_Start(&htim3);

Check this repo for CubeMX config.
« Last Edit: January 16, 2024, 12:33:22 pm by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 
The following users thanked this post: MattHollands

Offline wek

  • Frequent Contributor
  • **
  • Posts: 514
  • Country: sk
Re: STM32F4 DMA Mem->GPIO triggered by timer
« Reply #3 on: April 17, 2022, 07:14:01 pm »
Read out and check/post content of TIM, DMA and relevant GPIO registers.

JW
 
The following users thanked this post: MattHollands

Offline MattHollandsTopic starter

  • Frequent Contributor
  • **
  • Posts: 313
  • Country: gb
    • Matt's Projects
Re: STM32F4 DMA Mem->GPIO triggered by timer
« Reply #4 on: April 18, 2022, 03:18:43 am »
Thanks, for all the input - I think I cracked it!

Forget the HAL shit and use direct setting of the peripheral registers.
I get the sentiment but I prefer to use the HAL where possible since it makes the code a lot more understandable (except when it doesn't  :palm:) Nonetheless, your link was helpful when it came to debugging everything - thank you.

Seems like a job for the timer DMA burst feature.
I think this function specifically allows you to write to the timer peripheral only. The destination address you provide is referenced to the timer address.

Read out and check/post content of TIM, DMA and relevant GPIO registers.
This is what I did and I this helped me to find the issue.

Basically, I followed section 9.3.18 "Stream configuration procedure" of the STM32F446 reference manual and this showed me the registers I need to set to make it work. Something like this worked for me:
Code: [Select]
  uint32_t pixelclock[4] = {0x00000001, 0x00010000, 0x00000001, 0x00010000 };
  __HAL_RCC_DMA2_CLK_ENABLE();
  DMA2_Stream1->PAR = (uint32_t)&(GPIOA->BSRR); // Set peripheral address
  DMA2_Stream1->M0AR = (uint32_t) pixelclock; // Set memory address
  DMA2_Stream1->NDTR = 4; // set number of bytes to transfer
  DMA2_Stream1->CR |= (6 << DMA_SxCR_CHSEL_Pos); // enable channel 6 (TIM1_CH1)
  DMA2_Stream1->CR |= (2 << DMA_SxCR_PL_Pos); // set to high priority
//   FIFO should be disabled by default
  DMA2_Stream1->CR |= (1 << DMA_SxCR_DIR_Pos); //set memory-to-peripheral
  DMA2_Stream1->CR |= (1 << DMA_SxCR_MINC_Pos); //Incremement memory address
  // Assume PINC is 0
  //Assume burst is set to single transfer
  DMA2_Stream1->CR |= (2 << DMA_SxCR_MSIZE_Pos); // Set memory data width to a word
  DMA2_Stream1->CR |= (2 << DMA_SxCR_PSIZE_Pos); // Set peripheral data width to a word
  DMA2_Stream1->CR |= (1 << DMA_SxCR_CIRC_Pos); // Set to circular mode
  // Assume DBM is zero
  DMA2_Stream1->CR |= (1 << DMA_SxCR_EN_Pos); //enable DMA

I then compared the CR register to the value when I used the HAL code in my original post and I found that actually it only wrote a couple of the bits, but not enough to actually make the DMA run. I then stumbled across the "HAL_DMA_Init" function that basically does the exact code above!

So now I have a working implementation that looks like:

Code: [Select]
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_TIM1_Init();
  MX_DMA_Init();
  /* USER CODE BEGIN 2 */
  uint32_t pixelclock[4] = {0x00000001, 0x00010000, 0x00000001, 0x00010000 };
  HAL_DMA_Init(&hdma_tim1_ch1);
  HAL_DMA_RegisterCallback(&hdma_tim1_ch1, HAL_DMA_XFER_CPLT_CB_ID, DMA_XFER_CPLT);
  HAL_DMA_Start_IT(&hdma_tim1_ch1,  (uint32_t)pixelclock, (uint32_t)&(GPIOA->BSRR), 4);
  HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);

  TIM1->DIER |= (1 << 9);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
HAL_Delay(500);
HAL_DMA_Start_IT(&hdma_tim1_ch1, (uint32_t)pixelclock, (uint32_t)&(GPIOA->BSRR), 4);
HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);
  }
  /* USER CODE END 3 */
}

void DMA_XFER_CPLT(DMA_HandleTypeDef *hdma)
{
// Disable the DMA (not clear if this is necessary)
HAL_DMA_Abort_IT(&hdma_tim1_ch1);
// Stop the timer
HAL_TIM_OC_Stop(&htim1, TIM_CHANNEL_1);
// This appears necessary to deassert the trigger and prevent DMA triggering immediately when enabled
TIM1->DIER &= ~(1 << 9);
TIM1->DIER |= (1 << 9);
}

Read about my stuff at: projects.matthollands.com
 

Online DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6093
  • Country: es
Re: STM32F4 DMA Mem->GPIO triggered by timer
« Reply #5 on: November 25, 2022, 07:28:46 am »
I'm not a fan of reviving old threads, but had to do this recently, can be done much simpler.

CubeMX Timer config:
- Memory (No addr. increase) ==> Peripheral (Addr. Increase)
- Half-word (Both)
- Circular mode

Code: [Select]
uint16_t pixelclock = Clock_Pin;    // 16 bit, will be writing to lower/upper parts of BSRR (Set/Reset)

HAL_DMA_Start(htim1.hdma[TIM_DMA_ID_UPDATE], (uint32_t)&pixelclock, (uint32_t)&Clock_GPIO_Port->BSRR, 2);
__HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE);
__HAL_TIM_ENABLE(&htim1);
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf