Распиновка энкодера с кнопкой. Подключение поворотного энкодера к компьютеру через USB. Фактически у энкодера имеется четыре состояния

Windows 10

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

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

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

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

Совсем коротко, о чем идет речь, т.е. о классификации энкодеров.

Энкодеры это цифровые датчики угла поворота. Другими словами преобразователи угол-код.

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

Энкодеры бывают абсолютные и накапливающие (инкрементальные).

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

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

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

В подобных случаях инкрементальные энкодеры становятся идеальными устройствами управления, установки параметров, выбора меню. Они намного удобнее, чем кнопки ”+” и ”-”.

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

Модуль энкодер KY-040 является механическим датчиком угла поворота, он преобразует угол поворота вращающегося объекта(например вала) в электрические сигналы сдвинутые на 90 градусов относительно друг друга. Данный модуль имеет три вывода - CLK, DT и SW. Сигналы, сдвинутые на 90 градусов относительно друг друга появляются именно на выводах CLK и DT при вращении по/против часовой стрелки, вывод SW используется для получения состояния центральной оси энкодера, которая работает как кнопка.

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

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

Из диаграммы видно что, каждый раз когда сигнал А(контакт CLK энкодера) переходит от высокого уровня к низкому, считывается состояние сигнала Б(контакт DT энкодера). Если сигнал Б даёт высокий уровень сигнала, это означает что вращение энкодера происходит по часовой стрелке. Если сигнал Б даёт низкий уровень сигнала при переходе сигнала А из низкого в высокий уровень, то это означает что вращение энкодера происходит против часовой стрелки. Считывая оба этих сигнала в программе, можно определить направление вращения, также при подсчете импульсов сигнала Б, можно инкрементировать либо декрементировать программный счетчик импульсов.

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

//Временные переменные для хранения уровней сигналов //полученных от энкодера unsigned char encoder_A, encoder_B, encoder_A_prev; //Переменная для отслеживания нажатий кнопки - //центральной оси энкодера static bool SW_State = false; void setup() { //Предварительные установки //Объявления переменных //Инициализации портов и т.д. } void loop() { //CLK подключаем к пину 3 на плате Arduino //DT подключаем к пину 4 на плате Arduino //Считываем значения выходов энкодера //И сохраняем их в переменных encoder_A = digitalRead(3); encoder_B = digitalRead(4); //Если уровень сигнала А низкий, //и в предидущем цикле он был высокий if(!encoder_A && encoder_A_prev) { //Если уровень сигнала В высокий if(encoder_B) { //Значит вращение происходит по часовой стрелке //Здесь можно вставить операцию инкремента //Здесь можно вставлять какие либо свои //операции по обработке данных в нужном направлении } //Если уровень сигнала В низкий else { //Значит вращение происходит против часовой стрелки //Здесь можно вставить операцию декремента //Здесь можно вставлять какие либо свои //операции по обработке данных в нужном направлении } } //Обязательно нужно сохранить состояние текущего уровня сигнала А //для использования этого значения в следующем цикле сканирования программы encoder_A_prev = encoder_A; //Работаем с центральной осью энкодера - кнопкой //Этот кусок кода образует собой как бы перекидной триггер //Считываем значение пина 2 на плате Arduino //к которому подключен контакт SW энкодера //Если центральная ось нажата - то сигнал SW будет иметь низкий уровень if(!digitalRead(2)) { //Если переменная SW_State установлена в false то установить её в true if(!SW_State) { //И запомнить состояние SW_State = true; } //И наоборот - если переменная SW_State установлена в true, //то сбросить её в false else { //И запомнить состояние SW_State = false; } } }

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

#include "LedControl.h" /* * Подключаем библиотеку LedControl.h * и создаём объект класса LedControl * при этом, 7-ми сегметный дисплей с драйвером MAX72xx * должен быть подключен к плате Arduino следующим образом: * Arduino -> Display Module MAX72xx * Arduino -> Display Module MAX72xx * Arduino -> Display Module MAX72xx * Arduino -> Display Module MAX72xx * Arduino -> Display Module MAX72xx * Arduino -> Encoder Module * Arduino -> Encoder Module * Arduino -> Encoder Module */ LedControl lc = LedControl(12, 11, 10, 1); //Проименовываем адреса портов на плате Arduino const int Dir = 4; const int Step = 3; const int Switch = 2; //Counter - переменная для хранения значения счетчика static long Counter = 0; //SW_State - флаг триггер отслеживания нажатия центральной оси static bool SW_State = false; //Временные переменные для хранения уровней сигналов энкодера unsigned char encoder_A, encoder_B, encoder_A_prev; void setup() { //Устройство(7-ми сегментный дисплей) выводим из спящего режима lc.shutdown(0, false); //Установить яркость дисплея на 8 //Всего возможных режимов яркости от 0 до 15 lc.setIntensity(0,8); //Очистить дисплей lc.clearDisplay(0); //Конфигурируем порты для энкодера pinMode(Dir, INPUT); pinMode(Step, INPUT); pinMode(Switch, INPUT); } void loop() { //Считываем значения выходов энкодера //И сохраняем их в переменных encoder_A = digitalRead(Step); encoder_B = digitalRead(Dir); //Если уровень сигнала А низкий, //и в предидущем цикле он был высокий if(!encoder_A && encoder_A_prev) { //Если уровень сигнала В высокий if(encoder_B) { //Значит вращение происходит по часовой стрелке //Наше условие: //Если значение счетчика больше или равно максимальному числу if(Counter >= 99999999) { //Обнулить значение счетчика Counter = 0; } else { //Иначе инкрементировать при каждом щелчке на единицу Counter ++; } } //Если уровень сигнала В низкий else { //Значит вращение происходит против часовой стрелки //Если значение счетчика меньше или равно нулю if(Counter <= 0) { //проинициализировать значение максимальным числом Counter = 99999999; } else { //Иначе декрементировать при каждом щелчке на единицу Counter --; } } } //Обязательно нужно сохранить состояние текущего уровня сигнала А //для использования этого значения в следующем цикле сканирования программы encoder_A_prev = encoder_A; //Работаем с центральной осью энкодера - кнопкой //Этот кусок кода образует собой как бы перекидной триггер //Считываем значение пина 2 на плате Arduino //которомый проименован как Switch //Если центральная ось нажата - то сигнал Switch будет иметь низкий уровень if(!digitalRead(Switch)) { //Если переменная SW_State установлена в false то установить её в true if(!SW_State) { //И запомнить состояние SW_State = true; } //И наоборот - если переменная SW_State установлена в true, //то сбросить её в false else { //И запомнить состояние SW_State = false; } } //Часть программы которая заполняет разряды //семисегментного дисплея значением счетчика long intCounter = Counter; int divCounter; for(int i = 0; i < 8; i ++) { divCounter = intCounter % 10; intCounter = intCounter / 10; if(intCounter == 0 && SW_State) { if(divCounter == 0) { if(i == 0) { lc.setChar(0, 0, "0", false); } else { lc.setChar(0, i, " ", false); } } else { lc.setDigit(0, i, divCounter, false); } } else { lc.setDigit(0, i, divCounter, false); } } }

Видео, как это работает:


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

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

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

Подготовка

Создаем в пустом workspace`е новый проект

Project > Create New Project…

Тип шаблона C > main

Копируем в папку проекта файлы исходника библиотеки для работы с энкодером
encoder.h и encoder.c

Подключаем к нашему проекту файл encoder.c
Правая кнопка мышки в окне workspace и в открывшемся меню Add > Add Files…

Копируем файл bits_macros.h в папку проекта.


Подключаем заголовочные файлы

В начале файла main.c забиваем следующие строки
#include
#include
#include "encoder.h"
#include "bits_macros.h"

Задаем настройки проекта

Project > Options

Тип микроконтроллера
General Options > Target > Processor Configuration > ATMega8535

Разрешение использования имен битов определенных в заголовочных файлах
General Options > System > Enable bit defenitions...

Оптимизация кода по размеру
C/C++ Compiler > Optimisations >Size High

Тип выходного файла
Linker > Output File галочка Override default и поменять расширение на hex
Linker > Format > Other выбрать Intel Standart

Жмем Ок. Сохраняем проект и workspace.
Теперь у нас есть пустой проект с подключенной либой и заданными настройками.

Задача

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

Схема для нашего примера

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

Алгоритм программы

Прямоугольный сигнал генерируется с помощью 16 разрядного таймера Т1, который работает в режиме СТС – сброс при совпадении. Во флэш памяти микроконтроллера храниться массив, содержащий для каждого значения требуемой частоты константу. Для доступа к элементам массива используется переменная pTimerValue. В прерываниях таймера Т1 значение константы считывается и записывается в регистр сравнения.

Для генерации сигнала используется вывод PD5 (OC1A). У него есть альтернативные функции – он может менять свое состояние на противоположное при равенстве счетного регистра и регистра сравнения.

В основной программе в бесконечном цикле while микроконтроллер опрашивает буфер энкодера и в зависимости от его значения уменьшает или увеличивает переменную pTimerValue.

В самом начале main`а располагается код инициализации периферии и необходимых переменных.

