GR4 tutorial: flowgraphs 101

From GNU Radio
Revision as of 09:19, 23 August 2024 by Destevez (talk | contribs) (Created page with "This tutorial explains the basics of C++ flowgraphs in GNU Radio 4.0. == Minimal flowgraph == The following is an example of a minimal flowgraph. It connects a Null Source to a Null Sink and runs the flowgraph forever. The code for the flowgraph can be found in [https://github.com/daniestevez/gr4-packet-modem/blob/main/examples/minimal_flowgraph.cpp minimal_flowgraph.cpp]. <syntaxhighlight lang="cpp" line> #include <fmt/core.h> #include <gnuradio-4.0/Graph.hpp> #inclu...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

This tutorial explains the basics of C++ flowgraphs in GNU Radio 4.0.

Minimal flowgraph

The following is an example of a minimal flowgraph. It connects a Null Source to a Null Sink and runs the flowgraph forever. The code for the flowgraph can be found in minimal_flowgraph.cpp.

#include <fmt/core.h>
#include <gnuradio-4.0/Graph.hpp>
#include <gnuradio-4.0/Scheduler.hpp>
#include <gnuradio-4.0/packet-modem/null_sink.hpp>
#include <gnuradio-4.0/packet-modem/null_source.hpp>
#include <boost/ut.hpp>

int main()
{
    using namespace boost::ut;

    gr::Graph fg;
    auto& source = fg.emplaceBlock<gr::packet_modem::NullSource<int>>();
    auto& sink = fg.emplaceBlock<gr::packet_modem::NullSink<int>>();
    expect(eq(gr::ConnectionResult::SUCCESS, fg.connect<"out">(source).to<"in">(sink)));

    gr::scheduler::Simple sched{ std::move(fg) };
    expect(sched.runAndWait().has_value());

    return 0;
}

Here the boost-ut expect() function is used for error handling, since it is also used for unit testing in GNU Radio 4.0, but any other form of error handling can be used.

Creating a flowgraph in GNU Radio 4.0 begins with the instantiation of a gr::Graph object.

The emplaceBlock() method of the gr::Graph is used to add blocks to the flowgraph. Note that this method takes a template parameter which specifies the block class. The block classes can be templates, and they often have template parameters to indicate the input and output types. Here we are creating a NullSource<int>, which has int as output type, and similarly for the null sink. As in GNU Radio 3.10, blocks can get parameters when they are created. These are passed as a gr::property_map argument to emplaceBlock(). In this case, the null source and null sink don't take any parameters, so emplaceBlock() is called without arguments. We will see an example of blocks with parameters below.

A connection between blocks is done by calling the connect() method of the gr::Graph. Note how the syntax works, using template parameters to specify the name of the ports. In GNU Radio 3.10, block ports have numbers 0, 1, 2, etc. These numbers are specified when calling connect, and 0 is implicit if the number is omitted. In GNU Radio 4.0, all the ports have names which are a string. In simple blocks which only have an input or an output, "in" and "out" are often used as port names. The connect() method checks that the connection is valid (the types of the ports match, etc.) and returns a gr::ConnectionResult value indicating if the connection could be made.

Once all the blocks have been added to the flowgraph and connected, the flowgraph can be run. To do this, it is first necessary to put the flowgraph in a scheduler. GNU Radio 3.10 has a single scheduler implemented in the runtime. In GNU Radio 4.0, schedulers are modular and users can implement their own, so any scheduler could be used here. In this example we use the gr::Scheduler::Simple scheduler, which is included in the GNU Radio 4.0 core. By default it is a single-threaded scheduler, meaning that it runs all of the blocks in the same thread (except for dedicated IO threads). It can also be instantiated as a multi-threaded scheduler that uses a one-thread-per-block approach similar to GNU Radio 3.10.

After the scheduler is created, its runAndWait() method is called. This is equivalent to the GNU Radio 3.10 run() method. It runs the flowgraph until the flowgraph finishes on its own (because the end of an input file has been reached, the limit on a Head block has been hit, etc.) The runAndWait() method also returns if there is an error, such as a block raising an unhandled exception. The method returns a std::expected, so the has_value() method can be used to check if there was an error.