Collect Streaming Data in MATLAB via PicoScope 4824A

Mar. 13, 2023

本博客学习 PicoScope 4000 Series A API MATLAB Generic Instrument Driver - Github 所提供的一个收集Streaming data的示例:PS4000A_ID_Streaming_Example.m.

Load configuration information

1
PS4000aConfig;

相比于博客Connecting PicoScope 4824A in MATLAB - What a starry night~中加载配置信息的方式,这种方式会在工作空间中得到的输出变量更多,分别是:

  • ps4000aConfigInfo,结构体

  • ps4000aEnuminfo,结构体

  • ps4000aMethodinfo,结构体

  • ps4000aStructs,结构体

  • ps4000aThunkLibName,字符串,是动态链接库ps4000a_thunk_pcwin64.dll的地址:

    1
    
     'C:\...\MathWorks\MATLAB Add-Ons\Hardware Supports\PicoScope 4000 Series A API MATLAB Generic Instrument Driver\win64\ps4000a_thunk_pcwin64'
    


Parameter definitions

定义表示A通道和B通道的变量:

1
2
3
4
%% Parameter definitions
% Define any parameters that might be required throughout the script.
channelA = ps4000aEnuminfo.enPS4000AChannel.PS4000A_CHANNEL_A;
channelB = ps4000aEnuminfo.enPS4000AChannel.PS4000A_CHANNEL_B;
1
2
3
4
5
>> channelA,channelB
channelA =
     0
channelB =
     1

Device connnection

连接设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
%% Device connection
if (exist('ps4000aDeviceObj','var') && ps4000aDeviceObj.isvalid && strcmp(ps4000aDeviceObj.status,'open'))
    openDevice = questionDialog(['Device object ps4000aDeviceObj has an open connection. ' ...
        'Do you wish to close the connection and continue?'], ...
        'Device Object Connection Open');
    if (openDevice == PicoConstants.TRUE) 
        % Close connection to device
        disconnect(ps4000aDeviceObj);
        delete(ps4000aDeviceObj);
    else
        % Exit script if User 
        return;
    end
end
ps4000aDeviceObj = icdevice('picotech_ps4000a_generic','ObjectVisibility','on'); 
% Connect device
connect(ps4000aDeviceObj);

详见博客:Connecting PicoScope 4824A in MATLAB - What a starry night~.


Display unit information

1
2
3
4
5
%% Display unit information
[infoStatus, unitInfo] = invoke(ps4000aDeviceObj, 'getUnitInfo');

disp('Device information:-')
disp(unitInfo);

其中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>> [infoStatus, unitInfo] = invoke(ps4000aDeviceObj, 'getUnitInfo')
infoStatus =
  11×1 uint32 column vector
   0
   0
   0
   0
   0
   0
   0
   0
   0
   0
   0

unitInfo =
  11×1 cell array
    {'Driver version: 2.1.82.3072'}
    {'USB version: 3.0'           }
    {'Hardware version: 1'        }
    {'Variant: 4824A'             }
    {'Batch & Serial: JW851/0014' }
    {'Cal. Date: 15Jul22'         }
    {'Kernel version: 1.0'        }
    {'Digital HW version: 1'      }
    {'Analogue HW version: 1'     }
    {'Firmware 1: 1.7.5.0'        }
    {'Firmware 2: 1.0.11.0'       }


Channel setup

ps4000aSetChannel function

接下来,需要设置通道的参数,使除了通道A和通道B之外的其他通道都处于关闭状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
%% Channel setup
% All channels are enabled by default - switch off all except Channels A and B. 
% Channel A
channelSettings(1).enabled          = PicoConstants.TRUE; % 1
channelSettings(1).coupling         = ps4000aEnuminfo.enPS4000ACoupling.PS4000A_DC;% 1
channelSettings(1).range            = ps4000aEnuminfo.enPS4000ARange.PS4000A_2V;% 7
channelSettings(1).analogueOffset   = 0.0;

% Channel B
channelSettings(2).enabled          = PicoConstants.TRUE;
channelSettings(2).coupling         = ps4000aEnuminfo.enPS4000ACoupling.PS4000A_DC;
channelSettings(2).range            = ps4000aEnuminfo.enPS4000ARange.PS4000A_2V;
channelSettings(2).analogueOffset   = 0.0;

% Channel C
channelSettings(3).enabled          = PicoConstants.FALSE;
channelSettings(3).coupling         = ps4000aEnuminfo.enPS4000ACoupling.PS4000A_DC;
channelSettings(3).range            = ps4000aEnuminfo.enPS4000ARange.PS4000A_2V;
channelSettings(3).analogueOffset   = 0.0;

% Channel D
channelSettings(4).enabled          = PicoConstants.FALSE;
channelSettings(4).coupling         = ps4000aEnuminfo.enPS4000ACoupling.PS4000A_DC;
channelSettings(4).range            = ps4000aEnuminfo.enPS4000ARange.PS4000A_2V;
channelSettings(4).analogueOffset   = 0.0;

% Channel E
channelSettings(5).enabled          = PicoConstants.FALSE;
channelSettings(5).coupling         = ps4000aEnuminfo.enPS4000ACoupling.PS4000A_DC;
channelSettings(5).range            = ps4000aEnuminfo.enPS4000ARange.PS4000A_2V;
channelSettings(5).analogueOffset   = 0.0;

% Channel F
channelSettings(6).enabled          = PicoConstants.FALSE;
channelSettings(6).coupling         = ps4000aEnuminfo.enPS4000ACoupling.PS4000A_DC;
channelSettings(6).range            = ps4000aEnuminfo.enPS4000ARange.PS4000A_2V;
channelSettings(6).analogueOffset   = 0.0;

