Микроконтроллер LPC1788 периодически не отправляет сообщения USB

Я разрабатываю код для микроконтроллера NXP LPC1788, и часть моей работы - сделать продукт на его основе USB-совместимым. Большая часть работы выполнена, и в целом связь через USB работает почти так же, как и через CAN.

Однако одна из проблем, с которыми я сталкивался, заключается в том, что при создании постоянного вывода сообщений USB от микроконтроллера, которые отправляются довольно близко друг к другу, некоторые из этих сообщений иногда отбрасываются.

Я использую пользовательский драйвер, написанный на основе WinUSB, для получения сообщений на стороне ПК, и я изначально подозревал, что проблема заключалась в том, что проблема была получена. Однако, используя USBLyzer, я теперь уверен, что проблема на стороне отправителя - журналы USBLyzer идеально соответствуют журналам, полученным из того, что я получаю из WinUsb_ReadPipe ().

LPC1788 использует протокол USB 2.0 Full Speed, и я подтвердил, что информация отправляется и принимается на частоте около 12 МГц с использованием зонда, что и должно быть.

Устройство настроено на использование двух конечных точек: логической конечной точки 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        

3 ответа

Я думаю, что мне удалось внести некоторые существенные улучшения в способ, которым микроконтроллер отправляет сообщения USB.

На странице 400 руководства пользователя LPC178x/7x я увидел описание того, как правильно обрабатывать отправку данных с устройства на хост с использованием конечной точки массива 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 (генерируемых одним из моих драйверов 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

Добавив дополнительную проверку, чтобы убедиться, что буфер записи свободен, мне удалось увеличить "производительность" 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...%.

Это не идеальное решение, так как 1 мс - довольно долгое время для задержки, поэтому я пока оставляю вопрос нерешенным.

РЕДАКТИРОВАТЬ

Сейчас я твердо верю, что проблема в основном связана с драйвером для ПК, который я написал. Это не читается достаточно быстро.

У вас есть аппаратный анализатор USB? Я не уверен, как работает USBLyzer, но я предполагаю, что он все еще использует подсистему USB Windows на самых низких уровнях. Мой опыт работы с подсистемой Windows USB заключался в том, что она чрезвычайно ошибочна - один конкретный пример был при передаче данных, которые кратны максимальному размеру кадра, это не работает. Я не говорю, что это именно ваша проблема - у нас были симптомы, отличные от того, о чем вы сообщаете, - но на вашем месте я бы посмотрел а) изменение максимального количества данных, которые ваша передающая сторона отправит в одном кадре, поэтому это не является точным кратный размеру кадра, и б) получить аппаратный USB-анализатор, чтобы увидеть, что на самом деле на проводе.

Возможно, проблема в том, что 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;
}
Другие вопросы по тегам