Running a timer inside another timer

Hello, so I need to run a timer inside another timer, and I need to keep the properties of each timer specific to THAT timer. I was able to get it to run the inside timer once, but it never repeated once it went to the outside timer. Seems like there is an issue with the outside timer. May I please have some assistance with this by any ideas on how to go about it? Thank you.

6 Comments

Could you provide a minimal working example such as the lines of coded needed to set up both timers?
I have no idea, what "running a timer inside a timer" means. Timers fire events after a certain amount of time. Then what does it mean to run "inside" another timer? Does the problem concern sharing the UserData?
@Adam Danz here is the code I have that is relevant to the issue.
% deprivation round = how many times deprivTimer is executed
% wait time in between each round (it will now be the
% period), used to be 24 hours, but it is requested that the period can be
% manipulated by the user
properties (Access = public)
deprivTimer = timer; % defining the timer variable, this controls deprivation
length; % length of experiment in hours
dayTimer = timer; % defining timer to run for x number of days
numDeprivRounds; % tasks to execute
waitTime; % period for dayTimer
period; % period for deprivTimer
deprivTime; % how long the deprivation itself is (45 seconds)
randNum;
numExecutions; % tasks to execute for deprivTimer
year;
month;
day;
hour;
minute;
end
% Callbacks that handle component events
methods (Access = private)
% Code that executes after component creation
function startupFcn(app)
% defining time variables
app.period = 3 * 60; % interval between timer task executions (defined as 3 min)
app.deprivTime = 45; % seconds it takes for deprivator to oscillate 3 times (with some time cushion)
app.randNum = randi([0, 2.25 * 60], 1); % produces a random number for oscillation start
% connect to the deprivation motor
deprivator = visadev("ASRL3::INSTR");
% deprivTimer: controls when the deprivation motor performs a deprivation
app.deprivTimer.UserData = [app.deprivTime, app.randNum];
app.deprivTimer.Period = app.period;
app.deprivTimer.ExecutionMode = 'fixedRate';
app.deprivTimer.StartFcn = @SNAPstep;
app.deprivTimer.TimerFcn = @SNAPstep;
app.deprivTimer.StopFcn = @SNAPstep;
app.deprivTimer.ErrorFcn = @SNAPstep;
function SNAPstep(mTimer,event)
event_type = event.Type;
switch event_type
case 'StartFcn'
writeline(deprivator,"Q"); % sets the motor to get ready for deprivation/"off position"
disp([event_type ' deprivTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
case 'TimerFcn'
disp([event_type ' deprivTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
pause(app.deprivTimer.UserData(2)) % pauses for the specified random interval until the deprivation movement should start
disp(['Deprive executed ',datestr(clock,'dd-mmm-yyyy HH:MM:SS.FFF')]);
writeline(deprivator,"1") % commands the motor to begin depriving the flies
pause(mTimer.UserData(1)) % pauses the function until the deprivator is finished cycling
writeline(deprivator,"Q"); % resets the motor to get it ready for the next deprivation command
app.deprivTimer.UserData(2) = randi([0, 2.25 * 60] , 1); %sets a new random interval within the 3 minute window for the next deprivation
case 'StopFcn'
disp([event.Type ' deprivTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
stop(mTimer)
case 'ErrorFcn'
disp([event.Type,datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
end
end
% dayTimer: controls when all of the events associated with deprivTimer occurs
app.dayTimer.StartFcn = @CHATstep;
app.dayTimer.TimerFcn = @CHATstep;
app.dayTimer.StopFcn = @CHATstep;
app.dayTimer.ExecutionMode = 'fixedRate';
function CHATstep (cTimer, event)
event_type = event.Type;
switch event_type
case 'StartFcn'
disp([event_type ' dayTimer started ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
case 'TimerFcn'
app.deprivTimer.UserData = [app.deprivTime, app.randNum];
disp([event.Type ' dayTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
% need this part to make the function SNAPstep / deprivTimer to
% run all over again
case 'StopFcn'
disp([event.Type ' dayTimer Fcn complete ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
stop(cTimer);
end
end
end
% Value changed function: LengthofexperimentinhoursDropDown
function LengthofexperimentinhoursDropDownValueChanged2(app, event)
app.LengthofexperimentinhoursDropDown.Value;
% converting the drop down into the correct format for app.TasksToExecute
app.length = str2double(app.LengthofexperimentinhoursDropDown.Value)
execute = (app.length * 60) / 3
% the variable execute is sometimes read as a negative number, loop is to make sure
% execute is always positive
if execute < 0
app.numExecutions = -execute;
else
app.numExecutions = execute;
end
app.deprivTimer.TasksToExecute = app.numExecutions
end
% Value changed function: NumberofDeprivationRoundsDropDown
function NumberofDeprivationRoundsDropDownValueChanged(app, event)
app.NumberofDeprivationRoundsDropDown.Value;
app.numDeprivRounds = str2double(app.NumberofDeprivationRoundsDropDown.Value)
app.dayTimer.TasksToExecute = app.numDeprivRounds
end
% Value changed function: WaitTimeinBetweenEachRoundDropDown
function WaitTimeinBetweenEachRoundDropDownValueChanged(app, event)
app.WaitTimeinBetweenEachRoundDropDown.Value;
app.waitTime = str2double(app.WaitTimeinBetweenEachRoundDropDown.Value)
app.dayTimer.Period = app.waitTime * 3600 % selected number of hrs converted to sec
end
@Jan The phrase "running a timer inside a timer" can be further explained by the sample code I posted just recently. There are two timers. The first, denoted as "deprivTimer", communicates to a serial device that executes a certain action. As to when it executes, it is randomized within a 3 minute interval. The user has control has to how many 3 minute intervals they want, which is indicated by "app.period" under the timer properties set up for deprivTimer. The function "SNAPstep" is set up in that specific arrangement that lines up with the purpose of the GUI. The second timer, denoted as "dayTimer", is meant to control how often all of the properties and functions set up for deprivTimer occur. Let's say that deprivTimer is one round. As a user, I manipulate how many rounds I want deprivTimer to run, which is indicated by "app.numDeprivRounds". This is what I mean by "running a timer inside a timer."
Adam Danz
Adam Danz on 12 Jan 2023
Edited: Adam Danz on 12 Jan 2023
This isn't a minimal working example. If you could provide a watered down version with the bare minimum components that we could copy-paste-run, that would expedite the troubleshooting process.
Where are you starting these timers? I see thier start functions but I don't see thier start commands.
@Adam Danz Here is the the version where I could simplify as much as I could. It is a GUI, so the last 150 lines of code is just the setup.
classdef workingdepriv < matlab.apps.AppBase
% Properties that correspond to app components
properties (Access = public)
UIFigure matlab.ui.Figure
WaitTimeinBetweenEachRoundDropDown matlab.ui.control.DropDown
WaitTimeinBetweenEachRoundDropDownLabel matlab.ui.control.Label
NumberofDeprivationRoundsDropDown matlab.ui.control.DropDown
NumberofDeprivationRoundsDropDownLabel matlab.ui.control.Label
LengthofexperimentinhoursDropDown matlab.ui.control.DropDown
LengthofexperimentinhoursDropDownLabel matlab.ui.control.Label
STOPButton matlab.ui.control.Button
STARTButton matlab.ui.control.Button
end
properties (Access = public)
deprivTimer = timer; % defining the timer variable, this controls deprivation
length; % length of experiment in hours
dayTimer = timer; % defining timer to run for x number of days
numDeprivRounds; % tasks to execute for dayTimer
waitTime; % period for dayTimer
numExecutions; % tasks to execute for deprivTimer
end
% Callbacks that handle component events
methods (Access = private)
% Code that executes after component creation
function startupFcn(app)
% deprivTimer: controls when the deprivation motor performs a deprivation
app.deprivTimer.UserData = [45, randi([0, 2.25 * 60], 1)];
app.deprivTimer.Period = 180;
app.deprivTimer.ExecutionMode = 'fixedRate';
app.deprivTimer.StartFcn = @SNAPstep;
app.deprivTimer.TimerFcn = @SNAPstep;
app.deprivTimer.StopFcn = @SNAPstep;
app.deprivTimer.ErrorFcn = @SNAPstep;
function SNAPstep(mTimer,event)
event_type = event.Type;
switch event_type
case 'StartFcn'
disp([event_type ' deprivTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
case 'TimerFcn'
disp([event_type ' deprivTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
pause(app.deprivTimer.UserData(2)) % pauses for the specified random interval until the deprivation movement should start
disp(['Deprive executed ',datestr(clock,'dd-mmm-yyyy HH:MM:SS.FFF')]);
pause(mTimer.UserData(1)) % pauses the function until the deprivator is finished cycling
app.deprivTimer.UserData(2) = randi([0, 2.25 * 60] , 1); %sets a new random interval within the 3 minute window for the next deprivation
case 'StopFcn'
disp([event.Type ' deprivTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
stop(mTimer)
case 'ErrorFcn'
disp([event.Type,datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
end
end
% dayTimer: controls when all of the events associated with deprivTimer occurs
app.dayTimer.StartFcn = @CHATstep;
app.dayTimer.TimerFcn = @CHATstep;
app.dayTimer.StopFcn = @CHATstep;
app.dayTimer.ExecutionMode = 'fixedRate';
function CHATstep (cTimer, event)
event_type = event.Type;
switch event_type
case 'StartFcn'
disp([event_type ' dayTimer started ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
case 'TimerFcn'
disp([event.Type ' dayTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
start(app.deprivTimer);
case 'StopFcn'
disp([event.Type ' dayTimer Fcn complete ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
stop(cTimer);
end
end
end
% Value changed function: LengthofexperimentinhoursDropDown
function LengthofexperimentinhoursDropDownValueChanged2(app, event)
app.LengthofexperimentinhoursDropDown.Value;
% converting the drop down into the correct format for app.TasksToExecute
app.length = str2double(app.LengthofexperimentinhoursDropDown.Value)
execute = (app.length * 60) / 3
% the variable execute is sometimes read as a negative number, loop is to make sure
% execute is always positive
if execute < 0
app.numExecutions = -execute;
else
app.numExecutions = execute;
end
app.deprivTimer.TasksToExecute = app.numExecutions
end
% Value changed function: NumberofDeprivationRoundsDropDown
function NumberofDeprivationRoundsDropDownValueChanged(app, event)
app.NumberofDeprivationRoundsDropDown.Value;
app.numDeprivRounds = str2double(app.NumberofDeprivationRoundsDropDown.Value)
app.dayTimer.TasksToExecute = app.numDeprivRounds
end
% Value changed function: WaitTimeinBetweenEachRoundDropDown
function WaitTimeinBetweenEachRoundDropDownValueChanged(app, event)
app.WaitTimeinBetweenEachRoundDropDown.Value;
app.waitTime = str2double(app.WaitTimeinBetweenEachRoundDropDown.Value)
app.dayTimer.Period = app.waitTime * 3600 % selected number of hrs converted to sec
end
% Button pushed function: STARTButton
function STARTButtonPushed(app, event)
% prevents user from pushing START if it was pushed already
set(app.STARTButton, 'Enable', 'off');
% starting dayTimer
start(app.dayTimer);
% how to get deprivTimer to start within dayTimer? all of the events of deprivTimer need to
% repeat
% to keep record of when deprivTimer and dayTimer occur
disp(app.deprivTimer);
disp(app.dayTimer);
end
% Button pushed function: STOPButton
function STOPButtonPushed(app, event)
stop(app.deprivTimer);
stop(app.dayTimer);
set(app.STOPButton, 'Enable', 'on');
% loop means that if STOP is pushed, START will turn on
if strcmpi(app.STARTButton.Enable,'off')
set(app.STARTButton, 'Enable', 'on');
end
end
end
% Component initialization
methods (Access = private)
% Create UIFigure and components
function createComponents(app)
% Create UIFigure and hide until all components are created
app.UIFigure = uifigure('Visible', 'off');
app.UIFigure.Position = [100 100 640 480];
app.UIFigure.Name = 'MATLAB App';
% Create STARTButton
app.STARTButton = uibutton(app.UIFigure, 'push');
app.STARTButton.ButtonPushedFcn = createCallbackFcn(app, @STARTButtonPushed, true);
app.STARTButton.BackgroundColor = [0 1 0];
app.STARTButton.FontSize = 20;
app.STARTButton.Position = [112 96 146 66];
app.STARTButton.Text = 'START';
% Create STOPButton
app.STOPButton = uibutton(app.UIFigure, 'push');
app.STOPButton.ButtonPushedFcn = createCallbackFcn(app, @STOPButtonPushed, true);
app.STOPButton.BackgroundColor = [1 0 0];
app.STOPButton.FontSize = 20;
app.STOPButton.Position = [393 96 139 66];
app.STOPButton.Text = 'STOP';
% Create LengthofexperimentinhoursDropDownLabel
app.LengthofexperimentinhoursDropDownLabel = uilabel(app.UIFigure);
app.LengthofexperimentinhoursDropDownLabel.HorizontalAlignment = 'right';
app.LengthofexperimentinhoursDropDownLabel.FontSize = 17;
app.LengthofexperimentinhoursDropDownLabel.Position = [66 320 293 24];
app.LengthofexperimentinhoursDropDownLabel.Text = 'Length of experiment (in hours):';
% Create LengthofexperimentinhoursDropDown
app.LengthofexperimentinhoursDropDown = uidropdown(app.UIFigure);
app.LengthofexperimentinhoursDropDown.Items = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24'};
app.LengthofexperimentinhoursDropDown.ValueChangedFcn = createCallbackFcn(app, @LengthofexperimentinhoursDropDownValueChanged2, true);
app.LengthofexperimentinhoursDropDown.FontSize = 17;
app.LengthofexperimentinhoursDropDown.Position = [374 322 100 22];
app.LengthofexperimentinhoursDropDown.Value = '1';
% Create NumberofDeprivationRoundsDropDownLabel
app.NumberofDeprivationRoundsDropDownLabel = uilabel(app.UIFigure);
app.NumberofDeprivationRoundsDropDownLabel.HorizontalAlignment = 'right';
app.NumberofDeprivationRoundsDropDownLabel.FontSize = 17;
app.NumberofDeprivationRoundsDropDownLabel.Position = [66 261 293 26];
app.NumberofDeprivationRoundsDropDownLabel.Text = 'Number of Deprivation Rounds:';
% Create NumberofDeprivationRoundsDropDown
app.NumberofDeprivationRoundsDropDown = uidropdown(app.UIFigure);
app.NumberofDeprivationRoundsDropDown.Items = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '10'};
app.NumberofDeprivationRoundsDropDown.ValueChangedFcn = createCallbackFcn(app, @NumberofDeprivationRoundsDropDownValueChanged, true);
app.NumberofDeprivationRoundsDropDown.FontSize = 17;
app.NumberofDeprivationRoundsDropDown.Position = [374 265 100 22];
app.NumberofDeprivationRoundsDropDown.Value = '1';
% Create WaitTimeinBetweenEachRoundDropDownLabel
app.WaitTimeinBetweenEachRoundDropDownLabel = uilabel(app.UIFigure);
app.WaitTimeinBetweenEachRoundDropDownLabel.HorizontalAlignment = 'right';
app.WaitTimeinBetweenEachRoundDropDownLabel.FontSize = 17;
app.WaitTimeinBetweenEachRoundDropDownLabel.Position = [66 207 293 26];
app.WaitTimeinBetweenEachRoundDropDownLabel.Text = 'Wait Time in Between Each Round:';
% Create WaitTimeinBetweenEachRoundDropDown
app.WaitTimeinBetweenEachRoundDropDown = uidropdown(app.UIFigure);
app.WaitTimeinBetweenEachRoundDropDown.Items = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24'};
app.WaitTimeinBetweenEachRoundDropDown.ValueChangedFcn = createCallbackFcn(app, @WaitTimeinBetweenEachRoundDropDownValueChanged, true);
app.WaitTimeinBetweenEachRoundDropDown.FontSize = 17;
app.WaitTimeinBetweenEachRoundDropDown.Position = [374 211 100 22];
app.WaitTimeinBetweenEachRoundDropDown.Value = '1';
% Show the figure after all components are created
app.UIFigure.Visible = 'on';
end
end
% App creation and deletion
methods (Access = public)
% Construct app
function app = workingdepriv
% Create UIFigure and components
createComponents(app)
% Register the app with App Designer
registerApp(app, app.UIFigure)
% Execute the startup function
runStartupFcn(app, @startupFcn)
if nargout == 0
clear app
end
end
% Code that executes before app deletion
function delete(app)
% Delete UIFigure when app is deleted
delete(app.UIFigure)
end
end

Sign in to comment.

Answers (1)

Adam Danz
Adam Danz on 14 Jan 2023
The problem seems to be that the line that starts deprivTimer uses "app.deprivTimer" but "app" is not one of the input arguments in that function.
function CHATstep (cTimer, event) %<---------- "app" is not an input
event_type = event.Type;
switch event_type
case 'StartFcn'
disp([event_type ' dayTimer started ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
case 'TimerFcn'
disp([event.Type ' dayTimer executed ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
start(app.deprivTimer); % <--------------- HERE
case 'StopFcn'
disp([event.Type ' dayTimer Fcn complete ',datestr(event.Data.time,'dd-mmm-yyyy HH:MM:SS.FFF')]);
stop(cTimer);
end
end
To fix that, you need to include the app object in the function definitions. Here are examples you can apply to your file.
app.dayTimer.StartFcn = @(timer,event)CHATstep(app,timer,event);
app.dayTimer.TimerFcn = @(timer,event)CHATstep(app,timer,event);
app.dayTimer.StopFcn = @(timer,event)CHATstep(app,timer,event);
function CHATstep (app,cTimer, event) % <-- add app input
...
start(app.deprivTimer); % <--- now this will work
...
end
However there are some other puzzles here. First it appears that there is much time between events. This line below suggests several minutes. The other section below shows that you're pausing for potentially long periods. Make sure you reduces these to short periods of time when testing and developing.
app.dayTimer.Period = app.waitTime * 3600
case 'TimerFcn'
pause(app.deprivTimer.UserData(2)) % pauses for the specified random interval until the deprivation movement should start
pause(mTimer.UserData(1)) % pauses the function until the deprivator is finished cycling
Lastly, as mentioned in the documentation for timer, timers like these should not be used for precise control of temporal events. So if your program requires precise timing control (sub-second), be aware that of these limitations.

Products

Release

R2022b

Asked:

on 11 Jan 2023

Answered:

on 14 Jan 2023

Community Treasure Hunt

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

Start Hunting!