Управление светодиодами через usb из делфи

Обновлено: 30.06.2024

Здесь рассмотрено ПО хоста для взаимодействия с устройствами USB HID через одну из программных библиотек. Протокол USB очень сложен, и поэтому обычной практикой работы с ним является использование готовых библиотек подпрограмм (в виде DLL или исходного кода).

Один из вариантов использования интерфейса USB - применение стандартного класса устройств USB HID. Как известно, устройства USB HID очень удобны для подключения пользовательских устройств к компьютеру, так как не требуют написания драйвера, и максимальная скорость обмена с USB HID (64 килобайта/сек) в большинстве случаев оказывается достаточной.

[Краткий обзор библиотек]

Для наглядности библиотеки сведены в таблицу, далее более подробно рассмотрена каждая из библиотек.

Все примеры программ, перечисленные в таблице, Вы можете скачать по ссылке [1].

[HID.dll]

Пример ПО хоста (см. скриншот), которое общается с USB HID через библиотеку HID.dll, имеется в Windows DDK (см., например c:\WINDDK2003\3790.1830\src\wdm\hid\hclient\ для Windows DDK 2003). Сама HID.dll находится в папке %windir%\system32\, и всегда доступна для доступа использующих её программ.

hclient.PNG

Статью, описывающую пример, см. в [2]. Для того, чтобы скомпилировать пример, Вам нужен Windows DDK любой версии. Писать программу с использованием примера удобнее всего в Visual Studio C++, однако если Вы умеете загружать в память библиотеки DLL и вызывать её функции, то язык и система программирования никакого значения не имеют. Пример ПО хоста не привязан к конкретному USB HID устройству, просто предоставляет интерфейс для получения информации по подключенным устройствам USB HID. Достоинство использования HID.dll в том, что на платформе Windows это наиболее "прямой" метод получения доступа к устройствам USB HID, тогда как остальные библиотеки просто являются обертками над вызовами HID.dll. Недостаток библиотеки - неудобный интерфейс API, отсутствие исходного кода, ограниченное использование - только на операционных системах Windows. Исходный код примера можно взять в Windows DDK или скачать по ссылке [1], см. папку HID.dll\hclient\.

[HID.dll / hidlibrary.h]

usb-volume-control-USB-HID

[AtUsbHid.dll]

Библиотека достаточно хорошо документирована, имеет простой интерфейс API для использования. Есть примеры GUI-программ, написанных на C++ и Java, а также пример консольной программы.

У этой библиотеки, к сожалению, есть очень большой недостаток - разработчик (компания Atmel) её давно забросил где-то с 2008 года, и исходного кода не предоставил. Есть поддержка только 32-bit версий Windows, 64-bit версии не поддерживаются.

Удобнее всего программировать в среде Visual Studio, однако если Вы умеете загружать в память библиотеки DLL и вызывать её функции, то язык и система программирования никакого значения не имеют. Демонстрационная программа ПО хоста рассчитана на подключение к USB HID, собранному на основе чипа AVR с аппаратным USB и прошитым соответствующим firmware (например, чип AT90USB162, который установлен на макетной плате AVR-USB162 или AVR-USB162MU). Обзор работы с примером можно найти в [3]. Firmware управляет светодиодами и читает состояние кнопок (светодиоды и кнопки подключены к ножкам чипа AT90USB162). С библиотекой AtUsbHid.dll необязательно использовать именно микроконтроллеры Atmel, можно управлять любыми устройствами USB HID. Достоинство AtUsbHid.dll - простота применения. Недостаток - вместе с программой нужно предоставлять библиотеку AtUsbHid.dll, а также ограниченное использование - только на операционных системах Windows. Исходный код примера ПО хоста (и скомпилированные рабочие бинарники) можно найти на сайте Atmel или скачать по ссылке [1], см. папку AtUsbHid.dll\. Код firmware USB HID (исходники и прошивки) можно найти в папке AtUsbHid.dll\firmware\AVR-USB162 и в папке AtUsbHid.dll\firmware\userial (firmware для макетных плат AVR-USB162 и userial соответственно) архива по ссылке [1]. Для указаний по компиляции и использования примеров firmware читайте соответствующие файлы readme.txt в папках AVR-USB162 и userial. Также см. на сайте Atmel апноут AVR328: USB Generic HID Implementation on megaAVR devices, и статью [14].

[HID API]

