frappy_psi.ultrasound: add input_delay and other improvments

Change-Id: I6cb5690d82d96d6775fcb649fc633c4039932463
This commit is contained in:
2025-03-19 15:29:17 +01:00
parent 7c60daa568
commit 8d62375483
3 changed files with 64 additions and 101 deletions

View File

@ -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(