【RISC-V MCU CH32V103测评】 ---前进的维子---U盘枚举代码简析
前进的维子
2021年1月29日
目的:走读代码,具象化枚举过程。
在上一篇文章里面,“维子”用自己的语言,讲述了USB设备的枚举过程。限于篇幅限制,上一篇没有将枚举的代码实例相结合的说一说。官方给出了一个例子”EXAM13”。它的功能是:在文件根目录里添加一个长文件名的文件。我们尝试分析这个例程代码的部分片段,将上一篇文章中的概念具体化。——观察ch32v103是怎么实现对u盘进行枚举的。
代码比较多,本文力求简洁。想要深入学习,还是要读者自己分析源码。
“维子”把折叠好的代码截图放到下面。
先浏览一下,观察大体程序结构:
过程很简单:系统初始化,内部外设初始化(串口初始化,USBHD初始化,然后是USB主机初始化)文件库初始化,在主循环内,通过“轮询”状态位(图中折叠的第一部分),来实现功能的切入——当检查出“FoundNewDev 或者 ERROR_USB_CONNECT”的时候做进一步的写入文件处理。(第二个折叠代码的地方)上面的过程,是地步浏览。
我就是要明白这个芯片做主,怎么就把U盘枚举出来了。
带着要搞懂“枚举”的目标去看代码。(小伙伴还记得枚举的四个阶段吗?)我们就是要看着四个阶段怎么代码里体现的。如果记不得可以看我的上一篇帖子。(简单说,第一步,复位外设,和设备号0的端点0通信获得设备描述符。第二步,配置设备设备地址。第三步和这个新设备地址通信再次获取设备描述符。第四步,获取配置描述符集。)回顾一下就是下面表格:
过程 |
内容 |
1 |
首次复位,和0号设备的0端点通信,获取设备描述符长度 |
2 |
二次复位,和0号设备的0端点通信,设置设备地址 |
3 |
和新地址设备的0号端点通信,再次获取设备描述符 |
4 |
获取配置描述符 |
“维子”已经在代码里把注释写的很清楚了。不过还是简单描述一下吧:这是USB主机的初始化函数;包括下面几步:设置USB为主机设备,和端点0 设备号0的地址通信,设置主机的发送和接收端点使能,配置接收和发送缓存。清中断标志,使能传输中断和连接中断。
也就是初始化,和 允许传输和连接中断。
经过初始化后,在初始化一下“文件系统”就进入下循环了。
大循环里,以100m 的间隔,对是不是接入了数据进行判断
——if ( R8_USB_INT_FG & RB_UIF_DETECT ) 然后调用 AnalyzeRootHub 函数来判断是进来还是断开来了。AnalyzeRootHub 函数看起来不太整齐,我们整理一下,是下面的结果。具体看“维子”在截图里做的注释。不难理解。
这就是判断,当前是接入还是拔出。
这里我们思考一下,假设检测出来的是设备接入 而且 后面的工作我们自己来做的话,我们该干吗? 要回想枚举的四个阶段。——对啦!应该:复位设备,并且和设备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.1:ResetRootHubPort 复位过程
ResetRootHubPort( ); //设置总线速度 复位总线设备 并 清中断标志位
看到他里面包含有总线复位的功能,推断它这很可能是枚举的第一步的第一阶段——复位。
然后代码里开始执行一个时间段的周期检查(具体看代码,很好理解,但是我们要讲的是枚举这里就不说了。)在后来,如果检查到了接入,要进行速度设置(通过SetUsbSpeed)和获取设备描述符(通过CtrlGetDeviceDescr)。这个获取描述符,就是复位总线后 和从机的设备0和端点0的首次获取设备描述符。
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包。
s = CtrlSetUsbAddress( ((PUSB_SETUP_REQ)SetupSetUsbAddr)->wValue ); //设置新地址
维子把枚举(初始化)过程的代码,简化一下,就是下面的代码。
在阅读代码的时候我们看到:
1:枚举过程确实是全程通过控制传输实现的。
HostCtrlTransfer函数实现了控制传输。维子认为,控制传输其实只是一个理论上的概念,是特定数据包的组合形成的。类似于:用仿古砖撘的庙。仿古砖就是包类型。
2:USBHostTransact 函数实现了数据包的具体发送。
他的特点是,给定PID,端点号,tog类型,和延迟时间,来发送数据。这就是“砌砖”的机器。不管是仿古砖还是泡沫砖,你告诉我什么砖型我砌就好。
3:应该是先准备好除R8_UH_EP_PID寄存器以外的寄存器的值,最后赋值R8_UH_EP_PID寄存器开始传输。传输完没完 看TRANSFER标志。也就是说,ch32v103是以包为最基本的单元发送的。
本篇文章是,系列测评的第七篇文章。上一篇讲解了USB的枚举过程的一些概念。这一篇里讲解了具体例程代码里的用法。其实例程的代码,还是很有一些值得学习的。但是限于篇幅,维子在这里只能取几个点来描述。
本来我想把所有的过程写的再详细一些。想通过这篇和上一篇文章,把USB枚举过程在ch32v103芯片上的实现讲的清楚。最后还是选择,只是提纲挈领的写出来。详细有很多细节官方库写的还是很不错的。有疑问的小伙伴可以给我留言我解答。