cancel
Showing results for 
Search instead for 
Did you mean: 

TIM1+DMA Driven GPIO transmit speed limit

PZhan.11
Associate II

I am using STM32F407ZGT6 and configured the TIM1_CH1 to trigger DMA2 to transmit data through GPIO. Since the protocol is using both raising and failing edges, I need to control both data pins and the clock pin. So the TIM1 is just generating PWM signals without output. I have put all pins in GPIOC so I can use a half-word data width on both MSIZE and PSIZE to directly control both data pins and the clock pin.

The target clock frequency is about 5 MHz, so I need to transmit data from SRAM to the GPIO peripheral every 50ns like:

GPIOC->ODR = tmpodr[0];
GPIOC->ODR = tmpodr[0]|CLK_Pin;
GPIOC->ODR = tmpodr[1]|CLK_Pin;
GPIOC->ODR = tmpdor[1];
 
GPIOC->ODR = tmpodr[2];
GPIOC->ODR = tmpodr[2]|CLK_Pin;
GPIOC->ODR = tmpodr[3]|CLK_Pin;
GPIOC->ODR = tmpdor[3];

it is one cycle.

But what I have got is just about 3.70 MHz from the oscilloscope. After I enabled the FIFO, the maximum is about 4 MHz. And memory burst mode has no effect. I thought it is caused by the bus race since if I do this by CPU, the maximum frequency may up to 7 MHz with for loop unroll. But it is hard to slow down since I cannot exceed the limitation of 5 MHz or the peripheral would not work correctly. And 3.7 Mhz is a bit slow since I hope it works at least 4.5+ MHz. So do you have any suggestion?

1 ACCEPTED SOLUTION

Accepted Solutions

You can use two different timer channels to trigger DMA.

Or, maybe better, generate the clock using Toggle mode, and trigger DMA with a different channel or even using Update, in between.

JW

View solution in original post

6 REPLIES 6
TDK
Guru

I can get an update rate of 8.4 MHz in direct mode and 16.8 MHz with a FIFO on the STM32F401 (at 84 MHz clock). I imagine the F407 will be the same.

This cripples the bus, so good luck doing anything else at the same time.

// set up the timer
  __HAL_RCC_TIM1_CLK_ENABLE();
  __HAL_RCC_TIM1_FORCE_RESET();
  __HAL_RCC_TIM1_RELEASE_RESET();
  TIM1->PSC = 0;
  TIM1->ARR = 4;
  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".

Thanks for your reply. Yes I think it should be achievable with F407 168MHz. I am using STM32CubeMX and HAL to configure the system as follows:

static void MX_TIM1_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
 
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 5;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 31;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_OnePulse_Init(&htim1, TIM_OPMODE_SINGLE) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_TIMING;
  sConfigOC.Pulse = 1;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  __HAL_TIM_DISABLE_OCxPRELOAD(&htim1, TIM_CHANNEL_1);
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  } 
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
  if(htim_base->Instance==TIM1)
  {
    /* Peripheral clock enable */
    __HAL_RCC_TIM1_CLK_ENABLE();
 
    /* TIM1 DMA Init */
    /* TIM1_CH1 Init */
    hdma_tim1_ch1.Instance = DMA2_Stream1;
    hdma_tim1_ch1.Init.Channel = DMA_CHANNEL_6;
    hdma_tim1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_tim1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_tim1_ch1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_tim1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_tim1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_tim1_ch1.Init.Mode = DMA_NORMAL;
    hdma_tim1_ch1.Init.Priority = DMA_PRIORITY_LOW;
    hdma_tim1_ch1.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
    hdma_tim1_ch1.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_tim1_ch1.Init.MemBurst = DMA_MBURST_INC4;
    hdma_tim1_ch1.Init.PeriphBurst = DMA_PBURST_SINGLE;
    if (HAL_DMA_Init(&hdma_tim1_ch1) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(htim_base,hdma[TIM_DMA_ID_CC1],hdma_tim1_ch1); 
  }
  /* ... */
}

I have compared the configuration roughly but did not find anything totally different. But after I changed the ARR less than 7, no improvement shown in the oscilloscope even with FIFO and memory burst. I will compare the details according to the reference to find the reason. Thank you again.

The simple differences are:

  1. I am using TIM1_CH1 instead of TIM1_UP since I need the TRGO to trigger another TIM after the data is transmitted. That is also why I tried to use TIM+DMA driven GPIO
  2. I am using ODR instead of BSRR which should not have any difference
  3. I am enabled Double Buffer to prepare the next round of transmission when the transmit is going.

The main difference is that PZhan calls the whole 8-step sequence from the opening post as a "cycle".

I personally would generate the clock directly as PWM output of one of the TIM1's channels, that cuts down the DMA requirements to half. Timing may need fine-tuning, though.

JW

But as you can see in the pseudo-code, the protocol transmits two bits on both rising and falling edges. That is why the PWM clock doesn't work. But one thing you mentioned that DMA requested too fast may be the problem.

You can use two different timer channels to trigger DMA.

Or, maybe better, generate the clock using Toggle mode, and trigger DMA with a different channel or even using Update, in between.

JW