Обучаемое управление роботом по ИК пульту

в 9:26, , рубрики: arduino, diy или сделай сам, робототехника, роботы, метки: , , ,

Обучаемое управление роботом по ИК пульту
Недавно я присоединился к проекту Робот-Митя. Спасибо большое Дмитрию DmitryDzz, что сделал такой классный проект и помог с первоначальным запуском робота, особенно что касается запуска Android-приложения.
Роботом уже можно было управлять по Bluetooth и Wi-Fi (через Android-голову). И через некоторое время захотелось управлять Митей пультом. На борту штатного робота у Робота уже был ИК-приемник (ведь изначально он был собран для ИК-войнушки), поэтому дело оставалось за кодом. Довольно быстро удалось настроить управление по своему телевизионному пульту, считав и записав “коды” клавиш пульта, выдаваемые библиотекой IRremote.h. Однако для этого пришлось прописать в коде эти “коды”, что было, не универсально: каждому участнику пришлось бы отдельно считывать и прописывать вручную коды, и мне при смене пульта или небольшой смене команд, пришлось бы заново прописывать данные этих пультов в скетче. А как было бы здорово, взять ЛЮБОЙ пульт и просто начать управлять Митей с его помощью!

Обучение робота командам пульта

А действительно, почему и да? Ведь на борту Arduino 512 байт EEPROM, чего хватит на сохранение 128 команд. (Один “код” занимает 4 байта). У меня сейчас получилось всего 17 команд. Осталось придумать, как реализовать обучение робота пульту. Хотелось бы это сделать так, чтобы это было максимально просто и желательно даже без участия телефонной части робота (вдруг кто-то соберет робота и у него не будет Android телефона). В таком случае сразу после сборки робота, можно будет залив в него скетч, без всяких изменений сразу же управлять роботом. Это одна из маленьких побед, которая очень обрадует нового участника и у него будет больше мотивации двигаться дальше (сборка Android и Windows-части робота.) — Мы решили, что в процессе присоединения нового участника очень важно, чтобы на каждом этапе человек видел позитивную обратную связь от разработки, и не бросил на полпути, если что-то не получается. Поэтому обучение роботу команд от пульта было решено сделать исключительно с помощью пульта.
Чтобы робот узнал, что ему пора обучаться он будет ждать нажатия какой-нибудь любой клавиши на пульте по азбуке морзе, например, буквы К (я своего робота назвал в честь жены — Катюша), т.е. Длительное нажатие, затем короткое, снова длинное и короткое.
После этого робот переходит в интерактивный режим обучения. Это значит, что он начинает выполнять действие, а вам надо просто нажать кнопку для этого действия, затем следующее и так далее до последнего действия.
В заключение после сохранения всех параметров Катюша начинает кружиться в танце, приглашая поиграть.

Программирование

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

Загрузка старых команд

При запуске загружаем из памяти старые команды. Если они не были записаны до этого — ничего страшно не случиться.

#include <EEPROM.h>
...
const int IR_TOTAL_COMMANDS = 17; // Total Number of commands, that can be send by IR control
unsigned long IrCommands[IR_TOTAL_COMMANDS]; // All command from remote control
// Read all IR commands from EEPROM 
  for(int i=0; i<sizeof(IrCommands); i++)
  {
     *((byte*)&IrCommands + i) = EEPROM.read(i);
  }
Проверка нажатия клавиш на ИК-пульте

Проверяем, были ли нажаты кнопки на пульте и как долго, после этого запускается обработчик команд.
После нажатия какой-либо кнопки на пульте, функция irrecv.decode(&results); возвращает в results.value код кнопки, если кнопка была только что нажата. Если кнопка была нажата и удержана, то посылается код кнопки, а затем IR_COMMAND_SEPARATOR = 0xFFFFFFFF посылается с определенной периодичностью, пока кнопка не будет отпущена. Таким образом мы определяем длительность нажатия — быстрое IrRemoteButtonState = 1, короткое IrRemoteButtonState = 2 — пришел один IR_COMMAND_SEPARATOR после команды, и длинное IrRemoteButtonState = 3 — пришло двое и более IR_COMMAND_SEPARATOR.
Для определения длительности мы будем считать быстрое и короткое эквивалентными.

Функция CheckIrCommands() вызывается из главного цикла loop().

// Check if any IR remote buttons pressed
void CheckIrCommands()
{
// If we're in programm mode, just check if buttons vere pressed there
  if(IsInIrProgrammMode)
  {
    IrProgrammatorProcess();
    return;
  }
  
  if (irrecv.decode(&results)) {
    // Button was pressed for LONG or SHORT time, but not VERY SHORT )
    if(results.value==IR_COMMAND_SEPARATOR)
    {
      if(IrRemoteButtonState<3)
      { // Set the IR remote button state
        IrRemoteButtonState++;
      }
      // Update last pressed button state
      IrLastCommandsState[0] = IrRemoteButtonState;
    }else
    {
      IrRemoteLastCommand = results.value;
      IrRemoteButtonState = 1;
       // Saving last pressed buttons and their state - we will use it to determine when to start IR programmator.
      for(int i=IR_LAST_COMMANDS_BUFFER_SIZE-1; i>0; i--)
      {
        IrLastCommandsState[i] = IrLastCommandsState[i-1];
        IrLastCommandsValue[i] = IrLastCommandsValue[i-1];
      }
      IrLastCommandsValue[0] = IrRemoteLastCommand;  
      IrLastCommandsState[0] = 2; // For programmator mode short and very short press of the button is equal.

      checkIrHit(); // Check if robot was hit by another robot
    }
    ProcessIrCommands(); 

    irrecv.resume();
  }
}
Определение команды

