Interfacing Hardware with a C++ OOT Module

From GNU Radio
Revision as of 14:46, 5 March 2018 by M3x1m0m (talk | contribs) (Linking against Driver Library)
Jump to: navigation, search

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 file create a file e.g. FindLimeSuite.cmake. The find module can for instance look as follows:

# - Looking for LimeSuite on the machine
# Once done this will define
#
# LIMESUITE_INCLUDE_DIRS - Where to find the header files.
# LIMESUITE_LIBRARIES - Where to find the dynamically loaded lib. files (.so).
# LIMESUITE_FOUND - True if LIMESUITE found.

find_path(
                LIMESUITE_INCLUDE_DIR
                LimeSuite.h
                PATHS /usr/include /usr/include/lime /usr/local/include/lime
                DOC "LimeSuite include file."
)

find_library(
                LIMESUITE_LIBRARY
                LimeSuite
                PATHS /usr/lib/x86_64-linux-gnu /usr/lib /usr/local/lib
                DOC "LimeSuit shared library obejct file."
)

if(LIMESUITE_INCLUDE_DIR AND LIMESUITE_LIBRARY)
  set(LIMESUITE_FOUND 1)
  set(LIMESUITE_LIBRARIES ${LIMESUITE_LIBRARY})
  set(LIMESUITE_INCLUDE_DIRS ${LIMESUITE_INCLUDE_DIR})
else()
  set(LIMESUITE_FOUND 0)
  set(LIMESUITE_LIBRARIES)
  set(LIMESUITE_INCLUDE_DIRS)
endif()

mark_as_advanced(LIMESUITE_INCLUDE_DIR)
mark_as_advanced(LIMESUITE_LIBRARY)
mark_as_advanced(LIMESUITE_FOUND)

if(NOT LIMESUITE_FOUND)
        set(LIMESUITE_DIR_MESSAGE "LimeSuite was not found. Has it been installed?")
        if(NOT LIMESUITE_FIND_QUIETLY)
                message(STATUS "${LIMESUITE_DIR_MESSAGE}")
        else()
                if(LIMESUITE_FIND_REQUIRED)
                        message(FATAL_ERROR "${LIMESUITE_DIR_MESSAGE}")
                endif()
        endif()
else()
        message(STATUS "Found LimeSuite: ${LIMESUITE_LIBRARIES} and ${LIMESUITE_INCLUDE_DIRS}.")
endif()

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:

# Call the module
find_package(LimeSuite)
if(NOT LIMESUITE_FOUND)
        message(FATAL_ERROR "LimeSuite required to compile limesdr.")
endif()

That piece of code can be placed into the segment where cmake searches for GNURadio dependencies.

Well. So far we looked for 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. 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.

target_link_libraries(gnuradio-limesdr ${Boost_LIBRARIES} ${GNURADIO_ALL_LIBRARIES} ${LIMESUITE_LIBRARIES})

The final step to take is to take the last unused variable and communicate to cmake, that there are header files we want to have added to our project.

########################################################################
# 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}
    ${LIMESUITE_INCLUDE_DIRS}
)

In case you did not mess up anything, you will be able to use the the driver library from now own in your code with a normal include like

#include <LimeSuite.h>

You might might get a quite nasty and really annoying error message similar to AttributeError: 'module' object has no attribute 'MODULE_NAME'. This means usually, that you messed something up in the steps described above and you should iterate through everything again. 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 when you type sudo make install).

ldd /usr/local/lib/libgnuradio-limesdr.so | grep LimeSuite

The output should be something like

libLimeSuite.so.18.02-1 => /usr/local/lib/libLimeSuite.so.18.02-1 (0x00007f5b86be8000)

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

########################################################################
# Debugging
########################################################################
set(PRINT_ALL_VARS 0)
if(PRINT_ALL_VARS)
        get_cmake_property(_variableNames VARIABLES)
        foreach (_variableName ${_variableNames})
                message(STATUS "${_variableName}=${${_variableName}}")
        endforeach()
endif(PRINT_ALL_VARS)

which is borrowed from here.

Using gr::block Facilities

Luckily, the class gr::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 no solely trust on this mechanism as it seems not to be called when start () fails. One work around might be to call stop () oneself as part of the exception handling in a catch block. 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. 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, which is something nobody wants.