![]() | 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.

