【问题标题】:Detecting open PC COM port from USB Virtual Com Port device从 USB Virtual Com Port 设备检测打开的 PC COM 端口
【发布时间】:2011-07-17 08:51:10
【问题描述】:

我正在使用带有 STM32_USB-FS-Device_Lib_V3.2.1 USB 库的 STM32F105 微控制器,并已针对我们的目的调整了 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

我们使用 CONFIGURED 状态来决定是否将数据放入驱动缓冲区进行发送。然而,当连接电缆时设置 CONFIGURED 状态,而不是在主机打开端口并连接应用程序时设置。我看到当 Windows 确实打开端口时,会出现一连串中断,因此似乎在此事件上发生了一些通信;我想知道是否有可能因此检测主机是否打开了端口。

我可能需要以下两件事之一:

  1. 首先防止USB代码卡在ISR中
  2. 判断主机是否从设备端打开端口,打开时只推送数据发送。

【问题讨论】:

  • 你的意思是 stm32f105 而不是 stm32f015?
  • @Étienne :只有三年时间才能被发现!谢谢。

标签: embedded usb device cdc stm32


【解决方案1】:

我通过检查变量 hUsbDeviceFS.ep0_state 修复了它。 如果已连接,则等于 5,如果未连接或已断开连接,则等于 4。 但。 HAL 中存在一些问题。程序启动时等于 5。 后续步骤在程序开始时修复它

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

...

我没有任何学习 HAL 的愿望 - 我希望开发人员能看到这篇文章,他们会修复 HAL。 它帮助我解决了我的问题。

【讨论】:

    【解决方案2】:

    免责声明:我使用 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;
    }
    

    问题是,必须有多大的超时时间,而不是在端口打开时中断传输。我将 BUSY_TIMEOUT 设置为 3000,现在它可以工作了。

    【讨论】:

      【解决方案3】:

      我通过采用 CDC_Transmit_FS 找到了另一个解决方案。 现在可以通过覆盖 _write 函数将其用作 printf 的输出。

      首先它检查连接状态,然后尝试在忙循环中通过 USB 终端端口发送,如果 USB 忙则重复发送。

      我发现如果 dev_state 不是 USBD_STATE_CONFIGURED,则 USB 插头已断开。如果插头已连接,但没有通过 PuTTY 或白蚁打开 VCP 端口,则第二次检查失败。

      这个实现对我来说适用于 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;
          }
      }
      

      问候 伯恩哈德

      【讨论】:

      • 看来,您的解决方案不起作用。即使未在主机上打开端口,以下条件也是错误的。 hUsbDevice_0->ep0_state == USBD_EP0_STATUS_IN
      • 'USBD_EP0_STATUS_OUT' 如果端口打开则设置。如果端口未打开,则设置“USBD_EP0_STATUS_IN”。你确定你使用 '==',而不是 '!=' 吗?
      【解决方案4】:

      我可能需要以下两件事之一:

      1. 首先防止 USB 代码卡在 ISR 中
      2. 判断主机是否从设备端打开端口,打开时才推送数据发送。

      您应该尝试执行选项 1 而不是 2。在 Windows 和 Linux 上,可以打开一个 COM 端口并在不设置控制信号的情况下使用它,这意味着没有万无一失的跨平台方法检测 COM 端口是否打开。

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

      【讨论】:

      • 我发布了我在 2011 年 3 月的答案中使用的解决方案。中断锁定是供应商代码中的一个错误。无论如何,司机已经从一个队列中被喂饱了。检测端口打开已按描述实现,但对应用程序并不重要。从那时起,已经出货了 50000 台 - 所以我猜它是成功的。
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

        打开检测:

        • DTR 断言
        • CDC 批量传输

        关闭检测者:

        • DTR 无效
        • USB“拔出”、休眠等

        这似乎工作得相当好,尽管需要更彻底的测试来确认它工作得很好。

        【讨论】:

          【解决方案7】:

          第 (1) 部分 - 防止中断锁定 - 由 ST 支持的 USB 库错误修复促进;它没有正确清除 TxEmpty 中断。

          经过ST Support的一些研究和帮助,我确定了第(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 ;
          }
          

          2) 修改了对 SET_CONTROL_LINE_STATE 的 Virtual_Com_Port_NoData_Setup() 处理:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-01-30
            • 1970-01-01
            • 1970-01-01
            • 2016-01-05
            • 2016-05-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多