跳转至

定时器(timer)

一、定时器(timer)的概述

在 LuatOS 系统中,定时器(timer)是一项基础且关键的服务。它允许开发者在特定的时间点或周期性地执行代码段,为物联网设备的运行提供了精确的时间控制。定时器在多种应用场景中都发挥着重要作用,如定时发送数据、周期性检查传感器状态等。

二、演示功能概述

本 demo 通过使用 Air8101 开发板,带你快速 timer 的基本功能。

三、准备硬件环境

“古人云:‘工欲善其事,必先利其器。’在深入介绍本功能示例之前,我们首先需要确保以下硬件环境的准备工作已经完成。”

参考:硬件环境清单,准备以及组装好硬件环境。

四、软件环境

“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。

  1. Luatools 工具
  2. 内核固件文件(底层 core 固件文件):LuatOS-SoC_V10001_Air8101.soc;参考项目使用的内核固件
  3. luatos 需要的脚本和资源文件

脚本和资源文件:https://gitee.com/openLuat/LuatOS-Air8101/tree/master/demo/wlan/softAP

lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 开发板,将本篇文章中演示使用的项目文件烧录到 Air8101 开发板中。

五、API 介绍

5.1 sys.timerStop(val, …)

关闭 sys.timerStart 和 sys.timerLoopStart 创建的定时器

有两种方式可以唯一标识一个定时器:

1、定时器 ID

2、定时器回调函数和可变参数

  • 参数
**名称**
**传入值类型**
**释义**
val
param
有两种形式:1、为 number 类型时,表示定时器 ID2、为 function 类型时,表示定时器回调函数

param
可变参数,当 val 为定时器回调函数时,此可变参数才有意义,表示定时器回调函数的可变回调参数
  • 返回值

nil

  • 例子
-- 通过定时器ID关闭一个定时器:
_local timerId = sys.timerStart(publicTimerCbFnc,8000,"second")
sys.timerStop(timerId)
-- 通过定时器回调函数和可变参数关闭一个定时器:
sys.timerStart(publicTimerCbFnc,8000,"first")
sys.timerStop(publicTimerCbFnc,"first")

5.2 sys.timerStopAll(fnc)

关闭 sys.timerStart 和 sys.timerLoopStart 创建的某个回调函数的所有定时器

  • 参数
**名称**
**传入值类型**
**释义**
fnc
function
定时器回调函数
  • 返回值

nil

  • 例子
-- 关闭回调函数为publicTimerCbFnc的所有定时器
local function publicTimerCbFnc(tag)
log.info("publicTimerCbFnc",tag)
endsys.timerStart(publicTimerCbFnc,8000,"first")
sys.timerStart(publicTimerCbFnc,8000,"second")
sys.timerStart(publicTimerCbFnc,8000,"third")
sys.timerStopAll(publicTimerCbFnc)

5.3 sys.timerStart(fnc, ms, …)

创建并且启动一个单次定时器

有两种方式可以唯一标识一个定时器:

1、定时器 ID

2、定时器回调函数和可变参数

  • 参数
**名称**
**传入值类型**
**释义**
fnc
param
定时器回调函数,必须存在,不允许为 nil 当定时器超时时间到达时,回调函数的调用形式为 fnc(…),其中…为回调参数
ms
number
定时器超时时间,单位毫秒,最小 1,最大 0x7FFFFFFF 实际上支持的最小超时时间是 5 毫秒,小于 5 毫秒的时间都会被转化为 5 毫秒

param
可变参数,回调函数 fnc 的回调参数
  • 返回值

number timerId,创建成功返回定时器 ID;创建失败返回 nil

  • 例子
-- 创建一个5秒的单次定时器,回调函数打印"timerCb",没有可变参数:
sys.timerStart(function() log.info("timerCb") end, 5000)_
-- 创建一个5秒的单次定时器,回调函数打印"timerCb"和"test",可变参数为"test":
sys.timerStart(function(tag) log.info("timerCb",tag) end, 5000, "test")

5.4 sys.timerLoopStart(fnc, ms, …)

创建并且启动一个循环定时器

有两种方式可以唯一标识一个定时器:

1、定时器 ID

2、定时器回调函数和可变参数

  • 参数
**名称**
**传入值类型**
**释义**
fnc
param
定时器回调函数,必须存在,不允许为 nil 当定时器超时时间到达时,回调函数的调用形式为 fnc(…),其中…为回调参数
ms
number
定时器超时时间,单位毫秒,最小 1,最大 0x7FFFFFFF 实际上支持的最小超时时间是 5 毫秒,小于 5 毫秒的时间都会被转化为 5 毫秒

param
可变参数,回调函数 fnc 的回调参数
  • 返回值

number timerId,创建成功返回定时器 ID;创建失败返回 nil

  • 例子
