A GNU Radio Source Block for the FT232H USB 2.0 controller

Building a GNU Radio Source Block for the FT232H USB 2.0 controller

In my project “ADC readout and USB2.0 data transfer with an FPGA” I described how to couple an ADC (Analog Digital Converter) with an FPGA and how to use the FTDI FT232H Single Channel Hi-Speed USB to Multipurpose UART/FIFO IC to communicate with the PC. This was all in preparation in order to build a Software Defined Radio (SDR).

If one wants to work with SDR a very nice program is GNU Radio [1]. It provides a lot of pre-build blocks which can be connected in a graphical flow graph in order to do demodulation, filtering and many more. It is a very powerful tool to do signal processing for SDR. If one wants to do more complicated things one can also interface GNU Radio with Python scripts or write own modules with C++ and/or Python.

After having used GNU Radio with a simple USB SDR (Realtek RTL2832U R820T) I wanted to build my own SDR and use it with GNU Radio. The first problem one is confronted with if one wants to get the data from an ADC via a FPGA into GNU Radio one needs a source block which can interface the FPGA. In my case via the USB2.0 interface.

The block was build following the tutorial on the GNU Radio homepage [2] and [3].

Since the FTDI FT232H USB controller needs to be interfaced by the source block, a driver for the USB controller is needed. Therefor the library libftdi1-1.1 [4] is linked against the source block. This provides all the functionality needed to read from the USB interface.
This is all needed in order to get the data from the ADC into GNU Radio.

GNURadioFTDITest.grc FTDI_FFT_SCOPE

Those who are only interested in downloading the package and compiling it can scroll down to:
“How to set up the package in short” for s short description of the setup, skipping the details.

How to write the GNU Radio source block?

The first thing we need to do is to download the libftdi library. This can be done from [4], some Linux distributions already include this library in the repository, but mostly the repository is not up to date.

After downloading and extraction of the archive:


cd libftdi1-1.1
mkdir build
cd build/
cmake ../ -DCMAKE_INSTALL_PREFIX=$HOME/tmp/GnuRadioModuleTest
make
make install

Since I do not want to install into the system path, I rather prefer to install in a separate folder the option -DCMAKE_INSTALL_PREFIX=$HOME/tmp/GnuRadioModuleTest is set in order to install after compilation into $HOME/tmp/GnuRadioModuleTest. This opens the possibility to have different versions installed and prevents destroying the system :P.

After having installed the library we can start to write the GNU Radio block:
The tool gr_modtool makes our life much easier since it sets up the whole structure of the GNU Radio block and only code has to be added.


gr_modtool newmod FTxxxRead
cd gr-FTxxxRead
# Add a block
gr_modtool add -t sync ftdi_read_source

Answer the appearing questions with:

Enter valid argument list, including default arguments: int VendorID, int ProductID, unsigned int BufferLength
Add Python QA code? [Y/n] n
Add C++ QA code? [Y/n] n

This will create the right functions parameters.


#create xml file
gr_modtool makexml ftdi_read_source

Now the code can be modified:
The files ftdi_read_source_impl.cc, ftdi_read_source_impl.h in gr-FTxxxRead/lib need to be edited.

In ftdi_read_source_impl.h:

/* -*- c++ -*- */
/* 
 * Copyright 2014 Daniel Pelikan.
 * 
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifndef INCLUDED_FTXXXREAD_FTDI_READ_SOURCE_IMPL_H
#define INCLUDED_FTXXXREAD_FTDI_READ_SOURCE_IMPL_H

#include <FTxxxRead/ftdi_read_source.h>
#include <boost/circular_buffer.hpp>

#include  <ftdi.h>

#include "FiFo.h"

namespace gr {
  namespace FTxxxRead {

    class ftdi_read_source_impl : public ftdi_read_source
    {
     private:
    	boost::mutex fp_mutex;

    	static struct ftdi_context *ftdi;
    	static bool exitRequested;
    	//declare the ring buffer
    	static boost::circular_buffer<unsigned char> m_RingBuffer;

    	static bool m_StartSendData;
    	static long int m_DataAvailable;
    	static FiFo m_FiFo;
    	gr::thread::thread _thread;
    	int m_VendorID;
    	int m_Product_ID;

     public:
      ftdi_read_source_impl(int VendorID, int ProductID, unsigned int BufferLength);
      ~ftdi_read_source_impl();

      static int readCallback(uint8_t *buffer, int length, FTDIProgressInfo *progress, void *userdata);

      static void rtlsdr_wait(ftdi_read_source_impl *obj);



      void open();
      void close();

      // Where all the action really happens
      int work(int noutput_items,
	       gr_vector_const_void_star &input_items,
	       gr_vector_void_star &output_items);
    };

  } // namespace FTxxxRead
} // namespace gr



#endif /* INCLUDED_FTXXXREAD_FTDI_READ_SOURCE_IMPL_H */

