2011-9-21
These days, I was working in Gnuradio and USRP. I always felt confuse about this development platform. Because there were not an integrated documents and it also didn’t provide any tutorials to teach you how to use it.
Before we handle the signal using kinds of DSP method, I need to know what data come from the USRP. So I try to analyze usrp_rx_cfile.cc.
Every code has a header file.
Look at it.
#include <gr_top_block.h>
#include <usrp_source_base.h>
#include <usrp_source_c.h>
#include <usrp_source_s.h>
#include <gr_file_sink.h>
class usrp_rx_cfile;
typedef boost::shared_ptr<usrp_rx_cfile> usrp_rx_cfile_sptr;
usrp_rx_cfile_sptr make_usrp_rx_cfile(int which, usrp_subdev_spec spec,
int decim, double freq, float gain,
bool width8, bool nohb,
bool output_shorts, int nsamples,
const std::string &filename);
class usrp_rx_cfile : public gr_top_block
{
private:
usrp_rx_cfile(int which, usrp_subdev_spec spec,
int decim, double freq, float gain,
bool width8, bool nohb,
bool output_shorts, int nsamples,
const std::string &filename);
friend usrp_rx_cfile_sptr make_usrp_rx_cfile(int which, usrp_subdev_spec spec,
int decim, double freq, float gain,
bool width8, bool nohb,
bool output_shorts, int nsamples,
const std::string &filename);
int d_which;
usrp_subdev_spec d_spec;
int d_decim;
double d_freq;
float d_gain;
bool d_width8, d_nohb;
int d_nsamples;
std::string d_filename;
public:
gr_block_sptr d_head;
gr_block_sptr d_dst;
};
The header file of usrp_rx_cfile.cc define a class: usrp_rx_cfile.
It inherits from another class gr_top_block but it isn’t important for us now.
There are two private functions: one is constructor. Another is a friend function make_usrp_rx_cfile. It returns a pointer which point to a new allocated usrp_rx_cfile. This function implements in usrp_rx_cfile.cc:
// Shared pointer constructor
usrp_rx_cfile_sptr make_usrp_rx_cfile(int which, usrp_subdev_spec spec,
int decim, double freq, float gain,
bool width8, bool nohb,
bool output_shorts, int nsamples,
const std::string &filename)
{
return gnuradio::get_initial_sptr(new usrp_rx_cfile(which, spec,
decim, freq, gain,
width8, nohb,
output_shorts,
nsamples,
filename));
}
There are also some private values :
int d_which;
usrp_subdev_spec d_spec;
int d_decim;
double d_freq;
float d_gain;
bool d_width8, d_nohb;
int d_nsamples;
std::string d_filename;
these values describe parameters of USRP which they should be set before it works.
public:
gr_block_sptr d_head;
gr_block_sptr d_dst;
d_head and d_dst are gr_block pointers which provide a interface to other gnuradio parts to connect.
Now, let’s locate at usrp_rx_cfile::usrp_rx_cfile in usrp_rx_cfile.cc.
We can see that :
if(d_nsamples == -1) {
connect(usrp, 0, d_dst, 0);
}
else {
d_head = gr_make_head(sizeof(gr_complex), d_nsamples*2);
connect(usrp, 0, d_head, 0);
connect(d_head, 0, d_dst, 0);
}
Usrp connects to d_dst. What is usrp?
Look at this: usrp_source_c_sptr usrp;
usrp_source_c_sptr is a new pointer type defined in usrp_source_c.h :
class usrp_source_c;
typedef boost::shared_ptr<usrp_source_c> usrp_source_c_sptr;
yeah, it is a boost smart pointer which point to class usrp_source_c.
usrp_source_c is a class which handle complex data.
Inheritance diagram for usrp_source_c:
class usrp_source_c : public usrp_source_base {
private:
friend usrp_source_c_sptr
usrp_make_source_c (int which_board,
unsigned int decim_rate,
int nchan,
int mux,
int mode,
int fusb_block_size,
int fusb_nblocks,
const std::string fpga_filename,
const std::string firmware_filename
) throw (std::runtime_error);
protected:
usrp_source_c (int which_board,
unsigned int decim_rate,
int nchan,
int mux,
int mode,
int fusb_block_size,
int fusb_nblocks,
const std::string fpga_filename,
const std::string firmware_filename
) throw (std::runtime_error);
virtual int ninput_bytes_reqd_for_noutput_items (int noutput_items);
virtual void copy_from_usrp_buffer (gr_vector_void_star &output_items,
int output_index,
int output_items_available,
int &output_items_produced,
const void *usrp_buffer,
int usrp_buffer_length,
int &bytes_read);
public:
~usrp_source_c ();
};
Interface to Universal Software Radio Peripheral Rx path
output: 1 stream of complex<float>.
The only thing we are interesting is how it works and what data it returns.
ninput_bytes_reqd_for_noutput_items and copy_from_usrp_buffer are virtual functions.
In gnuradio system, it will call both functions.
Look at copy_from_usrp_buffer implementation.
void
usrp_source_c::copy_from_usrp_buffer (gr_vector_void_star &output_items,
int output_index,
int output_items_available,
int &output_items_produced,
const void *usrp_buffer,
int usrp_buffer_length,
int &bytes_read)
{
gr_complex *out = &((gr_complex *) output_items[0])[output_index];
unsigned sbs = sizeof_basic_sample();
unsigned nusrp_bytes_per_item = NBASIC_SAMPLES_PER_ITEM * sbs;
int nitems = std::min (output_items_available,
(int)(usrp_buffer_length / nusrp_bytes_per_item));
signed char *s8 = (signed char *) usrp_buffer;
short *s16 = (short *) usrp_buffer;
switch (sbs){
case 1:
for (int i = 0; i < nitems; i++){
out[i] = gr_complex ((float)(s8[2*i+0] << 8), (float)(s8[2*i+1] << 8));
}
break;
case 2:
for (int i = 0; i < nitems; i++){
out[i] = gr_complex ((float) usrp_to_host_short(s16[2*i+0]),
(float) usrp_to_host_short(s16[2*i+1]));
}
break;
default:
assert(0);
}
output_items_produced = nitems;
bytes_read = nitems * nusrp_bytes_per_item;
}
It defines a gr_complex pointer out
gr_complex *out = &((gr_complex *) output_items[0])[output_index];
It force output_items[0] to be gr_complex type. out points to output_items.
What is gr_complex ?
In gr_complex.h:
00025 #include <complex>
00026 typedef std::complex<float> gr_complex;
00027 typedef std::complex<double> gr_complexd;
00028
00029
00030 inline bool is_complex (gr_complex x) { return true;}
00031 inline bool is_complex (gr_complexd x) { return true;}
00032 inline bool is_complex (float x) { return false;}
00033 inline bool is_complex (double x) { return false;}
00034 inline bool is_complex (int x) { return false;}
00035 inline bool is_complex (char x) { return false;}
00036 inline bool is_complex (short x) { return false;}
It is compatible with C++ standard complex type.
Next it defines to pointers. One of them point to 8 bit (a byte) value and 16 bit (two byte) value.
signed char *s8 = (signed char *) usrp_buffer;
short *s16 = (short *) usrp_buffer;
Let s8 and s16 point to usrp_buffer. Because it provides both 8bit and 16bit choices for user. Default we use 16bit.
switch (sbs){
case 1:
for (int i = 0; i < nitems; i++){
out[i] = gr_complex ((float)(s8[2*i+0] << 8), (float)(s8[2*i+1] << 8));
}
break;
case 2:
for (int i = 0; i < nitems; i++){
out[i] = gr_complex ((float) usrp_to_host_short(s16[2*i+0]),
(float) usrp_to_host_short(s16[2*i+1]));
}
break;
default:
assert(0);
}
This part is main data transceiver. It handles the input data from usrp_buffer and dump them to out.
We can see that even in usrp_buffer is real part and odd in usrp_buffer is image part.
If we choose 8bit complex, the data shift left 8 bits and then dump to out.
If we choose 16bit complex, the data using a function to get and dump to out.
Now we conclude:
usrp_source_c converts interleaved 8 or 16-bit I & Q from usrp buffer into a single complex output stream.