接上一篇
對于 LuatOS 應用程序來說,定時器本質上也算是一種特殊的消息,因為定時器太常用了,所以把他單獨拎出來,單獨的一個章節進行講解;
2.3.1 基本概念
LuatOS 定時器的分類如下:

LuatOS 定時器管理的 API 列表如下:
(1) 單次定時器創建并且啟動:sys.timerStart(cbfunc, timeout, ...)
(2) 循環定時器創建并且啟動:sys.timerLoopStart(cbfunc, timeout, ...)
(3) 單個定時器停止并且刪除:sys.timerStop(timer_id)
(4) 單個定時器停止并且刪除:sys.timerStop(cbfunc, ...)
(5) 多個定時器停止并且刪除:sys.timerStopAll(cbfunc)
(6) 阻塞等待一段時間(只能在 task 中使用):sys.wait(timeout)
(7) 阻塞等待全局消息或者阻塞等待一段時間(只能在 task 中使用):sys.waitUntil(msg, timeout)
(8) 阻塞等待定向消息或者阻塞等待一段時間(只能在 task 中使用):sys.waitMsg(task_name, msg, timeout)
2.3.2 定時器消息處理的完整周期

2.3.3 sys.timerStart(cbfunc, timeout, ...)
功能
創建并且運行一個單次定時器;
注意事項
可以在能夠執行到的任意代碼位置使用此函數;
有兩種方式可以唯一標識一個定時器:
1、定時器 id;如果使用 sys.timerStart(cbfunc, timeout, ...)創建定時器成功,會返回定時器 id
2、定時器回調函數 cbfunc 和可變參數...,此種方式的說明如下:
如果 cbfunc 和...相同,重復調用 sys.timerStart(cbfunc, timeout, ...)接口創建并且運行定時器;
在 sys.timerStart 內部會自動停止并且刪除已經存在的重復定時器;
例如執行如下三行代碼后:
sys.timerStart(led_on_timer_cbfunc, 1000, "red")
sys.timerStart(led_on_timer_cbfunc, 2000, "red")
sys.timerStart(led_on_timer_cbfunc, 3000, "red")
最后只有 sys.timerStart(led_on_timer_cbfunc, 3000, "red") 這個定時器在運行,前面創建的兩個定時器都被自動刪除了,沒有完整運行;
參數
cbfunc

timeout

關于定時器精度的問題,我們再來看下面這張圖來理解:
1、FreeRTOS中的一些任務優先級比Lua虛擬機任務優先級高,尤其是4G網絡中斷的任務優先級最高,這些高優先級的任務的搶占執行,會直接影響Lua虛擬機任務執行的實時性,進而導致sys.run()調度器的運行實時性也不會很高;
2、在Lua虛擬機任務內部的sys.run()調度器中,首先是遍歷并且分發處理用戶全局消息隊列中的所有消息,這些消息全部處理完,才會去執行內核消息隊列中的第一條消息,定時器事件到達的消息是存儲在內核消息隊列中的,如果用戶全局消息隊列中的消息處理耗時較長,或者內核消息隊列中在定時器消息之前還有其他消息(例如串口消息,mqtt消息等),定時器消息都要排隊才能執行,所以整個項目的業務越復雜,系統負載就越重,消息數量就越多,定時器消息處理的實時性就越低;


返回值
local timer_id = sys.timerStart(cbfunc, timeout, ...)
有一個返回值為 timer_id
timer_id

示例

2.3.4 sys.timerLoopStart(cbfunc, timeout, ...)
功能
創建并且運行一個循環定時器;
注意事項
可以在能夠執行到的任意代碼位置使用此函數;
有兩種方式可以唯一標識一個定時器:
1、定時器 id;如果使用 sys.timerLoopStart(cbfunc, timeout, ...)創建定時器成功,會返回定時器 id
2、定時器回調函數 cbfunc 和可變參數...,此種方式的說明如下:
如果 cbfunc 和...相同,重復調用 sys.timerLoopStart(cbfunc, timeout, ...)接口創建并且運行定時器;
在 sys.timerLoopStart 內部會自動停止并且刪除已經存在的重復定時器;
例如執行如下三行代碼后:
sys.timerLoopStart(led_on_timer_cbfunc, 1000, "red")
sys.timerLoopStart(led_on_timer_cbfunc, 2000, "red")
sys.timerLoopStart(led_on_timer_cbfunc, 3000, "red")
最后只有 sys.timerLoopStart(led_on_timer_cbfunc, 3000, "red") 這個定時器在運行,前面創建的兩個定時器都被自動刪除了,沒有完整運行;
參數
cbfunc

