Gr4-packet-modem transmitter
This page describes the operation of the gr4-packet-modem packet transmitter.
The packet transmitter is implemented as a class that adds and connects multiple blocks to an existing gr::Graph
. This is done like so because at the moment hierarchical flowgraphs are not supported in GNU Radio 4.0. There are two variants of the packet transmitter:
- PacketTransmitter, which uses
packet_len
tags to delimit the boundaries of packets. These are tags that have a"packet_len"
key with auint64_t
value that indicates the length of the packet in items. Packets are present back to back in the item stream, so this method serves to delimit the packets.
- PacketTransmitterPdu, which uses the
Pdu<T>
struct to delimit the packets. This struct contains the packet in astd::vector<T>
.
Due to performance limitations of tags in GNU Radio 4.0, the PacketTransmitterPdu
is the variant that is used in most gr-packet-modem flowgraphs, and it is the one that is described below in detail.
Flowgraph
The flowgraph of PacketTransmitterPdu
is shown here. Blocks in yellow are only present in burst mode, and blocks in blue are only present in stream mode. Blocks in grey are not present, but could be added to the flowgraph. The functionality of the flowgraph is described below. This assumes some familiarity with the packet structure defined in gr4-packet-modem waveform design.
First a packet arrives to the Packet Ingress block. This block check that the packet length is between 1 and 65535 bytes and discards it if not. The packet can optionally have tags that indicate the packet type or the MODCOD to use to transmit the packet. The Packet Ingress block sends a gr::Message
to the Header Formatter with the details that need to be included in the header (packet length and packet type) and passes the packet to the CRC Append block.
For each gr::Message
received, the Header Formatter block creates a 4-byte header that contains the information specified in the message. This 4-byte header is then passed to the FEC encoder. The Header FEC Encoder block transforms each 4-byte header into a 32-byte encoded header by applying r=1/4 LDPC coding and r=1/2 repetition coding.
The CRC Append block receives packet payloads from Packet Ingress. It computes and appends a 4-byte CRC-32 at the end of each packet. There is no payload FEC in this implementation of the transmitter, but if there was, a Payload FEC Encoder block could be placed at the output of CRC Append. This block would use tags in the packet to select the appropriate FEC scheme for the MODCOD used in the packet.
The Packet Mux block gets a packet from each input, concatenates them, and sends the concatenated packet to the output. The first Packet Mux block in the flowgraph concatenates the header and payload plus CRC-32. The output of Packet Mux is sent to an Unpack Bits block that unpacks from 8 bits/byte to 1 bit/byte, since that is the required format for the scrambler. The Additive Scrambler block performs scrambling of the header, payload and CRC-32.
To modulate the header, payload and CRC-32 as QPSK, first a Pack Bits block is used convert from 1 bit/byte to 2 bits/byte. Then a Mapper block maps each value (between 0 and 3) to the corresponding std::complex<float>
symbol in the QPSK constellation. If modulations other than QPSK were supported for the payload, the header and payload would need to be split before this point, with each of them going to different Pack Bits and Mapper blocks (which perhaps would need to be updated dynamically depending on the MODCOD of the payload).
The synchronization word is generate in a Vector Source that continuously repeats the 64-bit sequence (as unpacked 1 bit/byte with the values 0 and 1). This is converted to BPSK symbols by a Mapper block.
In burst mode, to generate the ramp-down sequence there is a GLFSR Source that generates a pseudorandom sequence. The contents of the sequence are not important, since the values of the ramp-down sequence do not matter. Since GLFSR Source generates a continuous stream without any packet boundaries, a Stream to PDU block is used to convert each 18 consecutive bits into a Pdu<uint8_t>
. These are then QPSK modulate with a Pack Bits and a Mapper block. In burst mode there is also a Vector Source that generates some zeros at the end of the packet to flush the RRC pulse shape filter before the beginning of the next packet.
The synchronization word, the header+payload+CRC-32, and in burst mode the ramp-down sequence and zero flushing are all concatenated in a Packet Mux block. The Packet Mux block waits until there is data available in all its inputs before producing any data at the output. The technical reason for this is that regardless of whether the Packet Mux block operates with "packet_len" tags or with Pdu<T>
, it needs to know the length of the input packet on each port to determine the length of the output packet before producing any output for that packet. In this case this property is essential for the operation of the transmitter. The synchronization word input always has data available, but this data must not be sent to the transmitter until there is an accompanying header and payload.
At the output of the Packet Mux there are Pdu<std::complex<float>>
packets that contain the complex symbols a packet. What happens next depends on whether the transmitter is using burst mode or stream mode. In burst mode, the packets are sent to an Interpolating FIR Filter that implements pulse shape filtering and upsampling. The interpolating FIR filter requires an integer number of samples/symbol. Usually 4 samples/symbol is used. The length of the FIR filter is samples_per_symbol * 11
, so it covers 11 symbols, which gives a good trade-off between filter performance and CPU performance. The taps are scaled so that, for any possible combination of input symbols with amplitude less or equal than one, the output has amplitude less or equal than 0.9. This is done to avoid having an output that reaches or exceeds the DAC full scale. At the output of the FIR Filter there is a Burst Shaper block that multiplies the first and last samples of the packet by some given amplitude patterns. The patterns are quarter-sine curves, as defined in the modem waveform, but the Burst Shaper needs to take into account the group delay introduced by the FIR Filter, so these patterns are appropriately zero padded.
In the stream mode, packet boundaries are not needed anymore, so a PDU To Tagged Stream block concatenates all the Pdu<std::complex<float>>
packets into a stream of std::complex<float>
samples. This PDU To Tagged Stream is configured to omit "packet_len" tags at the output (which the block would normally insert to mark packet boundaries), so as to prevent any performance problems with the GNU Radio 4.0 runtime. The stream of complex samples runs as a continuous stream through an Interpolating FIR Filter. This FIR filter is configured in the same way as for the burst mode case, but it acts on a stream of complex samples instead of PDUs of complex samples.