Bluetooth helicopter под управлением с Android Часть.2

В первой части этого мини проекта я рассказал об изменения в «железе», с помощью которых китайский соосный вертолет-игрушка превращается в android-helicopter управляемый по bluetooth каналу.

Этот пост посвящен прошивке Arduino, которая на является мозгом верта и приложению для телефона. 3-х канальный вертолет состоит из трех двигателей, управление которыми и необходимо реализовать. Укрупненная схема взаимодействия телефона и вертолета, на рисунке:

Телефон «общается» по  bluetooth каналу с модулем BTM-112, который, в сою очередь, с помощь UART интерфейса взаимодействует с arduino nano. Контроллер arduino с помощью ШИМ выходов передает управляющие команды на IRL транзисторы основных двигателей и драйвер L293D двигателя хвостового. Для удержания задонного курса (хвостовой балки в заданном направлении) в контур управления вводится гироскоп, который измеряет угловую скорость вращения вокруг вертикальной оси  вертолета. Гироскоп имеет выходное и опорное напряжение, рассогласование которых показывает направление вращения и величину угловой скорости. По величине рассогласования arduino вырабатывает управляющие сигналы на коррекцию оборотов основных двигателей, которые и создают вращающий момент.

Интерфейс телефонного приложения BluCopter прост и на этом этапе не отличается потрясающим дизайном. Состоит из предварительной activity и рабочей. На первой расположена кнопка коннекта с синезубым устройством и перехода к рабочему окну. На второй управляющие ручки газа — слева и направления-тангжа — справа. А также кнопки настройки чувствительности гироскопа и триммирования. В левом верхнем углу textview, который выводит передаваемую информацию с борта вертолета:

Особенности android’овского приложения:
1. Мой HTC Wildfire упорно отказывался соединяться с модулем BTM-112 и создавать, по средствам рекомендуемого developer’ами bluetooth хост:

socket = device.createRfcommSocketToServiceRecord(MY_UUID)

Где My_UUID «00001101-0000-1000-8000-00805F9B34FB» для Bluetooth RFCOMM / SPP.

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

Method m = device.getClass().getMethod("createRfcommSocket",
new Class[] { int.class });
socket = (BluetoothSocket)m.invoke(device, Integer.valueOf(1));
Method m = device.getClass().getMethod("createRfcommSocket",
new Class[] { int.class });
socket = (BluetoothSocket)m.invoke(device, Integer.valueOf(1));

2. Команды управляющие передаются через заданный интервал времени с помощью таймера androida в событии TimerTask. На первом этому было решено не заморачиваться с структурой пакета команд, контрольной суммой и т.п. Поэтому  цепочка команд представляет собой последовательность из буквенного идентификатора и байта, несущего величину сигнала. Пример A10B200C200D0F1I10…  C вертолета такая же последовательность отправляется обратно, что было нужно для отладки.

3. При работе обнаружилось подвисание программы. Поэтому в прошивке arduino появился костыль в виде задержки в 9мсек. Так как не понятно, как в этой платформе реальзована для МК Atmega работа с UART’ом, какие есть прерывания и как они обрабатываюся, то мне сложно предложить более правильное решение этой проблемы. При переходе на чистый МК, попытаюсь этого избежать.

Прошивка для arduino тоже довольно проста:

const int GiroIn = A0; // гироскоп входящий 
const int GiroRf = A1; // гироскоп опорное напряжение
 
const int OutRudLeft = 3; // правый ду 
const int OutRudRight = 5; // левый ду
const int OutUpTangage = 6;  // тангаж up
const int OutDwnTangage = 10;  // тангаж down
 
// входные значение
int RudR = 0; // правый ДУ
int RudL = 0; // левый ДУ
 
int InGiro = 0; // показания гироскопа
int RfGiro = 0; // опорное напряжение гироскопа
int Rud = 0; // общий газ А(65)
int RLft = 0; // поворот левый B(66)
int RRght = 0; // поворот правый D(68)
int TngUp = 0; // тангаж вверх E(69)
int TngDwn = 0; // тангаж вверх F(70)
int Giro = 0; // чувствительность гироскопа G(71)
int Trim = 0; // тримирование I(73)
int timeUp = 0;
 
void setup() {
  // создаем порт
  Serial.begin(19200); 
}
 
