Обнаружение открытого COM-порта ПК с устройства USB Virtual Com Port - PullRequest
5 голосов
/ 17 марта 2011

Я использую микроконтроллер STM32F105 с библиотекой USB STM32_USB-FS-Device_Lib_V3.2.1 и адаптировал пример VCP для наших целей (интеграция с RTOS и последовательным API).

Проблема заключается в том, что если USB-кабель подключен, но порт на хосте Windows не открыт, через несколько минут устройство завершает постоянный повторный вход в USB ISR до тех пор, пока порт не будет открыт, а затем все начнется. работает нормально.

Я обработал обработчик прерываний и вижу, что при возникновении ошибки обработчик ISR завершается, а затем сразу же снова входит. Это происходит потому, что при выходе из прерывания флаг IEPINT в OTG_FS_GINTSTS неясен. В данный момент OTG_FS_DAINT содержит 0x00000002 (набор IEPINT1), а DIEPINT1 - 0x00000080 (TXFE). Вызывается строка в OTGD_FS_Handle_InEP_ISR (), которая очищает TXFE, но бит либо не очищается, либо сразу же восстанавливается. При повторном открытии COM-порта на хосте состояние OTG_FS_GINTSTS и OTG_FS_DAINT в конце прерывания всегда равно нулю, и дальнейшие прерывания происходят с нормальной скоростью. Обратите внимание, что проблема возникает, только если данные выводятся, но на хосте нет открытого порта. Если порт открыт или данные не выводятся, система работает бесконечно. Я полагаю, что чем больше данных выводится, тем раньше возникает проблема, но в настоящее время это нереально.

Код VCP имеет переменную состояния, которая принимает следующие перечисляемые значения:

  UNCONNECTED,
  ATTACHED,
  POWERED,
  SUSPENDED,
  ADDRESSED,
  CONFIGURED

и мы используем состояние CONFIGURED, чтобы определить, помещать ли данные в буфер драйвера для отправки. Однако состояние CONFIGURED устанавливается, когда кабель подключен, а не тогда, когда на хосте открыт порт и подключено приложение. Я вижу, что когда Windows действительно открывает порт, происходит множество прерываний, поэтому кажется, что происходит какое-то сообщение об этом событии; Интересно, возможно ли поэтому определить, открыт ли порт на хосте?

Мне нужна одна из двух вещей, возможно:

  1. Для предотвращения застревания кода USB в ISR в первом случае
  2. Чтобы определить, открыт ли порт хосту со стороны устройства, и отправлять данные только для отправки при открытии.

Ответы [ 7 ]

7 голосов
/ 25 марта 2011

Часть (1) - предотвращение блокировки прерываний - была облегчена исправлением ошибки библиотеки USB из поддержки ST;он не правильно очищал прерывание TxEmpty.

После некоторых исследований и поддержки со стороны службы поддержки ST я нашел решение для части (2) - определение, открыт ли порт хоста.Традиционно, когда порт открыт, устанавливается линия управления модемом DTR.Эта информация передается на устройство класса CDC, поэтому я могу использовать ее для достижения своей цели.Приложение может изменить поведение DTR, но это не должно происходить ни в одном из клиентских приложений, которые в этом случае могут подключиться к этому устройству.Однако существует план резервного копирования, который неявно предполагает, что порт открыт, если задано линейное кодирование (бод, кадрирование).В этом случае нет средств для обнаружения замыкания, но, по крайней мере, это не помешает нетрадиционному приложению работать с моим устройством, даже если оно затем вызывает его сбой при отключении.

Что касается примера кода VCP STЯ внес следующие изменения в usb_prop.c:

1) Добавил следующую функцию:

#include <stdbool.h>
static bool host_port_open = false ;
bool Virtual_Com_Port_IsHostPortOpen()
{
    return bDeviceState == CONFIGURED && host_port_open ;
}

2) Изменена обработка Virtual_Com_Port_NoData_Setup () для SET_CONTROL_LINE_STATE, таким образом:

