{ "metadata": { "name": "" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lab 3, Radio Communication Via a Computer Interface, Part I\n", "\n", "Now that you have gotten your radio and radio interfaces, we are ready to experiment with them. In this part of the lab we will learn how to use the interface and the radio, make sure that everything is working correctly so that you will be able to make progress on the second part as well as the project. It is important that you start early, since there may be many technical difficulties. \n", "\n", "
\"gsm\"
\n", "
Figure 1: The Computer-Radio Audio Interface
\n", "\n", "The interface you got has a Kenwood style audio connector with a 2.5mm and 3.5mm audio Jack that connects to your Baofeng radio, a ground-loop isolation box, and 2 3.5mm audio jacks color coded that connect to a USB audio card that is supplied with the interface packet. In order order to transmit using the interface we will use the VOX (voice activation) feature of the radio. When in VOX mode, the radio will transmit whenever there is an input amplitude above a certain threshold. \n", "\n", "There are several steps that you have to go through before and after each time you work on this lab. Please make sure you follow these simple guidelines:\n", "\n", "Before Starting, confirm the following settings on the radio (you will probably need to change a few settings) :\n", "\n", "* Make sure STE (press Menu then 35) is off\n", "* Make sure RP-STE (press Menu 36) is off\n", "* Make sure RPT-RL (press Menu 37) is off\n", "\n", "When starting:\n", "\n", "* Make sure that the channel you will use is not already in use by someone else\n", "* Choose one of the experimental channels (101-108) in the programmed channels, preferably a UHF one (105-108)\n", "* Transmit your call sign and that you intend to use this channel for performing experiments\n", "* Connect the audio interface to the radio on one side and to the USB audio interface on the computer\n", "* Turn on the VOX mode. Press Menu and then 4 on the keypad to get to the VOX menu. Press menu again to change. Set to 3 or 4 by using the arrow keys. Press menu again to set. If your radio transmits occationally without intention, then set the vox to a higher level. \n", "* Make sure in your computer settings that the volume on the USB interface is set to 3/4 of the max and that both input and output are not on mute.\n", "* Make sure the output volume on the radio is reasonable. Turning clockwise when the radio is off for 1/4 of a turn works well for me. \n", "\n", "During operation:\n", "\n", "* If the green light on the radio turns on frequently, it means that someone is transmitting on your channel. In that case, switch to a different one. \n", "* Announce your callsign every 10 minutes\n", "* Make sure that the antenna is not close to the audio cable and is as orthogonal as possible to it -- this will prevent RF to get into the audio interface. \n", "* Use low-power (# key) when possible\n", "\n", "When Finishing:\n", "\n", "* TURN OFF THE VOX MODE!!!!!!!!!!!!!!!!\n", "* Sign off with your call sign\n", "\n", "\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Import functions and libraries\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import pyaudio\n", "import Queue\n", "import threading,time\n", "import sys\n", "\n", "from numpy import pi\n", "from numpy import sin\n", "from numpy import zeros\n", "from numpy import r_\n", "from scipy import signal\n", "from scipy import integrate\n", "\n", "import threading,time\n", "import multiprocessing\n", "\n", "from rtlsdr import RtlSdr\n", "from numpy import mean\n", "from numpy import power\n", "from numpy.fft import fft\n", "from numpy.fft import fftshift\n", "from numpy.fft import ifft\n", "from numpy.fft import ifftshift\n", "\n", "%matplotlib inline" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's first define the spectrogram function, which we will use later in the lab" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Plot an image of the spectrogram y, with the axis labeled with time tl,\n", "# and frequency fl\n", "#\n", "# t_range -- time axis label, nt samples\n", "# f_range -- frequency axis label, nf samples\n", "# y -- spectrogram, nf by nt array\n", "# dbf -- Dynamic range of the spect\n", "\n", "def sg_plot( t_range, f_range, y, dbf = 60) :\n", " eps = 1e-3\n", " \n", " # find maximum\n", " y_max = abs(y).max()\n", " \n", " # compute 20*log magnitude, scaled to the max\n", " y_log = 20.0 * np.log10( abs( y ) / y_max + eps )\n", " \n", " fig=figure(figsize=(15,6))\n", " \n", " plt.imshow( np.flipud( 64.0*(y_log + dbf)/dbf ), extent= t_range + f_range ,cmap=plt.cm.gray, aspect='auto')\n", " plt.xlabel('Time, s')\n", " plt.ylabel('Frequency, Hz')\n", " plt.tight_layout()\n", "\n", "\n", "def myspectrogram_hann_ovlp(x, m, fs, fc,dbf = 60):\n", " # Plot the spectrogram of x.\n", " # First take the original signal x and split it into blocks of length m\n", " # This corresponds to using a rectangular window %\n", " \n", " \n", " isreal_bool = isreal(x).all()\n", " \n", " # pad x up to a multiple of m \n", " lx = len(x);\n", " nt = (lx + m - 1) // m\n", " x = append(x,zeros(-lx+nt*m))\n", " x = x.reshape((m/2,nt*2), order='F')\n", " x = concatenate((x,x),axis=0)\n", " x = x.reshape((m*nt*2,1),order='F')\n", " x = x[r_[m//2:len(x),ones(m//2)*(len(x)-1)].astype(int)].reshape((m,nt*2),order='F')\n", " \n", " \n", " xmw = x * hanning(m)[:,None];\n", " \n", " \n", " # frequency index\n", " t_range = [0.0, lx / fs]\n", " \n", " if isreal_bool:\n", " f_range = [ fc, fs / 2.0 + fc]\n", " xmf = np.fft.fft(xmw,len(xmw),axis=0)\n", " sg_plot(t_range, f_range, xmf[0:m/2,:],dbf=dbf)\n", " print 1\n", " else:\n", " f_range = [-fs / 2.0 + fc, fs / 2.0 + fc]\n", " xmf = np.fft.fftshift( np.fft.fft( xmw ,len(xmw),axis=0), axes=0 )\n", " sg_plot(t_range, f_range, xmf,dbf = dbf)\n", " \n", " return t_range, f_range, xmf" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Buffered Audio I/O\n", "\n", "In order to enable convinient audio processing in real-time we modified the I/O audio functions to use threading and python queues. The nice thing about queue is that it implements a buffered FIFO which we will use to fill in with captured samples or samples we would like to transmit. \n", "\n", "We are also going to use a nice feature in PyAudio that lets you access different audio interfaces. For example, you can record audio from the USB dongle and play it on the computer built-in speaker at the same time. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "def play_audio( Q, p, fs , dev):\n", " # play_audio plays audio with sampling rate = fs\n", " # Q - A queue object from which to play\n", " # p - pyAudio object\n", " # fs - sampling rate\n", " # dev - device number\n", " \n", " # Example:\n", " # fs = 44100\n", " # p = pyaudio.PyAudio() #instantiate PyAudio\n", " # Q = Queue.queue()\n", " # Q.put(data)\n", " # Q.put(\"EOT\") # when function gets EOT it will quit\n", " # play_audio( Q, p, fs,1 ) # play audio\n", " # p.terminate() # terminate pyAudio\n", " \n", " # open output stream\n", " ostream = p.open(format=pyaudio.paFloat32, channels=1, rate=int(fs),output=True,output_device_index=dev)\n", " # play audio\n", " while (1):\n", " data = Q.get()\n", " if data==\"EOT\" :\n", " break\n", " try:\n", " ostream.write( data.astype(np.float32).tostring() )\n", " except:\n", " break\n", " \n", "def record_audio( queue, p, fs ,dev,chunk=1024):\n", " # record_audio records audio with sampling rate = fs\n", " # queue - output data queue\n", " # p - pyAudio object\n", " # fs - sampling rate\n", " # dev - device number \n", " # chunk - chunks of samples at a time default 1024\n", " #\n", " # Example:\n", " # fs = 44100\n", " # Q = Queue.queue()\n", " # p = pyaudio.PyAudio() #instantiate PyAudio\n", " # record_audio( Q, p, fs, 1) # \n", " # p.terminate() # terminate pyAudio\n", " \n", " \n", " istream = p.open(format=pyaudio.paFloat32, channels=1, rate=int(fs),input=True,input_device_index=dev,frames_per_buffer=chunk)\n", "\n", " # record audio in chunks and append to frames\n", " frames = [];\n", " while (1):\n", " try: # when the pyaudio object is distroyed stops\n", " data_str = istream.read(chunk) # read a chunk of data\n", " except:\n", " break\n", " data_flt = np.fromstring( data_str, 'float32' ) # convert string to float\n", " queue.put( data_flt ) # append to list\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To find the device numbers of the built in input/output and the USB devices, we wrote the following function, which searches for them. We made sure this works on our systems, but if you are having trouble finding a device, you should look at debugging this function first!" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def audioDevNumbers(p):\n", " # din, dout, dusb = audioDevNumbers(p)\n", " # The function takes a pyaudio object\n", " # The function searches for the device numbers for built-in mic and \n", " # speaker and the USB audio interface\n", " # some devices will have the name \u201cGeneric USB Audio Device\u201d. In that case, replace it with the the right name.\n", " \n", " dusb = 'None'\n", " din = 'None'\n", " dout = 'None'\n", " if sys.platform == 'darwin':\n", " N = p.get_device_count()\n", " for n in range(0,N):\n", " name = p.get_device_info_by_index(n).get('name')\n", " if name == u'USB PnP Sound Device':\n", " dusb = n\n", " if name == u'Built-in Microph':\n", " din = n\n", " if name == u'Built-in Output':\n", " dout = n\n", " # Windows \n", " else:\n", " N = p.get_device_count()\n", " for n in range(0,N):\n", " name = p.get_device_info_by_index(n).get('name')\n", " if name == u'USB PnP Sound Device':\n", " dusb = n\n", " if name == u'Microsoft Sound Mapper - Input':\n", " din = n\n", " if name == u'Microsoft Sound Mapper - Output':\n", " dout = n\n", " \n", " if dusb == 'None':\n", " print('Could not find a usb audio device')\n", " return din, dout, dusb" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Testing the Buffered Audio:\n", "\n", "The first test/example would be to see if we can capture audio from the radio and play it on the computer.\n", "\n", "* Connect the audio interface to the radio and to the USB audio dongle. Insert the USB device. \n", "* On the radio, press on the orange button to switch to FM radio mode. Tune to 94.1 KPFA. Set the volume at quarter of a turn. You can also tune to the EE123 frequency and ask a friend to transmit to you to see if you can hear the result through the speakers. \n", "* The following code records the audio that is coming out of the radio into the usb port and plays it on the computer built-in speakers. Study it! It will be very usefull in the future. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "# create an input output FIFO queues\n", "Qin = Queue.Queue()\n", "Qout = Queue.Queue()\n", "\n", "\n", "# create a pyaudio object\n", "p = pyaudio.PyAudio()\n", "\n", "# find the device numbers for builtin I/O and the USB\n", "din, dout, dusb = audioDevNumbers(p)\n", "\n", "# initialize a recording thread. The USB device only supports 44.1KHz sampling rate\n", "t_rec = threading.Thread(target = record_audio, args = (Qin, p, 44100, dusb ))\n", "\n", "# initialize a playing thread. \n", "t_play = threading.Thread(target = play_audio, args = (Qout, p, 44100, dout ))\n", "\n", "# start the recording and playing threads\n", "t_rec.start()\n", "t_play.start()\n", "\n", "# record and play about 10 seconds of audio 430*1024/44100 = 9.98 s\n", "for n in range(0,430):\n", " \n", " samples = Qin.get()\n", " \n", " \n", " # You can add code here to do processing on samples in chunks of 1024\n", " # you will have to implement an overlap an add, or overlap an save to get\n", " # continuity between chunks\n", " \n", " Qout.put(samples)\n", "\n", "p.terminate()\n", "\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Testing VOX Radio Transmit\n", "\n", "The next step is to calibrate some parameters such that the voice activation is activated correctly and timely. \n", "\n", "* Follow the preparation steps listed in the beginning of the document\n", "* Start with setting the VOX level to 4\n", "\n", "We will use the SDR to listen to the transmitted signal. \n", "\n", "* Connect the SDR to an empty USB port. \n", "* Disconnect the antenna from the SDR -- the signal will be stong anyways\n", "* Place the radio as far away from the SDR as possible to minimize interference\n", "* Set the power of the radio to low (#)\n", "\n", "\n", "The VOX circuit has a certain delay before the radio starts transmitting. The following code generates a pure 2000Hz tone and plays it through the USB output. It also captures samples from the SDR and plots a spectrogram of the result. \n", "\n", "Before running the code, set the right frequencies on the radio and in software (for the SDR). Modify the code by playing with the length of the pulse and find (roughly) the minimum length that triggers the VOX and the maximum length in which sound is not transmitted. You can also listen to the transmitted signal using a friends radio. You will be able to see in the spectrogram when the radio starts transmitting and wether it is transmitting empty or tone.\n", "\n", "For us, we were getting numbers of minimum 5ms to activate and max 100ms before the tone is transmitted. This means that in order to guarentee transmission, I need to play a short pulse to activate vox and not start the real transmission before at least 0.1 second passes. Once the VOX is on, it will stay on for about 2-4 seconds. It's probably better to not start transmission before at least 0.25 seconds passes so Squelch on a receiving radio has time to open." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# SDR parameters \n", "fs = 240000\n", "fc0 = 443.610e6 # set your frequency!\n", "#apply frequency correction if your SDR needs it\n", "fc = fc0*(1.0-85e-6)\n", "sdr = RtlSdr()\n", "sdr.sample_rate = fs # sampling rate\n", "sdr.gain = 10 # if the gain is not enough, increase it\n", "sdr.center_freq = fc\n", "\n", "# pyaudio parameters\n", "p = pyaudio.PyAudio()\n", "din, dout, dusb = audioDevNumbers(p)\n", "Q = Queue.Queue()\n", "t_play = threading.Thread(target = play_audio, args = (Q, p, 44100, dusb ))\n", "t_play.start()\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "# Modify the length of the pulse. You can just execute this portion\n", "# as many time as you like without terminating the pyaudio object\n", "\n", "tlen = 0.15 # in seconds\n", "t=r_[0.0:tlen*44100.0]/44100\n", "sig = 0.5*sin(2*pi*t*2000)\n", "Q.put(sig)\n", "y = sdr.read_samples(256000*4)\n", "\n", "tt,ff,xmf = myspectrogram_hann_ovlp(y, 256, fs, fc,dbf = 60)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "sdr.close()\n", "p.terminate()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Write a function `pttsig = genPTT(plen,zlen,fs)`. The function will create a signal with a sinusoid pulse of 2000Hz of length plen in seconds and zero-pad it to zlen in seconds. The sampling frequency is fs. You will use the signal generated by this function to turn on VOX and transmit -- hence PTT (like the push to talk button)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def genPTT(plen,zlen,fs):\n", " # Function generates a short pulse to activate the VOX\n", " #\n", " # plen - 2000Hz pulse len in ms\n", " # zlen - total length of the signal in ms (zero-padded)\n", " # fs - sampling frequency in Hz\n", " #\n", " # the function returns the ptt signal\n", " \n", " \n", " \n", " \n", " " ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Measuring the Frequency Response of the Radio's Bandpass Audio Filter\n", "\n", "The audio input to the radio is filtered by a bandpass filter. Because later we are going to use the audio interface to transmit data, we need to know how this data is going to be affected by the filter. Much like in Lab1, we will use a chirp signal to estimate the magnitude frequency response. We will trasmit with the radio and receive using the SDR.\n", "\n", "* Generate a 5 second chirp pulse with frequencies ranging from DC to 8KHz. \n", "* Make sure the volume on the USB device is 2/3 of the way. \n", "* Scale the chirp signal by a factor of 2 to avoid non-linearities. If the VOX drops before the end, use a higher gain\n", "* Transmit the signal using the radio and receive using the SDR. (Don't forget to start with a ptt signal first (250ms recommended) !\n", "* Plot the Spectrogram of the received signal. Explain what you see.\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "\n", "\n", "\n", "\n", "\n", "\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In Order to look at the frequency response, we will need to FM demodulate the signal. \n", "\n", "* Demodulate the received FM signal by filtering and taking the derivative of the phase (like in lab 2) \n", "* Low-pass filter the result with a cutoff frequency of 8KHz and decimate by a factor of 16\n", "* Plot the spectrogram of the demodulated signal. Do you see non-linear effects?\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "\n", "\n", "\n", "\n", "\n", "\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, \n", "\n", "* Crop the porsion in which the carrier is active (signal, not noise)\n", "* Compute and plot the frequency response in db. Scale the graph that you can see things well.\n", "* What is the lowest frequency that passes? What is the highest?\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Transmitting your callsign in Morse code\n", "\n", "The next step is to see if you can transmit something more meaningful. If you are going to transmit for the first time using a computer, you might as well transmit your callsign in Morse code!\n", "\n", "Morse code is composed of dots ( . dit) and dashes ( - dah). The timing is relative to a dot duration which is one unit long. A dah is three units long. Gap between dots and dashes within a character is one unit. A short gap between letters is three units and a gap between words is seven units.\n", "\n", "Here's a dictionary of Morse code:\n", "\n" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "def text2Morse(text,fc,fs,dt):\n", " CODE = {'A': '.-', 'B': '-...', 'C': '-.-.', \n", " 'D': '-..', 'E': '.', 'F': '..-.',\n", " 'G': '--.', 'H': '....', 'I': '..',\n", " 'J': '.---', 'K': '-.-', 'L': '.-..',\n", " 'M': '--', 'N': '-.', 'O': '---',\n", " 'P': '.--.', 'Q': '--.-', 'R': '.-.',\n", " \t'S': '...', 'T': '-', 'U': '..-',\n", " 'V': '...-', 'W': '.--', 'X': '-..-',\n", " 'Y': '-.--', 'Z': '--..',\n", " \n", " '0': '-----', '1': '.----', '2': '..---',\n", " '3': '...--', '4': '....-', '5': '.....',\n", " '6': '-....', '7': '--...', '8': '---..',\n", " '9': '----.',\n", "\n", " ' ': ' ', \"'\": '.----.', '(': '-.--.-', ')': '-.--.-',\n", " ',': '--..--', '-': '-....-', '.': '.-.-.-',\n", " '/': '-..-.', ':': '---...', ';': '-.-.-.',\n", " '?': '..--..', '_': '..--.-'\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Implement a function `sig = text2Morse(text, fc, fs,dt)`. The function will take a string and convert it to a tone signal that plays the morse code of the text. The function will also take 'fc' the frequency of the tones (800-900Hz sounds nice), 'fs' the sampling frequency and 'dt' the morse unit time (hence the speed, 50-75ms recommended).\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def text2Morse(text,fc,fs,dt):\n", " CODE = {'A': '.-', 'B': '-...', 'C': '-.-.', \n", " 'D': '-..', 'E': '.', 'F': '..-.',\n", " 'G': '--.', 'H': '....', 'I': '..',\n", " 'J': '.---', 'K': '-.-', 'L': '.-..',\n", " 'M': '--', 'N': '-.', 'O': '---',\n", " 'P': '.--.', 'Q': '--.-', 'R': '.-.',\n", " \t'S': '...', 'T': '-', 'U': '..-',\n", " 'V': '...-', 'W': '.--', 'X': '-..-',\n", " 'Y': '-.--', 'Z': '--..',\n", " \n", " '0': '-----', '1': '.----', '2': '..---',\n", " '3': '...--', '4': '....-', '5': '.....',\n", " '6': '-....', '7': '--...', '8': '---..',\n", " '9': '----.',\n", "\n", " ' ': ' ', \"'\": '.----.', '(': '-.--.-', ')': '-.--.-',\n", " ',': '--..--', '-': '-....-', '.': '.-.-.-',\n", " '/': '-..-.', ':': '---...', ';': '-.-.-.',\n", " '?': '..--..', '_': '..--.-'\n", " }\n", " \n", " # your code here:\n", " " ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Transmit your call sign! You can use this function to identify yourself before, during and after a transmission from now on.\n", "* Validate the code by capturing a spectrogram using the SDR\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n" ], "language": "python", "metadata": {}, "outputs": [] } ], "metadata": {} } ] }