Ошибка при управлении скоростью через Arduino Bluetooth RC Car

Тема в разделе "Проводная и беспроводная связь", создана пользователем GreatNonentity, 4 янв 2017.

  1. GreatNonentity

    GreatNonentity Нерд

    Добрый день всем!
    Буду благодарен за помощь!
    Проблема весьма странная... Учебный проект - делаю машинку, которой управляю через БТ с помощью популярной проги для Андроида https://play.google.com/store/apps/details?id=braulio.calle.bluetoothRCcontroller
    Использую модуль HC-06

    До определенного момента все шло хорошо и никаких проблем не предвиделось. Все работало, я дописывал скетч, добавлял функционал, обрабатывал новые буковки в условиях. Но вот когда я попробовал управлять скоростью, началась мистика.
    Какое-то время после загрузки скетча все работает нормально, скорость регулируется, машинка управляется. Но в какой-то момент контроль теряется, программа не выполняется, хотя коннект с БТ остается (это видно и по светодиоду на БТ модуле и в приложении на телефоне).
    Самое замечательное в этом то, что простой ресет или отключение питания ничего не дает. Помогает только новая загрузка кода на Ардуину.
    Такое ощущение, что каким-то образом при управлении скоростью происходит модификация скетча. К такому предположению я пришел через следующее наблюдение: в сетапе скетча есть функция, включающая встроенный светодиод. Больше в программе нигде нет обращения к 13 пину. То есть он загорается при загрузке программы и не должен ни мигать ни гаснуть. Но именно это и происходит! То он начинает мигать, и мигает после ресета или отключения питания. То он гаснет, и "естественно" уже не загорается после перезагрузки. Единственный способ вернуть все к жизни, залить скетч заново.
    После того, как я закомментировал управление скоростью и залил скетч, все работает стабильно...
    Помогите, пожалуйста, разобраться с проблемой! Я мозг сломал, не укладывается в голове такое поведение :((

    Предлагаю вашему вниманию код:

    Код (C++):
    int val;
    int LED = 13;
    int vSpeed = 255;

    void setup()
    {
      Serial.begin(9600);
      pinMode(LED, OUTPUT);
      digitalWrite(LED, HIGH);
      pinMode(9, OUTPUT);
      pinMode(10, OUTPUT);
      pinMode(11, OUTPUT);
      pinMode(12, OUTPUT);
      pinMode(3, OUTPUT);
      pinMode(4, OUTPUT);
      analogWrite(9, 0);
      analogWrite(10, 0);
    }

    void forward(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, vSpeed);
      analogWrite(10, vSpeed);
      }

    void back(){
      digitalWrite(11, 0);
      digitalWrite(12, 1);
      digitalWrite(3, 0);
      digitalWrite(4, 1);
      analogWrite(9, vSpeed);
      analogWrite(10, vSpeed);
      }

    void right(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, 0);
      analogWrite(10, vSpeed);
      }

    void left(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, vSpeed);
      analogWrite(10, 0);
      }

    void forwardLeft(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, vSpeed);
      analogWrite(10, vSpeed/2);
      }

    void forwardRight(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, vSpeed/2);
      analogWrite(10, vSpeed);
      }

    void backLeft(){
      digitalWrite(11, 0);
      digitalWrite(12, 1);
      digitalWrite(3, 0);
      digitalWrite(4, 1);
      analogWrite(9, vSpeed);
      analogWrite(10, vSpeed/2);
      }

    void backRight(){
      digitalWrite(11, 0);
      digitalWrite(12, 1);
      digitalWrite(3, 0);
      digitalWrite(4, 1);
      analogWrite(9, vSpeed/2);
      analogWrite(10, vSpeed);
      }


    void stopcar(){

      analogWrite(9, 0);
      analogWrite(10, 0);
      }  

    void loop()
    {
      if (Serial.available())
      {
        val = Serial.read();

        if (val == 'F')
        {
          forward();
        }

        if (val == 'B')
        {
          back();
        }

        if (val == 'L')
        {
          left();
        }

        if (val == 'R')
        {
          right();
        }

        if (val == 'G')
        {
          forwardLeft();
        }

        if (val == 'I')
        {
          forwardRight();
        }

        if (val == 'H')
        {
          backLeft();
        }

        if (val == 'J')
        {
          backRight();
        }

        if ( val == 'S')
        {
          stopcar();
        }

    // управление скоростью

    /*   if ( val == '0')
        {
          vSpeed = 0;
        }

        else if ( val == '1')
        {
          vSpeed = 90;
        }

        else if ( val == '2')
        {
          vSpeed = 90;
        }

        else if ( val == '3')
        {
          vSpeed = 110;
        }

        else if ( val == '4')
        {
          vSpeed = 130;
        }

        else if ( val == '5')
        {
          vSpeed = 150;
        }

        else if ( val == '6')
        {
          vSpeed = 170;
        }
     
        else if ( val == '7')
        {
          vSpeed = 190;
        }

         else if ( val == '8')
        {
          vSpeed = 210;
        }

         else if ( val == '9')
        {
          vSpeed = 240;
        }

         else if ( val == 'q')
        {
          vSpeed = 255;
        }*/


     
      }
    }
     
  2. Spell

    Spell Нерд

    Не знаю, поможет ли, но иногда приведенная в порядок программа убирает всякую мистику.
    1. Serial.Read возвращает значение типа byte, а не int. Наверное, как есть работает, но все-таки правильнее val объявлять как char.
    2. Я бы вместо кучи if-ов использовал switch. Или else if. Просто получается, что всякий раз при поступлении символа отрабатывают ВСЕ проверки. хотя при обнаружении символа дальше можно и не проверять.
     
  3. GreatNonentity

    GreatNonentity Нерд

    Спасибо, большое за ответ! Как только будет минутка, попробую исправить.

    А пока хотел бы задать еще один вопрос. Дело в том, что пробовал я начать именно со switch case. Ошибок никаких он не выдал, загрузился, но никак не реагировал. Хотел бы я понять, что было не так. Код был такой:
    Код (C++):
    switch (val){
           case 'F':
           forward();
           break;

           case 'B':
           back();
           break;

           case 'L':
           left();
           break;

           case 'R':
           right();
           break;
    Те же функции с if работают без проблем.
     
  4. Spell

    Spell Нерд

    1. Val какого типа?
    2. Не вижу закрывающейся скобки на switch
    3. Я бы еще скобки в каждом кейсе воткнул.
     
  5. GreatNonentity

    GreatNonentity Нерд

    1. Val типа int, это была часть той же программы, что я выложил в первом посте.
    2. Скобка там была и есть, просто я ее сюда не скопировал. На синтаксис компилятор не ругался.
     
  6. Igor68

    Igor68 Гуру

    Простите, что вмешиваюсь но... есть предположение, что ваша программа обработала только один символ, в то время как их пришло больше. Возможно в настройках программы для андроида есть настройка "частоты" выдачи кода(символа) команды... и он непрерывно посылает последнюю команду (что вполне логично). очищайте буфер в ардуине чтением после обработки (if или switch)... то есть в конце
    Примерно:
    Код (C++):
    while (Serial.available() > 0) //ждём очистки приёма
    {
        int res = Serial.read();
    }
    Возможно, что символы приходят чаще, чем вы их успеваете обрабатывать.
    И конструкции типа:
    Код (C++):
    switch (val){
           case 'F':
                   forward();
                   break;
           case 'B':
                   back();
                   break;
           case 'L':
                   left();
                   break;
           case 'R':
                   right();
                   break;
           ...........
           ...........
           default:
                   break;
    }
     
    Работают явно быстрее, чем if
    Извините если не прав!
     
    Последнее редактирование: 4 янв 2017
  7. Spell

    Spell Нерд

    Я посмотрел на ссылку. Что-то там описания с гулькин нос. Где можно получить полное описание этой программы?
    Т.е. с чего Вы взяли, что она вообще какие-то символы возвращает?
     
  8. Igor68

    Igor68 Гуру

    Честно... первый раз слышу о том, что я говорил о возвращении символов...
    Я имел ввиду что программа на андроиде предположительно
    циклически посылает байты(символы)... Вы уверены что это не так?
    Я просто предложил в конце loop() только после выполнения команд поставить:
    Код (C++):
    while (Serial.available() > 0) //ждём очистки приёма
    {
        int res = Serial.read();
    }
    Этим вы выясните, что не "перегружается" буфер приёма. Одним словом гарантия начала "с чистого листа" каждого чтения... скорее для проверки.
     
  9. Spell

    Spell Нерд

    Вопрос, что и как посылает данная прога на Андроиде.
    Но такую конструкцию использовать не следует. Если прога передает данные постоянно, то можно попасть в бесконечный цикл. Читаете один символ, а еще один приходит, и Serial.available() > 0 выполняется всегда.
     
  10. Igor68

    Igor68 Гуру

    Вы правы! Я только предложил... понимаю так - вы гарантируете, что за время выполнения forward(); и других вы получили не более одного байта!
    Простите!
     
  11. GreatNonentity

    GreatNonentity Нерд

    По поводу того,
    Где взять описание, не знаю. В настройках программы есть перечень символов, которые она отправляет. Выглядит это так:
    [​IMG]
     
  12. Spell

    Spell Нерд

    Это я автору писал.
    Сейчас долго думал. Я раньше писал, что можно здесь в бесконечный цикл попасть. Это вряд ли. Процессор работает на 16 МГц. Даже если обмен на скорости 115 КБ, то обработать буфер он успеет. Так что может и не надо его обнулять?
    Разве что программу выполнять по другому. Скажем, создать некую буферную переменную, куда записывать текущее считанный байт, только в том случае, если они различаются.
    Вот так:
    Код (C++):
    void loop(){
        char buf = 0;
        char res = Serial.read();
        if (buf <> res){
            buf = res;
            switch (buf)
    .......................................
        }
    }
     
    GreatNonentity нравится это.
  13. GreatNonentity

    GreatNonentity Нерд

    Вы правы, нашел настройку. Правда, особо не разгуляешься выбор из двух опций:
    1. Постоянный поток, каждые 50 мс. - это то, что было включено.
    2. On change/touch. То есть на изменение или нажатие.
    Сейчас попробую, что будет в этом случае.
     
  14. Igor68

    Igor68 Гуру

    Простите - имел ввиду 1 байт и чтобы они не накапливались постепенно в буфере в процессе работы.
     
  15. Spell

    Spell Нерд

    Как я понял по скриншотам, общая концепция работы похожа на управление обычным автомобилем: отдельными кнопками задаем направление, а потом газом (поворотом экрана) рулим скоростью.
    Если передача каждые 50мс, то это 20Гц.
    Т.е. буфер сложно переполнить.
    Теперь по поводу управления скоростью.
    В исходной программе при получении символа изменения скорости некое значение сохраняется в буферную переменную, и дальше все. А ведь это значение надо на ШИМ передать. А эта передача произойдет только при очередном вызове команды направления. По идее, ничего страшного, если данные передаются автоматически каждые 50 мс. Хотя тоже не очень понятно, что именно будет передано. По идее должны передаваться 2 символа - направление и скорость, например, F5 или B8. Проверьте.
    Предложение:
    Используем две буферные переменные: направление и скорость. Направление и скорость - это также две группы получаемых символов.
    При получении символа определяем к какой группе он относится, и меняем значение буферной переменной. После чего вызываем switch для направления. В этом свитче будет вызвана одна из уже описанных процедур.
    Думаю, так работать будет.
     
  16. GreatNonentity

    GreatNonentity Нерд

    Спасибо всем большое за участие! Последний тест показал стабильность работающей программы при поточной передаче сигналов (Continious stream (every 50 ms)).
    Код здесь:

    Код (C++):
    int LED = 13;
    int vSpeed = 0;

    void setup()
    {
      Serial.begin(9600);
      pinMode(LED, OUTPUT);
      digitalWrite(LED, HIGH);
      pinMode(9, OUTPUT);
      pinMode(10, OUTPUT);
      pinMode(11, OUTPUT);
      pinMode(12, OUTPUT);
      pinMode(3, OUTPUT);
      pinMode(4, OUTPUT);
      analogWrite(9, 0);
      analogWrite(10, 0);
    }

    void forward(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, vSpeed);
      analogWrite(10, vSpeed);
      }

    void back(){
      digitalWrite(11, 0);
      digitalWrite(12, 1);
      digitalWrite(3, 0);
      digitalWrite(4, 1);
      analogWrite(9, vSpeed);
      analogWrite(10, vSpeed);
      }

    void right(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, 0);
      analogWrite(10, vSpeed);
      }

    void left(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, vSpeed);
      analogWrite(10, 0);
      }

    void forwardLeft(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, vSpeed);
      analogWrite(10, vSpeed/2);
      }

    void forwardRight(){
      digitalWrite(11, 1);
      digitalWrite(12, 0);
      digitalWrite(3, 1);
      digitalWrite(4, 0);
      analogWrite(9, vSpeed/2);
      analogWrite(10, vSpeed);
      }  

    void backLeft(){
      digitalWrite(11, 0);
      digitalWrite(12, 1);
      digitalWrite(3, 0);
      digitalWrite(4, 1);
      analogWrite(9, vSpeed);
      analogWrite(10, vSpeed/2);
      }

    void backRight(){
      digitalWrite(11, 0);
      digitalWrite(12, 1);
      digitalWrite(3, 0);
      digitalWrite(4, 1);
      analogWrite(9, vSpeed/2);
      analogWrite(10, vSpeed);
      }

    void stopcar(){
      analogWrite(9, 0);
      analogWrite(10, 0);
      }  
     
    void loop()
    {
      if (Serial.available())
      {
        char buf = 0;
        char res = Serial.read();
        if (buf != res){
            buf = res;
            switch (buf){

    //управление направлением        
              case 'F':
                    forward();
                    break;
              case 'B':
                    back();
                    break;
              case 'L':
                    left();
                    break;
              case 'R':
                    right();
                    break;
              case 'G':
                    forwardLeft();
                    break;
              case 'I':
                    forwardRight();
                    break;
              case 'H':
                    backLeft();
                    break;
              case 'J':
                    backRight();
                    break;
              case 'S':
                    stopcar();
                    break;

    // управление скоростью

              case '0':
                    vSpeed = 0;
                    break;
              case '1':
                    vSpeed = 100;
                    break;
              case '2':
                    vSpeed = 120;
                    break;
              case '3':
                    vSpeed = 140;
                    break;
              case '4':
                    vSpeed = 160;
                    break;
              case '5':
                    vSpeed = 180;
                    break;
              case '6':
                    vSpeed = 200;
                    break;
              case '7':
                    vSpeed = 220;
                    break;
              case '8':
                    vSpeed = 240;
                    break;
              case '9':
                    vSpeed = 247;
                    break;
              case 'q':
                    vSpeed = 255;
                    break;
                   
              default:
              break;                                              

              }
            }

      }
    }
    Теперь позвольте несколько выводов:
    1. Для начала я переключил приложение в режим "On change/touch". Проблема исчезла. Таким образом напрашивается вывод о том, что проблема все-таки в поточной передачи данных.
    2. Штука тут вот в чем. Управление скоростью происходит через смещение ползунка. Когда я двигаюсь, я скоростью не управляю. Пока я давлю кнопку вперед, отсылается 'F', как только я перестал давить, отсылается 'S' и машинка останавливается. Тогда я могу поменять скорость.
    3. После добавления конструкции с буфером включил режим передачи данных в потоке каждые 50мс. Машина прошла тест без сбоев, надеюсь и дальше сбоев не будет. Кажется, прием себя оправдал.
      Код (C++):
      if (Serial.available())
        {
          char buf = 0;
          char res = Serial.read();
          if (buf != res){
              buf = res;
              switch (buf){
      ..............................
      }}}

    4. Ну и последнее. Может это и фантастическое предположение, но ведь программирование Ардудино идет по тем же RX TX, что и связь с БТ модулем. Могла ли передача данных через него как-то его перепрошить?
     
    Igor68 нравится это.
  17. Igor68

    Igor68 Гуру

    Использую тот же порт - в моём случае шью и работаю в Raspberry через /dev/ttyUSB0 с Arduino. Никаких проблем по ModbusRTU протоколу. Правда при включении Arduino "вхолостую" принимает несколько первых пакетов не реагируя. Не замечал таких казусов.
     
  18. mcureenab

    mcureenab Гуру

    конструкция

    Код (C++):
    switch((int)v){
       case 'F':
    ...
     
    сначала преобразует char 'F' в int а потом сравнивает.

    Новая версия кода с char buf работает без преобразования.
     
  19. mcureenab

    mcureenab Гуру

    Много букв:

    Код (C++):
    if (Serial.available())
      {
        char buf = 0;
        char res = Serial.read();
        if (buf != res){
            buf = res;
            switch (buf){
    ..............................
    }}}
    Тут buf всегда ! = res. Разве что res==0 проскипает.
     
    GreatNonentity нравится это.
  20. Spell

    Spell Нерд

    Абсолютно согласен.
    Я проверил. Serial.read() действительно возвращает int.
    И char buf = 0; должна вызываться вне условия, да и вообще вне loop. Это срока для инициализации этой переменной, а дальше она должна содержать последнюю команду.
    А в этом коде она всегда будет 0.
     
    GreatNonentity нравится это.