Руководство · Начальный · 1 hour

Управление роботом по Bluetooth с модулем HC-05

Подключите HC-05 к Arduino, выполните сопряжение с телефоном и создайте безопасный протокол команд, сохраняющий отзывчивость робота при потере связи.

Введение

HC-05 — самый дешёвый способ добавить к роботу беспроводную связь. Это мост последовательной связи Bluetooth: всё, что отправляет телефон, приходит на последовательный порт Arduino как обычные байты, а всё, что Arduino печатает, возвращается на телефон. Ничто в скетче не обязано знать, что существует Bluetooth.

В этой простоте заключается привлекательность, но в ней же и ловушка. Радиосвязь — это не провод: она прерывается, запаздывает и отключается, пока робот ещё движется. Большая часть этого руководства посвящена именно этим различиям.

Подключение и ошибка, которую допускают почти все

Модуль HC-05 питает логику от 3.3 V, хотя плата-расширение принимает 5 V на VCC. Вывод RX не допускает 5 V.

HC-05 Arduino Uno / Nano Примечания
VCC 5V Стабилизатор платы справляется с этим
GND GND Общая земля со всем остальным
TXD D10 (программный RX) Выход 3.3 V корректно читается входом 5 V как логический уровень HIGH
RXD D11 (программный TX) через делитель Прямые 5 V со временем повредят модуль
EN / KEY D9 (необязательно) Подайте HIGH при включении для входа в режим AT-команд

Понизьте сигнал 5 V от Arduino до 3.3 V делителем из двух резисторов: 2 kΩ от вывода Arduino к RXD HC-05 и 3.3 kΩ от этой точки к земле. Это стоит двух компонентов и предотвращает сбой, который действительно невероятно трудно диагностировать: повреждённый таким образом модуль часто продолжает работать наполовину — он сопрягается, передаёт данные и молча игнорирует или портит то, что вы отправляете.

Схема подключения. Arduino Uno или Nano подключает линию TXD непосредственно к выводу D10 модуля HC-05; это обозначено как безопасное, поскольку выход 3.3 вольта читается как HIGH на 5-вольтовом входе. Обратный путь от вывода D11 к выводу RXD модуля проходит через делитель напряжения 2 кОм и 3.3 кОм, с предупреждением, что 5 вольт непосредственно на RXD частично повреждают модуль, поэтому он всё ещё сопрягается, но портит передаваемые данные.
Три из четырёх проводов просты. Четвёртому нужен делитель, потому что 5 V в вывод RXD модуля повреждают его медленно, а не очевидно. Скачать SVG

Используйте SoftwareSerial, а не выводы 0 и 1. Аппаратный последовательный порт используется совместно с USB, поэтому модуль, подключённый к нему, конфликтует с монитором последовательного порта и мешает загрузке.

#include <SoftwareSerial.h>
SoftwareSerial bt(10, 11);   // RX, TX

void setup() {
  Serial.begin(115200);      // USB, for debugging
  bt.begin(9600);            // HC-05 default baud
}

Сопряжение

Включите питание робота, и светодиод (LED) модуля начнёт быстро мигать — это означает «ожидание сопряжения». На телефоне выполните сопряжение с HC-05, используя код 1234 или 0000.

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

Проектирование протокола команд

Очевидный первый протокол — один символ на команду, и он работает примерно десять минут:

if (bt.available()) {
  char c = bt.read();
  if (c == 'F') forward();
  else if (c == 'B') back();
  else if (c == 'S') stop();
}

У него две проблемы.

Он не задаёт скорость. Каждая команда означает полный газ, поэтому робот либо стоит, либо несётся на полной. Вместо этого передавайте вместе с командой значение: одну букву и число, завершённые переводом строки, что легко разобрать и легко вводить вручную при отладке:

// "F180\n" — forward at 180 of 255
char buffer[16];
if (bt.available()) {
  int n = bt.readBytesUntil('\n', buffer, sizeof(buffer) - 1);
  buffer[n] = '\0';
  char cmd = buffer[0];
  int value = atoi(buffer + 1);
  ...
}

