Here is a simple demonstration of using embedded Python to write a GNURadio AM Envelope Detector and explore its performance with various signals.
Previously, I have described how the AM envelope detector works and talked about its limitations. Cheap and cheerful, yes. High performance, no. Envelope detectors are comprised of a diode – which rectifies an AM signal – and a low-pass filter to eliminate the carrier and leave the audio. A simple AM detector can be made from a diode, and a correctly selected resister and capacitor. The RC filter causes an exponential decay and filters higher frequencies.
So, I thought I would build a GNURadio Embedded Python Block to implement envelope detection. Here is a video of the experiment. You can see in the graphic above that the model eliminated most but not all of the carrier. That’s asking too much of a single pole RC filter, and additional filtering is required.
By the way, this GNURadio Envelope Detector does not work very well with IQ data derived from a software defined radio. Typically, with cheaper SDR, IQ data will contain a DC offset, and this DC offset is not stationary. As a result, the signal is not pure AC and using a rectifier does not work. For this reason, unless your IQ data is really cleaned up, you need to use other detectors for AM. For example, the built in GNURadio AM Demodulator works with the Magnitude signal from the complex data.
GNURadio Envelope Detector – Python Source Code
Here is the source code for the GNURadio Envelope Detector I implemented in an Embedded Python Block.
import numpy as np from gnuradio import gr class blk(gr.sync_block): # other base classes are basic_block, decim_block, interp_block """Embedded Python Block Envelope Detector """ def __init__(self, threshold=0.0, mode=0, coeff=0.15): # only default arguments here """arguments to this function show up as parameters in GRC""" gr.sync_block.__init__( self, name='Envelope Detector', # will show up in GRC in_sig=[np.float32], out_sig=[np.float32] ) # if an attribute with the same name as a parameter is found, # a callback is registered (properties work, too). self.ry = 0 self.threshold = threshold self.mode = mode self.coeff = coeff def work(self, input_items, output_items): """Envelope Detect with Half/Full Wave Rectifier""" buf = [0] * len(output_items[0]) a0 = self.coeff b1 = 1 - a0 # Rectify the signal. Mode 0 is half-wave, Mode 1 is full-wave for i in range (0, len(input_items[0])) : if self.mode == 1: buf[i] = abs(input_items[0][i]) else: if input_items[0][i] > self.threshold: buf[i] = input_items[0][i] else: buf[i] = 0 # Simulate simple RC filter with a variable decay coefficient for i in range(0, len(output_items[0])): if i==0: output_items[0][i] = a0*buf[i] + b1*self.ry else: output_items[0][i] = a0*buf[i] + b1*output_items[0][i-1] i = len(output_items[0])-1 self.ry = output_items[0][i] return len(output_items[0])
You can download the complete project here.