Как стать автором
Обновить

Аппаратный модуль USB в ATXMega. Инструкция по реализации HID

Время на прочтение5 мин
Количество просмотров28K
Аппаратный модуль 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). Далее в открывшемся окне необходимо выбрать пункты, показанные на картинке.

image

После создания нового проекта нужно запустить ASF Wizard (меню ASF->ASF Wizard). Во вкладке Availble modules нужно найти USB Device (service), и нажать Add. После этого во вкладке Selected Modules появится USB Device (service), а напротив него выпадающий список. В нем выбираем hid_generic. После чего нажимаем на кнопку Summary, и на этом создание проекта закончено. Теперь он готов к тому, чтобы наполнить его смыслом. В основном файле проекта main.c мы увидим следующее содержание:

#include <asf.h>

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             0x03EB
#define  USB_DEVICE_PRODUCT_ID            0x2013
#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 <asf.h>

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 0x03EB
#define PID 0x2013

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. Однако теперь, у тех, кто только начинает свое знакомство с этой темой, есть инструкция к действию, а уж дальше, ковыряйтесь, разбирайтесь, вам и карты в руки!

Ну и конечно в конце обязательно видео, как это работает у меня. Видеокамера с регулируемым зумом, фокусом и диафрагмой.

Теги:
Хабы:
Всего голосов 22: ↑20 и ↓2+18
Комментарии5

Публикации

Истории

Ближайшие события

One day offer от ВСК
Дата16 – 17 мая
Время09:00 – 18:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область