На этом этапе построения часов, мы познакомимся с таким весьма замечательным модулем-датчиком, как поворотный энкодер, модель KY-040.

        Основной функционал его состоит в том что этот модуль является механическим  датчиком угла поворота, состоит из центральной оси(вала), и кнопки, механически сконструированной на этой самой центральной оси. При вращении вала, угол поворота преобразуется в два электрических сигнала, сдвинутых на 90 градусов относительно друг друга. Модуль имеет три вывода STEP, DIR и SW. Именно на выводах STEP и DIR появляются вышеописанные сигналы. Выход SW даёт сигнал при нажатии на центральную ось. На фото ниже, можно видеть, как может выглядеть модуль энкодера KY-040:

 

 

         Итак, подключить модуль энкодера KY-040 можно по нижеследующей схеме. На схеме также показан дисплей на базе драйвера MAX7219. Об этом чуть ниже.

 

 

         Кроме схемы подключения не будет лишним привести диаграмму сигналов, снимаемых с выходов STEP и DIR.

 

       

        Из диаграммы видно что, каждый раз когда сигнал А(контакт STEP энкодера) переходит от высокого уровня к низкому, считывается состояние сигнала Б(контакт DIR энкодера). Если сигнал Б даёт высокий уровень сигнала, это означает что вращение энкодера происходит по часовой стрелке. Если сигнал Б даёт низкий уровень сигнала при переходе сигнала А из низкого в высокий уровень, то это означает что вращение энкодера происходит против часовой стрелки. Считывая оба этих сигнала в программе, можно определить направление вращения, также при подсчете импульсов сигнала Б, можно инкрементировать либо декрементировать счетчик импульсов.

        Существует конструкция-контейнер, которая позволяет корректно обработать сигналы поступающие с энкодера(вращение центральной оси), её можно встраивать в скетч а также расширять её функционал, добавляя новые условия(оставляя саму конструкцию неизменной конечно же).

 

/*Константы для пинов подключения энкодера*/
#define DIR 4
#define STEP 3

//Counter - переменная для хранения значения счетчика
static long Counter = 0;

//Временные переменные для хранения уровней сигналов энкодера
unsigned char encoder_A, encoder_B, encoder_A_prev;

void setup()
{

}

void loop()
{
  //Считываем значения выходов энкодера
  //И сохраняем их в переменных
  encoder_A = digitalRead(STEP);
  encoder_B = digitalRead(DIR);

  //Если уровень сигнала А низкий,
  //и в предыдущем цикле он был высокий
  if(!encoder_A && encoder_A_prev)
  {
    //Если уровень сигнала В высокий
    if(encoder_B)
    {
      //Значит вращение происходит по часовой стрелке
      //Наше условие:
      //Если значение счетчика больше или равно максимальному числу
      //Обнулить значение счетчика
      if(Counter >= 99999999)
        Counter = 0;
      //Иначе инкрементировать при каждом щелчке на единицу
      else
        Counter ++;
    }
    //Если уровень сигнала В низкий
    else
    {
      //Значит вращение происходит против часовой стрелки
      //Если значение счетчика меньше или равно нулю
      //проинициализировать значение максимальным числом
      if(Counter <= 0)
        Counter = 99999999;
      //Иначе декрементировать при каждом щелчке на единицу
      else
        Counter --;
    }
  }
  //Обязательно нужно сохранить состояние текущего уровня сигнала А
  //для использования этого значения в следующем цикле сканирования программы
  encoder_A_prev = encoder_A;
}

 

        Также для работы с энкодером, нам понадобится конструкция-контейнер, корректно обрабатывающая нажатие центральной оси. Так как функционал центральной оси при нажатии на неё не отличается от обычной кнопки, то в нём конечно же присутствует дребезг контактов. Для обработки дребезга при нажатии на центральную ось, используем обработчик дребезга - функцию Debounce().

 

/*
  Константа для пина подключения
  центральной оси энкодера
*/
#define SW 2

bool ZAxisCurr = false;
bool ZAxisPrev = false;

/*Переменная для получения состояния нажатий*/
bool Latch = false;

void setup()
{
  pinMode(SW, INPUT);
}

void loop()
{
  /*
    Проверка нажатия центральной оси энкодера
    и фильтр дребезга контактов
  */
  ZAxisCurr = Debounce(SW, ZAxisPrev);
  if(ZAxisPrev == true && ZAxisCurr == false)
  {
    if(Latch)
      Latch = false;
    else
      Latch = true;
  }
  ZAxisPrev = ZAxisCurr;
}

/*Функция обработчик дребезга*/
boolean Debounce(int ScanPort, boolean last)
{
  boolean current = digitalRead(ScanPort);
  if(current != last)
  {
    delay(5);
    current = digitalRead(ScanPort);
  }
  return current;
}

 

        Используем это в нашем тестовом ознакомительном примере. Нажатием на центральную ось будем устанавливать режим отображения посчитанных импульсов на дисплее MAX7219. Режима будет два, нажатие - режим "механического" счетчика с заполнением нулями свободных знакомест слева. Еще нажатие - режим электронного счетчика, с отображением только подсчитанных импульсов. При вращении центральной оси по часовой стрелке, значение на дисплее будет с каждым щелчком увеличиваться на единицу. При вращении против часовой стрелки, значение счетчика будет с каждым щелчком уменьшаться на единицу. Максимальное число которое можно будет накрутить - 99999999, большее число дисплей MAX7219 отобразить не сможет. Да и не нужно...

 

