Управляем машинкой через Bluetooth с планшета или телефона под Android

Привет, Хабр!

Сегодня я расскажу о том, до чего может дойти программист с кризисом среднего возраста в попытках хоть как то компенсировать недостаток моднейших игрушек в далеком и полузабытом детстве.

В радиоуправляемые вертолеты я с сыновьями уже поиграл, хорошо, но в стандартной московской квартире немного тесновато, а на улице холодно, грязно и ветер. Придется играть в машинки, благо, под рукой есть вот такой вот аппарат:

Дети его уже украсили граффити и немного пообтерли, но оно все еще ездит, если пульт найти… А вот пульт куда то потерялся. Ну, зато есть планшет с Bluetooth! Осталось как то их подружить вместе. Через Google ищется много таких проектов, поэтому усложним задание — сделаем пропорциональное рулевое управление и плавную регулировку скорости!

Нам понадобятся:

Безжалостно выкидываем мотор постоянного тока, который дергал рулевую тягу, вместо него надфилем, монтажным пистолетом и другими народными методами вставляем сервопривод. Собираем на макетке простейшую схему с Ардуиной и остальными компонентами. Схему подключений приводить не буду, просто цеплял все на первые попавшиеся ноги ардуины, с какой то итерации даже заработало. После нескольких попыток скомпоновать все так, чтобы поместилось на родное шасси получилось так:



Bluetooth модуль спрятан с нижней стороны макетки, dc converter потом прилепил сверху на заднюю часть шасси:



Осталось залить скетч, так как Bluetooth адаптер у нас висит на том же serial порту, что и USB TTL адаптер, перед тем, как залить скетч, не забываем сдернуть RX/TX с Bluetooth модуля. Кстати, была идея использовать SoftwareSerial для него, но пришлось ее отбросить, так как SoftwareSerial дает странные спонтанные подергивания на сервоприводе.

#include <Servo.h>

Servo servoSteering;
const int servoSignalPin = 7; // servo yellow(signal) pin

const int servoMiddle = 88; // servo middle position
const int servoMaxAngle = 35; // servo max angle (degrees)
const int servoLeftBound = servoMiddle - servoMaxAngle;
const int servoRightBound = servoMiddle + servoMaxAngle;

int servoPos = servoMiddle;
int servoTrim = 0;

const int dcMotorA1Pin = 11; // dc motor A+ pin
const int dcMotorA2Pin = 12; // dc motor A- pin
const int dcMotorPWMPin = 3; // dc motor PWM pin

const int ledPin = 13; // data received pin

const int dcMotorPWMLowerBound = 128; // dc motor min PWM (min speed)
const int dcMotorPWMHighBound = 255; // dc motor max PWM (max speed)

int dcMotorDir = 1; // 1 - forward, -1 - backward
int dcMotorSpd = 0; // 0 - stand still, value between dcMotorPWMLowerBound and dcMotorPWMHighBound to move

const int cmdTimeout = 2000; // max time between commands from rc control, will stop if no new commands arrive

typedef enum { NONE = 0, SERVO_L, SERVO_R, TRIM, DCMOTOR_F, DCMOTOR_B, STOP } mode_t;
mode_t opMode = NONE;

#define IS_DIGIT(x) ('9' >= (x) && '0' <= (x))

unsigned long lastCmd = 0;

void setup() {

  Serial.begin(9600);

  servoSteering.attach(servoSignalPin);

  pinMode(dcMotorA1Pin, OUTPUT);
  pinMode(dcMotorA2Pin, OUTPUT);
  pinMode(dcMotorPWMPin, OUTPUT);

  pinMode(ledPin, OUTPUT);

  digitalWrite(dcMotorA1Pin, 0);
  digitalWrite(dcMotorA2Pin, 0);
  analogWrite(dcMotorPWMPin, 0);
}

