Я не буду говорить ни о USB2 ни тем более о USB3. Это для меня в пока основном «высокие материи». Давайте поговорим о старом добром USB1.1.
И пожалуйста не смейтесь. На самом деле, если Вы поищите в русскоязычном интернете техническую спецификацию на USB1.1 (я уже не говорю про USB2, а в USB3 наверное Intel вообще не публикует деталей), то вряд ли Вы найдете там что нибудь стоящее. В основном у нас можно найти общие сведения и общие фразы. Нас же, как разработчиков аппаратуры интересуют технические детали стандарта и возможность реализации устройств поддерживающих его.
Еще конкретнее вопрос стоит так: сможем ли мы подключить платку Марсоход через USB к компьютеру?
Естественно прежде всего нужно ознакомиться со спецификацией USB1.1. Теоретически нужно посетить сайт www.usb.org и взять там все, что нужно. Однако не все так просто. Взять оттуда скорее всего у Вас просто так не получится. Спецификации они почему-то продают http://www.usb.org/developers/estoreinfo/USB_product_order_form.pdf да и найти нужное в дебрях этого сайта не просто.
Теперь спецификацию на USB1.1 можно взять на нашем сайте!
Здесь же, в этой статье, я попробую описать основные принципы работы USB1.1.
Сразу сделаю оговорку: во-первых, я не очень люблю USB1.1, слишком уж замудрено он придуман (мне кажется можно было сделать проще), во-вторых, естественно, в маленькой статье невозможно описать все. Прошу заметить, что в файле спецификации USB1.1 целых 327 ужасных страниц текста и картинок. Если у Вас есть мысль напечатать это на принтере, то не советую. Реально полезной информации там не очень много, зато «много букав».
Итак, приступим.
В разъеме USB1.1 всего четыре контакта:
- Земля (4, обычно черный провод в кабеле);
- DP (3, обычно зеленый провод в кабеле);
- DM (2, обычно белый провод в кабеле);
- +5V (1, обычно красный провод в кабеле).
Таким образом, мы видим всего 2 сигнала для обмена данными между хостом (компьютером) и подключаемым устройством. Эти сигналы DP и DM (иногда их обозначают D+ и D-) – это дифференциальная пара. Сигнал передается по ним в противофазе. Это позволяет на приемном конце бороться с помехами.
Как хост определяет, что подключено новое устройство? Довольно просто. На стороне хоста обе линии DP и DM притянуты к GND через резисторы 15кОм. Контроллер хоста проверяет состояние этих линий. Если на обеих линиях ноль, то это значит, что ничего не подключено. На стороне подключаемого устройства один из сигналов притянут через резистор 1,5кОм к напряжению питания. Таким образом, если устройство подключено, то одна из линий либо DP либо DM поднимается в состояние «единица» и хост контроллер видит, что подключено новое устройство.
Устройства для USB1.1 бывают двух типов: полноскоростные (full speed) и низкоскоростные (low speed). Посмотрите на эти две картинки:
Таким образом, полноскоростные устройства имеют подтягивающий резистор на +5В для сигнала D+, а низкоскоростные устройства – для сигнала D-.
Частота передачи данных для полноскоростных устройств 12МГц, а для низкоскоростных 1,5МГц. Низкоскоростное устройство принимает и посылает данные до 8 байт длиной. Высокоскоростное устройство может посылать или принимать до 64 байт данных.
Особо следует отметить, что эти две линии D+ и D- служат для передачи данных в обе стороны. Как же разрешаются конфликты на линиях, если оба и хост и подключенное устройство захотят передавать данные? Такая ситуация не должна случаться в принципе. Дело в том, что передача полностью управляется хост контроллером компьютера. Если хост контроллера должен прочитать данные с устройства, то он посылает соответствующую команду и переключается в режим приема, а затем ждет пакета от подключенного устройства.
Хост контроллер компьютера ведет опрос подключенных устройств каждую миллисекунду – этот временной промежуток называется фреймом. В начале каждого фрейма хост контроллер посылает специальный SOF (Start Of Frame) пакет для полноскоростных подключенных устройств или SE0 для низкоскоростных устройств.
Если устройство не получает SOF или SE0 некоторое время (несколько фреймов), то это означает, что оно должно уйти в спячку (suspend) и по возможности снизить энергопотребление.
Отдельно нужно обратить внимание на состояние SE0. Это состояние, когда обе линии DP и DM находятся «в нуле». Это состояние используется в 3-х случаях.
Во-первых, после подключения устройства программное обеспечение хоста дает ему команду «сброс» (Reset). Хост опускает обе линии DP и DM в «ноль» на время большее 10мс. Подключенное устройство должно воспринять это действие как общий «сброс».
Во-вторых, как я уже сказал, для низкоскоростных утройств каждый фрейм начинается с состояния SE0 (обе линии DP и DM в нуле) длительностью 2 такта от 1.5МГц.
В-третьих, каждый посланый пакет в любую сторону, от хоста к устройству или наоборот, всегда заканчивается состоянием EOP (End Of Packet), и этот EOP - это тот же самый SE0 – обе линии DP и DM в нуле на протяжении времени 2 бит передачи данных. Для полноскоростных устройств это 2 такта от 12МГц. Для низкоскоростных устройств это 2 такта от 1,5МГц.
Все данные в любую сторону оформлены в виде пакетов. Давайте посмотрим как они выглядят на физическом уровне. Лучше всего изучать по картинкам, а они в спецификации USB1.1 какие-то не очень понятные. Я нарисовал свою картинку.
Здесь видно, что подключено низкоскоростное устройство, так как фрейм начинается с SE0 – по времени 2 бита DP и DM находятся в нуле.
Дальше видно 3 пакета: хост посылает пакет SETUP, посылает пакет DATA0 и получает от устройства пакет ACK. Каждый пакет всегда начинается со специального символа SYN, его значение 0x80. Байт передается младшими битами вперед. Кодировка несколько странная. Каждый нулевой бит кодируется изменением сигнала DP/DM на противоположный. Каждый единичный бит состояние линий не изменяется. Однако есть исключение – если в передаваемом потоке окажется подряд шесть единиц, то состояние линий DP/DM принудительно меняется на противоположный. Этот нулевой бит должен быть удален на приемном конце при приеме пакета. Этот алгоритм называется «bit stuffing». Обратите внимание на завершение пакетов состоянием SE0 – EOP (End Of Packet). Интервал между пакетами должен быть не менее времени 2 бит, на практике обычно больше. Это был физический уровень связи.
Рассмотрим уровень протокола. Здесь все гораздо более запутанно. Без поллитра не разобраться (конечно если Вам больше восемнадцати). Для чего разработчики стандарта сделали все это так сложно я и сам не пойму.
Существуют пакеты нескольких типов. В каждом типе есть под-типы (это мое определение, в спецификации так не говорят).
Итак вот таблица:
Тип пакета | Идентификатор пакета PID в шестнадцатеричном виде | Описание |
Token OUT | 0xE1 | Используется для передачи адреса устройства и номера канала (endpoint) во время транзакции передачи данных от хоста к уствойству |
Token IN | 0x69 | Используется для передачи адреса устройства и номера канала (endpoint) во время транзакции передачи данных от устройства к хосту |
Token SETUP | 0x2D | Используется для передачи адреса устройства и номера канала (endpoint) во время транзакции передачи данных от хоста к уствойству к специальному управляющему каналу (control pipe) |
Token SOF | 0xA5 | Маркер начала фрейма и номер фрейма |
Data DATA0 | 0xC3 | Используется для передачи четного пакета данных |
Data DATA1 | 0x4B | Используется для передачи нечетного пакета данных |
Handshake ACK | 0xD2 | Подтверждение о приеме данных |
Handshake NAK | 0x5A | Либо приемник не может принят данных либо передатчик не может послать |
Handshake STALL | 0x1E | Останов endpoint или служебный запрос не поддерживается |
Special PRE | 0x3C | Посылается USB хабу, когда требуется переключить скорость в низкоскоростную |
Рассмотрим формат основных пакетов: token, data, handshake.
Не забудьте, что в линии USB пакеты посылаются начиная с символа SYN 0х80, а уж затем вот эти пакеты и завершаются они EOP (2 такта линии DP и DM в нуле).
Для token и data еще нужно посчитать контрольные суммы. Их можно считать вот так (написано на языке C):
//функция принимает двухбайтовое слово где адрес и номер канала, считает CRC5 и //вписывает контрольную сумму прямо в нужное место этого слова
USHORT CalcCrc5ForUsbTokenPacket(USHORT a)
{
ULONG b = 0x1f;
USHORT d = a;
for(int i=0; i<11; i++)
{
if((d^b)&1)
{
b >>= 1;
b ^= 0x14;
}
else
b >>= 1;
d >>= 1;
}
b ^= 0xffffffff;
b <<= 11;
a |= b;
return (USHORT)a;
}
//функция принимает указатель на пакет данных (без PID) и длину пакета
//возвращает CRC16
USHORT CalcCrc16ForUsbDataPacket(char *pData, int len)
{
USHORT b = 0xFFFF;
for(int i=0; i<len; i++)
{
char a = *pData++;
for(int j=0; j<8; j++)
{
if((a^b)&1)
{
b >>= 1;
b ^= 0xa001;
}
else
b >>= 1;
a >>= 1;
}
}
b ^= 0xffffffff;
return (USHORT)b;
}
Ну а на последок, чтобы Вы оценили все «прелесть» диалога между хостом (компьютером) и подключенным устройством посмотрите на следующую картинку:
Это снимок экрана с программы USB Tracker – устройства позволяющего записывать и анализировать весь трафик между хостом и устройством.
На самом деле, сделать что нибудь с USB не имея подобного инструмента практически нереально. У нас он есть и мы попробуем реализовать простую функцию USB в плате Марсоход. А что у нас получится возможно скоро Вы узнаете.
Подробнее...