cancel
Showing results for 
Search instead for 
Did you mean: 

Unable to get DMA to GPIO working on STM32F411

WJans.1
Associate II

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.

5 REPLIES 5
TDK
Guru

>  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".

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

Just moving the dma buffer out of the main block breaks this. I guess it's something to do with where the array is located in ram but I'm not clear on how to fix that.

> moving the dma buffer out of the main block

What does that mean?

JW

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.