PIC мк. Эксперимент №21. ИК управление (RC5).

Вот и подошло время разгребаться с удаленным ИК управлением.

Цель: Разобраться с принципами ИК управления по протоколу 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.

Алгоритм:

  1. Заведем выход приемника на ножку RB0 микроконтроллера и установим INTEDG = 0 (срабатывание по ниспадающему фронту).
  2. Если произошло прерывание по INTE в первый раз, то:
    • Запускаем таймер на интервал 1.250 мс (3 части из 4 времени передачи одного бита при 38 КГц)
    • Меняем значение INTEDG на противоположное
  3. Если прерывание по INTE происходит не в первый раз, то:
    • Меняем значение INTEDG на противоположное
    • Если INTEDG = bufer, запускаем таймер TMR1 на интервал 1.250 мс
  4. Если произошло прерывание по 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 мк. Эксперимент №21. ИК управление (RC5).: 25 комментариев

  1. Замечательный блог. Много поучительного. Как раз делаю такое же устройство, и тоже изучаю PIC. Хочу предложить свою функцию RC-5:

    void IR()
    {
    char i;
    unsigned int tmp,tmp1;
    static bit tmp2;
    ir_byte = 0;
    for (i=0;i<14;i++)
    	{
    	  tmp1=tmp1<>11;
    tmp2 = 0x01&tmp;
    if (ir_perekl!=tmp2){
    ir_byte = (tmp1 & 0x3F);
    }
    ir_perekl = tmp2;
    }
    

    Немного с “мусором”, но работает без сбоев.

  2. Так у вас же все получилось, зачем удалять, лучше всего пользоваться тегами code /code

    Насколько я понял у вас тут только побайтовый разбор полученной команды, для меня тут основным моментом был метод получения всего байта, и на него я и делал упор 🙂

    Кстати, а что означает tmp1=tmp1<>11, в первый раз вижу такое выражение?

  3. void IR()
    {
    char i;
    unsigned int tmp,tmp1;
    static bit tmp2;
    ir_byte = 0;
    for (i=0;i<14;i++)
    	{
    	  tmp1=tmp1<<1;
          if (!ir) SetBit(tmp1,0); else ClearBit(tmp1,0);
    	  __delay_us(1778); 
    	}
    tmp = tmp1>>11;
    tmp2 = 0x01&tmp;
    if (ir_perekl!=tmp2){
    ir_byte = (tmp1 & 0x3F);
    }
    ir_perekl = tmp2;
    }
    
  4. Хм, то есть вы без прерываний обрабатываете команду с помощью макроса __delay_us()?
    А когда вызывается функция IR()?
    А работает без сбоев в составе какого-то проекта (в котором есть прерывания, таймеры, индикация и прочие плюшки) или просто в тестовом варианте?

  5. Проект похож на Ваш, сейчас пытаюсь добавить приемник. Работает без сбоев. Функция вызывается прерыванием по входу RB0 по спаду сигнала на входе (инверсия сигнала от ИК-приемника, как Вы и писали). Все перечисленные плюшки в программе есть, за исключением таймера, как то без него получается:)

  6. Ну хз, мне как то стремно в ответственных местах макросы юзать 🙂 А так я более-менее уверен, и у меня голова не болит по этому поводу, единственное что я потом тут модифицировал – добавил обработку toggle бита, она оказалась полезной.

  7. спасибо…. таймер переконфигурил

    TMR1H = 0b11100111;
    TMR1L = 0b01110011;

    в Протеусе симулится на ура… а вот в железе хер(
    TSOP4836 пуллап резистор 10к

  8. забыл написать, в железе при нажатии кнопки на пульте он выдает случайный код…

  9. Ну, пока что глубоко не вникая, я бы посмотрел, что за код вываливается из пульта, точно ли там RC5, либо же попробовать подпаять кварц на 4 мега и прогнать мой код.

  10. Ну тогда попробуйте сразу мой код на кварце 4 МГц, не забыв что у меня частота 38 КГц, это будет быстрее подробного анализа кода и схемы, оставим это на крайний случай 🙂

  11. В моем случае таймер запускался на 1.250 мс, для 36 КГц расчет примерно такой:
    1 бит содержит 64 импульса с частотой 36 КГц ~ 27.78 мкс * 64 = 1.778 мс
    3 части из этого промежутка = 1.3335 мс

    То есть таймер нужно настроить на 1.3335 мс

  12. Попробовал повторить ваш пример с декодированием сигнала 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 все работает, в том числе и загрузка таймера 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); //команда

  13. Уведомление: Pic Lab, PIC16, Experiment #21, The IR remote protocol (RC5) | diymicro.org

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.