Embedded 从USB虚拟COM端口设备检测打开的PC COM端口

Embedded 从USB虚拟COM端口设备检测打开的PC COM端口,embedded,usb,device,cdc,stm32,Embedded,Usb,Device,Cdc,Stm32,我将STM32F105微控制器与STM32_USB-FS-Device_Lib_V3.2.1 USB库一起使用,并根据我们的目的调整了VCP示例(与RTOS和串行API集成) 问题是,如果连接了USB电缆,但Windows主机上的端口未打开,几分钟后,设备将永久性地重新进入USB ISR,直到端口打开,然后所有设备都开始正常工作 我已经检测了中断处理程序,可以看到当故障发生时,ISR处理程序退出,然后立即重新进入。这是因为退出中断时,OTG_FS_GINTSTS中的IEPINT标志不清楚。此时的

我将STM32F105微控制器与STM32_USB-FS-Device_Lib_V3.2.1 USB库一起使用,并根据我们的目的调整了VCP示例(与RTOS和串行API集成)

问题是,如果连接了USB电缆,但Windows主机上的端口未打开,几分钟后,设备将永久性地重新进入USB ISR,直到端口打开,然后所有设备都开始正常工作

我已经检测了中断处理程序,可以看到当故障发生时,ISR处理程序退出,然后立即重新进入。这是因为退出中断时,OTG_FS_GINTSTS中的IEPINT标志不清楚。此时的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
我们使用配置状态来确定是否将数据放入驱动程序缓冲区以进行发送。但是,已配置状态是在连接电缆时设置的,而不是在主机打开端口并连接应用程序时设置的。我看到当Windows确实打开端口时,会出现突发中断,因此似乎在此事件上发生了一些通信;我想知道是否有可能因此检测到主机的端口是否已打开

我可能需要两件事中的一件:

  • 防止USB代码首先卡在ISR中
  • 以确定主机是否从设备端打开了端口,并且仅在打开时推送用于发送的数据 第(1)部分-防止中断锁定-由ST支持的USB库错误修复提供帮助;它没有正确地清除TxEmpty中断

    经过ST支持部门的一些研究和帮助,我确定了第(2)部分的解决方案—检测主机端口是否打开。传统上,当端口打开时,DTR调制解调器控制线路被断言。该信息被传递到CDC类设备,因此我可以使用它来实现我的目标。应用程序可以更改DTR的行为,但在这种情况下,任何可能连接到此设备的客户端应用程序中都不应发生这种情况。但是,有一个备份计划,如果设置了线路编码(波特率、成帧),则隐式假设端口打开。在这种情况下,没有检测关闭的方法,但至少它不会阻止非常规应用程序使用我的设备,即使它在断开连接时导致它崩溃

    关于ST的VCP示例代码,我对usb_prop.c进行了以下更改:

    1) 增加了以下功能:

    #include <stdbool.h>
    static bool host_port_open = false ;
    bool Virtual_Com_Port_IsHostPortOpen()
    {
        return bDeviceState == CONFIGURED && host_port_open ;
    }
    
    3) 为了允许与不按常规操作DTR的应用程序一起使用,我还修改了SET_LINE_编码的Virtual_Com_Port_Data_Setup()处理:

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

    我对检测PC端口打开/关闭有相同的要求。我看到它的实施情况如下:

    检测到的打开状态为:

    • DTR断言
    • 批量转移
    通过以下方式检测到关闭:

    • DTR解除资产
    • USB“拔下”、睡眠等

    虽然需要更彻底的测试来确认它的工作是否可靠,但它似乎工作得相当好。

    经过如此多的搜索和一种逆向工程,我终于找到了检测开放终端及其终止的方法。我发现在CDC类中有三个数据节点,一个是控制节点,另外两个是数据输入和数据输出节点。现在,当你打开终端时,一个代码被发送到控制节点,当你关闭终端时也是如此。我们所需要做的就是获取这些代码,并通过它们启动和停止我们的数据传输任务。发送的代码分别是0x21和0x22,用于打开和关闭终端。在usb_cdc_if.c中,有一个函数用于接收和解释这些代码(有一个开关盒,变量cmd就是我们正在讨论的代码)。该函数是cdc_Control_FS。现在我们需要做的就是扩展这个函数,以便它解释0x22和0x21。现在,您可以在应用程序中知道端口是否打开

    我可能需要两件事中的一件:

  • 防止USB代码首先卡在ISR中
  • 以确定主机是否从设备端打开了端口,并且仅在打开时推送用于发送的数据
  • 您应该尝试执行选项1而不是选项2。在Windows和Linux上,可以在不设置控制信号的情况下打开并使用COM端口,这意味着没有傻瓜式的跨平台方法来检测COM端口是否打开


    一个编程良好的设备不会因为USB主机停止轮询数据而让自己停止工作;这是一件正常的事情,应该妥善处理。例如,您可以更改代码,以便在端点有可用缓冲区的情况下,只将要发送到USB主机的数据排队。如果没有可用的缓冲区空间,您可能会有一些特殊的错误处理代码。

    我通过采用CDC\u Transmit\u FS找到了另一种解决方案。 现在可以通过覆盖_write函数将其用作printf的输出

    首先它检查连接状态,然后在繁忙的lo中尝试通过USB端口发送
      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;
      }
    
    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;
    }
    
    // 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;
        }
    }
    
    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;
    }
    
            /* USER CODE BEGIN 2 */
                HAL_Delay(500);
                hUsbDeviceFS.ep0_state = 4;
    
    ...