cancel
Showing results for 
Search instead for 
Did you mean: 

How to tell if a terminal program is listening to a CDC device?

EBerl.1
Associate II

I am using STM32Cube_FW_F7_V1.15.0 with a STM32F723IET6 implementing the internal HS USB as device. I have implemented the CDC VCP device. I plug the USB cable into a host computer, running either Windows or linux. If I am running a terminal program on the host computer, I am able to send characters to the host. If I am not running any receiver on the host (but still plugged in), if I try to send characters to the host, CDC_Transmit_HS() returns USBD_BUSY. Since I need to check for this case when the connection is up and I am sending characters normally, my program hangs on this condition if there is no listener running on the host.

What I really need is to detect the condition of whether the host has opened the device, so I can buffer my data until the device is opened on the host. In testing with a Windows 10 host, I found that hUsbDeviceHS.dev_state == USBD_STATE_CONFIGURED is true after the device is configured, whether or not the connection is there. I also found that hUsbDeviceHS.ep0_state == USBD_EP0_IDLE on startup even if the connection is not there, and it does not change if the connection is established, but if the connection is then stopped, ep0_state changes to USBD_EP0_STATUS_IN. It goes back to USBD_EP0_IDLE when a new connection is opened. So after the first connection, I can use ep0_state to test if the connection is opened. This is nearly what I want, but the behavior is different when the host is linux. In that case, I cannot use ep0_state to determine if the connection is open.

I also looked at hUsbDeviceHS.dev_connection_status, but this is always 0. There doesn't seem to be any code to set it to another value. It would be great if I can add a bit of code to make this report the connection status.

I need a reliable way to determine this condition. I could not find any documentation indicating how to do this. Any help would be greatly appreciated!

9 REPLIES 9
Pavel A.
Evangelist III

A program is listening when host reads from the CDC data endpoint.

Your CDC device should have a data endpoint (not EP0) , and IN request on it means read.

-- pa

EBerl.1
Associate II

Sorry, I do not understand what code changes you are suggesting. Also, I need to detect the case where the host has opened the device, even if there is no data being transferred. When I open a terminal program on the host I am able to send and receive data. How can I determine that if I were to send data, the host would not receive it, so I can avoid sending it?

There's no good way to detect what you want. In theory, DTR is the signal intended to indicate this, but there's no single interpretation of this in implementations.

What Pavel said means, that is that if the host does not pick the data, there is no listening application running. I don't think this is necessarily so (the OS/class driver may IMO quite well pick data and throw them to /dev/nul), but maybe in practice this is an usable indicator. In other words, if you try to transmit data but the respective endpoint won't indicate them been picked up within some reasonable time (this is bulk so there is no requirement on host to handle it in any timeframe), say 5-10 SOFs, then there's probably nothing on the other end to pick them up.

I don't Cube so won't tell you what to do, you need to understand how the USB stack works (yes, it is hard).

JW

EBerl.1
Associate II

Thank you for your answer. I will try testing DTR. Also it sounds like you suggest using a timeout when sending data.

I send data this way:

            while(CDC_Transmit_HS(array, size) == USBD_BUSY) ;

but I only want to do this if there is a connection. I can add code to break out of the loop if it times out,

but will I know that no bytes have been transferred in that case? It is important for my application not

to duplicate data.

I have found something that nearly works. I modified USBD_LL_DataInStage() to set a flag that tells me

if the remote host is connected, but it only sets the flag after I try to send. Here is the code, with my additions

marked with

//!!!

and placed at the left margin so it is clear what I added:

//!!!

uint8_t Connected = 0;

/**

* @brief USBD_DataInStage

*        Handle data in stage

* @param pdev: device instance

* @param epnum: endpoint index

* @retval status

*/

USBD_StatusTypeDef USBD_LL_DataInStage(USBD_HandleTypeDef *pdev, uint8_t epnum,

                                      uint8_t *pdata)

