Atmega88 + энкодер (KY-040) (Arduino IDE)

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

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

На этой странице будет рассмотрен пример подключения энкодера KY-040 к микроконтроллеру ATmega88. Для управления энкодером будут использованы входы для внешнего прерывания которое срабатывает при изменении состояния на входах PCINT0..23.

KY-040

Atmega88

Для использования  Atmega48, Atmega88 в Arduino IDE Вам необходимо собрать следующую схему:

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

За внешние прерывания отвечают несколько регистров:

Регистр PCICR — определяет какую группу входов использовать в качестве источника прерывания:

Пример использования:Группа PCIE2 отвечает за входы PCINT[23:16], PCIE1 за PCINT[14:8], а группа PCIE0 за входы PCINT[7:0].

PCICR |= (1 << PCIE0); // определяет группу входов PCIE0 PCINT0…7

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

  • Регистр PCMSK2 отвечает за выходы PCINT[23:16]

  • Регистр PCMSK1 отвечает за выходы PCINT[14:8]

  • Регистр PCMSK0 отвечает за выходы PCINT[7:0]

Установка какого-нибудь бита из PCINT0…23 разрешает соответствующему выводу работать в качестве источника.

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

Обработчики прерывания PCINTx_vect, каждый для своей группы.

Пример использования:

ISR(PCINT0_vect){ // — код — //} — при возникновении прерывания исполняется // — код — //

ISR(PCINT1_vect){ // — код — //} — при возникновении прерывания исполняется // — код — //

ISR(PCINT2_vect){ // — код — //} — при возникновении прерывания исполняется // — код — //

Энкодер KY-040 как правило выполнен в виде модуля, который состоит из небольшой платы в которую установлен энкодер и 3 подтягивающих (к +5 В) резистора номиналом 10 кОм. При вращении ручки модуля мы получаем два сигнала (A и B), которые противоположны по фазе.  Каждый раз, когда сигнал А переходит от положительного уровня к нулю, мы считываем значение выхода В. Если В в этот момент находится в положительном состоянии, значит энкодер вращается по часовой стрелке, если В нуль, то энкодер вращается против часовой стрелки. Считывая оба выхода, можно определить направление вращения, и при помощи подсчета импульсов с А выхода — угол поворота.

Чтобы безошибочно определять угол поворота и направление вращения необходимо постоянного опрашивать (считывать) состояние выходов A и B  (CLK и DT) энкодера, при этом содержимое скетча не должно влиять на период опроса. Этого можно добиться двумя путями, использовать внешние прерывания или при помощи таймера.

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

Пример кода:

//  ATMEGA88

#define DT           PD1
#define CLK          PD2

#include <avr/io.h>
#include <util/delay.h>


volatile uint8_t _prevValueAB = 0;    
volatile uint8_t _currValueAB = 0;
volatile int16_t newPosition = 0;
int position = -999;
int enc;

int main(){ 
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT17)|(1 << PCINT18); 
            
   
while(1){
  if(newPosition != position){
      position = newPosition;
      enc = enc+newPosition;newPosition=0;
    }
    _delay_ms(100);
    }}

ISR(PCINT2_vect){ 
  bool pinA = ((PIND >> DT) & 1);
  bool pinB = ((PIND >> CLK) & 1);
   _currValueAB  = (pinA << 1) | pinB;
   switch(_prevValueAB | _currValueAB){
    case 0b0001: newPosition++;break;
    case 0b0100: newPosition--;break;
  }
  _prevValueAB = _currValueAB << 2;     
  } 

Как видно из кода энкодер использует выводы PD1 и PD2 что соответствует входам PCINT17 PCINT18 (группа PCIE2), при изменении состояния на входах PD1 или PD2 срабатывает прерывание в котором происходит считывание состояния входов PD1 и PD2.

Обработчик прерывания PCINT2_vect при повороте ручки энкодера считывает логический уровень выходов энкодера CLK и DT, получая два сигнала (A и B). Переменной newPosition из обработчика прерывания передается числовое состояние энкодера, которое в свою очередь передается в переменную enc, при этом состояние переменной newPosition сбрасывается. Что дает возможность передавать состояние энкодера сразу нескольким переменным, которые ответственны за разные параметры которые регулирует энкодер.

Так как в выше показанном скетче переменная enc, коротая содержит состояние энкодера ни куда его не выводит, то ниже показан скетч который выводит переменную enc на дисплей LCD1602 оснащенным модулем на PCF8574.(более подробно — http://rcl-radio.ru/?p=116535)

//  ATMEGA88

#define DT           PD1
#define CLK          PD2

#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
  

volatile uint8_t _prevValueAB = 0;    
volatile uint8_t _currValueAB = 0;
volatile int16_t newPosition = 0;
int position = -999;
int enc;


int main(){ 
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT17)|(1 << PCINT18); 
  wire_set(12000000,100000); // тактовая частота контроллера, частота шины I2C
  lcd.setInit();
  lcd.Clear(); // очистка экрана
  lcd.led(1);  // включение и отключение подсветки экрана
            
   
while(1){
  if(newPosition != position){
      position = newPosition;
      enc = enc+newPosition;newPosition=0;
      lcd.Curs(1,4);lcd.PrintInt(enc);lcd.PrintString(" ");
    }
    _delay_ms(100);
    }}

ISR(PCINT2_vect){ 
  bool pinA = ((PIND >> DT) & 1);
  bool pinB = ((PIND >> CLK) & 1);
   _currValueAB  = (pinA << 1) | pinB;
   switch(_prevValueAB | _currValueAB){
    case 0b0001: newPosition++;break;
    case 0b0100: newPosition--;break;
  }
  _prevValueAB = _currValueAB << 2;     
  } 

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

 

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

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