timeout

...

返回值
local timer_id = sys.timerLoopStart(cbfunc, timeout, ...)
有一個返回值為 timer_id
timer_id

示例

2.3.5 sys.timerStop(timer_id)
功能
根據定時器 id 停止運行并且刪除一個定時器;
注意事項
可以在能夠執行到的任意代碼位置使用此函數;
有兩種方式可以唯一標識一個定時器:
1、定時器 id;如果使用 sys.timerStart(cbfunc, timeout, ...)或者 sys.timerLoopStart(cbfunc, timeout, ...)創建定時器成功,會返回定時器 id
2、定時器回調函數 cbfunc 和可變參數...;
參數
timer_id

返回值
nil
示例

2.3.6 sys.timerStop(cbfunc, ...)
功能
根據定時器的回調函數 cbfunc 和可變參數...停止運行并且刪除一個定時器;
注意事項
可以在能夠執行到的任意代碼位置使用此函數;
有兩種方式可以唯一標識一個定時器:
1、定時器 id;如果使用 sys.timerStart(cbfunc, timeout, ...)或者 sys.timerLoopStart(cbfunc, timeout, ...)創建定時器成功,會返回定時器 id;
2、定時器回調函數 cbfunc 和可變參數...;
參數
cbfunc

返回值
nil
示例

2.3.7 sys.timerStopAll(cbfunc)
功能
停止運行并且刪除回調函數為 cbfunc 的所有定時器;
注意事項
可以在能夠執行到的任意代碼位置使用此函數;
參數
cbfunc

返回值
nil
示例

2.3.8 sys.wait(timeout)
功能
在 task 中阻塞等待一段時間;
注意事項
只能在基礎 task 和高級 task 處理函數的業務邏輯中使用此函數;
參數
timeout

返回值
nil
示例

sys.waitUntil(msg, timeout)
功能
在 task 中阻塞等待一個全局消息;
注意事項
只能在基礎 task 和高級 task 處理函數的業務邏輯中使用此函數;
sys.publish(msg, ...) 和 sys.waitUntil(msg, timeout)配合使用時:
在 sys.publish(msg, ...)之前,必須保證 task 正在 sys.waitUntil(msg, timeout)代碼處,處于阻塞等待狀態;
這樣才能保證發布的 msg 消息可以被 task 處理;
同一個全局消息 msg,可以被多個正在 sys.waitUntil(msg, timeout)代碼處阻塞等待的 task 處理;
參數
msg

timeout

返回值
local result, arg1, arg2, arg3, argN = sys.waitUntil(msg, timeout)
有數量不固定的返回值:
第一個返回值為 result
剩余的返回值 arg1, arg2, arg3, argN,表示可變數量的返回值,只有當第一個返回值 result 為 true 時,這些可變數量的返回值才有意義,和 sys.publish(msg, ...)中...表示的可變參數一一對應
result

arg1, arg2, arg3, argN

正確示例

錯誤示例

2.3.9 sys.waitUntil(msg, timeout)
功能
在 task 中阻塞等待一個全局消息;
注意事項
只能在基礎 task 和高級 task 處理函數的業務邏輯中使用此函數;
sys.publish(msg, ...) 和 sys.waitUntil(msg, timeout)配合使用時:
在 sys.publish(msg, ...)之前,必須保證 task 正在 sys.waitUntil(msg, timeout)代碼處,處于阻塞等待狀態;
這樣才能保證發布的 msg 消息可以被 task 處理;
同一個全局消息 msg,可以被多個正在 sys.waitUntil(msg, timeout)代碼處阻塞等待的 task 處理;
參數
msg