The ftdi.h looks like:

/*
 * FiFo.h
 *
 *  Created on: Aug 10, 2014
 *      Author: imp
 */

#ifndef FIFO_H_
#define FIFO_H_


const size_t BufferSize=1024*1024*100;


struct fifo_t {
     char * buf;
     int head;
     int tail;
     int size;
};



class FiFo {
private:



	struct fifo_t m_myFIFO;
	char *m_Buffer;

public:

	FiFo(size_t Size=52428800){
		m_Buffer= new char[Size];
		fifo_init(m_Buffer, Size);
	};

	~FiFo(){
		delete m_Buffer;
	};

	void fifo_init(char * buf, int size);
	int fifo_read(char &a);
	int fifo_write(const char * buf, int nbytes);
	int fifo_getHead(){return m_myFIFO.head;}
	int fifo_getSize(){return m_myFIFO.size;}
	void fifo_changeSize(size_t size);


};

void FiFo::fifo_changeSize(size_t size){
	if(m_Buffer!=NULL){
		delete m_Buffer;
	}
	m_Buffer= new char[size];
	fifo_init(m_Buffer, size);

}

//This initializes the FIFO structure with the given buffer and size
void FiFo::fifo_init(char * buf, int size){
	m_myFIFO.head = 0;
	m_myFIFO.tail = 0;
	m_myFIFO.size = size;
	m_myFIFO.buf = buf;
}


//Read one byte from fifo
int FiFo::fifo_read(char &a){

      if( m_myFIFO.tail != m_myFIFO.head ){ //see if any data is available
           a = m_myFIFO.buf[m_myFIFO.tail];  //grab a byte from the buffer
           m_myFIFO.tail++;  //increment the tail
           if( m_myFIFO.tail == m_myFIFO.size ){  //check for wrap-around
        	   m_myFIFO.tail = 0;
           }
      } else {
           return 0; //number of bytes read
      }
      return 1;

      //returns 0 when no byte was read
      //returns 1 when byte was read

}

//This writes up to nbytes bytes to the FIFO
//If the head runs in to the tail, not all bytes are written
//The number of bytes written is returned
int FiFo::fifo_write(const char * buf, int nbytes){
     int i;
     const char * p;
     p = buf;
     for(i=0; i < nbytes; i++){
           //first check to see if there is space in the buffer
           if( (m_myFIFO.head + 1 == m_myFIFO.tail) ||
                ( (m_myFIFO.head + 1 == m_myFIFO.size) && (m_myFIFO.tail == 0) )){
                 return i; //no more room
           } else {
        	   m_myFIFO.buf[m_myFIFO.head] = *p++;
        	   m_myFIFO.head++;  //increment the head
               if( m_myFIFO.head == m_myFIFO.size ){  //check for wrap-around
            	   m_myFIFO.head = 0;
               }
           }
     }
     return nbytes;
}

#endif /* FIFO_H_ */

This file is not created automatically. What is done on FiFo.h is to define a ring buffer [5].
This buffer is needed in order to buffer the incoming data from the ADC before GNU Radio reads it. It is important to have such a buffer in order to not get data losses when GNU Radio is processing the data. It is like a video buffer when watching streamed video. It makes sure that there is always data available when GNU Radio wants to read.

Now we can have a look on the ftdi_read_source_impl.cc.

/* -*- c++ -*- */
/* 
 * Copyright 2014 Daniel Pelikan.
 * 
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gnuradio/io_signature.h>
#include <gnuradio/thread/thread.h>

#include "ftdi_read_source_impl.h"
#include  <iostream>

Important is the inclusion of thread.h since multi-threading will be used.
We need to read from the FPGA at the same time as we deliver data to GNU RADIO.

namespace gr {
  namespace FTxxxRead {

    //declaration of the static variables
    bool ftdi_read_source_impl::exitRequested;
    bool ftdi_read_source_impl::m_StartSendData;
    long int ftdi_read_source_impl::m_DataAvailable;
    struct ftdi_context *ftdi_read_source_impl::ftdi;
    FiFo ftdi_read_source_impl::m_FiFo;

  //code
    ftdi_read_source::sptr
    ftdi_read_source::make(int VendorID, int ProductID, unsigned int BufferLength)
    {
      return gnuradio::get_initial_sptr
        (new ftdi_read_source_impl(VendorID, ProductID, BufferLength));
    }

    /*
     * The private constructor
     */
    ftdi_read_source_impl::ftdi_read_source_impl(int VendorID, int ProductID, unsigned int BufferLength)
      : gr::sync_block("ftdi_read_source",
              gr::io_signature::make(0,0,0),
              gr::io_signature::make(1, 1, sizeof(char)))
    {

    	m_FiFo.fifo_changeSize(100*1024*1024);//Set Buffer size to 100M

    	//This is the Setup of the FT232 devide
    	m_VendorID=0x0403;
    	m_Product_ID=0x6014;



    	exitRequested=false;
    	m_StartSendData=false;
    	m_DataAvailable=0;

    	open();
    	std::cout<<"FTxxxRead Initialized"<<std::endl;
    }

