LPC1788微控制器间歇性地不发送USB消息

时间:2014-08-18 09:18:08

标签: c usb microcontroller lpc

我正在为恩智浦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        

3 个答案:

答案 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对批量和等时端点使用双缓冲,因此您在该图像中看到的内容如下:

  1. 微控制器尝试向主机发送USB消息。两个写缓冲区都是免费的,所以它选择一个并使用它。
  2. 微型尝试发送第二条消息。它使用剩余的空闲写缓冲区。
  3. 微程序尝试发送第三条消息。两个缓冲区都在使用,因此必须等待。这会产生第三个通道中的一系列行,而微观轮询终点状态。
  4. 最终其中一个缓冲区(可能是第一个使用的缓冲区)变为空闲,并将消息写入该缓冲区。
  5. 微程序尝试发送第四条消息。两个缓冲区都在使用中,微观显然需要等待一段时间才能释放其中一个。
  6. 最终其中一个缓冲区变为空闲,并将消息写入该缓冲区。
  7. 在添加额外的支票之前,我得到了这样的行为:

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

    为了检查此更改如何解决问题,我创建了一个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端驱动程序。它的读取速度不够快。