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

Давно хотел приспособить к ноуту регулятор громкости, сделанный из энкодера . Подключать этот регулятор нужно будет к 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

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

  • 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, а также, принципиальная схема и топология печатной платы находятся .

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

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

Прежде всего энкодеры бывают нескольких типов, рассматриваемый в данной статье — механический инкрементальный. В качестве испытуемого, был использован pec12-4220f-s0024. Внешне он похож на переменный резистор, но, в отличие от резистора, он не имеет ограничителей, т.е. может крутиться бесконечно в любую сторону.

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

Теперь рассмотрим все более детально. Электрически он представляет собой 2 кнопки без фиксации, когда мы начинаем крутить они по очереди срабатывают — сначала одна, затем вторая. В зависимости от того, в какую сторону мы вращаем, одна из кнопок срабатывает раньше или позднее. Для того чтобы узнать, в каком состоянии находятся эти кнопки, ножки порта (к которому подсоединен энкодер) должны быть подтянуты к «+» питания.

На разобранном энкодере 1/3 площадки относится к 1 контакту, 1/3 к 2 контакту, сплошной участок — общий. Когда скользящие контакты попадают на изолированные участки (черные), слышны щелчки. В этот момент энкодер, находится в устойчивом состоянии, когда обе кнопки разомкнуты. На ножках порта будут лог единицы(состояние 11).

Как только мы начинаем вращать в какую либо сторону, один из контактов замыкается на землю. На этой ножеке появится лог 0, на второй ножке по прежнему будет лог1 (состояние 01). Если мы продолжаем вращать, на второй ножке появится лог0(состояние 00). Далее, на первой ножке пропадает контакт (состояние 10), в конце концов энкодер возвращается в устойчивое состояние (11). Т.е. на один щелчок приходится 4 изменения состояния. Временная диаграмма выглядит так:

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

Если выписать эти состояния в двоичной системе и перевести их в десятичную, то получится следующий порядок(для вращения в одну сторону):
11=3
01=1
00=0
10=2

При вращении в противоположную сторону:
11=3
10=2
00=0
01=1

Теперь осталось понять, как эти значение обрабатывать. Допустим, энкодер подключен к ножкам порта В0 и В1. Нам нужно прочитать эти ножки. Есть довольно хитрый способ, но для начала нам нужно понять операцию «логического и» (&).

Результат будет равен единице, только если оба числа равны 1, т.е. результат операции 1&1, будет равен 1. Следовательно 1&0=0, 0&0=0, 0&1=0.

Логическое & поможет нам вычленить из целого порта, только интересующие нас ножки. Т.е. операция x=0b00000011 & PINB; позволит нам прочитать в переменную «х» состояние первых двух ножек, независимо от того, что находится на остальных ножках. Число 0b00000011 можно перевести в шестнадцатеричную систему 0х3.

Теперь все необходимые знания для написания прошивки у нас есть. Задача: увеличивать/уменьшать переменную Vol, при помощи энкодера, результат вывести на lcd дисплей.

#include int NewState, OldState, Vol, upState, downState; #asm .equ __lcd_port= 0x12 ; PORTD #endasm #include #include interrupt [ TIM1_COMPA] void timer1_compa_isr(void ) { NewState= PINB & 0b00000011 ; if (NewState!= OldState) { switch (OldState) { case 2 : { if (NewState == 3 ) upState++; if (NewState == 0 ) downState++; break ; } case 0 : { if (NewState == 2 ) upState++; if (NewState == 1 ) downState++; break ; } case 1 : { if (NewState == 0 ) upState++; if (NewState == 3 ) downState++; break ; } case 3 : { if (NewState == 1 ) upState++; if (NewState == 2 ) downState++; break ; } } OldState= NewState; } TCNT1H= 0x00 ; TCNT1L= 0x00 ; } void main(void ) { char lcd_buf[ 17 ] ; // Input/Output Ports initialization // Port B initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=P State0=P PORTB= 0x03 ; DDRB= 0x00 ; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 1000,000 kHz // Mode: CTC top=OCR1A // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer 1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: On // Compare B Match Interrupt: Off TCCR1A= 0x00 ; TCCR1B= 0x0A ; TCNT1H= 0x00 ; TCNT1L= 0x00 ; ICR1H= 0x00 ; ICR1L= 0x00 ; OCR1AH= 0x03 ; OCR1AL= 0xE8 ; OCR1BH= 0x00 ; OCR1BL= 0x00 ; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK= 0x10 ; // Global enable interrupts #asm("sei") lcd_init(8 ) ; while (1 ) { if (upState >= 4 ) { Vol++; upState = 0 ; } if (downState >= 4 ) { Vol--; downState = 0 ; } sprintf (lcd_buf, "vol=%d" , Vol) ; lcd_gotoxy(0 , 0 ) ; lcd_clear() ; lcd_puts(lcd_buf) ; } ; }

