Автор Тема: Как работать с энкодером  (Прочитано 39040 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн blindman

  • Старожил
  • *****
  • Сообщений: 797
  • Репутация: 13
  • Андрей
  • Поблагодарили: +17
Как работать с энкодером
« : 14 Июня 2012, 06:07:29 »
0
Меня попросили объяснить, как работать с энкодером. Считаю, что эта информация может быть полезна начинающим программистам. Поэтому публикую здесь

Я рассмотрю принципы работы с наиболее распространенными механическими энкодерами, имеющими фиксированные положения вала ("щелчки") - 1 или 2 на импульс. В таком энкодере имеются 2 механических контакта. При равномерном вращении вала, ток через 2 контакта будет иметь вид прямоугольных импульсов, сдвинутых относительно друг друга приблизительно на 90 градусов, отсюда и название для таких энкодеров - квадратурные:

(Извините, но у Вас нет доступа в Галерею)

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

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

Самый простой способ опроса энкодера: в прерывании по фронту (или спаду) импульса от одного из контактов, опрашивается состояние второго контакта, если он замкнут - то вращение в одну сторону, если разомкнут - в другую. Если внимательно посмотреть на рисунок, станет ясно, что такой способ будет работать с энкодером с 1 щелчком на импульс. С 2 щелчками будет распознаваться только каждый второй щелчок. Чтобы это исправить, с такими энкодерами надо использовать прерывание, срабатывающее и по фронту, и по спаду, и в прерывании считывать состояния обоих контактов. Если они одинаковые - вращение в одну сторону, если разные - в другую.

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

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

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

Посмотрим на рисунок. Для энкодера с 1 щелчком на импульс, вращению по часовой стрелке соответствует переход из состояния 0 в состояние 3, а против часовой - из 2 в 1. Может возникнуть вопрос - откуда взялся переход из 3 в 0, если по рисунку идет сначала переход из 3 в 1, и потом из 1 в 0? Дело в том, что переход из 3 в 1 мы "не увидим", так так фиксируем смену состояния при переключении только одного из контактов. Как все это помогает бороться с дребезгом? Представим, что энкодер находится в устойчивом состоянии (3). Начинаем вращать вал по часовой стрелке, на 1 щелчок. Будут зафиксированы переходы 3-0-1-0-1-0-1-0-3-2-3-2-3-2-3. Переход из 0 в 3 мы увидели только один, то есть дребезг подавлен. Аналогично и при вращении в обратную сторону.

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

Но как сказано, "Talk is cheap. Show me the code" :) Приведу пример программы работы с энкодером.

Первое. Контроллер ATMega32, энкодер с 1 щелчком на импульс, подключен к контактам PORTD2 и PORTD3. Контроллер этот выбран потому, что у меня есть с ним удобная макетка, на которой проверялся код. Перенести на другой контроллер думаю не составит большого труда.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <util/delay.h>

// предыдущие состояния энкодера
// в битах 0,1 - текущее состояние
// в битах 2,3 - состояние перед текущим изменением
static uint8_t encoder_state;

// количество подсчитанных щелчков энкодера.
// при вращении в одну сторону увеличивается, в другую - уменьшается.
// Переменная объявлена как volatile, потому что ее состояние изменяется
// в прерывании.
static volatile int encoder_pos;

// макрос для удобства. Распихивает 2 числа, соответствующих
// текущему, и предыдущему состояниям,
// по нужным битам
#define TR(x, y) (((x) << 2) | (y) )

// процедура сканирования энкодера
static void scan_encoder(void)
{
// считываем состояние энкодер
uint8_t pins = (PIND & 0x0C) >> 2;
// если состояние 0-го бита не изменилось с предыдущего вызова, ничего не делаем
if ((encoder_state & 1) == (pins & 1))
return;
// задвигаем новое состояние в переменную, забывая самое старое из
// 2 сохраненных состояний
encoder_state = ((encoder_state << 2) | pins) & 0x0F;

// проверяем условия, и изменяем количество щелчков, если нужно
if (encoder_state == TR(0,3)) {
encoder_pos++;
}
if (encoder_state == TR(2, 1)) {
encoder_pos--;
}
}

// прерывание от таймера, раз приблизительно в 2 мс
ISR(SIG_OVERFLOW0)
{
// вызываем опрос энкодера
scan_encoder();
}

