Работа usb порта интерфейс программирования. Программирование USB в Android

09.10.2021 Игры

Начнем с минимума:
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.

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

unit Unit1;

interface

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();
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 , -- длина массива
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.
const byte USB_STRING2 =
{
28 , --
0x03 , -- bDescriptorType
"D" , 0x00 ,
"e" , 0x00 ,
"m" , 0x00 ,
"o" , 0x00 ,
" " , 0x00 ,
"B" , 0x00 ,
"o" , 0x00 ,
"a" , 0x00 ,
"r" , 0x00 ,
"d" , 0x00 ,
" " , 0x00 ,
"=" , 0x00 ,
")" , 0x00
}

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


DESCRIPTION=«Demo CDC»

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

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

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

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

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

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

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


%DESCRIPTION%=DriverInstall, USB\VID_FF10&PID_FF10


%DESCRIPTION%=DriverInstall, USB\VID_FF10&PID_FF10

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

Результат

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

Как видно, организовать обмен данными между МК и ПК через USB - не самое сложное занятие. Полученное соединение можно использовать не только для конечынх целей: оно так же подходит для отладки программы. Ведь отправить на компьютер результаты расчетов, текущие состояния регистров и переменных куда нагляднее, чем моргать парой светодиодов азбукой морзе.

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