#include int NewState,OldState,Vol,upState,downState; #asm .equ __lcd_port=0x12 ;PORTD #endasm #include #include interrupt void timer1_compa_isr(void) { NewState=PINB & 0b00000011; if(NewState!=OldState) { switch(OldState) { case 2: { if(NewState == 3) upState++; if(NewState == 0) downState++; break; } case 0: { if(NewState == 2) upState++; if(NewState == 1) downState++; break; } case 1: { if(NewState == 0) upState++; if(NewState == 3) downState++; break; } case 3: { if(NewState == 1) upState++; if(NewState == 2) downState++; break; } } OldState=NewState; } TCNT1H=0x00; TCNT1L=0x00; } void main(void) { char lcd_buf; // Input/Output Ports initialization // Port B initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=P State0=P PORTB=0x03; DDRB=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 1000,000 kHz // Mode: CTC top=OCR1A // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer 1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: On // Compare B Match Interrupt: Off TCCR1A=0x00; TCCR1B=0x0A; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x03; OCR1AL=0xE8; OCR1BH=0x00; OCR1BL=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x10; // Global enable interrupts #asm("sei") lcd_init(8); while (1) { if (upState >= 4) { Vol++; upState = 0; } if (downState >= 4) { Vol--; downState = 0; } sprintf(lcd_buf,"vol=%d",Vol); lcd_gotoxy(0,0); lcd_clear(); lcd_puts(lcd_buf); }; }

В качестве пояснений: таймер 1 настроен на срабатывание 1000 раз в секунду, строкой NewState=PINB & 0b00000011; считываем состояние ножек 0 и 1 портаВ. if(NewState!=OldState) если состояние не изменилось, значит вращения нет.
Если состояние изменилось конструкция switch определяет в какую сторону было произведено вращение, в зависимости от этого увеличивается значение переменной downState(влево) или upState(вправо).

От щелчка до следующего щелчка 4 изменения состояния, поэтому 1 раз за 4 импульса изменяем переменную Vol. Ее же и выводим на дисплей. Прошивка доступна

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

Особенности инкрементального энкодера

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

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

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

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

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

У энкодера есть три информационных вывода один общий, остальные два обычно называют «A» и «B», на рисунке выше вы видите цоколевку энкодера с кнопкой - вы можете получать сигнал при нажатии на его вал.

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

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

Вот пример цоколевки реального энкодера:

Интересно:

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

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

Не менее наглядно это проиллюстрировано на следующей анимации (нажмите на рисунок):

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

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

Точность управления зависит от разрешения энкодера - количества импульсов на оборот. Количество импульсов может быть от единиц до тысяч штук. Так как энкодер может выступать в качестве датчика положения, то чем больше импульсов - тем точнее будет происходить определение. Этот параметр обозначается как PPR - pulse per revolution.

Но есть небольшой нюанс, а именно похожее обозначение LPR - это количество меток на диске.

А количество обрабатываемых импульсов. Каждая метка на диске даёт 1 прямоугольный импульс на каждом из двух выходов. У импульса есть два фронта - задний и передний. Так как выхода два то с каждого из них мы в сумме получаем 4 импульса значения которых вы можете обработать.

Подключаем к Arduino

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

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

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

Разберем код подробнее, по порядку. В void setup() мы объявили что будем использовать связь через последовательный порт, а затем установили пины 2 и 8 в режим входа. Номера пинов выбираете сами исходя из вашей схемы подключения. Константа INPUT_PULLUP выставляет режим входа, у ардуино есть два варианта:

    INPUT - вход без подтягивающих резисторов;

    INPUT_PULLUP - подключение ко входу подтягивающих резисторов. Внутри микроконтроллера уже есть резисторы, через которые вход соединяется с плюсом питания (pullup).

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

Логика основной программы следующая: если на входе «2» у нас единица - выдаёт в монитор порта H, если нет - L. Таким образом при вращении в одну сторону на мониторе последовательного порта получится что-то вроде этого: LL HL HH LH LL. А в обратную: LL LH HH HL LL.

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

Заключение

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

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

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

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

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

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

Статьи по теме: