Полив на даче переехал из гаража в теплицу — тридцать метров по двору, и тянуть через них витую пару мне не хотелось: всё равно перережу лопатой. На столе лежали два HC-05 из коробки — те самые модули, что мы прицепляли к телефону в уроке про Bluetooth на SoftwareSerial. Одна и та же плата умеет обе роли: заводской режим — слейв, который ждёт звонка, а после пары команд модуль дозванивается сам. Идея простая: одна Uno становится мастером и звонит второй, дальше оба модуля ведут себя как кусок провода — что мастер напечатал в свой второй порт, то слейв читает из своего.
С проводной связкой двух плат мы разбирались в уроке про UART между двумя Arduino — крест-накрест и общая земля. Bluetooth-мост ставит на место провода радиопару, а схему на каждой плате оставляет прежнюю: TXD модуля напрямую в RX скетча, RXD через делитель 1 кОм и 2 кОм — вся раскладка с числами живёт в уроке про HC-05 с телефоном, повторять её не буду. Отличие только в настройке: проводу безразлично, кто кому хозяин, а модулям нужна пара «мастер со списком адресов» и «слейв, который ждёт».
План такой: сначала говорим мастеру, кому звонить — роль, фиксированный адрес и общий пароль в AT-режиме; затем проверяем, что после подачи питания мост собирается сам за несколько секунд; в конце — про то, что каналу мешает на реальной макетке: SoftwareSerial с его буфером на 64 байта и соседние библиотеки, которые закрывают прерывания в самый неподходящий момент. Механику входа в AT-режим на 38400 бод я не пересказываю — она целиком в уроке про AT-команды, здесь только команды этой пары.
Команды, из которых собирается пара
Роли задаёт AT+ROLE: единица делает модуль мастером, ноль оставляет слейвом. Дальше AT+CMODE=0 — режим фиксированного адреса: мастер не хватается за первого попавшегося соседа, а звонит только на записанную цель. Цель задаёт AT+BIND, и адрес для неё спрашивают у слейва командой AT+ADDR?: ответ — 12 шестнадцатеричных цифр, которые в BIND уходят без двоеточий, тремя группами через запятые, вида 2016,11,190232. Пароль AT+PSWD обязан совпасть на обоих модулях — заводской 1234 держится годами, но в мастерской, где дети и гости, я его меняю: это весь замок канала.
Скорости здесь двухэтажные, и на этажах спотыкаются чаще всего. В AT-режиме модуль говорит на 38400 бод — заводская цифра, вход через кнопку EN или пин KEY разобран в уроке про AT-команды. В режиме связи пара живёт на 9600 бод — и оба скетча зовут begin(9600). Если канал разгоняли командой AT+UART, скорость меняется на обоих концах сразу: мост собирается, светодиоды рапортуют о связи, а байты не идут — верный признак, что этажи разъехались.
Цепочку команд мастеру я гружу скетчем: руками через монитор я настраивал один раз ради понимания, а дальше скетч шлёт всё сам — у меня четыре модуля, и перепутать, кому какая роль досталась, проще, чем кажется. Ядро — функция at(): печатает команду во второй порт, копит ответ с таймаутом 800 мс на millis и возвращает строку для отчёта. Скрытая грабля списка сопряжённых: без AT+RMAAD мастер иногда цеплялся к старому знакомому с тем же именем — чистите список первой же командой.
С HC-06 в паре тоже можно: он вечно слейв — роль мастера всегда остаётся за HC-05, и командами этого урока его не настроишь: у соседа другой чип и другой синтаксис, свои AT+BAUD и имя по AT+NAME в ином виде. Пара HC-05 плюс HC-06 собирается проще: слейв ждёт с заводскими установками, мастер звонит на его адрес — и после установки связи оба говорят на 9600. Менять в HC-06 разве что имя и пин, чтобы не путать с соседями по столу.
// Конфигуратор МАСТЕРА: шлёт цепочку AT команд и печатает отчёт.// Схема и вход в AT-режим (кнопка EN / пин KEY) - в уроках про HC-05// и AT-команды. В AT-режиме скорость 38400, в связи будет 9600.#include <SoftwareSerial.h>SoftwareSerial bt(10, 11); // RX, TX String at(String cmd) { while (bt.available() > 0) bt.read(); // чистим хвосты прошлых ответов bt.println(cmd); String otvet = ""; unsigned long t0 = millis(); while (millis() - t0 < 800) { // таймаут на millis, не на delay if (bt.available() > 0) otvet += char(bt.read()); } return otvet;} void setup() { Serial.begin(38400); // монитор на скорости AT-режима bt.begin(38400); Serial.println(at("AT")); // OK — модуль жив Serial.println(at("AT+RMAAD")); // чистим список сопряжённых Serial.println(at("AT+ROLE=1")); // роль: мастер Serial.println(at("AT+CMODE=0")); // звонить только на фиксированный адрес Serial.println(at("AT+BIND=2016,11,190232")); // адрес слейва из AT+ADDR? Serial.println(at("AT+PSWD=1234")); // тот же пароль, что на слейве Serial.println(at("AT+UART=9600,0,0")); // скорость будущей связи Serial.println("— готово: перезапишите плату без KEY на KEY-ноге —");} void loop() {}Отчёт в мониторе читается сверху вниз, и каждая строка обязана заканчиваться OK. Пара нюансов, которые я собрал на своих модулях: BIND принимает адрес без двоеточий, и попытка вставить формат из ответа ADDR? с ними проваливается с надписью FAIL; команды уходят с парой управляющих символов в хвосте — println ставит их сам, поэтому после смены линии монитора на режим «без автопрокрутки» скетч перестаёт получать ответы; и AT+UART на слейве меняйте тем же скетчем, поменяв роль в цепочке — иначе получите мастера, который звонит на несуществующую скорость.
После перепрошивки и снятия KEY с логической единицы модуль возвращается в режим связи. Слейв ждёт с быстрым миганием, мастер перебирает — и через несколько секунд после подачи питания у обоих меняется характер вспышек: у мастера двойной сигнал с периодом около двух секунд, у слейва — ровный частый. Нога STATE на обоих модулях подскакивает до высокого уровня — на неё удобно повесить огонёк «канал жив», чтобы видеть состояние из-за шкафа. С этого момента оба SoftwareSerial-порта работают на 9600 бод, и мост ведёт себя как провод из урока про связку двух плат: что напечатано на одной стороне, читается на другой.
Канал в бою: кто и как портит байты
Первый враг — тот же, что у любого SoftwareSerial: буфер приёма на 64 байта и цикл, который не читает порт. На 9600 бод буфер переполняется примерно за 67 мс, как мы считали в уроке про буфер UART, поэтому в loop каждой платы чтение идёт в каждом обороте, а не раз в секунду. Полсекунды молчания — и хвост сообщения от слейва откушен молча: без флагов и сигналов, просто цифры дальше не идут.
Второй враг коварнее: прерывания. Приём SoftwareSerial ловится по фронту на пине и размечается по таймеру микросекунд — и если в этот момент библиотека закрыла прерывания, фронты едут мимо. Servo и tone дёргают их каждые несколько миллисекунд, лента FastLED в момент show() запирает на сотни микросекунд за раз — на 9600 бод бит длится около 104 мкс, и один такой затык ломает байт целиком. Механику фронта мы разбирали в уроке про attachInterrupt — здесь вывод практический: на плате, которая держит Bluetooth-канал, я не ставлю сервоприводы и ленты; если без них никак — опускаю канал до 4800 бод и читаю чаще.
Ещё одна ловушка — адрес с опечаткой: мастер с BIND, где в одной цифре промах, ищет цель вечно — вспышки идут, пара не собирается, и скетч тут ни при чём. Проверка занимает минуту: перечитать AT+ADDR? на слейве и сверить с шапкой скетча посимвольно. Вторая ловушка — питание, уже из урока с телефоном: в момент старта передатчика модуль просаживает линию, и без конденсатора на сотню микрофарад пара отваливается посреди работы — я ловил это как «отваливается после старта насоса», пока не повесил 100 мкФ на ноги модуля.
Ответы строю по короткой схеме эхо-протокола из урока про ok и bad: команда — одна строка ответа со статусом, и по ней видно, дошла ли посылка. Время хода по воздуху при 9600 — десятки миллисекунд на короткую команду; для полива и телеметрии раз в секунду этого за глаза, а плотные кадры по мосту собираются маркерами из урока про двоичный протокол. Эфир в симуляторе ниже заменён монитором: печатаемая строка считается командой, которую мастер шлёт по воздуху, ответ слейва возвращается той же дорогой.
| Команда | Мастеру | Слейву |
|---|---|---|
| AT+ROLE | =1 — дозванивается сам | =0 — заводской режим, ждёт звонка |
| AT+CMODE | =0 — фиксированный адрес | =0 или =1 — просто принимает звонок |
| AT+BIND | адрес слейва из AT+ADDR?, без двоеточий | не задаётся |
| AT+PSWD и AT+UART | тот же пин и та же скорость, что на слейве | тот же пин и та же скорость, что на мастере |
- 1. Что делает AT+CMODE=0 на мастере?
- 2. Модули соединились, светодиоды показывают пару, мониторы молчат. Первое, что я проверяю:
- 3. Адрес слейва в AT+BIND записывается...
- 4. Почему Servo на той же плате портит приём SoftwareSerial?
- 5. На 9600 бод буфер SoftwareSerial переполняется примерно за...
- Пара собирается тремя командами: AT+ROLE=1, AT+CMODE=0 и AT+BIND с адресом слейва без двоеточий; пароль AT+PSWD одинаков на обеих платах
- Два этажа скоростей: AT-режим 38400, связь 9600 — скетчи обеих плат обязаны говорить на одной цифре
- После подачи питания мост собирается сам за несколько секунд; двойная вспышка мастера раз в 2 с — связь есть, STATE-нога дублирует её огоньком
- Каналу мешают закрытые прерывания: Servo, tone и ленты бьют по SoftwareSerial — на плате с радио я их не ставлю
- Буфер на 64 байта при 9600 переполняется за ~67 мс: читать второй порт в каждом обороте loop
Частые вопросы по теме урока
- Что делает AT+CMODE=0 на мастере?
- CMODE=0 — режим фиксированного адреса: без него мастер цепляется к первому попавшемуся слейву с подходящим именем и пином. Фиксирует адрес, на который мастер звонит
- Модули соединились, светодиоды показывают пару, мониторы молчат. Первое, что я проверяю:
- Пара живёт на Bluetooth-уровне, скоростям скетчей безразлична. После AT+UART один конец часто остаётся на старой цифре — и байты едут мимо при живой связи. Совпадение этажей скоростей: 38400 в AT-режиме и 9600 в связи
- Адрес слейва в AT+BIND записывается...
- Ответ ADDR? даёт 12 hex-цифр; в BIND их вбивают группами 4-2-6 через запятые. Формат с двоеточиями модуль отклоняет с надписью FAIL. Тремя группами через запятые, без двоеточий
- Почему Servo на той же плате портит приём SoftwareSerial?
- Приём SoftwareSerial размечается по фронту и микросекундам; закрытые на сотни микросекунд прерывания ломают разметку — байт битый при исправном радио. Закрывает прерывания на десятки-сотни мкс — фронты битов едут мимо
- На 9600 бод буфер SoftwareSerial переполняется примерно за...
- 64 байта при 9600 бод — по миллисекунде на байт, итого около 67 мс. Молчание в loop дольше — хвост входящего потока гибнет без флагов. 67 мс