Кроссплатформенная библиотека с открытым исходным кодом, хорошо документированная. Самый лучший пример использования (ПО хоста и firmware USB HID для микроконтроллера AT90USB162), который мне удалось найти, это проект lightpack см. [5].

lightpack-hardware.jpg
lightpack-ambilight.jpg

Исходный код примера ПО хоста (и скомпилированные рабочие бинарники) можно скачать по ссылке [1], см. папку hidapi-0.5.2 и папку lightpack. Чтобы скомпилировать ПО хоста lightpack, Вам понадобится среда программирования QT, а чтобы скомпилировать firmware USB HID (для чипа AT90USB162, макетная плата AVR-USB162), понадобится пакет WinAVR. Для указаний по компиляции и использования примеров firmware читайте соответствующие файлы readme.txt.

[LibUSB]

Широко известная кроссплатформенная библиотека, используется во многих коммерческих продуктах. Хорошо документирована, исходный код открыт, примеры кода ПО хоста на ней найти довольно легко. На мой взгляд, самые лучшие примеры есть в составе библиотеки V-USB (см. [6]), причем примеры ПО хоста libusb сопряжены с firmware устройства USB HID (на библиотеке V-USB). Т. е. у Вас есть готовые примеры ПО хоста и готовые примеры устройств USB HID, которые обмениваются с этим ПО хоста данными. Устройства USB HID в библиотеке V-USB рассчитаны на обычные чипы AVR, которые не имеют на борту аппаратного контроллера USB (т. е. протокол USB обрабатывается внутри AVR чипа программно), однако можно написать программу, которая будет работать с любым устройством USB. Примеры ПО хоста рассчитаны на GCC (язык C) или Visual Studio (язык C++), однако если Вы знаете, как загружать в память DLL и вызывать её функции, то нет проблем портировать примеры на любую другую среду программирования (см. [7]). Недостаток библиотеки libusb в том, что для ПО хоста, написанной на ней, под Windows нужна установка самой библиотеки, а также нужна привязка к устройству USB HID специального драйвера фильтра (filter driver). Этот драйвер фильтра устанавливается с помощью специальной утилиты, входящей в состав пакета библиотеки libusb.

howto-use-libusb-cmdline.PNG

Исходный код примеров ПО хоста (и скомпилированные рабочие бинарники) можно скачать по ссылке [1], см. папки libusb\examples\hid-custom-rq\commandline, hid-custom-rq\set-led-gui, hid-data\commandline. Имеется также удобная тест-программа (папка examples\usbtool), показывающая параметры всех подключенных к компьютеру устройств USB (для которых также установлен драйвер фильтра). Код firmware USB HID (исходники и прошивки) можно найти в папках hid-custom-rq\firmware-mega, hid-custom-rq\firmware-tiny, hid-data\firmware, hid-mouse\firmware. Примеры firmware удобно запускать на макетных платах AVR-USB-MEGA16 (микроконтроллер ATmega32). Чтобы скомпилировать ПО хоста, Вам необходим компилятор GCC и набор утилит MinGW (см. [8]), или (для примера hid-custom-rq\set-led-gui) Visual Studio C++. Для указаний по компиляции и использования примеров firmware и ПО хоста читайте соответствующие файлы readme.txt, или см. информацию вики LibUSB [9]. Бинарники LibUSB, достаточные для запуска приложений Windows (в том числе и драйвер фильтра filter driver и мастер по его установке) можно скачать по ссылке [11], см файл с названием наподобие libusb-win32-devel-filter-1.2.5.0.exe (цифры версии могут отличаться).

[JEDI Visual Component Library (JVCL)]

Библиотека визуальных и невизуальных компонентов для популярной среды разработки Delphi. Пользоваться библиотекой довольно просто, хороший пример использования - конфигуратор устройства USB HID для управления частотой вращения вентиляторов системного блока в зависимости от температуры [15].

hid-mm-fan-control-pic07-host-software-connected

Здесь приведен только краткий обзор классов и компонентов JVCL, относящийся к управлению устройствами USB HID.

TJvHidDeviceController предоставляет доступ списку USB контроллеров, отслеживает изменение состояния контроллеров, подключение/отключение устройств.

TJvHidDeviceController:OnDeviceChange - обработчик подключения/отключения устройств.

TJvHidDeviceController:OnEnumerate - получение списка устройств.

TJvHidDevice - предоставляет доступ к конечному USB устройству. В конфигураторе используются только два метода для получения и отправки пакета данных.

