2

我正在为 NXP 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 微秒的人为延迟,消息传输所需的时间应该比这少得多)。这是关于我上周得到的;我现在无法检查,因为我的调试工具正在运行。

这是微控制器的 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        
4

3 回答 3

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;
}
于 2014-08-18T15:10:16.257 回答
0

我想我已经设法对微控制器发送 USB 消息的方式进行了一些重大改进。

LPC178x/7x 用户手册的第 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 循环中被捕获的位置,因为我正在使用的端点的写入缓冲区都不是空闲的。第四个通道中的行是将消息写入端点的写入缓冲区之一的位置。

LPC1788 对批量和同步端点使用双缓冲,因此您在该图像中看到的内容如下:

  1. 微控制器尝试向主机发送 USB 消息。两个写缓冲区都是免费的,所以它会选择一个并使用它。
  2. 微型尝试发送第二条消息。它使用剩余的空闲写缓冲区。
  3. 微型尝试发送第三条消息。两个缓冲区都在使用中,所以它必须等待。这会在第三个通道中产生一系列线路,同时微轮询端点状态。
  4. 最终,其中一个缓冲区(可能是第一个使用的缓冲区)变得空闲,并将消息写入其中。
  5. 微机尝试发送第四条消息。两个缓冲区都在使用中,而微型显然必须等待一段时间才能释放其中一个。
  6. 最终,其中一个缓冲区变得空闲,并将消息写入其中。

在添加额外的检查之前,我得到了这样的行为:

显然,如果没有检查,端点的缓冲区就会被覆盖!

为了检查此更改如何解决问题,我使用简单算法创建了一个 Python 脚本,我选择通过比较 USB 日志的输出(由我在 PC 端上的一个 USB 驱动程序生成)的符合程度来评估性能一个完美的循环(这是我理想中想要的)。

我在附加检查的情况下运行了我的程序 3 次,在没有的情况下运行了我的程序的 3 次,每次运行时间都足够长,以便 DLL 接收并记录在日志文件中的 1000 条 USB 消息。

带有附加检查的三个日志是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

通过添加额外的检查以确保写入缓冲区是空闲的,我设法将 USB 的“性能”提高了大约 78.1%(100% 意味着 USB 消息的完美循环:1、2、3、4、1 ,2,3,4,1,2,3,4...)。

除此之外,我发现当我签入时,消息吞吐量大约翻了一番,尽管在 while 循环中等待会产生延迟。

92.93% 仍然不完美,但手册谈到了检查来自主机的 ACKS 以及自由写入端点。我早些时候尝试过这样做(没有明显成功),但这是在我尝试此检查之前。希望如果我同时实现两者,我可以让 USB 性能与 CAN 相媲美。

编辑:等待 ACK 的事情不起作用,但如果我在发送消息之间强制延迟 1 毫秒,我可以获得 ~99.99...% 的性能。

这不是一个理想的解决方案,因为 1ms 的延迟时间相当长,所以我暂时不解决这个问题。

编辑

在这一点上,我非常坚信问题主要出在我编写的 PC 端驱动程序上。它的阅读速度不够快。

于 2014-08-19T12:48:48.640 回答
0

你有硬件 USB 分析仪吗?我不确定 USBLyzer 是如何工作的,但我认为它仍在最低级别使用 Windows USB 子系统。我对 Windows USB 子系统的体验是它非常容易出错 - 一个特殊的例子是,当传输的数据是最大帧大小的精确倍数时,它不起作用。我并不是说这是您的确切问题-我们的症状与您报告的不同-但在您的情况下,我会考虑 a) 更改传输端将在一帧中发送的最大数据量,因此这不是确切的帧大小的倍数,以及 b) 获取硬件 USB 分析器以查看实际在线上的内容。

于 2014-08-18T09:56:57.633 回答