// это основная программа
int main(void)
{
// на ногах PORTC5  и PORTB3 у меня светодиоды, настраиваю эти ноги как выходы
DDRC |= (1 << 5);
DDRB |= (1 << 3);

PORTD |= 0x0C; // подтяжка на входах, к которым подключен энкодер - но лучше использовать внешнюю подтяжку

// прескалер таймера0 = 64, при тактовой частоте 8 МГц,
// переполнение будет приблизительно раз в 2 мс
TCCR0 = 3 << CS00;

// разрешаю прерывание по переполнению таймера.
TIMSK |= 1 << TOIE0;
// глобальное разрешение прерываний
sei();

for (;;) {
// в бесконечном цикле, берем число, которое насчитала процедура
// обработки энкодера, и обнуляем. Делать это надо при запрещенных
// прерываниях, чтобы не сбился счет.
cli();
int n = encoder_pos;
encoder_pos = 0;
sei();

// и мигаем светодиодом столько раз, сколько щелчков насчитано
// Таймер продолжает тикать, энкодер опрошивается. Новое количество
// щелчков мы подхватим на следующем проходе цикла
while (n) {
if (n < 0) { // если против часовой - мигаем одним светодиодом
PORTC |= (1 << 5);
n++;
} else { // по часовой - другим
PORTB |= (1 << 3);
n--;
}
_delay_ms(30);
PORTC &= ~(1 << 5);
PORTB &= ~(1 << 3);
_delay_ms(30);
}
}
}


Второе. Контроллер ATMega32, энкодер с 2 щелчками на импульс, подключен к контактам PORTD2 и PORTD3.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <util/delay.h>

// предыдущие состояния энкодера
// в битах 0,1 - текущее состояние
// в битах 2,3 - состояние перед текущим изменением
// в битах 4,5 - состояние перед предыдущим изменением
static uint8_t encoder_state;

// количество подсчитанных щелчков энкодера.
// при вращении в одну сторону увеличивается, в другую - уменьшается.
// Переменная объявлена как volatile, потому что ее состояние изменяется
// в прерывании.
static volatile int encoder_pos;

// макрос для удобства. Распихивает 3 числа, соответствующих
// текущему, предыдущему и пред-предыдущему состояниям,
// по нужным битам
#define TR(x, y, z) (((x) << 4) | ((y) << 2) | (z) )

// процедура сканирования энкодера
static void scan_encoder(void)
{
// считываем состояние энкодер
uint8_t pins = (PIND & 0x0C) >> 2;
// если не изменилось с предыдущего вызова, ничего не делаем
if ((encoder_state & 3) == pins )
return;
// задвигаем новое состояние в переменную, забывая самое старое из
// 3 сохраненных состояний
encoder_state = ((encoder_state << 2) | pins) & 0x3F;

// проверяем условия, и изменяем количество щелчков, если нужно
if (encoder_state == TR(3, 1, 0) || encoder_state == TR(0,2,3)) {
encoder_pos++;
}
if (encoder_state == TR(0, 1, 3) || encoder_state == TR(3,2,0)) {
encoder_pos--;
}
}

// прерывание от таймера, раз приблизительно в 2 мс
ISR(SIG_OVERFLOW0)
{
// вызываем опрос энкодера
scan_encoder();
}

// это основная программа
int main(void)
{
// на ногах PORTC5  и PORTB3 у меня светодиоды, настраиваю эти ноги как выходы
DDRC |= (1 << 5);
DDRB |= (1 << 3);

PORTD |= 0x0C; // подтяжка на входах, к которым подключен энкодер - но лучше использовать внешнюю подтяжку

// прескалер таймера0 = 64, при тактовой частоте 8 МГц,
// переполнение будет приблизительно раз в 2 мс
TCCR0 = 3 << CS00;

// разрешаю прерывание по переполнению таймера.
TIMSK |= 1 << TOIE0;
// глобальное разрешение прерываний
sei();

for (;;) {
// в бесконечном цикле, берем число, которое насчитала процедура
// обработки энкодера, и обнуляем. Делать это надо при запрещенных
// прерываниях, чтобы не сбился счет.
cli();
int n = encoder_pos;
encoder_pos = 0;
sei();

// и мигаем светодиодом столько раз, сколько щелчков насчитано
// Таймер продолжает тикать, энкодер опрошивается. Новое количество
// щелчков мы подхватим на следующем проходе цикла
while (n) {
if (n < 0) { // если против часовой - мигаем одним светодиодом
PORTC |= (1 << 5);
n++;
} else { // по часовой - другим
PORTB |= (1 << 3);
n--;
}
_delay_ms(30);
PORTC &= ~(1 << 5);
PORTB &= ~(1 << 3);
_delay_ms(30);
}
}
}

