Atmega88 + IR (Arduino IDE)

Микроконтроллер Atmega88 может стать отличной заменой сильно подорожавших плат Arduino Nano. Большое кол-во проектов создаваемых на платах Arduino Nano часто используют небольшое объем памяти и применять Arduino Nano в данных проектах нецелесообразно.  Atmega88 имеет 8 кБ программируемой Flash памяти, 1кБ SRAM памяти и 512 байта EEPROM.

Микроконтроллер Atmega88 поддерживается средой программирования Arduino IDE, так же большинство библиотек совместимы с этими контроллерами.

Как добавить микроконтроллер Atmega88 в среду программирования Arduino IDE и прошивать микроконтроллер можно узнать из статьи http://rcl-radio.ru/?p=113040.

В этой странице будет рассмотрен пример использования ИК пульта в проектах на базе микроконтроллера Atmega88 (Atmega8, Atmega48, Atmega168, Atmega328).

Большинство пультов используемый в бытовой аппаратере используют формат передачи данных NEC. Формат NEC, разработанный одноименной японской фирмой. Протокол основан на кодировании логических уровней длиной паузы, то есть начало каждого бита, определено импульсом длиной 562,5 мкс, а длина паузы, следующей за импульсом определяет логическое состояние.

Импульс представляет собой пачку на несущей частоте 38.222 кГц.

Лог. 0 — 562,5 мкс пакет импульсов с последующей 562,5 мкс паузой, с общим временем передачи в 1.125 мс
Лог. 1 — 562,5 мкс пакет импульсов с последующей 1.6875 мкс паузой, с общим временем передачи в 2.25 мс

Формат посылки NEC состоит из преамбулы — пакета несущей частоты длительностью 9 мс, за которой следует пауза в 4.5 мс. Далее следует пакет данных который соответствует 4 байтам. Первый байт — адрес 8 бит, второй байт инвертированный байт адреса, третий байт — команда 8 бит, четвертый инвертированный байт команды. Так же существует расширенная версия формата протокола NEC, в которой адрес имеет формат 16 бит (нет инвертированного байта адреса), а байты команды состоят из байта команды и инвертированного байта команды.

При удержании кнопки пульта, команда передается только один раз, затем передаются короткие посылки, состоящие из преамбулы длительностью 9 мс и единичного интервала. Такие посылки передаются с периодичностью 110 мс.

Для платформы Arduino имеется несколько библиотек для чтения кода кнопки ИК пульта, но при использовании микроконтроллера Atmega88 использование стандартных библиотек нецелесообразно, так они занимаю много места в памяти микроконтроллера. И на этой странице будет показан пример использования ИК датчика без применения библиотек.

Для примера воспользуемся  ИК-датчиком VS1838B, который обладает следующими характеристиками:

  • несущая частота: 38 кГц;
  • напряжение питания: 2,7 — 5,5 В;
  • потребляемый ток: 50 мкА.

ИК датчик имеет три вывода, 2 вывода это питание, третий выход сигнала.

Ниже показан пример кода для получения кода кнопок ИК пульта, так как проект не предусматривает использование UART порта, то вся информация будет выводится на дисплей LCD1602 с модулем I2C.

Для использования LCD1602_I2C Вам понадобится скачать и установить две библиотеки:

Использование нестандартных библиотек при работе с LCD1602 так же позволяют уменьшить размер занимаемой ими памяти микроконтроллера.

#define IR_IN PC3
#define PORT  PINC

#include <avr/io.h>
#include <util/delay.h>
#include <Wire_low.h>         // http://forum.rcl-radio.ru/viewtopic.php?pid=5521#p5521
#include <Lcd1602_i2c_low.h>  // http://rcl-radio.ru/wp-content/uploads/2022/03/Lcd1602_i2c_low.zip
Lcd1602_i2c_low lcd(0x27);// адрес I2C

bool data[96],st,st1,raz;
uint32_t cod,data_ir;
byte i1,i2,s;

int main(){
  wire_set(12000000,100000); // тактовая частота контроллера, частота шины I2C
  lcd.setInit();
  lcd.Clear(); // очистка экрана
  lcd.led(1);  // включение и отключение подсветки экрана
  ////////// TIMER_1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
// (12000000/((6749+1)x1)) = 562.5 mks
  OCR1A = 6749;
  TCCR1B |= (1 << WGM12);
// Prescaler 1
  TCCR1B |= (1 << CS10);
  TIMSK1 |= (1 << OCIE1A);  
  sei();
  
  
while(1){  
    lcd.Curs(0,0);
    lcd.PrintString("IR_CODE");
 if(raz==1){
    lcd.Clear();
    data_ir = IR();
    String stringVar = String(data_ir, HEX);
    char charVar[10];
    stringVar.toCharArray(charVar, 10);
    lcd.Curs(1,0);
    lcd.PrintString(charVar);
    _delay_ms(200);
    }
    
  }}

