MATLAB 更改重复定时器周期

3

能否在TimerFcn中更改重复计时器的周期?

直觉上,在Windows编程时,我会处理WM_TIMER消息并使用SetTimer来编辑定时器的周期,但在MATLAB中似乎无法采用类似的方法,因为需要重新启动定时器才能更改周期属性。这会使执行混乱,最好的描述是将周期更改为接近零。不会产生任何错误。

下面是一些示例代码,用于创建任务数组:每个任务项都包含要执行的操作和延迟时间。该数组基本上由定时器遍历,根据当前任务延迟应更改其周期。

function obj = Scheduler(~)
    obj.scheduletimer = timer(...
        'TimerFcn',@obj.OnTimer,...
        'BusyMode','queue',...
        'TasksToExecute',length(obj.tasklist),...
        'ExecutionMode','fixedRate');
end
function OnTimer(obj,source,event)
    obj.Start(); // Executed task, schedule next
end
function Start(obj)

    // Stop timer if needed
    if(strcmp(obj.scheduletimer.Running,'on'))
        stop(obj.scheduletimer);
    end;

    // Set new period and resume
    if(~isempty(obj.tasklist))
        obj.scheduletimer.Period = obj.tasklist(1).something;
        start(obj.scheduletimer);
    end;
end

当我不干涉OnTimer中的计时器时,一切显然都正常工作,但是我想在每次迭代中更改Period。

编辑:我尝试了Pursuit建议的pingpong解决方案,但仍然不起作用。请注意,切换计时器的想法确实有效,但似乎未应用周期。

function obj = Scheduler(~)
    obj.timer1 = timer(...
        'TimerFcn',@obj.OnTimer);
     obj.timer2 = timer(...
        'TimerFcn',@obj.OnTimer);
end
function OnTimer(obj,source,event)
    obj.Start(); // Executed task, schedule next
end
function Start(obj)
    if(strcmp(obj.timer1.Running,'on'))
        obj.timer2.Period = obj.tasklist{1}{2};
        start(obj.timer2);
    else
        obj.timer1.Period = obj.tasklist{1}{2};
        start(obj.timer1);
    end;
end

呃。


你收到了什么错误信息? - wakjah
2
我能够重现你的问题,但不知道如何修复它。我唯一找到的解决方案是将计时器的模式设置为singleShot,并使用StartDelay属性来控制回调触发前的时间。然后,在回调中停止计时器并删除它,然后创建一个新的计时器并启动它。虽然不是最佳解决方案,但由于某种原因,它似乎没有其他可行的方法。 - wakjah
1
修正了你的评论。你可能也会对 SO 上的 Matlab 语法高亮用户脚本感兴趣(请查看 Matlab 标签信息获取链接)。 - Jonas
@wakjah:我快要放弃并切换到那种方法了... - Orwell
@wakjah:是的,使用StartDelay与计时器重建或乒乓似乎是(唯一)的解决方案。感谢您的建议。 - Orwell
我最终采用了wakjah的解决方案。虽然很丑陋,但它确实起作用了。Matlab真的应该有一个选项,其中“Period”可以是一个数组,其长度与TasksToExecute属性相匹配。这将使生活变得更加轻松。 - eric
2个回答

3

使用两个计时器(例如timerNamePingtimerNamePong)。在每个计时器的操作结束时,设置下一个计时器以延迟一次单发模式执行。

这样可以避免不断拆除和创建新计时器,并避免尝试编辑当前正在执行的计时器时出现的错误。

以下是一个工作示例:

function setupPingPong

timerPing = timer;
timerPong = timer;

timerPing.TimerFcn = @(~,~)pingPongActivity(true,  timerPing, timerPong);
timerPing.Name = 'PingTimer';

timerPong.TimerFcn = @(~,~)pingPongActivity(false, timerPing, timerPong);
timerPong.Name = 'PongTimer';

timerPing.StartDelay = 0;
start(timerPing);

function pingPongActivity(isPing, timerPing, timerPong)
    if isPing
        disp(['PING (' datestr(now,'yyyy-mm-dd HH:MM:SS.FFF') ')'])
    else
        disp(['PONG (' datestr(now,'yyyy-mm-dd HH:MM:SS.FFF') ')'])
    end
    delayTime = ceil(rand*10);
    display(['    delaying '  num2str(delayTime) ' sec.'])
    if isPing
        nextTimer = timerPong;
    else
        nextTimer = timerPing;
    end
    set(nextTimer,'StartDelay', delayTime);
    start(nextTimer);

一旦发生这种情况,为了停止混乱,我使用以下方法:
t = timerfind; stop(t); delete(t)

这是一个合理的方法,但我认为由于计划的到期时间与处理程序的实际执行之间的差异,错误会累积。 - Ben Voigt
1
如果这真的是一个问题,你可以在回调函数中使用tic/toc功能,并根据当前执行计时器的期望和实际时间之间的差异(以及任何过去累积的错误)来调整下一个执行定时器的StartDelay。 - wakjah
如果需要绝对时间精度,我会根据当前的 now 值计算到下一次迭代的偏移量。这将防止误差积累,尽管我仍然不会相信它非常准确。 - Pursuit
@Pursuit:准确性并不是非常重要的:大约50毫秒的误差不是问题。我已经尝试实施您的修复,但还没有生效。 - Orwell
是的,使用乒乓球或计时器重建(请参见wakjah的评论)可以解决问题,基本上因为无法更改正在运行的计时器的周期。这意味着StartDelay是唯一可配置的延迟选项。乒乓球和重建似乎同样快,所以在我看来,更简单的重建修复可能更可取。 - Orwell

1

我再次使用计时器对象中的“StopFcn”属性来重新启动计数器。

类似于这样(“TimerScale”更改下一个周期)

初始化:

Timer_OBJ = timer(  'ExecutionMode', 'singleShot', ...
                                    'StartDelay', SystemTicksSecs/TimerScale, ...
                                    'TimerFcn', @(src,evt)obj.TimerCallBack,...
                                    'StopFcn', @(src,evt)obj.TimerStopCallBack );

                start(Timer_OBJ);

并且在TimerStopCallBack内部

  set(Timer_OBJ, 'StartDelay', SystemTicksSecs/TimerScale);                

    start(Timer_OBJ);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接