Detecting open PC COM port from USB Virtual Com Port device

12,536

Solution 1

Part (1) - preventing the interrupt lock-up - was facilitated by a USB library bug fix from ST support; it was not correctly clearing the TxEmpty interrupt.

After some research and assistance from ST Support, I have determined a solution to part (2) - detecting whether the host port is open. Conventionally, when a port is opened the DTR modem control line is asserted. This information is passed to a CDC class device, so I can use this to achieve my aim. It is possible for an application to change the behaviour of DTR, but this should not happen in any of the client applications that are likely to connect to this device in this case. However there is a back-up plan that implicitly assumes the port to be open if the line-coding (baud, framing) are set. In this case there is no means of detecting closure but at least it will not prevent an unconventional application from working with my device, even if it then causes it to crash when it disconnects.

Regarding ST's VCP example code specifically I have made the following changes to usb_prop.c:

1) Added the following function:

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

2) Modified Virtual_Com_Port_NoData_Setup() handling of SET_CONTROL_LINE_STATE thus:

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) To allow use with applications that do not operate DTR conventionally I have also modified Virtual_Com_Port_Data_Setup() handling of SET_LINE_CODING thus:

  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;
  }

Solution 2

I found another solution by adopting CDC_Transmit_FS. It can now be used as output for printf by overwriting _write function.

First it checks the connection state, then it tries to send over USB endport in a busy loop, which repeats sending if USB is busy.

I found out if dev_state is not USBD_STATE_CONFIGURED the USB plug is disconnected. If the plug is connected but no VCP port is open via PuTTY or termite, the second check fails.

This implementation works fine for me for RTOS and CubeMX HAL application. The busy loop is not blocking low priority threads anymore.

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 is used by _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;
    }
}

Regards Bernhard

Solution 3

After so much searching and a kind of reverse engineering I finally found the method for detecting the open terminal and also it's termination. I found that in the CDC class there is three Data nodes , one is a control node and the other two are data In and data Out nodes.Now when you open a terminal a code is sent to the control node and also when you close it. all we need to do is to get those codes and by them start and stop our data transmission tasks. the code that is sent is respectively 0x21 and 0x22 for opening and closing the terminal.In the usb_cdc_if.c there is a function that receive and interpret those codes (there is a switch case and the variable cmd is the code we are talking about).that function is CDC_Control_FS . Here we are, Now all we need to do is to expand that function so that it interpret the 0x22 and 0x21 . there you are , now you know in your application whether the port is open or not.

Solution 4

I need one of two things perhaps:

  1. To prevent the USB code from getting stuck in the ISR in the first instance
  2. To determine whether the host has the port open from the device end, and only push data for sending when open.

You should attempt to do option 1 instead of 2. On Windows and Linux, it is possible to open a COM port and use it without setting the control signals, which means there is no fool-proof, cross-platform way to detect that the COM port is open.

A well programmed device will not let itself stop functioning just because the USB host stopped polling for data; this is a normal thing that should be handled properly. For example, you might change your code so that you only queue up data to be sent to the USB host if there is buffer space available for the endpoint. If there is no free buffer space, you might have some special error handling code.

Share:
12,536
Clifford
Author by

Clifford

Software Engineer, embedded systems.

Updated on June 13, 2022

Comments

  • Clifford
    Clifford almost 2 years

    I am using an STM32F105 microcontroller with the STM32_USB-FS-Device_Lib_V3.2.1 USB library and have adapted the VCP example for our purposes (integration with RTOS and serial API).

    The problem is that if the USB cable is attached, but the port is not open on the Windows host, after a few minutes the device ends up permanently re-entering the USB ISR until the port is opened and then it all starts working normally.

    I have instrumented interrupt handler and can see that when the fault occurs, the ISR handler exits and then immediately re-enters. This occurs because on exit from the interrupt the IEPINT flag in OTG_FS_GINTSTS is not clear. The OTG_FS_DAINT at this time contains 0x00000002 (IEPINT1 set), while DIEPINT1 has 0x00000080 (TXFE). The line in OTGD_FS_Handle_InEP_ISR() that clears TXFE is called, but the bit either does not clear or becomes immediately reasserted. When the COM port on the host is reopened, the state of OTG_FS_GINTSTS and OTG_FS_DAINT at the end of the interrupt is always zero, and further interrupts occur at the normal rate. Note that the problem only occurs if data is being output but the host has no port open. If either the port is open or no data is output, the system runs indefinitely. I believe that the more data that is output the sooner the problem occurs, but that is anecdotal at present.

    The VCP code has a state variable that takes the following enumerated values:

      UNCONNECTED,
      ATTACHED,
      POWERED,
      SUSPENDED,
      ADDRESSED,
      CONFIGURED
    

    and we use the CONFIGURED state to determine whether to put data into the driver buffer for sending. However the CONFIGURED state is set when the cable is attached not when the host has the port open and an application connected. I see that when Windows does open the port, there is a burst of interrupts so it seems that some communication occurs on this event; I wonder if it is possible therefore to detect whether the host has the port open,.

    I need one of two things perhaps:

    1. To prevent the USB code from getting stuck in the ISR in the first instance
    2. To determine whether the host has the port open from the device end, and only push data for sending when open.
    • Étienne
      Étienne about 10 years
      Did you mean stm32f105 instead of stm32f015?
    • Clifford
      Clifford about 10 years
      @Étienne : Only three years for that to be spotted! Thanks.
  • Clifford
    Clifford over 8 years
    I posted the solution I used in an answer in March 2011. The interrupt lock-up was a bug in the vendor's code. The driver was already fed from a queue that baulks when full in any case; detecting port open was implemented as described, but is not critical to the application. Since then 50000 units have shipped - so I guess it is successful.
  • Dmitriy Kozlov
    Dmitriy Kozlov over 7 years
    It seems, that you solution deosnt work. The following condition is false even if port is not opened on a host. hUsbDevice_0->ep0_state == USBD_EP0_STATUS_IN
  • Dmitriy Kozlov
    Dmitriy Kozlov over 7 years
    'USBD_EP0_STATUS_OUT' is set if port is opened. 'USBD_EP0_STATUS_IN' is set if port is not opened. Are you sure, that you use '==', but not '!=' ?