[MCU] 【RISC-V MCU CH32V103测评】 ---前进的维子---U盘枚举代码简析

wintonson   2021-1-30 18:36 楼主

RISC-V MCU CH32V103测评】 ---前进的维子---U盘枚举代码简析 

前进的维子

2021129

前言:

目的:走读代码,具象化枚举过程。

在上一篇文章里面,“维子”用自己的语言,讲述了USB设备的枚举过程。限于篇幅限制,上一篇没有将枚举的代码实例相结合的说一说。官方给出了一个例子EXAM13。它的功能是:在文件根目录里添加一个长文件名的文件。我们尝试分析这个例程代码的部分片段,将上一篇文章中的概念具体化。——观察ch32v103是怎么实现对u盘进行枚举的。

代码比较多,本文力求简洁。想要深入学习,还是要读者自己分析源码。

1: 通读代码,了解整体功能。

“维子”把折叠好的代码截图放到下面。

图片1.png

先浏览一下,观察大体程序结构:

过程很简单:系统初始化,内部外设初始化(串口初始化,USBHD初始化,然后是USB主机初始化)文件库初始化,在主循环内,通过“轮询”状态位(图中折叠的第一部分),来实现功能的切入——当检查出“FoundNewDev  或者 ERROR_USB_CONNECT”的时候做进一步的写入文件处理。(第二个折叠代码的地方)上面的过程,是地步浏览。

 

2:带着什么样的问题来读?

我就是要明白这个芯片做主,怎么就把U盘枚举出来了。

带着要搞懂“枚举”的目标去看代码。(小伙伴还记得枚举的四个阶段吗?)我们就是要看着四个阶段怎么代码里体现的。如果记不得可以看我的上一篇帖子。(简单说,第一步,复位外设,和设备号0的端点0通信获得设备描述符。第二步,配置设备设备地址。第三步和这个新设备地址通信再次获取设备描述符。第四步,获取配置描述符集。)回顾一下就是下面表格:

过程

内容

1

首次复位,和0号设备的0端点通信,获取设备描述符长度

2

二次复位,和0号设备的0端点通信,设置设备地址

3

和新地址设备的0号端点通信,再次获取设备描述符

4

获取配置描述符

3USB主初始化部分

图片2.png

“维子”已经在代码里把注释写的很清楚了。不过还是简单描述一下吧:这是USB主机的初始化函数;包括下面几步:设置USB为主机设备,和端点0 设备号0的地址通信,设置主机的发送和接收端点使能,配置接收和发送缓存。清中断标志,使能传输中断和连接中断。

也就是初始化,和 允许传输和连接中断

 

 

经过初始化后,在初始化一下“文件系统”就进入下循环了。

4:判断是接入还是拔出

大循环里,以100m 的间隔,对是不是接入了数据进行判断

——if ( R8_USB_INT_FG & RB_UIF_DETECT ) 然后调用 AnalyzeRootHub 函数来判断是进来还是断开来了。AnalyzeRootHub 函数看起来不太整齐,我们整理一下,是下面的结果。具体看“维子”在截图里做的注释。不难理解。

图片3.png

这就是判断,当前是接入还是拔出。

 

这里我们思考一下,假设检测出来的是设备接入 而且 后面的工作我们自己来做的话,我们该干吗? 要回想枚举的四个阶段。——对啦!应该:复位设备,并且和设备0和端点0通信了。 我们找找。

5:主机枚举U盘(本文最关心的地方)

if ( FoundNewDev || s == ERR_USB_CONNECT ) //有新连接 或者 连接状态

{

FoundNewDev = 0; //清新连接状态

Delay_Ms( 200 ); //延时0.2s

s = InitRootDevice( Com_Buffer );//这里是枚举的过程

…… 

}

先延时0.2s 然后 初始化设备。着重看:InitRootDevice函数

这里代码比较长,不好截图也不好复制。维子只是把关键几个函数列出来:

5.1ResetRootHubPort 复位过程

ResetRootHubPort( ); //设置总线速度 复位总线设备 并 清中断标志位

