Перейти к содержанию
    

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

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

Столкнулся тут со странным поведением 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  с чем-то коллидирует и пишет в буфер нули.

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Проблема решена, вопрос был в логике ожидания получения байта:

while ((DMA2_Stream1->CR & DMA_SxCR_EN) || (WaitingRxUART == true))

Изменение порядка условий все решило.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...