Sunday, 12 April 2015

MeArm 0.4 with bluetooth control

In the previous post I discussed the MeArm 0.4 and how it works and some modifications.  One of the things I really wanted to be able to do with it was control it remotely.  My intention is to mount the arm onto my bluetooth rover chassis and then control the arm and the robot using an android application on my phone.  If you didn't see the previous post it is here:

MeArm!

To that end I purchased a cheap (but functional) serial to Bluetooth radio module from Hobby Components.  It cost £6.49 which I think is a reasonable price.  They are available from most good vendors and if you are really stuck on ebay...

HC-05 Master Slave Bluetooth Module

I had already designed a port on the PCB to accept the module so this was always my intention...

Next I looked on Google Play for suitable Bluetooth robot control applications.  There are several to choose from, it appears everyone wants to be able to do this!

Arduino Bluetooth Joystick by Felipe Porge - This is great app and it is so simple to use.  I used this app originally for controlling the Bluetooth rover.  My only reason for not using it this time is I need more buttons!

Arduino Total Control by Juan Luis Gonzales Bello - This is a great app and a very polished offering based upon the demonstrations provided.  I found it very hard to get to grips with.  It wasn't clear how to connect the buttons to the control aspects and where the user actually obtained the images uploading as buttons for controlling the device.  There are examples provided however and the documentation needs improving. The facility to make an android phone talk and bleep is awesome as is the accelerometer servo control.

Remote XY by Shemanuev Evgeny - This is very similar to Arduino Total Control and is a good application.  It is much easier to use in my opinion.  All that is needed is to go to the associated website and drag and drop some control items to make up a graphical user interface.  The control code is then generated for use.

I decided after trying several of the apps to use Remote XY.  I was torn between using Arduino Total Control or Remote XY but remoteXY won because I found it easier to use.  I will certainly be keeping up with Arduino Total Control because I think it is awesome and once the documentation (and my programming skills) improve I will be using it.

All I did was download the app from Google play and install it on my mobile phone which is a Galaxy Note Edge (*very cool handset*)

I then went to the remote XY website and built a graphical user interface for the MeArm 0.4 combined with the rover chassis

http://remotexy.com/

I needed controls for the following things:

  • A joystick control to ensure the servo motors controlling the forward back left right motion of the robot.
  • A pair of buttons to control the waist movement of the meArm.
  • A pair of buttons to control the shoulder movement of the meArm.
  • A pair of buttons to control the elbow movement of the meArm.
  • A pair of buttons to control the gripper opening and closing.  
  • A stop button to stop all movement
  • A start button to restart all movement
Here is what I implemented in five minutes:
I then saved my design and clicked on the 'get source code' button.  The website then provided a window asking me to select which type of serial control I intended to use; I chose Arduino Serial as I am using the arduino mega and I have already laid out the control PCB to use the Serial 2 port.

I then downloaded the project and copied it with the remote XY.h and remote XY.cpp files to my arduino sketchbook folder.

I then loaded it up in the arduino ide.  It is here that I had to start splicing my original rover control code and the code that I had written for the meArm.  It didn't take very long.  Basically what was needed was to call the movement functions whenever a button pressed action is detected via Bluetooth. Here is the code:

MeArm Bluetooth Arduino Code

/* Alex's MeArm code Serial Bluetooth Control Code
12/04/2015 (c)

This code controls a meArm 0.4 via the
an Android phone and
using the remoteXY app

Enjoy!

Base servo connected to pin 9
shoulder servo connected to pin 8
Wrist servo connected to pin 7
Gripper servo connected to pin 6

Rover Servo Left connected to pin 5
Rover Servo Right Connected to pin 3

*/

#include "remotexy.h" // Include the header file for remote XY
#include <Servo.h>    // Include the servo control library
#include <avr/wdt.h>  // Include the watchdog library

Servo left, right, waist, shoulder, elbow, gripper ;  // create servo objects to control servos

