Обнаружение открытого COM-порта ПК с помощью устройства виртуального Com-порта USB

StackOverflow https://stackoverflow.com/questions/5338875

Вопрос

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

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

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

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

  UNCONNECTED,
  ATTACHED,
  POWERED,
  SUSPENDED,
  ADDRESSED,
  CONFIGURED

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

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

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

Решение

Часть (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;
  }

Другие советы

После такого большого поиска и своего рода обратной инженерии я наконец нашел метод обнаружения открытого терминала, а также его прекращения. Я обнаружил, что в классе CDC есть три узла данных, один из них - это управляющий узел, а два других - это данные и данные данных. Анкет Все, что нам нужно сделать, это получить эти коды, и их запустить и остановить наши задачи передачи данных. Отправляется код соответственно 0x21 и 0x22 для открытия и закрытия терминала. В USB_CDC_IF.C существует функция, которая получает и интерпретирует эти коды (есть случай переключения, и переменная CMD - это код, о котором мы говорим) . Эта функция - cdc_control_fs. Вот и мы, теперь все, что нам нужно сделать, это расширить эту функцию, чтобы она интерпретировала 0x22 и 0x21. Вот вы, теперь вы знаете в своем приложении, открыт ли порт или нет.

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

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

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

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

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

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

Я узнал, не является ли dev_state USBD_STATE_CONFIGUD, USB -заглушка отключена. Если заглушка подключена, но не открыт порт VCP через Putty или Termite, вторая проверка не выполняется.

Эта реализация отлично подходит для меня для приложения 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;
    }
}

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

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

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

  • DTR утверждал
  • CDC Bulk Transfer

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

  • DTR Deasserted
  • USB "Unplugged", Sleep и т. Д.

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

Отказ от ответственности: я использую код, сгенерированный 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;
}

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

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

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

...

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top