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