Введение:
В этом уроке мы дополним робота «Дройдика» радиомодулем HC-12, а так же построим пульт дистанционного управления на основе этого же модуля. При подаче питания на робота и включении пульта управления устройства будут соединяться самостоятельно на установленной частоте.
Видео:
Редактируется...
Нам понадобится:
Робот "Дройдик":
- Робот-платформа «Дройдик» x1 шт.
- Arduino / Piranha UNO x1 шт.
- Tream-Power Shield x1 шт.
- Trema-модуль Радиомодуль HC-12 x1 шт.
- Battery Shield x1 шт.
Пульт:
- Arduino / Piranha UNO x1 шт.
- Trema Shield x1 шт.
- Trema-модуль Радиомодуль HC-12 x1 шт.
- Trema-модуль кнопка х4 шт.
- Battery Shield x1 шт.
Для реализации проекта нам необходимо установить библиотеки:
- Библиотеки SoftwareSerial и Servo входят в базовый набор Arduino IDE и не требуют установки.
О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.
Схема подключения модуля для предварительной настройки:
Прежде, чем собирать весь проект, необходимо произвести предварительную настройку модулей. Для этого воспользуйтесь следующей инструкцией.
- Установите на Arduino / Piranha UNO плату Trema Shield;
- Подключите к Trema Shield модуль HC-12 как показано ниже:
- Загрузите в плату следующий скетч:
Радиомодуль HC-12 | Trema Shield |
---|---|
TX | D8 |
RX | D9 |
S | D13 |
V | 5V |
G | GND |
#include "SoftwareSerial.h" // подключаем библиотеку для работы с программным UART SoftwareSerial mySerial(8, 9) // Указываем выводы Arduino, к которым подключены (TX, RX) выводы МОДУЛЯ #define S 13 // Указываем, к какому выводу подключен вывод S модуля HC-12 void setup() { Serial.begin(9600); // Инициируем аппаратный последовательный порт mySerial.begin(9600); // Инициируем программный последовательный порт pinMode(S, OUTPUT); // Переводим вывод S модуля в режим выход digitalWrite(S, LOW); // Назначаем выводу уровень логического нуля delay(40); // Ждём пока модуль войдёт в режим AT команд } void loop() { if(mySerial.available()){ // Если в буфере программного последовательного порта есть данные Serial.write(mySerial.read()); // Перенаправляем их в аппаратный последовательный порт } if(Serial.available()){ // Если в буфере аппаратного последовательного порта есть данные mySerial.write(Serial.read()); // Перенаправляем их в программный последовательный порт } }
- Откройте монитор последовательного порта и в открывшемся окне введите следующие команды:
- AT+B**** - для задания скорости передачи данных;
- AT+C*** - для задания канала (частоты) работы модуля;
- Подробнее про AT-команды вы можете прочесть в статье, посвящённой модулю HC-12;
После того, как все необходимые манипуляции с модулем закончены - повторите всё тоже самое со вторым модулем, аналогично подключив его к той же плате Arduino и установив аналогичные скорость и канал.
После того, как все подготовительные работы выполнены, можно переходить непосредственно к сборке проекта.
Схема подключения робота «Дройдика»:
Соберите механику, подключите Trema Shield, сервоприводы и откалибруйте робота, как это описано в уроке 38 Сборка «Дройдика». Далее на боковую панель установите модуль HC-12, который подключается к выводам D13, D8 и D9 (в примере используется программная шина UART).
Сервоприводы | Trema Shield | |
---|---|---|
Верхние суставы | Левая нога «Дройдика» | D4 |
Правая нога «Дройдика» | D5 | |
Нижние суставы | Левая нога «Дройдика» | D6 |
Правая нога «Дройдика» | D7 |
Вы можете изменить выводы 4-7 для подключения сервоприводов на любые другие, указав их в скетче при определении констант pinLeftTop, pinRightTop, pinLeftBot, pinRightBot.
Подключите радиомодуль к Trema Shield на роботе так, как показано на рисунке ниже:
Радиомодуль HC-12 | Trema Shield |
---|---|
S | D13 |
TX | D8 |
RX | D9 |
V | 5V |
G | GND |
Трехпроводные шлейфы сервоприводов устанавливаются следующим образом:
- Оранжевый провод подключается к выводу на белой колодке.
- Красный провод подключается к выводу на красной колодке.
- Коричневый провод подключается к выводу на чёрной колодке.
Схема сборки пульта управления:
Соберём блок пульта управления. Для этого вам понадобится Arduino / Piranha UNO:
Теперь установим Battery Shield на Arduino / Piranha UNO:
Во время установки Battery Shield должен быть в выключенном состоянии!
Далее на Battery Shield установите Trema Shield для удобного подключения модулей:
Теперь вновь воспользуемся модулем HC-12, который подключим к Trema Shield вместе с 4 Trema-кнопками:
Вывод модуля Кнопка "ВПРАВО" | Вывод Arduino |
---|---|
S | D2 |
V | 5V |
G | GND |
Вывод модуля Кнопка "ВЛЕВО" | Вывод Arduino |
---|---|
S | D3 |
V | 5V |
G | GND |
Вывод модуля Кнопка "НАЗАД" | Вывод Arduino |
---|---|
S | D4 |
V | 5V |
G | GND |
Вывод модуля Кнопка "ВПЕРЁД" | Вывод Arduino |
---|---|
S | D5 |
V | 5V |
G | GND |
Вывод радиомодуля HC-12 | Вывод Arduino |
---|---|
S | D13 |
TX | D8 |
RX | D9 |
V | 5V |
G | GND |
Код программы для пульта дистанционного управления:
#include "SoftwareSerial.h" // Подключаем библиотеку для общения с модулем по шине UART SoftwareSerial mySerial(8, 9); // Создаём объект mySerial указывая выводы RX, TX #define FRAME_HEADER 0xAA // Задаём заголовок пакета #define FRAME_FOOTER '\n' // Задаём конец пакета #define button_up 5 // Вывод для кнопки "Вперёд" #define button_down 4 // Вывод для кнопки "Назад" #define button_left 3 // Вывод для кнопки "Влево" #define button_right 2 // Вывод для кнопки "Вправо" #define radio_pin 12 // Определяем номер вывода S радио модуля int8_t data[2]; // Массив для получения данных с пульта int SIZE = sizeof(data)/sizeof(data[0]); // Переменная, равная размерности массива с данными void setup() { mySerial.begin(9600); // инициируем работу с монитором последовательного порта pinMode(button_up, INPUT ); // настраиваем вывод на работу в режиме ВХОДА pinMode(button_down, INPUT ); // настраиваем вывод на работу в режиме ВХОДА pinMode(button_left, INPUT ); // настраиваем вывод на работу в режиме ВХОДА pinMode(button_right, INPUT ); // настраиваем вывод на работу в режиме ВХОДА } void loop() { if (digitalRead(button_up)) {data[0] = 1;} else // Если нажата кнопка ВПЕРЁД, то устанавливаем значение бита в 1 if (digitalRead(button_down)) {data[0] = -1;} else // Если нажата кнопка НАЗАД, то устанавливаем значение бита в -1 {data[0] = 0;} // иначе устанавливаем значение бита в 0 if (digitalRead(button_right)){data[1] = 1;} else // Если нажата кнопка ВПРАВО, то устанавливаем значение бита в 1 if (digitalRead(button_left)) {data[1] = -1;} else // Если нажата кнопка ВЛЕВО, то устанавливаем значение бита в -1 {data[1] = 0;} // иначе устанавливаем значение бита в 0 mySerial.write(FRAME_HEADER); // Передаём заголовок пакета в последовательный порт for (int i = 0; i < SIZE; i++) { // Побайтово передаём данные mySerial.write(data[i]); // в последовательный порт } mySerial.write(FRAME_FOOTER); // Выводим байт конца пакета в последовательный порт delay(50); // Ждём 50 миллисекунд }
Код программы для «Дройдика»:
// Подключаем библиотеки: #include "Servo.h" // Подключаем библиотеку Servo для работы с сервоприводами #include "SoftwareSerial.h" // Подключаем библиотеку SoftwareSerial для общения с модулем по программной шине UART SoftwareSerial mySerial(8, 9); // Создаём объект mySerial указывая выводы RX, TX // Определяем константы: #define FRAME_HEADER 0xAA // Определяем заголовок пакета #define FRAME_FOOTER '\n' // Определяем конец пакета #define radio_pin 13 // Определяем номер вывода S радио модуля // Определяем номера выводов: 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 cenLeftTop = 119; // Определяем константу центрального угла в градусах для верхнего сервопривода левой ноги (по умолчанию = 100) const uint8_t cenRightTop = 65; // Определяем константу центрального угла в градусах для верхнего сервопривода правой ноги (по умолчанию = 80 ) const uint8_t cenLeftBot = 79; // Определяем константу центрального угла в градусах для нижнего сервопривода левой ноги (по умолчанию = 60 ) const uint8_t cenRightBot = 76; // Определяем константу центрального угла в градусах для нижнего сервопривода правой ноги (по умолчанию = 120) const uint8_t maxStepSize = 25; // Определяем константу размера шага в градусах поворота верхних сервоприводов (чем больше угол, тем шире шаг) const uint8_t maxStepHeight = 20; // Определяем константу высоты шага в градусах наклона в стороны при ходьбе (чем больше угол, тем выше шаг) // Создаём объекты: Servo objServoLeftTop; // Создаём объект servoLeftTop для работы с верхним левым сервоприводом Servo objServoRightTop; // Создаём объект objServoRightTop для работы с верхним правым сервоприводом Servo objServoLeftBot; // Создаём объект objServoLeftBot для работы с нижним левым сервоприводом Servo objServoRightBot; // Создаём объект objServoRightBot для работы с нижним правым сервоприводом // Создаём переменные: 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 раза за отведённое время int8_t b = 0; // Создаём переменную для хранения текущего байта последовательного порта int8_t data[2]; // Создаём массив для хранения данных пакета void setup(){ mySerial.begin(9600); // инициируем работу с монитором последовательного порта // Указываем объектам сервоприводов их выводы: objServoLeftTop. attach(pinLeftTop); // Указываем объекту objServoLeftTop работать с выводом pinLeftTop objServoRightTop.attach(pinRightTop); // Указываем объекту objServoRightTop работать с выводом pinRightTop objServoLeftBot. attach(pinLeftBot); // Указываем объекту objServoLeftBot работать с выводом pinLeftBot objServoRightBot.attach(pinRightBot); // Указываем объекту objServoRightBot работать с выводом pinRightBot // Устанавливаем центральные углы сервоприводов: objServoLeftTop. write (cenLeftTop); // Устанавливаем центральную позицию (угол cenLeftTop ) для сервопривода подключённого к выводу pinLeftTop objServoRightTop.write (cenRightTop); // Устанавливаем центральную позицию (угол cenRightTop) для сервопривода подключённого к выводу pinRightTop objServoLeftBot. write (cenLeftBot); // Устанавливаем центральную позицию (угол cenLeftBot ) для сервопривода подключённого к выводу pinLeftBot objServoRightBot.write (cenRightBot); // Устанавливаем центральную позицию (угол cenRightBot) для сервопривода подключённого к выводу pinRightBot } void loop(){ memset(data, 0, 2); // очищаем массив if( mySerial.available() && mySerial.read() == 0xAA){ // Если получен пакет данных, начинающийся с 0xAA, то delay(10); // Ждём 10 мс для получения всего пакета данных for (int i = 0; (b = mySerial.read()) != FRAME_FOOTER; i++){ // выполняем цикл получения и записи полученных битов в массив пока не получим бит конца пакета data[i] = b; } } if(data[0]!=0){ // Если нажата кнопка ВПЕРЁД или НАЗАД, то if(data[1] != 0){ // Проверяем, нажаты ли кнопки поворота: if(data[1] >0) {valTurning = 5;} // Если так же нажата кнопка ВПРАВО, то увеличиваем значение поворота valTurning else {valTurning = -5;} // Если так же нажата кнопка ВЛЕВО, то уменьшаем значение поворота valTurning }else { valTurning = 0; // Иначе двигаемся прямо } if(data[0]>0){valPosition += 10;} // Если нажата кнопка ВПЕРЁД, то увеличиваем позицию походки varPosition. else {valPosition -= 10;} // Если нажата кнопка НАЗАД, то уменьшаем позицию походки varPosition. }else if(data[1]!=0){ // Иначе (если джойстик не отклонён вперёд или назад), но отклонён влево arrData[0]<0 или вправо arrData[0]>0, то ... valPosition += 10; // Увеличиваем позицию походки. if(data[1]>0){valTurning = 10;} // Если джойстик отклонён вправо arrData[0]>0, то присваиваем переменной valTurning максимальное значение = 10. else {valTurning = -10;} // Если джойстик отклонён влево arrData[0]<0, то присваиваем переменной valTurning минимальное значение = -10. } 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(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))); // Правая нога наклоняется влево => переносит центр тяжести с себя на левую ногу, которая станет опорной. } }
Управление:
После подачи питания на робота и на пульт они автоматически соединятся и можно переходить непосредственно к управлению! Нажатие кнопок будет заставлять робота двигаться в выбранном направлении.
Если скорость движения или углы поворота кажутся вам слишком большими или, наоборот, слишком малыми, то измените величину, на которую изменяются переменные valTurning
и ValPosition
в скетче выше!
Обсуждение