首页|资源下载
登录|注册

您现在的位置是:首页 > 技术阅读 >  【图解USB】USB 之CDC 程序结构(完结篇)

【图解USB】USB 之CDC 程序结构(完结篇)

时间:2022-09-29

来源:公众号【鱼鹰谈单片机】

作者:鱼鹰Osprey

ID   :emOsprey


本篇介绍整个例程的结构和程序流程。
Github 里面有一个仓库CMSIS-DAP(https://github.com/x893/CMSIS-DAP ,该工程可以导入到gitee中,加快下载速度,底部点击阅读原文即可进入该链接),x893大神移植的,网上大部分 CMSIS-DAP 的程序应该都是基于这个工程,鱼鹰提供的工程也是基于该工程,只不过为了简化工程,降低学习难度,鱼鹰把CDC 之外的移除了。
但是使用该工程有一个麻烦就是不能用KEIL直接编译通过,必须安装兼容包,这个鱼鹰在之前的笔记介绍过。
只有把兼容包安装好了才能正确编译,才能继续研究。
鱼鹰闲这一步太麻烦了,所以重新整理了一个新工程,把兼容包里面的东西放到新工程里面,这样一来,即使没有安装过兼容包,也是能正确编译并进行后续测试的。所以大家获取到该工程后直接编译即可。
(鱼鹰之前提供的工程因为已经安装了兼容包,所以编译没有发现什么问题,后来重新安装了KEIL发现,这个工程还是编译不了,需要在 CMSIS 工程下添加这两个文件,这两个文件可以在 KEIL 安装目录下找到,把它拷贝到这里就能正常编译了)
USB 所有的事件响应(发送完成、接收完成、复位等)都是通过中断来处理的,所以主要介绍一下这些中断,着重介绍正确传输中断CTR。
复位中断
Main 函数的初始化主要是针对STM32的 USB 外设来主动进行初始化的,但除了该初始化外,在USB 设备插入主机后,主机都会发送复位信号来复位 USB 外设,而我们的应用程序也可以通过开启该中断来来复位软件上的数据信息,比如指针复位,缓存清除、寄存器初始化等操作。这样可以在每次插入 USB 后自动完成必要的初始化工作。
主机一般会发送两次复位信号,所以这个中断会进入两次,不过对于复位信号而言,多执行几次其实没有问题,所以即使这段代码执行了两次,也没有问题。

挂起中断
一般是设备支持挂起才会产生这个中断,比如鼠标(用于省电),本例程设置的描述符并没有支持挂起,所以,该中断不会产生。
唤醒中断
这个中断产生于复位之前,当然,挂起后,主机也可以唤醒设备,也会触发该中断。
帧首中断
主机每隔 1 毫秒会发送一帧数据包,里面包含了帧号,通过这个中断,当设备有数据需要上传时,可以在这个中断中把数据放到对应端点中,等待主机获取数据。
而每次传输完成后,会产生正确传输中断,如果还有数据需要传输,我们可以继续在正确传输中断中继续放入数据。
简单来说,这个中断在 CDC 设备中就是用于启动第一次传输的(一批数据传输的开始),之后的传输工作由正确传输中断接管。
当然了,因为它的中断时间总是 1 ms,所以我们也可以用它来计时。
错误中断
这两个错误中断,一般来说只要清除它即可,不需要做处理,因为 USB 外设会自动帮我们处理这些错误,也会自动从这些错误中恢复,不需要我们操心。
当然我们可以通过这些错误来确定通信质量。
正确传输中断
这个中断是所有端点正确传输完成后产生的,包括端点0。
正确传输,意味着什么?
如果是从机接收数据,意味着从机发送了握手包给主机后才产生:
而如果是从机发送数据,那就是在从机收到主机的握手包才产生的:
也就是说,只要进入了这个中断,就意味着一次传输事务的完成,并且可以保证数据包传输的正确性(CRC校验通过,握手包完成)。

正因为如此,我们不必考虑这个数据是否需要校验,是否存在错误的可能,只要拿来用即可。
而枚举过程,所有的数据都是通过端点 0 来交互的,虽说是端点 0,实际上却包含了两个端点, IN 和 OUT,这里的方向是针对主机而言的,所以当主机发送数据过来时,存放在 OUT 端点,此时我们需要从 OUT 端点获取我们所需要的标准请求数据:
而如果主机接收数据,那么从机就事先把数据存放在IN端点,当主机发送 IN 令牌包时,USB外设就会按照USB传输方式将数据自动发送给主机。
关于STM32如何发送或接收USB数据,可以看这两个函数:
上面的 printf 函数是鱼鹰自己加的,用于捕获端点传输的数据,鱼鹰分享的资料中有一个txt文件就是通过该方式来分析数据传输过程的。
当然,因为使用的是事件记录器这种打印方式,速率比较低,所以会影响通信,并且可能在打印过程中丢失打印数据。
为了计算 CDC 设备的发送速率,鱼鹰写了一个简单的测试程序(放在发送函数中):
U32 USBD_WriteEP (U32 EPNum, U8 *pData, U32 cnt){  /* Double Buffering is not yet supported                  */  U32 num, *pv, n;  U16 statusEP;        static uint32_t time,send_cnt;    static uint32_t  max,min = (uint32_t)-1;    static uint32_t curr_send_cnt;        curr_send_cnt = cnt;    if(EPNum == 0x82)    {        if(time == 0)        {            time = DWT->CYCCNT;            if(time==0)time=1;        }                    send_cnt += cnt;        if(time != 0)        {            if((DWT->CYCCNT - time)/72 > 1000000)            {                time = DWT->CYCCNT;                                if(send_cnt > max)                {                    max = send_cnt;                 }                if(send_cnt < min)                {                    min = send_cnt;                 }                send_cnt = 0;            }        }    }      num = EPNum & 0x0F;
pv = (U32 *)(USB_PMA_ADDR + 2 * ((pBUF_DSCR + num)->ADDR_TX)); printf("\nw%04x\n", (EPNum << 8) |cnt); for (n = 0; n < (cnt + 1) / 2; n++) { *pv++ = *((__packed U16 *)pData);// if(n < 8) { printf("%x ", (uint32_t)*(U8 *)pData); printf("%x ", (uint32_t)*((U8 *)pData+1)); } pData += 2; } printf("\n");// printf("--\n"); (pBUF_DSCR + num)->COUNT_TX = cnt; statusEP = EPxREG(num); if ((statusEP & EP_STAT_TX) != EP_TX_STALL) { /* do not make EP valid if stalled */ EP_Status(EPNum, EP_TX_VALID); }
return (cnt);}
通过对端点 2 每隔 1 秒统计一次传输数据,可以大概计算USB批量传输的发送速率。

最后,鱼鹰再简单介绍一下枚举过程时的控制传输流程:
这个图是鱼鹰分析代码后画出来的(如果本图看不清,可以从鱼鹰分享的资料中找到,所有鱼鹰原创资料都在 Osprey文件夹中,请自行查看)。
可以看到,枚举过程的标准请求虽然只有 8 字真言,但里面涉及到分支却很多。
第一次进入正确传输完成中断时,首先判断断点号后进入 端点 0 的处理函数,而端点 0 一定是控制传输,所以这次传输事务一定属于控制传输的建立阶段。
而建立阶段一定是由主机发送的请求,所以我们需要通过读取 USB 外设的数据来确定本次请求的内容(即上图的拷贝数据)。
根据请求类型,可分为标准请求类请求,其他(产商)请求默认不进行处理。
标准请求又分为多种请求,比如设置地址、获取设备描述符、设置配置等各种请求,根据这些请求,又可以细分接收者,细分具体的描述符。
如果你想修改枚举过程,那么上面这张流程图一定要仔细看一遍,这样就可以很快的根据需要修改出自己想要的USB设备了。

因为上述内容太多,不再一一说明,大家对照鱼鹰给的例子和分享的资料自行学习即可,相信有了这个流程图,修改出属于自己的 USB 设备根本不是难事。
总之,师傅领进门,修行靠个人。关于USB你能领悟多少,就看自己能花多少精力在这里了。
鱼鹰这个系列的文章仅仅供各位道友入门使用,帮助大家快速理解USB 大概的内容,很多内容并没有详细介绍,所以鱼鹰从网上搜集了很多学习资料供各位参考,方便大家更系统的学习 USB 知识。
如果发现资料或本系列笔记有误,请以官方资料为准。


推荐阅读:
嵌入式系统优先级详解
KEIL 调试经验总结
线程CPU使用率到底该如何计算?
许久以后,你会感谢自己写的异常处理代码
终极串口接收方式,极致效率
延时功能进化论(合集)
如何写一个健壮且高效的串口接收程序?
打了多年的单片机调试断点到底应该怎么设置?| 颠覆认知

-THE END-



如果对你有帮助,记得转发分享哦


微信公众号「鱼鹰谈单片机

每周一更单片机知识

长按后前往图中包含的公众号关注


鱼鹰,一个被嵌入式耽误的畅销书作家

个人微信「EmbeddedOsprey

长按后打开对方的名片关注