TJvHidDevice:GetFeature - получение пакета данных (прием данных от устройства).

TJvHidDevice:SetFeature - отправка пакета данных (передача данных в устройство).

[Processing]

Пример работы с устройством HID (HIDSerialMonitor) предназначен для плат Arduino и USnooBie. В репозитории на GitHub [16] можно найти как готовые скомпилированные бинарники для Linux (32-бит, 64-бит) и для Windows, так и исходный код на языке Processing. Код использует вызовы библиотеки hidapi. Скомпилированные программы и отдельные приложения не требуют никакого дополнительного программного обеспечения.

Программа HIDSerialMonitor нужна как альтернатива Serial Monitor в среде разработки Arduino, это позволяет освободить порт UART микроконтроллера.

Чтобы скомпилировать код самостоятельно, Вам нужно установить Processing (см. сайт processing.org), и скопировать библиотеки (G4P и hiddapi) в папку библиотек Processing. Затем загрузите HIDSerialMonitor.pde в среде разработки Processing и кликните "Run".

[Что нужно, чтобы попробовать]

Затем нужно определиться с выбором устройства USB HID (если еще его у Вас нет). Дешевый и удобный вариант, для которых есть готовые примеры firmware USB HID - это макетные платы AVR-USB162, AVR-USB162MU, AVR-USB-MEGA16 (см. [4]). Для заливки прошивки firmware в эти платы не нужен программатор, firmware заливается в память чипа через USB бутлоадер. Для макетных плат AVR-USB162 и AVR-USB162MU можно взять готовые примеры кода USB HID от Atmel и из библиотеки LUFA (см. [10]), а для макетной платы AVR-USB-MEGA16 - из библиотеки V-USB (см. [6]).

[Ссылки]

Комментарии

Не получается скомпилировать lightpack, установил qt sdk и qt creator. Если сможете опишите процесс компиляции подробнее.

microsin: давно дело было, поэтому попробовать и описать подробно не выйдет. Но поверьте, там описывать нечего - тупо открыл готовый проект и тупо нажал на кнопку компиляции. Даже настройки никакие не менял и не смотрел. Почитайте доку к лайтпаку, там должен быть расписан процесс получения бинарников, и его подводные камни. По крайней мере во всех уважающих себя Open Source проектах так обычно делают.

Далее открываем cmd (командная строка) и переходим в этой командной строке в папку "firmware", если например папка v-usb находиться на диске d то сделать это можно 3мя командами: Далее для того чтобы откомпилировать пример вводится команда: Если компиляция прошла успешно то в папке "firmware" должен появиться файл "main.hex", его нужно прошить в микроконтроллер. Если используется программатор stk200 (5 проводков), lpt порт, микроконтроллер m8, частота кварцевого резонатора 12МГц, файл "main.hex" скопирован в корень диска C, на компьютере имеется утилита avrdude которая устанавливается вместе с WinAVR то для прошивки файла "main.hex" в микроконтроллер в командной строке достаточно выполнить две команды: avrdude -c stk200 -P lpt1 -p m8 -U lfuse:w:0x9f:m -U hfuse:w:0xc9:m -U flash:w:main.hex Обратите внимание на "-U lfuse:w:0x9f:m -U hfuse:w:0xc9:m" -это установка фьюзов если их неправильно установить то можно испортить микроконтроллер. Для правильной установки фьюзов может пригодится калькулятор фьюзов http://fusecalc.mirmk.net/. Для того чтобы команда в командной строке выполнилась необходимо вписать эту команду в чёрное текстовое поле командной строки и нажать клавишу "Enter" на клавиатуре компьютера в котором открыта эта командная строка. Для того чтобы hex-файл записался (прошился) в микроконтроллер необходимо чтобы в момент когда компьютер выполняет запись этой прошивки в микроконтроллер к этому компьютеру был подключён программатор к которому подключён микроконтроллер иначе прошить микроконтроллер не получиться. Если программатор stk200 (5 проводков) то на микроконтроллер в момент прошивки должно быть подано питание, питание следует подавать только после подключения программатора к компьютеру и отключать только перед выниманием из компьютера иначе можно испортить порт или микроконтроллер. Если прошивка прошла успешно то можно воткнуть устройство в usb порт и посмотреть в диспетчере устройств распозналось ли устройство или нет. Если в диспетчере устройств написано "неизвестное устройство" или что то вроде этого то устройство не распозналось и необходимо искать причину и устранять её, если в диспетчере устройств появилась строка где написано "HID-устройство","usb устройство ввода" или что то в этом роде то устройство распозналось. Для организации "общения" компьютера с микроконтроллером используя скачанный пример необходимо скачать и установить библиотеку libusb (libusb-win32-releases) при установке устройство должно быть подключено к компьютеру для того чтобы заодно установился драйвер фильтра. Подробнее про установку libusb на сайте: microsin.net. В папке "commandline" (которая находится там же где и папка "firmware") есть файл "Make" его надо открыть и найти в нём строки:

