Вот и подошло время разгребаться с удаленным ИК управлением.
Цель: Разобраться с принципами ИК управления по протоколу RC5
Средства: микроконтроллер PIC16f877a, Ик приемник TSOP1736(1738), devboard под мозги будущего усилителя
Вкратце о самом протоколе RC5:
Частота: частота несущей составляет 36 кГц, соответственно период около 27.8 мкс.
Модуляция: Модуляция в RC5 осуществляется следующим способом: каждый бит делится наполовину, и в каждой из половинок разные уровни, если в первой половине 0, а во второй 1, то это 1, ну и наоборот соответственно (Код Манчестера):
Здесь есть один нюанс, который необходимо учитывать. Дело в том, что усилитель передатчика обычно представляет собой транзистор, включенный по схеме с общим эмиттером, то на выходе у нас логика будет инвертированной, таким образом:
И это еще не все, в реальности, по протоколу на каждый бит информации приходится 64 импульса, 32 импульса частоты 36 кГц, показывающих высокий уровень и 32 периода тишины, то есть со светодиода летят такие пачки импульсов.
Представление пакетов данных: Посылка данных состоит из 14 бит и имеет примерно такой вид:
S1, S2 – стартовые биты, всегда равны 1, по ним ловим начало передачи.
T – бит переключалка, во время зажатия клавиши его состояние периодически изменяется на противоположное. Пример полезности: чтобы переключить телик на 22 канал вам нужно будет нажать клавишу 2 реально дважды.
A4-A0 – адрес, по нему система определяет что за устройство должно принимать сигнал.
Ниже список устройств и их адресов:
0 - TV 1 - TV2 2 - Телетекст 3 - Расширения для первых двух TV 4 - Лазерный видео проигрыватель 5 - Видеозапись1 6 - Видеозапись2 7 - Зарезервировано 8 - Спутниковое оборудование?(SAT1) 9 - Расширения для видеозаписывающих устройств 10 - SAT2 11 - Зарезервировано 12 - CD видео проигрыватель 13 - Зарезервировано 14 - CD фото (интересно что это за девайс) 15 - Зарезервировано 16 - Предусилок 17 - Приемник/тюнер 18 - Кассетник 19 - Предусилок 2 20 - CD 21 - Аудио стойка 22 - Спутниковый аудио приемник(?) 23 - DCC запись (?) 24 - Зарезервировано 25 - Зарезервировано 26 - Записывающий CD 27 - 31 - Зарезервировано
Ну и наконец С5-С0 – Непосредственно команда, у которой также есть стандартная спецификация, что на мой взгляд весьма разумно:
0-9 - Цифровые клавиши 12 - Режим ожидания 13 - Mute 14 - Настройка 16 - Звук + 17 - Звук - 18 - Яркость + 19 - Яркость - 20 - Насыщенность + 21 - Насыщенность - 22 - Басс + 23 - Басс - 24 - Тембр + 25 - Тембр - 26 - Баланс вправо 27 - Баланс влево 48 - Пауза 50 - Промотка назад 51 - Промотка вперед 53 - Воспроизведение 54 - Стоп 55 - Запись 63 - Выбор системы 71 - Выключение дисплея(?) 77 - Линейная функция + 78 - Линейная функция - 80 - Вверх 81 - Вниз 82 - Меню 83 - Выход из меню 84 - Отображение статуса A/V 85 - Влево 86 - Вправо 87 - OK 88 - Картинка в картинке (вкл/выкл) 89 - Сдвиг картинки 90 - Картинка в картинке - смена местами окон 91 - Строб 92 - Мультистроб 93 - Блокировака изображения 94 - Сканирование 95 - Выбор картинки в картинке 96 - Мозаика из видеоокошек 97 - Picture DNR(?) 98 - Сохранение 99 - Строб картинки в картинке 100 - Вызов главного изображения 101 - Заморозка картинки в картинке 102 - КВК вверх 103 - КВК вниз 118 - альтернативный режим 119 - Опции другой режим 123 - Соединение 124 - Отсоединение
Настало время проверить все на практике, вот наш пациент:
Китайский пульт, доставшийся мне в нагрузку вместе с тюнером. Предположение, что он работает по протоколу RC5 было сделано совершенно случайно, когда я заметил, что пульт отлично работает с телевизором марки Philips.
Для того, чтобы проверить свое предположение я отнес пульт на работу к человеческому осцилку и накинул щуп на ножку светодиода:
Такая форма сигнала обусловлена тем, что осциллограмма снимается с излучающего светодиода, а не с приемника. Пока с теорией сходится, 14 бит присутствует, но если мы измерим частоту, то увидим вот какую интересную штуку:
Частота 38 КГц!!! Ох уж эти китайцы, подключиться к осцилку было очень предусмотрительно, нужно брать приемник на 38 КГц, а не на 36, ну и в коде тоже надо быть осторожным. Сначала я подумал, что я промахнулся с протоколом, но потом в википедии нашел интересную фразу:
The command data is a Manchester coded bitstream modulating a 36 kHz carrier. (Often the carrier used is 38 kHz or 40 kHz, apparently due to misinformation about the actual protocol.)
Для меня в общем-то это не принципиально, и пульт у меня всего один, поэтому будем работать с тем что есть.
Следующий шаг сборка приемника. Я купил на рынке аналог TSOP1738 в таком корпусе:
В таком корпусе распиновка зеркальна стандартной распиновке TSOP1736 в здоровом корпусе.
Схему подключения берем из даташита, ничего не мудрим:
Хорошо, если есть осциллограф цифровой или логический анализатор, тогда мы можем подтыкнуться на выход приемника и посмотреть, что у нас за сигнал на выходе. Но стоп, у нас же есть логический анализатор! Встроенный в Pickit2.
Подключаемся, и загоняем цифру 8:
Четко видны два стартовых бита, и явно отслеживается 0b001000=8. Адрес 0, т.к. пульт от ТВ тюнера. Каким же образом заставить наш микроконтроллер работать со всей этой байдой? Для себя я разработал алгоритм на основе прерываний INTE и TMR1.
Основная сложность в обработке кода – мы не можем засечь абсолютное начало передачи, то что она началась мы видим только во второй половине бита S1.
Алгоритм:
- Заведем выход приемника на ножку RB0 микроконтроллера и установим INTEDG = 0 (срабатывание по ниспадающему фронту).
- Если произошло прерывание по INTE в первый раз, то:
- Запускаем таймер на интервал 1.250 мс (3 части из 4 времени передачи одного бита при 38 КГц)
- Меняем значение INTEDG на противоположное
- Если прерывание по INTE происходит не в первый раз, то:
- Меняем значение INTEDG на противоположное
- Если INTEDG = bufer, запускаем таймер TMR1 на интервал 1.250 мс
- Если произошло прерывание по TMR1, то снимаем значение на пине RB0 и помещаем его в буфер, а также загоняем в переменную для хранения данных
Чтобы было немного проще, я набросал картинку, по которой сам разбирался:
Внимательно посмотрим на картинку: нам прилетает байт 11000001001000, а снятый нашим алгоритмом байт будет выглядеть следующим образом 10000010010001. Мы потеряли одну 1 в начале, и добавилась еще одна 1 в конец (которая, к слову, всегда будет 1).
Чтобы отделить мух от котлет, уберем одну 1 с конца, и просто в зависимости от номера байта, будем пихать данные либо в адресную переменную, либо в командную.
Вот, теперь можно и код показывать 🙂
#include <htc.h> #include <stdio.h> #include "usart.h" #define _XTAL_FREQ 4000000 #define IRpin RB0 volatile static bit direction; //1 - rising, 0 - falling volatile unsigned char IRbyte; //принятый байт volatile bit buffer; //буфер для значений таймера volatile unsigned char bytecount; //количество байт __CONFIG(WDTDIS & UNPROTECT & LVPDIS & HS); void StartTimer() //Выносим настройки таймера в отдельную функцию { T1CKPS1 = 0; //Не используем делители T1CKPS0 = 0; T1OSCEN = 0; //выключаем внутренний генератор TMR1CS = 0; // Fosc/4 TMR1IE = 1; // прерывание по переполнению TMR1 TMR1H = 0b11111011; TMR1L = 0b00010110; //1.257ms TMR1ON = 1; // включаем таймер } void main() { TRISB0 = 1; //Порт RB0 на вход init_comms(); //Инициализируем уарт printf("\r\nStart"); //Отладочное сообщение buffer = 0; //Начальные условия IRbyte = 0; bytecount = 0; direction = 0; INTEDG = 0; //ждем прерывания по ниспадающему фронту GIE = 1; //Понеслась PEIE = 1; INTE = 1; while(1) { if (bytecount>13) //Если принято 14 бит { GIE = 0; //Временно блокируем прерывания printf("\r\nPressed button is %d", IRbyte); //Выводим команду по уарту IRbyte = 0; //И заново обнуляемся bytecount=0; INTEDG = 0; buffer = 0; GIE = 1; } }//while(1) } void interrupt isr() { if (INTF) //Прерывание по RB0 { if (bytecount<1) //Если произошло в первый раз { direction = 1; //Ждем срабатывания по возрастающему фронту INTEDG = 1; StartTimer(); //Запускаем таймер bytecount++; //увеличиваем счетчик принятых бит } else //Если уже не в первый раз { direction = !direction; //меняем фронт срабатывания на противоположный if (direction==buffer) //Проверяем можно ли запускать таймер { StartTimer(); bytecount++; } //if (direction==buffer) INTEDG = direction; } //else INTF = 0; } //INTF else { //срабатывание RB0 приоритетнее таймера if (TMR1IF) { buffer = IRpin; //Присваеваем буфферу значение RB0 TMR1ON = 0; //Выключаем таймер TMR1IF = 0; if ((bytecount>7)&&(bytecount<14)) //забираем только команду { IRbyte <<= 1; IRbyte |= buffer; } } } }
а вот и корявое видео с работой:
Все отлично распознается, если кнопка держится дольше положенного, срабатывает пару раз. Чтобы избежать подобного казуса, код должен модернизироваться с учетом обработки toggle бита, но это уже другая история 🙂
Замечательный блог. Много поучительного. Как раз делаю такое же устройство, и тоже изучаю PIC. Хочу предложить свою функцию RC-5:
Немного с “мусором”, но работает без сбоев.
не получилось вставить код, половину кода выбросило.
Так у вас же все получилось, зачем удалять, лучше всего пользоваться тегами code /code
Насколько я понял у вас тут только побайтовый разбор полученной команды, для меня тут основным моментом был метод получения всего байта, и на него я и делал упор 🙂
Кстати, а что означает tmp1=tmp1<>11, в первый раз вижу такое выражение?
УРА!!! Получилось.
а можете мне на sargein@gmail.com скинуть код, а я посмотрю че он не вставляется
ps. оп, слега запоздал))
Хм, то есть вы без прерываний обрабатываете команду с помощью макроса __delay_us()?
А когда вызывается функция IR()?
А работает без сбоев в составе какого-то проекта (в котором есть прерывания, таймеры, индикация и прочие плюшки) или просто в тестовом варианте?
Проект похож на Ваш, сейчас пытаюсь добавить приемник. Работает без сбоев. Функция вызывается прерыванием по входу RB0 по спаду сигнала на входе (инверсия сигнала от ИК-приемника, как Вы и писали). Все перечисленные плюшки в программе есть, за исключением таймера, как то без него получается:)
Ну хз, мне как то стремно в ответственных местах макросы юзать 🙂 А так я более-менее уверен, и у меня голова не болит по этому поводу, единственное что я потом тут модифицировал – добавил обработку toggle бита, она оказалась полезной.
доброе утро !
подскажите пож. как переделать сабжный код под 20 Mhz
Доброе, нужно просто таймер TMR0 перенастроить, подробнее про него можно здесь прочитать тут http://diymicro.ru/pic-mk-eksperiment-3-ispolzovanie-tajmera-tmr0.html
зы. выплывшие косяки в оформлении кода подправлю чутка попозже, как до нормального интернета доберусь.
спасибо…. таймер переконфигурил
TMR1H = 0b11100111;
TMR1L = 0b01110011;
в Протеусе симулится на ура… а вот в железе хер(
TSOP4836 пуллап резистор 10к
забыл написать, в железе при нажатии кнопки на пульте он выдает случайный код…
Ну, пока что глубоко не вникая, я бы посмотрел, что за код вываливается из пульта, точно ли там RC5, либо же попробовать подпаять кварц на 4 мега и прогнать мой код.
пуьтов два, 100% RC5, смотрел чипы внутри SAA1250
Ну тогда попробуйте сразу мой код на кварце 4 МГц, не забыв что у меня частота 38 КГц, это будет быстрее подробного анализа кода и схемы, оставим это на крайний случай 🙂
а на сколько нужно сдвинуть таймер для 36 КГц?
В моем случае таймер запускался на 1.250 мс, для 36 КГц расчет примерно такой:
1 бит содержит 64 импульса с частотой 36 КГц ~ 27.78 мкс * 64 = 1.778 мс
3 части из этого промежутка = 1.3335 мс
То есть таймер нужно настроить на 1.3335 мс
Попробовал повторить ваш пример с декодированием сигнала RC5 от IR пульта. Можно сказать ничего не получилось. Пошагово выставляя то там то сям единичку на тестовой ноге в разных частях кода выяснил, что не так как надо работает подпрограмма задержки StartTmr1. В моем случае получается где-то 50 микросекунд хотя должно быть 1250. Первое что не понятно, почему не используется делитель на 8 T1CKPS1 = 0; T1CKPS0 = 0; и второе если забить число 1257 в инженерный калькулятор то получается инвертированное ваше число 10011101001 TMR1H = 0b11111011; TMR1L = 0b00010110; //1.257ms. Почему ивертированное а не с калькулятора?
Прошу выслать ваш компилятор на почту, а то все мозоли посбивал правя код под
PIC ССS 1.104
Тьфу ты блин на вечер глядя вы заставили меня вспоминать какого хрена я делал именно так и я долго не мог 🙂
1. А зачем нам делитель?
2. Что по вашему произойдет, если настроить значения регистров таймера на 1257?
P.S. Подсказка, прерывание по таймеру сработает не через 1.25 мс, а через 64.278.
P.S.S. Ушел на AlexeyGN@yandex.ru
До поздна просидел разбираясь с TMR1, а только потом выяснил из helpa CCS C , что TMR1H и TMR1L не обрабатываются компилятором версии PCM 14bit. Типа работайте с помощю только делителя в ходной частоты. Если я все правильно понял 🙁
Я с CCS не работаю, так что ничего по этому поводу сказать не могу.
В CCS все работает, в том числе и загрузка таймера TMR1. Немного убраться и подчистить. сделать IRbyte long, тогда можно анализировать команду, адрес и toggle бит:
Вместо 49 строки можно так:
printf(“RC5 Code=0x%04LX DevAddr=0x%02X Toggle=%d Cmd=0x%02X\n\r”,
IRbyte,
((IRbyte >> 6) & 0x001F), //адрес
bit_test(IRbyte,11), //бит повтора
IRbyte&0x003F); //команда
Тогл бит уже в примере с реальным использованием ик управления без увеличения типа, но с добавлением двух битовых переменных.
http://diymicro.ru/ik-upravlenie-dlya-ventilyatora.html
Уведомление: Pic Lab, PIC16, Experiment #21, The IR remote protocol (RC5) | diymicro.org