diff options
| author | Paul David <paul.david@ettus.com> | 2017-06-29 16:15:58 -0400 | 
|---|---|---|
| committer | Martin Braun <martin.braun@ettus.com> | 2018-06-20 19:02:32 -0500 | 
| commit | a603c97d06bf26271141f875a55c3c76f26057c6 (patch) | |
| tree | 620c6865047bdf819b260b9b78ec59b02185f337 /host/examples | |
| parent | 134e3831f20f5806c616f27c00ed51a7edd389d6 (diff) | |
| download | uhd-a603c97d06bf26271141f875a55c3c76f26057c6.tar.gz uhd-a603c97d06bf26271141f875a55c3c76f26057c6.tar.bz2 uhd-a603c97d06bf26271141f875a55c3c76f26057c6.zip  | |
python: Added curses frequency plot example
Diffstat (limited to 'host/examples')
| -rw-r--r-- | host/examples/python/CMakeLists.txt | 1 | ||||
| -rwxr-xr-x | host/examples/python/curses_fft.py | 152 | 
2 files changed, 153 insertions, 0 deletions
diff --git a/host/examples/python/CMakeLists.txt b/host/examples/python/CMakeLists.txt index 4b75f57bd..669c80203 100644 --- a/host/examples/python/CMakeLists.txt +++ b/host/examples/python/CMakeLists.txt @@ -7,6 +7,7 @@  SET(python_examples      rx_to_file.py      tx_waveforms.py +    curses_fft.py  )  UHD_INSTALL(PROGRAMS ${python_examples} DESTINATION ${PKG_LIB_DIR}/examples/python COMPONENT examples) diff --git a/host/examples/python/curses_fft.py b/host/examples/python/curses_fft.py new file mode 100755 index 000000000..7a447cc67 --- /dev/null +++ b/host/examples/python/curses_fft.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# +# Copyright 2017-2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Curses FFT example using Python API +""" + +import argparse +import curses as cs +import numpy as np +import uhd + + +def parse_args(): +    """Parse the command line arguments""" +    parser = argparse.ArgumentParser() +    parser.add_argument("-a", "--args", default="", type=str) +    parser.add_argument("-f", "--freq", type=float, required=True) +    parser.add_argument("-r", "--rate", default=1e6, type=float) +    parser.add_argument("-g", "--gain", type=int, default=10) +    parser.add_argument("-c", "--channel", type=int, default=0) +    parser.add_argument("-n", "--nsamps", type=int, default=100000) +    parser.add_argument("--dyn", type=int, default=60) +    parser.add_argument("--ref", type=int, default=0) +    return parser.parse_args() + + +def psd(nfft, samples): +    """Return the power spectral density of `samples`""" +    window = np.hamming(nfft) +    result = np.multiply(window, samples) +    result = np.fft.fftshift(np.fft.fft(result, nfft)) +    result = np.square(np.abs(result)) +    result = np.nan_to_num(10.0 * np.log10(result)) +    result = np.abs(result) +    return result + + +def clip(minval, maxval, value): +    """Clip the value between a and b""" +    return min(minval, max(maxval, value)) + + +def main(): +    """Create Curses display of FFT""" +    args = parse_args() +    usrp = uhd.usrp.MultiUSRP(args.args) + +    # Set the USRP rate, freq, and gain +    usrp.set_rx_rate(args.rate, args.channel) +    usrp.set_rx_freq(uhd.types.TuneRequest(args.freq), args.channel) +    usrp.set_rx_gain(args.gain, args.channel) + +    # Initialize the curses screen +    screen = cs.initscr() +    cs.curs_set(0) +    cs.noecho() +    cs.cbreak() +    screen.keypad(1) +    height, width = screen.getmaxyx() + +    # Create a pad for the y-axis +    y_axis_width = 10 +    y_axis = cs.newwin(height, y_axis_width, 0, 0) + +    # Create the buffer to recv samples +    num_samps = max(args.nsamps, width) +    samples = np.empty((1, num_samps), dtype=np.complex64) + +    st_args = uhd.usrp.StreamArgs("fc32", "sc16") +    st_args.channels = [args.channel] + +    metadata = uhd.types.RXMetadata() +    streamer = usrp.get_rx_stream(st_args) +    buffer_samps = streamer.get_max_num_samps() +    recv_buffer = np.zeros((1, buffer_samps), dtype=np.complex64) + +    stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.start_cont) +    stream_cmd.stream_now = True +    streamer.issue_stream_cmd(stream_cmd) + +    db_step = float(args.dyn) / (height - 1.0) +    db_start = db_step * int((args.ref - args.dyn) / db_step) +    db_stop = db_step * int(args.ref / db_step) + +    try: +        while True: +            # Resize the frequency plot on screen resize +            screen.clear() +            if cs.is_term_resized(height, width): +                height, width = screen.getmaxyx() +                cs.resizeterm(height, width) + +                db_step = float(args.dyn) / (height - 1.0) +                db_start = db_step * int((args.ref - args.dyn) / db_step) +                db_stop = db_step * int(args.ref / db_step) + +                y_axis.clear() + +            # Create the vertical (dBfs) axis +            y_axis.addstr(0, 1, "{:> 6.2f} |-".format(db_stop)) +            for i in range(1, height - 1): +                label = db_stop - db_step * i +                y_axis.addstr(i, 1, "{:> 6.2f} |-".format(label)) +            try: +                y_axis.addstr(height - 1, 1, "{:> 6.2f} |-".format(db_start)) +            except cs.error: +                pass +            y_axis.refresh() + +            # Receive the samples +            recv_samps = 0 +            while recv_samps < num_samps: +                samps = streamer.recv(recv_buffer, metadata) + +                if metadata.error_code != uhd.types.RXMetadataErrorCode.none: +                    print(metadata.strerror()) +                if samps: +                    real_samps = min(num_samps - recv_samps, samps) +                    samples[:, recv_samps:recv_samps + real_samps] = recv_buffer[:, 0:real_samps] +                    recv_samps += real_samps + +            # Get the power in each bin +            bins = psd(width, samples[args.channel][0:width]) + +            for i in range(y_axis_width, width): +                vertical_slot = clip(height, 0, np.int(bins[i] / db_step)) +                try: +                    for j in range(vertical_slot, height): +                        screen.addch(j, i, '*') +                except cs.error: +                    pass +            screen.refresh() + +    except KeyboardInterrupt: +        pass + +    stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.stop_cont) +    streamer.issue_stream_cmd(stream_cmd) + +    cs.curs_set(1) +    cs.nocbreak() +    screen.keypad(0) +    cs.echo() +    cs.endwin() + + +if __name__ == "__main__": +    main()  | 
