can I create a controller for inverted rotary pendulum using DataAcquisitionToolbox, reading counters and output analog voltage on a NI-6211 board.

6 ビュー (過去 30 日間)
Lloyd
Lloyd 2024 年 6 月 13 日
回答済み: Kautuk Raj 2024 年 9 月 18 日
so my setup is as follows: I have a QUANSER inverted rotary pendulum, connected to a NI USB-6211. I want to retrieve data from counters (these corrospond to angles of the pendulum system) , and using a controller output a volatage in real time to the control the swing of the rotary pendulum (I don't need a strict RTS just something that can produce outputs as a response to the system inputs).
So far, I have managed to:
  1. retrieve data in real time using a callback function (without output).
  2. output volatage (without input).
The problem I am experiancing is that I can't recieve outputs and inputs simultaniously with apropreate sampling times. The code below is where I set the rate of the callback function. d.Rate = 1000, so with d.ScansAvailableFcnCount = 100 that gives 1000/100 = 10 calls/sec. With d.ScansRequiredFcnCount = 500, that is only 2calls/sec. not sufficent for a controller.
d.ScansAvailableFcnCount = 100; % this can't be changed lower for some reason.
d.ScansRequiredFcnCount = 500; % this is also as low as it will go.
These won't go lower. if I do put them lower I get the following error: "At the specified rate, the minimum count allowed is 100."
function [d, ch1] = initdatastream()
% Initialize data acquisition session
d = daq("ni");
enctyp = 'X4';
d.Rate = 1000;
% Add an analog input channel to provide the necessary clock
ch3 = addinput(d, "Dev1", "ai0", "Voltage");
ch3.Name = "ClockSource";
% Add input channels
% C1
ch1 = addinput(d, "Dev1", "ctr0", "Position");
ch1.EncoderType = enctyp;
ch1.Name = "alpha";
%C2
ch2 = addinput(d, "Dev1", "ctr1", "Position");
ch2.EncoderType = enctyp;
ch2.Name = "theta";
%C4 otpt
ch4 = addoutput(d,"dev1", "ao0","Voltage");
ch4.Name = "otptVoltage";
d.ScansAvailableFcnCount = 100; % this can't be changed lower for some reason.
d.ScansRequiredFcnCount = 500; % this is also as low as it will go.
d.ScansAvailableFcn = @rtPendulumOtpt;
d.ScansRequiredFcn = @rtPendulumInpt;
end
the below code is how I provide input to the device:
function rtPendulumInpt(src, event)
%provides input for pendulum.
%Define the buffer size
bufferSize = 30;
% Use persistent variables to store the rolling buffer
persistent vBuffer;
% Initialize the buffers if they are empty
if isempty(vBuffer)
vBuffer = zeros(bufferSize, 1);
end
%write inpt to pendulum.
%currently just writing 0v, will eventually implement controller and
%have v as a function of pendulum angles.
pendulumInptV = 0;
write(src, repmat(pendulumInptV, src.NumScansAvailable, 1)) % this line is wrong.?
% Add the mean values to the rolling buffer
vBuffer = [vBuffer(2:end); pendulumInptV];
% Plot the rolling buffer
figure(2);
plot(vBuffer, 'DisplayName', 'InptV');
legend;
drawnow;
end
the below code is how I get output from the device:
function rtPendulumOtpt(src, event)
% Define the buffer size
bufferSize = 30;
% Use persistent variables to store the rolling buffer
persistent pos1Buffer pos2Buffer;
% Initialize the buffers if they are empty
if isempty(pos1Buffer)
pos1Buffer = zeros(bufferSize, 1);
pos2Buffer = zeros(bufferSize, 1);
end
% Read the most recent available data
data = read(src, src.ScansAvailableFcnCount, "OutputFormat", "Matrix");
% Extract counter data (assuming pos1 data is in the second column and pos2 data in the third column)
pos1 = data(:, 2);
pos2 = data(:, 3);
% Convert encoder counts to degrees
pos1 = enc2deg(pos1) - 180; % -180 as starting facing down
pos2 = enc2deg(pos2);
% Calculate the mean of the current batch of data
alpha = mean(pos1); % hand ang
theta = mean(pos2); % arm ang
% Add the mean values to the rolling buffer
pos1Buffer = [pos1Buffer(2:end); alpha];
pos2Buffer = [pos2Buffer(2:end); theta];
% Plot the rolling buffer
figure(1);
plot(pos1Buffer, 'DisplayName', 'pos1');
hold on;
plot(pos2Buffer, 'DisplayName', 'pos2');
hold off;
legend;
drawnow;
end
if I run the code with a preloaded data like this:
%%
initOtpt = zeros(10000,1);
preload(d,initOtpt)
start(d,"continuous")
it will run for 10s and I can visualise inputs and outputs apropreately. the problem really is the real time samping rate being limited to 1/2 sec. Also as I am using a daq with both inputs and outputs, as soon as I am nolonger providing an input, the output aquesition stops...
NOTE: this isn't a hardware problem. without changing the system settings I have had it working in LabView - i just want it to work in matlab.

回答 (1 件)

Kautuk Raj
Kautuk Raj 2024 年 9 月 18 日
It looks like you are trying to implement a real-time data acquisition and control loop using MATLAB with a NI USB-6211 DAQ device. Your issue seems to stem from the inability to change the ScansAvailableFcnCount variable below a certain minimum, which is also indicated by the error message being encountered.
As of MATLAB R2024b, the Data Acquisition Toolbox does limit the data reading rate to 10Hz, and this is the expected behaviour. The reason for doing this is when the user has more than 10 callbacks in a second (10Hz) in 'DataAcquisition' object to read data from external hardware, the callbacks can stack in the buffer, and not execute due to hardware limitations. This limited rate is a safety feature to make sure that the users do not accidentally stack the callbacks, and think they are reading the current data.
I looked at the source code of MATLAB, it does look like the variable ScansAvailableFcnCount could be modified, specifically in the DataAcquisition.m file found at the path matlab/toolbox/daq/cli/+daq/+interfaces/.
In the code, the value of ScansAvailableFcnCount is hard-coded using the code out = uint64(ceil(obj.Rate * 0.1)); but you may change it according to your requirements.
While it is easy to change, doing so might cause issues, which could explain the limit.

製品


リリース

R2024a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by