ISR(TIMER1_COMPA_vect){
     if(((PORT >> IR_IN) & 1)==0&&st==0&&raz==0){st=1;OCR1A = 6749;}
     if(st==1){s++;}
     if(s>20&&((PORT >> IR_IN) & 1)==0){st1=1;}   
     if(st1==1){data[i1]=((PORT >> IR_IN) & 1);i1++;} 
     if(i1>96){i1=0;s=0;st=0;raz=1;st1=0;TCCR1B &= ~(1 << CS10); }
     }

uint32_t IR(){
 _delay_ms(100);
 cod=0;i2=0;
 for(int ai=0;ai<96;ai++){
   if(data[ai] + data[ai+2] == 2){cod += ((uint32_t)0 << 31-i2);i2++;ai=ai+1;}
   if(data[ai] + data[ai+2] == 1){cod += ((uint32_t)1 << 31-i2);i2++;ai=ai+3;}}
 raz=0;
 TCCR1B |= (1 << CS10);OCR1A = 239;//50000 kHz
 if(cod==1||cod>0x7fffffff){cod=0xFFFFFFFF;}
 return cod;}       

Скетч использует 2508 байт (30%) памяти устройства. Всего доступно 8192 байт.
Глобальные переменные используют 126 байт (12%) динамической памяти, оставляя 898 байт для локальных переменных. Максимум: 1024 байт.

Как видно выше показанный пример кода занимает всего 2508 байт памяти уст-ва и 126 байт динамической памяти.

Основной задачей при написании скетча было стабильное и безошибочное получение кодов кнопки пульта. При этом наличие стороннего кода размещенного в цикле write() не должно влиять на работу чтения ИК датчика, так же и ИК датчик не должен влиять на исполнение стороннего кода в цикле write().

ИК датчик подключается к цифровому входу PC3, для опроса состояния входа используется таймер 1.

В скетче можно изменить некоторые параметры:

  • Вход ИК датчика:
    • #define IR_IN PC3 — вход
    • #define PORT  PINC — порт который использует вход
  • wire_set(12000000,100000) — тактовая частота контроллера, частота шины I2C

В примере использован кварцевый резонатор на 12 МГц, если Вы используете другую частоту кварцевого резонатора, то Вам так же потребуется изменить данные регистра сравнения OCR1A таймера 1, для получения точных интервалов времени.

Так например при использовании кварцевого резонатора с частотой 16 МГц регистр OCR1A имеет значение 8999.

(12000000/((6749+1)x1)) = 1777,77 Hz = 562.5 mks

(16000000/((8999+1)x1))=1777.77 Hz = 562.5 mks

Для удобства настройки таймера 1 в режиме CTC можно воспользоваться онлайн калькулятором — http://rcl-radio.ru/?p=111487, настройки регистров таймера 1 микроконтроллера Atmega88 аналогичны микроконтроллеру Atmega 328.

Прерывания таймера и опрос входа происходят с интервалом 20 мкс:

OCR1A = 239;//50000 kHz

Как только на входе PC3 появляется лог. 0 (сигнал с ИК датчика инвертирован), таймер меняет период прерывания с 20 мкс на 562,5 мкс, что соответствует временному интервалу протокола NEC. Так как пауза между импульсами данных кратна 562,5 мкс, то импульс длительностью 562,5 мкс воспринимается как единица, а пауза длительность 562,5 мкс как ноль. Лог. ноль содержит импульс и паузу которая по длительности в три раза больше длительности импульса. лог. единица содержит импульс и паузу которая равна длительности импульса.

ISR(TIMER1_COMPA_vect){ // обработчик прерываний таймера 1
    if(((PORT >> IR_IN) & 1)==0&&st==0&&raz==0){st=1;OCR1A = 6749;} // если на PC3 лог 0, меняем частоту таймера на 1777,77 Гц
    if(st==1){s++;}// счетчик временных интервалов в 562,5 мкс
    if(s>20&&((PORT >> IR_IN) & 1)==0){st1=1;} // если прошло 20 циклов прерываний, а это 11,250 мс, то попадаем в паузу межу преамбулой и началом импульсов и ждем начала импульсов (см. рис.)

   if(st1==1){data[i1]=((PORT >> IR_IN) & 1);i1++;}// если начались импульсы, то заполняем массив
    if(i1>96){i1=0;s=0;st=0;raz=1;st1=0;TCCR1B &= ~(1 << CS10); }// как только прошло 96 импульсов, то отключаем прерывания.
    }

