[Из песочницы] Аппаратный модуль USB в ATXMega. Инструкция по реализации HID
Аппаратный модуль USB в восьмибитных микроконтроллерах от Atmel появился давно. Но четкого руководства «шаг за шагом» на русском языке для контроллеров семейства XMega я не нашел. Поэтому делюсь своим опытом. Опыт достаточно поверхностный, однако позволяет разработчикам, у которых нет возможности вникать в тонкости USB стека, в сжатые сроки обеспечить взаимодействие контроллера с компьютером по интерфейсу USB. На канале Atmel на YouTube есть ролики как это делается. Однако многим удобнее читать, нежели смотреть видео — для этих людей данный пост. Также, я опишу реализацию на стороне ПК.ЖЕЛЕЗО. Я использовал контроллер ATXMega256A3BU. Это та же самая ATXMega256A3, но с аппаратно реализованным USB модулем. К слову, я думал, что они pin-to-pin совместимы, но это не полностью так, будьте внимательны! Выводы контроллера D+ и D- я подключил напрямую к соответствующим пинам на разъеме компьютера без всяких резисторов. В моем случае, правда, был не компьютер, а микросхема USB хаба, а потом уже компьютер, но я не думаю, что это существенно меняет дело.СОФТ МИКРОКОНТРОЛЛЕРА. Для создания костяка прошивки, с уже реализованным программистами Atmel USB стеком, мы воспользуемся мастером ASF Wizard, который встроен в Atmel Studio 6 версии. Дня начала необходимо создать новый проект (File→New Project). Далее в открывшемся окне необходимо выбрать пункты, показанные на картинке.
После создания нового проекта нужно запустить ASF Wizard (меню ASF→ASF Wizard). Во вкладке Availble modules нужно найти USB Device (service), и нажать Add. После этого во вкладке Selected Modules появится USB Device (service), а напротив него выпадающий список. В нем выбираем hid_generic. После чего нажимаем на кнопку Summary, и на этом создание проекта закончено. Теперь он готов к тому, чтобы наполнить его смыслом. В основном файле проекта main.c мы увидим следующее содержание:
#include
int main (void) { board_init (); // Insert application code here, after the board has been initialized. }
Функция board_init () инициализирует отладочную плату, XMEGA-A3BU Xplained. Так как у меня была не эта плата, а своя, я эту функцию выкинул. Далее нам понадобится функция для инициализации USB. Это udc_start (). Она объявлена где-то в недрах asf.h, так что спокойненько ее прописываем. Она инициализирует USB в соответствии с настройками, описанными в файле conf_usb.h.Этот файл находится в папке с проектом. Приведу те строки этого файла которые особо важны для настройки USB HID: #define USB_DEVICE_VENDOR_ID 0×03EB #define USB_DEVICE_PRODUCT_ID 0×2013 #define USB_DEVICE_POWER 500 // Consumption on Vbus line (mA) #define USB_DEVICE_ATTR USB_CONFIG_ATTR_SELF_POWERED #define USB_DEVICE_MANUFACTURE_NAME «Company Name» #define USB_DEVICE_PRODUCT_NAME «Varior Lens» #define USB_DEVICE_SERIAL_NAME »00001»
#define UDI_HID_GENERIC_REPORT_OUT (ptr) my_callback_generic_report_out (ptr) extern void my_callback_generic_report_out (uint8_t *report);
#define UDI_HID_REPORT_IN_SIZE 64 #define UDI_HID_REPORT_OUT_SIZE 64 #define UDI_HID_REPORT_FEATURE_SIZE 4 Имена дефайнов говорят сами за себя. Информации о том, что такое vendor id и product id в сети достаточно. UDI_HID_REPORT_IN_SIZE и UDI_HID_REPORT_OUT_SIZE — это размеры входного и выходного буферов соответственно. Функция my_callback_generic_report_out () вызывается в тот момент, когда получен пакет данных с компьютера. В ней можно обрабатывать полученные данные.
После того, как USB инициализирован, его можно использовать. Код программы в общем случае выглядит вот так:
#define F_CPU 32000000UL
#include
int8_t ui_hid_report [64]; uint8_t report [64];
void my_callback_generic_report_out (uint8_t *data){ for (uint8_t i = 0; i < 64 i++){ report [i] = data[i]; } // теперь в массиве report находятся данные полученные в сообщении }
void main (){ irq_initialize_vectors (); cpu_irq_enable (); sysclk_init (); udc_start (); sysclk_enable_module (SYSCLK_PORT_C, SYSCLK_TC0); // включение тактирования таймера ТС0
while (1){ // отправка данных находящихся в массиве ui_hid_report на компьютер udi_hid_generic_send_report_in (ui_hid_report); } }
Скажу несколько слов о строчке sysclk_enable_module (SYSCLK_PORT_C, SYSCLK_TC0). Дело в том, что функция sysclk_init () по умолчанию выключает тактирование большей части периферии. Я так и не смог разобраться, по какому принципу она это делает, но разобрался, как включить периферию обратно:) Можно использовать sysclk_enable_module (), и дописывать в нее в качестве аргументов то что надо включить. Что именно дописывать можно понять, если использовать поиск по всему проекту и в качестве параметра поиска указать sysclk_disable_module.
Если залить этот код в контроллер, и подключить его к компьютеру, то в диспетчере устройств появится HID-совместимое устройство. А в устройствах и принтерах устройство с тем названием которое было указано в строке #define USB_DEVICE_PRODUCT_NAME. В моем случае все выглядит вот так:
СОФТ ДЛЯ КОМПЬЮТЕРА.
Все тот же Atmel любезно предоставляет нам примеры как это сделать на стороне компа. Они выполнены в Visual Studio, в которой я не силен, так что мне пришлось переписать под C++ Builder. По этому поводу коллеги говорили мне: «изыди». Но чем богаты тем и рады. В общем привожу пример на билдере.
Первым делом надо подключить библиотеку AtUsbHid.dll. Ее нужно взять в папке с атмеловским примером и скинуть в папку своего проекта. Для начала в .h файле проекта в соответсвующие места прописываем следующие строчки:
typedef ULONG HIDStatus; typedef HIDStatus WINAPI __import tcloseDevice (void); typedef HIDStatus WINAPI __import tfindHidDevice (const UINT VendorID, const UINT ProductID); typedef HIDStatus WINAPI __import twriteData (UCHAR* buf);
protected: tcloseDevice *closeDevice; tfindHidDevice *findHidDevice; twriteData *writeData; Далее в теле программы, например, при создании формы нужно написать следующее.
HINSTANCE AtUsbHidhandle;
AtUsbHidhandle = LoadLibrary («AtUsbHid.dll»); if (AtUsbHidhandle == 0) ShowMessage («Не найдена AtUsbHid.dll»); else{ closeDevice = (tcloseDevice*)GetProcAddress (AtUsbHidhandle, «closeDevice»); findHidDevice = (tfindHidDevice*)GetProcAddress (AtUsbHidhandle, «findHidDevice»); writeData = (twriteData*)GetProcAddress (AtUsbHidhandle, «writeData»); } Теперь мы имеем функции для работы с HID устройсnвом. findHidDevice (VID, PID) ищет в системе устройство с соответствующими VID и PID. После этого с ними можно работать. writeData () отправляет массив на устройство. Использовать это все можно например так:
#define VID 0×03EB #define PID 0×2013
char a = 0; a = findHidDevice (VID, PID_1); if (a!= 0){ Label1→Caption = «Подключено»; }
UCHAR leds[64]; leds[0] = 255; leds[1] = 10; leds[2] = 20; leds[3] = 30; writeData (leds);
closeDevice (); Вот так я реализовал HID девайс на ATXMega. Конечно, в статье не раскрыты тонкости и нюансы настрОйки USB. Однако теперь, у тех, кто только начинает свое знакомство с этой темой, есть инструкция к действию, а уж дальше, ковыряйтесь, разбирайтесь, вам и карты в руки!
Ну и конечно в конце обязательно видео, как это работает у меня. Видеокамера с регулируемым зумом, фокусом и диафрагмой.[embedded content]