Он предполагает, что сообщение приходит целиком. Ненулевое bt.available() означает, что здесь находятся какие-то байты, а не все. Чтение по одному символу за цикл и немедленное действие приведут к обработке половины команды. Чтение до перевода строки делает разбор надёжным.

Сбой, который действительно важен: потеря связи

Проводной робот останавливается, когда вы отпускаете кнопку. Беспроводной робот продолжает выполнять последнюю команду — а если последней командой было «вперёд на полной скорости» и вы только что вышли из зоны покрытия, робот на полной скорости уезжает под диван.

Временная диаграмма. Пока удерживается кнопка, каждые 100 миллисекунд приходит ряд командных импульсов, затем они прекращаются на пунктирной линии с пометкой «сигнал потерян». Ниже показана полоса: без тайм-аута она остаётся красной и неограниченно долго продолжает движение после потери сигнала. Вторая полоса с 400-миллисекундным аварийным тайм-аутом переходит в остановленное состояние вскоре после последней команды.
Поток команд прекращается при потере связи, а не тогда, когда пользователь отпускает кнопку, поэтому робот должен самостоятельно срабатывать по тайм-ауту. Скачать SVG

Каждому беспроводному роботу нужен аварийный тайм-аут. Правило простое: если недавно не пришло ни одной команды, робот должен остановиться.

unsigned long lastCommandAt = 0;
const unsigned long TIMEOUT_MS = 500;

void loop() {
  if (readCommand()) lastCommandAt = millis();

  // Nothing heard recently? Assume the link is gone.
  if (millis() - lastCommandAt > TIMEOUT_MS) stop();
}

Чтобы это помогало, контроллер должен постоянно общаться. Пусть телефон повторяет текущую команду — примерно каждые 100 ms, пока удерживается кнопка, — а не отправляет её один раз при нажатии и один раз при отпускании. Тогда «кнопка отпущена» и «телефон вышел из зоны покрытия» дают одинаковый безопасный результат, и вам не нужно полагаться на сообщение об отпускании, которое может не прийти.

Для этого используйте millis(), а не delay(). delay(200) в цикле — это 200 ms, в течение которых команды не читаются, тайм-аут не проверяется, а робот ничего не слышит.

Режим AT-команд

Чтобы переименовать модуль или изменить скорость передачи, удерживайте EN/KEY на HIGH при включении. Светодиод мигает медленно — примерно раз в две секунды — это показывает, что вы находитесь в режиме команд, а не данных.

AT              → OK
AT+NAME=MyRobot → OK
AT+PSWD="4821"  → OK
AT+UART=38400,0,0

Команды в режиме AT должны отправляться на скорости 38400 baud независимо от скорости в режиме данных, и большинство прошивок требует одновременно возврат каретки и перевод строки. Если AT не возвращает ничего, почти всегда причина в этом несоответствии.

Повышать скорость передачи данных стоит, если вы передаёте телеметрию, но пока протокол отлаживается, оставьте 9600: более быстрая связь в основном затрудняет обнаружение ошибок кадрирования.

Проверьте перед движением

Три проверки в таком порядке:

  1. Проверка эха. Пусть Arduino печатает всё, что получает. Если введённая строка возвращается без изменений, подключение и скорость передачи правильные.
  2. Проверка разбора. Напечатайте разобранную букву команды и значение. Убедитесь, что F180 даёт F и 180, а не F и 1.
  3. Проверка тайм-аута. Отправьте команду движения вперёд, затем закройте приложение или отойдите. Робот должен остановиться за время тайм-аута. Если нет, исправьте это до установки колёс на пол.

Когда связь станет надёжной, тот же протокол команд без изменений переносится в автономного робота: соберите беспроводной слой в проекте робота с управлением с телефона или перейдите к ESP32, который раздаёт страницу управления по Wi-Fi и вообще не требует приложения.

Исследуйте граф

Входит в эти сборки

Проекты и учебные пути, в которые входит это руководство.

Дополнительные материалы

Источники