图片4.png

看到他里面包含有总线复位的功能,推断它这很可能是枚举的第一步的第一阶段——复位。

然后代码里开始执行一个时间段的周期检查(具体看代码,很好理解,但是我们要讲的是枚举这里就不说了。)在后来,如果检查到了接入,要进行速度设置(通过SetUsbSpeed)和获取设备描述符(通过CtrlGetDeviceDescr)。这个获取描述符,就是复位总线后 和从机的设备0和端点0的首次获取设备描述符。

 

5.2CtrlGetDeviceDescr获取长度的过程

s = CtrlGetDeviceDescr( DataBuf );//获取设备描述符 到DataBuf  这里应该是第一次获取设备描述符S 是返回值, DataBuf是实际返回的内容。这些都是常规的用法。

UINT8 CtrlGetDeviceDescr( PUINT8 DataBuf )  

{

……

UsbDevEndp0Size = DEFAULT_ENDP0_SIZE; //8个 因为usb协议规定 端点0必须有8

CopySetupReqPkg( SetupGetDevDescr ); //把发送临时转存到 发送数据内存中

s = HostCtrlTransfer( DataBuf, &len ); //字面理解就是主机控制传输的意思

……

UsbDevEndp0Size = ( (PUSB_DEV_DESCR)DataBuf ) -> bMaxPacketSize0;

}

讲解一下:CopySetupReqPkg 函数把 SetupGetDevDescr 的数据存到 pSetupReq  指向的内存中。其中:SetupGetDevDescr const属性的“包”宏。就是不占用RAM把数据放到flash里。

#define pSetupReq   ((PUSB_SETUP_REQ)pHOST_TX_RAM_Addr)

HostCtrlTransfer( DataBuf, &len );从字面意思理解就是主机执行控制传输的意思。在这函数的内部我们可以看到:s = USBHostTransact( USB_PID_SETUP << 4 | 0x00, 0x00, 200000/20 ); 这条语句用来发送SETUP数据包。s = USBHostTransact( USB_PID_IN << 4 | 0x00, R8_UH_RX_CTRL, 200000/20 );这条语句用来发送In包。

 

5.3CtrlSetUsbAddress设置新地址

s = CtrlSetUsbAddress( ((PUSB_SETUP_REQ)SetupSetUsbAddr)->wValue );  //设置新地址

 

5.4CtrlGetConfigDescr从新地址获取设备描述符

维子把枚举(初始化)过程的代码,简化一下,就是下面的代码。

图片5.png

6:细节

在阅读代码的时候我们看到:

1:枚举过程确实是全程通过控制传输实现的。

HostCtrlTransfer函数实现了控制传输。维子认为,控制传输其实只是一个理论上的概念,是特定数据包的组合形成的。类似于:用仿古砖撘的庙。仿古砖就是包类型。

2USBHostTransact 函数实现了数据包的具体发送。

他的特点是,给定PID,端点号,tog类型,和延迟时间,来发送数据。这就是“砌砖”的机器。不管是仿古砖还是泡沫砖,你告诉我什么砖型我砌就好。

3:应该是先准备好除R8_UH_EP_PID寄存器以外的寄存器的值,最后赋值R8_UH_EP_PID寄存器开始传输。传输完没完 TRANSFER标志。也就是说,ch32v103是以包为最基本的单元发送的。

7:总结

本篇文章是,系列测评的第七篇文章。上一篇讲解了USB的枚举过程的一些概念。这一篇里讲解了具体例程代码里的用法。其实例程的代码,还是很有一些值得学习的。但是限于篇幅,维子在这里只能取几个点来描述。

 

回复评论 (2)

本来我想把所有的过程写的再详细一些。想通过这篇和上一篇文章,把USB枚举过程在ch32v103芯片上的实现讲的清楚。最后还是选择,只是提纲挈领的写出来。详细有很多细节官方库写的还是很不错的。有疑问的小伙伴可以给我留言我解答。

点赞  2021-1-30 18:49

谢谢分享

点赞  2021-4-4 11:20
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复