Здравствуй хабрачитатель! Меня зовут Константин и я программист, а именно занимаюсь программированием систем «Умный дом».
За четыре года работы в этой области, довелось попробовать много интересных контроллеров и ПО для решения задач домашней автоматизации. Одними из интереснейших устройств с которыми мне приходилось иметь дело — это устройства компании Global Cache серии IP2IR. Их предназначение состоит в том что-бы принять текстовую команду от клиента и излучить ее через светодиод ик-спектра.
Использование подобных устройств упрощает жизнь пользователям сразу по нескольким направлениям:
- в качестве ПДУ можно использовать планшеты, телефоны и ПК (с небольшой оговорочкой, нужен специальный софт);
- управление можно централизовать т.е. специальный софт можно поднять на сервере и обращаться к нему;
- в прицельной стрельбе нет необходимости, если с телевизором, например, таких проблем нет (мы в него и так смотрим), то вот для устройств вроде Blu-Ray проигрывателей или MediaServer'ов это может стать проблемой в случае если они заперты в шкафу или работают в режиме мультирум;
- одна кнопка ПДУ может выполнить целый макрос, например включить телевизор и Blu-Ray, затем переключить ТВ на нужный источник, например HDMI1, и пользователь может вообще не париться о том где и как у него подключены устройства.
Такая игрушка жизнь конечно упрощает, но и по карману тоже бьет, если мне не изменяет память, то GlobalCache для управления тремя устройствами стоит порядка 6000-7000 деревянных рублей. Не очень гуманное ценообразование, вот и было решено сделать собственную реализацию подобного устройства.
Честно признаться, идея крутилась достаточно давно, но вот опыта схемотехника и программиста МК у меня нет, ну или почти нет, вообщем из этих соображений была выбрана платформа Arduino со своей недосредой разработки. Поиски на просторах паутины дали мне готовое решение под названием IRRemote, как же я плевался обнаружив, что этот модуль заточен на работу с одним единственным выходом, явно не то что мне нужно, за то просмотр исходника дал мне уверенность в своих теоретических знаниях ИК управления оборудованием.
Теория проста, хоть и производители любят использовать собственные протоколы для описания команд, все сводится к одному и тому же принципу передачи данных. На некоторых сайтах можно найти команды для своих устройств в формате HEX или ProntoHEX, что по сути одно и тоже.
Команда в HEX формате выглядит следующим образом: 0000 FREQ CNT1 CNT2 ON_1 OFF1 ON_2 OFF2 ON_n OFFn.
Суть такова:
- 0000 — всегда четыре ноля, но мы для своих целей будем использовать первые два нуля заменяем на количество повторов кода, вторые два нуля это будет номер выхода на ардуинке. Таким образом код 010D — повторит команду дважды и отправит ее на выход 13 (тот что со встроенным светодиодом);
- FREQ — опорная частота сигнала. Обычно в диапазоне 35-40кГц и записывается хитро — 36кГц = 0073 = 115, не очень понятно почему десятичное 115 дает частоту 36кГц, скажу лишь, что посчитать это можно так 4145 / FREQ;
- CNT1 — не самая обязательная часть, я ее игнорирую;
- CNT2 — тоже игнорирую, если кому интересно, что это ссылка внизу;
- ON — а вот это уже данные. Это количество периодов на опорной частоте когда мы «мерцаем» ик-диодом;
- OFF — а это количество периодов когда мы держим на ножке логический ноль.
Что бы не заставлять Вас вдумчиво вчитываться в эту теорию просто проиллюстрируем выше сказанное:
Ну когда с теорией разобрались, самое время приступить к программированию, не буду описывать все трудности которые возникли у меня в начале работы с новым для меня зверьком и начнем пожалуй со следующего:
В составе среды ардуино существует специальная функция tone(pin,frequency,duration), она генерирует на порту вход/выхода сигнал — прямоугольную «волну», заданной частоты и с 50% рабочим циклом, используя прерывания таймера.
Сварганив на скорую руку, более или менее, рабочий вариант используя эту функцию, захотелось увеличить точность получаемых результатов, для этого в каталоге "Arduinohardwarearduinocoresarduino" был скопирован файл Tone.cpp и переименован в IR_Gen.cpp. Затем в получившемся файле я объявил недостающие переменные и удалил лишние, да и такие там тоже были. Функция tone модифицировалась в функцию startIR(frequency) которая ни где не объявлена и использовать ее можно только внутри этого файла.
Так же добавились еще две объявленные функции sendIR(char ir_string[]) и sendLastIR() — которые и вызывают бывший tone() (startIR()), однако перед тем как запустить модуляцию необходимо убедиться в том, что строка переданная в sendIR является набором данных ик-команды, с этой целью была включена функция парсига ParseIRString, она вернет ноль если данные не соответствую формату ик-команды и вернет значение опорной частоты если данные соответствуют. Приведу код на всякий случай, что бы стало ясно, как происходит парсинг строки:
unsigned int ParseIRString(char IRString[]){
byte xBool = 0;
unsigned int bInt = 0;
ir_data_array_length = 0;
unsigned int IR_Frequency = 0;
unsigned int IR_String_Length = 1024;
char Separator = ' ';
unsigned int i = 5;
char bChArr[4] = {0};
byte NumSys = 16;
ir_out_pin = 255;
if(IRString[0]=='s'){ //Если команда в формате Global Cache
ir_out_pin = (((IRString[7]-48)*3)-(3-(IRString[9]-48)))-1;
bChArr[0] = IRString[19];
bChArr[1] = IRString[20];
bChArr[2] = IRString[21];
bChArr[3] = 'r';
ir_repeat_count = StrToInt(bChArr,10)-1;
Separator = ',';
i = 13;
NumSys = 10;
}else{ //Если команда в формате HEX
bChArr[0] = IRString[3];
bChArr[1] = IRString[4];
bChArr[2] = 'r';
ir_out_pin = StrToInt(bChArr,16);
bChArr[0] = IRString[0];
bChArr[1] = IRString[1];
ir_repeat_count = StrToInt(bChArr,16);
}
if ((ir_out_pin>13)||(ir_out_pin<0)){return 0;}
for (i; i<IR_String_Length; i++){
if((IRString[i]>='a')&&(IRString[i]<='f')){IRString[i]-=32;} //UpperCase
if((IRString[i] != Separator)&&(IRString[i] != 'r')){ //Если число, а не сепаратор
if((IRString[i]>47)&&(IRString[i]<58)){ //Цифры в строке
bInt = bInt * NumSys + (IRString[i] - 48); //Получение числа
}else{
if ((IRString[i]>='A')&&(IRString[i]<='F')&&(NumSys==16)){//буквы от A до F
bInt = bInt * NumSys + (IRString[i] - 55); //Получение числа
}else{
return 0;
}
}
}else{
if(IR_Frequency==0){
if(NumSys==16){ //Если в HEX
IR_Frequency = (4145/bInt)*1000; //считаем значение частоты
}else{ //если Global Cache
IR_Frequency = bInt; //просто пишем частоту
}
xBool++;
}else{
if(xBool>2){
ir_data_array[ir_data_array_length]=bInt*2; //количество полупериодов
ir_data_array_length++;
}else{
xBool++;
}
}
bInt = 0;
if(IRString[i] == 'r'){Serial.println(IR_Frequency,DEC); return IR_Frequency;}
}
}
}
Затем исправляем прерывание таймера под наши цели и имеем в результате нечто вроде:
ISR(TIMER2_COMPA_vect){
if (ir_data_array[ir_data_current_step] > 0){
if(ir_data_current_step%2==0){
*timer2_pin_port ^= timer2_pin_mask; //мерцаем
}else{
*timer2_pin_port &= ~(timer2_pin_mask); //не мерцаем
}
ir_data_array[ir_data_current_step]--;
}else{
ir_data_array[ir_data_current_step] = ir_data_buffer;
ir_data_current_step++;
ir_data_buffer = ir_data_array[ir_data_current_step];
}
if (ir_data_current_step == ir_data_array_length){
if(ir_repeat_count>0){
ir_repeat_count--;
ir_data_current_step = 0;
}else{
stopIR(tone_pins[0]);
}
}
}
В конце этой феерии в файле Arduino.h необходимо объявить новоиспеченные функции byte sendIR(char ir_string[]); byte sendLastIR(); и вуа-ля, дело в шляпе. Результатом стала чуть расширенная среда ардуино, в составе которой есть готовое средство для управления бытовой техникой.
Знаю, не все фанатеют от исправления чего бы то ни было в среде разработки, но мне честно сказать так было удобнее, да и в конечном счете ничего не мешает сделать это просто сторонним модулем.
Информация по теме:
Сайт компании Global Cache
PDF с описанием API устройств Global Cache
Русскоязычная справка по функции tone
Как и обещал для любознательных, доп. инфа по ИК-управлению
Всем известная библиотека IRRemote
Автор: Janom