Инфракрасная связь (infrared, IR) – это один из самых простых и дешевых способов беспроводной передачи данных. Дальность связи при этом невелика, но ее вполне хватает для различных проектов автоматизации дома, управления телевизорами и т.д. В данной статье мы рассмотрим создание декодера инфракрасных сигналов под управлением микроконтроллера PIC. Поскольку готовых библиотек для решения этой задачи практически не существует, то автор данного проекта (ссылка на оригинал приведена в конце статьи) разработал для этой цели свою собственную библиотеку.
Ранее на нашем сайте мы рассматривали и другие проекты с применением инфракрасной связи:
- автоматизация дома с использованием инфракрасной связи и Arduino;
- универсальный инфракрасный пульт ДУ на основе Arduino и приложения для Android;
- пульт управления кондиционером на основе Arduino и смартфона;
- декодер инфракрасных сигналов на Arduino;
- автоматизация дома на микроконтроллере PIC с управлением по инфракрасной связи.
Необходимые компоненты
- Микроконтроллер PIC16F886 (купить на AliExpress).
- Инфракрасный приемник (IR Receiver) TSOP1838 (купить на AliExpress).
- Программатор PICkit 3 (купить на AliExpress).
- Резистор 4,7 кОм (купить на AliExpress).
- Модуль FTDI UART.
- Пульт ДУ (автор использовал NEC).
- Макетная плата.
- Соединительные провода.
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Внешний вид компонентов, необходимых для нашего проекта, приведен на следующем рисунке.
Различные типы протоколов инфракрасной связи и способы их декодирования
Существуют различные типы протоколов инфракрасной связи (IR protocols), но наиболее популярными среди них являются NEC Remote Protocol и RC5 Remote Protocol. В данном проекте мы будем рассматривать протокол NEC. Для предотвращения помех данный протокол использует несущую частоту 38 кГц, которая модулируется цифровыми сигналами. Данный процесс более наглядно представлен на следующем рисунке.
А на экране осциллографа это выглядит следующим образом:
Когда вы нажимаете какую либо кнопку на пульте ДУ (дистанционного управления) инфракрасный генератор (IR Blaster) передает поток данных, который принимается и обрабатывается приемником, который представляет собой специальную микросхему, осуществляющую демодуляцию сигнала (то есть снимается закон модуляции несущей) и дальнейшее его декодирование. Внешний вид принимаемого приемником сигнала выглядит следующим образом:
Как можно видеть из представленного рисунка, инфракрасный сигнал в протоколе NEC начинается со стартового импульса длительностью 9 мс, за которым следует инвертированный импульс длительностью 4,5 мс. Приняв эти два импульса мы можем быть уверенными в том, что следом за ними будут передаваться данные. Для распознавания следующих данных используется информация об их длительности. Каждый импульс в последовательности имеет длину 562.5 мкс, передаваемых на несущей частоте 38 кГц. Для логического 0 за импульсом длительностью 562.5 мкс и уровнем high следует импульс такой же длительности уровнем low. Таким образом, общее время передачи сигнала логического 0 составляет 1125 мкс. Для логической 1 за импульсом длительностью 562.5 мкс и уровнем high следует импульс с уровнем low длительностью 1687 мкс (562.5 x 3). Таким образом, общее время передачи сигнала логической 1 составляет 2250 мкс.
Инфракрасный приемник TSOP38238
Приемник TSOP38238 содержит специальную микросхему, которая демодулирует приходящий сигнал (снимает закон модуляции несущей) и декодирует принимаемые данные с помощью встроенного PIN диода, предварительного усилителя и полосового фильтра. Внешний вид инфракрасного приемника TSOP38238 и его структурная схема показаны на следующем рисунке.
Когда вы нажимает кнопку на пульте ДУ инфракрасный светодиод (IR LED) испускает инфракрасный сигнал, который модулируется частотой 38 кГц (для протокола NEC). Модуляция испускаемого светового потока необходима потому что Солнце также испускает инфракрасный свет и если бы не было модуляции, то свет Солнца мог бы создавать серьезные проблемы инфракрасной связи. Используя модуляцию мы можем свести эти проблемы к минимуму. После приема данных микроконтроллер или другое обрабатывающее устройство декодирует их и формирует на своем выходе результирующий сигнал.
Схема проекта
Схема декодера инфракрасных сигналов на основе микроконтроллера PIC представлена на следующем рисунке.
"Сердцем" представленной схемы является микроконтроллер PIC16F886, работающий на тактовой частоте 16MHz. Модуль FTDI UART в схеме используется для процесса отладки и передачи данных через последовательный порт в компьютер. Подтягивающий резистор используется для "подтягивания" контакта MCLR микроконтроллера до уровня VCC. Значение сопротивления этого подтягивающего резистора может быть в диапазоне 4.7-10 кОм. Для приема инфракрасных сигналов мы используем приемник TSOP1838.
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты. Для написания кода программы мы использовали MPLAB X IDE.
Первым делом в программе настроим биты конфигурации – если этого не сделать программа будет выдавать сообщение об ошибке. В некоторых из этих настройках мы выбираем источник тактовой частоты и преобразуем ее в тактовую частоту для микроконтроллера PIC. Это нельзя делать в основном коде программы потому что тактовая частота должна быть корректно сконфигурирована перед тем основной код программы начнет исполняться.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator: High-speed crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config MCLRE = OFF // RE3/MCLR pin function select bit (RE3/MCLR pin function is MCLR) #pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled) #pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled) #pragma config BOREN = ON // Brown Out Reset Selection bits (BOR enabled) #pragma config IESO = OFF // Internal External Switchover bit (Internal/External Switchover mode is enabled) #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled) #pragma config LVP = OFF // Low Voltage Programming Enable bit (RB3/PGM pin has PGM function, low voltage programming enabled) // CONFIG2 #pragma config BOR4V = BOR40V // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V) #pragma config WRT = OFF // Flash Program Memory Self Write Enable bits (Write protection off) |
Далее зададим макрос _XTAL_FREQ. Его также необходимо задавать в начале программы.
1 |
#define _XTAL_FREQ 16000000 //16mhz crystal oscillator |
Затем в программе подключим необходимые библиотеки. Необходимо отметить что библиотеки <xc.h>, <pic16f886.h> и <stdio.h> есть в самой MPLAB X IDE, а библиотеки “uart.h” и “IRlib.h” мы запрограммируем сами.
1 2 3 4 5 |
#include <xc.h> // XC library has all the necessary functions and definitions. #include <stdio.h> //We have Included the stdio library because we are using printf for serial.’ #include <pic16f886.h> // #include "uart.h" // Custom UART Library #include "IRlib.h" // Custom IR library |
Далее мы напишем еще ряд макросов – для работы с периферийными устройствами и прерываниями.
1 2 3 4 5 6 7 8 9 10 11 12 |
#define GLOBAL_INTERRUPT INTCONbits.GIE //1 = Enables all unmasked interrupts 0 = Disables all interrupts #define PERIPHERAL_INTERRUPT INTCONbits.PEIE //1 = Enables all unmasked peripheral interrupts 0 = Disables all peripheral interrupts #define TIMER0_OVERFLOW_INTERRUPT INTCONbits.T0IE //1 = Enables the Timer0 interrupt 0 = Disables the Timer0 interrupt #define PRESCALER_ASSIGNMENT OPTION_REGbits.PSA // 1 = Prescaler is assigned to the WDT 0 = Prescaler is assigned to the Timer0 module #define ENABLE 1 #define DISABLE 0 #define OUTPUT 0 #define INPUT 1 #define TO_WDT 1 #define ENABLE_LOG 1 #define DISABLE_LOG 0 |
После этого запрограммируем еще ряд функций. В функции INTERRUPTS_Init() мы будем разрешать все прерывания, которые нам будут необходимы в программе. В функции assign_prescaler() мы будем задавать необходимый нам коэффициент деления предделителя, а в функции init() мы будем вызывать эти две вышеописанные функции и инициализировать последовательную связь.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/* Enables all required Interrupts * Timer0 Overflow Interrupt is used to measure * the incoming pulse duration from the TSOP Sensor */ void INTERRUPTS_Init() { GLOBAL_INTERRUPT = ENABLE; // ENable Global Interrupt PERIPHERAL_INTERRUPT = ENABLE; // Enable Peripheral Interrupt TIMER0_OVERFLOW_INTERRUPT = ENABLE; // Enable Timer0 Overflow } /* The Prescaler inside the PIC16f886 microcontroller * is either assigned to Watchdog timer or it's assigned * to the TIMER0: For this code, we have set it to WDT * */ void assign_prescaler() { PRESCALER_ASSIGNMENT = TO_WDT; } void init() { serialBegin(9600, _XTAL_FREQ); // Enable Serial with 9600 Baud ) INTERRUPTS_Init(); // Enable all Interrupts assign_prescaler(); // assign prescaler } |
В основной функции нашей программы main() мы будем вызывать функцию init(), а затем функцию ir_init() из библиотеки для работы с инфракрасной связью. Далее мы будем использовать функцию printf() из библиотеки studio.h чтобы проверить работает ли printf или нет. Если работает, то мы будем выводить соответствующее сообщение в окно монитора последовательной связи. Далее проверим состояние контакта IR_SENSOR_PIN – high оно или low. Если low, то мы будем проверять поступают ли данные от инфракрасного приемника или нет.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void main(void) { init(); // we call the init() function ir_init(); // we call the iR_init function. printf("System Initialized \n"); // First print statement for debugging while (1) { if (IR_SENSOR_PIN == 0) { ir_result(ENABLE_LOG); } } return; } |
Библиотека для последовательной связи (UART)
Данная библиотека состоит из файлов uart.h и uart.c, оба этих файла необходимо подключить в проект. Функций в этой библиотеке немного. Принцип их работы вы достаточно легко поймете если ознакомитесь со статьей про использование последовательного порта в микроконтроллерах PIC. В файле uart.h содержится объявление следующих функций:
1 2 |
void putch(char data); // putch() function prototype void serialBegin(unsigned int baudRate,long freq); //serialBegin() function prototype |
В файле “uart.c” мы подключаем его заголовочный файл “uart. h”, далее определяем все биты, которые нам понадобятся для осуществления последовательной связи, затем объявляем функцию putch(). Для компиляторов от фирмы microchip функция putch() представляет собой специальную функцию в которой при вызове функции printf() компилятор знает о ее редиректе на функцию putch() и, таким образом, мы можем задействовать функцию printf() для компилятора xc8. Далее в функции serialBegin() мы задействуем последовательную связь. В ней мы конфигурируем контакт TX для работы на вывод данных, а RX – для работы на ввод данных. Также мы конфигурируем бит TXSTA таким образом чтобы задействовать асинхронный режим работы для UART. Затем мы настраиваем регистр состояния приема (Receive Status) и управляющий регистр (Control Register). В данной функции мы также делаем активным бит Single Receive. Для настройки необходимой скорости передачи мы используем регистр SPBRG.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <xc.h> #include <pic16f886.h> #include <math.h> #define SBIT_TXEN 5 #define SBIT_SPEN 7 #define SBIT_CREN 4 void putch(char data) { while (!TXIF); TXREG = data; } void serialBegin(unsigned int baudRate, long freq) { TRISC = 0x80; // Configure Rx pin as input and Tx as output TXSTA = (1 << SBIT_TXEN); // Asynchronous mode, 8-bit data & enable transmitter RCSTA = (1 << SBIT_SPEN) | (1 << SBIT_CREN); // Enable Serial Port and 8-bit continuous receive unsigned int val = round((freq / (long) (64UL * baudRate)) - 1); SPBRG = val; // calculated baud rate } |
Библиотека для работы с инфракрасной связью (IR library)
Данная библиотека также состоит у нас из двух файлов: IRlib.h и IRlib.c. В файле IRlib.h мы объявляем все необходимые функции и структуры. А в файле IRlib.c мы программируем все необходимые нам функции. Начинаем мы этот процесс с указания порта и контакта, к которому подключен инфракрасный датчик. Далее записываем макрос для переполнения timer0, этот флаг будет устанавливаться в 1 каждый раз при срабатывании прерывания. Мы будем определять длительность импульса с помощью этого флага. Также мы используем структуру, в которой будут храниться все поступающие данные от пульта ДУ.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#ifndef __IR_LIB #define __IR_LIB #define IR_SENSOR_PORT TRISCbits.TRISC4 // IR Sensor Port #define IR_SENSOR_PIN PORTCbits.RC4 // IR sensor Pin #define TIMER0_OVERFLOW_INTERRUPT_FLAG INTCONbits.T0IF // 1 = TMR0 register has overflowed (must be cleared in software) 0 = TMR0 register did not overflow #define OUTPUT 0 #define INPUT 1 typedef struct ir_data_struct { int first_start_pulse; int seccound_verification_puls; int ir_received_data; int ir_raw_data; } ir_strucet_t; // structure to hold IR data ir_strucet_t ir_str_t; // structure variable /*Function prototypes */ void enable_timer0(); void protocol_check(); int read_data(); int read_data(); int ir_result(char debug_info); void ir_init(); #endif |
Первым делом в файле IRlib.c мы подключаем все необходимые библиотеки.
1 2 3 4 |
#include "IRlib.h" #include <stdio.h> #include <xc.h> #include <pic16f886.h> |
Затем с помощью макроса _XTAL_FREQ укажем тактовую частоту микроконтроллера – это необходимо для корректного функционирования функций задержки в программе.
1 |
#define _XTAL_FREQ 16000000 |
В функции ir_init() мы просто конфигурируем режим работы контакта, к которому подключен инфракрасный приемник, на ввод данных.
1 2 3 |
void ir_init() { IR_SENSOR_PORT = INPUT; } |
Далее у нас идет функция ir_result(char debug_info), которая возвращает результат приема инфракрасных сигналов. Внутри данной функции вызывается функция protocol_check(). Мы уже знаем, что за первым стартовым импульсом длительностью 9 мс следует инвертированный импульс длительностью 4.5 мс. Функция protocol_check() проверяет наличие этих двух импульсов. Затем мы проверяем значение счетчика таймера (timer counter value), если оно true, то вызывается функция read_data(), внутри которой мы формируем двоичные данные из принятых импульсов и записываем их потом в нашу структуру.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
int ir_result(char debug_info) { protocol_check(); // call protocol_check function /*inside the protocol check function the timer counter checks the pulse width if it is greater than 141 and less than 145 the statement is true. you can do this calculation by taking the datasheet as reference*/ if (ir_str_t.first_start_pulse >= 141 && ir_str_t.first_start_pulse <= 145) { if (ir_str_t.seccound_verification_puls >= 68 && ir_str_t.seccound_verification_puls <= 73) { read_data(); //call the function ir_str_t.ir_received_data = read_data(); if (debug_info) { printf("Received Data: 0x%x startPuls %d secoundPuls %d \n", ir_str_t.ir_received_data, ir_str_t.first_start_pulse, ir_str_t.seccound_verification_puls); } return ir_str_t.ir_received_data; } } return ir_str_t.ir_received_data; } The protocol_check() function. void protocol_check() { ir_str_t.first_start_pulse = 0; // remove all data ir_str_t.seccound_verification_puls = 0; // remove all data while (IR_SENSOR_PIN == 0) { if (TIMER0_OVERFLOW_INTERRUPT_FLAG) { TIMER0_OVERFLOW_INTERRUPT_FLAG = 0; ir_str_t.first_start_pulse++; if (ir_str_t.first_start_pulse >= 200) { break; } } } while (IR_SENSOR_PIN == 1) { if (TIMER0_OVERFLOW_INTERRUPT_FLAG) { TIMER0_OVERFLOW_INTERRUPT_FLAG = 0; ir_str_t.seccound_verification_puls++; if (ir_str_t.seccound_verification_puls >= 150) { break; } } } } The read_data() function int read_data() { ir_str_t.ir_raw_data = 0; for (char j = 0; j < 16; j++) { // the data coming from the sensor is 16 bit long so we need a for loop while (!IR_SENSOR_PIN) ; //Wait until PORT goes HIGH __delay_us(700); if (IR_SENSOR_PIN == 0) { ir_str_t.ir_raw_data &= ~(1 << (15 - j)); //Clear bit (7-b) } else { ir_str_t.ir_raw_data |= (1 << (15 - j)); //Set bit (7-b) while (IR_SENSOR_PIN) ; } //Wait until PORT goes LOW } return ir_str_t.ir_raw_data; } |
Тестирование работы проекта
Для тестирования работы проекта его автор использовал аппаратное обеспечение, показанное на рисунке ниже. Как видно из этого рисунка, он подключил FTDI и осциллограф к компьютеру по USB. FTDI модуль используется для вывода информации в окно монитора последовательной связи.
После загрузки кода программы в микроконтроллер с помощью программатора и открытия окна монитора последовательной связи в нем можно будет увидеть выходные данные, которые можно использовать для отладки программы. Пример этих данных показан на следующем рисунке.
Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
/* * File: main.c * Author: Debashis * * Created on January 25, 2021, 4:11 PM */ #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator: High-speed crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN)#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config MCLRE = OFF // RE3/MCLR pin function select bit (RE3/MCLR pin function is MCLR) #pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled) #pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled) #pragma config BOREN = ON // Brown Out Reset Selection bits (BOR enabled) #pragma config IESO = OFF // Internal External Switchover bit (Internal/External Switchover mode is enabled) #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled) #pragma config LVP = OFF // Low Voltage Programming Enable bit (RB3/PGM pin has PGM function, low voltage programming enabled) // CONFIG2 #pragma config BOR4V = BOR40V // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V) #pragma config WRT = OFF // Flash Program Memory Self Write Enable bits (Write protection off) #define _XTAL_FREQ 16000000 // 16mhz crystal oscillator #include <xc.h> // XC library has all the necessary functions and definations. #include <stdio.h> // We have Included the stdio library because we are using printf for serial. #include <pic16f886.h> #include "uart.h" // библиотека последовательной связи (UART Library) #include "IRlib.h" // библиотека для работы с инфракрасной связью (IR library) #define GLOBAL_INTERRUPT INTCONbits.GIE //1 = Enables all unmasked interrupts (разрешаем все прерывания) 0 = Disables all interrupts (отключаем все прерывания) #define PERIPHERAL_INTERRUPT INTCONbits.PEIE //1 = Enables all unmasked peripheral interrupts 0 = Disables all peripheral interrupts #define TIMER0_OVERFLOW_INTERRUPT INTCONbits.T0IE //1 = Enables the Timer0 interrupt 0 = Disables the Timer0 interrupt #define PRESCALER_ASSIGNMENT OPTION_REGbits.PSA // 1 = Prescaler is assigned to the WDT 0 = Prescaler is assigned to the Timer0 module #define ENABLE 1 #define DISABLE 0 #define OUTPUT 0 #define INPUT 1 #define TO_WDT 1 #define ENABLE_LOG 1 #define DISABLE_LOG 0 /* Enables all required Interrupts (разрешаем все необходимые нам прерывания) прерывание переполнения Timer0 используется для измерения длительности импульсов от датчика TSOP */ void INTERRUPTS_Init() { GLOBAL_INTERRUPT = ENABLE; // глобальное разрешение прерываний PERIPHERAL_INTERRUPT = ENABLE; // разрешение прерываний от периферийных устройств TIMER0_OVERFLOW_INTERRUPT = ENABLE; // разрешаем прерывание переполнения Timer0 } /* The prescaler inside the PIC16f886 microcontroller * is either assigned to Watchdog timer or its assigned * to the TIMER0: For this code we have set it to WDT * */ void assign_prescaler() { PRESCALER_ASSIGNMENT = TO_WDT; } void init() { serialBegin(9600, _XTAL_FREQ); // последовательная связь со скоростью 9600 бод INTERRUPTS_Init(); // разрешаем все прерывания assign_prescaler(); // assign prescaler } void main(void) { init(); // we call the init() function ir_init(); // we call the iR_init function. printf("System Initialized \n"); // First print statement for debugging while (1) { if (IR_SENSOR_PIN == 0) { ir_result(ENABLE_LOG); } } return; } |