定时器(timer)
一、简介
定时器(timer)是一项基础且关键的服务。它允许开发者在特定的时间点或周期性地执行代码段,为物联网设备的运行提供了精确的时间控制。定时器在多种应用场景中都发挥着重要作用,如定时发送数据、周期性检查传感器状态等。
二、演示功能概述
本教程教你如何用 Air724 开发板,演示定时器功能,并且通过日志观察实验结果。
三、准备硬件环境
3.1 开发板准备
使用 EVB_Air724 开发板,如下图所示:
淘宝购买链接:Air724UG-NFM 开发板淘宝购买链接 ;
此开发板的详细使用说明参考:Air724UG 产品手册 中的《EVB_Air724UG_AXX 开发板使用说明》,写这篇文章时最新版本的使用说明为:《EVB_Air724UG_A14 开发板使用说明》;开发板使用过程中遇到任何问题,可以直接参考这份使用说明文档。
api:https://doc.openluat.com/wiki/21?wiki_page_id=2068
3.2 数据通信线
USB 数据线一根(micro USB)。
3.3 PC 电脑
WIN7 以及以上版本的 WINDOWS 系统。
3.4 SIM 卡
中国大陆环境下,可以上网的 SIM 卡。一般来说,使用移动,电信,联通的物联网卡或者手机卡都行。
3.5 组装硬件环境
USB 数据线插入 USB 口,另一端与电脑相连,拨码开关全部拨到 ON,串口切换开关选择 UART1,USB 供电的 4V 对应开关拨至 ON 档,SIM 卡放到 SIM 卡槽中锁紧,如下图所示。
四、准备软件环境
4.1 下载调试工具
使用说明参考:Luatools 下载和详细使用
4.2 源码及固件
1.底层 core 下载
下载底层固件,并解压
链接:https://docs.openluat.com/air724ug/luatos/firmware/
如下图所示,红框的是我们要使用到的。
2.本教程使用的 demo:
4.3 下载固件和脚本到开发板中
打开 Luatools,开发板上电开机,如开机成功 Luatools 会打印如下信息。
点击项目管理测试选项。
进入管理界面,如下图所示。
- 点击选择文件,选择底层固件,我的文件放在 D:\luatOS\Air724 路径中。
- 点击增加脚本或资源文件,选择 之前下载的程序源码,如下图所示。
- 点击下载底层和脚本,下载完成如下图所示。
五、代码示例介绍
5.1 API 说明
5.1.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.1.2 sys.timerStopAll(fnc)
关闭 sys.timerStart 和 sys.timerLoopStart 创建的某个回调函数的所有定时器
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
fnc | function | 定时器回调函数 |
- 返回值
nil
- 例子
_-- 关闭回调函数为publicTimerCbFnc的所有定时器_local function publicTimerCbFnc(tag)log.info("publicTimerCbFnc",tag)
end
sys.timerStart(publicTimerCbFnc,8000,"first")
sys.timerStart(publicTimerCbFnc,8000,"second")
sys.timerStart(publicTimerCbFnc,8000,"third")
sys.timerStopAll(publicTimerCbFnc)
5.1.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.1.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.1.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.1.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.1.7 sys.waitUntilExt(id, ms)
Task 任务的条件等待函数扩展(包括事件消息和定时器消息等条件),只能用于任务函数中。
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
id | param | 消息 ID |
ms | number | 等待超时时间,单位 ms,最大等待 126322567 毫秒 |
- 返回值
message 接收到消息返回 message,超时返回 false
data 接收到消息返回消息参数
- 例子
result, data = sys.waitUntilExt("SIM_IND", 120000)
5.2 timer.lua 代码
系统启动后创建了一个 sys.wait(3000),延时 3s 后再进行当前协程。避免开机后程序跑太快,我们看不清日志输出。开始后,创建了一个单次定时器且只有收到消息id才会执行 sys.waitUntil(“MSG_ID”),这个定时器又会将当前协程停下来,它需要等待一个指令才肯放行("MSG_ID"这个消息 ID)。还记得上面循环定时器里那个 sys.publish(“MSG_ID”)嘛,每隔 5s 就会发送 sys.waitUntil(“MSG_ID”)需要的消息 ID。
module(...,package.seeall)
--循环定时器打印函数
local function log1()
log.info("log","每5s打印一次,循环定时器的作用")
sys.publish("MSG_ID")
end
--循环定时器,5s执行一次log1函数
sys.timerLoopStart(log1,5000)
--第二个task的调用函数,给第一个task判断定时器是否激活用的。
local a
local function timer_2()
a=sys.timerStart(function ()
log.info("log", "我的用途就是给下面的sys.timerIsActive判断死活用的")
end,20000)
end
--第一个task的执行函数
local function timer_1()
--sys.wait(ms),单次定时器且只能用在task中,或者被task的主函数直接或者间接调用。
sys.wait(4000)
log.info("log", "开始!")
--sys.waitUntil(),单次定时器且只有收到消息id才会执行。有超时参数时:如果时间到达没有等到消息ID也会会执行。没有超时参数时:死等
sys.waitUntil("MSG_ID")
log.info("log", "接收到\"MSG_ID\"消息")
--判断timer_2里定时器的激活状态,如果激活则执行。
if sys.timerIsActive(a) then
sys.timerStart(function ()
log.info("log", "循环定时器活着,我是有timerStart定时器执行的")
end,1000)
end
--等待10s后开始执行rtos.sleep(6000),让lua虚拟机挂起6s。
sys.wait(10000)
log.info("log", "先睡6s")
--rtos.sleep(6000)
end
sys.taskInit(timer_2)
sys.taskInit(timer_1)
5.3 main.lua 代码
本代码为主程序脚本,系统启动后首先会对 4G 网络进行配置,等待网络连接成功,然后加载测试模块。
六、开机调试
6.1 开发板开机
连接好硬件并下载固件后,启动 Luatools 软件,系统运行信息将显示在界面中。红框中为开发板连接到 PC 机后正常打印的信息,如下图所示。
6.2 功能调试
定时器启动后创建了一个单次定时器 sys.waitUntil(“MSG_ID”),这个定时器又会将当前协程停下来,它需要等待一个指令才肯放行("MSG_ID"这个消息 ID)。
七、常见问题
7.1 使用定时器 sys.wait()或者 sys.waitUntil(),程序出错。
sys.wait()和 sys.waitUntil()只能用于 task 中。
7.2 定时器精度问题
LuatOS 开发最小仅支持 5 毫秒的定时器,超时时间最小 1ms(实际支持的最小时间是 5ms,小于 5ms 的时间都被转化为 5ms) 超时间最大 0x7FFFFFFF(24.85 天)。 另外毫秒级的定时器的误差较大,原因可参考 LuatOS 运行框架
给读者的话
本篇文章由
杨超
开发;本篇文章描述的内容,如果有错误、细节缺失、细节不清晰或者其他任何问题,总之就是无法解决您遇到的问题;
请登录合宙技术交流论坛,点击文档找错赢奖金-Air724UG-LuatOS-软件指南-常用功能实现-定时器(timer);
用截图标注+文字描述的方式跟帖回复,记录清楚您发现的问题;
我们会迅速核实并且修改文档;
同时也会为您累计找错积分,您还可能赢取月度找错奖金!