lpcopen中一个很隐蔽usb lib错误,解决过程分享
最近调试LPC4357的USB接口,出现一个很棘手的问题,拷贝文件时,莫名其妙的拷贝失败,这个问题如果不解决的话,即使将来用在产品上,肯定会有问题的,早晚会暴发,于是我就好好的研究了一下这个USB协议栈,LPC4357官方例程LPCOPEN使用的是Dean Camera开发的LUFA(The Lightweight USB Framework for AVRs),应该是在这上面修改而成的,令我吃惊的LUFA的作者Dean Camera,这个家伙居然很年轻,还没有我大,因为我之前常用的比如LWIP、FATFS都是一些老家伙(老教授)开发的,没想到这么年轻开发的软件居然入了大公司NXP的法眼,于是NXP在此基础上专门给LPCXXXX系列单片机做了修改,终于2012年成了“Copyright(C) NXP Semiconductors, 2012”
膜拜也好、惊叹也好,问题还得解决,LPC4357片子用的是通用的EHCI主机,这与我熟悉的STM32单片机不一样,我本来是不打算深究的,但是我简单查了一下EHCI控制器相关的资料,发展EHCI规范是INTER制定的,这个USB主机才是主流的,通用的,好多单片机、ARM的USB外设都是兼容EHCI的,而STM32的USB控制器我也没有找到来源,估计是ST公司自己设计的吧,很显然,研究EHCI的工作原理要比STM32的USB更有前景。
于是我从网上找了很多EHCI的资料,有中文的、有准备的英文规范,《USB2.0与OTG规范及开发指南》这是一本系统介绍USB的书籍,但是我不可能有时间会去慢慢回味,我要的是速战速决。《ehci-specification-for-usb》是英文原版的EHCI规范,这对我来说只是工具书,遇到中文资料有含糊不清的地方时我才会打开英文原版资料来确认。〈Host控制器之EHCI〉这是个无名大神的类学习笔记,在我学习EHCI、解决问题的过程中起了重要作用。
EHCI与我之前熟悉的那些外设如SPI、UART等最大的不同是USB2.0是标准,EHCI直接定义了USB20的一种实现方式,定义的非常之细,不仅定义了的寄存器,而定义了的数据结构体,更甚一步这些数据结构体在一定程度上还起着寄存器的作用,但这是数据结构体是定义在用户RAM区的,大多这些结构体要求是4096对齐的,(太浪费空间了,STM32不用是不是因为RAM太小)。因为我这次要解决的是LPC4357访问U盘,因此我只是详细看了,QH(queue header)、qTD(queue transfer descriptors),因为大容量存储设备大概只用了这两个结构体,还有一个iTD这是同步传输才使用的,我只是简单看了下。QH对应 一个pipe,一个端点,下面挂了一串qTD,所谓的发送接收USB数据包,就是填充这些结构体,然后启动发送就行了,然后EHCI控制器会自动的访问这些定义的QH、qTD来传送数据,数据传输完毕后在中断程序中结束发送。
有了这些基本的知识,再调试起LPC4357的USB来就不会一头雾水了,至少我大概能弄清数据的流向,如果你不弄清这些东西,即使单步调试到错误的地方,你还是不知道哪里错了。经过好多天学习-〉调试-〉学习-〉调试-〉学习-〉调试-〉反复过程,终于慢慢定位到真正的错误地方;期间我还找来了公司的USB ANALYST 总线分析仪,这个仪器对我定位问题起到决定性作用。 NXP提供的这个USB协议栈有个很隐蔽的问题,在EHCI.c文件中QueueQTDs()函数,
static HCD_STATUS QueueQTDs (uint8_t HostID,
uint32_t* pTdIdx,
uint8_t* dataBuff,
uint32_t xferLen,
HCD_TRANSFER_DIR PIDCode,
uint8_t DataToggle)
{
uint32_t TailTdIdx=0xFFFFFFFF;
while (xferLen > 0)
{
uint32_t TdLen;
uint32_t MaxTDLen = QTD_MAX_XFER_LENGTH - Offset4k((uint32_t)dataBuff);
if(PipeStreaming[HostID].PacketSize > 0)
TdLen = MIN(xferLen, PipeStreaming[HostID].PacketSize);
else
TdLen = MIN(xferLen, MaxTDLen);
xferLen -= TdLen;
if (TailTdIdx == 0xFFFFFFFF)
{
ASSERT_STATUS_OK ( AllocQTD(HostID, pTdIdx, dataBuff, TdLen, PIDCode, DataToggle, (xferLen==0) ? 1 : 0) );
TailTdIdx = *pTdIdx;
}
else
{
uint32_t NewTdIDx;
if(HCD_STATUS_OK == AllocQTD(HostID, &NewTdIDx, dataBuff, TdLen, PIDCode, DataToggle, (xferLen==0) ? 1 : 0))
{
HcdQTD(HostID,TailTdIdx)->NextQtd = Align32((uint32_t) HcdQTD(HostID,NewTdIDx));
TailTdIdx = NewTdIDx;
}
else
{
PipeStreaming[HostID].BufferAddress = (uint32_t)dataBuff;
PipeStreaming[HostID].RemainBytes = xferLen + TdLen;
PipeStreaming[HostID].DataToggle = DataToggle;
HcdQTD(HostID,HCD_MAX_QTD - 1)->IntOnComplete = 1;
break;
}
}
if(DataToggle == 1) DataToggle = 0;
else DataToggle = 1;
dataBuff += TdLen;
}
if(xferLen == 0)
{
memset(&PipeStreaming[HostID], 0, sizeof(Pipe_Stream_Handle_T));
}
return HCD_STATUS_OK;
}
即使不懂USB协议栈,完全分析这个函数,也能看出点问题的,传输长度xferLen在循环分配的过程中,先减去了将要分配的长度xferLen -= TdLen;再去分配QTD,如果分配失败的话,xferLen已经减过了,而在函数后面if(xferLen == 0)(),却依然使用了xferLen 这个错误的值,造成最后一包数据丢失。只需在这个地方这样修改就可以了。
xferLen = xferLen + TdLen; //willow add
PipeStreaming[HostID].RemainBytes = xferLen /*+ TdLen*/;
经过测试,确实是这的问题,测试通过。
这个问题之所以隐蔽,是因为不是所有的应用都会暴露出来的,只有当传送的数据包为(8*n)+1包时,才会出现。所以有的文件可以拷贝成功,有的文件不能成功,且与文件大小关系也不大
。