PIC Урок 12. Модуль CCP. Режим захвата. ИК-пульт. Часть 2

В предыдущей части урока мы познакомились с модулем CCP, а более подробно с режимом захвата, познакомились с передачей и приёмом данных от ИК-пульта дистанционного управления, а также с одним из протоколов такой передачи данных — RC5.

 

Займёмся протоколом NEC.

Поэтому давайте теперь разбираться, что из себя представляет этот протокол.

В протоколе NEC передаются в одной посылке 32 бита.

Рассказывать буду ориентируясь на инверсный вид сигнала, так как работаем мы именно с приёмником.

Сначала передаётся преамбула следующим образом. Сигнал из состояния 1 переходит в состояние 0 и находится в нём 9 милисекунд, затем возвращается в 1 и в этом состоянии находится 4,5 милисекунды.

После этого начинается передача наших 32 бит.

Бит передаётся следующим образом.

Сигнал переходит из состояния 1 в 0 и находится в нём в течении некоторого времени (абсолютно не важно какого), а затем возвращается в состояние 1.

Если мы передаём 0, то время между началом передачи бита и началом передачи следующего бита будет составлять 1,125 милисекунды, а если передаём 1, то 2,25 милисекунды.

Теперь поговорим о том, какие именно это 32 бита и за что они отвечают.

В типичном виде посредством протокола NEC передаются 8 бит адреса и 8 бит данных. Сначала передаются 8 бит адреса в обычном коде, затем в дополнительном (инверсном) коде. Затем передаются 8 бит данных в обычном виде и потом они же в инверсном. Таким образом, за счёт обычной и инверсной передачи длина посылки всегда будет при любом раскладе неизменной. Следует отметить, что все байты пердаются, начиная с младшего бита.

Правда, существует ещё одна из модификаций данного протокола, в которой адрес уже не 8-битный, а 16-битный, но при этом также передаются 32 бита, так как адрес передаётся только в обычном виде. Тут уже длина посылки зависит от адреса.

Перейдём на нашем многофункциональном пульте на код 720 и нажмём также кнопку POWER. Получим вот такую посылку (нажмите на картинку для увеличения изображения)

 

 

Попробуем разобрать данный код. Думаю, лучше это сделать по частям.

Начинается всё с преамбулы

 

 

Дальше адрес

 

 

И, помня о том, что биты следуют от младшего к старшим, читаем код справа налево. Получим значение адреса 0x6E.

Теперь попробуем прочитать инверсное значение адреса

 

 

Всё сходится. Нули и единицы поменялись местами.

Идём дальше.

Теперь код команды

 

 

Получается, что у данного пульта кнопка POWER имеет код 0x7D.

Теперь прочитаем инверсное значение кода команды

 

 

Также всё сходится и здесь. Обмен нулей и единиц прошел удачно.

Осталось теперь дело за малым. Считать это всё при помощи контроллера, вернее при помощи захвата таймера.

Мы не будем читать инверсные коды для экономии времени, думаю и так всё будет нормально считываться. А если у вас есть желание, то можете развить мою мысль, считав также и инверсные значения адреса и команды и провести сравнение, например, при помощи логической операции XOR, тем самым проверить правильность считывания кодов.

Проект мы сделаем из проекта урока 9 по символьному дисплею LCD2004_8BIT и назовём его REMCONTROL_NEC. Я думаю вы уже догадались, что просматривать адреса и коды команд мы будем на дисплее.

Откроем проект в MPLAB X, сделаем его главным, проверим в настройках проекта, что у нас не включено питание от программатора, откроем файл main.c и первым делом попробуем собрать и прошить наш код. Если строки нормально отображаются на дисплее, то движемся дальше.

В файле main.h добавим стандартную библиотеку ввода-вывод

 

#include "lcd.h"

#include <stdio.h>

 

Удалим из функции main() вывод тестовых строк

 

LCD_String((char*)"String 1");

LCD_SetPos(2,1);

LCD_String((char*)"String 2");

LCD_SetPos(4,2);

LCD_String((char*)"String 3");

LCD_SetPos(6,3);

LCD_String((char*)"String 4");

 

Добавим в данную функцию локальный строковый массив

 

void main()

