Pic18 Lab. Experiment #5. USB

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

Я во время изучения вопроса если честно неплохо так удивился, насколько умные люди тогда придумали как это сорганизовать и поставить на рельсы, а еще больше удивился когда увидел расценки на использование USB вашей компанией – Vendor ID стоит 5к долларов в год, хотите лого еще 6к надо бы, а ну и раз вы наше лого используете пришлите ка нам ваш девайс и мы его тестанем за н сумму денег еще… А список компаний которые используют Vendor ID можно найти на сайте www.usb.org (USB Implementers Forum – USB-IF), можно еще посчитать сколько там компаний и прикинуть прибыль этого сообщества чисто на этой подписке (читай охренеть). Но как я уже заметил люди это придумали очень умные, подсадили всех на иглу и грамотно этим пользуются.

Продолжаем дальше подсаживаться на иглу, добавляя USB и в свои проекты 🙂

Я не буду скидывать вообще кучу всей информации, просто приведу сначала сводную информацию которая мне показалось важной для понимания устройства USB (ну и дебага естественно), а потом сделаем два эксперимента на этом базисе.

usb_title

Как я уже писал, большинство информации можно найти просто на сайте usb.org, но я постараюсь как то суммировать основные моменты для понимания разработчика (в том числе и моего понимания и устаканивания в голове – никоим образом ни на что не претендую, я рыл инфу чисто под себя, если разбираться в этом досконально можно зашиться по самые уши, просто чертова куча работы, интерфейсов, разных типов и алгоритмов).

Хочу уточнить что я смотрю исключительно на USB 2.0, скорость которого ограничена 480 Мб/с. Другие стандарты это другая история (пока что). Еще раз стандарт USB придуман умными дядьками и хоть они и гребут за него деньги, но они при этом сделали оооочень большую работу, практически все что можно описано в документации и оттуда можно достать очень много информации. Собственно поэтому я и не буду растекаться по бумаге, а просто буду приводить важные вещи для меня за которые зацеплялись мои глаза. Кроме того, не менее умные дяденьки из компании Cypress сделали другую работу с сжали документацию с USB-IF до вразумительных размеров, плюс переписали это более понятным языком, так что даже я смог разобраться – рекомендую изучать по их мануалу в общем. А теперь я попытаюсь сжать ее еще больше, и применить полученные знания для практических целей.

Итак по-порядку

Общее описание

