Энкодер. Некоторые ньюансы.

Статей о работе с энкодером написано немало. В общем как и статей о других, рассматриваемых на этом сайте вопросах. Однако существуют, и имеют на это полное право, сотни рецептов приготовления борща или, допустим, шашлыка — каждый выбирает наиболее подходящий для себя. Это есть в некоторой степени выражение пресловутой «свободы выбора» и, если бы мой блог был философской направленности, то, вслед за вступлением, на головы читателей полились бы потоки дальнейших рассуждений о сущности бытия и прочее 🙂 Хорошо что это не так, и я здесь просто покажу мой подход к использованию этого весьма удобного элемента в связке с микроконтроллерами.
Об устройстве инкрементального энкодера неплохо написано здесь, или, допустим здесь — ничего сложного там не имеется. В простейшем случае — это как две кнопки, замыкающиеся определённым образом при вращении ротора. Удобная вещь, а, если ещё и с дополнительной кнопкой — то вообще универсальное устройство ввода для конструкций на микроконтроллерах!
Цена энкодеров почему-то до сих пор относительно высока, ну, конечно не слишком, но как для двух кнопок, то… Поэтому, прогуливаясь по различным барахолкам и блошиным рынкам, советую обращать внимание на распродажи битых плат зарубежных радиоэлектронных изделий по бросовым ценам — можно за копейки купить плату с установленным там хорошим качественным энкодером. Мне посчастливилось купить таким образом плату управления от фирменного музцентра «Sony» из которой я и выпаял героя сегодняшней публикации. Качественная сборка, отсутствие люфтов, чёткая фиксация и тактильные ощущения при вращении ротора позволили мне верить надписи на корпусе «Jpn». Поиск даташита в интернет по маркировке не принёс никаких результатов, поэтому для определения распиновки я применил хорошо работающую методику, которой пользуюсь уже давно. Прозвонка мультиметром не даст вам однозначного ответа, который из выводов общий.
Для точного определения выводов понадобится двухлучевой осциллограф, источник питания 3 — 5 В, резистор 5 — 100 кОм и сам энкодер. От плюса источника через резистор присоедините  параллельно два проводника к двум любым выводам энкодера и на каждый из этих двух выводов присоедините по каналу осциллографа. Оставшийся вывод энкодера вешаем на минус (общий) питания, туда же и корпус осциллографа. Крутим ротор и наблюдаем картинку…
Лично я, чтоб не морочиться с резистором и батарейками, просто подключил энкодер к микроконтроллерному модулю предварительно прошив кристалл программкой, переводящей какой-либо порт  в режим входов с подключенными pull-up внутренними резисторами — это суть та же схема, что я описывал выше.
Соединитель с жёлтым кембриком — это корпус, два с синим — +5В с порта чипа. Так вот, о картинке… Мне повезло, и я с первого раза подключил правильно. Картинка получается такая:
Поменяем местами «корпусной» провод с любым из двух других. Ещё раз крутим:

Сравнение осциллограмм явно показывает, что при правильном подключении общего вывода значений на входах порта будет четыре, а при любом другом только три — можете поэкспериментировать. Примем «жёлтый» канал осциллографа как старший разряд. Слева направо отсчитываем значения: 1-я осциллограмма — 00, 01, 11, 10, 00 … — код Грея ; 2-я осциллограмма — 00, 01, 11, 00 … —  болт от кода Грея. Четвёртый вывод моего энкодера оказался никуда не подключенным, видимо в других модификациях сюда подключается вывод дополнительной кнопки, замыкающейся при нажатии на ротор.
Теперь о программной реализации. Яростный холивар на форумах о том, как же лучше обрабатывать энкодер — по внешнему прерыванию или циклическим опросом, ясно показывает, что народ спорит каждый о своём. В дествительности же всё зависит от КОНКРЕТНОЙ задачи. Для меня энкодер — устройство ввода информации (управления меню), и отдавать на его обработку столь драгоценный ресурс как внешнее прерывание нет смысла. Поэтому для себя я написал универсальную библиотечку и пользуюсь ей во всех своих проектах, где применяется энкодер. В заголовочном файле выбираются порт и разряды, его обслуживающие . Кроме того при вызове функции устанавливаются минимальные и максимальные значения — это очень удобно! Ведь для установки одних параметров нужен, например,  диапазон 0 — 10, а для других — 2000 — 50000. Ещё в функцию добавил фрагмент который позволяет учитывать сколько изменений состояний в конкретном энкодере на один щелчок. Кстати, именно поэтому мультиметром трудно вызвонить распиновку энкодера! Ведь «поймать» четыре, даже два,  промежуточных состояния ротора между соседними щелчками ой как непросто! Функцию можно вызывать в главном цикле или по переполнению таймера с определённой периодичностью, или как Вам захочется. Переменные EncState, EncData объявляйте как глобальные в главном файле проекта, а если они «перевариваются» в обработчике прерываний — то ещё и volatile. Удачи, коллеги!