In this code piece some variables are defined and initialized and the constructor is presented.

    void ftdi_read_source_impl::open(){
    	// obtain exclusive access for duration of this function
    	gr::thread::scoped_lock lock(fp_mutex);

    	   char *descstring = NULL;
    	   int err;

    	   if ((ftdi = ftdi_new()) == 0)
    	   {
    	       std::cerr<<"ftdi_new failed\n"<<std::endl;
    	       return ;
    	   }

    	   if (ftdi_set_interface(ftdi, INTERFACE_A) < 0)
    	   {
    		   std::cerr<<"ftdi_set_interface failed\n"<<std::endl;
    	       ftdi_free(ftdi);
    	       return ;
    	   }

    	   if (ftdi_usb_open_desc(ftdi, m_VendorID, m_Product_ID, descstring, NULL) < 0)// tHIS WE CAN BRING OUT AS ARGUMENTS
    	   {
    		   std::cerr<<"Can't open ftdi device: "<<ftdi_get_error_string(ftdi)<<std::endl;
    	       ftdi_free(ftdi);
    	       return ;
    	   }

    	   /* A timeout value of 1 results in may skipped blocks */
    	   if(ftdi_set_latency_timer(ftdi, 2))
    	   {
    		   std::cerr<<"Can't set latency, Error"<<ftdi_get_error_string(ftdi)<<std::endl;;
    	       ftdi_usb_close(ftdi);
    	       ftdi_free(ftdi);
    	       return ;
    	   }

    	   std::cout<<"Before Thread Start"<<std::endl;

    	   _thread = gr::thread::thread(rtlsdr_wait, this);

    	   std::cout<<"After Thread Start"<<std::endl;
    }

  void ftdi_read_source_impl::rtlsdr_wait(ftdi_read_source_impl *obj)
    {
    	std::cout<<"Before Start"<<std::endl;

      ftdi_readstream(ftdi, readCallback, NULL, 8, 256);

      std::cout<<"After Start"<<std::endl;
    }

In the open() function the FTDI FT232H UBS controller is initialized and the thread is started which will read continuously from the device gr::thread::thread(rtlsdr_wait, this);.


    void ftdi_read_source_impl::close(){
     	// obtain exclusive access for duration of this function
     	gr::thread::scoped_lock lock(fp_mutex);

        if (ftdi_set_bitmode(ftdi,  0xff, BITMODE_RESET) < 0)
        {
            std::cerr<<"Can't set synchronous fifo mode, Error"<<ftdi_get_error_string(ftdi)<<std::endl;
            ftdi_usb_close(ftdi);
            ftdi_free(ftdi);
            return;
        }

        exitRequested=true;
        ftdi_usb_close(ftdi);
        ftdi_free(ftdi);
        m_FiFo.~FiFo();

     }



    /*
     * Our virtual destructor.
     */
    ftdi_read_source_impl::~ftdi_read_source_impl()
    {
    	//close file
    	std::cout<<"FTxxxRead Destructor"<<std::endl;
    	close();
    }

Closing the USB controller when the destructor is called.

int  ftdi_read_source_impl::readCallback(uint8_t *buffer, int length, FTDIProgressInfo *progress, void *userdata)
    {
       if (length)
       {
           if (true)//fill the buffer here
           {

        	   if(m_FiFo.fifo_write((const char*)buffer,length)!=length){
        		   std::cerr<<"Buffer Overflow"<<std::endl;
        	   }

        	   m_DataAvailable++;
           }
       }
       if (progress)
       {
           std::cout<< progress->totalTime<<" total time "<< progress->current.totalBytes / (1024.0 * 1024.0)
        		   <<" MiB captured "<<progress->currentRate / 1024.0
        		   <<" kB/s curr rate "<<progress->totalRate / 1024.0
        		   <<" kB/s totalrate "<<std::endl;

       }
       return exitRequested ? 1 : 0;
    }