Введение
Для кого эта книга
Что вы найдете в книге
Программные требования
Аппаратные требования
О программном коде
Краткое описание глав
Обозначения
Благодарности
Обратная связь
Часть I. Общие сведения о USB
Глава 1. Спецификация USB
1.1. Что такое USB и зачем это надо
1.1.1. Общая архитектура USB
1.1.2. Физическая и логическая архитектура USB
1.1.3. Составляющие USB
1.1.4. Свойства USB-устройств
1.1.5. Принципы передачи данных
1.1.6. Механизм прерываний
1.1.7. Режимы передачи данных
1.1.8. Логические уровни обмена данными
1.1.8.1. Уровень клиентского ПО
1.1.8.2. Уровень системного драйвера USB
1.1.8.3. Уровень хост-контроллера интерфейса
1.1.8.4. Уровень шины периферийного USB-устройства
1.1.8.5. Уровень логического USB-устройства
1.1.8.6. Функциональный уровень USB-устройства
1.1.9. Передача данных по уровням
1.1.10. Типы передач данных
1.1.11. Кадры
1.1.12. Конечные точки
1.1.13. Каналы
1.1.14. Пакеты
1.1.14.1. Формат маркер-пакетов IN, OUT, SETUP и PING
1.1.14.2. Формат пакета SOF
1.1.14.3. Формат пакета данных
1.1.14.4. Формат пакета подтверждения
1.1.14.5. Формат пакета SPLIT
1.1.15. Контрольная сумма
1.1.15.1. Алгоритм вычисления CRC
1.1.15.2. Программное вычисление CRC
1.1.16. Транзакции
1.1.16.1. Типы транзакций
1.1.16.2. Подтверждение транзакций и управление потоком
1.1.16.3. Протоколы транзакций
1.2. Запросы к USB-устройствам
1.2.1. Конфигурационный пакет
1.2.2. Стандартные запросы к USB-устройствам
1.2.2.1. Получение состояния GET_STATUS
1.2.2.2. Сброс свойства CLEAR_FEATURE
1.2.2.3. Разрешение свойства SET_FEATURE
1.2.2.4. Задание адреса на шине SET_ADDRESS
1.2.2.5. Получение дескриптора GET_DESCRIPTOR
1.2.2.6. Передача дескриптора SET_DESCRIPTOR
1.2.2.7. Получение кода конфигурации GET_CONFIGURATION
1.2.2.8. Задание кода конфигурации SET_CONFIGURATION
1.2.2.9. Получение кода настройки интерфейса GET_INTERFACE
1.2.2.10. Задание кода настройки интерфейса SET_INTERFACE
1.2.2.11. Задание номера кадра синхронизации SYNC_FRAME
1.2.2.12. Обработка стандартных запросов
1.2.3. Дескриптор устройства
1.2.3.1. Дескриптор устройства
1.2.3.2. Уточняющий дескриптор устройства
1.2.3.3. Дескриптор конфигурации
1.2.3.4. Дескриптор интерфейса
1.2.3.5. Дескриптор конечной точки
1.2.3.6. Дескриптор строки
1.2.3.7. Специфические дескрипторы
1.2.3.8. Порядок получения дескрипторов
1.3. Система Plug and Play (PnP)
1.3.1. Конфигурирование USB-устройств
1.3.2. Нумерация USB-устройств
1.3.3. PnP-идентификаторы USB-устройств
1.3.4. Символьные имена устройств
1.4. Модель WDM
Глава 2. Программирование на языке C для микроконтроллера
2.1. Общие сведения о языке С для микроконтроллеров
2.2. Использование стандартных библиотек
2.3. Программирование для АТ89С5131
2.3.1. Файл инициализации
2.3.2. Структуры дескрипторов
2.3.3. Структура проекта
Глава 3. Инструменты
3.1. Программаторы
3.1.1. Программатор Flip
3.1.2. Программатор ER-Tronik
3.2. Инструменты создания драйверов
3.2.1. NuMega Driver Studio
3.2.2. Jungo WinDriver
3.2.3. Jungo KernelDriver
3.3. Средства Microsoft Visual Studio
3.3.1. Depends (Dependency Walker)
3.3.2. Error Lookup
3.3.3. GuidGen
3.4. Средства Microsoft DDK
3.4.1. DeviceTree
3.4.2. DevCon
3.4.2.1. Ключ classes
3.4.2.2. Ключ driverfiles
3.4.2.3. Ключ hwids
3.4.2.4. Ключ rescan
3.4.2.5. Ключ stack
3.4.2.6. Ключ status
3.4.3. Chklnf и Genlnf
3.5. Средства CompuWare Corporation
3.5.1. Monitor
3.5.2. SymLink
3.5.3. EzDriverlnstaller
3.5.4. WdmSniff
3.6. Средства Syslnternals
3.6.1. WinObj
3.7. Средства USB Forum
3.7.1. HID Descriptor Tool
3.8. USB Command Verifier
3.9. Средства HDD Software
3.10. Средства Sourceforge
3.11. Программа мониторинга Bus Hound
Глава 4. Принципы использования функций Win32 в.NET
4.1. Общие сведения
4.2. Импорт функций Win32
4.3. Структуры
4.3.1. Атрибут StructLayout
4.3.2. Атрибут MarshalAs
4.4. Прямой доступ к данным
4.5. Обработка сообщений Windows
4.6. Общие сведения о WMI
4.7. Интернет-ресурсы к этой главе
Часть II. Классы USB
Глава 5. Класс CDC
5.1. Методы преобразования интерфейсов USB/RS-232
5.2. Общие сведения об интерфейсе RS-232
5.2.1. Линии обмена
5.2.1.1. Передаваемые данные (BA/TxD/TD)
5.2.1.2. Принимаемые данные (BB/RxD/RD)
5.2.1.3. Запрос передачи (CA/RTS)
5.2.1.4. Готовность к передаче (CB/CTS)
5.2.1.5. Готовность DCE (CC/DSR)
5.2.1.6. Готовность DTE (CD/DTR)
5.2.1.7. Индикатор вызова (CE/RI)
5.2.1.8. Обнаружение несущей (CF/DCD)
5.2.1.9. Готовность к приему (CJ)
5.3. Спецификация CDC
5.3.1. Стандартные дескрипторы
5.3.2. Функциональные дескрипторы
5.3.2.1. Заголовочный функциональный дескриптор
5.3.2.2. Дескриптор режима команд
5.3.2.3. Дескриптор абстрактного устройства
5.3.2.4. Дескриптор группирования
5.3.3. Специальные запросы
5.3.3.1. Запрос SET_LINE_CODING
5.3.3.2. Запрос GET_LINE_CODING
5.3.3.3. Запрос SET_CONTROL_LINE_STATE
5.3.3.4. Запрос SEND_BREAK
5.3.4. Нотификации
5.3.4.1. Нотификация RING^DETECT
5.3.4.2. Нотификация SERIAL_STATE
5.4. Поддержка CDC в Windows
5.4.1. Обзор функций Windows для работы с последовательными портами
5.4.1.1. Основные операции с портом
5.4.1.2. Функции настройки порта
5.4.1.3. Специальная настройка порта
5.4.1.4. Получение состояния линий модема
5.4.1.5. Работа с CDC на платформе. NET
5.4.2. Соответствие функций Windows и USB-запросов
Глава 6. Класс HID
6.1. Спецификация HID-устройств
6.2. Порядок обмена данными с HID-устройством
6.3. Установка драйвера HID-устройства
6.4. Идентификация HID-устройства
6.4.1. Идентификация загрузочных устройств
6.4.2. Дескриптор конфигурации HID-устройства
6.4.3. HID-дескриптор
6.4.4. Дескриптор репорта
6.5. Структура дескриптора репорта
6.5.1. Элементы репорта
6.5.1.1. Элементы короткого типа
6.5.1.2. Элементы длинного типа
6.5.2. Типы элементов репорта
6.5.2.1. Основные элементы
6.5.2.2. Глобальные элементы
6.5.2.3. Локальные элементы
6.5.3. Примеры дескрипторов
6.6. Запросы к НID-устройству
6.6.1. Запрос GET_REPORT
6.6.2. Запрос SET_REPORT
6.6.3. Запрос GET_IDLE
6.6.4. Запрос SET_IDLE
6.6.5. Запрос GET_PROTOCOL
6.6.6. Запрос SET_PROTOCOL
6.7. Инструменты
6.8. Драйверы для HID-устройств в Windows
Глава 7. Другие классы USB
Часть III. Практика программирования USB
Глава 8. Создание USB-устройства на основе АТ89С5131
8.1. Общая информация об АТ89С5131
8.2. Структурная схема АТ89С5131
8.3. USB-регистры AT89C5131
8.3.1. Регистр USBCON
8.3.2. Регистр USBADDR
8.3.3. Регистр USBINT
8.3.4. Регистр USBIEN
8.3.5. Регистр UEPNUM
8.3.6. Регистр UEPCONX
8.3.7. Регистр UEPSTAX
8.3.8. Регистр UEPRST
8.3.9. Регистр UEPINT
8.3.10. Регистр UEPIEN
8.3.11. Регистр UEPDATX
8.3.12. Регистр UBYCTLX
8.3.13. Регистр UFNUML
8.3.14. Регистр UFNUMH
8.4. Схемотехника АТ89С5131
8.5. Базовый проект для АТ89С5131
8.5.1. Первая версия программы для АТ89С5131
8.5.2. Добавляем строковые дескрипторы
8.5.3. Добавление конечных точек
8.6. Загрузка программы
Глава 9. Реализация класса CDC
9.1. Реализация CDC
9.2. Дескрипторы устройства
9.2.1. Инициализация конечных точек
9.2.2. Обработка CDC-запросов
9.2.3. Конфигурирование RS-порта и CDC-линии
9.2.4. Прием и передача данных
9.3. Установка драйвера
9.4. Программирование обмена данными с CDC-устройством на языке Delphi
9.5. Программирование обмена с CDC-устройством на языке С#
9.5.1. Использование компонента MSCOMM
9.5.2. Использование функций Win32
9.6. Проблемы CDC
Глава 10. Реализация класса HID
10.1. Реализация HID на АТ89С5131
10.2. Передача нескольких байтов
10.3. Feature-репорты
10.4. Передача данных от хоста (SET_REPORT)
10.5. Установка HID-устройства
10.6. Обмен данными с HID-устройством
10.6.1. Получение имени HID-устройства
10.6.2. Получение атрибутов устройства и чтение репортов
10.6.3. Передача данных от хоста к HID-устройству
10.7. Примеры HID-устройств
10.7.1. Реализация устройства "мышь"
10.7.2. Реализация устройства "клавиатура"
10.8. Использование HID-протокола
10.8.1. Интерпретация данных
10.8.2. Коллекции
10.8.3. Массивы и кнопки
10.9. HID-устройство с несколькими репортами
Глава 11. Специальные функции Windows
11.1. Функции Setup API
11.1.1. Перечисление USB-устройств
11.1.2. Получение состояния USB-устройства
11.2. Перечисление USB-устройств с помощью WMI
11.3. Специальные функции Windows XP
11.3.1. HidD_GetInputReport - чтение HID-репортов
11.3.2. Получение данных Raw Input
11.4. Функции DirectX
11.5. Диалог добавления нового оборудования
11.6. Работа с символьными именами устройств
11.7. Безопасное извлечение флэш-дисков
11.8. Обнаружение добавления и удаления устройств
11.9. Интернет-ресурсы
Глава 12. Разработка драйвера
12.1. Основные процедуры драйвера WDM
12.1.1. Процедура DriverEntry
12.1.2. Процедура AddDevice
12.1.3. Процедура Unload
12.1.4. Рабочие процедуры драйвера
12.1.4.1. Заголовок пакета
12.1.4.2. Ячейки стека ввода/вывода
12.4.1.3. Рабочие процедуры драйвера
12.1.5. Обслуживание запросов IOCTL
12.2. Загрузка драйвера и обращение к процедурам драйвера
12.2.1. Процедура работы с драйвером
12.2.2. Регистрация драйвера
12.2.2.1. Регистрация с помощью SCM-менеджера
12.2.2.2. Параметры драйвера в реестре
12.2.3. Обращение к рабочим процедурам
12.2.4. Хранение драйвера внутри исполняемого файла
12.3. Создание драйвера с помощью Driver Studio
12.3.1. Несколько слов о библиотеке Driver Studio
12.3.1.1. Класс KDriver
12.3.1.2. Класс KDevice
12.3.1.3. Класс Klrp
12.3.1.4. Класс KRegistryKey
12.3.1.5. Класс KLowerDevice
12.3.1.6. Классы USB
12.3.2. Другие классы Driver Studio
12.3.3. Создание шаблона драйвера с помощью Driver Studio
12.3.3.1. Шаг 1. Задание имени и пути проекта
12.3.3.2. Шаг 2. Выбор архитектуры драйвера
12.3.3.3. Шаг 3. Выбор шины
12.3.3.4. Шаг 4. Задание набора конечных точек
12.3.3.5. Шаг 5. Задание имени класса и файла
12.3.3.6. Шаг 6. Выбор функций драйвера
12.3.3.7. Шаг 7. Выбор способа обработки запросов
12.3.3.8. Шаг 8. Создание сохраняемых параметров драйвера
12.3.3.9. Шаг 9. Свойства драйвера
12.3.3.10. Шаг 10. Задание кодов IOCTL
12.3.3.11. Шаг 11. Дополнительные настройки
12.3.4. Доработка шаблона драйвера
12.3.5. Базовые методы класса устройства
12.3.6. Реализация чтения данных
12.3.7. Установка драйвера
12.3.8. Программа чтения данных
12.3.9. Чтение данных с конечных точек других типов
12.3.10. "Чистый" драйвер USB-устройства
Часть IV. Справочник
Глава 13. Формат INF-файла
13.1. Структура INF-файла
13.1.1. Секция Version
13.1.2. Секция Manufacturer
13.1.3. Секция DestinationDirs
13.1.3.1. Ключ DefaultDescDir
13.1.3.2. Ключи file-list-section
13.1.3.3. Ключ dirid
13.1.3.4. Ключ subdir
13.1.4. Секция описания модели
13.1.5. Секция xxx. AddRegw xxx. DelReg
13.1.6. Секция xxx. LogConfig
13.1.7. Секция xxx. CopyFiles
13.1.8. Секция Strings
13.1.9. Связи секций
13.2. Создание и тестирование INF-файлов
13.3. Установка устройств с помощью INF-файла
13.4. Ветки реестра для USB
Глава 14. Базовые функции Windows
14.1. Функции CreateFile и CloseHandle: открытие и закрытие объекта
14.1.1. Дополнительные сведения
14.1.2. Возвращаемое значение
14.1.3. Пример вызова
14.2. Функция ReadFile: чтение данных
14.2.1. Дополнительные сведения
14.2.2. Возвращаемое значение
14.2.3. Пример вызова
14.3. Функция Write File: передача данных
14.3.1. Дополнительные сведения
14.3.2. Возвращаемое значение
14.3.3. Пример вызова
14.4. Функция ReadFileEx. АРС-чтение данных
14.4.1. Возвращаемое значение
14.4.2. Дополнительные сведения
14.4.3. Пример вызова
14.5. Функция WriteFiieEx: АРС-передача данных
14.5.1. Возвращаемое значение
14.5.2. Пример вызова
14.6. Функция WaitForSingieObject ожидание сигнального состояния объекта
14.6.1. Возвращаемое значение
14.7. Функция WaitForMultipleObjects: ожидание сигнального состояния объектов
14.7.1. Возвращаемое значение
14.8. Функция GetOverlapped Result: результат асинхронной операции
14.8.1. Возвращаемое значение
14.9. Функция DeviceloControl: прямое управление драйвером
14.9.1. Возвращаемое значение
14.10. Функция Cancel/o: прерывание операции
14.10.1. Возвращаемое значение
14.11. Функция Query Dos Device, получение имени устройства по его DOS-имени
14.11.1. Возвращаемое значение
14.11.2. Пример вызова
14.12. Функция Define Dos Device: операции с DOS-именем устройства
14.12.1. Возвращаемое значение
14.12.2. Пример вызова
Глава 15. Структуры и функции Windows для последовательных портов
15.1. Структура настроек порта COMMCONFIG
15.2. Структура свойств порта COMMPROP
15.3. Структура тайм-аутов COMMTIMEOUTS
15.4. Структура статуса порта COMSTAT
15.5. Структура DCB
15.6. Функция BuildCommDCB: создание структуры DCB из строки
15.6.1. Дополнительные сведения
15.6.2. Возвращаемое значение
15.6.3. Пример вызова
15.7. Функция BuildCommDCBAndTimeouts: создание структуры DCB и тайм-аутов из строки
15.8. Функции SetCommBreak и ClearCommBreak: управление выводом данных
15.8.1. Возвращаемое значение
15.9. Функция ClearCommError: получение и сброс ошибок порта
15.9.1. Возвращаемое значение
15.10. Функция EscapeCommFunction: управление портом
15.10.1. Возвращаемое значение
15.11. Функции GetCommMask и SetCommMask: маска вызова событий
15.11.1. Возвращаемое значение
15.12. Функция WaitCommEvent ожидание события СОМ-порта
15.12.1. Возвращаемое значение
15.12.2. Дополнительные сведения
15.12.3. Пример вызова
15.13. Функции GetCommConfig и SetCommConfig: конфигурирование параметров порта
15.13.1. Возвращаемое значение
15.13.2. Пример вызова
15.14. Функция CommConfigDialog: диалог конфигурирования порта
15.14.1. Возвращаемое значение
15.14.2. Дополнительные сведения
15.14.3. Пример вызова
15.15. Функция GetCommProperties: прочитать свойства порта
15.15.1. Возвращаемое значение
15.15.2. Пример вызова
15.16. Функции GetCommState и SetCommState: состояние порта
15.16.1. Возвращаемое значение
15.16.2. Пример вызова
15.17. Функции GetCommTimeouts и SetComniTimeouts: тайм-ауты порта
15.17.1. Возвращаемое значение
15.17.2. Пример вызова
15.18. Функция PurgeComm: сброс буферов порта
15.18.1. Возвращаемое значение
15.18.2. Пример вызова
15.19. Функция SetupComm: конфигурирование размеров буферов
15.19.1. Возвращаемое значение
15.20. Функции GetDefaultCommConfig и SetDefaitltCommConfig: настройки порта по умолчанию
15.20.1. Возвращаемое значение
15.21. Функция TransmitCommChar. передача специальных символов
15.21.1. Возвращаемое значение
15.22. Функция GetCommModemStatus: статус модема
15.22.1. Возвращаемое значение
15.22.2. Пример вызова
15.23. Функция EnumPorts: перечисление портов
15.23.1. Дополнительные сведения
15.23.2. Возвращаемое значение
15.23.3. Пример вызова
Глава 16. Структуры и функции Windows Setup API
16.1. Функция Setup DiGetCiassDevs: перечисление устройств
16.1.1. Возвращаемое значение
16.2. Функция SetupDiDestroyDevicelnfoList освобождение блока описания устройства
16.2.1. Возвращаемое значение
16.3. Функция SetupDiEnumDevicelnterfaces: информация об устройстве
16.3.1. Возвращаемое значение
16.4. Функция SetupDiGetDevicelnterfaceDetaii: детальная информация об устройстве
16.5. Функция SetupDiEnumDevicelnfo: информация об устройстве
16.6. Функция SetupDiGetDeviceRegistryProperty: получение Plug and Play свойств устройства
16.7. Функция CM_Get_DevNode_Status: статус устройства
16.8. Функция CM_Request_Device_Eject безопасное извлечение устройства
Глава 17. Структуры и функции Windows HID API
17.1. Функция HidD_Hello: проверка библиотеки
17.2. Функция HidD_JetHidGuid: получение GUID
17.3. Функция HidD_GetPreparsedData: создание описателя устройства
17.4. Функция HidD_EreePreparsedData: освобождение описателя устройства
17.5. Функция HidD_Get Feature: получение Feature-репорта
17.6. Функция HidD_SetFeature: передача Feature-репорта
17.7. Функция HidD_GetNumlnputBuffers: получение числа буферов
17.8. Функция HidD_SetNumlnputBuffers: установка числа буферов
17.9. Функция HidD_GetAttributes: получение атрибутов устройства
17.10. Функция HidD_GetManufacturerString. получение строки производителя
17.11. Функция HidD_GetProductString получение строки продукта
17.12. Функция HidD_GetSerialNumberString. получение строки серийного номера
17.13. Функция HidD_GetIndexedString. получение строки по индексу
17.14. Функция HidD_Jetlnput Report получение Input-репорта
17.15. Функция HidD_SetOutputReport. передача Output-репорта
17.16. Функция HidP_GetCaps: получение свойств устройства
17.17. Функция HidP_MaxDataListLength: получение размеров репортов
17.18. Функция HidD_FIushQueue: сброс буферов
17.19. Функция HidP_GetLinkColiectionNodes: дерево коллекций
17.20. Функции HidP_GetScaledUsageValue u HidP_SetScaledUsage Value: получение и задание преобразованных значений
17.21. Функция HidF_MaxUsageListLength: размер буфера для кодов клавиш
17.22. Функция HidP_UsageListDifference: различие между массивами
Приложения
Приложение 1. Дополнительные функции
Приложение 2. Компиляция примеров в других версиях Delphi
Приложение 3. Таблица идентификаторов языков (LangID)
Приложение 4. Таблица кодов производителей (Vendor ID, Device ID)
Приложение 5. Как создать ярлык Device Manager
Приложение 6. Часто задаваемые вопросы
Приложение 7. Описание компакт-диска
Литература
Предметный указатель

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

