Поставить на своего робота ИК-глаз зачастую очень полезно. Это и просто беспроводной способ управления, и бесконтакный переключатель режима работы робота. В моем случае я хотел бы объединить эти функции, а также использовать ИК-пульт как внешнюю клавиатуру, меняя параметры робота на ходу (при помощи символьного ЖКИ). В качестве пульта я использовал дешевый ИК-пульт от ардуино, а также фотоприемник на частоту несущей 38 кГц. Этот пультик работает по протоколу NEC.
В процессе изучения прототипов ПО, я наткнулся на готовые библиотеки ардуино. Но я не ардуинщик, мне интересно самому докопаться до сути, поэтому расскажу о собственной реализации приемного протокола.
Сперва нужно вкратце описать сам протокол передатчика. Есть огибающая, заполненная несущей 38 кГц (речь о вспышках светодиода пультика). Наш приемник детектирует огибающую, вот ее нам и надо распознать. Дальнейшая работа будет описана с уже принятым сигналом, помним, что он в противофазе вспышкам пультика (единица пульта – ноль приемника). Когда на ИК-глаз пульт не светит – на его выходе высокий уровень. Команда начинается с нуля длиной 9 мс, затем единица длиной 4.5 мс, затем идут биты. Ноль длиной 0.56 мс и единица 0.56 мс – это бит нуля. Ноль длиной 0.56 мс и единица 1.68 мс – это бит единицы. Всего передаются 32 бита (4 байта), младшим битом вперед. Первый байт – адрес, у моего пульта всегда 0х00. Второй байт – инверсия адреса, то есть 0xff. Третий байт – команда, четвертый байт – инверсия команды. Всегда есть возможность проверить правильность принятого байта. Если кнопку зажать, то команда передается только один раз, а затем передается сигнал повтора. Сигнал повтора следует с периодом 110 мс (это между повторами, между командой и первым повтором будет пауза около 40 мс из-за длины команды более 60 мс). Повтор выглядит так: ноль длиной 9 мс, единица длиной 2.25 мс, ноль длиной 0.56 мс.
Во всех примерах, которые мне попались, использовались следующие ресурсы микроконтроллера: внешнее прерывание (по спаду) и восьмибитный таймер с СК/1024 (при тактовой частоте 8 или 16 МГц). Емкости таймера достаточно, чтобы измерять длительность любых составляющих команды (начало, бит нуля, бит единицы), но недостаточно, чтобы измерять время между повторами. В тех примерах повторами никто не заморачивался – команда ловится, и ладно. Но мне интересно отрабатывать и сами повторы, поэтому мой алгоритм чуть-чуть сложнее.
Чтобы хватило таймера на все, я настроил его на переполнение каждые 100 мкс с самоперезагрузкой. А сам таймер сделал на двух регистрах озу, которые инкрементируются каждые 100 мкс. Вся программа по приему команды и повторов находятся в двух прерываниях, работу прерывания по таймеру я уже описал (но к ней еще вернусь).
Теперь к алгоритму работы с повторами.
Есть переменная «Код команды» – один байт. Есть переменная «Количество повторов» – я также сделал ее один байт. Когда команда получена – количество повторов становится равным 1. После приема команды таймер продолжает считать. Если произошло новое прерывание, а насчитал таймер время между командой и повтором – значит скорее всего мы дождались начала повтора. Но это не точно, поэтому повтора еще нет, вместо этого ставим флаг «поймали возможную паузу перед повтором». На следующем прерывании мы должны в таймере получить длину повтора. Если это так – пойман настоящий повтор, переменная количества повторов инкрементируется (но не более чем до 0xff – у меня она однобайтная, но чтоб довести до такого состояния – кнопку пульта надо зажать на 25 секунд), флаг «поймали возможную паузу перед повтором» сбрасывается. Когда нам надоест держать кнопку – мы ее отпустим. Прерываний больше не будет, а таймер будет дальше считать. Поэтому через 128 мс (с запасом) таймер должен самовыключиться. Для этого в его прерывании есть проверка на достижение этого значения. Кроме самовыключения, таймер включает флажок «повторов больше не будет». Позже я объясню, зачем он нужен.
Перечислю используемые в прерываниях переменные:
– 4 байта для приема 32 битов сдвигом;
– 1 байт команды (помним, что принимаем мы все байты младшим вперед, команда будет битами в другую сторону);
– 1 байт количество повторов;
– 1 байт количество выполненных команд (о нем чуть позже);
– 1 байт счетчик принимаемых битов;
– 2 байта для таймера;
– флаг, что поймали паузу перед повтором;
– флаг, что больше повторов не будет.
Ну а теперь самое интересное – алгоритм работы подпрограммы обработки внешнего прерывания.
Когда прерывание происходит в первый раз – таймер при этом выключен. Это и является признаком начала активности на линии. Включаем таймер, обнуляем переменные таймера и выходим.
Когда внешнее прерывание происходит при включенном таймере – значит мы что-то уже измерили, и надо узнать, что именно. Тут простой выбор: длина паузы между повторами/длина начала команды/длина кода повтора/длина единицы/длина нуля – все они сильно отличаются друг от друга (смотрим описание протокола). Определяем, что именно измерил таймер, и выполняем следующие действия:
– если это пауза перед повтором – ставим флаг, сбрасываем таймер и выходим;
– если это начало команды – загружаем в счетчик цикла число 32, сбрасываем таймер и выходим;
– если это повтор, то смотрим, есть ли флаг паузы перед повтором; если нет – сбрасываем таймер и выходим; если есть – смотрим, не достигла ли переменная повторов максимума; если не достигла – инкрементируем переменную, если достигла – не инкрементируем, потом сбрасываем флаг паузы перед повтором, сбрасываем таймер и выходим;
– если это ноль или единица – проверяем счетчик цикла на ноль, если не все биты приняты – определяем, ноль или единица пришли, затем задвигаем бит в 4 байтную переменную; декрементирую счетчик цикла, и снова проверяю его на ноль. Если не ноль – сбрасываем таймер и выходим. Если же счетчик цикла обнулился – проверяю адрес и команду, затем привожу ее в божеский вид старшим вперед. Пишу полученную команду в регистр команд, а в регистр повторов пишу «1». Сбрасываем таймер и выходим. Все.
Ну а теперь как этим пользоваться.
Данные с пульта мы получаем в прерываниях. А пользоваться этими данными мы будем в основной программе. По коду команды понятно, что надо делать, а по количеству повторов понятно, сколько раз надо команду выполнить. Но выполнять команды надо с правильной скоростью, желательно не быстрее, чем идут повторы команды. Именно тут нам нужна переменная «количество выполненных команд». Когда переменная команды перестала быть нулем – значит пришла команда. Выполняем ее и инкрементируем переменную «количество выполненных команд». Если число повторов и число выполненных команд равны – значит пока ничего не делаем. Ждем, когда переменная повторов увеличится, и снова «догоняем» второй переменной. Так будем делать до тех пор, пока не появится флаг «повторов больше не будет». Выполняем команду последний раз (если еще не успели), и на этом все. Обнуляем три регистра: команд, повторов и выполненных команд.
Может возникнуть вопрос: зачем ловить паузу между повторами? Давайте просто ловить повторы? Так не получится, ведь надо вовремя остановиться. Хотя, наверное, возможно придумать более простой алгоритм. Может быть, кто-нибудь расскажет более простой способ.
Ну и напоследок – коды кнопок ИК-пультика:
;0x1C ;ОК
;0x08 ;влево
;0x52 ;вниз
;0x5A ;вправо
;0x18 ;вверх
;0x16 ;*
;0x0D ;#
;0x19 ;0
;0x45 ;1
;0x46 ;2
;0x47 ;3
;0x44 ;4
;0x40 ;5
;0x43 ;6
;0x07 ;7
;0x15 ;8
;0x09 ;9