我无法在基于自定义 USB CDC 类的 STM32 设备上接收超过 64 个字节

Posted

技术标签:

【中文标题】我无法在基于自定义 USB CDC 类的 STM32 设备上接收超过 64 个字节【英文标题】:I can't receive more than 64 bytes on custom USB CDC class based STM32 device 【发布时间】:2021-03-29 11:37:04 【问题描述】:

目前我尝试将 720 字节从 Windows 应用程序发送到自定义 STM32 设备(现在出于测试目的,我使用 Blue Pill - STM32F103xxx)。啊,我忘了指出我完全是编程新手:)。所以在设备端,我有 1000 字节的缓冲区用于接收和发送(感谢 STMCube)。带有终端程序(数据包

    计算 SINE weave (360) 样本 - 16 字节大小 将它们作为 720 字节发送到 USB 设备(COM 端口的字节大小协议) 我的问题是进入设备的字节数不超过 64 个。 在某处我读到这个原因可能是内置的 Rx,Tx Windows 缓冲区(64 字节长,在互联网上某处提到),为此我在下面的代码中插入: SetupComm(hCom,1000,1000) 希望这能解决我的麻烦,但不是。下面是“我的”代码,有什么办法可以解决这个问题吗?
    #include <windows.h>
    #include <tchar.h>
    #include <stdio.h>
    #include <math.h>  
    
    #define PI 3.14159265
  
    void PrintCommState(DCB dcb)
    
        //  Print some of the DCB structure values
        _tprintf(TEXT("\nBaudRate = %d, ByteSize = %d, Parity = %d, StopBits = %d\n"),
            dcb.BaudRate,
            dcb.ByteSize,
            dcb.Parity,
            dcb.StopBits);
    
    
    
    int _tmain(int argc, TCHAR* argv[])
    
        DCB dcb;
        HANDLE hCom;
        BOOL fSuccess;
        const TCHAR* pcCommPort = TEXT("COM3"); //  Most systems have a COM1 port
        unsigned __int8 aOutputBuffer[720];// Data that will sent to device
        unsigned __int16 aCalculatedWave[360];// Data that will sent to device
        int iCnt; // temp counter to use everywhere 
    
        for (iCnt = 0; iCnt < 360; iCnt = iCnt + 1)
        
            aCalculatedWave[iCnt] = (unsigned short)(0xFFFF * sin(iCnt * PI / 180));
            if (iCnt > 180) aCalculatedWave[iCnt] = 0 - aCalculatedWave[iCnt];
        
    
        // 16 bit aCalculatedWaveto to 8 bit aOutputBuffer
        for (int i = 0, j = 0; i < 720; i += 2, ++j)
        
            aOutputBuffer[i] = aCalculatedWave[j] >> 8; // Hi byte
            aOutputBuffer[i + 1] = aCalculatedWave[j] & 0xFF; // Lo byte
        
    
        //  Open a handle to the specified com port.
        hCom = CreateFile(pcCommPort,
            GENERIC_READ | GENERIC_WRITE,
            0,      //  must be opened with exclusive-access
            NULL,   //  default security attributes
            OPEN_EXISTING, //  must use OPEN_EXISTING
            0,      //  not overlapped I/O
            NULL); //  hTemplate must be NULL for comm devices
    
        if (hCom == INVALID_HANDLE_VALUE)
        
            //  Handle the error.
            printf("CreateFile failed with error %d.\n", GetLastError());
            return (1);
        
        if (SetupComm(hCom,1000,1000) !=0)
            printf("Windows In/Out serial buffers changed to 1000 bytes\n");
        else
            printf("Buffers not changed with error %d.\n", GetLastError());
    
        //  Initialize the DCB structure.
        SecureZeroMemory(&dcb, sizeof(DCB));
        dcb.DCBlength = sizeof(DCB);
    
        //  Build on the current configuration by first retrieving all current
        //  settings.
        fSuccess = GetCommState(hCom, &dcb);
    
        if (!fSuccess)
        
            //  Handle the error.
            printf("GetCommState failed with error %d.\n", GetLastError());
            return (2);
        
    
        PrintCommState(dcb);       //  Output to console
    
        //  Fill in some DCB values and set the com state: 
        //  57,600 bps, 8 data bits, no parity, and 1 stop bit.
        dcb.BaudRate = CBR_9600;     //  baud rate
        dcb.ByteSize = 8;             //  data size, xmit and rcv
        dcb.Parity = NOPARITY;      //  parity bit
        dcb.StopBits = ONESTOPBIT;    //  stop bit
    
        fSuccess = SetCommState(hCom, &dcb);
    
        if (!fSuccess)
        
            //  Handle the error.
            printf("SetCommState failed with error %d.\n", GetLastError());
            return (3);
        
    
        //  Get the comm config again.
        fSuccess = GetCommState(hCom, &dcb);
    
        if (!fSuccess)
        
            //  Handle the error.
            printf("GetCommState failed with error %d.\n", GetLastError());
            return (2);
        
    
        PrintCommState(dcb);       //  Output to console
    
        _tprintf(TEXT("Serial port %s successfully reconfigured.\n"), pcCommPort);
        if (WriteFile(hCom, aOutputBuffer, 720, NULL, 0) != 0)
            _tprintf(TEXT("720 bytes successfully writed to Serial port %s \n"), pcCommPort);
        else
            _tprintf(TEXT("Fail on write 720 bytes to Serial port %s \n"), pcCommPort);
        return (0);
    

【问题讨论】:

Windows 代码看起来不错。最有可能的是,问题出在设备方面。您也可以添加该代码吗? 顺便说一句:数据以 64 字节的数据包传输。这就是 USB 的工作原理。所以不要指望 STM32 端有一个大于 64 字节的数据包的回调。 720字节会自动分成12个包。 你好 Codo,设备代码在这里太大了,但如果你想看这里github.com/stm32dds/Lite。我猜测 usbser.sys 是由 64 字节块发送的,但在我阅读确认的文档中没有找到,所以现在将尝试将此设备更改为 WinUSB.sys 设备,并查看是否有可能更大的数据包。至少如果不可能,将接收这 720 个字节作为块。谢谢! 无法发送大于 64 字节的数据包。这就是 USB 的工作原理。司机不会有任何影响。 【参考方案1】:

USB 批量端点实现基于流的协议,即无穷无尽的字节流。这与基于消息的协议形成对比。因此 USB 批量端点没有消息、消息开始或结束的概念。这也适用于 USB CDC,因为它基于批量端点。

在较低的 USB 级别,字节流被分成最多 64 个字节的数据包。根据 USB 全速标准,数据包不能大于 64 字节。

如果主机发送间隔超过 1 毫秒的小数据块,它们将在单独的数据包中发送和接收,看起来 USB 是基于消息的协议。但是,对于超过 64 字节的块,它们被分成更小的数据包。如果小块的发送间隔小于 1 毫秒,主机会将它们合并成更大的数据包。

您的设计似乎要求对数据进行分组,例如问题中提到的 720 个字节组。如果这是一个要求,则必须实施分组,例如首先发送组的大小,然后发送数据。

由于较大的组被分成 64 字节的块,并且为每个数据包调用接收回调,因此必须加入数据包,直到整个组可用。

还要注意您当前代码中的一些问题(请参阅usbd_cdc_if.c, line 264):

  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  NewDataFromUsb = *Len;

USBD_CDC_SetRxBuffer 为要接收的下一个数据包设置缓冲区。如果您总是使用相同的缓冲区(如本例所示),则不需要。初始设置就足够了。但是,如果当前数据包不包含完整组,则可以使用它来设置新缓冲区。

尽管有它的名字,USBD_CDC_ReceivePacket 不接收数据包。相反,它允许接收下一个包裹。仅当缓冲区中的数据已被处理并且缓冲区已准备好接收下一个数据包时才应调用它。您当前的实现存在缓冲区在处理之前被覆盖的风险,特别是如果您发送一组超过 64 个字节的数据包,这可能会导致快速连续的数据包。

请注意,此处未提及 Windows。 Windows 代码似乎没问题。更改为 Winusb.sys 只会让您的生活更加艰难,但不会让您的数据包大于 64 字节。

【讨论】:

以上是关于我无法在基于自定义 USB CDC 类的 STM32 设备上接收超过 64 个字节的主要内容,如果未能解决你的问题,请参考以下文章

USB Composite 组合设备之耳机+多路CDC

USB Composite 组合设备之耳机+多路CDC

USB Composite 组合设备之耳机+多路CDC

STM32 USB CDC Rx 中断

CH552单片机 USB CDC虚拟串口调试信息输出案例

HAL 库无法与 USB 和 CAN 外围设备一起正常工作