/*Подключаем библиотеку LedControl.h*/
#include "LedControl.h"

/*Константы для пинов подключения дисплея*/
#define DATA_IN 13
#define CLK 12
#define LOAD_CS 11
#define NUM_DEV 1
#define DISP_ADDR 0

/*Константы для пинов подключения энкодера*/
#define DIR 4
#define STEP 3
#define SW 2

bool ZAxisCurr = false;
bool ZAxisPrev = false;

bool Latch = false;

//Counter - переменная для хранения значения счетчика
static long Counter = 0;

//Временные переменные для хранения уровней сигналов энкодера
unsigned char encoder_A, encoder_B, encoder_A_prev;

/*
  Подключение дисплея MAX7219 производим в
  соответсвии с нижеследующей таблицей
  Arduino [Pin 13] -> Max7219 [DataIn]
  Arduino [Pin 12] -> Max7219 [CLK]
  Arduino [Pin 11] -> Max7219 [LOAD]
*/
LedControl Display = LedControl(DATA_IN, CLK, LOAD_CS, NUM_DEV);

void setup()
{
  /*Выводим дисплей из спящего режима*/
  Display.shutdown(DISP_ADDR, false);
  /*Устанавливаем яркость свечения сегментов*/
  /*Доступные значения от 0 до 15*/
  Display.setIntensity(DISP_ADDR, 8);
  /*Очищаем дисплей*/
  Display.clearDisplay(DISP_ADDR);

  pinMode(SW, INPUT);
}

void loop()
{
  //Проверка нажатия центральной оси энкодера
  ZAxisCurr = Debounce(SW, ZAxisPrev);
  if(ZAxisPrev == true && ZAxisCurr == false)
  {
    if(Latch)
      Latch = false;
    else
      Latch = true;
  }
  ZAxisPrev = ZAxisCurr;

  //Считываем значения выходов энкодера
  //И сохраняем их в переменных
  encoder_A = digitalRead(STEP);
  encoder_B = digitalRead(DIR);

  //Если уровень сигнала А низкий,
  //и в предыдущем цикле он был высокий
  if(!encoder_A && encoder_A_prev)
  {
    //Если уровень сигнала В высокий
    if(encoder_B)
    {
      //Значит вращение происходит по часовой стрелке
      //Если значение счетчика больше или равно максимальному числу
      //Обнулить значение счетчика
      if(Counter >= 99999999)
        Counter = 0;
      //Иначе инкрементировать при каждом щелчке на единицу
      else
        Counter ++;
    }
    //Если уровень сигнала В низкий
    else
    {
      //Значит вращение происходит против часовой стрелки
      //Если значение счетчика меньше или равно нулю
      //проинициализировать значение максимальным числом
      if(Counter <= 0)
        Counter = 99999999;
      //Иначе декрементировать при каждом щелчке на единицу
      else
        Counter --;
    }
  }
  encoder_A_prev = encoder_A;

  //Часть программы которая заполняет разряды
  //семисегментного дисплея значением счетчика
  long intCounter = Counter;
  int divCounter;

  if(!Latch)
  {
    for(int Rank = 0; Rank < 8; Rank ++)
    {
      divCounter = intCounter % 10;
      intCounter = intCounter / 10;
      Display.setDigit(DISP_ADDR, Rank, divCounter, false);
    }
  }
  else
  {
    if(Counter == 0)
    {
      Display.setDigit(DISP_ADDR, 0, 0, false);
      for(int Rank = 1; Rank < 8; Rank ++)
        Display.setRow(DISP_ADDR, Rank, 0x00);
    }
    else
    {
      for(int Rank = 0; Rank < 8; Rank ++)
      {
        divCounter = intCounter % 10;
        intCounter = intCounter / 10;
        if(intCounter == 0 && divCounter == 0)
          Display.setRow(DISP_ADDR, Rank, 0x00);
        else
          Display.setDigit(DISP_ADDR, Rank, divCounter, false);
      }
    }
  }
}

/*Функция обработчик дребезга контакта центральной оси*/

boolean Debounce(int ScanPort, boolean last)
{
  boolean current = digitalRead(ScanPort);
  if(current != last)
  {
  delay(5);
  current = digitalRead(ScanPort);
  }
  return current;
}

 

         Как работает скетч, можно увидеть на фотографиях ниже:

 

 

         Также, вы можете посмотреть обзорное видео, раскрывающее суть работы этого устройства. Замечу, что видео старое, на момент съёмки еще не была реализована корректная фильтрация дребезга контакта, при нажатии центральной оси энкодера.  

 

 

         Итак, в дальнейшем, при построении часов, знания полученные из этого урока, мы используем для создания меню, при помощи которого мы будет управлять настройками наших часиков. Тема эта очень важна, ведь меню является своего рода человеко-машинным интерфейсом, посредником, в общении человека и контроллера. И нашей задачей будет, сделать его как можно качественнее и понятнее конечному пользователю. Крайне рекомендуем перед прочтением следующего шага, ознакомиться с функционалом библиотеки EEPROM и примерами работы. Ну что же, наш следующий шаг - построение меню для дисплея MAX7219, под управлением модуля энкодера KY-040.