Main Content

Control Hardware and Acquire Data in Parallel

Since R2025a

This example shows how to simultaneously control hardware and perform data acquisition on parallel workers.

You can use the parfeval function to asynchronously control hardware and acquire data with workers in a parallel pool. parfeval does not block the client, so you can continue to run computations on the client or, like in this example, send instructions to the workers.

This example demonstrates how to set up workers for simultaneous hardware control and data acquisition using a simulated test rig for a quarter-car suspension model. One worker controls an actuator on the test rig, and the other workers acquire data from four sensors on the test rig and send it to the client for visualization. To control the actuator, you send messages to a worker using a PollableDataQueue object.

You can adapt this approach for any application that requires simultaneous control and monitoring or data acquisition.

Start a parallel pool of five thread workers.

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

Define Road Profile Parameters

The actuator on the test rig follows a road profile to simulate road disturbances. Define the road profile function and its parameters. The profile function models a road surface as a cosine wave, and the parameters are frequency, disturbance duration, and time-step.

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

Create Plot for Visualization

Create a figure and set up animated plots for visualizing the road displacement, suspension deflection, body travel, and body acceleration.

[fig,p] = createPlot;

Set Up Data Queues

Create a DataQueue and use afterEach to specify the function to execute each time the queue receives data. The displayOnClient function plots the data from the workers and is defined at the end of the example.

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

To enable communication between the client and worker connected to the hardware device, create PollableDataQueue objects with the Destination argument set to "any". This type of PollableDataQueue object allows both the client and worker to send and receive messages.

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

Start Data Acquisition on Workers

To start collecting data from the different sensors, use parfeval to execute the readAndSend function for each sensor. The readAndSend function is defined at the end of the example.

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")

Perform Real-Time Hardware Control

To perform real-time hardware control, you define the worker function, connect to the hardware and send instructions to it, and then update the hardware parameters before stopping the process.

Define Worker Function for Hardware Control

Define a function for the worker that allows dynamic control of a hardware device in real time. The connectToActuator function performs initial hardware setup and signals readiness to the client using the workerToClientPdq queue. The function waits for initial parameters from the clientToWorkerPdq queue and begins generating and sending road profiles to the actuator based on these parameters. In a loop, the function continues to send profiles while actively checking for client instructions to update parameters or stop operations. The generateRoadProfile and sendToActuator functions are included as supporting files to this example.

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

Connect to Hardware

To instruct the worker to connect to the actuator, submit a parfeval computation to run the connectToActuator function on a worker. Poll the workerToClientPdq queue to receive confirmation, waiting indefinitely to ensure the actuator is ready to begin.

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

Send Instructions to Hardware

Next, use the clientToWorkerPdq queue to instruct the worker to start sending the road profiles to the actuator. Receive confirmation from the worker.

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

The parfeval function does not block the client, so you can continue working while the workers continue their computations. For this example, use pause to allow the worker to send data to the actuator for four seconds.

pause(4)

Update Hardware Parameters and Stop the Process

Use the clientToWorkerPdq queue to update the road profile parameters for the actuator. Change the road profile function and send the updated parameters to the worker. You can see a change in the road displacement plot after five seconds when the actuator receives the next road profile. Wait to receive confirmation from the worker.

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

To stop sending profiles to the actuator and terminate the parfeval computation, send a "stop" message to the worker. The road displacement plot shows a displacement of 0 after about 15 seconds. Again, wait to receive confirmation.

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

Retrieve Sensor Data

Wait for the sensor futures to complete and retrieve the sensor readings from the parfeval futures.

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

Supporting Functions

readAndSend

The readAndSend helper function reads sensor data in one-second increments and sends it to the client. The connectToSensor function simulates the reading of data from different sensors in a quarter-car test rig and is attached to this example as a supporting file.

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

The displayOnClient function updates the animated plots with new data points received from the sensors.

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

createPlot

The createPlot function sets up the figure and animated lines for displaying the sensor readings.

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

See Also

Functions

Objects

Topics