Аппаратный модуль 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 мы увидим следующее содержание:
Функция board_init() инициализирует отладочную плату, XMEGA-A3BU Xplained. Так как у меня была не эта плата, а своя, я эту функцию выкинул. Далее нам понадобится функция для инициализации USB. Это udc_start(). Она объявлена где-то в недрах asf.h, так что спокойненько ее прописываем. Она инициализирует USB в соответствии с настройками, описанными в файле conf_usb.h.Этот файл находится в папке с проектом. Приведу те строки этого файла которые особо важны для настройки USB HID:
Имена дефайнов говорят сами за себя. Информации о том, что такое vendor id и product id в сети достаточно. UDI_HID_REPORT_IN_SIZE и UDI_HID_REPORT_OUT_SIZE — это размеры входного и выходного буферов соответственно. Функция my_callback_generic_report_out() вызывается в тот момент, когда получен пакет данных с компьютера. В ней можно обрабатывать полученные данные.
После того, как USB инициализирован, его можно использовать. Код программы в общем случае выглядит вот так:
Скажу несколько слов о строчке 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 файле проекта в соответсвующие места прописываем следующие строчки:
Далее в теле программы, например, при создании формы нужно написать следующее.
Теперь мы имеем функции для работы с HID устройсnвом. findHidDevice(VID, PID) ищет в системе устройство с соответствующими VID и PID. После этого с ними можно работать. writeData() отправляет массив на устройство. Использовать это все можно например так:
Вот так я реализовал HID девайс на ATXMega. Конечно, в статье не раскрыты тонкости и нюансы настрОйки USB. Однако теперь, у тех, кто только начинает свое знакомство с этой темой, есть инструкция к действию, а уж дальше, ковыряйтесь, разбирайтесь, вам и карты в руки!
Ну и конечно в конце обязательно видео, как это работает у меня. Видеокамера с регулируемым зумом, фокусом и диафрагмой.
ЖЕЛЕЗО. Я использовал контроллер 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 <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. Однако теперь, у тех, кто только начинает свое знакомство с этой темой, есть инструкция к действию, а уж дальше, ковыряйтесь, разбирайтесь, вам и карты в руки!
Ну и конечно в конце обязательно видео, как это работает у меня. Видеокамера с регулируемым зумом, фокусом и диафрагмой.