Полученные данные записываются в массив который содержит 96 ячеек. Бит нуля кода кнопки ИК пульта записывается как 0b1000, а бит единицы как 0b10, в итоге получается массив в котором содержится 96 бит. Далее необходимо раскодировать полученные данные согласно протоколу NEC.

 for(int a=0;a<96;a++){
   if(data[a] + data[a+2] == 2){cod += ((uint32_t)0 << 31-i2);i2++;a=a+1;}
   if(data[a] + data[a+2] == 1){cod += ((uint32_t)1 << 31-i2);i2++;a=a+3;}}

Показанный выше код позволяет разобрать полученный массив (96 бит) на 32 бита (4 байта по 8 бит) согласно протоколу NEC. Например при последовательности массива 10100010, первые два бита 10, а третий бит 1, значит первые два бита лог единица (NEC), происходит сдвиг на 2 бита. После сдвига последовательность имеет вид — 100010, так как первый бит 1, а остальные 000, то это лог 0 по протоколу NEC, далее снова сдвиг, но уже на 4 бита.

После раскодирования массива переменная функция IR() содержит 32 разрядный код кнопки, который выводятся в монитор порта при нажатии кнопки пульта. Во время раскодировки массива работа таймера приостанавливается.

При удержании кнопки пульта скетч выдает код 0xFFFFFFFF

Передаем данные функции IR() переменной data_ir:

data_ir = IR();

Далее выводим на дисплей LCD1602_I2C код кнопки (переменную data_ir) пульта в шестнадцатеричном формате:

Вот небольшой пример кода который показывает работу ИК датчика:

//  ATMEGA88 12 MHz

#define IR_UP   0x33B810EF // >>>
#define IR_DOWN 0x33B8E01F // <<<
 
#define IR_IN PC3
#define PORT  PINC
 
#include <avr/io.h>
#include <util/delay.h>
#include <Wire_low.h>         // http://forum.rcl-radio.ru/viewtopic.php?pid=5521#p5521
#include <Lcd1602_i2c_low.h>  // http://rcl-radio.ru/wp-content/uploads/2022/03/Lcd1602_i2c_low.zip
Lcd1602_i2c_low lcd(0x27);// адрес I2C

unsigned long data_ir,d_ir;
bool data[96],st,st1,raz;
uint32_t cod;
byte i1,i2,s;
int reg_ir;
 
 
int main(){ 
  cli();
////////// TIMER_1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
// (12000000/((6749+1)x1)) = 562.5 mks
  OCR1A = 6749;
  TCCR1B |= (1 << WGM12);
// Prescaler 1
  TCCR1B |= (1 << CS10);
  TIMSK1 |= (1 << OCIE1A);  
  sei();
  wire_set(12000000,100000); // тактовая частота контроллера, частота шины I2C
  lcd.setInit();
  lcd.Clear(); // очистка экрана
  lcd.led(1);  // включение и отключение подсветки экрана
 
 
while(1){
/// IR READ    
 if(raz==1){d_ir=IR();}  

   if(d_ir==IR_UP){reg_ir++;d_ir=0;}
   if(d_ir==IR_DOWN){reg_ir--;d_ir=0;}
   
   lcd.Curs(0,0);lcd.PrintInt(reg_ir);lcd.PrintString(" ");    
}}// end while
 
ISR(TIMER1_COMPA_vect){
     if(((PORT >> IR_IN) & 1)==0&&st==0&&raz==0){st=1;OCR1A = 6749;}
     if(st==1){s++;}
     if(s>20&&((PORT >> IR_IN) & 1)==0){st1=1;}   
     if(st1==1){data[i1]=((PORT >> IR_IN) & 1);i1++;} 
     if(i1>96){i1=0;s=0;st=0;raz=1;st1=0;TCCR1B &= ~(1 << CS10); }
     }
 
uint32_t IR(){
 _delay_ms(100);
 cod=0;i2=0;
 for(int ai=0;ai<96;ai++){
   if(data[ai] + data[ai+2] == 2){cod += ((uint32_t)0 << 31-i2);i2++;ai=ai+1;}
   if(data[ai] + data[ai+2] == 1){cod += ((uint32_t)1 << 31-i2);i2++;ai=ai+3;}}
 raz=0;
 TCCR1B |= (1 << CS10);OCR1A = 239;//50000 kHz
 if(cod==1||cod>0x7fffffff){cod=0xFFFFFFFF;}
 return cod;}

В скетче прописываем коды кнопок:

  • #define IR_UP   0x33B810EF // >>>
  • #define IR_DOWN 0x33B8E01F // <<<

После загрузки скетча на LCD1602 выводится состояние переменной reg_ir, при нажатии на кнопки пульта указанные в скетче переменная reg_ir прибавляется или убавляется на единицу.

После нажатии кнопки пульта исполняется код, например увеличить переменную reg_ir на единицу, а далее переменная d_ir которая содержит код кнопки обнуляется, что бы повторно не исполнять код.

 if(d_ir==IR_UP){reg_ir++;d_ir=0;}

 

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

Войти с помощью: