Jump to content

    
Sign in to follow this  
Polaris

STM32F446 и DMA для вывода на GPIO

Recommended Posts

Доброго всем времени суток.

Столкнулся тут со странным поведением DMA у STM32F446 при использовании DMA2 для комутации GPIO. Необходимость возникла из-за того, что на новой плате не были предусмотрены инвертеры уровня UART, а ведомое устройство в режиме загрузчика их, как оказалось, требует. С STM32F302 проблем бы не было, там есть инверсия уровней, но вот в более быстром и сложном F446 такой инверсии нет, плату переделывать не хотелось, пришлось искать решение в виде софтового UART на DMA.

В плане передачи проблем никаких нет, все работает, тайминги и уровни не вызывают никаких вопросов, а вот в плане приема бьюсь уже неделю.

Вот так выглядит инициализация DMA:

  static uint32_t Buff[128];

  DMA_DeInit(DMA2_Stream1);
  DMA_StructInit(&DMA_InitStructure);
  DMA_InitStructure.DMA_BufferSize = 0;
  DMA_InitStructure.DMA_Channel = DMA_Channel_7;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&Buff;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(GPIOA->IDR);
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
  DMA_Init(DMA2_Stream1, &DMA_InitStructure);

Вот так инициализирую таймер, который будет задавать тактовую частоту (57600):

  TIM_DeInit(TIM8);

  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  TIM_TimeBaseStructure.TIM_Period = TIMER_PERIOD - 1;
  TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t)(SystemCoreClock / 1 / TIMER_FREQ) - 1;
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);

  TIM_GenerateEvent(TIM8, TIM_EventSource_Update);
  TIM_Cmd(TIM8, ENABLE);
  TIM_DMACmd(TIM8, TIM_DMA_Update, ENABLE);

Частоты однозначно верные, потому что тот же таймер используется для передачи, а там все ок, как в плане приема на стороне прибора, так и на осциллографе.

И вот так происходит прием:

  uint8_t i;
  uint8_t val = 0;

  /**< Empty buffer */
  memset(Buff, 0, 10);
  DMA_Cmd(DMA2_Stream1, DISABLE);
  DMA_ClearFlag(DMA2_Stream1, (DMA_FLAG_TCIF1 | DMA_FLAG_HTIF1 | DMA_FLAG_TEIF1));
  /**< Activate edge interrupt */
  WaitingRxUART = true;
  EXTI_ClearITPendingBit(EXTI_Line1);
  NVIC_EnableIRQ(EXTI1_IRQn);
  /**< Wait till DMA transmission is ready */
  while ((DMA2_Stream1->CR & DMA_SxCR_EN) || (WaitingRxUART == true));
  /**< Stop edge interrupt */
  NVIC_DisableIRQ(EXTI1_IRQn);
  DMA_Cmd(DMA2_Stream1, DISABLE);
  /**< Decode data from DMA buffer */
  /**< We are processing 8 data bits, don't know yet why it should be started with Buff[2]? */
  for (i = 2; i < 10; i++)
  {
    val >>= 1;
    if ((Buff[i] & (1 << 1)) == 0)
      val |= 0x80;
  }  

При комутации приемопередатчика возникает короткий импульс, который нужно фильтровать, это происходит в прерывании:

/**< External interrupt for detecting rising edge of UART packet */
void EXTI1_IRQHandler(void)
{
  if (EXTI_GetITStatus(EXTI_Line1) != RESET)
  {
    /**< Clear the UART EXTI line pending bit */
    EXTI_ClearITPendingBit(EXTI_Line1);
    /**< Wait 1 us and check input */
    TIM10->CNT = 0;
    TIM10->SR = 0;
    while ((TIM10->SR & TIM_IT_Update) == 0);
    if ((GPIOA->IDR & (1 << 1)) == 0)
      return;
    /**< Real start bit received, no spike */
    /**< Stop edge interrupt */
    NVIC_DisableIRQ(EXTI1_IRQn);
    WaitingRxUART = false;
    /**< Start DMA GPIO capture */
    /**< Receive 10 bits (8 data bits * 2 stop bits) */
    TIM8->CR1 &= ~TIM_CR1_CEN;
    TIM_SetCounter(TIM8, TIM8->ARR / 2);
    TIM_ClearITPendingBit(TIM8, TIM_IT_Update);
    TIM8->CR1 |= TIM_CR1_CEN;
    DMA_SetCurrDataCounter(DMA2_Stream1, 10);
    DMA_Cmd(DMA2_Stream1, ENABLE);
  }
}

Все вроде бы хорошо работает, прием идет, выбросы отфильтровываются, но есть одно но - периодически, причем достаточно часто, происходит сбой, вместо, к примеру 0xAA принимается 0xAB, я посмотрел, что оказывается в буфере, куда собираются значения IDR, и обнаружил там наряду с нормальными значениями состояния входов GPIO пару нулевых значений, причем они идут не первыми и не последними, то есть, буффер выглядит так:

0x9B6C, 0x00, 0x00, 0x9B6C, 0x9B6E, ...

Такого не может быть, потому что просто не может быть. Такое впечатление, что канал DMA  с чем-то коллидирует и пишет в буфер нули.

Может быть, кто-то сталкивался с подобным? Буду рад услышать какие-то советы, потому что голова уже сломалась.

На осциллографе данные выглядят нормально (за исключением выбросов), но они точно фильтруются.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this