USB转串口的实现过程
前面我已经说到了USB转串口过程中USB是怎么跟串口联系起来的,如何挂上去的。接下来对于程序的深入理解想写一下关于USB转串口的整个数据的收和发的过程,一达到理清思路解决遗留问题的目的。
关于初始化的部分不用详细说明,主要是USB转串口的获得USB设备属性,枚举接口,挂上串口的过程。现在主要是根据串口的API来说下数据流的过程,COM_OPEN,COM_READ,COM_WRITE,COM_CLOSE.
COM_OPEN是一个相当重要的函数,这个函数自然石不需要做任何修改这是属于微软MDD的函数,其对应的两个文件夹COM_MDD2,SERPDDCM,是基本上都不需要修改的。但是其流程需要好好的分析。
COM_INIT给的返回值是PHW_INDEP_INFO,这里面的pHWOBJ是联系COM_MDD2到SERPDDCM的纽带。
DCB是DATA Control Block的缩写,DCB的结构成员全部都是串口数据流传输的控制参数,这些参数都是属于硬件属性范畴,是所有串口的特性,当然我们现在的这个设备并不会去采用流控。OPEN初始化两个重要的结构体PHW_INDEP_INFO,PHW_OPEN_INFO,
HWSetCommTimeouts读写操作的超时的参数设置,HWOpen具体的做了SetDefaultConfiguration,InitLine,InitReceive,InitXmit。
SetDefaultConfiguration这个函数是设置系统默认的结构体的设置。流控,波特率,校验位等等。主要是InitReceive,InitXmit。
InitReceive会对应到serialDataIn这个类InitReceive,ResetTransferQueue主要是去将正在传输队列中的Transfer都清空,这些Transfer都是在init的时候new出来的,ResetPipe根据MSDN的说法是清除USB的halt状态位,复位endpoint的数据到DATA0,他不会复位stall状态,(所以后面就加了一个clearfeature来清除这个stall feature,但是这个feature会使得AT回复阻塞),之后就会让整个USB处于等待USB接收数据的状态。它其实给了USB 4个设备缓冲,即USB接收到数据后就是通过这4个缓冲来把数据移到串口的缓冲中。IssueBulkTransfer是这个函数最重要的函数,在理解USB协议的时候再来分析。
一个重要的问题就是线程函数是在初始化的时候如何就进入等待状态的,而CMiniThread这个类里就有ThreadStart()这个成员函数,作用是清除挂起状态(用到了这个函数ResumeThread)。构造函数创建了线程CMiniThread::ThreadProc,并且处于挂起状态,ThreadProc调用了ThreadRun()这个成员函数(是个纯虚函数,在CPddXXXUart里有实现,而这个实现就是IST)。当这个函数执行后那么整个初始化基本上就完成了其最后的结果就是让这个线程等着USB那边的数据过来。
InitXmit只是做了ResetPipe清除了相对应Pipe的一些标志位。
其实所有的动力源都是COM_WRITE这个函数做的。只有向USB设备发送了请求或者写入字节数就能收到USB设备相对应的返回。才能触动整个流程。
DoTxData这个函数通过HWTxIntrHandler这个中断处理函数来用bulk方式来把想要发的数发给USB设备。
对于IssueBulkTransfer这个函数中带有一个回调函数地址,当flag中带有USB_NO_WAIT的时候就会函数一执行就会直接返回,但是会用这个回调函数来等着USB设备给报告是否发送完成,这就是一个典型的异步模式,之后SerialDataOut这个线程会等到发送完成后收到USB的回调消息触发串口MDD层的NotifyPDDInterrupt,然后由SerialEventHandle这个函数来处理这个事件最后再由DoTxData来发送剩下的字节,知道发送完所有的字节。
而当
还记得前面有个InitReceive这个函数吗?当一有数据从USB设备返回的时候就会触动它的回调函数(我的理解是,返回的概念肯定有一个超时的限制,比如说我一次来了一个数,但是很长时间不来了,只能算一次回调,应该是在两个字符之间有多长时间的超时就会算上一次回调,当然得预先有个回调在那里等着)。当接收的回调函数执行后会又回到NotifyPddInterupt这个函数当中来判别这个貌似中断的类型是接收中断还是发送中断,之后会跟传输部分一样在SerialEventHandle这个函数里面来处理HWRxIntrHandler这个函数就是用来处理回调后来把收到的字符存储到串口缓存里面,
每一个Transfer对应着一个m_dwClientInfo,只有在CloseTransfer的时候才会吧每一次传输的句柄置零,每个USBPIPE就会去有一个自己的USBTransfer,所以很多个pipe就会有一个transfer List,
突然间又不整个枚举设备的过程重新的理了一下,从Attach()开始,在attach()中实际上回去做几件事情
1, 创建了一个interface的链条。说是个链表也不像,就是通过类的构造函数的强大讲所有interface的指针串成了一条线,
2, New出了所有的interface对象。
3, 对于interface进行了初始化,初始化得实际事情就是对于每个interface中的Endpoint进行初始化
4, 在EndPoint的构造中实际上就是将每个interface的Endpoint串成链。
5, 一个没有实际意义的Init.
在做完attach后就会在注册表的Active下去根据USB下的键值去创建相应的键值。这主要是通过一个函数GetSerialObject,这个函数是非常关键,至关重要的函数。它主要做的几件事情。
1, 构造一个Driver类。与前面的对应关系是一个client类对应一个driver类。
2, 做这个driver的初始化。在初始化的过程中可以对你想要的接口进行初始化,在这里面初始化的一些跟USB设备属性无关的都是跟USB相关的具体操作类,比如说PIPE,UsbTransfer.
3, 在这个初始化的过程中会先根据一个接口的属性来,多少个EndPoint,每个EndPoint的out或者In的属性来创建CreateBulkIn,CreateBulkOut。
4, CreateBulkIn在这个函数里面回去构造SerialDataIn这个类。在这个SerialDataIn类里面会去启动上面说的那个CminiThread这个线程,让ThreadRun起来,同时也会调用UsbAsyncClassPipe这个的构造函数,实质上是UsbClassPipe的构造。这个构造实际上就是根据EndPoint来创建相应的PIPE.那么就能得出这样的一个结论,就是一个USB对应着多个Interface,每个Interface对应着几个EndPoint,每个EndPoint对应着一个Pipe.
5, 接下来就是SerialDataIn的初始化了。这个初始化就会为各个PIPE来创建各自的缓存,这个缓存就是后来处于接收状态的时候USB的缓存。从程序来看每个PIPE都会有4个同样大小的缓存对应在里面。这四个缓存就是专门用于USB部分的接收。
6, 现在其实可以看出在COM_OPEN里面在InitReceive里面去初始化用IssueBulkTransfer来注册回调的过程中,就是有每个m_hUsbTransfer就对应着一个缓存。当这个m_hUsbTransfer这个句柄没有被清零就说明这次的传输就没有结束。转过头来看看InitReceive这个函数里面的4个注册回调的函数IssueBulkTransfer发出去后等着回调的过程也就是接收数据的过程也是一个传输。直到将这次回调收到得数全部填入串口缓存。然后就会调用CloseTransfer去讲m_hUsbTransfer置零就是一次Transfer的一次真正的结束。所以在串口真的处理接收字符的时候是传输一次并没有结束的时候,所以此时IsTransferEmpty肯定是个FALSE,所以按照上面的总结我想对USB数据的处理过程做一个分析。
每个In Pipe有4个Transfer,这4个Transfer组成一个队列,m_lpArmedTranfser在Pipe初始化的时候被赋予链表的首地址,IncTransfer这个函数就是遍历这4个Transfer组成的链表。这就不难理解每次去GetClientInfo,只要这个Trnasfer结束,我就会去用下一个Transfer每一个Transfer有自己对应的ClientInfo,前面也讲到了只有当把这个Transfer的USB部分的缓存得到的数据全部存入串口的缓存中,这次Transfer才算结束,才回去close,这个Transfer的句柄才会被清0,那么就会在GetClientInfo的时候m_lpArmedTranfser指向下一个,同时也会得到相应的clientinfo.这样也不会让数据乱序,搬完第一个buffer就会去搬运下一个buffer,知道把数据搬完。当然会是一次USB的回调才会去搬运一次,GetTransferStatus这个USBd的函数能告诉我们一次Transfer会有多少的数据。
其实感觉只要弄清逻辑关系写一个USB的驱动程序也不算是那么的难。