Skip to content

timer — 定时器 ​

模块名:timer

创建周期性或单次的定时任务。回调函数在 Lua 主线程的消息循环中执行,不是独立线程,因此回调里不要做长时间阻塞的事。

函数总览 ​

函数作用
timer.start(ms, callback, [once])创建并立即启动一个定时器,返回定时器对象
timer.stop()停止并销毁定时器

对象方法:定时器对象支持 t:stop(),与 timer.stop(t) 完全等价。


timer.start ​

lua
timer.start(ms, callback, [once]) --> timer

创建并立即启动一个定时器。

参数

#参数类型必填默认值说明
1msinteger是—定时周期,单位毫秒。传入小于 10 的值会被强制按 10 处理
2callbackfunction是—到时执行的回调,无入参、无返回值要求。类型不是 function 会直接报错
3onceboolean | number否falsetrue 或数值 >= 1 → 只触发一次;false、数值 < 1、省略 → 周期循环

返回值

类型说明
userdata定时器对象。只在需要 stop() 时才用得上,不接住也不影响定时器运行(见下方说明)

示例

lua
-- 周期定时器:每 1000ms 执行一次
local t1 = timer.start(1000, function()
    print("tick")
end)

-- 单次定时器:2000ms 后只执行一次
local t2 = timer.start(2000, function()
    print("只响一次")
end, true)

-- 计数到 10 次后自行停止
local n = 0
local t3
t3 = timer.start(500, function()
    n = n + 1
    if n >= 10 then t3:stop() end
end)

说明

  • 启动后的定时器由固件自己持有,返回值丢掉也照样跑:
    lua
    timer.start(1000, function() print("tick") end)   -- 可以这么写
    只有需要提前停掉它时才必须用变量接住。
  • 周期定时器不会自动停止,需要显式调用 :stop()。丢掉了返回值又想停,只能重启脚本。
  • 单次定时器触发一次后自动释放,不需要 stop()。

老固件的行为不一样

2026-08-22 之前的固件里,定时器对象一旦没有变量引用就会被 GC 回收, 连带把定时器杀掉(而且每次定时回调里都会走一次增量 GC,通常第一次都触发不了), 现象是"定时器完全不工作、也没有任何报错"。 如果设备固件较早,timer.start 的返回值必须用全局或长生命周期变量接住。


实用例程 ​

lua
--洞子塘程序2026-8-8
plc.df(6,0.072)
ds=timer.start(2000, function()
    
    print("D6:",plc.df(6))
    
    end)

把上面的地址、主题、引脚替换成你项目里的即可。

timer.stop ​

lua
timer.stop(t)
t:stop()

停止定时器、释放回调引用并销毁底层对象。

参数

#参数类型必填默认值说明
1tuserdata是—timer.start 返回的定时器对象。用 t:stop() 写法时此参数由冒号语法自动传入

返回值

无返回值。

示例

lua
local t = timer.start(1000, function() print("running") end)

-- 两种写法等价
t:stop()
-- timer.stop(t)

说明

  • 可以重复调用,第二次起是空操作,不会报错。
  • 停止后该对象不能再启动,需要重新 timer.start。
  • stop() 之外没有别的释放途径:跑着的定时器被固件自己持有,不会因为没人引用而被 GC 回收。

例程 ​

每个例程都是可直接下载运行的完整脚本:注册完定时器后脚本就结束,固件自动进入消息泵。 API 只用本页已文档化的函数;统一模板「场景 / 完整代码 / 要点 / 验证方法 / 注意事项」。

例程 1:周期采集 + 回调内异常保护 ​

适用:LP3201 / LP3202 / LP3203 / LP3222 / LP3232 / LP3288 / 软PLC模拟器 | 依赖模块:timer plc | 难度:★☆☆

场景:最常见的一种用法 —— 每隔 1 秒读一次现场数据并做判断。回调里必须自己用 pcall 兜住异常:定时器回调外面不是 try/catch,一处报错会让 Lua 状态机重建、脚本从头重跑,现象是"定时器突然重新开始计数"。

完整代码(可直接下载运行):

lua
local period = timer.start(1000, function()
    local ok, err = pcall(function()
        local temp = plc.df(100)              -- 读 D100-D101 浮点温度
        plc.d(200, math.floor(temp * 10))     -- 乘 10 取整给 modbus 用
        if temp > 80 then plc.y(0, 1) end     -- 超温联锁
    end)
    if not ok then
        print("采集回调出错:", err)
    end
end)

要点:

  • pcall 包的是整个回调体:出错时只丢这一拍,定时器不会被销毁、下一次照常触发。
  • 周期性的活不要再加任何形式的等待语句,回调干完就 return(隐含)。
  • 返回值不是必须接住的:只有要 stop() 时才用得到。这里接住是为了能在别的地方停掉它。
  • 周期建议 ≥ 100ms,最小技术值是 10ms,太小会挤占主线程。

验证方法:

  1. 下载运行后,每秒 D200 随着 DA100 附近温度更新,超 80 时 Y0 吸合。
  2. 故意写错一行(例如调用一个不存在的函数)→ 日志每秒打印 采集回调出错: …,但定时器没有停,下一秒还在刷(证明 pcall 生效)。
  3. 去掉 pcall 直接调用 → 报错后脚本会从头重执行(timer.start 被重复注册)。

