Проблема с преобразованием выходного сигнала поворотного энкодера в угол.
Я использую этот инкрементальный поворотный энкодер с вращающимся диском. .
Вот моя система:
Я кратко объясню систему, и выше приведена иллюстрация:
Поворотный энкодер (синий) прикреплен/закреплен к большему диску (желтый) концентрически в его начале, поэтому и энкодер, и диск вращаются вместе на один и тот же угол. Диск вращается на фиксированной плоскости (серый). На поверхности самолета имеется отметка фиксированной точки (красный крестик). На диске также имеется фиксированная красная стрелка.
Каждый раз перед включением системы я вручную поворачиваю диск так, чтобы его красная стрелка совпадала с красной отметкой X на поверхности. Эта корректировка каждый раз в начале будет служить отправной точкой.
После этой настройки я хочу вывести фактические углы через Arduino.
Я попробовал следующий пример кода с сайта Arduino:
#define encoder0PinA 2
#define encoder0PinB 4
volatile unsigned int encoder0Pos = 0;
void setup() {
pinMode(encoder0PinA, INPUT);
digitalWrite(encoder0PinA, HIGH); // включаем подтягивающий резистор
pinMode(encoder0PinB, INPUT);
digitalWrite(encoder0PinB, HIGH); // включаем подтягивающий резистор
attachInterrupt(0, doEncoder, CHANGE); // вывод энкодера на прерывании 0 - вывод 2
Serial.begin (9600);
Serial.println("start"); // личная причуда
}
void loop(){
// делаем здесь что-то — прелесть прерываний в том, что они сами о себе позаботятся
}
void doEncoder() {
/* If pinA and pinB are both high or both low, it is spinning
* forward. If they're different, it's going backward.
*
* For more information on speeding up this process, see
* [Reference/PortManipulation], specifically the PIND register.
*/
if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
encoder0Pos++;
} else {
encoder0Pos--;
}
Serial.println (encoder0Pos, DEC);
}
/* See this expanded function to get a better understanding of the
* meanings of the four possible (pinA, pinB) value pairs:
*/
void doEncoder_Expanded(){
if (digitalRead(encoder0PinA) == HIGH) { // обнаружен переход от низкого к высокому на канале A
if (digitalRead(encoder0PinB) == LOW) { // проверяем канал B, чтобы узнать, в какую сторону
// энкодер вращается
encoder0Pos = encoder0Pos - 1; // КОО
}
else {
encoder0Pos = encoder0Pos + 1; // CW
}
}
else // найден переход от высокого к низкому на канале A
{
if (digitalRead(encoder0PinB) == LOW) { // проверяем канал B, чтобы узнать, в какую сторону
// энкодер вращается
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // КОО
}
}
Serial.println (encoder0Pos, DEC); // отладка - не забудьте закомментировать
// перед окончательным запуском программы
// вы не хотите, чтобы ваша программа последовательно замедляла работу, если в этом нет необходимости
}
Но этот код увеличивает один счетчик для каждого такта кодера в направлении CW и уменьшает один счетчик для каждого такта в направлении против часовой стрелки. Он продолжает увеличиваться от нуля до 65535 по часовой стрелке и уменьшаться от 65535 до 0 по часовой стрелке. Я хочу перевести этот код так, чтобы можно было выводить угол в градусах.
Мой кодер говорит, что его ppr составляет 15 и 30 тиков. Этот код увеличивает/уменьшает один счетчик за каждый такт.
Есть идеи, как извлечь угол из этой установки? (Не обязательно использовать предоставленный мною конкретный код.)
@floppy380, 👍1
Обсуждение2 ответа
Комментарий Гербена в основном верен. Самый короткий способ адаптировать поведение этого скетча к вашей настройке — умножить результат на 24 (а не на 12, так как это 15 импульсов — 30 вмятин, но каждый канал меняется 15 раз), а затем сделать модуль:
if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
encoder0Pos += 24;
} else {
encoder0Pos -= 24;
}
encoder0Pos = encoder0Pos % 360;
НО... в опубликованном вами коде есть некоторые проблемы.
- "Прелесть прерываний в том, что они сами о себе заботятся" - это правда, но полагаясь на прерывания всегда есть некоторые недостатки, которые вы ДОЛЖНЫ учитывать. В основном вы блокируете остальную часть программы (например, сбиваете тайминги). Я предпочитаю использовать прерывания только в случае крайней необходимости (а это не так)
- Вы выполняете потенциально длинную функцию (последовательное взаимодействие) в прерывание. Абсолютно неправильно
- Вы используете ПОЛОВИНУ шагов, которые потенциально можете использовать (поскольку вы проверяете только один вход, вы игнорируете половину переходов).
Чтобы исправить это, я предлагаю вам переписать программу для проверки в цикле состояния кнопок. Затем, поскольку это механические переключатели, я собираюсь использовать библиотеку Bounce2 для устранения дребезга входов.
Это, наряду с решением предыдущих 3 пунктов, также позволит вам использовать любую произвольную булавку на доске.
#include <Bounce2.h>
#define encoder0PinA 2
#define encoder0PinB 4
#define STEP_SIZE 12
byte encoder0Pos = 0;
Bounce debouncerA = Bounce();
Bounce debouncerB = Bounce();
void setup()
{
debouncerA.attach(encoder0PinA, INPUT_PULLUP);
debouncerA.interval(5); // интервал в мс
debouncerB.attach(encoder0PinB, INPUT_PULLUP);
debouncerB.interval(5); // интервал в мс
Serial.begin (9600);
Serial.println("start"); // личная причуда
}
void loop()
{
// Прочитать статус входов
debouncerA.update();
debouncerB.update();
int8_t EncVariation = 0;
if (debouncerA.rose())
{ // если вход A изменился с низкого на высокий, это был CW, если B тоже высокий
EncVariation = (debouncerB.read()) ? 1 : -1;
}
else if (debouncerA.fell())
{ // если вход A изменился с высокого на низкий, это был CW, если B тоже низкий
EncVariation = (debouncerB.read()) ? -1 : 1;
}
else if (debouncerB.rose())
{ // если вход B изменился с низкого на высокий, это было против часовой стрелки, если A тоже высокий
EncVariation = (debouncerA.read()) ? -1 : 1;
}
else if (debouncerB.fell())
{ // если вход B изменился с высокого на низкий, это было против часовой стрелки, если B тоже низкий
EncVariation = (debouncerA.read()) ? 1 : -1;
}
if (EncVariation != 0)
{
encoder0Pos = (encoder0Pos + EncVariation * STEP_SIZE) % 360;
Serial.println (encoder0Pos, DEC);
}
}
Отказ от ответственности: я не проверял это, но я почти уверен, что это сработает.
Я настоятельно рекомендую вам использовать библиотеку Bounce2, так как она также имеет много вспомогательных функций, которые значительно сокращают код. Смотрите здесь инструкции о том, как ее получить и использовать. Я просто счастливый пользователь этой библиотеки, и я никак не связан с автором ;)
Этот код работает лучше, я не вижу ошибочных выходов. Возможно ли также выводить направление CCW или CW. Я имею в виду, что этот код будет работать, но я также хочу, чтобы светодиод включался, когда энкодер вращается CCW. Как это можно сделать? Заранее спасибо, @floppy380
о, я действительно заметил, что EncVariation указывает направление:), @floppy380
@doncarlos да, просто проверьте, и он скажет вам, поворачивает ли он (!= 0) и направление. Кстати, если ответ полностью отвечает на ваш вопрос, пожалуйста, отметьте его как принятый; в противном случае просто спросите ;), @frarugi87
Как отмечено в комментарии, обработка чисел по модулю 360° поместит результаты в нужный вам диапазон.
Однако следует отметить, что можно (и следует) избегать использования длинной
арифметики, взяв количество импульсов по модулю 30 перед умножением на 12°.
Чтобы компенсировать корректировку опорной точки, заставьте программу установить счетчик тиков, соответствующий 0°, когда стрелки выровнены. Если вы установите базовый счетчик на какое-то значение, кратное 30, скажем, 120, и сохраните его в беззнаковом байте, код будет выглядеть так:
angle = 12*(count%30);
Поскольку базовый счетчик 120 кратен 30, он автоматически выпадает при взятии модуля. Чтобы это работало в долгосрочной перспективе, вы бы установили счетчик энкодера на 120 всякий раз, когда стрелки выравниваются (или на (current%30)+120
, если требуется настроить его в других случаях).
Если бы ваш базовый счетчик был 0 вместо 120, а счетчик был бы целым числом без знака вместо байта, когда ротор идет против часовой стрелки, счетчики модуля имели бы 15 дополнительных счетчиков. Например, счетчик 0 правильно преобразуется в 0°, но счетчик 65535 равен 15 по модулю 30, поэтому преобразуется в 180° вместо 348°.
Обратите внимание, что нет необходимости устранять дребезг входов вращающегося энкодера, если используется правильный конечный автомат; и устранение дребезга замедляет обработку до такой степени, что такты будут потеряны. См. мой ответ на вопрос 32572, который включает код для чтения нескольких энкодеров, активных одновременно.
- Считывание нескольких поворотных энкодеров
- Использование поворотных энкодеров с прерываниями смены контактов
- Выводы прерываний Arduino Mega 2560 и отображение портов с помощью поворотного энкодера
- Взаимодействие с датчиком SSI?
- Будет ли простой RC-фильтр работать с механическим поворотным энкодером или понадобится триггер Шмитта?
- Чтение двух квадратурных кодировщиков с помощью одного ардуино
- Поворотный энкодер KY-040 пропускает шаги
- Как настроить выводы ввода-вывода второго квадратурного декодера в Arduino IDE
если это 30ppr, то каждый счет равен 12 градусам. Итак, умножьте число на 12. Сделайте это по модулю 360 (
x = x % 360
), так что вы получите значения только от 0 до 360. Но это не значит, что вы можете знать только разницу углов с момента первого запуска эскиза. Поэтому вам необходимо установить диск в нулевое положение перед включением Arduino., @GerbenSerial.println()
может вызвать проблемы при использовании в ISR. Вместо этого установите флаг и печатайте изloop()
, @James Waldby - jwpat7@jwpat7 Как я могу реализовать этот флаг установки и вывод из void? Я также сталкиваюсь с двумя выходными сигналами при некоторых одиночных тактах энкодера. Может ли это быть из-за подпрыгивания?, @floppy380
Объявите флаг, например
летучий байт cchange;
, и установите его в ISR. Вloop()
скажитеif (cchange) { cchange=false; печать(); }
¶ При двойном подсчете показанный ISR медленный, что может испортить подсчет. Если бы ваш ISR перехватывал все ребра и обрабатывал их правильно, например, с помощью конечного автомата, как в [мой ответ на вопрос 32572](http://arduinoprosto.ru/q/32582/3917), отскок был бы отменен. ., @James Waldby - jwpat7Как можно изменить этот код, чтобы использовать индексированный кодер для сброса счетчика в 0 каждый раз, когда он достигает индекса. Я хотел бы использовать это для отслеживания вращающейся мачты для определения направления., @Bkukuk62