Смотрим картинку из заглавия – Любую систему с усб устройством можно представить в виде хост – устройство. Для удобства не рассматриваем сложные случаи мультихостов, не рассматриваем хаб (хаб отдельный класс USB устройств, вообще говоря для системы с большего можно поделить все устройства на хаб и не хаб). Ок, тут вроде чисто – в системе может быть один хост и куча девайсов (куча кстати имеет ограничение вроде в 127 девайсов, плюс ограниченное колво ветвей хабов – опять же если заглянуть в любой док по усб то можно заметить представление усб от хоста на вершине до основания разрезанной на слайсы, всего может быть 7 таких слайсов, что по сути ограничивает уровень подключения хабов до 5 максимум.

Итак, возвращаясь снова к началу, для удобства восприятия рекомендуется рассматривать систему в паре хост-девайс, и не расфокусироваться на другие случаи. Дальнейший уровень абстракции хост с девайсом связывается в помощью каналов данных (труб – pipes). Эти трубы входят/выходят в специальные приемники девайса – endpoints. Всегда существует как минимум один ендпоинт и одна труба – это control pipe, используется для конфигурации, комманд, запросов и т.д. Двунаправленная, подключается к endpoint 0 (который к слову всегда есть в девайсе).

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

Хард часть

usb_cable

Кабель USB состоит из 4 проводов: земли, питания и дифферинциальной шины данных. Кстати интересная сноска – в то время как питание это 5В, данные идут до 3.3В.

Думал не вставлять изображений портов, но таки это полезно и я часто гуглю это, поэтому закину сюда тоже (картинка содрана с сайта Cypress):

usb_connectors

Оттуда же и пинаут:

usb_pinout

УСБ устройства используют специальные резисторы подтяжки, с помощью них хост понимает на какой скорости работает устройство:

USB_Speed

Полноскоростной режим определяется подтяжкой к 3.3 вольтам на D+, низкоскоростной подтяжкой к 3.3 вольтам ан D-.

Для режима высоких скоростей девайс начинает с подтяжкой на D+ (full-speed), а затем при подключении шлет определенный сиквенс и ожидает респонса, поддерживает ли хаб/хост высокую скорость, если поддерживает тогда резистор убирается. Да, еще, если подтяжки нет вообще хост с вашим девайсом не станет говорить.

По питанию думаю все и так знают что максимум 500мА для 2.0 версии, собственно есть три класса устройств: высокое потребление (500мА), низкое потребление (100мА) и с собственным источником питания. Все устройства должны обязательно при подключении потреблять максимум 100мА, потом команда SET_CONFIGURATION инициорована и устройство может быть сконфигурировано на 500мА. Гибридные установки описывать не буду, пока не так важно.

Еще – длина кабеля официально поддерживаемая USB-IF 5 метров для high/full speed, при этом диф. шины должны быть в виде витой пары, тогда как для low speed 3 метра это максимум и данные не обязательно должны быть витой парой. Это к слову про всякие супер длинные кабеля продающиеся в разных местах. Официально длинный кабель можно получить только используя промежуточные активные звенья.

Модуляция

В системе USB используется NRZI + Bit stuffing.

Чистый NRZI – 0 кодируется изменением состояния, 1 кодируется сигналом без изменений:

usb_nrzi

Bit stuffing это просто добавление 0 в том случае если было 7 единиц подряд. Делается для того чтобы все еще была возможность синхронизировать клоки и данные принимались правильно (если будет много единиц подряд система CDR не будет иметь достаточного количества переходов).

Еще, важное замечание LSB – идет первым, MSB последним. То бишь число 11 (dec) будет выталкиваться так – 11010000.

USB коммуницирует посредством специальных стейтов:

  • Differential 0/ Differential 1 – самые логичные и понятные стейты, 0 когда D- высокий уровень, D+ низкий уровень. 1 когда D+ высокий уровень, D- низкий уровень
  • J state/ K state – в зависимости от скорости имеет разные определения, для фул спида J state = Diff 1, K state = Diff 0, для лоу спид соответственно наоборот
  • SE0 – single ended 0 – когда обе шины данных на низком уровне. Означает либо ресет, либо дисконнект либо конец пакета (EOP)
  • SE1 – single ended 1 – обе шины на высоком уровне. Событие исключение, если оно произошло что-то идет не так.
  • Idle – событие перед посылкой пакетов, или после, в-общем то просто событие когда ничего не происходит, и опять таки зависит от скорости – фул спид имеет подтяжку на D+, поэтому для этого режима на д+ высокий, на д- низкий, а для лоу спид все наоборот
  • Resume – используется для выведения девайса из спящего режима, инициируется с помощью K-state.
  • Start of packet (SOP) – старт пакета, используется перед любой посылкой пакетов, характеризуется переходом с Idle в K-state
  • End of packet (EOP) – конец пакета, индиктор конца передачи. Характеризиуется SE0 держится время минимум двух бит, затем после него следует J state в течении 1 бита (временного отрезка)
  • Reset – сброс, SE0 держится минимум 10мс, при этом после 2.5 мс девайс уже может начинать подхотовку и сваливаться в сброс
  • Keep alive – используется только в лоу спид, они шлют EOP каждую 1 мс, чтобы предовращать девайс от сваливания в спячку

Для простоты и удобства содрал табличку с USB-IF (www.usb.org):

USB_states.PNG

USB скорости

Собсно для USB 2.0 как уже упоминалось есть три типа скорости работы:

  • Низкоскоростные девайсы – 1.5 Мб/с
  • Полноскоростные девайсы – 12 Мб/с
  • Высокоскоростные девайсы – 480 Мб/с

Теперь о выборе опорной частоты – понятно что на самой большой скорости будет и low-speed работать, но соответственно если у вас лоу power, то потребление будет увеличиваться с ростом частоты.

Список ок опорных частот (снова помните, я не истина в последней инстанции, для нашего обычного уровня абстракции этого достаточно, я не собираюсь делать глубокий дебаг(пока что)):

  • 6 МГц – ок для низкоскоростных
  • 12 МГц – ок для полноскоростных (но надо использовать умножитель на 4 вроде как)
  • 24 МГц – ок для высокоростных (конфигурируется с умножителем на 20)

Кстати многи микроконтроллеры семейства Microchip с USB поддерживают Active Clock Tuning – они используют хост для подстройки клока до необходимой погрешности, таким образом можно не использовать внешний опорный генератор.

Процессы при подключении USB девайса (dynamic detection/enumeration/configuration)

Сначала идет стадия dynamic detection:

  1. Как только девайс подключен, на данный момент девайс может потреблять максимум 100мА.
  2. Дальше хаб определяет что подключен правильный девайс мониторя д+ д-, т.к. хост имеет подтяжки на землю, а девайс должен иметь хоть одну подтяжку на питание
  3. (Enumeration пошло)хаб дает знать хосту что подключен новый девайс и хост запрашивает изучение больше инфы про девайс (GET_PORT_STATUS)
  4. Теперь хост определяет скорость, сначала просто определяется низкая или фул спид (второй запрос GET_PORT_STATUS)
  5. Инициируется ресет для нового подключенного девайса
  6. Определяется поддерживает ли девайс высокую скорость прямо во время ресета (шлется/получается специальные KJ паттерны) (эту часть я не совсем пониманию, т.к. ресет это оба нуля на обоих шинах данных, как он может K J слать я хз)
  7. Потом хост определяет все ли еще девайс в ресете, если все еще в ресете, то хост ждет пока девайс не выйдет из ресета и как только он выйдет из него, хост может общаться с девайсом по адресу 00h – это дефолтный адрес для любого нового устройства, поэтому если к хосту подключается несколько устройств одновременно, они не будут все сразу определены, последовательно, по одному.
  8. Хост начинает изучать больше информации о девайсе, шлется команда GET_DESCRIPTOR, а девайс соответственно шлет дескрипторы (о них чуть позже)
  9. Назначается адрес девайсу (SET_ADDRESS), теперь вся коммуникации будет происходить по этому новому адресу
  10. (Configuration пошла) Снова шлется GET_DESCRIPTOR и девайс теперь вываливает все дескрипторы сразу
  11. Хост ищет драйвера
  12. Устанавливается спец конфигурация для устройства  (или спец конфигурации для устройств которые поддерживает несколько их)
  13. Устройство сконфигурировано, максимальный ток берется из дескрипторов конфигурации

USB дескрипторы

В прошлой секции пункт 10 конфигурации упоминал что девайс выдает все дескрипторы. В них хранится все конфиги и вся необходимая инфа о режимах работы. Бессовестно сдираю картинку с Cypress:

usb_descriptors

Картинка отображает разные варианты конфигов. Устройство может иметь как один конфиг и один интерфейс так и конфиг и плюс 2 интерфейса (например какая нибудь усб гарнитурка имеет два интерфейса – один для аудио, второй для регулировок).

Во главе диаграммы стоит Device descriptor, тут и лежит все самое главное в общем то, пройдемся по всем битам отсюда:

OffsetПеременнаяРазмер (байт)Описание
0bLength1Длина дескриптора (18 байт)
1bDescriptorType1Тип дескриптора – DEVICE (01h)
2bcdUSB2USB ревизия используемая в девайсе (для 2.0 это будет выглядеть 0x0200)
4bDeviceClass1Девайса класс
5bDeviceSubClass1Подкласс
6bDeviceProtocol1И протокол
7bMaxPacketSize01Максимальный размер пакета для endpoint0 (8/16/32/64 байта)
8idVendor2Vendor id (VID) получаем за денюжку для своего девайса
10idProduct2Product id (PID) назначается разработчиком
12bcdDevice2Версия релиза
14iManufacturer1Индекс на строку производителя
15iProduct1Индекс на строку с описанием продукта
16iSerialNumber1Индекс на строку с серийным номером
17bNumConfiguration1Количество поддерживаемых конфигураций

Потом идет Configuration Descriptor, здесь у нас инфа про колво интерфейсов, режимы потребления тока и ограничение его же.

OffsetПеременнаяРазмер (байт)Описание
0bLength1Длина дескриптора (9 байт)
1bDescriptorType1Тип дескриптора – CONFIGURATION (02h)
2wTotalLength2Общая длина (пока хз что здесь имеется ввиду)
4bNumInterfaces1Колво интерфейсов в данном конфиге
5bConfigurationValue1Число этого конфига (будет выбираться с помощью SET_CONFIGURATION)
6iConfiguration1Индекс строки описывающей эту конфигурацию
7bmAttributes1Режимы потребления тока
8bMaxPower1Максимальный ток потребления (разрешение 2 мА)

Дальше у нас идет интерфейс, тут у нас и количество эндпоинтов и класс устройства.

OffsetПеременнаяРазмер (байт)Описание
0bLength1Длина дескриптора (9 байт)
1bDescriptorType1Тип дескриптора – INTERFACE (04h)
2bInterfaceNumber1непонятно (zero based index of the interface)
3bAlternateSetting1Значение альтернативной настройки (что?)
4bNumEndpoints1Количество эндпоинтов этого конфига (не включая endpoint0)
5bInterfaceClass1Класс интерфейса
6bInterfaceSubClass1Подкласс интерфейса
7bInterfaceProtocol1Протокол интерфейса
8iInterface1Индекс строки описывающей этот интерфейс

Дальше дескриптор эндпоинта

OffsetПеременнаяРазмер (байт)Описание
0bLength1Длина дескриптора (7 байт)
1bDescriptorType1Тип дескриптора – ENDPOINT (05h)
2bEndpointAddress1bit 3..0: номер эндпоинта, bit 6..4 – зарезервированы, нули, bit 7 направление (0 OUT)
3bmAttributes1Тип передачи, тип синхронизации, тип эндпоинта
4wMaxPacketSize2Maximum packet size
6bInterval1Интервал в миллисекундах

Ну и напоследок этой секции надо обязательно пройтись по такой важной секции конфигурации как класс устройства.

Выбор класса достаточно важное занятие, так как от класса зависит максимальная полоса плюс многие классы имеют нативную поддержку в операционках, а значит что не нужны будут конкретные драйвера.

ClassНазначениеОписаниеПример
00hДевайсБез спецификацииУстройство без класса, дескриптор интерфейса определяет нужный драйвер
01hИнтерфейсАудиоКолонка, микрофон, звуковая карта
02hДевайс + ИнтерфейсКоммуникации и CDC контрольМодем, вай фай адаптер
03hИнтерфейсHuman Interface Devicce (HID)клавиатура, мышь
05hИнтерфейсPhysical Interface Device (PID)джойстик с обратной связью
06hИнтерфейсРабота с изображениямикамера, сканер
07hИнтерфейсПринтерПринтер, cnc станки
08hИнтерфейсMass Storageфлешки, карты памяти
09hДевайсUsb hubхабы
0AhИнтерфейсCDC-DataНе понял точно зачем это дублирование 2, вроде как этот класс можно использовать после подтверждения на USB-IF
0BhИнтерфейсSmart cardкард ридеры
0DhИнтерфейсСекьюрный девайссканер отпечатков пальцев
0EhИнтерфейсВидеоВебкамера
0FhИнтерфейсПерсональный мед девайсМонитор сердцебиения, глюкометр
DChДевайс + ИнтерфейсUSB диагностический девайсДевайс использующийся USB-IF для их сертификации
E0hИнтерфейсБеспроводной контроллерБлютуз адаптер
EfhДевайс + ИнтерфейсMisc
FEhИнтерфейсСпец назначениеIrDa мост
FFhДевайс + ИнтерфейсVendor specificИндикатор того что нужно отдельный драйвер от вендора

Больше инфы можно найти непосредственно на USB-IF сайте.

Сфера наших интересов это всего лишь два класса – HID (03h) и CDC (02h). Первый весьма удобный тип, не требует драйверов других, но есть одно большое но – надо получить VID/PID, а это значит надо отслюнявливать подписку. Но я все равно попытаюсь поэкспереминтировать с ним.

Второй, может использоваться для эмуляции последовательного порта, соответственно софт может быть очень простым для хост стороны и если он уже был заточен (например под какойнить FTDI UART конвертер с виртуальным ком портом, то миграция может пройти достаточно легко. Кстати, начиная с Windows 10 появилась нативная поддержка драйверов CDC класса.

USB коммуникация

Последняя секция нудной теории.

Снова сдираем картинку с Cypress, с точки зрения времени все передачи в усб выглядят как фреймы:

usb_frames

Каждый фрейм состоит из стартового пакета SOF, транзакции или транзакций. Транзакции в свою очередь состоят из подпакетов, каждый из них начинается со специального SYNC паттерна:

sync_pattern

И заканчивается EOP паттерном. В транзакции как минимум должен быть токен пакет, опционально пакеты данных и хендшейки (ack).

Каждый пакет можно представить в следующем условном виде:

usb_packet

PID – Packet ID, тип пакета – IN/OUT/SETUP/SOF

ADDR – опционально – адрес девайса

EP – номер эндпоинта

Payload data – опциональные данные (от 0 до 1023 байт)

CRC – Опциональный CRC чек

Все поля кроме PID опциональны. Токены определяют направление и назначение и всегда инициируются хостом.

SOF используется для временных отметок фреймов (снова сдираем с мануала Cypress):

usb_frame_timing

1 милисекунда отбивает фреймы для фул скорости, для high speed это время равно 125 мкс.

За токенами следуют пакеты с данными, их композиция выглядит так:

usb_data_packets

DATA1 переключается DATA0 и наоборот, этот тогглинг служит дополнительным признаком передачи данных без ошибок, в случае если они остаются без изменения.

Замыкает пакет хендшейк, который может быть таким:

ACK – Передача завершена успешно

NAK – Неудачная передача

STALL – Ошибка передачи, шлется девайсом

NYET – Девайс еще не готов для следующего пакета (только высокая сокрость)

Если хост шлет данные, то пакеты хендшейк шлются девайсом, если же девайс шлет данные то хендшейк идет от хоста.

Все короче, хватит нудить теорией, идем непосредственно к делу.

Эксперимент: С помощью CDC/HID класса поморгать светодиодами, а потом прокачать данные серьезнее – например, считать показания АЦП.

Что у нас имеется в наличии: PIC18F14K50, копия Pickit3, в пути еще идет копия salea logic analyzer.

Повторюсь, поднимать USB с нуля это очень большой кусок работы, очень очень большой, просто охереть какой большой 🙂 Это не уарт и не обычные одно/двухпроводные интерфейсы, это целый стек поэтому писать самим это нужно быть отважным человеком.

Идеальный путь, по которому почти все статьи которые я находил – скачать официальную MLA либу с сайта microchip (Microchip Librarires for Application), а еще можно использовать модный тул от микрочипа  MCC (MPLAB Code Configurator), который раньше вроде как усб не поддерживал и XC8, но какой то он не спортивный чтоли. Ладно попробуем по порядку.

Начнем с MLA, скачиваем всю либу и открываем проект из примеров, я взял вот этот для начала – microchip\mla\v2018_11_26\apps\usb\device\cdc_basic\firmware\pic18f46j50_pim.x

Сразу же встречаемся с проблемой – там просто туча разных либ, хедеров и т.д. Как правильно перенести это в свой проект чтобы не упустить ни одного линка – надо его упаковать прямо в MplabX:

usb_cdc_mplabx_packaging

Эта комманда упакует весь архив в одну папку с сохранением всех связей.

Теперь распаковываем его, а потом меняем девайс на наш PIC18F14K50 и получаем конечно же фейл, законы Мерфи да. 4 ошибки, ну да камень другой, конфиг может не совпадать (и будет не совпадать) да и другие аспекты есть, идем по порядку:

Ошибка 1 – S2_PORT в оригинале подключен к RB2, а в PIC18F14K50 в принципе нету такого IO, меняем например на RB6.

Ошибка 2 – LEDы подключены к порту E, опять таки – нету такого в нашем чипе.

Ошибка 3 – ADCONbits регистр, такого нету в нашем чипе, ради простоты я просто рубанул весь аналоговый обвес ANSEL = 0; ANSELH = 0;

Ошибка 4 – у нашего чипа другая конфигурация PLL, для PIC18F14K50 софтварно управлять плл можно только для HSINTOSC = 8MHz, а для usb модуля это не позволено даташитом, поэтому идем в system.c файл и добавляем такое в конфигурацию:
#pragma config FOSC = HS 
#pragma config PLLEN = 1

Заодно все левые конфигурации там же повырубаем нафиг.

Теперь откомпилировалось все без проблем, пробуем сначала протеус симульнуть, у них оказывается поддерживается виртуальный усб порт уже, до чего техника дошла то :).

Схематика в протеусе

Кстати момент один заметил нехороший – для этого чипа этот код занял 67%, стремновато что мало места остается как то, посмотрим может попытаюсь подчистить его под исключительно мои нужды.

Так, ладно пробуем протеус для начала, этот пример делает следующее – мигает светодиодиком в начале, потом при подключении к нужному ком порту терминалом (а он появится как новый) при нажатии кнопки, код делает ascii code + 1 и возвращает значение суммы обратно. Таким образом, если нажать 1 прилетит 2, если нажать a прилетит b ну и т.д.

Если нажать кнопку то выскочит сообщение заранее подготовленное. Пробуем…

Результат работы симулятора

Работает, офигеть 🙂 Если честно на протеус не было у меня никаких надежд.

Запустил реальный чип – фиг там, не сработало, диод постоянно мигал, было ощущение что усб порт не может достаточное колво тока выдать. Ковыряемся дальше..

Явно какое то дерьмо с опорником, ковыряем дальше. …Прошло 5 дней, серьезно, обеспечил себе бляха хобби с полною занятостью на каждый вечер.

Так, с чего бы начать, чтобы так сказать было меньше матов, пожалуй с меня самого, сначала немножко самобичевания – я взял проект PIC18F46.. потому что я думал что в mla либе нету pic18f14k50. Но вот как обычно, проблема заставила прочитать и даташиты и теорию пару раз, да и попытаться найти другие варианты девборда. Вот последнее то меня и вывело на путь истины, я нашел что от Microchip есть один devboard с камнем PIC18F14K50 на борту, стоит он 50 баксов, но он зараза древний уже, там max232 до сих пор используется, да и сайт рекомендует покупать более новые продукты, но вот название его я запомнил low pin count development board. И вот, на очередном витке ада, выдергиваний проводов туда обратно и 100500 перепрошивки и перекидывания кода туда сюда я случайно снова попал в папку cdc_basic, и сюрприз сюрприз я увидел low_pin_count_usb_development_kit_pic18f14k50.x. Жесть блин, как я его просмотрел изначально я до сих пор понять не могу, не носить мне малиновых штанов в общем.

Вторыми на очереди бичевания я выставляю китайские девайсы, вот зарекался же связываться но нет, снова повелся на цену в 3 бакса и купил себе такое чудо:

Китайская чудо плата

Идея замечательная, есть usb, есть пара кнопок, есть потенциометр подключенный к АЦП, а еще два светодиодика. Мечта а не платка, ровно то что мне нужно, красота вроде как. Вот цена в 3 доллара очень подозрительная, с учетом того что на digikey pic18f14k50 стоит 2.4$, есть ооочень крупные сомнения в том что здесь оригинальный микроконтроллер.

Где-то на третий день мучений я начал копать плату, она стала моим подозреваемым №1. Пошла веселая история очередная – я попросил у продавца схематику и топологию платы, они прислали, но тоже какую то другую, старую и не совпадающую с текущей платой. В это же время я, тыкаясь мультиметром, понял что на плате нету коннекта vdd пина с vbus, vbus напрямую заводился на vusb, а vdd болтается в воздухе – мк запитывается тупо через защитные диоды, а vusb ldo выход заглушен этим VBUS. Я когда это увидел, долго не мог поверить как так можно.

Написал продавцу опять, что у них косяк с платой – пришел ответ, не может быть, вы что, у нас проверки проходят каждая плата, и прислали какую то новую топологию и схематику. На ней на дорожке от усб порта торчит виашка, получается что идет порт, от него сразу емкость фильтрующая, потом сопля – виашка – сопля и только тогда мк. Рукалицо, разводка даже в этом случае просто капец как далека от идеальной. Но фиг с ним, если эта виашка есть то хотя бы электрический коннект должен быть – а нету этой виашки, у них даже на сайте с фоткой продукта ее нету блина. Пишу снова так и так, нету этой виашки у вас вообще, даже на вашем сайте – ответ, мы проверили наши стоки, где то есть виашка а где то нету, вам плохие платы попали, бляха а сначала ведь пели что все все пре-все проверяется у нас. Короче, захотел дешевого, получи напильник и паяльник в руки и фигач костыли.

Едем дальше, третий в очереди на бичевание – компания Microchip, чего то у них случилось странное после ввода MplabX мне кажется, возможно глобальный аутсорс кучи ресурсов налево. Программа для загрузки hex под pickit теперь оказывается не поддерживается, вместо нее теперь недоделок Mplab X IPE, чтобы в нем какие то настройки потыкать надо во-первых перейти в режим advanced mode, введя пароль. Ну да ладно вроде как, но при подключении PIC18F14K50 надо провайдить питание, а в этом куске суперсофта при подключении Pickit3 все поля пропадают наглухо. Оказалось что это уже известно давно, и проблема именно в последней версии MplabX и этого X IPE, чтобы выставить нужные настройки надо сначала подключить пиккит, потом отключить пиккит, потом выставить нужные настройки, потом снова подключить пиккит, занавес.

Четвертый в очереди снова я – Получив код от PIC18F14K50 я откомпилировал его и завел плату сразу, потом начал еще один долгий вечер переноса разборки почему же мой код не работал. Законы Мерфи сработали снова против меня, из всех возможных проектов примеров я выбрал тот который имеет совсем другую структуры чипа. Оказалось надо включить файлик fixed_address_memory.h, в нем определены указатели на области USB RAM для конкретного чипа (PIC18F14K50) и без них ни хрена не работает.

И пруф, что у меня все случилось, для фиксации так сказать (сори свет ща так себе, тяжело сфоткать):

Нажаты были кнопки 1, 2, 3 и RC5. Можно выдыхать, теперь не пропадем. Теперь надо повтыкать больше и попытаться на живом примере так сказать разобраться во всем ну и плюс попытаться как то уменьшить размер кода, а то он на бесплатном XC8 почти 65 процентов от всего доступного места занимает.

Кстати, интересная ремарка – последняя текущая версия mla либ (на данный момент май 2020 года) использует прерывания, в то время когда прошлогодние версии используют polling метод, где просто с определенным интервалом запускаются usb таски.

Начнем с дескрипторов, они зашиты в файле usb_descriptors.

Сначала идет device descriptor, где как мы все помним определяется VID/PID и серийный номер, и описание девайса на шине, тут в принципе просто, дальше идет дескриптор конфигурации:


/* Configuration Descriptor */
    0x09,//sizeof(USB_CFG_DSC),    // Size of this descriptor in bytes
    USB_DESCRIPTOR_CONFIGURATION,                // CONFIGURATION descriptor type
    67,0,                   // Total length of data for this cfg
    2,                      // Number of interfaces in this cfg
    1,                      // Index value of this configuration
    0,                      // Configuration string index
    _DEFAULT | _SELF,               // Attributes, see usb_device.h
    50,                     // Max power consumption (2X mA)

Отсюда мы можем узнать следующие интересные вещи, в данном девайсе у нас имеется два интерфейса, индекс данного конфига равен 1, девайс питается от своего источника питания (что кстати ни хрена не так для моего девборда елки палки) и ток от шины ограничен 100мА.

Сразу и переделываем на правильный конфиг, оставляем только _DEFAULT.

Потом идет дескриптор интерфейса 0:


    9,//sizeof(USB_INTF_DSC),   // Size of this descriptor in bytes
    USB_DESCRIPTOR_INTERFACE,               // INTERFACE descriptor type
    0,                      // Interface Number
    0,                      // Alternate Setting Number
    1,                      // Number of endpoints in this intf
    COMM_INTF,              // Class code
    ABSTRACT_CONTROL_MODEL, // Subclass code
    V25TER,                 // Protocol code
    0,                      // Interface string index

Все снова обычное, класс снова как у CDC 0x02.

Затем пошел эндпоинт (номер, направление и тип передачи)


    //sizeof(USB_EP_DSC),DSC_EP,_EP02_IN,_INT,CDC_INT_EP_SIZE,0x02,
    0x07,/*sizeof(USB_EP_DSC)*/
    USB_DESCRIPTOR_ENDPOINT,    //Endpoint Descriptor
    _EP01_IN,            //EndpointAddress
    _INTERRUPT,                       //Attributes
    0x0A,0x00,                  //size
    0x02,                       //Interval

Потом следует второй интерфейс и два эндпоинта, я так понял это непосредственно для передачи данных используется (TX/RX):


    9,//sizeof(USB_INTF_DSC),   // Size of this descriptor in bytes
    USB_DESCRIPTOR_INTERFACE,               // INTERFACE descriptor type
    1,                      // Interface Number
    0,                      // Alternate Setting Number
    2,                      // Number of endpoints in this intf
    DATA_INTF,              // Class code
    0,                      // Subclass code
    NO_PROTOCOL,            // Protocol code
    0,                      // Interface string index
    
    /* Endpoint Descriptor */
    //sizeof(USB_EP_DSC),DSC_EP,_EP03_OUT,_BULK,CDC_BULK_OUT_EP_SIZE,0x00,
    0x07,/*sizeof(USB_EP_DSC)*/
    USB_DESCRIPTOR_ENDPOINT,    //Endpoint Descriptor
    _EP02_OUT,            //EndpointAddress
    _BULK,                       //Attributes
    0x40,0x00,                  //size
    0x00,                       //Interval

    /* Endpoint Descriptor */
    //sizeof(USB_EP_DSC),DSC_EP,_EP03_IN,_BULK,CDC_BULK_IN_EP_SIZE,0x00
    0x07,/*sizeof(USB_EP_DSC)*/
    USB_DESCRIPTOR_ENDPOINT,    //Endpoint Descriptor
    _EP02_IN,            //EndpointAddress
    _BULK,                       //Attributes
    0x40,0x00,                  //size
    0x00,                       //Interval
};

