Микроконтроллер 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 использует двойную буферизацию для объемных и изохронных конечных точек, поэтому на этом изображении вы видите следующее:
- Микроконтроллер пытается отправить сообщение USB на хост. Оба буфера записи свободны, поэтому он выбирает один и использует его.
- Микро пытается отправить второе сообщение. Он использует оставшийся свободный буфер записи.
- Микро пытается отправить третье сообщение. Оба буфера используются, поэтому приходится ждать. Это создает серию линий в третьем канале, в то время как микро опрашивает состояние конечной точки.
- В конце концов один из буферов (вероятно, первый использованный) становится свободным, и сообщение записывается в него.
- Микро пытается отправить четвертое сообщение. Оба буфера используются, и, видимо, микро придется подождать, пока один из них не освободится.
- В конце концов один из буферов становится свободным, и он записывает в него сообщение.
Перед добавлением этой дополнительной проверки я получил поведение, подобное этому:
Очевидно, что без проверки буферы конечной точки перезаписываются!
Чтобы проверить, насколько хорошо это изменение решило проблему, я создал скрипт 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;
}