В этих примерах для периодического вызова процедуры обработки энкодера, используется прерывание от таймера. Но можно обойтись и без него, если вызывать процедуру прямо в главном цикле программы - но для этого он должен быть достаточно шустрым. Между вызовами процедуры обработки энкодера должно быть не более 5 мс, иначе будут пропуски щелчков, и другие неприятные вещи
« Последнее редактирование: 14 Июня 2012, 06:11:48 от blindman »

Оффлайн WolfTheGrey

  • Старожил
  • *****
  • Сообщений: 2135
  • Репутация: 6
  • Алексей
  • Поблагодарили: +95
Re: Как работать с энкодером
« Ответ #1 : 14 Июня 2012, 14:04:42 »
0
Что такое один щелчек на импульс и 2 щелчка на импульс? Что такое импульс?
И у тебя схема не правильная, когда крутиш в одну сторону, то сперва замыкается на землю In0, потом In1, потом оба сбрасываются.
Когда крутишь в обратную сторону, то In0 и In1 сразу замыкаются на землю, потом размыкается In1 , а после него размыкается In0.

Основываясь на это, вывод In0 прицепляют к ТС, и заводят событие по прерыванию. Где смотрят, если In1 замкнут, то уменьшают, если нет, то увеличивают.
« Последнее редактирование: 14 Июня 2012, 14:09:45 от WolfTheGrey »

Оффлайн blindman

  • Старожил
  • *****
  • Сообщений: 797
  • Репутация: 13
  • Андрей
  • Поблагодарили: +17
Re: Как работать с энкодером
« Ответ #2 : 14 Июня 2012, 14:54:56 »
0
WolfTheGrey, к сожалению не знаю имени. Я с энкодерами не одну конструкцию сделал, и с механическими, и с оптическими. и с теми что рукой крутят, и на валах двигателей с большой частотой вращения. Схема правильная. Если бы энкодеры работали так, как ты описываешь, то подавить дребезг контактов было бы невозможно в принципе.

Импульс - это и есть импульс. Применительно к энкодеру -  замкнутое состояние контакта.  А щелчок,или клик - это устойчивое состояние вала, шаг. Можешь почувствовать эти клики, покрутив колёсико мышки.
1 клик на импульс - это значит что чтобы контакт сначала разомкнулся, а потом замкнулся, нужно повернуть вал на 1 шаг
2 клика на импульс - это значит что чтобы контакт сначала разомкнулся, а потом замкнулся, нужно повернуть вал на 2 шага
« Последнее редактирование: 14 Июня 2012, 15:16:03 от blindman »

Оффлайн lgedmitry

  • Старожил
  • *****
  • Сообщений: 4621
  • Репутация: 30
  • Сергей, Рыбинск
  • Поблагодарили: +701
Re: Как работать с энкодером
« Ответ #3 : 27 Февраля 2013, 11:57:08 »
0
Таки разобрался. Хоть и долго провозился, ибо Андрей не по-нашему программирует ??? :d_know: ??? :d_know:
Ну не учили нас таким языкам. Вот Турбопаскаль 5.0 - учили, но очень давно. CoDeSyS учили совсем недавно, но очень сжато. Ещё был Бейсик для ROBOTRON-1715, но ещё давнее, чем турбопаскаль.
Вот что вышло в итоге:
Tim0_isr:                                                   'частотомер для измерения скорости сэмплирования по счётчику 1, подключенному к выходу lrclk dir9001
If Pinb.6 = 0 Then If Timer1 > 102 Then Let Freq = 1 Else Let Freq = 0
Let Timer1 = 0
                                                            'энкодер по-хабаровски
If Enco <> Pind.6 Then
Let Enc1 = Enc
Let Enc = Pind
Let Enc = Enc And &B11000000

   If Enc = &HC0 And Enc1 = 0 Then
      If Volprint1 > 0 Then Decr Volprint1                                            'энкодер висит на PIND.6 PIND.7, общим контактом на корпус
      End If
   If Enc = &H40 And Enc1 = &H80 Then
      If Volprint1 < 80 Then Incr Volprint1
      End If
   End If
   Let Enco = Pind.6
Return

Работает на уррра!
А встроенная в BASCOM энкодеропроцедура почему-то работает через  _!_

Андрей, спасибище! :v: :v: :v: :v: :v: :v: :v: :v: :v: :v: :v: :v: :v: :v: :v: :v: :v: :v:

Оффлайн blindman

  • Старожил
  • *****
  • Сообщений: 797
  • Репутация: 13
  • Андрей
  • Поблагодарили: +17
