Форсирование энкодера

С одной стороны, определение направления вращения энкодера является достаточно простой задачей. Многие ссылки в интернете говорят нам о том, что «Каждый раз, когда сигнал А переходит от положительного уровня к нулю, мы считываем значение выхода В. Если В в этот момент находится в положительном состоянии, значит энкодер вращается по часовой стрелке, если В нуль, то энкодер вращается против часовой стрелки.»
Да, это верно для энкодеров а ля «потенциометр», используемых на приборной панели, однако при применении энкодера в качестве датчика вращения так небрежно относиться к данным с него — непозволительная роскошь.

Как известно, инкрементальный энкодер выдает код Грея:

И если в энкодере-«потенциометре» мы руководствуемся всем циклом, то для промышленного энкодера выгоднее руководствоваться каждым тактом, что увеличивает число импульсов на оборот аж в четыре раза по сравнению с паспортным. Приличное форсирование, да?
Сложность заключается в определении направления вращения — тут указанный выше алгоритм уже не поможет. Рассмотрим таблицу переходов:
л>_<п
2>0<1
0>1<3
3>2<0
1>3<2
Левый и правые столбцы — это предыдущие значения с энкодера, посередине — текущее.
Составим по ней функцию, которая будет определять направление вращения:
uint8_t EncoderDirection(uint8_t* GrayData){
	uint8_t EncoderRealDirection;
	switch(GrayData[1]){
		case 0:
		if (GrayData[0]==2)
			EncoderRealDirection=0;
		else EncoderRealDirection=1;
		break;
		case 1:
		if (GrayData[0])
			EncoderRealDirection=1;
		else EncoderRealDirection=0;
		break;
		case 2:
		if (GrayData[0])
			EncoderRealDirection=0;
		else EncoderRealDirection=1;		
		break;
		case 3:
		if (GrayData[0]==2)
			EncoderRealDirection=1;
		else EncoderRealDirection=0;
		break;
	}
	return EncoderRealDirection;
}

Соответственно, функция вернет единицу для правого вращения и нуль для левого.
Настраиваем контроллер:
#define F_CPU 16000000
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include "taskmanager.h"
uint8_t EncPortData[2];
uint8_t DriveRealDirection;
int16_t DataLog;
uint8_t number_x=0;
uint8_t hexnumber[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x67,0x77,0x7C,0x39,0x5E,0x79,0x71};

int main(void)
{
	static uint8_t number_x;
	DDRA=0xF0;//индикатор. разряды
	DDRC=0xFF;//индикатор. сегменты
    PCMSK2|=(1<<PCINT16)|(1<<PCINT17);//активируем ноги энкодера
	PCICR|=(1<<PCIE2);//активация PCINT2
	TSetup();
	TStart();
	TReload(0,*led,1);
	sei();
    while(1)
    {
    }
}

И описываем прерывание:

ISR (PCINT2_vect){//срабатывает энкодер
EncPortData[1]=(PINK)&0x03;//маска
DriveRealDirection=EncoderDirection(EncPortData);
if (DriveRealDirection){//вправо крутим
		DataLog++;//плюсую
	}
	else
	{
		DataLog--;//минусую
	}
EncPortData[0]=EncPortData[1];//обращаем значения		
}

Я слышу бурю негодования по поводу отсутствия защиты от дребезга. Да зачем же она нужна? У меня датчик магнитный, он дребезгу не подвержен. А в механике сами боритесь. Мне больше метод Set/Reset нравится. Он надежнее, да и дарит несколько плюшек в виде длительности нажатия и отпускания.
Для вывода информации использую семисегментник:
void led(){
		PORTC=hexnumber[(DataLog>>(number_x*4))&0x0F];
		PORTA=(1<<(4+number_x));
		number_x++;
		if (number_x>3) number_x=0;
		
	}

Функция, при помощи диспетчера вызывается каждые 10мс, обеспечивая полное обновление индикатора за 40мс, что дает частоту обновления 25кадров/с.
Итого мой энкодер на 1024 импульса выдает 4096 импульсов на оборот. Красота да и только(в видео косяк, как видно левый разряд толком не работает, в приведенном выше коде это исправлено):
Энкодер типа E40H12-1024 подключен к 12В источнику питания, из-за чего пришлось подключать его к резистивному делителю.

27 комментариев

avatar
Такие энкодеры стоят раза в два больше, чем ваша отладочная плата. Сомневаюсь, что они по карману радиолюбителям.
avatar
То, что энкодер дорогой не значит, что его не используют. да и метод работает с любым энкодером.
avatar
Просто странное сочетание.
avatar
Странное сочетание чего? Датчика положения и микроконтроллера, снимающего с него показания?
avatar
Любительской платы и промышленного энкодера.
avatar
Человек описал один из методов обработки данных с энкодера, что здесь странного? Или ему для этого нужно было прикупить УЧПУ или ПЛК, и на нем показывать? :) Что как раз странно, так данное замечание :)
avatar
Только появляются два спорных момента:
  1. Неточности при сдвиге между сигналами отличном от 90 градусов (такое бывает, допустим в наших станках хорошую долю энкодеров составляют ВЕ178 различных вариантов, так вот при ремонте их даже регулировать приходится чтобы получить нормальный сдвиг)
  2. Помехоустойчивость… Я понимаю, что у взрослых энкодеров выход дифференциальный, но суть кода грея как раз в том что без «форсирования» счетному устройству пофиг на мелкие помехи...
avatar
Такс, не внимательно простомтрел, пункт 2 более не актуален :) разницы то нет…
avatar
Ну, по первому тут главное код Грея чтоб выдавался. В противном случае любая логика ругаться начнет.
У конкретно того энкодера, который в кадре паспортный выход «открытый коллектор», но бывают и комплементарный и дифференциальный выходы.
Ну и сам энкодер отличается точностью выходного сигнала:
avatar
Ухты, очень чистый сигнал, без дребезга. Там фильтр внутри стоит?
avatar
Откуда у Холла дребезг?:)

Я тут игрался с энкодером стоящим на плате — там механика, так только промежуточная система Set/Reset спасла от дребезга. Но там и не надо каждый фронт следить.
avatar
Блин, не подумал, что там Холл. У тех механических энкодеров, которыми мне доводилось пользоваться на фронтах дребезг был ядреный.
avatar
Механика на высоких скоростях крайне ненадежна.
Посему либо Холл, либо оптика.

А защита от дребезга… Расскажу через пару часов про нее.
avatar
Забавно. У распаянного на плате энкодера сигнал на выходе — НЕ код Грея.
avatar
А что тогда?
avatar
В одну сторону крутишь, выдает последовательность 0/3
в другую сторону крутишь, выдает последовательность 1/2
Тут, правда, с определением направления вращения еще проще — XOR(a,b) и делов.
Взял с полки непаянный — та же хрень.
avatar
«Я не тупой» © Гомер Симпсон.

Ложась спать, меня вдруг покоробило «Но я же видел Правильные осциллограммы!»
Утром я буквально вскочил за пол часа до будильника с пониманием того, где закралась проблема. Разумеется, состояние энкодера выводится на семисегментник.

Считывается состояние так:
EncPortData[1]=(ENCODERINPORT>>5)&0x03;//прочли сырое значение


Выводится это состояние так:
PORTC&=~0x30; PORTC|=(EncPortData[1]<<4);


А между ними код, подавляющий дребезг контактов. К слову сказать работающий без сбоев для какой-то там кнопки, где-то на реальной железке. Вчера я его не смог приладить к энкодеру, догадайтесь почему. Код фиксирует фронты по друм линиям. По фронту одной линии сетит, другой — ресетит.
if ((EncPortData[1])&0x01 && !(EncPortData[0])&0x01)//нарастающий фронт по А!
if ((EncPortData[1]>>1) & 0x01 && !(EncPortData[0]>>1) & 0x01)//нарастающий фронт по B!


Вопрос знатокам — почему код правильно отрабатывает свое предназначение при вращении энкодера в одну сторону и взрывает мозг в другую?
avatar
Вчера я его не смог приладить к энкодеру, догадайтесь почему.
Телепаты, ау?

Вопрос знатокам — почему код правильно отрабатывает свое предназначение при вращении энкодера в одну сторону и взрывает мозг в другую?
Какой код? Что за предназначение?
avatar
Код указанный выше. И в нем есть очень неявная ошибка.
Задача кода — устанавливать выход в единицу по фронту сигнала А, а сбрасывать — по фронту сигнала Б.
Два «независимых» условия, каждое смотрит текущее и предыдущее состояния и в стучае фронта — делает свое дело.
Вот только если сначала появляется фронт А, а потом Б — все работает как надо (что идеально работает как раз с упомянутой кнопкой).
Но вот если крутить наоборот, т.е. сначала Б, потом А, то операция (EncPortData[1]>>1) портит данные. Именно этим меня утром и осенило :) Попытался найти подобный случай в Макконнелле(Совершенный код), но нашел только приведенную выше цитату на первой же странице результатов поиска :)
avatar
Спойлер
Смахивает на помешательство. Пойду ка я отдохну, а то уже void вместо vk.com в командной строке набираю
avatar
А не связано это с тем, что приоритет операции логического отрицания выше, чем побитовое «и»? Мб скобки переставить:
if (EncPortData[1]&0x01 && !(EncPortData[0]&0x01))//нарастающий фронт по А!
if (EncPortData[1]>>1)&0x01 && !(EncPortData[0]>>1)&0x01)//нарастающий фронт по B!
В первой строчке вообще не понятно зачем скобки вокруг элемента массива…
avatar
Сергей, обрати, плз, внимание на формат коммента: после редактирования кнопкой «изменить» в форме вылазит много мусора и после сохранения вылазит в видимость тэг «code»…
avatar
Угадал. Молодец, держи плюсик :)
Спасибо тебе добрый человек.
avatar
Столкнулся на днях с китайским дешевым энкодером (нечеткое начальное и конечное состояние контактов + дребезги). Пробовал программировать и по таблице переходов и по прерываниям — результаты отрицательные. Хотя видел устройство где именно он отлично работает. Выход нашел вот такой — настроил прерывание по таймеру на 1 Мгц(меньше дает пропуски) и в нем ловил в момент щелчка только одно самое быстрое и устойчивое переключение контакта 2>0<1. Метод работает прекрасно, но для него нужно прерывание по таймеру, что не везде применимо.
avatar
Вот здесь описан триггерный метод подавления дребезга.
но без форсирования.
tqfp.org/rln_electro/zaschita-ot-drebezga-mehanicheskogo-enkodera.html
avatar
Если кому не нравится дорогой энкодер, то самый смак это двигатель от HDD :) Он и магнитный и работает хорошо даже если медленно его рукой крутить, добавить LM 358 и готово :)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.