const int BUTTON_ON_OFF=1000;

usb_dev_handle *handle = NULL;
const unsigned char rawVid[2] = , rawPid[2] = ;
char vendor[] = , product[] = ;
char buffer[4];
int vid, pid;
unsigned short key_on_off=0;

/* Declare Windows procedure */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/* Make the class name into a global variable */
char szClassName[ ] = "WindowsApp";

int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nFunsterStil)

HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
WNDCLASSEX wincl; /* Data structure for the windowclass */

/* The Window structure */
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */
wincl.style = CS_DBLCLKS; /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);

/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL; /* No menu */
wincl.cbClsExtra = 0; /* No extra bytes after the window class */
wincl.cbWndExtra = 0; /* structure or the window instance */
/* Use Windows's default color as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
return 0;

/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
0, /* Extended possibilites for variation */
szClassName, /* Classname */
"Windows App", /* Title Text */
WS_OVERLAPPEDWINDOW, /* default window */
CW_USEDEFAULT, /* Windows decides the position */
CW_USEDEFAULT, /* where the window ends up on the screen */
544, /* The programs width */
375, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /* No menu */
hThisInstance, /* Program Instance handler */
NULL /* No Window Creation data */
);

CreateWindow("button", "on/off", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,50, 50, 100,30, hwnd,(HMENU)BUTTON_ON_OFF,hThisInstance,NULL);

usb_init();

vid = rawVid[1] * 256 + rawVid[0];
pid = rawPid[1] * 256 + rawPid[0];


if(usbOpenDevice(&handle, vid, vendor, pid, product, NULL, NULL, NULL) != 0) MessageBox(hwnd, "Could not find USB device\n","error", 0);
exit(1);
>
else
MessageBox(hwnd, "Find USB device done!\n","Done!", 0);
>

ShowWindow (hwnd, nFunsterStil);

while (GetMessage (&messages, NULL, 0, 0))
TranslateMessage(&messages);
DispatchMessage(&messages);
>
usb_close(handle);
return messages.wParam;
>

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
switch (message)
case WM_DESTROY:
PostQuitMessage (0);
break;
case WM_COMMAND:
if(LOWORD(wParam)==BUTTON_ON_OFF)
if(key_on_off==1)
usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, CUSTOM_RQ_SET_STATUS, 1, 0, buffer, 0, 5000);
key_on_off=0;
>
else
usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, CUSTOM_RQ_SET_STATUS, 0, 0, buffer, 0, 5000);
key_on_off=1;
>
>
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
>
return 0;
>

Далее для того чтобы откомпилировать пример вводится команда: make
Если компиляция прошла успешно то в папке "commandline" должен появиться файл
"set-led.exe" -это и есть программа которая включает светодиод при нажатии кнопки.
Изменённый (немного криво но работающий) проект можно скачать с Яндекс диска.
Если всё сделано правильно то при запуске "set-led.exe" появится окно с кнопкой нажатие на которую приводит к включению светодиода, если устройство подключено к usb.

В продолжение темы о создании собственного USB-гаджета.
Создание простого устройства.

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

Самым простым вариантом передачи данных является использование класса коммуникационных устройств USB (CDC).
При таком подключении устройство будет видно в системе как обычный виртуальный COM-порт.
Плюсом такого подключения является отсутствие необходимости писать собственные драйвера.
Так же радует простота приема и передачи данных: для работы с портом в Windows достаточно открыть его как текстовый файл и производить обычные операции чтения\записи.

Железо.

Возьмем схему с минимальной обвязкой МК.


На этот раз нам нужно добавить только 4 контакта к USB и одну кнопку (кнопка нужна только для бутлоадера: куда проще нажать ее и заменить прошивку в устройстве по USB, нежели переставлять чип в программатор).


Не сильно стараясь сделать красиво, разводка может выглядеть так:


Но при желании часто экспериментировать с подключаемыми компонентами лучше сразу развести каждую ногу МК сделав аналог ардуино — Jaluino.

Прошивка

include 18f2455 -- библиотека для используемого МК
--
enable_digital_io () -- переключение всех входов на цифровой режим
--
alias Button is pin_B7 -- раз уж у нас подключена кнопка, объявим ее
pin_B7_direction = input -- кнопка у нас работает на вход
--
-- одна строчка - и у нас есть все необходимое для работы с USB CDC
include usb_serial -- бибилотека для работы с usb
--
usb_serial_init () -- --инициализируем USB CDC
forever loop -- основной цикл, выполняется постоянно
usb_serial_flush () -- обновление usb. Данная процедура выполняет все необходимые
-- действия для поддержания соединения с ПК
end loop

Скомпилировав данный код, записав полученный HEX файл в МК при помощи бутлоадера и запустив устройство можно будет наблюдать как в системе опрделится новое устройство: Виртуальный сom-порт.


Теперь, когда устройство уже работает, научим его общаться.

Для чтения принятого байта существует функция usb_serial_read(byte):boolean. При наличии полученного байта она заносит его в указанную переменную и возвращает true, иначе возвращает false.

Для отправки байта существует процедура usb_serial_data. Она замаскирована под переменную, потому для отправки байта достаточно присвоить ей значение отправляемого байта.

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

include 18f2455
--
enable_digital_io ()
--
alias Button is pin_B7
pin_B7_direction = input
--
--
include usb_serial
--
usb_serial_init ()
var byte ch -- объявляем переменную
forever loop -- основной цикл
usb_serial_flush ()
if ( usb_serial_read ( ch ) ) then -- если байт получен, он будет записан в ch
usb_serial_data = ch -- отправляем полученный байт обратно
end if
end loop

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

Пока у нас нет своего, используем готовый терминал: я использовал программу RealTerm.
Открываем порт с нужным номером и отправляем данные.



И нам в ответ приходит то, что мы отправили. Значит, все работает как надо.

Итак, наш микроконтроллер умеет принимать байты и тут же отправлять их обратно. Теперь напишем свой софт для общения с ним (я буду использовать Delphi).

Создаем новый проект, раскидываем по форме необходимые компоненты:
SpinEdit1 — для указания номера порта
Button1 — для установки соединения
Button2 — для разрыва соединения
SpinEdit2 — для ввода байта в десятичном виде
Button3 — для отправки байта
Memo1 — для вывода принятой информации.

Как уже было сказано выше, с com-портом нужно работать так же, как и с обычным текстовым файлом: используя функции CreateFile, WriteFile и ReadFile.

Дабы не вдаваться в подробности, возьмем готовую библиотеку для работы с com-портом: ComPort.

Вешаем на каждую кнопку необходимую задачу и получаем конечный код:

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics , Controls, Forms,
Dialogs, StdCtrls, Spin,ComPort;

type
TForm1 = class (TForm)
SpinEdit1: TSpinEdit;
Button1: TButton;
Button2: TButton;
SpinEdit2: TSpinEdit;
Button3: TButton;
Memo1: TMemo;
procedure OnRead(Sender: TObject; ReadBytes: array of Byte );
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
< Private declarations >
Port: TComPort;
public
< Public declarations >
end;

var
Form1: TForm1;
num: integer;
implementation

procedure TForm1.Button1Click(Sender: TObject);
begin
Port := TComPort.Create(SpinEdit1.Value, br115200); //создаем соединение
Port.OnRead := OnRead; //создаем поток чтения принятых данных
Button2.Enabled := true ; //активируем кнопку закрытия соединения
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
Port.Free; //закрываем соединение
Button2.Enabled := false ; //отключаем кнопку
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
if Button2.Enabled then Port.Write([SpinEdit2.Value]);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
if Button2.Enabled then
Port.Free;
end;

procedure TForm1.OnRead(Sender: TObject; ReadBytes: array of Byte );
var
i:integer;
begin
for i := Low(ReadBytes) to High(ReadBytes) do //проходим по массиву принятых байт
begin
Memo1.Text := Memo1.Text + '.' +InttoHex(ReadBytes[i],2); //добавляем его HEX значение в окно
inc(num); //считаем колв-о принятых байт
end;
if num > 10 then begin
Memo1.Lines.Add( '' ); //переносим строку
num := 0;
end;
end;

