From c7537193da5b48d2e9cd0b85063af037a5c69782 Mon Sep 17 00:00:00 2001 From: terryFitch Date: Tue, 3 Mar 2015 14:17:21 +0100 Subject: [PATCH 01/24] New branch which supports multiple AWGs. AWGs are new MATLAB objects with a interface class AWG. The 'driver class'inherits from this AWG class and implements the hardware specific methods. This commit includes the UNTESTED classes for Tektronix and PXDAC AWGs. The interface is provided by the class VAWG which is a virtual awg and allows mapping of pulsecontrol channels to harware channels on diffrent AWGs. --- @AWG/AWG.m | 157 ++++++++++++ @AWG/waveforms.m | 0 @PXDAC/PXDAC.m | 281 +++++++++++++++++++++ @PXDAC/addPulseGroup.m | 57 +++++ @PXDAC/freeEnoughMemory.m | 41 ++++ @PXDAC/load.m | 63 +++++ @PXDAC/pxdac4800_wrapper.h | 77 ++++++ @PXDAC/registerPulses.m | 48 ++++ @PXDAC/upload.m | 103 ++++++++ @PXDAC/uploadPulsegroupToCard.m | 75 ++++++ @PXDAC_AC/PXDAC_AC.m | 16 ++ @PXDAC_DC/PXDAC_DC.m | 16 ++ @Tek7082/Tek7082.m | 14 ++ @TekAWG/TekAWG.m | 55 +++++ @TekAWG/add.m | 278 +++++++++++++++++++++ @TekAWG/addPulseGroup.m | 128 ++++++++++ @TekAWG/control.m | 154 ++++++++++++ @TekAWG/erase.m | 96 ++++++++ @TekAWG/load.m | 114 +++++++++ @TekAWG/loadPulsesOfGroup.m | 107 ++++++++ @TekAWG/loadwfm.m | 35 +++ @TekAWG/rm.m | 46 ++++ @TekAWG/syncwaveforms.m | 18 ++ @TekAWG/upload.m | 111 +++++++++ @VAWG/VAWG.m | 129 ++++++++++ @VAWG/add.m | 195 +++++++++++++++ AWGPULSEGROUP.m | 14 ++ AWGSTORAGE.m | 137 +++++++++++ PXDACMEMORY.m | 0 PXDACPULSE.m | 72 ++++++ PXDACPULSEGROUP.m | 22 ++ PXDACSEQ.m | 6 + TekPULSEGROUP.m | 21 ++ makeGroupDef.m | 418 ++++++++++++++++++++++++++++++++ plsinfo.m | 81 ++++--- plsmakegrp.m | 24 +- plsplot.m | 19 -- plstotab.m | 25 -- plstowf.m | 5 +- plsupdate.m | 10 +- smcPXDAC.m | 51 ++++ 41 files changed, 3230 insertions(+), 89 deletions(-) create mode 100644 @AWG/AWG.m create mode 100644 @AWG/waveforms.m create mode 100644 @PXDAC/PXDAC.m create mode 100644 @PXDAC/addPulseGroup.m create mode 100644 @PXDAC/freeEnoughMemory.m create mode 100644 @PXDAC/load.m create mode 100644 @PXDAC/pxdac4800_wrapper.h create mode 100644 @PXDAC/registerPulses.m create mode 100644 @PXDAC/upload.m create mode 100644 @PXDAC/uploadPulsegroupToCard.m create mode 100644 @PXDAC_AC/PXDAC_AC.m create mode 100644 @PXDAC_DC/PXDAC_DC.m create mode 100644 @Tek7082/Tek7082.m create mode 100644 @TekAWG/TekAWG.m create mode 100644 @TekAWG/add.m create mode 100644 @TekAWG/addPulseGroup.m create mode 100644 @TekAWG/control.m create mode 100644 @TekAWG/erase.m create mode 100644 @TekAWG/load.m create mode 100644 @TekAWG/loadPulsesOfGroup.m create mode 100644 @TekAWG/loadwfm.m create mode 100644 @TekAWG/rm.m create mode 100644 @TekAWG/syncwaveforms.m create mode 100644 @TekAWG/upload.m create mode 100644 @VAWG/VAWG.m create mode 100644 @VAWG/add.m create mode 100644 AWGPULSEGROUP.m create mode 100644 AWGSTORAGE.m create mode 100644 PXDACMEMORY.m create mode 100644 PXDACPULSE.m create mode 100644 PXDACPULSEGROUP.m create mode 100644 PXDACSEQ.m create mode 100644 TekPULSEGROUP.m create mode 100644 makeGroupDef.m create mode 100644 smcPXDAC.m diff --git a/@AWG/AWG.m b/@AWG/AWG.m new file mode 100644 index 0000000..67a1a80 --- /dev/null +++ b/@AWG/AWG.m @@ -0,0 +1,157 @@ +classdef AWG < handle & matlab.mixin.Heterogeneous + % AWG interface class which provides a generic interface to any + % arbitrary wavfeform generetor. The hardware specific implementation + % is done in derived classes. By deriving from the SuperClass handle + % an AWG instance is always a reference to an object. + + % defining properties of an harware instrument + properties (Constant,Abstract,GetAccess = public) + nChannels + possibleResolutions + end + + % object specific generic AWG properties + properties (GetAccess = public, SetAccess=protected) + identifier; + + %pulse to waveform conversion settings + resolution; %in bits + clk; + offset; + scale; + + storedPulsegroups = AWGSTORAGE(); + activePulsegroup = 'none'; + + end + + properties (GetAccess = public, SetAccess = ?VAWG) + %virtual channel assigned to each hardware channel. + %0 means no virtual channel assigned to the channel + virtualChannels; + end + + % hardware specific methods + methods (Abstract) + %make this pulsegroup playable by AWG + addPulseGroup(self,grpdef); + + %remove this pulsegroup from memory and forget about it + removePulseGroup(self,name); + + %update the changed pulses + updatePulseGroup(self,grpdef); + + %wait for trigger + arm(self); + + %this function is for debugging purposes + issueSoftwareTrigger(self); + +% %add a pulsegroup by name to the pulsegroups playable by this +% %machineuse is/ deprecated +% add(self,pulsegroup) +% +% load(self,grp, ind) +% +% erase(self,groups,options) +% +% syncwaveforms(self) +% +% rm(self,grp, ctrl) + end + + % Set methods controlling if argument is valid + methods + function obj = AWG(id) + obj.identifier = id; + + obj.virtualChannels = zeros(1,obj.nChannels); + + obj.offset = zeros(1,obj.nChannels); + obj.scale = ones(1,obj.nChannels); + obj.resolution = obj.possibleResolutions(1); + end + + function virtChan = getVirtualChannel(self,chan) + virtChan = self.virtualChannels(chan); + if virtChan == 0 + virtChan = []; + end + end + + function hardwareChannels = getHardwareChannel(self,virtualChannel) + if ~isscalar(virtualChannel) + error('Can not convert multiple virt channels at once, since one may refer to multiple harware channels.'); + end + hardwareChannels = find( self.virtualChannels == virtualChannel ); + end + + function setActivePulsegroup(self,pulsegroupName) + if isKey(self.storedPulsegroups,pulsegroupName) + self.activeSequence = pulsegroupName; + else + error('Can not activate pulsegroup "%s". Since it is not known',sequenceName); + end + end + + % set mapping function: output = scale * x + offset + function setVoltageMapping(this,channel,newScale,newOffset) + if channel>this.nChannels + error('Unknown channel %i',channel); + end + this.scale(channel) = newScale; + this.offset(channel) = newOffset; + end + + % set output resolution to bits if it is allowed + function setResolution(self,bits) + + if find(self.possibleResolutions==bits) + self.resolution = bits; + else + disp(bits) + error('%s does not support this resolution.',self.type) + end + end + + % set zerolen to positive pulselength if pulse is zero, otherwise + % to negative pulselength + function zerolen = zeroLength(self,grp,ind,zerolen) + + if isempty(ind) + ind = 1:length(grp.pulses); + end + + epsilon = self.scale/2^(self.resolution-1); + for i = 1:length(grp.pulses) + + dind = find([grp.pulses(i).data.clk] == self.clk); + npts = size(grp.pulses(i).data(dind).wf, 2); + + for virtChan=1:size(grp.pulses(i).data(dind).wf,1) + + hardChan = self.getHardwareChannel(grp.chan(virtChan)); + + if hardChan + + if any(abs(grp.pulses(i).data(dind).wf(virtChan,:)) > epsilon(hardChan) ) + zerolen(ind(i),virtChan) = -npts; + else + zerolen(ind(i),virtChan) = npts; + end + + end + end + + end + + end + + end + + methods (Static) + + end + +end \ No newline at end of file diff --git a/@AWG/waveforms.m b/@AWG/waveforms.m new file mode 100644 index 0000000..e69de29 diff --git a/@PXDAC/PXDAC.m b/@PXDAC/PXDAC.m new file mode 100644 index 0000000..9cf5439 --- /dev/null +++ b/@PXDAC/PXDAC.m @@ -0,0 +1,281 @@ +classdef PXDAC < AWG + properties (Constant,GetAccess = public) + nChannels = 4; + possibleResolutions = 14; %may implement 8 if needed + + %in bytes + totalMemory = 2^30; + + % memory manager chunk size + minimalChunkSize = 2^13; + end + + properties (Constant,Abstract,GetAccess = public) + allowedVoltageRange; + end + + properties (SetAccess = protected, GetAccess = public) + handle; + serialNumber; + + + + % wfMap; % equivalent to wf on AWG + + % Map to structs with fields + % start, length (position in memory) + % waveforms (array of PXDACPULSE) + % inherited storedPulsegroups; + + + %if the ChannelMask changes the whole RAM is dropped + activeChannelMask; + end + + methods (Access = protected) + % constructor + function obj = PXDAC(id,index) + obj = obj@AWG(id); + + %check architecture + if ~strcmpi(computer('arch'), 'win64') + error('This driver only supports 64 bit windows'); + end + + %load library + if ~libisloaded('PXDAC4800_64') + loadlibrary('PXDAC4800_64.dll', 'pxdac4800_wrapper.h'); + end + + + %check if device is present + deviceCount = calllib('PXDAC4800_64','GetDeviceCountXD48'); + if deviceCount == 0 + error('No device present.'); + elseif index>deviceCount + error('Device %i is not present. There are only %i devices.',index,deviceCount); + end + + obj.handle = libpointer('ulongPtr',uint32(0)); + + % connect + obj.library('ConnectToDeviceXD48',... + obj.handle,... + index); + + + temp = libpointer('uint32Ptr', uint32(0)); + obj.library('GetSerialNumberXD48',obj.handle,temp); + obj.serialNumber = get(temp); + obj.serialNumber = obj.serialNumber.Value; + clear('temp'); + + % init with defaults + % obj.resetToPowerupDefault(); + % + obj.activeChannelMask = uint16(15); + + obj.clk = 1.2e9; + + obj.library('SetTriggerModeXD48',obj.handle,2);%single shot trigger mode + + if ~libisloaded('PXDACMemoryManager') + loadlibrary('C:\Users\humpohl\Documents\Visual Studio 2013\Projects\PXDACMemoryManager\x64\Debug\PXDACMemoryManager.dll','C:\Users\humpohl\Documents\Visual Studio 2013\Projects\PXDACMemoryManager\PXDAC_memory_manager.h') + end + + %initialize memory manager + chunkexponent = 22; %2^22 * sizeof(U16) ~ 8 MB + maxsamples = 2^29; % 1 GB + + %make chunk size smaller as long as allocation fails + %bigger chunks are much faster to upload but are not as easy to + %create for the operating system + status = -1; + while status~=0 + chunksamples = 2^chunkexponent; + + status = calllib('PXDACMemoryManager','initializeU16',obj.handle,uint32(maxsamples),uint32(chunksamples)); + chunkexponent = chunkexponent-1; + end + fprintf('Initialized memory manager with 2^%d samples per chunk.\n', chunkexponent); + + + fprintf('%s: successfully connected to %s (your mother) with serial number %d\n',id,class(obj),obj.serialNumber); + end + + registerPulses(self,grp); + + end + + methods + function delete(self) + fprintf('Deleting PXDAC %s\n',self.identifier); + + status = calllib('PXDACMemoryManager','free_memory'); + if status + warning('Failure "%s" while freeing PXDAC manager memory.',PXDAC.statusToErrorMessage(status)); + end + + fprintf('Disconnecting from device...\n') + status = calllib('PXDAC4800_64','DisconnectFromDeviceXD48',self.handle); + if status + warning('Error "%s" while disconnecting from device %d',PXDAC.statusToErrorMessage(status),self.serialNumber); + else + fprintf('Successfully disconnected from %d\n',self.serialNumber); + end + end + + function setChannelMask(self,mask) + if mask~=self.activeChannelMask + warning('Channel mask changed. All stored pulsegroups will be removed.'); + self.clearBoardMemory(); + self.activeChannelMask = mask; + self.library('SetActiveChannelMaskXD48',self.handle,mask); + end + end + + %free board memory to have enough space and return start position + [start, index] = freeEnoughMemory(self,requiredize) + + uploadPulsegroupToCard(self,name) + + + + + + function setActivePulsegroup(self,pulsegroupName) + if isKey(self.storedPulsegroups,pulsegroupName) + self.activePulsegroup = pulsegroupName; + if isempty(self.storedPulsegroups(pulsegroupName).start) + warning('The pulsegroup %s is set active, but is not in PXDAC memory. Make sure to upload the pulsegroup before starting playback.',sequenceName); + end + else + error('Can not activate pulsegroup "%s". Since it is not known.',sequenceName); + end + end + + + % implemented abstact methods + %remove this pulsegroup from memory and forget about it + function removePulseGroup(self,name) + if strcmp(self.activePulsegroup,name) + self.activePulsegroup = 'none'; + end + self.storedPulsegroups.remove(name); + end + + %update the changed pulses + function updatePulseGroup(self,grpdef) + error('notimplemented'); + end + + %wait for trigger + function arm(self) + self.waitForTrigger(); + end + + %this function is for debugging purposes + function issueSoftwareTrigger(self) + error('not implemented'); + end + + + function clearBoardMemory(self) + if ~isempty(self.storedPulsegroups) + self.storedPulsegroups(1:end).start = []; + end + self.activePulsegroup = 'none'; + self.activeChannelMask = uint16(0); + end + + + function waitForTrigger(self) + + if strcmp(self.activePulsegroup,'none') + error('No pulsegroup activated on %s.',self.identifier); + elseif ~isKey(self.storedPulsegroups,self.activePulsegroup) + error('The pulsegroup %s is not known to %s although it is set as active pulsegroup. This hints a bug.',self.activePulsegroup,self.identifier); + end + + self.uploadPulsegroupToCard(self.activePulsegroup); + + pulsegroup = self.storedPulsegroups(self.activePulsegroup); + if pulsegroup.repetitions == Inf + pulsegroup.repetitions = 0; + end + + status = uint32(0); + self.library('GetFPGAStatusXD48',... + self.handle,... + status); + if status > 0 + fprintf('Recieved suspicious status from card.'); + if bitget(status,7) + error('Data playback in progress.') + end + end + + + + self.library('SetExternalTriggerEnableXD48',... + self.handle,int32(1)); + + self.library('SetTriggerModeXD48',... + self.handle,... + int32(2));% XD48TRIGMODE_SINGLE_SHOT (2) Trigger runs memory data once; subsequent triggers ignored + + + self.library('BeginRamPlaybackXD48',... + self.handle,... + uint32(self.acitveSequence.start),... + uint32(self.acitveSequence.length),... + uint32(self.acitveSequence.length*self.acitveSequence.repetitions)); + end + + function setOutputVoltage(self,channel,ppVoltage) + x = (ppVoltage-self.allowedVoltageRange(1))/(self.allowedVoltageRange(2)-self.allowedVoltageRange(1)); + if x > 1 + error('Voltage %d to large',ppVoltage); + elseif x < 0 + error('Voltage %d to small',ppVoltage); + end + + + self.library(sprintf('SetOutputVoltageCh%iXD48',channel),... + self.handle,... + int32(x*1023)); + end + + function ppVoltage = getOutputVoltage(self,channel) + %output voltage int + ppVoltageInt = calllib('PXDAC4800_64',sprintf('GetOutputVoltageCh%iXD48',channel),... + self.handle,0); + + %convert to double + temp = libpointer('double',zeros(1,1)); + self.library('GetOutputVoltageRangeVoltsXD48',... + ppVoltageInt,... + temp,self.handle) + ppVoltage = temp.Value; + end + end + + methods (Static) + function errormsg = statusToErrorMessage(status) + errormsg = calllib('PXDAC4800_64','GetErrorMessXD48',... + status,... + libpointer('stringPtr'),0,libpointer('voidPtr',[])); + error(errormsg); + end + + function testStatus(status) + if status ~= 0 + error(statusToErrorMessage); + end + end + + function library(fn, varargin) + PXDAC.testStatus( calllib('PXDAC4800_64', fn, varargin{:}) ); + end + end +end \ No newline at end of file diff --git a/@PXDAC/addPulseGroup.m b/@PXDAC/addPulseGroup.m new file mode 100644 index 0000000..9b28b96 --- /dev/null +++ b/@PXDAC/addPulseGroup.m @@ -0,0 +1,57 @@ +function addPulseGroup(self,grpdef) + +if ~isfield(grpdef, 'pulseind') || ~isfield(grpdef, 'repetitions') || ~isfield(grpdef,'pulses') || ~isfield(grpdef,'name') + error('missing field'); +end + +if ~isfield(grpdef,'ctrl') + grpdef.ctrl = ''; +end + +if length(grpdef.nrep)~=length(grpdef.pulseind) + error('dim mismatch'); +end + +% if self.storedPulsegroups.isKey(grpdef.name) +% warning('Ignoring stored pulsegroup %s for now.',grpdef.name); +% return; +% end + + +if isfield(grpdef, 'jump') + % this error should not be ignored since jumps on a TekAWG may be + % executed + error('The only situation where jump makes sense on PXDAC is for reordering. This is not implemented.') +end + +usetrig = isempty(strfind(grpdef.ctrl, 'notrig')); +if usetrig + warning('PXDAC has no trigger capability enabled yet'); +end + +if any(grpdef.nrep == Inf) || any(grpdef.nrep == 0) + error('infinitive loop not supported by PXDAC.'); +end + +% stores/caches the pulsedata in interleaved form for faster upload +self.registerPulses(grpdef); + +npls = size(grpdef.pulseind, 2); +for i = 1:npls + %too stupid to solve without for loop + self.storedPulsegroups(grpdef.name).pulseSequence(i).index = grpdef.pulseind(i); + self.storedPulsegroups(grpdef.name).pulseSequence(i).nrep = grpdef.nrep(i); +end + +% perform the actual upload on board memory +if strfind(grpdef.ctrl,'loop') + repeats = 0; +else + repeats = 1; +end + +self.storedPulsegroups(grpdef.name).repetitions = repeats; + +self.uploadPulsegroupToCard(grpdef.name); + +end \ No newline at end of file diff --git a/@PXDAC/freeEnoughMemory.m b/@PXDAC/freeEnoughMemory.m new file mode 100644 index 0000000..56db097 --- /dev/null +++ b/@PXDAC/freeEnoughMemory.m @@ -0,0 +1,41 @@ +function [start, index] = freeEnoughMemory(self,requiredSize) + uploadedGroups = find( ~isempty(self.storedPulsegroups.mData.start) ); + + %simple algorithm: delete the oldest groups until there is enough + %memory + + if isempty(uploadedGroups) + start = 0; index = 1; + return; + end + + alignment = 2^13; + + while true + start = 0; + index = 1; + for index = 1:uploadedGroups + if self.storedPulsegroups.mData(index).start-start < requiredSize + start = uint32( ceil( double(self.storedPulsegroups.mData(index).start + self.storedPulsegroups.mData(index).start)/double(alignment) ) * alignment ); + else + %found enough free memory + break; + + end + end + + if self.totalMemory - start >= requiredSize + %found enough free memory + break; + end + + oldestGroup = find( storedPulsegroups.mData(uploadedGroups).lastActivation == min(storedPulsegroups.mData(uploadedGroups).lastActivation) ); + + self.storedPulsegroups.mData(oldestGroup).start = []; + uploadedGroups(oldestGroup) = []; + + if isempty(uploadedGroups) + error('This is a bug or the pulsegroup is too large'); + end + end +end \ No newline at end of file diff --git a/@PXDAC/load.m b/@PXDAC/load.m new file mode 100644 index 0000000..b50c621 --- /dev/null +++ b/@PXDAC/load.m @@ -0,0 +1,63 @@ +function load(self,grp,ind) +fprintf('PXDAC.load\n') + +% ??? +dind=find([grp.pulses(1).data.clk] == self.clk); + + +% fixme; emit an error if this changes zerochan and wlist.size ~= 25. +% The screwiness here is to get each channel with a unique offset/scale combo +[~, offsetchan] = unique(self.offset./self.scale); +offsets=self.offset(offsetchan); + +channelMask = uint16(sum(2.^(self.getHardwareChannel(grp.chan)-1))); + +%create pulsegroup object +if ~isKey(self.storedPulsegroups,grp.name) + self.storedPulsegroups.add( PXDACPULSEGROUP(grp.name) ); +end + + +%reserve memory (at least i hope so) +self.storedPulsegroups(grp.name).waveformArray = repmat( struct('pulse',PXDACPULSE.empty(0,0),'repetitions',0), 1,length(grp.pulses) ); + +for i = 1:length(grp.pulses) + + + pulse = PXDACPULSE(channelMask,size(grp.pulses.data.wf,2)); + + + for virtChan = 1:size(grp.pulses(i).data(dind).wf, 1) + + hardChan = self.getHardwareChannel(grp.chan(virtChan)); + + %skip virtual channels not belonging to this AWG + if isempty(hardChan) + continue; + end + + %map to interval [0,2] + data = ((self.offset(min(hardChan,end)) + grp.pulses(i).data(dind).wf(virtChan, :))./self.scale(hardChan) + 1); + + %convert to uint16 0-2^14-1 + int16wf = uint16(min(... + data*(2^(14-1) - 1),... + 2^(14)-1)); + + pulse.writeToChannel(hardChan,int16wf); + + + end + + self.storedPulsegroups(grp.name).waveformArray(i).pulse = pulse; + +end + +self.storedPulsegroups(grp.name).lastload = now; + + +end + + + + diff --git a/@PXDAC/pxdac4800_wrapper.h b/@PXDAC/pxdac4800_wrapper.h new file mode 100644 index 0000000..19907d4 --- /dev/null +++ b/@PXDAC/pxdac4800_wrapper.h @@ -0,0 +1,77 @@ +#ifndef PXDAC4800WRAPPER +#define PXDAC4800WRAPPER + +#define AWGAPI + +typedef unsigned long* HXD48; +typedef struct _XD48S_CYCLE_CALC_CTX_tag +{ + unsigned int struct_size; + unsigned int flags; // XD48CYCLECALCF_* + unsigned int max_samples; // 0,Default: Maximal + unsigned int align_override; // 0,Default: 64 bytes + double dDacDataRateMHz; // 0,Default: Board's current + // -- Used when finding closest match + double dSearchAlignMHz; // 0, Default: ~100KHz + double dSearchDeltaMHz; // 0, Default: 128KHz + double dMaxDeviationMHz; // 0, Default: No max + double dClosestPPC; // out: Closest points-per-cycle + double dClosestMHz; // out: Output frequency for match +} XD48S_CYCLE_CALC_CTX; + +char* GetErrorMessXD48(int res, char* bufp, int flags, HXD48 hBrd); + +int GetMaxByteCountForActiveChanMaskXD48 (HXD48 hBrd, int chan_mask, unsigned int* pmax_bytes); + +int AllocateDmaBufferXD48 (HXD48 hBrd, unsigned int bytes, void** bufpp); + +int GetDeviceCountXD48(); + +int ConnectToDeviceXD48(HXD48* phDev, unsigned int brdNum); +int DisconnectFromDeviceXD48 (HXD48 hBrd); + +int GetSerialNumberXD48 (HXD48 hBrd, unsigned int* snp); + +int SetPowerupDefaultsXD48(HXD48 hBrd); + +int SetActiveChannelMaskXD48(HXD48 hBrd, int val); + +int FreeDmaBufferXD48 (HXD48 hBrd, void* bufp); + +int IsDcXD48 (HXD48 hBrd); + +// trigger configuration +int SetTriggerModeXD48(HXD48 hBrd, int val); +int SetExternalTriggerEnableXD48(HXD48 hBrd, int bEnable); + + +// helper functions +int CalculateCycleCountsXD48( + HXD48 hBrd, + double dPtsPerCycle, + unsigned int* pSampleCount, + XD48S_CYCLE_CALC_CTX* ctxp ); // = 0 is valid +int InterleaveData16bit2ChanXD48( + const unsigned short* src_ch1p, + const unsigned short* src_ch2p, + unsigned int samps_per_chan, + unsigned short* dstp); +int InterleaveData16bit4ChanXD48( + const unsigned short* src_ch1p, + const unsigned short* src_ch2p, + const unsigned short* src_ch3p, + const unsigned short* src_ch4p, + unsigned int samps_per_chan); + + +int LoadRamBufXD48( HXD48 hBrd, + unsigned int offset_bytes, + unsigned int length_bytes, + const void* bufp, + int bAsynchronous); +int IssueSoftwareTriggerXD48(HXD48 hBrd); + + + + +#endif diff --git a/@PXDAC/registerPulses.m b/@PXDAC/registerPulses.m new file mode 100644 index 0000000..cd5c1c0 --- /dev/null +++ b/@PXDAC/registerPulses.m @@ -0,0 +1,48 @@ +function registerPulses(self,grp) + usedHWchanels = []; + for c = grp.chan + usedHWchanels = [usedHWchanels self.getHardwareChannel(c)]; + end + channelMask = uint16(sum(2.^(usedHWchanels-1))); + + %create pulsegroup object + if ~isKey(self.storedPulsegroups,grp.name) + self.storedPulsegroups.add( PXDACPULSEGROUP(grp.name) ); + end + + dind = find([grp.pulses(1).data.clk] == self.clk); + + %reserve memory (at least i hope so) + self.storedPulsegroups(grp.name).waveformArray = repmat( PXDACPULSE.empty(0,0), 1,length(grp.pulses) ); + + for i = 1:length(grp.pulses) + + + pulse = PXDACPULSE(channelMask,size(grp.pulses(i).data.wf,2)); + + for virtChan = 1:size(grp.pulses(i).data(dind).wf, 1) + + for hardChan = self.getHardwareChannel(grp.chan(virtChan)); + + %map to interval [0,2] + data = ((self.offset(min(hardChan,end)) + grp.pulses(i).data(dind).wf(virtChan, :))./self.scale(hardChan) + 1); + + %convert to uint16 0-2^14-1 + int16wf = uint16(min(... + data*(2^(14-1) - 1),... + 2^(14)-1)); + + pulse.writeToChannel(hardChan,int16wf); + + end + + + end + + self.storedPulsegroups(grp.name).waveformArray(i) = pulse; + + end + + + + end \ No newline at end of file diff --git a/@PXDAC/upload.m b/@PXDAC/upload.m new file mode 100644 index 0000000..c2aec90 --- /dev/null +++ b/@PXDAC/upload.m @@ -0,0 +1,103 @@ +function upload(self,name) + +global plsdata; + + +if ~iscell(name) + name = {name}; +end + +for k = 1:length(name) + + if(~isstruct(name{k})) + zerolen = []; % avoid using zerolen from previous group. + plslog=[]; + load([plsdata.grpdir, 'pg_', name{k}]); + else + grpdef=name{k}; + end + + if exist('plslog','var') && length(plslog) > 100 + fprintf('Group %s has %d log entries.\n',name{k},length(plslog)); + end + if exist('plslog','var') && ~isempty(plslog) && ~isempty(opts.time) + le=plsinfo_logentry(plslog,opts.time); + grpdef.params=plslog(le).params; + grpdef.matrix=plslog(le).matrix; + grpdef.varpar=plslog(le).varpar; + grpdef.offset=plslog(le).offset; + grpdef.dict=plslog(le).dict; + grpdef.readout=plslog(le).readout; + end + + + pack = false;%~isempty(strfind(grpdef.ctrl,'pack')); + + if ~ isfield(grpdef, 'varpar') + grpdef.varpar = []; + end + if ~ isfield(grpdef, 'params') + grpdef.params = []; + end + if isfield(grpdef, 'time')&& ~isempty(grpdef.time) + fprintf('Ignoring opts.time\n'); + opts.time=grpdef.time; + + end + + if plsinfo('stale',grpdef.name) + % modified since last upload + + + + + % Actually handle the upload... + zerolen = self.load(grpdef, ind); + + + % save update time in log. + plslog(end+1).time = now; + plslog(end).params = grpdef.params; + plslog(end).matrix = grpdef.matrix; + plslog(end).varpar = grpdef.varpar; + plslog(end).offset = grpdef.offset; + + if isfield(grpdef.pulses(1).data,'readout') + readout=[]; + for ll=1:length(grpdef.pulses) + if ~isempty(grpdef.pulses(ll).data(1).readout) + readout(:,:,ll) = grpdef.pulses(ll).data(1).readout; + end + end + if any(abs(diff(readout,[],3)) > 1e-10) + warning('Readout changes between pulses in %s\n',name{k}); + end + if(size(readout,1) > 0) + plslog(end).readout = readout(:,:,1); + else + plslog(end).readout=[]; + end + end + + if isfield(grpdef, 'dict') + plslog(end).dict = grpdef.dict; + end + + if isfield(grpdef, 'trafofn') + plslog(end).trafofn = grpdef.trafofn; + end + + if length(plslog) > 2 % copy in case not all pulses updated. First plslog has no xval + plslog(end).xval = plslog(end-1).xval; + end + %plslog(end).xval(:, ind) = vertcat(grpdef.pulses.xval)'; + plslog(end).xval = vertcat(grpdef.pulses.xval)'; % temporary bug fix + plslog(end).ind = ind; + + save([plsdata.grpdir, 'pg_', name{k}], '-append','-v6', 'plslog', 'zerolen'); + logentry('Uploaded group %s, revisions %i.', grpdef.name, length(plslog)); + % fprintf(' in upload of group %s.\n', grpdef.name); + else + fprintf('Skipping group %s.\n', grpdef.name); + end +end \ No newline at end of file diff --git a/@PXDAC/uploadPulsegroupToCard.m b/@PXDAC/uploadPulsegroupToCard.m new file mode 100644 index 0000000..6ecc5cd --- /dev/null +++ b/@PXDAC/uploadPulsegroupToCard.m @@ -0,0 +1,75 @@ +function uploadPulsegroupToCard(self,name) + + + +if ~ischar(name) + error('No valid sequence name provided'); +end + +if ~isKey(self.storedPulsegroups,name) + error('Pulsegroup %s can not be loaded into memory because it does not exist.',name); +end + +pulsegroup = self.storedPulsegroups(name); +waveformArray = self.storedPulsegroups(name).waveformArray; + +if ~isempty(pulsegroup.start) + if pulsegroup.lastMemoryUpdate > pulsegroup.lastload + fprintf('pulsegroup %s already uploaded.\n',name); + return; + else + fprintf('pulsegroup %s is outdated and will be reloaded.\n',name); + pulsegroup.start = []; + end +end + + +%check mask +if waveformArray(1).channelMask ~= self.activeChannelMask + self.setChannelMask(waveformArray(1).channelMask); +end + + +totalByteSize = [waveformArray(pulsegroup.pulseSequence.index).byteSize]*[pulsegroup.pulseSequence.nrep]'; + + + +%find free coherent space in memory +[start,index] = self.freeEnoughMemory( totalByteSize ); + + +if isempty(index) || isempty(start) + error('Out of board memory. Please erase some sequences from board.'); +end + +% +self.storedPulsegroups.move(name,index); + +self.storedPulsegroups(name).start = start; +self.storedPulsegroups(name).totalByteSize = totalByteSize; + +for playedPulse = pulsegroup.pulseSequence + + pulse = waveformArray(playedPulse.index); + %check channelMask + if pulse.channelMask ~= self.activeChannelMask + error('Channel masks are not consistent.'); + end + wfSize = pulse.byteSize; + + for i = 1:playedPulse.nrep + + calllib('PXDACMemoryManager','writeRAW',... + uint32(start),... + uint32(wfSize),... + pulse.rawData); + + + + start = start + wfSize; + end +end +PXDAC.testStatus( calllib('PXDACMemoryManager','synchronize',false) ); +self.storedPulsegroups(name).lastMemoryUpdate = now; + +end \ No newline at end of file diff --git a/@PXDAC_AC/PXDAC_AC.m b/@PXDAC_AC/PXDAC_AC.m new file mode 100644 index 0000000..6ea35e1 --- /dev/null +++ b/@PXDAC_AC/PXDAC_AC.m @@ -0,0 +1,16 @@ +classdef PXDAC_AC < PXDAC + properties (Constant,GetAccess = public) + allowedVoltageRange = [0.47 1.450]; + end + + methods + function obj = PXDAC_AC(id,index) + obj = obj@PXDAC(id,index); + + %check version + if calllib('PXDAC4800_64','IsDcXD48',obj.handle) + error('Detected a DC coupled card in PXDAC_AC constructor.'); + end + end + end +end \ No newline at end of file diff --git a/@PXDAC_DC/PXDAC_DC.m b/@PXDAC_DC/PXDAC_DC.m new file mode 100644 index 0000000..fa2a7ff --- /dev/null +++ b/@PXDAC_DC/PXDAC_DC.m @@ -0,0 +1,16 @@ +classdef PXDAC_DC < PXDAC + properties (Constant,GetAccess = public) + allowedVoltageRange = [0.400 1.450]; + end + + methods + function obj = PXDAC_DC(id,index) + obj = obj@PXDAC(id,index); + + %check version + if ~calllib('PXDAC4800_64','IsDcXD48',obj.handle) + error('Detected a AC coupled card in PXDAC_DC constructor.'); + end + end + end +end \ No newline at end of file diff --git a/@Tek7082/Tek7082.m b/@Tek7082/Tek7082.m new file mode 100644 index 0000000..d77bf1f --- /dev/null +++ b/@Tek7082/Tek7082.m @@ -0,0 +1,14 @@ +classdef Tek7082 < TekAWG + properties (Constant,GetAccess = public) + nChannels = 2; + possibleResolutions = 14; + end + + methods + %constructor + function obj = Tek7082(id,handle) + obj = obj@TekAWG(id,handle); + obj.resolution = obj.possibleResolutions(1); %in bits + end + end +end \ No newline at end of file diff --git a/@TekAWG/TekAWG.m b/@TekAWG/TekAWG.m new file mode 100644 index 0000000..d5de81c --- /dev/null +++ b/@TekAWG/TekAWG.m @@ -0,0 +1,55 @@ +classdef TekAWG < AWG + + properties (GetAccess = public, SetAccess = protected) + handle; + + triglen = 1000; + zeropls; % length of zeropulses stored on the awg + + zerochan; % !!! functinoality not clear + + + seqpulses = []; % !!! functinoality not clear + + waveforms = {}; + end + + methods + % constructor + function obj = TekAWG(id,handle) + obj = obj@AWG(id); + obj.handle = handle; + + obj.clk = 1.2e9; + + obj.zerochan = ones(1,obj.nChannels); + %check for global awgdata + end + + function last = lastFreeSequenceLine(self) + if isempty(self.storedPulsegroups) + last = 1; + return; + end + + last = self.storedPulsegroups(end).seqind + sum(self.storedPulsegroups(end).nline); + end + + + % implemented abstact methods + add(self,pulsegroup) + + val = control(self,cntrl, chans) + + erase(self,groups,options) + + syncwaveforms(self) + + upload(self,name) + + + rm(self,grp, ctrl) + + loadwfm(self,data, marker, name, chan,define) + end +end \ No newline at end of file diff --git a/@TekAWG/add.m b/@TekAWG/add.m new file mode 100644 index 0000000..1f1deef --- /dev/null +++ b/@TekAWG/add.m @@ -0,0 +1,278 @@ +function add(self,groups) +% awgadd(groups) +% Add groups to end of sequence. Store group name and target index in +% awgdata.pulsgroups.name, seqind. + +% Group control 'seq' creates sequence combined groups +% ------------------------------------------------------------------------- +% Add groups like: pg.pulses.groups = {'group_1', 'group_2', 'group_4'}; +% At least one of the subgroup names should be the same name as the group +% name itself followed by an underscore and a number, e.g. +% pg.name = 'groups' for the above example of pg.pulses.groups. +% Set group control: pg.ctrl = 'loop seq'; +% Set order of pulses froms groups with pg.pulseind. Subgroups are indexed +% by row, pulses of the group are indexed by column. The value gives the +% number of pulse to use from original subgroup. A zero indicates not to +% use a pulse from the corresponding group in the respective position. For +% 8 pulses per group this might look like: +% pg.pulseind(1,:) = [1:8 zeros(1,16)]; +% pg.pulseind(2,:) = [zeros(1,8) 1:8 zeros(1,8)]; +% pg.pulseind(3,:) = [zeros(1,16) 1:8]; +% ------------------------------------------------------------------------- +% +% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. + +global plsdata; + +% astart=toc; +self.control('clr'); +self.control('stop'); + +if ~iscell(groups) + groups = {groups}; +end + +dosave = false; % keeps track of whether awgdata changed. +% gstart=toc; + + +for k = 1:length(groups) + load([plsdata.grpdir, 'pg_', groups{k}]); + + while plsinfo('stale',groups{k}) + fprintf('Latest pulses of group %s not loaded; %s > %s.\n', groups{k}, ... + datestr(lastupdate), datestr(plslog(end).time(end))); + % tstart=toc; + plsmakegrp(groups{k},'upload'); + % ts2 = toc; + self.control('wait'); + % fprintf('Load time=%f secs, wait time=%f\n',toc-tstart,toc-ts2); + load([plsdata.grpdir, 'pg_', groups{k}]); + end + + if strcmp(grpdef.ctrl(1:min([end find(grpdef.ctrl == ' ', 1)-1])), 'grp')... + && ~isempty(strfind(grpdef.ctrl, 'seq')) % combine groups at sequence level. + + % retrieve channels of component groups + clear chan; + for m = 1:length(grpdef.pulses.groups) + gd=plsinfo('gd', grpdef.pulses.groups{m}); + rf={'varpar', 'pulseind', 'time'}; % Required fields that may be missing + for qq=1:length(rf) + if ~isfield(gd,rf{qq}) + gd=setfield(gd,rf{qq},[]); + end + end + chan(m) = orderfields(gd); + end + chan = {chan.chan}; + seqmerge = true; + else + if ~isfield(grpdef, 'pulseind') + zerolen = plsinfo('zl', grpdef.name); % hack TB + grpdef.pulseind = 1:size(zerolen, 1); + end + + seqmerge = false; + end + + if ~isfield(grpdef, 'nrep') + grpdef.nrep = 1; + end + + + npls = size(grpdef.pulseind, 2); + nchan = self.nChannels; % alternatively use awgdata or data size + usetrig = (grpdef.nrep(1) ~= Inf) && isempty(strfind(grpdef.ctrl, 'notrig')); + + + + if ~iskey(self.storedPulsegroups,grpdef.name) % group is loaded -> update + error('awg.load should create a pulsegroup entry with the lastload time.'); + end + + existentNeededFields = sum( isfield(self.storedPulsegroups(grpdef.name),{'seqind','npulse','nline'}) ); + if ~isempty( self.storedPulsegroups(grpdef.name).seqind ) + + startline = self.storedPulsegroups(grpdef.name).seqind; + if npls + usetrig ~= sum(self.storedPulsegroups(grpdef.name).npulse); + error('Number of pulses changed in group %s. Use awgrm first!', grpdef.name); + end + + if strfind(grpdef.ctrl,'pack') + zlmult = npls; + npls=1; + else + zlmult=1; + end + + if isfield(plslog, 'readout') && exist('zerolen', 'var') + if any(self.storedPulsegroups(grpdef.name).nrep ~= grpdef.nrep) || ... + any(any(self.storedPulsegroups(grpdef.name).readout ~= plslog(end).readout)) || ... + any(any(self.storedPulsegroups(grpdef.name).zerolen ~= zerolen)) % nrep or similar changed + dosave = 1; + end + else + if any(self.storedPulsegroups(grpdef.name).nrep ~= grpdef.nrep) % nrep changed + dosave = 1; + end + end; + + else + % group loaded for the first time + startline = self.lastFreeSequenceLine(); + + self.storedPulsegroups(grpdef.name).seqind = startline; + self.storedPulsegroups(grpdef.name).npulse = [npls usetrig]; + self.storedPulsegroups(grpdef.name).nline = []; + + + if strfind(grpdef.ctrl,'pack') + self.storedPulsegroups(grpdef.name).nline = 1+usetrig; + % Hack alert; way too much code assumes zl == pulselen. For + % packed groups, we ignore it and work out the correct length + % ourselves. + zlmult=npls; + npls=1; + else + zlmult=1; + self.storedPulsegroups(grpdef.name).nline = npls+usetrig; + end + fprintf(self.handle, sprintf('SEQ:LENG %d', startline + self.storedPulsegroups(grpdef.name).nline-1)); + dosave = 1; + + end + + + if ~isfield(grpdef, 'jump') + if strfind(grpdef.ctrl, 'loop') + grpdef.jump = [npls; 1]; + else + grpdef.jump = []; + end + end + + + self.storedPulsegroups(grpdef.name).nrep = grpdef.nrep; + self.storedPulsegroups(grpdef.name).lastload = plslog(end).time(1); + + % Added block below to fix 'seq', where zerolen and plslog(end).readout + % are not available 02.05.2014 PC + if ~exist('zerolen', 'var') + zerolen = []; + for groupInd = 1:length(grpdef.pulses.groups) + tempGrp = load([plsdata.grpdir, 'pg_', grpdef.pulses.groups{groupInd}]); + zerolen = vertcat(zerolen, tempGrp.zerolen); + end + plslog(end).readout = tempGrp.plslog(end).readout; + self.storedPulsegroups(grpdef.name).lastload = now; + clear tempGrp groupInd + end; + + self.storedPulsegroups(grpdef.name).zerolen = zerolen; % Cache some handy stuff here. + self.storedPulsegroups(grpdef.name).readout = plslog(end).readout; + + if usetrig + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV1 "trig_%08d"', startline, self.triglen)); + for j = 2:nchan + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', startline, j, self.triglen, self.zerochan(j))); + end + if isfield(self,'slave') && ~isempty(self.slave) && (self.slave) + fprintf(self.handle, sprintf('SEQ:ELEM%d:TWAIT 1\n', startline)); + end + end + + + for i = 1:npls + ind = i-1 + startline + usetrig; + if ~seqmerge % pulses combined here. + for j = 1:nchan + ch = j; %self.getHardwareChannel(grpdef.chan(nchan)); + if ~isempty(ch) && zerolen(grpdef.pulseind(i), ch) < 0 + % channel in group and not zero + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV%d "%s_%05d_%d"', ind, j, ... + grpdef.name, grpdef.pulseind(i), ch)); + else + % hack alert. We should really make zerolen a cell array. fixme. + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', ind, j, ... + zlmult*abs(zerolen(grpdef.pulseind(i), 1)),self.zerochan(j))); % think of a way to make clocks sync + end + end + else % completely overhauled 02.05.2014 PC + + error('implement'); + for m = 1:length(grpdef.pulses.groups) + for j = 1:nchan % channels of component groups + ch = getHardwareChannel(chan{m}); + if grpdef.pulseind(m, i) > 0 % Do not add if pulseind == 0 + if ~isempty(ch) && zerolen(grpdef.pulseind(m, i), ch) < 0 + % channel in group and not zero + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV%d "%s_%05d_%d"', ind, j, ... + grpdef.pulses.groups{m}, grpdef.pulseind(m, i), ch)); + else + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', ind, j, ... + zlmult*abs(zerolen(grpdef.pulseind(m, i), 1))*self.clk/awgdata(1).clk,self.zerochan(j))); + end + end; + end + end + end + if grpdef.nrep(min(i, end)) == Inf || grpdef.nrep(min(i, end)) == 0 ... + || (i == npls && isempty(strfind(grpdef.ctrl, 'loop')) && (isempty(grpdef.jump) || all(grpdef.jump(1, :) ~= i))) + fprintf(self.handle, sprintf('SEQ:ELEM%d:LOOP:INF 1', ind)); + else + fprintf(self.handle, 'SEQ:ELEM%d:LOOP:INF 0', ind); % default + fprintf(self.handle, sprintf('SEQ:ELEM%d:LOOP:COUN %d', ind, grpdef.nrep(min(i, end)))); + end + + fprintf(self.handle, sprintf('SEQ:ELEM%d:GOTO:STAT 0', ind)); + + if grpdef.nrep(min(i, end)) == Inf && isreal(grpdef.pulses) && ... + (length(self.seqpulses) < ind || self.seqpulses(ind) ~= grpdef.pulses(grpdef.pulseind(i))); + dosave = 1; + self.seqpulses(ind) = grpdef.pulses(grpdef.pulseind(i)); + end + if ~mod(i, 100) + fprintf('%i/%i pulses added.\n', i, npls); + end + end + %fprintf('Group load time: %g secs\n',toc-gstart); + + % jstart=toc; + % event jumps + %SEQ:ELEM%d:JTARget:IND + %SEQ:ELEM%d:JTARget:TYPE + + for j = 1:size(grpdef.jump, 2) + fprintf(self.handle, sprintf('SEQ:ELEM%d:GOTO:IND %d', startline+usetrig-1 + grpdef.jump(:, j))); + fprintf(self.handle, sprintf('SEQ:ELEM%d:GOTO:STAT 1', startline+usetrig-1 + grpdef.jump(1, j))); + end + + if ~exist('seqlog','var') + seqlog.time = now; + else + seqlog(end+1).time = now; + end + seqlog(end).nrep = grpdef.nrep; + seqlog(end).jump = grpdef.jump; + + save([plsdata.grpdir, 'pg_', groups{k}], '-append', 'seqlog'); + %fprintf('Jump program time: %f secs\n',toc-jstart); + % wstart=toc; + self.control('wait'); + %fprintf('Wait time: %f secs; total time %f secs\n',toc-wstart,toc-astart); + nerr=0; + + err=query(self.handle, 'SYST:ERR?'); + if ~isempty(strfind(err, 'No error')) + nerr=nerr+1; + end + + if nerr == 0 + fprintf('Added group %s on index %i. %s', grpdef.name, gind, err); + logentry('Added group %s on index %i.', grpdef.name, gind); + end +end +if dosave + self.savedata; +end \ No newline at end of file diff --git a/@TekAWG/addPulseGroup.m b/@TekAWG/addPulseGroup.m new file mode 100644 index 0000000..372b0c5 --- /dev/null +++ b/@TekAWG/addPulseGroup.m @@ -0,0 +1,128 @@ +function addPulseGroup(self,grpdef) + self.loadPulsesOfGroup(grpdef); + + + npls = size(grpdef.pulseind, 2); + + + + usetrig = isempty(strfind(grpdef.ctrl, 'notrig')); + + + if ~isempty( self.storedPulsegroups(grpdef.name).seqind ) + + startline = self.storedPulsegroups(grpdef.name).seqind; + if npls + usetrig ~= sum(self.storedPulsegroups(grpdef.name).npulse); + error('Number of pulses changed in group %s. Use awgrm first!', grpdef.name); + end + + + if isfield(plslog, 'readout') && exist('zerolen', 'var') + if any(self.storedPulsegroups(grpdef.name).nrep ~= grpdef.nrep) || ... + any(any(self.storedPulsegroups(grpdef.name).readout ~= plslog(end).readout)) || ... + any(any(self.storedPulsegroups(grpdef.name).zerolen ~= zerolen)) % nrep or similar changed + dosave = 1; + end + else + if any(self.storedPulsegroups(grpdef.name).nrep ~= grpdef.nrep) % nrep changed + dosave = 1; + end + end; + + else + % group loaded for the first time + startline = self.lastFreeSequenceLine(); + + self.storedPulsegroups(grpdef.name).seqind = startline; + self.storedPulsegroups(grpdef.name).npulse = [npls usetrig]; + self.storedPulsegroups(grpdef.name).nline = npls+usetrig; + + fprintf(self.handle, sprintf('SEQ:LENG %d', startline + self.storedPulsegroups(grpdef.name).nline-1)); + dosave = 1; + + end + + %insert nrep logic here + loop = strfind(grpdef.ctrl, 'loop'); + + self.storedPulsegroups(grpdef.name).nrep = grpdef.repetitions; + + if usetrig + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV1 "trig_%08d"', startline, self.triglen)); + for j = 2:nchan + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', startline, j, self.triglen, self.zerochan(j))); + end + if isfield(self,'slave') && ~isempty(self.slave) && (self.slave) + fprintf(self.handle, sprintf('SEQ:ELEM%d:TWAIT 1\n', startline)); + end + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% continue work here + + for i = 1:npls + ind = i-1 + startline + usetrig; + + for ch = 1:nchan + if ~isempty(ch) && zerolen(grpdef.pulseind(i), ch) < 0 + % channel in group and not zero + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV%d "%s_%05d_%d"', ind, j, ... + grpdef.name, grpdef.pulseind(i), ch)); + else + % hack alert. We should really make zerolen a cell array. fixme. + fprintf(self.handle, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', ind, j, ... + abs(zerolen(grpdef.pulseind(i), 1)),self.zerochan(j))); % think of a way to make clocks sync + end + end + + if grpdef.nrep(min(i, end)) == Inf || grpdef.nrep(min(i, end)) == 0 ... + || (i == npls && isempty(strfind(grpdef.ctrl, 'loop')) && (isempty(grpdef.jump) || all(grpdef.jump(1, :) ~= i))) + fprintf(self.handle, sprintf('SEQ:ELEM%d:LOOP:INF 1', ind)); + else + fprintf(self.handle, 'SEQ:ELEM%d:LOOP:INF 0', ind); % default + fprintf(self.handle, sprintf('SEQ:ELEM%d:LOOP:COUN %d', ind, grpdef.nrep(min(i, end)))); + end + + fprintf(self.handle, sprintf('SEQ:ELEM%d:GOTO:STAT 0', ind)); + + if grpdef.nrep(min(i, end)) == Inf && isreal(grpdef.pulses) && ... + (length(self.seqpulses) < ind || self.seqpulses(ind) ~= grpdef.pulses(grpdef.pulseind(i))); + dosave = 1; + self.seqpulses(ind) = grpdef.pulses(grpdef.pulseind(i)); + end + if ~mod(i, 100) + fprintf('%i/%i pulses added.\n', i, npls); + end + end + %fprintf('Group load time: %g secs\n',toc-gstart); + + % jstart=toc; + % event jumps + %SEQ:ELEM%d:JTARget:IND + %SEQ:ELEM%d:JTARget:TYPE + + for j = 1:size(grpdef.jump, 2) + fprintf(self.handle, sprintf('SEQ:ELEM%d:GOTO:IND %d', startline+usetrig-1 + grpdef.jump(:, j))); + fprintf(self.handle, sprintf('SEQ:ELEM%d:GOTO:STAT 1', startline+usetrig-1 + grpdef.jump(1, j))); + end + + if ~exist('seqlog','var') + seqlog.time = now; + else + seqlog(end+1).time = now; + end + seqlog(end).nrep = grpdef.nrep; + seqlog(end).jump = grpdef.jump; + + save([plsdata.grpdir, 'pg_', groups{k}], '-append', 'seqlog'); + %fprintf('Jump program time: %f secs\n',toc-jstart); + % wstart=toc; + self.control('wait'); + %fprintf('Wait time: %f secs; total time %f secs\n',toc-wstart,toc-astart); + nerr=0; + + err=query(self.handle, 'SYST:ERR?'); + if ~isempty(strfind(err, 'No error')) + nerr=nerr+1; + end + +end \ No newline at end of file diff --git a/@TekAWG/control.m b/@TekAWG/control.m new file mode 100644 index 0000000..8cfaa13 --- /dev/null +++ b/@TekAWG/control.m @@ -0,0 +1,154 @@ +function val = control(self,cntrl, chans) +% awgcntrl(cntrl, chans) +% cntrl: stop, start, on off, wait, raw|amp, israw, extoff|exton, isexton, err, clr +% several commands given are processed in order. +% isamp and isexton return a vector of length chans specifying which are +% amp or in exton mode + +% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. + +val=[]; +if nargin <2 + chans = []; +end + +breaks = [regexp(cntrl, '\<\w'); regexp(cntrl, '\w\>')]; + +for k = 1:size(breaks, 2); + switch cntrl(breaks(1, k):breaks(2, k)) + case 'stop' + fprintf(self.handle, 'AWGC:STOP'); + + + case 'start' + fprintf(self.handle, 'AWGC:RUN'); + awgcntrl('wait'); + + case 'off' + + for i = ch(self, chans) %%%%%%%%%%%%%%%%%%%%%% hä? + fprintf(self.handle, 'OUTPUT%i:STAT 0', i); + end + + case 'on' + for i = ch(self, chans) + fprintf(self.handle, 'OUTPUT%i:STAT 1', i); + end + + + + case 'wait' + to = self.handle.timeout; + self.handle.timeout = 600; + query(self.handle, '*OPC?'); + self.handle.timeout = to; + + + case 'raw' + if any(any(~self.control('israw'))) + + if ~is7k(self) + for i = ch(self, chans) + fprintf(self.handle, 'AWGC:DOUT%i:STAT 1', i); + end + end + + else + fprintf('Already raw\n'); + end + + case 'amp' + if any(any(awg.control('israw'))) + + if ~is7k(self) + for i = ch(self, chans) + fprintf(self.handle, 'AWGC:DOUT%i:STAT 0', i); + end + end + + else + fprintf('Already amp\n'); + end + + case 'israw' + + val=[]; + if ~is7k(self) + for i = ch(self, chans) + fprintf(self.handle, 'AWGC:DOUT%i:STAT?',i); + val(end+1) = fscanf(self.handle,'%f'); + end + end + + case 'exton' %adds external DC to outputs specified in chans + + if ~is7k(self) + for i = ch(self,chans) + fprintf(self.handle, 'SOUR%i:COMB:FEED "ESIG"', i); + end + end + + + case 'extoff' %turns off external DC + + if ~is7k(self) + for i = ch(self,chans) + fprintf(self.handle, 'SOUR%i:COMB:FEED ""', i); + end + end + + case 'isexton' + val=[]; + if ~is7k(self) + for i = ch(self, chans) + + fprintf(self.handle, 'SOUR%i:COMB:FEED?',i); + val(end+1) = strcmp(fscanf(self.handle, '%f'), 'ESIG'); + end + end + + case 'err' + err=query(self.handle, 'SYST:ERR?'); + if strcmp(err(1:end-1), '0,"No error"') + % Supress blank error messages. + else + fprintf('%d: %s\n',a,err); + end + + + case 'clr' + i = 0; + err2 = sprintf('n/a.\n'); + while 1 + err = query(self.handle, 'SYST:ERR?'); + if strcmp(err(1:end-1), '0,"No error"') + if i > 0 + fprintf('%s: %i errors. Last %s', self.identifier, i, err2); + end + break; + end + err2 = err; + i = i + 1; + end + + case 'norm' + for i = 1:4 + fprintf(self.handle, 'SOUR%i:VOLT:AMPL .6', i); + end + case 'dbl' + for i = 1:4 + fprintf(self.handle, 'SOUR%i:VOLT:AMPL 1.2', i); + end + end + end +end + +function chans=ch(awg, chans) + if isempty(chans) + chans=1:length(awg.channelMap); + end +end + +function val=is7k(awg) + val=length(awg.channelMap) <= 2; +end \ No newline at end of file diff --git a/@TekAWG/erase.m b/@TekAWG/erase.m new file mode 100644 index 0000000..9ede804 --- /dev/null +++ b/@TekAWG/erase.m @@ -0,0 +1,96 @@ +function erase(self,groups,options) +% awgclear(groups) +% OR +% awgclear('all') +% awgclear('pack') removes all groups, adds back groups loaded in sequences +% awgclear('all','paranoid') removes all waveforms, including those not known to be loaded. +% awgclear('pack','paranoid') similar + +% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. + +if ~exist('options','var') + options=''; +end +global plsdata; + +if strcmp(groups, 'pack') + grps={self.pulsegroups.name}; + self.erase('all',options); + self.add(grps); + return; +end + +if strcmp(groups, 'all') + % Mark only groups known to be loaded as loaded. + if isempty(strfind(options,'paranoid')) + g=self.knownwaveforms(); + else % Mark all pulse groups as not loaded + g=plsinfo('ls'); + end + + fprintf(self.handle,'WLIS:WAV:DEL ALL\n') + self.zeropls=[]; + + + logentry('Cleared all pulses.'); + for i=1:length(g) + load([plsdata.grpdir, 'pg_', g{i}, '.mat'], 'plslog'); + if(plslog(end).time(end) <= 0) + fprintf('Skipping group ''%s''; already unloaded\n',g{i}); + else + plslog(end).time(end+1) = -now; + save([plsdata.grpdir, 'pg_', g{i}, '.mat'], 'plslog','-append'); + fprintf('Marking group ''%s'' as unloaded\n',g{i}); + end + end + self.rm('all'); + return; +end + +if strcmp(groups,'unused') + g=self.waf; + g2={self.pulsegroups.name}; + groups=setdiff(g,g2); + for i=1:length(groups) + fprintf('Unloading %s\n',groups{i}); + end +end + +if ischar(groups) + groups = {groups}; +end +tic; + +if isreal(groups) + groups = sort(groups, 'descend'); + for i = groups + wf = query(self.awg, sprintf('WLIS:NAME? %d', i)); + if ~query(self.awg, sprintf('WLIS:WAV:PRED? %s', wf), '%s\n', '%i') + fprintf(self.awg, 'WLIS:WAV:DEL %s', wf); + end + if toc > 20 + fprintf('%i/%i\n', i, length(groups)); + tic; + end + end + self.control('wait'); + return; +end + + +for k = 1:length(groups) + load([plsdata.grpdir, 'pg_', groups{k}], 'plslog'); + + wfms=self.knownwaveforms(groups{k},'delete'); + for i=1:length(wfms) + fprintf(self.handle, sprintf('WLIS:WAV:DEL "%s"', wfms{i})); + end + + + plslog(end).time(end+1) = -now; + save([plsdata.grpdir, 'pg_', groups{k}], '-append', 'plslog'); + logentry('Cleared group %s.', groups{k}); + fprintf('Cleared group %s.\n', groups{k}); + + self.rm(groups{k}); +end diff --git a/@TekAWG/load.m b/@TekAWG/load.m new file mode 100644 index 0000000..024fb9e --- /dev/null +++ b/@TekAWG/load.m @@ -0,0 +1,114 @@ +function load(self,grp,ind) +fprintf('awg.load\n') +% zerolen = awgload(grp) +% load pulses from group to AWG. + +% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. + +self.control('stop'); %changing pulses while running is slow. +self.syncwaveforms(); % make sure the waveform list is up-to-date. + +dind=find([grp.pulses(1).data.clk] == self.clk); + + +% fixme; emit an error if this changes zerochan and wlist.size ~= 25. +% The screwiness here is to get each channel with a unique offset/scale combo +[offsets offsetchan self.zerochan] = unique(self.offset./self.scale); +offsets=self.offset(offsetchan); + +dosave = false; + +% create trig pulse (and corresponding 0) if waveform list empty. +if query(self.handle, 'WLIS:SIZE?', '%s\n', '%i') == 25 % nothing loaded (except predefined) + zdata=zeros(1,self.triglen); + zmarker=repmat(1,1,self.triglen); + self.loadwfm(zdata,zmarker,sprintf('trig_%08d',self.triglen),1,1); + + for l=1:length(offsets) + self.loadwfm(zdata,zmarker,sprintf('zero_%08d_%d',self.triglen,l),offsetchan(l),1); + end + dosave = true; + self.zeropls = self.triglen; +end + + + +nzpls=0; + + + +for i = 1:length(grp.pulses) + npts = size(grp.pulses(i).data(dind).wf, 2); + if ~any(self.zeropls == npts) % create zero if not existing yet + zdata=zeros(1,npts); + for l=1:length(offsets) + zname=sprintf('zero_%08d_%d', npts, l); + self.loadwfm(zdata,zdata,zname,offsetchan(l),1); + end + zdata=[]; + self.zeropls(end+1) = npts; + dosave = true; + end + + for j = 1:size(grp.pulses(i).data(dind).wf, 1) + + ch = self.getHardwareChannel(grp.chan(j)); + + %virtual channels not belonging to this AWG + if isempty(ch) + continue; + end + + % data of channel 2 is uploaded seperatly and not as a zeropulse + if any(abs(grp.pulses(i).data(dind).wf(j, :)) > self.scale(ch)/(2^self.resolution)) || any(grp.pulses(i).data(dind).marker(j,:) ~= 0) + name = sprintf('%s_%05d_%d', grp.name, ind(i), j); + + if isempty(strmatch(name,self.waveforms)) + fprintf(self.handle, sprintf('WLIS:WAV:NEW "%s",%d,INT', name, npts)); + self.waveforms{end+1}=name; + err = query(self.handle, 'SYST:ERR?'); + if ~isempty(strfind(err,'E11113')) + fprintf(err(1:end-1)); + error('Error loading waveform; AWG is out of memory. Try awgclear(''all''); '); + end + end + self.loadwfm(grp.pulses(i).data(dind).wf(j,:), uint16(grp.pulses(i).data(dind).marker(j,:)), name, ch, 0); + zerolen(ind(i), j) = -npts; + nzpls=1; + else + zerolen(ind(i), j) = npts; + end + end +end + +% If no non-zero pulses were loaded, make a dummy waveform so awgclear +% knows this group was in memory. +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%consider removoing +if nzpls == 0 + name=sprintf('%s_1_1',grp.name); + npts=256; + if isempty(strmatch(name,self.waveforms)) + fprintf(self.handle, sprintf('WLIS:WAV:NEW "%s",%d,INT', name, npts)); + self.waveforms{end+1}=name; + end + self.loadwfm(zeros(1,npts), zeros(1,npts), name, 1, 0); +end + +if ~isKey(self.storedPulsegroups,grp.name) + self.storedPulsegroups.add( TekPULSEGROUP(grp.name) ); +end + +%update it's load time. +self.storedPulsegroups(grp.name).lastload = now; + + +end + +if dosave + self.savedata; +end +end + + + + diff --git a/@TekAWG/loadPulsesOfGroup.m b/@TekAWG/loadPulsesOfGroup.m new file mode 100644 index 0000000..a695210 --- /dev/null +++ b/@TekAWG/loadPulsesOfGroup.m @@ -0,0 +1,107 @@ +function loadPulsesOfGroup(self,grp) + +% load pulses from group to AWG. +self.control('stop'); %changing pulses while running is slow. +self.syncwaveforms(); % make sure the waveform list is up-to-date. + +dind=find([grp.pulses(1).data.clk] == self.clk); + + +% fixme; emit an error if this changes zerochan and wlist.size ~= 25. +% The screwiness here is to get each channel with a unique offset/scale combo +[offsets offsetchan self.zerochan] = unique(self.offset./self.scale); +offsets=self.offset(offsetchan); + +dosave = false; + +% create trig pulse (and corresponding 0) if waveform list empty. +if query(self.handle, 'WLIS:SIZE?', '%s\n', '%i') == 25 % nothing loaded (except predefined) + zdata=zeros(1,self.triglen); + zmarker=ones(1,self.triglen); + self.loadwfm(zdata,zmarker,sprintf('trig_%08d',self.triglen),1,1); + + for l=1:length(offsets) + self.loadwfm(zdata,zmarker,sprintf('zero_%08d_%d',self.triglen,l),offsetchan(l),1); + end + dosave = true; + self.zeropls = self.triglen; +end + + + +nonzeropls = false; + +zerolen = zeros(length(grp.pulses), size(grp.pulses.data.wf, 2) ); + +for i = 1:length(grp.pulses) + npts = size(grp.pulses(i).data(dind).wf, 2); + if ~any(self.zeropls == npts) % create zero if not existing yet + zdata=zeros(1,npts); + for l=1:length(offsets) + zname=sprintf('zero_%08d_%d', npts, l); + self.loadwfm(zdata,zdata,zname,offsetchan(l),1); + end + self.zeropls(end+1) = npts; + dosave = true; + end + + for j = 1:size(grp.pulses(i).data(dind).wf, 1) + + ch = self.getHardwareChannel(grp.chan(j)); + + %virtual channels not belonging to this AWG + if isempty(ch) + continue; + end + + % data of channel 2 is uploaded seperatly and not as a zeropulse + if any(abs(grp.pulses(i).data(dind).wf(j, :)) > self.scale(ch)/(2^self.resolution)) || any(grp.pulses(i).data(dind).marker(j,:) ~= 0) + name = sprintf('%s_%05d_%d', grp.name, ind(i), j); + + if isempty(strmatch(name,self.waveforms)) + fprintf(self.handle, sprintf('WLIS:WAV:NEW "%s",%d,INT', name, npts)); + self.waveforms{end+1}=name; + err = query(self.handle, 'SYST:ERR?'); + if ~isempty(strfind(err,'E11113')) + fprintf(err(1:end-1)); + error('Error loading waveform; AWG is out of memory. Try awg.clear(''all''); '); + end + end + self.loadwfm(grp.pulses(i).data(dind).wf(j,:), uint16(grp.pulses(i).data(dind).marker(j,:)), name, ch, 0); + nonzeropls = true; + zerolen(i,ch) = npts; + else + zerolen(i,ch) = -npts; + end + end +end + +% If no non-zero pulses were loaded, make a dummy waveform so awgclear +% knows this group was in memory. +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%consider removoing +if nonzeropls + name=sprintf('%s_1_1',grp.name); + npts=256; + if isempty(strmatch(name,self.waveforms)) + fprintf(self.handle, sprintf('WLIS:WAV:NEW "%s",%d,INT', name, npts)); + self.waveforms{end+1}=name; + end + self.loadwfm(zeros(1,npts), zeros(1,npts), name, 1, 0); +end + + +%update it's load time. +if isKey(self.storedPulsegroups,grp.name) + %Only one level of indexing is supported by a containers.Map... + self.storedPulsegroups(grp.name).lastload = now; +else + self.storedPulsegroups.add( TekPULSEGROUP(grp.name) ); +end + +self.storedPulsegroups.zerolen = zerolen; + +if dosave + self.savedata; +end + +end \ No newline at end of file diff --git a/@TekAWG/loadwfm.m b/@TekAWG/loadwfm.m new file mode 100644 index 0000000..a7cd747 --- /dev/null +++ b/@TekAWG/loadwfm.m @@ -0,0 +1,35 @@ +function loadwfm(self,data, marker, name, chan,define) +% Send waveform 'data,marker' to the awg with name 'name' intended for channel c. +% data is scaled and offset by awgdata.scale and awgdata.offset *before* sending. +start = now; +if exist('define','var') && define + fprintf(self.handle, sprintf('WLIS:WAV:NEW "%s",%d,INT', name, length(data))); + self.waveforms{end+1}=name; +end +chunksize=65536; +if(size(data,1) > size(data,2)) + data=data'; +end + data=(self.offset(min(chan,end)) + data)./self.scale(chan) + 1; + tb=find(data > 2); + tl=find(data < 0); + if ~isempty(tb) || ~isempty(tl) + % fprintf('Pulse exceeds allowed range: %g - %g\n',min(data),max(data)); + data(tb) = 2; + data(tl) = 0; + end % 14 bit data offset is hard-coded in the AWG. + data = uint16(min(data*(2^(14-1) - 1), 2^(14)-1)) + uint16(marker) * 2^14; + npts = length(data); + for os=0:chunksize:npts + if os + chunksize >= npts + fwrite(self.handle, [sprintf('WLIS:WAV:DATA "%s",%d,%d,#7%07d', name, os, npts-os,2 * (npts-os)),... + typecast(data((os+1):end), 'uint8')]); + else + fwrite(self.handle, [sprintf('WLIS:WAV:DATA "%s",%d,%d,#7%07d', name, os, chunksize,2 * chunksize),... + typecast(data((os+1):(os+chunksize)), 'uint8')]); + end + fprintf(self.handle,''); + end + time=(now-start)*24*60*60; + fprintf('Load time for pulse %s: %g seconds for %g points (%g bytes/sec)\n',name,time, npts,npts*2/time); +end \ No newline at end of file diff --git a/@TekAWG/rm.m b/@TekAWG/rm.m new file mode 100644 index 0000000..3839fe7 --- /dev/null +++ b/@TekAWG/rm.m @@ -0,0 +1,46 @@ +function rm(self,grp, ctrl) +% awgrm(grp, ctrl) +% grp: 'all' or group name +% ctrl: 'after' remove all following groups +% (otherwise, specified group is removed by removing it and following ones +% and then reloading the latter. + +% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. + + +if strcmp(grp, 'all') + self.control('stop'); + + fprintf(self.handle, 'SEQ:LENG 0'); + self.pulsegroups = []; + self.seqpulses = []; + + self.savedata(); + return; +end + +grp = self.grpind(grp); %strmatch(grp, strvcat(awgdata.pulsegroups.name), 'exact'); +grp(2:end) = []; +if isnan(grp) + return; +end + +self.control('stop'); + +if exist('ctrl','var') && strfind(ctrl, 'after') + + fprintf(self.handle, 'SEQ:LENG %d', self.pulsegroups(grp).seqind-1 + sum(self.pulsegroups(grp).nline)); + self.seqpulses(self.pulsegroups(grp).seqind + sum(self.pulsegroups(grp).npulse):end) = []; + self.pulsegroups(grp+1:end) = []; + + % may miss trigger line. + return; +end + +fprintf(self.handle, 'SEQ:LENG %d', self.pulsegroups(grp).seqind-1); +self.seqpulses(self.pulsegroups(grp).seqind:end) = []; +groups = {self.pulsegroups(grp+1:end).name}; +self.pulsegroups(grp:end) = []; + +% log unloading here if necessary +self.add(groups); diff --git a/@TekAWG/syncwaveforms.m b/@TekAWG/syncwaveforms.m new file mode 100644 index 0000000..13112d1 --- /dev/null +++ b/@TekAWG/syncwaveforms.m @@ -0,0 +1,18 @@ +function syncwaveforms(self) +% Make sure the list of pulses in awgdata is consistent with the awg. +% we assume if the number of pulses is right, everything is.\ +self.control('clr'); + + + npls=str2num(query(self.handle,'WLIS:SIZE?')); + if (length(self.waveforms) == npls) + return; + end + fprintf('TekAWG %s waveform list out of date. Syncing.',self.identifier); + self.waveforms=cell(npls,1); + for l=1:npls + r=query(self.handle,sprintf('WLIS:NAME? %d',l-1)); + self.waveforms{l}=r(2:end-2); %-2 since communication adds newline at end + end + fprintf('.. Done.\n'); +end diff --git a/@TekAWG/upload.m b/@TekAWG/upload.m new file mode 100644 index 0000000..1dc36be --- /dev/null +++ b/@TekAWG/upload.m @@ -0,0 +1,111 @@ +function upload(self,name) + +global plsdata; + + +if ~iscell(name) + name = {name}; +end + +ind = []; +% if ~exist('opts','var') +% opts=struct(); +% end +% +% opts=def(opts,'time',[]); + +for k = 1:length(name) + + if(~isstruct(name{k})) + zerolen = []; % avoid using zerolen from previous group. + plslog=[]; + load([plsdata.grpdir, 'pg_', name{k}]); + else + grpdef=name{k}; + end + + + if exist('plslog','var') && length(plslog) > 100 + fprintf('Group %s has %d log entries.\n',name{k},length(plslog)); + end +% if exist('plslog','var') && ~isempty(plslog) && ~isempty(opts.time) +% le=plsinfo_logentry(plslog,opts.time); +% grpdef.params=plslog(le).params; +% grpdef.matrix=plslog(le).matrix; +% grpdef.varpar=plslog(le).varpar; +% grpdef.offset=plslog(le).offset; +% grpdef.dict=plslog(le).dict; +% grpdef.readout=plslog(le).readout; +% end + + + pack = false;%~isempty(strfind(grpdef.ctrl,'pack')); + + if ~ isfield(grpdef, 'varpar') + grpdef.varpar = []; + end + if ~ isfield(grpdef, 'params') + grpdef.params = []; + end + if isfield(grpdef, 'time')&& ~isempty(grpdef.time) + fprintf('Ignoring opts.time\n'); + opts.time=grpdef.time; + + end + + if plsinfo('stale',grpdef.name) + % modified since last upload + + + + + % Actually handle the upload... + zerolen = self.load(grpdef, ind); + + + % save update time in log. + plslog(end+1).time = now; + plslog(end).params = grpdef.params; + plslog(end).matrix = grpdef.matrix; + plslog(end).varpar = grpdef.varpar; + plslog(end).offset = grpdef.offset; + + if isfield(grpdef.pulses(1).data,'readout') + readout=[]; + for ll=1:length(grpdef.pulses) + if ~isempty(grpdef.pulses(ll).data(1).readout) + readout(:,:,ll) = grpdef.pulses(ll).data(1).readout; + end + end + if any(abs(diff(readout,[],3)) > 1e-10) + warning('Readout changes between pulses in %s\n',name{k}); + end + if(size(readout,1) > 0) + plslog(end).readout = readout(:,:,1); + else + plslog(end).readout=[]; + end + end + + if isfield(grpdef, 'dict') + plslog(end).dict = grpdef.dict; + end + + if isfield(grpdef, 'trafofn') + plslog(end).trafofn = grpdef.trafofn; + end + + if length(plslog) > 2 % copy in case not all pulses updated. First plslog has no xval + plslog(end).xval = plslog(end-1).xval; + end + %plslog(end).xval(:, ind) = vertcat(grpdef.pulses.xval)'; + plslog(end).xval = vertcat(grpdef.pulses.xval)'; % temporary bug fix + plslog(end).ind = ind; + + save([plsdata.grpdir, 'pg_', name{k}], '-append','-v6', 'plslog', 'zerolen'); + logentry('Uploaded group %s, revisions %i.', grpdef.name, length(plslog)); + % fprintf(' in upload of group %s.\n', grpdef.name); + else + fprintf('Skipping group %s.\n', grpdef.name); + end +end \ No newline at end of file diff --git a/@VAWG/VAWG.m b/@VAWG/VAWG.m new file mode 100644 index 0000000..34fdc51 --- /dev/null +++ b/@VAWG/VAWG.m @@ -0,0 +1,129 @@ +classdef VAWG < handle + properties (SetAccess = protected, GetAccess = public) + awgs; + + %channel mapping + virtualToHardware = {}; + + %trigger length in nanoseconds + triggerLength = 4000; + end + + methods + add(self,groups); + + function zerolen = zero(self,grp,ind,zerolen) + for awg = 1:length(self.awgs) + zerolen = self.awgs(awg).zeroLength(grp,ind,zerolen); + end + + end + + function index = addAWG(self,awg) + if ~isa(awg,'AWG') + error('Object is no AWG.'); + else + if ~isempty(self.awgs) + if find( strcmp({self.awgs.identifier},awg.identifier) ) + error('AWG with identifier %s already exists.',awg.identifier); + end + end + + self.awgs = [self.awgs(:) awg]; + index = length(self.awgs); + end + end + + function removeAWG(self,awg) + index = self.getIndex(awg); + + self.awgs(index).virtualChannels = []; + + for virt = 1:length(self.virtualToHardware) + todelete = []; + for entry = 1:length( self.virtualToHardware{virt} ) + if self.virtualToHardware{virt}{entry}(1) == index + todelete(end+1) = entry; + end + end + self.virtualToHardware{virt}(todelete) = []; + end + + + self.awgs(index) = []; + end + + function index = getIndex(self,awg) + if isa(awg,'AWG') + index = find( eq(awg,self.awgs) ); + elseif ischar(awg) + index = find( strcmp({self.awgs.identifier},awg) ); + elseif isinteger(awg) || isfloat(awg) + index = awg; + else + error('Recieved an invalid type to determine index.'); + end + + if length(self.awgs) self.awgs(index).nChannels + error('AWG %s only has %i hardware channels. Requested was %i',self.awgs(index).nChannels,hardware); + end + + if size(self.virtualToHardware,2) < virtual + self.virtualToHardware{virtual}{1} = [index hardware]; + else + self.virtualToHardware{virtual}{end+1} = [index hardware]; + end + + self.awgs(index).virtualChannels(hardware) = virtual; + end + + function removeVirtualChannel(self,virtualChannel) + if size(self.virtualToHardWare,2) npls + error('length(grpdef.nrep) > npls'); + end + + + + +% if ~isfield(grpdef, 'jump') +% if strfind(grpdef.ctrl, 'loop') +% grpdef.jump = [npls; 1]; +% else +% grpdef.jump = []; +% end +% end + + groupDefAWG = struct('name',grpdef.name,'pulses',[],'pulseind',[],'repetitions',[],'ctrl',grpdef.ctrl); + if isfield(grpdef,'chan') + groupDefAWG.chan = grpdef.chan; + end + + if isfield(grpdef,'ctrl') + groupDefAWG.ctrl = grpdef.ctrl; + end + + % fill pulseind + if ~seqmerge % pulses combined here. + groupDefAWG.pulses = grpdef.pulses; + groupDefAWG.pulseind = grpdef.pulseind; + groupDefAWG.nrep = grpdef.nrep; + + elseif strfind(grpdef.ctrl,'pack') % added 14.11.2014 PC + %do nothing + groupDefAWG.pulses = grpdef.pulses; + groupDefAWG.pulseind = grpdef.pulseind; + groupDefAWG.nrep = grpdef.nrep; + + else % completely overhauled 02.05.2014 PC + error('work out what to du here'); + + groupDefAWG.nrep = grpdef.nrep; + %guess: + groupDefAWG.pulses = zeros(1,npls) + + for m = 1:length(grpdef.pulses.groups) + pulses = find( grpdef.pulseind(m,:) > 0 ) + + if any(groupDefAWG.pulseind(pulses)) + error('You can only play one pulse at a time.'); + end + + groupDefAWG.pulseind(pulses) = grpdef.pulseind(m,pulses)+length(groupDefAWG.pulses); + + groupDefAWG.pulses = [groupDefAWG.pulses(:) grpdef.groups.pulses{m}(:)]; + end + + +% for m = 1:length(grpdef.pulses.groups) +% for j = 1:nchan % channels of component groups +% ch = find(awgdata(a).chans(j) == chan{m}); +% if grpdef.pulseind(m, i) > 0 % Do not add if pulseind == 0 +% if ~isempty(ch) && zerolen(grpdef.pulseind(m, i), ch) < 0 +% % channel in group and not zero +% fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:WAV%d "%s_%05d_%d"', ind, j, ... +% grpdef.pulses.groups{m}, grpdef.pulseind(m, i), ch)); +% else +% fprintf(awgdata.awg, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', ind, j, ... +% zlmult*abs(zerolen(grpdef.pulseind(m, i), 1))*awgdata(a).clk/awgdata(1).clk,awgdata(a).zerochan(j))); +% end +% end; +% end +% +% end + end + + if isfield(grpdef,'jump') + error('jump not implemented yet. (too lazy)'); + end + + for awg = self.awgs + awg.addPulseGroup(groupDefAWG); + end +% end +% if ~exist('seqlog','var') +% seqlog.time = now; +% else +% seqlog(end+1).time = now; +% end +% seqlog(end).nrep = grpdef.nrep; +% seqlog(end).jump = grpdef.jump; +% +% save([plsdata.grpdir, 'pg_', groups{k}], '-append', 'seqlog'); +% %fprintf('Jump program time: %f secs\n',toc-jstart); +% wstart=toc; +% awgcntrl('wait'); +% %fprintf('Wait time: %f secs; total time %f secs\n',toc-wstart,toc-astart); +% nerr=0; +% for a=1:length(awgdata(a)) +% err=query(awgdata(a).awg, 'SYST:ERR?'); +% if ~isempty(strfind(err, 'No error')) +% nerr=nerr+1; +% end +% end +% if nerr == 0 +% fprintf('Added group %s on index %i. %s', grpdef.name, gind, err); +% logentry('Added group %s on index %i.', grpdef.name, gind); +% end +% +% if dosave +% awgsavedata; +% end + +end diff --git a/AWGPULSEGROUP.m b/AWGPULSEGROUP.m new file mode 100644 index 0000000..9f83bd5 --- /dev/null +++ b/AWGPULSEGROUP.m @@ -0,0 +1,14 @@ +% pulsegroup representation in AWG driver +classdef AWGPULSEGROUP < matlab.mixin.Heterogeneous & handle + properties + name; + lastload = -Inf; + repetitions = []; + end + + methods + function obj = AWGPULSEGROUP(name) + obj.name = name; + end + end +end \ No newline at end of file diff --git a/AWGSTORAGE.m b/AWGSTORAGE.m new file mode 100644 index 0000000..df94078 --- /dev/null +++ b/AWGSTORAGE.m @@ -0,0 +1,137 @@ +%this class is a map/dictionary implementation which supports multi level +%access like storage.member(3).bla +classdef AWGSTORAGE < handle + properties + mData = AWGPULSEGROUP.empty(); + end + + methods + function i = index(self,identifier) + i = find( strcmp({self.mData.name},identifier) ); + if size(i,2) > 1 + warning('contains %i elements with the key %s.',size(i,2),identifier); + end + end + + function val = subsref(self,S) + if strcmp(S(1).type,'()') + if ischar(S(1).subs{1}) + if size(S(1).subs,2) ~= 1 + error('only one key access impleneted'); + end + + index = self.index(S(1).subs{1}); + if isempty(index) + error('Key %s not found',S(1).subs{1}); + end + + if size(S,2) ~= 1 + val = subsref( self.mData(index),S(2:end)); + else + val = self.mData(index); + end + else + val = subsref(self.mData,S); + end + else + try + val = builtin('subsref',self,S); + catch + builtin('subsref',self,S); + end + end + end + + function self = subsasgn(self,S,val) + if strcmp(S(1).type,'()') + if ischar(S(1).subs{1}) + + if size(S(1).subs,2) ~= 1 + error('AWGSTORAGE only supports the access via key of a single element at once for now.'); + end + + index = self.index(S(1).subs{1}); + if isempty(index) + if size(S,2) ~= 1 + error('Can not access members of element %s since it does not exist',self.index(S(1).subs{1})); + else + error('Key %s not found. No implicit object creation for simplicity/debugging. Use AWGSTORAGE@add to add an object',S(1).subs{1}); + end + else + self.mData(index) = subsasgn(self.mData(index),S(2:end),val); + end + + + else + self.mData = subsasgn( self.mData, S,val); + end + else + self = builtin('subsasgn',self,S,val); + end + end + + function remove(self,id) + if ischar(id) + index = self.index(id); + self.mData(index) = []; + else + self.mData(id) = []; + end + end + + function add(self,object) + self.insert(object, size(self.mData,2)+1) + end + + function insert(self,object,position) + if ~(isfield(object,'name') || isprop(object,'name') ) + error('May only add objects with the field "name"'); + elseif ~ischar(object.name) + error('The name field must be a string'); + end + + if ~isempty( self.mData ) + if ~isempty( self.index(object.name) ) + error('Object with key %s already exists.',object.name); + end + end + + self.mData = [self.mData(1:position-1) object self.mData(position:end)]; + end + + function s = length(self) + try + s = length(self.mData(1:end)); + catch + s = 1; + end + end + + function swap(self,i,j) + temp = self.mData(i); + self.mData(i) = self.mData(j); + self.mData(j) = temp; + end + + function move(self,key,newPosition) + oldPosition = self.index(key); + if newPosition < oldPosition + self.mData = [self.mData(1:newPosition-1) self.mData(oldPosition) self.mData((newPosition+1):oldPosition-1) self.mData((oldPosition+1):end)]; + elseif newPosition > oldPosition + self.mData = [self.mData(1:oldPosition-1) self.mData((oldPosition+1):newPosition-1) self.mData(oldPosition) self.mData((newPosition+1):end)]; + end + end + + function empty = isempty(self) + empty = isempty(self.mData); + end + + function iskey = isKey(self,key) + if isempty(self) + iskey = false; + else + iskey = self.index(key)>0; + end + end + end +end \ No newline at end of file diff --git a/PXDACMEMORY.m b/PXDACMEMORY.m new file mode 100644 index 0000000..e69de29 diff --git a/PXDACPULSE.m b/PXDACPULSE.m new file mode 100644 index 0000000..5b0c4b2 --- /dev/null +++ b/PXDACPULSE.m @@ -0,0 +1,72 @@ +% +% This class represents one waveform of which +% the output during one scanline is composed +% +classdef PXDACPULSE < handle + properties (GetAccess = public, SetAccess = private) + channelMask; + channelCount; + + %number of samples in one channel + samplesPerChannel; + + %byte + byteSize; + + rawData; + + lastEdit; + end + + methods + function obj = PXDACPULSE(channelMask,samplesPerChannel) + if ~isa(channelMask,'uint16') + error('ChannelMask must be of type uint16'); + end + + if sum(bitget(channelMask,5:16)) ~= 0 + maskString = sprintf('%i',bitget(channelMask,1:16)); + error('Only 4 channels available. Recieved channel mask %s',maskString); + end + + + obj.channelCount = sum(bitget(channelMask,1:16)); + obj.channelMask = channelMask; + + if obj.channelCount == 3 + error('Playing on 3 channels not possible'); + elseif sum(bitget(channelMask,1:2)) == 1 && sum(bitget(channelMask,3:4)) == 1 + error('May not play channel (1 XOR 2) AND (3 XOR 4).'); + end + + obj.samplesPerChannel = samplesPerChannel; + bytesPerSample = 2; %sizeof(uint16) + obj.byteSize = obj.channelCount*obj.samplesPerChannel*bytesPerSample; + + %hardcoded resolution + obj.rawData = libpointer('uint16Ptr',zeros(1,obj.samplesPerChannel*obj.channelCount,'uint16')); + + + obj.lastEdit = now; + + end + + function writeToChannel(self,channel,data) + if length(data)~=self.samplesPerChannel + error('Dimensions do not fit. Expected %i points but got %i.',self.samplesPerChannel,length(data)); + end + + if bitand(channel,self.channelMask) == 0 + error('The channel %i is not included in channel mask %i%i%i%i',channel,bitget(self.channelMask,5-(1:4))); + end + + channelsBefore = sum(bitget(self.channelMask,1:channel)-1); + + self.rawData.Value(... + channelsBefore+... %offset + 1:self.channelCount:self.samplesPerChannel*self.channelCount)... % every channelCount datapoint + = data; + self.lastEdit=now; + end + end +end \ No newline at end of file diff --git a/PXDACPULSEGROUP.m b/PXDACPULSEGROUP.m new file mode 100644 index 0000000..d56979d --- /dev/null +++ b/PXDACPULSEGROUP.m @@ -0,0 +1,22 @@ +classdef PXDACPULSEGROUP < AWGPULSEGROUP + properties + start = []; + totalByteSize = []; + lastMemoryUpdate = -Inf; + + %if not enough onboard memory -> the oldest one will be deleted + lastActivation = -Inf; + + % equivalent to grpdef.pulses + waveformArray = PXDACPULSE.empty(0,0); + + %equivalent to grpdef.pulseind and grpdef.nrep + pulseSequence = repmat(struct('index',[],'nrep',[]),0,0); + end + + methods + function obj = PXDACPULSEGROUP(name) + obj = obj@AWGPULSEGROUP(name); + end + end +end \ No newline at end of file diff --git a/PXDACSEQ.m b/PXDACSEQ.m new file mode 100644 index 0000000..1adcbb8 --- /dev/null +++ b/PXDACSEQ.m @@ -0,0 +1,6 @@ +classdef PXDACSEQ + properties + + end + +end \ No newline at end of file diff --git a/TekPULSEGROUP.m b/TekPULSEGROUP.m new file mode 100644 index 0000000..345d93c --- /dev/null +++ b/TekPULSEGROUP.m @@ -0,0 +1,21 @@ +classdef TekPULSEGROUP < AWGPULSEGROUP + properties + %index of sequence start + seqind = []; + + %number of pulses and usetrig + npulse = [0 0]; + + % + nline = []; + + zerolen = []; + + end + + methods + function obj = TekPULSEGROUP(name) + obj = obj@AWGPULSEGROUP(name); + end + end +end \ No newline at end of file diff --git a/makeGroupDef.m b/makeGroupDef.m new file mode 100644 index 0000000..748573b --- /dev/null +++ b/makeGroupDef.m @@ -0,0 +1,418 @@ +function grpdef = makeGroupDef(name, ctrl, ind, opts) +% grpdef = plsmakegrp(name, ctrl, ind, opts) +% Covert pulses in pulsegroup to wf format. +% name: group name. +% ctrl: 'plot', 'plot chrg', 'check', 'upload' +% for maintenance/debugging: 'clrzero', 'local'. +% These may mess with the upload logging, so use with care. +% ind: optional pulse index. The default is pulseind or all pulses. +% opts is an option struct +% opts.time ; time to recreate group at. +% time: optional, make grp as it was at time.. +% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. + +global plsdata; + +global vawg; +if ~isa(vawg,'VAWG') + error('vawg has to be of type VAWG'); +end + +if ~exist('ctrl','var') + ctrl=''; +end +if ~exist('ind','var') + ind=[]; +end +if ~exist('opts','var') + opts=struct(); +end + +opts=def(opts,'time',[]); + +if ~iscell(name) + name = {name}; +end + +for k = 1:length(name) + if(~isstruct(name{k})) + zerolen = []; % avoid using zerolen from previous group. + plslog=[]; + load([plsdata.grpdir, 'pg_', name{k}]); + else + grpdef=name{k}; + end + + if exist('plslog','var') && length(plslog) > 100 + fprintf('Group %s has %d log entries.\n',name{k},length(plslog)); + end + if exist('plslog','var') && ~isempty(plslog) && ~isempty(opts.time) + le=plsinfo_logentry(plslog,opts.time); + grpdef.params=plslog(le).params; + grpdef.matrix=plslog(le).matrix; + grpdef.varpar=plslog(le).varpar; + grpdef.offset=plslog(le).offset; + grpdef.dict=plslog(le).dict; + grpdef.readout=plslog(le).readout; + end + + if strfind(grpdef.ctrl, 'seq') + fprintf('Sequence joined groups: %s\n',sprintf('%s ',grpdef.pulses.groups{:})); + for m = 1:length(grpdef.pulses.groups) + plsmakegrp(grpdef.pulses.groups{m},ctrl,ind,opts); + end + return; + end + + pack = ~isempty(strfind(grpdef.ctrl,'pack')); + + if ~ isfield(grpdef, 'varpar') + grpdef.varpar = []; + end + if ~ isfield(grpdef, 'params') + grpdef.params = []; + end + if isfield(grpdef, 'time')&& ~isempty(grpdef.time) + fprintf('Ignoring opts.time '); + opts.time=grpdef.time; + end + + switch grpdef.ctrl(1:min([end find(grpdef.ctrl == ' ', 1)-1])) + + case 'pls' + + grpdef.pulses = plsdefault(grpdef.pulses); + + npar = max(1, size(grpdef.varpar, 1)); + + plsdef = grpdef.pulses;%(ind); + + if nargin < 3 || isempty(ind) + if isfield(grpdef, 'pulseind') + ind = unique(grpdef.pulseind); + else + ind = 1:length(plsdef)*npar; + end + + end + + grpdef.pulses(length(ind)+1:end) = []; + if isfield(grpdef,'dict') && ~isempty(grpdef.dict) + grpdef.dict=pdpreload(grpdef.dict,opts.time); + end + for m = 1:length(ind) + + i = floor((ind(m)-1)/npar)+1; + j = mod(ind(m)-1, npar)+1; + + % transfer valid pulse dependent parameters. Interpretation of nan may not be so useful here, + % but should not hurt. + params = grpdef.params; + if ~isempty(grpdef.varpar) + mask = ~isnan(grpdef.varpar(j, :)); + params(end-size(grpdef.varpar, 2) + find(mask)) = grpdef.varpar(j, mask); + end + + if ~isempty(plsdef(i).trafofn) + params = plsdef(i).trafofn(params); + end + + % Apply dictionary before varpars; avoids many random bugs. + if isfield(grpdef,'dict') && ~isempty(grpdef.dict) && strcmp(plsdef(i).format,'elem') + plsdef(i)=pdapply(grpdef.dict, plsdef(i),opts.time); + end + mask = ~isnan(params); + % update parameters - could move to plstowf + if ~isempty(plsdef(i).pardef) + switch plsdef(i).format + case 'elem' + pardef = plsdef(i).pardef; + for n = 1:size(pardef, 1) + if isnan(params(n)) + continue; + end + if pardef(n, 2) < 0 + plsdef(i).data(pardef(n, 1)).time(-pardef(n, 2)) = params(n); + else + plsdef(i).data(pardef(n, 1)).val(pardef(n, 2)) = params(n); + end + end + + case 'tab' + pardef = plsdef(i).pardef; + for n = 1:size(pardef, 1) + if isnan(params(n)) + continue; + end + if pardef(n, 1) < 0 + plsdef(i).data.marktab(pardef(n, 2), -pardef(n, 1)) = params(n); + else + plsdef(i).data.pulsetab(pardef(n, 2), pardef(n, 1)) = params(n); + end + end + + otherwise + error('Parametrization of pulse format ''%s'' not implemented yet.', plsdef(i).format); + end + end + + grpdef.pulses(m) = plstowf(plsdef(i)); + + + if ~isempty(grpdef.varpar) + grpdef.pulses(m).xval = [grpdef.varpar(j, end:-1:1), grpdef.pulses(m).xval]; + end + end + + case 'grp' + + groupdef = grpdef.pulses; + grpdef.pulses = struct([]); + + nchan = size(grpdef.matrix, 2); % # input channels to matrix + + % if ~isfield(groupdef, 'chan') + % [groupdef.chan] = deal(length(groupdef.groups), nan(nchan)); + % end + % + % if ~isfield(groupdef, 'markchan') + % groupdef.markchan = groupdef.chan; + % end + + + for j = 1:length(groupdef.groups) + pg = plsmakegrp(groupdef.groups{j},'upload local'); + + if ~isfield(pg, 'pulseind') %|| some flag set to apply pulseind after adding, same for all groups + pg.pulseind = 1:length(pg.pulses); + end + + % probably pointless code: + % if j == 1 % set defaults from pg(1) + % if nargin < 3 + % ind = 1:length(pg.pulses); + % end + % end + % pg.pulses = pg.pulses(ind); + + + + % target channels for j-th group + if isfield(groupdef, 'chan') + chan = groupdef.chan(j, :); + else + chan = pg.chan; + end + mask = chan > 0; + chan(~mask) = []; + + % target channels for markers + if ~isfield(groupdef, 'markchan') + markchan = chan; + else + markchan = groupdef.markchan(j, :); + end + markmask = markchan > 0; + markchan(~markmask) = []; + + %ind not given to recursive call above, so plsmagegrp makes all pulses, specified by pulseind or default + % of source group. Need to reconstruct indices as used for file names by inverting unique + [pind, pind, pind] = unique(pg.pulseind(min(j,end),:)); + + for c = 1:length(pg.pulses(1).data) + for i = 1:length(pg.pulseind) + if j == 1 % first pf determines size + grpdef.pulses(i).data(c).wf = zeros(nchan, size(pg.pulses(pind(i)).data(c).wf, 2)); + grpdef.pulses(i).data(c).marker = zeros(nchan, size(pg.pulses(pind(i)).data(c).wf, 2), 'uint8'); + grpdef.pulses(i).data(c).readout = pg.pulses(pind(i)).data(c).readout; % a bit of a hack. + grpdef.pulses(i).data(c).clk = pg.pulses(pind(i)).data(c).clk; + grpdef.pulses(i).xval = []; + else + for ii=1:size(pg.pulses(pind(i)).data(c).readout,1) + roi=find(grpdef.pulses(pind(i)).data(c).readout(:,1) == pg.pulses(pind(i)).data(c).readout(ii,1)); + if ~isempty(roi) + fprintf('Overwriting readout window\n'); + grpdef.pulses(pind(i)).data(c).readout(roi(1),2:3) = pg.pulses(pind(i)).data(c).readout(ii,2:3); + else + grpdef.pulses(pind(i)).data(c).readout(end+1,:) = pg.pulses(pind(i)).data(c).readout(ii,1:3); + end + end + end + + grpdef.pulses(i).data(c).wf(chan, :) = grpdef.pulses(i).data(c).wf(chan, :) + pg.pulses(pind(i)).data(c).wf(mask, :); + grpdef.pulses(i).data(c).marker(markchan, :) = bitor(grpdef.pulses(i).data(c).marker(markchan, :), ... + pg.pulses(pind(i)).data(c).marker(markmask, :)); + grpdef.pulses(i).xval = [grpdef.pulses(i).xval, pg.pulses(pind(i)).xval]; + end + end + end + + if nargin < 3 || isempty(ind) + ind = 1:length(grpdef.pulses); + else + grpdef.pulses = grpdef.pulses(ind); + end + + [grpdef.pulses.format] = deal('wf'); + + %grpdef = rmfield(grpdef, 'groups', 'matrix', 'offset'); + + otherwise + error('Group control %s not understood.\n',grpdef.ctrl); + end + + if isfield(grpdef, 'xval') && ~isempty(grpdef.xval) + for i=1:length(grpdef.pulses) + grpdef.pulses(i).xval = [grpdef.xval grpdef.pulses(i).xval]; + end + end + + for i = 1:length(ind) + for c=1:length(grpdef.pulses(i).data) + grpdef.pulses(i).data(c).wf = grpdef.matrix * (grpdef.pulses(i).data(c).wf + ... + repmat(grpdef.offset, 1, size(grpdef.pulses(i).data(c).wf, 2))); + if isfield(grpdef, 'trafofn') && ~isempty(grpdef.trafofn) + wf=grpdef.pulses(i).data(c).wf; + for qq=1:length(grpdef.trafofn) + fn=grpdef.trafofn(qq).func; + args=grpdef.trafofn(qq).args; + if ~iscell(args) + args={args}; + end + for q=1:size(wf,1) + wf(q,:) = ... + fn(wf(q,:),q,args{:}); + end + end + grpdef.pulses(i).data(c).wf=wf; + end + if isfield(grpdef, 'markmap') + md = grpdef.pulses(i).data(c).marker; + grpdef.pulses(i).data(c).marker = zeros(size(grpdef.matrix, 1), size(md, 2), 'uint8'); + grpdef.pulses(i).data(c).marker(grpdef.markmap(2, :), :) = md(grpdef.markmap(1, :), :); + end + end + end + + grpdef.ctrl = ['pls', grpdef.ctrl(find(grpdef.ctrl == ' ', 1):end)]; + + + switch ctrl(1:min([end find(ctrl == ' ', 1)-1])) + case 'plot' + if isfield(grpdef,'dict') && ~isempty(grpdef.dict) + plsplot(grpdef.pulses,grpdef.dict,ctrl); + else + plsplot(grpdef.pulses,[],ctrl); + end + + + case 'uploadsimulation' + if ~isempty(strfind(ctrl, 'force')) || plsinfo('stale',grpdef.name) + % modified since last upload (or upload forced) + + % A little naughty; secretly pack all the pulse waveforms together for load... + if pack + if any(~strcmp('wf',{grpdef.pulses.format})) + error('Pack can only deal with waveforms.'); + end + packdef = grpdef; + packdef.pulses=[]; + packdef.pulses(1).format='wf'; + for c=1:length(grpdef.pulses(1).data) + data=vertcat(grpdef.pulses.data); + data=data(:,c); + packdef.pulses(1).data(c).marker = [data.marker]; + packdef.pulses(1).data(c).wf = [data.wf]; + packdef.pulses(1).data(c).clk = data(1).clk; + % awgload/zero doesn't use anything else. + end + else + packdef = grpdef; + end + + if isempty(zerolen) || ~isempty(strfind(ctrl, 'clrzero')) + zerolen = zeros(length(packdef.pulses), length(packdef.chan)); + end + + zerolen = vawg.zero(packdef, ind,[]); + + + if pack + zerolen=repmat(zerolen(1,:)/length(grpdef.pulses),length(grpdef.pulses),1); + end + + % save update time in log. + plslog(end+1).time = now; + plslog(end).params = grpdef.params; + plslog(end).matrix = grpdef.matrix; + plslog(end).varpar = grpdef.varpar; + plslog(end).offset = grpdef.offset; + + if isfield(grpdef.pulses(1).data,'readout') + readout=[]; + for ll=1:length(grpdef.pulses) + if ~isempty(grpdef.pulses(ll).data(1).readout) + readout(:,:,ll) = grpdef.pulses(ll).data(1).readout; + end + end + if any(abs(diff(readout,[],3)) > 1e-10) + warning('Readout changes between pulses in %s\n',name{k}); + end + if(size(readout,1) > 0) + plslog(end).readout = readout(:,:,1); + else + plslog(end).readout=[]; + end + end + + if isfield(grpdef, 'dict') + plslog(end).dict = grpdef.dict; + end + + if isfield(grpdef, 'trafofn') + plslog(end).trafofn = grpdef.trafofn; + end + + if length(plslog) > 2 % copy in case not all pulses updated. First plslog has no xval + plslog(end).xval = plslog(end-1).xval; + end + %plslog(end).xval(:, ind) = vertcat(grpdef.pulses.xval)'; + plslog(end).xval = vertcat(grpdef.pulses.xval)'; % temporary bug fix + plslog(end).ind = ind; + + save([plsdata.grpdir, 'pg_', name{k}], '-append','-v6', 'plslog', 'zerolen'); + logentry('Uploaded group %s, revisions %i.', grpdef.name, length(plslog)); + % fprintf(' in upload of group %s.\n', grpdef.name); + else + fprintf('Skipping group %s.\n', grpdef.name); + end + + end +end + +% Apply a default. +function s=def(s,f,v) + if(~isfield(s,f)) + s=setfield(s,f,v); + end +return; + +% Find an appropriate log entry. +function l=plsinfo_logentry(plslog, time) + +l = length(plslog); + +if ~isempty(time) + while plslog(l).time(1) > time + l = l - 1; + end + + if l == 0 + error('Time travellers beware!'); + end + + if length(plslog(l).time) > 1 && -plslog(l).time(2) < time + error('Group not loaded at requested time!'); + end +end +return \ No newline at end of file diff --git a/plsinfo.m b/plsinfo.m index d05d288..7f01702 100755 --- a/plsinfo.m +++ b/plsinfo.m @@ -10,10 +10,11 @@ global plsdata; -global awgdata; +global vawg; if nargin >= 2 && ~ischar(group) - group = awgdata(1).pulsegroups(group).name; + %assume group = [awg group] + group = vawg.awg(group(1)).pulsegroups(group(2)).name; end if ~exist('time','var') time=[]; @@ -95,9 +96,10 @@ end case 'ro' - ind=awggrpind(group); - if ~isnan(ind) && isfield(awgdata(1).pulsegroups(ind),'readout') && ~isempty(awgdata(1).pulsegroups(ind).readout) - val=awgdata(1).pulsegroups(ind).readout; + error('assure ind is awg index'); + grpIndex = vawg.awgs(ind).grpind(group); + if ~isnan(grpIndex) && isfield(vawg.awgs(ind).pulsegroups(grpIndex),'readout') && ~isempty(vawg.awgs(ind).pulsegroups(grpIndex).readout) + val=vawg.pulsegroups(grpIndex).readout; else warning('off', 'MATLAB:load:variableNotFound'); load([plsdata.grpdir, 'pg_', group], 'grpdef', 'zerolen','plslog'); @@ -126,10 +128,9 @@ end end case 'zl' - ind=awggrpind(group); - if ~isnan(ind) && isfield(awgdata(1).pulsegroups(ind),'zerolen') && ~isempty(awgdata(1).pulsegroups(ind).zerolen) - val=awgdata(1).pulsegroups(ind).zerolen; - else +% error('pulseinfo(zl) is deprecated'); + % vawg.awgs.getPulsegroupField(group,'zerolen'); + warning('off', 'MATLAB:load:variableNotFound'); load([plsdata.grpdir, 'pg_', group], 'zerolen'); warning('on', 'MATLAB:load:variableNotFound'); @@ -140,7 +141,23 @@ end % hack as a dirty bug fix. Size of zerolen does not match group format. % would have to read and merge all component groups. val = zerolen; - end + + +% ind=awggrpind(group); +% if ~isnan(ind) && isfield(vawg.pulsegroups(ind),'zerolen') && ~isempty(vawg.pulsegroups(ind).zerolen) +% val=vawg.pulsegroups(ind).zerolen; +% else +% warning('off', 'MATLAB:load:variableNotFound'); +% load([plsdata.grpdir, 'pg_', group], 'zerolen'); +% warning('on', 'MATLAB:load:variableNotFound'); +% +% if ~exist('zerolen', 'var') +% load([plsdata.grpdir, 'pg_', group], 'grpdef'); +% load([plsdata.grpdir, 'pg_', grpdef.pulses.groups{1}], 'zerolen'); +% end % hack as a dirty bug fix. Size of zerolen does not match group format. +% % would have to read and merge all component groups. +% val = zerolen; +% end case 'gd' load([plsdata.grpdir, 'pg_', group], 'grpdef'); val = grpdef; @@ -151,26 +168,34 @@ if(~isempty(time)) val=val(logentry(val,time)); end - case 'stale' - ind=awggrpind(group); - if isempty(awgwaveforms(group)) - val=1; - elseif ~isnan(ind) && isfield(awgdata(1).pulsegroups(ind),'lastupdate') && isfield(awgdata(1).pulsegroups(ind),'lastload') && ... - ~isempty(awgdata(1).pulsegroups(ind).lastupdate) && ~isempty(awgdata(1).pulsegroups(ind).lastload) - val = awgdata(1).pulsegroups(ind).lastload < awgdata(1).pulsegroups(ind).lastupdate; - else - load([plsdata.grpdir, 'pg_', group], 'lastupdate','plslog','grpdef'); - if(isempty(strfind(grpdef.ctrl,'seq'))) - val = lastupdate > plslog(end).time(end); - if val && nargout == 0 - fprintf('Pulse group ''%s'' is stale.\n',group); - end + case 'stale' + val = false; + for awg = vawg.awgs + + % pulsegroup is unknown to awg + % assure that awg.load creates a pulsegroup in + % storedPulsegroups even if the awg is not affected + if ~isKey(awg.storedPulsegroups,group) + val = true; else - val = 0; - for i=1:length(grpdef.pulses.groups) - val = val || plsinfo('stale',grpdef.pulses.groups{i}); + load([plsdata.grpdir, 'pg_', group], 'lastupdate','plslog','grpdef'); + + if(isempty(strfind(grpdef.ctrl,'seq'))) + + val = val || lastupdate > awg.storedPulsegroups(group).lastload; + if val && nargout == 0 + fprintf('Pulse group ''%s'' is stale.\n',group); + end + else + for i=1:length(grpdef.pulses.groups) + val = val || plsinfo('stale',grpdef.pulses.groups{i}); + end end end + + if val + return; + end end case 'sl' @@ -212,6 +237,8 @@ fprintf('%i: %s - % + further entries\n', i, datestr(plslog(i).time(1)), datestr(plslog(i).time(2))); end end + otherwise + warning('plsinfo: unknown command %s',ctrl); end return diff --git a/plsmakegrp.m b/plsmakegrp.m index be6b030..c9e9e4d 100755 --- a/plsmakegrp.m +++ b/plsmakegrp.m @@ -2,7 +2,7 @@ % grpdef = plsmakegrp(name, ctrl, ind, opts) % Covert pulses in pulsegroup to wf format. % name: group name. -% ctrl: 'plot', 'plot chrg', 'check', 'upload' +% ctrl: 'plot', 'check', 'upload' % for maintenance/debugging: 'clrzero', 'local'. % These may mess with the upload logging, so use with care. % ind: optional pulse index. The default is pulseind or all pulses. @@ -12,7 +12,7 @@ % (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. global plsdata; -global awgdata; +global vawg; if ~exist('ctrl','var') ctrl=''; @@ -69,8 +69,9 @@ grpdef.params = []; end if isfield(grpdef, 'time')&& ~isempty(grpdef.time) - fprintf('Ignoring opts.time '); - opts.time=grpdef.time; + fprintf('Ignoring opts.time\n'); + opts.time=grpdef.time; + end switch grpdef.ctrl(1:min([end find(grpdef.ctrl == ' ', 1)-1])) @@ -301,8 +302,9 @@ plsplot(grpdef.pulses,[],ctrl); end - case 'check' + case 'check' for i = 1:length(ind) + error('scale implementatoin for vawg is missing.'); over=0; for a=1:length(awgdata) for c=1:length(grpdef.pulses(i).data) @@ -345,11 +347,14 @@ % Actually handle the upload... if isempty(strfind(ctrl, 'local')) - zerolen = awgload(packdef, ind); - zerolen=zerolen{1}; + for awg = vawg.awgs + awg.load(packdef, ind); + zerolen = awg.zeroLength(packdef,ind,zerolen); + end else - zerolen = awgzero(packdef, ind); - zerolen=zerolen{1}; + error('local not supported in multiple awg upload yet') +% zerolen = awgzero(packdef, ind); +% zerolen = zerolen{1}; end if pack @@ -401,7 +406,6 @@ else fprintf('Skipping group %s.\n', grpdef.name); end - end end diff --git a/plsplot.m b/plsplot.m index d9e56d1..0a4db0a 100755 --- a/plsplot.m +++ b/plsplot.m @@ -13,7 +13,6 @@ function plsplot(pulse, dict, ctrl) styles={'b-','r-','g-'}; for i = 1:length(pulse) figure(30); - if isempty(strfind(ctrl,'hold')) clf; else @@ -22,15 +21,6 @@ function plsplot(pulse, dict, ctrl) subplot(222); hold on; subplot(223); hold on; end - - % Added by Pascal on 13.06.2014 to plot in a current charge diagram - if ~isempty(strfind(ctrl, 'chrg')) - if ishandle(1) - ax = findall(1,'type','axes'); - sp2 = subplot(222); hold on; - copyobj(allchild(ax(end)), sp2); - end; - end; pls = plstowf(pulse(i),dict); @@ -38,20 +28,11 @@ function plsplot(pulse, dict, ctrl) for c=1:length(pls.data) subplot(221) x=linspace(0,(size(pls.data(c).wf,2)-1)*1e9/pls.data(c).clk,size(pls.data(c).wf,2)); - plot(repmat(x,size(pls.data(c).wf,1),1)',pls.data(c).wf');%, time(1:end-1), round((data + vc)*2^13)./2^13); hold on; subplot(222) hold on; - % Added by Pascal on 13.06.2014 to plot in a current charge diagram - % This changes the units of subplot(222) so that charge diagram - % fits. Therefore everything multiplied by 1e-3. - if ~isempty(strfind(ctrl, 'chrg')) - pls.data(c).wf = pls.data(c).wf.*1e-3; - title('Multiplied by 1e-3'); - end; - for j = 1:2:size(pls.data(c).wf, 1)-1 plot(pls.data(c).wf(j, :), pls.data(c).wf(j+1, :),styles{(j+1)/2});%, data(1, :) + vc(1, :), data(2, :) + vc(2, :)); end diff --git a/plstotab.m b/plstotab.m index 99ad439..37b3a3d 100755 --- a/plstotab.m +++ b/plstotab.m @@ -232,32 +232,7 @@ % 'rf' has marktab HIGH for M1 and M2 during rf pulse marktab(:, end+1) = [pulsetab(1, end-1)-pulsedef(i).time(2); 0; pulsedef(i).time(1:3)*[1; 1; 1]; 0; 0]; - case 'rf_chirp' ;% linear sweep of freq over edsr burst - global plsdata; % needed for tbase to scale freq - - % val = [freq amplitude phase fm_amp(p-p) tau] - % pulsedef(i).val(1) = pulsedef(i).val(1)./ (1e9/plsdata.tbase); - - freq=pulsedef(i).val(1); %in MHz - amp=pulsedef(i).val(2); % in mV - phase=pulsedef(i).val(3); % in rads - fm_amp=pulsedef(i).val(4); % depth of FM modulation (full min-max range) - tau = pulsedef(i).time(1); % length of edsr burst - - I = @(t) 2*amp*cos(2*pi*((freq-fm_amp/2)*t+((fm_amp/tau)*t.^2)+phase)); % AWG expects Vpp on default, Vpp=2*Vp, I and Q scaled - zero = @(t) 0; - if pulsedef(i).time(1) > 1e-11 - pulsetab(1, end+1) = pulsetab(1, end) + pulsedef(i).time(1); - pulsetab(2:3,end) = [I(pulsedef(i).time(1));0]; - pulsefn(end+1).fn = {I,zero}; - pulsefn(end).t = [pulsetab(1,end+(-1:0))]; - else - pulsetab = [pulsetab, [0; I(0); 0]]; - end - % 'rf_chirp' has marktab HIGH for M1 and M2 during rf pulse - marktab(:, end+1) = [pulsetab(1, end-1)-pulsedef(i).time(2); 0; pulsedef(i).time(1:3)*[1; 1; 1]; 0; 0]; - case 'rfmarkoff' global plsdata; % needed for tbase to scale freq diff --git a/plstowf.m b/plstowf.m index 73b4e97..3e5a8ff 100755 --- a/plstowf.m +++ b/plstowf.m @@ -30,7 +30,8 @@ dt=1e-11; global plsdata; -global awgdata; +global vawg; + pulse = plsdefault(pulse); if strcmp(pulse.format, 'wf') @@ -72,7 +73,7 @@ if ~isfield(pulseinf, 'readout') pulseinf.readout = []; end -clk = unique([awgdata.clk]); +clk = unique([vawg.awgs.clk]); for c=1:length(clk) pulsetab = pulseinf.pulsetab; nchan = size(pulsetab, 1)-1; diff --git a/plsupdate.m b/plsupdate.m index 80b14d3..e0c6cbe 100755 --- a/plsupdate.m +++ b/plsupdate.m @@ -13,7 +13,7 @@ function plsupdate(newdef) % Not implmented: Missing or nan entries of params are taken from previous values. global plsdata; -global awgdata; +global vawg; if length(newdef) > 1 if iscell(newdef) @@ -147,13 +147,11 @@ function plsupdate(newdef) lastupdate = now; save(file, '-append', 'grpdef', 'lastupdate'); logentry('Updated group %s.', grpdef.name); - ind = awggrpind(grpdef.name); - if ~isnan(ind) - for i=1:length(awgdata) - awgdata(i).pulsegroups(ind).lastupdate=now; - end + for awg = vawg.awgs + awg.markforupdate(grpdef.name); end + else fprintf('Didn''t update group "%s": nothing changed\n',grpdef.name); end diff --git a/smcPXDAC.m b/smcPXDAC.m new file mode 100644 index 0000000..50f9554 --- /dev/null +++ b/smcPXDAC.m @@ -0,0 +1,51 @@ +function val = smcPXDAC(ico, val, rate) +% 1: none +% 2: clock, +% 3-6: peak to peak range for ch 1-4 +% 7: none +% 8-11 +% +% Extra fields that can go into smdata.inst(x).data +% chain ; setting the frequency or pulseline on this instrument 'chains' to the specified instrument; +% this allows one to seamlessly set the pulseline on many awg's together. +% clockmult; a multiplier to be applied to any clock frequency sets on this device. +% allows 7k and 5k to be mixed. + +global smdata; + +pxdac = smdata.inst(ico(1)).data; + +cmds = {':FREQ', ':FREQ', 'SOUR1:VOLT', 'SOUR2:VOLT', 'SOUR3:VOLT', 'SOUR4:VOLT', 'SEQ:JUMP', ... + 'SOUR1:VOLT:OFFS', 'SOUR2:VOLT:OFFS','SOUR3:VOLT:OFFS','SOUR4:VOLT:OFFS','SOUR1:MARK1:VOLT:LOW',... + 'SOUR1:MARK1:VOLT:HIGH', 'SOUR1:MARK2:VOLT:LOW', 'SOUR1:MARK2:VOLT:HIGH', ... + 'SOUR2:MARK1:VOLT:LOW', 'SOUR2:MARK1:VOLT:HIGH', 'SOUR2:MARK2:VOLT:LOW', 'SOUR2:MARK2:VOLT:HIGH'... + 'SOUR3:MARK1:VOLT:LOW', 'SOUR3:MARK1:VOLT:HIGH', 'SOUR3:MARK2:VOLT:LOW', 'SOUR3:MARK2:VOLT:HIGH'... + 'SOUR4:MARK1:VOLT:LOW', 'SOUR4:MARK1:VOLT:HIGH', 'SOUR4:MARK2:VOLT:LOW', 'SOUR4:MARK2:VOLT:HIGH'}; + +switch ico(2) + case 1; + error('PXDAC provides no frequency generator mode.'); + case 2; + switch ico(3) + case 1 + pxdac.setOutputVoltage(ico(2)-2,val); + case 0 + val = pxdac.getOutputVoltage(ico(2)-2); + otherwise + error('Only supports get and set operations.'); + end + case 3:6; + switch ico(3) + case 1 + pxdac.setOutputVoltage(ico(2)-2,val); + case 0 + val = pxdac.getOutputVoltage(ico(2)-2); + otherwise + error('Only supports get and set operations.'); + end + + case 8:11; + error('PXDAC supports no hardware DC offset.'); + otherwise + error('Operation %d not supported',ico(2)); +end From 507bd656084d258d33e1d8a13179fadc4af62127 Mon Sep 17 00:00:00 2001 From: terryFitch Date: Tue, 3 Mar 2015 14:38:39 +0100 Subject: [PATCH 02/24] Remove outdated files and documentation. --- .hgignore | 12 - COPYING | 674 ---------------------- awgadd.m | 279 --------- awgclear.m | 97 ---- awgcntrl.m | 165 ------ awggetdata.m | 18 - awggroups.m | 15 - awggrpind.m | 32 - awglist.m | 30 - awgload.m | 147 ----- awgloaddata.m | 20 - awgnpulse.m | 20 - awgrm.m | 49 -- awgsavedata.m | 12 - awgseqind.m | 43 -- awgswap.m | 106 ---- awgsyncwaveforms.m | 20 - awgupdate.m | 72 --- awgwaveforms.m | 34 -- awgzero.m | 23 - doc/awgsetup.m | 32 - doc/figures.odp | Bin 20435 -> 0 bytes doc/group_struct.png | Bin 55367 -> 0 bytes doc/helptoc.xml | 54 -- doc/mxdom2simplehtml.xsl | 360 ------------ doc/namespace.png | Bin 63144 -> 0 bytes doc/plssetup.m | 32 - doc/pulse_struct.png | Bin 61368 -> 0 bytes doc/pulsecontrol_features.html | 83 --- doc/pulsecontrol_features.m | 14 - doc/pulsecontrol_functions_by_cat.html | 425 -------------- doc/pulsecontrol_functions_by_cat.m | 293 ---------- doc/pulsecontrol_getting_started.html | 261 --------- doc/pulsecontrol_getting_started.m | 163 ------ doc/pulsecontrol_how_to_doc.html | 104 ---- doc/pulsecontrol_how_to_doc.m | 30 - doc/pulsecontrol_product_page.html | 86 --- doc/pulsecontrol_product_page.m | 16 - doc/pulsecontrol_recipes.html | 80 --- doc/pulsecontrol_recipes.m | 11 - doc/pulsecontrol_system_requirements.html | 77 --- doc/pulsecontrol_system_requirements.m | 7 - doc/pulsecontrol_troubleshooting.html | 72 --- doc/pulsecontrol_troubleshooting.m | 3 - doc/pulsecontrol_user_guide.html | 664 --------------------- doc/pulsecontrol_user_guide.m | 536 ----------------- doc/taurc.png | Bin 4387 -> 0 bytes doc/workflow.png | Bin 22785 -> 0 bytes plsdefgrp_v1.m | 104 ---- plsmakegrp_v1.m | 231 -------- 50 files changed, 5606 deletions(-) delete mode 100755 .hgignore delete mode 100755 COPYING delete mode 100755 awgadd.m delete mode 100755 awgclear.m delete mode 100755 awgcntrl.m delete mode 100755 awggetdata.m delete mode 100755 awggroups.m delete mode 100755 awggrpind.m delete mode 100755 awglist.m delete mode 100755 awgload.m delete mode 100755 awgloaddata.m delete mode 100755 awgnpulse.m delete mode 100755 awgrm.m delete mode 100755 awgsavedata.m delete mode 100755 awgseqind.m delete mode 100755 awgswap.m delete mode 100755 awgsyncwaveforms.m delete mode 100755 awgupdate.m delete mode 100755 awgwaveforms.m delete mode 100755 awgzero.m delete mode 100644 doc/awgsetup.m delete mode 100644 doc/figures.odp delete mode 100644 doc/group_struct.png delete mode 100644 doc/helptoc.xml delete mode 100755 doc/mxdom2simplehtml.xsl delete mode 100644 doc/namespace.png delete mode 100644 doc/plssetup.m delete mode 100644 doc/pulse_struct.png delete mode 100644 doc/pulsecontrol_features.html delete mode 100644 doc/pulsecontrol_features.m delete mode 100644 doc/pulsecontrol_functions_by_cat.html delete mode 100644 doc/pulsecontrol_functions_by_cat.m delete mode 100644 doc/pulsecontrol_getting_started.html delete mode 100644 doc/pulsecontrol_getting_started.m delete mode 100644 doc/pulsecontrol_how_to_doc.html delete mode 100644 doc/pulsecontrol_how_to_doc.m delete mode 100644 doc/pulsecontrol_product_page.html delete mode 100644 doc/pulsecontrol_product_page.m delete mode 100644 doc/pulsecontrol_recipes.html delete mode 100644 doc/pulsecontrol_recipes.m delete mode 100644 doc/pulsecontrol_system_requirements.html delete mode 100644 doc/pulsecontrol_system_requirements.m delete mode 100644 doc/pulsecontrol_troubleshooting.html delete mode 100644 doc/pulsecontrol_troubleshooting.m delete mode 100644 doc/pulsecontrol_user_guide.html delete mode 100644 doc/pulsecontrol_user_guide.m delete mode 100644 doc/taurc.png delete mode 100644 doc/workflow.png delete mode 100755 plsdefgrp_v1.m delete mode 100755 plsmakegrp_v1.m diff --git a/.hgignore b/.hgignore deleted file mode 100755 index 95cf8ac..0000000 --- a/.hgignore +++ /dev/null @@ -1,12 +0,0 @@ -# use glob syntax. -syntax: glob -# ignore emacs autosave files -*~ -# ignore matlab autosave files -*.asv - -# switch to regexp syntax. -syntax: regexp -# ignore unused backup subdirectories. -^bak/ -^new/ diff --git a/COPYING b/COPYING deleted file mode 100755 index 94a9ed0..0000000 --- a/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/awgadd.m b/awgadd.m deleted file mode 100755 index 0ce5a7b..0000000 --- a/awgadd.m +++ /dev/null @@ -1,279 +0,0 @@ -function awgadd(groups) -% awgadd(groups) -% Add groups to end of sequence. Store group name and target index in -% awgdata.pulsgroups.name, seqind. - -% Group control 'seq' creates sequence combined groups -% ------------------------------------------------------------------------- -% Add groups like: pg.pulses.groups = {'group_1', 'group_2', 'group_4'}; -% At least one of the subgroup names should be the same name as the group -% name itself followed by an underscore and a number, e.g. -% pg.name = 'groups' for the above example of pg.pulses.groups. -% Set group control: pg.ctrl = 'loop seq'; -% Set order of pulses froms groups with pg.pulseind. Subgroups are indexed -% by row, pulses of the group are indexed by column. The value gives the -% number of pulse to use from original subgroup. A zero indicates not to -% use a pulse from the corresponding group in the respective position. For -% 8 pulses per group this might look like: -% pg.pulseind(1,:) = [1:8 zeros(1,16)]; -% pg.pulseind(2,:) = [zeros(1,8) 1:8 zeros(1,8)]; -% pg.pulseind(3,:) = [zeros(1,16) 1:8]; -% ------------------------------------------------------------------------- -% -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - -global plsdata; -global awgdata; -astart=toc; -awgcntrl('clr'); -awgcntrl('stop'); - -if ~iscell(groups) - groups = {groups}; -end - -dosave = false; % keeps track of whether awgdata changed. -gstart=toc; - - -for k = 1:length(groups) - load([plsdata.grpdir, 'pg_', groups{k}]); - - stayInWhileLoop = true; - while plsinfo('stale',groups{k}) && stayInWhileLoop - fprintf('Latest pulses of group %s not loaded; %s > %s.\n', groups{k}, ... - datestr(lastupdate), datestr(plslog(end).time(end))); - tstart=toc; - plsmakegrp(groups{k},'upload'); - ts2 = toc; - awgcntrl('wait'); - % fprintf('Load time=%f secs, wait time=%f\n',toc-tstart,toc-ts2); - load([plsdata.grpdir, 'pg_', groups{k}]); - - if ~isempty(strfind(grpdef.ctrl, 'seqhack')) % Added by Pascal on 2014_08_01 to fix behaviour of sequence joined groups - stayInWhileLoop = false; - warning('Not checking whether pulses stale since grpdef.ctrl = ''seqhack''.'); - end; - end - - - if strcmp(grpdef.ctrl(1:min([end find(grpdef.ctrl == ' ', 1)-1])), 'grp')... - && ~isempty(strfind(grpdef.ctrl, 'seq')) % combine groups at sequence level. - - % retrieve channels of component groups - clear chan; - for m = 1:length(grpdef.pulses.groups) - gd=plsinfo('gd', grpdef.pulses.groups{m}); - rf={'varpar', 'pulseind', 'time'}; % Required fields that may be missing - for qq=1:length(rf) - if ~isfield(gd,rf{qq}) - gd=setfield(gd,rf{qq},[]); - end - end - chan(m) = orderfields(gd); - end - chan = {chan.chan}; - seqmerge = true; - else - if ~isfield(grpdef, 'pulseind') - zerolen = plsinfo('zl', grpdef.name); % hack TB - grpdef.pulseind = 1:size(zerolen, 1); - end - - seqmerge = false; - end - - if ~isfield(grpdef, 'nrep') - grpdef.nrep = 1; - end - - for a=1:length(awgdata) - npls = size(grpdef.pulseind, 2); - nchan = length(awgdata(a).chans); % alternatively use awgdata or data size - usetrig = (grpdef.nrep(1) ~= Inf) && isempty(strfind(grpdef.ctrl, 'notrig')); - - if isempty(awgdata(a).pulsegroups) - startline = 1; - gind = []; - else - gind = strmatch(grpdef.name, {awgdata(a).pulsegroups.name}, 'exact'); - if ~isempty(gind) - startline = awgdata(a).pulsegroups(gind(1)).seqind; - if npls + usetrig ~= sum(awgdata(a).pulsegroups(gind(1)).npulse); - error('Number of pulses changed in group %s. Use awgrm first!', grpdef.name); - end - else - startline = awgdata(a).pulsegroups(end).seqind + sum(awgdata(a).pulsegroups(end).nline); - end - end - - if isempty(gind) % group not loaded yet, extend sequence - - gind = length(awgdata(a).pulsegroups)+1; - awgdata(a).pulsegroups(gind).name = grpdef.name; - awgdata(a).pulsegroups(gind).seqind = startline; - awgdata(a).pulsegroups(gind).lastupdate = lastupdate; - awgdata(a).pulsegroups(gind).npulse = [npls usetrig]; - if strfind(grpdef.ctrl,'pack') - awgdata(a).pulsegroups(gind).nline = 1+usetrig; - % Hack alert; way too much code assumes zl == pulselen. For - % packed groups, we ignore it and work out the correct length - % ourselves. - zlmult=npls; - npls=1; - else - zlmult=1; - awgdata(a).pulsegroups(gind).nline = npls+usetrig; - end - fprintf(awgdata(a).awg, sprintf('SEQ:LENG %d', startline + awgdata(a).pulsegroups(gind).nline-1)); - dosave = 1; - else - if strfind(grpdef.ctrl,'pack') - zlmult = npls; - npls=1; - else - zlmult=1; - end - if isfield(plslog, 'readout') && exist('zerolen', 'var') - if any(awgdata(a).pulsegroups(gind).nrep ~= grpdef.nrep) || ... - any(any(awgdata(a).pulsegroups(gind).readout ~= plslog(end).readout)) || ... - any(any(awgdata(a).pulsegroups(gind).zerolen ~= zerolen)) % nrep or similar changed - dosave = 1; - end - else - if any(awgdata(a).pulsegroups(gind).nrep ~= grpdef.nrep) % nrep changed - dosave = 1; - end - end; - end - - if ~isfield(grpdef, 'jump') - if strfind(grpdef.ctrl, 'loop') - grpdef.jump = [npls; 1]; - else - grpdef.jump = []; - end - end - - - awgdata(a).pulsegroups(gind).nrep = grpdef.nrep; - awgdata(a).pulsegroups(gind).lastload = plslog(end).time(1); - - % Added block below to fix 'seq', where zerolen and plslog(end).readout - % are not available 02.05.2014 PC - if ~exist('zerolen', 'var') - zerolen = []; - for groupInd = 1:length(grpdef.pulses.groups) - tempGrp = load([plsdata.grpdir, 'pg_', grpdef.pulses.groups{groupInd}]); - zerolen = vertcat(zerolen, tempGrp.zerolen); - end - plslog(end).readout = tempGrp.plslog(end).readout; - awgdata(a).pulsegroups(gind).lastload = now; - clear tempGrp groupInd - end; - - awgdata(a).pulsegroups(gind).zerolen = zerolen; % Cache some handy stuff here. - awgdata(a).pulsegroups(gind).readout=plslog(end).readout; - - if usetrig - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:WAV1 "trig_%08d"', startline, awgdata(a).triglen)); - for j = 2:nchan - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', startline, j, awgdata(a).triglen, awgdata(a).zerochan(j))); - end - if isfield(awgdata(a),'slave') && ~isempty(awgdata(a).slave) && (awgdata(a).slave) - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:TWAIT 1\n', startline)); - end - end - - - for i = 1:npls - ind = i-1 + startline + usetrig; - if ~seqmerge % pulses combined here. - for j = 1:nchan - ch = find(awgdata(a).chans(j) == grpdef.chan); - if ~isempty(ch) && zerolen(grpdef.pulseind(i), ch) < 0 - % channel in group and not zero - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:WAV%d "%s_%05d_%d"', ind, j, ... - grpdef.name, grpdef.pulseind(i), ch)); - else - % hack alert. We should really make zerolen a cell array. fixme. - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', ind, j, ... - zlmult*abs(zerolen(grpdef.pulseind(i), 1))*awgdata(a).clk/awgdata(1).clk,awgdata(a).zerochan(j))); - end - end - else % completely overhauled 02.05.2014 PC - for m = 1:length(grpdef.pulses.groups) - for j = 1:nchan % channels of component groups - ch = find(awgdata(a).chans(j) == chan{m}); - if grpdef.pulseind(m, i) > 0 % Do not add if pulseind == 0 - if ~isempty(ch) && zerolen(grpdef.pulseind(m, i), ch) < 0 - % channel in group and not zero - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:WAV%d "%s_%05d_%d"', ind, j, ... - grpdef.pulses.groups{m}, grpdef.pulseind(m, i), ch)); - else - fprintf(awgdata.awg, sprintf('SEQ:ELEM%d:WAV%d "zero_%08d_%d"', ind, j, ... - zlmult*abs(zerolen(grpdef.pulseind(m, i), 1))*awgdata(a).clk/awgdata(1).clk,awgdata(a).zerochan(j))); - end - end; - end - end - end - if grpdef.nrep(min(i, end)) == Inf || grpdef.nrep(min(i, end)) == 0 ... - || (i == npls && isempty(strfind(grpdef.ctrl, 'loop')) && (isempty(grpdef.jump) || all(grpdef.jump(1, :) ~= i))) - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:LOOP:INF 1', ind)); - else - fprintf(awgdata(a).awg, 'SEQ:ELEM%d:LOOP:INF 0', ind); % default - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:LOOP:COUN %d', ind, grpdef.nrep(min(i, end)))); - end - - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:GOTO:STAT 0', ind)); - - if grpdef.nrep(min(i, end)) == Inf && isreal(grpdef.pulses) && ... - (length(awgdata(a).seqpulses) < ind || awgdata(a).seqpulses(ind) ~= grpdef.pulses(grpdef.pulseind(i))); - dosave = 1; - awgdata(a).seqpulses(ind) = grpdef.pulses(grpdef.pulseind(i)); - end - if ~mod(i, 100) - fprintf('%i/%i pulses added.\n', i, npls); - end - end - %fprintf('Group load time: %g secs\n',toc-gstart); - - jstart=toc; - % event jumps - %SEQ:ELEM%d:JTARget:IND - %SEQ:ELEM%d:JTARget:TYPE - - for j = 1:size(grpdef.jump, 2) - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:GOTO:IND %d', startline+usetrig-1 + grpdef.jump(:, j))); - fprintf(awgdata(a).awg, sprintf('SEQ:ELEM%d:GOTO:STAT 1', startline+usetrig-1 + grpdef.jump(1, j))); - end - end - if ~exist('seqlog','var') - seqlog.time = now; - else - seqlog(end+1).time = now; - end - seqlog(end).nrep = grpdef.nrep; - seqlog(end).jump = grpdef.jump; - - save([plsdata.grpdir, 'pg_', groups{k}], '-append', 'seqlog'); - %fprintf('Jump program time: %f secs\n',toc-jstart); - wstart=toc; - awgcntrl('wait'); - %fprintf('Wait time: %f secs; total time %f secs\n',toc-wstart,toc-astart); - nerr=0; - for a=1:length(awgdata(a)) - err=query(awgdata(a).awg, 'SYST:ERR?'); - if ~isempty(strfind(err, 'No error')) - nerr=nerr+1; - end - end - if nerr == 0 - fprintf('Added group %s on index %i. %s', grpdef.name, gind, err); - logentry('Added group %s on index %i.', grpdef.name, gind); - end -end -if dosave - awgsavedata; -end diff --git a/awgclear.m b/awgclear.m deleted file mode 100755 index c527442..0000000 --- a/awgclear.m +++ /dev/null @@ -1,97 +0,0 @@ -function awgclear(groups,options) -% awgclear(groups) -% OR -% awgclear('all') -% awgclear('pack') removes all groups, adds back groups loaded in sequences -% awgclear('all','paranoid') removes all waveforms, including those not known to be loaded. -% awgclear('pack','paranoid') similar - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - -if ~exist('options','var') - options=''; -end -global awgdata; -global plsdata; - -if strcmp(groups, 'pack') - grps={awgdata(1).pulsegroups.name}; - awgclear('all',options); - awgadd(grps); - return; -end - -if strcmp(groups, 'all') - % Mark only groups known to be loaded as loaded. - if isempty(strfind(options,'paranoid')) - g=awgwaveforms; - else % Mark all pulse groups as not loaded - g=plsinfo('ls'); - end - for a=1:length(awgdata) - fprintf(awgdata(a).awg,'WLIS:WAV:DEL ALL\n') - awgdata(a).zeropls=[]; - end - - logentry('Cleared all pulses.'); - for i=1:length(g) - load([plsdata.grpdir, 'pg_', g{i}, '.mat'], 'plslog'); - if(plslog(end).time(end) <= 0) - %fprintf('Skipping group ''%s''; already unloaded\n',g{i}); - else - plslog(end).time(end+1) = -now; - save([plsdata.grpdir, 'pg_', g{i}, '.mat'], 'plslog','-append'); - %fprintf('Marking group ''%s'' as unloaded\n',g{i}); - end - end - awgrm('all'); - return; -end - -if strcmp(groups,'unused') - g=awgwaveforms; - g2={awgdata(1).pulsegroups.name}; - groups=setdiff(g,g2); - for i=1:length(groups) - fprintf('Unloading %s\n',groups{i}); - end -end - -if ischar(groups) - groups = {groups}; -end -tic; -for a=1:length(awgdata) - if isreal(groups) - groups = sort(groups, 'descend'); - for i = groups - wf = query(awgdata(a).awg, sprintf('WLIS:NAME? %d', i)); - if ~query(awgdata(a).awg, sprintf('WLIS:WAV:PRED? %s', wf), '%s\n', '%i') - fprintf(awgdata(a).awg, 'WLIS:WAV:DEL %s', wf); - end - if toc > 20 - fprintf('%i/%i\n', i, length(groups)); - tic; - end - end - awgcntrl('wait'); - return; - end -end - -for k = 1:length(groups) - load([plsdata.grpdir, 'pg_', groups{k}], 'plslog'); - for a=1:length(awgdata) - wfms=awgwaveforms(groups{k},a,'delete'); - for i=1:length(wfms) - fprintf(awgdata(a).awg, sprintf('WLIS:WAV:DEL "%s"', wfms{i})); - end - end - -plslog(end).time(end+1) = -now; -save([plsdata.grpdir, 'pg_', groups{k}], '-append', 'plslog'); -logentry('Cleared group %s.', groups{k}); -fprintf('Cleared group %s.\n', groups{k}); - -awgrm(groups{k}); -end diff --git a/awgcntrl.m b/awgcntrl.m deleted file mode 100755 index 635bb80..0000000 --- a/awgcntrl.m +++ /dev/null @@ -1,165 +0,0 @@ -function val = awgcntrl(cntrl, chans) -% awgcntrl(cntrl, chans) -% cntrl: stop, start, on off, wait, raw|amp, israw, extoff|exton, isexton, err, clr -% several commands given are processed in order. -% isamp and isexton return a vector of length chans specifying which are -% amp or in exton mode - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - -global awgdata; -val=[]; -if nargin <2 - chans = []; -end - -breaks = [regexp(cntrl, '\<\w'); regexp(cntrl, '\w\>')]; - -for k = 1:size(breaks, 2); - switch cntrl(breaks(1, k):breaks(2, k)) - case 'stop' - for a=1:length(awgdata) - fprintf(awgdata(a).awg, 'AWGC:STOP'); - end - - case 'start' - for a=1:length(awgdata) - fprintf(awgdata(a).awg, 'AWGC:RUN'); - end - awgcntrl('wait'); - - case 'off' - for a=1:length(awgdata) - for i = ch(awgdata(a), chans) - fprintf(awgdata(a).awg, 'OUTPUT%i:STAT 0', i); - end - end - case 'on' - for a=1:length(awgdata) - for i = ch(awgdata(a), chans) - fprintf(awgdata(a).awg, 'OUTPUT%i:STAT 1', i); - end - end - - - case 'wait' - for a=1:length(awgdata) - to = awgdata(a).awg.timeout; - awgdata(a).awg.timeout = 600; - query(awgdata(a).awg, '*OPC?'); - awgdata(a).awg.timeout = to; - end - - case 'raw' - if any(any(~awgcntrl('israw'))) - for a=1:length(awgdata) - if ~is7k(awgdata(a)) - for i = ch(awgdata(a), chans) - fprintf(awgdata(a).awg, 'AWGC:DOUT%i:STAT 1', i); - end - end - end - else - fprintf('Already raw\n'); - end - - case 'amp' - if any(any(awgcntrl('israw'))) - for a=1:length(awgdata) - if ~is7k(awgdata(a)) - for i = ch(awgdata(a), chans) - fprintf(awgdata(a).awg, 'AWGC:DOUT%i:STAT 0', i); - end - end - end - else - fprintf('Already amp\n'); - end - - case 'israw' - - val=[]; - for a=1:length(awgdata) - if ~is7k(awgdata(a)) - for i = ch(awgdata(a), chans) - fprintf(awgdata(a).awg, 'AWGC:DOUT%i:STAT?',i); - val(end+1) = fscanf(awgdata(a).awg,'%f'); - end - end - end - - case 'exton' %adds external DC to outputs specified in chans - for a=1:length(awgdata) - if ~is7k(awgdata(a)) - for i = ch(awgdata(a),chans) - fprintf(awgdata(a).awg, 'SOUR%i:COMB:FEED "ESIG"', i); - end - end - end - - case 'extoff' %turns off external DC - for a=1:length(awgdata) - if ~is7k(awgdata(a)) - for i = ch(awgdata(a),chans) - fprintf(awgdata(a).awg, 'SOUR%i:COMB:FEED ""', i); - end - end - end - case 'isexton' - val=[]; - for a=1:length(awgdata) - if ~is7k(awgdata(a)) - for i = ch(awgdata(a), chans) - - fprintf(awgdata(a).awg, 'SOUR%i:COMB:FEED?',i); - val(end+1) = strcmp(fscanf(awgdata(a).awg, '%f'), 'ESIG'); - end - end - end - case 'err' - for a=1:length(awgdata) - err=query(awgdata(a).awg, 'SYST:ERR?'); - if strcmp(err(1:end-1), '0,"No error"') - % Supress blank error messages. - else - fprintf('%d: %s\n',a,err); - end - end - - case 'clr' - for a=1:length(awgdata) - i = 0; - err2 = sprintf('n/a.\n'); - while 1 - err = query(awgdata(a).awg, 'SYST:ERR?'); - if strcmp(err(1:end-1), '0,"No error"') - if i > 0 - fprintf('%d: %i errors. Last %s', a, i, err2); - end - break; - end - err2 = err; - i = i + 1; - end - end - case 'norm' - for i = 1:4 - fprintf(awgdata.awg, 'SOUR%i:VOLT:AMPL .6', i); - end - case 'dbl' - for i = 1:4 - fprintf(awgdata.awg, 'SOUR%i:VOLT:AMPL 1.2', i); - end - end - end -end - -function chans=ch(awg, chans) - if isempty(chans) - chans=1:length(awg.chans); - end -end - -function val=is7k(awg) - val=length(awg.chans) <= 2; -end \ No newline at end of file diff --git a/awggetdata.m b/awggetdata.m deleted file mode 100755 index 55b9cf9..0000000 --- a/awggetdata.m +++ /dev/null @@ -1,18 +0,0 @@ -function data = awggetdata(time) -% awgloaddata -% load latest awgdata file saved by awgsavedata. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global plsdata; - -if nargin < 1 - time = inf; -end - -d = dir(sprintf('%sawgdata_*', plsdata.grpdir)); -mi = find([d.datenum] < time, 1, 'last'); -load([plsdata.grpdir, d(mi).name]); - - diff --git a/awggroups.m b/awggroups.m deleted file mode 100755 index c6ffec1..0000000 --- a/awggroups.m +++ /dev/null @@ -1,15 +0,0 @@ -function awggroups(ind) - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global awgdata; - -if nargin < 1 - ind = 1:length(awgdata(1).pulsegroups); -end - -for i = ind - zl=plsinfo('zl',awgdata(1).pulsegroups(i).name); - fprintf('%2i: %-15s (%3i pulses, %5.2f us, %d lines)\n', i, awgdata(1).pulsegroups(i).name, awgdata(1).pulsegroups(i).npulse(1), abs(zl(1)*1e-3),awgdata(1).pulsegroups(i).nline); -end diff --git a/awggrpind.m b/awggrpind.m deleted file mode 100755 index d662ccc..0000000 --- a/awggrpind.m +++ /dev/null @@ -1,32 +0,0 @@ -function grp = awggrpind(grp) -% function grpind = grpind(grp) -% Find group index from name of loaded group. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global awgdata; - -if ischar(grp) - grp = {grp}; -end - -if ~isfield(awgdata(1).pulsegroups,'name') - names={}; -else - names={awgdata(1).pulsegroups.name}; -end - -if iscell(grp) - for i = 1:length(grp) - grp{i} = max(strmatch(grp{i}, names, 'exact')); - if isempty(grp{i}) - grp{i} = nan; - %fprintf('Group not loaded.\n'); - %error('Group not loaded.'); - end - end - grp = cell2mat(grp); -elseif any(grp > length(awgdata(1).pulsegroups)) - awgerror('Group index too large.'); -end diff --git a/awglist.m b/awglist.m deleted file mode 100755 index b72ece5..0000000 --- a/awglist.m +++ /dev/null @@ -1,30 +0,0 @@ -function awglist(ind,awg) -% function awglist([ind],[awg]) -% List the waveforms present on an awg. If ind is absent, list all waveforms. -% If ind is negative, list the last (-ind) waveforms. -% -% Awg specifies which awg in awgdata(i) to use, and defaults to 1. Nominally -% the output should not depend on awg. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global awgdata; - -if ~exist('awg','var') || isempty(awg) - awg=1; -end - -if nargin < 1 - ind = 1:query(awgdata(awg).awg, 'WLIS:SIZE?', '%s\n', '%i')-1; -elseif ind < 0 - ind = query(awgdata(awg).awg, 'WLIS:SIZE?', '%s\n', '%i')+(ind:-1); -end - -for i = ind - wf = query(awgdata(awg).awg, sprintf('WLIS:NAME? %d', i)); - if ~query(awgdata(awg).awg, sprintf('WLIS:WAV:PRED? %s', wf), '%s\n', '%i') - fprintf('%i: %s', i, wf); - end -end - diff --git a/awgload.m b/awgload.m deleted file mode 100755 index 5d0fccc..0000000 --- a/awgload.m +++ /dev/null @@ -1,147 +0,0 @@ -function zerolen = awgload(grp, ind) -% zerolen = awgload(grp) -% load pulses from group to AWG. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -%global plsdata; -global awgdata; - -%nchan = length(grp.chan); % alternatively use data size -%nchan = size(grp.pulses(1).data, 1); % assumed same for all. - -awgcntrl('stop'); %changing pulses while running is slow. -awgsyncwaveforms(); % make sure the waveform list is up-to-date. -dosave = false; - -for a=1:length(awgdata) - - dind=find([grp.pulses(1).data.clk] == awgdata(a).clk); - - nchan = length(awgdata(a).chans); % alternatively use awgdata or data size - - - % fixme; emit an error if this changes zerochan and wlist.size ~= 25. - % The screwiness here is to get each channel with a unique offset/scale combo - [offsets offsetchan awgdata(a).zerochan] = unique(awgdata(a).offset./awgdata(a).scale); - offsets=awgdata(a).offset(offsetchan); - - % create trig pulse (and corresponding 0) if waveform list empty. - if query(awgdata(a).awg, 'WLIS:SIZE?', '%s\n', '%i') == 25 % nothing loaded (except predefined) - zdata=zeros(1,awgdata(a).triglen); - zmarker=repmat(1,1,awgdata(a).triglen); - awgloadwfm(a,zdata,zmarker,sprintf('trig_%08d',awgdata(a).triglen),1,1); - - for l=1:length(offsets) - awgloadwfm(a,zdata,zmarker,sprintf('zero_%08d_%d',awgdata(a).triglen,l),offsetchan(l),1); - end - dosave = 1; - awgdata(a).zeropls = awgdata(a).triglen; - end - nzpls=0; - for i = 1:length(grp.pulses) - npts = size(grp.pulses(i).data(dind).wf, 2); - if ~any(awgdata(a).zeropls == npts) % create zero if not existing yet - zdata=zeros(1,npts); - for l=1:length(offsets) - zname=sprintf('zero_%08d_%d', npts, l); - awgloadwfm(a,zdata,zdata,zname,offsetchan(l),1); - end - zdata=[]; - awgdata(a).zeropls(end+1) = npts; - dosave = 1; - end - - for j = 1:size(grp.pulses(i).data(dind).wf, 1) - ch=find(grp.chan(j)==awgdata(a).chans); - if isempty(ch) - continue; - end - if any(abs(grp.pulses(i).data(dind).wf(j, :)) > awgdata(a).scale(ch)/(2^awgdata(a).bits)) || any(grp.pulses(i).data(dind).marker(j,:) ~= 0) - name = sprintf('%s_%05d_%d', grp.name, ind(i), j); - - if isempty(strmatch(name,awgdata(a).waveforms)) - fprintf(awgdata(a).awg, sprintf('WLIS:WAV:NEW "%s",%d,INT', name, npts)); - awgdata(a).waveforms{end+1}=name; - err = query(awgdata(a).awg, 'SYST:ERR?'); - if ~isempty(strfind(err,'E11113')) - fprintf(err(1:end-1)); - error('Error loading waveform; AWG is out of memory. Try awgclear(''all''); '); - end - end - awgloadwfm(a,grp.pulses(i).data(dind).wf(j,:), uint16(grp.pulses(i).data(dind).marker(j,:)), name, ch, 0); - zerolen{a}(ind(i), j) = -npts; - nzpls=1; - else - zerolen{a}(ind(i), j) = npts; - end - end - end - % If no non-zero pulses were loaded, make a dummy waveform so awgclear - % knows this group was in memory. - if nzpls == 0 - name=sprintf('%s_1_1',grp.name); - npts=256; - if isempty(strmatch(name,awgdata(a).waveforms)) - fprintf(awgdata(a).awg, sprintf('WLIS:WAV:NEW "%s",%d,INT', name, npts)); - awgdata(a).waveforms{end+1}=name; - end - awgloadwfm(a,zeros(1,npts), zeros(1,npts), name, 1, 0); - end -end - -% if the pulse group is added, update it's load time. -ind=awggrpind(grp.name); -if ~isnan(ind) - for i=1:length(awgdata) - awgdata(i).pulsegroups(ind).lastload=now; - end -end - -if dosave - awgsavedata; -end - -end - - -function zerolen = awgloadwfm(a, data, marker, name, chan,define) -% a is the awg index. -% Send waveform 'data,marker' to the awg with name 'name' intended for channel c. -% data is scaled and offset by awgdata.scale and awgdata.offset *before* sending. -global awgdata; -start = now; -if exist('define','var') && define - fprintf(awgdata(a).awg, sprintf('WLIS:WAV:NEW "%s",%d,INT', name, length(data))); - awgdata(a).waveforms{end+1}=name; -end -chunksize=65536; -if(size(data,1) > size(data,2)) - data=data'; -end - data=(awgdata(a).offset(min(chan,end)) + data)./awgdata(a).scale(chan) + 1; - tb=find(data > 2); - tl=find(data < 0); - if ~isempty(tb) || ~isempty(tl) - % fprintf('Pulse exceeds allowed range: %g - %g\n',min(data),max(data)); - data(tb) = 2; - data(tl) = 0; - end % 14 bit data offset is hard-coded in the AWG. - data = uint16(min(data*(2^(14-1) - 1), 2^(14)-1)) + uint16(marker) * 2^14; - npts = length(data); - for os=0:chunksize:npts - if os + chunksize >= npts - fwrite(awgdata(a).awg, [sprintf('WLIS:WAV:DATA "%s",%d,%d,#7%07d', name, os, npts-os,2 * (npts-os)),... - typecast(data((os+1):end), 'uint8')]); - else - fwrite(awgdata(a).awg, [sprintf('WLIS:WAV:DATA "%s",%d,%d,#7%07d', name, os, chunksize,2 * chunksize),... - typecast(data((os+1):(os+chunksize)), 'uint8')]); - end - fprintf(awgdata(a).awg,''); - end - time=(now-start)*24*60*60; - %fprintf('Load time: %g seconds for %g points (%g bytes/sec)\n',time, npts,npts*2/time); -end - - diff --git a/awgloaddata.m b/awgloaddata.m deleted file mode 100755 index 73c180a..0000000 --- a/awgloaddata.m +++ /dev/null @@ -1,20 +0,0 @@ -function awgloaddata -% awgloaddata -% load latest awgdata file saved by awgsavedata. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global awgdata; -global plsdata; - -d = dir(sprintf('%sawgdata_*', plsdata.grpdir)); -[mi, mi] = max([d.datenum]); -load([plsdata.grpdir, d(mi).name]); -if exist('awgdata','var') && isfield(awgdata,'awg') - for a=1:length(awgdata) - data(a).awg = awgdata(a).awg; - end -end -awgdata=data; - diff --git a/awgnpulse.m b/awgnpulse.m deleted file mode 100755 index 63e5d47..0000000 --- a/awgnpulse.m +++ /dev/null @@ -1,20 +0,0 @@ -function awgnpulse(groups, npulse) -% awgnpulse(groups, npulse) -% Set npulse for pulsegroups. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global awgdata; -groups = awggrpind(groups); - -for a=1:length(awgdata) - - for i = 1:length(groups) - pg.name = awgdata(a).pulsegrous(groups(i)).name; - pg.npulse = npulse(min(i, end)); - pulseupdate(pg); - awgadd(groups(i)); - end - -end diff --git a/awgrm.m b/awgrm.m deleted file mode 100755 index 12de2ec..0000000 --- a/awgrm.m +++ /dev/null @@ -1,49 +0,0 @@ -function awgrm(grp, ctrl) -% awgrm(grp, ctrl) -% grp: 'all' or group name -% ctrl: 'after' remove all following groups -% (otherwise, specified group is removed by removing it and following ones -% and then reloading the latter. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global awgdata; - - -if strcmp(grp, 'all') - awgcntrl('stop'); - for a=1:length(awgdata) - fprintf(awgdata(a).awg, 'SEQ:LENG 0'); - awgdata(a).pulsegroups = []; - awgdata(a).seqpulses = []; - end - awgsavedata; - return; -end - -grp = awggrpind(grp); %strmatch(grp, strvcat(awgdata.pulsegroups.name), 'exact'); -grp(2:end) = []; -if isnan(grp) - return; -end - -awgcntrl('stop'); - -if exist('ctrl','var') && strfind(ctrl, 'after') - for a=1:length(awgdata) - fprintf(awgdata(a).awg, 'SEQ:LENG %d', awgdata(a).pulsegroups(grp).seqind-1 + sum(awgdata(a).pulsegroups(grp).nline)); - awgdata(a).seqpulses(awgdata(a).pulsegroups(grp).seqind + sum(awgdata(a).pulsegroups(grp).npulse):end) = []; - awgdata(a).pulsegroups(grp+1:end) = []; - end - % may miss trigger line. - return; -end -for a=1:length(awgdata) - fprintf(awgdata(a).awg, 'SEQ:LENG %d', awgdata(a).pulsegroups(grp).seqind-1); - awgdata(a).seqpulses(awgdata(a).pulsegroups(grp).seqind:end) = []; - groups = {awgdata(a).pulsegroups(grp+1:end).name}; - awgdata(a).pulsegroups(grp:end) = []; -end -% log unloading here if necessary -awgadd(groups); diff --git a/awgsavedata.m b/awgsavedata.m deleted file mode 100755 index ef23af9..0000000 --- a/awgsavedata.m +++ /dev/null @@ -1,12 +0,0 @@ -function awgsavedata -% awgsavedata -% save awgdata in plsdata.grpdir, with name generated from date and time. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - -global awgdata; -global plsdata; - -data = rmfield(awgdata, 'awg'); -time = clock; -save(sprintf('%sawgdata_%02d%02d%02d_%02d%02d', plsdata.grpdir, mod(time(1), 100), time(2:5)), 'data','-v6'); diff --git a/awgseqind.m b/awgseqind.m deleted file mode 100755 index 2d963ee..0000000 --- a/awgseqind.m +++ /dev/null @@ -1,43 +0,0 @@ -function seqind = awgseqind(pulses,rep) -% seqind = awgseqind(pulses, rep) -% Find the pulse line associated with a pulse group or pulse index. -% negative for groups, positive for pulse index. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global awgdata; -if isstruct(pulses) - rep=[pulses.rep]; - pulses=[pulses.pulses]; -elseif ischar(pulses) - pulses = {pulses}; -end - -seqind = nan(1, length(pulses)); -for i = 1:length(pulses) - if iscell(pulses) % could allow ints as well - ind = strmatch(pulses{i}, {awgdata(1).pulsegroups.name}, 'exact'); - if isempty(ind) % no such group - seqind(i) = nan; - else - seqind(i) = awgdata(1).pulsegroups(ind).seqind; - end - elseif pulses(i) > 0 - if(exist('rep', 'var')) - ind = find(pulses(i) == awgdata(1).seqpulses); - ind=ind(rep(i)); - else - ind = find(pulses(i) == awgdata(1).seqpulses, 1); - end - if ~isempty(ind) - seqind(i) = ind; - end - else - seqind(i) = awgdata(1).pulsegroups(-pulses(i)).seqind; - end -end -if any(isnan(seqind)) - fprintf('WARNING: Some pulses not present in sequence.\nHit Ctrl-C to abort, or any key to continue.\n'); - pause; -end diff --git a/awgswap.m b/awgswap.m deleted file mode 100755 index 64013e1..0000000 --- a/awgswap.m +++ /dev/null @@ -1,106 +0,0 @@ -function awgswap(name) -global awgdata; -global smdata; -% strategy: store alternative awgdata sets in awgdata(1).alternates -% store current alternate in awgdata.current -if ~exist('name') || isempty('name') - fprintf('Available sets:\n'); - s=fieldnames(awgdata(1).alternates); - for i=1:length(s) - fprintf('\t%s\n',s{i}); - end - fprintf('Currently selected: %s\n',awgdata(1).current); - return; -end - -% Save the current setting -tmp=nicermfield(awgdata,{'alternates','current'}); -awgdata(1).alternates.(awgdata(1).current) = tmp; - -% Extract the current offsets. -for i=1:length(awgdata) - o(awgdata(i).chans) = awgdata(i).offset; -end - -% Check if new setting exists -if ~isfield(awgdata(1).alternates,name) - error('No AWG alternate named %s\n',name); -end - -if ~strcmp(name,'current') - fprintf('WARNING: Changing awg configuration. Some\n awgclear(''pack'',''paranoid'')\n is probably in order\n'); -end - -% Load the new setting. -tmp=awgdata(1).alternates; -awgdata=awgdata(1).alternates.(name); -awgdata(1).current=name; -awgdata(1).alternates = tmp; - -% Restore the offsets -for i=1:length(awgdata) - awgdata(i).offset = o(awgdata(i).chans); -end - -% Figure out new master/slave relationships -master=nan; -for i=1:length(awgdata) - if ~isfield(awgdata(i),'slave') || isempty(awgdata(i).slave) || ~awgdata(i).slave - master = i; - break; - end -end -if isnan(master) - error('No master AWG defined!\n'); -end -slaves = setdiff(1:length(awgdata), master); - -% Figure out what instruments different AWG's correspond to. -for i=1:length(awgdata) - insts(i) = findawg(awgdata(i)); -end - -if ~isempty(slaves) - for i=1:(length(slaves)-1) - smdata.inst(insts(slaves(i))).data.chain = insts(slaves(i+1)); - end - smdata.inst(insts(slaves(end))).data.chain = insts(master); -end - -smdata.inst(insts(master)).data.chain=[]; - -% Rewire pulseline to control the first slave. -plc = smchanlookup('PulseLine'); -if isempty(slaves) - smdata.channels(29).instchan(1) = insts(master); -else - smdata.channels(29).instchan(1) = insts(slaves(1)); -end - -end - -function s = nicermfield(s, fields) - if ~iscell(fields) - fields={fields}; - end - for i=1:length(fields) - if isfield(s,fields{i}) - s=rmfield(s,fields{i}); - end - end -end - -function val = findawg(awgdata) - global smdata; - val=nan; - for i=1:length(smdata.inst) - try - if smdata.inst(i).data.inst == awgdata.awg - val = i; - return; - end - catch - end - end - -end \ No newline at end of file diff --git a/awgsyncwaveforms.m b/awgsyncwaveforms.m deleted file mode 100755 index ea77a7d..0000000 --- a/awgsyncwaveforms.m +++ /dev/null @@ -1,20 +0,0 @@ -function awgsyncwaveforms() -% Make sure the list of pulses in awgdata is consistent with the awg. -% we assume if the number of pulses is right, everything is.\ -global awgdata; - awgcntrl('clr'); - - for a=1:length(awgdata) - npls=str2num(query(awgdata(a).awg,'WLIS:SIZE?')); - if isfield(awgdata(a),'waveforms') && (length(awgdata(a).waveforms) == npls) - return; - end - fprintf('AWG waveform list out of date. Syncing.'); - awgdata(a).waveforms=cell(npls,1); - for l=1:npls - r=query(awgdata(a).awg,sprintf('WLIS:NAME? %d',l-1)); - awgdata(a).waveforms{l}=r(2:end-2); %-2 since communication adds newline at end - end - fprintf('.. Done.\n'); - end -end \ No newline at end of file diff --git a/awgupdate.m b/awgupdate.m deleted file mode 100755 index f058562..0000000 --- a/awgupdate.m +++ /dev/null @@ -1,72 +0,0 @@ -function awgupdate(groups) -% Obsolete! awgadd now also updates groups already loaded. -% awgupdate(groups) -% Change nrep or jump of previously loaded groups -% Nothing done if fields don't exist. - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -global plsdata; -global awgdata; - -awgcntrl('stop'); - -groups = awggrpind(groups); - -for k = 1:length(groups) - if isnan(groups) - continue; - end - - load([plsdata.grpdir, 'pg_', awgdata.pulsegroups(groups(k)).name]); - - if isfield(grpdef, 'pulseind') - npls = length(grpdef.pulseind); - else - npls = size(zerolen, 1); - end - %awgdata.pulsegroups(groups(k)).npulse; - startline = awgdata.pulsegroups(groups(k)).seqind + (grpdef.nrep(1) ~= Inf && isempty(strfind(grpdef.ctrl, 'notrig'))); - - if isfield(grpdef, 'nrep') - for i = 1:npls - ind = i-1 + startline; - - if grpdef.nrep(min(i, end)) == Inf || grpdef.nrep(min(i, end)) == 0 ... - || (i == npls && isempty(strfind(grpdef.ctrl, 'loop')) && all(grpdef.jump(1, :) ~= i)) - - fprintf(awgdata.awg, sprintf('SEQ:ELEM%d:LOOP:INF 1', ind)); - else - fprintf(awgdata.awg, 'SEQ:ELEM%d:LOOP:INF 0', ind); % needed? - fprintf(awgdata.awg, sprintf('SEQ:ELEM%d:LOOP:COUN %d', ind, grpdef.nrep(min(i, end)))); - end - - end - end - - if isfield(grpdef, 'jump') - % event jumps - %SEQ:ELEM%d:JTARget:IND - %SEQ:ELEM%d:JTARget:TYPE - - for j = 1:size(grpdef.jump, 2) - fprintf(awgdata.awg, sprintf('SEQ:ELEM%d:GOTO:IND %d', startline-1 + grpdef.jump(:, j))); - fprintf(awgdata.awg, sprintf('SEQ:ELEM%d:GOTO:STAT 1', startline-1 + grpdef.jump(1, j))); - end - end - - if ~exist('seqlog','var') - seqlog=struct(); - end - seqlog(end+1).time = now; - seqlog(end).nrep = grpdef.nrep; - seqlog(end).jump = grpdef.jump; - - save([plsdata.grpdir, 'pg_', awgdata.pulsegroups(groups(k)).name], '-append', 'seqlog'); - err=query(awgdata.awg, 'SYST:ERR?'); - if isempty(strfind(err, 'No error')) - fprintf('Updated group %s on index %i. %s', grpdef.name, groups(k), err)); - end - logentry('Updated group %s on index %i.', grpdef.name, groups(k)); -end diff --git a/awgwaveforms.m b/awgwaveforms.m deleted file mode 100755 index dcf1ace..0000000 --- a/awgwaveforms.m +++ /dev/null @@ -1,34 +0,0 @@ -function waveforms = awgwaveforms(group,awg,opts) -% function waveforms = awgwaveforms(group,awg,opts) -% Give a list of waveforms that are known to be loaded. -% no arguments: return a list of group names -% if group is specified, give the names of the waveforms in that group -% if awg is specified, work on awg awg -% if 'opts' is 'delete' and group is specifed, erase the waveforms from the table. -global awgdata; -awgsyncwaveforms(); -if ~exist('awg','var') || isempty(awg) - awg=1; -end - - -if ~exist('group','var') || isempty(group) - waveforms = regexp(awgdata(awg).waveforms,'(.*)_\d+_\d+','tokens'); - waveforms = [waveforms{:}]; - waveforms = [waveforms{:}]; - waveforms=unique(sort(waveforms)); - i=strmatch('zero',waveforms,'exact'); - waveforms(i)=[]; - i=strmatch('trig',waveforms); - waveforms(i)=[]; -else - waveforms = regexp(awgdata(awg).waveforms,sprintf('(%s)_\\d+_\\d+',group)); - ind = find([cellfun(@(x) ~isempty(x) && x == 1 , waveforms)]); - - waveforms=awgdata(awg).waveforms(ind); - if exist('opts','var') && strcmp(opts,'delete') - awgdata(awg).waveforms(ind)=[]; - end -end - -end diff --git a/awgzero.m b/awgzero.m deleted file mode 100755 index c9136ed..0000000 --- a/awgzero.m +++ /dev/null @@ -1,23 +0,0 @@ -function zerolen = awgzero(grp, ind, zerolen) -% zerolen = awgzero(grp, ind, zerolen,awg) -% determine if pulse is zero (helper function) - -% (c) 2010 Hendrik Bluhm. Please see LICENSE and COPYRIGHT information in plssetup.m. - - -%global plsdata; -global awgdata; -for awg=1:length(awgdata) - scale=min(awgdata(awg).scale/2^(awgdata(awg).bits-1)); - for i = 1:length(grp.pulses) - dind = find([grp.pulses(i).data.clk] == awgdata(awg).clk); - npts = size(grp.pulses(i).data(dind).wf, 2); - for j=1:size(grp.pulses(i).data(dind).wf,1) % FIXME; channel mappings not honored here. - if any(abs(grp.pulses(i).data(dind).wf(j,:) > awgdata(awg).scale(min(j,end))/(2^awgdata(awg).bits))) - zerolen{awg}(ind(i),j) = -npts; - else - zerolen{awg}(ind(i),j) = npts; - end - end - end -end diff --git a/doc/awgsetup.m b/doc/awgsetup.m deleted file mode 100644 index 716c1bd..0000000 --- a/doc/awgsetup.m +++ /dev/null @@ -1,32 +0,0 @@ -%% create awg structure from scratch -clear global awgdata; -global awgdata; - -% number of instrument channels -nChan = 4; - - -awgdata.chans = 1:nChan; % determines order of channels, not used -awgdata.scale = ones(1,nChan); % pre-scale waveform valus before upload. - % Used with offset to relate arbitrary - % pulse scale to, e.g. mV -awgdata.pulsegroups = []; % pulsegroups synchroniced with the AWG -awgdata.zeropls = []; % available placeholder zero-pulse lengths -awgdata.triglen = 1200; % length of optional trigger pulse preceding a group -awgdata.clk = 1.2e9; % number of samples per second of instrument -awgdata.seqpulses = []; % !!! functinoality not clear -awgdata.waveforms = {''}; % waveforms present on instrument -awgdata.offset = zeros(1,nChan); % pre-offset waveform valus before upload -awgdata.zerochan = ones(1,nChan); % !!! functinoality not clear -awgdata.bits = 14; % bit-resolution of instrument. - % in case of AWG7000 12bit may be useable, check -awgdata.slave = []; % !!! elaborate -awgdata.current = 'awg5k'; % name of the current instrument -awgdata.awg=[]; % handle to the openend instrument object -awgdata.alternates = awgdata; % alternative configurations of awgdata - -% save current awg -awgsavedata; - -% clean up -clear nChan; \ No newline at end of file diff --git a/doc/figures.odp b/doc/figures.odp deleted file mode 100644 index c07310ca9132ab257599b6dde581536e0eb589ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20435 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
8k$g;B4PTC6f)XW!CZxs9G6Qr3c$3V1)~f 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 o7&AD$<~*9o-VT_3lojg6$8=Tm+uSFZmkBbCs| diff --git a/doc/group_struct.png b/doc/group_struct.png deleted file mode 100644 index 6c3977d7523eb56d48efa4ed3c9bd3608b8ea4e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55367 zcmY&&Y&&_9jh$?4+xEt`jg4*FHa50xCmY-Lm-qe7sXC{s@2RPp`^Qwx z^wrnZ(;X%+D+UjP0|Nj6;3dR`6#)QHbpQaQ4+`{q9LGQ^`d)yx6W4GA0Fci9^MS}G z9(w@*%P$hb0?KY{=Q_~bDl%v}mQVF2-j6J0v2zv@YfWYi|#i=I9`urTnY3K>6o}!5}1C6_XHq;66L^6Q&9x$Kj7Nx zQb)j+L=K&7wvwQh>Kw!@dwNWwv1pb}RT+!0=`Vf0^#L%#%HO!;hLwKjC7z?_I$p)&gde-6JR zmLbg7Jr~U4@^ascjlKHLgd_ZZXbxTyuC-vFF)5Y2pQm(x^yl^g>-sBrT zn?Bd<7}b2<1@%FVu#wKTn45?(D#KzX!14aCVe?|5p457R- zX~lLH2Xn=%|FLSZ)WSe-nF%d9jNw%@bIY8?R{iR$(ZGoTCPhou=j#Nv>FHYFc~7KE zzbd&Q?F>=LZmMP0KcMlNi_l(Lobu9Gn?5_x-f<}?Xpb48>8nZJXO2)_GxyYM~ z)^*G5$E0U$G1`f_`{Y~7)-H2iQy2^sC`djnx*|jUhWP>v_Xew_-OS$eN@4!F+|#F{ z=DGdnW_Nsluku*YJ~5T2c{o?p>;WpnIbt|G_64+K_S?)2%g&dT@d8 z@s@b-s^6YYd zdtQ7+(F zium?z^my)cC7tY51Zh90Xf>+^bsMIgrW>QOn7Y`@AqEMoKF*` zW4*kUu@kH3{?h$7F`X>d7wdG>hL?furXoTRLl4Aw$Tehlq>j(PCC#G0OH#TC6qyzs zZaM1=3fLAU_&h(hxy3zXx4c5fM_bN@lF)R(S{%Qzc_-(nxn0I`g1E>C>u9AR!_LO_m^CPzazA{}(4!^J|H*?I;@Eb$#0_4s0m~7-$$76%;TkZYikx z_{qD?=(TtNZm6i+Mk3Ggcc)1Cb3BgBDEvHHQWWgE3t;`c%8=P@ZkAJxbxo{=|JH1vs78DTzhTftk2k8r?#_h+ z#ZFOSn)pP3Uk=*t^}2Z#HqDWCkYQMib#lFaV$Pi~jzgTFbSt*6zA3o9BS05nv|@`P zpLpsntZp|`x|QM1n7+Tx1Q$c)f-NkZ9zD^`XD>G-VSQU^WDMretLw->XL_*d0X(H? zz>+Km%I~tBrX$WNm58nIT#Lst%=F)NNTpRZbtI6?bs zHYhJA(2yOQ$(7DGnRLZ!d#RJM!K$HM!i$^`On~ z$M4EW!25}DxKl6l=3=862r{L*M?me46reb^rW`;;30;P3pQG88e7W8cIpexsKWJ=g zQgEHop%*TR{Y&0wiAcr=xPy!Qq?Oa9sJXw9cE{&EaY_%CT>^$&o*o|V_89UMrlF@9 z+z0F&ByIK2TD-3rw6g9Kuqp3-85!^$=_j{!)HgIV>4t{!utZt%&@<6CDZMBvr$5X?u+vES7{-WfgK0ZQ*d|@OdCV>K^%&8inycELVg?PE zIzlPN9bsEQV>Ina7ugw<@~rR@3QOSVuqoU$Ifs8fLhEah-`|woaLTCidC?7jx>+w6 zKK?@Gn4kumm76o5yTGw2WwY3ARIR9EX0$E}nZqbqPeoRzJ<$Qig#s>!hu> z<6-pe`COLGzDm;LB@~(!*h1ITD0}y!k#uzM&}@(^_%*vqsAq3CCJ0g78R58k*7>B& z<3zyeG%_`H8AA!zk(667C!-^>v`Aw20dYrvl`~p?(&&FEgAbJjA~|5;0h_S%z^h2_ z-dz&wJ_22tQy^_6RP&6&jI)sow!?-di_O-*7kV|`FV*s*Tm0<;uSC{8evkFmLt|s% zzstr!ZVQO{fHl!mW{o`FYs>*EiNX_oDV(cRsAX9qD5UHk9BeU^-)8O8Uz)vFR~ zC!%uGefhuD7=2DVWVv`d+O{!1$!K}pjy{iO`xCi7I=?^Q(>Zu3hR}C!C<-!Wmm^$JcWBV@8paZiRXGV26$AY( z?juc4yETaNm+NhCwO_e?#iX#nZ7wyXjPaW;9TETDF8blWe+E|MAR7i_zV8c_CD2(EnO! zkc^m!gK+SG-g)~gOPpZOY!xMPhK*PWc5v7y!4Bo1=xqnSUl1{tNwfR%a0H~My4!gJ zp*RyrKRX|Jhw^2a$R*#IAv+K4t{?~?PGJY)Rn+L;&DqX4Io9Ao%6vdph^BkUW-F_~ zz3?sudCpf||6?+2jBb02@uK$hc+Jog3fVSGJsXSKlv$(n$zv{Np8qAo{&ik|rI!cN zmx7!UI$7TaaCnUSSWfDbq9Rw58Vshx9jj$uV|$a?S}ne%O=_fYX6|{l_Bn7mohM13 zX#N+fqf*vzFF&8@S%q!4g`_eq?d~vxNp?fQ)G+!?6dq=8s%B@YWpSjwcP3`Ps?DbF z&A-?Fr}#=IVb=4rx0S8bA9$oZ> zs(dL$#jRrhXt@o$he3h~EH-F=vZL}BAe>vSb4I>m4b$DB|6Q}JswD%~_geM#NG=Ib z0OoQ#ZtR@hEzBUz0^i^idHod3;`gwM0%+!>_2=WnS3?G$+|h%ry-t}sl4nS)F?23} zopY?CyF;OOAC2tMc2iq6n%GHLM#!loFN262-&fm~DSv=sCPUPFYQtXH>8R_1Q(Br_ zs+KYoT3DQH02-D7_X(ZSGIT5tLZU9%hIZSMIq#M`v;Mpt^2y}sOok9ep_vy#;RghL zEp%=0iH!RsmC{VvZ?C$Je8Ywg?EG?A1=Qy`JdkC`erzbxdjV7${JX3XAP86Jr zUfx>VJMZ*rG+u;^%sz2lVEMJ5)+G=_UK0W51li>39K2ChT~h_&JHg*j)(jm((49M| z7zu6o8*{SU=OdXLmmsn~K@nx(z$l z`lDO+6MZH65*uqu5EmN___f#?#aX4! zZ*y}J*DE(#?9R&`ELyN}4jAavoq_}#o1^Xa39L;EvV!z+!7nNMH;jxQ1B2}=>k=kg z-jfAv8nlLspPo8m!~XAJm8@OOHMAoIO~s=%-+Qu<)T^KrcKI_+;BTX^Z{K3mkxT7w zZiC_C*+Pep&t=?;gdJODb>u$sHYZCzOgoj!)Bu;#<4E{HQqeeRtWWEygU6Ts$HpTo zNlVSEg~jd&`0nWJ9Oa${p~G~1(W+xnuiecnD13IdJJ+SIK7KD~na*e9{kPHSqy-kk z=Ur8R71Q1RD2Syb#Z=7IhR#J0qa4N&X_5yI`7vwuq^P!zpza#dwIvvFu!08c{9(fR zePa5+a3a0gUeE0!|Ehr*({@0V?@f8cO06kr(SWb}%BynC{b2KSw9^i+#es9GmLE!z z=~9ls;&`{sCA28TPEU!CD+;?Am$8-6?et;+g=jcV0jBghMXZA<81Z8n2DeH{Ew=B5 zc&_IVLriJ-V~44e49&Z3%Y?=VPod$*`Ig`_X*+X27wK1M862uJ4V-e@u zWOKW?6`GsntH;QfV5BKD_sh4ZhH(5SC)^^_`OYt+~#V{?~C-u_}X*x{?%h_3G-n^3N z@B{9{W0qjR01PKAN;LG8+iv@0crY|%piC#U6JTYn^XJC{N?)*SfN%V5zla=D z3PYR$NYQnq?aFXZSWUOoHMAWVcE7K;leeQrX=#x!->=;aL0S z_pc1IChpx`{eE-U_P$)Z(t3UhDX{iAAMjfG^7Xy<0*@taj5(<;1YG0ipycd?xA`S~ zp;s=!i+7FDw^J2C%pJnO3bEV^3rDGoOTDfn0FmuZW4GySkax_DKWCfT<9hric>}6- zxag~x%=bQ4^mL5Bfh$BHoA^VtY68 zmRM$K;zLqXTGl_|&+R9+5ZVs~rEP7Dc*}gh1awQ02$^zD{5IyB8qx>KY+g(5-g~_%CJ688HYR z)Xz{bk)_51qedxqQY3|5adGMK7qosQ`9AwXcJR)HN4+V1?J5^cMd>#T+d9_%spLFt z4TU>lbJ?feo`C^~YF3fD&hdXnDGb{9{v4=VgFzYjicq5Ug>@viy-lPTnr%CT)A(h$ zrIJLPJVgAt2s--n5DkAobz43jd!H8N=hvsZkcWv#_X)iNRA+HL@%^rX(4VbiLnX+J ztk#}?gy6Vb!&pjM%DMP4LA9`%COlHXA0ctUk)w)wE17*DGn4zoD{f1g3H~#>8tTq% zUQamAjYD`~yLw{3cdiuTK-u>d1YC_UT!=rLLKhcZo;quF_kHQ94vS+Aq1h=a#cfEL zk;GxN-~>U@@+|mz#Rs|{C;BM&OnwdzW_dh5Z`|PCIl-Vs9zG?YwlFhu;tSD>haZDC z-xIB@t}?<^l$QF9Jx0jU4^3|hRkzut<*{nEzg6IM`7whO+z$=T-HuEn$6Cy}j0R{# zX!eRbIBJOFkm@VQRQyuRkX4?frl!`AFtiK$J@GRc2IfUg4c8W*WZA5S=M)wh36=|K z?Wp%WG)NopSl= z)H7zW&WzLZeGnRaiwwB@Nq@1SAHM-||4l zH&2B^Vn3pdLRBW83_b1E7-wH8Y-9kvJ;!m?Nkgm$*wKHy^sw+D{weA@YN<-o@2n&f z0Kio(d7K&wRdqzb+)@M(H`S{p9ZB{rWW-D>aB=n3W<=N~W>oRs-9Y);VllJ46l4aR zQsxLT#HxyyxENaEVys1}Nl8l6P}0V5Up^KEU{6@iNC8>Db0eXs!x0D}WG4s;etFRL zEdWE~YI1Z$9x=Ggr#uT1`8hp$!gB~T9!2XA1s5>`g)@Jc10 z1*4?$bS4XP)beW%fTmd|F(XbknZ*(U{A~)aQUtG}m?4tge4l@$WY93llJm!zv9l71 zEp5hZjVilkHYTkmKto)&t?*2h)qgB0oel$oozADp@NDD z6kCHvRUxVLjR+g~e6Sp>NM;RR?^11ikd(RTDn(6|5#KqMPY4jN#dz&&MUBa9)1wBv z&(}*{eNs%Zuz*#T2%*!!O-xKYJ#5uYe5d)zlSY|Sw4b!Dp2hL;*DubHGq`SXzx~(e zV2G30{7&aY%eix^GNPwsLugWyu?U`aV%x$NUo$*Idb(zd6K!W226rCpTVI=uhw5BK zIxh>Kp1S1SRy*v70 zi8s(Pqvv!h>P=jfh?bG*o7s>rMHaGIkIJDx95Et5#nn-t)ItLw+|<`wcwN>N5OtUfHiuu6tRg2 zz4=BQrYe0XO)=6g6Jrn){n!3GG~&Q&?Wg)J4Wn;c(^_r^J(4C+0h>HSQhJ|PKNb%< zHa8Xy+&s+SX=x@;_>8D@byIcyy5YEZ^owN-tWx8fXE!13XZ6+e2~c!}e{A|3*8aAj zZ26l?M8Dvt(|qZo!SQt5go#~~pOqrJpNQpGSj%$Fl+&H_Q?!Crv2JOw=;vNnMMea5 zL4K*6llb2Q60Z5+%wphP1!F;iou!_D#9#%a+Ngi=eEv;} zt>_D6_LoL?7%!!Ol&o>}Ye{9xUOrz!WV2ZR`T0kau}M(n}A`mrZZ3<4pD zp#JzkxvBih5#|=q8K*P~tK!c1O!DCs$&Qyuwa1GU6B?Zi z{jl^>oKh01uMXAq-gwC#rpVR1dc*bM<>3OwrhX^gR(0B%m9zkSF*aM zI)RZaA-DEG;2nKAuzxr;VDb3{5J6)GHE|yR~p3u+Yzvu+L8WDsSkblYW4Gg?em^ zVP&b+Z)-ES%0MKTT<`Q1nk9g1#^;=OQfoEKQ~GtK2CRj_?VYq z?(6ApxaU+LjyDu&CHMj+<5&r;Z7{lC_5?eh8*o`lG(v*8ae3`=q^sc`Aw%onj7*P0 zBO;n?DF?#PPuxiD;kvdj%?rq6GprWA9G>s!21|M^76gPKjsJQ3_#kj$zuF}lw5V$W z@n^_SC`^rzAet!wXdpmJ==-Q-=EEawKs|Q%{=NtPKW!pxJU`aKHWARX0nUTZ;ZHz z2s(IyuAn(D#;P4w#+o4@AS4Dm%pP@^Eev!SLR8e&)}Ay4u(7$m836H+hrHb6uW3y^ zfTkx;FJ9cw?U9W$vabTj2-oYTL+OKpbc+6T#ZU2nJTU82WYNp=@FRi%%Ce)!-A?vl1Sp2w!bt)TEG zFqXmxqLgHyWYVR@fjDGt95Z70_;H6e%TogrGigyFPMej=e|(fYnh_OkH=9Mkb^J0y zUXRW=kSoWncXFIVK(967rtBwApIiAu9KOw1KgO=xED?Mwxj7!Oc9AhnMrq+?3{_dZsxCC9&vj z&sS+&3E+?mIWHIk@869!rv>ZP03&QzP3fLw_YVWEgBj5Nz8o)tzYZi)Ldt4VsLf_hMrt`fm_eEl~X=BxJ@?&LS zOD6b_UT>%*a0ytw$sr8kb_wf&@kznRi-u;uCFkDi=s8o)Vp8UAjH7&sFxjmMdz(kV zKXvbEZ)!>hW7jJSqMWEE@5VKH8yUdVQO;># z)kZSG-tEKbbn*D9LymB+xeA0tZ+aD6at@ss^;~{v$fKUqSeq+96(?7=o!SiusAOxj z-DY+SW$vI_GlLg~?Pz=-Pgq90yw5tEK>KA~?qNla|1ZL653Q`a3mMTi-e1)JjeosL z-+ajvZ1XU^J+pAn_#C~cLQLGIBi^j3N0;f24xqCzfl~)zTkXGwWTtEI6&I z3i<&})~_NmYE2CAI_0#g(BL2VO@K$-Z!^fourh3Ka|RckLHU8_syiR*HO)E$@%vVC zyPDFgfTgHLY-_%F3~$?f6!O7OVQ)@%d%0gwS+gCexX_3nZ9 z2m25^ZFO7r&R<_MzPwUPoNojx9P>u+88ols*BKG`YJ@Az?h8<0AcF$&o9;P0kqg)s zGXxq?FRptIrFn7IG?P3eH9e(-zL-`b<9Db;U-f)}x`+q_#OXDL45G{S&IT)axJPpI z_LZD8?l+ZTa*D8PB(T6CIRX)nLJ{zyVVTQx@10gZfD$b1b_3YU4dI3XlR(JF1jbt0 zdabKp@V&87XmT$*KRtBoLu^bp{{$8N_5^VGv9!_yZnCn&9PujVY7sy2gC0K)S zAxUKj*gC2bCWFwEbuECKKZ;j|MgJF!6V)80FUDhABb2( z*x|_W?~Ppl_zK0a7jpRkP5C$G^Xq333t6@0!`-%^D49LE^n<0pfMgaOOj-m zq-Tp;=&KBy>0E>Jpmu>fhHbaFdekE5Z%BSKwCZY4Z5_3RW2Q*sTcp|HOeb0%OU%{{ z3T~E0Qe4H*f8{EeDtG?UG0i_iMM}IEL!hO2T-?|0|6y`rWV<-@KVPR3#s*<6;rbEd z$;IQEWD#IP@K^!(P_i-ptL1OGsck!*hb0>b0>Uwp1efru;Ava`o?0`TR}mWEQdeKq za4rnai7(wMaGxwE$n4+?M6%6Y5(0(3U)AA3deUcSeM&WhnZJ}_pMnN{e*EjKAYb!* zsdfDG)M`v`z?T+vsuK8T5sb1J7PH2QV?{{yJ1kJL`;O|>cT=v)RyQ!--oo0FMxoJD z8}+l(pWpa>c64kEKM9%$U?d~K+{8*G_>T+p{Q0Yd`ync=)#g^#UeTY}I_|;&r`NIq z!(YnUJW&$)rs2vHIjl}^f9!Q zz7mtZk4|veVh#2-9__E`kt-@`I`&$*&|90--)dg*j6(8=)P;028zD~hf*hvoYgRep z-3P;d-u{iLZ2{x8IQ3)83SZ`xiuO}81E@SQM1EQ9Jvg{PgnmJ-?b39PyeG=iUt9)4 znxGcHrs@fUoIY*1ZL8hxnO6Ps@-Ha7frbZ5ha(wBaFH_2>5sP^aq1e1^+tffvxOi2 z1Vg1>>wK`Q<5U6~0`+H-?RJK!PYsB0Xp~>>?EjMdGIFM^gX70vgp~&_GJ`TXBY$Y^ zTtUw-;CU&=Neu8`BatB8)ENFMbY_%@s(1L3DZ6xALe#Cc0bSz>Jn29|qhnQG!VeB& z0`=oYIaM?&I=~B(SE!IVGsz9O6sK{z?xruuRsQA!2C|eYsY;1dD$s8e(RzwnEgs1! zDJ;y$Ha3@{(JwaiG|Edv(l=pu5uxMG;3|x zy1MJmk6mrZ-}+TSm|{Y@#vMMF9VVY=k;F3Qk|mS{@9$nfU|;0Ok8YlMYt`65HM`Md z`_S%oUz1~DsqHL+W&~V7>3i~I1NsmbxWyOt_7K{d_F4|nZ zjUIx%wsBl`#&ViE1r1Gy=`_Ky|NA=b^UPLeEvV;J-%Y33mBWl9RaVRA#8j=zN{?=~ zO<|sF`muBrXOmHsCuD428!Y{KRcMs#kY?6tKTj*`*5u^HWfAJ*;oZ~^aOoUlrX_8m zX)9|JFJb!4I?U)d6fiwm_xdY}AQEa4t>aM{PYNooUk{rRw%g-&2$zo|eckL_B|?V# ziS9EjO|^hXM@0Q~szR>h(ylveUrG!Ko_L*D-*h5;2LSYaQ>v(|jPrVBM$f|b?b6?6ZV6`{#4RMVwy9Im)vw5ArE!$*1*q3xfuCJhYmGYl&7{iL{N(@tiJ)8;S=u(&*!= zNNC!>>F!?*-IbWs@4N#NR_T8aP#v<`uo8#9qiFlY^?82!l$8+86>DV%LNA(26F8$r zLW58aN_t_VmhnCzRbnpHEy;|vlI^~UUgIt2;QID`16R#{wJe9CWi=%GEWrtJMQE?a zKY7SMhBIlae5_%yWLDaseC6t6^@1}OaM)5hoZm)|X2BA1=1@<8{&A>o9NPYZ7AOq% zB|LH2)S+N8`wK(uvY3{~7yAd5(66Sp+(aLk1zR$UXA&5c*bQCr=b(bAH$TNqX3qV@ z%>7f0Mq+Pxgsr9TEL0|ghInEEJLre)5t93U1a#iKFYgz{2aD)3?jd^+4AFV)^Z)jF zL^P4+X!2RN3TBCi!Y$bU!w1d04Qo5-`w8XA7e7Cdjnuz_(U$dATXyPX&%sh98EJjt zm9Q2#+iiFrAnMq{gbK%1pZ)vpC`YV+euDsf>{JVF&#e=VoGAhS&<$@Jg@0*?5DRy` zGU@G)3+;|YZr9RD+u5`c3F&X5QPhW`#I*JJ^JT8(FNb#*xlZTGATU35_?R(=-=TS} z>z#fw@cXg4BA5U+heaP5F_dBR2Xn%Q1iw9zCJ#Xh{|WFs&Y78ScBK=$kd1uJa53SW zmfqq@2$h3R9kY2k#O=D7>`0d|leeDouif)lCI3b)gRuT-on6`AD-W1kVLWNYiqhVUKaMMtwBB%&fjuF+nNx zHmMsKB!zIqLOuosW&L2am34GZ{Y=0;6hg=G@c6wloK6A5p1m;O5bSqhs5Ca<{-7y% zghPBc9*Y+R4PYwPj7O3UH{_qJ+g zD~~B7UMR4?kH(b#juxr4Bd)xL%7cAq7m$NrF)piQx}0|DV}ctE?yjByw6!_^bD6!F zE#sjFQqj9Cs4g(M{EW1ygy1Dz)mpbbn#bGJ*(=Se2ug-StR`y%=C3k#_jE4?e;B0` zZl?6hbB`~r**9Dt;Z3@c#G#Z%)(8ZKY_;HUXxZcSGUPpuCs3SE2sCVgcY&&BJIRsN zNX*-?PjsuxaIzipvO`)+YlkKX*ueWS;z7!Vh8WL~Wa&Sw8yKa9fVV6KEj!4R6#YNg zp|7&gyo7B@$)Ix_!;>#^V;h&%U-#o}DH5U*dk&sA#^Sj-B=Q>Re+;-$GJ&)i5WhEJ z^_~k&z1WmVgvikxnYNm-SvQ!C ziCCGjCPHq)Tj*UCgBo6G%_^eY1@htFqUuJH0#BN6c@d=L$#S1q_JQzhSRA))#B%5~ zQZWAzG>)Ub_t$xEddoLS+tzI*W5DCi{KOvgvz%oGf_YUQT0KUU6DK`UMV<=Ce@|>8 znZKj|_fc7ICn5_G^2y>SnMKDmb z0u-|*IT(Tp^8bXI|0fc+bfg>?WFzGzr!v%&RKpDTN{vzOy@ept7qt*&o7^8Th#FB`C*2iB7Z$YG{XeG}FA?7!o}Rq+{B zr%GJG3U(dh{*YSc9R5s8meF(~jWt1?j3pcUon(qvI`oMp;BEZCz_2~H=D7IZZU!J; z7{&dOb_4wo!`-k@u)bfSG(R|SvQ^{0O#BAJN>W($@!khdkpGwxa(TNc*|Hep4IjAG zEf{RHM_FwX1BIW}euEvjX#fFU?*;*KDDm6F!=STUgxO;9P4GLLFnEb6^(o48O3b0w zq~m0dK@Q`$PK>{4-VP6sJ`~;?8sSV$NtWPgSuer-91d0+tmLQ0Bm8y&SkDwETz8Bn z;DAsYxkln{GQJH^y?Mez)1riwah(m%hthfk-IZcayzj&DEi{``v)YWYhu(2pyR-b{ zd>x?5lzgXa{gtI%=x`vyesk@~yvo5SsRALLYV^FdAGAiL#T$B!woS;KZW z``Y`}r1T#uabVYE&xK3BNrac16`P=M!n7CDmXOf})NtP$RdyMqP*o&j3fhGA%FV-Y zu$LFN(uwgJ7le*UN+GR5#rIT$MOrWBg?!h*_o=QpOfe7_)@Mf5>FMzs&r}V&bX6P? z@OetO=ij-s6iL(&pku0S$c_3q4{rC)>Yo?<{uww)%=%2X83@T6A~OaNNcbC=hOu8u@{6+$cj;|S=Qy2ii?VECeCFNV$SJez z0Cb>sDrEl+lFm1mE`7;Y{Hl)pQJU{b!X$3+Z(2f$7@sebroRJuYiQ=8;jb~hCQrm@ zZ&XN?jkh?=Dc|_467t%`qpf0=yEvpz(v`p9B3MWMW>3q+`%yqcNtii)|5To{GG??k zc~IyUG2eND4+Zaw#A7m=o7;;Fnu~IYK++29vJKLt1N0d^flP2vvzsT7&RWWrI38(Twy4PorqI_$hIKlg zBIhh7c6FmF{0#K{f&I{XE%wVEbT^npTm}3(Vl>}k)0x3z)5qF^4Ozbn-ID}B6m4r_ zXi7~j&N_EYOVc9+Cu!GKTgpIUV_ZVx-;CDxT^_t(uey;dXg3po5WG#RcCnCt`uq_m zw}=ux0=X7W{3rN$xKlPdEJi4)=?>Jeya3gVFtm4TYTfSvj0B*BMP5J5bV&@fTJ1A! z*z~oH&!$mkUWqK;mZrd%)_@DFr4j?y7-x_NwXp>hhZMjk+eQ)!jD|1uIdy8eXMp5_F)TiKiG8gry8dg~N?GO%LzbvC9F%4DvqBW2?YkYF92t-S$V18*n@Ii9 zPbhf}4YVn(cOh!bK`niC32dlcQ-p%Fj6sm0`MF+;6)bg{V+4g4D&*}PJx>=Y#gH7l zWKH6v*;s^BUyk&EP>`3;lojq4=ofgM145g`&u?Z36qX`ILgLqwn{aw4LV<$H<)IhW zScuuZ%a_D?@~~>${uljsky0U^0Qp{EbuLt$;r)1p)0~-}BfFSo2W!Z}sAbo4E@I;n zED1aV%Qq`3E=A8x$hAEjiM2~PAc`Fo3Xb3x&*4pw;hU=L4|QqDt4YPJ?)$a zD;Xn-QHX#-YwLX1bUanV`s(VctznKV9v6_DXIF-4n8-Nq6pHJ>z09X1nm&`qKN7k> zFBUe{>SQwu%*6bnZwGx_SJFb3&xPA=H?-H<>{q*tXP;<_q`dIY#y6RySag!TsYNyR zJ61lDfqvsB%9bQYdGsqJb>`M5y0FWP7+oD+P2?p{Xi^o!`3JE~8nM54xG{qj=4M{h z`y*aHeHMWm6hj(?_mccIKFQcSlV?b91pxW?PpPfy)t`LT5JJt#2}MHWsmu3&7TTKkU9ETlFzzQg80k?uO=pdJ z+I!U$fl>5HfMyA4ddEZxu61jM@-=*L?u9(#1E;Cee)_!}J`AOEQpyQPz$|LE$9r2b zOZ8=tQaU*N<`}7fMaW5RMeJYU58(Ky4cI_ zA}&ut)klTi@e2%dxcOlgz8U?KlU1+am*MI1lu?z6u}LZ$=^SOo{Lz0RFl8p*18&~~ zOND<>D8s685^i-CJ@$sE&g(<|eXcCgCp6!#joApIxB0?(>De*PU}Z0}G$%W{(OI>H zCD{vt_5>qb%Rm${n33&SW9Rk3V>6TAPOY_E>kP8>jVB~ZcYhe#e4%EwYxpBI{D|%- zIi1awq^oJ?_VpRZY?XL0&?ZaS6hVTBpF#`jjKKRdrcK>>zbkynFQ78ft1aJ#ka1~b z?PCF-%~ASSi}dUB%b1Vd(;Rfr=g{LJkHVrYpSr&Bm=&Jw56F#<{XGuX!`2n9>!l(? zF+|ABNRP~TZ0C5fU3W6b2)qa*v2Vc&WC+={%}gok-z!FJFF`$jCF10B(CS2HwIS9EV$ z-RSJIAkR~2=9Zn^Yfrm}dspcdw_Wy90*+Q+LL+We-si{765UfOai}jA$)>VC$+_K+ zBO7lg107%nQRlA|Iq^QL!1G1&BEnq){@HX*YX+m`at+6GJf0dK94>yC#q(&;r6C1z z$;k!5sC(G{p%lH|iMim8g^XVrWdv!O^km-hpyLrf^-9kxu}Gg>nXV08Ny-$xf6SFA z8S7mCu?qnE<*e#GFB{~QN`RK8aK1us6Th(Xa~Z)`@cB_zD0?mX%##Jh=EBX)z7G|eB3!TYG+tT|fpd{x2^Ms-Cf@}L2Fc5nve|nCS}V_ zoI+c_z*^&2JfDAkp3v>)n&7CdfFbsYBaW&O`+)N02R6MfZ?UBIeq|Db=uXe4wY9a+ z%{`f4J4Vt)8}H5#2&!1E$gnk;?}*SldI2dpeB@jyvh=3#l>-7I*jgO2U+EDDNhj=o z+UDj=c(Nb-+*4;XafO^S{&WV7lc%-2rG+DjrXQwFWU< zisE$QtpGnAf#H^Viw?X-Bx3C8^920HKaZmt&gQzxYj+*iRBmlYNDYDJi9yI|lsLY# z)?>=lFAURF)Rx9F+y!9|PDxy>GzE8hr4Rd!bqbA)^a;b>xuh>>G1SaviJ{yOCz_5G zoW?)0X=mcO$$>CvB?WwUdg@N$PKZP||Jr=^Su&=gSpt(1lUsE+^^%J#O|ku8o5m#U zvUa+9mwC&%?p$y9RHWj$+qzWJAl?QY6!nZ*JR_ABpcN+vVN>BdI#u~C9|IqMLzOtu zM^M-Jw>T1vqxJ7MH^f#JrQ>6gwfAfjoT%_L#g4G*&l!{rf)NR0&+!9i z*opCpBku{y;=>Ho@7MpkYSt*(o{;xG-g0$g%Elz>mq2T>8pH>+RHB$~CXyC(TbDN5 z_5sNR|HL%pzx4<5c!)cSOe~nCEHUY`;2odS$?V3!09`;^J2N?1wM0>@Q!rj#><^}) zgGF$tya*#UR8P-^5MV`+rYd#E%?-ldkhQ!a3n9{-UBm#KLiJYC`nQ~4%9qh)&G3o_ z{8Rj1PhA3Llga1#`FS#}R&inB!Y;~6^>E<6J2MnMS|7J><60!j-|ez7>Qd)JB8Axm zbJD=KsMI3QFMoM4!)|0?43-%6`w?%9eM=xqQp_sfC%yM)Ac zYEQys?D>Aopg)f7=utPaqs~BXzv1`QBEr_XE4#;Pnnea@ zDt)7&%qhIi;PT|BhxqkksRLiPjJ_ED)#1a9cHOaeZ9`vZnf8S|R4XY2pA7XrQ4Ajg zb2SG9_<4YD#WZgfI@9P0)Q&OUI1m1Xj^Hy`ZG|^SD^Xr#ihUst-{HQc{|Tj4ZnSXB9Eu zlQ-|x#!JJ$DzNotiKG&E-951bQ5E<+0h)Dt2<04#yMkA3?6bIcKy1nELS;j5caVVV zV|u!2j5{45TLp};B`85WhIbDuqfLU8S@0jpa5*8!n`(=mb0at3j&~keVvmCI*Z@cW zxoe=`7`=e$=#N|UUf!=xsqy%;lJnPPz`FwXzv9i*|s|n7Uhx3 zg$nB%!GJ&kiPOW9`#?&-Z8m56vRl9CcZRIeVb|7Np7rT6mp#N)D7PZVMm;ns%!=?K z7@MK3$>C)By>b2nHd2ag;}5ph{`mvy|FHJX(Um-5*kC5f#7-u*jfrjB#>BQcNp5UU zY)x$2wr$(qJHP$DZ}G>TJ$ttQxLv1jRh_EtuBx}5=Y5~vBlt^=B|QeswVQAEQ(P?4 zY-o{H$3z6IZ$(#FKLSH)yJfF)k9_4O&lV|HF%-tNOeUOr=c~^x3lf3aYa|)}f#0J2 zy(BX0z*?JTBrM*oF_MG~X?qJJQ?AlCoPauEN?Fwr6oDy`{k4=?JgFX{F$X-r^%wnG z4!Z~EuV~gi{_hefu2gX3sX{>w_3iQL2^;06O$|RmuElYqh{hFaWRKSJu!E=3_SMUx z%TEgjB>axCsX#~up+mtL{d0e^$}>%g@Ekjx6PT3K1zr12C*w%|oJ*X&@>OiN-*-PR zQ@~J8f)|{E98vHZl~R3VgT2RfdP{gG<<(U2N8(}v8vY@voK=NNLRe!a$ygB=oXurN z0anP|ndj}3v>EKB1gd!M(So=_0z%Jr$jTpuf&wo0FbgvPyK%vv0G;;5#Ez_g1d=v> z*bP&L;on*nWYgif(Cf~=NDij{pFnzvE+i`9HK=py(V?MUr$O23@7~z94cMy{nRn^= z%*9dLdG--8aeZkmF(}U??h?2a1P`vAcMt*bE!^H>y=?< z^k&c85_-1r5`~uXu^IhQ*oK$uy(+fUxscBW`(u~MjsXv;4r zT*vyajo>_$IOApQK4!S*(g@$f6p2aJ+db_ySZ#Tp6R%lr6bS#(VRT`4m}?Lm{R z;y^OY6sk)~BImy?w(Z?GU;1R{q8ge#Z%OMJSRNJ*y1TN|~x-Ue$FR?6?FL*r6|6WZFI6e474mCq_2y zCN4!~7Kq#30%+Lq#Dqle+|$yIU98^iq9WHj_3e&#LH%;Q!kd?Y6dLAF$;D(mot}G_ z?>}&wR|Kzo!H~YV(4{QmmiR{H*<-)j%pjj|*7{GHM70u!R`5(KD>v}%zR^QG^Si&t z>*_eY<;|#IwOC=u$vQw$#_)P_Y#Yz-B_ifk1ZzhC2M$?1sys6(=`nlJvvWB z)=OPHr`LC~f)IU`o^cdOgEnDZXaLg5;E6jqgr^}tDuIh+x>W35f_Z;l#96kb^T0B@ z>Z6_vdxrTn9><1BFt&0ZK-bFG6s7R>j~%7|G;p2KrSxa(paL?%tz25(JcF{VB}qlG ziH5>)ADd`I|94tgC|wZH8w~>_h=tO>rHTpvV~jojPed^il9oCYFruI{zWpC)@59}j z2nPDk*87iS{*Pt;S7f>b>1V|^5< z#MzaNO$7n!LLJJ>U|AewwNKLilRL8-WeIvVID4mj6kCk*VdmQLX}gDGU3-Bk>0@ZkKMGOC|ML zev{4B?L9qJ*?e$xEk47opDiZoG002ahW7+3>M>V;tXqO36-0(SYPLv0U0Ph;NVc+? zIz7L@QYBVmtmQzWIkmMsaI4fF7e$}F^x)s8`VGIoBmy$j)TDF*0jlq@N%2xsFC)#{ zAAcT1MFX;B!l8y+V0nK@ii*KB)aMw&<)fh=x$gZSt5Ub+YqrYABk3`YR7P%G30??K z01YmjFP>(L9|@KM>GfP}&Ns7*+s7c!xF9zYDJqZr71m1vAwkaLlkyFOKiV(Ezi2RU zDujoeU>(QZWo13=!E(U(Tl`74cC;69)n!^*Y#J=X0=(?zX4|^L&Vn?kBtzg#KA%e#`h)Bx zShNM#O@W1Rw{2xuKU3gQ?R}OUln~aqw9w24p=_J6>b5wvFw>_^$g}TtDxGJs@87^| zy<%E@)t@@LfBb{~Z5gxSy?dwbV5NqJ&0TT1z=0?KMuf0E85@Mq`D8WodAg3?E5`8` z8ZHAimqnxSZAxab(EGLj<9z2dM9I_r)WnDnL<{G1G19yiEDkl zec_@2n5v}zpZ);Ba8O1nU^LFO?1*1>T^glX#{ejqg4cdV$WnB92CL;$(Et_y3ccsr zV$`7OR$Y{FZ5+z<7186i(WMLy#+8fHAU1pu%CJl<-e(ek*9S5famnk|KYbGkn2K?wiD?e6!7ol~<-(6?jqgv_sxoq@gt3=RXKQTNZIq68oeK3&w{nhgs= zE6l1$A5hv3B9#+exl?xxg&aM&nT7cy$C?rGTC&6QW_zFm_RboQDKcE$_xkCkLCe_c z%11)?cU{CI007>LeheAad|VMZS8QMn3d=i*w`nkn0vn<#ZgU=>P9(lPkVY30@}Pok z+{u@ZDUqPttF8dpgS#3}XnW+P0r{TP43gXYXK<6tmzcUvG7JfRhy7SCi}BP-?%hvN zK3CsnyTpQ|HwQ^eORIOxWl>}yveOl`)e$yX|2^5B^e1pL8ayIh*H}IcRyJcmhP52k zT>wQ?KpAJTSXK6j`9*wBdXL+Ro3ts+;=V?#kd3qmdv%coZ(O^BvOO7s!ZG#H?_dV& zex|Dwjmw|>n9$&5n#|aIv% zo>KC{N~4wFb4fg^Kq_j=N{S42hrbM_=0{2pm4AN|ZQK;U5_G7o)!sSTVRCuMEiF5v zppI9ADG;j(F0Yy1z(?BX{|Su%Yx*j$bBl3e6t4xbpT>r7`5$*JL(K$!Z6-k&cJ^&k zOL?i5e2jJnj~g03IZ*(G=oiLwq8R@uEe`R(g8QZzy}i-v+ms9F;aNwA{F@!?*O?>v z`~$fJA@p1lcj{-ZT4i$*4L0h}RVI*Lu=XTS-%JgLD6yG0y6+7b(b zhnJRVBA*rmjSFC9S74$jK?|7gn7piZg?T0xw)2k^ zxc8AExg;K&kA5?V4%ZXswYCQ*g53O&l7fz!W>h`38gPv0`VvydE--m!2q4Nu;3(kt ztHi=m^7t&zz!9uyDlYZ~7ibde8~q?BqK*W_V!}P!rcF@;6rgt!@IOg-GqCd5Y?QXI%R$su4C^elni7wUStB5PXgACNn=51R$7_a=R z&ciKR!jA)m_eWMz^5kl?g&y1EP4a5NU++%R@9^$!maFKhQ~ifM`CL#jXBbqN z3x(<3(Hc$+0fD!zoBmu9@0+aq%QJNcM^y?;uFpzLRk110=XI903Va`>g}%Ar#&wpX zNQ4(3c}18<6yJqyF|DWhU9HI+T$SHA>zZ>UssA6`wA@lWyedkKpE*+k-D9xf5mqr2 zIFmq6Bt{>B7}yZ?2dUC`j?Ux8HNTaNnj#XoS*rFN0BZ<88Y_fPWDGQ~OZT!j9+Il^rP`E46EX@=mQCRF z2|kN{F@kN;r#h~QA8I|%?6UkX=ew?syU@4|ysq*e4S8R-+D`^nyqi($JYdim5QHb8 zV-LBL!cL__&QpmB$nqJDMRuhAc+~Ag^8bqDZ+_X^m4)^Yq%H+OVb1Hz-kpfS)fNiNY#2 zi7yayk0@Uy8n77o1KtFtVPHp3=5fSzOcW}v^Zoveyh`T~Z+*RQ3{-}w zS@QwIES4i}Ly`A6h*_`1g@q#tx(0_F&eEQ?tp{Y^2se?Sd3OKOCdxJ3bIjZ~$+PH( zrB{`w9|E0c)bUY?>k}-#`G2o7vtHw+*?}g?5#9Qkffxe1gI{6*w1n%im%Z60b@zQy z`X=Y^iS)Pf9R-D!S$o#VX)tZrA$#?2i=X(VslEO_?Hbs6 zFta&!Aqh?xVjfz1m$`rsCZpioMntV4#m(Cw3qo5VVK|MC+ zGDq8H04e_fBWul25lOfG?JRXXfI_@A_vHuqM&l4L1Wl&DO^MJ1%Xt_nMzVu=$;sSBavYO!6ZN{vlK)fO8r zKeEsA(&xK+raU{d&pfBRHk~fLHlI8u@1mWu+|c-V@}0wjElEn_egu}1Aom;oovdtF z)HpC1oTKG)*s)HH385cwrhwvUS6VxvRM*>1ebb~dqs}{`m1-vb-Lv12;HqgpTGhH| z%~@ApbT|(IOSCr^O-?(9j&F;dir?(N1CPOLx;HXs6_e$3;x6lJn6flDl#4iZ`5<^4 zJD8^s&TrQV?FwqRwYeh^>bctzcFsol9`ODLyLPUAElXSoHR72(Hd87JRkSNqg4?jA zTT~-MSxFOYM|DD03*F~R2*UF`A>XGP8lhoTk=U>=2w=ikHm~4826WpFw9H`>)jG7uz zx}0tU3HOSz@w+~~v)BxqzJA?CIZ1RQJB2hbUD3EI6;gjGwXfQ$>e5ghb+)~v-cK%9 zf}1T(u6z-S#x%KJ+HRE|u_+Uqc*L>?B}LGoa}j(TzF!(|N1VK`EG{O4X(;3X1pJWw zuKtz{_06o`b7Iw$R`E-@iBLzzqfJlq2!Gu>cK5aRUe^YU7gzCtXH%Sw%r%3L`ak0E zpymY<6wQizT#2-^XY=`R5nrIne1~m>n8?1>Oy0M?Ze`q^VribbK%A&oXo*5wP$%FK z{ z9(KO(Lo^A{6H9hvgE52XGG1lI3Vd3*qf3X62_cX0@#D=cE%2WxXI^9~eatuE66)Tg zk_)s2rOjJ( zCY}M}9jKGp>%p5CRT)|sx00&^v2JN`KJNV{%;_dUH+-dqQG7UNv*j=<=!U=r3#A`E zz~xH-+7H$hw_;{KjMQ{x9;eOaGCNPCwcNSi2)2k*g76-*Jqaz*uUWs|zx0cj1Raa@t_(SRUOW_Vw>B`4&ZVNrE zeMavHG(&BCzV4C@Y^Qg4HzC*3=1&G@Jt0W~BIHe??fDH4gX+8P#5%!*~FE#6S zI7-4#0gP}Eo=n^$Krzi>h`ZR3T(9`AeznBh+PwO+G^Fv31<@LLDIqsj5Ca zUaTo8Dfu~^7q&rrXkt~m)-h`IbKgT?I~{Kh{fV_&D38xF8_CKM`sldNl(%SN!E2AG zKjJ%B(lyQ_;xDde8y@qSIRX4K5QBI`k?0&kG@rC5Rx2nj?nl8JN=i=d8m1LPav${# zy}3P*wy>}WXL|)-7vTW-el3^OmD%x0?V#iR==hF2eeAqqY;L-hX%L(#J>*BO#GLnY zrko~ia?s@DnzF-RMTnKh$Z`ZE=g={;Z+70?(gKh!=e*;KO4Swkr7OE(XjB`d}J`AN}>)MGVRQiGcBC zaAv$xZw{w$G~di!d2C<%q1O!@N##9+8uV5}d@Q{30fJa(`CocLBxryHs6W2Nvn@+= zDj>04y2a6Q!`Xpc&(`$pLU283GwaET@2EX;)anmy&v^gHmc|EX2O8}!{lH|4*sB-# zZ<3Li84skK*z{yRKg3wFX1O87po%~E3R05Q(2sslxa*IV;|&P2Zx}knPD#q%#s98b z%PvC8wLq!6MWL^xu@MU{%*5TUAxp1>76`OBJZ7ix5!bINb3=%X^`&`zI`Gl5!6BZI z5)YeIM}n7TNV8)<5?#Vku*U6stoika0vzJcg;gdVA`-|eSQ&gu1)Dd<+@5MtMdf~F&ggoF0rVR_E03~Uh_-A1G!fwp zblirVxDZ0>n@-G;so3OzPcnw__g~)ypx=Tn5!i(cWEd(6T)BR_;j$Ih$3ZNp(nZ|w zb1SHlq_zf&sU2Ru0>0dokQ1*Fk5BFIi0}{*T)FYWT52KYY6AW7@pG_tkfO6&JFM1t zPD^%JN;$5jmKS!&rxx`kBTrqq0(`BU0VJD#-7REC%> zEJ}?1Y-3v8tlL|edW&tW_q@-B1K2TrYGAGiD1Hn%_-fSdP?qMotE+6jj-UwJDmnZ< zf^OqpPo!vUAsuo_(WOoTLoskieR|!)Uz_0u)2GngP+61-AE6zU3D;^c;0x%X=g4fH z7VD9)ab9u9iBXobe5T`!w%c1Js~hV_ctaC+hNw$Iu4-yD3gpjby0FC?h==^V17nYVba7|=EN%YO?uRxmk)=i` zK&f=jigK-@GSabyT#qV*5i!l!sYG0EuJYZY&ff^JUFW@cFUTQsAA@tfF(^cScIIXL ziK*iw6aoG7Kqgoyz|^`|L&*yPyH760Vb$r~+WzlsdyRs&Fen19&G5iSASv@-5~sLn zeo*0YsbhJyaR@@cVBi z?Z~Ln??MT6`3}RJq#rvjVBEzZU`$IZ&{^_bU|nOa~kULIb*|1quYN8YRF983gnC@L0l) z9ux$u9I*X={7blf3=INYD!~k`oN&-HbRxmSrN@G^h47UjZ8iEtlFRyWv`AfW+XT0+j>;Ah>jke(4>A7g_V zDG~z?3_--PYj*lo*OGn*5Q7_hSni;6T9`JL6O2gk*1MS*LC8XMq;qj}s;pGchSsJlAu;9sW!1#e zZ4elRsIRUV%my_SiPCeP#?UXsq@H&nJHmnq*ZNRiGHLeXr^}76pjhfv$-_1?+m4G#Q;zrc4MB{pyjL8I%CCsSCG^4i%>7>{}mGBa03U^euzW%|M+C0)@ zZus&Z*pU?EG|96`R#eHR#|qcdzNCHRV~F}hh|PnYsf-Bc5UgtmHTQl?OKi|9RXEnp zHcv+3Vi69{%huAs$YNBW6F`sE2YW-1s#jV}V7mjq~EWomoo3Mgx9_8+CflrQr zFZG}{IWRZ3zW!a*6dyfTaP29kFTYwsJEotN>H$%T!b&PEYj^z0@~GhKAg9J`1R<-beeB zMNas~4cCOM3i*MM#5>Y%uw?%y^Sa>tfxAmz!1a881XcTthCa*5O{*?5$p28X`lxtx z(E~Om00jY#bH~g^zQ2MIbtxX@qD0t{V$=FXRJKtZf61$r$GU7Hkx>uV!MiDq)MNGf z`1X4bk)A#h5z~rZ=IH-0sRbNW@pXAz8;0htl_NH-PPs5;e6mtaiHFdT zJkLn-qxY;EdBuI)Z0$kG-Wi%5*|9%dS6$#gk{NXXd&orV_mZBHsF;s`qNVlpzuQdA zO}J%Hlk;4up5E2)le%Vo?)-XAm>$M}QsVX-t4wDXnw94NeT+!$yC|gT#N*ydY$xg> z+&rOFQ0Uy6%+pIgO32J>6nM#bl!C*9A3*jVqp(QMy@0N&AljwrF(SV!*ZA^S4rdl} zB+T^dmib{6n=4V`v|mdsIdS-Kwiz8TG1&7B;9EVjA=G1dTpM0i3;IX`_3|y!23_lO z3I>O{ipM>grOAOu!qHhlRF;dLPLDY{fiS4!s{)kQM7bUvnuP3AR@9&NzrWb$7*a>;Lcmrb=+ zn)c2KS$xf2PrinR20Lpk)qpQnY(sB(J&ec%RIfzMd8>f?-+1rIUf+8*=8ytJ3T$)7 ztDR1sr$~tf@a59k(rd#Eur!XEoum6BvAKOz%o4=$60f_qcVRx-su)eKl=DbNczHug z_onT(awvSgE)D?S7cvlAWsn)uSVBr$PG8GLC%oEgiUa$yyS}a9Q@?w`7Phws#Kd4E zRukNWT1)zU<&|=L8lt_#J;o+JS-J-`NXvdaI<8pr8d)%Hz)d-y#}gn&G_Yp6S)?#m zadM<%Y09WFNig`pN-byCCuEnI+YuR@Xm*JUYKYig^p(G}rOjJ8cr2K# zJ^tMPCAykP#TkKm5flXvyl|#U%*vqTlbT^Bg-epR zT%&8NbUCM74cY{a%Wj_r4AaUn=)(1KMOxTcm>TEgQvR}%h9z@+&uDmbJjGaAn>(=) zKpGrju2_YGw@k+hnG5PGlj+V8u&WmlCi9qy3+>P+FWWxBqTJiQpcA!Y88450{afPm ziCab?k4vYjteFNeG1Galxg(lZ#*i91rZi3%B<>4IF|h`k$Enh7z*t{jZ#J2sS!+D9 zl}WVYQk>ARW?`aokrSOf18Fu|^gvEBCAnCCC7ZN)z;r?3j zU3%H~8~9FWs-MDO+=MOiaw-G3l(iw}>Zb z%@w~s4|VOH$-bD^aG?S??mpjV`cM+Ej`&>1=vJ$FWBiJ<<&Z zWJS3mGBV>`xcE5uS~pbU0wW6Y>Mbpuvrl;;r|!%)6Eims{{w1hbKUuK*nfj(=T4$N zb=gBBlVx4tbutQ6;18rUD=Dq4{iy&PJ0MceA@w|V;;|n`yz(yNy;id6tzK6v3F)RW z?DmR1#B??l_w~+%TO|GqlWg)Z*a1!B1{jF03hMF6cx|f`flXnRF*g{QSO3o@5~$m>fPvm?_P+xA%ouufyM+t;2H#^_g@NrfJk*8rCqTS3*_%6YBB8bH6FQdKv)fN7E1>vrsAI{T= z*xfZZvVZy^^Ee$#tj#;xyGKSz29GivJuRXUvSta0Xp&m|m(v5n*JC%J^618c-9?I$ zd3au`r9ek~K*5+nrrjp;LYC-a)>C#3U^<&`H!s@aPFA8SNUx@Gj_I4an-tyNf6%V0 zoK&|pvIf6;)r85dv1#Y?=>N9~g(HC#ml6iXYqEUp(ZWfxV zi4#c}ztk<4u*7Qi0+GVQQSy4|^Qqs_h12pYf-%p|qemmYLaXR>7&S z*O~dL)ygxOh3ovvj9R!+WWDneCS_^x4Q|;CDxR-0lN7hU=h?vlu{|N>tiWvq3g5@y zXt_MH>Y@yPOX7;Hf|m8C;N_UI8%&3MPyg`UbFcbM@xU27Ip;^bn1Kp@D1E3j=tJv_(K`$3-euSL|mSj|FlE19o)&u zd_FL4Qh}ZnmhSZ6zl?>_T8+p@VQ=jnrtg5#yN4+Y@jPyvuH0Y#ZNmnEL0J$Va9w9< zEEcoZ^?;2_pG&jDqfWVrNQyT#9QLV;^*C8o{n0ifG_!IJKkKHna^KfCwA)@Sl}SGk zgA(RzLmAgA;Wv-YjhXac-x8l{JeI%5J;uMdmbDqVa~5)Ab7fZfDsg`~)XW03z(28X zZeVYX7@C$a7WYI}yyk*!A|lbPpOVmiTp~{qS*YC8hX-ALjc+%h55)oCjv?z%Tw9(> z9rS-0l~MWckhMJ#Axh6(qjkc3&_fhfI0eV9z95DoU;8;^{O&?Y7$CK9n@xT@OZiE) zmZ_#JTNJ)%F{v3dJ!@~kXd)_aY2hJWCR450j|&qz&Fo}E2mRptsE8J_zn^^A8ScRS zb|AceLb0O`kM2GIc$-;>#jxV&au0`FD19X0JM=Ss>p8PKrrDLV)qOnz>v*ScwQ*2+ZvGq|Q^X~k zVi32{EJ;Owao@_#e`Df#zZFX~_L|&GBT3Ot#KuQ)OuMT)IEt2lmi%1IKBY`_NF>8=@K~B6g)%PJdNd%2J)streI~pVr}zvC>y9FekP?c#bTT^8}-}{eQ2~ACIS#a zo6hP3{4x?btxe~`k0wGXrdu1JAkt6ys(e+JX`*R1B|a%w;yhJuK$V1w6VY2smhk>y z6Vw;y-QI=8#?JWLD5<7~T1s!qNWCM!{)Xgw*(G=}Rh-UzR#eG@|6;^5;J_7%eGMy7 zP<8wSUAuM}hO9Q$y2n4@mA{2%w@EK%xIZKs%9=)wK5<1n-AFk~R7G*@=Aq|mMo@d! zJsSD5tnp04O0zlE?!@PV!gf|Gc(Z{CZ2dlvK@2-&+CR!+HIx(6tw-0z6{gi}22u;lDF@B7~vvA5?kl(SHN z<2QI6A)MAsyl8v-AmrK9A5L6Sg(OGgAV`HOv^yGC|Z3;J6QagT+Q}Bb5f)bh}o%=I(n^o)LGDfees}UPd9?jml#!C$j z*FPFU`{`cU9ND_CNQO>CmCU{&>297DdvF%y9W9T$Jj@xgtS-()PaKP`j~Tjcyp3(x zzhl331&0M5T{f%*6j~#|Rv_552fjjwMuyPBUhISi+-mW5n{1PV~7JTbRIKIjhs>XNEG8eK^1mta0}#e<}W|Wq;s=#ZYAXF@#0q%F^|nR6Kvn z5YH>AVFZ-{h58kRn$V#8OWdVM`dlXEastt=lQ$}J>n5vms^@#+M04%wrLHsRD)1e4 zv$H)WxMBr6e}7tn?o33#+YS=={d;F3#~0kQwiS+c0`@n`Bgy^~(Xdq_^Tbaz9T(0Z ztTkJXMZI)7nr!^+OxzTRQ%GPQ_K_94;W-}$ngLE6-6gy-%I8{;fR+99S3k1$hz8tTYY9 zcB-{0TPuNvCwn#z##A&*MYfrYxrHIq3+MtEFdq#|2}Iskmh=6G^AW+^MW~O^!ViEV z76_0$_>KG*a51&J=kh?|Y{%|P=kZo4BP@+a4F^JYE{$Hs>{0MAyRDNuRL8te;xf*` zc_N-Fs-O)*`D<%7UNL-Jznzuj7EMT?v8v{_J^POH$YeeQ5t<-fcidXTB7zY&uZo!u zcTG+&ee%h4UpQvtT&H;y{qwkAqJ$G9@p>y)9YbuL7olW_p(sUdamjmzg#ntQ?ZW~> z5hF@Iv_7MWZTZUiZx@cF9ct~~<0={Y_ZPBa#46=89SJNTWcvnpyL$f$llSXH^zP!Y z{a9xQY;Ii&p`n3oX2!LXV`>DVY#9u=hl4?7?@DVWo4tVvi-bBdUD9k&omhRu6>$Vt zDFZ2O~mB0FHLfbFx6_jq8@GAFb(v*?0HjvTy z<>o7q1i`*32!Viz#iRV)NiA1@vt2U=`vLJA1p$PL$!)%vvvpk2mYfI?85Q z(`_u!xFy?Y#E`phdsSxX^_o^ft5ASQEi;&7r-hO(`1u~n?{7tz39KVD6QoUez2)~e z9@NPml~4B$Y7<1~dU;KZ@h=R>fmxyfpA2HCN?%?kt=^H$)bDy^E#<7PEu!%tsH()^ zQE=dG`2ojEPED(uPEsoV{)a03q~*Yw;UkOfl&*Fz82caR0~Y)etvmoY^7&sFeBQdV zGoD^t48eL1A;S+;$Ef5XgSbv~l_P_krTOVEZ~=e6nZ;r0t|ci%}mI(mLHb= zEeT(3%uNyV_}$GZ;>LB@g*f5*4k1HaR^HDa1F0j0QTF2BfbIHD-21-o@_8rj##Y5uXhxBxu!>pjBxmhF zf`hKuM^ivS5gwy-4+jkf|F4@7$9pt)FHsm=t$9)sBTVSh+VX@oOuwclZk!fZ$4d2e z?Bt7^dIn%{$v}Z4s=Fj?kA0=;dXp!;T@XdI>)iFlrDK(5QR!Z+ZyctyDtz?_aRC}Q zT405=8c(jQOO0y#?0uc`<<@{a(6WX=rZ@m3i@4nwwY7!{q0ckkl3X40hXGSY57lv0}pOZd1 z@KY{KCu5k%Yl}Ospu8!gE8lcKoKNjXJ@CJ+!i*2NN>t%YckH}<`|pJ)L3MS z!mPLiz>R>0%FE6p+tvs@%f>1wHfuuk|*RkLnpxLSw&Oe24Ia=yMhgl8ig5>5Mw3xc7>nY{ktVj< z$E-bYk9Z!%HoY*%j*mA_Y~<;li2siKjV8BTnFvyge$fyfo`LI{_ifzvfq?_Oz;DmhMedG||fOHg#!wQqVWD zGJaTM3$y$Af=Ct6eU?J*zmpjM7Y9Z#v<2Ua?*8V>6Try9jeWJMhO>XrdF&hzOZY^z zIG;oFnb;}v49|crXfoa1Blhc8ZfGbJna+ZUE2fw8OTac#6+S&4 z=ij;rV;?9g9;c1?PvWsf)%)irsD(kte95yVMJbz#eRl+#t>Chc+w85;Znogs9MZ0Wtl9kxL8?#y)bAPDGq+fud z@I##}cH6u$ftc;97ZfS&SFC-8V9DF?u+6Fe~q_ty<1MzT_Rvxh{T?WZ)qGCVb%O-?AwH z{q4W>zf1qpz)PRV5ZG|+sPKtkKrb-^#<{s10!qmL=OX{J#+>XVpt~o$6e>tJaN?vS z0s&e8ARtmuApg?;=F)%Zf0uyiZY1E?cjPnyex)1a_afzgCn8{@KtLFQ8GuJH(!bws zBw#5J5KLhEfQK-!Di9C@C}5*OK;VG&1IzyZSLhL=-?E9dt<$iEl-S{*axXqZ!BaT+ zo`O9(vmF;bK|xzy028Zlz=ZAcPTRbc$KCPy-2W?T7>zcWvXIr|WgSZa*qdN`d+grT z-^^xw&fpVE_@KfX@6?{bQBH$x@_R>i-n%~T)r+f3VJHFeFgTRj<>%`<<<6XTcPv9O z*eWaIKG;5okqp?F`~;A}_Q!L+zsmY%1D9r|_udDg4I^VE83Enb1&plN4FQ2X38M1C zTor_C%cG|v^tAIh*96ljyMA~-p@Z!yZyIVjn08Le&UHZj2}y|y+0@i!^-dY={#?-6 z=@qq0jJ>Pq42BvdDJ#y+&Fz?;bd;*38z}>kE@iXgvX;WiP6VR3YG)2p+>a*bf@H)heR8rc z5&0m(NED@bOQU-6k?{9?CZP!82Tkkuh;h$)TKjSsYD*)pw)&#M)<)OarPkL;8{Ec* z&Q1hj;`c@K{ z*!Suh%gbJEN=h3`S_LHEXmGd>*{fczq;BO4YSLG01H@wp%sLqa7?dRyeP%_#Tutlc z*18rKjjA=krN>6cC0uIsCI84_k+Uv|uD7J$)2oY!Ih@X02(8odPU_rHag&o1aT9ca zgR8Pc)wELcE7muf3W4QfMGV|0Qd&Zj*5^Q*pV&w0LYI)HhGj*g^Gc7-Eiap$lw{aL zh0EgF70}YW%f~-yHwaYf)6UA6U#{C$R+Uv)^*N6SYdblC?WDIRwade|qHYL@7w1~B z=!s+f80%_##Gg;Z7#t!q_IVc|B*fJrh7c38gvsm52EI5Xq2twK$`!g&(EPHDc6T0I zfzp1`v|7~ae=sv6CZAV)ZJ7$Eht2Ug`A%^f9?Tzyg}{;Lb^orM`Z5$Qfg!$7grmKz z*xVkwR1IK1ZKFzmGEs~ytq*b~K(bvdUdpuBQ*?Dcffdv9qdK^LC&R5aE8Y9Hb*X!^ zBwwS;9~2w%z%?ZCfzZlj-EkiQn%=Z=cDni9Pep$rsEXF~&yeZi2kkeKuJeaCYhYn)0ZD&8_f;+MQf&{UQ25 z3r2&U;60N)xjZD-7rpea+UDG@T+Z)N0%P5cozs&tm_n z?D9pn4$NLlo5R>R%a_FCXAZg+jLHg`W2q}v%wchIYj;Nk6=#!X^L17+g-cDSTt+s= z-iO{oY!#`R@p?JC~Raqf;{|1dJa;KGUNBO?Bt8|#{X*Sc#Y z#E^{lN~G;inXhuvsXSXk)TN6?Pc|FYEehBjQrpB4{iW`xOoW965LwYIDW;LKq{gBn zN_|LOzd};OMMXyDlmY}wO;M};k?lar#NVq6969Rlhas*_T^Flm1$WemuY*FJT@hgKQK|{hnk2eS2?=p&SJV%Zf<` zur=)pH6f_@LhPs^w}Kx5@?D8uTJFY)!DwzzoxxKpt=MU)1Mu2-kp%oJPuI7NTjQjd zy*uM8@X=^Al)M%_Z{}55eab8{YrLpQ`^l+x_Tp)<@Du1ha@QX#NNQev+8vc|lMBzi z9-hR!D4|w=juO4qj&^|IG`4v9-nOTS2MJ`J1)8qgpDlD-yac&?W`X}3Y5y1<$@|5R zqM4aYY}=Wb6Wg|J+fK)}ZQItww%M_5=j8kQuXE12cingQZLO;AuG-blv-bykHypWN z4e!H6&70uo1jsCS1}Rym&~&=3+%aGQ5;6mwF#32#ATwkh-%PnpW(OKgbXOkjE&;dN zj4cEi9?R2-&23c;;;-+cG+&{sE{cn_+JmCgwDH-R4jJn6wE3{S8;sK z#g41XY3cr6WxBDA9mA@)8BQJ_jrh|6FPeU-;$ySa_j1o&&OP+C__`Ih!_|Cf)Cgtt|93ls_^X=)ex`yugf$>bQrV zp9`IVDEk(WHzi60v#PB44XJfoUP?X-eKk#c*!Kln0I^|L)aK5|XP>tHTyHAQs@!yG zdkwu9LQfOG%e|{Qwre&bnxc}jsyQLyeJXJLzsN}{9$)ZDkRQz&Qy<>eD5{eD@v`6- z@|h!(YWFV6r#jE2JH7q_a9-H|ybU14;Z^TI7w(&r`}vcIv4@SY_{t&Qwj6lYK(edg zvn1on=2p~vFg}J1A6u>!Vt%^7KFgG9Vq;-pYfCot1^!WTefGpzbqm^oU!_Op)M=@e zMJ^~(2MHg$1+$~uSJ`e-r(fe4nU!K!_5EleEOmnaq%G0mmoZ zAJ(=^+mD~mFK85HzzTXx!F!DaS5(M_$&%OK%nNVR9R}>Z>87)axQ9Vymre7L9!P?b z>@+$XmmB8jBk*_uFjjaNeYUY{oWDF&Br6{d09mPgx&2MfO}PNyl`ZcM6kvzX!`C8M zXJlVYTxTcs7eM|i8<`rnxJJTl@TL}ZcvL&_ywTL}2Y<|-fkDDn?mC72&avs6$8G1K zTS>pH7t80=D#V3#))Paz?oq8H9kyi-+%3HW!|bP2Y`bJuGT1y_{i`VHOAMpjQ`1Yy z+~V51&s5OGLF(lwC5OiO70&3C|5<{7p~2qkYPAYcT|*yzBTAF`*NM5tt9Z6g$MiGn zOzbwI7=2)mtKocx0m=btRB$=ONV#%_hqO}K+wEAF(V#qIe0;oCByZQKxm22;AY1}! zH=(Y}ISzJ}4ySCPo^6eY37$EHIVNTqpuPOZB&738qtEO0j2ojl;-~8!^RB! z;$;loJChTR#VjD>>JJ^KCOUE5m!}IdmEnG@ttkBGVMP4=nt#_SyF%7e5`l!Ayi5t5 zhl^uWgj@|yV|KPHe4WU z?%HYS_!N_3^m&syUo!P&-^LIso0DfpnD`DF)dN)a?3(N<4!?W2q((3y{f&4Cq!3ID zR2A=eKKQl*hon5P0GBjeHm_(&&6qah-(z%$a=0{?Qkt$MFP@_`zA7y7t182|h@Pwh z0%~xaaF;ckd=`(4WRGK2l_DyKx4%de^dLfhaZbMlZJ^7(ZDgXl@Jmtb%@&xM^ZYpl ztr6Hzp5O#`#Bp)cUcH+?!nPOi;4e{0tLBLcwjRDV&4lNvUD)kFv1V3j*XX5SPjvCH zSp~oRY%BfZAseXv*Wo(2fYszW2s1qrV>a?;MD6oUA?-zTTox(b6C9F#c$W^d&pZ9yZN62lfogz28w7-ZK3iD zHrwogO=+|jnOE)0G4=KncTnAD&V{hyg^K^P@w!*N!to1^orZjRMq`S4^%S)`{Eg`Yo9t0z;FlaCp0d ztLgFA7{`2bgTWvi5nF0w*&2QNM1Q{)g58fxh6%#M>QvN(Zt>4BpN{j3hrFSsUEH{3Wbe043Oj!f zg!aclD5VgTF|x4Gs-#}5UzpubIIF^Sx@<^Xme8h?bR38L%}|$thXhAxhhF-xZC!yMp=BQBLN$FOBCcdk+}#@~U!w{gpogHVdf?$%BB zn@_5mZ0$pnR;gJ8Ek)#qbrj9bt8-n0&#V8^ORHS_&AkvnsP*KDtRWGRVFYD`#(F3Q zHEvDe1a*YEg#!_9VzS%LCnmm8+*7ETmt2q=Gd80VmjG zJ*m)IHFhX{S{|M_r>MKU>|#tRn3|DK@En-|#TYbLWcp zkd>unZie0tS~6Wj%`7JVxQn&SA~W_n@tuh67iT|k4C-aaLEg&BY~&;LdClV|O|-a- zyy>IX-ybf;I_-yE;kG<^%Jb%Yj_FSe@FS5n3^JqWi_xoE5PfyETkYJ&)Gdh<*Gtv^+ z(>!hC*{yE)d7T%9{a9_f0B_xSWl4C$`MF#!mpFfE(O#Q_!}pumA<*>qE{sRgZ+T>$ zN8hLbtXl`DO>y++8$|Oa>6oXKABzy%z=gi{yF5!7R&LLG`WN%9}`B!VSHCX1)q^ga5~s_A!TYv9q^ zD&?5uZhHDw*Ej@B>toya>P)X?*jW`yUgv)R7QF%(Gzm&$_ze%tNm15~UR>usu5Q|kDZwn2Ao$C=roIaz&ssWzeWT-j zV&8z(HUVKNDwE?T#25q&uU%v6zq^n;95Behe6mO)mA>L#0NA20l zWJ>&iXiV?I;gL5b(Dk+yc$07(4cp+nWyL1_cDb=LUdMJ%hF~<=tfRs}!km0;c1F_m z$s_`--BzA3723UtFw)+_^8OGXRsU^xKaAe#anqL8|Kw7*=X5Yhi`?1A%aVcu8KXk$ z@llS4=$4uUm>i_9!{~LZ^vOwi*bZ%qj@EZ!-VC(I!q+KA@hRJM`+VFfN|x=be({MF zfYrA(9ho!os;K6;gpSIsPTyz{jWHKaRM06Fw+D1YaZ;~_)0qlY>1~f5G%RPMqp0qi z)l5}TSeFZO;iImN1B4{uH`fE3N3UP+O7!VKNeHE-6C$H@$%MR&lVzo36tFoR2XQnx zH7^iJRsX|IF*x)_EGzM0#nf#@bkJ@rWk@)7xgcd^ii3wBDDv0YOuEwp8-@ht1W zZFD?^X|1ts^NAko5PJhT3J0G%{Ov9Tm!J5@@m6>G+5q{YZ(0q+FgN)94LtkinZU-${9TqX6X{9A zz?|uLUkNViqzXeKpOkojoL=j6A$rgOAa3bOMZ_Uc7Z*i^YLrG#Hnzq_ znov+nbLItkDM$osXkd^2sWdc+)ATL5vr_vrgP|2#gF-i5$-tLf;r3wP@8a%w4-bXM>xbB@(C ziLqHnWcYIO3p70Um^EeNrfVITq}*XKDncSLVmL2r?ekXjp`qn4ap)b%EOx|WIbzs_ zkV48Var^~EoY+SJy3O!nOpZ`U??^PB_U$WkL7;_{S7&E}Us4_-T0k6LkwV;CeXxK6;LeYS)fqf(P7BdluG!jasArt?E60oE>2`BGco{mE+W74}>9JCkWJ9V_FXb)& zKNlReEr03fSgbR7y+v+Rjg1%m;V~z<=EjrT-qm;<=9fC7x6@2$PZfl>5;2P;ytOZs zmX#9_#R4D`l@+{l(nxtU-UJ=!H$xN2|63qo7yxdOMT5O=S`;cz*7Ct@oI{4w;mo4E zX0jx_XFCov!v}h%HDk^(_^&7SoUO6*F0Gf=Rg0J!TC}vh z2YiHpOKpp1VukIe)3!j9xtSeWE{Ui9hq?K6=hv+z7HSC8|F))J6g3Vwxah4bV>T7i z2L?HT&02@s7|i~!3YFi)&~V}Ar5FO=UW@m=qXOndd?4hjmUfMSUU^By2K&z$IZW^M zFGKUH((oM&_2ig?!a9y}xiS#?iBiJGES^q$9lA^YkwIb`8yh(|;y#4tZ~LoDGomtc z57zFq4DhmH$|}hCb+NIN6&(%dF_QP4z$Ps~ZL}i8We?}X8RuKl#;YObmyxK9 zX?KrMG|4P(hf$&jfguC1$j7H(>TZpcS8X!<6(Hh@3EM?cL^mbmg%kkZ6ls7MnI(4MM`h;Zkk@=)WGO%Dx2 z)_(iy4^97(z|&<%xRI}PG%5VJ35P$aKphV z$w|RK!3Ei_>9=9$B(Z%WO_Ak$(o=saLx2!R;s23f>ero3>JFcztX3HK3*>0(K?o`J zmfUKdG&qm&D9V_Ia$m)SM&g<*|M18W?xPTp|a06tLs{`({Khj+xV*+$r z=3`uWoYz;9qD3>KBOb>#F%X3b=R5KWVQ}dR1l3t;}v!5p<>{oedL`?kKUUu&#k2IwQgyOR+~r= zyJm6B-47=2FHm!OCN|H$6&_rAF&}Zzgg@)k&jaIKgGi%2tE+U(Aa{R*cu&awk&yeA zgPE+1I@{Qp%Y9N=x=zK{-iFF%Y|hh=CHpB)J6*!}@UvQ+-^&~JL?9{4h17os=KNnQ zK1qW^$AJ|;V1jyQw;i>kQ*yd8r~SAv@9v~VS@<`*EouM9FI!<=X$mB=5^D}*%>rt^ zm`2ppF!IpUOpXpGEG$olmx0pS(%GaqQCco7ul;lq@YNXIj}B8$m$e4FKdov3w;+7F zw+&%P-xTe0%JsE_;!i6asbg2;p{kR^==MjO^<|oczbf&2MESwBR8<&sWZqAHY{gTe z3g&i3=)xEK%xV~9X=xG-apMh>GkkLEN*;;Sq3GXB2S#5nlrcM!!jS_B$!$K(1vOGS zBT?pb^~Kivr@p)k2UQNyFaJ)!oknC6v9+m!0`}|Tt94tl3X%EHANZfUT$0gI0obx3 zq)JLkF^=pWDnO6{XprEafqP4@%Sb@6vQsY_v6_k@1ccAp_m+cR&O=*n8J#*jekE#F zJhjqhyUWaY5n5^I)d|yS^#SD#Tagi{h8%U)3;t>~(1XdSrM|TMTZ{3{C##Gup)9Hb zEW(++;j&SZp`oyf6&=s*{tcb34K-GV3JF2miINt&F{%?dFjA~DNj_X(LL3kenA*~b z%?ut_IxGkY%1fFTrZF8xr(Jy|@NW@oTRpK}*C_ahFV~NMvHiA3@0K;G1`*h+94ZEG z9_Am6E%Y#Rbw)IPHx?pkNW8dpJf8lMuM0GO>nc-)IL`V}br+57(~S7B=q(pK=)r-c zH5==7SX{9Armok=-O9K(O?SpO*ev0hs|;txDum!V5*qT0`VVl{G}2%mSfxMzS6%#q zM3anYOj$-2vy$2m+Qin#D0R8Lv2&u<3*^T@D6EQ_=_;rT@||hW?~s|rTwhue@wR{| z=EUQUkBaVsCAJ%Rs-7F5Xp}}hfxqI!P(#yeM9%)*AB6$4gOJ-)*WKP777~MwcJ$@%`cnAkT>D4mv-=BD40Cqtiai>=92jQ#%lXg zw0I=~Hb)%VHrSc5L~HTPr^4b9m==&7Qjfu%W>zz4q4QQ$n92*F#^yFX3JW+h3)L-< zwbr^cnxZNYs-&!}v{H9oLcQ$!UdK@b2T!%$RnT2+GnNg9*WW^LRe>I{Uue@HMW7%b z{+*p(l9H05Ur-f=g-oVXIF^={6clBjpPT2k$eJ5d?KluFbs0)=cTQ1V%ss^vyubXf z(iu+9vBzY(v*yMZ!j4Xc3|EE-vV5gtd9rhq>8vpud9qklcj2EeV%~Nk7_-v2W0~Eo(u%B7^zCv z{B-rL1PMT1fQlC@)VCx2o(h2cM~ZJUyRD7=yC3O8HI{&;&lg8ZEK|x%%cN5e?iBW~ z{#zjQ=LG=*GS&0P_fiD$=@pOS`L{ct5Xg2-=uW+W~DrU{s{^7yytp{OH-MlHrZ2zy{-?3#^WXY*0AY37@w}&1fm@G<)n(*xE zTQN6FZG91(Y75@zM#gW(df5>cL>hqxf#IZ_^Dj#4g22&Or7uuZycJ?z3ufBxBV_AbB~B7Qcmd0RniH8GV`}qB<|R zV$P0M?FW#jg1^l?#L$1Ak8_>AcjwE?+{NJW;}X&HZ)K*fA#qoi83f2bdSX&aEdG5K zYb~+MzS%l_=nr3~sbDc`oeL=dU97O&xZK}eL+E5>msyE3MN^jw;L7B`3EJMxo%HaP zRe|gV04Ha(mnd^O$1L5u3MXPX+J6$6e{y`5YfAeIOz z7X}{0(vSg<*J+w?WV=ffvsRYx^}{9-E`%0j9e7ng#=-wn;pma~6<8(dSSa5=zGP7y znVY$@fRaGkH?S(9xQA#sy0T+5t9vYzrpasYp9)6{TCH>umZUV&<6%&Ev299khP2>eW=>Fu-dcNx(7*4s!m+C zLG7eP^6bo_^=Tcj+c%F@?*4h3&QWvx8sowLT9MYFOSVc^Ny4`+>7z03dfKRSCXoH! zqrRC6q%K(-WYFX)S7UuX4DlL6d~^b<^E2x9H4m$Nb~?p$PXBLjl}QG9-Va)P@1qwL zaR0C6t+aoFiyuDkQ!sxY^)Wmjy~*m0D3XeZjKZMgDa0k@-6b@d%v87Kp;-0d&39H+XbGCg^pGCjM2wi@WW@Q#Ea*ChhC z3d9g>xN8{upHDBJ9YI7O0?&eZ@0&U>I-^GUZeDQpxBIY)%3`0g~5@H;1;j1u6qR*z!vhTmUO++n{%1_t__9G z+{UVLt4E!Le>;=l^A5%+B-J|YBm^ZveGh|Kd_axI-WPycI3Q^teo| z`GsIZvdEUk1XOeoXkmoDoDng)7IvvFu&x6U@EkgwcXIaqG?7k zr_PQroP*smJ3RfS{V_N+HD^$e0_4{SBzRGZt`}3<%l^2nH47@5E5sNnab1H`uL0Zi zo%Ag6X}Sp&tX{8~ejOd#sor@`O>7H=^(Yl%A;w;RF%~mV$pD3s$_)n#?cns?gMBh;?8Ze0!zF^l;G8UcRi; z&{amx(0G8I#2Wh)-)B@1(S?dtm)m55Z;#Z#Dq^6rymoUG-fJ0M_-{3Ge}vD%)!3=v zC)i9cf40M`uyA&S7<3R{;6XNh{!yt9BmTQqETYZs+K(r7EH54X(MOe{fVN76|h_I-|{bkXXN9P(l8E}jRhJ$WgMKnpa1YDRp}%)SToNC z9@sHWI+^5W6{gb|^Ix_xNJaQJ!uOt1-3QIfP9$Y9*#kSPs*9j9j+R}#B7%sJbvO!& z+36lM?&}IViQ<#HLtw1DusPGxAO-aDAOxNp2z)BJoZVtlPly~ItKF%1YPO;5cb0H3?#Gg)yOn)q-D+wdG{ax?pcEfyyv@+!KFHTD)DCDRVo?0h|+np&ukJS2~V z&Zng#Fd)y8D_8QzzvJBsbW($GtY0V7XntQ-JfZRO%a|j+-#{5MifLibtNoFTy7^ zy;u*D%M;j$2>}Tuy_+!_mFQI};@|}1$SZhM?i=*nB(%CchM6?T^NyV!LvJs_@`q)v zG#`Wy4Ah}xQDC$*s_EX|FzbST;4r;2^h5V+u>$$W)jtZ9b`H2eo}zd}i!Y1n&AUb- zx8Ex>+7A!X5w62DwTu3=|JB+8&lU4qw|Uetc)oa(fxQmn+8*yVX4$=G0>2+NZY?)o zTh!oqi$hEe0&C&JXqs38DLlWgLgcI8(^v2c)d0dpTP+#xJkN=Ih zf)L^uUq{F7;M$-(U3r1zr?TDXdHkos6krQ~LFWMpas3Oy1M+=<4?>jUA%-s9I|KgT zxG1`GGIsAI!86Ez!cIb*BCig3KBgmJ8zv~e@AnaczyhQ!+s7ZzvNm9O(BD`4B18*3 zFPI!IPrakz`!a^lu^SV&|Gy`v+TQ;N{V-(>*#7mOI5hBo3Q<0YFObqQQ$bxPa+=3< z1~*$%>-OwqVL7R~t~$0xM3Bg0$@U8KyZE4byf@PmXxObBcekdvrx(^7N)okCUN}~e zvQyf#)UXiS9&IM|bI#KmDPCHWdV+1e*GE#3eiJorpS}}?vbD$rpgdaOb&J2v@6<;= zY27ayFQtn&w4xgoWEke`&9Lqi&2aR@X6LlYElVV}0k7Bt^TP+j3|4zgy0KY=BU4LT z;X9TjBd!{A)*Oqrul?WV!c&>+VceDPd$8YQDfS^Dw-J2AwvJ_@}7{Fh*A5LicIFnM@GjL=0h4YJIKB9J#ZXdtqf; z9>(F>UEdvK8=1bY#c87g^Ffku8N#*o6tLX4#4jxYGF*}A_VP`k+OJ@#9g_)7btSkF7l7y=Ffp;@6qay3--8}3n)lI= z(#^@qXDV+n$wYs=r#-W%Okuh-FdKO+Gv&^amkkUusslh_?s89Od{y zp7WM%_Z+c1FzY$~;u{Si)pD;N$!6KyUh^vt>UgFtz#Fu!o*;Qu+Jmd4Jzu7`aNO>* zJo)*Tw0O4HMDrj#5wN+*SUUTqlb7svN<*GWo8h0NYM71QX{3s83Yr@i|FMrvKXuTB z<-WILuGDXHB`T7pr#jo}W7s&5HAsSx+FkN9@1U_hH~r2`i~it})yAo=VAXTHs5ql) zs$tyY%gm^!&^4*oG}6a`M_7<|&=Av@b-YgK<1pw9ia#OA)@V0M67vt zCxmBwMw0^VAF$Z2F{YeY6vgd|51;mBPS6Pgv4iiwUJl}7g3?EwS3pQgZdE`iUU{jiX`=~p z{ddKOCEmA4J9^@W=i>!;o)_Wnb`ZF;0f#KLuh#-LzUYvyW@`p4XKTwZM;ibB@TN(&|tk$;s zJ7-v>?B|eG%O&ewp(^VFpQ+PY4+e`umR%el53&zMn2v3~3qL5u6dt#?up7vh_{ERs z5ovuJ@(!|ljq5%OiFzL^-PZ;nqJoImo&x|eIdjWtm zUxYAhdWsNroxeNF~7Q++x_zzTASb8-jOkrk@e0B zj$0i}Ao^?P+I!q&1;~(yN-e?JhhW5BNn|GS+UQXBjDiaFVqtwaUC;jo$wp~cf& z)Hy6-+Jg{Q^q2k8{+4Vw^5f|`H9VKyoku%n)7zdFbW^UgCpu@^ds=nfrySw?7}Sh+ zf13;rKJjENgwp@gjK~)P|JrjIn(4{0sqPfZt1d?g^s{ZK-venDR6)e}iQ?hV^Da7c zmK5&K&&u*;<>6c#%^=&uNUg?_LyLc}H05|Y#S=qEjl-J7EYKX`~I}TKf z85D(_;x})PLD1pi9hKZRJN2E>frGb@f1wQ2*+Z5H1eD536o6dg+|F#7k@||RPU$jwCpPy*_*P%)Hrvo?D%!>AD1AdEsP&Ms?nqkdsfh!XktT11M`;NMKUQ46vSE?5*3&msTp zPMaO@Xpp*2CB;;%%Id<{Yzh{PzsVJmjDV7W{@!U2ylPjUwUbgq4LYc78eRc#MzG;$ zAnw&)wuV#um7Fzfv(!mv!^mdiOAwrSj4f= zshuUqkcPXG_qFe%K0Uym+}WPI@fXC-ba3O4KsxY`f#8gm88pbVd!wuTV~F+D>8XD) z1`J$?W|s%^f9JXO)%pzE@6=6LQJX1vdneQdse3pyM^cNfj=RnsQW?lvcXt|I9&)FE z8Z8W7i)|JBl#x#w$7Iix#=Q4$#4_uO)m1H;ZX;*58Gfhn%(qs-fl4JdzLsVF?sMmq z*MT2Qo?l3yoESG7YtO>%$F^ZB7HTBJN zP2DE`-^sek#MJ^dBqC?7?RR20@G_^JHJVg1B@!miCtsafdVooG*iz9HN6=5kZM(J=*$-uGE&5~#IDO(Uq1fro`xY+_byU5e6iw!&w^G)nwiPMBgV%8!(iQLhS>Y#D45=Rtc_pT-`>imsrZDtr6a zd0~(o6i3p?1+il*bXESBF=?)6kzEf#tOy%1j-8~*c=&pQ7rp* zVM06kDa_BS?8&Ra$BTM@Y+F zoyUKsrtbeoD*pf6AHUp)X^9y*CHDx+?Ta}hFI~i=rhxkB%mDrI2#h@;vzn`=Sr=dFyM{{Nn78{!ST6;Pul_GNXyqQ6mJL zHwS{M%X$i+F*1!kLyN~seE?KF4qzi^=sFPS{gux)D5;H%>yPD90s&h#6^i&)T9j`I0v zXl;5KC46VUkLY|ujVCZX7V|HkwvvRZa$HS@EH*elzd~+dty^Sx*rXE`XV!W|rl}mJdUTms#94wyb~1Wu!OkB%HvrrhK|1_r;bW zV_!b+BEO_?en-*a*26bQ)u@hXLmfjW&Z#*+)y}e8SsqV`AN)>z=b<7)*w>d=#%r9J zj8qVO+5Bo*hh7e3MG{i3!ZoqmrmteAqr->j`u3+^R$0D+C4fR!&WwV9n8HVRlil^<4v6=0$r9z8c(_IHV|!-&F5k#n>gNzDb#i;KKdLy=txvX&6D1u+>sW_8 zzU{JSQ4)Rv9^Pzm=1~J(M~9mq>n+jfP?uRc`plXfIdckLbQd&zK~F6gPwHc&Dk|5t zcM<+Gy&!r{{~(sC@~e_u4tC#)S)2_m1-(5giw?`<-*;WX63Dha3`Y`qBrJ=rPmfFW zkLh^mBU>4pNZBdMC~!%HBqsl@IPLn*&Yy|4*cNl;=BmLIvs+L`2oeV%d}>H#nAq<| ze!aqiw}8FrwWqtZu`3baoE@nViuKZ~jtn0~2O}3+|C}q|t1~+tq`6#&1D(DrXG$Z- z%L%CDa_?7C&abAPbiQR`38MI@`OUku%nT8Fn@0$O3=#>3(c3=AWHm)1v{Sb)_Jt{V zaXZZg5S$Fx*g;QG(xG19yp33`yXQ~QN9vcUOUa6W+0_jPN=KQ9#8!Bsr-OsybR|qa z{8{^+4i7DuJMwA-*4zy5(@6j7E>hwU=UPsU z8a~emn`IYw`!U)Yj|PY4K~KkAB3*Z@xU~=56Oc|sAmtS6LT(}sn2|u3Xu6KonEO#fH1sH zAQ*Jq^zIw4QjE?H-zt$w^`sl#<^}MGkA-eeh8V8_DsBx0-4CNm4`os0&6_gE;HW>E z%ae7_UM~j=EKT7ROQ}7e;b}K$e&{kl?9feS{i0gFoD`#^D@FsxNK!h%YxOdF^~a)_ zBZ8WU1oCZ=f;`XO-`{VX4X&?uTs;m9Fah4((wSs3Ird8Mv-O=~?9|uWy{l$2JCUtT z;jI`+UL8SgHPU7K9G2eml%d9=no_W#fz;n$1x+!rb*!Zl*BPwSe@2F{Ar2v+JFl!7 zkLON51t67%7%lnv=aTbHPE7}UQAJ)``^fd!Lfz%?12NJ`fBldwH?KF>Ytj1P7kRn#!&U7$*SQt7PeNJu?aSFRv(`_fa45#> zqVKmCo~=iJtF7dCAsp_fw==YZWsmE2GgBc@oG`qZY{3<{nv$Z^C~iOJ>Rk$$4?A>f znEcEY+-_L~i*x(29ddWM$r*ee^;u^f)RCTFJPiyw=fTkP-kqy}cD;hI0}v)FQ8raK zOYmWA{rSc}z0~9;Alljzq=ZvX9$}^WL(YWaJjumsRDr?hF~|xLT>>fsF+Y5B2EJ5& zi-w=w*S*HAGexk{NUg1v-}b~folgz2bk(&{x-?BZm&Z*mFL z8(#mGl{DjI_N?%Z#lWkzLlwXwCY!oD-cc-qq>{-5dTmToA6oqJ-&W<6eq3{%7dUD# z1wnxlJ|%c*<1{JW5F4RHKuqzE{_zcW)mKp6XH-Uvu<)OT}=n`Jv*yG$b z8-ZS(e%UsqrW)ORa=ut=-FvI-_x$V4Mn07$K{ren(9#7OA&oIY{!;VC;R9Z zJdhxew_$mD%g+5VxaJA!NmL@=IJNB*^x~)jrXwE6SMtLbn3QLq)$DmZ$SvZ6&skJb zR+DddyL&hazVCPb?zPc1-A`_UBaPO9^>#&@`pN#sLuK{GMUnadvk8fA)IO9_fHUAf z8RvUGOw?K820bY)saHcx0|Z_1wR%G(ldAQ6!%+2fgQ(NXer&{4JHzylj-#n>Bima= zuq?!HdA^bNy$xbnmrAY5wvaReDqruH)2!67$OP5t`EfZ46ev_Iod2M~?PUWqEwYH; zoaVfOjz~Pycn>xbX(U`#`03L94*C)=eR*%*{Wh-xj{oP!aJ*aCXAPZV>&8T}Ffpen zD+Jkse`Ec8n;f!akA$)&z1A+T*gqU<3%o>PQ*M50F`u{Oe zZ23CyIUM0JeyD5ULXW9$7RoDKm}Rr z{>{l+J0IXUNT(qN3APtb7)ErdvD>8g!PJl4)$%g@&%&e7Iur%tuC@*zA|`5hczBqY z-cM5M>g_*9!%?Zw=9PLNuI6~-P*H(s=+NV5BqX|eeR437NdDXH_TLnS~Hi>VSa78WHn zwbY;at5joSVYe7E3)vq2Uvos(JbsrEWDm5kawOX7fcKnn8ZpjgX8jhksY z7m_f!KZaMd1%qN`up~PA*ZR^ZRmIWrH=^Ux>2|Xl2#L&My~Y$i^#YG7098gveBl?~ ztg@CAah0*Q5LE^Hu!w=ryqsu4LblA*w6g-aM{{8`_}gsS`o%TAwMYq%RQEIRAp3GZ z+`^q(D!W6Ri}Cg0_p)OICFsDA2D^+kAj$LP7M7AqKe!TQ2MTp zVL2fMslk(Jf#h7tI48V@W!j%`b=J?QQM&Irg%wBGD4-EE#uV)Zw+B{LJqkAkVsSJ}GRtrG*41iEZP%Pc(DN+S5XxC%xTWEI#>p!dqwUcTYg}Gst(}ue_tZ zxLQX%zlgf8yl7qx2-jCPyckYdy~~x8lP%xSRP4ROvnnO~N6D=&)I68r%X?Z{@9WmeiFIA|XchUIwJgxTDeO*7PJGQxUoEu6if<2~d_~~WC#~y~R zyQ=nclK_cs7Pb?(KoDJ6Sa+QEkYqn$nFosvQkan3<=e@}Ly0Th4S21duEyJdCAO~U zp($;$$GgY!;jD48lxip=RQZ)gs?viUv6+aS(Yq8F@e_ER?l=6bz zK`q=jbEA&%QALI-EI-$#otv6|W7Awv0Px+n56d(Nw~mx78q7O{=yO(p&2}>QUGoT= zH~-v)1T1tmz~5wn>8MC2g2yFMRcLPRDb_E_){yK161?U8nH#4Dw5#j7ocKW9{vFTJ z77wf4@u!TY6JvH%Dr9-d-c_XhcZs6>QkTbdC!%ym7GBz@A!7`VwnX^@NwzM%+voDU?(-&j7c}Bm&`E}}SzBi%io(^~vKFs({lV;~pvDui zD!!I;dJd{_)9w4Jqp8^9(^O_h^T|qd$FgjO40&t9iS8Fkvd?LK@cE+ut;}@#S>nxc zFqvv-I+Xy?Yv9bnR6pg*hNq5dK{if6knJh7?aZYoGdvxH!πk39$DpO$7B(OTO~ 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 - - - - - Special Measure Pulsecontrol - - - - - - - - - - - - About Special Measure Pulsecontrol - System Requirements - - Features - - - - User Guide - - - - Function Reference - - AWG Control - awgadd - - Pulse Database Management - plstotab - - - - - - - - - - - Special Measure Pulsecontrol on Google Code - - - \ No newline at end of file diff --git a/doc/mxdom2simplehtml.xsl b/doc/mxdom2simplehtml.xsl deleted file mode 100755 index da90782..0000000 --- a/doc/mxdom2simplehtml.xsl +++ /dev/null @@ -1,360 +0,0 @@ - - - - - ]> - - - - - - - - - - - - - - - - - - - -This HTML was auto-generated from MATLAB code. -To make changes, update the MATLAB code and republish this document. - - - <xsl:value-of select="$title"/> - - - MATLAB - - - - - - - .m - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Contents

-
    - - -
  • #
  • -
    -
    -
-
- - - - -

-
- -
-
- -
-
- -
  • -
    - - - -
    -
    - -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - -
    
    -
    -
    - - - - - - - -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -##### SOURCE BEGIN ##### - -##### SOURCE END ##### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/namespace.png b/doc/namespace.png deleted file mode 100644 index e53425698529a23389a85c72eee5335b0e36f59b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63144 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 P@gNc+GQyRDdVc>0fS*Wa diff --git a/doc/plssetup.m b/doc/plssetup.m deleted file mode 100644 index f892c3b..0000000 --- a/doc/plssetup.m +++ /dev/null @@ -1,32 +0,0 @@ -%% create pulse database from scratch for iq mixer calibration -clear global plsdata; -global plsdata; - -% name of database -dbName = 'iqmx'; - -% get the path to this script -[scriptPath, ~, ~] = fileparts(mfilename('fullpath')); - -% save pathes in database -plsdata.datafile = [scriptPath '/awg_pulses/plsdata_' dbName '.mat']; -plsdata.grpdir = [scriptPath '/awg_pulses/pulsegroups_' dbName '/']; - -% create pulse structure -% plsdata.pulses = struct('data', {}, 'format', {}, ... -% 'xval',{}, 'taurc',{}, 'name',{},'trafofn',{},'pardef',{}); -plsdata.pulses = struct('name', {}, 'data', {}, ... - 'xval',{}, 'taurc',{}, 'pardef',{},'trafofn',{},'format',{}); - -% set timebase to microseconds -plsdata.tbase = 1000; - -% create folders -mkdir 'awg_pulses'; -mkdir(plsdata.grpdir); - -% save database -plssync('save'); - -% clean up -clear dbName scriptPath; \ No newline at end of file diff --git a/doc/pulse_struct.png b/doc/pulse_struct.png deleted file mode 100644 index 05daaf817291386377087f6b9d8f27a1d15b49dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61368 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 DgHZld diff --git a/doc/pulsecontrol_features.html b/doc/pulsecontrol_features.html deleted file mode 100644 index d4b113d..0000000 --- a/doc/pulsecontrol_features.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Special Measure Pulsecontrol Features

    Special Measure Pulsecontrol Features

    Special Measure Pulsecontrol can be used to:

    • create pulses for Arbitrary Waveform Generator (AWG) instruments on different levels of sophistication.
    • mange pulses as groups in a sequence on one or several AWGs at once.
    • mirror AWG states inside MATLAB®, adjust global offset and other calibration parameters for optimized pulse generation.
    • backup AWG content changes for automatic documentation purposes.
    • mange pulses in a global pulse database (plsdata).
    • examin pulses from plsdata graphically before upload to an AWG.
    \ No newline at end of file diff --git a/doc/pulsecontrol_features.m b/doc/pulsecontrol_features.m deleted file mode 100644 index 169f386..0000000 --- a/doc/pulsecontrol_features.m +++ /dev/null @@ -1,14 +0,0 @@ -%% Special Measure Pulsecontrol Features -% -% Special Measure Pulsecontrol can be used to: -% -% * create pulses for Arbitrary Waveform Generator (AWG) instruments on -% different levels of sophistication. -% * mange pulses as groups in a sequence on one or several AWGs at once. -% * mirror AWG states inside MATLAB(R), adjust global offset and other -% calibration parameters for optimized pulse generation. -% * backup AWG content changes for automatic documentation purposes. -% * mange pulses in a global pulse database (|plsdata|). -% * examin pulses from |plsdata| graphically before upload to an AWG. -% -% Copyright 2009 Not The MathWorks, Inc. \ No newline at end of file diff --git a/doc/pulsecontrol_functions_by_cat.html b/doc/pulsecontrol_functions_by_cat.html deleted file mode 100644 index e634648..0000000 --- a/doc/pulsecontrol_functions_by_cat.html +++ /dev/null @@ -1,425 +0,0 @@ - - - - - Functions by Category

    Functions by Category

    Special Measure Pulsecontrol

    Requires Instrument Control Toolbox™.

    Contents

    AWG Control

    awgadd(groups)

    Uploads one or more pulsegroups to the connected AWG.

    -
    -

    awgclear(groups,options)

    Used to delete pulses from the AWG or clear them up.

    -
    -

    val = awgcntrl(cntrl, chans))

    Control the AWG to start/stop, etc.

    -
    -

    data = awggetdata(time)

    Load latest awgdata.

    -
    -

    awggroups(ind)

    Prints a list of the pulsegroups in the awgdata struct.

    -
    -

    grp = awggrpind(grp)

    Find group index in awgdata.pulsegroups based on provided group name.

    -
    -

    awglist(ind,awg)

    List the waveforms present on an AWG.

    -
    -

    zerolen = awgload(grp, ind)

    Transmit pulses from group to AWG.

    -
    -

    awgloaddata()

    Load latest awgdata file saved by awgsavedata().

    -
    -

    awgnpulse(groups, npulse)

    Set npulse for pulsegroups.

    -
    -

    awgrm(grp, ctrl)

    Used to delete pulses from the AWG.

    -
    -

    awgsavedata()

    Save awgdata in plsdata.grpdir, with name generated from date and time.

    -
    -

    seqind = awgseqind(pulses,rep)

    Find the pulse line associated with a pulse group or pulse index.

    -
    -

    awgswap(name)

    Swap the current active AWG with an alternative.

    -
    -

    awgsyncwaveforms()

    Make sure the list of pulses is awgdata is consistent with the AWG.

    -
    -

    awgupdate(groups)

    Obsolete!

    -
    -

    waveforms = awgwaveforms(group,awg,opts)

    Give a list of waveforms that are known to be loaded.

    -
    -

    zerolen = awgzero(grp, ind, zerolen)

    Helper function to determine if a pulse is zero.

    -
    -

    Pulse Databse Management

    pulse = plsdefault(pulse)

    Enshures correct pulse-formating and updates the format-string pulse.format depending on the content of pulse.data.

    -
    -

    inds = plslist(rng, name)

    Tabular representation of specified pulses.

    -
    -

    plsplot(pulse, dict, ctrl)

    Plots pulse using plstowf() as time vs. channelA and channelB, channelA vs. channelB, time vs. markerA and markerB.

    -
    -

    plsreadxval()

    Extracts xval from pulses inside plsdata.pulses database and collecting them in plsdata.xval.

    -
    -

    plsnum = plsreg(pulse, plsnum)

    Adds pulse to plsdata.pulses database, while enshuring correct elements and correct order of elements.

    -
    -

    plssync(ctrl)

    Used to save and load pulse databese plsdata, while enshuring correct path-handling.

    -
    -

    pulse = plstotab(pulse)

    Downconverts pulses of format 'elem' to format 'tab'.

    -
    -

    [I Q] = plsIqFnCreate(coeff, ...)

    Auxiliary function for plstotab(), to process pulse elements of type 'rfpulse'.

    -
    -

    pulse = plstowf(pulse, dict)

    Downconverts pulses of format 'tab' to format 'wf'.

    -
    -

    plsdefgrp(grpdef)

    Enshures correct group-formating. Saves a pulse group to the path specified in plsdata.grpdir.

    -
    -

    plslint(pg)

    Some preliminary checks for a pulsegroups.

    -
    -

    grpdef = plsmakegrp(name, ctrl, ind, opts)

    Processes pulse group to 'wf' format and saves to disk. Used by awgadd(). Can be controlled by a string ctrl containing swiches:

    • 'plot': plot pulse group
    • 'check': check if any voltage value is out of awgdata.scale bounds
    • 'upload': uploads pulse group to the AWG

    Changes to the pulsegroup are logged inside plslog (stored with pulse).

    -
    -

    plsupdate(newdef)

    Updates the fields of a pulsegroup. The fields name, chan, markmap, offset, pulseind, pulses cannot be updated. Formatting of the other fields must remain the same.

    -
    -

    val = plsinfo(ctrl, group, ind, time)

    Used to retrieve fileds of a pulsegroup at a given time or check if the provided group is stale ( previously not existing, old timestamp, ...). Variable at a given time can be reclaimed using the ctrl strings:

    • 'ro': returns readout
    • 'zl': returns zerolen
    • 'gd': returns grpdef
    • 'sl': returns seqlog

    Ctrl strings not honoring time:

    • 'ls': list/returns pulsegroup(s) of provided name(mask) in grpdir. E.g. 'dBz*'
    • 'params': returns params
    • 'pl' or 'log': returns plslog
    • 'rev': prints avalible revisions of the pulse in plslog
    • 'stale': returns, whether the pulse is stale or not
    \ No newline at end of file diff --git a/doc/pulsecontrol_functions_by_cat.m b/doc/pulsecontrol_functions_by_cat.m deleted file mode 100644 index 68b630b..0000000 --- a/doc/pulsecontrol_functions_by_cat.m +++ /dev/null @@ -1,293 +0,0 @@ -%% Functions by Category -% Special Measure Pulsecontrol -% -% Requires Instrument Control Toolbox(TM). -% -%% AWG Control -% -% -% Uploads one or more pulsegroups to the connected AWG. -% -% -%
    -% -% -% -% -% Used to delete pulses from the AWG or clear them up. -% -% -%
    -% -% -% -% -% Control the AWG to start/stop, etc. -% -% -%
    -% -% -% -% -% Load latest awgdata. -% -% -%
    -% -% -% -% -% Prints a list of the pulsegroups in the awgdata struct. -% -% -%
    -% -% -% -% -% Find group index in awgdata.pulsegroups based on provided group name. -% -% -%
    -% -% -% -% -% List the waveforms present on an AWG. -% -% -%
    -% -% -% -% -% Transmit pulses from group to AWG. -% -% -%
    -% -% -% -% -% Load latest awgdata file saved by awgsavedata(). -% -% -%
    -% -% -% -% -% Set npulse for pulsegroups. -% -% -%
    -% -% -% -% -% Used to delete pulses from the AWG. -% -% -%
    -% -% -% -% -% Save awgdata in plsdata.grpdir, with name generated from date and time. -% -% -%
    -% -% -% -% -% Find the pulse line associated with a pulse group or pulse index. -% -% -%
    -% -% -% -% -% Swap the current active AWG with an alternative. -% -% -%
    -% -% -% -% -% Make sure the list of pulses is awgdata is consistent with the AWG. -% -% -%
    -% -% -% -% -% Obsolete! -% -% -%
    -% -% -% -% -% Give a list of waveforms that are known to be loaded. -% -% -%
    -% -% -% -% -% Helper function to determine if a pulse is zero. -% -% -%
    -% -% -%% Pulse Databse Management -% -% -% -% Enshures correct pulse-formating and updates the format-string pulse.format depending on -% the content of pulse.data. -% -% -%
    -% -% -% -% -% Tabular representation of specified pulses. -% -% -%
    -% -% -% -% -% Plots pulse using plstowf() as time vs. channelA and channelB, channelA -% vs. channelB, time vs. markerA and markerB. -% -% -%
    -% -% -% -% -% Extracts xval from pulses inside plsdata.pulses database and collecting -% them in plsdata.xval. -% -% -%
    -% -% -% -% -% Adds pulse to plsdata.pulses database, while enshuring correct elements -% and correct order of elements. -% -% -%
    -% -% -% -% -% Used to save and load pulse databese plsdata, while enshuring correct -% path-handling. -% -% -%
    -% -% -% -% -% Downconverts pulses of format 'elem' to format 'tab'. -% -% -%
    -% -% -% -% -% Auxiliary function for plstotab(), to process pulse elements of type -% 'rfpulse'. -% -% -%
    -% -% -% -% -% Downconverts pulses of format 'tab' to format 'wf'. -% -% -%
    -% -% -% -% -% Enshures correct group-formating. Saves a pulse group to the path -% specified in plsdata.grpdir. -% -% -%
    -% -% -% -% -% Some preliminary checks for a pulsegroups. -% -% -%
    -% -% -% -% -% Processes pulse group to 'wf' format and saves to disk. Used by -% awgadd(). Can be controlled by a string ctrl containing swiches: -% -% * 'plot': plot pulse group -% * 'check': check if any voltage value is out of awgdata.scale bounds -% * 'upload': uploads pulse group to the AWG -% -% Changes to the pulsegroup are logged inside plslog (stored with pulse). -% -% -%
    -% -% -% -% -% Updates the fields of a pulsegroup. The fields name, chan, markmap, offset, pulseind, -% pulses cannot be updated. Formatting of the other fields must remain the -% same. -% -% -%
    -% -% -% -% -% Used to retrieve fileds of a pulsegroup at a given time or -% check if the provided group is stale ( previously not existing, old -% timestamp, ...). -% Variable at a given time can be reclaimed using the ctrl strings: -% -% * 'ro': returns readout -% * 'zl': returns zerolen -% * 'gd': returns grpdef -% * 'sl': returns seqlog -% -% Ctrl strings not honoring time: -% -% * 'ls': list/returns pulsegroup(s) of provided name(mask) in grpdir. E.g. 'dBz*' -% * 'params': returns params -% * 'pl' or 'log': returns plslog -% * 'rev': prints avalible revisions of the pulse in plslog -% * 'stale': returns, whether the pulse is stale or not -% -% Copyright 2007-2009 Not The MathWorks, Inc. diff --git a/doc/pulsecontrol_getting_started.html b/doc/pulsecontrol_getting_started.html deleted file mode 100644 index 7cd5002..0000000 --- a/doc/pulsecontrol_getting_started.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - Special Measure Pulsecontrol: Getting Started

    Special Measure Pulsecontrol: Getting Started

    Contents

    What Is Pulsecontrol?

    Pulsecontrol is a script bundle with the aim to standardise the operation of Arbitrary Waveform Generators (AWGs) in a MATLAB® context. Pulses for AWGs can be created using a flexible syntax. They are stored in a global database (plsdata). Groups of pulses from plsdata can be transfered to an AWG for execution in sequence.

    An AWG is represented in the data structure awgdata. Changes to pulses loaded to an AWG are documented, by autosaving the current AWG state.

    What Is A Pulse?

    An AWG runs so called waveforms. Waveforms consist of a number of values stepped through with a certain clock rate.

    The values can range from -1 to 1 and are mapped according to the maximum peak-to-peak amplitude on the device to a voltage:

    - - - - -
    voltagenormalized value
    offset - amplitude(p-p)/2-1
    offset + amplitude(p-p)/2+1
    -

    Exceding values are clipped.

    Besides the analog part of the data, a waveform inclueds two digital marker parts, which can be set for every clock individually to 'on' or 'off' on additional output channels. This is useful for triggering other instruments based on the index position within the waveform. More Information on the 14-bit integer encoding used for transmission and storage of pulses on the AWG can be found in awgload.

    Waveforms are produced by a more general data type in Pulsecontrol called a pulse.

    In Pulsecontrol a pulse can be created in several ways defined by the following formats:

    • 'wf' is the most basic format. The pulse is described via data values for every clock of the AWG. This is the format transmitted to the instrument and corresponds to a waveform.
    • 'tab' defines pulses in a tabular manner. Data values are defined at distinct times of the pulse. The intermediate values are ramped.
    • 'elem' defines pulses out of several pulse elements. A pulse element describes distinct shapes controlled by parameters, e.g. 'wait' will stay at at a specific voltage for a given time.

    The format of a pulse is determinded by the string pinf.format where pinf is a data structure defining the pulse.

    Pulses of the format 'elem' and 'tab' are converted into 'wf' when transmitted to the AWG.

    What Setup Do I Need?

    To use Pulsecontrol the AWG must be connected and opened for communication via the Instrument Control Toolbox™. Two global data structures plsdata and awgdata must be present in the MATLAB® workspace. awgdata contains the instrument object, a representation of uploaded pulses and other important poroperties. plsdata contains pulsedefenitions and defines paths where to store groups of pulses, and backup awgdata. The next section describes the creation and upload of a pulse plus the necessery setup.

    Example: Creation Of A Simple Pulse Using Example plsdata, awgdata

    The example data structures plsdata and awgdata can be created by executing the scripts awgsetup.m and plssetup.m Copy them to your own location, for modification purposes.

    plssetup
    -awgsetup
    -

    They define necessary properties in the structures and create a folder structure to store pulses and AWG states for Pulsecontrol. Now a pulse can be defined.

    clear pinf;
    -pinf.name = 'pulse1';
    -pinf.data.pulsetab = [0 1; 0.1 0.3; 0.2 0.4];
    -

    The above code creates a pulse of the format 'tab' with the name 'pulse1'. The format is specified automatically depending on the content of pinf.data. In this case a pulsetab array creates a 'tab' pulse. The first row in the array defines the time. While the subsequent two rows define values at this times. Since start and end value are not equal, the intermediate values are linear interpolated. The length of the rows is arbitrary.

    The atomar unit of time is 1ns. A scaling factor plsdata.tbase is used when evaluating time in Pulsecontrol. The default value is 1000. Thus pinf is a pulse of 1ns* plsdata.tbase *1 = 1us, which reaches 1 starting from 0 in 1200 steps for a clock speed of 1.2GHz of the connected AWG. The used speed can be found in awgdata.clk.

    - - - - -
    timefirst chan.second chan.
    00.10.3
    10.20.4
    -

    Since the values have an arbitrary unit and the resulting voltage is dependent on the actual settings of the AWG peak-to-peak voltage, it is commune to normalize the values to the unit 'mV' using awgdata.scale and when requiered awgdata.offset.

    The created pulse can be plotted using the function plsplot().

    plsplot(pinf);
    -

    pinf is then added into the plsdata.pulses database at the index plsnum.

    plsnum = 1;
    -plsreg(pinf, plsnum);
    -

    Next a pulse group needs to be defined. Only groups can be uploaded to the AWG. In this case the group consist of only one pulse.

    clear pg;
    -pg.name='pulse1_loop';
    -pg.ctrl = 'notrig'; % no trigger signal
    -pg.pulses = plsnum; % index of pulse1
    -pg.nrep = Inf;
    -pg.chan = [1 2];
    -

    Pulse 'pulse1' is selected by the index plsnum of the database. For several pulses an 1xm index array can be specified in pg.pulses, then the pulses are concatenated together.

    pg.nrep can be used to specify the number of repetitions of the group. pg.chan defines which physical channels are used to output the pulse. More advanced options are possible. Further information can be found in the User Guide.

    The function plsdefgrp() is used to save the group as a mat-file to disk inside plsdata.grpdir with the name 'gr_pulse1_loop'.

    plsdefgrp(pg);
    -

    All supported features of a group can be found in the comment of plsdefgrp.

    Finally the group is added to end of the sequence of the connected AWG with:

    awgadd('pulse1_loop')
    -
    \ No newline at end of file diff --git a/doc/pulsecontrol_getting_started.m b/doc/pulsecontrol_getting_started.m deleted file mode 100644 index a409234..0000000 --- a/doc/pulsecontrol_getting_started.m +++ /dev/null @@ -1,163 +0,0 @@ -%% Special Measure Pulsecontrol: Getting Started -% -%% What Is Pulsecontrol? -% Pulsecontrol is a script bundle with the aim to standardise the operation -% of Arbitrary Waveform Generators (AWGs) in a MATLAB(R) context. Pulses -% for AWGs can be created using a flexible syntax. They are stored in a global -% database (|plsdata|). Groups of pulses from |plsdata| can be transfered -% to an AWG for execution in sequence. -% -% An AWG is represented in the data structure |awgdata|. Changes to pulses -% loaded to an AWG are documented, by autosaving the current AWG state. - -%% What Is A Pulse? -% An AWG runs so called waveforms. Waveforms consist of a number of -% values stepped through with a certain clock rate. -% -% The values can range from -1 to 1 and are mapped according to the maximum -% peak-to-peak amplitude on the device to a voltage: -% -% -% -% -% -% -%
    voltagenormalized value
    offset - amplitude(p-p)/2-1
    offset + amplitude(p-p)/2+1
    -% -% -% Exceding values are clipped. -% -% Besides the analog part of the data, a waveform inclueds two digital marker -% parts, which can be set for every clock individually to 'on' or 'off' on -% additional output channels. -% This is useful for triggering other instruments based on the index -% position within the waveform. -% More Information on the 14-bit integer encoding used for -% transmission and storage of pulses on the AWG can be found in -% -% -% Waveforms are produced by a more general data type in Pulsecontrol called -% a pulse. -% -% In Pulsecontrol a pulse can be created in several ways defined by the -% following formats: -% -% * |'wf'| is the most basic format. The pulse is described via -% data values for every clock of the AWG. This is the format transmitted to -% the instrument and corresponds to a waveform. -% * |'tab'| defines pulses in a tabular manner. Data values are defined at -% distinct times of the pulse. The intermediate values are ramped. -% * |'elem'| defines pulses out of several pulse elements. A pulse element -% describes distinct shapes controlled by parameters, e.g. 'wait' will stay -% at at a specific voltage for a given time. -% -% The format of a pulse is determinded by the string |pinf.format| where -% |pinf| is a data structure defining the pulse. -% -% Pulses of the format |'elem'| and |'tab'| are converted into |'wf'| when -% transmitted to the AWG. -% -%% What Setup Do I Need? -% To use Pulsecontrol the AWG must be connected and opened for -% communication via the Instrument Control Toolbox(TM). Two global data -% structures |plsdata| and |awgdata| must be present in the MATLAB(R) workspace. -% |awgdata| contains the instrument object, a representation of uploaded -% pulses and other important poroperties. |plsdata| contains -% pulsedefenitions and defines paths where to store groups of pulses, and -% backup |awgdata|. The next section -% describes the creation and upload of a pulse plus the necessery setup. -% -%% Example: Creation Of A Simple Pulse Using Example |plsdata|, |awgdata| -% The example data structures |plsdata| and |awgdata| can be created by -% executing the scripts and -% -% Copy them to your own location, for modification purposes. - -plssetup -awgsetup - - -%% -% They define necessary properties in the structures and create a folder -% structure to store pulses and AWG states for Pulsecontrol. Now a pulse -% can be defined. - -clear pinf; -pinf.name = 'pulse1'; -pinf.data.pulsetab = [0 1; 0.1 0.3; 0.2 0.4]; - -%% -% The above code creates a pulse of the format |'tab'| with the name -% 'pulse1'. The format is specified automatically depending on the content of -% |pinf.data|. In this case a |pulsetab| array creates a |'tab'| pulse. The -% first row in the array defines the time. While the subsequent two rows -% define values at this times. Since start and end value are not -% equal, the intermediate values are linear interpolated. The length of the -% rows is arbitrary. -% -% The atomar unit of time is 1ns. A scaling factor |plsdata.tbase| is used -% when evaluating time in Pulsecontrol. The default -% value is 1000. Thus |pinf| is a pulse of 1ns* |plsdata.tbase| *1 = 1us, -% which reaches 1 starting from 0 in 1200 steps for a clock speed of 1.2GHz -% of the connected AWG. The used speed can be found in awgdata.clk. -% -% -% -% -% -% -%
    timefirst chan.second chan.
    00.10.3
    10.20.4
    -% -% -% Since the values have an arbitrary unit and the resulting voltage is -% dependent on the actual settings of the AWG peak-to-peak voltage, it is -% commune to normalize the values to the unit 'mV' using awgdata.scale and -% when requiered awgdata.offset. -% -% The created pulse can be plotted using the function |plsplot()|. - -plsplot(pinf); - -%% -% |pinf| is then added into the |plsdata.pulses| database at the index |plsnum|. - -plsnum = 1; -plsreg(pinf, plsnum); - -%% -% Next a pulse group needs to be defined. Only groups can be -% uploaded to the AWG. In this case the group consist of only one pulse. - -clear pg; -pg.name='pulse1_loop'; -pg.ctrl = 'notrig'; % no trigger signal -pg.pulses = plsnum; % index of pulse1 -pg.nrep = Inf; -pg.chan = [1 2]; - -%% -% Pulse 'pulse1' is selected by the index |plsnum| of the database. For several -% pulses an 1xm index array can be specified in |pg.pulses|, then the pulses are -% concatenated together. -% -% |pg.nrep| can be used to specify the number of repetitions of the group. -% |pg.chan| defines which physical channels are used to output the pulse. -% More advanced options are possible. Further information can be found in the -% . -% -% The function |plsdefgrp()| is used to save the group as a mat-file to -% disk inside |plsdata.grpdir| with the name 'gr_pulse1_loop'. - -plsdefgrp(pg); - -%% -% All supported features of a group can be found in the comment of -% -% -% Finally the group is added to end of the sequence of the connected AWG -% with: - -awgadd('pulse1_loop') - -%% -% Copyright 2009 Not The MathWorks, Inc. diff --git a/doc/pulsecontrol_how_to_doc.html b/doc/pulsecontrol_how_to_doc.html deleted file mode 100644 index f849d7a..0000000 --- a/doc/pulsecontrol_how_to_doc.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - How to extend and modify this documentation using publish()

    How to extend and modify this documentation using publish()

    This documentation follows MATLAB® guidelines defined in Custom Documentation.

    Contents

    Short summary of the documentation structure

    • The content uses html-markup for styling
    • The topics are seperated into m-files, which are parsed with publish() to generate html-files
    • The html-files are linked together in the html/..._product_page.html
    • html/helptoc.xml provides links to main topics in the documentation browser tree-view
    • info.xml directs MATLAB® to the html/ folder containing all documentation files

    The m-files of the existing topics are provided as guidiance to write new topics. Existing spelling mistakes or contentual errors should also be corrected inside the m-files. The files must then be re-published as discussed below.

    How to publish an m-file

    The publish() function is used with the following options:

    opts = struct('outputDir', fileparts(which('pulsecontrol_features.m')),
    -			  'evalCode', false,
    -			  'stylesheet', [fileparts(which('pulsecontrol_features.m')) '/mxdom2simplehtml.xsl']);
    -
    -publish('pulsecontrol_how_to_doc.m', opts);
    -

    This would re-publish the document you are reading right now, if the current folder is the html/ directory containing this documentation.

    The opts structure contains important options for the publish function. A commun stylesheet mxdom2simplehtml.xsl is used throughout all created html-files to enshure a homogenous layout.

    \ No newline at end of file diff --git a/doc/pulsecontrol_how_to_doc.m b/doc/pulsecontrol_how_to_doc.m deleted file mode 100644 index 1ae7a81..0000000 --- a/doc/pulsecontrol_how_to_doc.m +++ /dev/null @@ -1,30 +0,0 @@ -%% How to extend and modify this documentation using |publish()| -% This documentation follows MATLAB(R) guidelines defined in -% . -% -%% Short summary of the documentation structure -% * The content uses for styling -% * The topics are seperated into m-files, which are parsed with |publish()| to generate html-files -% * The html-files are linked together in the html/..._product_page.html -% * html/helptoc.xml provides links to main topics in the documentation browser tree-view -% * info.xml directs MATLAB(R) to the html/ folder containing all documentation files -% -% The m-files of the existing topics are provided as guidiance to write new topics. -% Existing spelling mistakes or contentual errors should also be corrected inside the m-files. -% The files must then be re-published as discussed below. -% -%% How to publish an m-file -% The |publish()| function is used with the following options: -% -opts = struct('outputDir', fileparts(which('pulsecontrol_features.m')),... - 'evalCode', false,... - 'stylesheet', [fileparts(which('pulsecontrol_features.m')) '/mxdom2simplehtml.xsl']); - -publish('pulsecontrol_how_to_doc.m', opts); - -%% -% This would re-publish the document you are reading right now, -% if the current folder is the html/ directory containing this documentation. -%% -% The opts structure contains important options for the publish function. -% A commun stylesheet mxdom2simplehtml.xsl is used throughout all created html-files to enshure a homogenous layout throughout MATLAB(R) versions. \ No newline at end of file diff --git a/doc/pulsecontrol_product_page.html b/doc/pulsecontrol_product_page.html deleted file mode 100644 index cdf2c14..0000000 --- a/doc/pulsecontrol_product_page.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - Special Measure Pulsecontrol

    Special Measure Pulsecontrol

    Available Documentation

    Also see the pulsecontrol repository on the Google Code Special Measure page.

    \ No newline at end of file diff --git a/doc/pulsecontrol_product_page.m b/doc/pulsecontrol_product_page.m deleted file mode 100644 index 458a91d..0000000 --- a/doc/pulsecontrol_product_page.m +++ /dev/null @@ -1,16 +0,0 @@ -%% Special Measure Pulsecontrol -% -% *Available Documentation* -% -% * -% * -% * -% * -% * -% * -% * -% -% Also see the on the Google Code -% -% -% Copyright 2009 Not The MathWorks, Inc. diff --git a/doc/pulsecontrol_recipes.html b/doc/pulsecontrol_recipes.html deleted file mode 100644 index 3f316bf..0000000 --- a/doc/pulsecontrol_recipes.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - Recipes

    Recipes

    This is a selection of common code-snippets related to pulsecontrol.

    Contents

    Tomato soup

    • Some fresh tomatos
    • Hot Water
    • Tomato Pasta
    • salt, pepper
    • chopped, fryied onions
    \ No newline at end of file diff --git a/doc/pulsecontrol_recipes.m b/doc/pulsecontrol_recipes.m deleted file mode 100644 index 81c0007..0000000 --- a/doc/pulsecontrol_recipes.m +++ /dev/null @@ -1,11 +0,0 @@ -%% Recipes -% -% This is a selection of common code-snippets related to pulsecontrol. -% -%% Tomato soup -% -% * Some fresh tomatos -% * Hot Water -% * Tomato Pasta -% * salt, pepper -% * chopped, fryied onions \ No newline at end of file diff --git a/doc/pulsecontrol_system_requirements.html b/doc/pulsecontrol_system_requirements.html deleted file mode 100644 index c4d096c..0000000 --- a/doc/pulsecontrol_system_requirements.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - Special Measure Pulsecontrol System Requirements

    Special Measure Pulsecontrol System Requirements

    Special Measure Pulsecontrol requires MATLAB® and Instrument Control Toolbox™.

    Special Measure is recommended for control of the instrument itself.

    \ No newline at end of file diff --git a/doc/pulsecontrol_system_requirements.m b/doc/pulsecontrol_system_requirements.m deleted file mode 100644 index 2933aa5..0000000 --- a/doc/pulsecontrol_system_requirements.m +++ /dev/null @@ -1,7 +0,0 @@ -%% Special Measure Pulsecontrol System Requirements -% Special Measure Pulsecontrol requires MATLAB(R) and Instrument Control Toolbox(TM). -% -% is -% recommended for control of the instrument itself. -% -% Copyright 2009 Not The MathWorks, Inc. diff --git a/doc/pulsecontrol_troubleshooting.html b/doc/pulsecontrol_troubleshooting.html deleted file mode 100644 index b12da90..0000000 --- a/doc/pulsecontrol_troubleshooting.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - Troubleshooting

    Troubleshooting

    no trouble if used correctly.

    \ No newline at end of file diff --git a/doc/pulsecontrol_troubleshooting.m b/doc/pulsecontrol_troubleshooting.m deleted file mode 100644 index b289ea7..0000000 --- a/doc/pulsecontrol_troubleshooting.m +++ /dev/null @@ -1,3 +0,0 @@ -%% Troubleshooting -% -% no trouble if used correctly. \ No newline at end of file diff --git a/doc/pulsecontrol_user_guide.html b/doc/pulsecontrol_user_guide.html deleted file mode 100644 index 468271e..0000000 --- a/doc/pulsecontrol_user_guide.html +++ /dev/null @@ -1,664 +0,0 @@ - - - - - Special Measure Pulsecontrol User Guide

    Special Measure Pulsecontrol User Guide

    Contents

    General Structure

    The figure below depicts the general structure of the pulsecontrol package. The functionality is divided up into two main parts. This is indicated with a common prefix awg* or pls*.

    The namespace awg* contains functions that are directly associeted with the transfer of pulses to the physical instrument. Not all functions are intended for direct user interaction. The common used functions are awgadd(), awglist() and awgcntrl().

    The namespace pls* contains functions associeted with the management, grouping and conversion of pulses into different formats. This part of pulsecontrol is not instrument specific. The common used functions are plsreg(), plsdefgrp(), plsplot() and plslist().

    awgdata Structure

    awgdata is a global struct, which makes the instrument object, uploaded pulses, pulsegroups and other important properties availible in a central place. It is a virtual representation of the current state of the connected AWG(s).

    See the comments of the example file for how to setup the awgdata struct: awgsetup example

    Every time the functions awgadd() or awgrm() are called, the updated awgdata is saved inside the director provided in plsdata.grpdir. The format is:

    awgdata_yyMMdd_hhmm

    This provides a history of the awgstate and a backup method. The functions to save and load the latest awgdata manually are awgloaddata() and awgsavedata(). An awgdata struct at a specific time can be accessed with awggetdata(time) with time a serial date number.

    plsdata Structure

    plsdata is a global struct, which contains pulse definitions in a database, provides paths where to store groups of pulses and backup awgdata. Additionally, plsdata.tbase is a common factor to control the unit of time for the whole package.

    See the comments of the example file for how to setup the plsdata struct: plssetup example

    plssync(ctrl) with the ctrl string 'save' or 'load' is used to save or load plsdata from/to the location in plsdata.datafile.

    The pulse types wf, tab, elem

    'wf'

    'wf' is the most basic format. The pulse is described via data values for every clock of the AWG. This is the format transmitted to the instrument and corresponds to a waveform.

    Below is an example of this type:

    clear wf_p;
    -wf_p.name = 'myname1';
    -wf_p.data.wf = ones(2,1200) .* .5;
    -wf_p.data.marker = zeros(2,1200, 'uint8');
    -wf_p.data.clk = 1.2e9;
    -

    wf_p.data contains the raw waveform information. wf and marker contain an entry per clock (here 1200) per channel (here 2). clk is the number of clocks per second. The above pulse will generate a 1 us long pulse of 0.5 and no markers for a clock frequency of 1.2GHz on two channels.

    wf_p is not in the correct format yet. A call of plsdefault() generates additional fields and set the correct wf_p.format string. This function is called automatically when converting from one pulse to another.

    wf_p = plsdefault(wf_p);
    -

    This is equivalent of setting the following fields manually.

    wf_p.format = 'wf';
    -wf_p.taurc = Inf;
    -wf_p.xval = [];
    -wf_p.pardef = [];
    -wf_p.trafofn = [];
    -

    See the following chapter for how to use trafofn, pardef and taurc. The field xval is used by an external data evaluation package dataview to define the scaling of the x-axis. Look into dataview for more information about xval.

    As seen in this example it is enough to define the wf_p.data part of the pulse. The other fields are not necessary to define manually and are generated automatically. They are shown to give an overview of the complete pulse in this and the following example pulses.

    -
    -
    -

    'tab'

    'tab' is used for time-value pairs in a tabular format.

    Below is an example of this pulse type:

    clear tb_p;
    -tb_p.name = 'myname2';
    -tb_p.data.pulsetab = [0 1; 0 0.3; 0.0 0.4];
    -

    The first row in the pulsetab defines the time. While the subsequent two rows define values at this times for channel 1, channel 2, ... Since start and end value are not equal, the intermediate values are linear interpolated (from 0.0 to 0.3 on channel 1 and from 0.0 to 0.4 on channel 2). The length of the rows is arbitrary.

    The atomar unit of time is 1ns. A scaling factor plsdata.tbase is used when evaluating time in Pulsecontrol. The default value is 1000. Thus tb_p is a pulse of 1ns* plsdata.tbase *1 = 1us.

    When downconverted to the 'wf' format the voltage values are linear interpolated. For our example of a 1.2GHz clock frequency the voltage of of channel 1 would rise from 0.1 to 0.3 in 1200 stepps.

    It is also possible to set the field tb_p.data.pulsefn. This replaceses the default linear interpolation with a custume function handle, for arbitrary pulse shapes. Here is an example using two sinusoids:

    tb_p.data.pulsefn.t = [0, 1];
    -tb_p.data.pulsefn.fn = {@(x)sin(2*pi*x), @(x)0.5*sin(2*pi*x)};
    -

    After the pulse table is converted to the 'wf' format, the time segment specified in pulsefn.t is overwritten with pulsefn.fn evaluated at this points. pulsefn can be an array to define several custome funcitons to replace several parts of a pulse with arbitrary functions.

    Again tb_p is not in the correct format yet. A call of plsdefault() generates additional fields and set the correct tb_p.format string

    tb_p = plsdefault(tb_p);
    -

    This is equivalent of setting the following fields manually.

    tb_p.format = 'tab';
    -tb_p.taurc = Inf;
    -tb_p.xval = [];
    -tb_p.pardef = [];
    -tb_p.trafofn = [];
    -

    -
    -
    -

    'elem'

    'elem' is a flexible format to construct a pusle from prefedined building blocks. This pulse elements are defined in plstotab().

    Below is an example of this pulse type:

    clear pdstart pdwait pdramp;
    -
    -pdstart.type = 'raw';
    -pdstart.time = 0;
    -pdstart.val= [0; 0];
    -
    -pdramp.type = 'ramp';
    -pdramp.time = 1;
    -pdramp.val = [0.2, 0.3];
    -
    -pdwait.type = 'wait';
    -pdwait.time = 2;
    -pdwait.val = [1, 1];
    -
    -clear el_p;
    -
    -el_p.name = 'myname3';
    -el_p.data = [pdstart pdramp pdwait];
    -

    The pulse el_p uses three pulse elements pdstart, pdramp and pdwait to define a shape, which starts from zero than rises to 0.2 for channel 1 and 0.3 for channel 2 in 1us. It than jumps to 1.0 for both channels and stays there for 2us.

    Again el_p is not in the correct format yet. A call of plsdefault() generates additional fields and set the correct el_p.format string

    el_p = plsdefault(el_p);
    -

    This is equivalent of setting the following fields manually.

    el_p.format = 'elem';
    -el_p.taurc = Inf;
    -el_p.xval = [];
    -el_p.pardef = [];
    -el_p.trafofn = [];
    -

    Each element is defined by a type and two fields time and val. Dependind on the type the fields provide different parameters to define a pulse element. When downconverted to the 'tab' format correct pulse table entries are derived, to represent the shapes defined by this elements.

    -
    -
    -

    Here is an overview of the currently existing elements:

    raw insert [time; val] into pulse table.

    mark add time' to marktab

    fill stretch this element to make the total pulse duration equal to time. Idea for future development: allow several fills, each spreading a subset. Would need a second element to flush previous fill, could be fill without time.

    wait stay at val (row vector, one entry for each channel) for duration time. If val has 3 entries, third is a scaling factor for the first two.

    reload relaod pulse at val (row vector, one entry for each channel). time: [ramp time, wait time at load point, wait time at (0, 0) after load]

    meas measurement stage at [0, 0] for time(1), RF marker delayed by time(2) and off time(3) before end of the stage. [time(2) is lead delay, time(3) is negative tail delay. Optional val(1) is the readout tag. If it is given and not nan, time 4 and 5 set its delays with the sae convention as for the marker. Optional val(2,3) moves the measurement point away from 0,0. Makes meas_o obsolete.

    meas_o as meas, but measure at current voltages, not 0,0.

    ramp ramp to val (row vector, one entry for each channel) in time. opt val(3) is multiplier

    comp measurement compensation at val(1:2) (one for each channel) for duration time(1). Ramps voltage to target and back over time(2) and time(3) at the beginning and end of the stage, respectively. If length(val)>=4, val(3:4) are used as final value. The compensation value could be determined automatically, but this feature is not implemented yet.

    adprep adiabatic ramp along second diagonal (epsilon) from val(1) to val(2), ramp duration time.

    adread same, going the other way.

    rfpulse RF pulse using an IQ mixer with a mixer calibration present in caldata. Signals for I and Q are generated to produce a sinusoidal controllable in amplitude, phase and frequency. val(1:5)=[freqStart, freqStop, freqLO, amplitude, phase]. Using different values for freqStart and freqStop produces a chirp. val(6:7)=[calnumber, runind] are optional and are used as indices in caldata for a specific calibration, e.g. for multiple mixers. Otherwise the calibration with the name 'iqmx' is used. A starting element can be produced by setting the time to 0. Note: Vp amplitude expected (peak-to-zero amplitude). Specific: Tektroniks AWG expects Vpp on default, so Vpp=2*Vp is used inside rfpulse.

    • how to use dict related functionality

    -
    -
    -

    The figure below is a summary of the discussed pulse formats. It also shows how the pulse information is kept when converting from 'elem' to 'tab' to 'wf'. The original fields are not deletet. They are rather left in the pulse when it is converted. The data array of 'elem' for example is moved into the data.elem field when converting to 'tab'.

    trafofn, pardef, varpar, params, taurc

    implimentation of trafofn, pardef, ... plsmakegrp()

    basic documentation of trafofn, pardef, ... plsdefgrp()

    • pardef working on individual pulse (n x 2)-array: (i,1) pulse element to target, (i,2) if neg. time index/ if pos. val index
    • create variations of pulse based on varpar/parm in one group
    • replacing targeted value generated by trafofn(params(i)) or if trafofn empty params directly
    • works for 'elem' and 'tab'
    • different purpose of trafofn in group struct opposed to pulse struct: transform all final wf-values based on custome function + args
    • taurc is for compensation of physical setup: combination of dc + rf signal from AWG channel, see picture

    This section describes the process of parametrisation for 'elem' or 'tab' type pulses. This are currently the only types of pulses to support this feature.

    Using parametrisation enables the creation of a series of pulses, where an arbitrary parameters is varied.

    Markers

    Besides the analog part of the data, a waveform inclueds two digital marker parts, which can be set for every clock individually to 'on' or 'off' on additional output channels. This is useful for triggering other instruments based on the index position within the waveform.

    The creation of markers is supported on all levels of the pulse format:

    In an 'wf' the marker is set using the data.marker field. The field has to be the same length as the data.wf field. It must contain 0-3 of the type unsigned char. Zero if no marker should be set. One for the first marker on channel one two for the second and three for both markers. For example:

    wf_p.data.marker = zeros(2,1200, 'uint8');
    -wf_p.data.marker(1,1:120) = 1;
    -

    This generates a short marker pulse on the first channel for one tenth of the total pulselength of 1200 for the first marker.

    It is more convinient to use data.marktab when creating a 'tab' pulse. Analogous to the pulsetab the marktab contains the start times in the first rows. The subsequent rows contain the widths of the marker pulses. For two channels one two marker per channel this follows the schema:

    [time; channel1_marker1_duration; channel1_marker2_duration; channel2_marker1_duration; channel2_marker2_duration;]

    Here is an example:

    tb_p.data.marktab = [ 2 ; 1 ; 0 ; 0 ; 1 ];
    -

    This fires marker1 of channel one and marker2 of channel two for 1us starting at 2us.

    When using 'elem' the pulse element 'mark' is used to write to the marktab.

    An advanced feature is the routing of marker channels. It is for example possible to define markers for one channel and have them executed in parallel on additional channels. Or exchange the markers of several channels. This is realised using the markmap field in a pulse group. Consult the section 'groups' below for group specific questions.

    Note that you can only route both markers of a channel at the same time. This is not a problem when using just one marker per channel.

    The usage is defind in plsdefgrp() as follows:

    markmap = [marker_source_channel1 marker_source_channel2, ...; marker_output_channel1 marker_output_channel2, ...]

    An example would be to route the markers from channel one to all channels for four channels in total:

    group.markmap = [1 1 1 1; 1 2 3 4];
    -

    or to exchange the markers of the first two channels:

    group.markmap = [1 2 3 4; 2 1 3 4];
    -

    Groups

    Only groups can be uploaded to the AWG. They consist of a number of pulses in various forms, or just one pulse in the most simple case. The group struct is similar to the struct of the pulse itself and is depicted in the following figure. Refer to the comments in plsdefgrp() for a quick reference.

    The group pg with the name pg.name has a number of fields. The field pg.markmap is discussed in the above section about markers. It is used to map markers from different channels to different marker outputs. The field pg.xval is a merging of the xval fields of the individual pulses. It is used by an external data evaluation package dataview to define the scaling of the x-axis. Look into dataview for more information about xval.

    The fields pg.varpar and pg.params are used to parametrize certain aspects of an 'elem' or 'tab' pulse. Refer to the section above discussing it in connection with pardef and trafofn of the individual pulses inside the group.

    -
    -

    ctrl, nrep, jump, pulseind

    This fields control how the group behaves.

    • explain single fields with example

    -
    -

    chan, matrix, offset, trafofn

    This fields modify the group in several ways and set its output destination.

    • explain single fields with example

    -
    -

    dict

    This is a dictionary of predifined pulse elements used to create 'elem' pulses.

    • ask harvard nicely to write about it and provide example uses

    -
    -

    pulses

    This field contains information about the pulses used in the group.

    The above figure about the pulsegroup struct shows three possible cases of content of the pg.pulses field (starting from the bottom):

    1. The pulses are part of the database in plsdata.pulses and are referenced via their index, e.g., pg.pulses = [1, 2, 3] will use plsdata.pulses(1:3).
    2. An array of pulse structs is provided directly, without the step of retrieving the pulses from the database.
    3. Instead of single pulses use whole groups. This creates a group of groups in a recursive manner. To use this feature pg.pulses.groups has to be an array of already existing groups adressed by their group names, e.g., pg.pulses.groups = {'pg1', 'pg2'} will use pulse groups with the names 'pg1' and 'pg2', which were created with plsdefgrp() earlier to create a new group

    Cases 1 and 2 create groups of the type 'pls', since pulses are used. The third case creates groups of the type 'grp'. The type of group is specified inside the ctrl string pg.ctrl by plsdefgrp() automatically or it can be set manually when the group is defined.

    Besides pg.pulses.groups for type 'grp' the field pg.pulses.chan and pg.pulses.markchan exist to overwrite the output of the inner groups indiviually oposed to pg.chan and pg.markmap for all groups at once. The field pg.pulses.chan has as many rows as inner groups. The columns specify the output channels. The same is true for pg.pulses.markchan. If the output channel is set to 0 the specific output is ignored.

    For the example of using the two inner groups 'pg1' and 'pg2' as in case 3 above:

    pg.pulses.chan = [1 2; 2 0];
    -

    The inner group 'pg1' is routet to channel 1 und 2. The first channel of 'pg2' is routed to the physical channel 2 and the second channel of the group is omitted.

    Workflow

    At first a working configuration of pulsecontrol has to be enshured. Refer to the sections awgdata, plsdata above and the section "What Setup Do I Need?" in the Getting Started guide.

    A typical procedure how to use pulsecontrol is depicted in the figure below.

    1. at first the desiered pulses are defined
    2. the pulses can be plotted by plsplot() to enshure the correct shape visually
    3. using plsreg() the pulses are 'registered' in the plsdata database. Since the database is backad up on disk, this enshures consistency and safty
    4. a pulse group is defined using the pulses created in the first step. It is convinient to use the index of the pulse in the plsdata database. An alternative is to write the pulse struct(s) directly in the .pulses field of the group
    5. the function plsdefgrp() saves the group to disk inside the directory plsdata.grpdir. Only groups saved in this directory can be send to the AWG. This enshures that no information is lost.
    6. finally awgadd() is used to transmit the group or several groups to the AWG
    7. the function plsupdate is used to update some parameters of the group. The fields name, chan, markmap, offset, pulseind, pulses cannot be updated. The modified group can be retransmitted to the AWG to reflect the changes

    Refer to the section "Example: Creation Of A Simple Pulse..." in the Getting Started guide.

    • explain when to use plsinfo()
    • explain how to use plsmakegrp()
    • explain how pulse is documented in plslog and how to retrieve states

    Multiple AWGs

    • remains ToDo
    \ No newline at end of file diff --git a/doc/pulsecontrol_user_guide.m b/doc/pulsecontrol_user_guide.m deleted file mode 100644 index 9b802d8..0000000 --- a/doc/pulsecontrol_user_guide.m +++ /dev/null @@ -1,536 +0,0 @@ -%% Special Measure Pulsecontrol User Guide -% -% -%% General Structure -% The figure below depicts the general structure of the pulsecontrol -% package. The functionality is divided up into two main parts. This is -% indicated with a common prefix awg* or pls*. -% -% The namespace awg* contains -% functions that are directly associeted with the transfer of pulses to the -% physical instrument. Not all functions are intended for direct user -% interaction. The common used functions are awgadd(), awglist() and -% awgcntrl(). -% -% The namespace pls* contains functions associeted with the management, -% grouping and conversion of pulses into different formats. This part of -% pulsecontrol is not instrument specific. The common used functions are -% plsreg(), plsdefgrp(), plsplot() and plslist(). -% -% <> -% -%% awgdata Structure -% awgdata is a global struct, which makes the instrument object, uploaded -% pulses, pulsegroups and other important properties availible in a central -% place. It is a virtual representation of the current state of the -% connected AWG(s). -% -% See the comments of the example file for how to setup the awgdata struct: -% -% -% Every time the functions awgadd() or awgrm() are called, the updated -% awgdata is saved inside the director provided in plsdata.grpdir. The -% format is: -% -% |awgdata_yyMMdd_hhmm| -% -% This provides a history of the awgstate and a backup method. The -% functions to save and load the latest awgdata manually are awgloaddata() -% and awgsavedata(). An awgdata struct at a specific time can be accessed -% with awggetdata(time) with time a serial date number. -% -%% plsdata Structure -% plsdata is a global struct, which contains pulse definitions in a -% database, provides paths where to store groups of pulses and backup -% awgdata. Additionally, plsdata.tbase is a common factor to control the unit -% of time for the whole package. -% -% See the comments of the example file for how to setup the plsdata struct: -% -% -% plssync(ctrl) with the ctrl string 'save' or 'load' is used to save or -% load plsdata from/to the location in plsdata.datafile. -% -%% The pulse types wf, tab, elem -% -% *|'wf'|* -% -% |'wf'| is the most basic format. The pulse is described via data values -% for every clock of the AWG. This is the format transmitted to the -% instrument and corresponds to a waveform. -% -% Below is an example of this type: - -clear wf_p; -wf_p.name = 'myname1'; -wf_p.data.wf = ones(2,1200) .* .5; -wf_p.data.marker = zeros(2,1200, 'uint8'); -wf_p.data.clk = 1.2e9; - -%% -% wf_p.data contains the raw waveform information. wf and marker contain an -% entry per clock (here 1200) per channel (here 2). clk is the number of clocks per -% second. The above pulse will generate a 1 us long pulse of 0.5 and no markers -% for a clock frequency of 1.2GHz on two channels. -% -% wf_p is not in the correct format yet. A call of plsdefault() generates -% additional fields and set the correct wf_p.format string. This function -% is called automatically when converting from one pulse to another. - -wf_p = plsdefault(wf_p); - -%% -% This is equivalent of setting the following fields manually. - -wf_p.format = 'wf'; -wf_p.taurc = Inf; -wf_p.xval = []; -wf_p.pardef = []; -wf_p.trafofn = []; - -%% -% See the following chapter for how to use trafofn, pardef and taurc. The -% field xval is used by an external data evaluation package -% -% to define the scaling of the x-axis. Look into dataview for more -% information about xval. -% -% As seen in this example it is enough to define the wf_p.data part of the -% pulse. The other fields are not necessary to define manually and are generated -% automatically. They are shown to give an overview of the complete -% pulse in this and the following example pulses. -% -% -%
    -%
    -% -% -% *|'tab'|* -% -% |'tab'| is used for time-value pairs in a tabular format. -% -% Below is an example of this pulse type: - -clear tb_p; -tb_p.name = 'myname2'; -tb_p.data.pulsetab = [0 1; 0 0.3; 0.0 0.4]; - -%% -% The first row in the pulsetab defines the time. While the subsequent two -% rows define values at this times for channel 1, channel 2, ... -% Since start and end value are not -% equal, the intermediate values are linear interpolated (from 0.0 -% to 0.3 on channel 1 and from 0.0 to 0.4 on channel 2). The length of -% the rows is arbitrary. -% -% The atomar unit of time is 1ns. A scaling factor plsdata.tbase is used -% when evaluating time in Pulsecontrol. The default value is 1000. Thus -% tb_p is a pulse of 1ns* plsdata.tbase *1 = 1us. -% -% When downconverted to the -% |'wf'| format the voltage values are linear interpolated. For our example -% of a 1.2GHz clock frequency the voltage of of channel 1 would rise from -% 0.1 to 0.3 in 1200 stepps. -% -% It is also possible to set the field tb_p.data.pulsefn. This replaceses the -% default linear interpolation with a custume function handle, for -% arbitrary pulse shapes. Here is an example using two sinusoids: - -tb_p.data.pulsefn.t = [0, 1]; -tb_p.data.pulsefn.fn = {@(x)sin(2*pi*x), @(x)0.5*sin(2*pi*x)}; - -%% -% After the pulse table is converted to the |'wf'| format, the time segment -% specified in pulsefn.t is overwritten with pulsefn.fn evaluated at this -% points. pulsefn can be an array to define several custome funcitons to -% replace several parts of a pulse with arbitrary functions. -% -% Again tb_p is not in the correct format yet. A call of plsdefault() generates -% additional fields and set the correct tb_p.format string - -tb_p = plsdefault(tb_p); - -%% -% This is equivalent of setting the following fields manually. - -tb_p.format = 'tab'; -tb_p.taurc = Inf; -tb_p.xval = []; -tb_p.pardef = []; -tb_p.trafofn = []; - -%% -% -% -%
    -%
    -% -% -% *|'elem'|* -% -% |'elem'| is a flexible format to construct a pusle from prefedined -% building blocks. This pulse elements are defined in -% . -% -% Below is an example of this pulse type: - -clear pdstart pdwait pdramp; - -pdstart.type = 'raw'; -pdstart.time = 0; -pdstart.val= [0; 0]; - -pdramp.type = 'ramp'; -pdramp.time = 1; -pdramp.val = [0.2, 0.3]; - -pdwait.type = 'wait'; -pdwait.time = 2; -pdwait.val = [1, 1]; - -clear el_p; - -el_p.name = 'myname3'; -el_p.data = [pdstart pdramp pdwait]; - -%% -% The pulse el_p uses three pulse elements pdstart, pdramp and pdwait to -% define a shape, which starts from zero than rises to 0.2 for channel 1 -% and 0.3 for channel 2 in 1us. It than jumps to 1.0 for both channels and -% stays there for 2us. -% -% Again el_p is not in the correct format yet. A call of plsdefault() generates -% additional fields and set the correct el_p.format string - -el_p = plsdefault(el_p); - -%% -% This is equivalent of setting the following fields manually. - -el_p.format = 'elem'; -el_p.taurc = Inf; -el_p.xval = []; -el_p.pardef = []; -el_p.trafofn = []; - -%% -% Each element is defined by a type and two fields time and val. Dependind -% on the type the fields provide different parameters to define a pulse -% element. When downconverted to the |'tab'| format correct pulse table -% entries are derived, to represent the shapes defined by this elements. -% -% -% -%
    -%
    -% -% -% Here is an overview of the currently existing elements: -% -% *raw* insert [time; val] into pulse table. -% -% *mark* add time' to marktab -% -% *fill* stretch this element to make the total pulse duration equal to time. -% Idea for future development: allow several fills, each spreading a subset. -% Would need a second element to flush previous fill, could be fill without time. -% -% *wait* stay at val (row vector, one entry for each channel) for duration time. -% If val has 3 entries, third is a scaling factor for the first two. -% -% *reload* relaod pulse at val (row vector, one entry for each channel). -% time: [ramp time, wait time at load point, wait time at (0, 0) after load] -% -% *meas* measurement stage at [0, 0] for time(1), RF marker delayed by time(2) and -% off time(3) before end of the stage. [time(2) is lead delay, -% time(3) is negative tail delay. -% Optional val(1) is the readout tag. If it is given and not nan, time 4 and 5 set its delays -% with the sae convention as for the marker. -% Optional val(2,3) moves the measurement point away from 0,0. Makes -% meas_o obsolete. -% -% *meas_o* as meas, but measure at current voltages, not 0,0. -% -% *ramp* ramp to val (row vector, one entry for each channel) in time. opt val(3) is multiplier -% -% *comp* measurement compensation at val(1:2) (one for each channel) for duration time(1). -% Ramps voltage to target and back over time(2) and time(3) at the beginning and -% end of the stage, respectively. If length(val)>=4, val(3:4) are used as final value. -% The compensation value could be determined automatically, but this feature is not -% implemented yet. -% -% *adprep* adiabatic ramp along second diagonal (epsilon) from val(1) to val(2), ramp duration time. -% -% *adread* same, going the other way. -% -% *rfpulse* RF pulse using an IQ mixer with a mixer calibration present in caldata. -% Signals for I and Q are generated to produce a sinusoidal controllable in amplitude, -% phase and frequency. val(1:5)=[freqStart, freqStop, freqLO, amplitude, phase]. Using different -% values for freqStart and freqStop produces a chirp. val(6:7)=[calnumber, runind] are optional -% and are used as indices in caldata for a specific calibration, e.g. for multiple mixers. -% Otherwise the calibration with the name 'iqmx' is used. A starting element can be produced -% by setting the time to 0. Note: Vp amplitude expected -% (peak-to-zero amplitude). Specific: Tektroniks AWG expects Vpp on default, so -% Vpp=2*Vp is used inside rfpulse. -% -% * how to use dict related functionality -% -% -%
    -%
    -% -% -% The figure below is a summary of the discussed pulse formats. It also -% shows how the pulse information is kept when converting from |'elem'| to -% |'tab'| to |'wf'|. The original fields are not deletet. They are rather -% left in the pulse when it is converted. The data array of |'elem'| for -% example is moved into the data.elem field when converting to |'tab'|. -% -% <> -% -%% trafofn, pardef, varpar, params, taurc -% implimentation of trafofn, pardef, ... -% -% -% basic documentation of trafofn, pardef, ... -% -% -% * pardef working on individual pulse (n x 2)-array: (i,1) pulse element to -% target, (i,2) if neg. time index/ if pos. val index -% * create variations of pulse based on varpar/parm in one group -% * replacing targeted value generated by trafofn(params(i)) or if trafofn empty -% params directly -% * works for |'elem'| and |'tab'| -% * different purpose of trafofn in group struct opposed to pulse struct: -% transform all final wf-values based on custome function + args -% * taurc is for compensation of physical setup: combination of dc + rf -% signal from AWG channel, see picture -% -% <> -% -% This section describes the process of parametrisation for 'elem' or 'tab' -% type pulses. This are currently the only types of pulses to support this -% feature. -% -% Using parametrisation enables the creation of a series of pulses, where -% an arbitrary parameter is varied, e.g the value of a pulse element -% for 'elem': - -pdstart.type = 'raw'; -pdstart.time = 0; -pdstart.val= [0; 0]; - -pdramp.type = 'ramp'; -pdramp.time = 1; -pdramp.val = [0.5, 0.6]; - -el_p.name = 'myname3'; -el_p.data = [pdstart pdramp]; -el_p.pardef = [2 1]; -el_p.trafofn = @(x) x; - -%% Markers -% Besides the analog part of the data, a waveform inclueds two -% digital marker parts, which can be set for every clock individually -% to 'on' or 'off' on additional output channels. This is useful for -% triggering other instruments based on the index position within -% the waveform. -% -% The creation of markers is supported on all levels of the pulse format: -% -% In an 'wf' the marker is set using the data.marker field. The field has -% to be the same length as the data.wf field. It must contain 0-3 of the -% type unsigned char. Zero if no marker should be set. One for the first -% marker on channel one two for the second and three for both markers. -% For example: - -wf_p.data.marker = zeros(2,1200, 'uint8'); -wf_p.data.marker(1,1:120) = 1; - -%% -% This generates a short marker pulse on the first channel for one tenth of -% the total pulselength of 1200 for the first marker. -% -% It is more convinient to use data.marktab when creating a |'tab'| pulse. -% Analogous to the pulsetab the marktab contains the start times in the -% first rows. The subsequent rows contain the widths of the marker pulses. -% For two channels one two marker per channel this follows the schema: -% -% [time; channel1_marker1_duration; channel1_marker2_duration; -% channel2_marker1_duration; channel2_marker2_duration;] -% -% Here is an example: - -tb_p.data.marktab = [ 2 ; 1 ; 0 ; 0 ; 1 ]; - -%% -% This fires marker1 of channel one and marker2 of channel two for 1us -% starting at 2us. -% -% When using |'elem'| the pulse element 'mark' is used to write to the -% marktab. -% -% An advanced feature is the routing of marker channels. It is for example -% possible to define markers for one channel and have them executed in -% parallel on additional channels. Or exchange the markers of several -% channels. This is realised using the markmap field in a pulse group. -% Consult the section 'groups' below for group specific questions. -% -% Note that you can only route both markers of a channel at the same time. -% This is not a problem when using just one marker per channel. -% -% The usage is defind in as follows: -% -% markmap = [marker_source_channel1 marker_source_channel2, ...; -% marker_output_channel1 marker_output_channel2, ...] -% -% An example would be to route the markers from channel one to all channels -% for four channels in total: - -group.markmap = [1 1 1 1; 1 2 3 4]; - -%% -% or to exchange the markers of the first two channels: - -group.markmap = [1 2 3 4; 2 1 3 4]; - -%% Groups -% Only groups can be uploaded to the AWG. They consist of a number of -% pulses in various forms, or just one pulse in the most simple case. The -% group struct is similar to the struct of the pulse itself and is depicted -% in the following figure. Refer to the comments in -% -% for a quick reference. -% -% <> -% -% The group pg with the name pg.name has a number of fields. The field -% pg.markmap is discussed in the above section about markers. It is used to -% map markers from different channels to different marker outputs. The field -% pg.xval is a merging of the xval fields of the individual pulses. It is -% used by an external data evaluation package -% -% to define the scaling of the x-axis. Look into dataview for more -% information about xval. -% -% The fields pg.varpar and pg.params are used to parametrize certain -% aspects of an |'elem'| or |'tab'| pulse. Refer to the section above discussing it in -% connection with pardef and trafofn of the individual pulses inside the -% group. -% -% -%
    -% -% -% *ctrl, nrep, jump, pulseind* -% -% This fields control how the group behaves. -% -% * explain single fields with example -% -% -%
    -% -% -% *chan, matrix, offset, trafofn* -% -% This fields modify the group in several ways and set its output destination. -% -% * explain single fields with example -% -% -%
    -% -% -% *dict* -% -% This is a dictionary of predifined pulse elements used to create |'elem'| -% pulses. -% -% * ask harvard nicely to write about it and provide example uses -% -% -%
    -% -% -% *pulses* -% -% This field contains information about the pulses used in the group. -% -% The above figure about the pulsegroup struct shows three possible cases -% of content of the pg.pulses field (starting from the bottom): -% -% # The pulses are part of the database in plsdata.pulses and are -% referenced via their index, e.g., pg.pulses = [1, 2, 3] will use -% plsdata.pulses(1:3). -% # An array of pulse structs is provided directly, without the step of -% retrieving the pulses from the database. -% # Instead of single pulses use whole groups. This creates a group of groups -% in a recursive manner. To use this feature pg.pulses.groups has to be an -% array of already existing groups adressed by their group names, e.g., -% pg.pulses.groups = {'pg1', 'pg2'} will use pulse groups with the names 'pg1' and -% 'pg2', which were created with plsdefgrp() earlier to create a new group -% -% Cases 1 and 2 create groups of the type 'pls', since pulses are used. The -% third case creates groups of the type 'grp'. The type of group is -% specified inside the ctrl string pg.ctrl by plsdefgrp() automatically or -% it can be set manually when the group is defined. -% -% Besides pg.pulses.groups for type 'grp' the field pg.pulses.chan and -% pg.pulses.markchan exist to overwrite the output of the inner groups -% indiviually oposed to pg.chan and pg.markmap for all groups at once. The field pg.pulses.chan -% has as many rows as inner groups. The columns specify the output -% channels. The same is true for pg.pulses.markchan. If the output channel -% is set to 0 the specific output is ignored. -% -% For the example of using the two inner groups 'pg1' and 'pg2' as in case -% 3 above: - -pg.pulses.chan = [1 2; 2 0]; - -%% -% The inner group 'pg1' is routet to channel 1 und 2. The first channel of -% 'pg2' is routed to the physical channel 2 and the second channel of the -% group is omitted. - -%% Workflow -% At first a working configuration of pulsecontrol has to be enshured. -% Refer to the sections awgdata, plsdata above and the section "What Setup -% Do I Need?" in the Getting Started guide. -% -% A typical procedure how to use pulsecontrol is depicted in the figure -% below. -% -% <> -% -% # at first the desiered pulses are defined -% # the pulses can be plotted by plsplot() to enshure the correct shape -% visually -% # using plsreg() the pulses are 'registered' in the plsdata database. -% Since the database is backad up on disk, this enshures consistency and -% safty -% # a pulse group is defined using the pulses created in the first step. It -% is convinient to use the index of the pulse in the plsdata database. An -% alternative is to write the pulse struct(s) directly in the .pulses field -% of the group -% # the function plsdefgrp() saves the group to disk inside the directory -% plsdata.grpdir. Only groups saved in this directory can be send to the AWG. -% This enshures that no information is lost. -% # finally awgadd() is used to transmit the group or several groups to the AWG -% # the function plsupdate is used to update some parameters of the group. -% The fields name, chan, markmap, offset, pulseind, pulses cannot be -% updated. The modified group can be retransmitted to the AWG to reflect -% the changes -% -% Refer to the section "Example: Creation Of A Simple Pulse..." in the -% Getting Started guide. -% -% * explain when to use plsinfo() -% * explain how to use plsmakegrp() -% * explain how pulse is documented in plslog and how to retrieve states -% -%% Multiple AWGs -% -% * remains ToDo -% -% Copyright 2009 Not The MathWorks, Inc. diff --git a/doc/taurc.png b/doc/taurc.png deleted file mode 100644 index 03e2f5d9887acd4217385f2a1f57c4e03933eca7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4387 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 AUH||9 diff --git a/doc/workflow.png b/doc/workflow.png deleted file mode 100644 index 1a63d2c323d64504a2779e9213f69d2eada770ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22785 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 0; - chan(~mask) = []; - - % target channels for markers - if ~isfield(groupdef, 'markchan') - markchan = chan; - else - markchan = groupdef.markchan(j, :); - end - markmask = markchan > 0; - markchan(~markmask) = []; - - for i = 1:length(ind) - if j == 1 % first pf determines size - grpdef.pulses(i).data.wf = zeros(nchan, size(pg.pulses(i).data.wf, 2)); - grpdef.pulses(i).data.marker = zeros(nchan, size(pg.pulses(i).data.wf, 2), 'uint8'); - grpdef.pulses(i).xval = []; - end - - grpdef.pulses(i).data.wf(chan, :) = grpdef.pulses(i).data.wf(chan, :) + pg.pulses(i).data.wf(mask, :); - grpdef.pulses(i).data.marker(markchan, :) = bitor(grpdef.pulses(i).data.marker(markchan, :), pg.pulses(i).data.marker(markmask, :)); - grpdef.pulses(i).xval = [grpdef.pulses(i).xval, pg.pulses(i).xval]; - end - end - - [grpdef.pulses.format] = deal('wf'); - - %grpdef = rmfield(grpdef, 'groups', 'matrix', 'offset'); - end - - - for i = 1:length(ind) - grpdef.pulses(i).data.wf = grpdef.matrix * (grpdef.pulses(i).data.wf + ... - repmat(grpdef.offset, 1, size(grpdef.pulses(i).data.wf, 2))); - - if isfield(grpdef, 'markmap') - md = grpdef.pulses(i).data.marker; - grpdef.pulses(i).data.marker = zeros(size(grpdef.matrix, 1), size(md, 2), 'uint8'); - grpdef.pulses(i).data.marker(grpdef.markmap(2, :), :) = md(grpdef.markmap(1, :), :); - end - end - - grpdef.ctrl = ['pls', grpdef.ctrl(find(grpdef.ctrl == ' ', 1):end)]; - - - switch ctrl(1:min([end find(ctrl == ' ', 1)-1])) - case 'plot' - plsplot(grpdef.pulses); - - case 'check' - - for i = 1:length(ind) - if any(abs(grpdef.pulses(i).data.wf) > awgdata.scale) - fprintf('Pulse %i exceeds range.\n', i); - end - end - - case 'upload' - if logdata(end).time < lastupdate || ~isempty(strfind(ctrl, 'force')) - % modified since last upload (or upload forced) - - if isempty(zerolen) || ~isempty(strfind(ctrl, 'clrzero')) - zerolen = zeros(length(grpdef.pulses), length(grpdef.chan)); - end - - if isempty(strfind(ctrl, 'local')) - zerolen = awgload(grpdef, ind, zerolen); - else - zerolen = awgzero(grpdef, ind, zerolen); - end - - switch grpdef.ctrl(1:min([end find(grpdef.ctrl == ' ', 1)-1])); - case 'par' - lp = plsdef.params; - - case 'pls' - lp = []; - - case 'grp' - if isfield(groupdef, 'matrix'); - lp.matrix = groupdef.matrix; - end - if isfield(groupdef, 'offset'); - lp.offset = groupdef.offset; - end - end - % save update time in log. - logdata(end+1).time = now; - logdata(end).params = lp; - if length(logdata) > 2 % copy in case not all pulses updated. First logdata has no xval - logdata(end).xval = logdata(end-1).xval; - end - logdata(end).xval(:, ind) = vertcat(grpdef.pulses.xval)'; - logdata(end).ind = ind; - - save([plsdata.grpdir, 'pg_', name{k}], '-append', 'logdata', 'zerolen'); - logentry('Uploaded group %s.', grpdef.name); - fprintf('Uploaded group %s.\n', grpdef.name); - else - fprintf('Skipping group %s.\n', grpdef.name); - end - - end -end From c6405402c7900cdb38e0ccbb0e906268dea36be8 Mon Sep 17 00:00:00 2001 From: terryFitch Date: Tue, 3 Mar 2015 18:36:25 +0100 Subject: [PATCH 03/24] Began implementation of driver test. --- @AWG/AWG.m | 4 ++- @PXDAC/PXDAC.m | 7 ++-- @PXDAC/registerPulses.m | 7 +++- @Tek7082/Tek7082.m | 1 - @TekAWG/TekAWG.m | 20 +++++++---- @VAWG/VAWG.m | 23 +++++++++++-- hardwarePulseTest.m | 76 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 hardwarePulseTest.m diff --git a/@AWG/AWG.m b/@AWG/AWG.m index 67a1a80..81abbdf 100644 --- a/@AWG/AWG.m +++ b/@AWG/AWG.m @@ -48,6 +48,8 @@ %this function is for debugging purposes issueSoftwareTrigger(self); + val = isPlaybackInProgress(self); + % %add a pulsegroup by name to the pulsegroups playable by this % %machineuse is/ deprecated % add(self,pulsegroup) @@ -87,7 +89,7 @@ hardwareChannels = find( self.virtualChannels == virtualChannel ); end - function setActivePulsegroup(self,pulsegroupName) + function setActivePulseGroup(self,pulsegroupName) if isKey(self.storedPulsegroups,pulsegroupName) self.activeSequence = pulsegroupName; else diff --git a/@PXDAC/PXDAC.m b/@PXDAC/PXDAC.m index 9cf5439..2cd5606 100644 --- a/@PXDAC/PXDAC.m +++ b/@PXDAC/PXDAC.m @@ -75,7 +75,7 @@ % obj.activeChannelMask = uint16(15); - obj.clk = 1.2e9; + obj.clk = 100e6; obj.library('SetTriggerModeXD48',obj.handle,2);%single shot trigger mode @@ -215,6 +215,9 @@ function waitForTrigger(self) end end + self.library('SetPlaybackClockSourceXD48',self.handle,0); + self.library('SetClockDivider1XD48',self.handle,12); + self.library('SetClockDivider2XD48',self.handle,1); self.library('SetExternalTriggerEnableXD48',... @@ -269,7 +272,7 @@ function setOutputVoltage(self,channel,ppVoltage) end function testStatus(status) - if status ~= 0 + if status < 0 error(statusToErrorMessage); end end diff --git a/@PXDAC/registerPulses.m b/@PXDAC/registerPulses.m index cd5c1c0..1010263 100644 --- a/@PXDAC/registerPulses.m +++ b/@PXDAC/registerPulses.m @@ -1,8 +1,13 @@ function registerPulses(self,grp) + usedHWchanels = []; - for c = grp.chan + error('TODO: hahahaha'); + for c = self.virtualChannels + + usedHWchanels = [usedHWchanels self.getHardwareChannel(c)]; end + channelMask = uint16(sum(2.^(usedHWchanels-1))); %create pulsegroup object diff --git a/@Tek7082/Tek7082.m b/@Tek7082/Tek7082.m index d77bf1f..b0ad7ab 100644 --- a/@Tek7082/Tek7082.m +++ b/@Tek7082/Tek7082.m @@ -8,7 +8,6 @@ %constructor function obj = Tek7082(id,handle) obj = obj@TekAWG(id,handle); - obj.resolution = obj.possibleResolutions(1); %in bits end end end \ No newline at end of file diff --git a/@TekAWG/TekAWG.m b/@TekAWG/TekAWG.m index d5de81c..f02e4f3 100644 --- a/@TekAWG/TekAWG.m +++ b/@TekAWG/TekAWG.m @@ -20,6 +20,8 @@ obj = obj@AWG(id); obj.handle = handle; + class(obj); + obj.clk = 1.2e9; obj.zerochan = ones(1,obj.nChannels); @@ -37,18 +39,22 @@ % implemented abstact methods - add(self,pulsegroup) + %make this pulsegroup playable by AWG + addPulseGroup(self,grpdef); - val = control(self,cntrl, chans) - - erase(self,groups,options) + %remove this pulsegroup from memory and forget about it + removePulseGroup(self,name); - syncwaveforms(self) + %update the changed pulses + updatePulseGroup(self,grpdef); - upload(self,name) + %wait for trigger + arm(self); + %this function is for debugging purposes + issueSoftwareTrigger(self); - rm(self,grp, ctrl) + syncwaveforms(self) loadwfm(self,data, marker, name, chan,define) end diff --git a/@VAWG/VAWG.m b/@VAWG/VAWG.m index 34fdc51..12aa4e2 100644 --- a/@VAWG/VAWG.m +++ b/@VAWG/VAWG.m @@ -113,9 +113,28 @@ function removeVirtualChannelMapping(self,virtualChannel,awg,hardware) self.virtualToHardWare{virtualChannel}{entry} = []; end - function setActivePulsegroup(self,groupName) + function setActivePulseGroup(self,groupName) for awg = self.awgs - awg.setActivePulsegroup(groupName); + awg.setActivePulseGroup(groupName); + end + end + + function arm(self) + for awg = self.awgs + awg.arm(); + end + end + + function val = isPlaybackInProgress(self) + activePlaybacks = zeros(1,length(self.awgs)); + for awg = 1:length(self.awgs) + activePlaybacks(awg) = self.awgs(awg).isPlaybackInProgress(); + end + + val = sum(activePlaybacks)>0; + + if sum(activePlaybacks) ~= 0 && sum(activePlaybacks) ~= length(self.awgs) + warning('One AWGs is still playing while another one has finished!'); end end diff --git a/hardwarePulseTest.m b/hardwarePulseTest.m new file mode 100644 index 0000000..a854b70 --- /dev/null +++ b/hardwarePulseTest.m @@ -0,0 +1,76 @@ +function hardwarePulseTest() + time = 1000000; %us + + % pulse to test + testPulseGroup = initPulseGroup(time); + + % DAQ card + inputChannel = 1; + testDAC = initDAC(time,inputChannel); + + % awg to test + testVAWG = initVAWG(); + + + testVAWG.add(testPulseGroup.name); + testVAWG.setActivePulseGroup(testPulseGroup.name); + + testVAWG.arm(); + + % has to be adapted + testDAC.issueTrigger(); + + while testVAWG.playbackInProgress() + pause(1); + fprintf('Waiting for playback to finish...\n'); + end + + measuredData = testDAC.getResult(inputChannel); + + compareData(testPulseGroup.pulses,measuredData); + +end + +% +function dacobject = initDAC(time,inputChannel) + dacobject = ATS9440(1); + + dacobject.samprate = 100e6; %samples per second + sis = time * dacobject.samprate / 1e6; % samples in scanline + + dacobject.configureMeasurement(1,sis,inputChannel); +end + +function vawg = initVAWG() + vawg = VAWG(); + + awg = PXDAC_DC('messrechnerDC',1); + awg.setOutputVoltage(1,1); + + vawg.addAWG(awg); + vawg.createVirtualChannel(awg,1,1); +end + +function pulsegroup = initPulseGroup(time) + N = 1000; + rng(42); + + pulse.data.pulsetab = zeros(2,N); + pulse.data.pulsetab(1,:) = linspace(1,time,N); + pulse.data.pulsetab(2,:) = rand(1,N)*2 - 1; + + pulse.name = 'hardwareTestPulse'; + + pulsegroup.pulses = plsreg(pulse); + pulsegroup.nrep = 1; + pulsegroup.name = 'hardwareTestPulseGroup'; + pulsegroup.chan = 1; + pulsegroup.ctrl = 'notrig'; + + plsdefgrp(pulsegroup); +end + +function compareData(expected,measured) + error(''); +end + From 7468699dd57c408996928bda663a451a6736accd Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Wed, 4 Mar 2015 13:28:57 +0100 Subject: [PATCH 04/24] Implemented isPlaybackInProgress in class PXDAC (for PXDAC4800). --- @PXDAC/PXDAC.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/@PXDAC/PXDAC.m b/@PXDAC/PXDAC.m index 2cd5606..fce42ef 100644 --- a/@PXDAC/PXDAC.m +++ b/@PXDAC/PXDAC.m @@ -261,6 +261,14 @@ function setOutputVoltage(self,channel,ppVoltage) temp,self.handle) ppVoltage = temp.Value; end + + function playbackInProgress = isPlaybackInProgress(self) + libReturn = calllib('PXDAC4800_64','IsPlaybackInProgressXD48', self.handle); + if (libReturn < 0) + error(statusToErrorMessage(libReturn)); + end + playbackInProgress = (libReturn > 0); + end end methods (Static) From 5471e06c7754c2e811f6d7fa10b4fcb1e118f288 Mon Sep 17 00:00:00 2001 From: terryFitch Date: Tue, 10 Mar 2015 10:50:57 +0100 Subject: [PATCH 05/24] remove unnecessary files. --- @PXDAC/load.m | 63 ----------------------------- @PXDAC/upload.m | 103 ------------------------------------------------ 2 files changed, 166 deletions(-) delete mode 100644 @PXDAC/load.m delete mode 100644 @PXDAC/upload.m diff --git a/@PXDAC/load.m b/@PXDAC/load.m deleted file mode 100644 index b50c621..0000000 --- a/@PXDAC/load.m +++ /dev/null @@ -1,63 +0,0 @@ -function load(self,grp,ind) -fprintf('PXDAC.load\n') - -% ??? -dind=find([grp.pulses(1).data.clk] == self.clk); - - -% fixme; emit an error if this changes zerochan and wlist.size ~= 25. -% The screwiness here is to get each channel with a unique offset/scale combo -[~, offsetchan] = unique(self.offset./self.scale); -offsets=self.offset(offsetchan); - -channelMask = uint16(sum(2.^(self.getHardwareChannel(grp.chan)-1))); - -%create pulsegroup object -if ~isKey(self.storedPulsegroups,grp.name) - self.storedPulsegroups.add( PXDACPULSEGROUP(grp.name) ); -end - - -%reserve memory (at least i hope so) -self.storedPulsegroups(grp.name).waveformArray = repmat( struct('pulse',PXDACPULSE.empty(0,0),'repetitions',0), 1,length(grp.pulses) ); - -for i = 1:length(grp.pulses) - - - pulse = PXDACPULSE(channelMask,size(grp.pulses.data.wf,2)); - - - for virtChan = 1:size(grp.pulses(i).data(dind).wf, 1) - - hardChan = self.getHardwareChannel(grp.chan(virtChan)); - - %skip virtual channels not belonging to this AWG - if isempty(hardChan) - continue; - end - - %map to interval [0,2] - data = ((self.offset(min(hardChan,end)) + grp.pulses(i).data(dind).wf(virtChan, :))./self.scale(hardChan) + 1); - - %convert to uint16 0-2^14-1 - int16wf = uint16(min(... - data*(2^(14-1) - 1),... - 2^(14)-1)); - - pulse.writeToChannel(hardChan,int16wf); - - - end - - self.storedPulsegroups(grp.name).waveformArray(i).pulse = pulse; - -end - -self.storedPulsegroups(grp.name).lastload = now; - - -end - - - - diff --git a/@PXDAC/upload.m b/@PXDAC/upload.m deleted file mode 100644 index c2aec90..0000000 --- a/@PXDAC/upload.m +++ /dev/null @@ -1,103 +0,0 @@ -function upload(self,name) - -global plsdata; - - -if ~iscell(name) - name = {name}; -end - -for k = 1:length(name) - - if(~isstruct(name{k})) - zerolen = []; % avoid using zerolen from previous group. - plslog=[]; - load([plsdata.grpdir, 'pg_', name{k}]); - else - grpdef=name{k}; - end - - if exist('plslog','var') && length(plslog) > 100 - fprintf('Group %s has %d log entries.\n',name{k},length(plslog)); - end - if exist('plslog','var') && ~isempty(plslog) && ~isempty(opts.time) - le=plsinfo_logentry(plslog,opts.time); - grpdef.params=plslog(le).params; - grpdef.matrix=plslog(le).matrix; - grpdef.varpar=plslog(le).varpar; - grpdef.offset=plslog(le).offset; - grpdef.dict=plslog(le).dict; - grpdef.readout=plslog(le).readout; - end - - - pack = false;%~isempty(strfind(grpdef.ctrl,'pack')); - - if ~ isfield(grpdef, 'varpar') - grpdef.varpar = []; - end - if ~ isfield(grpdef, 'params') - grpdef.params = []; - end - if isfield(grpdef, 'time')&& ~isempty(grpdef.time) - fprintf('Ignoring opts.time\n'); - opts.time=grpdef.time; - - end - - if plsinfo('stale',grpdef.name) - % modified since last upload - - - - - % Actually handle the upload... - zerolen = self.load(grpdef, ind); - - - % save update time in log. - plslog(end+1).time = now; - plslog(end).params = grpdef.params; - plslog(end).matrix = grpdef.matrix; - plslog(end).varpar = grpdef.varpar; - plslog(end).offset = grpdef.offset; - - if isfield(grpdef.pulses(1).data,'readout') - readout=[]; - for ll=1:length(grpdef.pulses) - if ~isempty(grpdef.pulses(ll).data(1).readout) - readout(:,:,ll) = grpdef.pulses(ll).data(1).readout; - end - end - if any(abs(diff(readout,[],3)) > 1e-10) - warning('Readout changes between pulses in %s\n',name{k}); - end - if(size(readout,1) > 0) - plslog(end).readout = readout(:,:,1); - else - plslog(end).readout=[]; - end - end - - if isfield(grpdef, 'dict') - plslog(end).dict = grpdef.dict; - end - - if isfield(grpdef, 'trafofn') - plslog(end).trafofn = grpdef.trafofn; - end - - if length(plslog) > 2 % copy in case not all pulses updated. First plslog has no xval - plslog(end).xval = plslog(end-1).xval; - end - %plslog(end).xval(:, ind) = vertcat(grpdef.pulses.xval)'; - plslog(end).xval = vertcat(grpdef.pulses.xval)'; % temporary bug fix - plslog(end).ind = ind; - - save([plsdata.grpdir, 'pg_', name{k}], '-append','-v6', 'plslog', 'zerolen'); - logentry('Uploaded group %s, revisions %i.', grpdef.name, length(plslog)); - % fprintf(' in upload of group %s.\n', grpdef.name); - else - fprintf('Skipping group %s.\n', grpdef.name); - end -end \ No newline at end of file From 49a4596a8d11d54b425bf4472c7af17692037fce Mon Sep 17 00:00:00 2001 From: j340m3 Date: Tue, 10 Mar 2015 11:10:08 +0100 Subject: [PATCH 06/24] correlation tool for the data analysis --- correlation.m | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 correlation.m diff --git a/correlation.m b/correlation.m new file mode 100644 index 0000000..2ed1824 --- /dev/null +++ b/correlation.m @@ -0,0 +1,29 @@ +function retval = isCorrelated(expected,measured,maxSingle,maxAll) +retval = true; +% expected = randomList(1000,0); +% measured = disturbList(expected,0,0); +diffvec = abs(expected - measured); +if (max(diffvec) < maxSingle): + Disp('Single value differs to wide') + retval = false; + plot(diffvec,'.') +if (sum(diffvec.^2))/length(diffvec) < maxAll): + Disp('Overall values differs to wide') + retval = false; + plot(diffvec, '.') +else + retval = true; +end +end +end + +function retval = randomList(length,seed) +rng(seed); +retval = rand(1,length)*2-1; +end + +function retval = disturbList(list,disturbance,seed) +rng(seed); +lengthOfTheDisturbance = length(list); +retval = list + (rand(1,lengthOfTheDisturbance)-1)*disturbance; +end \ No newline at end of file From 0e0e22b17a54611f0b217a0f4dd48b2dcc547a12 Mon Sep 17 00:00:00 2001 From: j340m3 Date: Tue, 10 Mar 2015 12:32:30 +0100 Subject: [PATCH 07/24] introduced comparison of performance for the pulstab to waveform function --- plstowf.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plstowf.m b/plstowf.m index 3e5a8ff..eadcf1c 100755 --- a/plstowf.m +++ b/plstowf.m @@ -93,6 +93,7 @@ end for j = 1:nchan + old_start = clock; for i = 2:size(pulsetab, 2) mask = time >= pulsetab(1, i-1)-dt & time <= pulsetab(1, i)+dt; % added small shifts to mitigate rounding errors 08/04/09. Never seen to matter. @@ -109,6 +110,14 @@ end end + old_finish = clock; + sprintf('OLD:%d',etime(old_finish,old_start)) + + new_start = clock; + myout = interp1(pulsetab(1,:),pulsetab(2,:),0:dt:pulsetab(1,end)); + new_finish = clock; + sprintf('NEW:%d',etime(new_finish,new_start)) + sptintf('DIFF:%d',sum(myout-data)); end % lets pulses be defined with functions (eg. sin, cos) instead of % just lines From 7dbfa3777ac2b2cf854d34c07c6c9ed5ddaa24e9 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Tue, 10 Mar 2015 15:15:55 +0100 Subject: [PATCH 08/24] Removed debug output in plstowf and fixed call to interp1. --- plstowf.m | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/plstowf.m b/plstowf.m index eadcf1c..b04b409 100755 --- a/plstowf.m +++ b/plstowf.m @@ -93,31 +93,7 @@ end for j = 1:nchan - old_start = clock; - for i = 2:size(pulsetab, 2) - mask = time >= pulsetab(1, i-1)-dt & time <= pulsetab(1, i)+dt; - % added small shifts to mitigate rounding errors 08/04/09. Never seen to matter. - % below makes writes the pulse into data using lines to connect the - % corners defined in pulstab - if 0 - data(j, mask) = (-pulsetab(j+1, i-1) * (time(mask) - pulsetab(1, i)) ... - + pulsetab(j+1, i) * (time(mask) - pulsetab(1, i-1)))./... - (pulsetab(1, i) - pulsetab(1, i-1)); - else - data(j, mask) = ((-pulsetab(j+1, i-1) + pulsetab(j+1,i)) * time(mask) + ... - pulsetab(j+1,i-1) * pulsetab(1, i) - pulsetab(j+1,i) * pulsetab(1, i-1))./... - (pulsetab(1, i) - pulsetab(1, i-1)); - end - - end - old_finish = clock; - sprintf('OLD:%d',etime(old_finish,old_start)) - - new_start = clock; - myout = interp1(pulsetab(1,:),pulsetab(2,:),0:dt:pulsetab(1,end)); - new_finish = clock; - sprintf('NEW:%d',etime(new_finish,new_start)) - sptintf('DIFF:%d',sum(myout-data)); + data(j,:) = interp1(pulsetab(1,:),pulsetab(2,:),time); end % lets pulses be defined with functions (eg. sin, cos) instead of % just lines From 38bc028f8ffab9f79972ef5f93357a41adc1717b Mon Sep 17 00:00:00 2001 From: terryFitch Date: Tue, 10 Mar 2015 15:32:47 +0100 Subject: [PATCH 09/24] Fixed minor bugs and implemented correct virtual channel support --- @AWG/AWG.m | 2 +- @PXDAC/PXDAC.m | 4 ++++ @PXDAC/pxdac4800_wrapper.h | 24 ++++++++++++++++++++++ @PXDAC/registerPulses.m | 10 +++++----- hardwarePulseTest.m | 41 +++++++++++++++++++++++++++----------- 5 files changed, 63 insertions(+), 18 deletions(-) diff --git a/@AWG/AWG.m b/@AWG/AWG.m index 81abbdf..9dcd929 100644 --- a/@AWG/AWG.m +++ b/@AWG/AWG.m @@ -91,7 +91,7 @@ function setActivePulseGroup(self,pulsegroupName) if isKey(self.storedPulsegroups,pulsegroupName) - self.activeSequence = pulsegroupName; + self.activePulsegroup = pulsegroupName; else error('Can not activate pulsegroup "%s". Since it is not known',sequenceName); end diff --git a/@PXDAC/PXDAC.m b/@PXDAC/PXDAC.m index fce42ef..dcb677c 100644 --- a/@PXDAC/PXDAC.m +++ b/@PXDAC/PXDAC.m @@ -96,6 +96,10 @@ status = calllib('PXDACMemoryManager','initializeU16',obj.handle,uint32(maxsamples),uint32(chunksamples)); chunkexponent = chunkexponent-1; + + if chunkexponent == 0 + error('can not allocate enouch DMA buffers for PXDAC.'); + end end fprintf('Initialized memory manager with 2^%d samples per chunk.\n', chunkexponent); diff --git a/@PXDAC/pxdac4800_wrapper.h b/@PXDAC/pxdac4800_wrapper.h index 19907d4..c8b39d7 100644 --- a/@PXDAC/pxdac4800_wrapper.h +++ b/@PXDAC/pxdac4800_wrapper.h @@ -72,6 +72,30 @@ int LoadRamBufXD48( HXD48 hBrd, int IssueSoftwareTriggerXD48(HXD48 hBrd); +// Channel 1 output voltage; [0, 1023] +int SetOutputVoltageCh1XD48(HXD48 hBrd, int val); +// Channel 1 output voltage; [0, 1023] +int GetOutputVoltageCh1XD48(HXD48 hBrd, int bFromCache); + +// Channel 2 output voltage; [0, 1023] +int SetOutputVoltageCh2XD48(HXD48 hBrd, int val); +// Channel 2 output voltage; [0, 1023] +int GetOutputVoltageCh2XD48(HXD48 hBrd, int bFromCache); + +// Channel 3 output voltage; [0, 1023] +int SetOutputVoltageCh3XD48(HXD48 hBrd, int val); +// Channel 3 output voltage; [0, 1023] +int GetOutputVoltageCh3XD48(HXD48 hBrd, int bFromCache ); + +// Channel 4 output voltage; [0, 1023] +int SetOutputVoltageCh4XD48(HXD48 hBrd, int val); +// Channel 4 output voltage; [0, 1023] +int GetOutputVoltageCh4XD48(HXD48 hBrd, int bFromCache ); + +// Obtain peak-to-peak voltage for given output voltage encoding [0, 1023] +int GetOutputVoltageRangeVoltsXD48(int val, + double* pPeakToPeakVolts, + HXD48 hBrd); #endif diff --git a/@PXDAC/registerPulses.m b/@PXDAC/registerPulses.m index 1010263..e08698e 100644 --- a/@PXDAC/registerPulses.m +++ b/@PXDAC/registerPulses.m @@ -1,11 +1,11 @@ function registerPulses(self,grp) usedHWchanels = []; - error('TODO: hahahaha'); - for c = self.virtualChannels - - - usedHWchanels = [usedHWchanels self.getHardwareChannel(c)]; + + for c = grp.chan + if any(self.virtualChannels == c) + usedHWchanels = [usedHWchanels self.getHardwareChannel( self.getHardwareChannel(grp.chan(c))) ]; + end end channelMask = uint16(sum(2.^(usedHWchanels-1))); diff --git a/hardwarePulseTest.m b/hardwarePulseTest.m index a854b70..57fd9b9 100644 --- a/hardwarePulseTest.m +++ b/hardwarePulseTest.m @@ -4,23 +4,26 @@ function hardwarePulseTest() % pulse to test testPulseGroup = initPulseGroup(time); - % DAQ card - inputChannel = 1; - testDAC = initDAC(time,inputChannel); + + global vawg; % awg to test - testVAWG = initVAWG(); + vawg = initVAWG(); - testVAWG.add(testPulseGroup.name); - testVAWG.setActivePulseGroup(testPulseGroup.name); + vawg.add(testPulseGroup.name); + vawg.setActivePulseGroup(testPulseGroup.name); - testVAWG.arm(); + vawg.arm(); + + % DAQ card + inputChannel = 1; + testDAC = initDAC(time,inputChannel); - % has to be adapted - testDAC.issueTrigger(); + % issues trigger + testDAC.startMeasurement(); - while testVAWG.playbackInProgress() + while vawg.playbackInProgress() pause(1); fprintf('Waiting for playback to finish...\n'); end @@ -38,7 +41,9 @@ function hardwarePulseTest() dacobject.samprate = 100e6; %samples per second sis = time * dacobject.samprate / 1e6; % samples in scanline - dacobject.configureMeasurement(1,sis,inputChannel); + dacobject.useAsTriggerSource(); + + dacobject.configureMeasurement(1,sis,1,inputChannel); end function vawg = initVAWG() @@ -52,9 +57,21 @@ function hardwarePulseTest() end function pulsegroup = initPulseGroup(time) - N = 1000; + N = 10; rng(42); + global plsdata; + plsdata = []; + plsdata.datafile = [tempdir 'hardwaretest\plsdata_hw']; + plsdata.grpdir = [tempdir 'hardwaretest\plsdef\plsgrp']; + try + rmdir([tempdir 'hardwaretest'],'s'); + end + mkdir(plsdata.grpdir); + + plsdata.pulses = struct('data', {}, 'name', {}, 'xval',{}, 'taurc',{}, 'pardef',{},'trafofn',{},'format',{}); + plsdata.tbase = 1000; + pulse.data.pulsetab = zeros(2,N); pulse.data.pulsetab(1,:) = linspace(1,time,N); pulse.data.pulsetab(2,:) = rand(1,N)*2 - 1; From 46ce45e2fc1b0a2534416ec952074a4e14591c17 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Tue, 10 Mar 2015 17:18:04 +0100 Subject: [PATCH 10/24] Restructuring of hardwarePulseTest. It is now object oriented which increases modularity of tests. --- Testing/DefaultTestSetup.m | 91 +++++++++++++++++++++++++++++++++++++ Testing/RawIOTestSetup.m | 10 ++++ Testing/TestSetup.m | 26 +++++++++++ Testing/hardwarePulseTest.m | 6 +++ hardwarePulseTest.m | 76 ------------------------------- 5 files changed, 133 insertions(+), 76 deletions(-) create mode 100644 Testing/DefaultTestSetup.m create mode 100644 Testing/RawIOTestSetup.m create mode 100644 Testing/TestSetup.m create mode 100644 Testing/hardwarePulseTest.m delete mode 100644 hardwarePulseTest.m diff --git a/Testing/DefaultTestSetup.m b/Testing/DefaultTestSetup.m new file mode 100644 index 0000000..b961f81 --- /dev/null +++ b/Testing/DefaultTestSetup.m @@ -0,0 +1,91 @@ +classdef DefaultTestSetup < TestSetup + + properties(GetAccess = protected, SetAccess = protected) + pulsegroup; + dac; + vawg; + end + + methods (Access = public) + + function obj = DefaultTestSetup() + obj = obj@TestSetup(); + + obj.duration = 1000000; %us + obj.inputChannel = 1; + end + + function initiate(self) + % pulse to test + self.initPulseGroup(); + + % awg to test + self.initVAWG(); + + self.vawg.add(self.pulsegroup.name); + self.vawg.setActivePulseGroup(self.pulsegroup.name); + + % DAQ card + self.initDAC(); + + self.vawg.arm(); + end + + function run(self) + self.dac.issueTrigger(); + + while self.vawg.playbackInProgress() + pause(1); + fprintf('Waiting for playback to finish...\n'); + end + + measuredData = self.dac.getResult(self.inputChannel); + self.evaluate(measuredData); + end + + end + + methods (Access = protected) + + function initVAWG(self) + self.vawg = VAWG(); + + awg = PXDAC_DC('messrechnerDC',1); + awg.setOutputVoltage(1,1); + + self.vawg.addAWG(awg); + self.vawg.createVirtualChannel(awg,1,1); + end + + function initDAC(self) + self.dac = ATS9440(1); + + self.dac.samprate = 100e6; %samples per second + sis = self.duration * self.dac.samprate / 1e6; % samples in scanline + + self.dac.configureMeasurement(1, sis, self.inputChannel); + end + + function initPulseGroup(self) + N = 1000; + rng(42); + + pulse.data.pulsetab = zeros(2, N); + pulse.data.pulsetab(1,:) = linspace(1, self.duration, N); + pulse.data.pulsetab(2,:) = rand(1, N) * 2 - 1; + + pulse.name = 'hardwareTestPulse'; + + self.pulsegroup.pulses = plsreg(pulse); + self.pulsegroup.nrep = 1; + self.pulsegroup.name = 'hardwareTestPulseGroup'; + self.pulsegroup.chan = 1; + self.pulsegroup.ctrl = 'notrig'; + + plsdefgrp(self.pulsegroup); + end + + end + +end + diff --git a/Testing/RawIOTestSetup.m b/Testing/RawIOTestSetup.m new file mode 100644 index 0000000..d03caf8 --- /dev/null +++ b/Testing/RawIOTestSetup.m @@ -0,0 +1,10 @@ +classdef RawIOTestSetup < DefaultTestSetup + + methods (Access = protected) + function evaluate(self, measured) + error('Not implemented yet!'); + end + end + +end + diff --git a/Testing/TestSetup.m b/Testing/TestSetup.m new file mode 100644 index 0000000..e4af09f --- /dev/null +++ b/Testing/TestSetup.m @@ -0,0 +1,26 @@ +classdef TestSetup + + properties(SetAccess = protected, GetAccess = protected) + duration; + inputChannel; + end + + methods(Abstract, Access = public) + initiate(self); + run(self); + end + + methods(Abstract, Access = protected) + + initVAWG(self); + + initDAC(self); + + initPulseGroup(self); + + evaluate(self, measured); + + end + +end + diff --git a/Testing/hardwarePulseTest.m b/Testing/hardwarePulseTest.m new file mode 100644 index 0000000..35d492f --- /dev/null +++ b/Testing/hardwarePulseTest.m @@ -0,0 +1,6 @@ +function hardwarePulseTest() + testSetup = RawIOTestSetup(); + + testSetup.initiate(); + testSetup.run(); +end \ No newline at end of file diff --git a/hardwarePulseTest.m b/hardwarePulseTest.m deleted file mode 100644 index a854b70..0000000 --- a/hardwarePulseTest.m +++ /dev/null @@ -1,76 +0,0 @@ -function hardwarePulseTest() - time = 1000000; %us - - % pulse to test - testPulseGroup = initPulseGroup(time); - - % DAQ card - inputChannel = 1; - testDAC = initDAC(time,inputChannel); - - % awg to test - testVAWG = initVAWG(); - - - testVAWG.add(testPulseGroup.name); - testVAWG.setActivePulseGroup(testPulseGroup.name); - - testVAWG.arm(); - - % has to be adapted - testDAC.issueTrigger(); - - while testVAWG.playbackInProgress() - pause(1); - fprintf('Waiting for playback to finish...\n'); - end - - measuredData = testDAC.getResult(inputChannel); - - compareData(testPulseGroup.pulses,measuredData); - -end - -% -function dacobject = initDAC(time,inputChannel) - dacobject = ATS9440(1); - - dacobject.samprate = 100e6; %samples per second - sis = time * dacobject.samprate / 1e6; % samples in scanline - - dacobject.configureMeasurement(1,sis,inputChannel); -end - -function vawg = initVAWG() - vawg = VAWG(); - - awg = PXDAC_DC('messrechnerDC',1); - awg.setOutputVoltage(1,1); - - vawg.addAWG(awg); - vawg.createVirtualChannel(awg,1,1); -end - -function pulsegroup = initPulseGroup(time) - N = 1000; - rng(42); - - pulse.data.pulsetab = zeros(2,N); - pulse.data.pulsetab(1,:) = linspace(1,time,N); - pulse.data.pulsetab(2,:) = rand(1,N)*2 - 1; - - pulse.name = 'hardwareTestPulse'; - - pulsegroup.pulses = plsreg(pulse); - pulsegroup.nrep = 1; - pulsegroup.name = 'hardwareTestPulseGroup'; - pulsegroup.chan = 1; - pulsegroup.ctrl = 'notrig'; - - plsdefgrp(pulsegroup); -end - -function compareData(expected,measured) - error(''); -end - From fbb001fbb3e1a5979a1215b6de169f85f3e89ac1 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Tue, 10 Mar 2015 18:53:48 +0100 Subject: [PATCH 11/24] Implemented DSIOTestSetup and RSAIOTestSetup. --- Testing/DSIOTestSetup.m | 65 ++++++++++++++++++++++++++++++++++++++++ Testing/RSAIOTestSetup.m | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 Testing/DSIOTestSetup.m create mode 100644 Testing/RSAIOTestSetup.m diff --git a/Testing/DSIOTestSetup.m b/Testing/DSIOTestSetup.m new file mode 100644 index 0000000..4f6d0e7 --- /dev/null +++ b/Testing/DSIOTestSetup.m @@ -0,0 +1,65 @@ +classdef DSIOTestSetup < DefaultTestSetup + + properties (GetAccess = public, SetAccess = public) + test_start = 400; + test_end = 600; + test_period = 1000; + test_iterations = 100; + end + + methods (Access = public) + + function obj = DSIOTestSetup() + obj = obj@DefaultTestSetup(); + obj.duration = obj.test_iterations * obj.test_period; + end + + end + + methods (Access = protected) + + function initDAC(self) + error('TODO: Must configure DAC accordingly.'); + end + + function initPulseGroup(self) + dt = 1; % us + + rng(42); + + randomValues = rand(1, self.test_iterations) * 2 - 1; + + self.pulsegroup.pulses = zeros(1,self.test_iterations); + self.pulsegroup.chan = 1; + self.pulsegroup.name = 'DSIOTestPulseGroup'; + self.pulsegroup.ctrl = 'notrig'; + + for i = 1:self.test_iterations + randomValue = randomValues(i); + pulse.data.pulsetab = zeros(2, 6); + pulse.data.pulsetab(1,:) = [0, self.test_start - dt, ... + self.test_start, self.test_end, self.test_end + dt, ... + self.test_period]; + pulse.data.pulsetab(2,:) = [-randomValue, -randomValue, ... + randomValue, randomValue, -randomValue, ... + -randomValue]; + pulse.name = sprintf('DSIOTestPulse%i', i); + self.pulsegroup.pulses(i) = plsreg(pulse); + end + + plsdefgrp(self.pulsegroup); + end + + function evaluate(self, measured) + waveform = []; + for i = 1:self.test_iterations + pls = plstowf(self.pulsegroup.pulses(i)); + waveform = [waveform pls.data.wf]; + end + plot(waveform); + end + + end + +end + diff --git a/Testing/RSAIOTestSetup.m b/Testing/RSAIOTestSetup.m new file mode 100644 index 0000000..4f095a0 --- /dev/null +++ b/Testing/RSAIOTestSetup.m @@ -0,0 +1,62 @@ +classdef RSAIOTestSetup < DefaultTestSetup + + properties (GetAccess = public, SetAccess = public) + test_start = 400; + test_end = 600; + test_period = 1000; + test_iterations = 100; + test_random_points = 20; % must be < test_end - test_start + end + + methods (Access = public) + + function obj = RSAIOTestSetup() + obj = obj@DefaultTestSetup(); + obj.duration = obj.test_iterations * obj.test_period; + end + + end + + methods (Access = protected) + + function initDAC(self) + error('TODO: Must configure DAC accordingly.'); + end + + function initPulseGroup(self) + dt = 1; % us + + rng(42); + + randomValues = rand(1, self.test_random_points) * 2 - 1; + + pulse.data.pulsetab = zeros(2, 4 + self.test_random_points); + + pulse.data.pulsetab(1,:) = [0, self.test_start - dt, ... + linspace(self.test_start, self.test_end, self.test_random_points), ... + self.test_end + dt, self.test_period]; + + pulse.data.pulsetab(2,:) = [-randomValues(1), -randomValues(1), ... + randomValues(1:end), ... + -randomValues(end), -randomValues(end)]; + + pulse.name = 'RSAIOTestPulse'; + + self.pulsegroup.pulses = plsreg(pulse); + self.pulsegroup.chan = 1; + self.pulsegroup.nrep = self.test_iterations; + self.pulsegroup.name = 'RSAIOTestPulseGroup'; + self.pulsegroup.ctrl = 'notrig'; + + plsdefgrp(self.pulsegroup); + end + + function evaluate(self, measured) + pls = plstowf(self.pulsegroup.pulses); + plot(pls.data.wf); + end + + end + +end + From 023271bf2957dc564ee3ce194c29354d97403528 Mon Sep 17 00:00:00 2001 From: Patrick Bethke Date: Fri, 13 Mar 2015 12:25:49 +0100 Subject: [PATCH 12/24] Changes to the test class, to make it more suitable for experiments, implemented generic test criterion --- Testing/DSIOTestSetup.m | 9 ++++++++- Testing/DefaultTestSetup.m | 13 ++++++++++--- Testing/RSAIOTestSetup.m | 12 +++++++----- Testing/RawIOTestSetup.m | 7 ++----- Testing/TestSetup.m | 28 +++++++++++++++++++++------- Testing/hardwarePulseTest.m | 2 +- 6 files changed, 49 insertions(+), 22 deletions(-) diff --git a/Testing/DSIOTestSetup.m b/Testing/DSIOTestSetup.m index 4f6d0e7..5d304e7 100644 --- a/Testing/DSIOTestSetup.m +++ b/Testing/DSIOTestSetup.m @@ -7,6 +7,10 @@ test_iterations = 100; end + properties(SetAccess = protected, GetAccess = public) + errorThreshold = 1e-4; % RMS error threshold in Volt + end + methods (Access = public) function obj = DSIOTestSetup() @@ -50,13 +54,16 @@ function initPulseGroup(self) plsdefgrp(self.pulsegroup); end - function evaluate(self, measured) + function calcExpectedData(self) + %TODO: get masks from somewhere, combine with waveform to + %calculate the expected data, related to DAC setup waveform = []; for i = 1:self.test_iterations pls = plstowf(self.pulsegroup.pulses(i)); waveform = [waveform pls.data.wf]; end plot(waveform); + self.expectedData = waveform; end end diff --git a/Testing/DefaultTestSetup.m b/Testing/DefaultTestSetup.m index 6b01b58..36d5dbe 100644 --- a/Testing/DefaultTestSetup.m +++ b/Testing/DefaultTestSetup.m @@ -6,6 +6,10 @@ vawg; end + properties(SetAccess = protected, GetAccess = public) + errorThreshold = 1e-3; % RMS error threshold in Volt + end + methods (Access = public) function obj = DefaultTestSetup() @@ -15,7 +19,7 @@ obj.inputChannel = 1; end - function initiate(self) + function init(self) % pulse to test self.initPulseGroup(); @@ -31,7 +35,7 @@ function initiate(self) self.vawg.arm(); end - function run(self) + function success = run(self) self.dac.issueTrigger(); while self.vawg.playbackInProgress() @@ -40,7 +44,7 @@ function run(self) end measuredData = self.dac.getResult(self.inputChannel); - self.evaluate(measuredData); + success = self.evaluate(measuredData); end end @@ -85,6 +89,9 @@ function initPulseGroup(self) self.pulsegroup.ctrl = 'notrig'; plsdefgrp(self.pulsegroup); + + p = plstowf(pulse); + self.expectedData = p.data.wf; end end diff --git a/Testing/RSAIOTestSetup.m b/Testing/RSAIOTestSetup.m index 4f095a0..0bdabfd 100644 --- a/Testing/RSAIOTestSetup.m +++ b/Testing/RSAIOTestSetup.m @@ -8,6 +8,10 @@ test_random_points = 20; % must be < test_end - test_start end + properties(SetAccess = protected, GetAccess = public) + errorThreshold = 1e-4; % RMS error threshold in Volt + end + methods (Access = public) function obj = RSAIOTestSetup() @@ -49,12 +53,10 @@ function initPulseGroup(self) self.pulsegroup.ctrl = 'notrig'; plsdefgrp(self.pulsegroup); + + % TODO: calculate expected measurement signal end - - function evaluate(self, measured) - pls = plstowf(self.pulsegroup.pulses); - plot(pls.data.wf); - end + end diff --git a/Testing/RawIOTestSetup.m b/Testing/RawIOTestSetup.m index d03caf8..d405a20 100644 --- a/Testing/RawIOTestSetup.m +++ b/Testing/RawIOTestSetup.m @@ -1,10 +1,7 @@ classdef RawIOTestSetup < DefaultTestSetup - methods (Access = protected) - function evaluate(self, measured) - error('Not implemented yet!'); - end + properties(SetAccess = protected, GetAccess = public) + errorThreshold = 1e-3; % RMS error threshold in Volt end - end diff --git a/Testing/TestSetup.m b/Testing/TestSetup.m index e4af09f..ad9e58f 100644 --- a/Testing/TestSetup.m +++ b/Testing/TestSetup.m @@ -5,22 +5,36 @@ inputChannel; end + properties(Abstract, SetAccess = protected, GetAccess = protected) + expectedData; % expected measurement values if everything works as intended + end + + properties(Abstract, SetAccess = protected, GetAccess = public) + errorThreshold; % tolerated RMS error threshold in Volts; value strongly depends on the test + end + methods(Abstract, Access = public) - initiate(self); - run(self); + init(self); % initialize the test + success = run(self); % run the test and return whether or not it was successful end methods(Abstract, Access = protected) - initVAWG(self); + initVAWG(self); % initialize the arbitrary waveform generator - initDAC(self); + initDAC(self); % initialize the data acquisition device - initPulseGroup(self); - - evaluate(self, measured); + initPulseGroup(self); % initializes pulses and calculates expected data end + methods(Access = protected) + function success = evaluate(self, measured) + err = measured - self.expectedData; % error signal + rms = std(err,0); % average error per sample + success = rms < self.errorThreshold; + end + end + end diff --git a/Testing/hardwarePulseTest.m b/Testing/hardwarePulseTest.m index 75b3790..849712c 100644 --- a/Testing/hardwarePulseTest.m +++ b/Testing/hardwarePulseTest.m @@ -14,6 +14,6 @@ function hardwarePulseTest() testSetup = RawIOTestSetup(); - testSetup.initiate(); + testSetup.init(); testSetup.run(); end \ No newline at end of file From d50d448753a7cb07b4fcc04eb7d2c9d47d5839da Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Sun, 15 Mar 2015 15:48:15 +0100 Subject: [PATCH 13/24] Moved implementation of evaluate method as well as specific properties from TestSetup to DefaultTestSetup. Property errorThreshold is no longer abstract but is given as a constructor argument to DefaultTestSetup, as are duration and inputChannel. --- Testing/DSIOTestSetup.m | 9 ++------- Testing/DefaultTestSetup.m | 18 +++++++++--------- Testing/RSAIOTestSetup.m | 9 ++------- Testing/RawIOTestSetup.m | 9 +++++++-- Testing/TestSetup.m | 29 ++++++++++++----------------- 5 files changed, 32 insertions(+), 42 deletions(-) diff --git a/Testing/DSIOTestSetup.m b/Testing/DSIOTestSetup.m index 5d304e7..d04c37f 100644 --- a/Testing/DSIOTestSetup.m +++ b/Testing/DSIOTestSetup.m @@ -1,21 +1,16 @@ classdef DSIOTestSetup < DefaultTestSetup - properties (GetAccess = public, SetAccess = public) + properties (Constant, GetAccess = private) test_start = 400; test_end = 600; test_period = 1000; test_iterations = 100; end - properties(SetAccess = protected, GetAccess = public) - errorThreshold = 1e-4; % RMS error threshold in Volt - end - methods (Access = public) function obj = DSIOTestSetup() - obj = obj@DefaultTestSetup(); - obj.duration = obj.test_iterations * obj.test_period; + obj = obj@DefaultTestSetup(DSIOTestSetup.test_iterations * DSIOTestSetup.test_period, 1, 1e-4); end end diff --git a/Testing/DefaultTestSetup.m b/Testing/DefaultTestSetup.m index 36d5dbe..79364e9 100644 --- a/Testing/DefaultTestSetup.m +++ b/Testing/DefaultTestSetup.m @@ -4,19 +4,13 @@ pulsegroup; dac; vawg; - end - - properties(SetAccess = protected, GetAccess = public) - errorThreshold = 1e-3; % RMS error threshold in Volt + expectedData; end methods (Access = public) - function obj = DefaultTestSetup() - obj = obj@TestSetup(); - - obj.duration = 1000000; %us - obj.inputChannel = 1; + function obj = DefaultTestSetup(duration, inputChannel, errorThreshold) + obj = obj@TestSetup(duration, inputChannel, errorThreshold); end function init(self) @@ -94,6 +88,12 @@ function initPulseGroup(self) self.expectedData = p.data.wf; end + function success = evaluate(self, measured) + err = measured - self.expectedData; % error signal + rms = std(err,0); % average error per sample + success = rms < self.errorThreshold; + end + end end diff --git a/Testing/RSAIOTestSetup.m b/Testing/RSAIOTestSetup.m index 0bdabfd..f5e1562 100644 --- a/Testing/RSAIOTestSetup.m +++ b/Testing/RSAIOTestSetup.m @@ -1,6 +1,6 @@ classdef RSAIOTestSetup < DefaultTestSetup - properties (GetAccess = public, SetAccess = public) + properties (Constant, GetAccess = private) test_start = 400; test_end = 600; test_period = 1000; @@ -8,15 +8,10 @@ test_random_points = 20; % must be < test_end - test_start end - properties(SetAccess = protected, GetAccess = public) - errorThreshold = 1e-4; % RMS error threshold in Volt - end - methods (Access = public) function obj = RSAIOTestSetup() - obj = obj@DefaultTestSetup(); - obj.duration = obj.test_iterations * obj.test_period; + obj = obj@DefaultTestSetup(RSAIOTestSetup.test_iterations * RSAIOTestSetup.test_period, 1, 1e-4); end end diff --git a/Testing/RawIOTestSetup.m b/Testing/RawIOTestSetup.m index d405a20..224b427 100644 --- a/Testing/RawIOTestSetup.m +++ b/Testing/RawIOTestSetup.m @@ -1,7 +1,12 @@ classdef RawIOTestSetup < DefaultTestSetup - properties(SetAccess = protected, GetAccess = public) - errorThreshold = 1e-3; % RMS error threshold in Volt + methods(Access = public) + + function obj = RawIOTestSetup() + obj = obj@DefaultTestSetup(1000000, 1, 1e-3); + end + end + end diff --git a/Testing/TestSetup.m b/Testing/TestSetup.m index ad9e58f..6d7ad16 100644 --- a/Testing/TestSetup.m +++ b/Testing/TestSetup.m @@ -1,19 +1,20 @@ classdef TestSetup - - properties(SetAccess = protected, GetAccess = protected) + + properties(SetAccess = private, GetAccess = protected) duration; inputChannel; + errorThreshold; % tolerated RMS error threshold in Volts; value strongly depends on the test end - properties(Abstract, SetAccess = protected, GetAccess = protected) - expectedData; % expected measurement values if everything works as intended - end - - properties(Abstract, SetAccess = protected, GetAccess = public) - errorThreshold; % tolerated RMS error threshold in Volts; value strongly depends on the test + methods(Access = protected) + function obj = TestSetup(duration, inputChannel, errorThreshold) + obj.duration = duration; + obj.inputChannel = inputChannel; + obj.errorThreshold = errorThreshold; + end end - methods(Abstract, Access = public) + methods(Abstract, Access = public) init(self); % initialize the test success = run(self); % run the test and return whether or not it was successful end @@ -26,14 +27,8 @@ initPulseGroup(self); % initializes pulses and calculates expected data - end - - methods(Access = protected) - function success = evaluate(self, measured) - err = measured - self.expectedData; % error signal - rms = std(err,0); % average error per sample - success = rms < self.errorThreshold; - end + success = evaluate(self, measured); + end end From 8f432127424b2e1b899b9e21e416a3ab9da3f68c Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Sun, 15 Mar 2015 16:51:27 +0100 Subject: [PATCH 14/24] Created script InitializePulseData which initializes the global plsdata struct. --- Testing/InitializePulseData.m | 11 +++++++++++ Testing/hardwarePulseTest.m | 12 +----------- 2 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 Testing/InitializePulseData.m diff --git a/Testing/InitializePulseData.m b/Testing/InitializePulseData.m new file mode 100644 index 0000000..fee695a --- /dev/null +++ b/Testing/InitializePulseData.m @@ -0,0 +1,11 @@ +global plsdata; +plsdata = []; +plsdata.datafile = [tempdir 'hardwaretest\plsdata_hw']; +plsdata.grpdir = [tempdir 'hardwaretest\plsdef\plsgrp']; +try + rmdir([tempdir 'hardwaretest'],'s'); +end +mkdir(plsdata.grpdir); + +plsdata.pulses = struct('data', {}, 'name', {}, 'xval',{}, 'taurc',{}, 'pardef',{},'trafofn',{},'format',{}); +plsdata.tbase = 1000; \ No newline at end of file diff --git a/Testing/hardwarePulseTest.m b/Testing/hardwarePulseTest.m index 849712c..80bdd6d 100644 --- a/Testing/hardwarePulseTest.m +++ b/Testing/hardwarePulseTest.m @@ -1,15 +1,5 @@ function hardwarePulseTest() - global plsdata; - plsdata = []; - plsdata.datafile = [tempdir 'hardwaretest\plsdata_hw']; - plsdata.grpdir = [tempdir 'hardwaretest\plsdef\plsgrp']; - try - rmdir([tempdir 'hardwaretest'],'s'); - end - mkdir(plsdata.grpdir); - - plsdata.pulses = struct('data', {}, 'name', {}, 'xval',{}, 'taurc',{}, 'pardef',{},'trafofn',{},'format',{}); - plsdata.tbase = 1000; + InitializePulseData; testSetup = RawIOTestSetup(); From fc3facb9cea3d1d3990c8cb161a4f73ff889d295 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Sun, 15 Mar 2015 16:52:47 +0100 Subject: [PATCH 15/24] MeasuredData is now a property of the DefaultTestSetup class to allow access to measured as well as expected data for in-depth evaluation if the evaluate function reports failure. --- Testing/DefaultTestSetup.m | 16 ++++++++++------ Testing/TestSetup.m | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Testing/DefaultTestSetup.m b/Testing/DefaultTestSetup.m index 79364e9..906b8a1 100644 --- a/Testing/DefaultTestSetup.m +++ b/Testing/DefaultTestSetup.m @@ -4,7 +4,11 @@ pulsegroup; dac; vawg; - expectedData; + end + + properties(GetAccess = public, SetAccess = protected) + expectedData = []; + measuredData = []; end methods (Access = public) @@ -37,13 +41,13 @@ function init(self) fprintf('Waiting for playback to finish...\n'); end - measuredData = self.dac.getResult(self.inputChannel); - success = self.evaluate(measuredData); + self.measuredData = self.dac.getResult(self.inputChannel); + success = self.evaluate(); end end - methods (Access = protected) + methods (Access = protected) function initVAWG(self) self.vawg = VAWG(); @@ -88,8 +92,8 @@ function initPulseGroup(self) self.expectedData = p.data.wf; end - function success = evaluate(self, measured) - err = measured - self.expectedData; % error signal + function success = evaluate(self) + err = self.measuredData - self.expectedData; % error signal rms = std(err,0); % average error per sample success = rms < self.errorThreshold; end diff --git a/Testing/TestSetup.m b/Testing/TestSetup.m index 6d7ad16..5d4c674 100644 --- a/Testing/TestSetup.m +++ b/Testing/TestSetup.m @@ -27,7 +27,7 @@ initPulseGroup(self); % initializes pulses and calculates expected data - success = evaluate(self, measured); + success = evaluate(self); end From 82432bbae5bf4188bf50c7138b266d35a765f5a3 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Sun, 15 Mar 2015 16:55:58 +0100 Subject: [PATCH 16/24] DSIOTestSetup and RSAIOTestSetup now configure the DAC correctly for downsampling or repetitive averaging respectively and set expectedData in initPulseGroup(). --- Testing/DSIOTestSetup.m | 26 +++++++++++++------------- Testing/RSAIOTestSetup.m | 25 ++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/Testing/DSIOTestSetup.m b/Testing/DSIOTestSetup.m index d04c37f..cfaca9e 100644 --- a/Testing/DSIOTestSetup.m +++ b/Testing/DSIOTestSetup.m @@ -18,7 +18,18 @@ methods (Access = protected) function initDAC(self) - error('TODO: Must configure DAC accordingly.'); + mask = struct('begin', self.test_start, 'end', self.test_end, 'period', self.test_period, 'hwChannel', self.inputChannel); + + self.dac = ATS9440(1); + + self.dac.samprate = 100e6; %samples per second + samplesInPeriod = self.test_period * self.dac.samprate / 1e6; + + self.dac.masks = { mask }; + + self.dac.useAsTriggerSource(); + + self.dac.configureMeasurement(samplesInPeriod, self.test_iterations, 1, 4 + inputChannel); end function initPulseGroup(self) @@ -27,6 +38,7 @@ function initPulseGroup(self) rng(42); randomValues = rand(1, self.test_iterations) * 2 - 1; + self.expectedData = randomValues; self.pulsegroup.pulses = zeros(1,self.test_iterations); self.pulsegroup.chan = 1; @@ -49,18 +61,6 @@ function initPulseGroup(self) plsdefgrp(self.pulsegroup); end - function calcExpectedData(self) - %TODO: get masks from somewhere, combine with waveform to - %calculate the expected data, related to DAC setup - waveform = []; - for i = 1:self.test_iterations - pls = plstowf(self.pulsegroup.pulses(i)); - waveform = [waveform pls.data.wf]; - end - plot(waveform); - self.expectedData = waveform; - end - end end diff --git a/Testing/RSAIOTestSetup.m b/Testing/RSAIOTestSetup.m index f5e1562..df92d76 100644 --- a/Testing/RSAIOTestSetup.m +++ b/Testing/RSAIOTestSetup.m @@ -19,7 +19,18 @@ methods (Access = protected) function initDAC(self) - error('TODO: Must configure DAC accordingly.'); + mask = struct('begin', self.test_start, 'end', self.test_end, 'period', self.test_period, 'hwChannel', self.inputChannel); + + self.dac = ATS9440(1); + + self.dac.samprate = 100e6; %samples per second + samplesInPeriod = self.test_period * self.dac.samprate / 1e6; + + self.dac.masks = { mask }; + + self.dac.useAsTriggerSource(); + + self.dac.configureMeasurement(samplesInPeriod, self.test_iterations, 1, 8 + inputChannel); end function initPulseGroup(self) @@ -27,7 +38,7 @@ function initPulseGroup(self) rng(42); - randomValues = rand(1, self.test_random_points) * 2 - 1; + randomValues = rand(1, self.test_random_points) * 2 - 1; pulse.data.pulsetab = zeros(2, 4 + self.test_random_points); @@ -36,7 +47,7 @@ function initPulseGroup(self) self.test_end + dt, self.test_period]; pulse.data.pulsetab(2,:) = [-randomValues(1), -randomValues(1), ... - randomValues(1:end), ... + randomValues, ... -randomValues(end), -randomValues(end)]; pulse.name = 'RSAIOTestPulse'; @@ -49,6 +60,14 @@ function initPulseGroup(self) plsdefgrp(self.pulsegroup); + mainPulse.data.pulsetab = zeros(2, self.test_random_points); + mainPulse.data.pulsetab(1, :) = linspace(0, self.test_end - self.test_start, self.test_random_points); + mainPulse.data.pulsetab(2, :) = randomValues; + + mainPulse = plstowf(mainPulse); + self.expectedData = mainPulse.data.wf; + + %elementalPulse.data.wf % TODO: calculate expected measurement signal end From b925676f365544ee5c67382f8f5bd5a183268ce5 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Sun, 15 Mar 2015 16:58:04 +0100 Subject: [PATCH 17/24] Ignored Matlabs autosave files (.asv). --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21229f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.asv \ No newline at end of file From 0416f7646652a7f2c0e2fba42f616545472296db Mon Sep 17 00:00:00 2001 From: Patrick Bethke Date: Mon, 16 Mar 2015 10:15:34 +0100 Subject: [PATCH 18/24] add a second error criterion: a threshold for the tolerable single sample error --- Testing/DSIOTestSetup.m | 2 +- Testing/DefaultTestSetup.m | 7 ++++--- Testing/RawIOTestSetup.m | 2 +- Testing/TestSetup.m | 8 +++++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Testing/DSIOTestSetup.m b/Testing/DSIOTestSetup.m index cfaca9e..b3c45b8 100644 --- a/Testing/DSIOTestSetup.m +++ b/Testing/DSIOTestSetup.m @@ -10,7 +10,7 @@ methods (Access = public) function obj = DSIOTestSetup() - obj = obj@DefaultTestSetup(DSIOTestSetup.test_iterations * DSIOTestSetup.test_period, 1, 1e-4); + obj = obj@DefaultTestSetup(DSIOTestSetup.test_iterations * DSIOTestSetup.test_period, 1, 1e-4, 1e-3); end end diff --git a/Testing/DefaultTestSetup.m b/Testing/DefaultTestSetup.m index 906b8a1..011e352 100644 --- a/Testing/DefaultTestSetup.m +++ b/Testing/DefaultTestSetup.m @@ -13,8 +13,8 @@ methods (Access = public) - function obj = DefaultTestSetup(duration, inputChannel, errorThreshold) - obj = obj@TestSetup(duration, inputChannel, errorThreshold); + function obj = DefaultTestSetup(duration, inputChannel, meanErrorThreshold, singleErrorThreshold) + obj = obj@TestSetup(duration, inputChannel, meanErrorThreshold, singleErrorThreshold); end function init(self) @@ -95,7 +95,8 @@ function initPulseGroup(self) function success = evaluate(self) err = self.measuredData - self.expectedData; % error signal rms = std(err,0); % average error per sample - success = rms < self.errorThreshold; + maxerr = max(abs(err)); % maximum single error + success = (rms < self.meanErrorThreshold) && (maxerr < self.singleErrorThreshold); end end diff --git a/Testing/RawIOTestSetup.m b/Testing/RawIOTestSetup.m index 224b427..6198aaa 100644 --- a/Testing/RawIOTestSetup.m +++ b/Testing/RawIOTestSetup.m @@ -3,7 +3,7 @@ methods(Access = public) function obj = RawIOTestSetup() - obj = obj@DefaultTestSetup(1000000, 1, 1e-3); + obj = obj@DefaultTestSetup(1000000, 1, 1e-3, .5e-2); end end diff --git a/Testing/TestSetup.m b/Testing/TestSetup.m index 5d4c674..cfe4f89 100644 --- a/Testing/TestSetup.m +++ b/Testing/TestSetup.m @@ -3,14 +3,16 @@ properties(SetAccess = private, GetAccess = protected) duration; inputChannel; - errorThreshold; % tolerated RMS error threshold in Volts; value strongly depends on the test + meanErrorThreshold; % tolerated RMS error threshold in Volts; value strongly depends on the test + singleErrorThreshold; % tolerated single maximum error end methods(Access = protected) - function obj = TestSetup(duration, inputChannel, errorThreshold) + function obj = TestSetup(duration, inputChannel, meanErrorThreshold, singleErrorThreshold) obj.duration = duration; obj.inputChannel = inputChannel; - obj.errorThreshold = errorThreshold; + obj.meanErrorThreshold = meanErrorThreshold; + obj.singleErrorThreshold = singleErrorThreshold; end end From 855f89f3e21371df2263e022379bbd088d427186 Mon Sep 17 00:00:00 2001 From: Patrick Bethke Date: Mon, 16 Mar 2015 10:41:26 +0100 Subject: [PATCH 19/24] ignore backup files and binaries in all directories --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 21229f7..7f90ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -*.asv \ No newline at end of file +# Windows default autosave extension +**/*.asv +# OSX / *nix default autosave extension +**/*.m~ +# Compiled MEX binaries (all platforms) +**/*.mex* \ No newline at end of file From 1b050503e30de9c8259c513471f907999bae6747 Mon Sep 17 00:00:00 2001 From: Patrick Bethke Date: Mon, 16 Mar 2015 11:28:38 +0100 Subject: [PATCH 20/24] add test for table mask --- Testing/TableMaskTestSetup.m | 68 ++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Testing/TableMaskTestSetup.m diff --git a/Testing/TableMaskTestSetup.m b/Testing/TableMaskTestSetup.m new file mode 100644 index 0000000..99e0d07 --- /dev/null +++ b/Testing/TableMaskTestSetup.m @@ -0,0 +1,68 @@ +classdef TableMaskTestSetup < DefaultTestSetup + + properties (Constant, GetAccess = private) + test_iterations = 100; + test_start = 400 + floor(linspace(0,200,test_iterations)); + test_end = 600 + floor(linspace(0,200,test_iterations)); + test_period = 1000; + end + + methods (Access = public) + + function obj = TableMaskTestSetup() + obj = obj@DefaultTestSetup(TableMaskTestSetup.test_iterations * TableMaskTestSetup.test_period, 1, 1e-4, 1e-3); + end + + end + + methods (Access = protected) + + function initDAC(self) + % prepare a table mask + mask = struct('type', 'Table Mask',... + 'begin', self.test_start,... + 'end', self.test_end,... + 'period', self.test_period,... + 'hwChannel', self.inputChannel); + + self.dac = ATS9440(1); + + self.dac.samprate = 100e6; %samples per second + samplesInPeriod = self.test_period * self.dac.samprate / 1e6; + + self.dac.masks = { mask }; + + self.dac.useAsTriggerSource(); + + self.dac.configureMeasurement(samplesInPeriod, self.test_iterations, 1, 4 + inputChannel); + end + + function initPulseGroup(self) + dt = 1; % us + + rng(42); + + randomValues = rand(1, self.test_iterations) * 2 - 1; + self.expectedData = randomValues; + + self.pulsegroup.pulses = zeros(1,self.test_iterations); + self.pulsegroup.chan = 1; + self.pulsegroup.name = 'DSIOTestPulseGroup'; + self.pulsegroup.ctrl = 'notrig'; + + for i = 1:self.test_iterations + randomValue = randomValues(i); + pulse.data.pulsetab = zeros(2, 6); + pulse.data.pulsetab(1,:) = [0, self.test_start(i) - dt, self.test_start(i), self.test_end(i), self.test_end(i) + dt, self.test_period]; + pulse.data.pulsetab(2,:) = [-randomValue, -randomValue, randomValue, randomValue, -randomValue, -randomValue]; + pulse.name = sprintf('DSIOTestPulse%i', i); + self.pulsegroup.pulses(i) = plsreg(pulse); + end + + plsdefgrp(self.pulsegroup); + end + + end + +end + From 316a52a223c405bdf2e0dc9fc5590c5f530475a3 Mon Sep 17 00:00:00 2001 From: Patrick Bethke Date: Mon, 16 Mar 2015 11:29:16 +0100 Subject: [PATCH 21/24] reformat DSIOTestSetup pulse initialization for readability --- Testing/DSIOTestSetup.m | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Testing/DSIOTestSetup.m b/Testing/DSIOTestSetup.m index b3c45b8..b77933f 100644 --- a/Testing/DSIOTestSetup.m +++ b/Testing/DSIOTestSetup.m @@ -48,12 +48,8 @@ function initPulseGroup(self) for i = 1:self.test_iterations randomValue = randomValues(i); pulse.data.pulsetab = zeros(2, 6); - pulse.data.pulsetab(1,:) = [0, self.test_start - dt, ... - self.test_start, self.test_end, self.test_end + dt, ... - self.test_period]; - pulse.data.pulsetab(2,:) = [-randomValue, -randomValue, ... - randomValue, randomValue, -randomValue, ... - -randomValue]; + pulse.data.pulsetab(1,:) = [0, self.test_start - dt, self.test_start, self.test_end, self.test_end + dt, self.test_period]; + pulse.data.pulsetab(2,:) = [-randomValue, -randomValue, randomValue, randomValue, -randomValue, -randomValue]; pulse.name = sprintf('DSIOTestPulse%i', i); self.pulsegroup.pulses(i) = plsreg(pulse); end From 9a5d9c0daf369c3eb80ecfc81a72676cfc0d85f6 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Wed, 18 Mar 2015 19:21:04 +0100 Subject: [PATCH 22/24] Begun implementation of revised test hierarchy (issue #6). --- Testing/IOTestDriver.m | 82 ++++++++++++++++++++++++ Testing/RandomTestPulseBuilder.m | 55 ++++++++++++++++ Testing/RawIOTestConfigurationProvider.m | 27 ++++++++ Testing/TestConfigurationProvider.m | 66 +++++++++++++++++++ Testing/TestPulseBuilder.m | 49 ++++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 Testing/IOTestDriver.m create mode 100644 Testing/RandomTestPulseBuilder.m create mode 100644 Testing/RawIOTestConfigurationProvider.m create mode 100644 Testing/TestConfigurationProvider.m create mode 100644 Testing/TestPulseBuilder.m diff --git a/Testing/IOTestDriver.m b/Testing/IOTestDriver.m new file mode 100644 index 0000000..f4f2aaf --- /dev/null +++ b/Testing/IOTestDriver.m @@ -0,0 +1,82 @@ +classdef IOTestDriver + + properties (SetAccess = private, GetAccess = private) + vawg; + configurationProvider; + dac; + end + + properties (SetAccess = private, GetAccess = public) + expectedData = []; + measuredData = []; + end + + methods (Access = public) + + function obj = IOTestDriver(configurationProvider) + if ~isa(configurationProvider, 'TestConfigurationProvider') + error('IOTestDriver requires an instance of TestConfigurationProvider for parameter configurationProvider!'); + end + obj.configurationProvider = configurationProvider; + end + + function success = run(self) + self.init(); + + self.dac.issueTrigger(); + + while self.vawg.playbackInProgress() + pause(1); + fprintf('Waiting for playback to finish...\n'); + end + + % move all dac transparently to TestConfigurationProvider? + self.measuredData = self.dac.getResult(self.configurationProvider.getInputChannel()); + + success = self.evaluate(); + end + + end + + methods (Access = private) + + function init(self) + + % obtain pulse group and expected data from test configuration + self.configurationProvider.createPulseGroup(); + pulseGroup = self.configurationProvider.getPulseGroup(); + self.expectedData = self.configurationProvider.getExpectedData(); + + % obtain DAC instance from test configuration + self.dac = self.configurationProvider.createDAC(); + self.dac.useAsTriggerSource(); + + % setup and arm awg + self.initVAWG(); + self.vawg.add(pulseGroup); + self.vawg.setActivePulseGroup(pulseGroup); + self.vawg.arm(); + end + + function initVAWG(self) + self.vawg = VAWG(); + awg = PXDAC_DC('messrechnerDC', 1); + awg.setOutputVoltage(1, 1); + + self.vawg.addAWG(awg); + self.vawg.createVirtualChannel(awg, 1, 1); + end + + function success = evaluate(self) + err = self.measuredData - self.expectedData; % error signal + rms = std(err,0); % average error per sample + maxerr = max(abs(err)); % maximum single error + satisfiesMeanThreshold = rms < self.configurationProvider.getMeanErrorThreshold(); + satisfiesSingleThreshold = maxerr < self.configurationProvider.getSingleErrorThreshold(); + success = satisfiesMeanThreshold && satisfiesSingleThreshold; + end + + end + +end + diff --git a/Testing/RandomTestPulseBuilder.m b/Testing/RandomTestPulseBuilder.m new file mode 100644 index 0000000..57647d0 --- /dev/null +++ b/Testing/RandomTestPulseBuilder.m @@ -0,0 +1,55 @@ +classdef RandomTestPulseBuilder < TestPulseBuilder + + properties (Constant, GetAccess = public) + meanErrorThreshold = 1e-3; + singleErrorThreshold = 0.5e-2; + dacOperation = 'raw'; + end + + properties (Constant, GetAccess = protected) + pulseGroupPrototype = struct( ... + 'pulses', [], ... + 'nrep', [], ... + 'name', 'RandomTestPulseGroup', ... + 'chan', 1, ... + 'ctrl', 'notrig' ... + ); + + voltageHoldDuration = 100; + end + + methods (Access = public) + + function self = RandomTestPulseBuilder() + self = self@TestPulseBuilder(); + rng(42); + end + + end + + methods (Access = protected) + + function createPulse(self, mask, repetitions) + readoutDuration = mask.period; + voltagesAmount = ceil(readoutDuration / self.voltageHoldDuration); + + randomVoltages = rand(1, voltagesAmount) * 2 - 1; + pulse.data.pulsetab = zeros(2, voltagesAmount); + pulse.data.pulsetab(1, :) = linspace(mask.begin, mask.end, voltagesAmount); + pulse.data.pulsetab(2, :) = randomVoltages; + + pulse.name = sprintf('RandomTestPulse%i', self.pulseCount); + + self.pulseGroup.pulses(end + 1) = plsreg(pulse); + self.pulseGroup.nrep(end + 1) = repetitions; + + p = plstowf(pulse); + for i = 1:repetitions + self.expectedData = [self.expectedData, p.data.wf]; + end + end + + end + +end + diff --git a/Testing/RawIOTestConfigurationProvider.m b/Testing/RawIOTestConfigurationProvider.m new file mode 100644 index 0000000..e4e0bce --- /dev/null +++ b/Testing/RawIOTestConfigurationProvider.m @@ -0,0 +1,27 @@ +classdef RawIOTestConfigurationProvider < TestConfigurationProvider + + properties (Constant, GetAccess = protected) + mask = struct( ... + 'begin', 0, ... + 'end', 10000, ... + 'period', 10000 ... + ); + end + + methods (Access = protected) + + function computePulseGroup(self) + self.pulseBuilder.addPulse(self.mask, 1); + end + + end + + methods (Access = public) + + function self = RawIOTestConfigurationProvider(inputChannel, pulseBuilder) + self = self@TestConfigurationProvider(inputChannel, pulseBuilder); + end + end + +end + diff --git a/Testing/TestConfigurationProvider.m b/Testing/TestConfigurationProvider.m new file mode 100644 index 0000000..e3e46fa --- /dev/null +++ b/Testing/TestConfigurationProvider.m @@ -0,0 +1,66 @@ +classdef TestConfigurationProvider < handle + + properties (SetAccess = private, GetAccess = public) + inputChannel = 1; + meanErrorThreshold; + singleErrorThreshold; + end + + properties (SetAccess = private, GetAccess = protected) + pulseBuilder; + end + + properties (Abstract, Constant, GetAccess = protected) + mask; + end + + methods (Abstract, Access = protected) + computePulseGroup(self); + end + + methods (Access = public) + + function self = TestConfigurationProvider(inputChannel, pulseBuilder) + self.pulseBuilder = pulseBuilder; + self.inputChannel = inputChannel; + self.meanErrorThreshold = self.pulseBuilder.meanErrorThreshold; + self.singleErrorThreshold = self.pulseBuilder.singleErrorThreshold; + end + + function createPulseGroup(self) + self.pulseBuilder.reset(); + self.computePulseGroup(); + plsdefgrp(self.pulseBuilder.pulseGroup); + end + + function pulseGroup = getPulseGroup(self) + pulseGroup = self.pulseBuilder.pulseGroup; + end + + function expectedData = getExpectedData(self) + expectedData = self.pulseBuilder.expectedData; + end + + function dac = createDAC(self) + switch (self.pulseBuilder.dacOperation) + case 'raw' + operation = self.inputChannel; + case 'ds' + operation = self.inputChannel + 4; + case 'rsa' + operation = self.inputChannel + 8; + otherwise + error('DAC operation %s unknown', self.pulseBuilder.dacOperation); + end + + dac = ATS9440(1); + dac.samprate = 100e6; + samplesInPeriod = self.mask.period * dac.samprate / 1e6; + dac.masks = { self.mask }; + dac.configureMeasurement(samplesInPeriod, self.pulseBuilder.pulseCount, 1, operation); + end + + end + +end + diff --git a/Testing/TestPulseBuilder.m b/Testing/TestPulseBuilder.m new file mode 100644 index 0000000..013c179 --- /dev/null +++ b/Testing/TestPulseBuilder.m @@ -0,0 +1,49 @@ +classdef TestPulseBuilder < handle + + properties (Constant, Abstract, GetAccess = public) + meanErrorThreshold; + singleErrorThreshold; + dacOperation; + end + + properties (Constant, Abstract, GetAccess = protected) + pulseGroupPrototype; + end + + properties (SetAccess = private, GetAccess = public) + pulseCount = 0; + end + + properties (SetAccess = protected, GetAccess = public) + pulseGroup; + expectedData; + end + + methods (Abstract, Access = protected) + createPulse(self, mask, repetitions); + end + + methods (Access = protected) + + function self = TestPulseBuilder() + self.reset(); + end + + end + + methods (Access = public) + + function addPulse(self, mask, repetitions) + self.pulseCount = self.pulseCount + repetitions * mask.period; + self.createPulse(mask, repetitions); + end + + function reset(self) + self.pulseGroup = self.pulseGroupPrototype; + self.pulseCount = 0; + self.expectedData = []; + end + end + +end + From ad2f279b09b7780c8f49e9555bd64624348b2491 Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Tue, 24 Mar 2015 12:05:14 +0100 Subject: [PATCH 23/24] Implemented TestPulseBuilder for the downsampling and repetitive signal averaging operations as well as TestConfigurationProvider for periodic and table masks. --- Testing/DSTestPulseBuilder.m | 72 ++++++++++++++ Testing/IOTestDriver.m | 1 - .../PeriodicMaskTestConfigurationProvider.m | 39 ++++++++ Testing/RSATestPulseBuilder.m | 96 +++++++++++++++++++ Testing/RawIOTestConfigurationProvider.m | 18 +++- Testing/TableMaskTestConfigurationProvider.m | 40 ++++++++ Testing/TestConfigurationProvider.m | 5 +- 7 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 Testing/DSTestPulseBuilder.m create mode 100644 Testing/PeriodicMaskTestConfigurationProvider.m create mode 100644 Testing/RSATestPulseBuilder.m create mode 100644 Testing/TableMaskTestConfigurationProvider.m diff --git a/Testing/DSTestPulseBuilder.m b/Testing/DSTestPulseBuilder.m new file mode 100644 index 0000000..d94a049 --- /dev/null +++ b/Testing/DSTestPulseBuilder.m @@ -0,0 +1,72 @@ +classdef DSTestPulseBuilder < TestPulseBuilder + + properties (Constant, GetAccess = public) + meanErrorThreshold = 1e-4; + singleErrorThreshold = 1e-3; + dacOperation = 'ds'; + end + + properties (Constant, GetAccess = protected) + pulseGroupPrototype = struct( ... + 'pulses', [], ... + 'nrep', [], ... + 'name', 'DownsamplingTestPulseGroup', ... + 'chan', 1, ... + 'ctrl', 'notrig' ... + ); + + voltageHoldDuration = 100; + end + + methods (Access = public) + + function self = DSTestPulseBuilder() + self = self@TestPulseBuilder(); + rng(42); + end + + end + + methods (Access = protected) + + function createPulse(self, mask, repetitions) + dt = 1; % us + + readoutDuration = mask.end - mask.begin; + + assert(readoutDuration >= 0, 'Duration of readout window in mask was less than zero!'); + assert(mask.begin == 0 || mask.begin >= dt, 'Could not compute pulse because begin of readout window was to close to zero.'); + assert(mask.end == mask.period || mask.end <= mask.period - dt, 'Could not compute pulse because end of readout windows was to close to period.'); + + randomVoltage = rand(1, 1) * 2 - 1; + + readoutVoltages = [ mask.begin, mask.end; ... + randomVoltage, randomVoltage ]; + + preReadoutVoltages = []; + postReadoutVoltages = []; + if (mask.begin > 0) + preReadoutVoltages = [ 0, mask.begin - dt; ... + -randomVoltage, -randomVoltage ]; + end + if (mask.end < mask.period) + postReadoutVoltages = [ mask.end + dt, mask.period; ... + -randomVoltage, -randomVoltage ]; + end + + pulse.data.pulsetab = [preReadoutVoltages readoutVoltages postReadoutVoltages]; + pulse.name = sprintf('DSIOTestPulse%i', self.pulseCount); + + self.pulseGroup.pulses(end + 1) = plsreg(pulse); + self.pulseGroup.nrep(end + 1) = repetitions; + + for i = 1:repetitions + self.expectedData = [self.expectedData, randomVoltage]; + end + + end + + end + +end + diff --git a/Testing/IOTestDriver.m b/Testing/IOTestDriver.m index f4f2aaf..bbd0ceb 100644 --- a/Testing/IOTestDriver.m +++ b/Testing/IOTestDriver.m @@ -30,7 +30,6 @@ fprintf('Waiting for playback to finish...\n'); end - % move all dac transparently to TestConfigurationProvider? self.measuredData = self.dac.getResult(self.configurationProvider.getInputChannel()); success = self.evaluate(); diff --git a/Testing/PeriodicMaskTestConfigurationProvider.m b/Testing/PeriodicMaskTestConfigurationProvider.m new file mode 100644 index 0000000..65bbc52 --- /dev/null +++ b/Testing/PeriodicMaskTestConfigurationProvider.m @@ -0,0 +1,39 @@ +classdef PeriodicMaskTestConfigurationProvider < TestConfigurationProvider + + properties (GetAccess = protected) + mask = struct( ... + 'begin', 400, ... + 'end', 600, ... + 'period', 1000 ... + ); + iterations = 100; + end + + methods (Access = protected) + + function computePulseGroup(self) + for i = 1:self.iterations + self.pulseBuilder.addPulse(self.mask, 1); + end + end + + end + + methods (Access = public) + + function self = PeriodicMaskTestConfigurationProvider(inputChannel, pulseBuilder, mask, iterations) + self = self@TestConfigurationProvider(inputChannel, pulseBuilder); + if (nargin >= 3) + assert(~isfield(mask, 'type') || ~strcmp(mask.type, 'Table Mask'), 'mask must not be a table mask!'); + self.mask = mask; + end + if (nargin == 4) + assert(isnumeric(iterations) && iterations > 0, 'iterations must be a positive integer value!'); + self.iterations = iterations; + end + end + + end + +end + diff --git a/Testing/RSATestPulseBuilder.m b/Testing/RSATestPulseBuilder.m new file mode 100644 index 0000000..7060b77 --- /dev/null +++ b/Testing/RSATestPulseBuilder.m @@ -0,0 +1,96 @@ +classdef RSATestPulseBuilder < TestPulseBuilder + + properties (Constant, GetAccess = public) + meanErrorThreshold = 1e-4; + singleErrorThreshold = 1e-3; + dacOperation = 'rsa'; + end + + properties (Constant, GetAccess = protected) + pulseGroupPrototype = struct( ... + 'pulses', [], ... + 'nrep', [], ... + 'name', 'RSATestPulseGroup', ... + 'chan', 1, ... + 'ctrl', 'notrig' ... + ); + + voltageHoldDuration = 20; + end + + properties (SetAccess = private, GetAccess = private) + readoutDuration = 0; + readoutVoltages = []; + period = 0; + end + + methods (Access = public) + + function self = RSATestPulseBuilder() + self = self@TestPulseBuilder(); + rng(42); + end + + function reset(self) + reset@TestPulseBuilder(self); + self.readoutDuration = 0; + self.readoutVoltages = []; + self.period = 0; + end + + end + + methods (Access = protected) + + function createPulse(self, mask, repetitions) + dt = 1; % us + + if (self.readoutDuration == 0) + self.readoutDuration = mask.end - mask.begin; + end + + if (self.period == 0) + self.period = mask.period; + end + + assert(self.period == mask.period, 'Period of mask deviates from previous values.'); + assert(self.readoutDuration == mask.end - mask.begin, 'Duration of readout window in mask deviates from previous values!'); + assert(self.readoutDuration >= 0, 'Duration of readout window in mask was less than zero!'); + assert(mask.begin == 0 || mask.begin >= dt, 'Could not compute pulse because begin of readout window was to close to zero.'); + assert(mask.end == mask.period || mask.end <= mask.period - dt, 'Could not compute pulse because end of readout windows was to close to period.'); + + if (isempty(self.readoutVoltages)) + readoutVoltageCount = floor(self.readoutDuration / self.voltageHoldDuration) + 1; + self.readoutVoltages = rand(1, readoutVoltageCount) * 2 - 1; + + readoutPulse.data.pulsetab = [linspace(0, self.readoutDuration, readoutVoltageCount); self.readoutVoltages]; + + readoutPulse = plstowf(readoutPulse); + self.expectedData = readoutPulse.data.wf; + end + + readoutVoltageTable = [linspace(mask.begin, mask.end, size(self.readoutVoltages, 2)); self.readoutVoltages]; + + preReadoutVoltages = []; + postReadoutVoltages = []; + if (mask.begin > 0) + preReadoutVoltages = [ 0, mask.begin - dt; ... + -self.readoutVoltages(1), -self.readoutVoltages(1) ]; + end + if (mask.end < mask.period) + postReadoutVoltages = [ mask.end + dt, mask.period; ... + -self.readoutVoltages(end), -self.readoutVoltages(end) ]; + end + + pulse.data.pulsetab = [preReadoutVoltages readoutVoltageTable postReadoutVoltages]; + pulse.name = sprintf('RSAIOTestPulse%i', self.pulseCount); + + self.pulseGroup.pulses(end + 1) = plsreg(pulse); + self.pulseGroup.nrep(end + 1) = repetitions; + + end + + end + +end + diff --git a/Testing/RawIOTestConfigurationProvider.m b/Testing/RawIOTestConfigurationProvider.m index e4e0bce..f9992b0 100644 --- a/Testing/RawIOTestConfigurationProvider.m +++ b/Testing/RawIOTestConfigurationProvider.m @@ -1,25 +1,37 @@ classdef RawIOTestConfigurationProvider < TestConfigurationProvider - properties (Constant, GetAccess = protected) + properties (GetAccess = protected) mask = struct( ... 'begin', 0, ... 'end', 10000, ... 'period', 10000 ... ); + iterations = 1; end methods (Access = protected) function computePulseGroup(self) - self.pulseBuilder.addPulse(self.mask, 1); + for i = 1:self.iterations + self.pulseBuilder.addPulse(self.mask, 1); + end end end methods (Access = public) - function self = RawIOTestConfigurationProvider(inputChannel, pulseBuilder) + function self = RawIOTestConfigurationProvider(inputChannel, pulseBuilder, mask, iterations) self = self@TestConfigurationProvider(inputChannel, pulseBuilder); + if (nargin >= 3) + assert(~isfield(mask, 'type') || ~strcmp(mask.type, 'Table Mask'), 'mask must not be a table mask!'); + assert(mask.begin == 0 && mask.end == mask.period, 'Readout window of mask must be equal to its period!'); + self.mask = mask; + end + if (nargin == 4) + assert(isnumeric(iterations) && iterations > 0, 'iterations must be a positive integer value!'); + self.iterations = iterations; + end end end diff --git a/Testing/TableMaskTestConfigurationProvider.m b/Testing/TableMaskTestConfigurationProvider.m new file mode 100644 index 0000000..dc77589 --- /dev/null +++ b/Testing/TableMaskTestConfigurationProvider.m @@ -0,0 +1,40 @@ +classdef TableMaskTestConfigurationProvider < TestConfigurationProvider + + properties (GetAccess = protected) + mask = struct( ... + 'type', 'Table Mask', ... + 'begin', 400 + floor(linspace(0, 200, 100)), ... + 'end', 600 + floor(linspace(0, 200, 100)), ... + 'period', 1000 ... + ); + end + + methods (Access = protected) + + function computePulseGroup(self) + currentMask = self.mask; + for i = 1:numel(self.mask.begin) + currentMask.begin = self.mask.begin(i); + currentMask.end = self.mask.end(i); + self.pulseBuilder.addPulse(currentMask, 1); + end + end + + end + + methods (Access = public) + + function self = TableMaskTestConfigurationProvider(inputChannel, pulseBuilder, mask) + self = self@TestConfigurationProvider(inputChannel, pulseBuilder); + if (nargin >= 3) + assert(isfield(mask, 'type'), 'mask must be a table mask!'); + assert(strcmp(mask.type, 'Table Mask'), 'mask must be a table mask!'); + assert(numel(mask.begin) == numel(mask.end), 'begin and end table dimensions of mask are not equal!'); + self.mask = mask; + end + end + + end + +end + diff --git a/Testing/TestConfigurationProvider.m b/Testing/TestConfigurationProvider.m index e3e46fa..905289f 100644 --- a/Testing/TestConfigurationProvider.m +++ b/Testing/TestConfigurationProvider.m @@ -10,7 +10,7 @@ pulseBuilder; end - properties (Abstract, Constant, GetAccess = protected) + properties (Abstract, GetAccess = protected) mask; end @@ -21,6 +21,9 @@ methods (Access = public) function self = TestConfigurationProvider(inputChannel, pulseBuilder) + assert(isa(pulseBuilder, 'TestPulseBuilder'), 'pulseBuilder must be an instance of TestPulseBuilder!'); + assert(isnumeric(inputChannel) && inputChannel > 0, 'inputChannel must be a positive integer value!'); + self.pulseBuilder = pulseBuilder; self.inputChannel = inputChannel; self.meanErrorThreshold = self.pulseBuilder.meanErrorThreshold; From 1b03f933f6fad06bb0873a71d44d693f29528e6c Mon Sep 17 00:00:00 2001 From: Lukas Prediger Date: Tue, 24 Mar 2015 12:09:38 +0100 Subject: [PATCH 24/24] Removed old classes and adapted the hardwarePulseTest.m to use the new ones accordingly. --- Testing/DSIOTestSetup.m | 63 --------------------- Testing/DefaultTestSetup.m | 105 ----------------------------------- Testing/RSAIOTestSetup.m | 78 -------------------------- Testing/RawIOTestSetup.m | 12 ---- Testing/TableMaskTestSetup.m | 68 ----------------------- Testing/TestSetup.m | 37 ------------ Testing/hardwarePulseTest.m | 5 +- 7 files changed, 3 insertions(+), 365 deletions(-) delete mode 100644 Testing/DSIOTestSetup.m delete mode 100644 Testing/DefaultTestSetup.m delete mode 100644 Testing/RSAIOTestSetup.m delete mode 100644 Testing/RawIOTestSetup.m delete mode 100644 Testing/TableMaskTestSetup.m delete mode 100644 Testing/TestSetup.m diff --git a/Testing/DSIOTestSetup.m b/Testing/DSIOTestSetup.m deleted file mode 100644 index b77933f..0000000 --- a/Testing/DSIOTestSetup.m +++ /dev/null @@ -1,63 +0,0 @@ -classdef DSIOTestSetup < DefaultTestSetup - - properties (Constant, GetAccess = private) - test_start = 400; - test_end = 600; - test_period = 1000; - test_iterations = 100; - end - - methods (Access = public) - - function obj = DSIOTestSetup() - obj = obj@DefaultTestSetup(DSIOTestSetup.test_iterations * DSIOTestSetup.test_period, 1, 1e-4, 1e-3); - end - - end - - methods (Access = protected) - - function initDAC(self) - mask = struct('begin', self.test_start, 'end', self.test_end, 'period', self.test_period, 'hwChannel', self.inputChannel); - - self.dac = ATS9440(1); - - self.dac.samprate = 100e6; %samples per second - samplesInPeriod = self.test_period * self.dac.samprate / 1e6; - - self.dac.masks = { mask }; - - self.dac.useAsTriggerSource(); - - self.dac.configureMeasurement(samplesInPeriod, self.test_iterations, 1, 4 + inputChannel); - end - - function initPulseGroup(self) - dt = 1; % us - - rng(42); - - randomValues = rand(1, self.test_iterations) * 2 - 1; - self.expectedData = randomValues; - - self.pulsegroup.pulses = zeros(1,self.test_iterations); - self.pulsegroup.chan = 1; - self.pulsegroup.name = 'DSIOTestPulseGroup'; - self.pulsegroup.ctrl = 'notrig'; - - for i = 1:self.test_iterations - randomValue = randomValues(i); - pulse.data.pulsetab = zeros(2, 6); - pulse.data.pulsetab(1,:) = [0, self.test_start - dt, self.test_start, self.test_end, self.test_end + dt, self.test_period]; - pulse.data.pulsetab(2,:) = [-randomValue, -randomValue, randomValue, randomValue, -randomValue, -randomValue]; - pulse.name = sprintf('DSIOTestPulse%i', i); - self.pulsegroup.pulses(i) = plsreg(pulse); - end - - plsdefgrp(self.pulsegroup); - end - - end - -end - diff --git a/Testing/DefaultTestSetup.m b/Testing/DefaultTestSetup.m deleted file mode 100644 index 011e352..0000000 --- a/Testing/DefaultTestSetup.m +++ /dev/null @@ -1,105 +0,0 @@ -classdef DefaultTestSetup < TestSetup - - properties(GetAccess = protected, SetAccess = protected) - pulsegroup; - dac; - vawg; - end - - properties(GetAccess = public, SetAccess = protected) - expectedData = []; - measuredData = []; - end - - methods (Access = public) - - function obj = DefaultTestSetup(duration, inputChannel, meanErrorThreshold, singleErrorThreshold) - obj = obj@TestSetup(duration, inputChannel, meanErrorThreshold, singleErrorThreshold); - end - - function init(self) - % pulse to test - self.initPulseGroup(); - - % awg to test - self.initVAWG(); - - self.vawg.add(self.pulsegroup.name); - self.vawg.setActivePulseGroup(self.pulsegroup.name); - - % DAQ card - self.initDAC(); - - self.vawg.arm(); - end - - function success = run(self) - self.dac.issueTrigger(); - - while self.vawg.playbackInProgress() - pause(1); - fprintf('Waiting for playback to finish...\n'); - end - - self.measuredData = self.dac.getResult(self.inputChannel); - success = self.evaluate(); - end - - end - - methods (Access = protected) - - function initVAWG(self) - self.vawg = VAWG(); - - awg = PXDAC_DC('messrechnerDC',1); - awg.setOutputVoltage(1,1); - - self.vawg.addAWG(awg); - self.vawg.createVirtualChannel(awg,1,1); - end - - function initDAC(self) - self.dac = ATS9440(1); - - self.dac.samprate = 100e6; %samples per second - sis = self.duration * self.dac.samprate / 1e6; % samples in scanline - - self.dac.useAsTriggerSource(); - - self.dac.configureMeasurement(1, sis, 1, self.inputChannel); - end - - function initPulseGroup(self) - N = 1000; - rng(42); - - pulse.data.pulsetab = zeros(2, N); - pulse.data.pulsetab(1,:) = linspace(1, self.duration, N); - pulse.data.pulsetab(2,:) = rand(1, N) * 2 - 1; - - pulse.name = 'hardwareTestPulse'; - - self.pulsegroup.pulses = plsreg(pulse); - self.pulsegroup.nrep = 1; - self.pulsegroup.name = 'hardwareTestPulseGroup'; - self.pulsegroup.chan = 1; - self.pulsegroup.ctrl = 'notrig'; - - plsdefgrp(self.pulsegroup); - - p = plstowf(pulse); - self.expectedData = p.data.wf; - end - - function success = evaluate(self) - err = self.measuredData - self.expectedData; % error signal - rms = std(err,0); % average error per sample - maxerr = max(abs(err)); % maximum single error - success = (rms < self.meanErrorThreshold) && (maxerr < self.singleErrorThreshold); - end - - end - -end - diff --git a/Testing/RSAIOTestSetup.m b/Testing/RSAIOTestSetup.m deleted file mode 100644 index df92d76..0000000 --- a/Testing/RSAIOTestSetup.m +++ /dev/null @@ -1,78 +0,0 @@ -classdef RSAIOTestSetup < DefaultTestSetup - - properties (Constant, GetAccess = private) - test_start = 400; - test_end = 600; - test_period = 1000; - test_iterations = 100; - test_random_points = 20; % must be < test_end - test_start - end - - methods (Access = public) - - function obj = RSAIOTestSetup() - obj = obj@DefaultTestSetup(RSAIOTestSetup.test_iterations * RSAIOTestSetup.test_period, 1, 1e-4); - end - - end - - methods (Access = protected) - - function initDAC(self) - mask = struct('begin', self.test_start, 'end', self.test_end, 'period', self.test_period, 'hwChannel', self.inputChannel); - - self.dac = ATS9440(1); - - self.dac.samprate = 100e6; %samples per second - samplesInPeriod = self.test_period * self.dac.samprate / 1e6; - - self.dac.masks = { mask }; - - self.dac.useAsTriggerSource(); - - self.dac.configureMeasurement(samplesInPeriod, self.test_iterations, 1, 8 + inputChannel); - end - - function initPulseGroup(self) - dt = 1; % us - - rng(42); - - randomValues = rand(1, self.test_random_points) * 2 - 1; - - pulse.data.pulsetab = zeros(2, 4 + self.test_random_points); - - pulse.data.pulsetab(1,:) = [0, self.test_start - dt, ... - linspace(self.test_start, self.test_end, self.test_random_points), ... - self.test_end + dt, self.test_period]; - - pulse.data.pulsetab(2,:) = [-randomValues(1), -randomValues(1), ... - randomValues, ... - -randomValues(end), -randomValues(end)]; - - pulse.name = 'RSAIOTestPulse'; - - self.pulsegroup.pulses = plsreg(pulse); - self.pulsegroup.chan = 1; - self.pulsegroup.nrep = self.test_iterations; - self.pulsegroup.name = 'RSAIOTestPulseGroup'; - self.pulsegroup.ctrl = 'notrig'; - - plsdefgrp(self.pulsegroup); - - mainPulse.data.pulsetab = zeros(2, self.test_random_points); - mainPulse.data.pulsetab(1, :) = linspace(0, self.test_end - self.test_start, self.test_random_points); - mainPulse.data.pulsetab(2, :) = randomValues; - - mainPulse = plstowf(mainPulse); - self.expectedData = mainPulse.data.wf; - - %elementalPulse.data.wf - % TODO: calculate expected measurement signal - end - - - end - -end - diff --git a/Testing/RawIOTestSetup.m b/Testing/RawIOTestSetup.m deleted file mode 100644 index 6198aaa..0000000 --- a/Testing/RawIOTestSetup.m +++ /dev/null @@ -1,12 +0,0 @@ -classdef RawIOTestSetup < DefaultTestSetup - - methods(Access = public) - - function obj = RawIOTestSetup() - obj = obj@DefaultTestSetup(1000000, 1, 1e-3, .5e-2); - end - - end - -end - diff --git a/Testing/TableMaskTestSetup.m b/Testing/TableMaskTestSetup.m deleted file mode 100644 index 99e0d07..0000000 --- a/Testing/TableMaskTestSetup.m +++ /dev/null @@ -1,68 +0,0 @@ -classdef TableMaskTestSetup < DefaultTestSetup - - properties (Constant, GetAccess = private) - test_iterations = 100; - test_start = 400 + floor(linspace(0,200,test_iterations)); - test_end = 600 + floor(linspace(0,200,test_iterations)); - test_period = 1000; - end - - methods (Access = public) - - function obj = TableMaskTestSetup() - obj = obj@DefaultTestSetup(TableMaskTestSetup.test_iterations * TableMaskTestSetup.test_period, 1, 1e-4, 1e-3); - end - - end - - methods (Access = protected) - - function initDAC(self) - % prepare a table mask - mask = struct('type', 'Table Mask',... - 'begin', self.test_start,... - 'end', self.test_end,... - 'period', self.test_period,... - 'hwChannel', self.inputChannel); - - self.dac = ATS9440(1); - - self.dac.samprate = 100e6; %samples per second - samplesInPeriod = self.test_period * self.dac.samprate / 1e6; - - self.dac.masks = { mask }; - - self.dac.useAsTriggerSource(); - - self.dac.configureMeasurement(samplesInPeriod, self.test_iterations, 1, 4 + inputChannel); - end - - function initPulseGroup(self) - dt = 1; % us - - rng(42); - - randomValues = rand(1, self.test_iterations) * 2 - 1; - self.expectedData = randomValues; - - self.pulsegroup.pulses = zeros(1,self.test_iterations); - self.pulsegroup.chan = 1; - self.pulsegroup.name = 'DSIOTestPulseGroup'; - self.pulsegroup.ctrl = 'notrig'; - - for i = 1:self.test_iterations - randomValue = randomValues(i); - pulse.data.pulsetab = zeros(2, 6); - pulse.data.pulsetab(1,:) = [0, self.test_start(i) - dt, self.test_start(i), self.test_end(i), self.test_end(i) + dt, self.test_period]; - pulse.data.pulsetab(2,:) = [-randomValue, -randomValue, randomValue, randomValue, -randomValue, -randomValue]; - pulse.name = sprintf('DSIOTestPulse%i', i); - self.pulsegroup.pulses(i) = plsreg(pulse); - end - - plsdefgrp(self.pulsegroup); - end - - end - -end - diff --git a/Testing/TestSetup.m b/Testing/TestSetup.m deleted file mode 100644 index cfe4f89..0000000 --- a/Testing/TestSetup.m +++ /dev/null @@ -1,37 +0,0 @@ -classdef TestSetup - - properties(SetAccess = private, GetAccess = protected) - duration; - inputChannel; - meanErrorThreshold; % tolerated RMS error threshold in Volts; value strongly depends on the test - singleErrorThreshold; % tolerated single maximum error - end - - methods(Access = protected) - function obj = TestSetup(duration, inputChannel, meanErrorThreshold, singleErrorThreshold) - obj.duration = duration; - obj.inputChannel = inputChannel; - obj.meanErrorThreshold = meanErrorThreshold; - obj.singleErrorThreshold = singleErrorThreshold; - end - end - - methods(Abstract, Access = public) - init(self); % initialize the test - success = run(self); % run the test and return whether or not it was successful - end - - methods(Abstract, Access = protected) - - initVAWG(self); % initialize the arbitrary waveform generator - - initDAC(self); % initialize the data acquisition device - - initPulseGroup(self); % initializes pulses and calculates expected data - - success = evaluate(self); - - end - -end - diff --git a/Testing/hardwarePulseTest.m b/Testing/hardwarePulseTest.m index 80bdd6d..0dabe90 100644 --- a/Testing/hardwarePulseTest.m +++ b/Testing/hardwarePulseTest.m @@ -2,8 +2,9 @@ function hardwarePulseTest() InitializePulseData; - testSetup = RawIOTestSetup(); + pulseBuilder = RandomTestPulseBuilder(); + configurationProvider = RawIOTestConfigurationProvider(1, pulseBuilder); + testSetup = IOTestDriver(configurationProvider); - testSetup.init(); testSetup.run(); end \ No newline at end of file