Один эндпоинт на данных выход, другой на вход. Потом пошли строки константы, которые дают больше инфы о девайсе (отображаются в винде).

Следующий на очереди у нас будет usb_config.h – здесь в принципе все проще, просто настройки усб – скорость, буфера, подтяжки и тип работы POLLING/INTERRUPT.

Теперь можно приступать к рассмотрению usb_device.c, у него вначале идет USB SENSE чек – можно настроить мк на проверку типа питания, указывается количество конфигураций. Потом следуют важные функции:

USBDeviceInit() – Инициализация усб -> Выключение прерываний, очистка регистров прерываний, очистка регистра ошибок (прерываний), обнуление регистра эндпоинтов. Подгрузка конфигурации определенной в хедере usb_config.h. Сброс на дефолтный адрес 0х00. Установка стейта в DETACHED_STATE, и куча всего другого.

USBDeviceTasks() – вообще главная функция стека усб на девайсе. В поллинг методе ее вообще надо вызывать периодично для прогона проверки. Разбираемся в ней подробнее, в том случае если девайс в ATTACHED_STATE, проиходит рутинная проверка разбитая на подтаски:

  • Если еще не было SE0, то врубается ресет, включаются прерывания по ресету и девайс переводится в POWERED_STATE
  • Если было засечено прерывание ActivityIF, то запускается процедура пробуждения из сна USBWakeFromSuspend() из этого же файла
  • Если ресет прерывание, то запускается снова USBDeviceInit()
  • Если получен IDLEIF то вызываем процедуру перехода в спячку, снова определенную здесь же
  • Обработка Start of Frame прерывания здесь же (я так понял таймера тут и 0 и 1 данные тогл)
  • Обработка StallIF – ошибка передачи данных, снова тут же определенна процедура обработчика ошибки
  • Обработка прерывания ошибки
  • Обработчик окончания передачи

