В уроке расскажу о принципе действия инкрементального энкодера, о подключении его к плате Ардуино. Покажу способ программной обработки сигналов энкодера и представлю библиотеку для работы с ним.
Предыдущий урок Список уроков Следующий урок
Мне заказали разработку программы для устройства, в котором в качестве управляющего элемента используется инкрементальный энкодер. Поэтому я решил написать внеплановый урок о работе с энкодером в системе Ардуино.
Совсем коротко, о чем идет речь, т.е. о классификации энкодеров.
Энкодеры это цифровые датчики угла поворота. Другими словами преобразователи угол-код.
Я подчеркиваю, что это цифровые датчики, потому что существует большое число датчиков с выходными аналоговыми сигналами. Простой переменный резистор можно считать датчиком угла. Энкодеры формируют на выходе дискретные сигналы.
Энкодеры бывают абсолютные и накапливающие (инкрементальные).
Абсолютные энкодеры формируют на выходе код, соответствующий текущему углу положения вала. У них нет памяти. Можно выключить устройство, повернуть вал энкодера, включить и на выходе будет новый код, показывающий новое положение вала. Такие энкодеры сложные, дорогие, часто используют для подключения стандартные цифровые интерфейсы RS-485 и им подобные.
Инкрементальные энкодеры формируют на выходе импульсы, появляющиеся при повороте вала. Принимающее устройство может определить текущий угол вала энкодера, подсчитав количество импульсов на его выходе. После включения питания инкрементальные энкодеры не способны определить положение вала. Необходимо привязать его к началу отсчета.
Но в большинстве случаев нет необходимости знать абсолютное значение текущего угла. Если мы энкодером, например, регулируем уровень громкости, то нам надо увеличить ее на несколько градаций или уменьшить. Мы не смотрим на ручку энкодера, на ней нет шкалы. Нам необходимо определить изменение угла относительно текущего положения. То же самое касается установки параметров на дисплее. Мы крутим ручку энкодера и смотрим, как изменяется значение параметра на экране дисплея.
В подобных случаях инкрементальные энкодеры становятся идеальными устройствами управления, установки параметров, выбора меню. Они намного удобнее, чем кнопки ”+” и ”-”.
В этих случаях можно использовать самые простые механические инкрементальные энкодеры, которые отличаются низкой ценой.
Принцип действия механического инкрементального энкодера.
Импульсы на выходе инкрементального энкодера должны сообщать не только о повороте вала, но и о направлении поворота. Поэтому необходимо использовать 2 сигнала, которые обычно обозначаются A и B. Эти сигналы подключены к механическим контактам энкодера, другие выводы которых соединены на выводе C.
Если подключить к сигналам A и B подтягивающие резисторы, то диаграмма выходных сигналов при вращении энкодера будет выглядеть так.
Если вы покрутите вал энкодера, то заметите, что у него есть фиксированные положения. У моего энкодера 20 таких положений на оборот. Т.е. точность определения угла 360° / 20 = 18° или 20 импульсов на оборот.
Эти, механически зафиксированные, положения отмечены на диаграмме стрелочками снизу вверх. В этот момент оба контакта разомкнуты, сигналы A и B находятся в высоком уровне.
На первых 2 отрезках диаграммы энкодер повернули по часовой стрелке. Сначала в низкий уровень упал сигнал A, затем B. На 3 и 4 интервалах вал вращают против часовой стрелки. После фиксированного положения сначала становится равным нулю сигнал B. По последовательности изменения сигналов можно определить, в какую сторону повернули энкодер.
Формализованный алгоритм анализа сигналов энкодера выглядит так.
- Если в момент перехода сигнала A в низкое состояние сигнал B находится в высоком уровне, то произошел поворот по часовой стрелке.
- Если в момент перехода сигнала A в низкое состояние сигнал B находится в низком уровне, то был поворот против часовой стрелки.
Возможны другие кодировки положения энкодера. Все, что я пишу верно, относительно EC11.
Параметры инкрементальных энкодеров.
Подробно технические характеристики и параметры энкодера EC11 в формате PDF можно посмотреть по этой ссылке EC11.pdf. Я приведу и поясню основные.
- Главный параметр – число импульсов на оборот. Определяет точность измерения угла. У моего датчика 20 импульсов на оборот, точность 18°.
- Предельно допустимые электрические параметры определяют предельные значения тока и напряжения для контактов. В моем случае это 10 мА и 5 В. Эти параметры влияют на выбор подтягивающих резисторов. Сопротивление резисторов не должно быть ниже 500 Ом.
- Прочность изоляции. Я бы не рискнул использовать подобный энкодер в цепях, гальванически связанных с высоким напряжением.
- Износоустойчивость. Разработчики гарантируют от 15 000 до 1 000 000 циклов, в зависимости от модификации.
- Рабочий диапазон температур -30 … +85 °C.
- Механические параметры: габаритные и установочные размеры, моменты вращения и т.п.
Подключение энкодера к плате Ардуино.
С электрической точки зрения энкодер это 3 кнопки: сигналы A, B и кнопка. Я использовал внутренние подтягивающие резисторы, но в рабочих схемах лучше добавить внешние резисторы сопротивлением 2 – 10 кОм.
Программная обработка сигналов энкодера.
Сначала приведу простую программу, позволяющую определить кодировку энкодера.
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
pinMode(2, INPUT_PULLUP); // определяем вывод как вход
pinMode(8, INPUT_PULLUP); // определяем вывод как вход
}
void loop() {
if( digitalRead(2) == HIGH ) Serial.print("H ");
else Serial.print("L ");
if( digitalRead(8) == HIGH ) Serial.println("H");
else Serial.println("L");
delay(200);
}
У меня энкодер подключен к выводам 2 и 8. Программа в цикле выводит через последовательный порт состояния сигналов A и B.
Если я плавно вращаю вал по часовой стрелке у меня монитор последовательного порта выводит:
HH -> LH -> LL -> HL -> HH
При вращении против часовой стрелки:
HH -> HL -> LL -> LH -> HH
Все как на диаграмме выше.
Алгоритм обработки сигналов энкодера простой:
- выделяем отрицательный фронт сигнала A;
- считываем состояние сигнала B;
- если у сигнала B:
- высокий уровень – был поворот по часовой стрелке;
- низкий уровень – был поворот против часовой стрелки.
Но у механического энкодера механические контакты. Им свойственно явление дребезга. К тому же энкодеры часто устанавливаю на передней панели устройства, и подключают к контроллеру достаточно длинными проводами. Т.е. нужна цифровая фильтрация сигналов и устранение дребезга. Все это реализовано в моей библиотеке Button.h. Ее вполне можно использовать для работы с энкодером.
Ну и самая главная мысль в каждом моем уроке. Давайте сделаем обработку энкодера параллельным процессом. Программа в основном цикле занимается своими задачами, а параллельный процесс обрабатывает сигналы энкодера в прерывании по таймеру.
#include <TimerOne.h>
#include <Button.h>
Button encoderA (2, 4); // сигнал A
Button encoderB (8, 4); // сигнал B
Button encoderButton(10, 40); // кнопка
long pos=0; // пооложение энкодера
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
Timer1.initialize(250); // инициализация таймера 1, период 250 мкс
Timer1.attachInterrupt(timerInterrupt, 250); // задаем обработчик прерываний
}
void loop() {
// сброс положения
if( encoderButton.flagClick == true ) {
encoderButton.flagClick= false;
pos= 0;
}
Serial.println(pos); // вывод положения
}
// обработчик прерывания 250 мкс
void timerInterrupt() {
encoderA.filterAvarage(); // вызов метода фильтрации
encoderB.filterAvarage(); // вызов метода фильтрации
encoderButton.filterAvarage(); // вызов метода фильтрации
// обработка сигналов энкодера
if( encoderA.flagClick == true ) {
encoderA.flagClick= false;
if( encoderB.flagPress == true) {
// против часовой стрелки
pos--;
}
else {
// по часовой стрелке
pos++;
}
}
}
В прерывании по таймеру программа обрабатывает сигналы энкодера и вычисляет его абсолютный угол. В основном цикле выводит текущее положение вала в монитор последовательного порта. При нажатии на кнопку сбрасывает текущее положение в 0. По сути, мы сделали абсолютный энкодер, только до момента выключения питания.
Вопрос, с какой частотой необходимо опрашивать энкодер. У меня в программе цикл прерывания 250 мкс и 4 выборки при фильтрации. В итоге период реакции на изменения состояния сигналов 1 мс.
Все зависит от того с какой частотой сигналы энкодера могут изменять свое состояние. А это определяют максимальная скорость вращения энкодера и параметр - число импульсов на оборот.
Если вы сделаете период опроса больше, например, несколько миллисекунд, то увидите в окне монитора порта, что импульсы пропускаются при резком повороте. Все это вы можете посчитать сами. Но я бы рекомендовал в итоге проверить работу энкодера на этой программе.
- Запомнить механическое положение вала.
- Сбросить вычисленное положение кнопкой (привязать к механическому положению).
- Теперь сколько бы вы ни вращали вал, при попадании в механическое положение, к которому он был привязан, на экране должно быть значение кратное числу импульсов на оборот. В моем случае это 0,20, 40…
Библиотека Encod_er.h.
Представленный выше способ обработки энкодера вполне имеет право на жизнь. Но в моей задаче надо было определять, с какой скоростью вращают энкодер.
Энкодером перемещался шпиндель сверлильного станка. С одной стороны нужна была высокая точность позиционирования. С другой – приходилось долго крутить ручку, чтобы переместить шпиндель в нижнее положение.
Решили при медленном вращении энкодера перемещать шпиндель по одному шагу, а при увеличении скорости вращения – увеличивать число шагов перемещения шпинделя на каждый шаг энкодера. Т.е. если медленно крутить энкодер, то шпиндель перемещается точно и медленно. При быстром вращении – шпиндель перемещается быстро.
Для реализации этого алгоритма необходимо вычислять скорость вращения энкодера. Я разработал библиотеку обработки энкодера с этой функцией.
Загрузить библиотеку Encod_er.h можно по этой ссылке:
Описание класса Encod_er.
Я привел только public свойства и методы.
class Encod_er {
public:
Encod_er(byte pinA, byte pinB, byte timeFilter);
byte timeRight; // время/признак вращения вправо (* 8 периодов)
byte timeLeft; // время/признак вращения влево (* 8 периодов)
long position; // текущее положение
void scanState(); // метод проверки состояния
long read(); // метод чтения положения
};
Encod_er(byte pinA, byte pinB, byte timeFilter) - конструктор.
- pinA – вывод сигнала A;
- pinB – вывод сигнала B;
- timeFilter – число выборок фильтрации сигналов.
Encod_er myEncoder(2, 3, 5); // энкодер к выводам 2 и 3, 5 выборок фильтрации
void scanState() – метод сканирования состояния энкодера. Должен вызываться регулярно в параллельном процессе.
// обработчик прерывания 250 мкс
void timerInterrupt() {
myEncoder.scanState();
}
byte timeRight и timeLeft – признаки поворота энкодера соответственно вправо и влево. Если равны 0, то поворота не было. Если не равны 0, то содержат время поворота на один шаг. Время вычисляется, как
Tповорота = Tвызова scanState() * 8 * timeRight.
Т.е. единица значения timeRight или timeLeft это период вызова scanState(), умноженный на 8. В моей программе это 250 мкс * 8 = 2 мс. На 8 я умножил, чтобы использовать один байт. Значение переменных timeRight или timeLeft ограничиваются на уровне 255.
Если признаки используются, то должны сбрасываться в основном цикле.
if(encoder.timeRight != 0) {
encoder.timeRight= 0;
...........
}
long position – текущее положение энкодера. Может быть прочитано или записано.
long read() – метод чтения положения энкодера. Возвращает переменную position.
Все просто. Предыдущую программу можно переписать с использованием библиотеки Encod_er.h.
#include <TimerOne.h>
#include <Encod_er.h>
Encod_er encoder( 2, 8, 4);
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
Timer1.initialize(250); // инициализация таймера 1, период 250 мкс
Timer1.attachInterrupt(timerInterrupt, 250); // задаем обработчик прерываний
}
void loop() {
if(encoder.timeRight != 0) {
Serial.print("R=");
Serial.print(encoder.timeRight);
Serial.print(" Pos=");
Serial.println(encoder.read()); // вывод текущего положения
encoder.timeRight= 0;
}
if(encoder.timeLeft != 0) {
Serial.print("L=");
Serial.print(encoder.timeLeft);
Serial.print(" Pos=");
Serial.println(encoder.read()); // вывод текущего положения
encoder.timeLeft= 0;
}
}
// обработчик прерывания 250 мкс
void timerInterrupt() {
encoder.scanState();
}
На каждый поворот вала в монитор последовательного порта выводится направление вращение и значение timeRight или timeLeft.
Обработку кнопки энкодера я в библиотеку не включил. Кнопку лучше обрабатывать библиотекой Button.h.
С помощью этой библиотеки я реализовал управление перемещением шпинделя сверлильного станка по описанному выше алгоритму.
Анализировал признаки timeRight и timeLeft. При ненулевом значении признаков шаговый двигатель перемещения шпинделя делал количество шагов в зависимости от значения активного признака. Зависимость задал в массиве.
Здравствуйте, мне нужно добавить в условие,
if(encoder.read() = 4095)
{??? = 4095;
}
как правильно записать в энкодер, что если меньше нуля то всегда равен нулю и если больше 4095, то равен 4095?
Подскажите пожалуйста?
Здравствуйте!
noInterrupts();
if(encoder.position < 0) encoder.position=0; if(encoder.position > 4095) encoder.position=4095;
interrupts();
У переменной position права public.
x=encoder.read(); и x=encoder.position; одно и то же. Только прерывание надо запрещать, чтобы не считывать частично модифицированную переменную.
encoder.read()=constrain(encoder.read(),0,4095);
//constrain(входное значение,мин. число,мах. число);
// получится если encoder.read() = меньше 0 или больше 4095
//то оно будет ровно 0 или 4095 несмотря не на што.
оператор constrain долго выполняется.
Предложенное Эдуардом быстрее.
Здравствуйте!
Очень интересные уроки. Все замечательно работает.
Есть такой вопрос:
Возможна ли одновременная корректная работа библиотек , из этого урока и высокочастотной ШИМ из Урока 37 (Широтно-импульсная модуляция в Ардуино)? Как лучше реализовать управление заполнением высокочастотной ШИМ с помощью енкодера и учетом скорости вращения?
Конечно. ШИМ реализуется аппаратным способом. Программа не тратит на него время.
Для управления двигателем надо делать регулятор. Измерять скорость вращения. Возможно вам не подойдет эта библиотека. При высоких скоростях вращения не хватит времени на обработку 2 сигналов энкодера. Вам достаточно обрабатывать один сигнал. Вы же знаете в какую сторону крутите двигатель. И энкодер должен быть другой, не механический, с большим ресурсом.
Добрый день. Нужно чтобы нажали кнопку, реле 1 замкнулось, как только будет n оборотов энкодера, реле выключилось. Как это можно реализовать?
Здравствуйте!
У Вас очень хорошие уроки, спасибо Вам за труд!
Подскажите пожалуйста, почему Вы решили делать прерывания по таймеру? Почему просто не привязать аппаратное прерывание к А с параметром FALLING?
Здравствуйте!
Спасибо за отзыв по сайту.
Я стараюсь использовать внешние прерывания только в крайнем случае. Они обладают низкой помехозащищенностью, срабатывают на дребезг контактов, система становится асинхронной. К тому же я предполагаю, что программа выполняет параллельные задачи, которые требуют прерывания по таймеру.
Добрый день, пожалуйста приведите пример добавления произвольного таймера, в код. Пробую по этому примеру: http://arduino.ru/Reference/Millis
В итоге компилятор ошибки выдаёт. Полагаю TimerOne не позволяет использовать эти средства. Как с его помощью реализовать тоже самое, что и в примере на ардуино.ру ?
Здравствуйте!
millis() возвращает время (в мс), с момента запуска контроллера. Для отработки временного интервала необходимо в начале отсчета считать функцией millis() текущее время. Затем периодически считывать время этой функцией, проверяя, не достигло ли оно требуемого значения (значение считанного в начале отсчета + требуемое время интервала). В промежутках между проверками текущего времени программа может заниматься другими задачами.
Т.е. при использовании millis() вы должны все время проверять значение времени и реализовывать переход при выполнении условия. На проверку тратится определенное время. Кроме того точность отработки времени таймера будет зависеть и от частоты вызова функции millis().
При использовании TimerOne программа перейдет на обработку прерывания аппаратными средствами без затрат программного времени.
Уважаемый,Эдуард, доброго вам времени суток!
У вас очень интересные уроки на различные, актуальные темы.
Большим плюсом, что они используют прерывания.
Однако у меня, начинающего в этой области, возникли трудности со скрещиванием этой библиотеки (энкодер) со StepDirDriver.
Никак не могу понять, как это правильно организовать.
Еще одним камнем преткновения является то, что энкодер у меня круговой оптический с 1000 рисками.
А задача следить за перемещением энкодера и делать соответствующее кол-во шагов в том же направлении.
И вообще, возможно ли скрещивание этих двух библиотек малой кровью?
Заранее,благодарен, с Уважением Андрей.
Здравствуйте!
Ничего скрещивать не надо. Библиотеки работают независимо друг от друга параллельными процессами. Надо в прерывании по таймеру вызывать методы control() и scanState(). Теперь можно пользоваться ресурсами библиотек.
Только почитайте в уроке 55 о частоте вызова scanState(). Возможно у вас энкодер высокого разрешения и вращается с большой скоростью.
Можете открыть тему на форуме сайта, там удобнее обсуждать.
Добрый день.
Подскажите как реализовать проект:
Необходимо определить угол наклона и вылет стрелы при помощи 2х энкодеров.
При определенном угле ограничить перемещение стрелы.
если угол больше снять ограничение
Спасибо
Здравствуйте!
Это достаточно сложная задача. И, насколько я понимаю, такая система должна быть очень надежной.
Да есть задача разработать прибор безопасности гидроподъемника.
Мне стало очень интересно, кем вы являетесь на работе и кто вам разрешил (и знает ли об этом) что разработку такого узла как прибор БЕЗОПАСНОСТИ гидроподъемника вы делаете на ардуино, при околонулевом (судя по вопросам) уровне своих познаний.
Если этот прибор будет использоваться не только лично вами, то это вообще-то преступление.
И ардуино это не более чем детские кубики для обучения. Но делать на них что-то промышленного назначения — это полный звиздец.
Так вроде все в порядке,
Но при подключении серва прога реагирует только на него.
Если убрать строку //servo1.attach(pinServo); то работает только энкодер.
Поможете разобраться.
Спасибо.
#include
#include
#include
#include
int ledPin = 8; // the number of the LED pin
int Pin_3 = 3;
int Pin_4 = 6;
Servo servo1;
int pinServo=9; // Пин для подключения сервопривода
int POT=0; // Аналоговый вход A0 для подключения потенциометра
int valpot = 0; // переменная для хранения значения потенциометра
int angleServo = 0; // переменная для хранения угла поворота серв
Encod_er encoder( 2, 4, 5);
Encod_er encoder_1( 5, 7, 5);
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
Timer1.initialize(250); // инициализация таймера 1, период 250 мкс
Timer1.attachInterrupt(timerInterrupt, 250); // задаем обработчик прерываний
pinMode(ledPin, OUTPUT);
// initialize the pushbutton pin as an input:
pinMode(Pin_3, OUTPUT);
pinMode(Pin_4, OUTPUT);
// подключить переменную servo к выводу pinServo
servo1.attach(pinServo);
}
void loop() {
valpot = analogRead(POT); // чтение данных потенциометра
// масштабируем значение к интервалу 0-180
angleServo=map(valpot,120,1240,0,230); //120,1240,0,230
// поворот сервопривода на полученный угол
servo1.write(angleServo);
if(encoder.timeRight != 0) {
Serial.print(«R=»);
Serial.print(encoder.timeRight);
Serial.print(» Ugol podyema ap=»);
Serial.println(encoder.read()); // вывод текущего положения
encoder.timeRight= 0;
}
if(encoder.timeLeft != 0) {
Serial.print(«L=»);
Serial.print(encoder.timeLeft);
Serial.print(» Ugol podyema doun=»);
Serial.println(encoder.read()); // вывод текущего положения
encoder.timeLeft= 0;
}
if(encoder_1.timeRight != 0) {
Serial.print(«\t R_1=»);
Serial.print(encoder_1.timeRight);
Serial.print(«\t vylet ap=»);
Serial.println(encoder_1.read()); // вывод текущего положения
encoder_1.timeRight= 0;
}
if(encoder_1.timeLeft != 0) {
Serial.print(«\t L_1=»);
Serial.print(encoder_1.timeLeft);
Serial.print(«\t vylet doun =»);
Serial.println(encoder_1.read()); // вывод текущего положения
encoder_1.timeLeft= 0;
}
digitalWrite(Pin_3, LOW);
digitalWrite(Pin_4, LOW);
// Функция управления условиями
if (encoder_1.read()>= 10&& encoder.read()>= 20) //Если encoder_1 >= 10&& encoder <= 10 то включаем реле
{
// turn LED on:
digitalWrite(ledPin, HIGH);
} else {
// turn LED off:
digitalWrite(ledPin, LOW);
}
}
// обработчик прерывания 250 мкс
void timerInterrupt() {
encoder.scanState();
encoder_1.scanState();
}
Здравствуйте!
Может быть библиотека Servo использует таймер 1. Попробуйте сделать прерывание для энкодеров от таймера 2.
Подскажите как это сделать???
Спасибо.
Здравствуйте!
Библиотека для таймера 2 описана в уроке10.
Подключаете библиотеку:
#include
Инициализируете в setup():
MsTimer2::set(1, timerInterupt); // задаем период прерывания по таймеру 1 мс
MsTimer2::start(); // разрешаем прерывание по таймеру
Вызов обработчика происходит так:
// обработчик прерывания
void timerInterupt() {
. . . .
}
Все также, как и с таймером 1. Только библиотека MsTimer2 устанавливает прерывание с дискретностью 1 мс, т.е. минимальное время 1 мс. Энкодер будет обрабатывать с меньшей скоростью. Попробуйте так, потом будем думать.
Здравствуйте, спасибо за уроки. Без Вас бы закопался в куче бесполезной информации.
Прикручивая энкодер, с Вашими библиотеками натолкнулся на интересный момент: При повороте на 1/8(примерно) шага и возврате в прежнее положение, программа изменяет счетчик на 1. Работает в обе стороны.
// обработка сигналов энкодера
if( encoderA.flagClick == true ) {
encoderA.flagClick= false;
if( encoderB.flagPress == true) {
// против часовой стрелки
pos—;
}
else {
// по часовой стрелке
pos++;
}
}
А вот с этим кодом, без таймера такого не происходит. Хотя, на быстрых скоростях он теряет шаги(отличие 4 шага на щелчек):
old=val;
val=(digitalRead(2)<<1)+digitalRead(8);
if (old!=val)
{
if ((old==3)&&(val==1))++cur;
if ((old==3)&&(val==2))—cur;
if ((old==1)&&(val==0))++cur;
if ((old==1)&&(val==3))—cur;
if ((old==0)&&(val==2))++cur;
if ((old==0)&&(val==1))—cur;
if ((old==2)&&(val==3))++cur;
if ((old==2)&&(val==0))—cur;
printing=true;
}
Здравствуйте!
Спасибо за отзыв.
У меня самый простой и быстрый способ обработки сигналов энкодера. Тот недостаток, который вы обнаружили, во многих приложениях вряд ли играет какую-нибудь роль.
Если я правильно разобрался в библиотеке Button, то в ней не реализован признак отжатия кнопки. Если бы он был, то легко бы этот недостаток исключался:
// обработка сигналов энкодера
if( encoderA.flagClick == true )
{ encoderA.flagClick= false;
if( encoderB.flagPress == true) {pos—;} else {pos++;}
};
if( encoderA.flagUnClick == true )
{ encoderA.flagUnClick==false;
if ( encoderB.flagPress == true) pos++ else pos—
}
Шагов будет в 2 раза больше…
Здравствуйте, Эдуард
Делаю обработку энкодера параллельным процессом на Arduino Pro Micro. Установил:
#include
#include
Button encoderA (7, 40); // сигнал A
Button encoderB (5, 40); // сигнал B
Button encoderButton(2, 40); // кнопка
…
Управляющие сигналы создаю электронным имитатором энкодера. Счет идет, но только на увеличение. В обратную сторону не вычитает, хотя меандры А и В поменялись местами, что подтверждается миганием светодиодов на выходах имитатора. Исследую схему на частотах около 1 Гц.
void setup() {
Serial.begin(9600); // инициализируем порт, скорость 9600
pinMode(2, INPUT_PULLUP); // определяем вывод как вход
pinMode(8, INPUT_PULLUP); // определяем вывод как вход
}
void loop() {
if( digitalRead(2) == HIGH ) Serial.print(«H «);
else Serial.print(«L «);
if( digitalRead(8) == HIGH ) Serial.println(«H»);
else Serial.println(«L»);
delay(200);
}
Эта программа тоже видит на 5 и 7 входах HH-HL-LL-LH…
Здравствуйте!
А почему у вас такое большое число подтверждений сигналов энкодера 40? А если энкодер подключить, работает?
Что то я всю голову сломал. Новичок я во всем этом. Буду очень признателен если вы покажете код для управления биполярным шаговым двигателем при помощи энкодера. Мотор имеет 984 шага, вылет тяги 41 миллиметр т.е. один сигнал с энкодера крути движок 24 шага (или 1 мм) при этом нужно чтобы сделав 984 шага двигатель не крутился (т.е. ограничить его от 0 шагов до 984). Заранее благодарен…
Здравствуйте!
Вот проект управления сверлильным станком http://mypractic-forum.ru/viewtopic.php?t=66. Там перемещение шпинделя происходит энкодером. Но программа сложная. У меня другой нет.
Доброго вам времени суток. Спасибо вам за ваши труды. Вопрос такой у меня возник… при вращении энкодера показания в порт появляются только со второго щелчка, а как сделать так чтобы обрабатывалось каждое изменение сигнала как в этом коде
#include «Stepper.h»
#define STEPS 24 // Number of steps for one revolution of Internal shaft
// 2048 steps for one revolution of External shaft
volatile boolean TurnDetected; // need volatile for Interrupts
volatile boolean rotationdirection; // CW or CCW rotation
const int PinCLK=2; // Generating interrupts using CLK signal
const int PinDT=3; // Reading DT signal
const int PinSW=4; // Reading Push Button switch
int RotaryPosition=0; // To store Stepper Motor Position
int PrevPosition; // Previous Rotary position Value to check accuracy
int StepsToTake; // How much to move Stepper
// Setup of proper sequencing for Motor Driver Pins
// In1, In2, In3, In4 in the sequence 1-3-2-4
Stepper small_stepper(STEPS, 8, 10, 9, 11);
// Interrupt routine runs if CLK goes from HIGH to LOW
void isr () {
delay(10); // delay for Debouncing
if (digitalRead(PinCLK))
rotationdirection= digitalRead(PinDT);
else
rotationdirection= !digitalRead(PinDT);
TurnDetected = true;
}
void setup () {
Serial.begin(9600);
pinMode(PinCLK,INPUT);
pinMode(PinDT,INPUT);
pinMode(PinSW,INPUT);
digitalWrite(PinSW, HIGH); // Pull-Up resistor for switch
attachInterrupt (0,isr,CHANGE); // interrupt 0 always connected to pin 2
}
void loop () {
small_stepper.setSpeed(830); //Max seems to be 700
if (!(digitalRead(PinSW))) { // check if button is pressed
if (RotaryPosition == 0) { // check if button was already pressed
} else {
small_stepper.step(-(RotaryPosition*24));
RotaryPosition=0; // Reset position to ZERO
}
}
// Runs if rotation was detected
if (TurnDetected) {
PrevPosition = RotaryPosition; // Save previous position in variable
if (rotationdirection) {
RotaryPosition=RotaryPosition-1;} // decrase Position by 1
else {
RotaryPosition=RotaryPosition+1;} // increase Position by 1
TurnDetected = false; // do NOT repeat IF loop until new rotation detected
// Which direction to move Stepper motor
if ((PrevPosition + 1) == RotaryPosition) { // Move motor CW
StepsToTake=24;
small_stepper.step(StepsToTake);
}
if ((RotaryPosition + 1) == PrevPosition) { // Move motor CCW
StepsToTake=-24;
small_stepper.step(StepsToTake);
}
}
Serial.println(RotaryPosition*24);
}
Здравствуйте!
Моя библиотека рассчитана больше для работы с энкодером для задания параметров, замены кнопок, обеспечения интерфейса пользователя. Она написана с минимальным размером кода, минимальными требованиями к ресурсам микроконтроллера.
Чтобы реализовать ваш алгоритм надо писать другую библиотеку.
Доброго вам времени суток. Спасибо вам за ваши труды. Вопрос такой у меня возник… при вращении энкодера показания в порт появляются только со второго щелчка, а как сделать так чтобы обрабатывалось каждое изменение сигнала как в этом коде
#include «Stepper.h»
#define STEPS 24 // Number of steps for one revolution of Internal shaft
// 2048 steps for one revolution of External shaft
volatile boolean TurnDetected; // need volatile for Interrupts
volatile boolean rotationdirection; // CW or CCW rotation
const int PinCLK=2; // Generating interrupts using CLK signal
const int PinDT=3; // Reading DT signal
const int PinSW=4; // Reading Push Button switch
int RotaryPosition=0; // To store Stepper Motor Position
int PrevPosition; // Previous Rotary position Value to check accuracy
int StepsToTake; // How much to move Stepper
// Setup of proper sequencing for Motor Driver Pins
// In1, In2, In3, In4 in the sequence 1-3-2-4
Stepper small_stepper(STEPS, 8, 10, 9, 11);
// Interrupt routine runs if CLK goes from HIGH to LOW
void isr () {
delay(10); // delay for Debouncing
if (digitalRead(PinCLK))
rotationdirection= digitalRead(PinDT);
else
rotationdirection= !digitalRead(PinDT);
TurnDetected = true;
}
void setup () {
Serial.begin(9600);
pinMode(PinCLK,INPUT);
pinMode(PinDT,INPUT);
pinMode(PinSW,INPUT);
digitalWrite(PinSW, HIGH); // Pull-Up resistor for switch
attachInterrupt (0,isr,CHANGE); // interrupt 0 always connected to pin 2
}
void loop () {
small_stepper.setSpeed(830); //Max seems to be 700
if (!(digitalRead(PinSW))) { // check if button is pressed
if (RotaryPosition == 0) { // check if button was already pressed
} else {
small_stepper.step(-(RotaryPosition*24));
RotaryPosition=0; // Reset position to ZERO
}
}
// Runs if rotation was detected
if (TurnDetected) {
PrevPosition = RotaryPosition; // Save previous position in variable
if (rotationdirection) {
RotaryPosition=RotaryPosition-1;} // decrase Position by 1
else {
RotaryPosition=RotaryPosition+1;} // increase Position by 1
TurnDetected = false; // do NOT repeat IF loop until new rotation detected
// Which direction to move Stepper motor
if ((PrevPosition + 1) == RotaryPosition) { // Move motor CW
StepsToTake=24;
small_stepper.step(StepsToTake);
}
if ((RotaryPosition + 1) == PrevPosition) { // Move motor CCW
StepsToTake=-24;
small_stepper.step(StepsToTake);
}
}
Serial.println(RotaryPosition*24);
}
Здравствуйте! Отличная статья и отличная библиотека. Но вот незадача, у меня помимо энкодера используется сервомотор. И из за прерываний сервомотор не может нормально прочитать сигнал с ШИМа. Есть ли способ на время остановить считывание энкодера, а потом его возобновить.
Прошу прощения, не заметил комментарий другого человека с такой же проблемой. Ваше решение помогло, спасибо
Добрый день!
Мне именно Ваш пример подходит.
Большое спасибо за пример.
Но не могу реализовать следующее:
нужно управлять обычным мотором с редуктором в зависимости от интенсивности поворота энкодера хотелось увидеть увеличение скорости мотора, а у меня он постоянно крутится то влево, то вправо
Добрый день, перепробовал много разных скетчей, но все почему то не работали, а этот работает! Но подскажите, можно ли с этим скетчем обрабатывать 2 и более энкодеров?
Пробовал немного подкорректировать код для обработки 2-х энкодеров, но получается, что второй корректно работает, а первый обрабатывается сам и к значению второго тоже прибавляет по единице, не зависимо в какую сторону крутить ручку первого энкодера.
добавил код
Button encoder2A (4, 4); // энкодер 2 сигнал A
Button encoder2B (12, 4); // энкодер 2 сигнал B
и в обработчик прерывания
encoder2A.filterAvarage(); // вызов метода фильтрации
encoder2B.filterAvarage(); // вызов метода фильтрации
if( encoder2A.flagClick == true ) {
encoder2A.flagClick= false;
if( encoder2B.flagPress == true) {
// против часовой стрелки
pos2—;
}
else {
// по часовой стрелке
pos2++;
}
}
Что-то делаю не так или может такими простыми изменениями не получится?
Здравствуйте!
Библиотека должна работать с несколькими энкодерами. Создавайте для них объекты и обрабатывайте признаки. Что не работает?
Спасибо что откликнулись…
«Создавайте для них объекты»
вот это оно?:
Button encoder2A (4, 4); // энкодер 2 сигнал A
Button encoder2B (12, 4); // энкодер 2 сигнал B
«и обрабатывайте признаки»
вот это оно?:
в обработчик прерывания
encoder2A.filterAvarage(); // вызов метода фильтрации
encoder2B.filterAvarage(); // вызов метода фильтрации
if( encoder2A.flagClick == true ) {
encoder2A.flagClick= false;
if( encoder2B.flagPress == true) {
// против часовой стрелки
pos2—;
}
else {
// по часовой стрелке
pos2++;
}
}
если «оно», то не корректно работает… если в обработчике прерывания оставить только мой новый код, то второй энкодер работает корректно, но вот если оба проверять, то не корректно.
Здравствуйте!
Вы объекты Button создаете. Я имел в виду объекты Encod_er.
Добрый день… у меня нет таких объектов, я использую вторую версию кода, которая начинается с
#include
#include
мне угол поворота/скорость вращения не нужны, мне нужно просто «отлавливать» сдвиг энкодера в одну или другую сторону… т.е. второй вариант мне показался наиболее простым… но пока затык с подключением нескольких энкодеров.
что-то названия инклудов не вставились
#include
#include
В общем попробовал по третьему коду где используется Encod_er — проблема таже — второй корректно работает, а первый обрабатывается сам и к значению второго тоже прибавляет по единице, не зависимо в какую сторону крутить ручку первого энкодера.
Если закоментить код первого энкодера — то второй корректно работает.
Ладно… нашел другой скетч, который работает с несколькими энкодерами.
Спасибо за материал Эдуард.
Не могли бы Вы привести пример скетча где при повороте энкодера загорался и гас оди светодиод, при повороте в другую сторону другой.
Моя цель замена кнопок громкости на энкодер.
Благодарю
Здравствуйте!
Я сейчас в Абхазии. Лучше откройте тему на форуме сайта. Вернусь домой, посмотрю.
«Я использовал внутренние подтягивающие резисторы, но в рабочих схемах лучше добавить внешние резисторы сопротивлением 2 – 10 кОм.» Почему?
Здравствуйте!
Внутренние подтягивающие резисторы имеют слишком большое сопротивление, до 50 кОм. Чем ниже сопротивление, тем больший ток должна сформировать электромагнитная наводка, для того чтобы исказить сигнал.
Спасибо за ответ!
Здравствуйте! Нужно написать программу для углового энкодера и присоединенного к нему МК8051. Описание сигнала А и В. Помогите пожалуйста))
Здравствуйте!
Я уже забыл, когда я работал с этими микроконтроллерами.
Добрый день.
Задача при помощи энкодера с мерным колесом на валу учитывать расход материала (счетчик длина). При длине окружности 300 мм и имея энкодер 600 им./ оборот, получаем как определить частоту опроса и счетчик для подавления дребезга дабы не пропускать импульсы?
Прикидываю, что при движении материала со скоростью1 м/с выходит 2000 имп. в сек.т.е. каждые 0,5 мс. импульс. Боюсь, что с настройками Timer1.initialize(250) вообще ничего подсчитать не получится, т.к. Encod_er encoder( 2, 8, 4) требует 4-х кратной перепроверки состояния входа, что приводит к задержке в обработке 4 х 250 мкс = 1 мс.
Или я не правильно что то понимаю?
Здравствуйте!
Можно попробовать уменьшить число подтверждений и уменьшить период прерывания до 100 мкс.
Если у контроллера задача всего одна — учет длины, ну и вывод данных на LCD, может вообще можно до 10 мкс. убавить? Или тогда теряется смысл в такой защите от «дребезга»? Получится, что ожидание установившегося режима всего 40 мкс?
Правда у меня не механический энкодер предполагается и у него, возможно, и нет «дребезга»?! Я его в руках еще не держал.
Мне кажется 10 мкс это слишком мало. В самом Ардуино еще системный таймер тикает, вызывает прерывания.
Ну при частоте кварца 16 МГц получается 0,0625 мкс на такт, при периоде в 10 мск получим всего 160 простых инструкций, боюсь этого крайне мало.
А насчет системного таймера вообще не понял.
Таймер 0 в Ардуино периодически вызывает прерывания для отсчета системного времени. Эти прерывания тоже надо учитывать или отключить.
Добрый день. Возвращаясь к вчерашней беседе.
Сегодня провел эксперимент. На одном Uno собрал генератор имитирующий работы энкодера, на второй приемное устройство которое, используя Вашу библиотеку, определяло направление и скорость движения.
Итог. Максимум на что удалось выти это 1200 Гц, что в моем случаи соответствует 0,6 м/с для конкретного энкодера. При этом прерывание 100 мкс, и фильтр Encod_er encoder( 2, 5, 4);
Попытка уменьшить период между прерываниями ничего хорошего не дает, даже на маленьких скоростях сбивается счет.
Т.о. для больших скоростей необходимо либо брать другую библиотеку, либо переходить на Duo.
Тем не менее Вам огромное спасибо за труды!
Здравствуйте!
Эта библиотека разрабатывалась для энкодера интерфейса с пользователем. В ней вычисляется скорость поворота, для того чтобы изменять приращение при установке параметров. Можете попробовать обрабатывать энкодер без библиотеки. Примеры есть в уроке.
Попробовал . Радикального увеличения скорости не получил. Было 1200 Гц, стало 1300 Гц. Дальше только уменьшением периода между прерываниями и ухудшением фильтрации.
Боюсь на «живом объекте» это «боком вылезет».
Если вам нужно просто вычислять скорость вращения на высоких оборотах, то посмотрите серию уроков ПИД-регулятор скорости вращения двигателя постоянного тока. Начиная с урока 73. Там есть способ измерения скорости вращения.
Нет. Мне требуется измерять длину материала при перемотке.
Добрый день, сделал проэкт ардуино, тфт дисплей и энкодер, всё работант, но при повороте энкодера или нажатии его кнопки моргает дисплей, почему? И как это устранить? Заранее спосибо!
Здравствуйте!
Наверное, вы при смене данных очищаете дисплей и выводите все данные. Выводите только те данные, которые изменились, без очистки экрана. Устанавливайте курсор на начало данных и выводите.
Добрый день Эдуард!
Подскажите пожалуйста как в третьем скетче/примере используя Вашу библиотеку Енкодер сбрасывать
счетчик поворота ручки? Меню с прокруткой пробую сделать.
Сделал так:
int posA=encoder.read();
if (posA==0||posA==3||posA==-3) posA=0;
else if (posA==1||posA==-2)posA=1;
else if (posA==2||posA==-1)posA=2;
на третьем щелчке posA==3 сбрасывается в posA=0, а четвертый и последующий щелчки счетчик увеличивается.
Другими словами, хочу установить границу работы счетчика.
Спасибо.
Здравствуйте!
Не совсем понял, что вы хотите получить.
Переменная position описана как public. Можете ее сбрасывать непосредственно encoder.position=0;
А ваша программа проверяет только значения около 0.
Может кому пригодится.
Мучался с подключением инкодера LIKA I41-100ZCU46L2 в небольшем проектике на Arduino UNO.
Перепробовал множество библиотек завести так и не удалось.
Пришлось писать либу самому. Класс инкодера в отдельную библиотеку не выносил.
Инкодер подкличал так:
красный провод = +5v, синий = GND, Зеленый = PIN 3, Желтый = PIN 2, розовый = GND, черный = GND.
Пример кода:
/***************************************
* Written by Aleksandr Zhukov 2021. *
***************************************/
// Docs http://easyelectronics.ru/avr-uchebnyj-kurs-inkrementalnyj-enkoder.html
#include
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO_EVERY)
// Example for Arduino UNO with input signals on pin 2 and 3
#define PIN_IN1 2
#define PIN_IN2 3
#elif defined(ESP8266)
// Example for ESP8266 NodeMCU with input signals on pin D5 and D6
#define PIN_IN1 D5
#define PIN_IN2 D6
#endif
class Encoder {
private:
int lastState_A;
int lastState_B;
long encPosition = 0; // (+1)-Forward; (-1)-Backward; (0)-Undefined
int pinA, pinB, encDirection = 0;
public:
Encoder(int iPinA, int iPinB) {
pinA = iPinA;
pinB = iPinB;
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
}
long getPosition() {
return encPosition;
}
int getDirection() {
return encDirection;
}
void tick()
{
//encoder->tick(); // just call tick() to check the state.
int state_A = digitalRead(pinA), state_B = digitalRead(pinB);
if( state_A != lastState_A ) {
// Changed A
if (state_A == HIGH) {
if (state_B == LOW) {
//A1
encPosition++; //A: LOW-> HIGH && B: LOW
encDirection=+1;
//Serial.println(» 1: 1-2> +1″);
}
else {
encPosition—; //A: LOW-> HIGH && B: HIGH
encDirection=-1;
//Serial.println(» 1: 1-2> -1″);
}
} else {
if (state_B == HIGH) {
//A2
encPosition++; //A: HIGH->LOW && B: HIGH
encDirection=1;
//Serial.println(» 1: 2-1> +1″);
}
else {
encPosition—; //A: HIGH->LOW && B: LOW
encDirection=-1;
//Serial.println(» 1: 2-1> -1″);
}
}
} else {
// Changed B
if (state_B == HIGH) {
if (state_A == HIGH) {
//B2
encPosition++; // B: LOW-> HIGH && A: HIGH
encDirection=1;
//Serial.println(» 2: 1-2> +1″);
}
else {
encPosition—; // B: LOW-> HIGH && A: LOW
encDirection=-1;
//Serial.println(» 2: 1-2> -1″);
}
} else {
if (state_A == HIGH) {
//B1
encPosition—; // B: HIGH -> LOW && A: HIGH
encDirection=-1;
//Serial.println(» 2: 2-1> -1″);
}
else {
encPosition++; // B: HIGH -> LOW && A: LOW
encDirection=1;
//Serial.println(» 2: 2-1> +1″);
}
}
}
lastState_A = state_A;
lastState_B = state_B;
if (abs(encPosition) > 9223372036854775800 ) encPosition=0;
}
};
// A pointer to the dynamic created rotary encoder instance.
// This will be done in setup()
Encoder *encoder = nullptr;
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO_EVERY)
// This interrupt routine will be called on any change of one of the input signals
void checkPosition()
{
encoder->tick(); // just call tick() to check the state.
}
#elif defined(ESP8266)
/**
* @brief The interrupt service routine will be called on any change of one of the input signals.
*/
IRAM_ATTR void checkPosition()
{
encoder->tick(); // just call tick() to check the state.
}
#endif
void setup()
{
Serial.begin(115200);
while (!Serial)
;
Serial.println(«InterruptRotator example for the RotaryEncoder library.»);
// setup the rotary encoder functionality
encoder = new Encoder(PIN_IN1, PIN_IN2);
// register interrupt routine
attachInterrupt(digitalPinToInterrupt(PIN_IN1), checkPosition, CHANGE);
attachInterrupt(digitalPinToInterrupt(PIN_IN2), checkPosition, CHANGE);
} // setup()
// Read the current position of the encoder and print out when changed.
void loop()
{
static int pos = 0;
int newPos = encoder->getPosition();
if (pos != newPos) {
Serial.print(«pos:»);
Serial.print(newPos);
Serial.print(» dir:»);
Serial.println(encoder->getDirection());
pos = newPos;
} // if
} // loop ()
Здравствуйте Эдуард. Столкнулся с проблемой. В прошлом году я зарегался и скачал данную библиотеку. На работе украли ноутбук и теперь все заново. Код у меня сохранился на компьютере дома, а вот этой библиотеки как оказалось нет. Сейчас снова зарегался но. Купить уже не даёт пишет мол минимальная сумма от 1000р. Думаю Вы понимаете что, 1к за библиотеку оч много? Поэтому прошу вас при наличии времени и возможности поделиться со мной данной библиотекой, с меня 200р на вашу карту. Связь: gav707090@yandex.ru
Здравствуйте! Отправил пароль на почту.