Подключение инкрементального энкодера. Подключение инкрементального энкодера к микроконтроллеру

Для Windows Phone 23.06.2019
Для Windows Phone

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

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

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

Если осциллограммы как применение последовательности задач логических нулей и единиц, то они будут иметь такой вид:

Возьмем обычный энкодер, у которого есть дребезг контактов. Зона дребезга:

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

Аппаратный способ – это подключение серии конденсаторов частотника, триггеров Шмитта, как указано на схеме панели управления:

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

Функция серии Get position vfd возвращает значение энкодера. Данная фукнция нужна для получения количества импульсов, которые считал энкодер. Функция set Position vfd нужна для загрузки значения, с которого энкодер начнет свой счет.

Функция tick должна быть рассмотрена подробнее. Переменные этой функции sig1 и sig2 записывают состояние векторного pin, к которой подключен энкодер. Дальше эти pin записываются в переменную thisState vfd, которая является текущим состоянием энкодера. Если текущее состояние энкодера не равно предыдущему, то вычисляются новые направления счета и количество импульсов мощности сохраняется в переменной Position. Когда энкодер вернется в свое начальное векторное положение, произойдет сдвиг вправо на два разряда, и новое значение управления нужно записать в переменную PositionExt. Данная переменная нужна для сохранения серии результатов задач, которые будут иметь применение в основной программе.

Счет

Проанализировав состояние энкодера при вращении влево и вправо, составляем таблицу:

Его начальное положение 1-1. При повороте вправо произошел щелчок, единица стала логическим нулем. Новое значение this State vfd равно 01. Согласно команды данный результат суммируется со значением переменной Position.

Из-за того, что произошел дребезг, позиция стала 11, после перерасчета порядковый номер стал 7. После того, как дребезг закончился, нужно фиксировать новое положение 01 и к предыдущему нулю добавляется единица. При повороте энкодера произошел один щелчок, и значение переменной Position стало единицей.

Происходит второй щелчок при повороте энкодера направо, и вместо позиции 01 мы имеем позицию 00. После того, как весь дребезг закончится, на выходе управления также имеем значение единицы. При четвертом щелчке, когда позиция с 10 стала 11, мы имеем значение 6. После окончания дребезга остается 6.

В некоторых энкодерах имеет применение кнопка панели. При ее нажатии и отпускании тоже будет дребезг контактов, нужно применить библиотеку Bounce. Функции этой библиотеки нужны для задания pin, к которому будет подключена кнопка, задач времени задержки в миллисекундах. Если произошло нажатие на кнопку, то функция мощности (кВт) возвращает векторное значение true, если нет, то false vfd.

Принципиальная схема подключения энкодера к преобразователю частоты

В станкостроении энкодеры широко применяются для преобразователей частоты асинхронных двигателей. Они монтируются как датчики обратной связи по своей скорости. Такие энкодеры имеют большую дискретность от 100 импульсов на оборот до 1 млн импульсов на оборот. У этой марки дискретность равна 500 имп. на оборот.

Энкодеры подразделяются на виды задач по . Они бывают абсолютными и инкрементальными. Наш энкодер выполняет обычную функцию – выдает сигнал дифференцирования при отключении мощности питания, и ее подачи снова. Раннее состояние не сохраняется.

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

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

Рассмотрим энкодеры синуса и косинуса. Они выдают выходной сигнал косинуса или синуса. Далее, с помощью устройства интерполятора мощности образуют из них импульсы. Сигналы такого вида можно изменять в размерах. Питание энкодера осуществляется от напряжения 5 вольт.

Сигнал «А» — это сигнал импульса прямого типа. Количество импульсов с этого сигнала приходит на каждом обороте. Оно равно 500 (дискретность датчика).

Сигнал «В» — тоже прямой сигнал импульса. С него на каждом обороте поступает число импульсов по дискретности датчика, который смещен от канала «А» на 90 градусов (500).

Сигнал «R» — это сигнал метки «нуль». С одного оборота датчика получается один импульс.

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

Подключение

