Цветомузыкальное устройство управляемое по USB Часть 3 Программа управления, взаимодействие с USB. ВВЕДЕНИЕ В двух предыдущих статьях я постарался наиболее полно дать описание микроконтроллерного устройства для реализации различных светодинамических эффектов и его программы управления. Поступило предложение более подробно раскрыть механизм взаимодействия программы и микроконтроллера, что я попытаюсь сделать в третей части описания. Как было сказано раньше в качестве базового компонента для работы с HID USB я взял TjvHidDeviceController из известной и, что не мало важно, бесплатной библиотеки JEDI Visual Component Library, которую можно совершенно свободно скачать с сайта http://jvcl.delphi-jedi.org. В данной статье мы не будем останавливаться на всем многообразии применения данного компонента, а изучим минимально необходимый набор функций, которые помогут начинающим начать писать собственные программы для взаимодействия своих устройств с компьютером по шине USB. ОПИСАНИЕ ФУНКЦИЙ Все не один раз наблюдали, как при подключении какого либо USB устройства к компьютеру появлялось сообщение, что найдено некое устройство и через некоторое время оно было готово к работе. Такая автоматизация очень сильно упрощает нашу жизнь. Вот и мы попробуем написать такую программу. Для начала создадим чистую форму и поместим на нее компонент TjvHidDeviceController. В окне инспектора объектов заменим его имя на более удобоваримое, например HIDCtl. Поместим на форму кнопку для инициации процедуру записи и метки Label для вывода сообщений. Вот в принципе все готово для начала кодирования. Опишем наше устройство в глобальной секции модуля: Var CurrentDevice: TJvHidDevice; Сonst VID = $FAFA; PID = $0003; SerN = 'CO-000001A'; Полагаю, что первым действием, которое мы должны выполнить, является действие по самому факту обнаружения подключения нашего устройства. Наш компонент сделает это легко и просто. Для этого воспользуемся стандартным методом HIDCtlDeviceChange. В инспекторе объекта переходим на закладку Events и дважды кликаем на данном методе. В автоматически сформированной процедуре напишем всего две коротенькие строчки. procedure TForm1.HIDCtlDeviceChange(Sender: TObject); begin CurrentDevice:=nil; HidCtl.Enumerate; end; Первая строка сбрасывает все найденные ранее устройства, а вторая запускает процедуру поиска подключенных устройств. Результатом всех этих действий будет перебор всех подключенных к компьютеру устройств. Но нам нужно только наше устройство, как быть? Спокойствие, только спокойствие, как говорил один известный персонаж, нам на помощь вновь приходит HID компонент. На закладке Events находим процедуру OnEnumerate и, как и сказано выше двойным кликом создаем процедуру в которой и находим свое устройство. Вот тут нам и помогут VID, PID и SerN. function TForm1.HIDCtlEnumerate(HidDev: TJvHidDevice; const Idx: Integer): Boolean; begin //Если совпали VID, PID и серийный номер то получим ссылку на экземпляр нашего //устройства if (HidDev.Attributes.VendorID = VID) and (HidDev.Attributes.ProductID = PID) and (HidDev.SerialNumber=SerN) then begin //Вот тут то мы его и получаем HidCtl.CheckOutByIndex(CurrentDevice, Idx); //Дальше просто выведем значения параметров в метки //Как видите мы работаем только с нашим обнаруженным устройством Label1.Caption:= CurrentDevice.Attributes.VendorID; Label2.Caption:= CurrentDevice.Attributes.ProductID; Label3.Caption:= CurrentDevice.SerialNumber; Result := True; end; end; Вот таким образом используя всего две коротенькие процедуры мы смогли найти и опознать свое устройство. Хорошим стилем программирования считается освобождение всех занятых ресурсов по окончании работы, поэтому напишем еще одну процедуру, которая будет вызываться при отключении устройства. Эта процедура освобождает ресурсы, занятые нашим приложением. procedure TForm1.HidCtlDeviceUnplug(HidDev: TJvHidDevice); begin CurrentDevice.Free; CurrentDevice:=Nil; end; Тоже самое необходимо сделать при закрытии нашего приложения, когда устройство все еще подключено к компьютеру. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin If CurrentDevice<>Nil Then CurrentDevice.Free; end; Пора переходить процедурам записи и чтения. С нашим компонентом и в этом не будет никаких препятствий. Так, как все, что написано выше, было привязано к некому абстрактному контроллеру, то сложно объяснить, как прочесть из него данные о которых мы ничего не знаем. Поэтому для более простого понимания процедуры чтения я приведу пример из своей программы. В моем устройстве из микроконтроллера передается 6 параметров. Это две цифры версии ПО контроллера, значения сгенерированных цветов и величина установленной задержки. (Для более точного понимания того, о чем идет речь я отсылаю читателя к Части 1 моей статьи, где подробно рассказывается о структуре данных контроллера). Так вот, для приема данных по шине существует стандартная процедура, которая автоматически прмет все, что вы хотели сообщить компьютеру. Вам остается только грамотно распорядиться принятыми данными. procedure TForm1.HidCtlDeviceData(HidDev: TJvHidDevice; ReportID: Byte; const Data: Pointer; Size: Word); var i : integer; m: array[0..7] of Byte; Zad: Float; begin // Для начала сохраним посылку в буфере. For i:=0 to 7 do begin m[i]:= cardinal(PChar(Data)[i]); end; //Теперь можно и проанализировать принятые данные. Выведем в StatusBar сведения о //версии ПО МК, которые передаются в байтах 5 и 6 StatusBar.Panels[0].Text:=' Версия ПО МК: '+IntToStr(m[5])+'.'+IntToStr(m[6]); //Позиционируем движки регуляторов. If Regim=1 Then begin tbR.Position:=m[0]; tbG.Position:=m[1]; tbY.Position:=m[2]; tbB.Position:=m[3]; //Рассчитываем и выводим значение задержки с мены цвета. i:=m[4]*255; Zad:=i/1000; lblZad.Caption:=Format('%3.2f',[Zad])+' сек'; end; end; (Данная процедура приведена только для примера, т.к. в нашем тестовом приложении нет ни статус бара, ни движков.) Необходимо также понимать, что мы ведем обмен с контроллером репортами типа INPUT и OUTPUT с одним единственным репортом, который имеет идентификатор 0. При использовании других типов репортов или с несколькими репортами, процедуры будут другими. Эта тема для другой статьи, поэтому здесь обсуждаться не будет. Приступим к записи данных из РС в контроллер. Для этого создадим процедуру обработки нажатия нашей кнопочки. Я приведу пример из своей программы, которая передает в МК 16 управляющих байт, описание которой любопытный читатель найдет в первой части статьи. procedure TForm1.cmdWriteClick(Sender: TObject); var i : integer; Buf: array [0..16] of Byte; Written: Cardinal; ToWrite: Cardinal; begin //Проверяем подключено ли наше устройство if Assigned(CurrentDevice) then begin //Узнаем какова длина OUTPUT репорта, котрым владеет МК ToWrite := CurrentDevice.Caps.OutputReportByteLength; //Заполняем буфер нужными нам параметрами. Необходимо отметить, что самым первым //значением в буфере стоит номер репорта. В нашем случае это 0! Buf[0] := 0; Buf[1] := Rx; Buf[2] := Gx; Buf[3] := Yx; Buf[4] := Bx; Buf[5] := Rf; Buf[6] := Gf; Buf[7] := Yf; Buf[8] := Bf; Buf[9] := Regim; Buf[10] := Zmax; Buf[11] := Zmin; Buf[12] := Tim; Buf[13] := HiZat; Buf[14] := LoZat; Buf[15] := HiZatF; Buf[16] := LoZatF; //Все скопом сбрасываем в контроллер. В переменной Written будет количество //переданных байт CurrentDevice.WriteFile(Buf, ToWrite, Written); end; end; Отмечу еще одну особенность. Если внимательно присмотреться к этой процедуре и посмотреть код микроконтроллера, который ведет прием данных, то мы увидим, что значение Buf[0], т.е. номер репорта "пропал в недрах" библиотеки LUFA, а первое значение в контроллере принялось в буфер по индексу 0. R1=ReportData_u8[0];. Об этом не следует забывать. Вот в принципе и все, что нужно знать для выполнения простеньких задач по обмену данными между компьютером и микроконтроллером по шине USB. Ведь действительно не сложно? Отмечу, что мы охватили только малую часть возможностей замечательного компонента TjvHidDeviceController. Желающие более подробно разобраться со всем его возможностями найдут всю необходимую информацию в интернете.