void loop() {

  while (Serial.available()) {

    digitalWrite(ledPin, HIGH);

    int ch = Serial.read();
    lastCmd = millis(); // save command receive time for later

    if (opMode == NONE) {
      switch (ch) {
        case 'T':
        case 't': // trim mode
          opMode = TRIM;
          break;
        case 'L':
        case 'l': // servo left mode
          opMode = SERVO_L;
          break;
        case 'R':
        case 'r': // servo right mode
          opMode = SERVO_R;
          break;
        case 'F':
        case 'f': // dc motor move front mode
          opMode = DCMOTOR_F;
          break;
        case 'B':
        case 'b': // dc motor move back mode
          opMode = DCMOTOR_B;
          break;
        case 'S':
        case 's': // full stop mode
          servoPos = servoMiddle + servoTrim; // center front wheels
          dcMotorDir = 1; // 1st gear
          dcMotorSpd = 0; // neutral on
        default:
          opMode = NONE;
          break;
      }
    } else if (IS_DIGIT(ch)) {
      switch (opMode) {
        case TRIM:
          servoTrim = '5' - ch;
          break;
        case SERVO_L:
          servoPos = servoMiddle + servoTrim - (ch - '0') * servoMaxAngle / 10;
          if (servoPos < servoLeftBound) servoPos = servoLeftBound;
          break;
        case SERVO_R:
          servoPos = servoMiddle + servoTrim + (ch - '0') * servoMaxAngle / 10;
          if (servoPos > servoRightBound) servoPos = servoRightBound;
          break;
        case DCMOTOR_F:
          dcMotorDir = 1;
          dcMotorSpd = ch == '0' ? 0 : dcMotorPWMLowerBound + (dcMotorPWMHighBound - dcMotorPWMLowerBound) * (ch - '0') / 10;
          break;
        case DCMOTOR_B:
          dcMotorDir = -1;
          dcMotorSpd = ch == '0' ? 0 : dcMotorPWMLowerBound + (dcMotorPWMHighBound - dcMotorPWMLowerBound) * (ch - '0') / 10;
          break;
        default:
          break;
      }
      
      opMode = NONE;
    }

    if (dcMotorDir == 1)
      Serial.print("F ");
    else
      Serial.print("R ");

    Serial.print(dcMotorSpd);
    Serial.print(" S ");
    Serial.print(servoPos);
    Serial.print(" T ");
    Serial.println(servoTrim);

    digitalWrite(dcMotorA1Pin, dcMotorDir == 1 ? 1 : 0);
    digitalWrite(dcMotorA2Pin, dcMotorDir == 1 ? 0 : 1);
    analogWrite(dcMotorPWMPin, dcMotorSpd);

    servoSteering.write(servoPos);
  }
  
  if (dcMotorSpd > 0 && (lastCmd + cmdTimeout) < millis()) {

    // rc control doesn't send anything, out of range or hang up, do full stop
    dcMotorDir = 1; // 1st gear
    dcMotorSpd = 0; // neutral on

    digitalWrite(dcMotorA1Pin, 1);
    digitalWrite(dcMotorA2Pin, 0);
    analogWrite(dcMotorPWMPin, 0);

    servoSteering.write(servoPos);
  }

  digitalWrite(ledPin, LOW);
}


Протестировал через PuTTY с ноутбука посылая команды в подцепленное Bluetooth сериал устройство, вроде работает. Осталось соорудить приложение под Android. Надергал в сети картинок, примеров кода, попытался как то склеить, вроде работает:



Попутно вылез глюк с реализацией Bluetooth на некоторых ICS устройствах:
code.google.com/p/android/issues/detail?id=34161

У меня проявилось на Lenovo A800, проявляется в том, что открытый Bluetooth сокет сразу же закрывается. Лечить, видимо, только сменой прошивки девайса, все опробованные обходные пути не заработали…

Ну и на закуску код Android приложения:
github.com/robotsrulz/BluetoothRC