Подключение простое. Подсоединяем напряжение 5 вольт на выходы энкодера. У нас раскладка: провод коричневого цвета – 0 В, белого цвета — +5 В, розовый, зеленый и красный – А, В, R.

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

В нашем случае мы не будем применять прерывания, потому что мы работаем с 4-мя датчиками, они эксплуатируются одновременно. Если применять схему прерываний, наверняка возникнет ситуация потери импульсов. У нас эта проблема решается путем установления значка наличия движения. А мы рассматривали эксплуатацию энкодеров промышленного назначения.

Работа счетчика импульсов на основе модуля энкодера

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

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

Для примера накрутим 120 импульсов. Теперь скручиваем обратно, вращая ручку против часовой стрелки. Центральная ось энкодера работает как кнопка. Она очищает от нулей свободные разряды индикатора. У кнопки есть небольшой дребезг контактов, поэтому выключение и включение происходит не сразу. Программным путем, дребезг устраняется. Это основа работы с модулем энкодера.

Энкодер это всего лишь цифровой датчик угла поворота, не более того.

Энкодеры бывают абсолютные — сразу выдающие двоичный код угла и инкрементальные, дающие лишь указание на направление и частоту вращения, а контроллер, посчитав импульсы и зная число импульсов на оборот, сам определит положение.

Если с абсолютным энкодером все просто, то с инкрементальным бывают сложности. Как его обрабатывать?

С Энкодера выходят два сигнала А и В, сдвинутых на 90 градусов по фазе, выглядит это так:

В оптическом же может быть два фонаря и два фотодиода, святящие через диск с прорезями (шариковая мышка, ага. Оно самое).

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


Оптический подключается в зависимости от типа оптодатчика, обычно там стоит два с общим анодом.

Обычно, все пытаются работать с ними через прерывания INT, но этот метод так себе. Проблема тут в дребезге — механические контакты, особенно после длительного пользования, начинают давать сбои и ложные импульсы в момент переключения. А прерывание на эти ложные импульсы все равно сработает и посчитает что нибудь не то.

Метод прост:
Подставим нули и единички, в соответствии с уровнем сигнала и запишем последовательность кода:


A:0 0 1 1 0 0 1 1 0 0 1 1 0
B:1 0 0 1 1 0 0 1 1 0 0 1 1

Если A и B идут на одни порт контроллера (например на A=PB0 B=PB1), то при вращении энкодера у нас возникает меняющийся код:

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

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

Выжал меньше килогерца. Т.е. опрашивать надо примерно 1000 раз в секунду. Можно даже реже, будет надежней в плане возможного дребезга. Сейчас, кстати, дребезга почти нет, но далеко не факт что его не будет потом, когда девайсина разболтается.

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

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 // Эту задачу надо запускать каждую миллисекунду. // EncState глобальная переменная u08 -- предыдущее состояние энкодера // EncData глобальная переменная u16 -- счетный регистр энкодера void EncoderScan(void ) { u08 New; New = PINB & 0x03 ; // Берем текущее значение // И сравниваем со старым // Смотря в какую сторону оно поменялось -- увеличиваем // Или уменьшаем счетный регистр switch (EncState) { case 2 : { if (New == 3 ) EncData++; if (New == 0 ) EncData--; break ; } case 0 : { if (New == 2 ) EncData++; if (New == 1 ) EncData--; break ; } case 1 : { if (New == 0 ) EncData++; if (New == 3 ) EncData--; break ; } case 3 : { if (New == 1 ) EncData++; if (New == 2 ) EncData--; break ; } } EncState = New; // Записываем новое значение // Предыдущего состояния SetTimerTask(EncoderScan, 1 ) ; // Перезапускаем задачу через таймер диспетчера }

// Эту задачу надо запускать каждую миллисекунду. // EncState глобальная переменная u08 -- предыдущее состояние энкодера // EncData глобальная переменная u16 -- счетный регистр энкодера void EncoderScan(void) { u08 New; New = PINB & 0x03; // Берем текущее значение // И сравниваем со старым // Смотря в какую сторону оно поменялось -- увеличиваем // Или уменьшаем счетный регистр switch(EncState) { case 2: { if(New == 3) EncData++; if(New == 0) EncData--; break; } case 0: { if(New == 2) EncData++; if(New == 1) EncData--; break; } case 1: { if(New == 0) EncData++; if(New == 3) EncData--; break; } case 3: { if(New == 1) EncData++; if(New == 2) EncData--; break; } } EncState = New; // Записываем новое значение // Предыдущего состояния SetTimerTask(EncoderScan,1); // Перезапускаем задачу через таймер диспетчера }

Почему я под счетчик завел такую большую переменную? Целых два байта? Да все дело в том, что у моего энкодера, кроме импульсов есть еще тактильные щелчки. 24 импульса и 24 щелчка на оборот. А по моей логике, на один импульс приходится четыре смены состояния, т.е. полный период 3201_3201_3201 и один щелчок дает 4ре деления, что некрасиво. Поэтому я считаю до 1024, а потом делю сдвигом на четыре. Получаем на выходе один щелочок — один тик.

Скоростной опрос на прерываниях
Но это механические, с ними можно простым опросом обойтись — частота импульсов позволяет. А бывают еще и высокоскоростные энкодеры. Дающие несколько тысяч импульсов на оборот, либо работающие на приводах и вращающиеся очень быстро. Что с ними делать?

Ускорять опрос занятие тупиковое. Но нас спасает то, что у таких энкодеров, как правило, есть уже свои схемы подавления дребезгов и неопределенностей, так что на выходе у них четкий прямоугольный сигнал (правда и стоят они совершенно негуманно. От 5000р и до нескольких сотен тысяч. А что ты хотел — промышленное оборудование дешевым не бывает).

Так что без проблем можно применять прерывания. И тогда все упрощается неимоверно. Настраиваем всего одно прерывание по внешнему сигналу. Например, INT0 настраиваем так, чтобы сработка шла по восходящему фронту. И подаем на INT0 канал А.


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

А в обработчике прерывания INT0 щупаем вторым выводом канал В. И дальше все элементарно!

Если там высокий уровень — делаем +1, если низкий -1 нашему счетному регистру. Кода на три строчки, мне даже писать его лень.

Конечно, можно такой метод прикрутить и на механический энкодер. Но тут надо будет заблокировать прерывания INT0 на несколько миллисекунд. И НИ В КОЕМ СЛУЧАЕ нельзя делать это в обработчике.

Алгоритм прерывания с антидребезгом будет выглядеть так:

  • Зашли в обработчик INT0
  • Пощупали второй канал
  • +1 или -1
  • Запретили локально INT0
  • Поставили на таймер событие разрешающее INT0 через несколько миллисекунд
  • Вышли из обработчика

Сложно? Нет, не сложно. Но зачем? Проще сделать банальный опрос, как указано выше и не зависеть от выводов прерываний. Впрочем, хозяин барин.

Давно хотел приспособить к ноуту регулятор громкости, сделанный из энкодера . Подключать этот регулятор нужно будет к USB, чтобы все было «по-взрослому» (да и по-другому никак внешнее устройство к ноуту не подключишь). Крутим энкодер влево - громкость должна уменьшаться, вправо - должна увеличиваться. Жмем вниз ручку энкодера - запускаем какую-нибудь полезную программу, или переключаемся на регулирование тембра.

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

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

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

Чтобы заработал регулятор громкости, нужно решить, как минимум, три инженерные задачи:

Шаг 1 . Создание низкоскоростного USB-устройства на макетке.
Шаг 2 . Подключить к этому USB-устройству энкодер, добиться, чтобы микроконтроллер его отрабатывал, и передавал в компьютер информацию о вращении энкодера.
Шаг 3 . Разобраться, как можно программно управлять регулятором громкости. Наверняка есть какое-нибудь мультимедиа-API, которое позволяет это делать. Программа минимум - нужно написать программку, которая будет принимать сигналы от USB-устройства и управлять громкостью. Неплохо бы, конечно, написать драйвер, но за это браться страшновато. Лучше оставим на потом.

Итак, опишу процесс создания регулятора по шагам. Подробности опускаю, иначе будет слишком скучно. Кому интересно, см. исходники и документацию по ссылкам.

[Шаг 1. Создание низкоскоростного USB-устройства на макетке ]

Этот шаг прошел, даже не начавшись - как-то слишком просто и банально. Тупо скачал пример проекта по ссылке . Поправил файлик usbconfig.h - для понтов назвал мое устройство ENCODER DEMO , на большее фантазии не хватило. Проверил в Makefile тип проца (ATmega16), частоту кварца (16 МГц) - чтобы соответствовало моей макетке AVR-USB-MEGA16. Скомпилил проект в AVRStudio, прошил макетку, подключил к компьютеру - все завелось с полоборота, мое USB-устройство исправно заработало как виртуальный COM-порт - все в точности так, как написано в статье .

[Шаг 2. Подключить к USB-устройству энкодер ]

Этот шаг у меня вызывал самые большие опасения, что все заработает как надо. Что энкодер подключу и смогу его читать - в этом я не сомневался. Были сомнения, что смогу его считывать качественно, когда в фоне работает ещё и обработка протокола USB - все-таки это задача для микроконтроллера не из легких (как впоследствии оказалось - волновался я совершенно напрасно).

Как обычно, начал рыться в Интернете в поисках готовых подпрограмм для чтения энкодера. Нашел очень быстро то, что нужно - именно для AVR, очень простой код на C , файлики encoder.c и encoder.h. Что ни говори, а open source крутая штука.

Приделал два индикационных светодиода - ЗЕЛЕНЫЙ и ЖЕЛТЫЙ - для обозначения направления вращения энкодера. Подключил энкодер для удобства прямо к разъему ISP, воспользовавшись тем, что сигналы MOSI, MISO и SCK - это всего лишь ножки PB5, PB6 и PB7 микроконтроллера ATmega16 (подключил туда фазы A и B, а также кнопку энкодера).

Поправил определения ножек, добавил код инициализации. Присоединил к проекту модуль encoder.c. Добавил в главный цикл main управление зеленым и желтым светодиодами, когда приходит инфа с энкодера. КРАСНЫЙ светодиод привязал к кнопке энкодера - когда её нажимаем, красный светодиод зажигается, отпускаем - гаснет. Скомпилировал, прошил - работает. Кручу ручку влево, и в такт щелчкам энкодера вспыхивает зеленый светодиод. Кручу ручку вправо - вспыхивает желтый светодиод. Несмотря на то, что чтение энкодера происходит методом поллинга, благодаря эффективному коду к чтению энкодеру НИКАКИХ нареканий даже при одновременной работе с библиотекой V-USB (респект, Pashgan!). Добавил вывод информации от энкодера в виртуальный COM-порт (крутим энкодер влево вывожу в консоль минусики "-", крутим вправо вывожу в консоль плюсики "+"). По таймеру каждые 10 мс вывожу состояние кнопки энкодера и индицирую её красным светодиодом (кнопка нажата - передаю символ "1", отпущена - "0"). Все работает. Скукотища.

В заключение выкинул модули cmd.c, crc16.c, eepromutil.c, strval.c. Объем кода упал до 3 килобайт - отлично, теперь поместится и в память ATtiny45 (можно в будущем задействовать макетку AVR-USB-TINY45, она меньше по размерам и дешевле).

[Шаг 3. Разобраться, как можно программно управлять регулятором громкости ]

Как обычно, прогуглил вопрос. Отсеял кучу мусора, и наконец выгреб жемчужину - . Дальше дело техники. Достаю любимый детский конструктор - Visual Studio. Ни о чем не думая, визардом генерю dialog-based приложение. Бросаю на панель движок регулятора громкости, привязываю к нему переменную, добавляю обработчик положения движка. При старте приложения настраиваю движок на минимум 0 и максимум 65535 (чтобы соответствовало границам значения громкости, которым манипулируют библиотеки управления микшером). Считываю функцией mixerGetControlDetails текущее значение громкости, и ставлю движок регулятора в соответствующее положение. В обработчике положения движка все наоборот - читаю положение движка и функцией mixerSetControlDetails устанавливаю нужную громкость. Управление громкостью делаю в точности так, как написано в статье . Проверил - работает.

Теперь осталось дело за малым - читать, что приходит с виртуального COM-порта (на нём у нас висит свежеиспеченное USB-устройство с энкодером). Если пришел минусик (-) то двигаем движок влево (уменьшаем громкость), плюсик (+), то двигаем движок вправо (увеличиваем громкость). Если приходят символы 0 и 1, то соответственно управляем состоянием чекбокса (просто для индикации - нажата кнопка энкодера, или нет). С COM-портом можно работать, как с обычным файлом (см. ). Инициализируем подключение к COM-порту как открытие файла (вызовом ::CreateFile ) в блокирующем режиме. Запускаем отдельный поток, туда в бесконечный цикл добавляем чтение файла (блокирующим вызовом ::ReadFile ) по одному символу, и этот символ анализируем. По тому, какой символ пришел, крутим движок слайдера в нужную сторону (громкость будет регулировать обработчик слайдера) или обновляем состояние чекбокса. Проверил - работает.

Вот и все, собственно. Дальше можно заниматься бесконечным (и, наверное, бесполезным) улучшательством. Сделать автоматический поиск нужного виртуального COM-порта (сейчас для упрощения имя COM-порта передается через командную строку). Переделать USB-устройство с CDC -класса на HID - это может упростить код USB-устройства, а также упростить программный поиск и открытие устройства на компьютере по VID и HID. Или написать вместо программы сервис (чтобы не надо было запускать отдельную программу). Или даже драйвер. Это очень интересно, но не умею (может, кто из хабравчан научит уму-разуму?..). Прикрутить к кнопке энкодера какое-нибудь действие. Ну и так далее до бесконечности.

Надеюсь, кому-нибудь мои изыскания пригодятся в собственных разработках. Если чего-нибудь упустил, буду рад выслушать замечания в комментариях.

[UPD120803 ]

Один грамотный человек собрал на микроконтроллере AVR

Узнайте, как использовать инкрементальный поворотный энкодер в проекте на Arduino.

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

В данной статье мы покажем вам, как использовать инкрементальный поворотный энкодер в проекте на Arduino. Мы объясним, как бороться с дребезгом контактов и интерпретировать сигналы энкодера в программе микроконтроллера, используя прерывания.

Сигнал квадратурного выхода инкрементального энкодера

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

Как видно из рисунка, оба выхода в изначально находятся в состоянии логической единицы. Когда вал энкодера начинает вращаться в направлении по часовой стрелке, первым падает до логического нуля состояние на выходе A, а затем с отставанием за ним следует и выход B. При вращении против часовой стрелки всё происходит наоборот. Временные интервалы на диаграмме сигнала зависят от скорости вращения, но отставание сигналов гарантируется в любом случае. На основе этой характеристики инкрементального поворотного энкодера мы напишем программу для Arduino.

Фильтрация дребезга контактов механического энкодера

Механические энкодеры имеют встроенные переключатели, которые формируют сигнал на квадратурном выходе во время вращения.

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

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

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

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

Простое приложение

Мы создадим приложение, демонстрирующее, как использовать поворотный энкодер в проекте на Arduino. Мы будем использовать энкодер для навигации, ввода данных и выбора. Ниже приведена принципиальная схема приложения.

Схема построена на базе платы Arduino Uno. Для графического интерфейса используется LCD дисплей Nokia 5110. В качестве средств управления добален механический поворотный энкодер с кнопкой и RC-фильтрами.

Мы разработаем простое программное меню, в котором и продемонстрируем работу поворотного энкодера.

Обработка сигналов энкодера с помощью прерываний

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