Архив с исходниками на С.

Запись опубликована в рубрике Микроконтроллерный конструктор с метками , . Добавьте в закладки постоянную ссылку.

23 комментария: Энкодер. Некоторые ньюансы.

  1. Konoplj2010 говорит:

    А вот такое дело, было бы не проще применять порт INTO или таймер со счётчиком внешних событий. Вот на ссылке сайта http://easyelectronics.ru/avr-uchebnyj-kurs-inkrementalnyj-enkoder.html показано что опрос идёт от времени таймера. Лично по моему занимать целый таймер пусть даже не очень серьёзный типа 8 битный, это не цели сообразно их и так мало.

  2. s_black говорит:

    Всё зависит от поставленной задачи — в статье на это обращено внимание! Я здесь показал общий подход к вопросам эксплуатации энкодера.

  3. Konoplj2010 говорит:

    Я не в коем случае не хотел Вас обидить или обвенить в нецелисообразности. Я просто не правильно выразился. На этой ссылке говорится что для ускорения опроса энкодера применяют вывод внешнего прерывания, а разрешение следующего опроса производится после подачи команды с таймера. Но зачем использовать и вывод внешнего прерывания и внутренний таймер? Я же предлогаю следующие:
    Проинициализировать вывод INITO для генерации прерывания по фронту
    При подачи фронта на выводе в подпрограмме обработки прерываний сначало определить состояние другого канала, если на другом канале 0 то EncData— если же 1 то EncData++.
    После этого переключить вывод INITO на формирование прерывания по срезу и снова ожидать изменений. После очередного прерывания анализируем другой канал но уже с обратным положением, если на канале 1 то EncData— если же 0 то EncData++.
    Ну разве это не экономнее. Я повторюсь, я не про вас говорил, а принцип работы вполне понятен:)

  4. Konoplj2010 говорит:

    Добрый вечер. Это сново я, я тут приобрёл энкодер, написал прогу по его опросу но она не пашет. А вернее пашет но не так как надо. Если вращаю энкодер в право то счётчик перепрыгивает через два-три шага стабатываний, но даже если вращать в лево то счётчик считает в туже сторону и тоже перепрыгивает, парадокс!? Я думал что это дребезг контактов, хотя в принципи какой там может быть дребезг, контакты всё равно ползучие, но для уверенности добавил временную задержку и не помогло. Можете посмотреть?

    ISR (INT0_vect){
    if (bit_is_set (MCUCR, ISC00)){MCUCR&= (1<<ISC00);
    if (bit_is_set (PIND, PD1)) {i--;} else{i++;}}
    else{MCUCR|= (1<<ISC00);
    if (bit_is_set (PIND, PD1)) {i++;} else{i--;}}}

    Работаю с WinAVR.

  5. s_black говорит:

    Добрый ))) Как обычно куча грамматических ошибок ((( Ну не стыдно Вам, господин студент!?
    {MCUCR &= (1 < < ISC00); - это, надо полагать, установка прерывания по спадающему фронту, если было по нарастающему. Забыли поставить тильду (~), т.е. отрицание, для сброса в ноль разряда ISC00. Из-за этого работает постоянно по нарастающему фронту со всеми вытекающими последствиями. Можно сделать проще, написано здесь в последнем абзаце.

  6. Konoplj2010 говорит:

    Я извеняюсь за орфографию, вобше я Укаиноязычный и Руский язык изучаю всего с июня 2010 года (именно тогда я перёхал из Луцка на Донбас). А на счёт прогаммы я хотел делать так;
    В инициализации устанавливаем прерывание по фронту
    При наступании события фронта в подпрогамме обработки прерываний определяемя как было настроено прерывание. Если по фронту то перенастраиваем на спад, если же было настроено на спад то перенастраиваем на фронт. Это позволит формировать по два прерывание на один шаг энкодера.
    Затем определяемся об состоянии второго вывода энкодера. Еслт прерывание было настроено на фронт и на втором выводе энкодера установлен 1 то врашаемся в обратную сторону, а ели установлен 0 то в прямую
    Обратите внимание если прерывание было настроено на спад то опрос второго вывода идёт наоборот. Это зашитит систему от дребезга(если таковой имётся) и увеличит колличество срабатываний на один полный оборот энкодера(у меня энкодер на 16 срабатываний на оборот, а так будит 32).

  7. s_black говорит:

    Якщо Вам так зручніше, пишіть мені українською — я однаково вільно спілкуюсь, як українською, так і російською мовами.)))
    Смысл Вашего кода я понял, и не говорю, что так работать не будет. Другое дело, что этот код можно предельно упростить — почитайте материал по ссылке, которую я Вам привёл. Можно настроить прерывание хоть по восходящему, хоть по ниспадающему фронту, и, в зависимости от считанного на втором выводе значения, принимать решение об инкременте или декременте переменной.

  8. Konoplj2010 говорит:

    Та не, пусть будет на Русском (для меня так больше практики).
    А на счёт кода я понял, да ошибочка вишла. А вот еще почему же тогда счётчик считает только вперёд даже если я вращаю назад, ниужели это дребезг? И вообще, зачем выжидать время после подпрограммы обработки прерываний? Если это для защиты от дребезга то да, в случие когда перивание проходит толко по фронту и не перенастраивается, то данная защита нужна так как на один цикл счетчик может посчитать цикл + колличество дребезгових срабатываний. А в моём случие когда меняется; при дребезге тот импульс которий получился считается как ++, а затем сразу в следующем прерывании как —. То есть так ми и устранили дребезг програмно, правильно?

  9. s_black говорит:

    Да нет же! Ваш код рабочий, только ошибку исправьте. Я говорю о том, что можно не проверять и не изменять условие внешнего прерывания (нарастающий, спадающий). Почитайте ещё раз внимательно статью DI HALTA — там же всё расписано!

  10. Konoplj2010 говорит:

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

  11. s_black говорит:

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

  12. Konoplj2010 говорит:

    Я исправил ошибку но проблема не пропала. Хотя вернее будет так:
    Поварачиваю по часовой стрелке он не считает вообще
    Поварачиваю против часовой стрели всё считается правильно
    В чём же может быть теперь проблема?

    unsigned int i;
    ISR (INT0_vect){
    if (bit_is_set	(MCUCR, ISC00)){MCUCR&amp;= ~(1&lt;&lt;ISC00);
    if (bit_is_set	(PIND, PD1))	{i--;}else{i++;}}
    else{MCUCR|= (1&lt;&lt;ISC00);
    if (bit_is_set	(PIND, PD1))	{i++;}else{i--;}}}
  13. s_black говорит:

    Вообще-то нужно было бы посмотреть весь код…
    Навскидку, что бросается в глаза — переменные, которые изменяются в обработчике прерывания (в твоём случае i), обычно объявляют volatile. Добавь и проверь. Если не поможет — прогони в Студии в симуляторе.

  14. Konoplj2010 говорит:

    Получилось!!! Заработало!!!
    И знаете проблема даже не в коде. Я заметил что энкодер считает нормально только в одну сторону, к чиму бы это? Снова перечитал Вашу статью и обратил внимание на то что если подключить энкодер неправильно (Вы писали если поменять местами корпус с каналом) то код от вращения основательно меняется, теперь уже только три устойчевых значения. и получилось что считать направление вращения стало возможным только в одну сторону.
    по инструкции которая прилогалась к энкодеру я подключил правильно, но решил разобрать один из них и глянуть на сеточку. Моелу удивлению небыло придела после того как я увидил что общий в реале с лева, а по инструкции посередине! Собрал, перепаял провода и сразу же, с первого раза запустил. Я незнаю, толи инструкция не к этому енкодеру, толи Китайци отличились но факт есть факт.
    В результате имеется програмка обработки кода от енкодера используя только один вывод прерывания и один вывод МК:

    ISR (INT0_vect){
    if (bit_is_clear(MCUCR, ISC00))	{
    if (bit_is_set(PIND, PD1))	{i++;}else	{i--;}MCUCR|= (1&lt;&lt;ISC00);} 
    else 							{
    if (bit_is_clear(PIND, PD1))	{i++;}else	{i--;}MCUCR&amp;=~(1&lt;&lt;ISC00);}
    }

    Обратите внимание я внутренний таймер не использую и защита от дребезга контактов имеется. Ведь если происходит дредезг то считается сначало i++ затем i— и снова i++, свё класс.
    Вобщем дякую за помощь и особую благодарность за Ваш сайт и стремление помочь людям:) Привет из Донбаса.

  15. s_black говорит:

    Так с этого нужно было и начинать, мой юный друг (я имею в виду правильное подключение). Для того-то и статья была написана — ведь вариантов программной реализации есть множество, но ни один из них не будет работать, если энкодер неправильно подключён. Я искренне рад, что Вы разобрались в своей проблеме. Всего хорошего 🙂

  16. Viktor говорит:

    очень интересная статья, но для многих было бы намного больше пользы если бы был хоть один рабочий демо проект с использованием ваших исходников и не просто мигоние LED, a там где используеться прерывание и для другого ( часы, таймер и т.д. )

  17. s_black говорит:

    Согласен полностью 🙂 А рабочие примеры есть здесь и здесь.

  18. Stranger2748 говорит:

    В выложенной библиотеке ошибка в условиях обработки позиций энкодера. На практике он в обе стороны то прибавляет то уьбавляет при вращении в одном направлении. Вот исправленный мной кусок кода encoder.c

    switch(EncState) //Перебор прошлого значения энкодера
    {
    case state_2:if(New == state_3) EncPlus++;//В зависимости от значения увеличиваем
    if(New == state_1) EncMinus++;//Или уменьшаем
    break;
    case state_0:if(New == state_1) EncPlus++;
    if(New == state_3) EncMinus++;
    break;
    case state_1:if(New == state_2) EncPlus++;
    if(New == state_0) EncMinus++;
    break;
    case state_3:if(New == state_0) EncPlus++;
    if(New == state_2) EncMinus++;
    break;
    default:break;
    }

    и в encoder.h надо кое-что поправить

    #define state_0 0x00 /*состояние выводов 0 энкодера*/
    #define state_1 _BV(ENC_0) /*состояние выводов 1 энкодера*/
    #define state_2 _BV(ENC_1) + _BV(ENC_0) /*состояние выводов 2 энкодера*/
    #define state_3 _BV(ENC_1)/*состояние выводов 3 энкодера*/

  19. s_black говорит:

    Прикольно))) Выходит я столько лет пользовался неисправным кодом, в том числе и для АРМ-ов!
    Уважаемый коллега! Очень внимательно посмотрите на мой код и на получившийся Ваш. Нарисуйте два меандра, сдвинутых друг относительно друга. Подпишите каждое состояние и граничные с ним состояния вправо и влево. Если внимательно всмотритесь, то увидите, что наши коды суть одно и то же. Только Вы обозначили состояния по порядку, а я — по их двоичному коду. Успехов!

  20. tower говорит:

    to Konoplj2010 зачем такие сложности???
    Если вы используете опрос по прерыванию, то достаточно отследить состояние второй ноги энкодера (первая подкл. к прерыванию).
    ISR (INT0_vect){
    if (bit_is_set (PIND, …)){
    »»’ left;
    }else{
    »»» right;
    }
    такой код прекрасно работает, важно только что бы прерывание было настроено на фронт, все равно какой rising или falling.

  21. Stranger2748 говорит:

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

  22. Аноним говорит:

    Чёрт возьми. Большое вам спасибо за статью, а именно за её начало. Понятия не имел что у энкодеров бывают разные выводы. То есть я уперто верил что средняя ножка это общий контакт… Всю голову изломал, все программу опроса колошматив. А оказалось что не правильно подключен энкодер.

  23. s_black говорит:

    Рад, что помог вам.

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

Ваш e-mail не будет опубликован.