Recently I was fiddling around with my TV antenna and got curious how the raw signal from it would look like. I hooked it up to my ever faitful USB oscilloscope from digilent and realized it doesnt have enough bandwidth to capture the signal. Ohh, let me wind my own antenna which can receive AM signals so that I can see them on my oscilloscope.
Down the rabbit hole I went....
After few hours of scavenging around in Frys for ferrite rods and wire, I made a loop antenna following the instructions on this page. I hooked it back to USB oscilloscope and fiddled with the tuning capacitor until I got this
|
|
Looking at the modulated carrier gave a huge nostalgic rush of analog communications lab. I had a huge urge to build an amplifier and demodulator on breadboard but better sense (and mental laziness) prevailed. I wondered if there is a way to get this capture as a dump so that I can convert it into an array and process it with code.
Huge shoutout to digilent's wonderful waveforms API and its detailed documentationI was able to dump the whole data as a numpy array and once I am in python I was able to whip out a simple square law demodulator and down-sampler to get audio signal out.
Connecting to the device and opening it is very straight forward. You define a device handle and open the device with try and except block
# Set the library
dwf = cdll.LoadLibrary("libdwf.so")
hdwf = c_int() # Device handle
# Try opening the device
try:
print("Opening device...")
dwf.FDwfDeviceOpen(c_int(-1), byref(hdwf))
except:
print("Failed to open device...")
exit()
Once the device is open we set various acquistion parameters, especially sampling frequency. I found that 800KHz works best for the AM station I detected above.
# Get the device buffer size.
cBufMax = c_int()
dwf.FDwfAnalogInBufferSizeInfo(hdwf, 0, byref(cBufMax))
print("Device buffer size: "+str(cBufMax.value))
# Set sampling frequency
dwf.FDwfAnalogInFrequencySet(hdwf, c_double(sam_freq))
# Set buffer size
dwf.FDwfAnalogInBufferSizeSet(hdwf, c_int(buf_size))
# Enable all channels
dwf.FDwfAnalogInChannelEnableSet(hdwf, c_int(-1), c_bool(True))
# Setting voltage range to 10mV
dwf.FDwfAnalogInChannelRangeSet(hdwf, c_int(-1), c_double(0.001))
# Setting filter to decimate
dwf.FDwfAnalogInChannelFilterSet(hdwf, c_int(-1), 0)
# Waiting for setting to reflect
time.sleep(2)
print("Completed setting up ADC")
I have set the buffer size to maximum buffer size possible, 8192. Next steps were to capture 300 samples of the signal in a list and convert that into a numpy array resulting in an array of size 300x8192. I flattend this array and multiplied it with 100 to amplify it. Multiplying directly is not a good method as it can amplify unwanted components too.
# Convert into numpy array and flatten it
am_mod_data = np.asarray(long_sample)
am_mod_flat = np.reshape(am_mod_data, -1)
# Amplify by multiplication
am_mod_amp = 100 * am_mod_flat
Taking the (noisy) amplified data I squared it to achieve an effect similar to what a didoe does in an envelope demodulator. Then I downsampled it by 16 times by resizing and taking mean. Then the audio signal is recovered by taking square root.
# 1. Square the signal to invert negative side
am_data_sq = am_mod_amp ** 2
# 2. Downsample by reshaping and averaging
am_dwn_sam = am_data_sq.reshape(-1, 16).mean(axis=1)
# 3. Recover audio by square root
am_aud = np.sqrt(am_dwn_sam)
Then I used soundplay python library to stream audio from the numpy array directly.
# Stream audio at a bitrate of 44100
sd.play(aud, 44100)
A sample of the reception is as follows
Complete code can be found here https://github.com/chinnikrishna/amradio
Extrapolating this we can imagine a scenario where if we have proper analog frontend, with a fast enough ADC, we can process the digital samples to eek out wide variety of signals, including TV signals like in a software defined radio. Interfacing the output of ADC to an FPGA can allow us to perform real time processing as well as design different kind of receivers.