Запускаем, устанавливаем соединение, отправляем байты:


Вот и готов наш самый простой терминал для работы с самым простым usb-устройством.

Как видно, чтение и запись происходит динамическими массивами байт.

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

include 18f2455
--
enable_digital_io ()
--
alias Button is pin_B7
pin_B7_direction = input
--
--
include usb_serial
--
usb_serial_init ()
var byte ch
var byte i -- объявляем вторую переменную
forever loop -- основной цикл
usb_serial_flush ()
if ( usb_serial_read ( ch ) ) then -- если байт получен выполняем необходимые действия
case ch of -- перебираем номер байта
0 : usb_serial_data = 0xff
1 : usb_serial_data = Button -- отправка состояния кнопки
OTHERWISE block -- если получено что-то иное
for 16 using i loop -- отправляем 10 байт с данными
usb_serial_data = ch + i -- от ch до ch+15
end loop
end block
end case
end if
end loop

Дополнительные возможности

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

Упрощение отправки данных

Отправлять информацию по одному байту — не всегда удобно. Очень часто может пригодиться библиотека print. Она содержит процедуры по отправке данных всевозможной длины всевозможными форматами: byte,hex,dec,bin,boolean что может упростить вывод данных в программе.

> include print
.
var dword data
print_dword_hex ( usb_serial_data , data )

Название всех команд можно посмотреть в файле библиотеки.

Ожидание подключения к ПК

Если перед стартом основного цикла микроконтроллера необходимо предварительно установить соединение с ПК, то можно дописать перед ним строчки

while ( usb_cdc_line_status () == 0x00 ) loop
end loop

Привязываем к устройству номер порта

Если оставить все как есть, система при каждом новом подключении будет выделять первый свободный номер порта. А это значит что за ним придется всегда следить.
Для того, что бы этого не происходило, необходимо устройству присвоить уникальное значение серийного номера до подключения библиотеки usb:
Номер может быть любой длины и содержать различные символы.

const byte USB_STRING3 [ 24 ] =
24 , -- длина массива
0x03 , -- bDescriptorType
"0" , 0x00 ,
"1" , 0x00 ,
"2" , 0x00 ,
"3" , 0x00 ,
"4" , 0x00 ,
"5" , 0x00 ,
"6" , 0x00 ,
"7" , 0x00 ,
"8" , 0x00 ,
"9" , 0x00 ,
"X" , 0x00
>

Меняем имя устройства на свое

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


Но увы, после установки драйверов устройство поменяет имя на указанное в .inf файле, потому поменяем имя и там


Организуем автоподключение устройства

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

Прежде всего необходимо присвоить своему устройству уникальное значение производителя и продукта, дабы легко определять его среди сотен других стандартных CDC-прошивок.
VID и PID выдаются за денюжку, потому пойдем по пуути китайцев: втихую возьмем себе заведомо свободные значения.

Прошивка:
В прошивке необходимо объявить две переменные до подключения библиотеки USB

const word USB_SERIAL_PRODUCT_ID = 0xFF10
const word USB_SERIAL_VENDOR_ID = 0xFF10

Вместо FF10 можно вставить любые два слова (2 байта). Конечный результат содержится в прилагаемом архиве.

Драйвера:
Так как драйвера не предназначены для нашей комбинации VID и PID, допишем наши значения в .inf файл вручную:

[DeviceList]
%DESCRIPTION%=DriverInstall, USB\VID_FF10&PID_FF10

[DeviceList.NTamd64]
%DESCRIPTION%=DriverInstall, USB\VID_FF10&PID_FF10

Софт:
Для отлова событий подключения\отключения устройства подключим библиотеку ComponentUSB. Не считаю нужным пояснять каждую строчку: все изменения можно увидеть в прилагаемом проекте.

Результат

На скриншоте сложно разглядеть, но кнопка отправки активна только в момент наличия подключенного устройства, при этом каждые 50мс программа подает запрос на получение состояния кнопки (что, впрочем, неправильно, потому как нажатие кнопки должно обрабатываться на МК).


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


Что только нынче не подключают к компьютеру! Помимо традиционных клавиатуры, мыши и принтера можно встретить ключи защиты данных, кофейные кружки с подогревом, нестандартные преобразователи данных, всевозможные датчики, контроллеры и многое другое. Но ведь мало только физически подсоединить устройство к компьютеру, нужно еще и наладить обмен данными между ними. Как же выбрать порт и организовать подключение? Несколько лет назад стандартным решением было использование 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; — устрой-ство, от которого были получены данные;

