Введение:
В этом уроке мы дополним робота «Малыш» радиомодулем HC-12, а так же построим пульт дистанционного управления на основе этого же модуля. При подаче питания на робота и включении пульта управления устройства будут соединяться самостоятельно на установленной частоте.
Видео:
Редактируется...
Нам понадобится:
Робот "Малыш":
- Робот «Малыш» в сборе x1 шт.
- Trema-модуль Радиомодуль HC-12 x1 шт.
Пульт:
- Arduino / Piranha UNO x1 шт.
- Trema Shield x1 шт.
- Trema-модуль Радиомодуль HC-12 x1 шт.
- Trema-модуль кнопка х5 шт.
- Battery Shield x1 шт.
Для реализации проекта нам необходимо установить библиотеки:
- Библиотека SoftwareSerial входит в базовый набор 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 и установив аналогичные скорость и канал.
После того, как все подготовительные работы выполнены, можно переходить непосредственно к сборке проекта.
Схема подключения робота «Малыш»:
Соберём механическую и электрическую части, как это описано в проектах № 1-8 в книге "Набор "Малыш"". Далее, на верхнюю стенку установим модуль HC-12, который подключается к шине UART (в примере используется программная шина UART).
Bluetooth | Motor Shield | |
---|---|---|
Радиомодуль HC-12 | вывод RX | цифровой вывод D9 |
вывод TX | цифровой вывод D8 | |
вывод S (Key) | цифровой вывод D13 | |
вывод V (Vcc) | любой вывод 5V на красной колодке | |
вывод G (GND) | любой вывод GND на чёрной колодке |
Воспользуемся схемой сборки из проекта №9 книги "Набор "Малыш"", но, вместо Trema-модуля ИК-приёмник, установим в корпус Trema-модуль Радиомодуль HC-12 (подключается к выводам D13, D8 и D9).
Схема сборки пульта управления:
Соберём блок пульта управления. Для этого вам понадобится Arduino / Piranha UNO:
Теперь установим Battery Shield на Arduino / Piranha UNO:
Во время установки Battery Shield должен быть в выключенном состоянии!
Далее на Battery Shield установите Trema Shield для удобного подключения модулей:
Теперь вновь воспользуемся модулем HC-12, который подключим к Trema Shield вместе с 5 Trema-кнопками:
Кнопка "ВПРАВО" | Trema Shield |
---|---|
S | D2 |
V | 5V |
G | GND |
Кнопка "ВЛЕВО" | Trema Shield |
---|---|
S | D3 |
V | 5V |
G | GND |
Кнопка "НАЗАД" | Trema Shield |
---|---|
S | D4 |
V | 5V |
G | GND |
Кнопка "ВПЕРЁД" | Trema Shield |
---|---|
S | D5 |
V | 5V |
G | GND |
Кнопка "СВЕТОДИОДЫ" | Trema Shield |
---|---|
S | D6 |
V | 5V |
G | GND |
Радиомодуль HC-12 | Trema Shield |
---|---|
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_led 6 // Вывод для кнопки управления светодиодами #define button_up 5 // Вывод для кнопки "Вперёд" #define button_down 4 // Вывод для кнопки "Назад" #define button_left 3 // Вывод для кнопки "Влево" #define button_right 2 // Вывод для кнопки "Вправо" #define radio_pin 13 // Определяем номер вывода S радио модуля byte data[5]; // Массив для получения данных с пульта bool LedFlag; // Переменная-флаг 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; // Если нажата кнопка ВПЕРЁД, то устанавливаем значение бита в 1 else data[0] = 0; // Если кнопка отпущена, то устанавливаем значение бита в 0 if (digitalRead(button_down)) data[1] = 1; // Если нажата кнопка НАЗАД, то устанавливаем значение бита в 1 else data[1] = 0; // Если кнопка отпущена, то устанавливаем значение бита в 0 if (digitalRead(button_left)) data[2] = 1; // Если нажата кнопка ВЛЕВО, то устанавливаем значение бита в 1 else data[2] = 0; // Если кнопка отпущена, то устанавливаем значение бита в 0 if (digitalRead(button_right)) data[3] = 1; // Если нажата кнопка ВПРАВО, то устанавливаем значение бита в 1 else data[3] = 0; // Если кнопка отпущена, то устанавливаем значение бита в 0 if (digitalRead(button_led) && LedFlag){ // Если кнопка НАЖАТА, и ФЛАГ УСТАНОВЛЕН, то LedFlag = false; // сбрасываем флаг data[4] = 1; // устанавиваем в 4 ячейке массива значение 1 } if (!digitalRead(button_led)){ // Если кнопка ОТПУЩЕНА, то LedFlag = true; // устанавливаем флаг и data[4] = 0; // меняем значение в 4 ячейке массива на 0 } mySerial.write(FRAME_HEADER); // Передаём заголовок пакета в последовательный порт for (int i = 0; i < SIZE; i++){ // Побайтово передаём данные mySerial.write(data[i]); // в последовательный порт } mySerial.write(FRAME_FOOTER); // Выводим байт конца пакета в последовательный порт data[4] = 0; // обнуляем значение в 4 ячейке массива delay(50); // Ждём 50 миллисекунд }
Код программы для «Малыша»:
// // ПОСЛЕ ЗАГРУЗКИ СКЕТЧА ОТКЛЮЧИ НА МАЛЫШЕ ВЫВОД TX, ИДУЩИЙ К ВЫВОДУ RX НА МОДУЛЕ!!! // #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 радио модуля uint8_t pinShield_H2 = 4; // Вывод, подключенный к драйверу, для задания направления вращения левым мотором uint8_t pinShield_E2 = 5; // Вывод ШИМ, подключенный к драйверу, для задания скорости левого мотора uint8_t pinShield_E1 = 6; // Вывод ШИМ, подключенный к драйверу, для задания скорости правого мотора uint8_t pinShield_H1 = 7; // Вывод, подключенный к драйверу, для задания направления вращения правым мотором uint8_t pinLED_RED = 11; // Вывод с красным светодиодом uint8_t pinLED_BLUE = 10; // Вывод с синим светодиодом uint16_t time_period = 200; // Частота мигания светодиодов (в миллисекундах) uint8_t valSpeed = 255; // Максимальная скорость ШИМ (число от 0 до 255) bool arrRoute[2] = {1, 1}; // Направление движения для каждого мотора ([0]- правый мотор, [1] - левый мотор) byte b = 0; // Создаём переменную для хранения текущего байта последовательного порта byte data[5]; // Создаём массив для хранения данных пакета uint16_t arrSpeed[2]; // Скорость для каждого мотора ([0]- правый мотор, [1] - левый мотор) uint32_t tmrLED; // Время последнего включения светодиодов uint32_t flgTime; // Флаг для задания времени принятия пакетов от Bluetooth телефона uint8_t flg; // Флаг кнопок uint32_t tmrWait; // Время до начала сопряжения с новыми устройствами bool flg_LED; // Флаг включения светодиодов uint32_t FindTimer; // Таймер получения команды void setup() { // ПОРТЫ mySerial.begin(9600); // инициируем работу с програмным монитором последовательного порта на скорости 9600 БОД Serial.begin(9600); // инициируем работу с аппаратным монитором последовательного порта на скорости 9600 БОД // МОТОРЫ pinMode(pinShield_H2, OUTPUT); // Конфигурируем вывод pinShield_H2 как выход (направление вращения левого мотора) pinMode(pinShield_E2, OUTPUT); // Конфигурируем вывод pinShield_E2 как выход (скорость вращения левого мотора, ШИМ) pinMode(pinShield_E1, OUTPUT); // Конфигурируем вывод pinShield_E1 как выход (скорость вращения правого мотора, ШИМ) pinMode(pinShield_H1, OUTPUT); // Конфигурируем вывод pinShield_H1 как выход (направление вращения правого мотора) // СВЕТОДИОДЫ pinMode(pinLED_RED,OUTPUT); // Конфигурируем вывод pinLED_RED как выход pinMode(pinLED_BLUE,OUTPUT); // Конфигурируем вывод pinLED_BLUE как выход tmrLED = millis(); // Устанавливаем таймер светодиодов равным millis() flg_LED = 0; // Сбрасываем флаг светодиодов } void loop() { if (mySerial.available() && mySerial.read() == 0xAA){ // Если получен какой-то пакет данных, начинающийся с 0xAA, то delay(10); // Ждём 10 мс для получения всего пакета данных FindTimer = millis(); // обновляем таймер получения команды for (int i = 0; (b = mySerial.read()) != FRAME_FOOTER; i++) data[i] = b; // выполняем цикл получения и записи полученных битов в массив пока не получим бит конца пакета if (data[0] == 1){ // Проверяем, если первый элемент массива равен 0, то нажата кнопка "ВВЕРХ". После этого if(data[2] == 1){flgTime = millis(); flg = 1;} else // проверяем, нажата ли кнопка ВЛЕВО и если да, то устанавливаем флаг 1 (Кнопка "стрелка вверх-влево") if(data[3] == 1){flgTime = millis(); flg = 3;} else // проверяем, нажата ли кнопка ВПРАВО и если да, то устанавливаем флаг 3 (Кнопка "стрелка вверх-вправо") {flgTime = millis(); flg = 2;} // Иначе устанавливаем флаг 2 (Кнопка "стрелка вверх") } else if (data[1] == 1){ // Проверяем, если второй элемент массива равен 1, то нажата кнопка "НАЗАД". После этого if(data[2] == 1){flgTime = millis(); flg = 7;} else // проверяем, нажата ли кнопка ВЛЕВО и если да, то устанавливаем флаг 7 (Кнопка "стрелка вниз-влево") if(data[3] == 1){flgTime = millis(); flg = 9;} else // проверяем, нажата ли кнопка ВПРАВО и если да, то устанавливаем флаг 9 (Кнопка "стрелка вниз-вправо") {flgTime = millis(); flg = 8;} // Иначе устанавливаем флаг 8 (Кнопка "стрелка вниз") } else // Иначе проверяем if(data[2] == 1) {flgTime = millis(); flg = 4;} else // если нажата только кнопка "ВЛЕВО", то устанавливаем флаг 4 if(data[3] == 1) {flgTime = millis(); flg = 6;} else // если нажата только кнопка "ВПРАВО", то устанавливаем флаг 5 {flgTime = millis(); flg = 5;} // Иначе если ничего не нажато, то устанавливаем флаг 5 if(data[4] == 1) {flg_LED = !flg_LED;} // Если нажата кнопка включения светодиодов, то меняем значение флага на противоположное // ======================================================================================================================================== switch (flg){ // Направ.вращ.лев.мотора Направ.вращ.прав.мотора Скорость лев.мотора Скорость прав.мотора case 1: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = (valSpeed / 2); arrSpeed[0] = valSpeed; break; // С-З case 2: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = valSpeed; arrSpeed[0] = valSpeed; break; // С case 3: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = valSpeed; arrSpeed[0] = (valSpeed / 2); break; // С-В case 4: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = 0; arrSpeed[0] = valSpeed; break; // З case 5: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = 0; arrSpeed[0] = 0; break; // Стоп case 6: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = valSpeed; arrSpeed[0] = 0; break; // В case 7: arrRoute[1] = 0; arrRoute[0] = 0; arrSpeed[1] = (valSpeed / 2); arrSpeed[0] = valSpeed; break; // Ю-З case 8: arrRoute[1] = 0; arrRoute[0] = 0; arrSpeed[1] = valSpeed; arrSpeed[0] = valSpeed; break; // Ю case 9: arrRoute[1] = 0; arrRoute[0] = 0; arrSpeed[1] = valSpeed; arrSpeed[0] = (valSpeed / 2); break; // Ю-В }} // =========================================================================================================================================== if (flg_LED) { // Если флаг установлен (была нажата кнопка включения фары) if (millis() - tmrLED > time_period) { // мигаем светодиодами с заданной частотой tmrLED = millis(); // сохраняем время digitalWrite(pinLED_RED, digitalRead(pinLED_BLUE)); // управляем питанием красного светодиода digitalWrite(pinLED_BLUE, !digitalRead(pinLED_BLUE)); // управляем питанием синего светодиода } } else { // если флаг сброшен, то digitalWrite(pinLED_RED, LOW); // гасим светодиоды digitalWrite(pinLED_BLUE, LOW); // } if(FindTimer + 1000 > millis()){ // Если в течение секунды был установлен флаг движения, то digitalWrite(pinShield_H2, arrRoute[1]); // тогда задаем направление вращения правого мотора digitalWrite(pinShield_H1, arrRoute[0]); // и левого мотора analogWrite(pinShield_E2, arrSpeed[1]); // Задаём скорость вращения для правого мотора analogWrite(pinShield_E1, arrSpeed[0]); // и для левого мотора } else { // Если сигнала к движению нет или связь с пультом утеряна, то digitalWrite(pinShield_H2, 1); // тогда задаем направление вращения правого мотора digitalWrite(pinShield_H1, 1); // и левого мотора analogWrite(pinShield_E2, 0); // Задаём скорость вращения для правого мотора analogWrite(pinShield_E1, 0); // и для левого мотора } }
В данном коде управление роботом осуществляется в три основных этапа:
- получение данных с модуля;
- установка флага движения, соответствующего одному из полученных значений;
- подача питания на моторы и задание направления их вращения.
Так же в коде присутствуют дополнительный блок включения/выключения светодиодов.
Подключение:
После подачи питания на робот и на пульт они автоматически соединятся и можно переходить непосредственно к управлению!
Обсуждение