-- 创建一个5秒的循环定时器,回调函数打印"timerCb",没有可变参数:
sys.timerLoopStart(function() log.info("timerCb") end, 5000)_
-- 创建一个5秒的循环定时器,回调函数打印"timerCb"和"test",可变参数为"test":
sys.timerLoopStart(function(tag) log.info("timerCb",tag) end, 5000, "test")

5.5 sys.timerIsActive(val, …)

判断“通过 timerStart 或者 timerLoopStart 创建的定时器”是否处于激活状态

  • 参数
**名称**
**传入值类型**
**释义**
val
param
定时器标识,有两种表示形式 1、number 类型,通过 timerStart 或者 timerLoopStart 创建定时器时返回的定时器 ID,此情况下,不需要传入回调参数…就能唯一标识一个定时器 2、function 类型,通过 timerStart 或者 timerLoopStart 创建定时器时的回调函数,此情况下,如果存在回调参数,需要传入回调参数…才能唯一标识一个定时器

param
回调参数,和“通过 timerStart 或者 timerLoopStart 创建定时器”的回调参数保持一致
  • 返回值

status,定时器激活状态;根据 val 的表示形式,有不同的返回值:

1、val 为 number 类型时:如果处于激活状态,则返回 function 类型的定时器回调函数;否则返回 nil

2、val 为 function 类型时:如果处于激活状态,则返回 bool 类型的 true;否则返回 nil

  • 例子
-- 定时器ID形式标识定时器的使用参考:
local timerId1 = sys.timerStart(function() end,5000)
sys.taskInit(function()
sys.wait(3000)
log.info("after 3 senonds, timerId1 isActive?",sys.timerIsActive(timerId1))
sys.wait(3000)
log.info("after 6 senonds, timerId1 isActive?",sys.timerIsActive(timerId1))end)_
-- 回调函数和回调参数标识定时器的使用参考:
local function timerCbFnc2(tag)log.info("timerCbFnc2",tag)end
sys.timerStart(timerCbFnc2,5000,"test")
sys.taskInit(function()
sys.wait(3000)
log.info("after 3 senonds, timerCbFnc2 test isActive?",sys.timerIsActive(timerCbFnc2,"test"))
sys.wait(3000)
log.info("after 6 senonds, timerCbFnc2 test isActive?",sys.timerIsActive(timerCbFnc2,"test"))end)

5.6 sys.waitUntil(id, ms)

task 任务条件等待函数(支持事件消息和定时器消息)

只能直接或者间接的被 task 任务主函数调用,调用本接口的 task 会挂起

  • 参数
**名称**
**传入值类型**
**释义**
id
string
消息 ID,建议使用 string 类型
ms
number
可选参数,默认为 nil 延时时间,单位毫秒,最小 1,最大 0x7FFFFFFF 实际上支持的最小超时时间是 5 毫秒,小于 5 毫秒的时间都会被转化为 5 毫秒
  • 返回值

result,data,分为如下三种情况:

1、如果存在超时时间参数:

(1)、在超时时间到达之前,如果收到了等待的消息 ID,则 result 为 true,data 为消息 ID 携带的参数(可能是多个参数)

(2)、在超时时间到达之前,如果没收到等待的消息 ID,则 result 为 false,data 为 nil

2、如果不存在超时时间参数:如果收到了等待的消息 ID,则 result 为 true,data 为消息 ID 携带的参数(可能是多个参数)

(1)、如果收到了等待的消息 ID,则 result 为 true,data 为消息 ID 携带的参数(可能是多个参数)

(2)、如果没收到等待的消息 ID,则 task 一直挂起

3、还存在一种特殊情况,本 task 挂起时,可能被 task 的外部应用逻辑给主动激活(如果不是故意为之,可能是写 bug 了)

  • 例子
--task延时120秒或者收到"SIM_IND"消息:
sys.taskInit(function()local result, data = sys.waitUntil("SIM_IND",120000)end)

5.7 sys.waitUntilExt(id, ms)

Task 任务的条件等待函数扩展(包括事件消息和定时器消息等条件),只能用于任务函数中。

  • 参数
**名称**
**传入值类型**
**释义**
id
param
消息 ID
ms
number
等待超时时间,单位 ms,最大等待 126322567 毫秒
  • 返回值

message 接收到消息返回 message,超时返回 false

data 接收到消息返回消息参数

  • 例子
result, data = sys.waitUntilExt("SIM_IND", 120000)

六、代码示例介绍

6.1 DEMO 软件总体设计

6.1.1 初始化部分:

PROJECT 和 VERSION 定义了项目名称和版本,适用于 LuaTools。

sys 库是管理系统任务、定时器等功能的核心库。

6.1.2 定时器回调函数:

oneShotCallback:单次定时器触发时执行的回调,输出 "One-shot timer triggered"。

periodicCallback 和 periodicCallback1:周期性定时器触发时执行的回调,分别打印触发的次数。

