Open Arduino

Enter Your Project for a chance to win a grand prize for the most innovative use of Arduino or a $200 shopping cart! The Birthday Special: Arduino Projects for Arduino Day!

Back to The Project14 homepage

Project14 Home
Monthly Themes
Monthly Theme Poll

 

 

Star Wars MSE-6 (Mouse Droid) INDEX:

 

Arduino Powered MSE-6 (Mouse Droid)

Arduino Powered MSE-6 (Mouse Droid) - Arduino Yun UI

Arduino Powered MSE-6 (Mouse Droid) - Arduino Uno and FreeRTOS

Arduino Powered MSE-6 (Mouse Droid) - Droid in Action

Arduino Powered MSE-6 (Mouse Droid) – Arduino Code

 

Arduino Powered MSE-6 (Mouse Droid) – Arduino Code

 

Yun Code

 

Although the challenge is completed, I thought I would go over some of the details of the Arduino code I used in my Mouse Droid.  On the Arduino side, I had to come up with code for both the Arduino Yun as wel as for the UNO.  The Yun controlled hosting the web-page via the Flask web-server which is used to send message to the Yun and the UNO. The one interesting thing with the Yun is that it has both a Atheros 9331 running Linux and a ATmega32U4 where the Arduino code is run.  In order to easily communicate between the  Linux and the Arduino sides of the board, a Bridge Library was created.  The Bridge takes commands from the Arduino device and passes them to the Linux side and vise-versa.

https://www.arduino.cc/en/Reference/YunBridgeLibrary

 

However, this communication is performed through a serial port on the Yun leaving no serial ports for the user.  The way I got around this was to use a SoftwareSerial interface on the Yun and the UNO to pass messages between the two boards.

 

So, for instances when a message is entered from the Mouse Droid web page using "Enter a New message" , I use a form action to post the message to the Flask web-server with in input type and a unique id.

 

index.html code for processing a message:

     <p align="center">
       <div>
         <label name="mse6DS" id="mse6DS">"Join the Dark Side"</label>
       </div>
     </p>
      <p>


      </p>
      <p>
        <form action="/mse6Msg" method="post">
          <div align="center">
            <label for="newMsg" id="msgLbl">Enter New Message:</label>
            <input type="text" name="newMsg" id="newMsg"></input>
            <input type="submit" value="Send Message"></input>
         </div>
       </form>
     </p>

 

On the Flask side, in my "views.py" file I have an app.route that preforms a request.form to get the new message from the input type with id="newMsg".  I then take the message, and pass this along with an added "vader#" heading for message identification to a script that handles the sending of the message to the ATmega32U4 for processing.

@app.route('/mse6Msg', methods = ['POST'])
def show_mes6msg():
    newMSE6Msg = request.form['newMsg']
    print("Message received '" + newMSE6Msg + "'")
    yun_bridge.main('vader#' + newMSE6Msg);
    return ('', 204)

 

In the bridge script, I take the message and pass it to the ATmega32U4 through the Bridge Library by way of a mailbox.  I did not find a way to create a separate mailbox, so the same process is used to handle the direction buttons from the web-page. To use the mailbox method, the BridgeClient must be imported and then create an instance of the BridgeClient.

 

yun_bridge.py

# yun_bridge.py


#!/usr/bin/python 


import sys
from time import sleep
sys.path.insert(0, '/usr/lib/python2.7/bridge')
from bridgeclient import BridgeClient as bridgeclient


value = bridgeclient()


def main(arg1):


    try:
        value.mailbox(arg1);
        sleep(2)


    except:
        print "Process failed to open!"
    else:
        print("Message sent %s\n" % arg1)


if __name__=='__main__':
    if len(sys.argv) < 2:
        print("ERROR: Enter a LED command!")
        sys.exit(1)
    main(sys.argv[1])

 

Now, in the Arduino code, I have a couple of structures to collect the inbound message as well as set the screen location and Font processing.

struct  SCREENLOC{
  size_t msgSz;
  uint16_t msgStartX;
  uint16_t msgStartY;
  uint16_t msgIncX;
  uint16_t msgIncY;
  uint16_t msgFont;
  uint16_t msgColor;
};


struct  incomingMesg{
  char msgStr[IN_MSGSIZE];
  struct SCREENLOC scrnloc;
}vaderMsg;


struct  MSGE6MSG{
  char msgStr[MSGSIZE];
  struct SCREENLOC scrnloc;
}mseData;

 

To have access to the Bridge, Mailbox and Process methods, it is necessary to include these in the Arduino code.

