Jump to content

    

USB CDC: Как "притормозить" приём данных

Добрый день,

реализуем YModem поверх USB CDC. Тестируем под Win7 с помощью TeraTerm.

Как только начинается передача пакетов данных от ПК (TeraTerm) в STM32, пакеты данных (по 512) прилетают с такой скоростью, что забивают кольцевой буфер под завязку.
Посмотрели более детально, TeraTerm (Ymodem TX) повторяет пакеты до тех пор, пока STM32 не пришлёт ACK (тогда шлёт следующий кусок данных).

Возник вопрос - как "притормозить" приходящие пакеты? Мы рассматривали вариант отключать прерывания USB:

hpcd_USB_OTG_HS.Instance->GAHBCFG &= ~USB_OTG_GAHBCFG_GINT;

Но это как-то жестоко, хоть и работает.

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

Обработчик который сейчас реализован:

static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 11 */
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceHS.pClassData;
  if (hcdc)
  {
    USBD_CDC_SetRxBuffer(&hUsbDeviceHS, Buf);
    USBD_CDC_ReceivePacket(&hUsbDeviceHS);

    if (bs_usb_rx_command(Buf, hcdc->RxLength) == 0)
    {
        return (USBD_BUSY);
    }
  }
  return (USBD_OK);
  /* USER CODE END 11 */
}

Если функция bs_usb_rx_command не может записать в кольцевой буфер (он полон) данные - возвращаем USBD_BUSY (по сути теряем данные). Но следующие тут же прилетают.

Поделитесь, пожалуйста, опытом, как вы решали подобную задачу. Спасибо!

MCU: STM32H743XIH
STM32CubeIDE Version: 1.0.2
 

Share this post


Link to post
Share on other sites
9 минут назад, SimpleSoft сказал:

Поделитесь, пожалуйста, опытом, как вы решали подобную задачу. Спасибо!

Написать парсер выделяющий границы кадров прям в этом обработчике приёма (CDC_Receive_HS()). Выделил кадр - записал его в буфер (послал на обработку задаче обработчика) и взвёл флаг - "занято".

Когда задача-обработчик полностью вычитает кадр из буфера - она снимет флаг. Пока флаг стоит - обработчик приёма должен кадры пропускать (но всё равно сначала выделять их границы! - и только целиком пропускать). 

Т.е. - говоря грамотным языком: Вам надо процесс приёма потока кадров разбить на уровни (канальный, прикладной, ...). Канальный выполнять в обработчике приёма, прикладной - в фоновой задаче.

 

PS: Я не читал описание протокола YModem - ACK там является признаком чего? Просто признаком приёма кадра (вне зависимости от его содержимого) или признаком что удалённая сторона обработала данные из этого кадра и готова принять ещё? Если первое - то ACK должен генерить обработчик приёма сразу по завершении обнаружения границ кадра (ну или если там есть CRC - после обнаружения границ и проверки CRC).

Share this post


Link to post
Share on other sites
52 минуты назад, SimpleSoft сказал:

Возник вопрос - как "притормозить" приходящие пакеты? Мы рассматривали вариант отключать прерывания USB:

Почему? Вот почему вы не прочитали хотя бы какую-нибудь статью вроде "USB на пальцах"? Там, ближе к началу, обязательно должно быть сказано, что если конечная точка не готова принимать данные, она должна отвечать NAK. Все остальное будет решаться на уровне контроллера.

Edited by Сергей Борщ

Share this post


Link to post
Share on other sites
51 minutes ago, SimpleSoft said:

Возник вопрос - как "притормозить" приходящие пакеты?

Просто не читать, чтобы USB device возвращал хосту NAK.

Share this post


Link to post
Share on other sites
11 минут назад, Сергей Борщ сказал:

Почему? Вот почему вы не прочитали хотя бы какую-нибудь статью вроде "USB на пальцах"? Там, ближе к началу, обязательно должно быть сказано, что если конечная точка не готова принимать данные, она должна отвечать NAK. Все остальное будет решаться на уровне контроллера.

А если кадры YModema-а не будут совпадать с USB-кадрами? Т.е. - комп пошлёт 2 кадра YModem-а "впритык" друг к другу не дожидаясь ACK.

Share this post


Link to post
Share on other sites
1 hour ago, jcxz said:

А если кадры YModema-а не будут совпадать с USB-кадрами? Т.е. - комп пошлёт 2 кадра YModem-а "впритык" друг к другу не дожидаясь ACK.

И каким боком это помешает управлению потоком на уровне USB?

Share this post


Link to post
Share on other sites
11 часов назад, jcxz сказал:

А если кадры YModema-а не будут совпадать с USB-кадрами?

А если YModem передается через RS232, где вообще кадров нет? А если этот RS232 с "железным" управлением потоком и приемная сторона дернет RTS?

Share this post


Link to post
Share on other sites
9 часов назад, aaarrr сказал:

И каким боком это помешает управлению потоком на уровне USB?

Управлению потоком - никак, но вот парсингу на кадры - запросто может помешать. Так как будут приходить обрывки кадров.

36 минут назад, Сергей Борщ сказал:

А если YModem передается через RS232, где вообще кадров нет? А если этот RS232 с "железным" управлением потоком и приемная сторона дернет RTS?

Т.е. Вы хотите сказать, что по NAK-у USB-хост будет просто повторять данные позже?

Share this post


Link to post
Share on other sites
3 minutes ago, jcxz said:

Т.е. Вы хотите сказать, что по NAK-у USB-хост будет просто повторять данные позже?

Разумеется.

Share this post


Link to post
Share on other sites
8 минут назад, jcxz сказал:

Т.е. Вы хотите сказать, что по NAK-у USB-хост будет просто повторять данные позже?

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

Share this post


Link to post
Share on other sites
54 минуты назад, Сергей Борщ сказал:

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

Причём тут? Я его не использовал для CDC.

Да и не хвастался я самописным. Брал его из примеров IAR. Память точно изменяет... :smile:

Share this post


Link to post
Share on other sites
14 minutes ago, jcxz said:

Причём тут? Я его не использовал для CDC.

Так это общий механизм USB, причем тут CDC?

Share this post


Link to post
Share on other sites

Спасибо всем за ответы.

18 hours ago, Сергей Борщ said:

Почему? Вот почему вы не прочитали хотя бы какую-нибудь статью вроде "USB на пальцах"?

Мы хорошо разбираемся в USB, мой вопрос был про опыт решения на STM32. 

По поводу не читать USB RX - мы не пробовали, но возник вопрос - а не будет ли STM32 дергать постоянно прерывание о приёме данных? (в Tech Ref не копались) Может кто-то знает как OTG в STM32H7 обрабатывает RX data interrupt?

Edited by SimpleSoft

Share this post


Link to post
Share on other sites
On 8/5/2019 at 6:59 PM, SimpleSoft said:

Мы хорошо разбираемся в USB

Не похоже, извините.

On 8/5/2019 at 6:59 PM, SimpleSoft said:

мой вопрос был про опыт решения на STM32

Контроллер позволяет отправлять и ACK, и NAK. То, что это не позволяет сделать кубохал (кажется. Детально не исследовал) - не проблема контроллера.

Share this post


Link to post
Share on other sites
On 8/4/2019 at 11:55 PM, SimpleSoft said:

Поделитесь, пожалуйста, опытом, как вы решали подобную задачу. Спасибо!

Это делается созданием дополнительной задачи. Она только читает из USB и отправляет указатели на полученные пакеты в очередь.

Просто запомните на будущее - все зависимые друг от друга процессы обрабатывающие данные с разной скоростью используют для обмена между собой очереди.
Если у вас нет вытесняющей RTOS, то такую очередь делать довольно хлопотно. 

А если есть RTOS то все проще.  Вместе с задачей  Ymodem надо создать параллельную задачу чтения из USB. 
Вот как такая задача выглядит в RTOS ThreadX:

Spoiler

static void Mb_usbfs_rcv_task(ULONG ptr)
{
  UINT              res;
  ULONG             actual_length;
  ULONG             actual_flags;
  T_usbfs_drv_cbl * p = (T_usbfs_drv_cbl *)ptr;
  uint32_t          n;

  do
  {
    if (p->active)
    {
      n = p->head_n;
      res = ux_device_class_cdc_acm_read(p->cdc, p->rd_pack[n].buff,USBDRV_BUFFER_MAX_LENGTH,  &actual_length); // Чтение пакета из USB
      p->rd_pack[n].len = actual_length;
      if (res == UX_SUCCESS)
      {
        // Перемещаем указатель головы очереди
        n++; 
        if (n >= IN_BUF_QUANTITY) n = 0;
        p->head_n = n;

        // Выставляем флаг выполненного чтения
        if (tx_event_flags_set(&(p->evt), MB_USBFS_READ_DONE, TX_OR) != TX_SUCCESS)
        {
          tx_thread_sleep(2); // Задержка после ошибки 
        }

        // Если все буферы на прием заполнены, то значит системе не требуются данные
        if (p->tail_n == n)
        {
          // Перестаем принимать данные из USB и ждем когда система обработает уже принятые данные и подаст сигнал к началу приема по USB
          p->no_space = 1;
          if (tx_event_flags_get(&(p->evt), MB_USBFS_READ_REQUEST, TX_AND_CLEAR,&actual_flags, TX_WAIT_FOREVER) != TX_SUCCESS)
          {
            tx_thread_sleep(2); // Задержка после ошибки 
          }
        }
      }
      else
      {
        tx_thread_sleep(2); // Задержка после ошибки
      }
    }
    else
    {
      tx_thread_sleep(2); // Задержки после ошибки нужны для того чтобы задача не захватила все ресурсы в случает постоянного появления ошибки
    }

  } while (1);
}

 

Т.е. здесь задача чтения из USB при заполненной очереди перестает вызывать функцию получения пакетов, а зависает на ожидании флага от задачи  Ymodem-а
Заполненность очереди определяется просто по равенству указателей на хвост (p->tail_n) и на голову очереди (p->head_n).

И очередь здесь не очередь байт, а очередь буферов.
В простейшем случае  очередь состоит из двух буферов.  

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

Edited by AlexandrY

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now