USBConfigureEndpoint() – фиг пойми эту функцию, понятно что она определяет направление эндпоинта, внутрянка сходу непонятна

USBEnableEndpoint() – включение конкретного эндпоинта, и вызов предыдущей процедуры

USBDeviceDetach() – софтверное отключение девайса от шины, функция в общем то ни фига кроме отключения усб модуля и прерываний и не делает то…

USBDeviceAttach() – подлкючение девайса к усб, изменение стейта, установка конфигурации, врубание прерываний и модуля усб

Парочка непонятных функций касающихся контрол трансфера.

USBCtrlTrfTxService(), USBCtrlTrfRxService()  – девайс -> хост контрол и наоборот трансферы

USBStdGetDscHandler() – хендлер запроса GET_DESCRIPTOR, чем больше читаю тем больше понимаешь сколько тут работы елки 🙂

USBStdGetStatusHandler() – хендлер запроса GET_STATUS

Дальше куча функций касающихся эндпоинтов, вход в саспенд выход из него и одна маленькая интересная функция:

USBGet1msTickCount() – которая показывает наработку в милисекундах после вызова функции USBDeviceInit(), правда там же сноска что для 8 битных контроллеров эта функция не пашет в слипе, а для 16 битных валит даже в слип моде.

Хватит про этот файл, переходим к следующим – system.c. Небольшой файл, но важный здесь расположился конфиг, System_initialize() – которая в общем то только кнопки и леды врубает для нашего случая и обработчик прерываний, который к слову не проверяет срабатывает ли конктретно USBIF, а просто по любому прерыванию перекидывается USBDeviceTasks(), поэтому надо учесть это на будущее – если докидывать свой код сюда, то надо добавлять проверку своих флагов.

