Skip to main content
Graduate
May 27, 2022
Question

How do I determine when Virtual Com Port is closed/opened in my STM32L476 firmware?

  • May 27, 2022
  • 7 replies
  • 5266 views

I'm working with the Nucleo-64 development board with an STM32L476 microcontroller and I'm using the USB source code that STM32CubeMX has generated for me. I want to be able to find out when the Virtual Com Port is closed/opened on the PC that the Nucleo-64 board is connected to. I've googled quite a bit, some suggests the condition is (Bool_t)(hUsbDeviceFS.ep0_state == USBD_EP0_STATUS_OUT), others suggest I should evaluate req->wValue inside the CDC_SET_CONTROL_LINE_STATE case statement in CDC_Control_FS. However, none of these methods work reliably for me. Can anybody please help me?

    This topic has been closed for replies.

    7 replies

    Graduate II
    May 27, 2022

    >>However, none of these methods work reliably for me.

    Applications PC side really don't signal you, they can come and go, read and write data as it suits them. This is not a strongly architected communication channel. I presume you a) don't control the app code, and b) aren't planning on writing any drivers to facilitate the functionality.

    I have observed that Arduino boards typically do a reasonable job sensing a connection, so perhaps look at those, and perhaps how the nuance end-point transactions to see that a terminal opens. I'd perhaps expect some back-n-forth querying. When it stop/disconnects might be harder.

    Super User
    May 27, 2022

    You'll spare much headache if you work simply with the fact that there's no way to detect that generically.

    The long story is that RS232 has a dedicated signal - DTR - to indicate from "computer" to "modem" that computer is generally ready to accept data. Fast forward through several decades of ignoring and violating standards, and it's what it is. Applications still can set or clear DTR even through virtual serial port, and the "free" ("class", again a heavily abused term) M$ driver forwards this to the device indeed, but Windows won't change it when the application aborts, making it totally useless. Other/minority hosts/OS have then their own quirks and idiosyncracies in this regard (as well as in all other USB-related issues).

    The original intention of USB was not that it creates universally interworking hosts and devices, but that it provides a rough framework within which individual device vendors provide both ends (i.e. the device itself and drivers into the host(s)). That there exist some half-working class drivers providing rudimentary functionality, is mostly an unwanted side effect.

    JW

    Graduate
    May 30, 2022

    If you use standard terminal program on PC side, then normally such a program changes DTE_PRESENT flag in ControlLine State to active when starting the session and to inactive when closing it.

    You should handle the SetControlLineState request and monitor the change of DTE_PRESENT flag from 0 to 1. Then wait at least 20 ms before sending any data to the host via VCOM. That's my experience - using this approach I am able to display the greeting message whenever the terminal opens the VCOM port.

    arnold_wAuthor
    Graduate
    May 30, 2022

    When I search for "DTE_PRESENT", "ControlLine" and "SetControlLineState" in my entire STM32CubeIDE project, I find nothing. Perhaps are you working with a different microcontroller family?

    I communicate with my own C# application and I'm calling System.IO.Ports.SerialPort.Open and System.IO.Ports.SerialPort.Close. If I add the following to usbd_cdc_if.c:

    static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length) {
     /* USER CODE BEGIN 5 */
     switch(cmd) {
     .
     .
     .
     case CDC_SET_CONTROL_LINE_STATE: {
     USBD_SetupReqTypedef * req = (USBD_SetupReqTypedef *)pbuf;
     LOG_STRING("\r\nreq->wValue: 0x");
     LOG_HEX((uint16_t)req->wValue, 4);
     break;
     }
     .
     .
     .
     return (USBD_OK);
     /* USER CODE END 5 */
    }

    then I can see the following printout every time I open/close the connection:

    req->wValue: 0x0000  

    If I instead make the following changes in usbd_ioreq.c:

    Bool_t hostComPortOpen = FALSE;
    USBD_StatusTypeDef USBD_CtlSendStatus(USBD_HandleTypeDef *pdev) {
     /* Set EP0 State */
     pdev->ep0_state = USBD_EP0_STATUS_IN;
     
     if (hostComPortOpen) {
     LOG_STRING("\r\nThe COM-port was closed");
     }
     hostComPortOpen = FALSE;
     
     /* Start the transfer */
     (void)USBD_LL_Transmit(pdev, 0x00U, NULL, 0U);
      return USBD_OK;
    }
     
    USBD_StatusTypeDef USBD_CtlReceiveStatus(USBD_HandleTypeDef *pdev) {
     /* Set EP0 State */
     pdev->ep0_state = USBD_EP0_STATUS_OUT;
     
     if ((!hostComPortOpen) && (pdev->dev_state == USBD_STATE_CONFIGURED)) {
     LOG_STRING("\r\nThe COM-port was opened");
     hostComPortOpen = TRUE;
     }
     
     /* Start the transfer */
     (void)USBD_LL_PrepareReceive(pdev, 0U, NULL, 0U);
      return USBD_OK;
    }

    then I get "The COM-port was closed" (once) when I close the connection and I get the following when I open the connection:

    The COM-port was opened

    The COM-port was closed

    The COM-port was opened

    The COM-port was closed

    The COM-port was opened

    The COM-port was closed

    The COM-port was opened

    The COM-port was closed

    The COM-port was opened

    The COM-port was closed

    The COM-port was opened

    The COM-port was closed

    The COM-port was opened

    The COM-port was closed

    The COM-port was opened

    This looks promising (after all, it end up with the correct state), but the problem is that after power-up it thinks the connection is opened, even though I haven't opened it yet.

    Graduate
    May 30, 2022

    SetLineState method works for me in many devices based on many different STM32 microcontrollers (F0, F1, L4 series) and it does not depend on the microcontroller type - it's all in the CDC class requests, so I think there is some problem with your implementation or the version of ST USB device software you use (there were some problems with them; I use custom, heavily modified ST USB stack with my own composite device support).

    Connection detection based on device connection status does not generally work - you can't see when the device was disconnected.

    DTE_PRESENT is bit 0 of wValue of SET_CONTROL_LINE_STATE request. Make sure you use the working terminal emulator, like TeraTerm. Otherwise check if DTR and/or RTS/CTS handshake is turned on in the terminal emulator.

    Added:

    I found the problem in your code. You are not accessing the request packet. This is my working version:

     case CDC_SET_CONTROL_LINE_STATE:
    		if (hUsbDeviceFS.request.wValue != cdc_data[0].controllinestate)
    		{
    			cdc_data[0].controllinestate = hUsbDeviceFS.request.wValue;
    			cdc_data[0].ctrllinestate_changed = 1;
    		}
     break;

    arnold_wAuthor
    Graduate
    May 31, 2022

    I get the following compile error when I try that code:

    'cdc_data' undeclared (first use in this function) usbd_cdc_if.c

    Graduate
    May 31, 2022

    I am not surprised, as cdc_data is my own variable and this is just a small excerpt from my program. The code above illustrates how you could handle SetControlLineState request to get connection status from wValue and what to do with it.

    arnold_wAuthor
    Graduate
    June 2, 2022

    There seems to be a difference if I use Tera Term instead of my own C# application.

     case CDC_SET_CONTROL_LINE_STATE:
     LOG_STRING("\r\nhUsbDeviceFS.request.wValue: 0x");
     LOG_HEX((uint16_t)hUsbDeviceFS.request.wValue, 4);
     break;
     break;

    When I connect in Tera Term, then I get this:

    hUsbDeviceFS.request.wValue: 0x0003

    When I disconnect in Tera Term, then I get this:

    hUsbDeviceFS.request.wValue: 0x0002

    However, I'm sure the customers will be disappointed if I ask them to upgrade firmware using Tera Term, where they would have to type in all the firmware bytes manually on the keyboard (or I guess they could speed it up a little by using copy-and-paste, but it's still a bit cumbersome).

    Super User
    June 2, 2022

    > When I connect in Tera Term, then I get this:

    > hUsbDeviceFS.request.wValue: 0x0003

    > When I disconnect in Tera Term, then I get this:

    > hUsbDeviceFS.request.wValue: 0x0002

    That's DTR.

    How Tera Term handles DTR may depend on its handhake settings. Try to play with handshake in the Serial setup.

    You program should be able to change DTR, too.

    > ask them to upgrade firmware using Tera Term, where they would have to type in all the firmware bytes manually

    A relatively usual way to handle firmware update is using X/Y/ZMODEM, Tera Term speaks them all.

    Also, Tera Term is scriptable, read its manual(s).

    JW

    Graduate II
    May 31, 2022

    I normally do (from the pc side) a ping every second, this way if the ping is not received i know something is wrong or the com port is closed

    arnold_wAuthor
    Graduate
    June 2, 2022

    How exactly do you send a ping (what functions do you call)? What do you do in case the ping fails to cleanup (do you just call USB_FlushTxFifo(USB_OTG_FS, 15) or do you do anything else)?

    Graduate
    June 2, 2022

    Then correct your application to handle DTE present correctly - it should set it after connecting/opening serial port and reset before disconnecting/closing serial port.