Часть 1
Вчера я рассказал, как собрать USB-IRPC на основе Arduino. Зачем? Чтобы показать, как быстро собрать макет и сложности особой тут нет.
Сегодня сделаем все как положено. С самого начала — с принципиальной схемы, PCB, ЛУТ. Кусочек фольгированного стеклотекстолита превратим с помощью кучки деталек в готовое устройство. Разумеется с корпусом, мы же хотим аккуратно, правда?
Вот наша цель:
«USB-IRPC Bare Front»
«USB-IRPC Finished»
Итак, начинаем с принципиальной схемы. На схеме будет только сам USB-IRPC, так как модуль розеток с реле и ИК приемник со светодиодом — это «периферия» и мы будем использовать ее без изменений.
Я использовал DipTrace для рисования схемы и печатной платы по следующим соображениям:
- Он бесплатен до 1000 выводов, чего для моих любительских целей хватает.
- Он неплохо разводит односторонние платы без заморочек.
- Пользоваться им проще и удобнее, чем Eagle, который, на мой взгляд очень уж специфическим интерфейсом обладает.
У него есть существенный недостаток — не очень богатая библиотека элементов, а также не самый удобный поиск по ней. Но редактировать элементы не слишком сложно. В общем, пока что это наименее неудобная для меня программа :)
Вот сама схема (смотреть лучше полноразмерную версию перейдя по ссылке и открыв в оригинальном размере):
«USB-IRPC Scheme»
В центре у нас U1 — я использовал Atmega168PA-AU, но подойдет и Atmega88 и Atmega328P. Размер прошивки всего чуть больше 4 кб.
Работу микроконтроллера (МК) на частоте 16МГц обеспечивают конденсаторы С4 и С5 с кварцем X1.
С2 — просто блокировочный конденсатор, немного сглаживает пульсации в цепи питания.
Блок из R3 и U2 служит для «нажатия» кнопки питания ПК.
J1 — разъем для подключения к материнской плате параллельно кнопке питания.
S1 — кнопка Reset. Может пригодиться при отладке.
J5 и J6 — разъем ISP6 для внутрисхемного программирования (прошивки).
J2 — разъем для подключения модуля розеток с реле.
J3 — разъем для подключения модуля ИК приемника со светодиодом.
D1 — светодиод, подключенный через токоограничивающий резистор R1, дублирует на плате выносной светодиод. Полезен при отладке, после сборки его не видно внутри коробки, поэтому впаивать его не обязательно. R4 — резистор для выносного светодиода на модуле приемника.
J4 — разъем USB Mini-B.
D2 и D3 — стабилитроны на 3.6В.
R5, R6, R7 — резисторы для обеспечения работы V-USB.
Вот, собственно и вся схема.
В конце статьи ссылки на скачивание ее в формате .sch (DipTrace)
Сложнее оказалось развести печатную плату. У меня это первый опыт и один их проводников в одном слое развести так и не удалось, пришлось оставить перемычку. К тому же я забыл, что вилки на плату будут устанавливаться с другой стороны и пришлось потом делать раздельную вилку для программатора. Оптопару я тоже по ошибке расположил не с той стороны, но запаять ее это не помешало. Все это я исправил в версии печатной платы, которую выложил для скачивания. Так что ваша плата будет чуть-чуть отличаться от той, что на моих фото. В лучшую сторону :)
В ходе запайки элементов я обнаружил, что заливка медью слишком близко к дорожкам добавляет неудобств и изменил заливку, избавившись от островков и сделав отступы от дорожек пошире.
Вот что получилось:
Красная дорожка — перемычка, которую развести не удалось.
Приятный бонус DipTrace — возможность посмотреть как будет выглядеть печатная плата. К сожалению, в библиотеке есть далеко не все элементы, но общий резуьтат все равно куда более наглядный чем в плоском виде:
Видно, что на плате я добавил разъем J7 просто еще пара штырьков, запараллеленных с J1, чтобы подключить коннектор кнопки питания с корпуса ПК.
Ок, схема и образец печатной платы готовы, закупаем детали и изготавливаем.
Список деталей:
U1 — Atmega168PA-AU — в корпусе TQFP-32 — от 64 рублей (в icdarom, минимальный заказ 1500р, доставка 200), штучно можно купить по 130 рублей свободно. Подойдет Atmega88PA-AU или Atmega328P-AU
Выводные элементы:
U2 — PC817, корпус PDIP4
S1 — DTS-61 Кнопка тактовая 6х6х4.3.
D2, D3 — стабилитроны на 3.6В
J2, J3 — PBS-4
J5, J6 — PBS-3
J1, J7 — PBS-2
SMD элементы размера 0805
R1, R4 — 390Ом
R5, R6 — 68Ом
R7 — 2.2 кОм
D1 — светодиод (любого цвета, у меня оранжевый)
J4 — USB miniB разъем
С1, С2 — конденсаторы 0.1 мкФ керамические.
С4, С5 — конденсаторы 22 пФ керамические
X1- кварц 16МГц
R3 — 100 Ом.
R2 — 10 кОм
Выводные элементы покупаются на радиорынке — рублей на 50 примерно.
SMD элементы я купил сразу пачку за $9.85 — там по 40 штук каждого номинала резисторов и по 30 каждого конденсатора.
Коробочку я брал в icdarom за 40р, но она есть и в других магазинах BOX-KA08 разного цвета, в районе 65 рублей.
Бывает даже прозрачные:
Учить вас делать платы ЛУТом не буду — DiHALT все описал просто и понятно. Кстати, поздравьте его с днем варенья. :)
Остановлюсь только на моментах, специфических для DipTrace. При печати не забудьте отзеркалить и отключите печать контуров компонентов:
Можно обойтись и без DipTrace. Я экспортировал в PNG файл, берем FastStone Viewer и печатаем вот так:
Качество печати на максимум, экономию отключить, разрешение на максимум.
PNG подготовлен в 600 PPI.
Дальше все как обычно: переводим, травим, отмываем тонер, выпиливаем, лудим, сверлим:
«USB-IRPC»
У меня бумага была матовая и тонер немного скатался по краям — может не прогладил как следует, но там ничего важного нет, пострадала только эстетика и то не сильно.
Запаиваем компоненты и наносим маркировку:
«USB-IRPC Bare Front»
«USB-IRPC Bare Back»
обратите внимание, у меня PC817 не с той стороны и ряды гребенки ISP разъема в обратном порядке — это моя ошибка, на вашей плате все будет правильно.
Из-за того, что маску в своем варианте сделал не очень удачно, при запайке элементов появились наплывы припоя, которые очень трудно снять совсем. Этот момент я тоже учел и исправил в окончательной версии разводки платы. Но переделывать плату не стал — работает хорошо, только выглядит не очень, к сожалению. Первый блин всегда комом. Зато залудить в этот раз удалось аккуратнее.
Сборка и оформление
Советую распечатать цветную картинку с надписями элементов в масштабе 1:1 (USB-IRPC_pcb.jpg) и использовать как шпаргралку при запайке элементов и сверловке отверстий.
Надфилем аккуратно выпиливаем отверстия в коробочке. Контуры отверстий намечаем на корпусе маркером или карандашом, прорезав канцелярским ножом их контуры в бумажном шаблоне и приложив шаблон к корпусу. Сбоку пропиливаем паз для miniUSB разъема. Вставляем плату и примериваем:
«USB-IRPC»
Если не планируете дорабатывать прошивку, то отверстия под ISP вырезать не обязательно — пины не упираются в крышку.
Затем печатаем и наклеиваем маркировку. В архиве найдете версию наклейки с ISP и без него. в зависимости от того, будете вы прорезать под них дырки или нет. (дырку под Reset я просверлил, но в наклейке прорезать не стал, ее можно проколоть зубочисткой, если понадобится, но пока не пригодилось).
Все, аппаратная часть готова.
«USB-IRPC Size»
ИК модуль я просто залил термоклеем и на двусторонний скотч прикрепил к ножке монитора.
«USB-IRPC IR-Led module»
Я не люблю яркий свет дома, а при нормальном освещении его практически незаметно — размер 1х3 см.
Программируем девайс
Прошивка написана на С в Code::Blocks IDE. Компилятор — AVR GCC из комплекта AVR Toolchain
Я здесь поясню основные моменты, чтобы вы могли разобраться в коде проекта. Исходники лежат на странице проекта, ссылки в конце статьи.
Я написал имитацию функций DigitalRead и DigitalWrite, т.к. некоторые библиотеки беру из Arduino и чтобы не переписывать просто подсовываю им свой вариант вызываемых ими функций Wiring.
ir.h — это интерфейс с ИК приемником. Бессовестно адаптирован пример, который шел с китайским набором для экспериментов с ИК пультом. Практически без изменений.
usb_comm.h — определяет коды команд для обмена с ПК и структуру данных, в которой они передаются.
//USB Commands
#define cmdGetIRPCState 1 // Запрос состояния IRPC
#define cmdSetDigitalPinStates 2 // Установить состояния реле и выносного светодиода - битовое поле
#define cmdDisableIR 3 // Запретить обработку нажатий кнопок ИК пульта
#define cmdEnableIR 4 // Разрешить обработку нажатий кнопок ИК пульта
#define cmdGetIRBtnMapping 5 // Получить привязку внутренней функции к коду кнопки пульта
#define cmdSetIRBtnMapping 6 // Установить привязку внутренней функции к коду кнопки пульта
#define cmdGetIRBtn 7 // Считать код последней нажатой кнопки пульта (на данный момент уже не используется)
#define cmdSetLedState 8 // Вкл/выкл светодиод
#define cmdDoInternalFunc 9 // выполнить внутреннюю функцию IRPC
#define cmdSaveToEEPROM 10 // сохранить состояние реле и светодиода в EEPROM
#define cmdLoadFromEEPROM 11 // загрузить состояние реле и светодиода из EEPROM
//USB pinstate bits
#define PWRRelay1Bit 0
#define PWRRelay2Bit 1
#define ledBit 7
//Internal functions
#define fRelay1Switch 1
#define fRelay2Switch 2
#define fPCPwrSwitch 3
struct irpcdata_t // Описание структуры для передачи данных
{
uint8_t cmd; // Switches state
uint8_t data[4]; // data
};
USB pinstate bits — номера битов управления реле и светодиодом в пакете.
Внутренние функции IRPC:
fRelay1Switch — переключить реле 1
fRelay2Switch — переключить реле 2
fPCPwrSwitch — нажать кнопку питания ПК
irpcdata_t — это структура пакета, которым обмениваются устройство и ПК.
Обмен происходит по запросу ПК.
cmd — код команды (например cmdGetIRPCState)
data[] — массив данных из 4 байт. Каждая команда интерпретирует его по своему.
irrc.h — соответствие кодов кнопк названиям, просто для удобства. Я записал для своих пультов. Примерно так:
//AverMedia RM-FR
#define amTVFM 0xFE01
#define amPWR 0xFF00
#define amB1 0xFA05
#define amB2 0xF906
#define amB3 0xF807
#define amB4 0xF609
В файле usbconfig.h настройки библиотеки V-USB. Я черпал информацию из этой статьи.
Если вы не разбираетесь в USB протоколе, то там можно ничего не трогать.
Основаная программа main.c
Определяем макрос SetPinState, который занимается установкой выходов в требуемое положение и устанавливает соответствующее состояние бит этого выхода в переменной pinStates.
Также определяем пины, к которым подключена периферия и количество соответствий кнопок внутренним функциям, которые мы можем хранить (MaxBtnMapings).
//Usefull macroses
#define SetPinState(Pin, State, bit) digitalWrite(Pin, State); if (State>0) pinStates |=_BV(bit); else pinStates &= ~_BV(bit);
//PORTB
#define PWRPin 16 // PC2
#define ledPin 9 // PB1
//PORTD
#define PWRRelay1 6 // PD6
#define PWRRelay2 5 // PD5
#define MaxBtnMapings 15
Переменные (комментарии в исходнике делал для себя, поэтому в файле они в основном на корявом английском :)
//Состояние
uint8_t ledState = 0; // состояние светодиода
uint8_t PWRRelay1State = 1; // Состояние реле 1
uint8_t PWRRelay2State = 1; // Состояние реле2
uint8_t PWRPinState=0; // состояние кнопки питания ПК
uint8_t IRBtnL, IRBtnH; // Переменные, хранящие для отправки ПК код кнопки пульта
uint8_t IREnabled=1; // Декодировать ли ИК коды
struct btn_mapping btn_mappings[MaxBtnMapings]; // мэппинг кнопок внутренним функциям
//--
uint8_t cmd; // Полученная от команда
uint8_t pinStates=0x00; //Состояния управляемых пинов побитово (0-PWRRelay1State, 1-PWRRelay2State)
uint8_t cmdReceived=0; //Команда от PC получена (флаг)
//IR variables
uint16_t btn; // код кнопки пульта
uint16_t LastBtn=0x00; // последняя нажатая кнопка пульта
//----------------- USB Section---------------------------
struct irpcdata_t pdata, pcdata; //pdata - output buffer, pcdata - input buffer
Принимаемые команды от ПК храним в pcdata, отправляемые — в pdata.
Основной цикл
int main(void)
{
//Initialize ports
DDRB = 0b00000010; // PB1 - выход
DDRC = 0b00000100; // PC2 - выход
DDRD = 0b01100000; // PD5, PD6 - выход
//clear btn mappings
for (i=0; i<MaxBtnMapings;i++){
btn_mappings[i].func=0;
btn_mappings[i].btn_code=0;
}
loadStateFromEEPROM();
//USB Init & connect
cli(); // Clear interrupts while perfoorming time-critical operations
usbInit();
usbDeviceDisconnect(); // принудительно отключаемся от хоста, так делать можно только при выключенных прерываниях!
uchar i = 0;
while(--i){ // пауза > 250 ms
_delay_ms(1);
}
usbDeviceConnect(); // подключаемся
sei(); // разрешаем прерывания
//Timer1 Init for IR
timer1_init(); // init IR timer
loop();
return 0;
}
Настраиваем пины, к которым подключены реле и светодиод как выходы.
Очищаем соответствия кнопок внутренним функциям — мало ли какой мусор в памяти при включени.
Загружаем байт состояний реле и светодиода — pinStates и устанавливаем их в соответствующее состояние в функции loadStateFromEEPROM().
Отключаем прерывания, отключаемся от ПК, делаем паузу и даем нас обнаружить. Затем разрешаем прерывания.
Подготавливаем таймер для работы с ИК приемником.
Запускаем цикл обработки данных от ИК приемника и ПК:
void loop()
{
// Main loop
for(;;){ // главный цикл программы
usbPoll(); // эту функцию надо регулярно вызывать с главного цикла, максимальная задержка между вызовами - 50 ms
if (cmdReceived) {processUSBcmd();}
else if (IREnabled) {
remote_decode();
process_IR();
}
}
return;
}
Тут все просто:
- Интересуемся у ПК, не было ли чего для нас.
- Если была принята команда, обрабатываем ее в processUSBcmd()
- Дальше если разрешено обрабатывать коды ИК пульта, вызываем функцию прием и декодирования — это самая длинная по времени выполнения функция.
- Обрабатываем код нажатой кнопки и записываем его для передачи ПК когда он спросит.
Когда ПК хочет передать нам пакет, вызывается функция usbFunctionWrite, когда он запрашивает пакет данных — функция usbFunctionRead.
Разберем их:
uchar usbFunctionRead(uchar *data, uchar len)
{
if(len > bytesRemaining)
len = bytesRemaining;
uchar *buffer = (uchar*)&pdata;
if(!currentAddress) // Ни один кусок данных еще не прочитан.
{ // Заполним структуру для передачи
pdata.cmd=cmd; //last received cmd
switch(cmd){
case cmdGetIRPCState:
pdata.data[0]=pinStates;
pdata.data[1]=IRBtnH;
pdata.data[2]=IRBtnL;
if (IREnabled){
//clear IR button code
IRBtnL=0;
IRBtnH=0;
}
break;
case cmdGetIRBtnMapping:
pdata.data[0]=pcdata.data[0];
pdata.data[1]=(btn_mappings[pcdata.data[0]].btn_code>>8);
pdata.data[2]=(btn_mappings[pcdata.data[0]].btn_code & 0xFF);
pdata.data[3]=btn_mappings[pcdata.data[0]].func;
break;
}
}
uchar j;
for(j=0; j<len; j++)
data[j] = buffer[j+currentAddress];
currentAddress += len;
bytesRemaining -= len;
return len;
}
Сначала заполняем структуру pdata — если нас спросили состояние девайса (cmdGetIRPCState), записываем pinStates и код нажатой кнопки пульта
Если запросили одно из соответствий кнопок внутренним функциям — отдаем его, взяв номер функции из первого байта присланного ПК пакета (pcdata.data[0])
Если команду не узнали, просто отдаем структуру pdata — при обработке команд результаты записываются в нее.
Затем побайтово записываем наш пакет по указателю, переданному в параметре data.
uchar usbFunctionWrite(uchar *data, uchar len)
{
uchar *buffer = (uchar*)&pcdata;
uchar j;
for(j=0; j<len; j++)
buffer[j]=data[j];
cmdReceived=1; // Выставим флаг принятых данных
return 1;
}
Тут еще проще — просто переписываем пакет с данными в pcdata, не разбираясь что к чему, обработка будет выполняться processUSBcmd(), а нам нужно вернуть управление как можно быстрее — функция вызывает прерыванием.
А вот, собственно, и она:
void processUSBcmd(){
cmd=pcdata.cmd;
switch(cmd)
{
case cmdSetDigitalPinStates:
//Get pin states from USB cmd
PWRRelay1State=(pcdata.data[0] & _BV(PWRRelay1Bit));
PWRRelay2State=(pcdata.data[0] & _BV(PWRRelay2Bit));
ledState=(pcdata.data[0] & _BV(ledBit));
//Execute cmd
SetPinState(PWRRelay1, PWRRelay1State, PWRRelay1Bit);
SetPinState(PWRRelay2, PWRRelay2State, PWRRelay2Bit);
break;
case cmdGetIRPCState:
if (PWRRelay1State>0) pinStates |=_BV(PWRRelay1Bit); else pinStates &= ~_BV(PWRRelay1Bit);
if (PWRRelay2State>0) pinStates |=_BV(PWRRelay2Bit); else pinStates &= ~_BV(PWRRelay2Bit);
if (ledState>0) pinStates |=_BV(ledBit); else pinStates &= ~_BV(ledBit);
break;
case cmdGetIRBtn:
pdata.data[1]=IRBtnH;
pdata.data[2]=IRBtnL;
IRBtnL=0;
IRBtnH=0;
break;
case cmdSetLedState:
ledState=(pcdata.data[0] & _BV(ledBit));
SetPinState(ledPin, ledState, ledBit);
break;
case cmdEnableIR:
IREnabled=1;
break;
case cmdDisableIR:
IREnabled=0;
IRBtnL=0xFF;
IRBtnH=0xFF;
break;
case cmdSetIRBtnMapping:
btn_mappings[pcdata.data[0]].btn_code=(uint16_t)(pcdata.data[1]<<8)+pcdata.data[2];
btn_mappings[pcdata.data[0]].func=pcdata.data[3];
break;
case cmdDoInternalFunc:
switch (pcdata.data[0]){
case fRelay1Switch:
PWRRelay1State=!PWRRelay1State;
SetPinState(PWRRelay1, PWRRelay1State, PWRRelay1Bit);
break;
case fRelay2Switch:
PWRRelay2State=!PWRRelay2State;
SetPinState(PWRRelay2, PWRRelay2State, PWRRelay2Bit);
break;
case fPCPwrSwitch:
digitalWrite(PWRPin, 1);
_delay_ms(100);
digitalWrite(PWRPin, 0);
break;
}
break;
case cmdLoadFromEEPROM:
loadStateFromEEPROM();
break;
case cmdSaveToEEPROM:
saveStatetoEEPROM();
break;
}
_delay_ms(5);
cmdReceived=0;
}
В зависимости от принятой команды (ее код в поле pcdata.cmd) разбираем пакет pcdata.data
тут все довольно прозрачно, я думаю.
Выполнив команду, сбрасываем флаг cmdReceived.
В функции process_IR() мы собираем 16 битный код кнопки:
// button code is 16 bit
btn=(adrH_code << 8) + adrL_code;
Если код не равен 0, значит что-то нажато было, сохраним для передачи ПК и выставим флаг, что нужно зажечь светодиод — показать, что мы увидели нажатую кнопку.
//Button pressed
if (btn>0x00) {
LastBtn=btn;
IRBtnL=adrL_code;
IRBtnH=adrH_code;
ledState=1;
}
дальше я обрабатываю коды кнопок своего пульта:
//Button is "Power"
if ((btn==ykPWR) || (btn==amPWR)) {
PWRPinState=1;
}
switch (LastBtn){
// case ykB1:
case amSNAPSHOT:
PWRRelay1State=!PWRRelay1State;
SetPinState(PWRRelay1, PWRRelay1State, PWRRelay1Bit);
break;
// case ykB2:
case am16CH:
PWRRelay2State=!PWRRelay2State;
SetPinState(PWRRelay2, PWRRelay2State, PWRRelay2Bit);
break;
}
По кнопке «Power» нажимаю кнопку питания ПК до тех пор пока не отпустят кнопку пульта.
По двум другим кнопкам переключаю состояния реле.
Затем проверяем массив назначенных соответствий кодов кнопок внутренним функциям — это для использования с другими пультами, соответствия задаются с ПК командами cmdSetIRBtnMapping — по одной команде на кнопку. Обработку этой команды мы видели выше в processUSBcmd()
for (i=0; i<MaxBtnMapings;i++){
if (btn==btn_mappings[i].btn_code) {
switch (btn_mappings[1].func){
case fRelay1Switch:
PWRRelay1State=!PWRRelay1State;
SetPinState(PWRRelay1, PWRRelay1State, PWRRelay1Bit);
break;
case fRelay2Switch:
PWRRelay2State=!PWRRelay2State;
SetPinState(PWRRelay2, PWRRelay2State, PWRRelay2Bit);
break;
case fPCPwrSwitch:
digitalWrite(PWRPin, 1);
_delay_ms(100);
digitalWrite(PWRPin, 0);
break;
}
}
}
В целом — главное успевать быстро обрабатывать прерывания и периодически опрашивать ИК приемник и вызывать usbPoll, иначе ПК нас потеряет — «USB device not recognized».
Версия обозначена как альфа недаром — есть несколько кусков, как вы успели заметить, где привязка к конкретному пульту пока не закоментирована, потому что настройка мэппинга кнопок и функций с ПК еще не написана, не успел. Ну и иногда бывает, что девайс перестает принимать команды от ПК, хотя в системе виден и на кнопки пульта реагирует нормально. Подозреваю, что это связано с большой длительностью опроса ИК приемника, это нужно оптимизировать. Однако, бывает это редко и не доставляет особых неудобств.
Теперь переходим к программе для ПК.
Остановлюсь на ключевых моментах, чтобы можно было разобраться в исходниках.
Покажу на примере плагина для MKey — в нем проще разобраться.
Плагин написан на Delphi7 потому что выяснилось, что MKey не поддерживает юникод, а в Delphi 2010 с ANSI нужно дополнительно возиться.
Из дополнительных библиотек используется только JVCL.
Основная работа выполняется в модуле uHID.pas
В нем определяются те же константы команд, что и в девайсе, не буду их дублировать.
Та же запись, описывающая пакет, с одним добавлением — reportID — мы его не поддерживаем и там всегда 0, но вообще репортов у девайса может быть не один. Просто нам хватает и одного.
Дополнительные константы только id девайса — Vendor ID (Vid), Product ID (Pid) и DeviceName. По Vid и Pid мы будем искать среди подключенных HID устройств наше.
const
//USB-IRPC IDs
Vid=$16C0;
Pid=$05DF;
DeviceName='USB-IRPC';
//PC<->IRPC
TIRPCReport= packed record
reportID:byte; //not used, shoud be 0
cmd:byte;
data: array [0..3] of byte;
end;
Все функции общения с девайсом собраны в класс
TIRPC=class
private
Dev:TJvHidDevice;
HID:TJvHidDeviceController;
fIRButtons:TStringList;
fIRName:string;
fIRLoaded:boolean;
function IsConnected: boolean;
public
constructor Create;
destructor Destroy; override;
procedure Connect;
procedure Disconnect;
function ReadIRPCState(var Report:TIRPCReport):boolean;
procedure WriteIRPCcmd(cmd:TIRPCReport);
procedure LoadIRcodes(irFile:string);
function BtnName(btnCode:word):string;overload;
function BtnName(btnCode:string):string;overload;
property Connected:boolean read IsConnected;
property Device:TJvHidDevice read Dev;
property IRButtons:TStringList read fIRButtons;
property IRName:string read fIRName;
end;
поле Dev — указатель на класс устройства, мы его получаем при удачном подключении к устройству (Connect).
при отключении (Disconnect) он устанавливается в nil.
Методы очень простые, поэтому покажу одну для примера, остальные примерно того же порядка сложности:
procedure TIRPC.Connect;
begin
if Assigned(dev) then Exit;
HID.CheckOutByID(Dev,VID,Pid);
end;
состояние подключения (свойство Connected) определяется указателем Dev — если он не пустой, значит подключение прошло успешно.
Метод WriteIRPCcmd(cmd:TIRPCReport) отправляет пакет с командой, которую вы предварительно заполняете, девайсу.
Метод ReadIRPCState(var Report:TIRPCReport):boolean запаршивает пакет у девайса. Если пакет успешно получил, вернет true, а сам пакет запишет в параметр Report.
procedure LoadIRcodes(irFile:string); — загружает файл соответствий названий кнопок пульта их кодам. Файл имеет простой формат: каждая строка содержит через запятую название кнопки и код
TVFM,$FE01
В плагине этот метод не востребован — в Mkey передается код кнопки, вы сами называете ее как хотите, потому что программа не показывает что вы ей отправили. А вот в своей программе можно показывать нормальные человеческие названия кнопок.
Функции BtnName возвращают название кнопки по коду, переданному в виде 16 битноо значения или строки с хексом.
function BtnName(btnCode:word):string;overload;
function BtnName(btnCode:string):string;overload;
свойство IRButtons содержит список кнопок и их кодов
property IRButtons:TStringList read fIRButtons;
свойство IRname — имя загруженного методом LoadIRcodes пульта (имя файла без расширения .ir).
property IRName:string read fIRName;
Как получить пакет от девайса?
function TIRPC.ReadIRPCState;
begin
Result:=false;
if not Assigned(dev) then Exit;
Result:=dev.GetFeature(report,sizeof(TIRPCReport){Dev.Caps.FeatureReportByteLength});
end;
С помощью метода GetFeature — передаем буфер для пакета, получаем в него пакет. Если подключения нет, метод вернет False.
Передача пакета девайсу производится аналогично:
dev.SetFeature(cmd,SizeOf(cmd));
Собственно сам плагин выполнен в виде dll.
Реализованы требуемые им функции:
Enable, Disable, GetName, GetInfo, GetVer, GetAuthor, IsInteractive, IsHaveSettings;
Соответственно в Enable мы подключаемся к девайсу методом Connect, запускаем таймер, который будет каждые 500 мс опрашивать девайс с помощью ReadState и если получил код кнопки, отдавать его в MKey:
function Enable: boolean; stdcall;
begin
IRPCPlugin:=TIrPCPlugin.Create;
IRPCPlugin.IRPC.Connect;
tmrReadState:=TTimer.Create(nil);
tmrReadState.Interval:=500;
tmrReadState.OnTimer:=IRPCPlugin.ReadState;
tmrReadState.Enabled:=true;
result:=true;
end;
procedure TIRPCPlugin.ReadState(Sender: TObject);
var
mainWnd:hWnd;
pluginact:string;
Btn:Word;
begin
IRPCPlugin.IRPC.Connect;
If not IRPCPlugin.IRPC.Connected then MessageBox(0,'IRPC device not connected', 'IRPC', MB_ICONEXCLAMATION);
if rep.cmd<>cmdGetIRPCState then begin
rep.cmd:=cmdGetIRPCState;
IRPCPlugin.IRPC.WriteIRPCcmd(rep);
end;
IRPCPlugin.IRPC.ReadIRPCState(rep);
Btn:=rep.data[1] shl 8+rep.data[2];
If btn<>0 then begin
pluginact:=Format('%x',[Btn]);
mainwnd:=Findwindow('TMainForm', 'MKey');
SendMessage(mainwnd, WM_PLUGIN, plugin_interface_version, DWORD(PChar(pluginact)));
end;
end;
В процедуре Disable отключаемся от девайса:
function Disable: boolean; stdcall;
begin
IRPCPlugin.IRPC.Disconnect;
FreeAndNil(IRPCPlugin);
FreeAndNil(tmrReadState);
result:=true;
end;
Модуль uHID у плагина и полноценной программы управления идентичный.
Основную программу выложил пока в скомпилированном виде, исходники не выкладываю, потому что над функционалом идет работа, там пока бардак :) Как будет более-менее приличный вариант, в Downloads положу.
Пока выглядит вот так:
- Сворачивается в область уведомлений.
- По клику разворачивается.
- Период опроса настраивается в Settings
- S — сохранение состояния в EEPROM
- L — загрузка состояния из EEPROM
- Power Btn — выполняет функцию fPCPwrSwitch (то бишь нажимает кнопку питания на ПК)
- Show log покажет список событий — засыпание / просыпание ПК. (у меня кнопка питания отправляет в Sleep).
- Остальное — тестовые кнопки.
- Кликая по одной из трех лампочек, переключаем соответственно — светодиод, Реле1, Реле2 (всплывающие подсказки помогут разобраться)
Вроде все важное рассказал. Если остались вопросы, задавайте в комментариях, постараюсь ответить.
Исходники
- Иходники прошивки USB-IRPC
- Принципиальная схема, печатная плата, наклейка, картинка-шпаргалка для сборки в формате DipTrace и png.
- Исходники плагина для MKey
- Программа управления — альфа версия на Delphi 2010.
Автор: RaJa