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.