我正在为恩智浦LPC1788微控制器开发代码,我的部分工作是使产品基于USB兼容。大部分的工作都已完成,通常通过USB进行的通信几乎和CAN一样。
然而,我遇到的一个问题是,当从微控制器产生的USB消息的恒定输出非常接近地发送时,其中一些消息偶尔会被丢弃。
我使用基于WinUSB编写的自定义驱动程序来接收PC端的消息,我原本怀疑问题出在接收端。但是,使用USBLyzer我现在确信问题出在发送端 - USBLyzer日志完全匹配我从 WinUsb_ReadPipe()中获得的日志。
LPC1788使用USB 2.0全速协议,我已经确认使用探头以大约12 MHz的频率发送和接收信息,这应该是它。
设备配置为使用两个端点:逻辑端点2 IN和逻辑端点2 OUT。这两个都配置为批量传输,最大数据包大小为64字节。
我会认为至少在500-600微秒左右发送消息(我已经在线程中引入了一个人为的500 us延迟并且消息传输应该会少得多时间比)。这是我上周得到的;我现在无法检查,因为我的调试工具正在运行。
这是微控制器的USB初始化代码:
void USBInit()
{
// Configure USB pins.
PINSEL_ConfigPin(0, 29, 1); // USB_D+1
PINSEL_ConfigPin(0, 30, 1); // USB_D-1
PINSEL_ConfigPin(1, 18, 1); // USB_UP_LED1
PINSEL_ConfigPin(2, 9, 1); // USB_CONNECT1
PINSEL_ConfigPin(1, 30, 2); // USB_VBUS
// Turn on power and clock
CLKPWR_ConfigPPWR(CLKPWR_PCONP_PCUSB, ENABLE);
PINSEL_SetPinMode(1, 30, PINSEL_BASICMODE_PLAINOUT);
// Set DEV_CLK_EN and AHB_CLK_EN.
LPC_USB->USBClkCtrl |= 0x12;
// Wait until change is reflected in clock status register.
while((LPC_USB->USBClkSt & 0x12) != 0x12);
// Enable NVIC USB interrupts.
NVIC_EnableIRQ(USB_IRQn);
// Reset the USB.
USBReset();
// Set device address to 0x0 and enable device & connection.
USBSetAddress(0);
}
这是微控制器用于通过USB发送消息的代码:
uint32_t USB_Send(uint32_t endpoint, uint8_t *pData, uint32_t count)
{
// Convert into a form that can be sent successfully using USB.
uint8_t data[USB_MAX_PACKET_SIZE];
for(int i=0; i < count; i++)
{
data[i*2] = hex[(pData[i] >> 4)];
data[(i*2)+1] = hex[(pData[i] & 0xF)];
}
return USBWriteEndpoint(endpoint, data, count*2);
}
uint32_t USBWriteEndpoint(uint32_t endpoint, uint8_t *pData, uint32_t count)
{
uint32_t i;
LPC_USB->Ctrl = ((endpoint & 0xF) << 2) | CTRL_WR_EN;
LPC_USB->TxPLen = count;
for(i=0; i < (count+3)/4; i++)
{
LPC_USB->TxData = *((__packed uint32_t *)pData);
pData += 4;
}
LPC_USB->Ctrl = 0;
USBValidateEndpoint(endpoint);
return count;
}
void USBValidateEndpoint(uint32_t endpoint)
{
writeSIEEndpointCommand(endpoint, CMD_VALID_BUF);
}
void writeSIECommandData(uint32_t cmd, uint32_t data)
{
LPC_USB->DevIntClr = CCEMTY_INT;
LPC_USB->CmdCode = cmd;
while((LPC_USB->DevIntSt & CCEMTY_INT) == 0);
LPC_USB->DevIntClr = CCEMTY_INT;
LPC_USB->CmdCode = data;
while((LPC_USB->DevIntSt & CCEMTY_INT) == 0);
}
修改
为了了解发生了什么,这是一个由我的USB驱动程序的接收功能生成的日志文件(来自USBLyzer的日志文件实际上是相同的):
0000030D000D
0000010D002D0004001B0024
0000000D0FFF001600310016
0000010D002D0004001F0028
0000020D00280028001B002D
0000030D0009
0000000D0FFF001600310016
0000010D002D0004001F0028
0000020D00280028001B002D
0000030D0009
0000010D002D0004001F0028
0000020D00280028001B002D
0000030D0009
0000010D002D0004001F0028
0000020D00280028001B002D
0000030D0009
0000010D002D0004001F0028
0000020D00280028001B002D
0000030D0009
0000000D0FFF001600310016
0000010D002D0004001F0028
0000020D00280028001B002D
0000030D0009
0000010D002D0004001F0028
我应该在以下周期中收到消息:
0000000D...
0000010D...
0000020D...
0000030D...
您可以从此日志中看到循环中的某些消息被跳过。
编辑2
下面列出了USBLyzer生成的原始和过滤捕获日志的摘录。原始日志主要包括已取消的请求,因为我的驱动程序是轮询驱动的并且使用超时。
原始日志:
USBlyzer Report
Capture List
Type Seq Time Elapsed Duration Request Request Details Raw Data I/O C:I:E Device Object Device Name Driver Name IRP Status
START 0001 11:09:15.413
URB 0002 11:09:18.484 3.071197 s Bulk or Interrupt Transfer 10 bytes data 30 30 30 30 30 30 31 46... out 01:00:02 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA801142FAB0h
00000000 30 30 30 30 30 30 31 46 30 31 0000001F01
URB 0003 11:09:18.484 3.071212 s Bulk or Interrupt Transfer 64 bytes buffer in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h
URB 0004-0002 11:09:18.484 3.071371 s 174 us Bulk or Interrupt Transfer 10 bytes buffer out 01:00:02 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA801142FAB0h Success (Success)
URB 0005-0003 11:09:18.485 3.071586 s 374 us Bulk or Interrupt Transfer in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Cancelled (Canceled)
URB 0006 11:09:18.485 3.071608 s Bulk or Interrupt Transfer 64 bytes buffer in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h
URB 0007-0006 11:09:18.486 3.072582 s 974 us Bulk or Interrupt Transfer in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Cancelled (Canceled)
URB 0008 11:09:18.486 3.072603 s Bulk or Interrupt Transfer 64 bytes buffer in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h
URB 0009-0008 11:09:18.487 3.073598 s 996 us Bulk or Interrupt Transfer in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Cancelled (Canceled)
URB 0010 11:09:18.487 3.073630 s Bulk or Interrupt Transfer 64 bytes buffer in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h
URB 0011-0010 11:09:18.488 3.074601 s 970 us Bulk or Interrupt Transfer in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Cancelled (Canceled)
[...]
URB 2504-2501 11:09:19.734 4.320666 s 161 us Bulk or Interrupt Transfer 14 bytes buffer out 01:00:02 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA800CF662D0h Success (Success)
URB 2505-2503 11:09:19.734 4.320785 s 192 us Bulk or Interrupt Transfer 24 bytes data 30 30 30 30 30 30 30 44... in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Success (Success)
00000000 30 30 30 30 30 30 30 44 30 46 46 46 30 30 31 36 0000000D0FFF0016
00000010 30 30 33 31 30 30 31 42 0031001B
过滤日志:
USBlyzer Report
Capture List
Type Seq Time Elapsed Duration Request Request Details Raw Data I/O C:I:E Device Object Device Name Driver Name IRP Status
URB 0004-0002 11:09:18.484 3.071371 s 174 us Bulk or Interrupt Transfer 10 bytes buffer out 01:00:02 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA801142FAB0h Success (Success)
URB 2504-2501 11:09:19.734 4.320666 s 161 us Bulk or Interrupt Transfer 14 bytes buffer out 01:00:02 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA800CF662D0h Success (Success)
URB 2505-2503 11:09:19.734 4.320785 s 192 us Bulk or Interrupt Transfer 24 bytes data 30 30 30 30 30 30 30 44... in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Success (Success)
00000000 30 30 30 30 30 30 30 44 30 46 46 46 30 30 31 36 0000000D0FFF0016
00000010 30 30 33 31 30 30 31 42 0031001B
URB 2507-2506 11:09:19.734 4.321309 s 459 us Bulk or Interrupt Transfer 24 bytes data 30 30 30 30 30 31 30 44... in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Success (Success)
00000000 30 30 30 30 30 31 30 44 30 30 32 44 30 30 30 34 0000010D002D0004
00000010 30 30 31 46 30 30 32 44 001F002D
URB 2511-2510 11:09:19.735 4.321931 s 311 us Bulk or Interrupt Transfer 24 bytes data 30 30 30 30 30 32 30 44... in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Success (Success)
00000000 30 30 30 30 30 32 30 44 30 30 32 38 30 30 32 38 0000020D00280028
00000010 30 30 31 42 30 30 33 31 001B0031
URB 2513-2512 11:09:19.735 4.322306 s 332 us Bulk or Interrupt Transfer 12 bytes data 30 30 30 30 30 33 30 44... in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Success (Success)
00000000 30 30 30 30 30 33 30 44 30 30 30 44 0000030D000D
URB 2725-2724 11:09:19.840 4.426662 s 89 us Bulk or Interrupt Transfer 24 bytes data 30 30 30 30 30 30 30 44... in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Success (Success)
00000000 30 30 30 30 30 30 30 44 30 46 46 46 30 30 31 36 0000000D0FFF0016
00000010 30 30 33 31 30 30 31 42 0031001B
URB 2727-2726 11:09:19.840 4.427183 s 471 us Bulk or Interrupt Transfer 24 bytes data 30 30 30 30 30 31 30 44... in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Success (Success)
00000000 30 30 30 30 30 31 30 44 30 30 32 44 30 30 30 34 0000010D002D0004
00000010 30 30 31 46 30 30 32 44 001F002D
URB 2731-2730 11:09:19.841 4.427803 s 209 us Bulk or Interrupt Transfer 24 bytes data 30 30 30 30 30 32 30 44... in 01:00:82 FFFFFA800FC98440h USBPDO-11 usbhub FFFFFA8010E965A0h Success (Success)
00000000 30 30 30 30 30 32 30 44 30 30 32 38 30 30 32 38 0000020D00280028
00000010 30 30 31 42 30 30 33 31 001B0031
答案 0 :(得分:0)
您是否有硬件USB分析仪?我不确定USBLyzer是如何工作的,但我认为它仍然在最低级别使用Windows USB子系统。我对Windows USB子系统的经验是它非常容易出错 - 一个特别的例子是当传输数据是最大帧大小的精确倍数时,它不起作用。我不是说这是你的确切问题 - 我们的报告与你的报告有不同的症状 - 但在你的鞋子里,我会看a)改变你的发送端将在一帧中发送的最大数据量,所以这不是一个确切的问题多个帧大小,以及b)获取硬件USB分析仪以查看线路上的实际内容。
答案 1 :(得分:0)
问题可能是USB_IRQHandler中断了您的写入功能。这会使USB意外处于不同的状态,因此写入失败。
您可以临时禁用IRQ作为解决方法:
uint32_t USBWriteEndpoint(uint32_t endpoint, uint8_t *pData, uint32_t count)
{
uint32_t i;
NVIC_DisableIRQ(USB_IRQn); // USB IRQ handlaer must not run ...
LPC_USB->Ctrl = ((endpoint & 0xF) << 2) | CTRL_WR_EN;
LPC_USB->TxPLen = count;
for(i=0; i < (count+3)/4; i++)
{
LPC_USB->TxData = *((__packed uint32_t *)pData);
pData += 4;
}
LPC_USB->Ctrl = 0;
USBValidateEndpoint(endpoint);
NVIC_EnableIRQ(USB_IRQn); // ... until we are here. Enable USB IRQ again
return count;
}
答案 2 :(得分:0)
我想我已经成功地对微控制器发送USB消息的方式做了一些重大改进。
在LPC178x/7x user manual的第400页,我看到了如何使用批量IN端点正确处理从设备向主机发送数据的说明。很幸运的是,我最终偶然发现它,因为它在本章的DMA部分内(我不使用DMA,所以直到现在我都忽略了它。)
根据我在那里阅读的内容,我添加了以下方法:
// Params: endpoint - the logical endpoint number.
// Returns: TRUE if at least one write buffer is free,
// FALSE otherwise.
// Description: Checks that the IN endpoint has a free write buffer.
uint8_t USBCheckInEndpointFree(uint32_t endpoint)
{
uint16_t data;
uint32_t physicalEndpoint = getEndpointPhysicalAddress(endpoint);
writeSIECommand(CMD_SEL_EP(physicalEndpoint));
data = readSIECommandData(DAT_SEL_EP(physicalEndpoint));
return (data & 0x1) == 0;
}
我将USBWriteEndpoint
更改为以下内容:
uint32_t USBWriteEndpoint(uint32_t endpoint, uint8_t *pData, uint32_t count)
{
uint32_t i = 0;
NVIC_DisableIRQ(USB_IRQn);
if((endpoint & 0xF) != 0)
{
while(getSendMessageFlag(endpoint & 0xF) != 0);
setSendMessageFlag(endpoint & 0xF);
}
while(!USBCheckInEndpointFree(endpoint))
{
uint32_t physicalEndpoint = getEndpointPhysicalAddress(endpoint);
writeSIECommand(CMD_SEL_EP(physicalEndpoint));
ITM_EVENT32_WITH_PC(3, readSIECommandData(DAT_SEL_EP(physicalEndpoint)));
}
LPC_USB->Ctrl = ((endpoint & 0xF) << 2) | CTRL_WR_EN;
LPC_USB->TxPLen = count;
for(i=0; i < (count+3)/4; i++)
{
LPC_USB->TxData = *((__packed uint32_t *)(pData+i*4));
//pData += 4;
}
ITM_EVENT32_WITH_PC(4, (pData[4] << 24) | (pData[5] << 16) | (pData[6] << 8) | pData[7]);
LPC_USB->Ctrl = 0;
USBValidateEndpoint(endpoint);
NVIC_EnableIRQ(USB_IRQn);
return count;
}
ITM_EVENT32_WITH_PC
宏用于调试。
在高速发送USB消息时,我在事件时间轴中注意到了这种模式:
在该图像中,第三个通道中的行显示了在while循环中捕获线程的位置,因为使用的端点I的写缓冲区都没有空闲。第四个通道中的行是将消息写入端点的一个写缓冲区的地方。
LPC1788对批量和等时端点使用双缓冲,因此您在该图像中看到的内容如下:
在添加额外的支票之前,我得到了这样的行为:
显然没有检查,端点的缓冲区就会被覆盖!
为了检查此更改如何解决问题,我创建了一个Python脚本,其中包含一个简单的算法,我选择通过比较USB日志的输出(由PC上的一个USB驱动程序生成)来评估性能)符合完美的循环(这是我理想的想法)。
我用我的程序进行了三次运行附加检查和三次运行我的程序而没有它,每次运行足够长时间以便1000条USB消息由DLL接收并记录在日志文件中。
附加检查的三个日志是USBLogGood1.txt
... USBLogGood3.txt
。没有检查的三个是USBLogBad1.txt
... USBLogBad3.txt
。
Python代码如下:
# Checks how well the USB is performing by checking how
# closely the stream of USB messages returned by the DLL
# matches a perfect cyclical pattern.
from statistics import *
cycle = [1,2,3,4]
sampleSize = 1000
class Globals:
totalCount = 0
errorCount = 0
usbLogFile = "usbLog.txt"
usbGoodLogFiles = ["usbLogGood1.txt",
"usbLogGood2.txt",
"usbLogGood3.txt"]
usbBadLogFiles = ["usbLogBad1.txt",
"usbLogBad2.txt",
"usbLogBad3.txt"]
# Switch between sets of log files.
usbLogFiles = usbBadLogFiles
# Read log file.
def readLog(logFile):
with open(logFile) as fIn:
return fIn.readlines()
# Converts raw log data into a list of cycle values.
def processLog(log):
data = []
for line in log:
data.append(processLogLine(line))
return data
# Converts raw log line into a cycle value.
def processLogLine(logLine):
l = logLine.replace("Message ", "")
return int(l[5],16)+1
# Counts distance in one direction, so the distance
# will be high if i2 is behind i1.
def getListDistance(val1, val2):
cycleLen = len(cycle)
i1 = cycle.index(val1)
i2 = cycle.index(val2)
if i1 <= i2:
return i2 - i1
else:
return (cycleLen - i1) + i2
def getNextValueInCycle(val):
cycleLen = len(cycle)
i = cycle.index(val)
if i < cycleLen-1:
return cycle[i+1]
else:
return cycle[0]
def checkCycleValue(expected, value):
Globals.totalCount += 1
if value != expected:
Globals.errorCount += getListDistance(expected, value)
expected = getNextValueInCycle(value)
return expected
def getPerformance():
return 1-float(Globals.errorCount)/float(Globals.totalCount)
def printPerformance():
print("Sampled %d values. USB performance: %0.1f%%"
% (Globals.totalCount, getPerformance()*100))
# Read log file and evaluate performance.
def evaluatePerformance(logFile):
Globals.totalCount = 0
Globals.errorCount = 0
log = readLog(logFile)
data = processLog(log)
if not data:
print("No data available")
return
if len(data) < sampleSize:
print("Not enough data available to meet requirements")
return
else:
data = data[:sampleSize]
expected = data[0]
for value in data:
expected = checkCycleValue(expected, value)
return getPerformance()
def printAggregatePerformanceData(logFiles, performances):
performances = [100*p for p in performances]
for f, p in zip(logFiles, performances):
print("%s: %0.2f%%" % (f, p))
print("\nAverage performance: %0.2f%%" % mean(performances))
print("Standard deviation: %0.2f" % stdev(performances))
def main():
performances = []
for logFile in usbLogFiles:
performances.append(evaluatePerformance(logFile))
printAggregatePerformanceData(usbLogFiles, performances)
if __name__ == "__main__":
main()
使用好的日志集,我得到以下输出:
usbLogGood1.txt: 93.70%
usbLogGood2.txt: 92.50%
usbLogGood3.txt: 92.60%
Average performance: 92.93%
Standard deviation: 0.67
对于坏集,我得到了这个:
usbLogBad1.txt: 16.60%
usbLogBad2.txt: 13.80%
usbLogBad3.txt: 14.10%
Average performance: 14.83%
Standard deviation: 1.54
通过添加额外的检查以确保写缓冲区是免费的,我设法提高了性能&#39; USB大约78.1%(100%意味着USB消息的完美循环:1,2,3,4,1,2,3,4,1,2,3,4 ......)。
除此之外,我发现当我进行检查时,消息吞吐量大约翻了一倍,尽管与等待while循环相关的延迟。
92.93%仍然不完美,但手册讨论了从主机检查ACKS以及只是免费写端点。我试图提前做到这一点(没有明显的成功),但这是在我尝试这个检查之前。希望如果我同时实现这两者,我可以使USB性能与CAN相媲美。
编辑:等待ACK的事情没有用,但是如果我在发送消息之间强制延迟1ms,我可以获得~99.99%的性能。
这不是一个理想的解决方案,因为1ms需要很长时间才能推迟,所以我暂时还没有解决这个问题。
修改强>
我非常坚信在这一点上,问题主要是我写的PC端驱动程序。它的读取速度不够快。