5

我正在使用带有 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. 从设备端判断主机是否打开了端口,打开时才推送数据发送。
4

7 回答 7

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;
  }
于 2011-03-25T12:19:13.230 回答
2

我通过采用 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;
}

_write 使用 CDC_Transmit_FS:

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

问候伯恩哈德

于 2016-01-13T12:52:48.450 回答
1

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

于 2015-08-24T21:58:43.497 回答
1

我可能需要两件事之一:

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

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

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

于 2015-08-26T00:14:47.120 回答
0

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

通过以下方式检测到打开:

  • DTR 断言
  • CDC 批量转移

关闭检测到:

  • DTR 无效
  • USB“拔出”,睡眠等

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

于 2011-04-13T00:43:02.823 回答
0

免责声明:我使用 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,现在它可以工作了。

于 2017-01-16T17:41:28.987 回答
0

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

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

...

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

于 2018-01-01T20:10:39.027 回答