Re: Как работать с энкодером
« Ответ #4 : 27 Февраля 2013, 12:23:29 »
0
Андрей не по-нашему программирует ??? :d_know: ??? :d_know:
Ну не учили нас таким языкам
Меня тоже не учили, вот так и разбирался, глядя на чужой код. Еще в школе когда учился, была у меня книжка про контроллеры 8051. Но ни самих контроллеров, ни тем более компьютера было не купить. Так в одной тетрадке рисовалась схема, а другая использовалась в качестве отладчика программ. Слева код на ассемблере, справа состояние переменных на каждом шаге. Сколько я таких тетрадок исписал - не сосчитать  :%):

Сергей, я рад, что все получилось! А DIR9001 всегда на выход импульсы выдает, даже если входного сигнала нет?

Оффлайн lgedmitry

  • Старожил
  • *****
  • Сообщений: 4621
  • Репутация: 30
  • Сергей, Рыбинск
  • Поблагодарили: +701
Re: Как работать с энкодером
« Ответ #5 : 27 Февраля 2013, 12:29:52 »
0
А DIR9001 всегда на выход импульсы выдает, даже если входного сигнала нет?
Ага  выдаёт какие-то непонятные 25кГц. Поэтому на PINB.6 я error c неё засунул, чтоб всякую ерунду не измерять. ???

Оффлайн L0ki

  • Старожил
  • *****
  • Сообщений: 1740
  • Репутация: 7
  • Поблагодарили: +30
Re: Как работать с энкодером
« Ответ #6 : 28 Февраля 2013, 20:21:50 »
0
А еще существует вот такое лобовое решение как поставить отдельный мелкий МК на обработку дребезга энкодера:
http://rgb73.mylivepage.ru/wiki/1952/591_%D0%AD%D0%BD%D0%BA%D0%BE%D0%B4%D0%B5%D1%80.
??? иногда может быть полезно, когда ресурсы основного МК сильно заняты.

Оффлайн lgedmitry

  • Старожил
  • *****
  • Сообщений: 4621
  • Репутация: 30
  • Сергей, Рыбинск
  • Поблагодарили: +701
