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! | Project14 Home | |
Monthly Themes | ||
Monthly Theme Poll |
Star Wars MSE-6 (Mouse Droid) INDEX:
|
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.