int waistPos = 74;      // variable to store the base servo position
// Set to 10 to prevent movement at start

int shoulderPos = 64;   // variable to store the shoulder servo position
// Set to 50 to prevent movement at start

int elbowPos = 98;      // variable to store the elbow servo position
// Set to 70 to prevent movement at start

int gripperPos = 0;     // variable to store the gripper servo position
// Set to 0 to prevent movement at start

int leftPos = 89;       // variable to store the left servo position
// set to 89 to prevent movement at start

int rightPos = 89;      // variable to store the right servo position
// set to 89 to prevent movement at start

void setup()
{
  Serial.begin(9600);

  RemoteXY_Init ();     // Initialise the remote XY code - set screen and buttons etc

  left.attach(5);       // attaches the left drive servo on pin 5 to the servo object
  right.attach(3);      // attaches the right drive servo on pin 4 to the servo object
  waist.attach(9);      // attaches the waist servo on pin 9 to the servo object
  shoulder.attach(8);   // attaches the uppoer shoulder servo on pin 9 to the servo object
  elbow.attach(7);      // attaches the lower shoulder servo on pin 7 to the servo object
  gripper.attach(6);    // attaches the gripper servo on pin 6 to the servo object

  left.write(leftPos);         // Halt left servo
  right.write(rightPos);       // Halt right servo
  waist.write(waistPos);       // set intial waist position
  shoulder.write(shoulderPos); // set initial shoulder position
  elbow.write(elbowPos);       // Set inital elbow position
  gripper.write(gripperPos);   // Ensure gripper is open!
}

void loop()
{
  RemoteXY_Handler ();  //read user data from mobile phone

  if (RemoteXY.roverForwards == 1) // move rover forwards button pressed
  {
    moveForward();
  }

  if (RemoteXY.roverBackwards == 1) // move rover backwards button pressed
  {
    moveBackward();
  }

  if (RemoteXY.roverLeft == 1) // move rover left button pressed 
  {
    leftTurn();
  }

  if (RemoteXY.roverRight == 1) // move rover right button pressed
  {
    rightTurn();
  }

  if (RemoteXY.roverStop == 1) // stop rover button pressed
  {
    roverStop();
  }

  if (RemoteXY.waistAntiClockwise == 1) // move meArm waist antiClockwise button pressed 
  {
    moveWaistAntiClockwise();
  }

  if (RemoteXY.waistClockwise == 1) // move meArm waist clockwise button pressed
  {
    moveWaistClockwise();
  }

  if (RemoteXY.shoulderExtend == 1) // extend meArm shoulder button pressed
  {
    shoulderExtend();
  }

  if (RemoteXY.shoulderRetract == 1) // retract meArm shoulder button pressed
  {
    shoulderRetract();
  }

  if (RemoteXY.elbowUp == 1) // extend meArm elbow button pressed
  {
    elbowUp();
  }

  if (RemoteXY.elbowDown == 1) // retract meArm elbow button pressed
  {
    elbowDown();
  }

  if (RemoteXY.gripperOpen == 1) // open meArm gripper button pressed
  {
    openGripper();
  }

  if (RemoteXY.gripperClose == 1) // close meArm gripper button pressed
  {
    closeGripper();
  }

  if (RemoteXY.stopAllMovement == 1) // stop all movement button pressed
  {
    stopMoving();
  }

  if (RemoteXY.startAllMovement == 1) // start all movement button pressed
  {
    startMoving();
  }

  if (RemoteXY.resetArduino == 1) // reset arduino button pressed
  {
    resetArduino();
  }
}

