Interfacing Hardware with a C++ OOT Module

This page aims to give a rough overview about what needs to be considered developing a GNURadio OOT module, that accesses hardware as for instance a SDR.

Linking against Driver Library
The first thing you will need when writing a block, that connects your design to hardware, is getting the driver compiled and linked against your module. To do this make sure you have installed the driver library and corresponding dependencies correctly on your system e.g. by compiling a small example application executing some basic routines of the driver. What you need next is to tell cmake where to find the libraries you want to include. It is known as best practice to do this writing a find module for every external library. Modules for cmake in GNURadio OOTs are located under cmake/Modules. In this directory create a file and give it a proper name e.g. FindLimeSuite.cmake. The find module can for instance look as follows:

As one can see this module, silently assumed it is written correctly, sets three variables: LIMESUITE_INCLUDE_DIRS, LIMESUITE_LIBRARIES and LIMESUITE_FOUND. These variables are put to use later on. Our module is executed by calling it in the CMakeLists.txt, located in the root of the OOT module: The code above can be placed into the segment where cmake searches for GNURadio dependencies.

So far so good. We triggered cmake to seek the library and used LIMESUITE_FOUND to see if cmake was successful. However, we have not made use of the other two variables yet, right? LIMESUITE_LIBRARIES holds the directory where the dynamically linked library (a .so file) is installed on the system. cmake needs to know, that we want to link this library against our code. Therefore, it provides a macro, which is called in lib/CMakeLists.txt. The final step is to take the last unused variable and communicate to cmake, that there are header files we want to have added to our project. In case you did not mess up something, you will be able to use the the driver library from now own in your code with a normal include like If something is wrong, you perhaps will get a quite nasty and really annoying error message similar to AttributeError: 'module' object has no attribute 'MODULE_NAME'. Usually, this can be interpreted as, that you did configure something wrong in the steps described above and you should iterate through the whole procedure again.

Fortunately, there is a way provided by a program called ldd ("print shared object dependencies", says man ldd) to find out if you linked the dynamic library properly against your library (produced and deployed when you type sudo make install): The output should be something like and can be interpreted as a success of the previous steps. Hurray! If you can not scream the 'Hurray!' yet, since it does not work, the following might be some helpful piece code for you which is borrowed from here.

Using gr::block Facilities
But how is the hardware actually correctly initialized and deinitialized within GNURadio? What to do when something goes wrong? This section tries to answer these questions.

Luckily, the class gr::block, which is the base class for every block, provides functions, that can be used to initialize and deinitialize hardware. These member functions are virtual bool start  and virtual bool stop .

start  is generally used to initialize the hardware and is not coupled with the block construction, when the hole flowgraph is instantiated, i.e. the scheduler can take up working without having to wait for the hardware. The constructor of the block in the module is called before start  is invoked. stop  facilitates hardware deinitialization, such as turning off a transceiver, destroying a stream etc. GNURadio calls stop  when something goes wrong, for instance when a exception is thrown in the work  function.

However, one can not solely trust on this mechanism as it seems not to be called when start  fails. One approach for a sane design might be to call stop  oneself as part of the exception handling in a catch block. An example implementation of start is the following:

The above example tries to initialize the HW, if it fails, the device will be deinitialized again as stop is called.

It is in any case better to use exceptions combined with these two functions instead of relying on constructor and destructor designing blocks to interface with hardware. When a flowgraph is exited normally (e.g. using your preferred window manager), the blocks destructors are called as well as the stop  function of your hardware I/O block, which closes the connection to your device properly, given, that you implemented the stop function correctly.

It is a well-known fact, that throwing exceptions in a destructor can be dangerous (cf. here). This is the same with the stop  function. If the exception is allowed to escape the function std::terminate might be called acc. to standard C++, which is something nobody wants, right? So DO NOT THROW EXCEPTIONS IN stop unless you know exactly what you are doing.

The implementation of the stop function can be much simpler as it avoids exceptions:

One more thing to mention is, that having a signal source with a fixed sample rate, should embed the function set_output_multiple(...) provided by gr::block somewhere in the constructor. The argument of this function should be the size of the buffer for communication between the (hopefully) asynchronous driver and the GNURadio block. This makes sure, that the data size the work function has to output (noutput_items) is always a multiple of buffer's size and it is always OK to return less samples. Yet, it is not recommended to produce less samples than specified with noutput_items.