主要内容

本页采用了机器翻译。点击此处可查看最新英文版本。

并行控制硬件和采集数据

自 R2025a 起

此示例展示了如何同时控制硬件并在并行工作单元上执行数据采集。

您可以使用 parfeval 函数在并行池中使用工作单元异步控制硬件和获取数据。parfeval 不会阻止客户端,因此您可以继续在客户端上运行计算,或者像本示例中一样向工作单元发送指令。

此示例演示了如何使用四分之一汽车悬架模型的仿真测试台架设置工作单元,以同时进行硬件控制和数据采集。一个工作单元控制测试台上的作动器,其他工作单元从测试台上的四个传感器获取数据,并将其发送至客户端进行可视化。要控制作动器,您需要使用 PollableDataQueue 对象向工作单元发送消息。

您可以将此方法应用于任何需要同时进行控制和监控或数据采集的应用。

启动一个由五个线程工作单元组成的并行池。

pool = parpool("Threads",5);
Starting parallel pool (parpool) using the 'Threads' profile ...
Connected to parallel pool with 5 workers.

定义道路横断面参数

测试台架上的作动器沿着道路轮廓行驶,以仿真道路扰动。定义道路横断面函数及其参数。轮廓函数将路面模型化为正弦波,其参数包括频率、扰动持续时间和时间步长。

params.profileFun = @(t,f) 0.025*(1-cos(8*pi*f*t));
params.frequency = 1;
params.duration = 5;
params.timeStep = 0.0025;

创建可视化图表

创建一个图形并设置动画曲线,用于可视化道路位移、悬架变形、车身位移和车身加速度。

[fig,p] = createPlot;

设置数据队列

创建一个 DataQueue,并使用 afterEach 来指定每次队列接收数据时执行的函数。displayOnClient 函数绘制工作单元中的数据,并在示例末尾定义。

resultsDq = parallel.pool.DataQueue;
afterEach(resultsDq,@(readings) displayOnClient(p,readings));

要启用连接到硬件设备的客户端与工作单元之间的通信,请创建 PollableDataQueue 对象,并将 Destination 参量设置为 "any"。这种类型的 PollableDataQueue 对象允许客户端和工作单元发送和接收消息。

workerToClientPdq = parallel.pool.PollableDataQueue(Destination="any");
clientToWorkerPdq = parallel.pool.PollableDataQueue(Destination="any");

在工作单元上启动数据采集

要开始从不同传感器收集数据,请使用 parfeval 执行每个传感器的 readAndSend 函数。readAndSend 函数在示例的末尾定义。

readDuration = 30; % seconds
numSensors = 4;
sensorFutures(1,numSensors) = parallel.FevalFuture;
for s = 1:numSensors
    sensorFutures(s) = parfeval(@readAndSend,2,s,readDuration,resultsDq);
end
set(fig,"Visible","on")

实现实时硬件控制

要执行实时硬件控制,您需要定义工作单元函数、连接到硬件并向其发送指令,然后在停止进程之前更新硬件参数。

定义硬件控制的工作单元函数

为工作单元定义一个函数,以实现对硬件设备的实时动态控制。connectToActuator 函数执行初始硬件设置,并使用 workerToClientPdq 队列向客户端发出就绪信号。该函数等待来自 clientToWorkerPdq 队列的初始参数,然后根据这些参数开始生成道路轮廓并将其发送给作动器。在循环中,该函数继续发送配置文件,同时积极检查客户端的指令以更新参数或停止操作。generateRoadProfilesendToActuator 函数作为支持文件包含在此示例中。

function connectToActuator(workerToClientPdq,clientToWorkerPdq)
% Perform additional hardware setup.
send(workerToClientPdq,"Ready");
% Wait for instructions.
params = poll(clientToWorkerPdq,inf);
send(workerToClientPdq,sprintf("Started sending road profiles to actuator."));
while true
    roadProfile = generateRoadProfile(params);
    sendToActuator(roadProfile);
    [change,OK] = poll(clientToWorkerPdq,roadProfile.time(end)-1);
    if OK
        if isstruct(change)
            params = change;
            send(workerToClientPdq,sprintf("Updated road profile parameters."));
        else
            strcmp(change,"stop")
            send(workerToClientPdq,sprintf("Stopped sending profiles to actuator."));
            return
        end
    end
