1. 问题提出 STM32开发者在实现CDC类虚拟串口与PC主机通信过程中,有时会遇到点麻烦而不得其解。那就是当主机端单次发送的数据不超过64字节时,接收正常。一旦发送数据量大于64字节时就接收失败,总是出现丢包现象,似乎只能接收64字节以内的数据。网上有人干脆建议主机每次发送不要超过64字节,当然,也有人提及要作分包处理但没具体实现代码可以参考 这个问题在网络上也有些人在试图寻求答案 3 T3 n% Y* P- g( J- q* k 图1 部分社区咨询截图 2. 解决思路及原理 作为CDC类的USB设备,到底能不能正确接收来自主机64字节以上的批量数据呢? 其实是可以的,只是当我们一次传输的数据大于当前端点所支持的最大包长时【这里端点使用BULK传输,最大包长默认设置为64字节】,USB模块会做分包传输,将一批数据传输分成多个处理[或称事务],即多个transaction来完成,每个Transaction里的数据包传输的最大数据量为64字节 / i. |2 s% J9 B, }3 E' m 图2 USB BULK传输流程图 , `3 {+ x$ ]7 }, j9 d 原理性的东西,这里不多罗嗦了,网上有成堆的介绍资料,在STMCU中文网也有很多USB的培训资料,需要的可以前往搜索下载。当我们弄清整个原理后,就可以编写接收处理代码了。下面是验证过程。 1 B$ i! }5 {+ y3. 验证测试$ k% C2 f9 r r' t) V5 t- r. j ] 下面我利用HAL库,基于STM32F429芯片演示实现过程,重点在接收处理代码。我使用STM32F429 Discovery开发板,使用HS USB模块并令其工作在FS MODE,这样我们就可以方便地使用片内USB FSPHY。 我使用STM32CubeMX工具进行配置,生成基于STM32 HAL库的工程。使用ST提供的STM32CubeIDE进行编译调试。有关配置就不截图了。 ) f: o9 j- i+ S+ j% t2 K' z/ o 另外,我还配置了1个按键并开启相应外部中断。每发生按键事件时,F429 USB设备向PC主机发送一段打招呼的字符串,并通过串口助手显示出来。 我在main.c文件里定义了下面几个变量。 图3 变量截图 8 c V& b3 I4 A7 j4 z 其中,Flag_KeyPressed和Flag_DataReceived分别标示按键操作和收到从主机发过来的数据的情况。Rx_buffer【】数组用来存放接收来自主机的数据,我这里的定义长度为512字节【具体使用时按需设置】。下图是Main.c里的主循环代码截图,见图4。 图4 主循环代码截图 # J& R' V7 {! ?; y1 H% [ 主循环里检查按键标志和收到数据的标志,如有按键发生,则向主机发送前面提到的打招呼的字符串;如有收到来自主机的数据,则向主机回送过去。 / \. \& ?; G8 T 今天的重点是讨论USB设备如何从主机接收64字节以上的数据。基于现有HAL库,对于USB设备的接收,我们只需关注一个USB中断接收回调函数,那就是CDC_Receive_HS()函数。该函数在usbd_cdc_if.c文件里。我具体编写的函数代码如下面两幅截图所示。 图5 接收回调函数中相关变量 图6 接收回调函数处理代码截图 代码很简单,我在库代码的基础上增加了橙色方框内的代码。基本功能就是,先读取当前收到的数据长度【SinglePackLength】,分整包和非整包两种情况鉴别后再处理。若是接收的整包数据,继续等待接收下一包;若是非整包,视为此次传输结束,并设置收到标志Flag_DataReceived为非0值,然后在主循环里将收到的数据回送给主机。 & L$ t6 w) q/ _8 o6 u2 m/ h 其中,Max_Pack_Size是当前CDC类BULK传输端点的最长传输包长,这里为64字节。 Num_Rx_Data表示接收到数据个数,Num_Out_Pack表示接收到的数据包个数,Num_Packet跟Num_Out_Pack内容一样,不过,Num_Packet等于0还表示准备开始新一轮传输的接收。这里多定义基于上面的接收处理代码,我们来验证结果: 6 n, |8 O5 N$ M0 _1 Q8 Z 图7 接收5个字符的结果截图 * `$ M4 C* D7 K& P2 E4 L( S 借助PC端的串口助手向STM32F429设备发送了5个字符,我们通过STM32CubeIDE调试环境可以清晰地从上图看到设备收到的数据个数为5,数据包个数为1。显然没问题。那个Rx_buffer数组是我用来存放接收数据的,若在调试窗口打开,数据较多列表显示会很长,这里就没打开了。事实上接收的数据内容也是没问题的。 下图是借助PC端的串口助手向STM32F429 USB设备发送了305个字符的接收情况。 9 k8 a7 q! T* v0 w5 M 图8 接收305个字符的结果截图 显然,对于305个字符,PC主机端要分成5包才能发送完毕,即4整包【每包64字节】再加1个非完整包。所以USB设备接收结果也正好是5包,即上图中Num_Out_pack的数据,接收到的数据量为305,即上图中Num_Rx_Data的数据。同样,结果OK。 下图是PC端刚好发送一个完整包64字节数据的USB设备接收情况,也一切正常。 图9 接收64个字符的结果截图 6 b h! N4 v$ e& e- O 也就是说,使用我上面编写的接收处理代码,对主机发送的数据的个数不再局限于64字节以内了。当然,具体应用时我们还可以根据主机端单次传输数据的大小情况及提取数据的方式适当调整这里的Rx_buffer[]数组大小。【注:上面测试时我临时关闭了设备端的数据回送功能,是为了避免截图后数据混乱,让人分不清原始数据和回显数据】 有人可能在上面的接收代码里看到了一个变量Wait_Rx_Dly。刚开始,代码里没有这个变量。后来我在测试主机发送64字节整包数据时,发现了一个小问题【最终到底算不算问题,或许要视具体应用场景而定,我这里稍作了点处理】。 问题是这样的: 主机每发送1包64字节数据时,设备端接收没有问题,但只要主机端每次发送64字节完整数据包过来,不论相隔时间多久,设备端依然接收,且总在前次结果上累加,除非主机端发一个非64字节数据包过来,设备接收就总是视为同一次传输。比方像下面截图所示: $ y5 ~- A* T5 S( U1 R. I* x2 X 图10 接收64整数倍个字符的结果截图 上图就是PC端主机借助串口助手分四次发送4包64字节数据时USB设备的接收情况。我是希望一次传输做一次处理,于是我让设备每收到一个完整数据包,就重新给定一个延时,比方3~5ms,用变量Wait_Rx_Dly来记录延时值,在某毫秒定时中断里对该变量做减1操作。若延时到了还没有收到数据或延时过程中收到非完整包则视为本次传输结束。 9 |# m e% H( S: T 我顺便使用了HAL库里自带的systick中断函数来做这个延时管理。当接收处理代码加上这个延时管理后,就不会出现不同传输批次的数据挤在一块了。 - Z) }8 O6 ^2 B; u& Z4.小结 其实,我上面分享的接收处理代码也有很好的通用性,并不局限于Bulk传输端点。我们知道,不同传输类型的端点的最大包长往往并不一样,如果使用上面的参考代码,我们只需调整那个最大包长参数【Max_Pack_Size】,并根据应用适当调整Rx_buffer[]数组的大小就可以使用了。数据个数、传输包个数这些变量定义及使用都可以保留参考。 8 o0 \6 R+ [, ~+ e5 y. m1 z* Q( C. s) G ?$ u: L/ l8 n 转载自:STM32单片机- K) P; r- @4 R$ O* h- W/ P* W 如有侵权请联系删除) Z% N; v+ v. C1 i . S7 B" o2 j. S5 ]4 t3 | |
【经验分享】在进行 USB CDC 类开发时,无法发送 64整数倍的数据
【源码】STLINK-V3MINI 高速USB仿真器,成功改刷【高速CMSIS-DAP】
在线直播|无需编写任何代码即可在STM32上实现USB-C Power Delivery
STM32 USB CDC 虚拟多串口
最全USB HID开发资料,悉心整理一个月,亲自测试
圈圈发布USB图书第二版有感,以及分享一些我学习USB过程...
USB Audio设计与实现
【MCU实战经验】+STM32F107的USB使用
STM32F4-DISC 实现USB主机(U盘)和USB设备(虚拟串口)自动切换
STM32 USB-HID通信移植步骤STM32 USB HID键盘例程