В предыдущей части урока мы познакомились с модулем 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 870 и дисплей 1602. При сборке проекта выдает ошибку. При проверке заметил что ошибка появляется при добавлении глобальных переменных unsigned char rc_code=0, rc_addr=0;. Подскажите что делаю не так?
Здравствуйте!
А какая ошибка хоть?
make[2]: *** [dist/default/production/REMCONTROL_NEC.X.production.hex] Error 1
make[1]: *** [.build-conf] Error 2
make: *** [.build-impl] Error 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)
По ходу у Вас нехватка памяти.
Да вы правы.
а как сделать, чтоб светодиод загорался и потухал при нажатии на 1 и 0 например?
Спасибо за ваши уроки. Правда за неимением соответствующего микроконтроллера (ранее использовал pic16f870) под данный проект не хватило памяти. Пришлось изворачиваться в Proteus и имитировать пульт ДУ с помощью Pattern генератора. Заказал себе 877А. Пока придет, буду как то вертеться.
Еще раз спасибо!
if(result[i+18]>240) rc_code|=0x80;
if(result[i+2]>240) rc_addr|=0x80;
Добрый день. Скажите пожалуйста число 240 откуда вы взяли?
Посчитал. Потом проверил в программе логического анализа. Вроде по таймингам протокола сходится.