Введение:
В этом уроке мы дополним робота «Дройдика» модулем Bluetooth и создадим пульт дистанционного управления на базе аналогичного Bluetooth модуля. Управление направлением и скоростью движения робота будет осуществляться с помощью джойстика, а при обнаружении препятствий «Дройдик» откажется идти прямо, но согласится пойти назад.
Bluetooth модуль пульта будет выполнять роль мастера, а Bluetooth модуль робота - роль ведомого. Сопряжение мастера и ведомого достаточно выполнить только один раз. В дальнейшем, при подаче питания робота и питания пульта, устройства будут соединяться самостоятельно.
Скорость и направление движения робота будет зависеть от степени и направления отклонения джойстика. Робот сможет выполнять такие команды как движение вперёд или назад, с заворотом или без, разворот на месте влево или вправо и устанавливать все суставы в центральные положения. При разрыве связи между bluetooth модулями, робот перестанет идти, а все его сервоприводы ослабнут.
Подробно об управлении роботом и сопряжении Bluetooth модулей рассказано ниже, в разделе «Управление».
Видео:
Нам понадобится:
Робот "Дройдик":
- Робот-платформа «Дройдик» x1 шт.
- Arduino Uno x1 шт.
- Tream-Power Shield x1 шт.
- Trema-модуль Bluetooth HC-05 x1 шт.
- Trema-модуль кнопка x1 шт.
- Аккумулятор Ni-MH размером AA x5 шт. (либо два аккумулятора Li-ion размером 18650 и батарейный отсек для них).
Пульт:
- Arduino Uno x1 шт.
- Trema-Set Shield x1 шт.
- Trema-модуль Bluetooth HC-05 x1 шт.
- Trema-модуль джойстик x1 шт.
- аккумулятор Ni-MH (крона) и переходник под DC-jack x1 шт. (либо иной источник питания для Arduino Uno).
Для реализации проекта нам необходимо установить библиотеки:
- iarduino_Bluetooth_HC05 - для работы с Trema Bluetooth модулем HC-05.
- iarduino_HC_SR04_int - для работы с ультразвуковым датчиком расстояния HC-SR04+.
- Библиотеки SoftwareSerial и Servo входят в базовый набор Arduino IDE и не требуют установки.
О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.
Схема подключения пульта дистанционного управления:
- Установите Trema-Set Shield на Arduino Uno.
- Установите Trema-модуль джойстик на 4 посадочную площадку Trema-Set Shield.
- Установите Trema-модуль Bluetooth HC-05 на 3 посадочную площадку Trema-Set Shield.
- При желании все установленные на Trema-Set Shield модули можно закрепить, используя нейлоновые винты и стойки.
| Модули | Trema Set Shield |
|---|---|
| Trema-bluetooth (колодка RX-G-V-K-TX) | 3 площадка (выводы 8-G-V-3-9) |
| Trema-джойстик (колодка Y-G-V-K-X) | 4 площадка (выводы A1-G-V-2-A2) |
Благодаря применению Trema-Set Shield все модули устанавливаются без проводов, а наличие на каждой площадке всего двух колодок с разным количеством выводов, гарантирует правильность установки модулей.
Схема подключения робота «Дройдика»:
Соберите механику, подключите Tream-Power Shield, сервоприводы, датчик расстояния и откалибруйте робота, как это описано в уроке 38 Сборка «Дройдика». Далее на боковые панели установите Bluetooth HC-05 и кнопку, первый модуль подключается к шине UART (в примере используется аппаратная шина UART), а второй к любому выводу (в примере используется вывод D9).
| Датчик расстояния | Trema Power Shield | |
|---|---|---|
| Датчик HC-SR04+ | вывод Echo | вывод 2 на белой колодке |
| вывод Trig | вывод 3 на белой колодке | |
| вывод Vcc | любой вывод на красной колодке | |
| вывод Gnd | любой вывод на чёрной колодке |
Вы можете изменить вывод 3 используемый для подключения вывода Trig на любой другой, указав его при определении константы pinTrig. Но вывод Echo датчика HC-SR04+ можно подключать только к выводам с аппаратным прерыванием (2 или 3) так как в скетче используется библиотека iarduino_HC_SR04_int (если использовать библиотеку iarduino_HC_SR04, то и вывод Echo можно будет подключать к любым выводам).
| Сервоприводы | Trema Power Shield | |
|---|---|---|
| Верхние суставы | Левая нога «Дройдика» | вывод 4 на белой колодке |
| Правая нога «Дройдика» | вывод 5 на белой колодке | |
| Нижние суставы | Левая нога «Дройдика» | вывод 6 на белой колодке |
| Правая нога «Дройдика» | вывод 7 на белой колодке |
Вы можете изменить выводы 4-7 для подключения сервоприводов на любые другие, указав их в скетче при определении констант pinLeftTop, pinRightTop, pinLeftBot, pinRightBot.
Трехпроводные шлейфы сервоприводов устанавливаются следующим образом:
- Оранжевый провод подключается к выводу на белой колодке.
- Красный провод подключается к выводу на красной колодке.
- Коричневый провод подключается к выводу на чёрной колодке.
| Bluetooth | Trema Power Shield | |
|---|---|---|
| Bluetooth HC-05 | вывод RX | вывод TX на колодке Serial |
| вывод TX | вывод RX на колодке Serial | |
| вывод K (Key) | вывод D10 на белой колодке | |
| вывод V (Vcc) | любой вывод на красной колодке | |
| вывод G (GND) | любой вывод на чёрной колодке |
Вы можете изменить вывод D10 для подключения Bluetooth на любой другой, указав его в скетче при определении константы pinBTKey.
Выводы RX и ТХ модуля подключаются проводами к выводам TX и RX колодки с надписью Serial. Трёхпроводной шлейф подключённый к выводам K, V, G, устанавливается следующим образом:
- Вывод K (Key) подключается к выводу на белой колодке.
- Вывод V (Vcc) подключается к выводу на красной колодке.
- Вывод G (GND) подключается к выводу на чёрной колодке.
| Кнопка | Trema Power Shield | |
|---|---|---|
| Trema-кнопка | вывод S (Signal) | вывод D9 на белой колодке |
| вывод V (Vcc) | любой вывод на красной колодке | |
| вывод G (GND) | любой вывод на чёрной колодке |
Вы можете изменить вывод D9 для подключения кнопки на любой другой, указав его в скетче при определении константы pinKey.
Трёхпроводной шлейф подключённый к выводам S, V, G, устанавливается следующим образом:
- Вывод S (Signal) подключается к выводу на белой колодке.
- Вывод V (Vcc) подключается к выводу на красной колодке.
- Вывод G (GND) подключается к выводу на чёрной колодке.
Представленная ниже схема совпадает со схемой из инструкции по сборке «Дройдика», но к ней добавились два модуля: bluetooth (подключается к выводам D10, TX и RX) и кнопка (подключается к выводу D9).
Элементы схемы: «Э1»...«Э4» - сервоприводы, «Э5» - датчик расстояния, «Э6» - Trema blurtooth модуль, «Э7» Trema кнопка.
Код программы для пульта дистанционного управления:
// Подключаем библиотеки: //
#include <SoftwareSerial.h> // Подключаем библиотеку SoftwareSerial для общения с модулем по программной шине UART.
#include <iarduino_Bluetooth_HC05.h> // Подключаем библиотеку iarduino_Bluetooth_HC05 для работы с Trema Bluetooth модулем HC-05 (ссылка на библиотеку: /file/301.html).
// Объявляем объекты для работы с библиотеками: //
SoftwareSerial objSerial(9, 8); // Создаём объект objSerial указывая выводы (RX, TX) Arduino UNO, можно указывать любые выводы. Вывод RX Arduino подключается к выводу TX модуля, вывод TX Arduino подключается к выводу RX модуля.
iarduino_Bluetooth_HC05 objHC05(3); // Создаём объект objHC05 указывая любой вывод Arduino, который подключается к выводу «K» Bluetooth модуля.
// Определяем константы хранящие номера выводов Arduino: //
const uint8_t pinX = A2; // Определяем константу pinX указывая номер вывода Arduino к которому подключён вывод X джойстика (аналоговый).
const uint8_t pinY = A1; // Определяем константу pinY указывая номер вывода Arduino к которому подключён вывод Y джойстика (аналоговый).
const uint8_t pinK = 2; // Определяем константу pinK указывая номер вывода Arduino к которому подключён вывод K джойстика (цифровой).
// Определяем константы задающие люфт для центра джойстика: //
const uint16_t gapX = 30; // Изменение положения джойстика на +-gapX от центрального значения по оси X, будет восприниматься как 0
const uint16_t gapY = 30; // Изменение положения джойстика на +-gapY от центрального значения по оси Y, будет восприниматься как 0
// Объявляем переменные и массивы: //
uint16_t varX, cenX; // Объявляем переменную varX для хранения текущего положения джойстика по оси X и переменную cenX для хранения центрального положения джойстика по оси X.
uint16_t varY, cenY; // Объявляем переменную varY для хранения текущего положения джойстика по оси Y и переменную cenY для хранения центрального положения джойстика по оси Y.
bool varK; // Объявляем переменную varK для хранения состояния кнопки джойстика.
int8_t arrData[3]; // Объявляем массив arrData значения которого будем передавать по bluetooth, можно создавать массивы или переменные любых типов в т.ч. и char
//
void setup(){ //
// Устанавливаем режимы работы объявленных выводов: //
pinMode(pinX, INPUT); // Устанавливаем режим работы вывода pinX как вход.
pinMode(pinY, INPUT); // Устанавливаем режим работы вывода pinY как вход.
pinMode(pinK, INPUT); // Устанавливаем режим работы вывода pinK как вход.
// Считываем показания с кнопки джойстика: // Для входа в режим поиска и сопряжения с ведомым bluetooth модулем
varK=digitalRead(pinK); // Читаем логический уровень со входа pinK в переменную varK.
// Инициируем работу с bluetooth модулем: //
while(!objHC05.begin(objSerial)){;} // Инициируем работу с bluetooth модулем, указывая имя объекта или класса для управления шиной UART. При провале инициализации функция begin() вернёт false и тогда оператор while запустит её вновь.
// Определяем центральные положения джойстика по осям X, Y: //
cenX=analogRead(pinX); // Читаем уровень сигнала со входа pinX в переменную cenX, этот уровень будет считаться центральным положением джойстика.
cenY=analogRead(pinY); // Читаем уровень сигнала со входа pinY в переменную cenY, этот уровень будет считаться центральным положением джойстика.
// Выполняем поиск и сопряжение с ведомым bluetooth модулем: // Если сопряжение не выполнить (не нажать на джойстик при старте), то bluetooth модуль создаст соединение на основе своих старых настроек.
if(varK){while(!objHC05.createMaster("Droidik","1234")){;}} // Если кнопка джойстика нажата при старте, то устанавливаем bluetooth модулю роль мастера и пытаемся подключиться к ведомому с именем "Droidik" и PIN-кодом 1234. Если ведомый не доступен, то функция createMaster() вернёт false и тогда оператор while запустит её вновь.
while(!objHC05.checkConnect()){delay(1000);} // Проверка подключения к ведомому Bluetooth устройству. Если ведомый недоступен, то функция checkConnect() вернёт false и тогда оператор while запустит её снова через паузу в 1000 мс.
} //
//
void loop(){ //
// Считываем показания с джойстика: //
varX=analogRead(pinX); // Читаем уровень сигнала со входа pinX в переменную varX
varY=analogRead(pinY); // Читаем уровень сигнала со входа pinY в переменную varY
varK=digitalRead(pinK); // Читаем логический уровень со входа pinK в переменную varK
// Преобразуем считанные показания и добавляем люфт: //
if(varX<cenX-gapX){varX=map(varX,cenX-gapX, 0,0,-100);}else // Преобразуем значения varX от диапазона cenX-люфт...0 к диапазону 0...-100
if(varX>cenX+gapX){varX=map(varX,cenX+gapX,1023,0, 100);}else // Преобразуем значения varX от диапазона cenX+люфт...1023 к диапазону 0...+100
{varX=0;} // Оставшиеся значения cenX+-люфт преобразуем в 0
if(varY<cenY-gapY){varY=map(varY,cenY-gapY, 0,0,-100);}else // Преобразуем значения varY от диапазона cenY-люфт...0 к диапазону 0...-100
if(varY>cenY+gapY){varY=map(varY,cenY+gapY,1023,0, 100);}else // Преобразуем значения varY от диапазона cenY+люфт...1023 к диапазону 0...+100
{varY=0;} // Оставшиеся значения cenY+-люфт преобразуем в 0
// Сохраняем полученные значения в массив и передаём его: //
arrData[0]=int8_t(varX); // Положение джойстика по оси X в диапазоне от -100 до +100 с добавлением люфта по середине.
arrData[1]=int8_t(varY); // Положение джойстика по оси Y в диапазоне от -100 до +100 с добавлением люфта по середине.
arrData[2]=int8_t(varK); // Состояние кнопки джойстика 0 или 1.
objHC05.send(arrData); // Отправляем массив arrData на ведомое bluetooth устройство.
delay(50); // Примечания:
} // Функция send работает только при передаче данных на внешний Trema Bluetooth модуль использующий данную библиотеку!
// Если не ввести такое понятие как люфт для осей X (gapX) и Y (gapY), тогда будет трудно найти положение джойстика при котором квадропед будет стоять неподвижно.
В данном коде показания джойстика постоянно сохраняются в массив arrData после чего он отправляется по радиоканалу через bluetooth модуль. Скетч калибрует джойстик при старте, считывая показания для осей X и Y, которые до отключения питания считаются центральными. Показания осей X и Y отправляются в пределах значений от -100 до +100, а состояние кнопки джойстика отправляется как число 0 или 1.
Если подать питание с нажатой кнопкой джойстика, то в коде setup выполнится код вызова функции createMaster, которая установит bluetooth модулю роль мастера, инициирует поиск ведомого с именем "Droidik" и PIN-кодом "1234", и если такое ведомое устройство будет доступно, то произойдёт сопряжение и соединение с этим ведомым bluetooth модулем (именно такое имя и PIN будут присвоены bluetooth модулю на роботе). Если не нажимать на джойстик при подаче питания, то функция createMaster будет пропущена, а bluetooth модуль будет принимать попытки создать соединение на основе своих последних настроек. Таким образом сопряжение с bluetooth модулем робота достаточно выполнить всего один раз.
Функции begin(), createMaster() и checkConnect() объекта objHC05 возвращают true или false, и вызываются как условие оператора while(), то есть инициализация, назначение роли и проверка соединения bluetooth модулей выполняются до тех пор пока не будет получен положительный результат. Эти функции можно вызывать однократно только в том случае если Вы уверены что второй bluetooth модуль (модуль робота) точно включён, ему назначена роль ведомого, он готов к соединению и находится в радиусе действия связи. Иначе функция вернёт false, а код продолжит выполняться не отреагировав на ошибку.
Функция send() объекта objHC05 способна отправлять массивы и переменные любых типов, и так же возвращает true или false, сообщая о результате приёма данных вторым bluetooth модулем. В нашем случае данную функцию нет необходимости вызывать в условии оператора while(), так как эта функция и так постоянно вызывается в коде цикла loop().
С подробным описанием всех функций объекта objHC05 библиотеки iarduino_Bluetooth_HC05 можно ознакомиться на странице Wiki - Trema-модуль bluetooth HC-05.
Код программы для «Дройдика»:
Так как Trema-модуль Bluetooth HC-05 подключён к аппаратной шине UART, то перед загрузкой данного скетча нужно отсоединить провод, либо от вывода TX модуля, либо от вывода RX на плате Tream-Power Shield, а после загрузки скетча, подсоединить обратно.
// Подключаем библиотеки: //
#include <Servo.h> // Подключаем библиотеку Servo для работы с сервоприводами
#include <iarduino_HC_SR04_int.h> // Подключаем библиотеку iarduino_HC_SR04_int для работы с ультразвуковым датчиком HC-SR04+ (ссылка на библиотеку: /file/283.html).
#include <iarduino_Bluetooth_HC05.h> // Подключаем библиотеку iarduino_Bluetooth_HC05 для работы с Trema Bluetooth модулем HC-05 (ссылка на библиотеку: /file/301.html).
// Определяем номера выводов: //
const uint8_t pinEcho = 2; // Определяем константу с номером вывода подключённым к выводу Echo датчика расстояний (можно указывать только те выводы Arduino, которые могут работать с внешними прерываниями)
const uint8_t pinTrig = 3; // Определяем константу с номером вывода подключённым к выводу Trig датчика расстояний (может быть любым)
const uint8_t pinLeftTop = 4; // Определяем константу с номером вывода подключённым к верхнему сервоприводу левой ноги (может быть любым)
const uint8_t pinRightTop = 5; // Определяем константу с номером вывода подключённым к верхнему сервоприводу правой ноги (может быть любым)
const uint8_t pinLeftBot = 6; // Определяем константу с номером вывода подключённым к нижнему сервоприводу левой ноги (может быть любым)
const uint8_t pinRightBot = 7; // Определяем константу с номером вывода подключённым к нижнему сервоприводу правой ноги (может быть любым)
const uint8_t pinKey = 9; // Определяем константу с номером вывода подключённым к кнопке (может быть любым)
const uint8_t pinBTKey = 10; // Определяем константу с номером вывода подключённым к выводу K модуля bluetooth (может быть любым)
// Определяем константы: //
const uint8_t cenLeftTop = 100; // Определяем константу центрального угла в градусах для верхнего сервопривода левой ноги (по умолчанию = 100)
const uint8_t cenRightTop = 80; // Определяем константу центрального угла в градусах для верхнего сервопривода правой ноги (по умолчанию = 80 )
const uint8_t cenLeftBot = 60; // Определяем константу центрального угла в градусах для нижнего сервопривода левой ноги (по умолчанию = 60 )
const uint8_t cenRightBot = 120; // Определяем константу центрального угла в градусах для нижнего сервопривода правой ноги (по умолчанию = 120)
const uint8_t maxStepSize = 15; // Определяем константу максимального размера шага в градусах поворота верхних сервоприводов (чем больше угол, тем шире шаг)
const uint8_t maxStepHeight = 10; // Определяем константу максимальной высоты шага в градусах наклона в стороны при ходьбе (чем больше угол, тем выше шаг)
const uint32_t maxSpeed = 1000; // Определяем константу максимальной скорости, она хранит значение задержки в мкс, чем меньше это значение, тем выше максимальная скорость
const uint32_t minSpeed = 10000; // Определяем константу минимальной скорости, она хранит значение задержки в мкс, чем больше это значение, тем ниже минимальная скорость
const uint8_t minDistance = 10; // Определяем константу минимального расстояния в см, при котором робот должен остановиться
// Создаём объекты: //
iarduino_HC_SR04_int objSensor(pinTrig, pinEcho); // Создаём объект sensor для работы с датчиком расстояний, указывая номера выводов Trig и Echo
iarduino_Bluetooth_HC05 objHC05(pinBTKey); // Создаём объект objHC05 для работы с модулем bluetooth, указывая вывод K модуля
Servo objServoLeftTop; // Создаём объект servoLeftTop для работы с верхним левым сервоприводом
Servo objServoRightTop; // Создаём объект servoRightTop для работы с верхним правым сервоприводом
Servo objServoLeftBot; // Создаём объект servoLeftBot для работы с нижним левым сервоприводом
Servo objServoRightBot; // Создаём объект servoRightBot для работы с нижним правым сервоприводом
// Создаём переменные: //
uint8_t valPosition = 224; // Определяем переменную (движение) для хранения текущей позиции шага (счёт от 0 до 255 или обратно), начальная позиция 224
int8_t valTurning = 0; // Определяем переменную (поворот ) для пересчета размера шага в градусах поворота верхних сервоприводов (-10 - влево ... 0 - прямо ... +10 вправо)
uint8_t maxLeftSize = maxStepSize; // Определяем переменную максимального размера шага в градусах поворота верхнего левого сервопривода (чем меньше угол, тем сильнее робот будет уходить вправо)
uint8_t maxRightSize = maxStepSize; // Определяем переменную максимального размера шага в градусах поворота верхнего правого сервопривода (чем меньше угол, тем сильнее робот будет уходить влево )
bool flgPult = false; // Определяем флаг запрещающий чтение данных с пульта более 1 раза за отведённое время
bool flgPosition = false; // Определяем флаг запрещающий изменение позиции шага более 1 раза за отведённое время
uint32_t valSpeed = minSpeed; // Определяем переменную скорости (это задержка изменения позиции шага в мкс, чем меньше значение, тем выше скорость)
// Создаём переменные получения данных с пульта: //
uint8_t sumPultErr = 20; // Определяем переменную для подсчёта количества ошибок чтения данных с пульта
int8_t arrData[3]; // Объявляем массив arrData значения которого будут обновляться по bluetooth, можно создавать массивы или переменные любых типов в т.ч. и char
//
void setup(){ //
// Настраиваем работу с Trema Bluetooth модулем //
pinMode(pinKey, INPUT); // Переводим вывод pinKey (кнопка сопряжения) в режим входа
while(!objHC05.begin(Serial)) {;} // Инициируем работу с bluetooth модулем, указывая имя объекта или класса для управления шиной UART. При провале инициализации функция begin() вернёт false и тогда оператор while запустит её вновь.
while(!objHC05.checkConnect()){ // Проверка подключения к внешнему Bluetooth устройству, до тех пор пока связь не будет установлена...
if(digitalRead(pinKey)){ // Во время проверки наблюдаем не нажата ли кнопка сопряжения, если нажата, то ...
objHC05.createSlave("Droidik","1234"); // Назначаем Bluetooth модулю роль ведомого с именем "Droidik" и PIN-кодом "1234"
while(digitalRead(pinKey)){;} // Ждём пока кнопка сопряжения не будет отпущена
} delay(1000); // Устанавливаем задержку на 1 секунду
} //
// Настраиваем работу с датчиком расстояния: //
objSensor.averaging=50; // Устанавливаем коэффициент усреднения показаний датчика (0-без усреднений, 1-минимальное усреднение, ... 50-высокое усреднение, ...)
} //
void loop(){ //
// ========================================================================== //
// Получаем данные с пульта в массив arrData: //
if(millis()%50<5){ // Каждые 50 мс в течении первых 5 мс ...
if(flgPult){ // Если установлен флаг «flgPult» ...
if( objHC05.available()){ // Если есть принятые данные ...
objHC05.read(arrData); // Читаем полученные данные в массив arrData
sumPultErr=0; // Сбрасываем счётчик ошибок при получении данных с пульта
}else{if(sumPultErr<20){sumPultErr++;}} // Если нет принятых данных, то увеличиваем счётчик ошибок пульта sumPultErr
flgPult=false;} // Сбрасываем флаг «flgPult», чтоб чтение данных выполнилось только один раз за отведённые 5 мс
}else{flgPult=true;} // Если первые 5 мс из очередных 50 мс прошли, то устанавливаем флаг «flgPult» разрешая прочитать новые данные в следующие 50 мс.
// ========================================================================== //
// Читаем показания с датчика расстояния: //
if(objSensor.distance()<=minDistance){ // Если обнаружено препятствие (реальное расстояние до объекта возвращённое функцией sensor.distance() меньше чем указано в константе minDistance), то ...
if(arrData[1]>=0){ // Если джойстик не отклонен назад, то ...
arrData[1]=0; // Запрещаем роботу идти вперёд.
arrData[0]=0; // Запрещаем роботу разворачиваться на месте.
} // Роботу остаётся только идти назад (с заворотом или без).
} //
// ========================================================================== //
// Меняем значения переменных valPosition, valTurning и valSpeed: // Назначение переменных valPosition и valTurning подробно описано ниже, в разделе «Шагаем». А переменная valSpeed определяет скорость с которой эти переменные меняются.
if(micros()%valSpeed<500){ // Каждые valSpeed мкс в течении первых 500 мкс ...
if(flgPosition){ // Если установлен флаг «flgPosition» ...
// Если джойстик отклонён вперёд или назад: //
if(arrData[1]!=0){ // Если джойстик отклонён вперёд arrData[1]>0 или назад arrData[1]<0, то ...
valSpeed = map(abs(arrData[1]), 0, 100, minSpeed, maxSpeed); // Присваиваем переменной valSpeed задержку в мкс пропорционально отклонению джойстика arrData[1] вперёд или назад.
valTurning = arrData[0]/10; // Присваиваем переменной valTurning значение поворота от -10 до +10 пропорционально отклонению джойстика arrData[0] влево или вправо.
if(arrData[1]>0){valPosition++;} // Если джойстик отклонён вперёд arrData[1]>0, то увеличиваем позицию походки varPosition.
else {valPosition--;} // Если джойстик отклонён назад arrData[1]<0, то уменьшаем позицию походки varPosition.
// Если джойстик отклонён только влево или вправо: //
}else if(arrData[0]!=0){ // Иначе (если джойстик не отклонён вперёд или назад), но отклонён влево arrData[0]<0 или вправо arrData[0]>0, то ...
valSpeed = map(abs(arrData[0]), 0, 100, minSpeed, maxSpeed); // Присваиваем переменной valSpeed задержку в мкс пропорционально отклонению джойстика arrData[0] влево или вправо.
valPosition++; // Увеличиваем позицию походки.
if(arrData[0]>0){valTurning= 10;} // Если джойстик отклонён вправо arrData[0]>0, то присваиваем переменной valTurning максимальное значение = 10.
else {valTurning=-10;} // Если джойстик отклонён влево arrData[0]<0, то присваиваем переменной valTurning минимальное значение = -10.
// Стоим на месте: //
}else{ // Иначе (если джойстик не отклонён вперёд или назад, или влево, или вправо), то ...
/* Тут можно прописать действия */ //
/* которые будут выполняться если */ //
/* джойстик пульта не отклонён */ //
} //
flgPosition=false; // Сбрасываем флаг «flgPosition», чтоб чтение данных выполнилось только один раз за отведённые 500 мкс.
}}else{flgPosition=true;} // Если первые 500 мкс из очередных valSpeed мкс прошли, то устанавливаем флаг «flgPosition» разрешая прочитать новые данные в следующие valSpeed мкс.
// ========================================================================== //
// Установка сервоприводов в требуемые позиции (Шагаем): //
// Движение осуществляется в соответствии со значениями переменных valPosition и valTurning.
// Если значение переменной valPosition приращается, то робот будет идти вперёд. Если значение переменной valPosition убывает, то робот будет идти назад. Чем быстрее выполняется приращение/убавление переменной valPosition, тем быстрее шагает робот.
// Значение переменной valTurning управляет поворотом робота, 0-прямо, 1-прямо и чуть правее, (чем выше значение - тем круче поворот), 10 поворот вправо на месте. Отрицательные значения переменной valTurning действуют аналогично положительным, но поворот осуществляется влево.
// Полный шаг каждой ноги разбит на 4 сектора в соответствии со значением переменной valPosition. Каждая нога последовательно выполняет определённое движение для каждого сектора: вверх, вперёд, вниз, назад.
// Если получены данные о нажатии на джойстик arrData[2], то все суставы ног установятся в центральные положения.
if(sumPultErr<20){ // Если данные с пульта приходят, то ...
if(!objServoLeftTop. attached()){objServoLeftTop. attach(pinLeftTop );} // Указываем объекту servoLeftTop работать с выводом pinLeftTop, если он об этом не знает.
if(!objServoRightTop.attached()){objServoRightTop.attach(pinRightTop);} // Указываем объекту servoRightTop работать с выводом pinRightTop, если он об этом не знает.
if(!objServoLeftBot. attached()){objServoLeftBot. attach(pinLeftBot );} // Указываем объекту servoLeftBot работать с выводом pinLeftBot, если он об этом не знает.
if(!objServoRightBot.attached()){objServoRightBot.attach(pinRightBot);} // Указываем объекту servoRightBot работать с выводом pinRightBot, если он об этом не знает.
//
maxRightSize=maxStepSize; if(valTurning<0){maxRightSize=map(valTurning, 0,-10, maxStepSize, 0);} /* Прямо или влево */ // Корректируем значение maxRightSize (размера шага правой ноги) в соответствии со значением valTurning.
maxLeftSize =maxStepSize; if(valTurning>0){maxLeftSize =map(valTurning, 0, 10, maxStepSize, 0);} /* Прямо или вправо */ // Корректируем значение maxLeftSize (размера шага левой ноги) в соответствии со значением valTurning.
//
if(arrData[2]) {objServoLeftTop. write( cenLeftTop ); // Левая нога поворачивается в центр.
objServoRightTop.write( cenRightTop ); // Правая нога поворачивается в центр.
objServoLeftBot. write( cenLeftBot ); // Левая нога наклоняется в центр.
objServoRightBot.write( cenRightBot );}else // Правая нога наклоняется в центр.
if(valPosition<64 ){objServoLeftTop. write(map(valPosition, 0, 63, cenLeftTop - maxLeftSize , cenLeftTop + maxLeftSize )); // Левая нога поворачивается вправо => отходит назад.
objServoRightTop.write(map(valPosition, 0, 63, cenRightTop - maxRightSize , cenRightTop + maxRightSize ));}else // Правая нога поворачивается вправо => выходит вперёд.
if(valPosition<128){objServoLeftBot. write(map(valPosition, 64, 127, cenLeftBot - maxStepHeight , cenLeftBot +(maxStepHeight/2))); // Левая нога наклоняется вправо => переносит центр тяжести с себя на правую ногу, которая станет опорной.
objServoRightBot.write(map(valPosition, 64, 127, cenRightBot -(maxStepHeight/2) , cenRightBot + maxStepHeight ));}else // Правая нога наклоняется вправо => опускается вниз (становится опорной) и поднимает левую ногу.
if(valPosition<192){objServoLeftTop. write(map(valPosition, 128, 191, cenLeftTop + maxLeftSize , cenLeftTop - maxLeftSize )); // Левая нога поворачивается влево => выходит вперёд.
objServoRightTop.write(map(valPosition, 128, 191, cenRightTop + maxRightSize , cenRightTop - maxRightSize ));}else // Правая нога поворачивается влево => отходит назад.
/*valPosition<255*/{objServoLeftBot. write(map(valPosition, 192, 255, cenLeftBot +(maxStepHeight/2) , cenLeftBot - maxStepHeight )); // Левая нога наклоняется влево => опускается вниз (становится опорной) и поднимает правую ногу.
objServoRightBot.write(map(valPosition, 192, 255, cenRightBot + maxStepHeight , cenRightBot -(maxStepHeight/2)));} // Правая нога наклоняется влево => переносит центр тяжести с себя на левую ногу, которая станет опорной.
// ========================================================================== //
// Разрыв связи с пультом: //
}else{ // Если данные с пульта не приходят, то ...
objServoLeftTop. detach(); digitalWrite(pinLeftTop, LOW); // Разрываем связь объекта servoLeftTop с выводом pinLeftTop и устанавливаем на этом выводе уровень логического 0.
objServoRightTop.detach(); digitalWrite(pinRightTop, LOW); // Разрываем связь объекта servoRightTop с выводом pinRightTop и устанавливаем на этом выводе уровень логического 0.
objServoLeftBot. detach(); digitalWrite(pinLeftBot, LOW); // Разрываем связь объекта servoLeftBot с выводом pinLeftBot и устанавливаем на этом выводе уровень логического 0.
objServoRightBot.detach(); digitalWrite(pinRightBot, LOW); // Разрываем связь объекта servoRightBot с выводом pinRightBot и устанавливаем на этом выводе уровень логического 0.
} //
// ========================================================================== //
// Режим сопряжения: //
if(digitalRead(pinKey)){ // Проверяем не нажата ли кнопка сопряжения, если нажата, то ...
objHC05.createSlave("Droidik","1234"); // Назначаем Bluetooth модулю роль ведомого с именем "Droidik" и PIN-кодом "1234".
while(digitalRead(pinKey)){;} // Ждём пока кнопка сопряжения не будет отпущена.
} // Программа продолжит выполняться, но Дройдик никуда не пойдёт, так как модуль не сможет получать новые данные пока мастер не установит с ним соединение.
} //
Значения констант cenLeftTop, cenRightTop, cenLeftBot и cenRightBot должны быть изменены (откалиброваны) на действительные углы сервоприводов в градусах, при которых все суставы робота находятся в центральном положении. Это выполняется с использованием калибровочного скетча, как описано в уроке 38 Сборка «Дройдика».
В данном коде управление роботом осуществляется в три основных этапа: получение данных с пульта; изменение значений переменных valPosition, valTurning и valSpeed; установка сервоприводов в требуемые позиции. А так же в коде присутствуют дополнительные блоки: получение показаний с датчика расстояний; действия при разрыве связи с пультом; вход в режим сопряжения.
- Получение данных с пульта - выполняется один раз за 50 мс. Данный блок начинается с оператора «if» в условии которого написано «millis()%50<5». Это условие будет верно в течении 5 мс и ложно в течении оставшихся 45 из 50 мс, далее опять верно в течении 5 мс, и ложно в течении 45 мс, и т.д. Далее следует еще один оператор «if» условием которого является флаг «flgPult». Если флаг установлен, то выполнится код в теле оператора «if» где флаг «flgPult» будет сброшен. Этот флаг позволяет выполнять код получения данных с пульта только 1 раз за отведённые 50 мс. Последний оператор «if» данного блока в качестве условия принимает результат выполнения функции «objHC05.available()». Условие будет верно только если blurtooth модуль принял данные с пульта, тогда мы их сохраняем в массив «arrData» и сбрасываем счётчик ошибок пульта «sumPultErr». Если условие не выполнено (нет принятых данных), то мы увеличиваем счётчик ошибок пульта «sumPultErr», но счёт ошибок ведётся только до 20.
- Изменение значений переменных valPosition, valTurning и valSpeed - выполняется один раз за «valSpeed» мкс. Данный блок начинается с оператора «if» в условии которого написано «micros()%valSpeed<500». Это условие будет верно в течении 500 мкс и ложно в течении оставшегося времени от «valSpeed» мкс, далее опять верно в течении 500 мкс, и опять ложно, и т.д. Далее следует еще один оператор «if» условием которого является флаг «flgPosition». Этот флаг действует по аналогии с флагом «flgPult», он позволяет выполнять код изменения значений переменных только 1 раз за отведённые «valSpeed» мкс. Переменная «valSpeed» определяет интервал времени через который выполняется данный блок. Переменная «valPosition» используется в следующем блоке и определяет позицию шага от 0 до 255. Переменная «valTurning» используется в следующем блоке и определяет степень поворота от -10 до +10. Значения переменным «valPosition», «valTurning» и «valSpeed» присваиваются в соответствии с принятыми данными о положении джойстика по осям X «arrData[0]» и Y «arrData[1]».
- Установка сервоприводов в требуемые позиции - выполняется при наличии связи с пультом. Данный блок начинается с оператора «if» в условии которого написано «sumPultErr<20». Это условие будет верно если хотя бы одна из последних 20 попыток получить данные с пульта увенчалась успехом. Значит пульт включён и можно «шагать». Движение осуществляется в соответствии со значениями переменных «valPosition» и «valTurning». Полный шаг робота разбит на 256 частей (от 0 до 255) на которые и указывает значение переменной «valPosition». Если значение переменной «valPosition» не меняется, то робот стоит на месте. Если значение переменной «valPosition» увеличивается, то робот будет идти вперёд (переход от 255 к 0 считается увеличением). Если значение переменной «valPosition» убывает, то робот будет идти назад (переход от 0 к 255 считается уменьшением). Чем быстрее выполняется увеличение/уменьшение переменной «valPosition», тем быстрее шагает робот. Так как в предыдущем блоке переменная «valPosition» либо уменьшается, либо увеличивается, только на 1, значит скорость её уменьшения или увеличения зависит от переменной «valSpeed». Значение переменной «valTurning» ограничивает движение левой или правой ноги, что приводит к повороту робота влево или вправо. Переменная может содержать значения от -10 (максимальный поворот влево), 0 (без поворота), до +10 (максимальный поворот вправо).
- Получение показаний с датчика расстояний - выполняется постоянно. Данный блок начинается с оператора «if» в условии которого написано «objSensor.distance()<=minDistance». Это условие будет верно если расстояние в см. до препятствия полученное от функции «objSensor.distance()» меньше чем значение константы «minDistance» в см. Далее следует еще один оператор «if» условием которого является положение джойстика по оси Y «arrData[1]>=0». Это условие будет верно при любом положении джойстика, кроме его отклонения назад, или назад и в бок. Если оба условия выполнены, значит мы пытаемся идти вперёд (с заворотом или без) или развернуться на месте, не смотря на наличие препятствия. В таком случае присваиваем 0-му и 1-му элементам массива «arrData» значение 0, что соответствует центральному положению джойстика по осям X и Y. Тогда остальные блоки кода будут думать что джойстик не отклонён и робот будет стоять на месте.
- Действия при разрыве связи с пультом - выполняется если не выполнялся блок установки сервоприводов. Данный блок начинается после оператора «else» в условии оператора «if» которого написано «sumPultErr<20». Значит данный блок будет выполняться если последние 20 попыток чтения данных с пульта увенчались провалом, то есть пульт выключён. В этом блоке вызывается функция «detach()» для каждого объекта управления сервоприводом, которая разрывает связь объекта с выводом сервопривода. Далее на каждом выводе сервопривода устанавливается уровень логического 0. В результате выполнения кода данного блока, все сервоприводы ослабнут.
- Вход в режим сопряжения - выполняется при нажатии на кнопку сопряжения. Данный блок начинается с оператора «if» в условии которого написано «digitalRead(pinKey)». Это условие будет верно если нажата кнопка подключённая ко входу, номер которого указан в константе «pinKey». Далее следует вызов функции «createSlave("Droidik","1234");» объекта «objHC05» которая назначает bluetooth модулю роль ведомого с именем "Droidik" и PIN-кодом "1234", разрывает ранее установленную связь с ведомым (если она была) и стирает список ранее созданных пар. После чего модуль начинает ожидать подключение мастера который правильно укажет имя и PIN-код модуля. В конце блока вызывается функция «digitalRead(pinKey)» как условие оператора «while», то есть программа входит в цикл пока не будет отпущена кнопка подключённая ко входу, номер которого указан в константе «pinKey» и только потом продолжит выполнение кода. Эта строка предотвращает возможность многократного создания ведомой роли bluetooth модулю за одно нажатие кнопки.
Получение данных и работа с Trema-модулем Bluetooth HC-05 осуществляется через функции и методы объекта objHC05 библиотеки iarduino_Bluetooth_HC05, с подробным описанием которых можно ознакомиться на странице Wiki - Trema-модуль bluetooth HC-05.
Управление:
Сразу после сборки, загрузки скетча и подачи питания на пульт, и «Дройдика», суставы робота будут ослаблены, и он не будет реагировать на команды с пульта, так как Bluetooth модулям требуется сопряжение (создание пары). Сопряжение достаточно выполнить только один раз, bluetooth модули запомнят созданную пару в своей энергонезависимой памяти и будут пытаться соединится друг с другом при каждой последующей подаче питания.
- Отключите питание пульта (если оно было подано), нажмите на джойстик (как на кнопку) и подайте питание пульта. После выполнения этих действий bluetooth модулю пульта будет назначена роль мастера и он начнёт поиск ведомого с именем «Droidik» и PIN-кодом «1234».
- Подключите питание робота (если оно не было подано), нажмите и удерживайте кнопку сопряжения не менее 1 секунды (её можно нажимать в любое время). После нажатия на кнопку, bluetooth модулю робота будет назначена роль ведомого с именем «Droidik» и PIN-кодом «1234», и он будет ожидать подключение мастера.
- Для выполнения повторного сопряжения (если оно потребуется) нужно выполнить те же действия как для пульта, так и для робота.
- Как только связь будет установлена, суставы робота «оживут» и он будет выполнять команды пульта. Если Вы отключите питание пульта, то суставы робота ослабнут и вновь оживут при подаче питания пульта.
Управление роботом с пульта выполняется следующим образом:
- Если отклонить джойстик вперёд, то и робот пойдёт вперёд, а скорость будет зависеть от степени отклонения джойстика.
- Если отклонить джойстик назад, то и робот пойдёт назад, а скорость будет зависеть от степени отклонения джойстика.
- Если отклонить джойстик вперёд и влево, или вправо, то и робот пойдёт вперёд заворачивая влево, или вправо. Скорость будет зависеть от степени отклонения джойстика вперёд, а радиус поворота от степени отклонения джойстика влево, или вправо.
- Если отклонить джойстик назад и влево, или вправо, то и робот пойдёт назад заворачивая влево, или вправо. Скорость будет зависеть от степени отклонения джойстика назад, а радиус поворота от степени отклонения джойстика влево, или вправо.
- Если отклонить джойстик влево или вправо, но не отклонять его вперёд, или назад, то робот начнёт разворачиваться на месте влево, или вправо, а скорость разворота будет зависеть от степени отклонения джойстика.
- Если нажать на джойстик (при включённом питании), то все суставы ног робота установятся в центральные положения.
Примечание:
Так как Trema-модуль Bluetooth HC-05 установленный на «Дройдике» подключён к аппаратной шине UART, то перед загрузкой скетча робота нужно отсоединить провод, либо от вывода TX модуля, либо от вывода RX на плате Tream-Power Shield, а после загрузки скетча, подсоединить его обратно. Не рекомендуется использовать программную шину UART в устройствах управляющих сервоприводами, так как библиотека SoftwareSerial влияет на поведение библиотеки Servo во время передачи данных.
Можно обойтись без добавления кнопки сопряжения к «Дройдику», тогда в скетче нужно выполнять сопряжение автоматически при каждой подаче питания робота и пульта. Делается это следующим образом:
В скетче пульта исключите оператор «if» из предпоследней строки кода setup, оставив только тело оператора:
/* Было так: */ if(varK){while(!objHC05.createMaster("Droidik","1234")){;}} // Если кнопка джойстика нажата при старте ...
/* Стало так: */ while(!objHC05.createMaster("Droidik","1234")){;} // Теперь bluetooth модулю назначается роль мастера при каждом включении пульта!
В скетче робота исключите раздел «Режим сопряжения:» из кода loop, а код setup перепишите так:
void setup(){ //
while( !objHC05.begin(Serial) ){;} // Инициируем работу с bluetooth модулем, указывая имя объекта или класса для управления шиной UART. При провале инициализации функция begin() вернёт false и тогда оператор while запустит её вновь.
while( !objHC05.createSlave("Droidik","1234")){;} // Назначаем Bluetooth модулю роль ведомого с именем "Droidik" и PIN-кодом "1234"
while( !objHC05.checkConnect() ){delay(1000);} // Проверка подключения к внешнему Bluetooth устройству, до тех пор пока связь не будет установлена.
delay(500); //
objSensor.distance(); //
objSensor.distance(); //
} //
После внесения этих изменений к роботу можно не подключать кнопку, а при подаче питания не требуется нажимать на джойстик (или кнопку робота), но установка связи между пультом и роботом будет занимать больше времени.
Ссылки:
Комплект для повторения проекта
Что понадобится
Это быстрый ориентир по основным модулям и деталям. Полный список для повторения проекта смотрите в самой статье.
12 позиций
Платформа «Дройдик»
Комплект механики, для сборки платформы ходячего робота
Открыть в iArduino
Arduino Uno R3 (USB ATmega16U2)
Платформа c микроконтроллером ATmega328P на ядре AVR с чипом USB ATmega16U2 для разработки электронных устройств на языке C++
Открыть в iArduino
Trema-Power Shield
Плата расширения со встроенным стабилизатором на 5 В. 2 А. для удобного подключения периферийных устройств
Открыть в iArduino
Bluetooth HC-05 (Trema-модуль)
Позволит управлять вашими проектами с телефона или передавать данные между двумя Arduino
Открыть в iArduino
Кнопка, красная (Trema-модуль)
Тактовая кнопка, которая может служить источником сигналов (команд) для Ваших проектов
Открыть в iArduino
Аккумулятор Robiton AA Ni-MH (1,2 В / 2500 м·Ач)
Аккумулятор Ni-MH размером AA
Открыть в iArduino
Батарейный отсек 2×18650
Держатель для двух элементов питания форм-фактора 18650
Открыть в iArduino
Trema Set Shield
Плата расширения, которая упрощает процесс подключения модулей к Arduino.
Открыть в iArduino
Джойстик (Trema-модуль)
Фиксировать положение джойстика по двум координатам.
Открыть в iArduino
Аккумулятор Robiton Крона Ni-MH (9 В / 270 м·Ач)
аккумулятор Ni-MH (крона)
Открыть в iArduino
Кабель питания (DC Barrel Jack – Крона)
Соединительный провод для питания электроники от элемента питания «Крона»
Открыть в iArduino
Ультразвуковой дальномер HC-SR04
Сенсор определения расстояния до 4 м по отраженному звуковому сигналу
Открыть в iArduino