Вот сейчас мы и будем ее создавать.

Опять же в пакете Microchip solutions есть пример, для нашего контроллера, но я переписал его по своему. Во-первых я сделал без использования графического интерфейса, и размер кода уменьшился раза в 3-4. Что намного лучше для изучения. Тем не менее, принцип работы один. код в стандартном примере от microchip работает так же как этот.

Программа написана в C++. Проект собран в бесплатной студенческой версии Visual C++ express 2010. Я буду давать общие идеи и комментарии по коду, но подразумеваю, что вы уже имеете хоть какой-то опыт программирования в C++.

Итак, начинаем

Здесь находится весь проект, включая исходный текст ссылка

Для того, чтобы читать и записывать данные через USB порт нам нужно получить указатель на наше устройство. Поскольку в отличие от старых портов, подключено может быть устройств свыше сотни, то получить этот указатель не так-то просто. Для того, чтобы это сделать мы будем обращаться к драйверам windows, а именно SetupAPI. Кстати, как только мы получим указатель, то будем общаться с USB портом, будто бы это файл.
Передача данных будет занимать лишь пару команд. Но вот подготовка!
Поскольку мы программируем в С++ то нам нужно быть очень аккуратными с типами данных.
Создаем консольный проект win32. И добавляем туда единственный файл main.cpp

