定时器(timer)
一、定时器(timer)的概述
在 LuatOS 系统中,定时器(timer)是一项基础且关键的服务。它允许开发者在特定的时间点或周期性地执行代码段,为物联网设备的运行提供了精确的时间控制。定时器在多种应用场景中都发挥着重要作用,如定时发送数据、周期性检查传感器状态等。
二、演示功能概述
本 demo 通过使用 Air8101 开发板,带你快速 timer 的基本功能。
三、准备硬件环境
“古人云:‘工欲善其事,必先利其器。’在深入介绍本功能示例之前,我们首先需要确保以下硬件环境的准备工作已经完成。”
参考:硬件环境清单,准备以及组装好硬件环境。
四、软件环境
“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。
- Luatools 工具;
- 内核固件文件(底层 core 固件文件):LuatOS-SoC_V10001_Air8101.soc;参考项目使用的内核固件;
- 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 定时器的动态调整
- 周期调整:在某些应用中,可能需要动态调整定时器的周期。这通常涉及停止当前定时器并重新启动一个新周期的定时器。
- 任务优先级调整:对于某些实时性要求较高的任务,可能需要动态调整定时器的优先级,以确保任务能够及时执行。