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