GRAndWalkthroughUSRP

= Building a GNU Radio for Android App with a USRP =

In this walk-through, we will add a connection to a USRP radio over USB. This will require a USRP B2xx (B200, B210, or B200mini) as these are the only ones fully tested. We will also need an OTG (USB On-the-Go) cable, which plugs in to the USB port of the Android device and the standard USB cable plugs into the USRP and the other end of the OTG cable.



See the main Android page for a list of devices that have been tested and work with USRPs.

I suspect that network-connected USRPs will work pretty easily with Android. The biggest challenge with the B2xx series of USRPs is that they must have their firmware and FPGA images programmed in every time they are powered on. A network-connected USRP like an N200/N210 or X310 is pre-programmed and does not require the same workarounds for permissions issues that we face with USB devices. The only permission for a networked USRP is the "android.permission.INTERNET", which we have been using, anyways, for access to ControlPort.

RTL-SDR devices can also work with GNU Radio for Android apps. Like the USRP B2xx devices, these need the USB permissions workaround, but they do not need to be programmed. We build the RTL-SDR library as well as gr-osmosdr support for them into our GR for Android dependency bundle. I suspect, too, that adding support for other USB-based devices like the BladeRF, HackRF, Airspy, etc. will also be fairly straight-forward.

You can find the full example GrTemplateUSRP project on Github.

Getting Started
We will build a new application here, much like we did in the other two walk-throughs. To start with, follow the procedure outlined in GRAndWalkthroughCP to create a new app project space, but this time, call it GrTemplateUSRP. This sets us up with a project that has ControlPort support and a basic flowgraph in fg.cpp. Before you go any farther, make sure the application builds and runs on your device just like GrTemplateCP did.

Second, clone and build the GrHardwareService application. Install this app onto your Android device. If you don't have a USRP plugged in when this first starts, it will just crash (not particularly nice, I know, but we'll get there). With this loaded onto your machine, it installs the USRP firmware and FPGA images for the B2xx devices and starts listening for USB connections. And USB device that's plugged in where the vendor and produce IDs match any of the known-working USRPs, this app is launched and starts programming the device. It will have to ask you for permission to use the device twice, once to program the firmware and then again to program the FPGA image. Once that's done, the hardware is programmed and ready to go.

If the program crashes on you when you first plug in a USRP, it's likely a permissions issue. I've noticed that on Marshmallow (Android version 6), I had to go into the App section under Settings, find the GrHardwareService app, go in there to Permissions, and enable the Storage permission. This is a new "feature" in Marshmallow, and there are standard ways to handle it inside of the app. I'm not worrying about that here because I don't want to confuse the code any more than we have to by having version-specific checks.

Adding Support for USB Devices
We have to play some games with Android to get information in the Java app and pass this to the C++ flowgraph. Android doesn't like to hand out USB permissions to native apps, so we need to get the file descriptor (fd) and USB filesystem path (usrpfs_path) in Java and pass that through GNU Radio to libUHD and eventually libUSB to allow us access.

Here is another area where I am hoping we can eventually figure this out better and simplify the procedure. Also, I do not take great care here to add much fault protection, so when crashes occur, they often just shut down the app with an error. I am sure anyone competent with Android can do a better job than I can here.

Next, we need to add some support for getting USB permissions and finding the fd and usbfs_path information we require to connect to the USRP.

First, add these variables to your MainActivity class:

We need to make sure we have the USB permissions set before we can launch the radio. So instead of doing everything in OnCreate like we've been doing, we'll separate into a few different functions. First, in OnCreate, we need to get the USB device, set up a broadcast receiver, and then ask the user for permission to use the device. When the user grants permission, the broadcast receiver is launched. The broadcast receiver finishes up getting access to the USB device, and now we can act on the USB device to get the information out of it we need. This is what our OnCreate function looks like now:

Notice that we're still creating the Seek Bar here as the GUI setup.

We are also calling a new function here called getAllowedDevices. This function parses our XML file of devices that we want to be able to talk to. So the broadcast receiver is only fired if one of the devices listed in this file, based on the vendor and product ID, is seen.