{

 USBD_EndpointTypeDef *pep;

 if(epnum == 0U)

 {

   pep = &pdev->ep_in[0];

   if ( pdev->ep0_state == USBD_EP0_DATA_IN)

   {

     if(pep->rem_length > pep->maxpacket)

     {

       pep->rem_length -= pep->maxpacket;

       USBD_CtlContinueSendData (pdev, pdata, (uint16_t)pep->rem_length);

       /* Prepare endpoint for premature end of transfer */

       USBD_LL_PrepareReceive (pdev, 0U, NULL, 0U);

     }

     else

     { /* last packet is MPS multiple, so send ZLP packet */

       if((pep->total_length % pep->maxpacket == 0U) &&

          (pep->total_length >= pep->maxpacket) &&

          (pep->total_length < pdev->ep0_data_len))

       {

         USBD_CtlContinueSendData(pdev, NULL, 0U);

         pdev->ep0_data_len = 0U;

         /* Prepare endpoint for premature end of transfer */

         USBD_LL_PrepareReceive (pdev, 0U, NULL, 0U);

       }

       else

       {

         if((pdev->pClass->EP0_TxSent != NULL)&&

            (pdev->dev_state == USBD_STATE_CONFIGURED))

         {

           pdev->pClass->EP0_TxSent(pdev);

         }

         USBD_LL_StallEP(pdev, 0x80U);

         USBD_CtlReceiveStatus(pdev);

       }

     }

   }

   else

   {

     if ((pdev->ep0_state == USBD_EP0_STATUS_IN) ||

         (pdev->ep0_state == USBD_EP0_IDLE))

     {

       USBD_LL_StallEP(pdev, 0x80U);

//!!!

Connected = 0;

     }

   }

   if (pdev->dev_test_mode == 1U)

   {

     USBD_RunTestMode(pdev);

     pdev->dev_test_mode = 0U;

   }

 }

 else if((pdev->pClass->DataIn != NULL) &&

         (pdev->dev_state == USBD_STATE_CONFIGURED))

 {

   pdev->pClass->DataIn(pdev, epnum);

//!!!

Connected = 1;

 }

 else

 {

   /* should never be in this condition */

   return USBD_FAIL;

 }

 return USBD_OK;

}

I feel like this is close to what I need. Any suggestions on how to make the code set or clear a flag

on read instead of write? Or, can I write a null packet? Is it OK to do something like this:

CDC_Transmit_HS(NULL, 0);

Endpoint 0 in CDC is not used for data transfer (which you want to detect). Look in usbd_cdc.h, there the data EP is defined as 0x81.

Then, look in usbd_cdc.c, function USBD_CDC_DataIn. This is low level handler of read request from host.

As can be seen from there, it calls TransmitCplt callback in the "class" interface struct.

This can be used as indication that host tries to read data.

Jan is right, unfortunately USB is hard. Low cost of microcontrollers attracts people, but then they are hit by cost of development :(

-- pa

EBerl.1
Associate II

I found this link:

https://community.st.com/s/question/0D50X00009XkfHnSAJ/usb-vcp-how-to-know-if-host-com-port-is-open

which uses CDC_SET_CONTROL_LINE_STATE to set and clear a flag. It reads data in pbuf even though length is 0.

Is this a bug in the code somewhere that the length is 0?

I had to do something tricky in my Windows code to get this to work with windows, but it worked right away with linux

as the host. I think this will solve my problem.

Where the length is 0? CDC_SET_CONTROL_LINE_STATE is a request packet passed over EP0. It has a well defined structure and should not be 0 length,

Glad to know your problem is solved though.

-- pa

EBerl.1
Associate II

In CDC_Control_HS(uint8_t cmd, uint8_t* pbuf, uint16_t length), when cmd is CDC_SET_CONTROL_LINE_STATE, length is 0. It seems like it should be sizeof(USBD_SetupReqTypedef) = 8.

Hmm, strange. Maybe you've found a bug.