I'm trying to implement a custom source block for the Analog Discovery 2 oscilloscope in GNU radio. I already have a working python script to record samples from the Analog Discovery 2 device to a WAV file (code at the end of the question).
I'd like to be able to connect this sample source directly in GNUradio companion. I've followed the official tutorial to create custom block to generate template code for my block:
import numpy
from gnuradio import gr
class AnalogDiscovery2(gr.sync_block):
def __init__(self, sample_rate):
gr.sync_block.__init__(self,
name="Analog Discovery 2",
in_sig=None,
out_sig=[numpy.float32])
self.sample_rate = sample_rate
def work(self, input_items, output_items):
out = output_items[0]
# <+signal processing here+>
out[:] = whatever
return len(output_items[0])
I understand I have to modify the work
function to acquire the samples and copy them to the out
variable, however I wonder how I can tune the sample rate? I don't know how the work
function is called, and what is its timing. How can I set the sample rate??
Python code to record samples into a WAV file:
from ctypes import *
from dwfconstants import *
import math
import time
import matplotlib.pyplot as plt
import sys
import wave
import struct
if sys.platform.startswith("win"):
dwf = cdll.dwf
elif sys.platform.startswith("darwin"):
dwf = cdll.LoadLibrary("/Library/Frameworks/dwf.framework/dwf")
else:
dwf = cdll.LoadLibrary("libdwf.so")
#declare ctype variables
hdwf = c_int()
sts = c_byte()
hzAcq = c_double(48000)
nSamples = 96000
rgdSamples = (c_double*nSamples)()
cAvailable = c_int()
cLost = c_int()
cCorrupted = c_int()
fLost = 0
fCorrupted = 0
#print DWF version
version = create_string_buffer(16)
dwf.FDwfGetVersion(version)
print "DWF Version: "+version.value
#open device
print "Opening first device"
dwf.FDwfDeviceOpen(c_int(-1), byref(hdwf))
if hdwf.value == hdwfNone.value:
szerr = create_string_buffer(512)
dwf.FDwfGetLastErrorMsg(szerr)
print szerr.value
print "failed to open device"
quit()
print "Preparing to read sample..."
#print "Generating sine wave..."
#dwf.FDwfAnalogOutNodeEnableSet(hdwf, c_int(0), AnalogOutNodeCarrier, c_bool(True))
#dwf.FDwfAnalogOutNodeFunctionSet(hdwf, c_int(0), AnalogOutNodeCarrier, funcSine)
#dwf.FDwfAnalogOutNodeFrequencySet(hdwf, c_int(0), AnalogOutNodeCarrier, c_double(1))
#dwf.FDwfAnalogOutNodeAmplitudeSet(hdwf, c_int(0), AnalogOutNodeCarrier, c_double(2))
#dwf.FDwfAnalogOutConfigure(hdwf, c_int(0), c_bool(True))
# enable positive supply
dwf.FDwfAnalogIOChannelNodeSet(hdwf, c_int(0), c_int(0), c_double(True))
# set voltage to 3 V
dwf.FDwfAnalogIOChannelNodeSet(hdwf, c_int(0), c_int(1), c_double(3.0))
# enable negative supply
dwf.FDwfAnalogIOChannelNodeSet(hdwf, c_int(1), c_int(0), c_double(True))
# set voltage to -1 V
dwf.FDwfAnalogIOChannelNodeSet(hdwf, c_int(1), c_int(1), c_double(-1.0))
# master enable
dwf.FDwfAnalogIOEnableSet(hdwf, c_int(True))
#set up acquisition
dwf.FDwfAnalogInChannelEnableSet(hdwf, c_int(0), c_bool(True))
dwf.FDwfAnalogInChannelRangeSet(hdwf, c_int(0), c_double(0.1))
dwf.FDwfAnalogInAcquisitionModeSet(hdwf, acqmodeRecord)
dwf.FDwfAnalogInFrequencySet(hdwf, hzAcq)
dwf.FDwfAnalogInRecordLengthSet(hdwf, c_double(nSamples/hzAcq.value))
#wait at least 2 seconds for the offset to stabilize
time.sleep(2)
#begin acquisition
dwf.FDwfAnalogInConfigure(hdwf, c_int(0), c_int(1))
print " waiting to finish"
cSamples = 0
while cSamples < nSamples:
dwf.FDwfAnalogInStatus(hdwf, c_int(1), byref(sts))
if cSamples == 0 and (sts == DwfStateConfig or sts == DwfStatePrefill or sts == DwfStateArmed) :
# Acquisition not yet started.
continue
dwf.FDwfAnalogInStatusRecord(hdwf, byref(cAvailable), byref(cLost), byref(cCorrupted))
cSamples += cLost.value
if cLost.value :
fLost = 1
if cCorrupted.value :
fCorrupted = 1
if cAvailable.value==0 :
continue
if cSamples+cAvailable.value > nSamples :
cAvailable = c_int(nSamples-cSamples)
# get samples
dwf.FDwfAnalogInStatusData(hdwf, c_int(0), byref(rgdSamples, 8*cSamples), cAvailable)
cSamples += cAvailable.value
print "Recording finished"
if fLost:
print "Samples were lost! Reduce frequency"
if cCorrupted:
print "Samples could be corrupted! Reduce frequency"
#f = open("record.bin", "w")
#for v in rgdSamples:
# f.write("%s\n" % v)
#f.close()
# Write samples to file
wav_output = wave.open('record.wav', 'w')
wav_output.setparams((1, 2, 48000, nSamples, 'NONE', 'not compressed'))
values = []
for v in rgdSamples:
packed_value = struct.pack('h', 32768*v)
values.append(packed_value)
value_str = ''.join(values)
wav_output.writeframes(value_str)
wav_output.close()
You have a misconception.
Sampling rate is not a meaningful concept for GNU Radio. GNU Radio blocks are scheduled as fast as possible; that means a source block is called repeatedly until the output buffer is full.
The "wall clock" has nothing to do with how fast samples are processed.
For example, the Signal Source only uses the info on sampling rate to calculate how many samples a period of eg. a sine has. The signal source configured to produce a sine of frequency 10 at a sampling rate of 500 prices exactly the same samples as if you configure it to frequency of 50 and a sampling rate of 2500. There is absolutely no behavioural difference.
I can only stress that GNU Radio has no notion of sample rates whatsoever. The signals passed between the blocks are just sequences of numbers. The fact that the original audio was sampled at a specific rate is probably necessary to correctly parameterize the blocks that process the samples, but it's nothing inherent to the samples.
By the way, there's no reason to write your own wav file source: gr-audio already contains one.