ReportID: Byte; // номер дескриптора репорта
dta: Pointer; Size: word; //параметры для получения данных

Событие 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 выводит в окно отладочную информацию в процессе работы программы.

Листинг 1. Инициирование диска поиска устройства

procedure TITCR2.ScanDevices;
begin
if DriverComponentonil then
begin
if (DriverComponent as TComponent).ClassName='TJvHidDeviceController' then
begin (DriverComponent as TJvHidDeviceCont ro1ler) OnEnumerate:=HidCtlEnumerate;
// инициируем nepe6op имеющихся в системе
// HID-устройств
(DriverComponent as TJvHidDeviceCont ro1ler).Enumerate;
end
else
begin
self.AddError('TITCR2.Init: ClassNameoTJvHidDeviceController') ;
Exit;
end;
end
else
begin self.AddError('TITCR2.Init: DriverComponent = nil');
Exit;
end;
end;

В листинге 2 приведен обработчик события OnEnumerate для поиска необходимого внешнего устройства. Для простоты будем считать, что программа может работать только с одним устройством нужного ей типа.

Листинг 2. Обработчик поиска


function TITCR2.HidCtlEnumerate(HidDev: TJvHidDevice;
const Idx: Integer): Boolean; var
N: Integer;
PN:String; begin
PN:= Trim(HidDev.ProductName);
if PN <> '' then begin // Пытаемся найти необходимое нам оборуждование
if (PN = 'Card Reader UUS1') then begin
ProductName_: = PN; SerialNuraber_:=HidDev.SerialNunber;
VendorName_:=HidDev.VendorName; // Принимаем это устройство как текущее для дальнейшей работы
Device_:=HidDev; end else
begin
// другие действия. если устройство не распознано
end;
end;
Result:=True; end;

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

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

Ибо лучшее решение — то, которое будет реализовано в сжатые сроки с минимальной привязкой к программной среде и с большими возможностями дальнейшего развития. На основе этих принципов был создан протокол обмена верхнего уровня, где главное понятие — «команда». Из листинга 3 видно, насколько автор любит строковые данные, не раз спасавшие его при отладке программных модулей. Как же замечательно, что у нас вообще есть тип String! Все команды протокола делятся на категории (классы), внутри которых существует код команды, однозначно характеризующий ее назначение. Параметр edParam служит для отсылки данных в устройство, а параметр edAnswerData содержит в себе полученные от устройства данные.

Строковый тип описанных членов записи позволяет свободно и наглядно манипулировать данными в формате HEX-строки. И что самое приятное, формат описанной записи идеологически стоит где-то посередине между ее непосредственным назначением и различными формами ее представления (INI, HEX, XML и т. д.)

Листинг 3. Пример структуры отсылки данных для описания алгортимов обмена данными с внешним устройством.

// структура соответствует протоколу

// верхнего уровня,
// оговоренному для специфического устройства.
TedCommand = RECORD Caption: String; // метка команды
INDEX:Integer;
// индекс команды Enabled:Boolean; // флаг пригодности команды
WT:Cardinal; // время обработки
edClass: String [2] ;// HBX-строка класса команды
edCode:String[2];// HEX-строка кода команды
edLength:String[2]; // длина
edParam: String; // HEX-CTpoкa (параметр отсылки)
edAnswerdta:String;// HEX-CTpoкa
// полученных данных
IsAnswerdta:Boolean;
// флаг новых полученных данных ERROR:String; // поле для последней ошибки
WORKED:Boolean; //флаг обработки команды
end;

Выполнение команды, т. е. отсылка данных в устройство, реализовано с применением отсылки пакетов данных длиной 8 байт (листинг 4). Эта длина — не единственное решение, такой выбор продиктован требованиями протокола верхнего уровня и в каждом конкретном случае может быть другим. Это, что называется, дело вкуса. Странный флаг IsUSBMode в методе ExecuteCommand (листинг 5 на «Мир ПК-диске») оставлен как напоминание о том, что вместо работы с USB нам может потребоваться использовать COM-порт или какой-то другой интерфейс. В начале отсылаемой группы данных в устройство передается синхросерия произвольно выбранного формата (например, 3E3E3E2B), сообщающая устройству, что у него на входе вполне легальные данные. Напомню, что в данном случае речь идет не столько о HID, сколько о специфическом протоколе верхнего уровня, идеологически оторванном от «железа» и предназначенном для решения особых прикладных задач.

Листинг 4. Отсылка данных


function TITCR2.ExecuteCommand(var edCommand: TedCommand; const paraml,
param2: Integer): Integer; var HD:TJvHidDevice;
Buf: array [0..64] of Byte; Written: Cardinal; ToWrite: Cardinal; blockString,sblockString:String; bN,i:Integer; begin result: = -l; try
if not (edCommand.Enabled! then Exit; edCommand.WORKED:= false; edCommand.WT:=GetTickCount; ifIsUSBMode then begin try
Last CMD_: = edCommand;
// подготовка отсылки данных,
// начальная последовательность байт соответствует протоколу обмена
// верхнего уровня, специфичному для данного устройства
blockString:=Trim('3E3E3E2B'+edCommand.edClass*edCommand.edCode+edCommand.edLength+edCommand.edParam);// достраиваем блок
bN:=Length(Trim(blockString)) div 16; if ((Length(Trim(blockString)) / 16) -bN)>0 then bN:=bN+l;
while true do
begin
if LengthfTrim(blockString))>=(bN*16) then break;
blockString:=blockString+' 00';
end;
//отсылаем данные по 8 байт
for i:=0 to bN-1 do begin
sblockString:=Copy(blockString,1*16,16);
SendData8b(sblockString); end;
LastCMDTime_:=Now; edCommand.WORKED: = true;
edCommand.WT:=GetTickCount-edCommand.WT; except on e:Exception do begin result:=-3;
/ ======================================
// обработка ошибки
end; end; end;
except on e:Exception do begin result:=-2;
//==========================:
// обработка ошибки
end;
end;

В обработчике GetDataExecutor полученных от устройства данных (пакет по 8 байт) использовано специально созданное событие OnNewInputData для передачи первично обработанных данных на дальнейшую обработку, причем с указанием их старого и нового значений (листинг 6 на «Мир ПК-диске»). Таким образом, события поступления необработанных данных и указание на дальнейшую обработку развязываются, позволяя добавлять какой-то специфический алгоритм предупреждения на раннем этапе ошибочной, повторной или ненужной входной информации.

Представленные здесь примеры работы с HID-устройством иллюстрируют общую идею статьи — относительную простоту программирования нестандартных HID-устройств средствами Delphi.

Научитесь программировать микроконтроллеры прямо сейчас - получить видеокурс по микроконтроллерам!

Программно зажигаем светодиод на определенное время.

Нам понадобятся:

-флюс-припой Работа с COM портом в Delphi (Реле времени)

  • Вставляем получившийся девайс в имеющийся на вашей машинке COM порт и переходим к программной части;
  • Запускаем Delphi, создаем новый проект и размещаем на форме следующие компоненты: 1 компонент Сombobox, 1 Edit и 1 Button, и настраиваем примерно так:

-светодиод-на-Дельфи Работа с COM портом в Delphi (Реле времени)

  • В разделе Var создаем глобальную переменную JRHandleтипThandle (JRHandle: Thandle);
  • В строки items компонента Combobox, вбиваем цифры от 1 до 9, по одной в каждую строку, при помощи него мы будем выбирать номер COM порта для подключения;
  • Дважды кликнув на Button1, создаем для него обработчик событий, где между, прописываем следующий код:

JRHandle : = CreateFile ( PChar ( 'com' + Combobox1 . text ) , GENERIC_READ or GENERIC_WRITE , 0 , nil , OPEN_EXISTING , FILE_FLAG_OVERLAPPED , 0 ) ;

ShowMessage ( 'Выбранный порт не найден на этом компьютере' ) ; //Проверка на обнаружение СОМ порта;

SetCommBreak ( JRHandle ) ; //Прерываем связь, приводим передачу в разорванное состояние - логический нуль (Высокий уровень сигнала);

sleep ( StrToInt ( Edit1 . Text ) ) ; //Удержание прерывания;

ClearCommBreak ( JRHandle ) ; //Сброс состояния прерывания - восстанавливает передачу данных - логическая единица (опять в низкий уровень сигнала);

  • Заходим в диспетчер устройств Windows (Мой компьютер-Свойства-Диспетчер устройств) находим раздел Порты (COM и LPT).

-устройств-Delphi Работа с COM портом в Delphi (Реле времени)

Поиск по сайту

Okolokompa в Яндекс Дзен

Читайте также: