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 наиболее часто применяемых протокола передачи данных посредством ИК-лучей, используемых системами дистанционного управления.

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

 

 

Предыдущая часть Программирование МК PIC Следующий урок

 

Исходный код

 

 

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

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

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

 

 

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

 

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

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

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

  2. Cerentiy:

    make[2]: *** [dist/default/production/REMCONTROL_NEC.X.production.hex] Error 1
    make[1]: *** [.build-conf] Error 2
    make: *** [.build-impl] Error 2

  3. Cerentiy:

    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)

  4. Александр:

    а как сделать, чтоб светодиод загорался и потухал при нажатии на 1 и 0 например?

  5. Алексей:

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

  6. Михаил:

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

    if(result[i+2]>240) rc_addr|=0x80;
    Добрый день. Скажите пожалуйста число 240 откуда вы взяли?

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

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

*