6.1.3 定时器的启动:

sys.timerStart 启动一个定时器并传入回调函数、延迟时间(毫秒),以及可选的定时器标识和传递的消息。

第一个定时器是单次触发,延迟 3 秒后触发 oneShotCallback。

接下来,分别在 7 秒、6 秒、5 秒后触发三次 periodicCallback。

最后,启动一个周期性定时器,每 2 秒触发一次 periodicCallback1,并计数打印日志。

6.1.4 等待与停止定时器:

使用 sys.wait(5000) 来等待 5 秒。

在等待过程中,使用 sys.timerStart 启动新的定时器来停止周期性定时器 periodicTimerId2,并打印日志“stop 2s loop timer periodicCallback1”。

使用 sys.timerStart 再次启动定时器来停止所有 periodicCallback 类型的定时器,并打印日志。

6.2 完整程序清单

注:完整复制后保存为 main.lua,可直接使用

-- main.lua文件
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "timer_demo"
VERSION = "1.0.0"

-- sys库是标配
sys = require("sys")

sys.taskInit(function()
log.info("------start timer_demo------")
-- 定义一个单次触发的定时器回调函数
local function oneShotCallback(message)
log.info("One-shot timer triggered: " .. message)
end

-- 定义一个周期性触发的定时器回调函数
local function periodicCallback(count)
log.info("Periodic timer triggered (Count: " .. count .. ")")
end

-- 定义一个周期性触发的定时器回调函数
local function periodicCallback1(count)
log.info("Periodic timer triggered1 (Count: " .. count .. ")")
end

-- 初始化计数器,用于周期性定时器
local periodicCount = 0

-- 启动一个单次触发的定时器,延迟3秒后触发
local oneShotTimerId = sys.timerStart(oneShotCallback, 3000, 0, "Hello from one-shot timer!")

-- 启动一个一次性定时器,分别再第7秒触发一次
sys.timerStart(periodicCallback,7000,"first")
-- 启动一个一次性定时器,分别再第6秒触发一次
sys.timerStart(periodicCallback,6000,"second")
-- 启动一个一次性定时器,分别再第5秒触发一次
sys.timerStart(periodicCallback,5000,"third")

-- 启动一个周期性触发的定时器,每2秒触发一次
local periodicTimerId2 = sys.timerLoopStart(function()    periodicCount = periodicCount + 1    periodicCallback1(periodicCount)
end, 2000)

--等待5000ms
sys.wait(5000)

-- 停止周期性定时器
sys.timerStart(function()
sys.timerStop(periodicTimerId2)
log.info("stop 2s loop timer periodicCallback1")
end,5000)

-- 停止所有定时器(仅作为测试,实际应用中应根据需要停止)
sys.timerStart(function()
sys.timerStopAll(periodicCallback)
log.info("stop periodicCallback loop timer ")
end,4000)

end)

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

七、功能验证

7.1 下载固件

7.2 单次定时器触发输出 One-shot timer triggered

7.3 分别在 7、6、5 秒触发的定时器

7.4 周期触发定时器输出

7.5 停止 periodicTimerId2 定时器

7.6 停止所有定时器

八、常见问题:

8.1 回调函数执行异常:

  • 如果定时器的回调函数中存在异常处理不当的情况,可能会导致程序崩溃或产生不可预知的行为。
  • 需要在回调函数中做好异常处理,确保程序的健壮性。

8.2 定时器冲突:

  • 在多个定时器同时存在的情况下,可能会存在定时器冲突的问题,即多个定时器同时触发或相互干扰。
  • 需要合理设计定时器的触发时间和周期,避免冲突的发生。

8.3 资源占用问题:

  • 定时器的创建、启动和停止等操作可能会占用一定的系统资源,如内存、CPU 等。
  • 在资源受限的嵌入式系统中,需要合理管理定时器的使用,避免资源过度占用。

8.4 定时器 ID 管理:

  • 在使用定时器接口函数时,通常会返回一个定时器 ID 用于后续操作。如果定时器 ID 管理不当,可能会导致无法正确停止或删除定时器。
  • 需要建立良好的定时器 ID 管理机制,确保定时器的正确操作。

九、扩展

9.1 定时器的嵌套与递归

  • 嵌套定时器:在某些情况下,一个定时器的回调函数可能会启动另一个定时器。这种嵌套定时器的使用需要特别小心,以避免无限递归或资源耗尽。
  • 递归定时器:递归定时器是指一个定时器在其回调函数中重新启动自己。这种用法需要特别注意避免无限循环和堆栈溢出。

9.2 定时器的动态调整

  • 周期调整:在某些应用中,可能需要动态调整定时器的周期。这通常涉及停止当前定时器并重新启动一个新周期的定时器。
  • 任务优先级调整:对于某些实时性要求较高的任务,可能需要动态调整定时器的优先级,以确保任务能够及时执行。