% Channel G
channelSettings(7).enabled          = PicoConstants.FALSE;
channelSettings(7).coupling         = ps4000aEnuminfo.enPS4000ACoupling.PS4000A_DC;
channelSettings(7).range            = ps4000aEnuminfo.enPS4000ARange.PS4000A_2V;
channelSettings(7).analogueOffset   = 0.0;

% Channel H
channelSettings(8).enabled          = PicoConstants.FALSE;
channelSettings(8).coupling         = ps4000aEnuminfo.enPS4000ACoupling.PS4000A_DC;
channelSettings(8).range            = ps4000aEnuminfo.enPS4000ARange.PS4000A_2V;
channelSettings(8).analogueOffset   = 0.0;

% Obtain the number of analog channels on the device from the driver
numChannels = get(ps4000aDeviceObj, 'channelCount');

for ch = 1:numChannels
    status.setChannelStatus(ch) = invoke(ps4000aDeviceObj, 'ps4000aSetChannel', ...
        (ch - 1), channelSettings(ch).enabled, ...
        channelSettings(ch).coupling, channelSettings(ch).range, ...
        channelSettings(ch).analogueOffset);
end

其中channelSettings结构体的内容是:

image-20230227083252181

这部分的内容和博客Collecting Block Data in MATLAB via PicoScope 4824A: Set channels中设置通道的方式是类似的,后者的代码更加紧凑,如果对于ps4000aSetChannel函数的输入参数中常数所代表的含义比较熟悉,可以后面这种方式。

getChannelInputRangeAndUnits function

获取通道A和通道B的输出范围和输出单位,供后续使用:

1
2
3
% Obtain the range and units for each enabled channel. For the PicoScope 4824, this will be in millivolts.
[chARange, chAUnits] = invoke(ps4000aDeviceObj, 'getChannelInputRangeAndUnits', ps4000aEnuminfo.enPS4000AChannel.PS4000A_CHANNEL_A);
[chBRange, chBUnits] = invoke(ps4000aDeviceObj, 'getChannelInputRangeAndUnits', ps4000aEnuminfo.enPS4000AChannel.PS4000A_CHANNEL_B);

其中,对于通道A和通道B,getChannelInputRangeAndUnits的输入参数分别为:

1
2
3
4
5
6
7
>> ps4000aEnuminfo.enPS4000AChannel.PS4000A_CHANNEL_A
ans =
     0

>> ps4000aEnuminfo.enPS4000AChannel.PS4000A_CHANNEL_B
ans =
     1

输出参数分别为:

1
2
3
4
5
6
7
8
9
10
11
>> chARange, chAUnits
chARange =
        2000
chAUnits =
    'mV'

>> chBRange, chBUnits
chBRange =
        2000
chBUnits =
    'mV'

maxADCValue property

1
2
3
4
% Obtain the maximum Analog Digital Converter Count value from the driver
% - this is used for scaling values returned from the driver when data is
% collected.
maxADCCount = double(get(ps4000aDeviceObj, 'maxADCValue'));
1
2
3
>> maxADCCount
maxADCCount =
       32767

从PicoScope中读取出的数据是模拟量,是由整数表示的。在后面的代码中,会使用adc2mv函数转换为单位为mv的、以浮点数表示的值,在转换时就会用到maxADCCount的值。


Set Data Buffers

设置数据缓存区。

1
2
3
4
5
%% Set data buffers
% Data buffers for Channel A and B - buffers should be set with the
% (lib)ps400a shared library, and these *MUST* be passed with application
% buffers to the wrapper shared library. This will ensure that data is
% correctly copied from the shared library buffers for later processing.

Constants for data buffers: overviewBufferSize, segmentIndex, and ratioMode

1
2
3
overviewBufferSize  = 250000; % Size of the buffer(s) to collect data from the driver's buffer(s).
segmentIndex        = 0;   
ratioMode           = ps4000aEnuminfo.enPS4000ARatioMode.PS4000A_RATIO_MODE_NONE; % rationMode = 0;

Buffers to be passed to the driver: pDriverBufferChA, pDriverBufferChB

1
2
3
% Buffers to be passed to the driver
pDriverBufferChA = libpointer('int16Ptr',zeros(overviewBufferSize,1,'int16'));
pDriverBufferChB = libpointer('int16Ptr',zeros(overviewBufferSize,1,'int16'));
1
2
3
4
5
6
7
>> pDriverBufferChA
pDriverBufferChA =
libpointer

>> pDriverBufferChB
pDriverBufferChB =
libpointer

这两个变量都是指针lib.pointer类的变量:

1
2
3
4
>> whos pDriverBufferChA pDriverBufferChB
  Name                  Size            Bytes  Class          Attributes
  pDriverBufferChA      1x1                    lib.pointer              
  pDriverBufferChB      1x1                    lib.pointer     

之后,使用ps4000aSetDataBuffer函数设置通道A和通道B的buffer:

1
2
3
4
status.setDataBufferChA = invoke(ps4000aDeviceObj, 'ps4000aSetDataBuffer', ...
    channelA, pDriverBufferChA, overviewBufferSize, segmentIndex, ratioMode);% status.setDataBufferChA = 0;
status.setDataBufferChB = invoke(ps4000aDeviceObj, 'ps4000aSetDataBuffer', ...
   channelB, pDriverBufferChB, overviewBufferSize, segmentIndex, ratioMode);% status.setDataBufferChB = 0;

Application Buffers: pAppBufferChA and pAppBufferChB

