2.3. Энкодер.md


До сих пор мы управляли двигателями «вслепую». Мы подавали команду, например, analogWrite(motorPin, 150), и надеялись, что робот поедет с нужной скоростью. Но что, если один аккумулятор заряжен чуть меньше другого? Или одно колесо едет по ковру, а другое — по гладкому полу? Робота начнёт уводить в сторону, а мы об этом даже не узнаем.

Чтобы создавать по-настоящему умных роботов, нам нужна обратная связь - способ получать от робота информацию о том, что он делает на самом деле. Для измерения скорости и пройденного расстояния используются специальные датчики — энкодеры.

Что такое энкодер и как он работает?

Инкрементальный энкодер — это датчик, который генерирует импульсы при вращении вала. В нашем наборе используются простые и надёжные оптические энкодеры. Их устройство элементарно: 1. Диск с прорезями. На вал двигателя устанавливается диск, по краю которого проделаны отверстия (прорези). 2. Оптическая пара. С одной стороны диска находится инфракрасный светодиод, а с другой — фототранзистор (приёмник).

Когда вал двигателя вращается, диск с прорезями прерывает световой луч. Каждый раз, когда прорезь проходит между светодиодом и фототранзистором, приёмник улавливает свет и генерирует цифровой импульс (HIGH). Когда луч перекрыт, сигнал падает на LOW. В итоге мы получаем на выходе датчика последовательность прямоугольных импульсов, частота которых прямо пропорциональна скорости вращения колеса.

Представьте, что вы машете рукой перед датчиком света. Каждое «промахивание» — это импульс. Считая, как часто вы машете в секунду, можно узнать скорость движения вашей руки. Энкодер делает то же самое, только тысячи раз в минуту.

Как посчитать быстрые импульсы?

Давайте посчитаем. Диск нашего энкодера имеет 20 прорезей. Если колесо делает всего три оборота в секунду (180 об/мин), то датчик будет генерировать 3 об/с * 20 имп/об = 60 импульсов в секунду! Пытаться «поймать» их все с помощью digitalRead() внутри loop() — очень ненадёжная затея. Пока микроконтроллер будет выполнять другие команды, он неизбежно пропустит часть импульсов, и измерения будут неверными.

Аппаратные прерывания

Именно для таких задач и созданы аппаратные прерывания. Мы «говорим» Рудирону - «Следи за пином, к которому подключён энкодер. Как только на нём появится импульс (RISING), немедленно бросай все дела и выполни одну простую команду — прибавь единицу к счётчику».

Это гарантирует, что ни один импульс не будет пропущен, даже если основной цикл loop() занят сложными вычислениями или ждёт ответа от другого датчика.

Подключение энкодера

Датчики скорости в наборе AQROBO.PRO уже установлены рядом с моторами. Нам нужно лишь подключить их к Рудирону. У каждого датчика три вывода:

  • VCC - питание датчика (подключается к 5V).
  • GND - земля.
  • OUT - сигнальный выход, с которого мы будем считывать импульсы.

Важно! Сигнальный выход энкодера нужно подключать к пину Рудирона, который поддерживает аппаратные прерывания. Вспомним, что это пины 31, 32 и 35.

Схема подключения (для одного энкодера):

  • VCC энкодера → к шине +5V на макетной плате (или к пину 5V Рудирона).
  • GND энкодера → к шине GND.
  • OUT энкодера → к пину 31 Рудирона.
  • Не забудьте снять перемычку IRQ, если она установлена, так как она может конфликтовать с прерыванием на пине 31 (кнопка B2).

Программное измерение скорости (RPM)

Теперь напишем программу, которая будет считать импульсы с помощью прерывания, а в основном цикле — раз в секунду вычислять и выводить скорость вращения в оборотах в минуту (RPM).

// Пин, к которому подключён энкодер (должен поддерживать прерывание)
const int encoderPin = 31;

// Счётчик импульсов. "volatile" обязательно, так как переменная
// изменяется внутри прерывания!
volatile unsigned long pulseCount = 0;

// Переменные для расчёта RPM
unsigned long lastTime = 0;
const int pulsesPerRevolution = 20; // 20 прорезей на диске

// --- Обработчик прерывания (ISR) ---
// Эта функция вызывается АВТОМАТИЧЕСКИ при каждом импульсе
void countPulse() {
  pulseCount++; // Просто увеличиваем счётчик
}

void setup() {
  Serial.begin(115200);
  pinMode(encoderPin, INPUT);

  // Настраиваем прерывание:
  // При каждом нарастающем фронте (RISING) сигнала на encoderPin
  // будет вызываться функция countPulse
  attachInterrupt(digitalPinToInterrupt(encoderPin), countPulse, RISING);
}

void loop() {
  // Этот код будет выполняться примерно раз в секунду
  if (millis() - lastTime >= 1000) {
    // Временно отключаем прерывания, чтобы безопасно прочитать счётчик
    noInterrupts();
    unsigned long currentCount = pulseCount;
    pulseCount = 0; // Сбрасываем счётчик для следующего замера
    interrupts(); // Включаем прерывания обратно

    // --- Математика ---
    // 1. Считаем импульсы в секунду
    // (так как мы замеряем ровно за 1 секунду, currentCount это и есть имп/с)
    float pulsesPerSecond = currentCount; 
    
    // 2. Считаем обороты в секунду
    float revolutionsPerSecond = pulsesPerSecond / pulsesPerRevolution;
    
    // 3. Считаем обороты в минуту (RPM)
    float rpm = revolutionsPerSecond * 60;

    Serial.print("Скорость: ");
    Serial.print(rpm);
    Serial.println(" RPM");

    lastTime = millis(); // Сбрасываем таймер для следующей секунды
  }

  // Здесь может быть код управления моторами.
  // Например, заставим мотор вращаться на средней скорости:
  // analogWrite(motorSpeedPin, 128); 
}

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