Re: Как работать с энкодером
« Ответ #7 : 01 Марта 2013, 09:27:08 »
0
 :off: :off: :off: :off: Там камушек микрочиповский. А скажите камрады, есть в них что-нить, чего у атмег нету. Я в смысле, стОит ли эту кухню освоить как-нить на досуге ;-[ ;-[ ;-[ ;-[

Оффлайн L0ki

  • Старожил
  • *****
  • Сообщений: 1740
  • Репутация: 7
  • Поблагодарили: +30
Re: Как работать с энкодером
« Ответ #8 : 01 Марта 2013, 11:12:54 »
0
Там камушек микрочиповский.

ну а в чем проблема то ?
заливаем в него готовую прошивку и не паримся  :)
Если нечем шить, то упрощенный вариант PICkit2 спаять не так уж и сложно:
http://dmitrstas.ucoz.ru/publ/chasy/programmatory/pickit_2_lite_studencheskij_usb_programmator_pic_mikrokontrollerov/8-1-0-68
Либо купить у китайцев (на ебее например) клон пиккита, он стоит далеко не запредельные деньги.
Или же  :D "религия" не позволяет соединять вместе атмел и микрочип ?  ;D

Оффлайн WolfTheGrey

  • Старожил
  • *****
  • Сообщений: 2135
  • Репутация: 6
  • Алексей
  • Поблагодарили: +95
Re: Как работать с энкодером
« Ответ #9 : 01 Марта 2013, 11:17:19 »
0
Цитировать (выделенное)
Я в смысле, стОит ли эту кухню освоить как-нить на досуге   
все одинаковое. Разница в наборе поддерживаемых команд. У атмела на сотню больше, микрочип может только складывать и сдвигать.
Еще не официально, но народ говорит, что атмел стабильнее работает.

Оффлайн L0ki

  • Старожил
  • *****
  • Сообщений: 1740
  • Репутация: 7
  • Поблагодарили: +30
Re: Как работать с энкодером
« Ответ #10 : 01 Марта 2013, 11:24:30 »
0
А скажите камрады, есть в них что-нить, чего у атмег нету.
Есть.... ???
Ну частотомер гоооораздо более высокочастотный на пиках получается чем на аврках.
Полноценный аппаратный USB во многих пиках есть.
Для старших моделей пиков есть халявный код от микрочипа полноценного TCP-IP стека.

Что еще в них есть -  х/з я не пиковод  :d_know: ,
с самого момента их появления не по душе они мне как то....,
в частности крайне дебильно-уродским (с моей личной точки зрения) набором команд.
Вот и сижу на х51, а и аврках.
Сейчас правда  вот начал потихоньку ковыряться в STM32  :v:
Их (армы) вот точно есть смысл осваивать.

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


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


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


и добавил...
P.S.
строго imho.
Из идеологии/архитектуры атмег на настоящий момент выжато все что можно,
дальнейшее их тупое наращивание опять же imho бессмысленно.
Такой девайс как хмега - это уже тупик.  :facepalm:
« Последнее редактирование: 01 Марта 2013, 11:42:57 от L0ki »

Оффлайн xar

  • Старожил
  • *****
  • Сообщений: 5223
  • Репутация: 16
  • Ренат
  • Поблагодарили: +271
Re: Как работать с энкодером
« Ответ #11 : 01 Марта 2013, 11:52:21 »
0
да в каком месте с фьюзами геморрой то... один два камня убил больше не будешь.
с атмела на пик прыгать смысла нет наверно.

Оффлайн L0ki

  • Старожил
  • *****
  • Сообщений: 1740
  • Репутация: 7
  • Поблагодарили: +30
Re: Как работать с энкодером
« Ответ #12 : 01 Марта 2013, 12:37:19 »
0
с атмела на пик прыгать смысла нет наверно.
Тоже придерживаюсь такого же мнения.
Смысл есть осваивать АРМы, ибо за ними точно будущее,
ибо атмеги в своем развитии уже зашли в тупик (XMega).
А вот использовать чьи-то уже готовые разработки на пиках,
(в том числе и в своих проектах на атмелах)
я считаю что смысл есть.


и добавил...
P.S.
да и вообще... це он и в африке це   :d_know:
и если не углубляться в ассемблер,
то лично меня "прыганье" между х86, х51, AVR, и STM32 не особо напрягает  :)
« Последнее редактирование: 01 Марта 2013, 12:46:17 от L0ki »

Оффлайн Dim1112

  • Постоялец
  • ***
  • Сообщений: 165
  • Репутация: 1
  • Дмитрий г. Сумы
  • Поблагодарили: +6
Re: Как работать с энкодером
« Ответ #13 : 01 Марта 2013, 19:28:42 »
0
Андрей, спасибо за код! Я частенько энкодеры колхозю на шаговиках от 5-ти дюймовых дисководов, мне нравится как в них "шажки" щелкают и нет дребезга.
PIC ICD - рулит!
« Последнее редактирование: 01 Марта 2013, 19:43:26 от Dim1112 »

Оффлайн L0ki

  • Старожил
  • *****
  • Сообщений: 1740
  • Репутация: 7
  • Поблагодарили: +30
Re: Как работать с энкодером
« Ответ #14 : 01 Марта 2013, 23:50:00 »
0
Кстати, насчет таких энкодеров (из ШД),
с ними можно сделать одну очень красивую "фишку".
Насколько мне известно впервые "это" придумал и применил Алекс Торрес.
У него такой шаговик стоял как энкодер для управления громкостью увеселителя.
Причем помимо того что шаговик у него работал как энкодер,
при управлении с пульта ДУ он еще и соответствующим образом вращался.  :v:
(схемотехнически ничего хитрого - добавить ключи).

Оффлайн Dim1112

  • Постоялец
  • ***
  • Сообщений: 165
  • Репутация: 1
  • Дмитрий г. Сумы
  • Поблагодарили: +6
Re: Как работать с энкодером
« Ответ #15 : 02 Марта 2013, 08:13:57 »
0
В масспектрометре видел такую "крутилку громкости" еще лет 15 назад, а сколько этой идее было до этого и кто автор - трудно сказать.  Да и сам принцип не нов, к примеру, в трехфазных безколлекторных безсенсорных двигателях положение ротора отслеживается именно по такому принципу.

Оффлайн lgedmitry

  • Старожил
  • *****
  • Сообщений: 4621
  • Репутация: 30
  • Сергей, Рыбинск
  • Поблагодарили: +701
Re: Как работать с энкодером
« Ответ #16 : 09 Октября 2013, 12:53:18 »
0
blindman, Андрей, я тут Сей мучить начал.
Ну, и конечно, твою энкодерную разработку к себе в программу пихнул.
Есть у мене 2 вопроса в этой связи:
а) Объясни, плиз, для чего работу с переменной encoder_pos ты осуществляешь только при выключенных прерываниях?
Я, глядючи на тебя, тоже написал:
volatile signed char encoder_pos;
signed char volume;


//...


static void encoder(void){// считываем состояние энкодер
char pins = (PINB & 0xC0) >> 2;
// если состояние 0-го бита не изменилось с предыдущего вызова, ничего не делаем
    if ((encoder_state & 16) == (pins & 16))
return;
// задвигаем новое состояние в переменную, забывая самое старое из
// 2 сохраненных состояний
encoder_state = ((encoder_state << 2) | pins) & 0xF0;
    // проверяем условия, и изменяем количество щелчков, если нужно
if (encoder_state == TR(0,48)) encoder_pos--;
if (encoder_state == TR(32,16)) encoder_pos++; 
    }

//...


void main(void)
{

//...

while (1)

// в бесконечном цикле, берем число, которое насчитала процедура
// обработки энкодера, и обнуляем. Делать это надо при запрещенных
// прерываниях, чтобы не сбился счет.
#asm("cli")
volume = volume+encoder_pos;
encoder_pos = 0;
#asm("sei")
        if (volume<0)volume=0;
        if (volume>31)volume=31;     

//...}

вместо того, чтоб написать так, как моя примитивная логика подсказывает:
static void encoder(void){// считываем состояние энкодер
char pins = (PINB & 0xC0) >> 2;
// если состояние 0-го бита не изменилось с предыдущего вызова, ничего не делаем

    if ((encoder_state & 16) == (pins & 16))
return;
// задвигаем новое состояние в переменную, забывая самое старое из
// 2 сохраненных состояний
encoder_state = ((encoder_state << 2) | pins) & 0xF0;

// проверяем условия, и изменяем количество щелчков, если нужно
if ((encoder_state == TR(0,48)) && (encoder_pos<31)) encoder_pos++;
if ((encoder_state == TR(32,16)) && (encoder_pos>0)) encoder_pos--; 
   
    }

и б) Ты зачем-то в прерывании interrupt [TIM0_OVF] void timer0_ovf_isr(void) просто ссылаешься на процедуру обработки энкодера, вместо того, чтоб прямо в нём её и написать. Полагаю, это для того, чтоб перед процедурой написать сакральное слово static. Я спросил про него у Яндекса. Тот мне предложил разъяснение ещё более сакральными словами. Можешь ли ты разъяснить как-то ситуацию?

Оффлайн blindman

  • Старожил
  • *****
  • Сообщений: 797
  • Репутация: 13
  • Андрей
  • Поблагодарили: +17
Re: Как работать с энкодером
« Ответ #17 : 09 Октября 2013, 14:06:15 »
0
Попробую объяснить  ???

а) Переменная encoder_pos является разделяемым ресурсом - к ней независимо друг от друга могут обращаться 2 потока программы. Один поток - это основная программа, второй - обработчик прерывания. Их работа никак не синхронизирована, а значит, может возникнуть ситуация, когда основная программа считает значение переменной, потом в прерывании она будет изменена, а затем основная программа обнулит переменную. При этом будет пропущен один клик энкодера. Запретив прерывания, мы гарантируем, что обработчик прерывания не вклинится в неподходящий момент. Если бы программа была построена так, что изменять значение переменной может только один поток, и при этом значение переменной может быть прочитано за одну операцию процессора  - то можно было бы обойтись без запрета прерываний. Проиллюстрирую кодом:

static volatile int8_t encoder_pos = 0; // 1 байт !
static int8_t old_encoder_pos = 0;

static void encoder(void){ // эта подпрограмма вызывается только из обработчика прерываний !
// bla
// bla
// bla
  if (....) encoder_pos++;
  else if (....) encoder_pos--;
// bla
// bla
// bla
}

void main(void) {
// bla
// bla
// bla
// прерывания не запрещаем !
  int8_t pos = encoder_pos;
  int8_t delta = encoder_pos - old_encoder_pos;
  old_encoder_pos = pos;
  volume += delta;
// bla
// bla
// bla
}

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

б) это сделано исключительно для удобства чтения программы. Модификатор static при функции или глобальной переменной говорит о том, что функция или переменная доступна только в пределах данного исходного файла (или модуля в терминах языка C). Компиляторы современные достаточно умны, чтобы увидеть: функция доступна только в данном модуле, и вызывается только один раз. А значит ее можно не компилировать как функцию, а просто встроить в программу в том месте, где она вызывается. То есть, при включенной оптимизации, сгенерированный код будет точно таким, как если бы я просто встроил тело функции scan_encoder в обработчик прерываний.

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

int foo(void) { // эта функция всегда возвращает 1
  int a = 0; // переменная инициализируется при каждом вызове foo()
  a++;
  return a;
}

int bar(void) { // эта функция возвращает 1, 2, 3 и т. д.
  static int a = 0; // переменная инициализируется только один раз
  a++;
  return a;
}

