Interfacing Hardware with a C++ OOT Module: Difference between revisions

From GNU Radio
Jump to navigation Jump to search
No edit summary
No edit summary
Line 61: Line 61:
endif()
endif()
</source>   
</source>   
That piece of code can be placed into the segment where cmake searches for GNURadio dependencies. 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.  
That piece of code can be placed into the segment where cmake searches for GNURadio dependencies. 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''.
<source lang="cmake">
target_link_libraries(gnuradio-limesdr ${Boost_LIBRARIES} ${GNURADIO_ALL_LIBRARIES} ${LIMESUITE_LIBRARIES})
</source>
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.
<source lang="cmake">
########################################################################
# 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}
)
</source>


== Using gr::block Facilities ==
== Using gr::block Facilities ==
Luckily, the class [https://gnuradio.org/doc/doxygen/classgr_1_1block.html 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. [https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor#130123 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.
Luckily, the class [https://gnuradio.org/doc/doxygen/classgr_1_1block.html 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. [https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor#130123 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.

Revision as of 13:06, 5 March 2018

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 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 using a find module. Modules for cmake 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. 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}
)

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.