Sunday, April 17, 2016

Phone controlled ArduinoBot - USB I/O

Note - as this is a work in progress, all the github code is going to update as I write the blog. For ease of following, I will tag each revision when I publish a blog post, so it is still consistent with the blog post. This will also give you a glimpse into the evolution of the design, which can be helpful to get a sense of the thought process if you're just getting started developing software.

All the code for this blog post can be found at https://github.com/nirsoffer/ArduinoBot/tree/v1.0

So for our robot, we will use a phone as a USB Host, as opposed to a USB Device; in USB terminology a host is something that sends the commands to the devices; the Arduino is a device. The PC you hook up the Arduino to is a host.

We will make the phone, essentially, act like a PC; it will control the Arduino through the serial interface.

This is a demo of what this stage ends up looking like - assuming you're already done assembling your robot, and, well, it works.


It doesn't look like much now - but believe me - this is the foundation of something wonderful.

Compile your Arduino sketch and upload it to the board; debug your robot by hooking it up to a PC and using the "g", "r", and "l" characters through the serial monitor to see if it works. Remember to switch on the external power, otherwise you'd be using your PC's USB port to power the motors, and that might not be a good thing and could potentially, if you have a shoddily made PC, fry your motherboard. I take no responsibility. 

The next step is to make the phone a USB Host and talk to the Arduino device as a serial device:
To do so we will use UsbSerial - which can be found at https://github.com/felHR85/UsbSerial - UsbSerial in turn relies on the Android USBHost class and functionality (see documentation here: http://developer.android.com/intl/es/guide/topics/connectivity/usb/host.html)

You will also need what's called a "USB On-the-Go Adapter", also commonly named "USB OTG" here's one that I found worked out well in practice. Note that the USB OTG standard typically does not allow powering the host device with a USB OTG cable. I've heard mixed success with people using cables such as these - I personally bought one and it did not power my S2 - but your success with other devices might vary.

Let's start with the first version of both our Arduino code and the Android code to control it.

Arduino Side:

The first revision we will have is kind of dumb. It will use Serial.available() and Serial.read() to identify the opcode sent to it. At first it will only have a limited subset of the protocol I have in mind: to-wit - "f" for going forward a preset amount milliseconds, "r" for right, and "l" for left.

This is to ensure you have the USB to serial interface done right. Otherwise debugging will be hell later.

Android Side (also known as the "ArduinoRobot" class):
The ArduinoRobot class is essentially a wrapper for the UsbService class (from here https://github.com/felHR85/UsbSerial/blob/master/example/src/main/java/com/felhr/serialportexample/ ) ; I've had to modify the class slightly to look for Arduino devices specifically rather than any device that comes along, as well as work around some Android bugs and some design issues in the UsbService itself (I will send those suggestions to the author, soon).

The ArduinoRobot class is meant to abstract the serial device and the robot commands away from the main activity. Due to the quirks of the Android platform, it's designed as a Singleton that owns a Context static variable. I dislike the design intensely - but the Android platform requires that a Context be available if I want to bind to any service or register any broadcasters; the only way to do it would be either through extending a Service class (which may yet happen in later iterations) or pass a Context object to a Singleton. I went with the latter. Android savvy programmers - if you have any better ideas, let me know in the comments.

The key methods currently defined in the ArduinoRobot class are:

engage():  begin talking to the serial interface, register receivers, and do all that good stuff. Meant to be called from the main activity's onCreate(). Due to an Android bug in my old Android phone I also have to use onNewIntent() and re-engage the robot there. That bug is pretty gnarly - essentially the USB Attached intents are never received by the receiver, so I had to define the intent filter on the manifest, look for that specific intent on onNewIntent, and rebroadcast a different intent (with the same extra) so that UsbService knows that something actually happened. Assuming you are using Android versions slightly newer than my 4.1.2 then it probably wouldn't happen, but it did to me.

disengage(): unregister receivers; unbind from service. Meant to be called from onDestroy()

Another important class member to note is the interface RobotCallback which currently has only 3 functions to implement: handleData(), onServiceDisconnected(), and onServiceConnected() - the final two are currently not even used but are there for later. The handleData() function is meant for when new data is received.

Later revisions will see this structure changed significantly: remember we are only at the stage of making sure the USB bridge works, so a lot of those hooks were inserted in for debugging purposes. This is code in evolution, people, not finished product.

The callback and context for the Singleton are set with setCallback() and setContext(). No, I'm not happy with it. Yes, this is a hack. We'll refactor this once we're happy with functionality.  Honest. I swear.

There are of course a few more members - for now - mostly those that instruct the robot to go, turn left, and turn right.


The other things still there are quite simply there to test stuff: the buttons are there to test the functionality. Press "forward" and the robot should go forward, etc. See video above for a quick demo.

A few important notes: The application manifest specifically declares an intent filter:
   <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"  
         android:resource="@xml/device_filter" />  

that would signal the application when it is time to act; in addition, the code in ArduinoRobot that defines the device that is being sought after:
    int[] paramarray = {0x2341, 67}; // my arduino's pid, vid  
     bindingIntent.putExtra(UsbService.BIND_PARAMETERS_EXTRA, paramarray);  

is defined in startService. You should find out the VID and PID of your own brand of Arduino, and modify appropriately, as it may be different than mine.









No comments:

Post a Comment