В Atmega328 есть два типа прерываний, которые можно использовать для этих целей; внешнее прерывание и прерывание по изменению состояния вывода. Выводы INT0 и INT1 назначены на внешнее прерывание, а PCINT0 - PCIN15 назначены на прерывание по изменению состояния вывода. Внешнее прерывание может определить, произошел ли спад или нарастание входного сигнала, и может быть запущено при одном из следующих состояний: нарастание, спад или переключение. Для прерывания по изменению состояния выводов существует гораздо больше аппаратных ресурсов, но оно не может обнаруживать нарастающий и спадающий фронты, и оно вызывается, когда происходит любое изменение логического состояния (переключение) на выводе.

Чтобы использовать прерывание по изменению состояния выводов, подключите выходы поворота энкодера A и B к выводам A1 и A2 , а выход кнопки - к выводу A0 платы Arduino, как показано на принципиальной схеме. Установите выводы A0 , A1 и A2 в режим входа и включите их внутренние подтягивающие резисторы. Включите прерывание по изменению состояния выводов в регистре PCICR и включите прерывания для выводов A0 , A1 и A2 в регистре PCMS1 . При обнаружении любого изменения логического состояния на одном из этих входов будет вызовано ISR(PCINT1_vect) (прерывание по изменению состояния выводов).

Поскольку прерывание по изменению состояния выводов вызывается для любого логического изменения, нам необходимо отслеживать оба сигнала (и A, и B) и обнаруживать вращение при получение ожидаемой последовательности. Как видно из диаграммы сигналов, движение по часовой стрелке генерирует A = …0011… и B = …1001… . Когда мы записываем оба сигналы в байты seqA и seqB , сдвигая последнее чтение вправо, мы можем сравнить эти значения и определить новый шаг вращения.

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

Void setup() { pinMode(A0, INPUT); pinMode(A1, INPUT); pinMode(A2, INPUT); // Включить внутренние подтягивающие резисторы digitalWrite(A0, HIGH); digitalWrite(A1, HIGH); digitalWrite(A2, HIGH); PCICR = 0b00000010; // 1. PCIE1: Включить прерывание 1 по изменению состояния PCMSK1 = 0b00000111; // Включить прерывание по изменению состояния для A0, A1, A2 } void loop() { // Основной цикл } ISR (PCINT1_vect) { // Если прерывание вызвано кнопкой if (!digitalRead(A0)) { button = true; } // Если прерывание вызвано сигналами энкодера else { // Прочитать сигналы A и B boolean A_val = digitalRead(A1); boolean B_val = digitalRead(A2); // Записать сигналы A и B в отдельные последовательности seqA <<= 1; seqA |= A_val; seqB <<= 1; seqB |= B_val; // Маскировать четыре старших бита seqA &= 0b00001111; seqB &= 0b00001111; // Сравнить запсанную последовательность с ожидаемой последовательностью if (seqA == 0b00001001 && seqB == 0b00000011) { cnt1++; left = true; } if (seqA == 0b00000011 && seqB == 0b00001001) { cnt2++; right = true; } } }

Использование внешнего прерывания делает процесс более простым, но поскольку для этого прерывания назначено только два вывода, то вы не сможете использовать его для других целей, если займете его энкодером. Чтобы использовать внешнее прерывание, вы должны установить выводы 2 (INT0) и 3 (INT1) в режим входа и включить их внутренние подтягивающие резисторы. Затем выберите вариант спадающего фронта для вызова обоих прерываний в регистре EICRA . Включите внешние прерывания в регистре EIMSK . Когда начнется вращение вала энкодера, сначала ведущий сигнал падает до логического нуля, а второй сигнал некоторое время остается на уровне логической единицы. Поэтому нам нужно определить, какой из сигналов во время прерывания находится в состоянии логической единицы. После того, как ведущий сигнал упал до логического нуля, через некоторое время второй сигнал также упадет до логического нуля, что вызовет другое прерывание. Но этот раз и другой (ведущий) сигнал будет на низком логическом уровне, что означает, что это не начало вращения, поэтому мы игнорируем его.

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

Void setup() { pinMode(2, INPUT); pinMode(3, INPUT); // Включить внутренние подтягивающие резисторы digitalWrite(2, HIGH); digitalWrite(3, HIGH); EICRA = 0b00001010; // Выбрать вызов по спадающему фронту EIMSK = 0b00000011; // Включить внешнее прерывание } void loop() { // Основной цикл } ISR (INT0_vect) { // Если второй сигнал находится в состоянии логической единицы, то это новое вращение if (digitalRead(3) == HIGH) { left = true; } } ISR (INT1_vect) { // Если второй сигнал находится в состоянии логической единицы, то это новое вращение if (digitalRead(2) == HIGH) { right = true; } }

Полный код скетча Arduino, включающий основной цикл приведен ниже:

#include #include #include volatile byte seqA = 0; volatile byte seqB = 0; volatile byte cnt1 = 0; volatile byte cnt2 = 0; volatile boolean right = false; volatile boolean left = false; volatile boolean button = false; boolean backlight = true; byte menuitem = 1; byte page = 1; Adafruit_PCD8544 display = Adafruit_PCD8544(13, 12,11, 8, 10); void setup() { pinMode(A0, INPUT); pinMode(A1, INPUT); pinMode(A2, INPUT); // Включить внутренние подтягивающие резисторы digitalWrite(A0, HIGH); digitalWrite(A1, HIGH); digitalWrite(A2, HIGH); // Включить подсветку LCD pinMode(9, OUTPUT); digitalWrite(9, HIGH); PCICR = 0b00000010; // 1. PCIE1: Включить прерывание 1 по изменению состояния PCMSK1 = 0b00000111; // Включить прерывание по изменению состояния для A0, A1, A2 // Initialize LCD display.setRotation(2); // Установить ориентацию LDC display.begin(60); // Установить контрастность LCD display.clearDisplay(); // Очистить дисплей display.display(); // Применить изменения sei(); } void loop() { // Создать страницы меню if (page==1) { display.setTextSize(1); display.clearDisplay(); display.setTextColor(BLACK, WHITE); display.setCursor(15, 0); display.print("MAIN MENU"); display.drawFastHLine(0,10,83,BLACK); display.setCursor(0, 15); if (menuitem==1) { display.setTextColor(WHITE, BLACK); } else { display.setTextColor(BLACK, WHITE); } display.print(">Contrast: 99%"); display.setCursor(0, 25); if (menuitem==2) { display.setTextColor(WHITE, BLACK); } else { display.setTextColor(BLACK, WHITE); } display.print(">Test Encoder"); if (menuitem==3) { display.setTextColor(WHITE, BLACK); } else { display.setTextColor(BLACK, WHITE); } display.setCursor(0, 35); display.print(">Backlight:"); if (backlight) { display.print("ON"); } else { display.print("OFF"); } display.display(); } else if (page==2) { display.setTextSize(1); display.clearDisplay(); display.setTextColor(BLACK, WHITE); display.setCursor(15, 0); display.print("ENC. TEST"); display.drawFastHLine(0,10,83,BLACK); display.setCursor(5, 15); display.print("LEFT RIGHT"); display.setTextSize(2); display.setCursor(5, 25); display.print(cnt1); display.setCursor(55, 25); display.print(cnt2); display.setTextSize(2); display.display(); } // Выполнить действие, если от энкодера принята новая команда if (left) { left = false; menuitem--; if (menuitem==0) { menuitem=3; } } if (right) { right = false; menuitem++; if (menuitem==4) { menuitem=1; } } if (button) { button = false; if (page == 1 && menuitem==3) { digitalWrite(9, LOW); if (backlight) { backlight = false; digitalWrite(9, LOW); } else { backlight = true; digitalWrite(9, HIGH); } } else if (page == 1 && menuitem==2) { page=2; cnt1=0; cnt2=0; } else if (page == 2) { page=1; } } } ISR (PCINT1_vect) { // Если прерывание вызвано кнопкой if (!digitalRead(A0)) { button = true; } // Или если прерывание вызвано сигналами энкодера else { // Прочитать сигналы A и B boolean A_val = digitalRead(A1); boolean B_val = digitalRead(A2); // Записать сигналы A и B в отдельные последовательности seqA <<= 1; seqA |= A_val; seqB <<= 1; seqB |= B_val; // Маскировать четыре старших бита seqA &= 0b00001111; seqB &= 0b00001111; // Сравнить запсанную последовательность с ожидаемой последовательностью if (seqA == 0b00001001 && seqB == 0b00000011) { cnt1++; left = true; } if (seqA == 0b00000011 && seqB == 0b00001001) { cnt2++; right = true; } } }

Энкодер в действии вы можете увидеть на видео, приведенном ниже.

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

Инкрементальный энкодер представляет собой два контакта, порядок замыкания которых зависит от направления вращения .


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

Давайте соберём тестовую схему изображенную на картинке выше и подключимся к выводу A и B осциллографом , резисторы подтяжки - 4.7К.
Покрутим энкодер по часовой стрелке.


Теперь против часовой.


На осциллограммах видно, что в зависимости от направления вращения, изменяется порядок замыкания контактов. Но фронта не всегда получаются такие красивые.


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

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


Так как дребезг явление кратковременное, он легко гасится конденсатором.


На осциллограмме видно, что после установки конденсаторов, фронты стали менее крутыми, а дребезг исчез.

Второй способ - программный и тут всё зависит от реализации опроса выводов энкодера. Если состояние энкодера отслеживается с помощью внешних прерываний , то после срабатывания прерывания необходимо сделать задержку 20 - 30 миллисекунд, во время которой МК не будет реагировать на изменение состояния вывода, то есть не будет чувствовать дребезг. Если опрос выводов энкодера реализован на таймере , то интервал между опросами должно быть больше длительности дребезга, те же 20 -30 миллисекунд.

Давайте рассмотрим методы обработки данных, приходящих с энкодера .
Первый метод, заключается в том, что одну из ножек энкодера мы подключаем к выходу внешних прерываний и настраиваем её на прерывание по спадающему фронту. В прерывании мы проверяем состояние другой ножки и если на ней ноль, то вращение происходит в одну сторону, иначе в другую. Ниже приведён код, реализующий этот метод для AVR.
#include ISR(INT2_vect) { if (PINB & 0X02) { PORTB |= (1<<0); } else { PORTB &= ~(1<<0); } //антидребезг _delay_ms(20); //сбрасываем флаг прерывания вызванный дребезгом GIFR = (1<При повороте энкодера в одну сторону светодиод загорается, при повороте в другую - гаснет.

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


Тогда мы получим конечное число состояний энкодера. Первая цифра - логический уровень первого вывода энкодера, вторая - логический уровень второго вывода.

Предположим последнее состояние в котором находился энкодер равно трем, если следующее состояние будет равно единице, то он вращается в одну сторону, если двум, то в другую. Получается, что можно фиксировать переход из одного состояние в другое и определять направление вращения, но наиболее простой является реализация при переходе от 11 к 01 и 10. Ниже приведён код реализующий описанный алгоритм для AVR,
#define F_CPU 8000000UL #include #include uint8_t last_state = 0; ISR(TIMER0_COMP_vect) { //оба вывода энкодера подключены к 2 и 3 выводу порта B //считываем их состояние uint8_t current_state = (PINB & 0x06)>>1; //учитываем переход только если пред.состояние 11 //и если оно не равно новому if ((last_state == 3) && (last_state != current_state)) { //если новое сост 01 - включаем светодиод if(current_state == 1) { PORTB |= 0x01; } //если новое сост 10 - гасим светодиод if(current_state == 2) { PORTB &= ~0x01; } } //при выходе из прерывания текущее состояние становится прошлым last_state = current_state; } int main(void) { //два входа для подключения энкодера DDRB &= ~0x06; //подтягиваем входы к питанию PORTB |= 0x06; //выход для подключения светодиода DDRB |= 0x01; //настраиваем таймер по в режим сброс по совпадению TCCR0=(1<На этом всё.
Энкодер покупал



Рекомендуем почитать

Наверх