timeout

返回值
local result, arg1, arg2, arg3, argN = sys.waitUntil(msg, timeout)
有數量不固定的返回值:
第一個返回值為 result
剩余的返回值 arg1, arg2, arg3, argN,表示可變數量的返回值,只有當第一個返回值 result 為 true 時,這些可變數量的返回值才有意義,和 sys.publish(msg, ...)中...表示的可變參數一一對應
result

arg1, arg2, arg3, argN

正確示例

錯誤示例

2.3.10 sys.waitMsg(task_name, msg, timeout)
功能
在 task 中阻塞等待名稱為 task_name 的 task 的定向消息;
注意事項
只能在高級 task 處理函數的業務邏輯中使用此函數;
sys.sendMsg(task_name, msg, arg2, arg3, arg4)是定向消息的生產者,定向消息有生產就會有消費,不然消息就沒有存在的意義了;
sys.waitMsg(task_name, msg, timeout)所在的 task 是定向消息的消費者;
sys.sendMsg(task_name, msg, arg2, arg3, arg4) 和 sys.waitMsg(task_name, msg, timeout)配合使用;
在 sys.sendMsg(task_name, msg, arg2, arg3, arg4)之前,需要保證名稱為 task_name 的 task 已經被創建,否則定向消息也會丟失;
參數
task_name

msg

timeout

返回值
local message = sys.waitMsg(task_name, msg, timeout)
有一個返回值為 message
message

示例

2.3.11 定時器代碼示例
在了解了定時器的 api 之后,我們再看下圖回顧一下定時器消息處理的完整周期

下面這個例子用來說明定時器的使用方法;
這個例子的完整代碼鏈接:timer.lua
核心代碼片段如下,我們首先分析下這段代碼的業務邏輯

我們在模擬器上實際運行一下看看,輸入命令
luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini
運行日志如下:

我們結合運行日志分析一下代碼的業務邏輯是否執行正常;
2.4 task 內部運行環境 vs task 外部運行環境
在前文內容中,我們提到了應用腳本代碼的兩種運行環境;當時僅僅對這兩種概念做了一個初步的介紹,并沒有結合示例來講解,現在我們已經學習了 task,msg,timer,可以結合 task,msg,timer 來舉一些實際的例子,來進一步理解這兩種運行環境;
2.4.1 基本概念
首先復現一下這兩種運行環境的概念:
在 LuatOS 應用腳本開發過程中,我們所編寫的應用腳本代碼,存在兩種業務邏輯的運行環境:
1、一種是在 task 的任務處理函數內部的業務環境中運行,我們簡稱為:在 task 內部運行;
2、一種是在 task 的任務處理函數外部的業務環境中運行,我們簡稱為:在 task 外部運行;
怎么理解這兩種業務邏輯運行環境?我們看下面這張圖
看右邊生長出分支的這棵大樹,這棵大樹就是 FreeRTOS 創建的 Lua 虛擬機 task,是一個 FreeRTOS task;
在這個 Lua 虛擬機 FreeRTOS task 上,這棵大樹再分為兩部分:
1、樹干部分:樹干部分運行的業務邏輯環境就是 LuatOS task 外部運行環境;
2、樹枝部分:每個樹枝都是一個獨立的 LuatOS task,樹枝部分運行的業務邏輯環境就是 LuatOS task 內部運行環境;


2.4.2 sys api 需要的運行環境
接下來對 task、msg、timer 的 api 需要的運行環境做一個說明

從以上表格可以看出,sys 核心庫中的 api,從需要的運行環境來看,分為以下三類:
1、大部分的 api,既可以在 task 內部運行,也可以在 task 外部運行;
2、sys.waitUntil,sys.waitMsg,sys.wait,這三個 spi,只能在 task 內部運行;
3、sys.run,只能在 task 外部運行;
2.4.3 sys api 的回調函數提供的運行環境