else if (RequestNo == SET_CONTROL_LINE_STATE)
{
  // Test DTR state to determine if host port is open
  host_port_open = (pInformation->USBwValues.bw.bb0 & 0x01) != 0 ;
  return USB_SUCCESS;
}

3) Чтобы разрешить использование с приложениями, которые обычно не работают с DTR, я также изменил Virtual_Com_Port_Data_Setup () для обработки SET_LINE_CODING таким образом:

  else if (RequestNo == SET_LINE_CODING)
  {
    if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
    {
      CopyRoutine = Virtual_Com_Port_SetLineCoding;

      // If line coding is set the port is implicitly open 
      // regardless of host's DTR control.  Note: if this is 
      // the only indicator of port open, there will be no indication 
      // of closure, but this will at least allow applications that 
      // do not assert DTR to connect.
      host_port_open = true ;

    }
    Request = SET_LINE_CODING;
  }
1 голос
/ 13 января 2016

Я нашел другое решение, приняв CDC_Transmit_FS.Теперь его можно использовать в качестве вывода для printf, перезаписав функцию _write.

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

Я узнал, что если dev_state не USBD_STATE_CONFIGURED, USB-штекер отключен.Если штекер подключен, но порт VCP не открыт через PuTTY или термит, вторая проверка не пройдена.

Эта реализация отлично работает для меня для приложений RTOS и CubeMX HAL.Занятый цикл больше не блокирует потоки с низким приоритетом.

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
    uint8_t result = USBD_OK;


    // Check if USB interface is online and VCP connection is open.
    // prior to send:
    if ((hUsbDevice_0->dev_state != USBD_STATE_CONFIGURED)
            || (hUsbDevice_0->ep0_state == USBD_EP0_STATUS_IN))
    {
        // The physical connection fails.
        // Or: The phycical connection is open, but no VCP link up.
        result = USBD_FAIL;
    }
    else
    {

        USBD_CDC_SetTxBuffer(hUsbDevice_0, Buf, Len);

        // Busy wait if USB is busy or exit on success or disconnection happens
        while(1)
        {

            //Check if USB went offline while retrying
            if ((hUsbDevice_0->dev_state != USBD_STATE_CONFIGURED)
                        || (hUsbDevice_0->ep0_state == USBD_EP0_STATUS_IN))
            {
                result = USBD_FAIL;
                break;
            }

            // Try send
            result = USBD_CDC_TransmitPacket(hUsbDevice_0);
            if(result == USBD_OK)
            {
                break;
            }
            else if(result == USBD_BUSY)
            {
                // Retry until USB device free.
            }
            else
            {
                // Any other failure
                result = USBD_FAIL;
                break;
            }

        }
    }

    return result;
}

CDC_Transmit_FS используется _write:

// This function is used by printf and puts.
int _write(int file, char *ptr, int len)
{
    (void) file; // Ignore file descriptor
    uint8_t result;

    result = CDC_Transmit_FS((uint8_t*)ptr, len);
    if(result == USBD_OK)
    {
        return (int)len;
    }
    else
    {
        return EOF;
    }
}

С уважением, Бернхард

1 голос
/ 26 августа 2015

Возможно, мне нужна одна из двух вещей:

  1. Чтобы предотвратить застревание кода USB в ISR в первом случае
  2. Чтобы определить, имеет ли хостпорт, открытый со стороны устройства, и только отправляет данные для отправки при открытии.

Вы должны попытаться сделать вариант 1 вместо 2. В Windows и Linux можно открытьCOM-порт и используйте его, не устанавливая управляющие сигналы, что означает, что не существует надежного кроссплатформенного способа определения того, что COM-порт открыт.

Хорошо запрограммированное устройство не позволит себе перестать функционироватьтолько потому, что хост USB перестал опрашивать данные;это нормальная вещь, с которой нужно обращаться правильно.Например, вы можете изменить свой код так, чтобы вы ставили в очередь данные для отправки на хост USB, если для конечной точки доступно буферное пространство.Если свободного буферного пространства нет, возможно, у вас есть специальный код обработки ошибок.