Оффлайн lgedmitry

  • Старожил
  • *****
  • Сообщений: 4621
  • Репутация: 30
  • Сергей, Рыбинск
  • Поблагодарили: +701
Re: Как работать с энкодером
« Ответ #18 : 09 Октября 2013, 14:20:27 »
0
blindman, Спасибо, Андрей. Кажется, начинает доходить ;)

Оффлайн Mitrych

  • Знакомый
  • *
  • Сообщений: 24
  • Репутация: 0
  • Дмитрий Набережные Челны
  • Поблагодарили: 0
Re: Как работать с энкодером
« Ответ #19 : 04 Ноября 2015, 22:31:16 »
0
Товарищи, подскажите пожалста, насколько сложно вместо переменника поставить энкодер и схему для него? Мне для блока питания, ручку регулировки напряжения заменить. Переменники китайские, быстро выходят из строя, напряжение прыгает. Замучился. Менять - спасает на время, быстро истираются, а хорошие типа Bourns дороговато.
Было бы здорово вместо крутилок поставить одну крутилку без конца и начала и устанавливать напряжение с какой-нить малой величиной, скажем, 0.1В


Оффлайн Althair

  • Старожил
  • *****
  • Сообщений: 1766
  • Репутация: 11
  • Поблагодарили: +348
Re: Как работать с энкодером
« Ответ #20 : 05 Ноября 2015, 00:03:05 »
0
Учитывая "китайскую родословную", не проще ли заменить резисторы на заведомо надежные? Энкодер требует подхода с контроллером, разводить сыр-бор ради регулировки напряжения в китайском БП - сомнительное мероприятие.
Намёк: регулятор Никитина - суть тот же электронный резистор. Гляньте схему/принцип, когда не лень...

Оффлайн andrik

  • Прохожий
  • Сообщений: 3
  • Репутация: 0
  • Андрей Запорожье
  • Поблагодарили: 0
Re: Как работать с энкодером
« Ответ #21 : 05 Ноября 2015, 00:32:39 »
0
С энкодером работать на самом деле очень просто. Код приводить не буду, там одна строка всего.
Контакт  А подключается к входу с прерыванием.
Контакт В считывается во время прерывания, если 1 увеличиваем переменную, если 0 уменьшаем.

Оффлайн lgedmitry

  • Старожил
  • *****
  • Сообщений: 4621
  • Репутация: 30
  • Сергей, Рыбинск
  • Поблагодарили: +701
Re: Как работать с энкодером
« Ответ #22 : 05 Ноября 2015, 05:09:25 »
0
С энкодером работать на самом деле очень просто. Код приводить не буду, там одна строка всего.
Контакт  А подключается к входу с прерыванием.
Контакт В считывается во время прерывания, если 1 увеличиваем переменную, если 0 уменьшаем.
Это если дребезга нет ;)

Оффлайн andrik

  • Прохожий
  • Сообщений: 3
  • Репутация: 0
  • Андрей Запорожье
  • Поблагодарили: 0
Re: Как работать с энкодером
« Ответ #23 : 05 Ноября 2015, 08:58:18 »
0
Дребезг есть всегда, но с ним можно бороться.

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

Оффлайн Mitrych

  • Знакомый
  • *
  • Сообщений: 24
  • Репутация: 0
  • Дмитрий Набережные Челны
  • Поблагодарили: 0
Re: Как работать с энкодером
« Ответ #24 : 05 Ноября 2015, 16:26:17 »
0
..не проще ли заменить резисторы на заведомо надежные?
Спасибо за ответ, заведомо надежные - подскажите какие? Хотя бы фирму, по названиям не сталкивался, на чипдипе китай и боурнс, первое - фигня, второе дорого.
Значит с энкодером смысла нет огород городить.
Ну а регулятор Никитина - на главной странице этого сайта нашел статью, очень впечатляюще, вообще не вариант.
Тогда запасной вариант - поставить двухкнопочное управление, схема вроде делается на счетчике и дешифраторе, я когда -то видел в журнале радио, щас уже не вспомню даже какого года журнал..

Оффлайн ДДО

  • Старожил
  • *****
  • Сообщений: 1563
  • Репутация: 17
  • Митрич. 23.08.46 - 12.03.16†
  • Поблагодарили: +114
Re: Как работать с энкодером
« Ответ #25 : 05 Ноября 2015, 16:56:14 »
0
А многооборотный подстроечник чем не годится? Если конечно часто напряжение не менять, а только периодически подстраивать

Оффлайн xar

  • Старожил
  • *****
  • Сообщений: 5223
  • Репутация: 16
  • Ренат
  • Поблагодарили: +271