前面一步设置了Driver buffer,接下来需要设置Application buffer,这个buffer保存着从driver中暂时复制出的数据。设置与前面的一步也是类似的,但是需要特别针对Streaming mode进行设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
% Application Buffers - these are for temporarily copying data from the driver.
pAppBufferChA = libpointer('int16Ptr',zeros(overviewBufferSize,1)); % overviewBufferSize = 250000;
pAppBufferChB = libpointer('int16Ptr',zeros(overviewBufferSize,1));

% Streaming properties and functions are located in the Instrument Driver's Streaming group.
streamingGroupObj = get(ps4000aDeviceObj, 'Streaming');
streamingGroupObj = streamingGroupObj(1);

% Register application buffer and driver buffers (with the wrapper driver).
status.setAppAndDriverBuffersA = invoke(streamingGroupObj, 'setAppAndDriverBuffers', channelA, ...
    pAppBufferChA, pDriverBufferChA, overviewBufferSize); % status.setAppAndDriverBuffersA = 0;
status.setAppAndDriverBuffersB = invoke(streamingGroupObj, 'setAppAndDriverBuffers', channelB, ...
   pAppBufferChB, pDriverBufferChB, overviewBufferSize);  % status.setAppAndDriverBuffersB = 0;

在后面从Application buffer读取数据时,会使用索引在这两个指针中取值:

1
2
3
% Convert data values from the application buffer(s) - in this example
bufferChAmV = adc2mv(pAppBufferChA.Value(firstValuePosn:lastValuePosn),chARange,maxADCCount);
bufferChBmV = adc2mv(pAppBufferChB.Value(firstValuePosn:lastValuePosn),chBRange,maxADCCount);


Streaming Mode Settings and Other Settings

Streaming interval: streamingInterval property

设置Streaming mode的采样间隔。这里就采用了默认的1e-6的采样间隔;除此之外,用户可以通过设置streamingGroupObj类的streamingInterval的属性进行设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
% Use default value for streaming interval which is 1e-6 for 1 MS/s.
% Collect data for 1 second with auto stop. The maximum array size will
% depend on the PC's resources. For further information, type |memory| in
% the MATLAB Command Window and press Enter.
%
% To change the sample interval set the |streamingInterval| property of the
% |Streaming| group object. The call to |ps4000aRunStreaming()| will output the actual
% sampling interval used by the driver.

% For 200 kS/s, specify 5 us
%set(streamingGroupObj, 'streamingInterval', 5e-6);

% For 10 MS/s, specify 100 ns (0.1 us)
%set(streamingGroupObj, 'streamingInterval', 100e-9);

numPreTriggerSamples and numPostTriggerSamples properties

设置触发前后需要采集的样本点数:

1
2
3
4
% Set the number of pre- and post-trigger samples
% If no trigger is set 'numPreTriggerSamples' is ignored
set(ps4000aDeviceObj, 'numPreTriggerSamples', 0);
set(ps4000aDeviceObj, 'numPostTriggerSamples', 2000000);

如果没有使用trigger,则不需要设置'numPreTriggerSamples'属性,但是'numPostTriggerSamples'仍然是需要的,后面会再一次提到这一点,这里的设置是和streaming mode的auto stop的行为(即和maxSamples)有关。

autoStop property

可以设置Streaming mode是否停止(默认是自动停止)

1
2
% The autoStop parameter can be set to false (0).
%set(streamingGroupObj, 'autoStop', PicoConstants.FALSE);

默认情况的Streaming mode,会在收集到maxSamples(后面会讲到)个样本点后停止;如果使用set(streamingGroupObj, 'autoStop', PicoConstants.FALSE);的代码,则数据收集是不会停止的,直到点击stop按钮(示例后面会设置一个GUI的stop按钮)。

downSampleRatio and downSampleRatioMode

Streaming mode中关于下采样的设置

1
2
3
% Set other streaming parameters
downSampleRatio     = 1;
downSampleRatioMode = ps4000aEnuminfo.enPS4000ARatioMode.PS4000A_RATIO_MODE_NONE; % downSampleRatioMode = 0;

maxSamples

如果获取数据的时候(1)没有使用autoStop flag或者(2)使用trigger停止收集,例如像本示例仅采用2 s的数据,则收集到的数据大小是可以预期的,为这样一个数组预分配空间就是很更加高效的。

1
2
3
4
5
6
7
% Defined buffers to store data collected from the channels.
% If capturing data without using the autoStop flag, or if using a trigger 
% with the autoStop flag, allocate sufficient space (1.5 times the size is 
% shown below) to allow for pre-trigger data. Pre-allocating the array is 
% more efficient than using vertcat to combine data.
maxSamples = get(ps4000aDeviceObj, 'numPreTriggerSamples') + ...
    get(ps4000aDeviceObj, 'numPostTriggerSamples');
1
2
3
>> maxSamples
maxSamples =
     2000000

在预分配数组大小时,需要考虑下采样的设置:

1
2
3
% Take into account the downsampling ratio mode - required if collecting
% data without a trigger and using the autoStop flag. 
% finalBufferLength = round(1.5 * maxSamples / downSampleRatio);

虽然注释中提到预分配的空间要是sufficient,采用1.5倍的设置的大小,但是实际的代码中并没有这么做,在下一节创建Final buffer时直接采用与预期的大小一样的数组大小,即maxSamples = 2000000

Create final data buffers: pBufferChAFinal and pBufferChAFinal

上面在创建Application buffer时提到,Application buffer只是暂时的一个buffer,它其中的数据需要传入到Final buffer中保存,因此同样需要为A通道和B通道创建指针:

1
2
pBufferChAFinal = libpointer('int16Ptr', zeros(maxSamples, 1, 'int16'));
pBufferChBFinal = libpointer('int16Ptr', zeros(maxSamples, 1, 'int16'));
1
2
3
4
5
6
7
>> pBufferChAFinal
pBufferChAFinal =
libpointer

>> pBufferChBFinal
pBufferChBFinal =
libpointer

和Application buffer类似,在后面向Final buffer中传入数据时,同样也是通过索引进行操作:

1
2
3
% Copy data into the final buffer(s).
pBufferChAFinal.Value(previousTotal + 1:totalSamples) = bufferChAmV;
pBufferChBFinal.Value(previousTotal + 1:totalSamples) = bufferChBmV;

Decide whether plot live streaming data: plotLiveData

1
2
3
4
5
6
7
% Prompt User to indicate if they wish to plot live streaming data.
plotLiveData = questionDialog('Plot live streaming data?', 'Streaming Data Plot');
if (plotLiveData == PicoConstants.TRUE)
    disp('Live streaming data collection with second plot on completion.');
else
    disp('Streaming data plot on completion.');
end

image-20230227081059416

Start streaming data collection: ps4000aRunStreaming function

1
2
3
4
% Start streaming data collection.
[status.runStreaming, actualSampleInterval, sampleIntervalTimeUnitsStr] = ...
    invoke(streamingGroupObj, 'ps4000aRunStreaming', downSampleRatio, ...
    downSampleRatioMode, overviewBufferSize);
1
2
3
4
5
6
7
8
9
10
>> status.runStreaming, actualSampleInterval, sampleIntervalTimeUnitsStr
ans =
     0

actualSampleInterval =
  uint32
   1

sampleIntervalTimeUnitsStr =
    'us'

Initialize variables to be used when collecting the data: isAutoStopSet, newSamples, previousTotal, totalSamples, startIndex, hasTriggered, and triggeredAtIndex

1
2
3
4
5
6
7
8
% Variables to be used when collecting the data
isAutoStopSet       = PicoConstants.FALSE; % 0
newSamples          = 0; % Number of new samples returned from the driver.
previousTotal       = 0; % The previous total number of samples.
totalSamples        = 0; % Total number of samples captured by the device.
startIndex          = 0; % Start index of data in the buffer returned (zero-based).
hasTriggered        = 0; % To indicate if trigger event has occurred.
triggeredAtIndex    = 0; % The index in the overall buffer where the trigger occurred (zero-based).

Display a ‘Stop’ button

创建一个停止按钮:

1
2
% Display a 'Stop' button.
[~, stopFig.h] = stopButton(); % `stopButton` from PicoScope Support Toolbox   

image-20230227080512640

