RTL2832U and rtl-sdr on an STM32 microcontroller, Part 1

This is a series dedicated to describing the development of software-defined-radio applications on a microcontroller, utilizing the ubiquitous RTL2832-based DVB-T dongle and librtlsdr. See justification and all of the source code for this project on GitHub.

Screen Shot 2016-09-13 at 11.35.54 PM.png

In this entry, I explain how the Realtek RTL2832U DVT-B dongle is integrated with the microcontroller processor target, culminating in a successful enumeration of the device and demonstration of a few radio features. These tasks are accomplished via the integration of various codebases and porting a host-based USB library (libusb) to the new processor target. To begin, I create a bare-metal firmware image consisting of the the minimum components necessary to accomplish this task: a board support library to control the dev board hardware, the librtlsdr library, minimally-ported libusb library and some application glue logic. In doing so, I’ll use a strategy of building-up from known baselines; Particularly useful for embedded projects where even non-software related issues can be a factor.

The development environment consists of the test rig hardware, build toolchain and various software and hardware debug tools. The test rig is comprised of an ST STM32F7-Discovery processor development board, the Realtek radio dongle and a VHF antenna. All software is cross-compiled with the ARM GNU toolchain, using a series of makefiles to automate and organize the process, and the target is manipulated using OpenOCD as a GDB server. Many other tools and techniques are employed throughout this project, such as static analysis, trace tools, ARM semihosting, symbolic debugging, and a USB traffic analyzer- each of which I will attempt to further describe along the way.

Enumerating a high-speed USB device with the target processor operating as host #

In this configuration, the microcontroller (MCU) acts as the USB host, servicing the radio dongle, which means the MCU is scheduling all of the traffic and then notifying the application software upon completion of certain events (ie, completion of data transfer). The very first thing we want to prove, is simply that the MCU can transfer any amount of data successfully, validating our hardware, physical connections and some of the USB driver code provided by the processor manufacturer. The data to verify is the USB enumeration process (which basically is how the host becomes aware of the attached device and figures out what its capabilities are). This transfer always occurs on the USB endpoint 0, using control transfer types. These are very simple frame transfers that exchange identification data such as the vendor identification and product identification codes (VID/PID), as well as, more verbose strings describing the model name, (ie “Realtek RTL2832U DVT-B Adapter”, or something like that, hardcoded in the device).

Before we fire up our demonstration application, it would be best to know what we’re looking for, and have a way to verify what is actually happening ‘over the wire’, in case things don’t go as planned. For this, we turn the to Total Phase Beagle USB Protocol Analyzer. The Beagle is placed between the target host and the RTL dongle, with a third interface to a workstation for analysis of the real-time traffic. If you are embarking on any serious USB development work, I highly recommend this tool! The Beagle verifies that the device is receiving power via the VBUS from the host and also captures all of the bus traffic to ensure the transactions occur correctly, as depicted in the diagram below (for all of the gory detail, check out the USB spec, or USB In a NutShell for more in-depth, yet very readable, descriptions of how USB packets are defined).

Screen Shot 2016-09-13 at 9.06.03 PM.png

After capturing several initialization sessions, it should become immediately clear which parts, if any, are failing. Below are the exact steps I have taken to verify a successful device enumeration on the STM32 host.

Step 1: Setup a development environment #

Step 2: Bring in librtlsdr and get it to build for the embedded target #

Step 3: Implement libusb routines, as necessary #

For the control transfer implementation, I used in the libusb function arguments to modify the USBH_HandleTypeDef structure, fed to the USBH_CtlReq() function.

int libusb_control_transfer(...) {

  hUSBHost.Control.setup.b.bmRequestType = request_type;
  hUSBHost.Control.setup.b.bRequest      = bRequest;
  hUSBHost.Control.setup.b.wValue.w      = wValue;
  hUSBHost.Control.setup.b.wIndex.w      = wIndex;
  hUSBHost.Control.setup.b.wLength.w     = wLength;

  do {
    status = USBH_CtlReq(&hUSBHost, data, wLength);
  } while (status == 1);

...

The libusb bulk transfer function is redirected to the USBH_BulkReceiveData() ST middleware routine.

int libusb_bulk_transfer(...) {

      USBH_BulkReceiveData(&hUSBHost,
                            data,    // rx buffer
                            length,  // data count to rx
                            pipe); 
...

Step 4: Write a simple application to prove the concept #

The demo firmware image is extremely simple. After all normal initialization steps have been completed, simply create the inbound data pipe for the control frames (on inbound endpoint 0x81), then attempt to open the control pipe.

InPipe = USBH_AllocPipe(&hUSBHost, USB_PIPE_NUMBER);
USBH_StatusTypeDef status = USBH_OpenPipe(&hUSBHost,
            InPipe,
            USB_PIPE_NUMBER,
            hUSBHost.device.address,
            hUSBHost.device.speed,
            USB_EP_TYPE_BULK,
            USBH_MAX_DATA_BUFFER);

Success #

As seen in the terminal output below, the application is able to retrieve some descriptor strings that seem very promising. The manufacturer and full product identification strings are evidence of successful data transfer via the USB control pipe.

Screen Shot 2016-09-09 at 2.42.08 PM.png

Now that the device enumerates and is communicating successfully with the MCU, perhaps some of the application functions will work? The following dongle setup procedure is taken from some of the example applications in the librtlsdr codebase, with librtlsdr.c modified gently:

    // open device
    rtlsdr_open(&dev, 0);

    // Set the sample rate
    verbose_set_sample_rate(dev, adc_samp_rate);

    //  Set the frequency
    verbose_set_frequency(dev, tuner_frequency);

    // set auto gain
    verbose_auto_gain(dev);

    // set ppm error
    verbose_ppm_set(dev, tuner_ppm_error);

    // Reset endpoint
    verbose_reset_buffer(dev);

Finally, read from device:

   rtlsdr_read_sync(dev, dest_buffer, usb_pkt_bytes, dev_id);

After each execution of that last line, I am able to examine the the dest_buffer memory location and verify that data is changing. This hints that the bulk data transfer operation is also possibly working. Now, hopefully there is an FM radio station in there somewhere…but I’ll save demodulation for another post.

Next steps #

 
155
Kudos
 
155
Kudos

Now read this

For a More Portable RTL2832 USB Dongle

I’m super interested in the rtl-sdr project because, as a coincidence, a large portion of the the rtl-sdr project community seems to be attracted to it’s use as an aviation communications interface (see the plentiful aircraft... Continue →