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

от автора


Недавно я присоединился к проекту Робот-Митя. Спасибо большое Дмитрию 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

ссылка на оригинал статьи http://habrahabr.ru/post/168197/


Комментарии

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

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