1. Кабели, поддерживающие стандарт Microsoft Windows Easy Transfer - с протоколом можно работать с помощью функций пакета WinUSB (чип кабеля - Prolific);
2. Кабели с собственным протоколом, заданным на этапе производства - стандарт Windows Easy Transfer не поддерживается и с протоколом можно работать только с помощью SDK разработчика (чип кабеля - OTi, других не встречал).
С первой группой всё более-менее понятно, в сети есть рекомендации, с которых можно начать программирование, поэтому эти кабели я оставил на потом. Собираюсь рассказать о них позже. А пока что опишу в общих чертах кабели на OTi-чипе. Один из примеров - кабель UANC22V7 фирмы Gembird с контроллером OTi-2208NF. Вот как он выглядит:
Довольно распространены также кабели на чипе OTi-2108, небольшие обзоры таких кабелей можно увидеть тут или вот тут.
TU2-PCLINK (фирма TRENDnet): DL-USB015: У меня имеются в наличии кабели на обоих чипах. Отличие 2208NF от 2108, кроме собственно маркировки и внешнего вида кабеля - при подключении кабеля на чипе 2208NF в системе появляется съемный диск (как при вставке флэшки) размером 1 МБ и виртуальный CD-ROM с файловым менеджером, тогда как контроллер 2108 создаёт только виртуальный CD-ROM с программой. Идентифицировать чип кабеля визуально невозможно (к сожалению), но при подключении к ПК это сделать очень легко - нужно лишь зайти в Диспетчер устройств Windows, найти съемный диск, появившийся при подключении кабеля, и посмотреть VID и PID его родителя (т.е., контроллера моста). Для чипов OTi VID равен 0EA0, а PID соответствует маркировке чипа: 2108, 2208 и т.д. Взаимодействие с контроллером кабеля выполняется с помощью стандартных функций WinAPI CreateFile, DeviceIoControl, CloseHandle, применяемых к съемному диску (2208NF) или виртуальному CD-ROM'у (2108). Функция CreateFile открывает физический диск, создаваемый контроллером моста, возвращая дескриптор (хэндл) для дальнейшей работы с устройством. Функция DeviceIoControl используется для передачи управляющих команд драйверу контроллера, выполняя таким образом функции приёма-передачи данных и ряд служебных операций. Функция CloseHandle закрывает диск по его дескриптору (хэндлу). Как видим, практически вся работа с контроллером кабеля выполняется через DeviceIoControl. Подробное описание этой функции приведено в MSDN.
Функция имеет следующие параметры:
- • hDevice — дескриптор (хэндл) устройства, получаемый с помощью CreateFile
- • IoControlCode — управляющий код для драйвера устройства, в нашем случае это 4D004 (IOCTL_SCSI_PASS_THROUGH) или 4D014 (IOCTL_SCSI_PASS_THROUGH_DIRECT)
- • InBuffer — указатель на буфер с входными данными
- • InSize — размер входного буфера
- • OutBuffer — указатель на буфер с выходными данными
- • OutSize — размер выходного буфера
- • BytesReturned — указатель на переменную, куда сохраняется число байт, записанных в выходной буфер
- • pOverlapped — указатель на структуру OVERLAPPED, в нашем случае не используется (NULL)
Таким образом, буфер должен содержать следующие поля:
- • Length (U16) — размер всей структуры (sizeof)
- • ScsiStatus (U8) — статус выполнения команды устройством, возвращаемый хост-адаптером USB-шины
- • PathId (U8) — SCSI-порт или шина, где размещено устройство
- • TargetId (U8) — контроллер/устройство на шине, которому посылается команда
- • Lun (U8) — логический номер устройства внутри контроллера
- • CdbLength (U8) — длина CDB-пакета (CDB - Command Descriptor Block, блок дескриптора команды)
- • SenseInfoLength (U8) — длина Sense-буфера для возврата ошибки
- • DataIn (U8) — направление передачи данных: 00 - SCSI_IOCTL_DATA_OUT или 01 - SCSI_IOCTL_DATA_IN
- • DataTransferLength (I32) — размер буфера для приёма/передачи данных
- • TimeOutValue (U32) — максимальное время выполнения команды, сек.
- • DataBufferOffset / DataBuffer (U32) — смещение от начала структуры до буфера для приёма/передачи данных (SCSI_PASS_THROUGH) или указатель на буфер (SCSI_PASS_THROUGH_DIRECT)
- • SenseInfoOffset (U32) — смещение от начала структуры до Sense-буфера для возврата ошибки
- • Cdb (16 x U8) — CDB-блок с управляющим кодом
- • ucSenseBuf (24 x U8) — Sense-буфер для возврата ошибки
1. Отправка данных
Эта команда осуществляет передачу данных на удалённый ПК.
IoControlCode = 4D014 (IOCTL_SCSI_PASS_THROUGH_DIRECT), InBuffer заполняется следующим образом:
Length = 00 2C (44d)
ScsiStatus = 0
PathId = FF (255d)
TargetId = FF (255d)
Lun = FF (255d)
CdbLength = 10 (16d)
SenseInfoLength = 18 (24d)
DataIn = 00
Filler1 (1 байт) = 00
Filler2 (1 байт) = 00
Filler3 (1 байт) = 00
DataTransferLength = 00 01 00 00 (65536d)
TimeOutValue = 00 00 00 0A (10d)
DataBuffer = [указатель на заранее выделенный в памяти буфер]
SenseInfoOffset = 00 00 00 30 (48d)
Cdb[16] = D9 2A FF 00|00 00 00 00|00 00 00 00|00 00 4F 54
2. Приём данных
Эта команда осуществляет получение данных с удалённого ПК.
IoControlCode = 4D014 (IOCTL_SCSI_PASS_THROUGH_DIRECT), InBuffer заполняется следующим образом:
Length = 00 2C (44d)
ScsiStatus = 0
PathId = FF (255d)
TargetId = FF (255d)
Lun = FF (255d)
CdbLength = 10 (16d)
SenseInfoLength = 18 (24d)
DataIn = 01
Filler1 (1 байт) = 00
Filler2 (1 байт) = 00
Filler3 (1 байт) = 00
DataTransferLength = 00 01 00 00 (65536d)
TimeOutValue = 00 00 00 0A (10d)
DataBuffer = [указатель на заранее выделенный в памяти буфер]
SenseInfoOffset = 00 00 00 30 (48d)
Cdb[16] = D9 28 64 00|00 00 00 00|00 00 00 00|00 00 4F 54
После выполнения команды буфер DataBuffer будет содержать полученные данные.
3. Запрос статуса удалённого ПК + Установка/получение числа пакетов на передачу/приём
Эта команда возвращает состояние второго компьютера, к которому подключен дальний конец кабеля. Кроме того, этой же командой задаётся количество пакетов, которое требуется передать с текущего ПК, а также возвращается количество пакетов, которые будут переданы с удалённого ПК на текущий.
IoControlCode = 4D004 (IOCTL_SCSI_PASS_THROUGH), InBuffer заполняется следующим образом:
Length = 00 2C (44d)
ScsiStatus = 0
PathId = 0
TargetId = 0
Lun = 0
CdbLength = 10 (16d)
SenseInfoLength = 18 (24d)
DataIn = 01
Filler1 (1 байт) = 00
Filler2 (1 байт) = 00
Filler3 (1 байт) = 00
DataTransferLength = 00 00 00 08 (8d)
TimeOutValue = 00 00 00 05 (5d)
DataBufferOffset = [смещение от начала структуры до заранее выделенного в памяти буфера, например sizeof(SCSI_PASS_THROUGH) + Fillers (8 байт)]
SenseInfoOffset = 00 00 00 30 (48d)
Cdb[16] = D8 00 03 00|00 00 00 00|00 00 00 00|00 00 4F 54
Приведённый CDB используется для обычного запроса статуса. После выполнения команды буфер по смещению DataBufferOffset будет содержать полученный статус. Список возможных статусов приведён ниже:
0, 1, 8 — компьютер офф-лайн (пауза 200 мс):
0 — отключение на текущей стороне
1 — программа на удалённом ПК не запущена
8 — отключение на удалённой стороне
2, 3, 4 — значения по умолчанию, на практике не используются (пауза 50 мс)
5 — пришли новые данные (требуется чтение) (без задержки)
6 — удалённый ПК выполняет чтение данных, но данных для отправки нет (пауза 5 мс)
7 — компьютер он-лайн (пауза 1 мс)
9 — нужна повторная отправка данных / ошибка при получении данных /сбой подключения (без задержки)
A — ?, на практике не встречалось (пауза 5 мс)
B — полное переподключение, на практике не встречалось (пауза 5 мс)
Два байта в CDB, идущие за D8 00 03 (выделены оранжевым), обозначают число пакетов, которое нужно считать удалённому компьютеру. Если эти два байта не равны 0, то удаленный ПК получает при запросе статуса не 07 00 00 00|00 00 00 00|, а 05 00 00 00|00 00 00 00|, т.е. 05 и два отправленных байта.
4. Установка статуса ошибки при передаче (статус плохого соединения)
Эта команда используется для уведомления удалённой стороны об ошибке при передаче данных. Вызывается сразу после чтения (статус=5), признанного ошибочным. В результате выполнения команды удалённый ПК считывает статус=9. Если статус=9, то следует переслать предыдущее сообщение.
IoControlCode = 4D004 (IOCTL_SCSI_PASS_THROUGH), InBuffer заполняется следующим образом:
Length = 00 2C (44d)
ScsiStatus = 0
PathId = 0
TargetId = 0
Lun = 0
CdbLength = 10 (16d)
SenseInfoLength = 0
DataIn = 01
Filler1 (1 байт) = 00
Filler2 (1 байт) = 00
Filler3 (1 байт) = 00
DataTransferLength = 00 00 00 00 (0d)
TimeOutValue = 00 00 00 05 (5d)
DataBufferOffset = sizeof(SCSI_PASS_THROUGH) (в данном случае ничего не принимаем и буфер не создаём)
SenseInfoOffset = 00 00 00 30 (48d)
Cdb[16] = D8 01 0B 00|00 00 00 00|00 00 00 00|00 00 4F 54
5. Получение идентификатора чипа/контроллера
Эта команда возвращает заводскую версию контроллера, заданную при прошивке.
IoControlCode = 4D004 (IOCTL_SCSI_PASS_THROUGH), InBuffer заполняется следующим образом:
Length = 00 2C (44d)
ScsiStatus = 0
PathId = 0
TargetId = 0
Lun = 0
CdbLength = 10 (16d)
SenseInfoLength = 0
DataIn = 01
Filler1 (1 байт) = 00
Filler2 (1 байт) = 00
Filler3 (1 байт) = 00
DataTransferLength = 00 00 00 0C (12d)
TimeOutValue = 00 00 00 05 (5d)
DataBufferOffset = [смещение от начала структуры до заранее выделенного в памяти буфера, например sizeof(SCSI_PASS_THROUGH) + Fillers (8 байт)]
SenseInfoOffset = 00 00 00 30 (48d)
Cdb[16] = F0 00 00 00|00 00 00 00|00 00 00 00|00 00 4F 54
После выполнения команды буфер DataBuffer будет содержать идентификатор контроллера, например:
для 2108: 52 64 22 08|00 01 30 39|30 33 30 31
для 2208NF: 00 00 22 08|00 01 30 38|30 38 32 32
6. Установка величины таймаута для приложения
Эта команда задаёт время, в течение которого контроллер считает приложение активным (т.е. онлайн). Время начинает отсчитываться при каждом запросе статуса удалённой стороны. Иными словами, пока программа проверяет статус удалённого ПК не реже, чем значение таймаута, она считается "живой" (статус=7). В противном случае статус=1.
IoControlCode = 4D004 (IOCTL_SCSI_PASS_THROUGH), InBuffer заполняется следующим образом:
Length = 00 2C (44d)
ScsiStatus = 0
PathId = 0
TargetId = 0
Lun = 0
CdbLength = 10 (16d)
SenseInfoLength = 18 (24d)
DataIn = 01
Filler1 (1 байт) = 00
Filler2 (1 байт) = 00
Filler3 (1 байт) = 00
DataTransferLength = 00 00 00 00 (0d)
TimeOutValue = 00 00 00 05 (5d)
DataBufferOffset = sizeof(SCSI_PASS_THROUGH) (в данном случае ничего не принимаем и буфер не создаём)
SenseInfoOffset = 00 00 00 30 (48d)
Cdb[16] = D8 01 04 00|00 00 00 00|00 00 00 00|00 00 4F 54
Байты, выделенные оранжевым, должны содержать значение таймаута. Принимаются значения от 1000 мс до 10 000 мс. Само значение должно быть приведено к числу, кратному 1000 и уменьшенному в 5 раз. Далее это число в формате U16 делится на старший и младший байты. Старший байт записывается в Cdb[3], младший - в Cdb[4].
7. Получение типа USB-шины удалённой стороны
Эта команда представляет собой более расширенную версию команды 5 (получение идентификатора чипа/контроллера). Она возвращает не только заводскую версию контроллера, заданную при прошивке, но также и тип USB-интерфейса на удалённом ПК: Full Speed (USB 1.1) или High Speed (USB 2.0).
IoControlCode = 4D004 (IOCTL_SCSI_PASS_THROUGH), InBuffer заполняется следующим образом:
Length = 00 2C (44d)
ScsiStatus = 0
PathId = 0
TargetId = 0
Lun = 0
CdbLength = 10 (16d)
SenseInfoLength = 0
DataIn = 01
Filler1 (1 байт) = 00
Filler2 (1 байт) = 00
Filler3 (1 байт) = 00
DataTransferLength = 00 00 00 40 (64d)
TimeOutValue = 00 00 00 05 (5d)
DataBufferOffset = [смещение от начала структуры до заранее выделенного в памяти буфера, например sizeof(SCSI_PASS_THROUGH) + Fillers (8 байт)]
SenseInfoOffset = 00 00 00 30 (48d)
Cdb[16] = F0 00 00 00|00 00 00 00|00 00 00 00|00 00 4F 54
После выполнения команды буфер DataBuffer будет содержать идентификатор контроллера (см. описание команды 5), а также тип USB-шины удалённого компьютера.
Например, выходной буфер DataBuffer после вызова команды содержит следующие данные (64 байта):
00 00 22 08|00 01 30 38|30 38 32 32|01 00 01 00
00 02 00 00|00 20 20 01|00 00 00 00|00 00 00 00
00 00 00 00|00 00 00 00|00 00 00 00|00 00 00 00
00 00 00 00|00 00 00 00|00 00 00 00|00 00 00 00
Тип USB-шины определяется следующим образом:
Код: Выделить всё
AL = Byte(14-й байт, выделенный оранжевым)
if (AL<>0) then begin
if (Byte(21-й байт, выделенный голубым)<>20) then
status:=9 //Full Speed
else
status:=8 //High Speed
end
else begin
if (Byte(22-й байт, выделенный розовым)<>20) then
status:=9 //Full Speed
else
status:=8 //High Speed
end
- USB Bridge Find.vi — выполняет поиск USB-девайса в системе по его VID и PID. Если устройство есть в системе, то SubVI выдаст букву виртуального диска, принадлежащего контроллеру, а также символическое имя устройства. За основу USB Bridge Find.vi взят Get USB Info.vi отсюда.
- USB Bridge Open.vi — открывает сеанс работы с устройством. Используется функция CreateFile из WinAPI, возвращающая хэндл для дальнейших операций чтения/записи.
- USB Bridge Set Timeout.vi — устанавливает величину таймаута для программы (см. описание команды 6).
- USB Bridge Get Status.vi — получает статус удалённого ПК (см. описание команды 3).
- USB Bridge Send Data.vi — отправляет данные на удалённый ПК (см. описание команды 1).
- USB Bridge Receive Data.vi — принимает данные с удалённого ПК (см. описание команды 2).
- USB Bridge Set Bad Status.vi — устанавливает статус ошибки при передаче данных, если принятый пакет "битый" (см. описание команды 4).
- USB Bridge Close.vi — закрывает сеанс работы с устройством. Используется функция CloseHandle из WinAPI.
Логика программы довольно проста: в цикле While непрерывно запрашивается статус удалённой стороны (USB Bridge Get Status.vi). В зависимости от значения статуса выбирается тот или иной кадр Case-структуры согласно описанию команды 3. Программа работает по принципу "аськи": отправленные с одного компа данные сразу же отображаются на другом.
Программа посложнее Send Write - Queue+FileTransfer.vi кроме передачи текстовых сообщений способна передать отдельный файл. Она содержит внутри себя несколько параллельных циклов: цикл коммуникации с удалённой стороной, цикл обработки UI-событий (нажатия кнопок), цикл записи в файл (когда удалённый комп передаёт файл) и цикл чтения из файла (когда удалённый комп принимает файл). Связь между циклами организована через очереди и уведомители. Очереди Data Out и Data In используются для отправки данных на удалённый ПК и приёма данных с него. Уведомитель Stop используется для остановки всех циклов. Уведомитель FilePath используется для передачи пути файла из цикла обработки UI в цикл чтения из файла.
Следует отметить, что в полную силу используется функционал обнаружения и коррекции ошибок в цикле коммуникации. При ошибке приёма данных устанавливается статус ошибки (USB Bridge Set Bad Status.vi), в результате чего удалённая сторона получает статус=9. Это приводит к тому, что последнее отправленное сообщение пересылается удалённым ПК ещё раз. В процессе отправки (USB Bridge Send Data.vi) и приёма (USB Bridge Receive Data.vi) данных используется следующий механизм контроля целостности пакета: в буфере на отправку/приём первые 4 байта - это номер пакета (U32), вторые 4 байта - это номер пакета + 1. Эти два числа также размещены в самом конце буфера (байты, начиная с FFF8 (65528d)). Такой механизм позволяет обнаружить потерю пакета или элементарные повреждения пакета (отсутствие начала/конца, замену полезных данных посторонними). При передаче/приёме файла также проверяется его целостность по алгоритму CRC-64. Конечно, до полноценного файлового менеджера этой программе очень далеко, но с задачей передачи и приёма отдельного файла она неплохо справляется.
Теперь то, что касается собственно кабелей USB Bridge. Данные передаются вполне себе неплохо в обе стороны. Скорость приёма/передачи данных где-то 15-16 МБ/с для кабеля на чипе 2208NF и около 17-18 МБ/с для кабеля на чипе 2108. Использовалась пара ПК с W7 Ultimate x64 и W8.1 Professional x64. В качестве иллюстрации могу приложить вот такие картинки.
Кабель DL-USB015 на чипе 2108:
Компьютер 1: Компьютер 2: Кабель UANC22V7 на чипе 2208NF:
Компьютер 1: Компьютер 2: Однако, стоит заметить, что кабель UANC22V7 показал себя хуже в работе, нежели его предшественник на 2108. Выражается это в том, что время от времени возникают "провалы" в передаче данных (см. картинки): контроллер по какой-то причине не воспринимает команду передачи пакета и встаёт как вкопанный. Через несколько секунд возникает таймаут, пакет теряется. Происходит пересылка пакета и возобновление передачи данных. На файл весом в 1-1.5 ГБ такое может произойти 2-3 раза. Событие нечастое, кроме того, не страшное, т.к. программно предусмотрен механизм коррекции потерянных пакетов. Это влияет лишь на время передачи - оно увеличивается на число тайм-аутов в процессе передачи файла. Скорее всего, это аппаратная проблема (или особенность), т.к. на кабелях с другими чипами такого я не наблюдал.
Второй недостаток кабеля на 2208NF - если воткнуть оба конца кабеля в оба компа и включить компы почти одновременно, то на одном из них девайс может никак вообще не определиться или определиться как неизвестное устройство. Похоже, это тоже аппаратная "фича". Лечится только извлечением конца кабеля и включением обратно. Для рядового юзера это мелочи, в принципе, но для того, кто собирается закладывать такой кабель в систему автоматизации, это довольно неприятно, так как там иной раз нет возможности переткнуть кабель. В таком случае можно взять пару USB-реле с обоих концов кабеля и управлять ими программно (пример подобного реле - Cleware USB Connect). Опять же, у кабелей на 2108 или на Prolific такого поведения не замечал.
Помимо прочего, на кабелях с чипами 2108 есть светодиод, моргающий при передаче данных (в отличие от кабеля на базе 2208NF). Мелочь, а приятно.
В этом плане не совсем понятно, почему чип 2208NF позиционируется производителем как более новый по сравнению с 2108. Казалось бы, более современное устройство должно быть более совершенным. Видимо, это не всегда так. Или же здесь задействован какой-то третий фактор...
В качестве заключения могу сказать, что кабели USB Bridge вполне могут служить альтернативой связи через обычные сетевые карты. Конечно, не без ограничений: максимальная длина кабеля USB - 5 метров, скорость ниже на порядок по сравнению с Gigabit-картами, топология "точка-точка" и только. Зато есть и плюс: простота организации связи - не нужно прописывать никакие настройки, воткнул кабель в оба порта и всё работает. Вывод таков: с задачей обмена файлами или текстовыми данными на небольшом расстоянии кабель USB Bridge справляется на 100%.
P.S.: Тему, в идеале, хотел бы дополнить описанием работы с кабелями стандарта Windows Easy Transfer, а также аналогичным небольшим обзором. Будет зависеть от времени, сил и желания.