void loop() {
     timeUp++;
 
  int decod;
  if (Serial.available() >= 2 ) {
      timeUp=0;
 
    // читаем байт изпорта
    decod = Serial.read();
 
    switch (decod) {
      // если общий газ
       case 'A': 
        Rud = Serial.read();
        Serial.print("A");
        Serial.println(Rud); 
        break;
       // если левый поворот
      case 'B': 
        RLft = Serial.read();
        Serial.print("B");
        Serial.println(RLft);
        break;
        // если правый поворот
        case 'D': 
        RRght = Serial.read();
        Serial.print("D");
        Serial.println(RRght);
        break;
        // если тангаж вверх
        case 'E': 
        TngUp = Serial.read();
        Serial.print("E");
        Serial.println(TngUp);
        break;
        // если тангаж вниз
        case 'F': 
        TngDwn = Serial.read();
        Serial.print("F");
        Serial.println(TngDwn);
        break;
        // если чувствительность гироскопа
        case 'G': 
        Giro = Serial.read();
        break;
        // тримирование
        case 'I':
        Trim = Serial.read();
        Serial.print("I");
        Serial.println(Trim);
        break;
 
    }
 
  } 
 
  // читаем показания гироскопа
    InGiro = analogRead(GiroIn);
    RfGiro = analogRead(GiroRf);
    InGiro = -1*((InGiro - RfGiro+16) - Trim+50)*Giro - RRght + RLft;
    // пишем в порт показания гироскопа
    Serial.print("H");
    //Serial.print(InGiro);
    Serial.print(InGiro);
 
  // если сигнал пропал больше 4-х циклов
  if (timeUp > 500) {
    //Rud = Rud - int (map(abs(timeUp), 1000, timeUp, 0, Rud));
    Rud = 0;
    TngUp = 0;
  }
  // реакция на общий газ
  RudR = Rud;
  RudL = Rud;
 
  // вырабатываем управляющие сигналы на ДУ
  if (InGiro < 0 && Rud > 0) {
    RudR = RudR + int (map(abs(InGiro), 0, 255, 0, 255-RudR));
    RudL = RudL - int (map(abs(InGiro), 0, 255, 0, RudL));
  } 
  if (InGiro > 0 && Rud > 0) {
    RudL = RudL + int (map(abs(InGiro), 0, 255, 0, 255-RudL));
    RudR = RudR - int (map(abs(InGiro), 0, 255, 0, RudR));
  } 
 
  analogWrite(OutUpTangage, TngUp);    
  analogWrite(OutDwnTangage, TngDwn); 
  analogWrite(OutRudRight, RudR);
  analogWrite(OutRudLeft, RudL);
  Serial.flush();
 
  delay(9);  
}
const int GiroIn = A0; // гироскоп входящий 
const int GiroRf = A1; // гироскоп опорное напряжение

const int OutRudLeft = 3; // правый ду 
const int OutRudRight = 5; // левый ду
const int OutUpTangage = 6;  // тангаж up
const int OutDwnTangage = 10;  // тангаж down

// входные значение
int RudR = 0; // правый ДУ
int RudL = 0; // левый ДУ

int InGiro = 0; // показания гироскопа
int RfGiro = 0; // опорное напряжение гироскопа
int Rud = 0; // общий газ А(65)
int RLft = 0; // поворот левый B(66)
int RRght = 0; // поворот правый D(68)
int TngUp = 0; // тангаж вверх E(69)
int TngDwn = 0; // тангаж вверх F(70)
int Giro = 0; // чувствительность гироскопа G(71)
int Trim = 0; // тримирование I(73)
int timeUp = 0;

void setup() {
  // создаем порт
  Serial.begin(19200); 
}

void loop() {
     timeUp++;

  int decod;
  if (Serial.available() >= 2 ) {
      timeUp=0;

    // читаем байт изпорта
    decod = Serial.read();

    switch (decod) {
      // если общий газ
       case 'A': 
        Rud = Serial.read();
        Serial.print("A");
        Serial.println(Rud); 
        break;
       // если левый поворот
      case 'B': 
        RLft = Serial.read();
        Serial.print("B");
        Serial.println(RLft);
        break;
        // если правый поворот
        case 'D': 
        RRght = Serial.read();
        Serial.print("D");
        Serial.println(RRght);
        break;
        // если тангаж вверх
        case 'E': 
        TngUp = Serial.read();
        Serial.print("E");
        Serial.println(TngUp);
        break;
        // если тангаж вниз
        case 'F': 
        TngDwn = Serial.read();
        Serial.print("F");
        Serial.println(TngDwn);
        break;
        // если чувствительность гироскопа
        case 'G': 
        Giro = Serial.read();
        break;
        // тримирование
        case 'I':
        Trim = Serial.read();
        Serial.print("I");
        Serial.println(Trim);
        break;

    }

  } 

  // читаем показания гироскопа
    InGiro = analogRead(GiroIn);
    RfGiro = analogRead(GiroRf);
    InGiro = -1*((InGiro - RfGiro+16) - Trim+50)*Giro - RRght + RLft;
    // пишем в порт показания гироскопа
    Serial.print("H");
    //Serial.print(InGiro);
    Serial.print(InGiro);

  // если сигнал пропал больше 4-х циклов
  if (timeUp > 500) {
    //Rud = Rud - int (map(abs(timeUp), 1000, timeUp, 0, Rud));
    Rud = 0;
    TngUp = 0;
  }
  // реакция на общий газ
  RudR = Rud;
  RudL = Rud;

  // вырабатываем управляющие сигналы на ДУ
  if (InGiro < 0 && Rud > 0) {
    RudR = RudR + int (map(abs(InGiro), 0, 255, 0, 255-RudR));
    RudL = RudL - int (map(abs(InGiro), 0, 255, 0, RudL));
  } 
  if (InGiro > 0 && Rud > 0) {
    RudL = RudL + int (map(abs(InGiro), 0, 255, 0, 255-RudL));
    RudR = RudR - int (map(abs(InGiro), 0, 255, 0, RudR));
  } 

  analogWrite(OutUpTangage, TngUp);    
  analogWrite(OutDwnTangage, TngDwn); 
  analogWrite(OutRudRight, RudR);
  analogWrite(OutRudLeft, RudL);
  Serial.flush();

  delay(9);  
}

В бесконечно повторяющемся цикле происходит считывание и распознавание управляющих команд по буквенному идентификатору и передача сигнала по соответствующим ШИМ выходам. Если происходит потеря bluetooth соединения и по UART интерфейсу команды в течении 300мсек не проходят, программно отключаются двигатели вертолета. Сделано так в целях обеспечения безопасности, чтобы неуправляемый верт не превращался в взвесившуюся мясорубку.)

В дальнейшем планируется:

  1. Переход к МК и, соответственно, новая прошивка контроллера.
  2. Пакетная передача команд с проверкой контрольной суммы.
Запись опубликована в рубрике Разработки. Добавьте в закладки постоянную ссылку.

         
Подписаться на новые статьи блога:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.