====== Бутылочный Bluetooth-катер ====== {{ :projects:bottleboat:bottleboat_overvew.jpg?nolink&700 |}} * Платформы: Strela * Языки программирования: Arduino (C++) * Тэги: лодка, катер, бутылка, утки, корм. ===== Что это? ===== Прогуливаясь в городских парках, часто можно встретить людей, которые с радостью и интересом бросают корм в воду и наблюдают как утки ныряют за ним. Вы также можете поучаствовать в этом забавном процессе, собрав катер, управляемый с мобильного устройства, который плавает по воде и по сигналу кормит уток всякими вкусняшками, тем самым привлекая и удивляя прохожих. ===== Что нам понадобится? ===== {{ :projects:bottleboat:bottleboat_parts.jpg?nolink&700 |}} - [[amp>product/strela?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Strela]] - [[amp>product/bluetooth-bee?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Bluetooth Bee]] - [[amp>product/dc-motor-12mm?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Мотор 12 мм 1:100]] 2 шт. - [[amp>product/shaft-coupler-3mm?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Втулки на вал мотора (⌀ 3 мм, пара)]] - [[amp>product/servo-fs5103r?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Привод постоянного вращения FS5103R]] - [[amp>product/aluminum-servo-horn?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Втулка на вал сервопривода]] - [[amp>product/servo-bracket-pair?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Соединительные скобы]] - [[amp>product/usb-cable?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Кабель USB (A — B)]] - 4× винт М1.6 для крепления моторов - NiMH-аккумулятор, подойдёт например от радиоуправляемой игрушки - Телефон на базе ОС Android - Многожильный монтажный провод с сечением не менее 1 мм² (2 шт. разного цвета) - Одножильный монтажный провод c сечением не менее 4 мм² - Крепёжные элементы: двусторонняя клейкая лента, болты, гайками, шайбы. - Набор пластиковых бутылок разной формы и ёмкости - Корм для уток, в нашем случае сушки ===== Как собрать? ===== - Вставьте модуль Bluetooth Bee в специальный разъём форм-фактора Xbee на платформе Strela.{{ :projects:bottleboat:bottleboat_build1.jpg?nolink&700 |}} - Возьмите 2 коллекторных мотора, припаяйте к их контактным площадкам провода и подключите к платформе Strela через специальные клеммники для подключение моторов. Полярность тут не важна, если вдруг мотор будет вращаться в другую сторону, это можно будет исправить программно.{{ :projects:bottleboat:bottleboat_build2.jpg?nolink&700 |}} - Перейдём к изготовлению корпуса катера: * Возьмите 1 большую бутылку, вырежьте в ней сверху что то наподобие люка и через него вставьте в носовую часть бутылки аккумулятор. * В задней центральной части с помощью маленьких винтов установите коллекторные моторы по бокам бутылки, так чтобы они находились внутри бутылки, а вал выходил наружу. * Возьмите ещё 2 средних по размеру бутылки отрежьте у них донышко и через специальные втулки закрепите их к валу каждого из моторов. Также вырежьте в них лопасти, чтоб нашему катеру было легче передвигаться по воде. * Подключите платформу Strela к компьютеру, прошейте скетч, приведённый ниже, и подключите к ней аккумулятор через клеммник ''PWR'', соблюдая полярность. * В таком состоянии удобно откалибровать скорость вращения моторов. Про калибровку читайте ниже.{{ :projects:bottleboat:bottleboat_build3.jpg?nolink&700 |}} - Теперь подключите сервопривод постоянного вращения через 3-проводной шлейф в разъём ''P1''. В итоге должна получиться схема, как на рисунке ниже.{{ :projects:bottleboat:bottleboat_scheme.png?nolink&700 |}} - Установите платформу Strela в носовой части бутылки сверху на аккумулятор. Закрепите сервопривод через соединительную скобу в верхней центральной части бутылки. Возьмите толстый одножильный провод, скрутите его в спираль и, используя втулку, прикрутите к сервоприводу.{{ :projects:bottleboat:bottleboat_build4.jpg?nolink&700 |}} - Так как носовая часть катера самая тяжелая, нам понадобятся дополнительные поплавки, чтобы удерживать её над поверхностью воды. Для этого возьмите две маленьких бутылки и примотайте их по бокам скотчем в передней части большой бутылки. Для защиты устройства кормления от брызг возьмите среднюю бутылку, отрежьте донышко и оденьте её на манер чехла, чтобы сервопривод оказался внутри бутылки. Убедитесь, что ничего не мешает спирали свободно вращаться.{{ :projects:bottleboat:bottleboat_build5.jpg?nolink&700 |}} - Теперь можно смело ставить катер на воду, загружать его вкусняшками и отправлять в плаванье. Если катер при движении клонит в сторону, повторите процесс калибровки. ==== Калибровка: настройка скорости вращения моторов ==== * Сразу при подаче питания удерживайте кнопку ''S1'', должен пикнуть 3 раза зуммер, зажечься ''2'' и ''3'' светодиод, моторы начнут вращаться. * Для выхода из режима калибровки без сохранения снова нажмите на кнопку ''S1''. * Кнопками ''S2'' и ''S3'' отрегулируйте скорость вращения обоих моторов, чтобы они вращались с одинаковой частотой. * Нажмите кнопку ''S4'' для сохранения результатов в энергонезависимую память. После чего зуммер снова пикнет 3 раза, светодиоды ''2'' и ''3'' погаснут, а светодиоды ''1'' и ''4'' в свою очередь зажгутся, моторы остановятся. ==== Настройка управления ==== * Для Android существует огромное количество приложений, при помощи которых можно управлять Arduino при помощи Bluetooth. В данном случае для управления Strela мы использовали [[https://play.google.com/store/apps/details?id=braulio.calle.bluetoothRCcontroller|Arduino Bluetooth RC Car]]. * Интерфейс приложения напоминает джойстик от игровой консоли. Зелёный индикатор в верхнем левом углу сигнализирует о том, что мы соединены с Bluetooth устройством. Если же горит красный индикатор, зайдите в меню «Настройки», выполните поиск Bluetooth устройств и подключитесь к Bluetooth Bee. При первом запуске возможен запрос пароля, по умолчанию «1234». {{ :projects:bottleboat:bottleboat_control.png?nolink&700 |}} * В управлении моторов всё интуитивно понятно (вперёд, назад, влево, вправо). В правом верхнем углу есть ползунок настройки скорости моторов. По нажатию на кнопку ''Δ'' сервопривод начнёт вращаться и утиные угощения посыпятся в воду. Повторное нажатие остановит процесс. Также можно посигналить уткам с помощью зуммера, нажав на кнопку с изображением пищалки. ===== Алгоритм ===== * Сразу после подачи питания программа проверяет было ли что-либо записано в энергонезависимую память. * если да, то считываем калибровочные значения скорости вращения моторов из энергонезависимой памяти. * Моторы вращаются не синхронно? * если да, то производим калибровку скорости вращения моторов относительно друг друга. * Находим поправочный коэффициент, как отношение значений скоростей одного мотора к другому, при которых оба мотора вращаются с одинаковой скоростью. * Если есть данные, которые приходят через Bluetooth, выполняем команду, пришедшую со смартфона. ===== Исходный код ===== // библиотека для работы с платформой Strela #include // библиотека для работы с I2C-расширителем портов #include // EEPROM — энергонезависимая память // библиотека для записи и считывания информации с EEPROM #include // библиотека для работы с сервоприводами #include // создадим объект для управления сервоприводом Servo myservo; // это число мы будем использовать в логике поворотов int defaultSpeed = 100; // w1 и w2 - это скорость вращения первого и второго мотора // скорость регулируется в пределах от -255 до 255 // если это число положительное - мотор будет вращаться вперёд // если отрицательное - назад // если баланс скорости вращения моторов не бы совершен // по умолчанию равны 100 int w1 = 100; int w2 = 100; // значение поправочного коефициента // скорости одного мотора к другому float k = 1; // переменная хранит контрольную сумму // проверка на то, было ли что нибудь записано в EEPROM int sum; // переменные состояния каждой из 4 кнопок // была ли кнопка отпущена? boolean button1WasUp = true; boolean button2WasUp = true; boolean button3WasUp = true; boolean button4WasUp = true; void setup() { // я неправильно прикрутил один мотор // поэтому, чтобы их не перекручивать // можно воспользоваться этой функцией. // направление вращения мотора 2 будет изменено. motorConnection(0, 1); // открываем последовательный порт со скоростью 9600 бод Serial.begin(9600); // Bluetooth Bee по умолчанию использует скорость 9600 бод Serial1.begin(9600); // пикнем зуммером с частотой 1000 Гц, 100 мс tone(BUZZER, 1000, 100); delay(500); // считываем значение из 4 ячейки памяти EEPROM sum = EEPROMReadInt(4); // записывали ли мы в EEPROM значение баланса скоростей if (sum == 777) { // чтение из памяти значение баланса скоростей w1 = EEPROMReadInt(0); w2 = EEPROMReadInt(2); } delay(100); // нажата ли кнопка S1 // вход в меню настройки баланса скорости моторов if (uDigitalRead(S1)) { // пищим 3 раза зуммером tone(BUZZER, 500, 50); delay(300); tone(BUZZER, 500, 50); delay(300); tone(BUZZER, 500, 50); delay(300); // вызываем функцию баланса скорости моторов balanceMotors(); } // зажгём первый и четвёртый светодиод uDigitalWrite(L1, HIGH); uDigitalWrite(L4, HIGH); // вызываем функцию нахождение поправочного коефициента // скорости одного мотора к другому correction(); } void loop() { // если появились новые команды // вызываем функцию управления if (Serial1.available() > 0) { control(); } // вывод скоростей serialPrint(); } // функция настройки баланса скорости моторов void balanceMotors() { while (1) { // зажгём второй и третий светодиод uDigitalWrite(L2, HIGH); uDigitalWrite(L3, HIGH); // если левое колесо (мотор 1) медленнее правого (мотор 2) // нам нужно определить клик кнопки // определить момент «клика» несколько сложнее, чем факт того, // что кнопка сейчас просто нажата. Для определения клика мы // сначала понимаем, отпущена ли кнопка прямо сейчас boolean button2IsUp = uDigitalRead(S2); // если кнопка была отпущена и не отпущена сейчас // и значение первого мотора менее 255 if (!button2WasUp && button2IsUp && w1 < 255) { // может это «клик», а может и ложный сигнал (дребезг), // возникающий в момент замыкания/размыкания пластин кнопки, // поэтому даём кнопке полностью «успокоиться» delay(10); // и считываем сигнал снова button2IsUp = uDigitalRead(S2); // если она всё ещё нажата, значит это клик! if (button2IsUp) { // Скорость первого мотора увеличиваем, а второго уменьшаем w1++; w2--; } } // запоминаем последнее состояние кнопки для новой итерации button2WasUp = button2IsUp; // если правое колесо (мотор 2) медленнее левого (мотор 1) // нам нужно определить клик кнопки // определить момент «клика» несколько сложнее, чем факт того, // что кнопка сейчас просто нажата. Для определения клика мы // сначала понимаем, отпущена ли кнопка прямо сейчас boolean button4IsUp = uDigitalRead(S4); // если кнопка была отпущена и не отпущена сейчас // и значение второго мотора менее 255 if (!button4WasUp && button4IsUp && w2 < 255) { // может это «клик», а может и ложный сигнал (дребезг), // возникающий в момент замыкания/размыкания пластин кнопки, // поэтому даём кнопке полностью «успокоиться» delay(10); // и считываем сигнал снова button4IsUp = uDigitalRead(S4); // если она всё ещё нажата, значит это клик! if (button4IsUp) { // Скорость второго мотора увеличиваем, а первого уменьшаем w1--; w2++; } } // запоминаем последнее состояние кнопки для новой итерации button4WasUp = button4IsUp; // Индикация увеличение скорости первого мотора if (uDigitalRead(S2)) { uDigitalWrite(L4, HIGH); } else { uDigitalWrite(L4, LOW); } // Индикация увеличение скорости второго мотора if (uDigitalRead(S4)) { uDigitalWrite(L1, HIGH); } else { uDigitalWrite(L1, LOW); } // вывод скоростей serialPrint(); // ход по значениям скоростей w1 и w2 drive(w1, w2); // если нажата кнопка S1 // пишем CANCEL в Serial // и выходим из бесконечного цикла while(1) без сохранения if (!button1WasUp && uDigitalRead(S1)) { Serial.println("CANCEL"); break; } button1WasUp = uDigitalRead(S1); // если нажата кнопка S3 if (!button3WasUp && uDigitalRead(S3)) { // сохраняем значение первого мотора EEPROMWriteInt(0, w1); // сохраняем значение второго мотора EEPROMWriteInt(2, w2); // сохраняем значение контрольной суммы EEPROMWriteInt(4, 777); // Пишем SAVE в Serial и выходим из бесконечно цикла Serial.println("SAVE"); break; } button3WasUp = uDigitalRead(S3); } /// while (1) // останавливаем моторы drive(0, 0); // погасим второй и третий светодиод uDigitalWrite(L2, LOW); uDigitalWrite(L3, LOW); // пикнем 3 раза зуммером tone(BUZZER, 1000, 50); delay(100); tone(BUZZER, 1000, 50); delay(100); tone(BUZZER, 1000, 50); delay(100); } /// balanceMotors //запись двухбайтового числа в память void EEPROMWriteInt(int address, int value) { EEPROM.write(address, lowByte(value)); EEPROM.write(address + 1, highByte(value)); } //чтение двухбайтового из числа из памяти unsigned int EEPROMReadInt(int address) { byte lowByte = EEPROM.read(address); byte highByte = EEPROM.read(address + 1); return (highByte << 8) | lowByte; } void serialPrint() { Serial.print("speed w1 = "); Serial.print(w1); Serial.print(" "); Serial.print("speed w2 = "); Serial.println(w2); } // пуск сервопривода постоянного вращения void servoStart(void) { myservo.attach(11); myservo.write(0); } // остановка сервопривода void servoStop(void) { // Самый простой способ остановить серву постоянного вращения // отсоединиться от неё myservo.detach(); } void control() // функция управления { // считаем значение пришедшей команды char dataIn = Serial1.read(); if (dataIn == 'F') { // пришла команда "F", едем вперёд drive(w1, w2); } else if (dataIn == 'B') { // пришла команда "B", едем назад drive(-w1, -w2); } else if (dataIn == 'L') { // пришла команда "L", поворачиваем налево на месте drive(-w1, w2); } else if (dataIn == 'R') { // пришла команда "R", поворачиваем направо на месте drive(w1, -w2); } else if (dataIn == 'I') { // пришла команда "I", едем вперёд и направо drive(defaultSpeed + w1, defaultSpeed - w2); } else if (dataIn == 'J') { // пришла команда "J", едем назад и направо drive(-defaultSpeed - w1, -defaultSpeed + w2); } else if (dataIn == 'G') { // пришла команда "G", едем вперёд и налево drive(defaultSpeed - w1, defaultSpeed + w2); } else if (dataIn == 'H') { // пришла команда "H", едем назад и налево drive(-defaultSpeed + w1, -defaultSpeed - w2); } else if (dataIn == 'S') { // если пришла команда "S", стоим на месте drive(0, 0); } else if (dataIn == 'X') { // пришла команда "X", крутим серву servoStart(); } else if (dataIn == 'x') { // пришла команда "x", останавливаем серву servoStop(); } else if (dataIn == 'V') { // пришла команда "V", пищим tone(BUZZER, 1000); } else if (dataIn == 'v') { // пришла команда "v", не пищим noTone(BUZZER); } else if (((dataIn - '0') >= 0) && ((dataIn - '0') <= 9)) { // настройка скорости вращения обоих моторов от 0 до 9 // если первый мотор быстрее if (w1 > w2) { // сохраняем новое значение скорости обоих моторов // второй с поправкой на баланс w1 = (dataIn - '0') * 25; w2 = (dataIn - '0') * 25*k; } else { // сохраняем новое значение скорости обоих моторов // первый с поправкой на баланс w1 = (dataIn - '0') * 25*k; w2 = (dataIn - '0') * 25; } } else if (dataIn == 'q') { // если "q" - полный газ if (w1 > w2) { // первый мотор максимум, второй с поправкой на баланс w1 = 255; w2 = 255*k; } else { // второй мотор максимум, первый с поправкой на баланс w1 = 255*k; w2 = 255; } } } /// end control // функция нахождения поправочного коефициента // скорости одного мотора к другому void correction() { float m1 = w1; float m2 = w2; if (m1 > m2) { k = m2 / m1; } else { k = m1 / m2; } } ===== Демонстрация работы устройства ===== {{youtube>-_6EWEBVotE?large}} ===== Что дальше? ===== Если у вас вдруг не оказалось платформы [[amp>product/strela?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Strela]], вы можете использовать, к примеру, [[amp>product/arduino-uno?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Arduino Uno]], добавить к ней [[amp>product/arduino-motor-shield?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Motor Shield]] и, немного изменив скетч, управлять моторами. А плата расширения [[amp>product/arduino-wireless-shield?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Wireless Shield]] позволит вам подключить [[amp>product/bluetooth-bee?utm_source=proj&utm_campaign=bottleboat&utm_medium=wiki | Bluetooth Bee]]. Если у вас завалялась ненужная радиоуправляемая игрушка, вы сможете использовать её пульт для управления катером вместо Bluetooth и смартфона. Для этого вам необходимо прочесть документацию на функцию [[http://arduino.cc/en/Reference/pulseIn/|pulseIn]], заменить Bluetooth Bee на приёмник из вашей игрушки и подредактировать скетч.