stopButton()函数来自PicoScope Support Toolbox,它有两个输出,分别是figure的和一个stop按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function [f, h] = stopButton()
% STOPBUTTON Displays a stop button.
%   [F, H] = STOPBUTTON() displays a Stop button to check abort data collection - 
%   based on MathWorks solution 1-15JIQ (http://uk.mathworks.com/matlabcentral/answers/100558-how-do-i-set-up-a-uicontrol-callback-to-interrupt-a-routine) and MATLAB Central Forum.
%   F is the handle to the button's figure, H is the uicontrol object.
%
%   Copyright: © 2014 - 2015 Pico Technology Ltd. All rights reserved.
%
%   See also FIGURE, UICONTROL.
    f = figure('Name', 'Stop Button', 'menubar', 'none',...
              'units', 'pix',...
              'pos', [400 400 100 50]);
    h = uicontrol('string', 'STOP', ...
                'callback', 'setappdata(gcf, ''run'', 0)', 'units', 'pixels',...
                'position',[10 10 100 30]);
end

可以在源代码中看到,STOP按钮的回调函数是,'setappdata(gcf, ''run'', 0)',表示将figure中的run 变量赋零。

setappdata()是MATLAB所提供的函数(setappdata - MathWorks),其作用是:store data in a UI. 但是这个数据在UI中是不显示的,比如对于官方所提供的一个示例:

1
2
3
f = figure;
val = date;
setappdata(f,'todaydate',val)

image-20230227102020211

UI中是不显示该数据的,但同时该UI的属性中也找不到这个值,不太清楚这个值具体保存在哪里,但是可以通过getappdata()函数(getappdata - MathWorks)重新获取到这个值:

1
2
3
>> getappdata(f,'todaydate')
ans =
    '27-Feb-2023'

首先需要对UI中的run变量幅值为1,并且在外部使用flag变量表示:

1
2
flag = 1; % Use flag variable to indicate if the stop button has been clicked (0).
setappdata(gcf, 'run', flag);

Figure for plotting live streaming data

创建用于绘制live streaming data的figure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
% Plot Properties - these are for displaying data as it is collected. In
% this example, data is displayed in millivolts. 
if (plotLiveData == PicoConstants.TRUE)
    % Plot on a single figure 
    figure1 = figure('Name','PicoScope 4000 Series (A API) Example - Streaming Mode Capture', ...
         'NumberTitle','off');
     axes1 = axes('Parent', figure1);

    % Estimate x-axis limit to try and avoid using too much CPU resources
    % when drawing - use max voltage range selected if plotting multiple
    % channels on the same graph.
    xlim(axes1, [0 (actualSampleInterval * maxSamples)]);

    yRange = max(chARange, chBRange);
    ylim(axes1,[(-1 * yRange) yRange]);

    hold(axes1,'on');
    grid(axes1, 'on');

    title(axes1, 'Live Streaming Data Capture');
    xLabelStr = strcat('Time (', sampleIntervalTimeUnitsStr, ')');
    xlabel(axes1, xLabelStr);
    ylabel(axes1, getVerticalAxisLabel(chAUnits));
end

需要注意的是横坐标范围的设置:xlim(axes1, [0 (actualSampleInterval * maxSamples)]);

image-20230227104140854


Collecting Data and Live Plot

Two-levels nested while loop and getStreamingLatestValues function

在进行完以上的设置之后,就可以循环地采集数据并绘制实时数据采集的曲线。其中,循环中的数据采集是通过getStreamingLatestValues函数进行的:

1
2
3
...
status.getStreamingLatestValues = invoke(streamingGroupObj, 'getStreamingLatestValues');
...

它包裹在一个两层的while循环当中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
while (isAutoStopSet == PicoConstants.FALSE && status.getStreamingLatestValues == PicoStatus.PICO_OK)
    ready = PicoConstants.FALSE; % PicoConstants.FALSE = 0;
   
    while (ready == PicoConstants.FALSE)
       % Collect data
       status.getStreamingLatestValues = invoke(streamingGroupObj, 'getStreamingLatestValues');

       % Chek whether ready
       ready = invoke(streamingGroupObj, 'isReady');

       % Give option to abort from here
       flag = getappdata(gcf, 'run');
       drawnow;
       
       if (flag == 0)
            disp('STOP button clicked - aborting data collection.')
            break
       end
       if (plotLiveData == PicoConstants.TRUE)
            drawnow;
       end
    end
    ...
    % Check if auto stop has occurred.
    isAutoStopSet = invoke(streamingGroupObj, 'autoStopped');
    if (isAutoStopSet == PicoConstants.TRUE)
       disp('AutoStop: TRUE - exiting loop.');
       break
    end
   
    % Check if 'STOP' button pressed.
    flag = getappdata(gcf, 'run');
    drawnow;
    if (flag == 0)
        disp('STOP button clicked - aborting data collection.')
        break
    end
end

整个内层的循环代表一次数据收集,这些数据都暂时保存在Application buffer中,之后会在外层循环中进行处理(在后面会介绍)。内层的停止条件有两个,满足其中之一就停止收集数据:

(1)用户点击了停止按钮:变量flag的值为0

(2)Device object不是Ready的状态:变量ready的值为真;

外层循环代表整个的数据收集过程,其停止的条件有三个:

(1)设备自动停止收集:isAutoStopSet为真;

(2)Streaming mode工作状态不正常:status.getStreamingLatestValues不等于PicoStatus.PICO_OK

(3)用户点击了停止按钮:变量flag的值为0

Processing one-time collected data at outer while loop

内层循环中收集到的数据会保存在Application buffer中,用户需要在外层循环中使用availableData函数获取到新收集到的数据的大小newSamples和起始位置startIndex。如果收集到了数据,即newSamples的值大于0,则进入一个if条件结构处理数据,整体的代码结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
while (isAutoStopSet == PicoConstants.FALSE && status.getStreamingLatestValues == PicoStatus.PICO_OK)
    ...
    while (ready == PicoConstants.FALSE)
      ...
    end
    % Check for new data values
    [newSamples, startIndex] = invoke(streamingGroupObj, 'availableData');

    if (newSamples > 0)
        % Check if the scope has triggered
        [triggered, triggeredAt] = invoke(streamingGroupObj, 'isTriggerReady');

        if (triggered == PicoConstants.TRUE)
            % Adjust trigger position as MATLAB does not use zero-based indexing.
            bufferTriggerPosition = triggeredAt + 1;
            fprintf('Triggered - index in buffer: %d\n', bufferTriggerPosition);
            hasTriggered = triggered;

            % Set the total number of samples at which the device triggered.
            triggeredAtIndex = totalSamples + bufferTriggerPosition;
        end

        previousTotal = totalSamples;
        totalSamples  = totalSamples + newSamples;

        % Printing to console can slow down acquisition - use for demonstration.
        fprintf('Collected %d samples, startIndex: %d total: %d.\n', newSamples, startIndex, totalSamples);
        
        % Position indices of data in the buffer(s).
        firstValuePosn = startIndex + 1;
        lastValuePosn = startIndex + newSamples;
        
        % Convert data values from the application buffer(s) - in this example
        bufferChAmV = adc2mv(pAppBufferChA.Value(firstValuePosn:lastValuePosn),chARange,maxADCCount);
        bufferChBmV = adc2mv(pAppBufferChB.Value(firstValuePosn:lastValuePosn),chBRange,maxADCCount);

        % Process collected data further if required - this example plots
        % the data if the User has selected 'Yes' at the prompt.
        
        % Copy data into the final buffer(s).
        pBufferChAFinal.Value(previousTotal + 1:totalSamples) = bufferChAmV;
        pBufferChBFinal.Value(previousTotal + 1:totalSamples) = bufferChBmV;
        
        if (plotLiveData == PicoConstants.TRUE)
            % Time axis
            % Multiply by ratio mode as samples get reduced.
            time = (double(actualSampleInterval) * double(downSampleRatio)) * (previousTotal:(totalSamples - 1));
            plot(time, bufferChAmV, time, bufferChBmV);
        end

        % Clear variables.
        clear bufferChAmV bufferChBmV firstValuePosn lastValuePosn ...
            startIndex triggered triggerAt;
    end
	...
end

我们假定内层循环中的getStreamingLatestValues收集到了数据,并进入到if条件结构中进行处理。

isTriggerReady function

if条件结构的一开始,代码首先使用isTriggerReady函数判断是否存在trigger,并且记录下trigger的位置,并且在主代码的部分将这一信息打印出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
while (isAutoStopSet == PicoConstants.FALSE && status.getStreamingLatestValues == PicoStatus.PICO_OK)
    ...
    if (newSamples > 0)
        % Check if the scope has triggered
        [triggered, triggeredAt] = invoke(streamingGroupObj, 'isTriggerReady');

        if (triggered == PicoConstants.TRUE)
            % Adjust trigger position as MATLAB does not use zero-based indexing.
            bufferTriggerPosition = triggeredAt + 1;
            fprintf('Triggered - index in buffer: %d\n', bufferTriggerPosition);
            hasTriggered = triggered;

            % Set the total number of samples at which the device triggered.
            triggeredAtIndex = totalSamples + bufferTriggerPosition;
        end
    ...
    end
 end
 ...
if (hasTriggered == PicoConstants.TRUE)
    fprintf('Triggered at overall index: %d\n', triggeredAtIndex);
end
...

我在调试代码的时候并没有使用Trigger,无法查看这段代码的效果,但是我有一点不是很明白:如果在收集代码的过程存在多个Trigger,那么这样的代码设计只能记录下最后一个trigger的位置吗?看起来是这样的。

Update previousTotal and totalSamples variables

更新previousTotaltotalSamples 变量,这一步是为了向Final buffer中传递Application buffer中暂时保存的数据:

1
2
previousTotal = totalSamples;
totalSamples  = totalSamples + newSamples;

Obtain data from Application buffer

获取到Application buffer中数据所在的索引,并使用adc2mv函数将其转换为单位为mv的数据:

1
2
3
4
5
6
7
% Position indices of data in the buffer(s).
firstValuePosn = startIndex + 1;
lastValuePosn = startIndex + newSamples;

% Convert data values from the application buffer(s) - in this example
bufferChAmV = adc2mv(pAppBufferChA.Value(firstValuePosn:lastValuePosn),chARange,maxADCCount);
bufferChBmV = adc2mv(pAppBufferChB.Value(firstValuePosn:lastValuePosn),chBRange,maxADCCount);

其中,adc2mv函数的作用是:Converts raw Analog to Digital Converter count values to millivolts. 这一点很像像博客Correction and Supplement for Blog “An Arduino Sample Project Based on Temperature Sensor”:From Arduino Output Value to Resistance中所述的模拟输出与输出电压之间的线性映射。

转换为数组bufferChAmVbufferChBmV后,我们可以对数组进行处理,当然示例中仅仅是将数据绘制出来,而实际上在这里可以进行一些更复杂的操作:

1
2
% Process collected data further if required - this example plots
% the data if the User has selected 'Yes' at the prompt.

Pass collected data from Application buffer to Final buffer

之后,同样利用我们之前为Final buffer定义的指针pBufferChAFinalpBufferChBFinal,将数据Application buffer中传递到Final buffer中:

1
2
3
% Copy data into the final buffer(s).
pBufferChAFinal.Value(previousTotal + 1:totalSamples) = bufferChAmV;
pBufferChBFinal.Value(previousTotal + 1:totalSamples) = bufferChBmV;

之后,绘制live streaming data:

1
2
3
4
5
6
if (plotLiveData == PicoConstants.TRUE)
    % Time axis
    % Multiply by ratio mode as samples get reduced.
    time = (double(actualSampleInterval) * double(downSampleRatio)) * (previousTotal:(totalSamples - 1));
    plot(time, bufferChAmV, time, bufferChBmV);
end

并清除掉为Driver buffer所设置的变量:

1
2
clear bufferChAmV bufferChBmV firstValuePosn lastValuePosn ...
    startIndex triggered triggerAt;

从清除变量这一步可以清楚地体会到,Application buffer和Final buffer是不同的,在每次循环中Application buffer的数据都会清掉,而Final buffer则保存着所有的数据,因此,正如前面所指出的那样,最后能收集到多大体量的数据取决于PC的内存有多大。


Final process

在实现了实时数据的采集和实时绘制后,剩下的就是一些比较简单的、常规的操作和处理,这里不再赘述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
% Close the STOP button window
if (exist('stopFig', 'var'))
    close('Stop Button');
    clear stopFig;
end

if (plotLiveData == PicoConstants.TRUE)
    drawnow
end

if (hasTriggered == PicoConstants.TRUE)
    fprintf('Triggered at overall index: %d\n', triggeredAtIndex);
end

if (plotLiveData == PicoConstants.TRUE)
    % Take hold off the current figure
    hold off;
    movegui(figure1, 'west');
end
fprintf('\n');

%% Stop the device
% This function should be called regardless of whether the |autoStop|
% property is enabled or not.
status.stop = invoke(ps4000aDeviceObj, 'ps4000aStop');

%% Find the number of samples
% This is the number of samples held in the |(lib)ps4000a| shared library itself. The actual
% number of samples collected when using a trigger is likely to be greater.
[status.noOfStreamingValues, numStreamingValues] = invoke(streamingGroupObj, 'ps4000aNoOfStreamingValues');
fprintf('Number of samples available from the driver: %u.\n\n', numStreamingValues);

%% Process data
% Process all data if required

% Reduce size of arrays if required.
if (totalSamples < maxSamples)
    pBufferChAFinal.Value(totalSamples + 1:end) = [];
    pBufferChBFinal.Value(totalSamples + 1:end) = [];
end


% Retrieve data for the Channels.
channelAFinal = pBufferChAFinal.Value();
channelBFinal = pBufferChBFinal.Value();

% Plot the total data collected on another figure.
finalFigure = figure('Name','PicoScope 4000 Series (A API) Example - Streaming Mode Capture', ...
    'NumberTitle','off');
finalFigureAxes = axes('Parent', finalFigure);
hold(finalFigureAxes, 'on');

title('Streaming Data Capture (Final)');
xLabelStr = strcat('Time (', sampleIntervalTimeUnitsStr, ')');
xlabel(finalFigureAxes, xLabelStr);
ylabel(finalFigureAxes, 'Voltage (mV)');

% Find the maximum voltage range.
maxYRange = max(chARange, chBRange);
ylim(finalFigureAxes,[(-1 * maxYRange) maxYRange]);

% Calculated values for time axis, then plot.
timeAxis = (double(actualSampleInterval) * double(downSampleRatio)) * (0:length(channelAFinal) - 1);
plot(finalFigureAxes, timeAxis, channelAFinal, timeAxis, channelBFinal);

grid(finalFigureAxes, 'on');
legend(finalFigureAxes, 'Channel A', 'Channel B');
hold(finalFigureAxes, 'off');

movegui(finalFigureAxes, 'east');
%% Disconnect device
% Disconnect device object from hardware.
disconnect(ps4000aDeviceObj);
delete(ps4000aDeviceObj);


Discussion about Buffers

以上就是整个示例的所有代码,其中比较难理解的部分是buffer的设置和使用。对于A和B两个通道,代码都设置了三个buffer,分别是driver buffer(data buffer),application buffer和final buffer,并且设置的方式都是一样的,我们这里就以A通道为例整理一下这些buffer的作用。

Driver buffer pDriverBufferChA: ps4000aSetDataBuffer function

在设置Data buffer时,使用了ps4000aSetDataBuffer函数:

1
2
3
4
5
6
7
8
9
overviewBufferSize  = 250000; % Size of the buffer(s) to collect data from the driver's buffer(s).
segmentIndex        = 0;   
ratioMode           = ps4000aEnuminfo.enPS4000ARatioMode.PS4000A_RATIO_MODE_NONE;

% Buffers to be passed to the driver
pDriverBufferChA = libpointer('int16Ptr',zeros(overviewBufferSize,1,'int16'));

status.setDataBufferChA = invoke(ps4000aDeviceObj, 'ps4000aSetDataBuffer', ...
    channelA, pDriverBufferChA, overviewBufferSize, segmentIndex, ratioMode);

image-20230313124731594

改代码将buffer size设置为250000。

Application buffer pAppBufferChA: setAppAndDriverBuffers function

在设置application buffer时,使用的是函数 setAppAndDriverBuffers

1
2
3
4
5
6
7
8
9
10
% Application Buffers - these are for temporarily copying data from the driver.
pAppBufferChA = libpointer('int16Ptr',zeros(overviewBufferSize,1));

% Streaming properties and functions are located in the Instrument Driver's Streaming group.
streamingGroupObj = get(ps4000aDeviceObj, 'Streaming');
streamingGroupObj = streamingGroupObj(1);

% Register application buffer and driver buffers (with the wrapper driver).
status.setAppAndDriverBuffersA = invoke(streamingGroupObj, 'setAppAndDriverBuffers', channelA, ...
    pAppBufferChA, pDriverBufferChA, overviewBufferSize);

setAppAndDriverBuffers函数需要将data buffer和driver buffer都作为输入函数。

Final buffer pBufferChAFinal

创建A通道的final buffer:

1
2
3
4
maxSamples = get(ps4000aDeviceObj, 'numPreTriggerSamples') + ...
    get(ps4000aDeviceObj, 'numPostTriggerSamples');

pBufferChAFinal = libpointer('int16Ptr',zeros(maxSamples,1,'int16'));

创建一个指针变量即可,其中maxSamples的值为:

1
2
3
>> maxSamples
maxSamples =
     2000000

Collect data

overviewBufferSize

在收集数据的时候,仅仅需要使用到的是application bufferpAppBufferChA和final bufferpBufferChAFinal,两者的关系上文中已经介绍过,不再赘述。需要注意的是,当运行程序时,代码会在命令行窗口打印相关的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Collected 139264 samples, startIndex: 0 total: 139264.
Collected 110736 samples, startIndex: 139264 total: 250000.

Collected 237424 samples, startIndex: 0 total: 487424.
Collected 12576 samples, startIndex: 237424 total: 500000.

Collected 126688 samples, startIndex: 0 total: 626688.
Collected 69632 samples, startIndex: 126688 total: 696320.
Collected 53680 samples, startIndex: 196320 total: 750000.

Collected 15952 samples, startIndex: 0 total: 765952.
Collected 69632 samples, startIndex: 15952 total: 835584.
Collected 69632 samples, startIndex: 85584 total: 905216.
Collected 69632 samples, startIndex: 155216 total: 974848.
Collected 25152 samples, startIndex: 224848 total: 1000000.

Collected 44480 samples, startIndex: 0 total: 1044480.
Collected 69632 samples, startIndex: 44480 total: 1114112.
Collected 69632 samples, startIndex: 114112 total: 1183744.
Collected 66256 samples, startIndex: 183744 total: 1250000.

Collected 3376 samples, startIndex: 0 total: 1253376.
Collected 69632 samples, startIndex: 3376 total: 1323008.
Collected 69632 samples, startIndex: 73008 total: 1392640.
Collected 69632 samples, startIndex: 142640 total: 1462272.
Collected 37728 samples, startIndex: 212272 total: 1500000.

Collected 31904 samples, startIndex: 0 total: 1531904.
Collected 69632 samples, startIndex: 31904 total: 1601536.
Collected 69632 samples, startIndex: 101536 total: 1671168.
Collected 69632 samples, startIndex: 171168 total: 1740800.
Collected 9200 samples, startIndex: 240800 total: 1750000.

Collected 60432 samples, startIndex: 0 total: 1810432.
Collected 69632 samples, startIndex: 60432 total: 1880064.
Collected 69632 samples, startIndex: 130064 total: 1949696.
Collected 50304 samples, startIndex: 199696 total: 2000000.
AutoStop: TRUE - exiting loop.

Number of samples available from the driver: 2000000.

可以看到,当使用application buffer收集数据时,每一次收集到的数据量是不同的,但是当收集到250000个数据点时,就会清掉之前buffer中的数据,重新利用这一块application buffer进行收集,这一过程重复了8次。这个点数是由overviewBufferSize变量决定的。如果修改overviewBufferSize值为500000,则会发生相应的改变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Collected 139264 samples, startIndex: 0 total: 139264.
Collected 278528 samples, startIndex: 139264 total: 417792.
Collected 69632 samples, startIndex: 417792 total: 487424.
Collected 12576 samples, startIndex: 487424 total: 500000.

Collected 126688 samples, startIndex: 0 total: 626688.
Collected 69632 samples, startIndex: 126688 total: 696320.
Collected 69632 samples, startIndex: 196320 total: 765952.
Collected 69632 samples, startIndex: 265952 total: 835584.
Collected 69632 samples, startIndex: 335584 total: 905216.
Collected 69632 samples, startIndex: 405216 total: 974848.
Collected 25152 samples, startIndex: 474848 total: 1000000.

Collected 44480 samples, startIndex: 0 total: 1044480.
Collected 69632 samples, startIndex: 44480 total: 1114112.
Collected 69632 samples, startIndex: 114112 total: 1183744.
Collected 69632 samples, startIndex: 183744 total: 1253376.
Collected 69632 samples, startIndex: 253376 total: 1323008.
Collected 69632 samples, startIndex: 323008 total: 1392640.
Collected 69632 samples, startIndex: 392640 total: 1462272.
Collected 37728 samples, startIndex: 462272 total: 1500000.

Collected 31904 samples, startIndex: 0 total: 1531904.
Collected 69632 samples, startIndex: 31904 total: 1601536.
Collected 69632 samples, startIndex: 101536 total: 1671168.
Collected 69632 samples, startIndex: 171168 total: 1740800.
Collected 69632 samples, startIndex: 240800 total: 1810432.
Collected 69632 samples, startIndex: 310432 total: 1880064.
Collected 69632 samples, startIndex: 380064 total: 1949696.
Collected 50304 samples, startIndex: 449696 total: 2000000.
AutoStop: TRUE - exiting loop.

maxSamples

同样地,代码为final buffer也设置了指定的长度,即maxSamples的值,长度为2000000,这个值是由numPreTriggerSamplesnumPostTriggerSamples的值共同决定的:

1
2
maxSamples = get(ps4000aDeviceObj, 'numPreTriggerSamples') + ...
    get(ps4000aDeviceObj, 'numPostTriggerSamples');

当收集到这个数量的值时,程序就会停止收集数据(auto stop),这一点在官方文档中也可以看到:

image-20230313132544155

如果我们想要修改maxSamples的值,则需要修改前面对应的设置这两个值的代码,例如修改为收集300000个点:

1
2
3
4
5
6
7
% Set the number of pre- and post-trigger samples
% If no trigger is set 'numPreTriggerSamples' is ignored
set(ps4000aDeviceObj, 'numPreTriggerSamples', 0);
set(ps4000aDeviceObj, 'numPostTriggerSamples', 1000000);
...
maxSamples = get(ps4000aDeviceObj, 'numPreTriggerSamples') + ...
    get(ps4000aDeviceObj, 'numPostTriggerSamples');

则效果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Collected 208896 samples, startIndex: 0 total: 208896.
Collected 278528 samples, startIndex: 208896 total: 487424.
Collected 12576 samples, startIndex: 487424 total: 500000.

Collected 57056 samples, startIndex: 0 total: 557056.
Collected 69632 samples, startIndex: 57056 total: 626688.
Collected 69632 samples, startIndex: 126688 total: 696320.
Collected 69632 samples, startIndex: 196320 total: 765952.
Collected 69632 samples, startIndex: 265952 total: 835584.
Collected 69632 samples, startIndex: 335584 total: 905216.
Collected 69632 samples, startIndex: 405216 total: 974848.
Collected 25152 samples, startIndex: 474848 total: 1000000.
AutoStop: TRUE - exiting loop.

Number of samples available from the driver: 1000000.

但是这一值仅仅只是针对auto stop这一行为而设定的。如果我们将autostop关掉:

1
2
% The autoStop parameter can be set to false (0).
set(streamingGroupObj, 'autoStop', PicoConstants.FALSE);

虽然此时仍然是将final buffer的长度设置为100000,但是如果我们不点击停止按钮,则就会一直收集数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...

Collected 499744 samples, startIndex: 0 total: 35999744.
Collected 256 samples, startIndex: 499744 total: 36000000.

Collected 487168 samples, startIndex: 0 total: 36487168.
Collected 12832 samples, startIndex: 487168 total: 36500000.

Collected 474592 samples, startIndex: 0 total: 36974592.
Collected 25408 samples, startIndex: 474592 total: 37000000.

Collected 462016 samples, startIndex: 0 total: 37462016.
Collected 37984 samples, startIndex: 462016 total: 37500000.

Collected 449440 samples, startIndex: 0 total: 37949440.
Collected 50560 samples, startIndex: 449440 total: 38000000.

Collected 449439 samples, startIndex: 0 total: 38449439.
STOP button clicked - aborting data collection.

Collected 50561 samples, startIndex: 449439 total: 38500000.
STOP button clicked - aborting data collection.

Number of samples available from the driver: 1000000.

并不会报错。