Дальше usb_device_cdc.c – тут куча разных непонятных процедур, но я таки остановлюсь на некоторых моментах, которые помогут разбираться с CDC уартом.

getsUSBUSART(uint8_t *buffer, uint8_t len) – функция которая возвращает данные из USB буферов, будет использоваться для чтения данных видимо. Если нет доступных данных то возвращает просто 0.

putUSBUSART(uint8_t *data, uint8_t  length) – вталкивание в USB уарт посимвольно

putsUSBUSART(char *data) – вталкивание строковых данных из RAM (если определение через переменные)

putrsUSBUSART(const char *data) – вталкиваие строковых данных из програм мемори, то бишь можно напрямую в кавычках тут фигачить

CDCTxService(void) – какая то поготовка усб уарта, надо вызвать хотя бы один раз из главного цикла.

И последний на очереди, app_device_cdc_basic.c

APP_DeviceCDCBasicDemoInitialize() – инициализация с параметрами последовательного порта. Кстати тут цифры отличаются от таких же из прошлого файла, но в реальности какую бы скорость я в терминале не ставил, сломать не удалось, работает на всех.

APP_DeviceCDCBasicDemoTasks() – главная процедура демки, она обрабатывает кнопки нажатие и выбрасывает заготовленное сообщение, а также чекает нажатую кнопку и добавляет +1 к ASCII символу и выкидывает его в терминал.