{

  char str1[21];

 

 

Зададим направление ножке захвата таймера

 

LCD_Init();

TRISC2=1;

 

Разрешим прерывания от модуля CCP1

 

TRISC2=1;

CCP1IE=1;

 

Включим режим захвата каждого импульса по заднему фронту

 

CCP1IE=1;

CCP1CON=0x04;

 

Мы будем измерять время между двумя отрицательными импульсами.

Сбросим флаг прерываний от модуля CCP1

 

CCP1CON=0x04;

CCP1IF=0;

 

Настроим таймер 1, так как без него модуль CCP работать не будет

 

CCP1IF=0;

T1CKPS0=1;

T1CKPS1=1;

TMR1CS=0;

TMR1IE=1;

 

Мы включили делитель 1:8, тем самым обеспечив частоту счёта около Fosc/4/8 = 125 килогерц. Получается, что 1 период счёта таймера у нас будет длиться приблизительно 8 микросекунд. Также мы выбрали тактирование от внутреннего источника и включили прерывания от таймера.

Разрешим глобальные прерывания и включим таймер

 

TMR1IE=1;

GIE=0X1;

PEIE=0X1;

TMR1ON=1;

 

Над функцией main() добавим обработчик прерываний

 

//------------------------------------------------

void interrupt isr(void)

{

  if(TMR1IE && TMR1IF)

  {

  }

  if(CCP1IF)

  {

  }

}

//------------------------------------------------

 

Мы будем обрабатывать раздельно прерывания от таймера и от модуля CCP1, поэтому мы и добавили 2 условия в функцию-обработчик.

 

 

В тело условия обработки прерывания от таймера добавим код, который сбросит счётчик таймера, так как он сам не сбросится, и флаг прерываний

 

if(TMR1IE && TMR1IF)

{

  TMR1=0;

  TMR1IF=0;

}

 

А в теле условия обработки прерывания от модуля CCP мы также сбросим счётчик таймера и необходимые флаги

 

if(CCP1IF)

{

  TMR1=0;

  CCP1IE=0;

  CCP1IF=0;

 

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

 

#include "main.h"

//------------------------------------------------

unsigned char lock=0;

//------------------------------------------------

 

Добавим условие сброшенного флага блокировки в тело условия обработки прерываний от модуля CCP и после тела добавленного условия разрешим прерывания от модуля

 

CCP1IF=0;

if(!lock)

{

}

CCP1IE=1;

 

Также добавим глобальную переменную для счёта событий захватов

 

unsigned char lock=0;

unsigned int n=0;

 

Также добавим глобальный массив для сохранения результатов счёта в момент возникновения прерываний от модуля CCP

 

unsigned int n=0;

unsigned int result[40]={0};

 

Вернёмся в наше условие обработки прерывания от модуля CCP и в теле условия отсутствия блокировки сохраним регистр CCP1R в массиве

 

if(!lock)

{

  result[n]=CCPR1;

}

 

Увеличим переменную счёта захватов на 1

 

result[n]=CCPR1;

n++;

 

Если дошли до 33 (у нас 32 байта и 1 преамбула), то начнём изучать наш массив. Для этого в нашем теле условия отсутствия блокировки добавим ещё одно условие

 

n++;

if(n>33)

{

}

 

Добавим глобальные переменные для хранения считанного адреса и кода команды, а также добавим переменную для счётчика битов

 

unsigned int n=0, i=0;

unsigned char rc_code=0, rc_addr=0;

 

Перейдём опять в наш обработчик и в теле только что добавленного условия обнулим наши переменные адреса и команды

 

if(n>33)

{

  rc_code=0;

  rc_addr=0;

}

 

Вернёмся в тело нашего условия и добавим цикл счёта от 0 до 7

 

rc_addr=0;

for(i=0;i<8;i++)

{

}

 

В теле цикла мы считаем биты адреса и кода команды и запишем их в соответствующие переменные

 

for(i=0;i<8;i++)

{

  rc_code >>= 1;

  rc_addr >>= 1;

  if(result[i+18]>240) rc_code|=0x80;

  if(result[i+2]>240) rc_addr|=0x80;

}

 

Мы записываем значение бита в самый старший бит переменных, а затем в следующем цикле сдвигаем их вправо, так и набирается у нас байт. Число 240 выбрано потому, что при счёте до 240 мы приблизительно получаем период около 240×8 = 1920, что находится примерно в середине между периодом нуля (1,125 милисекунды) и периодом 1 (2,25 милисекунды). Поэтому если мы насчитаем больше 240, то значит это единица и мы её запишем в соответствующий бит, а если не досчитаем, то ничего не запишем и там останется 0.

Выйдем из тела цикла (но не из тела условия!) и сбросим счётчик захватов, а также установим блокировку

 

  if(result[i+2]>240) rc_addr|=0x80;

}

n=0;

lock=1;

 

Также нам нужно не забыть данную блокировку сбросить. Сделаем это в обработчике прерываний от таймера. Также сбросим счётчик захватов и там

 

TMR1IF=0;

lock=0;

n=0;

 

Осталось нам лишь только отобразить принятый адрес и код команды на дисплее. Конечно же, этим заниматься мы будем не в теле обработчика прерываний.

Сделаем это в бесконечном цикле функции main(). Мы будем здесь раз в 100 милисекунд сканировать переменную адреса и команды на изменение, и если хотя бы одна из них изменится, то отобразим адрес и команду на дисплее. Для этого мы сначала добавим ещё две глобальные переменные адреса и команды, которые будут хранить прошлые результаты

 

unsigned char rc_code=0, rc_addr=0, rc_code_old=0, rc_addr_old=0;

 

Ну и теперь проделаем, что мы запланировали, в бесконечном цикле функции main()

 

while(1)

{

  __delay_ms(100);

  if((rc_code!=rc_code_old)||(rc_addr!=rc_addr_old))

  {

    sprintf(str1,"Command: 0x%02X ",rc_code);

    LCD_SetPos(2,0);

    LCD_String(str1);

    sprintf(str1,"Addres:  0x%02X ",rc_addr);

    LCD_SetPos(2,1);

    LCD_String(str1);

    rc_code_old = rc_code;

    rc_addr_old = rc_addr;

  }

}

 

Осталось нам только собрать и прошить код.

Ну и, конечно же, начнём мы с кнопки POWER пульта, код которой мы так тщательно анализировали

 

 

Также испробуем работу других кнопок пульта

 

   

 

Ну и для полноты эксперемента исследуем работу ещё нескольких пультов дистанционного управления

 

         

 

Команды кнопок всех пультов, а также адреса устройств, прекрасно считываются и отображаются.

Значит, мы в очередной раз достигли цель занятия.

Мы научились работать с модулем CCP в режиме захвата, а также мы изучили 2 наиболее часто применяемых протокола передачи данных посредством ИК-лучей, используемых системами дистанционного управления.

Всем спасибо за внимание!

 

 

 

Исходный код

 

 

Купить программатор (неоригинальный) можно здесь: PICKit3

Купить программатор (оригинальный) можно здесь: PICKit3 original

Дисплей LCD 20×4 можно приобрести тут: Дисплей LCD 20×4

 

 

Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)