void moveForward()  // move the rover forwards
{
  left.attach(5);       // attaches the left drive servo on pin 5 to the servo object
  right.attach(3);      // attaches the right drive servo on pin 4 to the servo object

  //set left servo going forward
  leftPos = 179;
  left.write(leftPos);

  //set Right servo going forward
  rightPos = 0;
  right.write(rightPos);

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void moveBackward() // move the rover backwards
{
  left.attach(5);       // attaches the left drive servo on pin 5 to the servo object
  right.attach(3);      // attaches the right drive servo on pin 4 to the servo object

  //set left servo going back
  leftPos = 0;
  left.write(leftPos);

  //set light servo going back
  rightPos = 179;
  right.write(rightPos);

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void leftTurn() // turn rover left
{
  left.attach(5);       // attaches the left drive servo on pin 5 to the servo object
  right.attach(3);      // attaches the right drive servo on pin 4 to the servo object

  //set left servo going left
  leftPos = 0;
  left.write(leftPos);

  //set Right servo going left
  rightPos = 0;
  right.write(rightPos);

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void rightTurn() //turn rover right
{
  left.attach(5);       // attaches the left drive servo on pin 5 to the servo object
  right.attach(3);      // attaches the right drive servo on pin 4 to the servo object

  //set left servo going right
  leftPos = 179;
  left.write(leftPos);

  //set Right servo going right
  rightPos = 179;
  right.write(rightPos);

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void roverStop() // halt the rover
{
  Serial.print("Robot Halted");
  Serial.println();

  //Halt left servo
  leftPos = 89;
  left.write(leftPos);

  //Halt right servo
  rightPos = 89;
  right.write(rightPos);

  left.detach();
  right.detach();

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void moveWaistAntiClockwise() // move waist anti clockwise
{

  Serial.print("Moving waist Anti clockwise ");
  Serial.println();
  Serial.print("Waist Position Value: ");
  Serial.print(waistPos);
  Serial.println();

  if (waistPos >= 0 && waistPos != 179)
  {
    waistPos++;
    waist.write(waistPos);
    delay(15);
  }

  if (waistPos == 179)
  {
    Serial.print("Reached end of travel....stopping");
    Serial.println();
  }

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void moveWaistClockwise() // move waist clockwise
{

  Serial.print("Moving base clockwise ");
  Serial.println();
  Serial.print("waistPos Value: ");
  Serial.print(waistPos);
  Serial.println();

  if (waistPos <= 179 && waistPos != 0)
  {
    waistPos--;
    waist.write(waistPos);
    delay(15);
  }

  if (waistPos == 0)
  {
    Serial.print("Reached end of travel....stopping");
    Serial.println();
  }

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void shoulderExtend() //extend shoulder section
{

  Serial.print("Extending from shoulder");
  Serial.println();

  Serial.print("shoulderPos Value: ");
  Serial.print(shoulderPos);
  Serial.println();

  if (shoulderPos >= 32 && shoulderPos != 179)
  {
    shoulderPos++;
    shoulder.write(shoulderPos);
    delay(15);
  }

  if (shoulderPos == 179)
  {
    Serial.print("Reached end of travel....stopping");
    Serial.println();
  }

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void shoulderRetract() // retract shoulder section
{

  Serial.print("Retracting from shoulder");
  Serial.println();

  Serial.print("shoulderPos Value: ");
  Serial.print(shoulderPos);
  Serial.println();

  if (shoulderPos <= 179 && shoulderPos != 32)
  {
    shoulderPos--;
    shoulder.write(shoulderPos);
    delay(15);
  }

  if (shoulderPos == 32)
  {
    Serial.print("Reached end of travel....stopping");
    Serial.println();
  }

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void elbowUp() // move elbow up
{

  Serial.print("Extending elbow ");
  Serial.println();

  Serial.print("elbowPos Value: ");
  Serial.print(elbowPos);
  Serial.println();

  if (elbowPos >= 45 && elbowPos != 147)
  {
    elbowPos++;
    elbow.write(elbowPos);
    delay(15);
  }

  if (elbowPos == 147)
  {
    Serial.print("Reached end of travel....stopping");
    Serial.println();
  }

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void elbowDown() // move elbow down
{

  Serial.print("Retracting elbow");
  Serial.println();

  Serial.print("elbowPos Value: ");
  Serial.print(elbowPos);
  Serial.println();

  if (elbowPos <= 147 && elbowPos != 45)
  {
    elbowPos--;
    elbow.write(elbowPos);
    delay(15);
  }

  if (elbowPos == 45)
  {
    Serial.print("Reached end of travel....stopping");
    Serial.println();
  }

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void closeGripper() // close gripper
{

  Serial.print("Closing Gripper Jaws ");
  Serial.println();

  Serial.print("gripperPos Value: ");
  Serial.print(gripperPos);
  Serial.println();

  if (gripperPos >= 0 && gripperPos != 40)
  {
    gripperPos++;
    gripper.write(gripperPos);
    delay(15);
  }

  if (gripperPos == 40)
  {
    Serial.print("Reached end of travel....stopping");
    Serial.println();
  }

  Serial2.flush();
  Serial.flush();

  delay(15);
}

void openGripper() // open gripper
{

  Serial.print("Opening Gripper Jaws ");
  Serial.println();

  Serial.print("gripperPos Value: ");
  Serial.print(gripperPos);
  Serial.print(gripperPos);
  Serial.println();

  if (gripperPos <= 40 && gripperPos != 0)
  {
    gripperPos--;
    gripper.write(gripperPos);
    delay(15);
  }

  if (gripperPos == 0)
  {
    Serial.print("Reached end of travel....stopping");
    Serial.println();
  }

  Serial.flush();

}

void startMoving() // start robot moving again
{

  Serial.print("Start All Movement ");
  Serial.println();

  left.attach(5);
  right.attach(3);
  waist.attach(9);
  shoulder.attach(8);
  elbow.attach(7);
  gripper.attach(6);

  Serial2.flush();
  Serial.flush();

  delay(15);

  delay(15);

}


void stopMoving() // stop robot moving completely
{

  Serial.print("Stop All Movement ");
  Serial.println();

  left.detach();
  right.detach();
  waist.detach();
  shoulder.detach();
  elbow.detach();
  gripper.detach();

  Serial2.flush();
  Serial.flush();

  delay(15);

}

void resetArduino() // reset the arduino remotely
{
  wdt_enable(WDTO_15MS);
  while (1)
  {
  } 
}  

Here is the remotexy.cpp:

#include "remotexy.h" 
#include <Arduino.h> 

#define REMOTEXY_CONF_LENGTH   271 
#define REMOTEXY_BUFFER_LENGTH 17 
#define REMOTEXY_VALUES_LENGTH 16 
#define REMOTEXY_OUTPUT_INDEX  16 
#define REMOTEXY_TIMOUT  300 
#define REMOTEXY_TIMOUT_DISCONNECTED  2000 

typedef struct {   
  unsigned int count; 
  unsigned int index; 
  unsigned char command; 
  unsigned char crc; 
  unsigned long timeout; 
} RemoteXY_State_TypeDef; 

RemoteXY_State_TypeDef RemoteXY_State; 
RemoteXY_TypeDef RemoteXY; 
unsigned char RemoteXY_buffer[REMOTEXY_BUFFER_LENGTH]; 

unsigned char RemoteXY_conf[REMOTEXY_CONF_LENGTH] = 
  { 3,5,1,0,14,25,12,12,6,70
  ,119,100,0,1,0,14,51,12,12,6
  ,66,107,119,100,0,1,0,0,38,12
  ,12,6,76,70,84,0,1,0,28,38
  ,12,12,6,82,72,84,0,1,0,5
  ,9,12,12,1,87,45,0,1,0,20
  ,9,12,12,1,87,43,0,1,0,41
  ,25,12,12,4,83,45,0,1,0,41
  ,10,12,12,4,83,43,0,1,0,62
  ,10,12,12,5,69,43,0,1,0,62
  ,25,12,12,5,69,45,0,1,0,82
  ,10,12,12,2,71,43,0,1,0,82
  ,25,12,12,2,71,45,0,1,0,48
  ,48,12,12,1,88,0,1,0,66,48
  ,12,12,4,88,0,1,0,14,38,12
  ,12,1,88,0,1,0,83,48,12,12
  ,0,88,0,129,0,12,3,13,5,9
  ,87,97,105,115,116,0,129,0,35,3
  ,22,5,9,83,104,111,117,108,100,101
  ,114,0,129,0,60,3,15,5,9,69
  ,108,98,111,119,0,129,0,79,3,18
  ,5,9,71,114,105,112,112,101,114,0
  ,129,0,2,29,11,4,9,82,111,118
  ,101,114,0,129,0,48,40,13,6,9
  ,83,116,111,112,0,129,0,65,40,14
  ,6,9,83,116,97,114,116,0,129,0
  ,82,40,16,6,9,82,101,115,101,116
  ,0 }; 


void RemoteXY_SerialInit (void) { 
  REMOTEXY_SERIAL.begin(9600); 
} 
#define RemoteXY_SerialAvailable() REMOTEXY_SERIAL.available() 
#define RemoteXY_SerialWrite(b)    REMOTEXY_SERIAL.write (b) 
#define RemoteXY_SerialRead()      REMOTEXY_SERIAL.read() 

void RemoteXY_Init (void) {  
  unsigned char i;  
  unsigned char* p = (unsigned char*)&RemoteXY;  
  for (i=0;i<REMOTEXY_VALUES_LENGTH;i++) *p++=0;       
  RemoteXY_State.index = 0;  
  RemoteXY_State.crc = 0;   
  RemoteXY_SerialInit ();  
  RemoteXY.connect_flag=0; 
}  

void RemoteXY_WriteByte (unsigned char c, unsigned char * crc) {  
  RemoteXY_SerialWrite (c);   
  *crc-=c;    
}  

void RemoteXY_Send (unsigned char *p, unsigned int len) {  
  unsigned char c;    
  unsigned char crc = 0;  
  unsigned int i = len+4;  
    
  RemoteXY_WriteByte (i & 0xff, &crc);  
  RemoteXY_WriteByte ((i & 0xff00)>>8, &crc);  
  RemoteXY_WriteByte (RemoteXY_State.command, &crc);    
  while (len--) {  
    RemoteXY_WriteByte (*(p++), &crc);  
  }    
  RemoteXY_SerialWrite (crc);  
}  

void RemoteXY_Receive (unsigned char *p, unsigned int len) {  
  unsigned char *pi = RemoteXY_buffer;  
  while (len--) {  
    *p++=*pi++;  
  }  
}  

void RemoteXY_Handler (void) {  
  unsigned char c;  
  unsigned long tim;  
  while (RemoteXY_SerialAvailable() > 0) {    
    c = RemoteXY_SerialRead ();  
    RemoteXY_State.crc+=c;  
    switch (RemoteXY_State.index) {  
      case 0:   
        RemoteXY_State.count=c;  
        break;  
      case 1:   
        RemoteXY_State.count+=c<<8;  
        break;  
      case 2:   
        RemoteXY_State.command=c;  
        break;  
      default:   
        if (RemoteXY_State.index-3<REMOTEXY_BUFFER_LENGTH)  
          RemoteXY_buffer[RemoteXY_State.index-3]=c;  
    }  
    RemoteXY_State.index++;  
    RemoteXY_State.timeout=millis();  
    if ((RemoteXY_State.index == RemoteXY_State.count) && (RemoteXY_State.count >= 4)) {  
      if (RemoteXY_State.crc == 0) {  
        switch (RemoteXY_State.command) {  
          case 0x00:  
            RemoteXY_Send (RemoteXY_conf, REMOTEXY_CONF_LENGTH);  
            RemoteXY.connect_flag=1; 
            break;   
          case 0x40:  
            RemoteXY_Send ((unsigned char*)&RemoteXY, REMOTEXY_VALUES_LENGTH);  
            break;   
          case 0x80:  
            RemoteXY_Receive ((unsigned char*)&RemoteXY, RemoteXY_State.index-4);   
            RemoteXY_Send (0, 0);  
            break;   
          case 0xC0:  
            RemoteXY_Send ((unsigned char*)&RemoteXY+REMOTEXY_OUTPUT_INDEX,   
              REMOTEXY_VALUES_LENGTH-REMOTEXY_OUTPUT_INDEX);  
            break;   
        }  
      }  
      RemoteXY_State.index = 0;  
      RemoteXY_State.crc = 0;         
    }  
  }  
  if (RemoteXY_State.index>0) {  
    if (millis()-RemoteXY_State.timeout>REMOTEXY_TIMOUT) {  
      RemoteXY_State.index = 0;  
      RemoteXY_State.crc = 0;               
    }  
  }  
  if (millis()-RemoteXY_State.timeout>REMOTEXY_TIMOUT_DISCONNECTED) { 
    RemoteXY.connect_flag=0;  
  }  
    
}

Here is the remotexy.h:

#ifndef _REMOTEXY_H_ 
#define _REMOTEXY_H_ 


/* enter your serial port */ 
#define REMOTEXY_SERIAL Serial2 


/* this structure defines all the variables of your control interface */ 
typedef struct { 
  
    /* input variable */
  unsigned char roverForwards; /* =1 if button pressed, else =0 */
  unsigned char roverBackwards; /* =1 if button pressed, else =0 */
  unsigned char roverLeft; /* =1 if button pressed, else =0 */
  unsigned char roverRight; /* =1 if button pressed, else =0 */
  unsigned char waistAntiClockwise; /* =1 if button pressed, else =0 */
  unsigned char waistClockwise; /* =1 if button pressed, else =0 */
  unsigned char shoulderRetract; /* =1 if button pressed, else =0 */
  unsigned char shoulderExtend; /* =1 if button pressed, else =0 */
  unsigned char elbowUp; /* =1 if button pressed, else =0 */
  unsigned char elbowDown; /* =1 if button pressed, else =0 */
  unsigned char gripperOpen; /* =1 if button pressed, else =0 */
  unsigned char gripperClose; /* =1 if button pressed, else =0 */
  unsigned char stopAllMovement; /* =1 if button pressed, else =0 */
  unsigned char startAllMovement; /* =1 if button pressed, else =0 */
  unsigned char roverStop; /* =1 if button pressed, else =0 */
  unsigned char resetArduino; /* =1 if button pressed, else =0 */

    /* other variable */
  unsigned char connect_flag;  /* =1 if wire connected, else =0 */

} RemoteXY_TypeDef; 

/* this variable must be used for data transfer */ 
extern RemoteXY_TypeDef RemoteXY; 


void RemoteXY_Init (void); 
void RemoteXY_Handler (void); 

#endif //_REMOTEXY_H_ 

The code itself is pretty self-explanatory.  I have added as many comments as I felt necessary. Essentially the remoteXY.cpp and remoteXY.h files tell the application on the mobile phone what buttons are present and where to put them with the required size, text and colour.  The main loop within the arduino code then listens for button press actions on the phone when the remoteXY application is running on the mobile phone handset and connected via the Bluetooth serial communications.  If a button press is detected and the arduino processes the action and the robot responds accordingly.

It is important to ensure that the associated arduino library for use with the remoteXY application has been downloaded and correctly installed in the arduino libraries folder.

remoteXY Arduino Library

I then uploaded the code to my arduino mega, connected a stack of batteries to the rover and powered up the robot - it helpfully starts in a sensible state, no movement with the arm suitably positioned.  I then loaded up the remoteXY app on my android mobile phone and connected the Bluetooth communications to the HC-05 module and SUCCESS!  The buttons control the rover and meArm perfectly - I am very happy with the results.  Here is a video of the robot in action:


Well....thats all for now, I''m off to play with my robot.  Take care people - Langster!