Проблема с преобразованием выходного сигнала поворотного энкодера в угол.

Я использую этот инкрементальный поворотный энкодер с вращающимся диском. .

Вот моя система:

Я кратко объясню систему, и выше приведена иллюстрация:

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

Каждый раз перед включением системы я вручную поворачиваю диск так, чтобы его красная стрелка совпадала с красной отметкой 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 тиков. Этот код увеличивает/уменьшает один счетчик за каждый такт.

Есть идеи, как извлечь угол из этой установки? (Не обязательно использовать предоставленный мною конкретный код.)

, 👍1

Обсуждение

если это 30ppr, то каждый счет равен 12 градусам. Итак, умножьте число на 12. Сделайте это по модулю 360 ( x = x % 360), так что вы получите значения только от 0 до 360. Но это не значит, что вы можете знать только разницу углов с момента первого запуска эскиза. Поэтому вам необходимо установить диск в нулевое положение перед включением Arduino., @Gerben

Serial.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


2 ответа


1

Комментарий Гербена в основном верен. Самый короткий способ адаптировать поведение этого скетча к вашей настройке — умножить результат на 24 (а не на 12, так как это 15 импульсов — 30 вмятин, но каждый канал меняется 15 раз), а затем сделать модуль:

if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos += 24;
} else {
    encoder0Pos -= 24;
}
encoder0Pos = encoder0Pos % 360;

НО... в опубликованном вами коде есть некоторые проблемы.

  1. "Прелесть прерываний в том, что они сами о себе заботятся" - это правда, но полагаясь на прерывания всегда есть некоторые недостатки, которые вы ДОЛЖНЫ учитывать. В основном вы блокируете остальную часть программы (например, сбиваете тайминги). Я предпочитаю использовать прерывания только в случае крайней необходимости (а это не так)
  2. Вы выполняете потенциально длинную функцию (последовательное взаимодействие) в прерывание. Абсолютно неправильно
  3. Вы используете ПОЛОВИНУ шагов, которые потенциально можете использовать (поскольку вы проверяете только один вход, вы игнорируете половину переходов).

Чтобы исправить это, я предлагаю вам переписать программу для проверки в цикле состояния кнопок. Затем, поскольку это механические переключатели, я собираюсь использовать библиотеку 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


1

Как отмечено в комментарии, обработка чисел по модулю 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, который включает код для чтения нескольких энкодеров, активных одновременно.

,