Как программировать usb порт
Что только нынче не подключают к компьютеру! Помимо традиционных клавиатуры, мыши и принтера можно встретить ключи защиты данных, кофейные кружки с подогревом, нестандартные преобразователи данных, всевозможные датчики, контроллеры и многое другое.
Но ведь мало только физически подсоединить устройство к компьютеру, нужно еще и наладить обмен данными между ними. Как же выбрать порт и организовать подключение? Несколько лет назад стандартным решением было использование COM-порта. Кстати, до сих пор различные специалисты доустанавливают на промышленные компьютеры по 8, по 16, а то и по 32 COM-порта (есть целая категория различных PCI-плат расширения последовательных портов, контроллеров и т. д.). Таким образом, если нужно подключить несколько внешних устройств с интерфейсом RS-232, могут потребоваться дорогие адаптеры и экзотические платы расширения, которые по старой традиции неделями плывут в Россию на пароходах. Кстати, название обычного переходника «адаптер DB9m/DB25f» у менеджера компьютерного магазина может вызвать разве что раздражение.
Что такое HID-устройство
Сейчас практически все устройства подключаются к компьютеру через USB-интерфейс. Поэтому во многих новых ПК COM-порт отсутствует вообще.
USB-интерфейс — типовое решение по сопряжению нового внешнего устройства с компьютером, точнее, это HID-интерфейс, базирующийся на протоколе USB 1.1.
Хотя многие и считают, что HID-интерфейс (Human Interface Device) предназначен исключительно для клавиатуры, мыши и джойстика, он годится для множества решений, связанных с сопряжением внешних устройств и компьютера.
Если пользователю необходимо производить низкоскоростной обмен данными (до 64 кбит/c) и при этом желательно сократить время на утомительной разработке собственных драйверов, то ему вполне подойдет HID. На выходе же получится простое и вполне современное решение на базе стандартного программного USB-интерфейса с гарантированной поддержкой на всех распространенных программных платформах.
Свойства HID-устройства
С точки зрения организации программной поддержки HID-устройства, все выглядит достаточно привлекательно: для работы под управлением Windows можно быстро создавать понятный компактный код на базе готовых проверенных алгоритмов. При этом у разработчика останется масса времени на реализацию собственного протокола обмена данными верхнего уровня, поскольку необходимый уровень абстрагирования уже организован за счет HID-протокола (см. таблицу). Кроме того, программисту легко проводить отладку написанного протокола обмена (разумеется, при наличии работающего HID-устройства) — благодаря относительной жесткости самого протокола достаточно просто разработать программу поддержки устройства компьютером. Еще бы! Массу работы уже взял на себя создатель HID-устройства.
Организация обмена данными между HID-устройством и компьютером
Чтобы описать взаимодействие HID-устройства с компьютером, употребим термин «хост». В данном случае под ним понимается управляющее устройство в общей физической архитектуре взаимодействия по USB-протоколу. Так, все порты в компьютере — хосты. К ним можно подключать различные USB-устройства (флэшки, мыши, веб-камеры, фотоаппараты и проч.), которые хоста не имеют. Хост обеспечивает обнаружение, подключение, отключение, конфигурирование устройств, а также сбор статистики и управление энергопотреблением.
HID-устройство может само установить частоту опроса, во время которого выясняется наличие в нем каких-либо новых данных. Значит, даже на таком низком уровне программист может довериться системе, поскольку частота опроса и другие параметры обмена данными должны быть заранее заданы в программе контроллера HID-устройства. Этим протокол HID отличается от общего описания USB 1.1 или USB 2.0, в котором нет жестких требований к организации протокола. Однако при специфических задачах, требующих повышенного уровня безопасности, может оказаться довольно сложно избавиться от циклических опросов, когда постоянно передаются почти одни и те же блоки данных.
Особенности программирования HID-устройств
HID-устройства имеют специальные дескрипторы. Когда хост определит, что устройство принадлежит к классу HID, он передает управление им соответствующему драйверу. Предполагается, что дальнейший обмен данными ведется под его руководством.
В Windows за доступ к HID-устройствам отвечает системная служба HidServ. Подробнее о функциях запросов к HID-устройствам и других особенностях работы с HID-драйвером рассказывается в работе П. В. Агурова «Интерфейс USB. Практика использования и программирования» (СПб.: БХВ-Петербург, 2005).
Программирование HID-устройств на «верхнем уровне»
Для работы с HID-устройством в среде Delphi for win32 применяется компонент TJvHidDeviceController, представляющий собой удобный глобальный менеджер для доступа к HID-устройствам. А уже на его базе можно получить объектный экземпляр для работы с конкретным устройством.
Основные свойства и события компонента TJvHidDeviceController
Рассмотрим компонент TJvHidDeviceController более подробно. Событие OnArrival срабатывает на поступление (подключение) в систему HID-устройства, доступ к устройству предоставляется в обработчике этого события через экземпляр класса TJvHidDevice. Простое событие OnDeviceChange реагирует на изменение состояния устройства, оно только сигнализирует об изменениях в системе. Событие OnDeviceData срабатывает при поступлении данных от одного из HID-устройств и передает обработчику следующее: HidDev: TJvHidDevice; — устрой-ство, от которого были получены данные;
Событие OnDeviceDataError уведомляет об ошибке передачи данных, передавая в процедуру обработки параметры HidDev: TJvHidDevice; — HID-устройство и Error: DWORD; — код ошибки. Событие OnDeviceUnplug уведомляет об извлечении устройства из списка установленных в системе. Типы обработчиков событий на Plug и Unplug одинаковы (в исходном тексте: TJvHidUnplugEvent = TJvHidPlugEvent). В обработчик передается объект класса TJvHidDevice, соответствующий HID-устройству.
Для последовательного перечисления имеющихся в системе HID-устройств по вызову метода Enumerate предназначено событие OnEnumerate, т. е. в обработчике события найденные устройства последовательно передаются в виде объектов. Это событие принудительно инициируется методом Enumerate, использующимся для «проведения» имеющихся HID-устройств через обработчик, например при ревизии состояния HID-устройств по инициативе хоста (компьютера).
Событие OnRemoval срабатывает на физическое извлечение устройства из системы и имеет тот же тип обработчика TJvHidUnplugEvent, что и для OnDeviceUnplug. Функция CountByProductName выдает количество устройств, удовлетворяющих указанному в аргументе имени продукта, а CountByVendorName — указанному в аргументе имени производителя.
Основные свойства и события класса TJvHidDevice
Класс TJvHidDevice — виртуальное представление отдельно взятого HID-устройства. Новый объект этого класса можно получить, как было уже сказано, из события OnArrival или OnEnumerate. Функционал классов TJvHidDeviceController и TJvHidDevice частично дублируется, поскольку в первом из них интегрированы общий инструментарий для работы с набором имеющихся в системе HID-устройств и механизм доступа к одному из них. Устройство можно однозначно идентифицировать по свойствам SerialNumber, ProductName и VendorName. Чтобы получить сведения о поступлении данных с применением такого объекта, можно воспользоваться событием OnData. Отсылка данных ведется через метод WriteFile (в строгом смысле — через функцию). WriteFile — это оболочка системной функции WriteFile (kernel32).
Чтобы проконтролировать факт извлечения устройства, следует присвоить свой обработчик событию OnUnplug. Перед началом обмена данными с HID-устройством нужно удостовериться в самой возможности такого обмена с помощью HasReadWriteAccess. В этом классе на возникновение ошибки обмена данными даже есть отдельное событие OnDataError.
А теперь рассмотрим фрагменты кода из «живого» проекта, реализующего тестовое клиентское приложение для организации обмена данными с нестандартным устройством — пластиковыми чип-картами на базе HID. В борьбе за реализм автор взял на себя смелость не выкидывать из листингов «лишние» технологические обвязки кода.
Метод ScanDevices (листинг 1) предназначен для инициирования процесса поиска в системе необходимого HID-устройства. Большая часть кода, за исключением вызова метода Enumerate, необязательна и обеспечивает гибкость приложения, например, для того, чтобы в эту же тестовую программу можно было добавить возможность работы по интерфейсу, отличному от HID. Метод AddError выводит в окно отладочную информацию в процессе работы программы.
В листинге 2 приведен обработчик события OnEnumerate для поиска необходимого внешнего устройства. Для простоты будем считать, что программа может работать только с одним устройством нужного ей типа.
Прежде чем рассматривать дальнейшую реализацию проекта, следует немного рассказать о принятом формате обмена данными верхнего уровня, т. е. о структуре, призванной быть посредником между методами приема-передачи данных и конкретной решаемой прикладной задачей. Дело в том, что здесь разработчику предоставляется возможность реализовать свои творческие способности. Вернее, разработчикам, потому что процесс создания нового протокола очень часто бывает двусторонним, и при этом первую скрипку играет тот, кому труднее реализовывать алгоритм обмена. В общем, каким бы ни был протокол обмена, всегда приятно делать каждую программную сущность максимально наглядной и самодостаточной, пусть даже в ущерб некоторым общепринятым традициям. Ибо лучшее решение — то, которое будет реализовано в сжатые сроки с минимальной привязкой к программной среде и с большими возможностями дальнейшего развития. На основе этих принципов был создан протокол обмена верхнего уровня, где главное понятие — «команда». Из листинга 3 видно, насколько автор любит строковые данные, не раз спасавшие его при отладке программных модулей. Как же замечательно, что у нас вообще есть тип String! Все команды протокола делятся на категории (классы), внутри которых существует код команды, однозначно характеризующий ее назначение. Параметр edParam служит для отсылки данных в устройство, а параметр edAnswerData содержит в себе полученные от устройства данные. Строковый тип описанных членов записи позволяет свободно и наглядно манипулировать данными в формате HEX-строки. И что самое приятное, формат описанной записи идеологически стоит где-то посередине между ее непосредственным назначением и различными формами ее представления (INI, HEX, XML и т. д.)
Выполнение команды, т. е. отсылка данных в устройство, реализовано с применением отсылки пакетов данных длиной 8 байт (листинг 4). Эта длина — не единственное решение, такой выбор продиктован требованиями протокола верхнего уровня и в каждом конкретном случае может быть другим. Это, что называется, дело вкуса. Странный флаг IsUSBMode в методе ExecuteCommand (листинг 5 на «Мир ПК-диске») оставлен как напоминание о том, что вместо работы с USB нам может потребоваться использовать COM-порт или какой-то другой интерфейс. В начале отсылаемой группы данных в устройство передается синхросерия произвольно выбранного формата (например, 3E3E3E2B), сообщающая устройству, что у него на входе вполне легальные данные. Напомню, что в данном случае речь идет не столько о HID, сколько о специфическом протоколе верхнего уровня, идеологически оторванном от «железа» и предназначенном для решения особых прикладных задач.
В обработчике GetDataExecutor полученных от устройства данных (пакет по 8 байт) использовано специально созданное событие OnNewInputData для передачи первично обработанных данных на дальнейшую обработку, причем с указанием их старого и нового значений (листинг 6 на «Мир ПК-диске»). Таким образом, события поступления необработанных данных и указание на дальнейшую обработку развязываются, позволяя добавлять какой-то специфический алгоритм предупреждения на раннем этапе ошибочной, повторной или ненужной входной информации.
Представленные здесь примеры работы с HID-устройством иллюстрируют общую идею статьи — относительную простоту программирования нестандартных HID-устройств средствами Delphi.
Разработка различных устройств на основе микроконтроллеров — занятие, достойное настоящего компьютерного гика. Несомненно, полезной фишкой любого гаджета будет USB-интерфейс для подключения к компьютеру. Но что делать, если в микросхеме AVR не предусмотрена аппаратная поддержка USB?
V-USB: размер имеет значение
При разработке собственного гаджета часто встает проблема его подключения к компьютеру. Надо сказать, что порты LPT и COM — теперь экзотика на материнских платах ПК, не говоря о ноутбуках, у которых эти интерфейсы исчезли давным-давно. Поэтому у современных компьютеров практически не осталось альтернатив интерфейсу USB.
Если ресурсы чипа используются на все сто процентов, то лучше сразу смотреть в сторону устройств с аппаратной поддержкой универсальной последовательной шины (такие микроконтроллеры присутствуют в линейке любого производителя). В остальных случаях можно использовать софтовый USB.
Для микроконтроллеров Atmel существует замечательный проект V-USB, который предлагает программную реализацию низкоскоростного устройства USB 1.1. Код V-USB будет работать на любом устройстве AVR, у которого есть хотя бы 2 Кб Flash-памяти и 128 байт ОЗУ, с тактовой частотой 12; 12,8; 15; 16; 16,8 или 20 МГц.
Использование продукта возможно как в рамках open source лицензии GPL, так и на коммерческой основе. Для того чтобы разрабатывать собственные USB-устройства, обычно также нужно покупать что-то вроде лицензии. Но ребята из V-USB позаботились и об этом, приобретя пару Vendor ID — Product ID и разрешив их использовать любому желающему.
Аппаратная обвязка для подключения USB-шины к микроконтроллеру очень простая. Если устройство потребляет не слишком много, то запитать его можно прямо от шины (считается, что линия питания USB компьютера способна отдавать ток до 500 мА). Так как информационные линии (D+ и D–) используют уровень сигнала 3,6 В, кроме токоограничивающих резисторов, необходимы стабилитроны для согласования с 5-вольтовой логикой чипа. Чтобы обозначить тип подключения, нужно «подтянуть» напряжение питания через сопротивление 1,5 кОм к линии D–.
Подключение USB к микроконтроллеру ATtiny2313
Альтернативный вариант сопряжения по USB — снизить напряжение питания контроллера посредством соответствующей микросхемы стабилизации или просто парой диодов. Последнюю схему можно найти на сайте проекта V-USB.
Программатор USBtiny
Для микроконтроллеров AVR существует множество различных программаторов. USBtiny здесь упоминается, в частности, потому, что содержит программную реализацию USB, аналогичную V-USB. Схема этого программатора проста: версия 2 содержит две микросхемы, а первая версия — лишь одну (собственно чип ATtiny2313). Благодаря подробному описанию на сайте и простым комплектующим устройство легко сделать даже начинающему. USBtiny совместим с популярной программой avrdude, используемой для программирования микроконтроллеров AVR.
Единственная проблема заключается в заливке прошивки в чип программатора — для этого нужен… программатор. Если есть компьютер с LPT-портом, то можно сделать один из вариантов FBPRG ака «пять проводков», который железно
Готовим санки
Программный инструментарий, необходимый для реализации простейшей прошивки USB-гаджета, предельно аскетичен: компилятор gcc-avr, библиотека avr-libc, программатор avrdude и набор binutils для AVR. В Debian/Ubuntu все, что нужно, устанавливается одной командой:
Работа с программатором avrdude
Чтобы воспользоваться лицензией V-USB для устройства, числовые идентификаторы производителя и устройства изменять не надо. А вот символьные имена можно выбрать по своему вкусу (они позволят отличить несколько устройств на основе V-USB, подключенных к одному и тому же компьютеру):
Разнообразные гаджеты
У тебя есть идея какого-нибудь устройства? Не спеши паять и кодить, а поищи, возможно, кто-то подобное уже делал. Если не получится воспользоваться готовыми схемами и исходниками, то хотя бы не придется начинать все с нуля.
Например, проект V-USB благодаря лицензионной политике накопил приличную базу готовых (в том числе и свободно распространяемых) решений. Здесь можно найти различные реализации клавиатур, USB-адаптеров для джойстиков, геймпадов (в том числе и раритетных, например SNES/NES, Nintendo 64, ZX Spectrum джойстик, Sony PlayStation 1/2) и тому подобное. Адаптеры DMX, виртуальные порты COM и UART, i2c, Servo, беспроводные интерфейсы DCF77, IR — все, что поможет подключить к ПК больше новых устройств. Логгеры, платформы для датчиков и сенсоров, адаптеры для LCD-дисплеев, программаторы и загрузчики также могут оказаться полезными в хозяйстве.
Программа для чипа — элементарно!
Создадим файл main.c на одном уровне с папкой usbdrv и опишем в нем необходимые заголовочные файлы, определения и переменные:
В main.c переопределим функцию usbFunctionSetup, которая вызывается автоматически при получении нового запроса:
Собственно, функция usbFunctionWrite занимается тем, что заполняет буфер replyBuf полученными данными.
Кстати, чтобы этот метод работал, нужно внести изменения в usbconfig.h:
Ну и последняя функция прошивки — main:
Задействуем USART/UART
Хорошая альтернатива программному/аппаратному USB — использование в чипе популярного интерфейса USART/UART со сторонним преобразователем этого протокола в USB, который можно выполнить, например, на основе микросхемы FT232RL.
Libusb: и не одетая, и не обнаженная
Ты спросишь: а придется ли писать драйвер для операционной системы компьютера, чтобы подключить USB-устройство? Если использовать libusb, то можно обойтись без реализации полноценного модуля ядра. Libusb — это open source библиотека, которая позволяет быстро запрограммировать, во-первых, поиск устройства на шине, а во-вторых — обмен данными с ним.
Под Linux библиотеку и необходимые заголовочные файлы можно получить из исходных кодов. А лучше воспользоваться стандартным репозиторием твоего дистрибутива. Для Debian/Ubuntu, например, так:
Существует также порт libusb под Windows — libusb-win32. Вопреки названию проекта, также поддерживаются 64-битные ОС от Microsoft (начиная с версии 1.2.0.0).
Но libusb — это отдельная тема разговора. Думаю, с программированием для ПК ты знаком и сможешь в этом разобраться сам. Поэтому буду краток. Создаем файл usbtest.c и начинаем наполнять его контентом. Сначала необходимые заголовочные файлы и определения:
Функция usbOpenDevice для инициализации устройства:
Как видно, параметрами usbOpenDevice выступают числовые идентификаторы производителя и устройства. В случае если устройство присутствует на шине, возвращается его дескриптор. Если устройств на V-USB будет несколько — придется дописать проверку символьных имен вендора и продукта.
И функция main консольной утилиты usbtest:
Proteus отдыхает
Всенародно любимый симулятор электрических схем Proteus ISIS бесполезен при разработке устройств с программной реализацией USB. Его эмулятор USB поддерживает только чипы с аппаратной поддержкой универсальной последовательной шины (например, AT90USB646 или AT90USB1286).
Собираем, прошиваем, тестируем
Ниже приведен небольшой, но очень полезный Makefile, c помощью которого командой make из main.c и usbtest.c легко получить прошивку для чипа — main.hex и бинарник утилиты usbtest:
Чтобы залить прошивку в микроконтроллер с помощью программатора usbtiny, набираем команду:
В avrdude фьюзы задаются не слишком наглядно, но их можно легко рассчитать в одном из online-калькуляторов.
Online-калькулятор фьюзов
Подключаем устройство к компьютеру и проверяем, как оно работает (usbtest c параметром out считывает строку, in — записывает указанную строку в буфер чипа):
Тестирование взаимодействия с ATtiny2313 по USB (заливаем в чип строку, а затем cчитываем ее)
Ложка дегтя
Софтовый USB не есть панацея. Программные реализации обычно имеют ряд упрощений, таких как отсутствие проверки контрольной суммы и симметричности канала, что отрицательно сказывается на помехозащищенности. Также обычно софтовые библиотеки используют низкоскоростные режимы работы USB. Да и код USB-библиотеки «кушает» и без того небольшую память чипа.
Подглядываем…
На уровне логики протокол USB — это, по сути, многоуровневая пакетная передача данных. В этом нетрудно убедиться (а заодно узнать много интересного про USB), воспользовавшись анализатором сетевых протоколов Wireshark. Предварительно необходимо загрузить драйвер USB-монитора:
Теперь в списке интерфейсов Wireshark можно выбирать шины USB. Посмотреть номер шины устройства можно, например, в логах.
Перехват USB-пакетов в Wireshark
- Сайт проекта V-USB;
- сайт проекта libusb;
- сайт проекта libusb-win32; про V-USB и libusb (англ.);
- статья про libusb, часть 1;
- статья про libusb, часть 2; на основе V-USB;
- сайт проекта USBtiny;
- сайт проекта AVReAl; фьюзов.
Заключение
Надеюсь, после того, как ты научился пересылать данные между компьютером и микроконтроллером AVR, твоя страсть к электронике воспылает с новой силой, породив немало оригинальных и полезных устройств. Остается лишь пожелать тебе успехов на этом сложном, но интересном поприще.
Продолжим цикл статей, посвященных интерфейсу USB. Как и договаривались, мы приступим к «практической» части – начнем писать код. В данной статье мы создадим программную прослойку, которая абстрагирует нас от «железа» (USB периферии конкретного МК). Это позволит нам в дальнейшем перейти к написанию высокого уровня «стека» USB (уровня обмена данными с хостом, обработки стандартных запросов USB и т. д.).
Небольшое лирическое отступление.
Нужно сказать пару слов о инструментах, которые облегчают разработку USB устройств. Очень полезно иметь под рукой «анализатор трафика» интерфейса USB (снифер обмена). Идеальное решение – использовать аппаратный анализатор. Но это очень дорогой и специфический девайс. Существуют программные анализаторы, они более доступны. Но, те программные анализаторы которые я пробовал использовать, были немного глючными и немного платными (хотя возможно я просто не нашел подходящего инструмента).
Я поступил немного экстремально – взял ядро Linux, залез в исподники USB хоста и «понатыкивал» в определенных местах вывод дополнительной отладочной информации. После этого пересобрал ядро и проводил отладку на нем, анализируя логи ядра. Метод конечно радикальный, но эффективный :). Позволяет не только анализировать обмен, но и отлавливать ошибки в логике работы USB устройства.
Пару слов о нашем МК – AT91SAM7S512. Чип как чип, агитировать использовать его я не буду (сам думаю о переходе на STM32 ), но и ругать чип тоже особо не буду. Производитель – ATMEL, ядро ARM7. По набору периферии, субъективно, незначительно превосходит ATmega128. Из приятных плюшек – чип можно программировать напрямую через USB. Соответственно, если данный интерфейс разведен на плате – можно обойтись без программатора.
Нас, в первую очередь, интересует USB периферия. Работа в режиме хоста данным чипом не поддерживается. Производителем заявлена поддержка USB 2.0, хотя и со многими ограничениями в т. ч. по скорости обмена.
На аппаратном уровне периферия поддерживает до четырех «конечных точек». Каждая конечная точка имеет свой аппаратный регистр управления/состояния и свои буферы для приема/отправки данных. Конечная точка с индексом 0 («нулевая конечная точка», та, которая нужна для управления) аппаратно «урезана» — размер буферов приема/отправки ограничен — 8 байт. Остальные конечные точки универсальны (могут быть любого типа) и имеют один буфер для отправки данных (размер буфера – 64 байта) и 2 буфера (2 банка) для чтения данных (по 64 байта каждый). Наличие двух банков для приема данных позволяет повысить скорость обмена: пока наша программа будет вычитывать данные из одного банка, периферия МК может принимать данные от хоста в другой. Переключать банки нужно вручную, через соответствующий регистр.
За USB интерфейсом МК закреплено оно аппаратное прерывание. Для того, чтобы внутри прерывания определить тип события, которое вызвало прерывание (источник), имеется соответствующий статусный регистр. В принципе, можно работать с USB «по опросу», без прерываний, но это как-то слишком «по-пионэрски», наш пример реализации будет работать «по прерыванию».
Теперь, немного «кепства» для облегчения дальнейшего понимания кода. В некоторых МК (например, ATmega) для работы с периферий (например, для «дерганья» ногой МК) используется один регистр управления, который доступен как для чтения, так и для записи. Соответственно, для того чтобы «дернуть» ногой нужно наложить на управляющий регистр соответствующую битовую маску через побитовое логическое «или» или «и».
В данном МК часто используется другой подход – для подобных операций МК имеет 2 регистра доступных только для записи. Для того чтобы «поднять» ногу нужно записать соответствующую битовую маску в первый регистр (SET регистр). Для того чтобы «опустить» эту же ногу — нужно записать ту же маску в другой (CLEAN) регистр. Для того чтобы определить текучее состояние – есть третий (GET) регистр, доступный только для чтения.
Стандартные отмазки. Мой код ужасен, не ругайте меня. Да нет, код как код :). Рефакторинг ему бы не помешал, но и создавался он не для конкурса «лучший код на С для МК». В коде в небольшом количестве присутствует «синтаксический сахар». В некоторых местах я сознательно отказался от обработки ошибок.
Переходим к реализации.
Начнем с высокоуровневого API нашего «стека». Так как наше устройство будет работать как виртуальный СОМ-порт, наше API будет состоять из трех функций: инициализация стека, прием данных и передача данных. В дальнейшем, программа МК будет использовать эти функции для обмена данными с ПК.
С функцией USB_Init() думаю все понятно — эта функция инициализирует соответствующую периферию МК, «запускает» стек и завершается после того, как хост закончит конфигурацию устройства (присвоит устройству адрес и т. д.).
Немного подробнее остановимся на функциях чтения/записи. Дело в том, что на эти функции поддерживают два режима работы – синхронный и асинхронный.
В синхронном режиме в функцию передается указатель на буфер обмена данными (data) и размер буфера/размер данных в буфере (length). В качестве оставшихся параметров (callback, param) передается NULL. В таком режиме функция завершится после того как указанное количество байт будет получено/передано. Однако, пока функция будет ожидать завершения приема/передачи данных, будут «впустую» тратиться драгоценные мегагерцы МК :)
Более оптимальным, в некоторых случаях, будет работа со стеком в асинхронном режиме. В этом случае, в качестве параметра callback передается указатель на функцию, которая будет вызвана по завершению приема/передачи данных. Сама функция USB_Read()/USB_Write() завершится сразу после начала обмена данными, не дожидаясь завершения обмена.
Смысл остальных параметров будет понятен из реализации.
Определим несколько «дефайнов» для повышения читабельности кода
Так как вся реализация будет «крутиться» вокруг конечных точек – объединим все переменные логически связанные с конечной точкой в структуру и напишем несколько простых inline функций для базовых операций с конечной точкой.
Теперь можно начинать писать осмысленный код. Начнем с функции инициализации.
Здесь следует пояснить, что такое CurrentConfiguration. Это глобальная переменная, значение которой (отличное от 0) мы присвоим из прерывания, когда хост назначит нам «USB конфигурацию». Но об этом позже.
Теперь функции чтения/записи.
Резюмируем: для того чтобы передать данные через конечную точку достаточно записать эти данные в аппаратный буфер конечной точки (но, нужно помнить о том, что размер буфера ограничен) и выставить в регистре управления флаг AT91C_UDP_TXPKTRDY. После того, как данные будут переданы хосту, произойдет аппаратное прерывание – можно передавать следующую порцию.
Чтение данных: разрешаем прерывания для соответствующей конечной точки и ждем. Когда хост предаст данные, периферия МК запишет их в один из внутренних буферов (в один из банков) и инициирует аппаратное прерывание.
Как вы, наверное, поняли – все «мясо», вся логика работы USB реализована в обработчике прерывания. Это тема следующей статьи. После того, как мы разберемся с реализаций обработчика, я выложу исходные коды готового проекта.
В этой статье получилось «много кода, мало смысла». Ничего, в следующей произойдет слияние теории с практикой.
Комментарии ( 37 )
Эх, слишком близко к железу:)
Лучше бы про дескрипторы продолжили и про описание самого протокола обмнена.
Кстати, на STM32(даже на самых дешевых с USB — STM32F102) можно реализовать уже до 16 однонаправленных или 8 двунаправленных эндпоинтов.
Плюс размер буфера можно установить до 512байт(1кб).
Минус только в их стэке, очень неудобный.
Загрузчик (bootloader)
Загрузчик живёт в самом конце Flash памяти МК и позволяет записывать прошивку, отправляемую через UART. Загрузчик стартует при подаче питания на МК, ждёт некоторое время (вдруг кто-то начнёт слать код прошивки по UART), затем передаёт управление основной программе. И так происходит каждый каждый раз при старте МК.
- Загрузчик позволяет прошивать МК через UART;
- Загрузчик замедляет запуск МК, т.к. при каждом запуске ждёт некоторое время для потенциальной загрузки прошивки;
- Загрузчик занимает место во Flash памяти. Стандартный старый для Arduino NANO занимает около 2 кБ, что весьма существенно!
- Именно загрузчик мигает светодиодом на 13 пине при включении, как индикация работы.
Программатор
Помимо записи прошивки во flash память, программатор позволяет:
- Считывать содержимое Flash памяти (скачать прошивку на компьютер)
- Полностью очищать чип от всех данных и настроек
- Записывать и читать загрузчик
- Считывать/записывать EEPROM память
- Читать и настраивать фьюзы (fuses, fuse-bits) и лок биты.
USB-TTL (UART)
USB-TTL | Arduino |
DTR | DTR |
RX | TX |
TX | RX |
GND | GND |
VCC/5V/3.3V | VCC |
Фьюзы (Pro)
Фьюзы (фьюз-биты) являются низкоуровневыми настройками микроконтроллера, которые хранятся в специальном месте в памяти и могут быть изменены только при помощи ISP программатора. Это такие настройки как выбор источника тактирования, размер области памяти под загрузчик, настройка отсечки по напряжению и прочее. Фьюз-биты собраны по 8 штук в байты (т.н. байты конфигурации), как типичный регистр микроконтроллера AVR. Таких байтов может быть несколько, они называются low fuses, high fuses, extended fuses. Для конфигурации байтов рекомендуется использовать калькулятор фьюзов (например, вот такой), в котором просто ставятся галочки на нужных битах, и на выходе получается готовый байт в hex виде. Рассмотрим на примере ATmega328p:
Лок-биты (Pro)
Лок-биты (lock-bits) позволяют управлять доступом к памяти микроконтроллера, что обычно используется для защиты устройства от копирования. Лок-биты собраны опять же в конфигурационный лок-байт, который содержит: BOOTLOCK01, BOOTLOCK02, BOOTLOCK11, BOOTLOCK12, LOCKBIT1, LOCKBIT2 (для ATmega328). Калькулятор лок-битов можно использовать этот. BOOTLOCK биты позволяют запретить самому МК запись (самопрограммирование) во flash память (область программы и область загрузчика)
А вот локбиты LOCKBIT позволяют запретить запись и чтение flash и EEPROM памяти извне, при помощи программатора, т.е. полностью защитить прошивку от скачивания и копирования:
Таким образом включив LOCKBIT1 (лок-байт будет 0x3E) мы запретим внешнюю запись во Flash и EEPROM память, т.е. при помощи ISP программатора, а включив LOCKBIT1 и LOCKBIT2 (лок-байт: 0x3C) полностью заблокируем заодно и чтение данных из памяти микроконтроллера. Повторюсь, всё описанное выше относится к ATmega328p, для других моделей МК читайте в соответствующих даташитах.
ISP программатор
USBasp
Решение проблем
Решение большинства проблем с загрузкой через программатор (независимо от того, что написано в логе ошибки):
- Вытащить и обратно вставить usbasp в usb порт
- Вставить в другой usb порт
- Переустановить драйвер на usbasp
- Проверить качество соединения USBasp с МК
- Перепаять переходник и отмыть флюс
Для прошивки микроконтроллера, тактирующегося низкой частотой (менее 1 МГц внутренний клок):
Основные ошибки в логе Arduino IDE
Arduino as ISP
Почти любая другая плата Arduino может стать ISP программатором, для этого нужно просто загрузить в неё скетч ArduinoISP:
- Открыть скетч Файл > Примеры > 11. ArduinoISP > ArduinoISP
- Всё! Ваша Arduino теперь стала ISP программатором
- Подключаем к ней другую Arduino или голый чип по схеме ниже
- Выбираем Arduino as ISP в Инструменты > Программатор
- И можем писать загрузчики, фьюзы или загружать прошивку напрямую во Flash
-
Либо поставить поставить конденсатор ёмкостью
Решение проблем
Для прошивки микроконтроллера, тактирующегося низкой частотой (менее 1 МГц внутренний клок):
- Arduino ISP: нужно изменить частоту загрузки прошивки в скетче Arduino ISP и снова прошить его в ардуино-программатор (см. строку в скетче 45 и ниже);
Работа в Arduino IDE
Прошивка загрузчика
Как убрать загрузчик?
Загрузка скетча
В Arduino IDE можно зашить скетч через программатор, для этого надо нажать Скетч > Загрузить через программатор. Это очень удобно в том случае, когда МК используется без загрузчика, или просто голый МК.
Фьюзы
Конфигуратор платы в Arduino IDE устроен следующим образом: каждой плате в Инструменты > Плата соответствует свой набор настроек, включая фьюзы, которые прошиваются вместе с загрузчиком . Некоторые из них:
- Загрузчик (путь к файлу)
- Скорость загрузки (через загрузчик)
- Объем доступной flash и sram памяти
- Весь набор фьюзов и лок-биты
Файл конфигурации называется boards.txt и найти его можно в папке с ядром Arduino: C:\Program Files (x86)\Arduino\hardware\arduino\avr\boards.txt. Документацию на boards.txt можно почитать здесь. При желании можно вывести нужные фьюзы через калькулятор (читайте выше), изменить их в boards.txt (главное не запутаться, для какой выбранной конфигурации платы делается изменение) и прошить в МК, нажав Инструменты > Записать загрузчик.
Такая работа с фьюзами максимально неудобна, но есть и другие варианты:
- Ядро GyverCore для atmega328, в нем мы сделали кучу готовых настроек фьюзов прямо в настройках платы, читайте в уроке про GyverCore. Несколько загрузчиков, включая вариант без загрузчика, выбор источника тактирования и другие настройки в один клик мышкой.
- Программа AVRdudeprog, про нее поговорим ниже
Avrdudeprog
- Чтение/запись/очистка flash памяти
- Чтение/запись/очистка eeprom памяти
- Полная очистка чипа
- Калькулятор фьюзов и локбитов (чтение/запись)
Более подробный обзор на avrdudeprog можно посмотреть здесь . Давайте посмотрим на калькулятор фьюзов. Выбираем свой микроконтроллер и программатор (можно добавить другие модели микроконтроллеров и программаторов, читай тут). Переходим во вкладку Fuses, нажимаем прочитать. При успешном чтении увидим текущий набор настроек своего чипа. Можно их поменять и загрузить. Важно! Галку инверсные биты не трогаем! Лок-биты и отключение RST заблокирует микроконтроллер, не трогайте их, если такой цели нет! Можно загружать прошивку или загрузчик из .hex файла, указав путь к ней на первой вкладке в окне Flash. Очень удобная утилита для низкоуровневой работы с МК.
Видео
Читайте также: