frappy_psi.ultrasound: add input_delay and other improvments
Change-Id: I6cb5690d82d96d6775fcb649fc633c4039932463
This commit is contained in:
parent
7c60daa568
commit
8d62375483
62
cfg/RUS.py
62
cfg/RUS.py
@ -1,62 +0,0 @@
|
||||
Node(equipment_id = 'r_ultrasound.psi.ch',
|
||||
description = 'resonant ultra sound setup',
|
||||
interface = 'tcp://5000',
|
||||
)
|
||||
|
||||
Mod('f',
|
||||
cls = 'frappy_psi.ultrasound.Frequency',
|
||||
description = 'ultrasound frequency and acquisition loop',
|
||||
uri = 'serial:///dev/ttyS1',
|
||||
pars = 'pars',
|
||||
pollinterval = 0.1,
|
||||
time = 900, # start time
|
||||
size = 5000,
|
||||
freq = 1.e+03,
|
||||
basefreq = 1.E+3,
|
||||
control = False,
|
||||
rusmode = False,
|
||||
amp = 2.5,
|
||||
nr = 1, #500 #300 #100 #50 #30 #10 #5 #3 #1 #1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #500
|
||||
sr = 1E8, #16384
|
||||
plot = True,
|
||||
maxstep = 100000,
|
||||
bw = 10E6, #butter worth filter bandwidth
|
||||
maxy = 0.7, # y scale for plot
|
||||
curves = 'curves', # module to transmit curves:
|
||||
)
|
||||
|
||||
Mod('curves',
|
||||
cls = 'frappy_psi.ultrasound.Curves',
|
||||
description = 't, I, Q and pulse arrays for plot',
|
||||
)
|
||||
|
||||
Mod('roi0',
|
||||
cls = 'frappy_psi.ultrasound.Roi',
|
||||
description = 'I/Q of region in the control loop',
|
||||
time = 300, # this is the center of roi:
|
||||
size = 5000,
|
||||
main = f,
|
||||
)
|
||||
|
||||
Mod('roi1',
|
||||
cls = 'frappy_psi.ultrasound.Roi',
|
||||
description = 'I/Q of region 1',
|
||||
time = 100, # this is the center of roi:
|
||||
size = 300,
|
||||
main = f,
|
||||
)
|
||||
|
||||
Mod('delay',
|
||||
cls = 'frappy__psi.dg645.Delay',
|
||||
description = 'delay line with 2 channels',
|
||||
uri = 'serial:///dev/ttyS2',
|
||||
on1 = 1e-9,
|
||||
on2 = 1E-9,
|
||||
off1 = 400e-9,
|
||||
off2 = 600e-9,
|
||||
)
|
||||
|
||||
Mod('pars',
|
||||
cls = 'frappy_psi.ultrasound.Pars',
|
||||
description = 'SEA parameters',
|
||||
)
|
@ -47,6 +47,7 @@ ADQ_TRANSFER_MODE_NORMAL = 0x00
|
||||
ADQ_CHANNELS_MASK = 0x3
|
||||
|
||||
GHz = 1e9
|
||||
RMS_TO_VPP = 2 * np.sqrt(2)
|
||||
|
||||
|
||||
class Adq:
|
||||
@ -88,19 +89,15 @@ class Adq:
|
||||
|
||||
rev = ADQAPI.ADQ_GetRevision(self.adq_cu, self.adq_num)
|
||||
revision = ct.cast(rev, ct.POINTER(ct.c_int))
|
||||
print('\nConnected to ADQ #1')
|
||||
# Print revision information
|
||||
print('FPGA Revision: {}'.format(revision[0]))
|
||||
out = [f'Connected to ADQ #1, FPGA Revision: {revision[0]}']
|
||||
if revision[1]:
|
||||
print('Local copy')
|
||||
out.append('Local copy')
|
||||
else:
|
||||
print('SVN Managed')
|
||||
if revision[2]:
|
||||
print('Mixed Revision')
|
||||
out.append('SVN Managed - Mixed Revision')
|
||||
else:
|
||||
print('SVN Updated')
|
||||
print('')
|
||||
|
||||
out.append('SVN Updated')
|
||||
print(', '.join(out))
|
||||
ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF)
|
||||
|
||||
##########################
|
||||
@ -126,9 +123,10 @@ class Adq:
|
||||
elif self.trigger == EXT_TRIG_1:
|
||||
if not ADQAPI.ADQ_SetExternTrigEdge(self.adq_cu, self.adq_num, 2):
|
||||
raise RuntimeError('ADQ_SetLvlTrigEdge failed.')
|
||||
# if not ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)):
|
||||
# raise RuntimeError('SetTriggerThresholdVoltage failed.')
|
||||
print("CHANNEL:"+str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num))))
|
||||
# if not ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)):
|
||||
# raise RuntimeError('SetTriggerThresholdVoltage failed.')
|
||||
# proabably the folloiwng is wrong.
|
||||
# print("CHANNEL:" + str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num))))
|
||||
|
||||
def init(self, samples_per_record=None, number_of_records=None):
|
||||
"""initialize dimensions and store result object"""
|
||||
@ -145,16 +143,14 @@ class Adq:
|
||||
def deletecu(self):
|
||||
cu = self.__dict__.pop('adq_cu', None)
|
||||
if cu is None:
|
||||
print('deletecu already called')
|
||||
return
|
||||
print('deletecu called')
|
||||
print('shut down ADQ')
|
||||
# Only disarm trigger after data is collected
|
||||
ADQAPI.ADQ_DisarmTrigger(cu, self.adq_num)
|
||||
ADQAPI.ADQ_MultiRecordClose(cu, self.adq_num)
|
||||
print('delete cu')
|
||||
# Delete ADQControlunit
|
||||
ADQAPI.DeleteADQControlUnit(cu)
|
||||
print('cu deleted')
|
||||
print('ADQ closed')
|
||||
|
||||
def start(self, data):
|
||||
# Start acquisition
|
||||
@ -165,6 +161,7 @@ class Adq:
|
||||
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
|
||||
ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num)
|
||||
self.data = data
|
||||
self.starttime = time.time()
|
||||
|
||||
def get_data(self):
|
||||
"""get new data if available"""
|
||||
@ -182,6 +179,7 @@ class Adq:
|
||||
if not ready:
|
||||
self.busy = True
|
||||
return None
|
||||
t = time.time()
|
||||
# Get data from ADQ
|
||||
if not ADQAPI.ADQ_GetData(
|
||||
self.adq_cu, self.adq_num, self.target_buffers,
|
||||
@ -283,25 +281,31 @@ class Namespace:
|
||||
|
||||
|
||||
class RUSdata:
|
||||
def __init__(self, adq, freq, periods):
|
||||
def __init__(self, adq, freq, periods, delay_samples):
|
||||
self.sample_rate = adq.sample_rate
|
||||
self.freq = freq
|
||||
self.periods = periods
|
||||
self.delay_samples = delay_samples
|
||||
self.samples_per_record = adq.samples_per_record
|
||||
self.inp = Namespace(idx=0, name='input')
|
||||
self.out = Namespace(idx=1, name='output')
|
||||
self.channels = (self.inp, self.out)
|
||||
|
||||
def retrieve(self, adq):
|
||||
npts = self.samples_per_record
|
||||
npts = self.samples_per_record - self.delay_samples
|
||||
complex_sinusoid = np.exp(1j * 2 * np.pi * self.freq / self.sample_rate * np.arange(npts))
|
||||
for chan in self.channels: # looping over input and output
|
||||
signal = np.frombuffer(adq.target_buffers[chan.idx].contents, dtype=np.int16)
|
||||
chan.ovr_rate = (np.abs(signal) > 8000).sum() / npts # fraction of points outside range
|
||||
# although the ADC is only 14 bit it is representet as unsigend 16 bit numbers,
|
||||
# and due to some calculations (calibration) the last 2 bits are not zero
|
||||
isignal = np.frombuffer(adq.target_buffers[chan.idx].contents, dtype=np.int16)[self.delay_samples:]
|
||||
# importatn convert to float first, before doing any calculations.
|
||||
# calculations with int16 may silently overflow
|
||||
chan.signal = isignal / float(2 ** 16) # in V -> peak to peak 1 V ~ +- 0.5 V
|
||||
chan.ovr_rate = (np.abs(isignal) > 32000).sum() / npts # fraction of points near end of range
|
||||
# calculate RMS * sqrt(2) -> peak sinus amplitude.
|
||||
# may be higher than the input range by a factor 1.4 when heavily clipped
|
||||
# we need to convert signal from int16 to float -> use 2.0 as exponent does the trick
|
||||
chan.amplitude = np.sqrt((signal ** 2.0).mean())
|
||||
signal = chan.signal - np.median(chan.signal)
|
||||
chan.amplitude = np.sqrt((signal ** 2).mean()) * RMS_TO_VPP
|
||||
chan.mixed = signal * complex_sinusoid
|
||||
chan.mean = chan.mixed.mean()
|
||||
if self.inp.mean:
|
||||
@ -320,10 +324,13 @@ class RUSdata:
|
||||
the imaginary part indicates a turning phase (rad/sec)
|
||||
the real part indicates changes in amplitude (0.01 ~= 1%/sec)
|
||||
"""
|
||||
nper = self.samples_per_record // self.periods
|
||||
npts = len(self.channels[0].signal)
|
||||
nper = npts // self.periods
|
||||
nbin = 50 # 50 samples, 25 ns
|
||||
for chan in self.channels:
|
||||
mean = chan.mixed.mean()
|
||||
chan.reduced = chan.mixed[:self.periods * nper].reshape((-1, nper)).mean(axis=0) / mean
|
||||
chan.reduced = chan.mixed[:self.periods * nper].reshape((-1, nper)).mean(axis=1) / mean
|
||||
chan.binned = chan.signal[:npts // nbin * nbin].reshape((-1, nbin)).mean(axis=1)
|
||||
|
||||
timeaxis = np.arange(len(self.out.reduced)) * self.sample_rate / self.freq
|
||||
return Namespace(
|
||||
|
@ -27,9 +27,9 @@ import numpy as np
|
||||
from frappy_psi.adq_mr import Adq, PEdata, RUSdata
|
||||
from frappy.core import Attached, BoolType, Done, FloatRange, HasIO, StatusType, \
|
||||
IntRange, Module, Parameter, Readable, Writable, Drivable, StringIO, StringType, \
|
||||
IDLE, BUSY, DISABLED, WARN, ERROR, TupleOf, ArrayOf, Command, Attached
|
||||
IDLE, BUSY, DISABLED, WARN, ERROR, TupleOf, ArrayOf, Command, Attached, EnumType
|
||||
from frappy.properties import Property
|
||||
#from frappy.modules import Collector
|
||||
# from frappy.modules import Collector
|
||||
|
||||
Collector = Readable
|
||||
|
||||
@ -95,8 +95,8 @@ class FreqStringIO(StringIO):
|
||||
|
||||
class Frequency(HasIO, Writable):
|
||||
value = Parameter('frequency', unit='Hz')
|
||||
amp = Parameter('amplitude', FloatRange(unit='dBm'), readonly=False)
|
||||
|
||||
amp = Parameter('amplitude (VPP)', FloatRange(unit='V'), readonly=False)
|
||||
output = Parameter('output: L or R', EnumType(L=1, R=0), readonly=False, default='L')
|
||||
last_change = 0
|
||||
ioClass = FreqStringIO
|
||||
dif = None
|
||||
@ -115,14 +115,15 @@ class Frequency(HasIO, Writable):
|
||||
self.last_change = time.time()
|
||||
if self.dif:
|
||||
self.dif.read_value()
|
||||
self.read_value()
|
||||
return self._freq
|
||||
|
||||
def write_amp(self, amp):
|
||||
reply = self.communicate('AMPR %g;AMPR?' % amp)
|
||||
reply = self.communicate(f'AMP{self.output.name} {amp} VPP;AMP{self.output.name}? VPP')
|
||||
return float(reply)
|
||||
|
||||
def read_amp(self):
|
||||
reply = self.communicate('AMPR?')
|
||||
reply = self.communicate(f'AMP{self.output.name}? VPP')
|
||||
return float(reply)
|
||||
|
||||
|
||||
@ -146,9 +147,7 @@ class Base:
|
||||
|
||||
def shutdownModule(self):
|
||||
if self.adq:
|
||||
print('shutdownModule')
|
||||
self.adq.deletecu()
|
||||
print('shutdoneModule done')
|
||||
self.adq = None
|
||||
|
||||
|
||||
@ -236,11 +235,17 @@ class RUS(Base, Collector):
|
||||
freq = Attached()
|
||||
imod = Attached(mandatory=False)
|
||||
qmod = Attached(mandatory=False)
|
||||
input_signal = Attached(mandatory=False)
|
||||
output_signal = Attached(mandatory=False)
|
||||
value = Parameter('averaged (I, Q) tuple', TupleOf(FloatRange(), FloatRange()))
|
||||
status = Parameter(datatype=StatusType(Readable, 'BUSY'))
|
||||
periods = Parameter('number of periods', IntRange(1, 9999), default=12)
|
||||
input_range = Parameter('input range (taking in to account attenuation)', FloatRange(unit='V'), default=0.1, readonly=False)
|
||||
output_range = Parameter('output range', FloatRange(unit='V'), default=0.1, readonly=False)
|
||||
input_delay = Parameter('throw away everything before this time',
|
||||
FloatRange(unit='ns'), default=10000, readonly=False)
|
||||
input_range = Parameter('input range (taking in to account attenuation)', FloatRange(unit='V'),
|
||||
default=10, readonly=False)
|
||||
output_range = Parameter('output range', FloatRange(unit='V'),
|
||||
default=1, readonly=False)
|
||||
input_phase_stddev = Parameter('input signal quality', FloatRange(unit='rad'), default=0)
|
||||
output_phase_slope = Parameter('output signal phase slope', FloatRange(unit='rad/sec'), default=0)
|
||||
output_amp_slope = Parameter('output signal amplitude change', FloatRange(unit='1/sec'), default=0)
|
||||
@ -249,7 +254,7 @@ class RUS(Base, Collector):
|
||||
phase = Parameter('phase', FloatRange(unit='deg'), default=0)
|
||||
amp = Parameter('amplitude', FloatRange(), default=0)
|
||||
continuous = Parameter('continuous mode', BoolType(), readonly=False, default=True)
|
||||
pollinterval = Parameter(default=1)
|
||||
pollinterval = Parameter(datatype=FloatRange(0, 120), default=1)
|
||||
|
||||
_starttime = None
|
||||
_iq = 0
|
||||
@ -262,8 +267,12 @@ class RUS(Base, Collector):
|
||||
super().initModule()
|
||||
self.adq = Adq()
|
||||
self._ovr_rate = {}
|
||||
self.freq.addCallback('value', self.update_freq)
|
||||
# self.write_periods(self.periods)
|
||||
|
||||
def update_freq(self, value):
|
||||
self.setFastPoll(True, 0.001)
|
||||
|
||||
def doPoll(self):
|
||||
try:
|
||||
data = self.adq.get_data()
|
||||
@ -283,11 +292,15 @@ class RUS(Base, Collector):
|
||||
else:
|
||||
self._ovr_rate.pop(chan.name, None)
|
||||
qual = data.get_quality()
|
||||
if self.input_signal:
|
||||
self.input_signal.value = np.round(data.channels[0].binned, 3)
|
||||
if self.output_signal:
|
||||
self.output_signal.value = np.round(data.channels[1].binned, 3)
|
||||
self.input_phase_stddev = qual.input_stddev.imag
|
||||
self.output_phase_slope = qual.output_slope.imag
|
||||
self.output_amp_slope = qual.output_slope.real
|
||||
self.input_amplitude = data.inp.amplitude / 2 ** 15 * self.input_range
|
||||
self.output_amplitude = data.out.amplitude / 2 ** 15 * self.output_range
|
||||
self.input_amplitude = data.inp.amplitude * self.input_range
|
||||
self.output_amplitude = data.out.amplitude * self.output_range
|
||||
self._iq = iq = data.iq * self.output_range / self.input_range
|
||||
self.phase = np.arctan2(iq.imag, iq.real) * 180 / np.pi
|
||||
self.amp = np.abs(iq)
|
||||
@ -351,12 +364,17 @@ class RUS(Base, Collector):
|
||||
def start_acquisition(self):
|
||||
freq = self.freq.read_value()
|
||||
self.sr = round(self.periods * self.adq.sample_rate / freq)
|
||||
self.adq.init(self.sr, 1)
|
||||
self.adq.start(RUSdata(self.adq, freq, self.periods))
|
||||
delay_samples = round(self.input_delay * self.adq.sample_rate * 1e-9)
|
||||
self.adq.init(self.sr + delay_samples, 1)
|
||||
self.adq.start(RUSdata(self.adq, freq, self.periods, delay_samples))
|
||||
self._busy = True
|
||||
self.setFastPoll(True, 0.001)
|
||||
|
||||
|
||||
class Signal(Readable):
|
||||
value = Parameter('pulse', ArrayOf(FloatRange(), maxlen=9999))
|
||||
|
||||
|
||||
class ControlLoop(Module):
|
||||
roi = Attached(Roi)
|
||||
maxstep = Parameter('max frequency step', FloatRange(unit='Hz'), readonly=False,
|
||||
|
Loading…
x
Reference in New Issue
Block a user