Использование Delphi + Jedi, потеря USB-данных, когда устройство отправляет их "слишком быстро"
С помощью Delphi XE2
а также TJvHidDevice
класс от Jedi
библиотека, мне удалось успешно связаться с USB-устройством (pic32mx7
доска, с моим кодом, работающим на нем). Обычный способ "отправить запрос, дождаться одного ответа" работает.
Проблема с командой, которая приводит к большему количеству последовательных ответов. Если эти ответы отправляются устройством как можно быстрее - или даже если я добавляю небольшую задержку между ними, например 5 мс, - я теряю пакеты (отчеты? Кадры?). OnDeviceData
событие просто не похоже на огонь для всех из них. Если я добавлю большие задержки в коде устройства, проблема исчезнет.
я использовал USBPcap
программа для захвата данных USB и вывода их в файл, который, как только я открою его в WireShark, содержит все данные, отправленные устройством (я отправляю 255 пакетов в качестве теста, все нули и одна "1" смещают свое место на 1 позиция в каждом пакете). Итак, я думаю, что и устройство, и Windows делают свою работу.
Чтобы убедиться, что мой код Delphi не является неисправным, я попробовал пример проекта Jedi "DevReader" (здесь код main.pas), который выводит данные на экран, а также пропускает пакеты.
Я чувствую, что в сети должно быть больше информации о классах USB джедаев, но у меня возникают проблемы с их поиском.
Возможно, я смогу избежать этой проблемы, агрегируя / уплотняя ответы устройства, но все же хотел бы знать, что происходит.
Редактировать:
- Пробовал из консольного приложения: пакеты больше не терялись.
- Модифицировано демо-приложение Jedi для подсчета только полученных пакетов и обновления метки счетчика на экране (без принудительной перерисовки окна) - нет потерянных пакетов.
- Добавлен sleep(1) в событии OnData - нет потерянных пакетов.
- Добавлен sleep(2) в событии OnData - потеря пакетов снова.
Это похоже на то, что поток джедаев, который читает данные, не должен задерживаться какой-либо обработкой - не должна ли происходить некоторая буферизация данных (в Windows?), Которая допускала бы такой тип обработки? Судя по "шаблону" потери пакетов, создается впечатление, что буферизация существует, но этого недостаточно, потому что я могу получить, например, 30 пакетов, затем потерять 5, затем получить еще 20 и т. Д.
Я изменю свой код, чтобы скопировать данные и как можно быстрее выйти из события OnData, чтобы у потока было минимальное время простоя, и я сообщу о результате.
1 ответ
Поскольку причина проблемы, по-видимому, связана с периодом времени, когда поток чтения USB блокируется Synchronise
, т. е. обработку данных, выполняемую основным потоком, я внес в код потока, (класс TJvHidDeviceReadThread, блок JvHidControllerClass.pas). Любой код, который использовал этот модуль и содержащиеся в нем классы, должен работать без каких-либо изменений, ничего публичного не изменилось.
Новое поведение: каждый раз, когда данные читаются, они помещаются в потокобезопасный список. Вместо синхронизации теперь используется Queue
, но только если он еще не поставлен в очередь. Метод Queued читает из списка безопасных потоков, пока он не станет пустым. Он запускает событие (то же, что и в старом коде) для каждого буферизованного отчета в списке. Как только список пуст, флаг "Очередь" сбрасывается, и при следующем чтении очередь снова становится.
В тестах до сих пор я не встречал потерянных пакетов.
Класс потока был расширен:
TJvHidDeviceReadThread = class(TJvCustomThread)
private
FErr: DWORD;
// start of additions
ReceivedReports : TThreadList;
Queued: boolean;
procedure PushReceivedReport(const bytes: array of byte; const NumBytesRead: cardinal);
function PopReceivedReport(var ReportID: byte; var ReportBytes: TBytes): boolean;
procedure FlushBuffer;
// end of additions
procedure DoData;
procedure DoDataError;
constructor CtlCreate(const Dev: TJvHidDevice);
protected
procedure Execute; override;
public
Device: TJvHidDevice;
NumBytesRead: Cardinal;
Report: array of Byte;
constructor Create(CreateSuspended: Boolean);
//added destructor:
destructor Destroy; override;
end;
В разделе реализации было изменено следующее:
constructor TJvHidDeviceReadThread.CtlCreate(const Dev: TJvHidDevice);
begin
inherited Create(False);
// start of changes
ReceivedReports := TThreadList.Create;
// end of changes
Device := Dev;
NumBytesRead := 0;
SetLength(Report, Dev.Caps.InputReportByteLength);
end;
procedure TJvHidDeviceReadThread.Execute;
...
...
...
//replaced: Synchronize(DoData); with:
PushReceivedReport (Report, NumBytesRead);
...
И было добавлено следующее:
type
TReport = class
ID: byte;
Bytes: TBytes;
end;
destructor TJvHidDeviceReadThread.Destroy;
var
l: TList;
begin
RemoveQueuedEvents (self);
try
l := ReceivedReports.LockList;
while l.Count>0 do
begin
TReport(l[0]).Free;
l.Delete(0);
end;
finally
ReceivedReports.UnlockList;
FreeAndNil (ReceivedReports);
end;
inherited;
end;
procedure TJvHidDeviceReadThread.FlushBuffer;
var
ReportID: byte;
ReportBytes: TBytes;
begin
while PopReceivedReport (ReportID, ReportBytes) do
Device.OnData(Device, ReportID, ReportBytes, length(ReportBytes));
end;
function TJvHidDeviceReadThread.PopReceivedReport(var ReportID: byte; var ReportBytes: TBytes): boolean;
var
l: TList;
rep: TReport;
begin
l := ReceivedReports.LockList;
rep := nil;
try
result := l.Count>0;
if result
then
begin
rep := l[0];
l.Delete(0);
end
else Queued := false;
finally
ReceivedReports.UnlockList;
end;
if result then
begin
ReportID := rep.ID;
SetLength(ReportBytes, length(rep.Bytes));
System.move (rep.Bytes[0], ReportBytes[0], length(rep.Bytes));
rep.Free;
end;
end;
procedure TJvHidDeviceReadThread.PushReceivedReport(const bytes: array of byte; const NumBytesRead: cardinal);
var
rep: TReport;
begin
rep := TReport.Create;
setlength (rep.Bytes, NumBytesRead-1);
rep.ID := Bytes[0];
System.move (Bytes[1], rep.Bytes[0], NumBytesRead-1);
// explicitely lock the list just to provide a locking mechanism for the Queue flag as well
ReceivedReports.LockList;
try
if not Queued then
begin
Queued := true;
Queue (FlushBuffer);
end;
ReceivedReports.Add(rep);
finally
ReceivedReports.UnlockList;
end;
end;