Энкодер и шкала


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

  • Энкодер механический инкрементальный EC12E24404A6
  • Шкала линейная 10-разрядная DC-10GWA

Энкодер по-научному он называется «преобразователь угол-код», или сокращённо ПУК (:
Название говорит само за себя: он позволяет перевести угол поворота в некий код. Абсолютные энкодеры выдают непосредственно угол, абсолютное положение, тогда как инкрементальный — определённое число щелчков на оборот и направление.

Вообще, энкодеры бывают механические, оптические или магнитные.
Внутри находятся два датчика (расположенные друг за другом над кодирующим диском), которые при вращении ручки последовательно замыкают крайние выводы на средний.
И если средний подключить к питанию, то при вращении ручки на крайних увидим следующую картину:

Здесь изображены датчики которые реагируют на «дырки» — это могут быть оптические датчики работающие на просвет или отражение, магнитные, реагирующие на намагниченность диска или контактные, размыкаемые диэлектрическим диском.

А как насчёт рассматриваемого нами экземпляра?

При вращении ручки вращается скользящий контакт сложной формы над тремя секторами, которые соединены с соответствующими выводами. Вот он и замыкает средний сектор на контактные площадки то левого, то правого вывода в нужной последовательности.

Если записать уровни напряжения как биты 2-битного числа, то последовательность двоичных кодов за один «щелчок» ручкой энкодера будет такой:

00 - 0
01 - 1
11 - 3
10 - 2

Если крутить ручку в обратную сторону, последовательность кодов тоже будет обратной. На первый взгляд, особого смысла в этой последовательности нет. А на второй взгляд, это код Грея (:
Приведённая выше последовательность кодов — это ничто иное, как числа от 0 до 3, записанные в коде Грея. То есть, после декодирования мы получим такую последовательность:

00 - 0
01 - 1
10 - 2
11 - 3

Если крутить ручку энкодера в другую сторону, получим обратную последовательность: 3, 2, 1, 0. То есть, зная последовательность чисел, поступающую с энкодера, можно легко понять, в какую сторону крутится ручка.

Рассматриваемый энкодер EC12E24404A6 имеет 24 отсчёта, то есть его выводы сменят состояние от 00 до 11 ровно 24 раза за один полный оборот ручки, а это значит, что за один щелчок ручка поворачивается на 15 градусов.

С теорией ознакомились — пора проверять на практике. Подсоединим энкодер к Arduino следующим образом:

  • средний вывод — к +5 В
  • остальные два вывода — прижаты к земле (GND) через резисторы 10 кОм и подключены к пинам 2 и 3 Arduino

При нейтральном положении ручки с энкодера будут считываться нули. Наваяем простой скетч, считывающий значения с энкодера и показывающий направление вращения. В статье на Википедии приведена функция декодирования двоичного кода Грея в обычный двоичный, которой мы и воспользуемся, лишь слегка улучшив стиль кода.

/* Пины, к которым подключен энкодер */
enum { ENC_PIN1 = 2, ENC_PIN2 = 3 };

void setup()
{
 pinMode(ENC_PIN1, INPUT);
 pinMode(ENC_PIN2, INPUT);

 Serial.begin(9600);
}

/* Функция декодирования кода Грея, взятая с Википедии.
* Принимает число в коде Грея, возвращает обычное его представление.
*/
unsigned graydecode(unsigned gray)
{
 unsigned bin;

 for (bin = 0; gray; gray >>= 1)
    bin ^= gray;

 return bin;
}

void loop()
{
 static uint8_t previous_code = 0; // предыдущий считанный код

 /* gray_code - считанное с энкодера значение
  * code - декодированное значение
  */
 uint8_t gray_code = digitalRead(ENC_PIN1) | (digitalRead(ENC_PIN2) << 1),
         code = graydecode(gray_code);

 /* Если считался нуль, значит был произведён щелчок ручкой энкодера */
 if (code == 0)
 {
   /* Если переход к нулю был из состояния 3 - ручка вращалась
    * по часовой стрелке, если из 1 - против.
    */
   if (previous_code == 3)
     Serial.println("->");
   else if (previous_code == 1)
     Serial.println("<-");
 }

 /* Сохраняем код и ждём 1 мс - вполне достаточно опрашивать энкодер
  * не более 1000 раз в секунду.
  */
 previous_code = code;
 delay(1);
}

Залейте скетч и откройте Serial monitor. Повращайте ручку энкодера и посмотрите, как скетч определяет направление вращения. Выглядеть это будет так:

Ну, покрутили ручкой, а дальше что? А дальше — покрутим ещё раз, но на сей раз визуализировав процесс при помощи 10-разрядной шкалы DC-10GWA:

Шкала представляет собой банальную сборку независимых светодиодов с катодами со стороны надписи на корпусе. Ставить 10 резисторов к шкале на макетную плату мне было жутко влом, так что я применил резисторную сборку из 9 резисторов по 470 Ом и один резистор на 510 Ом. Сборка резисторов выглядит так:

Общий провод — там, где нарисован белый ромбик. Подключаем общий провод к земле, оставшиеся 9 выводов — к анодам шкалы, а один оставшийся анод подключаем к земле отдельным резистором. Катоды шкалы подключаем последовательно к пинам 4-13 Arduino. Далее шьём такой скетч:

enum
{
 ENC_PIN1 = 2,
 ENC_PIN2 = 3,
 BAR_FIRST_PIN = 4, // первый пин, поключённый к шкале
 BAR_PINS_COUNT = 10 // число пинов (делений шкалы)
};

void setup()
{
 pinMode(ENC_PIN1, INPUT);
 pinMode(ENC_PIN2, INPUT);

 /* Конфигурируем все пины, к которым подключена шкала, на выход */
 for (int i = 0; i < BAR_PINS_COUNT; ++i)
   pinMode(BAR_FIRST_PIN + i, OUTPUT);

 Serial.begin(9600);
}

unsigned graydecode(unsigned gray)
{
 unsigned bin;

 for (bin = 0; gray; gray >>= 1)
   bin ^= gray;

 return bin;
}

void loop()
{
 static uint8_t previous_code = 0;
 static int bar_level = 0; // "уровень", который показывает шкала

 uint8_t gray_code = digitalRead(ENC_PIN1) | (digitalRead(ENC_PIN2) << 1),
         code = graydecode(gray_code);

 if (code == 0)
 {
   /* Увеличиваем или уменьшаем "уровень" шкалы, запихивая его
    * в диапазон от 0 до 10 стандартной для Arduino функцией constrain().
    */
   if (previous_code == 3)
   {
     bar_level = constrain(bar_level + 1, 0, BAR_PINS_COUNT);
     Serial.println(bar_level);
   }
   else if (previous_code == 1)
   {
     bar_level = constrain(bar_level - 1, 0, BAR_PINS_COUNT);
     Serial.println(bar_level);
   }
 }

 /* Зажигаем количество полосок на шкале, соответствующее
  * текущему "уровню", гасим остальные полоски.
  */
 for (int i = 0; i < BAR_PINS_COUNT; ++i)
   digitalWrite(BAR_FIRST_PIN + i, (i < bar_level ? HIGH : LOW));

 previous_code = code;
 delay(1);
}

Теперь в Serial monitor будет закидываться текущий «уровень», который будет отображаться на шкале:

Так как самый правый катод шкалы оказался подключен к 13 пину, при выкручивании максимального уровня дополнительно загорается светодиод L (:

Шкала занимает аж 10 выводов, а ведь хочется подключать и другие устройства — эту проблему можно решить, используя сдвиговые регистры 74HC595, о использовании которых я писал тут и тут.

Кроме зажигания шкалы, у энкодеров могут найтись и более интересные применения: регулировка звука в магнитоле, навигация в меню, и вообще можно задавать всякие дискретные величины. Ещё энкодеры применяют для получения обратной связи от вращающихся механизмов (моторов, например), обычно оптические и магнитные. Оптический можно сделать из двух фотоинтерапторов или дискретных ИК-передатчиков и приёмников, а магнитный — из датчиков Холла (цифровых или аналоговых). Механические энкодеры тут не подойдут — сотрутся контакты.

UPDATE: по однократной просьбе представителя прекрасной половины человечества выкладываю видео-демонстрацию (:


18 комментариев на «“Энкодер и шкала”»

  1. А есть идеи как определить направление вращения с помощью датчиков Холла? Нужно определять кол-во оборотов и направление вращения лебедки.

    • Ну, тут принцип будет такой же: размещаешь два датчика на небольшом расстоянии друг от друга на неподвижной части, а на лебёдке — магнит. При вращении лебёдки и прохождении магнита напротив датчиков они будут поочерёдно срабатывать, выдавая тот же код Грея. Как на цветной схеме вначале статьи, только вместо «дырки» на вращающемся диске — магнит.
      Может, следующую статью посвятить датчикам Холла? (:

    • А какое максимальное расстояние между датчиком и магнитом?
      Да, статья про датчики Холла была бы интересна)

    • А какое максимальное расстояние между датчиком и магнитом?

      Этого я пока не знаю, но предполагаю, что около сантиметра или даже больше. Как с датчиками поиграю — будет ясно (:

    • Буду с нетерпением ждать статью:)
      Расстояние важно так как лебедка достаточно быстрая ( около 100об\мин) и мощная (около 250тонн). Не хотелось бы чтобы при работе разнесло конструкцию из датчиков)

  2. Мне не нравятся такие схемы, как в этом топике и поэтому я создал схему в Fritzing.
    Энекодер в Fritzing
    «Энекодер в Fritzing» на Яндекс.Фотках

    За место энкодера тут потенциометр!!! Просто в стандартной библиотеке элементов нет энкодера, поэтому изобразил так. Надо считать, что энкодер расположен ножками к нам.

    • Вообще-то, в топике вообще нет ни одной схемы, только фотки. А в этом Фрице сглаживание линий нихт арбайтен лесенка гут? (:

    • Извиняюсь) Вы, конечно, правы, что схем нет, а только фотки 🙂
      Я сам этой программой пользовался один раз и не нашёл там как в макетной плате выровнять провода. В принципиальной схеме и печатной плате есть автотрассировка для выравнивания соединений.

    • Да не, я к тому, что странно это — в 21 веке есть ещё программы, которые не используют сглаживание линий, и в результате возникает «лесенка», алгоритмы сглаживания-то уже десятки раз описаны (:

    • Возможно, где-то оно есть, просто я не знаю как включать ^^
      К другим статьям тоже буду делать такие схемы — там разберусь как включить)

    • Крутим в одну сторону — 0, 1, 2, 3
      Крутим в другую — 3, 2, 1, 0
      Я говорю сейчас о кодах Грея (не Саши, а Франка). 2, 3, 1, 0 — это если читать числа, как есть. А в коде Грея это 3, 2, 1, 0. Если что-то не понятно, почитайте статью о коде Грея по приведённой в статье ссылке.

    • Да, про декодирование упустил.
      Только в нем вообще нет никакого смысла.
      Важна только последовательность смены кодов.
      А преобразования — это лишние телодвижения.

  3. С вашего позволения свой вариантик простого кода выложу. Может кому полезно будет.

    /*Простой логический код на энкодер от Peter=))*/
    
    
    int n;
    
    void setup()
    {
     pinMode(2, INPUT);
     pinMode(3, INPUT);
     
     Serial.begin(9600);
    }
    
    void loop()
    {
    
    
    if (digitalRead(3)==HIGH) // В одну сторону
    {
    while(digitalRead(3)==HIGH);
    while(digitalRead(2)==HIGH);
    
    delayMicroseconds(10);
     
    n=n-1;
    Serial.println(n);
    }
    
    
    if (digitalRead(2)==HIGH) // В другую
    {
    while(digitalRead(2)==HIGH);
    while(digitalRead(3)==HIGH);
    
    delayMicroseconds(10);
     
    n=n+1;
    Serial.println(n);
    }
    }
    
    
    
    
    
    
    
    
    
    • А оно у Вас работает? Именно в таком виде???
      Мне просто интересно, что Вы хотели сказать вот этим:

      while(digitalRead(3)==HIGH);
      while(digitalRead(2)==HIGH);

      Что оно должно делать?
      Это задержка до смены уровня?
      А если энкодер остановлен и уровень не сменится?
      А если мне надо делать еще что-нибудь, кроме обслуживания единственного энкодера?

  4. Это конечно же тестовый код…

    Да он просто ждет смены уровня.

    while(digitalRead(3)==HIGH);
    while(digitalRead(2)==HIGH); 

    Только единственное, на среднюю ногу энкодора я подавал HIGH.

    • Но вопрос всё тот же:
      А если энкодер остановлен и уровень не сменится?
      Программа-то зависнет. Намертво.
      Так код писать просто нельзя.

    • Согласен. Этот код я написал, чтобы понять насколько быстро можно вращать энкодер, чтобы данные были четкими, т.к. коды выше не могут похвастаться стабильной работой при быстром вращении энкодера. Чтобы прога не «висела», то можно сделать не пустой цикл, а таймер с выходом… Это просто демо для проверки энкодера, идея так скажем… Но критику тем не менее принимаю, и согласен, что если нужна работа программы во время вращения энкодера, и защитится от зависания, код надо изменить.

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

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Arduino Bluetooth CraftDuino DIY Google IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение