Потребовалось применить энкодер в одном из проектов. Готовые варианты показались либо слишком громоздкими либо не учитывающими реалии типа дребезга механических контактов реального энкодера. Сделал собственную функцию циклического опроса, которую можно вызывать в loop либо по прерыванию таймера (что, пожалуй, лучше и правильнее). В примере первый вариант, если нужно опубликую и второй. Суть работы такая: некая переменная, представляя из себя по сути кольцевой буфер, хранит 4 последних состояния одного из контактов. Изменение состояния и устаканивание дребезга оценивается по набору этих данных, а именно 2 вкл и 2 выкл, после чего проверяется состояние второго контакта и в зависимости от него делается либо инкремент либо декремент положения энкодера. Получилось компактно, работает стабильно. Может кому пригодится: Код (C++): #define C1 6 // первый контакт #define C2 5 // второй контакт byte bufEnc; // кольцевой буфер в байте unsigned long timerEnc; // таймер опроса unsigned int timEnc = 1; // пауза опроса (если короче проскакиевт дребезг, если длиннее проскакивают щелчки) int stepEnc = 0; // состояние энкодера unsigned long timerPrint; void setup() { pinMode(C1, INPUT); pinMode(C2, INPUT); timerEnc = millis() + timEnc; timerPrint = millis() + 1000; Serial.begin(9600); } void loop() { encod (); // проверка состояния пинов энкодера с извлечением выводов if (millis() > timerPrint) { // показываем что получилось каждую секунду Serial.println(stepEnc); timerPrint = millis() + 1000; } } void encod() { // выполняется за 8мкс, при обращению к порту - за 3мк if (millis() < timerEnc) return; bufEnc = ((bufEnc << 1) | digitalRead(C1)) & 0b00001111; if (bufEnc == 12) { stepEnc += digitalRead(C2) ? 1 : -1; } timerEnc = millis() + timEnc; }
Без использования прерываний, можете даже не пытаться достичь стабильной работы энкодера. Объясняю. Если у вас в цикле кроме опроса энкодера живет какая то еще обработка, то вы просто можете пропустить момент для чтения digitalRead(C2).
Разумеется этот способ для малонагруженной программы. Такая у меня и была, опрашивала энкодер, кнопку от него и выводила на индикатор цифру, причем даже не динамически. А пропустить переключение очень запросто может, особенно если крутить бешенно. Можно правда буфер увеличить, например до 8 и искать в нем перепад с двумя вкл и двумя выкл, так будет стабильнее на мой взгляд. Но и сложнее.
Вот содрал у колеги dimax на другом форуме. Просто и безглючно. Только я прерывание ставлю только на один вход, а не на два - так правильнее и ресурсов меньше жрет. Код (C++): volatile int enc; void setup() { Serial.begin(9600); PCICR=1<<PCIE1; //разрешить пренрывание PCMSK1=(1<<PCINT9)|(1<<PCINT8); //выбрать входы } ISR (PCINT1_vect){ static byte old_n=PINC&3; // маска B00000011 что б читать только нужные 2 бита byte new_n=PINC&3; if (old_n==1&&new_n==3||old_n==2&&new_n==0) {enc++;} if (old_n==2&&new_n==3||old_n==1&&new_n==0) {enc--;} old_n= new_n; } void loop() { Serial.println(enc); }
Входов то все равно два надо, а прерывание по таймеру в моем случае, а не по событию. Да, по событию я делал, но проблема в том, что срабатывало на дребезг контактов. За один щелчек счетчик прыгал на непредсказуемое ко-во шагов. Надо либо аппаратно давить, либо придумать что то еще.
Брал такой. Может у вас такой. У меня он действительно не работал. Показания скакали, то на два, то на три, то на один тик. В основном отсчитывал по два тика. - Не рекомендую. Подключил другой и все заработало как часы. Даже при очень быстром вращении.
Какой то такой. Смотрел анализатором, ни одного переключения без дребезга. Хорошо хоть дребезг короткий. Вот потому и сделал измерения через интервал, вроде бы заработало. Хотя, возможно, надо просто найти хорошие энкодеры без дребезга, если такие бывают в природе.
Там не в дребезге дело. Он выдает по два клика на одно положение. Может где то такой нужен, но я с ним нае*бался еще как. Потом забросил и взял вот такой. С ним все в порядке. Даже без конденсаторов четко отрабатывает.
Два это еще неплохо, можно поделить на два. А вот дребезг угадать невозможно. Заказал в Китае три разных, посмотрю вот.
Да нифига там не поделишь. Он в основном выдает по два импульса, но в 20% случаев три, или один. Второй работает четко - 99.9% одинарных импульсов без конденсаторов. Конденсаторы я все же поставил, мало чего.
А там что? На схему посмотрите. Резистор подтяжки плюс конденсатор, вот вам и RC цепь. Весь мир работает на энкодерах. "Вы не любите кошек?! Вы просто не умеете их готовить!"
Вот пока что самым устойчивым к помехам и пропускам оказался еще более короткий код, который нужно вызывать каждые 2, а лучше 1мс: Код (C++): b1 = b2; b2 = digitalRead(C1); b3 = digitalRead(C2); if (b1 && !b2) stepEnc += b3 - !b3; где: b1, b2, b3 - переменные типа bool, stepEnc - счетчик энкодера. Основная проблема механического энкодера "обычного" качества в том, что если делать паузы между опросами слишком малые - будет засчитан дребезг (то есть непредсказуемое количество шагов в любую сторону), если слишком большие, то будут пропуски. То же самое касается аппаратного подавления. Приходится балансировать.
А как сделать, что бы показания выводились не каждую секунду, а только если они (показания) меняются?
Вот ещё рабочий код, поменьше, чем в 1 посте: #include <Encoder.h> Encoder Enc_1(4, 5); long old_pos = 0; void setup() {Serial.begin(9600);} void loop() { long new_pos; new_pos = Enc_1.read()/4; if (new_pos != old_pos) { old_pos = new_pos; Serial.println(old_pos); } }
Подскажите как можно программно проделать следующее. Сигнал энкодера нужно поделить пополам, чтобы при одном клике выдавал только половину импульса, так как в устройстве использовался энкодер с " половинчатыми" импульсами, но с малым кол-вом положений.