DSP Basics Tutorial
Note that this tutorial purposefully does not have screenshots of the flowgraphs, to encourage you to assemble them without just copying what you see. Also note that you can search for a specific block in GNU Radio at the top-right. Lastly, when I say a number like 10k, to enter it into GNU Radio you usually have to say 10000 or 10e3, because Python does *not* interpret "k" as 1e3.
- Download and open this .grc file. This is the starting point that you will work off of, it just includes a special source I made, and a time and frequency sink (GUI). A throttle block is added after the source, and all it does is limit how fasts the simulation runs, to make sure your computer's CPU does not peg to 100% usage. It doesn't affect the signal at all.
- Run the flowgraph and use the time sink to figure out what this special source is outputting. What it's doing is creating 1's and 0's randomly (the 1's and 0's repeat for 15 samples), for the real part of the IQ signal (I), and then zeros for the imaginary part of the IQ signal (Q). This is called "On Off Keying" because we're just turning the I part of the signal on and off randomly (and we are leaving Q at zero).
- When we receive anything in the real world there will be noise added. So lets add some noise. Add a Noise Source block with amplitude 0.1. The amplitude adjusts how strong the noise is.
- Lets add this noise to our sinusoid to see what it looks like in the time and frequency domain. To do this use the Add block, with the two inputs connected to the throttle block and the noise source. Reconnect the time and frequency sink to the output of this add block.
- Run the flowgraph and note what changed in both time and frequency.
- One issue with transmitting 1's and 0's directly (i.e. switching from 0 to 1 and back) is that the frequency domain of our signal is VERY "wide", i.e. the signal takes up a huge amount of spectrum/bandwidth.
- Lets filter the signal to reduce the amount of bandwidth it takes up. Add a Low Pass Filter block right after the throttle block. Use a cutoff freq of 2500 and transition width of 500.
- Run the flowgraph. Notice how the spikes in the frequency domain stop around 2500 Hz. That's what setting a cutoff frequency of 2500 did for us. The transition width just tells it how quickly the passband (frequencies we pass) transitions to a stopband (frequencies we block), don't worry as much about this parameter for now.
- Now look at the time sink. It doesn't go immediately from on-to-off anymore does it? By limiting the frequencies our signal can be composed of, we prevent ourselves from being able to create a steep edge of a square wave. Recall when we looked at the spectrum of a square wave, it was very wide in the frequency domain.
- You may have also noticed that there is a lot of energy at "DC" (i.e. 0 Hz in the frequency sink, see the screenshot below). This is because our signal goes between 0 and 1. So the average value of our signal (in the time domain) is definitely not zero. If you recall Fourier series theory, a signal with a non-zero average means there is a constant offset, which has a frequency of zero, hence the energy at 0 Hz. Lets try to make it zero (you will soon see why we want to).
- Add an Add Const block right after the throttle block. This block lets us add a constant value to our signal. Use a "constant" of -0.5 (negative 0.5), making this is more of a subtract constant block. Remember that the throttle block doesn't do anything to our signal, it's just there so our CPU doesn't explode =). So this Add Const block is now the first thing that is done after generating the 1's and 0's, thus making it so we generate 0.5's and -0.5's instead.
- Run the flowgraph and look at the signal in the time domain. It should be centered around zero now. In the frequency domain we should see no more spike at DC. Good job, we are finally transmitting a decent signal!
- If you haven't realized it by now, we are essentially simulating a wireless transmitter. The chain of blocks going into the top port of the Add block is like our transmitter. The Add block combined with the Noise Source is like a simulated wireless channel that adds noise. The Time and Frequency sink show us what we might see at a receiver.
- Right now this signal is centered around DC (in the frequency domain). I.e., the center of the signal is at 0 Hz. Also, there are negative frequencies. This is all because we generated the signal at "baseband". This may or may not be a problem. When we transmit a signal with an SDR we typically tell the SDR what frequency to tune to, then we send the SDR a baseband signal (in the form of IQ samples). The SDR then does the up-converting on its own and transmits our baseband signal at whatever frequency we told it to. But for now lets simulate the process of "up-converting" the baseband signal to a high frequency to see what happens.
- If you did not understand the previous bullet, that's ok. Our goal is to shift the signal UP in frequency, so that it's transmitted on a "carrier". Lets use a carrier of 10 kHz. To do this we need to multiply our signal by a 10 kHz sinusoid (i.e. a 10 kHz carrier).
- Create a Signal Source block, using a cosine waveform, with a 10k frequency. Also add a Multiply block. The output of the Low Pass Filter should connect to one input of the Multiply block (does not matter which one). The new signal source (our carrier) should be connected to the other input.
- Reconnect your flowgraph so the output of the multiply block goes into the input of the Add block.
- Run the flow graph and spend some time observing the time and frequency domain changes, and think about what has happened here. Remember our Fourier transform pairs.
- On a side note, when we have signals on a high frequency like this (i.e., NOT baseband), the time domain starts being less useful.
- Alright, we are done with the transmit side, go take a break.
- Now what if we want to receive and decode this signal? From this point on, we will be simulating a receiver, which means we will be adding a chain of blocks AFTER the Add block. We won't be adding anything else to the transmit side.
- To shift the signal back to DC, redo the process of multiplying it by a carrier, except we need to use -10k instead of 10k. So lets add another Signal Source block, and add another Multiply block. Alternatively, you can just copy and paste the two blocks we just made (the newest Signal Source and the Multiply block). Edit the frequency and change it from 10k to -10k because we have to shift it the other way in frequency this time. And we must connect the output of the Add block to the input of the new Multiply block. Reconnect the Time/Frequency sinks to the output of the new Multiply block.
- Run the flowgraph and your signal should be back at baseband (centered around 0 Hz).
- Typically in a receiver we use a "matched filter", and we aren't going to get into the details here, but just know that we call it a matched filter because it's a filter that matches the filter at the transmitter. So lets copy and paste the Low Pass Filter we made earlier, and connect it after the new Multiply block (and reconnect the sinks to it). Observe what happens in time and freq after you do that.
- Tip: If you want to compare the signal before and after a block at the same time, you can create a 2nd Time Sink and 2nd Frequency Sink block and have one connected before the block, and one after. Just remember which is which, or set the titles appropriately.
- Lets also shift the signal back to 0 and 1, so add an Add Const block with a constant of (positive) 0.5, between the newest filter and the two sinks.
- Run the flowgraph to verify these last two steps worked. The most noticeable change will be that in the time sink, the signal will go between roughly 0 and 1.
- If you recall, the On Off Keying transmitter created the 1's and 0's but there was 15 samples for every 1 and 15 samples for every 0. So lets save only 1 out of every 15 samples, since we only need to have one sample representing each bit. Add a Keep 1 in N block and set N to 15. It should go right after the newest Add Const block.
- At this point we want to recover the 1's and 0's so lets disable the Frequency Sink, because it won't show us anything useful at this point. Do that by clicking it and hitting D, or right clicking and clicking disable.
- The imaginary portion (Q) does not contain our 1's and 0's so lets get rid of it for now, using the Complex to Real block, which essentially just throws away the imaginary portion of the signal, and our signal will now be *not* complex anymore, just real. Add it right after the Keep 1 in N block. That means we have to modify our Time Sink block to show floats instead of complex numbers, so double click it and choose float for the type.
- Make sure your flowgraph runs, you should just see the Time Sink and there should only be 1 line showing, and it switches between 1 and 0 really fast because each sample is a bit now
- To make things easier to see, open your Time Sink and change number of points shown from 200 to 20. Also, in the config tab, change marker to "circle" and line style to "none". Run the flowgraph to see what we changed.
- If you want to verify you did the tutorial correctly, you should see something similar to this
- At this point we are done with the tutorial, we have recovered the 1's and 0's! Wireless hackers will often do something similar to what we just did in order to decode data in signals they find. In a real system there are some more steps, that you will learn about over time, but this demonstrated the basics.
- You also have a .py file now in the directory you put the .grc file, and that is what was generated by GNU Radio Companion, and that Python script is what runs when you hit the play button. So you can also run the flowgraph by opening up a terminal and running the command: python dsp_basics_tutorial.py. Also note that you may have a epy_block_0.py and epy_block_0.pyc file in that same directory. Feel free to delete those, they are just temporary python files created when we generated the flowgraph due to the custom block I made (the On Off Keying Source).