PIC Модуль CCP. Режим захвата. ИК-пульт

 

Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)

PIC Модуль CCP. Режим захвата. ИК-пульт

11 комментариев к “PIC Урок 12. Модуль CCP. Режим захвата. ИК-пульт. Часть 2

  1. Здравствуйте. Написал код по примеру, только на pic 870 и дисплей 1602. При сборке проекта выдает ошибку. При проверке заметил что ошибка появляется при добавлении глобальных переменных unsigned char rc_code=0, rc_addr=0;. Подскажите что делаю не так?

  2. make -f nbproject/Makefile-default.mk SUBPROJECTS= .build-conf
    make[1]: Entering directory 'D:/Doc/Obshie/Treadill/REMCONTROL_NEC.X'
    make -f nbproject/Makefile-default.mk dist/default/production/REMCONTROL_NEC.X.production.hex
    make[2]: Entering directory 'D:/Doc/Obshie/Treadill/REMCONTROL_NEC.X'
    «C:\Program Files (x86)\Microchip\xc8\v1.45\bin\xc8.exe» —pass1 —chip=16F870 -Q -G —double=24 —float=24 —opt=+asm,+asmfile,-speed,+space,-debug,-local —addrqual=ignore —mode=free -P -N255 —warn=-3 —asmlist -DXPRJ_default=default —summary=default,-psect,-class,+mem,-hex,-file —output=default,-inhx032 —runtime=default,+clear,+init,-keep,-no_startup,-osccal,-resetbits,-download,-stackcall,+clib —output=-mcof,+elf:multilocs —stack=compiled:auto:auto «—errformat=%f:%l: error: (%n) %s» «—warnformat=%f:%l: warning: (%n) %s» «—msgformat=%f:%l: advisory: (%n) %s» -obuild/default/production/main.p1 main.c
    «C:\Program Files (x86)\Microchip\xc8\v1.45\bin\xc8.exe» —pass1 —chip=16F870 -Q -G —double=24 —float=24 —opt=+asm,+asmfile,-speed,+space,-debug,-local —addrqual=ignore —mode=free -P -N255 —warn=-3 —asmlist -DXPRJ_default=default —summary=default,-psect,-class,+mem,-hex,-file —output=default,-inhx032 —runtime=default,+clear,+init,-keep,-no_startup,-osccal,-resetbits,-download,-stackcall,+clib —output=-mcof,+elf:multilocs —stack=compiled:auto:auto «—errformat=%f:%l: error: (%n) %s» «—warnformat=%f:%l: warning: (%n) %s» «—msgformat=%f:%l: advisory: (%n) %s» -obuild/default/production/lcd.p1 lcd.c
    «C:\Program Files (x86)\Microchip\xc8\v1.45\bin\xc8.exe» —chip=16F870 -G -mdist/default/production/REMCONTROL_NEC.X.production.map —double=24 —float=24 —opt=+asm,+asmfile,-speed,+space,-debug,-local —addrqual=ignore —mode=free -P -N255 —warn=-3 —asmlist -DXPRJ_default=default —summary=default,-psect,-class,+mem,-hex,-file —output=default,-inhx032 —runtime=default,+clear,+init,-keep,-no_startup,-osccal,-resetbits,-download,-stackcall,+clib —output=-mcof,+elf:multilocs —stack=compiled:auto:auto «—errformat=%f:%l: error: (%n) %s» «—warnformat=%f:%l: warning: (%n) %s» «—msgformat=%f:%l: advisory: (%n) %s» —memorysummary dist/default/production/memoryfile.xml -odist/default/production/REMCONTROL_NEC.X.production.elf build/default/production/lcd.p1 build/default/production/main.p1
    Microchip MPLAB XC8 C Compiler (Free Mode) V1.45
    Build date: Nov 15 2017
    Part Support Version: 1.45
    Copyright (C) 2017 Microchip Technology Inc.
    License type: Node Configuration

    :: warning: (1273) Omniscient Code Generation not available in Free mode
    C:\Program Files (x86)\Microchip\xc8\v1.45\sources\common\doprnt.c:538: warning: (373) implicit signed to unsigned conversion
    C:\Program Files (x86)\Microchip\xc8\v1.45\sources\common\doprnt.c:541: warning: (373) implicit signed to unsigned conversion
    C:\Program Files (x86)\Microchip\xc8\v1.45\sources\common\doprnt.c:1316: warning: (373) implicit signed to unsigned conversion
    C:\Program Files (x86)\Microchip\xc8\v1.45\sources\common\doprnt.c:1317: warning: (373) implicit signed to unsigned conversion
    C:\Program Files (x86)\Microchip\xc8\v1.45\sources\common\doprnt.c:1500: warning: (373) implicit signed to unsigned conversion
    C:\Program Files (x86)\Microchip\xc8\v1.45\sources\common\doprnt.c:1524: warning: (373) implicit signed to unsigned conversion
    main.c:6: error: (1250) could not find space (80 bytes) for variable _result
    (908) exit status = 1
    nbproject/Makefile-default.mk:147: recipe for target 'dist/default/production/REMCONTROL_NEC.X.production.hex' failed
    make[2]: Leaving directory 'D:/Doc/Obshie/Treadill/REMCONTROL_NEC.X'
    nbproject/Makefile-default.mk:90: recipe for target '.build-conf' failed
    make[1]: Leaving directory 'D:/Doc/Obshie/Treadill/REMCONTROL_NEC.X'
    nbproject/Makefile-impl.mk:39: recipe for target '.build-impl' failed
    make[2]: *** [dist/default/production/REMCONTROL_NEC.X.production.hex] Error 1
    make[1]: *** [.build-conf] Error 2
    make: *** [.build-impl] Error 2

    BUILD FAILED (exit value 2, total time: 3s)

  3. Спасибо за ваши уроки. Правда за неимением соответствующего микроконтроллера (ранее использовал pic16f870) под данный проект не хватило памяти. Пришлось изворачиваться в Proteus и имитировать пульт ДУ с помощью Pattern генератора. Заказал себе 877А. Пока придет, буду как то вертеться.
    Еще раз спасибо!

  4. Не кажется ли вам, что обьявлять массив int на 40 слишком ЖИРНО для этого микроконтроллера? Гораздо экономичнее обьявить переменную типа uint32_t(long) и с присвоением нужного результата сдвигать на 1.
    Как у меня (наброски из программы)
    If n>0 //пропускаем приамбулу
    if n>=1;
    if(n==32) lock=1;
    Это в прерывании
    В цикле считываем значение rezult с разбивкой на байты
    b0 = (unsigned char) (rezult);
    b1 = (unsigned char) (rezult>>8);
    b2 = (unsigned char) (rezult>>16);
    b3 = (unsigned char) (rezult>>24);

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

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

*