Функция ProcessIrCommands() определяет какая из известных клавиш была нажата и выполняет соответствующую команду.
Если команда неизвестна, то проверяем на нажатие одной и той же кнопки по азбуке морзе (Длинная-Короткая-Длинная-Короткая) для запуска режима обучения.

void ProcessIrCommands()
{
  if(IrRemoteLastCommand==0) return; // The signal was not good enough to get the signal data
  
  // Check for known commands and executing them
  for(int i=0; i<IR_TOTAL_COMMANDS; i++)
  {
    if(IrCommands[i]==IrRemoteLastCommand)
    {
      executeIrCommand(i);
      return;
    }
  }
  // We recieved unknown command. Let's check if we need to enter in programmator mode. (Same button should be pressed in the following order: LONG->SHORT->LONG->SHORT
  if((IrLastCommandsValue[0]==IrLastCommandsValue[1])&&(IrLastCommandsValue[0]==IrLastCommandsValue[2])&&(IrLastCommandsValue[0]==IrLastCommandsValue[3])
    &&(IrLastCommandsState[0]==2)&&(IrLastCommandsState[1]==3)&&(IrLastCommandsState[2]==2)&&(IrLastCommandsState[1]==3))
  {
    // Starting Programmator mode.
    IrServoStep = IR_SERVO_STEP_PROGRAM_MODE; // Maximun step for Programm mode, so that user will notice servo movement
    IsInIrProgrammMode = true;
    IrProgrammatorStep = 0;
    executeIrCommand(0); //  Executing command and waiting for user to press the button to save it.
  }
}
Исполнение команды

void executeIrCommand(int cmd)
{
  switch(cmd)
  {
    case 0: // move forward
      moveMotor( "G", IR_MOVE_SPEED );
      break;
    case 1: // move backwards
      moveMotor( "G", -IR_MOVE_SPEED );
      break;        
    case 2: // turn left
      moveMotor( "L", -IR_MOVE_SPEED );
      moveMotor( "R", IR_MOVE_SPEED );
      break;
    case 3: // turn right
      moveMotor( "L", IR_MOVE_SPEED );      
      moveMotor( "R", -IR_MOVE_SPEED );
      break;   
    case 4: //stop
      moveMotor( "G", 0 );
      break;
      
    case 5: // Move Horizontal Head Left
      moveHead( "H", servoHeadHorizontal.read()-IrServoStep );
      break;
    case 6: // Move Horizontal Head Right
      moveHead( "H", servoHeadHorizontal.read()+IrServoStep );
      break;
    case 7: // Move Vertical Head Up
      moveHead( "V", servoHeadVertical.read()-IrServoStep );
      break;        
    case 8: // Move Vertical Head Down
      moveHead( "V", servoHeadVertical.read()+IrServoStep );
      break;
    case 9: //no
      noSwinger.startSwing(90, 1, 400, 2.5, 60, 0.75, true);      
      break;
    case 10: //yes
      yesSwinger.startSwing(60, 1, 400, 2.5, 30, 0.8, true);
      break;
    case 11: //tail
      tailSwinger.startSwing(90, 1, 250, 6, 70, 0.9, true);
      break;
    case 12: // Mood 
      executeAction("M", 0x0102, true );
      break;
    case 13: // Mood 
      executeAction("M", 0x0103, true );
      break;
    case 14: // Mood 
      executeAction("M", 0x0104, true );
      break;
    case 15: // Mood 
      executeAction("M", 0x0105, true );
      break;
    case 16: // Mood 
      executeAction("M", 0x0106, true );
      break;
  }
}
Запоминание команд

Запускаем действие робота и ждем пока пользователь нажмет кнопку на ИК-пульте. После нажатия кнопки запускаем следующее действие и т.д.
После получения заключительной команды, сохраняем все в память EEPROM и запускаем вращение Катюши по кругу, как индикацию что она готова поиграть с пультом.

void IrProgrammatorProcess()
{
  if (irrecv.decode(&results))
  {
      if((results.value!=IR_COMMAND_SEPARATOR)&&(results.value!=0)&&(results.value!=IrLastCommandsValue[0]))
      {
        IrCommands[IrProgrammatorStep]=results.value;
        if(IrProgrammatorStep==IR_TOTAL_COMMANDS-1) // Was it the last command?
        {
          // Save all IR commands to EEPROM 
          for(int i=0; i<sizeof(IrCommands); i++)
          {
             EEPROM.write(i, *((byte*)&IrCommands + i) );
          }
          IsInIrProgrammMode = false;
          IrServoStep = IR_SERVO_STEP_DEFAULT;
          executeIrCommand(2); // Tell the user that we finished programm mode
        }else
        {
          executeIrCommand(++IrProgrammatorStep);
        }

      }            
      irrecv.resume();        
  }
}

Полный код в SVN: http://code.google.com/p/robot-mitya/source/checkout

Автор: ilfumo

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js