We need to make our device_filter.xml file. In the res directory, add a new directory called xml. In the xml directory, create a new file called device_filter.xml. This file looks like:

       We may have to add more devices here for other hardware support in the future.

And this is what getAllowedDevices looks like:

The broadcast receiver (See the main Android USB Page for more in this) that we registered, mUsbReceiver, looks like this:

If the device registers and looks good, we assign it to our class object mUsbDevice and launch the next stage of starting up our application by calling SetupUSB.

This function gets information from the USB device, prints it to logcat, and then calls StartRadio. We use a parsing function called properDeviceName to extract the USB device path from the device itself:

This always seems to be "/dev/bus/usb", but we get it here each time in case something else changes.

Finally, we call StartRadio, which is really the rest of the OnCreate stuff we have done previously, but now we're waiting until the USB device is properly assigned.

When we build and run this on the device with a USRP plugged in, a dialog box pops up asking if we want to grant permission to the USB device. Click OK, but do not click the check box. We don't want this app to be associated by default with USRPs since we want that to be done by the GrHardwareService app. After clicking OK, we should see the log messages in logcat that give us the fd, usbfs_path, and the vid and pid of the discovered device. This makes sure that we are properly talking to the USRP in Java. We'll then pass this information to the flowgraph to create a UHD USRP source block. You should see something like this in logcat:

04-22 11:30:01.624 14537 14537 D GrTemplateUSRP: Didn't get a device; finding it now. 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: Selected Device: UsbDevice[mName=/dev/bus/usb/002/003,mVendorId=9472,mProductId=34,mClass=255,mSubclass=0,mProtocol=0,mManufacturerName=Ettus Research LLC,mProductName=USRP B200,mVersion=2.16,mSerialNumber=30BFC51,mConfigurations=[ 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=1,mInterfaces=[ 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbInterface[mId=0,mAlternateSetting=0,mName=USRP B200,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[] 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbInterface[mId=1,mAlternateSetting=0,mName=USRP B200,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[ 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=512,mInterval=0]] 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbInterface[mId=2,mAlternateSetting=0,mName=USRP B200,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[ 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbEndpoint[mAddress=134,mAttributes=2,mMaxPacketSize=512,mInterval=0]] 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbInterface[mId=3,mAlternateSetting=0,mName=USRP B200,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[ 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbEndpoint[mAddress=4,mAttributes=2,mMaxPacketSize=512,mInterval=0]] 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbInterface[mId=4,mAlternateSetting=0,mName=USRP B200,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[ 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: UsbEndpoint[mAddress=136,mAttributes=2,mMaxPacketSize=512,mInterval=0]]]] 04-22 11:30:01.626 14537 14537 D GrTemplateUSRP: Called Request Permission 04-22 11:30:01.627 14537 14537 D GrTemplateUSRP: Found fd: 22 usbfs_path: /dev/bus/usb 04-22 11:30:01.627 14537 14537 D GrTemplateUSRP: Found vid: 9472 pid: 34 Since we're launching our flowgraph from GRAndWalkthroughCP, we'll also see the flowgraph start messages, though we still haven't done anything with the USRP, yet.

Creating a UHD USRP Source
Now we have the permissions and all of the information we need to talk to the USRP. Now its time to change the flowgraph to use a UHD USRP Source block as the source instead of the sig_source_f.

First, we need to pass the fd and usrpfs_path values to the flowgraph. Change the function signature in fg.cpp to this:

And in MainActivity.java where we declare the JNI functions, we need to add the two arguments:

And in SetupRadio, make sure to pass this information as "FgInit(fd, usbfs_path)".

We then extract the information and format a device argument string for the usrp_source block:

The src is now a complex source. So what I'll do is create a simple flowgraph that looks like:

The full flowgraph FgInit looks like this

This flowgraph does nothing particularly useful with the USRP, but we should be able to listen to static. We are not event setting up the sample rate, frequency, or gain values, so the incoming samples have no connection to anything real.

For more complex usage of a USRP app, take a look at the GrRxFM, which uses a USRP to receive and demodulate broadcast FM signals. It sets up sliders to adjust the frequency and gain as well as handles sample rates and rate changes within the flowgraph.