end
end

连接到硬件

要指示工作单元连接到作动器,请提交一个 parfeval 计算,在工作单元上运行 connectToActuator 函数。轮询 workerToClientPdq 队列以接收确认,无限期等待以确保作动器已准备就绪。

actuatorFuture = parfeval(@connectToActuator,0,workerToClientPdq,clientToWorkerPdq);
poll(workerToClientPdq,inf)
ans = 
"Ready"

向硬件发送指令

接下来,使用 clientToWorkerPdq 队列指示工作单元开始将道路轮廓发送给作动器。从工作单元接收确认。

send(clientToWorkerPdq,params);
poll(workerToClientPdq,inf)
ans = 
"Started sending road profiles to actuator."

parfeval 函数不会阻止客户端,因此您可以在工作单元继续计算的同时继续工作。在此示例中,使用 pause 允许工作单元在四秒钟内向作动器发送数据。

pause(4)

更新硬件参数并停止进程

使用 clientToWorkerPdq 队列更新作动器的道路轮廓参数。更改道路轮廓函数,并将更新后的参数发送至工作单元。当作动器接收下一个道路轮廓时,您可以在五秒钟后看到道路位移图的变化。等待工作单元的确认。

params.profileFun = @(t,f) 0.02*sin(2*pi*f*t);
send(clientToWorkerPdq,params);
poll(workerToClientPdq,inf)
ans = 
"Updated road profile parameters."

要停止向作动器发送配置文件并终止 parfeval 计算,请向工作单元发送 "stop" 消息。道路位移图显示,约 15 秒后位移为 0。再次,请等待确认。

pause(4)
send(clientToWorkerPdq,"stop");
poll(workerToClientPdq,2)
ans = 
"Stopped sending profiles to actuator."

获取传感器数据

等待传感器未来值完成,并从 parfeval 未来值中获取传感器读数。

wait(sensorFutures);
[tAll,dAll] = fetchOutputs(sensorFutures);

支持函数

readAndSend

readAndSend 辅助函数以一秒为增量读取传感器数据,并将其发送至客户端。connectToSensor 函数可仿真从四分之一汽车测试台架上的不同传感器读取数据,并作为支持文件附加在此示例中。

function [tAll,dAll] = readAndSend(sensorID,readDuration,resultDq)
tAll = [];
dAll = [];
for duration = 1:readDuration
    [t,d] = connectToSensor(sensorID,1);
    readings.id = sensorID;
    readings.x = t;
    readings.y = d;
    tAll = [tAll,t];
    dAll = [dAll,d];

    send(resultDq,readings)
end
clear connectToSensor
end

displayOnClient

displayOnClient 函数使用从传感器接收到的新的数据点更新动画图表。

function displayOnClient(p,readings)
idx = readings.id;
addpoints(p(idx),readings.x,readings.y)
drawnow limitrate;
end

createPlot

createPlot 函数用于设置图形和动画线,以显示传感器读数。

function [fig,p] = createPlot
fig = figure(Name="Quarter Car Test Rig",Visible="off",Position=[263 429 1124 417]);
tl = tiledlayout(fig,2,3,TileSpacing ="compact",Padding ="compact");
title(tl,"Quarter Car Test Rig");
nexttile(tl,[2 1])
imagesc(imread("car-suspension.png"));
lineColor = ["k","b","g","m"];
titleStrs = ["Road Displacement","Suspension Deflection","Body Travel","Body Acceleration"];
yAxisStrs = ["Displacement (m)","Deflection (m)","Displacement (m)","Acceleration (m/s^2)"];
p = gobjects(4);
for idx=1:4
    nexttile(tl);
    xlabel("Time (s)");
    ylabel(yAxisStrs(idx));
    title(titleStrs(idx))
    p(idx) = animatedline(NaN,NaN,Color=lineColor(idx));
    xlim([0 20])
end
end

另请参阅

函数

对象

主题