從以上表格可以看出,sys 核心庫中的 api,如果支持回調函數,這些回調函數內部提供的運行環境,分為以下兩類:
sys.taskInitEx(task_func, task_name,non_targeted_msg_cbfunc, ...)中的回調函數non_targeted_msg_cbfunc,提供的是 task 內部運行環境;
sys.subscribe(msg,msg_cbfunc),sys.timerStart(cbfunc, timeout, ...),sys.timerLoopStart(cbfunc, timeout, ...)中的回調函數,提供的是 task 外部運行環境;所以這些回調函數內部不能調用“只能在 task 內部運行”的 api,例如在 sys.subscribe(msg,msg_cbfunc)的msg_cbfunc內部不能調用 sys.waitUntil,sys.waitMsg,sys.wait;
2.4.4 常犯的錯誤
新接觸 LuatOS 開發的用戶,經常會犯上面黃色背景標注的這個錯誤;
下面這個例子用來說明常犯的這種錯誤;
這個例子的完整代碼鏈接:task_inout_env_err.lua
核心代碼片段如下,我們首先分析下這段代碼的業務邏輯(實際運行演示時,每次打開三段黃色背景代碼中的其中一段)

我們在模擬器上實際運行一下看看,輸入命令
luatos --llt=H:Luatoolsprojectluatos_framework_luatos_task_Air8000.ini
運行日志如下:



2.5 sys 核心庫 api 的組合使用關系
我們已經學習過了 sys 核心庫中的 task,msg,timer 的 api,在這些 api 中:
1、有些 api 必須在一起組合使用,才能實現完整的業務流程;
2、有些 api 禁止在一起組合使用,否則會導致業務出錯;
在這些 api 中,主要是消息的發送和接收 api 容易混用,組合使用關系參考下表(每一行的兩個單元格所表示的 api 必須組合使用):

2.6 LuatOS 應用軟件調度機制(sys.run()函數)
1、sys 核心庫是 LuatOS 運行框架庫,是 LuatOS 應用程序運行的核心大腦,所有 LuatOS 應用項目都會使用到 sys 核心庫;
2、截止到目前,我們已經學習了 sys 核心庫提供的 task,msg,timer 功能;
3、sys 核心庫還剩最后一個功能 api,sys.run();
4、sys 核心庫是 LuatOS 應用程序運行的核心大腦,sys.run()是 sys 核心庫的大腦,負責整個 LuatOS 應用腳本程序的調度和管理,是 LuatOS 應用程序的調度器;
sys.run()非常重要,但是 sys.run()使用起來非常簡單,僅僅在 main.lua 的最后一行調用 sys.run()即可。
雖然 sys.run()使用起來非常簡單,但是如果大家對 sys.run()的運行原理有一個總體性的理解和認識,對開發 LuatOS 應用項目來說,幫助很大。
所以在這里,我先對 sys.run()內部的工作原理做一個簡化后的總體介紹,至于更詳細的原理介紹,我們會在后續的 LuatOS 直播課程中講解;

我們看上面這張圖:
1、LuatOS 內核固件中的 FreeRTOS 會創建一個 Lua 虛擬機任務;
2、Lua 虛擬機任務的處理函數中,首先進行初始化:
(1) 在內核固件的 C 代碼中,加載 Lua 標準庫和 LuatOS 核心庫;
(2) 從 LuatOS 的腳本分區找到 main.lua
(3) 開始逐行嵌套解析執行 main.lua 中的腳本代碼(加載必要的擴展庫腳本文件和自己開發的應用腳本文件,并且運行這些腳本文件的初始化代碼)
3、運行 main.lua 的最后一行代碼 sys.run()
4、sys.run()中的實現是一個 while true 循環,在這個循環內,不斷地從內核消息隊列和用戶全局消息隊列中讀取消息,并且分發消息給接收者進行處理。

2.7 分析 mqtt demo 中的 task,msg,timer,run 的使用案例
現在,LuatOS 框架的使用,基本上講完了,接下來,我們來實際看一個完整 mqtt demo 項目代碼,重點分析下這份 demo 項目代碼中,使用到的本章節講解的知識點;
mqtt demo 代碼路徑:Air8000 mqtt demo;
Mqtt demo 項目的總體設計框圖如下:

這份mqtt demo中的readme文件,以及代碼中的注釋都比較詳細,接下來我用vscode直接打開這份demo項目代碼,從以下幾方面講解一下:
1、先總體看一下mqtt demo的readme文件,讓大家對這個demo項目的業務邏輯有一個總體的認識;
2、從以下幾方面來詳細分析mqtt demo項目代碼:
mqtt demo項目腳本的整體運行邏輯;
mqtt demo項目腳本中使用到的LuatOS task,message,timer,調度器代碼解讀;
通過分析和本篇文章有關的代碼,讓大家對本節理解更加深刻;
現在我們開始進入mqtt demo項目中去分析;
三、課后作業
至少二選一
3.1 開發代碼,在 LuatOS 模擬器 上驗證可以同時運行的定時器數量
作業提交內容:
1、 6 個 Lua 文件
(1) main.lua:初始化,加載下面的 5 個 lua 文件功能模塊(每次只打開其中的 1 個進行驗證),執行 sys.run;(可以參考本講課程中的 demo)
(2) timer_start.lua:使用 sys.timerStart 接口來驗證可以同時運行的定時器數量;
(3) timer_loop_start.lua:使用 sys.timerLoopStart 接口來驗證可以同時運行的定時器數量;
(4) wait.lua:使用 sys.wait 接口來驗證可以同時運行的定時器數量;
(5) wait_until.lua:使用 sys.waitUntil 接口來驗證可以同時運行的定時器數量;
(6) wait_msg.lua:使用 sys.waitMsg 接口來驗證可以同時運行的定時器數量;
2、1 個運行日志文件
3、1 個分析文件,給出可以同時運行多少個定時器的結論,然后結合代碼和日志分析出來為什么可以同時運行這么多的定時器;
3.2 開發代碼,在 Air 系列模組的開發板或者核心板 上驗證可以同時運行的定時器數量
作業提交內容:
1、 6 個 Lua 文件
(1) main.lua:初始化,加載下面的 5 個 lua 文件功能模塊(每次只打開其中的 1 個進行驗證),執行 sys.run;(可以參考本講課程中的 demo)
(2) timer_start.lua:使用 sys.timerStart 接口來驗證可以同時運行的定時器數量;
(3) timer_loop_start.lua:使用 sys.timerLoopStart 接口來驗證可以同時運行的定時器數量;
(4) wait.lua:使用 sys.wait 接口來驗證可以同時運行的定時器數量;
(5) wait_until.lua:使用 sys.waitUntil 接口來驗證可以同時運行的定時器數量;
(6) wait_msg.lua:使用 sys.waitMsg 接口來驗證可以同時運行的定時器數量;
2、 1 個運行日志文件
3、 1 個分析文件,給出可以同時運行多少個定時器的結論,然后結合代碼和日志分析出來為什么可以同時運行這么多的定時器;
今天的內容就分享到這里了~
審核編輯 黃宇
-
定時器
+關注
關注
23文章
3368瀏覽量
123575 -
LuatOS
+關注
關注
0文章
156瀏覽量
2692
發布評論請先 登錄
LuatOS 框架的嵌入式系統架構設計原理
LuatOS 系統框架的模塊化設計原理
基于LuatOS的MQTT物聯網通信全解
輕松掌握——LuatOS socket基礎知識和應用開發
LuatOS框架的使用(上)
LuatOS-Air轉LuatOS常見故障排查手冊
警惕兼容性陷阱:LuatOS-Air腳本在LuatOS中的運行異常分析
掌握LuatOS系統消息:新手也能看懂的列表詳解
LuatOS腳本開發入門:嵌入式運行框架全解析!
嵌入式開發新選擇:LuatOS腳本框架入門教程
Task任務:LuatOS實現“任務級并發”的核心引擎
揭秘LuatOS Task:多任務管理的“智能中樞”
解碼LuatOS:短信功能的底層運作機制
解鎖LuatOS新世界:二次開發必備的3個核心常識
解鎖LuatOS-log庫:全棧工程師的日志管理實戰課!
解鎖:LuatOS框架的使用(下篇)
評論