Вот и все функции, сюрприз сюрприз опять ни фига нету функции чтобы числа выталкивать в терминал, ну или я плохо смотрю. Написал снова свою локальную функцию:

void NumToUART(uint16_t number)
{
	uint16_t bignum = 10000; //max divider for uint16
    uint16_t temp_Number = 0;
	uint8_t numtemp = 5;    //an amount of digits (max)
    static uint8_t temp_data[5];

	while (numtemp>0)       //checking how many digits number has
    {
        if (number/bignum)
            break;
        numtemp--;
        bignum = bignum/10;
    }

   for (uint8_t i = numtemp; i>0; i--)    
	{     
       if (i == numtemp)
           temp_data[numtemp-i]=number/bignum;
       else
       {
            temp_data[numtemp-i]=number/bignum - (number/bignum/10)*10;   
       }
           bignum = bignum/10;
       }
       
    for (uint8_t i =0; i<numtemp; i++)
    {
        temp_data[i]=temp_data[i]+'0';
    }
        if( USBUSARTIsTxTrfReady() == true)
            putUSBUSART(temp_data,numtemp);

}

Эта функция может вытолкнуть максимум 5 разрядов (для unsigned integer это 65535), в принципе легко расширить, но я не думаю что это будет мне полезно + займет больше памяти.

