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

Инкрементальный энкодер внешне похож на потенциометр , но в отличие от потенциометра у него нет крайних положений, он может вращаться в обоих направлениях неограниченное количество оборотов. Также надо отметить, что инкрементальный энкодер вращается не так плавно как потенциометр, а шагами. Его можно увидеть на автомобильной магнитоле, осциллографе , музыкальном центре, стиральной машине и прочей технике, где регулировка какого-то параметра осуществляется в больших пределах. Конечно, параметры можно изменять и с помощью кнопок, например, для того чтобы сделать музыку на 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<На этом всё.
Энкодер покупал

Для реализации демонстрационного проекта нам понадобятся:

  • 24-позиционный энкодер;
  • 16 светодиодов (3 мм);
  • драйвер светодиодов A6276 ;
  • микроконтроллер PIC18F2550 .

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

Принцип работы энкодера

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

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

Принципиальная схема

Выводы энкодера A и B подключаются к портам микроконтроллера RB4 и RB5, вывод С энкодера подключается к «земле». Стоит заметить, что на сигнальные линии выводов A и B должны быть подключены подтягивающие резисторы. Энкодер не случайно подключен к указанным линиям ввода/вывода микроконтроллера: во-первых, порт B имеет встроенные подтягивающие резисторы и нам не придется подключать внешние, во-вторых, порт B микроконтроллера имеет очень полезную функцию - «interrupt-on-change» - прерывание по изменению уровня, что позволит нам отслеживать состояние энкодера.

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

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

Программное обеспечение микроконтроллера для решения нашей задачи относительно простое. Реализуется 3 режима работы (ввод информации) и обратная связь:

  • Режим позиционирования на 360° - в этом режиме светодиоды указывают текущую «позицию» энкодера, пользователь может поворачивать вал энкодера влево и вправо на любой угол;
  • Режим «Громкость/Уровень» - в этом режиме светодиоды указывают текущее значение между минимальным и максимальным уровнями диапазона ввода (как уровень громкости в аудиоустройствах);
  • Режим 3-позиционного ротационного тумблера - в этом режиме имеется только три выбираемых позиции, которые пользователь выбирает, поворачивая вал энкодера влево/вправо.

Демонстрация работы проекта

Загрузки

ZIP-архив с проектом в среде MPLAB и исходным кодом на Hitech C, а также, принципиальная схема и топология печатной платы находятся .

Наконец то у меня появилась эта замечательная крутилка и теперь я хочу рассказать вам как с ней работать. Мой энкодер (EC12E24204A9) представляет из себя примерно следующее:

Энкодер имеет три контакта: Общий, А и В. Общий обычно всегда подключается к земле а два других к любым пинам микроконтроллера. Выводы А и В должны быть подтянуты к плюсу питания через резисторы порядка 10 кОм, чтоб исключить ложные срабатывания от наводок. Использовать внутреннюю подтяжку микроконтроллера я бы не посоветовал. Уж очень она слабая. Для демонстрации работы, повесим еще 8 светодиодов. (примечание: Если используется мега16, мега32 и старше то необходимо выключить jtag иначе половина светодиодов гореть не будет)
Итак получили следующую схему:


Крутим ручку энкодера вправо — огонек бежит вправо. Крутим влево — огонек бежит влево. Как работает энкодер? Будем разбираться. Ничего сложного нет. Посмотрим на графики ниже.
При вращении энкодера в одну сторону сигнал выглядит так:

В другую:

Возникает вопрос, а как же микроконтроллеру различить направление вращения энкодера?

Существуют два популярных алгоритма опроса энкодера:

  • Опрос при помощи прерываний
  • Опрос со сравнением предыдущего состояния энкодера и текущего

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

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

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

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

С Энкодера выходят два сигнала А и В, сдвинутых на 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 через несколько миллисекунд
  • Вышли из обработчика

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

Из этой статьи вы узнаете, что такое энкодер, зачем он нужен, и как его подружить с микроконтроллером. Если вы пользовались современной стиральной машиной, микроволновой печью или аудио системой то, скорее всего вы уже имели дело с энкодером, сами того не подозревая. Например, в большинстве современных домашних и автомобильных стерео систем энкодеры используются для регулировки громкости звука.
Энкодер или датчик угла поворота – это электромеханическое устройство, предназначенное для преобразования углового положения вала или оси в электрические сигналы. Существует два основных типа энкодеров - инкрементные и абсолютные.
Инкрементный энкодер при вращении формирует импульсы, число которых пропорционально углу поворота. Подсчет числа этих импульсов даст нам величину угла поворота вала энкодера относительно его начального положения. Этот тип энкодеров не формирует выходные импульсы, когда его вал находится в покое. Инкрементные энкодеры находят широкое применение в индустриальных средствах управления, бытовой и музыкальной технике.
Абсолютный энкодер для каждой позиции своего вала выдает уникальный код. Ему, в отличии от инкрементного энкодера, счетчик не нужен, угол вращения всегда известен. Абсолютный энкодер формирует сигнал и когда вал вращается, и когда он находится в покое. Абсолютный энкодер не теряет информацию о своем положении при потере питания и не требует возврата в начальную позицию. Этот тип энкодеров применяется в промышленно оборудовании - робототехнике, станках, конвейерных линиях.
Я хотел бы рассказать о сопряжении инкрементного механического энкодера с микроконтроллером. Для этого я приобрел инкрементный энкодер фирмы Bourns - PEC12-4220F-S0024. Вот расшифровка его названия согласно datasheet: PEC12 – модель, 4 – вертикальное положение выводов, 2 – 24 стопора, 20 – длина вала в мм, S – наличие кнопки, 0024 – 24 импульса за оборот.

У него 5 выводов. 2 вывода на фотографии слева – выводы кнопки, 3 вывода на фотографии справа – выводы энкодера. Из них - 2 сигнальных и 1 общий. Он посередине. Схема подключения энкодера ничем не отличается от подключения обычных кнопок. Сигнальные выводы энкодера подключаем к любому порту ввода вывода микроконтроллера. Общий вывод энкодера сажаем на землю. Для защиты от дребезга контактов не лишним будет добавить еще пару керамических конденсаторов номиналом в несколько нанофарад. Выводы микроконтроллера в программе конфигурируем как входы и включаем подтягивающие резисторы. Можно использовать внешние.

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


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


Сигналы сняты со старого энкодера, включенного без фильтрующих конденсаторов.


Алгоритм обработки сигналов энкодера выглядит следующим образом. В обработчике прерывания таймера запускается функция опроса энкодера. Она считывает логические уровни, присутствующие на выводах микроконтроллера к которым подключен энкодер и записывает их во временную переменную. Внутри функции есть статическая переменная (переменная, которая сохраняет свое значение при выходе из функции) хранящая последовательность предыдущих состояний. С помощью битовой маски микроконтроллер выделяет из этой переменной последнее состояние и сравнивает его с текущим, чтобы определить произошли ли изменения. Если состояния равны – функция завершает работу, если отличны – значение статической переменной сдвигается влево на 2 разряда и на «освободившееся» место записывается текущее состояние. Таким образом, если вал энкодера вращается, функция будет постоянно сохранять некую повторяющуюся кодовую последовательность. При вращении вправо – это будет 11100001. При вращении влево – 11010010. По этим последовательностям микроконтроллер и будет понимать, в какую сторону происходит вращение.

Исходник для работы с энкодером можно скачать . Архив содержит два файла: encoder.h и encoder.c. В хедере задаются порт и номера выводов, к которым подключен энкодер, константы LEFT_SPIN и RIGHT_SPIN. Также там описаны прототипы функций. Сишный файл содержит реализацию функций.


void InitEncoder(void) – инициализирует выводы порта.

void PollEncoder(void)
– однократно опрашивает энкодер. Если зафиксировано вращение, записывает в буфер одну из констант, если нет, просто завершает работу.

unsigned char GetStateEncoder(void)
– возвращает содержимое буфера и очищает его.

Опрос энкодера я обычно произвожу с частотой ~ 4 кГц. Если опрашивать медленней, микроконтроллер будет пропускать импульсы при быстрых поворотах ручки энкодера. Если энкодер используется для установки линейно меняющейся величины, например для установки времени в часах, то в качестве констант LEFT_SPIN и RIGHT_SPIN удобно использовать числа 255 и 1 соответственно. В обработчике сигналов энкодера эти числа просто складываются с устанавливаемой величиной. При сложении с 1 величина увеличивается на 1, при сложении с 255 уменьшается на 1. Конечно это актуально если эта величина однобайтная. Ну а в принципе константы LEFT_SPIN и RIGHT_SPIN можно выбирать произвольно, главное правильно написать обработчик. На этом все.

Исходник для работы с энкодером .