Структура программы

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

Это типовая структура построения простых программ. Прерывания естественно происходят в произвольном месте цикла.

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

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

Расчет констант для таймера Т1

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

Тактовая частота микроконтроллера 16 МГц (смотрите схему). Коэффициент предделителя таймера - 256. Он позволяет получить прерывания с любой частотой из нашего диапазона.

Период одного тика таймера будет равен 1/(16 МГц/ 256) = 16 мкс

На выводе PD5 нам нужно получить сигнал частотой 1 Гц. Вывод меняет свое состояние на каждое прерывание таймера и значит, частота прерываний должна быть в 2 раза больше. Для нашего случая - 2 Гц.

Сколько тиков таймера уложится в 2 Герца? (1/2 Гц)/16 мкс = 31250
Это и есть искомая константа.

Остальные значения рассчитываются аналогично. Я для этого обычно использую Exel.


Полученные значения мы помещаем в массив

__flash unsigned int timerValue =
{

сохраняем его в отдельном файле – timer_value.h и подлючаем его к файлу main.c

#include "timer_value.h"

Да, еще нужно добавить парочку констант в этот файл

#define MAX_TIM_VALUE 99
#define MIN_TIM_VALUE 0

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

//программирование AVR на Си

//сайт 17.10.09
#include
#include
#include "encoder.h"
#include "bits_macros.h"
#include "timer_value.h"

//индекс для доступа к элементам массива
volatile unsigned char pTimerValue = 0;

int main(void )
{
//инициализация таймера Т1
TCNT1 = 0;
TCCR1A = (0<TCCR1B = (0<

//настройка вывода PD5 на выход
SetBit(PORTD, PD5);
SetBit(DDRD, PD5);

//ничего не делаем в бесконечном цикле
while (1);
return 0;
}

Думаю, пояснения требует только кусок инициализации таймера.

Обнуление счетного регистра
TCNT1 = 0;

Инициализация конфигурационных регистров таймера Т1.
TCCR1A = (0<TCCR1B = (0<

Где биты WGM13, WGM12, WGM11, WGM10 задают режим работы таймера – СТС,
CS12, CS11, CS10 – определяют коэффициент предделителя таймера –256,

COM1A1, COM1A0 – определяют поведение вывода PD5(OC1F) – в данном случае по сигналу таймера он будет менять свое состояние на противоположное


Инициализация регистра совпадения начальным значением.
OCR1A = timerValue;

Компилируем программу и грузим в микроконтроллер. Светодиод должен моргать с частотой 1 Гц.
В программе нет никаких прерываний. Нет никаких манипуляций с выводом PD5. Однако светодиод моргает!

Программа

Теперь нужно “прикрутить” к этой программе энкодер. Зададим настройки в хедер файле encoder.h – порт и выводы, к которым подключен энкодер, значения констант.


#define PORT_Enc PORTA
#define PIN_Enc PINA
#define DDR_Enc DDRA
#define Pin1_Enc 2
#define Pin2_Enc 1

#define RIGHT_SPIN 0x01
#define LEFT_SPIN 0xff

Хедер содержит прототипы трех функций. Вспомним их назначение.

void ENC_InitEncoder(void) настраивает выводы микроконтроллера, к которым подключен энкодер на вход. Эту функцию нужно вызвать в начале main`а.


void ENC_PollEncoder(void) – однократно опрашивает энкодер, анализирует текущее и предыдущее состояния и записывает в буфер соответствующие константы (RIGHT_SPIN и LEFT_SPIN). Эта функция будет сидеть в прерывании таймера Т0.


unsigned char ENC_GetStateEncoder(void) – возвращает содержимое буфера энкодера. Если поворот на одну позицию не был зафиксирован – функция вернет 0, если поворот был зафиксирован функция вернет значение соответствующей константы. При этом значение буфера очистится. Эта функция будет вызываться в основном программе – в цикле while.


Дополняем нашу программу. Можете попробовать сделать это самостоятельно.

//программирование AVR на Си
//пример использования энкодера
//сайт 17.10.09

#include
#include
#include "encoder.h"
#include "bits_macros.h"
#include "timer_value.h"

#define TCNT0_const 253
#define TCCR0_const 5

volatile unsigned char pTimerValue = 0;

int main(void )
{
ENC_InitEncoder();

//инициализация таймера т0
TCNT0 = TCNT0_const;
TCCR0 = TCCR0_const;

//инициализация таймера т1
TCNT1 = 0;
TCCR1A = (0<TCCR1B = (0<OCR1A = timerValue;

//разрешение прерываний от таймеров
//т0 - по переполнению, т1 - по совпадению

TIMSK = (1<

//настраиваем PD5 на выход
SetBit(PORTD, PD5);
SetBit(DDRD, PD5);

__enable_interrupt ();
while (1){
//считываем содержимое буфера энкодера
//после считывания он очищается

unsigned char stateEnc = ENC_GetStateEncoder();

//если не пустой
if (stateEnc != 0){
//определяем направление вращения и изменяем переменную timerValue
if (stateEnc == RIGHT_SPIN){
if (pTimerValue == MAX_TIM_VALUE) pTimerValue = MIN_TIM_VALUE;
else pTimerValue++;
}
if (stateEnc == LEFT_SPIN) {
if (pTimerValue == MIN_TIM_VALUE) pTimerValue = MAX_TIM_VALUE;
else pTimerValue--;
}
}
}
return 0;
}

//опрос энкодера
#pragma vector=TIMER0_OVF_vect
__interrupt void timer0_ovf_my(void )
{
TCNT0 = TCNT0_const;
ENC_PollEncoder();
}

#pragma vector=TIMER1_COMPA_vect
__interrupt void timer1_compa_my(void )
{
//обновляем значение регистра стравнения
OCR1A = timerValue;
}

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

if (stateEnc != 0) {
pTimerValue = pTimerValue + stateEnc;
if (pTimerValue == (MAX_TIM_VALUE + 1)) pTimerValue = MIN_TIM_VALUE;
else if (pTimerValue == (MIN_TIM_VALUE - 1)) pTimerValue = MAX_TIM_VALUE;
}

При вращении энкодера вправо pTimerValue складывается с 1, то есть инкрементируется.

При вращении энкодера влево pTimerValue складывается с 0хff, что равносильно вычитанию 1. Одна и та же операция, а результат прямо противоположный.

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

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

Виды и принцип действия

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

У такого энкодера, показанного на рисунке, ручка также может применяться в качестве кнопки. Это является вспомогательной функцией конкретного вида энкодера.

По типу выдаваемых данных энкодеры делятся на две большие группы:
  1. Абсолютные.
  2. Инкрементальные.
Абсолютные энкодеры

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

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

У данного алгоритма имеются свои недостатки. Из таблицы 1 виден порядок выдаваемых значений n-го энкодера. Стоит обратить внимание на две последние строчки, переход от 127 на 128.

Таблица 1

Здесь меняются абсолютно все биты. В идеальном энкодере они все меняются одновременно и нет никаких проблем. Практически в реальном энкодере биты меняются быстро, однако не одновременно. И в какой-то момент на выходе энкодера оказывается совершенно произвольное значение. Так как меняются все биты, следовательно, у энкодера будет произвольное значение от нуля до всех единиц.

Справа изображен пример такого переключения. Чем это может грозить? Разберем пример. Микроконтроллер с помощью двигателя управляет валом и поворачивает его на определенный угол. В определенный момент при переключении со 127 на 128 ячейку он получает определенное случайное значение. Контроллер делает вывод, что вал находится совершенно в другом месте, в отличие от фактического места, и начинает его вращать в другую сторону, с другой скоростью и т.д.

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

Код Грея

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

Таблица 2

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

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

Инкрементальные энкодеры

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

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

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

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

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

Фактически у энкодера имеется четыре состояния:
  1. Две единицы.
  2. Ноль и единица.
  3. Ноль и ноль.
  4. Единица и ноль.

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

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

Оптические энкодеры

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

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

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

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

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

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