1 голос
/ 25 августа 2015

После стольких поисков и некоторого реверс-инжиниринга я наконец-то нашел метод обнаружения открытого терминала, а также его завершения.Я обнаружил, что в классе CDC есть три узла данных, один из которых является управляющим узлом, а два других являются узлами ввода и вывода данных. Теперь, когда вы открываете терминал, код отправляется на управляющий узел, а также при его закрытии.,все, что нам нужно сделать, это получить эти коды, а затем запустить и остановить наши задачи по передаче данных.отправляемый код имеет соответственно 0x21 и 0x22 для открытия и закрытия терминала. В файле usb_cdc_if.c есть функция, которая получает и интерпретирует эти коды (есть регистр переключателя, а переменная cmd - это код, о котором мы говорим).этой функцией является CDC_Control_FS.Теперь мы, теперь все, что нам нужно сделать, это расширить эту функцию, чтобы она интерпретировала 0x22 и 0x21.теперь вы знаете в своем приложении, открыт порт или нет.

0 голосов
/ 01 января 2018

Я исправил это путем проверки переменной hUsbDeviceFS.ep0_state.Он равен 5, если подключен, и 4, если не подключен или был отключен.Но.Есть некоторые проблемы в HAL.Это равнялось 5, когда программа запустилась.Следующие шаги исправили это в начале программы

        /* USER CODE BEGIN 2 */
            HAL_Delay(500);
            hUsbDeviceFS.ep0_state = 4;

...

У меня нет никаких желаний изучать HAL - я надеюсь, что этот пост увидят разработчики и они исправят HAL.Это помогло мне исправить мою проблему.

0 голосов
/ 16 января 2017

Отказ от ответственности: я использую код, сгенерированный Cube, и в результате он работает с драйверами HAL. Решения, предложенные здесь ранее, не работают для меня, поэтому я нашел одно. Это не хорошо, но работает для некоторых целей.

Один из косвенных признаков не открытого порта возникает, когда вы пытаетесь передать пакет по CDC_Transmit_FS, а затем ждете, пока TxState не будет установлен в 0. Если порт не открыт, этого никогда не происходит. Поэтому мое решение - исправить тайм-аут:

uint16_t count = 0;
USBD_CDC_HandleTypeDef *hcdc =
        (USBD_CDC_HandleTypeDef*) USBD_Device.pClassData;

while (hcdc->TxState != 0) {
    if (++count > BUSY_TIMEOUT) { //number of cycles to wait till it makes decision
        //here it's clear that port is not opened
    }
}

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

uint8_t waitForTransferCompletion(void) {

    uint16_t count = 0;
    USBD_CDC_HandleTypeDef *hcdc =
             (USBD_CDC_HandleTypeDef*) USBD_Device.pClassData;

    while (hcdc->TxState != 0) {
        if (++count > BUSY_TIMEOUT) { //number of cycles to wait till it makes decision
            USBD_Stop(&USBD_Device); // stop and
            MX_USB_DEVICE_Init(); //            init device again
            HAL_Delay(RESET_DELAY); // give a chance to open port
            return USBD_FAIL; // return fail, to send last packet again
        }
    }

    return USBD_OK;
}

Вопрос в том, насколько большим должно быть время ожидания, чтобы не прерывать передачу, пока порт открыт. Я установил BUSY_TIMEOUT на 3000, и теперь он работает.

0 голосов
/ 13 апреля 2011

У меня такое же требование для обнаружения открытия / закрытия порта ПК. Я видел, как это реализовано следующим образом:

Открытие обнаружено:

  • подтверждено DTR
  • CDC массовый перевод

Закрытие обнаружено:

  • DTR сброшено
  • USB "отключен", спит и т.д.

Кажется, это работает достаточно хорошо, хотя для подтверждения его надежной работы потребуется более тщательное тестирование.

...