This function is reading the actual data from the USB interface, put it into the FiFo, and keeps track how much data is added to the FiFo. The last part prints to stdout how much data was read and how fast the data was read.

int  ftdi_read_source_impl::work(int noutput_items,
			  gr_vector_const_void_star &input_items,
			  gr_vector_void_star &output_items)
    {

        char *out = (char *) output_items[0];

        int size = noutput_items;

        int n_out_item=0;


        gr::thread::scoped_lock lock(fp_mutex); // hold for the rest of this function
        // Do <+signal processing+>

        while(!m_StartSendData){//Wait until starting to send data, buffer needs to have some data first

        	if(m_DataAvailable<m_FiFo.fifo_getSize()/2) m_StartSendData=true;
        	std::cout<<"m_DataAvailable "<<m_DataAvailable<<std::endl;

        }

      
        	for(int i = 0; i < noutput_items; i++) {
        		if(m_FiFo.fifo_read(out[i])!=1){
        			//std::cerr<<"Not enough data to read"<<std::endl;
        		}
        		n_out_item++;

        		
            }


        noutput_items=n_out_item;
        // Tell runtime system how many output items we produced.
        return noutput_items;
    }

  } /* namespace FTxxxRead */
} /* namespace gr */

The work() function is the one which is called from GNU Radio in order to read from the source. It reads from the FiFo and returns the data for further processing of other blocks in GNU Radio.

The last change needed is in the file gr-FTxxxRead/include/FTxxxRead/ftdi_read_source.h
Here one line needs to be changed in order to set initial arguments and for later configuration:

static sptr make(int VendorID=0x0403, int ProductID=0x6014, unsigned int bufferLength=52428800);

And then in order to provide default values in the GNU Radio source block, the file
grc/FTxxxRead_ftdi_read_source.xml has to be edited to

<block>
  <name>Ftdi read source</name>
  <key>FTxxxRead_ftdi_read_source</key>
  <category>FTXXXREAD</category>
  <import>import FTxxxRead</import>
  <make>FTxxxRead.ftdi_read_source($VendorID, $ProductID, $BufferLength)</make>
  <param>
    <name>Vendorid</name>
    <key>VendorID</key>
    <value>0x0403</value>
    <type>hex</type>
  </param>
  <param>
    <name>Productid</name>
    <key>ProductID</key>
    <value>0x6014</value>
    <type>hex</type>
  </param>
  <param>
    <name>Bufferlength</name>
    <key>BufferLength</key>
    <value>52428800</value>
    <type>raw</type>
  </param>
  <source>
    <name>out</name>
    <type>byte</type>
  </source>
</block>

Now all code is available, the whole source package can be found in GitHub, see below.

What is missing is to set up some linking to the libftdi library.

Editing the cmake files

In order to make the gr-FTxxxRead package compile what is needed is that the cmake files know about the libftdi library.

Edit: gr-FTxxxRead/lib/CMakeLists.txt

add at the beginning the following lines:

########################################################################
# My Own Init
########################################################################
include_directories(${LIBFTDI_INCLUDE_DIR})
link_directories(${LIBFTDI_LIBRARY_DIRS})

change the line:


target_link_libraries(gnuradio-FTxxxRead ${Boost_LIBRARIES} ${GNURADIO_ALL_LIBRARIES} ${LIBFTDI_LIBRARIES}) ##Changed

In the file: gr-FTxxxRead/CMakeLists.txt

add at the beginning:

########################################################################
# My Own Init
########################################################################
find_package(LibFTDI1)
if(LibFTDI1_FOUND)
message(STATUS "LibFTDI1 is available")

else(LibFTDI1_FOUND)
message(FATAL_ERROR "LibFTDI1 NOT available!!!!!")
endif(LibFTDI1_FOUND)

change the lines:


########################################################################
# Setup the include and linker paths
########################################################################
include_directories(
${CMAKE_SOURCE_DIR}/lib
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/lib
${CMAKE_BINARY_DIR}/include
${Boost_INCLUDE_DIRS}
${CPPUNIT_INCLUDE_DIRS}
${GNURADIO_ALL_INCLUDE_DIRS}
${LIBFTDI_INCLUDE_DIRS} ## own added
)

link_directories(
${Boost_LIBRARY_DIRS}
${CPPUNIT_LIBRARY_DIRS}
${GNURADIO_RUNTIME_LIBRARY_DIRS}
${LIBFTDI_LIBRARY_DIRS} ## own added
)

Now we are almost ready for compilation:

If one is running a installation of GNU Radio which is not in the standard search path, one needs to initialize GNU Radio first:

