lneuhaus / pyrpl

pyrpl turns your RedPitaya into a powerful DSP device, especially suitable as a lockbox in quantum optics experiments.
http://lneuhaus.github.io/pyrpl/
MIT License
140 stars 108 forks source link

Arbitrary waveform in ASG generates unexpected waveform #390

Closed svilches closed 4 years ago

svilches commented 4 years ago

I am trying to output a custom, periodic signal using the ASG. As a test, I want to output a ramp between -500 mV and 500 mV. Looping OUT1 to IN1 and running the attached test script, I get this unexpected waveform:

Figure_1

from pyrpl import Pyrpl
from pyrpl.async_utils import sleep
import numpy as np
import time
import matplotlib.pyplot as plt

self=Pyrpl(config='testing',hostname="10.8.182.28")
self.scope=self.rp.scope    # oscilloscope (signal acquisition class)
self.asg=self.rp.asg0       # arbitrary signal generator class

self.fsweep=5000 # Frequency of repetition of the ramp
self.asg.output_direct = "off" # Turn asg off
self.scope.input1='in1'
self.scope.average=True # "Enables averaging during decimation if set to True"

# Set acquisition parameters: https://redpitaya.readthedocs.io/en/latest/appsFeatures/examples/acqRF-exm1.html
decimation=8
sampleClock=125e6
buffSize=2**14
self.scope.decimation = decimation
self.fs=sampleClock/decimation  # sampling frequency
self.n=(int)(self.fs/self.fsweep)      # number of datapoints per ramp
self.buffTime=buffSize/self.fs # Max acquisition time

# Configure trigger
# trigger on the input signal positive slope
self.scope.trigger_source = 'asg0'
self.scope.trigger_delay = self.buffTime/2
self.scope.threshold = 0
self.scope.hysteresis = 0.01

# Setup arbitrary signal generator: waveform='halframp'
waveform=np.linspace(-0.5,0.5,self.n) 
self.asg.data=waveform
#self.asg.setup(frequency=self.fsweep, amplitude=0.1, offset=0, waveform='halframp', trigger_source='immediately') # This works
self.asg.output_direct = "out1"

# Acquire and plot data
ch1,ch2 = self.scope.curve()
plt.plot(ch1)
plt.show()

I have tried for 10 hours to understand asg.py, using the information from #335, but still I wasn't able to find a way to set up a custom waveform for the ASG.

Is there a bug or am I missing something?

lneuhaus commented 4 years ago

Here is an example how to do this:

from pyrpl import Pyrpl
from pyrpl.async_utils import sleep
import numpy as np
import time
%matplotlib inline
import matplotlib.pyplot as plt

p = Pyrpl(config='', hostname="rp", gui=False)
asg = p.rp.asg0
scope = p.rp.scope

# user constants
fsweep = 5000 # Frequency of repetition of the ramp
decimation = 8
sampleClock =125e6
buffSize = asg.data_length  # 2**14
fs = sampleClock/decimation  # sampling frequency
buffTime = buffSize / fs  # Max acquisition time
n = (int)(fs / fsweep)  # number of datapoints per ramp

scope.setup(
    input1='asg0',  # can directly look at asg signal, no need to output it (though you could)
    average=True,
    duration=0.001, # another way to specify decimation indirectly by the total trace duration
    # the asg generates a digital trigger signal when it crosses index=0 of its waveform, which we trigger on with the asg0-setting
    trigger_source='asg0',  
    # trigger_delay is not needed, since we start acquisition when the signal starts (i.e. trigger_delay=0)
    # trigger_delay = self.buffTime/2
    # the settings dont matter because trigger_source is not "ch1_positive_edge"
    # threshold=0, 
    # hysteresis=0.01,
)

# set asg to something harmless - DC with 0 volts
asg.setup(
    output_direct='off',
    frequency=fsweep,  # repetition rate of the waveform (assuming 2**14 points)
    amplitude=1,  # amplitude = 1 => custom waveform gets multiplied by unity on the FPGA
    offset=0,  # no offset, so we faithfully reproduce the waveform
    waveform='dc',  # choose DC, so the waveform is zero to start with
    trigger_source='immediately',  # I think mostly this setting was missing in your example -> asg never started
)

# replace asg data with custom waveform
waveform = np.linspace(-0.5, 0.5, n) 
asg.data = waveform
# important: n is only slightly larger than 3000 with above parameters
# this means we will not fill the buffer of 2**14 points, and the rest of the waveform will be zero.
# it is unclear what you want - you should be clear on this, as there are many "hidden" settings in the 
# asg that might enable you do do that

# if you want to switch on the analog signal
#asg.output_direct = "out1"

# Acquire and plot data
ch1, ch2 = scope.curve()
times = scope.times
plt.plot(times, ch1)
plt.show()

image

I know the waveform I generated is likely not what you want, but you have not really told me. The issue I a having comes from n being less than 2**14, i.e. not filling the entire buffer.

One way would be to stretch your waveform to fill the entire buffer (change n to 2**14 in above example). This is the best solution most of the time, as it gives you the largest interpolation grid (good phase resolution) for outputting the desired waveform.

Another option, if you really have to use 3125 or so points, is to modify the asg such that it only outputs the first 3125 points of its buffer, and then wraps back to the start. Internally, the asg has a counter of 30 bits, and at each clock cycle it adds _counter_step to this value. It then takes the highest 14 bits of this counter as the index of your waveform to output. You can specify

svilches commented 4 years ago

Thanks a lot @lneuhaus for the detailed answer!

I was able to output a custom, periodic signal of length n by setting the counter wrap accordingly: asg._counter_wrap=n*2**16 image

I hope this helps other users setting up the ASG. Maybe a link to this issue in the API manual would also be useful!