И короткое видео, где сначала сын пытается водить машину, но получается только по прямой, а потом я пытаюсь одной рукой рулить, а другой продолжать съемку:
plus.google.com/u/0/photos?pid=5988360256454694594&pids&oid=106468370342958692108
Поделиться публикацией
Комментарии 25
    0
    Отличная работа! Один вопрос — а можно ли программировать перемещение? Не непосредственно с экрана управлять, а сделать игрушку, которая будет перемещаться в соответствии с заложенной в управляющее устройство программой? Типа — проедь метр вперед, поверни вправо на 5 градусов, вперед 0,5 метра ну и т.д.?
      +3
      Спасибо! Я думал на эту тему, тут возникает немаленький набор проблем:

      1. Если для движения вперед-назад используется не степпер, а мотор постоянного тока, как в этой версии машинки (вторая версия в работе!), то сложно отслеживать пройденное расстояние, вариант типа «установить скорость 50%, подождать 5 секунд, стоп» слишком неточен. Лучше использовать откалиброванный под эту задачу степпер. Жду посылку из Поднебесной…

      2. Нужно как то отслеживать положение в пространстве. Вариант упереться носом в диван и буксовать как то не очень… Ультразвуковой дальномер тут не годится, слишком широкая диаграмма направленности. Пробовал из двух дешевых USB-вебкамер собрать стереокамеру и обрабатывать картинки, строя из них облако точек и т.д. — получается очень ресурсоемкая задача, тут даже бывший тонкий клиент на ARM v5TE 400 МГц не очень то справляется, такая машинка будет надолго останавливаться, чтобы попытаться понять, где же она находится. Если только реализовывать внешний мозг на ПК + передача с машинки изображения по WiFi. Нужен серьезный апгрейд!

      Ну а если задача стоит просто выполнить последовательность команд без анализа того, получится приехать куда надо или нет — это достаточно просто, даже не понадобится скетч менять.
        0
        Первое, что приходит в голову, это использовать KInect для определения местоположения. Второе — для игры использовать напечатанную карту, которая расстилается на полу, машина устанавливается в стартовое положение и задача играющего — запрограммировать трек так, чтобы машина проходила его автоматически, без вмешательства оператора. Правда, не совсем понятно как отслеживать зарядку аккумулятора, т.к. при прочих равных при разном заряде машина пройдет разный путь, правильно?
          +1
          Кинект стоит почти 200 долларов, это дорого для одноразового проекта. Хотя работать, конечно же, будет. Плюс нужен мозг, малинка или cubieboard. Еще нужен более мощный аккумулятор и более мощные приводы. Думаю, бюджет такой постройки приблизится где-то к $500. С напечатанной картой можно сделать намного дешевле, но это уже не так интересно, по сути что карта, что рельсы — разница небольшая.

          Заряд аккумулятора можно отслеживать по падению напряжения.
            +1
            Мне кажется, посоревноваться по нарисованному треку на двух заранее запрограммированных машинках было бы прикольно. Кстати в таком случае можно позиционироваться по напечатанным на треке меткам, считывая их какой-нибудь оптической системой (вебкамерой, например).
              +2
              Если бы это было соревнование роботов с призовым фондом, болельщиками и т.д. — наверное, да. Строить несколько месяцев две машинки чтобы посоревноваться сам с собой — почему то не хочется…
                0
                Почему сам с собой. Сам против своего алгоритма в гаджете.
                И дальше анализ двух итогов: 1) я крут, я побеждаю роботов, и 2) я крут, я сделал робота который победил человека. :)
                  0
                  Я просто попытался донести мысль, что мне сейчас эта идея кажется недостаточно привлекательной :) Я думаю, что моим следующим проектом будет что-то из области умного дома, по крайней мере сейчас очень хочется куда нибудь приспособить валяющуюся без дела hd-камеру.
      +1
      Ай-яй. Зараза Вы, автор!
      У меня хоть и дочки, но все пять конечностей затрепетали — сложился тот паззл, который собирался из радиоэлектронного детства и айтишно-программерского отрочества :)
        +2
        Смеюсь и катаюсь по полу :) А у меня еще два вертолета есть и мешок деталей для умного дома едет, никак не приедет, а еще я в платяном шкафу собрал почти настоящую серверную стойку с ИБП, SIP-телефонией и микро-сервером, короче, жена и дети уже как на ненормального смотрят… :)
          0
          Можете про умный дом и сервер рассказать? Особенно интересует SIP.
            0
            Обязательно расскажу чуть позже, сейчас пока еще особенно нечего рассказывать.
            0
            знакомая ситуация, поэтому я выселился на балкон с серверочками, бесперебойником, осцилографом и прочими, по словам моей жены «странными железяками».
          0
          ))) У меня такой же набор дума лежит — ждёт своего часа.
          Правда в ходе тестирования отдельных модулей выявилась неприятность: iPhone не видит bluetooth-модуль HC-06 :( Пока не разбирался, но возможно поможет перепрошивка в HC-05.
            0
            Не пробовал с iPhone, нет опыта разработки под него и не хочется делать jailbreak. А вот андроид девайсов дома много и эксперименты на них ставить легко и приятно.
            –1
            А чем оно отличается от этого?
              0
              Там — дискретное управление, у меня — пропорциональное. Плюс, у меня используются более дешевые и простые компоненты, интерфейс управления более приятно выглядит. А если глобально — то да, все то же самое, 4 колеса, телефон… :)
              0
              Я делал практически тоже самое, но правда у меня корпус несколько другой был. У меня детей нет, но есть у одного хорошего знакомого у которого сын решил покатать себя на радиоуправляемой машинке. В итоге она мне досталась без одного колеса, место которого потом заняло колесико из советского конструктора.
              Фото моей Ласточки ))
              image

              Но как показала практика простое управление с ПК не доставляет того удовольствия, которое хотелось бы от этого дела получать. У вас есть планы по совершенствованию своей машинки или проект достиг своего финиша в развитии?
                +2
                Симпатичная! Я думал насчет легкового корпуса, жаль что внутри меньше места по сравнению с внедорожниками.

                Сейчас проект застрял на стадии замены привода задних колес, установленный с завода моторчик меня не устраивал невысокой тягой, с вывернутыми на 35 градусов в бок колесами машинка не едет по ковру или другой мягкой поверхности. Я уже перепробовал десяток других моторов, включая различные степперы и даже пробовал приспособить мотор от жесткого диска. Но удовлетворительный вариант пока получить не удалось, до прихода следующей партии запчастей машинка пылится на полке без задних колес.

                Что касается доработки ПО управления — можно добавить регулятор нулевого положения передних колес (это уже есть в скетче, команда «T»), можно сделать управление фарами, поворотниками, сигналом, добавить 2-4 кнопки для произвольных функций… Можно сделать работающий спидометр.

                Пока не знаю, когда я это буду делать и буду ли вообще. Весь код доступен на GitHub, если есть желание — могу подсказать, как все это в него добавить, никаких ограничений по его использованию нет.
                  0
                  В управление с ПК можно привести новый шарм, поставив на машинку видеокамеру и оборудовав «трассу» на природе.
                    0
                    Понадобится дополнительно изменить схему связи с Bluetooth на WiFi и позаботится о защите электроники и механики от грязи и песка. Интересная тема, надо будет подумать…
                  0
                  Тоже делал похожую конструкцию: взял простую китайскую машинкку, выкинул из нее все внутренности, поставил Arduino, Arduino Motor Shield — к нему подключил основной коллекторный двигатель машинки. Передние колеса изначально поворачивались другим коллекторным двигателем, который сдвигал подпружиненную рулевую рейку в одну или другую сторону. Все это выкинул, поставил туда микро серву и сделал от сервы тягу на рулевую рейку при помощи скрепки. Серву подключил на незадействованный ШИМ и управлял стандартной библиотекой Servo. Двигатель также мог плавно изменять направление и скорость движения. И заключительный этап — похожий Bluetooth модуль, подключаемый в режиме виртуального ком порта. Протокол обмена был весьма примитивный — команды по 2 байта, первый байт — угол поворота руля, второй — скорость.

                  В общем все это ездило. Потом Bluetooth оттуда выкинул, поставил приемник от типичной системы радиоуправления (вроде Turnigy 9x, кто в курсе), Рулевую серву подключил непосредственно к приемнику, а ардуино считывало PWM сигнал от приемника (как бы притворяясь сервой) и далее изменяло обороты двигателя.

                  В общем из китайской резкой машинки получился монстр, но довольно аккуратный и хорошо управляемый.
                    0
                    Ну вот я примерно это и пытался сделать, почти получилось. Если бы такое можно было купить в магазине за разумные деньги — возможно, и не заморачивался бы. Но почему то в магазинах детских игрушек либо машинки с чисто дискретным управлением, либо — с пропорциональным, но цена — просто космос! Хотя мне сейчас скинули в личку ссылку на китайского производителя машинок с bluetooth управлением, выглядит аккуратно и цена разумная.
                      0
                      Согласен. Подавляющее большинство машинок не только дискретны, но и настроены на максимальную скорость перемещения, что может быть для детей совсем маленьких является плюсом. Понятно что дискретная схемотехника значительно проще, но и простая пропорциональная могла бы быть вполне конкурентноспособная, но середины почти нет.
                    0
                    Полезные ссылки для тех, у кого нет Android:

                    — пример и код на Java: developer.nokia.com/community/wiki/Series_40_communicating_with_Arduino_using_Bluetooth

                    — то же, для Windows Phone: developer.nokia.com/community/wiki/Windows_Phone_8_communicating_with_Arduino_using_Bluetooth

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое