Клуб DiyAudio

Разное и полезное => Микроконтроллеры и их программирование => Тема начата: blindman от 14 Июня 2012, 06:07:29

Название: Как работать с энкодером
Отправлено: blindman от 14 Июня 2012, 06:07:29
Меня попросили объяснить, как работать с энкодером. Считаю, что эта информация может быть полезна начинающим программистам. Поэтому публикую здесь

Я рассмотрю принципы работы с наиболее распространенными механическими энкодерами, имеющими фиксированные положения вала ("щелчки") - 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 мс, иначе будут пропуски щелчков, и другие неприятные вещи
Название: Re: Как работать с энкодером
Отправлено: WolfTheGrey от 14 Июня 2012, 14:04:42
Что такое один щелчек на импульс и 2 щелчка на импульс? Что такое импульс?
И у тебя схема не правильная, когда крутиш в одну сторону, то сперва замыкается на землю In0, потом In1, потом оба сбрасываются.
Когда крутишь в обратную сторону, то In0 и In1 сразу замыкаются на землю, потом размыкается In1 , а после него размыкается In0.

Основываясь на это, вывод In0 прицепляют к ТС, и заводят событие по прерыванию. Где смотрят, если In1 замкнут, то уменьшают, если нет, то увеличивают.
Название: Re: Как работать с энкодером
Отправлено: blindman от 14 Июня 2012, 14:54:56
WolfTheGrey, к сожалению не знаю имени. Я с энкодерами не одну конструкцию сделал, и с механическими, и с оптическими. и с теми что рукой крутят, и на валах двигателей с большой частотой вращения. Схема правильная. Если бы энкодеры работали так, как ты описываешь, то подавить дребезг контактов было бы невозможно в принципе.

Импульс - это и есть импульс. Применительно к энкодеру -  замкнутое состояние контакта.  А щелчок,или клик - это устойчивое состояние вала, шаг. Можешь почувствовать эти клики, покрутив колёсико мышки.
1 клик на импульс - это значит что чтобы контакт сначала разомкнулся, а потом замкнулся, нужно повернуть вал на 1 шаг
2 клика на импульс - это значит что чтобы контакт сначала разомкнулся, а потом замкнулся, нужно повернуть вал на 2 шага
Название: Re: Как работать с энкодером
Отправлено: lgedmitry от 27 Февраля 2013, 11:57:08
Таки разобрался. Хоть и долго провозился, ибо Андрей не по-нашему программирует ??? :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:
Название: Re: Как работать с энкодером
Отправлено: blindman от 27 Февраля 2013, 12:23:29
Андрей не по-нашему программирует ??? :d_know: ??? :d_know:
Ну не учили нас таким языкам
Меня тоже не учили, вот так и разбирался, глядя на чужой код. Еще в школе когда учился, была у меня книжка про контроллеры 8051. Но ни самих контроллеров, ни тем более компьютера было не купить. Так в одной тетрадке рисовалась схема, а другая использовалась в качестве отладчика программ. Слева код на ассемблере, справа состояние переменных на каждом шаге. Сколько я таких тетрадок исписал - не сосчитать  :%):

Сергей, я рад, что все получилось! А DIR9001 всегда на выход импульсы выдает, даже если входного сигнала нет?
Название: Re: Как работать с энкодером
Отправлено: lgedmitry от 27 Февраля 2013, 12:29:52
А DIR9001 всегда на выход импульсы выдает, даже если входного сигнала нет?
Ага  выдаёт какие-то непонятные 25кГц. Поэтому на PINB.6 я error c неё засунул, чтоб всякую ерунду не измерять. ???
Название: Re: Как работать с энкодером
Отправлено: L0ki от 28 Февраля 2013, 20:21:50
А еще существует вот такое лобовое решение как поставить отдельный мелкий МК на обработку дребезга энкодера:
http://rgb73.mylivepage.ru/wiki/1952/591_%D0%AD%D0%BD%D0%BA%D0%BE%D0%B4%D0%B5%D1%80.
??? иногда может быть полезно, когда ресурсы основного МК сильно заняты.
Название: Re: Как работать с энкодером
Отправлено: lgedmitry от 01 Марта 2013, 09:27:08
 :off: :off: :off: :off: Там камушек микрочиповский. А скажите камрады, есть в них что-нить, чего у атмег нету. Я в смысле, стОит ли эту кухню освоить как-нить на досуге ;-[ ;-[ ;-[ ;-[
Название: Re: Как работать с энкодером
Отправлено: L0ki от 01 Марта 2013, 11:12:54
Там камушек микрочиповский.

ну а в чем проблема то ?
заливаем в него готовую прошивку и не паримся  :)
Если нечем шить, то упрощенный вариант PICkit2 спаять не так уж и сложно:
http://dmitrstas.ucoz.ru/publ/chasy/programmatory/pickit_2_lite_studencheskij_usb_programmator_pic_mikrokontrollerov/8-1-0-68
Либо купить у китайцев (на ебее например) клон пиккита, он стоит далеко не запредельные деньги.
Или же  :D "религия" не позволяет соединять вместе атмел и микрочип ?  ;D
Название: Re: Как работать с энкодером
Отправлено: WolfTheGrey от 01 Марта 2013, 11:17:19
Цитировать (выделенное)
Я в смысле, стОит ли эту кухню освоить как-нить на досуге   
все одинаковое. Разница в наборе поддерживаемых команд. У атмела на сотню больше, микрочип может только складывать и сдвигать.
Еще не официально, но народ говорит, что атмел стабильнее работает.
Название: Re: Как работать с энкодером
Отправлено: L0ki от 01 Марта 2013, 11:24:30
А скажите камрады, есть в них что-нить, чего у атмег нету.
Есть.... ???
Ну частотомер гоооораздо более высокочастотный на пиках получается чем на аврках.
Полноценный аппаратный USB во многих пиках есть.
Для старших моделей пиков есть халявный код от микрочипа полноценного TCP-IP стека.

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

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


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


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


и добавил...
P.S.
строго imho.
Из идеологии/архитектуры атмег на настоящий момент выжато все что можно,
дальнейшее их тупое наращивание опять же imho бессмысленно.
Такой девайс как хмега - это уже тупик.  :facepalm:
Название: Re: Как работать с энкодером
Отправлено: xar от 01 Марта 2013, 11:52:21
да в каком месте с фьюзами геморрой то... один два камня убил больше не будешь.
с атмела на пик прыгать смысла нет наверно.
Название: Re: Как работать с энкодером
Отправлено: L0ki от 01 Марта 2013, 12:37:19
с атмела на пик прыгать смысла нет наверно.
Тоже придерживаюсь такого же мнения.
Смысл есть осваивать АРМы, ибо за ними точно будущее,
ибо атмеги в своем развитии уже зашли в тупик (XMega).
А вот использовать чьи-то уже готовые разработки на пиках,
(в том числе и в своих проектах на атмелах)
я считаю что смысл есть.


и добавил...
P.S.
да и вообще... це он и в африке це   :d_know:
и если не углубляться в ассемблер,
то лично меня "прыганье" между х86, х51, AVR, и STM32 не особо напрягает  :)
Название: Re: Как работать с энкодером
Отправлено: Dim1112 от 01 Марта 2013, 19:28:42
Андрей, спасибо за код! Я частенько энкодеры колхозю на шаговиках от 5-ти дюймовых дисководов, мне нравится как в них "шажки" щелкают и нет дребезга.
PIC ICD - рулит!
Название: Re: Как работать с энкодером
Отправлено: L0ki от 01 Марта 2013, 23:50:00
Кстати, насчет таких энкодеров (из ШД),
с ними можно сделать одну очень красивую "фишку".
Насколько мне известно впервые "это" придумал и применил Алекс Торрес.
У него такой шаговик стоял как энкодер для управления громкостью увеселителя.
Причем помимо того что шаговик у него работал как энкодер,
при управлении с пульта ДУ он еще и соответствующим образом вращался.  :v:
(схемотехнически ничего хитрого - добавить ключи).
Название: Re: Как работать с энкодером
Отправлено: Dim1112 от 02 Марта 2013, 08:13:57
В масспектрометре видел такую "крутилку громкости" еще лет 15 назад, а сколько этой идее было до этого и кто автор - трудно сказать.  Да и сам принцип не нов, к примеру, в трехфазных безколлекторных безсенсорных двигателях положение ротора отслеживается именно по такому принципу.
Название: Re: Как работать с энкодером
Отправлено: lgedmitry от 09 Октября 2013, 12:53:18
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. Я спросил про него у Яндекса. Тот мне предложил разъяснение ещё более сакральными словами. Можешь ли ты разъяснить как-то ситуацию?
Название: Re: Как работать с энкодером
Отправлено: blindman от 09 Октября 2013, 14:06:15
Попробую объяснить  ???

а) Переменная 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;
}
Название: Re: Как работать с энкодером
Отправлено: lgedmitry от 09 Октября 2013, 14:20:27
blindman, Спасибо, Андрей. Кажется, начинает доходить ;)
Название: Re: Как работать с энкодером
Отправлено: Mitrych от 04 Ноября 2015, 22:31:16
Товарищи, подскажите пожалста, насколько сложно вместо переменника поставить энкодер и схему для него? Мне для блока питания, ручку регулировки напряжения заменить. Переменники китайские, быстро выходят из строя, напряжение прыгает. Замучился. Менять - спасает на время, быстро истираются, а хорошие типа Bourns дороговато.
Было бы здорово вместо крутилок поставить одну крутилку без конца и начала и устанавливать напряжение с какой-нить малой величиной, скажем, 0.1В

Название: Re: Как работать с энкодером
Отправлено: Althair от 05 Ноября 2015, 00:03:05
Учитывая "китайскую родословную", не проще ли заменить резисторы на заведомо надежные? Энкодер требует подхода с контроллером, разводить сыр-бор ради регулировки напряжения в китайском БП - сомнительное мероприятие.
Намёк: регулятор Никитина - суть тот же электронный резистор. Гляньте схему/принцип, когда не лень...
Название: Re: Как работать с энкодером
Отправлено: andrik от 05 Ноября 2015, 00:32:39
С энкодером работать на самом деле очень просто. Код приводить не буду, там одна строка всего.
Контакт  А подключается к входу с прерыванием.
Контакт В считывается во время прерывания, если 1 увеличиваем переменную, если 0 уменьшаем.
Название: Re: Как работать с энкодером
Отправлено: lgedmitry от 05 Ноября 2015, 05:09:25
С энкодером работать на самом деле очень просто. Код приводить не буду, там одна строка всего.
Контакт  А подключается к входу с прерыванием.
Контакт В считывается во время прерывания, если 1 увеличиваем переменную, если 0 уменьшаем.
Это если дребезга нет ;)
Название: Re: Как работать с энкодером
Отправлено: andrik от 05 Ноября 2015, 08:58:18
Дребезг есть всегда, но с ним можно бороться.
(https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQj57LY4br27lr1MYrYda9XUcioVF-ztU06-AEJt-V3A04IHQlO)
А там где нужна высокая точность, нужно использовать оптические, индуктивные и тп. энкодеры.
Название: Re: Как работать с энкодером
Отправлено: Mitrych от 05 Ноября 2015, 16:26:17
..не проще ли заменить резисторы на заведомо надежные?
Спасибо за ответ, заведомо надежные - подскажите какие? Хотя бы фирму, по названиям не сталкивался, на чипдипе китай и боурнс, первое - фигня, второе дорого.
Значит с энкодером смысла нет огород городить.
Ну а регулятор Никитина - на главной странице этого сайта нашел статью, очень впечатляюще, вообще не вариант.
Тогда запасной вариант - поставить двухкнопочное управление, схема вроде делается на счетчике и дешифраторе, я когда -то видел в журнале радио, щас уже не вспомню даже какого года журнал..
Название: Re: Как работать с энкодером
Отправлено: ДДО от 05 Ноября 2015, 16:56:14
А многооборотный подстроечник чем не годится? Если конечно часто напряжение не менять, а только периодически подстраивать
Название: Re: Как работать с энкодером
Отправлено: xar от 05 Ноября 2015, 17:23:44
andrik, ни в коем случае. При быстром вращении будет херня, которую программно уже не исправить. Вообще характер дребезга энкодера таков, что частота дребезга может быть сравнима с частотой переключения, так что даже классическими программными способами бороться с ним неэффективно. Кроме того все это не решает проблемы когда пользователь теребит ручку в пределах одного щелчка. Андрей выше описал отличный способ. Кроме него можно нагуглить еще кучу вариантов.
Название: Re: Как работать с энкодером
Отправлено: andrik от 05 Ноября 2015, 17:44:14
Каюсь, сейчас только прочел, очень интересно, нужно будет по пробовать.
Название: Re: Как работать с энкодером
Отправлено: lgedmitry от 05 Ноября 2015, 18:10:39
вот ещё что-то похожее на метод Андрея. Только более топорно: http://easyelectronics.ru/avr-uchebnyj-kurs-inkrementalnyj-enkoder.html
Название: Re: Как работать с энкодером
Отправлено: blindman от 24 Ноября 2015, 16:35:43
вот ещё что-то похожее
Недостаток в том, что дребезг фактически не подавляется, а "транслируется" в переменную, которая содержит текущее положение вала. то есть при вращении в этой переменной будет примерно такая последовательность: 10-11-10-11-12-11-12-13-12-13-14, а должно быть 10-10-10-11-11-11-12-12-12-13-13
Название: Re: Как работать с энкодером
Отправлено: lgedmitry от 04 Августа 2016, 23:32:03
Небольшая вариация на тему: процедура параллельно работает сразу с двумя энкодерами:
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-ой - реле переключения трёх входов усилителя. Очень удобно всё разом отправить в какой-нибудь из портов контроллера
   
}