Communicate with Your Arduino Through Android
Want to send text from your Android phone to your Arduino? Here's how!
Requirements
- Android Phone which supports USB Host Mode (i.e OTG Support) - Most devices running Android 3.1+ support this. Check if your phone does using the USB Host Diagnostics App from the Play Store.
- Arduino - Any version will do. I will be using an Uno R3 .
- Arduino USB Cable
- USB OTG Cable - You will need this to connect the USB cable of the Arduino to the micro-USB port of the Smartphone. It costs about $5 on Amazon.
- Android Studio - You will need to have this installed and setup. It is pretty easy to do that. Android Studio makes app development easier with its predictions and code generation. It's one of the best IDEs out there. You could also follow this article to set up Android Studio on your computer.
Main Components of an Android App
There are 3 major files in an Android app:- MainActivity.java
This is where the Java code goes. It controls the way the app will function.
- activity_main.xml
This contains the layout of the app, i.e. the components or the widget-like buttons, TextViews etc. - AndroidManifest.xml
This is where you define when the app must start, which permissions it needs, and which hardware it needs to access.
An activity can be described as a screen where the user interacts with the phone. Activities contain widgets like buttons, text fields, images, etc., which help in the transfer of information. This tutorial will use one activity, the Main Activity, which will take the user's input to send to the Arduino and also display the received text.
The Layout
We will be using the same layout for both the USB App and the Bluetooth App. It's a simple one with the minimum required widgets to test the connection between the devices.
As you can see, it has an EditText widget for getting input from the user, buttons to start the connection, transmit the data, end the connection and clear the TextView. The recieved data is displayed in the TextView (the empty part below the buttons).
Here's a part of the XML. Since the code for buttons is similar I've left them out here. Get the complete code at the end of this article.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editText"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Begin"
android:id="@+id/buttonStart"
android:layout_below="@+id/editText"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:onClick="onClickStart"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:layout_below="@+id/buttonSend"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignRight="@+id/editText"
android:layout_alignEnd="@+id/editText"
android:layout_alignParentBottom="true" />
</RelativeLayout>
I have used a RelativeLayout here, meaning that every widget is arranged with respect to the widgets around it. The layout can be easily recreated using the Design Tab where you can drag-and-drop the widgets wherever you want. When a button is clicked, we will have to describe what needs to happen. For this, an OnClick method is used. Specify the name of the method in the XML of the button. To do this add the line:
android:onClick="onClickMethod"
Now hover on this line and an alert will pop up on the left like this:
Click Create 'OnClick...'. This will automatically inject code for the onClick method in MainActivity.java. You will have to do this for each button.
The Usb Serial Library
Setting up a serial connection in Android is quite a hassle because it will require you to manually configure a lot of stuff, so I had been looking at some libraries which do all this automatically. I tested out a few of them and finally settled on the UsbSerial library by Github user felHR85. Among all the relevant libraries I had found, this was the only one which is still being updated. It is pretty easy to set up and use. To add this library to your project, download the latest JAR file from Github. Move it to the 'libs' folder in your project's directory. Then, in the file explorer of Android Studio, right-click the JAR and select "Add as Library". That's it!The Program Flow
Opening a Connection
First off, let us define the onClick method for the Begin button. When clicked, it should search for all connected devices and then check if the vendor ID of the Arduino matches that of a connected device. If found, permission must be requested from the user. Each USB slave device has a vendor and product ID which can be used to identify what drivers should be used for it. The vendor ID for any Arduino is 0x2341 or 9025. public void onClickStart(View view) {
HashMap usbDevices = usbManager.getDeviceList();
if (!usbDevices.isEmpty()) {
boolean keep = true;
for (Map.Entry entry : usbDevices.entrySet()) {
device = entry.getValue();
int deviceVID = device.getVendorId();
if (deviceVID == 0x2341)//Arduino Vendor ID
{
PendingIntent pi = PendingIntent.getBroadcast(this, 0,
new Intent(ACTION_USB_PERMISSION), 0);
usbManager.requestPermission(device, pi);
keep = false;
} else {
connection = null;
device = null;
}
if (!keep)
break;
}
}
}
Now let us define the BroadcastReceiver to receive the broadcast to ask for user permission and also to start the connection automatically when a device is connected and to close the connection when it is disconnected.
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { //Broadcast Receiver to automatically start and stop the Serial connection.
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_USB_PERMISSION)) {
boolean granted =
intent.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED);
if (granted) {
connection = usbManager.openDevice(device);
serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection);
if (serialPort != null) {
if (serialPort.open()) { //Set Serial Connection Parameters.
setUiEnabled(true); //Enable Buttons in UI
serialPort.setBaudRate(9600);
serialPort.setDataBits(UsbSerialInterface.DATA_BITS_8);
serialPort.setStopBits(UsbSerialInterface.STOP_BITS_1);
serialPort.setParity(UsbSerialInterface.PARITY_NONE);
serialPort.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF);
serialPort.read(mCallback); //
tvAppend(textView,"Serial Connection Opened!\n");
} else {
Log.d("SERIAL", "PORT NOT OPEN");
}
} else {
Log.d("SERIAL", "PORT IS NULL");
}
} else {
Log.d("SERIAL", "PERM NOT GRANTED");
}
} else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
onClickStart(startButton);
} else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
onClickStop(stopButton);
}
};
};
If the first IF condition is satisfied, and if the user has granted permission, initiate a connection for the device whose vendor ID matched our required vendor ID. Also, if a broadcast for a device attach or detach is received, manually call the onClick methods for the Start and Stop buttons. A SerialPort is defined using the device as the connection as the arguments. If this is successful, open the SerialPort and set the parameters accordingly. For an Uno, the default parameters are 8 Data bits, 1 Stop bit, no parity bit and Flow Control is Off. The baud rate can be 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, or 115200, but let us go with the standard 9600.
Receiving data from the Device
In the code fragment above, notice the line that says serialPort.read(mCallback). Here a reference of a Callback is passed to the read function so that it will automatically trigger when any incoming data is detected. UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() {
//Defining a Callback which triggers whenever data is read.
@Override
public void onReceivedData(byte[] arg0) {
String data = null;
try {
data = new String(arg0, "UTF-8");
data.concat("/n");
tvAppend(textView, data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
};
The data received will be in the form of raw bytes. We will have to re-encode it into a readable format like UTF-8. Then it is appended to the TextView using a custom method named tvAppend(). This is done because any change to the UI can only happen on the UI thread. Since this Callback will be running as a background thread, it can't affect the UI directly.
private void tvAppend(TextView tv, CharSequence text) { final TextView ftv = tv; final CharSequence ftext = text; runOnUiThread(new Runnable() { @Override public void run() { ftv.append(ftext); } }); }
Sending Data to the Device
Sending data is relatively easy when compared to reading data from the device. It is a simple function call with bytes of data which needs to be sent as the argument. This will be defined in the OnClick method of the Send Button.serialPort.write(string.getBytes());
Closing the connection
To close the connection, just close the SerialPort.serialPort.close();
The Application Manifest
In the manifest, state what extra permissions the app might require. The only one needed is the permission to make the phone a USB host. Add the following to the manifest:<uses-feature android:name="android.hardware.usb.host" />
The
app can be made to start automatically by adding an IntentFilter to the
MainActivity. This IntentFilter will be triggered when any new device
is attached. The kind of device can be explicitly specified by providing
the vendor ID and/or product ID in an XML file. <?xml version="1.0" encoding="utf-8"?>
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
Notice the line "android:resource="@xml/device_filter". This tells the compiler that it can find the device properties in a file named device_filter in src/main/res/xml, so make a folder named "xml" in src/main/res and put the following in it:
<resources>
<usb-device vendor-id="9025" />
<!-- Vendor ID of Arduino -->
</resources>
Testing the App
Build and run the app on your smartphone. Now fire up the Arduino IDE and set up the Arduino to simply echo whatever it receives on the serial port. Here's a very simple code to do that. void setup()
{
Serial.begin(9600);
}
void loop()
{
char c;
if(Serial.available())
{
c = Serial.read();
Serial.print(c);
}
}
Now connect the Arduino to the microUSB port using the OTG Cable. The app must auto-start. Try sending some text and the same data will be echoed back!