#include <Process.h>
#include <Mailbox.h>
#include <Bridge.h>
#include <SoftwareSerial.h>

 

To set-up the SoftwareSerial between the two boards I create an instance of the SoftwareSerial object using Analog pins.  With the SoftwareSerial object, you can use either digital pins or Analog pins. In my case I opted for the Analog pins since these were available after the TFT screen took up pretty much all of the Digital pins.

#define rxPin A5
#define txPin A4
char mystr[5] = "Left"; //String data


SoftwareSerial mySerial(rxPin, txPin);

 

To initialize the SoftwareSerial port, I set the rx pin as an INPUT and the tx pin as an OUTPUT then start the serial port.

void initSoftSerial(void)
{
  // Begin the Serial at 115200 Baud
  pinMode(rxPin, INPUT);
  pinMode(txPin, OUTPUT);
  
  mySerial.begin(115200);
}

 

I have an initHeading function that initializes the screen as well as the vaderMsg structure with its screen location data.

void initHeading(void)
{
   Process setInit;
   
  uint16_t x = 0;
  uint16_t y = 10;
  Tft.paintScreenBlack();
  //Send date to Screen through Bridge;
  setInit.begin("date");
  setInit.run();
   // Print command output on the SerialUSB.
  // A process output can be read with the stream methods
  while (setInit.available() > 0) {
    unsigned char c = setInit.read();
    Tft.drawChar(c,x,y,1,WHITE);
    //Serial.print(c);
    if(x == 180)
    {
      x = 0;
      y = y + 10;
    } else {
      x = x + 5;
    }
  }
    
   Tft.drawString("Incoming",50,40,2,WHITE);
   Tft.drawString("Message from",30,60,2,WHITE);
   Tft.drawString("Vader",50,100,4,RED);


   delay(10);


   strncpy(vaderMsg.msgStr, mse6MsgStr,mse6Msg_SZ) ;
   vaderMsg.scrnloc.msgSz= mse6Msg_SZ;
   vaderMsg.scrnloc.msgStartX = 20;
   vaderMsg.scrnloc.msgStartY = 179;
   vaderMsg.scrnloc.msgIncX = 15;
   vaderMsg.scrnloc.msgIncY = 40;
   vaderMsg.scrnloc.msgFont = 2;
   vaderMsg.scrnloc.msgColor = GREEN;


}

 

After initializing the Bridge, Mailbox and serial port, all that is in my loop is a check to see if there are any messages in the Mailbox. "Mailbox.messageAvailable()"

void setup()
{


  Bridge.begin();
  Mailbox.begin();
  Serial.begin(9600);


  memset(vaderMsg.msgStr, 0, vaderMsg.scrnloc.msgSz);
  Tft.init();  //init TFT library


  initMSE6();


  initSoftSerial();


}


void loop()
{




  while(1)
  {

    if(Mailbox.messageAvailable()) {
      // read all the messages present in the queue


      readMailboxMsg();


    }
  } 
}

 

If there is a message available, a readMailboxMsg function is called which reads in the message from Mailbox,readMessage and then checks the message to see if it is a message to display "vader#" or to pass to the UNO for processing "direct#" .

void readMailboxMsg(void)
{
   String message;
   String vaderStr = "vader#";
   String directStr = "direct#";
   char   aStr[6];
   int msgSz = 0;


    // read all the messages present in the queue
    while(Mailbox.messageAvailable()) {
      Mailbox.readMessage(message);
      if(message.startsWith(vaderStr))
      {
        drawNewMessage(message);
        delay(5000);
        initMSE6();
      }else if(message.startsWith(directStr)) {
        message.remove(0, 7);
        msgSz = (message.length() -1);
        message.toCharArray(aStr, message.length());
        SerialUSB.println(aStr);
        sendControlMsg(aStr, msgSz);
      }
    }
}

 

If the message is a "vader#' message, it is sent to drawNewMessage to be sent to the TFT Display. Here the "vader#" heading is removed and the message is converted from a String type to a char array to be added to the vaderMsg structure. Then the struct is sent to printChars which prints the message a character at a time using the TFT library.

void drawNewMessage(String newMessage)
{
    String message;
    message = newMessage;


    // Remove the 'vader#' preface from message
    message.remove(0, 6);
    if(message != NULL)
    {  
      //SerialUSB.println(message.length());
      //SerialUSB.println(message);
      initHeading();
      // clear existing message by writing black
      vaderMsg.scrnloc.msgColor = BLACK;        
      printChars(&vaderMsg);
      memset(vaderMsg.msgStr, 0, vaderMsg.scrnloc.msgSz);
      vaderMsg.scrnloc.msgSz = (message.length() -1);
      message.toCharArray(vaderMsg.msgStr, message.length());
      vaderMsg.scrnloc.msgColor = GREEN;
      printChars(&vaderMsg);
      //SerialUSB.println(vaderMsg.msgStr);
    }
    
}

 

If the inbound message is a Direction message "#direct", the message is passed to the sendControlMsg function to be sent to the UNO via the software serial port.

void sendControlMsg(char *newDirection, int msgSize)
{
    //SerialUSB.println("Waiting before checking the Mailbox again");
    mySerial.write(newDirection,msgSize); //Write the serial data
    delay(1000);
}

 

 

 

UNO Code

 

As i shown in a previous post, the UNO Arduino code uses FreeRTOS Tasks for control of the bot.  Here I use Tasks for the Ultra Sonic sensor, Serial Read, ESC (Electronic Speed Control for Motor control), and Steering servo control.

// the setup function runs once when you press reset or power the board
void setup() {


   //pinMode(servoPin, OUTPUT);
   Serial.begin(9600);
   mShield.motorShieldInit();


   // Attach the servo to the correct pin and set the pulse range
   esc.attach(escPin,  minPulseRate, maxPulseRate);
   
   // Initialize ESC
   intitESC();


   // Start the software serial port 
   mySerial.begin(115200);


   // Initialize servos 
   myservo.attach(servoPin);  // attaches the servo on pin 4 to the servo object
   steerservo.attach(steerPin);
  // Task declarations.


  xTaskCreate(
    TaskPingRead
    ,  (const portCHAR *) "PingRead"
    ,  128 // This stack size can be checked & adjusted by reading Highwater
    ,  NULL
    ,  1 // priority
    ,  NULL );


  // Create task to handle software serial read
  xTaskCreate(
    TaskSerialRead
    ,  (const portCHAR *) "SerialRead"
    ,  128 // This stack size can be checked & adjusted by reading Highwater
    ,  NULL
    ,  1 // priority
    ,  NULL );


  // Create ESC task
  xTaskCreate(
    TaskESC
    ,  (const portCHAR *) "ESCControl"
    ,  128 // This stack size can be checked & adjusted by reading Highwater
    ,  NULL
    ,  1 // priority
    ,  NULL );


  // Create steering Servo task
  xTaskCreate(
    TaskSteering
    ,  (const portCHAR *) "SteeringControl"
    ,  128 // This stack size can be checked & adjusted by reading Highwater
    ,  NULL
    ,  1 // priority
    ,  NULL );


  // Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started.
}

 

To initialize the ESC, after some trial and error, I found if send the ESC a value of 180 to set the MAX level, then 70 for a center value , a 0 to set the ESC then 70 again to get the ESC to function in Forward and Reverse mode. This also requires removing the Mode jumper on the ESC.

Once the "direct#" message is passed to the UNO through the SoftwareSerial interface,  task TaskSerialRead reads in the string via readBytes from the serial object and places it int a char array.   Here the string is checked to determine the direction that was sent then sets the DirectState and RightLeftState states.

 

TaskSerialRead

/**
 * @brief Software Serial Read Task
 */
void TaskSerialRead( void *pvParameters )
{
  (void) pvParameters;
  // Loop for Ping sensor
  for(;;)
  {
      if (mySerial.available() > 0)
      {
        //Serial.readBytes(mystr,5); //Read the serial data and store in var
        mySerial.readBytes(mystr,5); //Read the serial data and store in var
        if (strncmp(mystr,"FWD", 3) == 0)
        {
            Serial.print("Lets go: ");
            Serial.println("Forward");
            DirectState = STATE_FWD;
            RightLeftState = STATE_CENTER;
        } else if (strncmp(mystr,"BACK", 4) == 0) {
            Serial.print("Lets go: ");
            Serial.println("Back");
            DirectState = STATE_BACK;
        } else if (strncmp(mystr,"LEFT", 4) == 0) {
            Serial.print("Lets go: ");
            Serial.println("Left");
            RightLeftState = STATE_LEFT;
        } else if (strncmp(mystr,"RIGHT", 5) == 0) {
            Serial.print("Lets go: ");
            Serial.println("Right");
            RightLeftState = STATE_RIGHT;
        } else if (strncmp(mystr,"STOP", 4) == 0) {
            Serial.print("Lets: ");
            Serial.println("Stop");
            DirectState = STATE_STOP;
            RightLeftState = STATE_CENTER;
        } else {
          Serial.println("Oops something is not right!");
          Serial.println(mystr);
          DirectState = STATE_STOP;
          RightLeftState = STATE_CENTER;
          esc.write(ESC_STOP);
          delay(throttleChangeDelay);
          
        }
    }
      delay(1000);
  }
  vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
}

 

 

TaskESC keys off of the the OBJECT_STATE that is set by the Ping sensor if an object is detected as well as STATE_FWD and STATE_BACK to control whether the motor moves forward, backwards or stops.

/**
 * @brief ESC Motor control Task
 */
void TaskESC( void *pvParameters)
{
  (void) pvParameters;
  int currentESCVal;
  // Loop for Ping sensor
  for(;;)
  {
    if(OBJECT_STATE)
    {
        avoidObject();
    } else {
      switch (DirectState)
      {
        case STATE_FWD:
        {
          currentESCVal = readESC();
          if(currentESCVal != ESC_FWD)
          {
           esc.write(ESC_FWD);
           delay(throttleChangeDelay);
          }
        }
          break;
        case STATE_BACK:
        {
          currentESCVal = readESC();
          Serial.print("Current Val: ");
          Serial.println(currentESCVal);
          if(currentESCVal >= ESC_STOP)
          {
             esc.write(ESC_STOP);
             delay(throttleChangeDelay);
             esc.write(ESC_BACK);
             delay(throttleChangeDelay);
             esc.write(ESC_STOP);
             delay(throttleChangeDelay);
             esc.write(ESC_BACK);
             delay(throttleChangeDelay);
          }else if (currentESCVal != ESC_BACK) {
             esc.write(ESC_BACK);
             delay(throttleChangeDelay);
          } else {
            Serial.println("Still going Back");
          }
        }
          break;
        case STATE_STOP:
        {
          esc.write(ESC_STOP);
          delay(throttleChangeDelay);
        }
          break;
        default:
          esc.write(ESC_STOP);
          delay(throttleChangeDelay);
          break;
      } // end of switch
    } // end OBJECT_STATE check
  }
  vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
}

 

TaskSteering checks for the RightLeftState to determine the direction of the Steering servo.

/**
 * @brief Steering Servo Task
 */
void TaskSteering( void *pvParameters)
{  
  (void) pvParameters;
  int currentESCVal;
  // Loop for Ping sensor
  for(;;)
  {
    switch (RightLeftState)
    {
      case STATE_RIGHT:
      {
        steerservo.write(120);
        Serial.println("Steer Right");
        //delay(100);
      }
        break;
      case STATE_LEFT:
      {
        steerservo.write(60);
        Serial.println("Steer Left");
        //delay(100);        
      }
        break;
      case STATE_CENTER:
      {
        steerservo.write(90);
        Serial.println("Steer Center");
        //delay(000);        
      }
        break;
      default:
        //esc.write(ESC_FWD);
        //delay(throttleChangeDelay);
        break;
    } // end of switch
     delay(1000);
  }
  vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
}

 

The TaskPingRead Task checks the value from the Ping Sensor (Ultrasonic Sensor) and will set the OBJECT_STATE to true if there is an object detected.

/**
 * @brief Read Ping Task
 */
void TaskPingRead(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
  long duration, inches, cm;
  const int pingPin = 7;
   // initialize serial communication at 9600 bits per second:
  //Serial.begin(9600);


  // Loop for Ping sensor
  for(;;)
  {
    // Code borrowed from Parallax
    // http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
    // The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
    // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
    pinMode(pingPin, OUTPUT);
    digitalWrite(pingPin, LOW);
    delayMicroseconds(2);
    digitalWrite(pingPin, HIGH);
    delayMicroseconds(5);
    digitalWrite(pingPin, LOW);
  
    // The same pin is used to read the signal from the PING))): a HIGH pulse
    // whose duration is the time (in microseconds) from the sending of the ping
    // to the reception of its echo off of an object.
    pinMode(pingPin, INPUT);
    duration = pulseIn(pingPin, HIGH);
  
    // convert the time into a distance
    inches = microsecondsToInches(duration);
    cm = microsecondsToCentimeters(duration);
  
    if(cm < 20)
    {
      //xSemaphoreTake(xLEDSem, 0);
      OBJECT_STATE = true;
      Serial.print(inches);
      Serial.print("in, ");
      Serial.print(cm);
      Serial.print("cm");
      Serial.println();
    }
    else
    {
      //xSemaphoreGive(xLEDSem);
      OBJECT_STATE = false;
    }
    delay(100);
    vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
  } 
}

 

All of this is set to control the Mouse Droid from a webpage. I intend to continue to build on this and work toward having an autonomous Mouse Droid.