Итак, нам необходимо подключить некоторые библиотеки.
#include
#include
#include

Так же подключаем внешнюю библиотеку:
#pragma comment (lib, "Setupapi.lib")

Первая описанная функция в программе - это getUSBHandle(). Ее описание - в комментариях кода. Да и вообще основные комментарии приведены в коде. Она служит для того, чтобы найти наше устройство и подготовить указатели, чтобы мы могли записывать в устройство и читать из него.
Если в двух словах, то она использует стандартные windows функции для доступа к драйверам USB и через них получает указатель на само устройство.
Если интересно, что все эти функции делают и как, то обращайтесь к MSDN или к книге Агурова, что лежит в оглавлении. Что важно знать: у каждого устройства есть путь, и нам важно получить его. Мы сначала проверяем, совпадает ли ID с устройством, что мы ищем. А потом находим путь устройства.Мы ищем только среди устройств HID класса. это определяется в переменной Guid. Остальное см в комментариях программы
Следующая функция - writeReadUSB. Это просто вспомогательная функция, которая записывает в наше устройство. Обращаю внимание, что запись и чтение устройства после того, как мы создали указатель на него реализуется с помощью стандартных комманд WriteFile и ReadFile
И после этого уже мы видим функцию main с которой и начинается выполнение программы. Она вызывает getUSBHandle, пока мы не получим указатель на устройство, потом читает с клавиатуры комманду и в зависимости от нее, передает и читает данные с USB устройства.
В проекте по ссылке выше лежит исходный код с комментариями и сама скомпилированная программа. Удачи.

В процессе поиска ошибки я наткнулся на библиотеку hidapi. Она является кроссплатформенной. И Только для работы с hid устройствами. Очень проста в использовании. Привожу проект под нее. ссылка .
Hidapi скачан с официального сайта. Для того чтобы начать проект нужно добавить setupapi.lib в линковщик. project->properties->linker->input и подписать туда setupapi.lib;
Удачи.
Нашел пристойное описание библиотеки здесь: http://microsin.net/programming/PC/multi-platform-hid-api.html.
Спасибо!

Но ведь мало только физически подсоединить устройство к компьютеру, нужно еще и наладить обмен данными между ними. Как же выбрать порт и организовать подключение? Несколько лет назад стандартным решением было использование 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. PAS, программная оболочка для hid. dll (Hid User Library - как указано в свойствах файла). В комментариях к файлу сообщается, что в основе его лежат модули hidsdi.h и hidpi.h корпорации Microsoft. А сам файл HID. PAS - часть пакета JEDI ().

Для работы с 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.