Я немного видоизмел и код обработки кнопок, засунул 12345 число выпихивание по кнопке ‘p’, результат тут:

 

Нажаты были сначала 1, 2, 3 потом ввод, потом непосредственно кнопка с девборда, все отработало.

Сначала я думал, что качну логер и покажу степ бай степ какие процессы происходят, но USB логгеры оказались раздутыми и сложными и сильно ковыряться у меня желания в них не возникло, поэтому я просто покажу нормальный выход в виде конфигурации в одном из них:

Я не засовывал все, только основные вещи – дескриптор девайса, конфиги, интерфейсы, и эндпоинты. В приципе в этой софтине это более менее удобно реализовано, но за данными следить это жесть сколько всякой шелухи передается ради этой функции. Плюс я к этому моменту стабильно решил что выбор чипа не особо оправдан, код занимает почти 70% от всего места.

Гоним дальше, осталось добавить простую оцифровку данных АЦП и выпихивание по нажатию кнопки оцифрованного значения в усб.

Настраиваем АЦП:

   TRISB4 = 1;
    ANSEL = 0;
    ANSELH = 4;
    ADCON0 = 0b00101000;
    ADCON2 = 0b10000100;
    ADON = 1;

Здесь ничего особенного, смотрим прошлую статью про ацп – просто настройка определенного канала на вход.

В коде cdc_basic я добавил спец флаг по клавише p:

       if((numBytesRead > 0)&&(num_flag))
        {
            /* After processing all of the received data, we need to send out
             * the "echo" data now.
             */
            //putUSBUSART(writeBuffer,numBytesRead);
            GO = 1;
            while(GO){};
            data_int = (ADRESH<<8) | ADRESL;
            NumToUART(data_int);
            num_flag=0;
        }else

Без прерываний, просто для проверки и чекаем…

Реф у нас питание, 5.060, значит 1 lsb = 4.95 mV, 144*4.95 = 712.8mV… Мда. Попробовал подрубить FVR 1.024, потом 2048 – ни фига, всегда есть косяк, такое чувство что есть косяк в разводке земли еще в том числе, а еще – я ни фига не уверен что камень оригинальный а не фейковый, чтобы я еще раз в Китае брал серьезную комплектуху…

В принципе функцию обновление по USB я показал, он ее выполняет, сюда с легкостью можно засовывать любую нужную инфу, эсперимент с CDC можно считать удачным я считаю, а HID оставим уже как нибудь для другой статьи 🙂

Как обычно исходники на bitbucket.

Две вещи которые я для себя вынес из этого эксперимента:

1. Не связываться больше с китайской электроникой

2. PIC18F14K50 конечно прикольный, но чисто USB CDC код этот вкупе с бесплатным XC компилером занимает почти 70% от всего доступного на нем места, так что я думаю если что то и делать, то нужно все-таки брать более продвинутый чип.

Pic18 Lab. Experiment #5. USB: 4 комментария

  1. я уж было подумал, что ты совсем блог закинул ))) с возвращением, статья мощная ))) я недавно к теме юсб тоже возвращался, конкретно к HID – Simple Custom Demo

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.