Re: Как работать с энкодером
« Ответ #26 : 05 Ноября 2015, 17:23:44 »
0
andrik, ни в коем случае. При быстром вращении будет херня, которую программно уже не исправить. Вообще характер дребезга энкодера таков, что частота дребезга может быть сравнима с частотой переключения, так что даже классическими программными способами бороться с ним неэффективно. Кроме того все это не решает проблемы когда пользователь теребит ручку в пределах одного щелчка. Андрей выше описал отличный способ. Кроме него можно нагуглить еще кучу вариантов.

Оффлайн andrik

  • Прохожий
  • Сообщений: 3
  • Репутация: 0
  • Андрей Запорожье
  • Поблагодарили: 0
Re: Как работать с энкодером
« Ответ #27 : 05 Ноября 2015, 17:44:14 »
0
Каюсь, сейчас только прочел, очень интересно, нужно будет по пробовать.

Оффлайн lgedmitry

  • Старожил
  • *****
  • Сообщений: 4621
  • Репутация: 30
  • Сергей, Рыбинск
  • Поблагодарили: +701
Re: Как работать с энкодером
« Ответ #28 : 05 Ноября 2015, 18:10:39 »
0
вот ещё что-то похожее на метод Андрея. Только более топорно: http://easyelectronics.ru/avr-uchebnyj-kurs-inkrementalnyj-enkoder.html

Оффлайн blindman

  • Старожил
  • *****
  • Сообщений: 797
  • Репутация: 13
  • Андрей
  • Поблагодарили: +17
Re: Как работать с энкодером
« Ответ #29 : 24 Ноября 2015, 16:35:43 »
0
вот ещё что-то похожее
Недостаток в том, что дребезг фактически не подавляется, а "транслируется" в переменную, которая содержит текущее положение вала. то есть при вращении в этой переменной будет примерно такая последовательность: 10-11-10-11-12-11-12-13-12-13-14, а должно быть 10-10-10-11-11-11-12-12-12-13-13

Оффлайн lgedmitry

  • Старожил
  • *****
  • Сообщений: 4621
  • Репутация: 30
  • Сергей, Рыбинск
  • Поблагодарили: +701
Re: Как работать с энкодером
« Ответ #30 : 04 Августа 2016, 23:32:03 »
0
Небольшая вариация на тему: процедура параллельно работает сразу с двумя энкодерами:
volatile char encoder_pos, inp_shift;
...
#define TR(x, y) (((x) << 4) | (y) )

// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{

// Place your code here
// Имеем 2 энкодера: первый подключен к PORTD.0 и PORTD.1. Второй - к PORTD.2 и PORTD.3.
    char pins = (PIND & 0x0F) << 4;
// если состояние 0-го бита ни одного из энкодеров не изменилось с предыдущего вызова, ничего не делаем
    if ((encoder_state & 0b01010000) == (pins & 0b01010000))
return;
// задвигаем новое состояние в переменную, забывая самое старое из
// 2 сохраненных состояний
encoder_state = ((encoder_state >> 4) | pins);// & 0xF0;
    // проверяем условия, и изменяем количество щелчков, если нужно
if ((encoder_state&0b00110011) == TR(3,0)&&(encoder_pos&31)>0) encoder_pos--;     //уменьшение громкости
if ((encoder_state&0b00110011) == TR(1,2)&&(encoder_pos&31)<31) encoder_pos++;    //увеличение громкости
        if ((encoder_state&0b11001100) == TR(12,0)&&inp_shift>0) inp_shift--;             //к предыдущему входу
if ((encoder_state&0b11001100) == TR(4,8)&&inp_shift<2) inp_shift++;              //к следующему входу
    encoder_pos &= 31;
    encoder_pos |= (1<<(inp_shift+5)); 
    // В переменной encoder_state легко и непринуждённо размещаются: с нулевого по 4 бит - положение регулятора громкости.
    // с 5-ого по 7-ой - реле переключения трёх входов усилителя. Очень удобно всё разом отправить в какой-нибудь из портов контроллера
   
}

 

Похожие темы

  Тема / Автор Ответов Последний ответ
24 Ответов
47013 Просмотров
Последний ответ 20 Октября 2020, 19:51:09
от drummer
15 Ответов
16090 Просмотров
Последний ответ 21 Октября 2011, 12:13:29
от hippo64
2 Ответов
3118 Просмотров
Последний ответ 11 Ноября 2015, 08:53:24
от rubenlukin
1 Ответов
1970 Просмотров
Последний ответ 23 Апреля 2020, 18:23:10
от dm34