export PATH=$PATH:$HOME/bin/GnuRadioInstall
export PATH=$PATH:$HOME/bin/GnuRadioInstall/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/bin/GnuRadioInstall/lib
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$HOME/bin/GnuRadioInstall/lib/pkgconfig
export PYTHONPATH=$PYTHONPATH:$HOME/bin/GnuRadioInstall/lib/python2.7/dist-packages
export GRC_BLOCKS_PATH=$HOME/bin/GnuRadioInstall/share/gnuradio/grc/blocks:$GRC_BLOCKS_PATH

The installation of GNU Radio is in that case in $HOME/bin, those who have GNU Radio already running can skip this step probably, it is here mainly for documentation.

The next step is to set the correct path for our previously compiled and installed libftdi library.


export PATH=$PATH:$HOME/tmp/GnuRadioModuleTest
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/tmp/GnuRadioModuleTest/lib
export PYTHONPATH=$PYTHONPATH:$HOME/tmp/GnuRadioModuleTest/lib/python2.7/dist-packages

The next is the actual compilation:


mkdir build
cd build/
cmake ../ -DCMAKE_INSTALL_PREFIX=$HOME/tmp/GnuRadioModuleTest
make
make install

This will compile the module and install it into the folder $HOME/tmp/GnuRadioModuleTest.

Now one is able to use the module in GNU Radio.

To find it one needs to tell GNU Radio where to search for it:

export PATH=$PATH:$HOME/tmp/GnuRadioModuleTest
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/tmp/GnuRadioModuleTest/lib
export PYTHONPATH=$PYTHONPATH:$HOME/tmp/GnuRadioModuleTest/lib/python2.7/dist-packages
export GRC_BLOCKS_PATH=$HOME/tmp/GnuRadioModuleTest/share/gnuradio/grc/blocks:$GRC_BLOCKS_PATH

In GNU Radio the whole source block appears the following:

GNURadioFTDITest.grc

Running test flow graph: GNURadioFTDITest.grc shows a Fourier transform and the time domain of the input signal from the ADC.

FTDI_FFT_SCOPE

The output rate is written to stdout:

FTDI_RateOutput

How to set up the package in short
If you only want to use the package without much coding by yourself this paragraph is a short description how to set up the package in a few steps:


INSTALL_PATH=$HOME/tmp/GnuRadioModuleTest
GNURadio_PATH=$HOME/bin/GnuRadioInstall

cd libftdi1-1.1
mkdir build
cd build/
cmake ../ -DCMAKE_INSTALL_PREFIX=$INSTALL_PATH
make
make install
cd ../..

export PATH=$PATH:$GNURadio_PATH
export PATH=$PATH:$GNURadio_PATH/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GNURadio_PATH/lib
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$GNURadio_PATH/lib/pkgconfig
export PYTHONPATH=$PYTHONPATH:$GNURadio_PATH/lib/python2.7/dist-packages
export GRC_BLOCKS_PATH=$GNURadio_PATH/share/gnuradio/grc/blocks:$GRC_BLOCKS_PATH
export PATH=$PATH:$INSTALL_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$INSTALL_PATH/lib
export PYTHONPATH=$PYTHONPATH:$INSTALL_PATH/lib/python2.7/dist-packages

cd gr-FTxxxRead
mkdir build
cd build/
cmake ../ -DCMAKE_INSTALL_PREFIX=$INSTALL_PATH
make
make install

cd ../..

export GRC_BLOCKS_PATH=$INSTALL_PATH/share/gnuradio/grc/blocks:$GRC_BLOCKS_PATH

gnuradio-companion gr-FTxxxRead/examples/GNURadioFTDITest.grc

This compiles and installs the whole bundle. Probably some changes in the path are needed.

Then just open the example file and start playing.

Setup of the sample rate
It should be mentioned that the sample rate setup in GNU Radio has to be the same as the speed the FPGA reads the ADC. This can be done for example by using a PLL to clock the ADC, or by a frequency divider. If one has no ADC, one can also simply generate a sine wave or any other signal on the FPGA and sample it with the same speed as GNU Radio expects the signal to come in.

The code for this package can be found on GitHub:

git clone https://github.com/digibird1/GNURadioFTDIBlock

[1] http://gnuradio.org
[2] http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules
[3] http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModulesConfig
[4] http://www.intra2net.com/en/developer/libftdi/index.php
[5] http://en.wikipedia.org/wiki/Circular_buffer

Advertisements
One comment on “A GNU Radio Source Block for the FT232H USB 2.0 controller
  1. […] A GNU Radio Source Block for the FT232H USB 2.0 controller […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s