diff options
author | Josh Blum <josh@joshknows.com> | 2010-10-25 10:23:59 -0700 |
---|---|---|
committer | Josh Blum <josh@joshknows.com> | 2010-10-25 10:23:59 -0700 |
commit | 3c6ede3c4104d483f39b3c4d062e9e837a9e2e08 (patch) | |
tree | 93a0a50a22dfe08a6f27d0c98b0cca82dfe0f8c9 | |
parent | 3974e544393bd5a720838cbf4790c532bca2aae4 (diff) | |
download | uhd-3c6ede3c4104d483f39b3c4d062e9e837a9e2e08.tar.gz uhd-3c6ede3c4104d483f39b3c4d062e9e837a9e2e08.tar.bz2 uhd-3c6ede3c4104d483f39b3c4d062e9e837a9e2e08.zip |
created ascii art dft plotter in uhd
-rw-r--r-- | host/examples/CMakeLists.txt | 12 | ||||
-rw-r--r-- | host/examples/ascii_art_dft.hpp | 320 | ||||
-rw-r--r-- | host/examples/benchmark_rx_rate.cpp | 1 | ||||
-rw-r--r-- | host/examples/rx_ascii_art_dft.cpp | 143 |
4 files changed, 475 insertions, 1 deletions
diff --git a/host/examples/CMakeLists.txt b/host/examples/CMakeLists.txt index d19838335..a8873f8d3 100644 --- a/host/examples/CMakeLists.txt +++ b/host/examples/CMakeLists.txt @@ -43,3 +43,15 @@ INSTALL(TARGETS tx_waveforms RUNTIME DESTINATION ${PKG_DATA_DIR}/examples ) + +######################################################################## +# ASCII Art DFT - requires curses, so this part is optional +######################################################################## +INCLUDE(FindCurses) + +IF(CURSES_FOUND) + INCLUDE_DIRECTORIES(${CURSES_INCLUDE_DIR}) + ADD_EXECUTABLE(rx_ascii_art_dft rx_ascii_art_dft.cpp) + TARGET_LINK_LIBRARIES(rx_ascii_art_dft uhd ${CURSES_LIBRARIES}) + INSTALL(TARGETS rx_ascii_art_dft RUNTIME DESTINATION ${PKG_DATA_DIR}/examples) +ENDIF(CURSES_FOUND) diff --git a/host/examples/ascii_art_dft.hpp b/host/examples/ascii_art_dft.hpp new file mode 100644 index 000000000..ee2267c2d --- /dev/null +++ b/host/examples/ascii_art_dft.hpp @@ -0,0 +1,320 @@ +// +// ASCII Art DFT Plotter - Josh Blum +// + +#ifndef ASCII_ART_DFT_HPP +#define ASCII_ART_DFT_HPP + +#include <string> +#include <cstddef> +#include <vector> +#include <complex> +#include <stdexcept> + +namespace acsii_art_dft{ + + //! Type produced by the log power DFT function + typedef std::vector<float> log_pwr_dft_type; + + /*! + * Get a logarithmic power DFT of the input samples. + * Samples are expected to be in the range [-1.0, 1.0]. + * \param samps a pointer to an array of complex samples + * \param nsamps the number of samples in the array + * \return a real range of DFT bins in units of dB + */ + template <typename T> log_pwr_dft_type log_pwr_dft( + const std::complex<T> *samps, size_t nsamps + ); + + /*! + * Convert a DFT to a piroundable ascii plot. + * \param dft the log power dft bins + * \param width the frame width in characters + * \param height the frame height in characters + * \param samp_rate the sample rate in Sps + * \param dc_freq the DC frequency in Hz + * \param dyn_rng the dynamic range in dB + * \param ref_lvl the reference level in dB + * \return the plot as an ascii string + */ + std::string dft_to_plot( + const log_pwr_dft_type &dft, + size_t width, + size_t height, + double samp_rate, + double dc_freq, + float dyn_rng, + float ref_lvl + ); + +} //namespace ascii_dft + +/*********************************************************************** + * Implementation includes + **********************************************************************/ +#include <cmath> +#include <sstream> +#include <algorithm> + +/*********************************************************************** + * Helper functions + **********************************************************************/ +namespace {/*anon*/ + + static const double pi = double(std::acos(-1.0)); + + //! Round a floating-point value to the nearest integer + template <typename T> int iround(T val){ + return (val > 0)? int(val + 0.5) : int(val - 0.5); + } + + //! Pick the closest number that is nice to display + template <typename T> T to_clean_num(const T num){ + if (num == 0) return 0; + const T pow10 = std::pow(T(10), int(std::floor(std::log10(std::abs(num))))); + const T norm = std::abs(num)/pow10; + static const int cleans[] = {1, 2, 5, 10}; + int clean = cleans[0]; + for (size_t i = 1; i < sizeof(cleans)/sizeof(cleans[0]); i++){ + if (std::abs(norm - cleans[i]) < std::abs(norm - clean)) + clean = cleans[i]; + } + return ((num < 0)? -1 : 1)*clean*pow10; + } + + //! Compute an FFT with pre-computed factors using Cooley-Tukey + template <typename T> std::complex<T> ct_fft_f( + const std::complex<T> *samps, size_t nsamps, + const std::complex<T> *factors, + size_t start = 0, size_t step = 1 + ){ + if (nsamps == 1) return samps[start]; + std::complex<T> E_k = ct_fft_f(samps, nsamps/2, factors+1, start, step*2); + std::complex<T> O_k = ct_fft_f(samps, nsamps/2, factors+1, start+step, step*2); + return E_k + factors[0]*O_k; + } + + //! Compute an FFT for a particular bin k using Cooley-Tukey + template <typename T> std::complex<T> ct_fft_k( + const std::complex<T> *samps, size_t nsamps, size_t k + ){ + //pre-compute the factors to use in Cooley-Tukey + std::vector<std::complex<T> > factors; + for (size_t N = nsamps; N != 0; N /= 2){ + factors.push_back(std::exp(std::complex<T>(0, T(-2*pi*k/N)))); + } + return ct_fft_f(samps, nsamps, &factors.front()); + } + + //! Helper class to build a DFT plot frame + class frame_type{ + public: + frame_type(size_t width, size_t height): + _frame(width-1, std::vector<char>(height, ' ')) + { + /* NOP */ + } + + //accessors to parts of the frame + char &get_plot(size_t b, size_t z){return _frame.at(b+albl_w).at(z+flbl_h);} + char &get_albl(size_t b, size_t z){return _frame.at(b) .at(z+flbl_h);} + char &get_ulbl(size_t b) {return _frame.at(b) .at(flbl_h-1);} + char &get_flbl(size_t b) {return _frame.at(b+albl_w).at(flbl_h-1);} + + //dimension accessors + size_t get_plot_h(void) const{return _frame.front().size() - flbl_h;} + size_t get_plot_w(void) const{return _frame.size() - albl_w;} + size_t get_albl_w(void) const{return albl_w;} + + std::string to_string(void){ + std::stringstream frame_ss; + for (size_t z = 0; z < _frame.front().size(); z++){ + for (size_t b = 0; b < _frame.size(); b++){ + frame_ss << _frame[b][_frame[b].size()-z-1]; + } + frame_ss << std::endl; + } + return frame_ss.str(); + } + + private: + static const size_t albl_w = 6, flbl_h = 1; + std::vector<std::vector<char> > _frame; + }; + +} //namespace /*anon*/ + +/*********************************************************************** + * Implementation code + **********************************************************************/ +namespace acsii_art_dft{ + + //! skip constants for amplitude and frequency labels + static const size_t albl_skip = 5, flbl_skip = 20; + + template <typename T> log_pwr_dft_type log_pwr_dft( + const std::complex<T> *samps, size_t nsamps + ){ + if (nsamps & (nsamps - 1)) + throw std::runtime_error("num samps is not a power of 2"); + + //compute the window + double win_pwr = 0; + std::vector<std::complex<T> > win_samps; + for(size_t n = 0; n < nsamps; n++){ + //double w_n = 1; + //double w_n = 0.54 //hamming window + // -0.46*std::cos(2*pi*n/(nsamps-1)) + //; + double w_n = 0.35875 //blackman-harris window + -0.48829*std::cos(2*pi*n/(nsamps-1)) + +0.14128*std::cos(4*pi*n/(nsamps-1)) + -0.01168*std::cos(6*pi*n/(nsamps-1)) + ; + //double w_n = 1 // flat top window + // -1.930*std::cos(2*pi*n/(nsamps-1)) + // +1.290*std::cos(4*pi*n/(nsamps-1)) + // -0.388*std::cos(6*pi*n/(nsamps-1)) + // +0.032*std::cos(8*pi*n/(nsamps-1)) + //; + win_samps.push_back(T(w_n)*samps[n]); + win_pwr += w_n*w_n; + } + + //compute the log-power dft + log_pwr_dft_type log_pwr_dft; + for(size_t k = 0; k < nsamps; k++){ + std::complex<T> dft_k = ct_fft_k(&win_samps.front(), nsamps, k); + log_pwr_dft.push_back(float( + + 20*std::log10(std::abs(dft_k)) + - 20*std::log10(T(nsamps)) + - 10*std::log10(win_pwr/nsamps) + + 3 + )); + } + + return log_pwr_dft; + } + + std::string dft_to_plot( + const log_pwr_dft_type &dft_, + size_t width, + size_t height, + double samp_rate, + double dc_freq, + float dyn_rng, + float ref_lvl + ){ + frame_type frame(width, height); //fill this frame + + //re-order the dft so dc in in the center + const size_t num_bins = dft_.size() - 1 + dft_.size()%2; //make it odd + log_pwr_dft_type dft(num_bins); + for (size_t n = 0; n < num_bins; n++){ + dft[n] = dft_[(n + num_bins/2)%num_bins]; + } + + //fill the plot with dft bins + for (size_t b = 0; b < frame.get_plot_w(); b++){ + //indexes from the dft to grab for the plot + const size_t n_start = std::max(iround(double(b-0.5)*(num_bins-1)/(frame.get_plot_w()-1)), 0); + const size_t n_stop = std::min(iround(double(b+0.5)*(num_bins-1)/(frame.get_plot_w()-1)), int(num_bins)); + + //calculate val as the max across points + float val = dft.at(n_start); + for (size_t n = n_start; n < n_stop; n++) val = std::max(val, dft.at(n)); + + const float scaled = (val - (ref_lvl - dyn_rng))*(frame.get_plot_h()-1)/dyn_rng; + for (size_t z = 0; z < frame.get_plot_h(); z++){ + static const std::string syms(".:!|"); + if (scaled-z > 1) frame.get_plot(b, z) = syms.at(syms.size()-1); + else if (scaled-z > 0) frame.get_plot(b, z) = syms.at(size_t((scaled-z)*syms.size())); + } + } + + //create vertical amplitude labels + const float db_step = to_clean_num(dyn_rng/(frame.get_plot_h()-1)*albl_skip); + for ( + float db = db_step*(int((ref_lvl - dyn_rng)/db_step)); + db <= db_step*(int(ref_lvl/db_step)); + db += db_step + ){ + const int z = iround((db - (ref_lvl - dyn_rng))*(frame.get_plot_h()-1)/dyn_rng); + if (z < 0 or size_t(z) >= frame.get_plot_h()) continue; + std::stringstream ss; ss << db; std::string lbl = ss.str(); + for (size_t i = 0; i < lbl.size() and i < frame.get_albl_w(); i++){ + frame.get_albl(i, z) = lbl[i]; + } + } + + //create vertical units label + std::string ulbl = "dBfs"; + for (size_t i = 0; i < ulbl.size(); i++){ + frame.get_ulbl(i+1) = ulbl[i]; + } + + //create horizontal frequency labels + const double f_step = to_clean_num(samp_rate/frame.get_plot_w()*flbl_skip); + for ( + double freq = f_step*int((-samp_rate/2/f_step)); + freq <= f_step*int((+samp_rate/2/f_step)); + freq += f_step + ){ + const int b = iround((freq + samp_rate/2)*(frame.get_plot_w()-1)/samp_rate); + std::stringstream ss; ss << (freq+dc_freq)/1e6 << "MHz"; std::string lbl = ss.str(); + if (b < int(lbl.size()/2) or b + lbl.size() - lbl.size()/2 >= frame.get_plot_w()) continue; + for (size_t i = 0; i < lbl.size(); i++){ + frame.get_flbl(b + i - lbl.size()/2) = lbl[i]; + } + } + + return frame.to_string(); + } +} //namespace ascii_dft + +#endif /*ASCII_ART_DFT_HPP*/ + +/* + +//example main function to test the dft + +#include <iostream> +#include <cstdlib> +#include <curses.h> + +int main(void){ + initscr(); + + while (true){ + clear(); + + std::vector<std::complex<float> > samples; + for(size_t i = 0; i < 512; i++){ + samples.push_back(std::complex<float>( + float(std::rand() - RAND_MAX/2)/(RAND_MAX)/4, + float(std::rand() - RAND_MAX/2)/(RAND_MAX)/4 + )); + samples[i] += 0.5*std::sin(i*3.14/2) + 0.7; + } + + acsii_art_dft::log_pwr_dft_type dft; + dft = acsii_art_dft::log_pwr_dft(&samples.front(), samples.size()); + + printw("%s", acsii_art_dft::dft_to_plot( + dft, COLS, LINES, + 12.5e4, 2.45e9, + 60, 0 + ).c_str()); + + sleep(1); + } + + + endwin(); + std::cout << "here\n"; + return 0; +} + +*/ + diff --git a/host/examples/benchmark_rx_rate.cpp b/host/examples/benchmark_rx_rate.cpp index 36611f97f..b189368f9 100644 --- a/host/examples/benchmark_rx_rate.cpp +++ b/host/examples/benchmark_rx_rate.cpp @@ -137,7 +137,6 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; uhd::usrp::single_usrp::sptr sdev = uhd::usrp::single_usrp::make(args); std::cout << boost::format("Using Device: %s") % sdev->get_pp_string() << std::endl; - sdev->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); //stop if left running if (not vm.count("rate")){ sdev->set_rx_rate(500e3); //initial rate diff --git a/host/examples/rx_ascii_art_dft.cpp b/host/examples/rx_ascii_art_dft.cpp new file mode 100644 index 000000000..5a24867b4 --- /dev/null +++ b/host/examples/rx_ascii_art_dft.cpp @@ -0,0 +1,143 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/single_usrp.hpp> +#include "ascii_art_dft.hpp" //implementation +#include <boost/program_options.hpp> +#include <boost/thread.hpp> //gets time +#include <boost/format.hpp> +#include <curses.h> +#include <iostream> +#include <complex> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + size_t num_bins; + double rate, freq, frame_rate; + float gain, ref_lvl, dyn_rng; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + // hardware parameters + ("rate", po::value<double>(&rate), "rate of incoming samples (sps)") + ("freq", po::value<double>(&freq)->default_value(0), "RF center frequency in Hz") + ("gain", po::value<float>(&gain)->default_value(0), "gain for the RF chain") + // display parameters + ("num-bins", po::value<size_t>(&num_bins)->default_value(512), "the number of bins in the DFT") + ("frame-rate", po::value<double>(&frame_rate)->default_value(5), "frame rate of the display (fps)") + ("ref-lvl", po::value<float>(&ref_lvl)->default_value(0), "reference level for the display (dB)") + ("dyn-rng", po::value<float>(&dyn_rng)->default_value(60), "dynamic range for the display (dB)") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help") or not vm.count("rate")){ + std::cout << boost::format("UHD RX ASCII Art DFT %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::single_usrp::sptr sdev = uhd::usrp::single_usrp::make(args); + std::cout << boost::format("Using Device: %s") % sdev->get_pp_string() << std::endl; + + //set the rx sample rate + std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate/1e6) << std::endl; + sdev->set_rx_rate(rate); + std::cout << boost::format("Actual RX Rate: %f Msps...") % (sdev->get_rx_rate()/1e6) << std::endl << std::endl; + + //set the rx center frequency + std::cout << boost::format("Setting RX Freq: %f Mhz...") % (freq/1e6) << std::endl; + sdev->set_rx_freq(freq); + std::cout << boost::format("Actual RX Freq: %f Mhz...") % (sdev->get_rx_freq()/1e6) << std::endl << std::endl; + + //set the rx rf gain + std::cout << boost::format("Setting RX Gain: %f dB...") % gain << std::endl; + sdev->set_rx_gain(gain); + std::cout << boost::format("Actual RX Gain: %f dB...") % sdev->get_rx_gain() << std::endl << std::endl; + + //allocate recv buffer and metatdata + uhd::rx_metadata_t md; + std::vector<std::complex<float> > buff(num_bins); + //------------------------------------------------------------------ + //-- Initialize + //------------------------------------------------------------------ + initscr(); //curses init + sdev->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + boost::system_time next_refresh = boost::get_system_time(); + + //------------------------------------------------------------------ + //-- Main loop + //------------------------------------------------------------------ + while (true){ + //read a buffer's worth of samples every iteration + size_t num_rx_samps = sdev->get_device()->recv( + &buff.front(), buff.size(), md, + uhd::io_type_t::COMPLEX_FLOAT32, + uhd::device::RECV_MODE_FULL_BUFF + ); + if (num_rx_samps != buff.size()) continue; + + //check and update the display refresh condition + if (boost::get_system_time() < next_refresh) continue; + next_refresh = boost::get_system_time() + boost::posix_time::microseconds(long(1e6/frame_rate)); + + //calculate the dft and create the ascii art frame + acsii_art_dft::log_pwr_dft_type lpdft( + acsii_art_dft::log_pwr_dft(&buff.front(), num_rx_samps) + ); + std::string frame = acsii_art_dft::dft_to_plot( + lpdft, COLS, LINES, + sdev->get_rx_rate(), + sdev->get_rx_freq(), + dyn_rng, ref_lvl + ); + + //curses screen handling: clear and print frame + clear(); + printw("%s", frame.c_str()); + + //curses key handling: no timeout, any key to exit + timeout(0); + int ch = getch(); + if (ch != KEY_RESIZE and ch != ERR) break; + } + + //------------------------------------------------------------------ + //-- Cleanup + //------------------------------------------------------------------ + sdev->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + endwin(); //curses done + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return 0; +} |