Skip to main content
WJans.1
Associate
September 20, 2020
Question

Unable to get DMA to GPIO working on STM32F411

  • September 20, 2020
  • 2 replies
  • 2248 views

I'm new to using the STM32, coming from AVR. I'm trying to switch in order to take advantage of DMA but I can't get it working. I have a Nucleo STM32F411re.

I've generated a new project, configured a few pins on GPIOC as output, configured TIM1 to use an internal clock, and generate a PWM out on CH1, and configured a DMA to use TIM_UP (DMA2 Stream 5) Memory to Peripheral.

in the code I start the PWM output -- this works and I see it on the scope.

if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1) != HAL_OK)

{

Error_Handler();

}

then I try to start the DMA transfer - this doesn't work.

if (HAL_DMA_Start(&hdma_tim1_up, (uint32_t)data, ((uint32_t)&(GPIOC->ODR)), 16) != HAL_OK) {

Error_Handler();

};

I'm not sure what I'm doing wrong. I've read a lot of forum posts about this with mixed results but even when I follow the examples that claim to work they don't work for me.

Eventually I'd like to get it working with 11 data lines and 1 clock line and would like to use interrupts and circular mode but I'm happy to just get the basics working first.

This topic has been closed for replies.

2 replies

TDK
Super User
September 20, 2020

>  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

Fairly sure this should be TIM_TRGO_UPDATE since that's the event you want to trigger from.

Here is some code that works, although it doesn't use HAL directly. You could cross-reference the register values with your example to see what's different.

 // set up the timer
 __HAL_RCC_TIM1_CLK_ENABLE();
 __HAL_RCC_TIM1_FORCE_RESET();
 __HAL_RCC_TIM1_RELEASE_RESET();
 TIM1->PSC = 0;
 TIM1->ARR = 16;
 TIM1->CNT = 0;
 TIM1->DIER |= TIM_DIER_UDE;
 
 uint32_t gpio_data[] = {
 (1 << 10) | (1 << 11),
 1 << 26,
 (1 << 10) | (1 << 27),
 1 << 26};
 
 // set up the DMA channel
 __HAL_RCC_DMA2_CLK_ENABLE();
 __HAL_RCC_DMA2_FORCE_RESET();
 __HAL_RCC_DMA2_RELEASE_RESET();
 
 DMA_Stream_TypeDef * stream = DMA2_Stream5;
 stream->M0AR = (uint32_t) gpio_data;
 stream->NDTR = sizeof(gpio_data) / (sizeof(*gpio_data));
 
 // disable direct mode
 stream->FCR |= DMA_SxFCR_DMDIS;
 //stream->FCR |= 0b11 << DMA_SxFCR_FTH_Pos;
 
 // TIM1_UP is channel 6 on DMA2 stream 5
 stream->CR |= 6 << DMA_SxCR_CHSEL_Pos;
 stream->CR |= 0b10 << DMA_SxCR_MSIZE_Pos;
 stream->CR |= 0b10 << DMA_SxCR_PSIZE_Pos;
 stream->CR |= DMA_SxCR_MINC;
 stream->CR |= DMA_SxCR_CIRC;
 stream->CR |= 0b01 << DMA_SxCR_DIR_Pos;
 stream->PAR = (uint32_t) &GPIOC->BSRR;
 stream->CR |= 0b1 << DMA_SxCR_PL_Pos;
 
 stream->CR |= DMA_SxCR_EN;
 
 // start timer
 TIM1->CR1 |= TIM_CR1_CEN;

"If you feel a post has answered your question, please click ""Accept as Solution""."
WJans.1
WJans.1Author
Associate
September 22, 2020

Thank you so much -- this worked for me.

I also got it working with ODR with some slight changes, below.

Now I just need to get it working with interrupts for double buffered transfers but at least this gives me some confidence. Maybe it's time to jump on the HAL ***** bandwagon that everyone else seems to be on. Working with registers is closer to my AVR experience anyways.

// set up the timer

__HAL_RCC_TIM1_CLK_ENABLE();

__HAL_RCC_TIM1_FORCE_RESET();

__HAL_RCC_TIM1_RELEASE_RESET();

TIM1->PSC = 0; // prescaler

TIM1->ARR = 167; // period (auto reload)

TIM1->CNT = 0; // counter

TIM1->DIER |= TIM_DIER_UDE; // Update DMA request enable

uint16_t gpio_data[] = {

0xAA55,

0x55AA

};

// set up the DMA channel

__HAL_RCC_DMA2_CLK_ENABLE();

__HAL_RCC_DMA2_FORCE_RESET();

__HAL_RCC_DMA2_RELEASE_RESET();

DMA_Stream_TypeDef * stream = DMA2_Stream5;

stream->M0AR = (uint32_t) gpio_data; // memory 0 address

stream->NDTR = sizeof(gpio_data) / (sizeof(*gpio_data)); // number of data

stream->FCR |= DMA_SxFCR_DMDIS; // direct mode disabled

//stream->FCR |= 0b11 << DMA_SxFCR_FTH_Pos; // fifo threshold full

// TIM1_UP is channel 6 on DMA2 stream 5

// stream->CR = 0;

stream->CR |= 6 << DMA_SxCR_CHSEL_Pos; // channel 6

stream->CR |= 0b01 << DMA_SxCR_MSIZE_Pos; // memory size half-word

stream->CR |= 0b01 << DMA_SxCR_PSIZE_Pos; // peripheral size half-word

stream->CR |= DMA_SxCR_MINC; // memory increment

stream->CR |= DMA_SxCR_CIRC; // circular

stream->CR |= 0b01 << DMA_SxCR_DIR_Pos; // memory to peripheral

stream->PAR = (uint32_t) &GPIOC->ODR; // peripheral address

stream->CR |= 0b1 << DMA_SxCR_PL_Pos; // priority medium

stream->CR |= DMA_SxCR_EN; // enable

TIM1->CR1 |= TIM_CR1_CEN; // enable counter

waclawek.jan
Super User
September 24, 2020

> moving the dma buffer out of the main block

What does that mean?

JW

WJans.1
WJans.1Author
Associate
September 24, 2020

If I put the gpio_data array inside `int main(void) {}`, after `USER CODE BEGIN 2` then it works. If I put the gpio_data array in the private variables at the top in `USER CODE BEGIN PV` then it stops working.