注意事项:

  • 回调报错不是只跳过一次 —— 未捕获的错误会让整个 Lua 状态机重建,脚本 5 秒后从头再来,注册过的定时器会重复注册。
  • 别在回调里 system.sleep:事件泵停了,其它定时器一起卡住。

例程 2:单次定时器做"延时一步"(替换 sleep 的正解) ​

适用:同上 | 依赖模块:timer plc | 难度:★☆☆

场景:按下启动后先开阀,2 秒后自动关阀。很多人会本能写成"开阀 → system.sleep(2000) → 关阀",那会把整台设备冻住 2 秒。单次定时器才是这里的正解:第三参传 true,到点跑一次就自动释放。

完整代码(可直接下载运行):

lua
-- X0 上升沿 → Y0 打开 → 2 秒后自动关闭
local last_x0 = 0

timer.start(200, function()
    local x0 = plc.x(0)
    if x0 == 1 and last_x0 == 0 then
        plc.y(0, 1)
        print("阀已开")
        timer.start(2000, function()
            plc.y(0, 0)
            print("2 秒到,阀已关")
        end, true)                      -- true = 只触发一次,跑完自动释放
    end
    last_x0 = x0
end)

要点:

  • 第三参 true(或数值 ≥ 1)表示单次触发;false / 省略是周期循环。
  • 单次定时器触发后自动释放,不需要 stop()。
  • 用 200ms 轮询 + last_x0 记录上一拍状态来识别上升沿;Lua 侧没有"边沿寄存器"可用。
  • 想做多步工序就继续在每一步里挂下一个单次定时器,形成"定时器接力"。

验证方法:

  1. 让 X0 由断变通(真机上短接 X0 与 COM):日志打印 阀已开,Y0 立刻吸合。
  2. 2 秒后自动打印 2 秒到,阀已关,Y0 断开,不需要再操作 X0。
  3. 这 2 秒里给设备发一条 MQTT 下发或看另一个定时器:仍能正常响应(证明没有阻塞)。
  4. X0 保持 ON 不会重复开阀(上升沿只在 0 → 1 那一次触发)。

注意事项:

  • 写成 plc.y(0,1) system.sleep(2000) plc.y(0,0):主线程被卡住 2 秒,期间 MQTT / 串口 / 其它定时器全部失效。
  • X0 抖动会导致多次触发:单次定时器只擎得住一次,必要时加"动作中"标志位。

例程 3:一个定时器跑多个周期 + 到次数自停 ​

适用:同上 | 依赖模块:timer plc | 难度:★★☆

场景:设备资源有限,与其给每件事开一个定时器,不如用一个基准定时器按 tick 取模分时;另外有些活(比如上电重试 10 次)跑到次数就该把自己停掉 —— 这两件事合在一起就是下面这段。

完整代码(可直接下载运行):

lua
-- 一个 1s 基准定时器承担 1s / 5s / 60s 三件事;重试类任务到次数自停
local tick = 0
local retry = 0
local retry_timer

local t = timer.start(1000, function()
    tick = tick + 1

    local temp = plc.df(100)                              -- 每 1s 采集
    if tick % 5  == 0 then print("5s 上报", temp) end     -- 每 5s
    if tick % 60 == 0 then tick = 0 end                   -- 每 60s 归零,防溢出
end)

-- 独立的重试任务:连不上就重来,最多 10 次
retry_timer = timer.start(3000, function()
    retry = retry + 1
    print("第", retry, "次重试")
    if retry >= 10 then
        retry_timer:stop()                                -- 到次数停掉,与被停对象在同一层可见
        print("重试上限已到,停止")
    end
end)

要点:

  • 多个周期任务合用一个定时器:tick % N 判这一段该不该跑,比开 N 个定时器省资源。
  • tick 长时间运行会持续增长,建议定期归零(整数溢出到不了,但归零后日志好读、判断也更直观)。
  • 想在回调里停掉自己,必须先用外层变量接住 timer.start 的返回值(闭包引用自身变量)。
  • stop() 可以重复调用,第二次起是空操作;停掉之后要再用只能重新 timer.start。

验证方法:

  1. 下载后日志每 5 秒出现一条 5s 上报 <温度>;60 秒内 tick 归零一次。
  2. 重试任务每 3 秒打印一次,第 10 次打印 重试上限已到,停止 后不再出现 —— 定时器真的停了。
  3. 把 :stop() 那行删掉重跑作为对照:重试会无限刷下去。

注意事项:

  • 早于 2026-08-22 的固件里,定时器没人引用会被 GC 回收并连带杀掉(现象是"定时器完全不工作、一句报错都没有");那种固件上必须用全局变量接住。
  • 周期任务不要嵌套加入新定时器却又忘了停掉,会越滚越多。

注意事项 ​

  • 最小周期 10ms;周期设得过小会挤占主线程,采集类任务建议 ≥ 100ms。
  • 回调在主线程执行,避免在回调里做 system.sleep 之类的长阻塞,否则会拖慢其它定时器和消息处理。
  • 需要真正并行执行的耗时任务,不要靠定时器堆叠。

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研