18video性欧美19sex,欧美高清videosddfsexhd,性少妇videosexfreexxx片中国,激情五月激情综合五月看花,亚洲人成网77777色在线播放

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

LuatOS腳本開發(fā)入門:嵌入式運行框架全解析!

合宙LuatOS ? 來源:合宙LuatOS ? 作者:合宙LuatOS ? 2025-09-26 17:45 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

想搞懂LuatOS如何運行Lua腳本?本文深入剖析其嵌入式運行框架,涵蓋虛擬機加載、任務協程、系統初始化等關鍵環(huán)節(jié),適合初學者。

一、LuatOS 編程起步

1.1 底層固件怎么啟動 LuatOS 腳本

1.1.1 腳本入口執(zhí)行文件

簡單來說,底層固件首先就是要找到 main.lua 這個文件,然后啟動它。

所有的其他功能,都需要在 main.lua 發(fā)起。

1.1.2 LuatOS 啟動腳本的詳細流程

進一步詳細的說,LuatOS 的底層固件啟動腳本的流程如下:

1,系統上電或者復位后,底層固件(core)首先啟動,進行硬件初始化、內存分配、文件系統掛載等系統底層的基礎操作。

2,加載 Lua 虛擬機:底層固件加載 Lua 虛擬機,為執(zhí)行 Lua 腳本提供運行環(huán)境;

3,自動查找并加載存儲在設備上的主腳本 main.lua;

4,按順序執(zhí)行 main.lua 腳本中的代碼,通常包括任務創(chuàng)建(如sys.taskInit)、功能初始化等,從這一步,已經正式開始運行用戶邏輯。

5,進入任務調度:腳本最后通常調用sys.run(),進入事件循環(huán)和多任務調度。

1.1.3 怎么把固件和腳本燒錄到硬件:

1,使用LuatTools ,將底層固件和用戶 Lua 腳本燒錄到模組或者引擎硬件;

2,上電后,底層固件自動完成上述啟動和腳本加載流程,無需手動干預。

1.2 main.lua 需要包含哪些部分?

1.2.1 項目信息聲明

在 main.lua 的文件開頭,需要聲明項目名和版本號,便于管理和調試。后續(xù)的遠程升級,也需要用到項目名和版本號。

例如:

wKgZO2jWUOGAIwzMAABxJMLKJJ8624.png


1.2.2 核心庫,擴展庫以及如何加載

在 main.lua 需要加載 LuatOS 的基礎庫和擴展庫(如 zbuff,onewire,gnss 等)用來實現具體的業(yè)務邏輯。

核心庫和擴展庫的內容,在后續(xù)的章節(jié)里面介紹。

核心庫在底層固件加載Lua虛擬機的時候,在底層固件已經自動加載,不需要在用戶腳本中再去加載,例如sys,rtos等;

擴展庫是Lua腳本文件寫的庫,需要在用戶腳本中,使用require語句加載,例如libnet,httpplus等,加載方式如下:

wKgZO2jWUXiAV6SBAABY1MvYK9s803.png

1.2.3 至少啟動一個任務

在 main.lua 里面,至少需要啟動一個任務,否則這個 main 就無所事事,是一個沒什么實際用處的主腳本了。

啟動一個任務的方法,分為 2 個步驟:

1,創(chuàng)建一個函數,把要做的事情,放在這個函數里面使用。這個函數必須是無限循環(huán)的,防止很快結束生命,不妨把這個函數命名為 task1(),

2,調用 sys.taskInit(task1),啟動這個函數,于是這個任務,就放在待運行的任務列表里面了。

1.2.4 初步理解 sys.run()

sys.run() 是一個無限循環(huán)的函數。

main.lua 的最后一行, 只能是 sys.run(),代表 sys.run() 接管了 LuatOS 的所有的執(zhí)行調度工作。

sys.run() 是 LuatOS 的運行中樞。

在本文的 3.3 節(jié)和 7.3 節(jié),還會繼續(xù)介紹 sys.run()這個函數。

1.3 LuatOS 腳本編程的核心要點

1.3.1 LuatOS 實現的典型功能

LuatOS 腳本是利用了 Lua 的語法,以及基于 LuatOS 的核心庫和擴展庫提供的 API,進行簡便的編程,實現如下功能:

1,實現和云端服務器通信;

2,采集外設的數據,控制外設設備;

3,實現人機交互,包括圖形交互和語音交互;

1.3.2 LuatOS 的學習要點

要想寫好 LuatOS 的軟件,實現上述三個功能,除了逐漸掌握 Lua 的基本語法之外,還需要熟悉 LuatOS 的核心庫和擴展庫,這樣才能開發(fā)出優(yōu)質的基于 LuatOS 的物聯網設備軟件。

學習的方法有如下幾個:

1, 運行各個功能模塊的 demo 代碼;

2, 閱讀 docs.openluat.com 的教程文檔;

3, 遇到不懂問 AI;

1.3.3 一個典型的 LuatOS 實現

一個典型的 LuatOS 實現,包含 main.lua 入口文件和若干個功能模塊文件。

這里用 Air780EPM 模組的蜂鳴器的代碼為例, 有兩個腳本文件以及一個管腳描述 json 文件:

1, main.lua 文件, 作用是啟動一個任務,讓蜂鳴器響一秒鐘,再停頓一秒鐘,如此往復;

2, airbuzzer.lua 封裝了驅動蜂鳴器的功能實現;

3, pins_Air780EPM.json 描述了本例使用到的管腳的功能,780EPM 的 26 管腳,用作 PWM4。

main.lua 內容如下:

wKgZO2jWUn-AWJRaAAKAcy3IaWY302.png

airbuzzer.lua 內容如下:

wKgZPGjWUr2AQOdjAAIMsxtcII8010.png

pins_air780EPM.json 內容如下:

wKgZPGjWUviAAvadAADh8uf_JqA979.png

把上述幾個文件,連同 airr780EPM 最新的固件版本,用 Luatools 建立一個工程,燒錄到 780EPM 開發(fā)板,就可以聽到蜂鳴器的播放聲音了。

二、幾個要熟悉的常識

2.1 匿名函數

在 Lua 代碼里面,經常看到沒有名字的函數。

這種函數定義之后, 要么馬上運行,要么作為另一個函數的返回值賦給其他變量,所以并不需要一個函數名字。

這種函數,稱為匿名函數。

匿名函數可以某些時候簡化代碼,初學者寫代碼可以先不考慮匿名函數。

但是由于匿名函數在你能閱讀到的 Lua 代碼里面出現的頻次實在是太高了,所以你也不得不重視和習慣匿名函數。

2.2 閉包

閉包的實現通常是通過在外部函數內部定義一個函數,并將這個內部函數作為外部函數的返回值。

這樣一來,內部函數就可以訪問外部函數作用域中的變量,即使外部函數已經執(zhí)行完畢,這些變量依然可以被內部函數訪問,從而形成閉包。

常見的閉包實現模式如下:

wKgZPGjWU8yAJPlrAAEZPBSMJ6A664.png

這樣的好處是,可以定義一個函數,能夠在一定范圍內,訪問外部的變量,實現可控的持續(xù)行為。

很多初學者會被這段代碼迷惑,會被繞暈。

這里做一下解釋:

(1)z 不是函數里面聲明的變量,z 是函數的參數;

所以 在代碼里面, 因為 f=outer(10), 所以, f(5)就意味著是調用了 兩次函數,傳入了兩個函數的參數: outer(10)(5)。

第一次調用,out(10) ,意味著 在 outer 函數里面, y = x 這句, x 換成 10, 就是 y = 10;

outer(10)(5)意味著 5 是內部匿名函數的參數,就是替代 z 的;

匿名函數返回 y+z, 這里 y 是 10,z 是 5, 返回的就是 10+5=15.

這里比較繞的,就是給了兩次參數,一個是 10 對應 x, 一個是 5 對應 z。

匿名參數和閉包,對初學者有點繞,很多讀者不明白為什么 z 為什么是 outer 的第二個參數,

這里需要特別搞清楚的是, outer 這個函數的返回值是個函數, 而且這個函數是有參數的。

那么,這個帶參數的函數賦值給 f 之后, f 就是個函數了, 于是給 f 一個參數 5, 這個 5 自然就是返回的函數的參數了,也就是 z 了。

雖然并不是所有的閉包都是上面這種代碼的實現形式,但是初學者可以先記住這樣的閉包形式。

如果不習慣閉包,初學者可以先避免在代碼里面體現閉包的代碼形式。

2.3 回調函數

2.3.1 回調函數是什么


回調函數是在 LuatOS 編程過程中經常用到的一個技術。

理解 LuatOS 的回調函數,可以從“事件驅動”和“函數作為參數”兩個角度來把握:

回調函數(Callback)是在特定事件發(fā)生時,由系統或框架自動調用你事先定義好的函數。你只需要把自己的函數注冊給系統,等事件觸發(fā)時,系統就會幫你調用它。

本質上,回調函數就是一個普通函數,但它被作為參數傳遞或注冊到其他地方,由系統或其他代碼在合適的時機自動執(zhí)行。

回調函數的作用是實現事件響應,異步處理。

消息到來,定時器到點,網絡收發(fā)等功能都經常會用到回調函數的處理。

總之,LuatOS 的回調函數,就是你注冊給系統的,在特定事件發(fā)生時自動被調用的函數。

回調函數讓事件響應、異步處理、任務解耦變得簡單靈活,是 LuatOS 事件驅動編程的核心機制之一。

2.3.2 回調函數做消息訂閱與發(fā)布

LuatOS 支持通過sys.subscribe訂閱消息并注冊回調函數,消息發(fā)布時自動調用回調:

wKgZPGjWVH-AXceZAAEn-P9VxJQ823.png

當sys.publish("TEST", 123)被調用時,"TEST"消息以及攜帶的參數123會被插入到用戶消息列表中,LuatOS 內部的sys.run()調度中樞會遍歷訂閱者列表,找到所有訂閱了 "TEST" 的回調函數,并自動把參數 123 傳給這些回調函數。

通過這樣的處理,事件觸發(fā)和處理邏輯就被解耦,方便擴展和維護。

2.3.3 回調函數做定時器和異步操作

定時器到點后自動調用注冊的回調函數:

wKgZPGjWVLKAQFO-AACcCSIndd0859.png

2.3.4 任務和協程場景的回調函數使用

在多任務,也就是 LuatOS 的協程場景下,回調函數也常用于任務喚醒、事件響應等。

解耦調用者與被調用者:調用者只需知道“有回調”,不用關心回調具體做什么,提升靈活性。

你只需更換回調函數,就能實現不同的處理邏輯,無需修改底層框架代碼。

任務和協程的詳細信息,在下一章講解。

三、LuatOS 的多任務并行實現詳解

3.1 LuatOS 的多任務是怎么實現的

3.1.1 通過協程實現多任務的效果

LuatOS 使用一種協程(coroutine)的機制,實現多任務。

協程并不是真的多任務,也不是多線程,而是通過同一時間只可能有一個協程執(zhí)行,來等價實現多任務的效果。

和 RTOS 的搶占式多任務方式不同,協程不能搶占其他任務的時間片,只能由一個獨立的調度器來判斷是哪個協程占用 CPU 時間來運行。

一個 LuatOS 可以創(chuàng)建多個任務,每一個任務都是協程,為了簡化描述,后續(xù)我們經常會用”任務“這個詞來指代協程。

LuatOS 創(chuàng)建的任務無法設定優(yōu)先級, 所以 LuatOS 的每個任務的優(yōu)先級都是相同的。

每一個 LuatOS 的任務在做運算的時候,是 100% 占用了 CPU 時間片的。

執(zhí)行完運算之后,要主動調用 yield() 函數,讓自己掛起,其他任務才能獲得時間片運行。

如果某個任務, 持續(xù)進行運算,不做 yield() 調用,其他任務是無法獲取 CPU 時間片的。

協程掛起后,自己是無法恢復的,只能其他的任務調用 resume 系統函數來恢復。

我們在寫代碼的時候,不需要調用 yield() 把自己掛起,只需要調用 sys.wait() 做時延,由調度器統一在 sys.wait()里面把任務掛起。

在 LuatOS 里面,所有掛起的協程,都由一個獨立的調度器通過調用 resume 來恢復。

這個獨立的調度器, 在 LuatOS 里面是 sys.run() 函數。

3.1.2 LuatOS 的任務函數怎么掛起和恢復

LuatOS 的每一個通過 sys.taskInit() 發(fā)起的任務函數,都不會直接調用 yield 把自己掛起,因為直接調用 yield 掛起的話,并不知道什么時候恢復這個任務。

LuatOS 的做法是,每個任務在執(zhí)行完自己的事情之后,都必須是調用一個等待函數, 這樣的等待函數有如下幾個:

1,sys.wait(timeout)

這個函數,會在掛起任務的同時,啟動一個定時器,定時器的觸發(fā)時間就是 timeout,并且把任務 id 跟這個定時器綁定。

到定時器觸發(fā)之后,sys.run 會根據該定時器綁定的任務 id,重新恢復該任務的運行。

2,sys.waitUntil(topic, timeout)

在掛起任務的同時,訂閱一個名為 topic 的消息。待到有其他的任務發(fā)布這個消息后,sys.run 恢復這個任務。

如果沒有等到其他任務發(fā)布這個topic 消息,超時timeout 了,sys.run()也會恢復任務的運行。

總結來說,LuatOS 的任務在掛起自己之前,會在系統的表里面,放一個讓自己恢復運行的條件,這個條件或者是一個超時時間,或者是其他任務發(fā)布一個消息。sys.run() 函數會去判斷這些恢復運行的條件是否滿足,一旦滿足條件,就會恢復對應的任務。

3.2 怎么實現單個任務

在 LuatOS 里面,一個任務,可以理解為一個無限循環(huán)的函數,啟動一個任務,有如下步驟:

1,定義這個無限循環(huán)的函數 task1;

2,調用 sys.taskInit(task1), 在 taskInit 函數里面,先為 task1 函數創(chuàng)建一個協程,同時把這個協程注冊到系統的協程列表,這樣 sys.run() 就會去運行這個協程。

這樣就新增了一個持續(xù)運行,永不退出的協程了。

一個在 LuatOS 系統里面合法的任務, 必須運行很少量的時間,執(zhí)行完自己的操作之后,馬上就把自己掛起。 掛起的方式就是 調用 sys.wait 或者 sys.waitUtil 函數。

一個正常的 LuatOS 任務,執(zhí)行計算的時間是很短暫的,絕大部分的時間,都是在掛起狀態(tài)。

在掛起狀態(tài), 是不消耗 CPU 資源的。

所以, LuatOS 的協程機制,具備了實現低功耗系統的前提。

3.3 進一步理解 sys.run()

LuatOS 的sys.run()函數是系統任務調度器的啟動入口,其主要工作流程如下:

3.3.1 進入任務調度主循環(huán)

當執(zhí)行到sys.run()時,LuatOS 會啟動任務調度器,正式進入事件驅動和多任務調度階段。

此后,所有通過sys.taskInit注冊的任務都會被納入系統統一調度。

3.3.2 循環(huán)處理底層消息與事件

sys.run()會不斷從底層(如硬件中斷、驅動、系統內核,定時器等)獲取消息或事件,并將這些消息分發(fā)到相應的任務或回調函數進行處理。

這包括定時器到期、外設事件、網絡數據到達、用戶自定義消息等。

3.3.3 定時器與任務切換

sys.run 會周期性檢查所有注冊的定時器,并在定時器到期時喚醒相應的任務協程。

同時,系統會根據任務的掛起或喚醒狀態(tài),合理切換協程,實現多任務并發(fā)。

3.3.4 任務間消息通信與同步

sys.run()支持任務間通過消息發(fā)布/訂閱、等待/喚醒等機制進行通信與同步。

例如,任務可以通過sys.publish發(fā)布消息,其他任務通過sys.waitUntil或sys.subscribe等方式等待或響應這些消息。

3.3.5 持續(xù)運行,直至系統重啟或退出

sys.run()會持續(xù)運行,不會主動退出。

sys.run() 系統的主循環(huán),確保所有任務和事件都能被及時處理。

只有在系統重啟、腳本異常終止或手動退出時,sys.run() 這個調度循環(huán)才會結束。

3.3.6 簡要流程圖

(1)啟動任務調度器;

(2)進入主循環(huán)

(3)輪詢底層消息、定時器

(4)喚醒/調度任務協程

(5)分發(fā)和處理事件、消息

(6)返回主循環(huán),直到系統重啟或退出

3.4 怎么實現多個任務

3.4.1 協程大多數時間應該是掛起狀態(tài)

由于協程的運行原理是,同一時間只有一個協程在運行,其他協程在掛起狀態(tài)。

所以如果有多個協程存在的話,多個協程的運行,只可能有兩種情況:

第一種情況, 所有的協程都在掛起狀態(tài),這時候系統有可能進入低功耗;

第二種情況, 有一個協程在運行,其他協程在掛起。這時候系統是喚醒狀態(tài),不可能是低功耗狀態(tài)。

3.4.2 LuatOS 多任務的核心是掛起和恢復的調度

一個協程運行的時間越長,掛起的就越慢,其他的協程就無法得到時間片運行。

只有所有的協程都盡量減少時間占用, 都盡快掛起自己,這樣的多任務的調度的效率才能更高。

因此, LuatOS 多任務的編程核心,是使得每個任務函數的執(zhí)行時間盡可能的短,盡可能快速的掛起自己,整個系統的多任務并發(fā)處理的效率才會更高。

如果某個協程的運算時間很長,導致自己無法很快掛起,就會拖累整個系統,使得整個系統的實時響應的性能降低。

3.4.3 怎么防止某個協程長時間不掛起

為了防止某個協程長時間做運算,不把自己掛起,LuatOS 設計了 watchdog 機制,起一個定時器,幾秒鐘喂狗一次。

如果超時沒有喂狗,系統就會被重啟。

把下面這段代碼放到 main.lua,即可實現喂狗的功能:

wKgZO2jWVcmAa_SdAAESSQ3Vh5s064.png

3.5 多個任務之間怎么分配時間片

LuatOS 系統里面,是沒有給某個任務分配時間片這樣的動作的。

LuatOS 的任務,必須盡快把自己掛起,釋放出 CPU,才能夠讓整個系統實時運行。

當所有任務都把自己掛起后,系統就就可能會低功耗休眠狀態(tài)。

只要有任何一個任務沒有掛起,系統都不可能進入低功耗休眠狀態(tài)。

通過 sys.run()函數, 對多個任務按照業(yè)務需要進行恢復運行的調度,保證整個系統的順暢運行。

sys.run()調度的依據,一個是定時器機制,一個是消息機制。

四、LuatOS 的定時器機制

LuatOS 的定時器機制是實現多任務系統的核心組件之一。

支持單次觸發(fā)和周期循環(huán),適用于物聯網設備中的定時任務、數據采集、狀態(tài)監(jiān)測等場景。

4.1 定時器類型與適用場景

類型
特點
適用場景
單次定時器
延遲指定時間后觸發(fā)一次
初始化延時、事件超時處理
循環(huán)定時器
周期性觸發(fā),可指定次數
心跳包發(fā)送、傳感器輪詢

4.2 核心 API 與用法

4.2.1 單次定時器

功能: 延遲 timeout 毫秒后執(zhí)行函數, 可傳多個參數local timerId = sys.timerStart(callback, timeout, arg1, arg2, ...) 參數說明: callback: 定時器觸發(fā)時執(zhí)行的函數 timeout: 延遲時間(毫秒) argN: 傳遞給回調函數的參數 代碼示例:

wKgZO2jWVwOARKTCAADhw2Ctp_4116.png

wKgZPGjWVxyAIP0RAABBwYdnhcU843.png

4.2.2 循環(huán)定時器

功能: 每隔 timeout 毫秒重復執(zhí)行函數localtimerId = sys.timerLoopStart(callback, timeout, arg1, arg2, ...)

代碼示例:

wKgZPGjWV0aAVZ-IAAFMek5u5pg503.png

運行結果為:

wKgZPGjWV4eAfnDVAADWuw8Xi3A944.png

4.2.3 定時器停止

LuatOS 有兩個 API 用于停止正在生效的定時器:

1, 停止制定 timerid 的單個定時器

sys.timerStop(timerId)

2,停止制定回調函數的所有定時器。

sys.timerStopAll(callback)

4.3 典型代碼示例

4.3.1 組合使用單次與循環(huán)定時器

wKgZPGjWV7-AFHXuAAH8jz32Ur4983.png


4.3.2 動態(tài)管理定時器

wKgZO2jWV-uAMIiPAAFtHLuYLI0354.png

4.3.3 5 秒后重連網絡

wKgZO2jWWC2ARvk7AADrz3Ro-tM440.png

4.4 定時器的數量限制

LuatOS 最多支持 64 個定時器。

由于任務里面的 sys.wait()、帶timeout參數的sys.waitUntil()、帶timeout參數的sys.waitMsg()調用也會引發(fā)調度器啟動一個定時器管理該任務的運行恢復,所以用戶實際能夠啟用的定時器,會比 64 個更少。

所以,在開發(fā)過程中, 需要注意這一點,不要無節(jié)制的使用定時器。

4.5 為什么 LuatOS 的定時器不太準

LuatOS 的定時器往往“不太準”,主要原因在于其定時器機制依賴于消息總線(Message Bus)和系統調度,而不是直接精準地控制硬件定時。具體來說有如下幾點原因:

4.5.1 定時器基于消息機制

LuatOS 的定時器設計是基于 RTOS 的 timer API。

當定時器超時時,系統只是在消息總線中插入一條定時器消息,由主循環(huán) sys.run()消費和處理,這會帶來兩種可能的時延:

1,當調度器在處理消息時,可能會因為其他任務、消息隊列長度、系統負載等原因出現延遲。

2,定時器回調的實際執(zhí)行時機,取決于消息被調度和消費的時刻,而不是定時器超時的精確時刻。

4.5.2 系統調度與任務競爭

LuatOS 采用事件驅動和多任務協作,主循環(huán)需要處理各種消息(包括定時器、外設、網絡等);

如果系統中有大量任務或消息,定時器消息可能會被延后處理,導致定時精度下降。

4.5.3 軟件定時器的局限

(1)軟件定時器本質上依賴于系統 tick(通常為 1ms),但 tick 的處理、消息入隊、Lua 虛擬機調度等環(huán)節(jié)都會引入微小延遲。

(2)在高負載或消息堆積時,這種延遲會被放大,表現為“定時器不準”。

4.5.4 Lua 腳本無法實現高精度定時器

LuatOS 定時器不太準的根本原因是:定時器只是觸發(fā)消息,實際執(zhí)行依賴消息總線和主循環(huán)調度,受系統負載、任務數量、消息堆積、網絡中斷優(yōu)先級最高等多因素影響。

LuatOS 定時器不能實現高精度定時器(例如微秒級別,幾十毫秒級別,甚至幾百毫秒級別也會有誤差)。

如果要實現高精度定時器,只能外掛單片機實現,或者后續(xù)有可能推出集成單片機,專門用來實現高精度定時器和其他對實時性和精度要求比較高的需求。

4.6 sys.lua 里面的 timerPool 變量

如果你有興趣查看 sys.lua 的話,會發(fā)現 timerPool 這個 table 類型的變量,在 0-0x1FFFFF 范圍內存儲 恢復運行協程的定時器消息 ID, 在 0x200000-0x7FFFF 范圍內存儲有回調函數的定時器消息 ID。

所以,凡是某個協程調用 sys.wait()延時函數,都會在注冊一個定時器,定時器超時后,就會由調度器重新恢復這個協程的運行;

當使用 timerStart 函數注冊的定時器超時后, 調度器會調用定時器回調函數。

這兩種情況的超時處理,都是在 timerPool 這個變量實現的。

4.7 LuatOS 定時器總結

LuatOS 的定時器機制通過sys庫提供了消息驅動架構,合理運用定時器可顯著提升物聯網設備的自動化程度和能效比。

在使用定時器機制的時候,需要注意如下幾點:

4.7.1 避免阻塞回調

1, 定時器回調函數中禁止使用sys.wait操作

因為定時器回調函數是由調度器直接調用的,如果在定時器回調函數里面使用 sys.wait 操作,會使得調度器阻塞,從而使得整個系統停止運行。

2, 定時器回調函數禁止進行長時間阻塞操作

這樣會極大的降低系統效率,使得系統的反應變慢。

4.7.2 注意資源釋放

任務退出時,如果在任務運行過程中創(chuàng)建的定時器不再需要,需調用sys.timerStop()或者sys.timerStopAll()清理關聯定時器,防止內存泄漏,或者引起定時器資源耗盡。

如果不想主動寫代碼清理關聯定時器,只能等待定時器時間到了之后,自動清除,這時就會多占用了一個沒有任何實際功能的定時器,如果定時器資源非常緊張的情況下,創(chuàng)建新的定時器有可能會失敗。

4.7.3 不要期待有高精確度的延時和定時

由于消息機制和虛擬機的運行限制,導致延時函數和定時器的精度都不會很高,在實現業(yè)務邏輯的時候,一定要注意這一點。


五、 LuatOS 的消息機制

LuatOS 的消息機制是其多任務協作的核心,通過sys庫實現事件驅動編程。以下從消息發(fā)送、消息接收、消息訂閱三個維度詳細解析:

5.1 發(fā)送消息

5.1.1 廣播式消息(一對多)

API:sys.publish(topic, arg1, arg2, ...)

功能:向所有訂閱者廣播消息,無目標標識。

代碼示例:

wKgZO2jWWSSAYyl7AADHbohWgA0000.png

5.1.2 定向消息(點對點)

API:sys.sendMsg(taskName, target, arg2, arg3, arg4)

功能:向指定任務發(fā)送消息,支持目標標識和參數。

代碼示例:

wKgZO2jWWVuAawNVAADJAGxZKmo453.png

5.2 消息接收

5.2.1 等待消息

在協程內部等待:sys.waitUntil(topic, timeout)

特別提醒: 該 API 只能在協程內執(zhí)行

代碼示例:

wKgZPGjWWYqAAmOPAAEo-23-at8768.png

5.2.2 定向接收

API:sys.waitMsg(taskName, target, timeout)

特點:按任務名和目標標識精準接收,支持超時,該代碼只能在協程內執(zhí)行。

注意,該 API 的第一個參數 taskName, 是指等待消息的任務名稱,也就是自己的任務名稱,不是發(fā)送消息的任務名稱。

調用該 API 的任務,和接收任務,不一定是同一個任務。

當接收消息的任務在掛起的時候,可以由其他任務或者調度器通過 WaitMsg API 喚醒掛起的任務。

代碼示例:

wKgZPGjWWcCADlyMAAEuAc6Uu0Q065.png

5.3 消息訂閱

5.3.1 全局訂閱

API:sys.subscribe(topic, func)

特點:如果訂閱了同一主題有多個回調函數,這些回調函數都會被觸發(fā)。

代碼示例:

wKgZO2jWWfGAVDYpAACtu1ef4is485.png

5.3.2 任務私有訂閱

實現方式:通過sys.taskInitEx創(chuàng)建任務時注冊回調。

當有其他的任務發(fā)送消息給目標任務的時候, 但是目標任務并沒有通過 WaitMsg 函數設定消息處理,這時候該消息的處理就交給回調函數處理。

代碼示例:

wKgZPGjWWiGAeqbIAAJL_UPBeFM687.png

5.4 LuatOS 消息機制的典型應用場景

5.4.1 網絡模塊與主任務通信

wKgZO2jWWk-AVlj0AAJy1EAXiWY350.png


5.4.2 全局事件通知(sys)

wKgZPGjWWneANOX8AAIZITPbR1I878.png

5.5 消息機制設計要點

5.5.1 消息機制的不同設計

(1)處理全局事件,sys.publish發(fā)布的消息,已經訂閱這個消息的所有sys.subscribe對應的處理函數都能收到。 (2)處理模塊間通信(如網絡請求-響應),sys.sendMsg發(fā)布的消息,會攜帶一個task name參數,只有sys.waitMsg時也攜帶同樣的task name參數,才能收到消息。

5.5.2 避免消息風暴:

高頻消息(如傳感器數據)建議合并發(fā)送或降低頻率。

5.5.3 消息機制的核心目的之一是軟件解耦

通過合理運用sys庫的消息機制,可構建高效、解耦的物聯網應用架構。

六、多任務之間的信息交換

6.1 用全局變量做信息交換

如果信息量很小,比如就一個字符串或者標志位,任務之間可以通過共享全局變量來通信,一個任務去對這個全局變量賦值,其他任務讀取這個全局變量,任務之間就達到了通信的目的了;

6.2 用消息做信息交換

但是如果想要交換多個數據,每個數據都用全局變量的話,就有點過于累贅了。

這時候,可以通過發(fā)送消息來通信。

任務之間怎么發(fā)送消息,接收消息,參考第五章的內容。

七、再次理解調度器 sys 庫

LuatOS 的 sys 庫是系統調度和多任務管理的核心庫,提供了豐富的 API 用于任務創(chuàng)建、延時、消息通信、定時器管理等。

7.1.1 任務與協程管理

API: sys.taskInit(func, arg1, arg2, ...)功能: 創(chuàng)建一個新的任務(協程),并傳遞參數給任務函數。

7.1.2 延時與等待

(1) sys.wait(timeout)功能: 任務延時掛起指定毫秒數,只能在任務函數中調用。

(2)sys.waitUntil(topic, timeout)功能: 任務掛起,直到收到指定 topic 的消息或超時,只能在任務函數中調用。

7.1.3 定時器相關

(1) sys.timerStart(func, timeout, arg1, ...)創(chuàng)建單次定時器,到時后執(zhí)行回調函數。

(2)sys.timerLoopStart(func, timeout, arg1, ...)創(chuàng)建循環(huán)定時器,周期性執(zhí)行回調函數。

(3)sys.timerStop(timerId)停止指定 ID 的定時器。

(4)sys.timerStopAll(func)停止所有與指定回調函數相關的定時器。

(5)sys.timerIsActive(timerId)判斷定時器是否處于激活狀態(tài)。

7.1.4 消息通信

(1)sys.publish(topic, arg1, ...)發(fā)布(廣播)一個消息,喚醒等待該 topic 的任務或觸發(fā)訂閱回調。

(2)sys.subscribe(topic, callback)訂閱指定 topic 的消息,消息到來時自動執(zhí)行回調。

(3)sys.unsubscribe(topic, callback)取消訂閱。

7.1.5 主循環(huán)控制

sys.run()功能: 是 LuatOS 的調度器,是系統主循環(huán),調度所有注冊的任務和定時器。

7.1.6 典型用法示例

wKgZO2jWWwaAKFFTAANU3gIj2mM923.png

7.1.7 任務與協程管理

(1)sys.taskInitEx(func, taskName, cbFun, ...)

功能:創(chuàng)建一個具名任務線程,并注冊任務函數和非目標消息回調。

(2)sys.taskDel(taskName)

功能:刪除由taskInitEx創(chuàng)建的任務線程,釋放資源。

7.1.8 消息通信機制

(1)sys.waitMsg(taskName, target, timeout)

功能:等待接收一個目標消息(可指定超時),任務會掛起直到收到目標消息或超時。

(2)sys.sendMsg(taskName, target, arg2, arg3, arg4)

功能:向目標任務發(fā)送一個消息,可攜帶最多 4 個參數。

(3)sys.cleanMsg(taskName)

功能:清除指定任務的消息隊列,防止消息堆積。

7.1.9 sys.run() 怎么實現多個任務的協同工作

sys.run()函數的實現過程是這樣的:

1, 查看消息隊列里面是否有未處理的消息, 如果有,就根據消息的處理類型,調用回調函數或者是喚醒對應的任務進行消息處理;

2, 等待底層 RTOS 操作系統的定時器消息;等待的過程,就是低功耗的過程;

3, 定時器消息等到之后, 調用定時器回調函數或者喚醒對應的任務。

4, 循環(huán) 1-3 步。

通過以上過程,我們可以看到,這個 LuatOS 系統, 大多數時間都是在等待底層 RTOS 操作系統的定時器消息,在等待期間,系統是可以處于低功耗休眠狀態(tài)的。

當任務的時延很短, 或者定時器非常頻繁,或者是消息太多,是會影響到系統的低功耗性能的。

八、怎么封裝一個 LuatOS 的軟件功能模塊

在 LuatOS 中封裝功能模塊為單獨 Lua 文件的標準做法:

1、新建一個 Lua 文件,定義一個 table,比如名字為 myflib,所有對外接口作為其字段。

2、用 local 修飾內部變量和函數,實現信息隱藏。

3、定義 myflib 的成員變量,成員函數,用作對外的接口。

4、文件末尾用returnmyflib ,導出模塊 table。

5、外部的文件,用require("模塊名")加載和復用模塊。

這樣可以讓你的功能模塊獨立、可維護、易擴展,是 Lua 及 LuatOS 推薦的開發(fā)范式。

代碼示例:

wKgZPGjWW4SAYcqyAAb3zMmZzHE881.png

九、LuatOS 的核心庫和擴展庫

LuatOS 在 Lua 5.3 版本的基礎上, 封裝了 74 個核心庫,17 個擴展庫,提供了極其強大的通信和硬件的開發(fā)功能。

9.1 LuatOS 核心庫

LuatOS 核心庫,提供了 LuatOS 系統的核心功能。 不同的硬件型號,支持不同的核心庫的子集。

LuatOS 的核心庫, 是不需要用戶 require,可以直接調用的。

780EPM 對這些核心庫的支持情況參見如下鏈接:

http://docs.openluat.com/air780epm/common/lutos_coreapilist/

9.2 LuatOS 擴展庫

除了用戶可以直接使用的核心庫之外, LuatOS 還提供了豐富的擴展庫。

使用擴展庫,需要用戶在代碼里面做 require 動作,Luatools 看到 require 關鍵字后,會把用到的擴展庫合并入燒錄包,一起燒錄到硬件里面。

如果用戶不做 require 的動作, luatools 就不會合并這個擴展庫的代碼。

所有的擴展庫,都是用 Lua 代碼實現的。

當前 LuatOS 已經支持的擴展庫參見如下鏈接:

http://docs.openluat.com/air780epm/common/lutos_coreapilist/

十、LuatOS 實際工程代碼解讀

780EPM 1.3 開發(fā)板的出廠固件代碼, 是一個實際的 LuatOS 開發(fā)的簡單案例。

代碼的位置在:

780EPM 開發(fā)板 V1.3 出廠固件源碼

這個固件分為幾個部分:

1, 780EPM core 固件: 目前最新的固件是 2005 版本,后續(xù)更新的固件版本也可以繼續(xù)使用;

最新的 780EPM 固件在這里下載:

http://docs.openluat.com/air780epm/luatos/firmware/version/

2,管腳復用描述文件

wKgZO2jWXC6AXcg7AABBziyXB5U106.png

3, 資源圖片

實現開機固件所需的圖片。(詳見780EPM 開發(fā)板 V1.3 出廠固件源碼的pic目錄下的圖片 )

4, 實現腳本。

下面重點講解一下腳本實現的邏輯。

10.1 編碼要求

為了降低用戶理解成本,這份開機固件的代碼有如下要求:

(1)不允許用云編譯擴大腳本區(qū),不允許用云編譯擴大文件系統,保持腳本 + 資源總體尺寸不能大于 256K 字節(jié);

(2)main.lua 作為邏輯主線,其他的功能代碼封裝成子模塊,提供成員函數,也可以提供成員變量, 被 main.lua 調用;

(3)不允許使用匿名函數;

10.2 已實現功能

如下代碼,已經實現了如下功能:

(1)主界面九宮格的按鍵切換,

(2)長按進入具體功能界面;再長按回到主界面;

(3)圖片顯示功能,

(4)攝像頭預覽,

(5)俄羅斯方塊,

(6)天氣數據獲取,并顯示不同的天氣圖標;

使用的是 780EPM 默認 2005 固件,不需要擴大文件系統和代碼區(qū)。

其中,airlcd.lua, camera780epm_simple.lua, russia.lua,statusbar.lua, 分別用 table 的方式,封裝了 LCD 的參數初始化,camera 的初始化,預覽,退出,俄羅斯方塊的初始化,更新數據,響應按鍵等事件。

在 main.lua 調用這些封裝好的 table 的函數即可,不需要過度關心子模塊的實現細節(jié)。

10.3 待實現功能

(1)以太網 LAN

(2)以太網 WAN

(3)硬件自檢

(4)modbus TCP

(5)modbus RTU

(6)CAN 總線

10.4 main.lua 解讀

10.4.1 系統初始化

整個系統,做了兩個全局初始化:

1, 看門狗的初始化,wdt.Init()防止系統被某個任務異常占用 CPU 讓系統鎖死;

2, LCD 的初始化: airlcd.lcd_init("AirLCD_0001"),其中 AirLCD_0001 是合宙 LCD 配件的型號。

10.4.2 業(yè)務主循環(huán)

UITask() 函數, 是 main.lua 啟動之后的主循環(huán)。

在 UITask 函數里面,先做按鍵的初始化之后,就無限循環(huán)的不斷調用三個函數:keypressed, update, draw。

其中,keypressed 是查看按鍵是否有待處理的事件;

update 是更新業(yè)務數據;

draw 是更新 UI 畫面。

10.4.3 按鍵事件的處理

由于 780EPM 開發(fā)板只有三個按鍵: 開機鍵,boot 鍵,reset 鍵。

reset 鍵無法被捕獲事件,只能復位硬件,所以固件只能處理 開機鍵和 boot 鍵的事件。

boot 按鍵是一個特殊的 GPIO, 編號為 GPIO0。

開機鍵也是個特殊的 GPIO, 編號為 GPIO.PWR_KEY。

在 KeyInit()這個函數, 分別配置了 gpio.PWR_KEY 和 GPIO0 為雙邊沿中斷,中斷處理函數分別為 PowerInterrupt 和 BootInterrupt。

根據開發(fā)板的原理圖,開機鍵初始電平是上拉為高電平,boot 鍵初始電平為下拉低電平。

在 PowerInterrupt() 和 BootInterrupt()這兩個函數的處理邏輯是類似的,都是計算按下和抬起的時間間隔,從而判斷是短按還是長按,然后給 key 這個全局變量賦值。

key 是字符串類型,是一個比較關鍵的變量,根據 key 的值不同, main.lua 進入不同的功能。

這個邏輯是在 keypressed 來實現。

在 keypressed() 函數里面,檢查 key 變量的值,然后做不同的處理。

在主界面, 處理 "main" 和 "enter"這兩個值,分別是切換按鈕加亮顯示,以及進入具體功能按鈕;

在具體功能界面, enter 按鍵時間會返回主界面;

在俄羅斯方塊界面, 有 5 種按鍵:

(1)right: 短按 boot, 往右移動方塊;

(2) left:短按開機,往左移動方塊;

(3)up:長按 boot,旋轉方塊;

(4)fast:長按開機,快速下落;

(5)quit:超長按開機,退出游戲回到主界面。

10.4.4 UI 界面的循環(huán)刷新

在 draw 函數里面刷新界面。

當需要把繪圖權限交給其他的功能模塊的時候, 根據情況做不同的處理:

(1)俄羅斯方塊的刷新函數就是自己實現 drawrus 函數, draw 函數調用 drawrus 函數刷新屏幕;

(2)攝像頭預覽功能接管了屏幕后,draw 函數判斷當前是攝像頭預覽功能,就直接退出,如果判斷不是攝像頭,再繼續(xù)處理刷新任務;

(3)在刷新之前,調用 update 函數更新用于刷新的關鍵數據。

10.4.5 管理當前功能狀態(tài)機

有兩個關鍵變量:

cur_sel: 整數,范圍是 1-9, 當前選擇的九宮格是哪個;

cur_fun: 字符串,10 種值:

記錄當前已經進入的界面是主界面,還是 9 個之一。

“main”: 主界面;

另外 9 個界面用一個數組記錄,并根據 cur_sel 賦值給到 cur_fun。

local funlist = {

"picshow", "camshow","russia",

"LAN", "WAN","selftest",

"modbusTCP","modbusRTU","CAN"

}

cur_sel 和 cur_fun,結合 key 的值,組成了整個的邏輯切換,可以決定該進入什么軟件功能,該顯示什么界面。

理解了 cur_sel, cur_fun, key 這三個變量的運用,就可以看明白整個軟件的邏輯。

10.5 總結

這份 780EPM 的開發(fā)板的出廠固件的代碼,展示了一個完整的 LuatOS 工程的基本實現的方法。

腳本文件一共只有 10 個,全部加一起只有 30k 字節(jié),1000 行代碼,實現了 9 宮格界面,電量,信號強度,天氣的狀態(tài)欄顯示,包括俄羅斯方塊在內的多種功能的演示。

這份代碼后續(xù)還會繼續(xù)更新,并且都不會采用非常高難度的編碼技巧,只需要用最簡單的編程邏輯就可以實現相對復雜的業(yè)務邏輯。

今天的內容就分享到這里了~

審核編輯 黃宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯系本站處理。 舉報投訴
  • 嵌入式
    +關注

    關注

    5177

    文章

    19992

    瀏覽量

    325067
  • 腳本
    +關注

    關注

    1

    文章

    405

    瀏覽量

    28979
  • LuatOS
    +關注

    關注

    0

    文章

    119

    瀏覽量

    2501
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    Air780EPM開發(fā)板NTP對時教程:LuatOS腳本開發(fā)入門指南

    通過簡單的LuatOS腳本,Air780EPM即可實現NTP時間同步。本指南從零開始,帶你掌握嵌入式設備聯網校時的核心方法。 一、NTP通信概述 1.1 NTP 網絡時間協議(英語:Network
    的頭像 發(fā)表于 09-30 16:01 ?861次閱讀
    Air780EPM<b class='flag-5'>開發(fā)</b>板NTP對時教程:<b class='flag-5'>LuatOS</b><b class='flag-5'>腳本</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>入門</b>指南

    Air780EPM開發(fā)板FTP功能實戰(zhàn):LuatOS嵌入式開發(fā)解析

    本文深入講解Air780EPM模組在LuatOS環(huán)境下的FTP應用開發(fā),涵蓋AT指令配置、網絡連接與文件傳輸流程,助你高效完成嵌入式通信項目。 一、什么是 FTP ? 1.1 基本概念 FTP 協議
    的頭像 發(fā)表于 09-29 15:37 ?100次閱讀
    Air780EPM<b class='flag-5'>開發(fā)</b>板FTP功能實戰(zhàn):<b class='flag-5'>LuatOS</b><b class='flag-5'>嵌入式開發(fā)</b><b class='flag-5'>全</b><b class='flag-5'>解析</b>

    嵌入式開發(fā)新選擇:LuatOS腳本框架入門教程

    LuatOS正成為嵌入式開發(fā)的新趨勢!本教程帶你從基礎入手,全面了解其基于Lua的腳本開發(fā)模式與輕量級運行
    的頭像 發(fā)表于 09-26 17:34 ?160次閱讀
    <b class='flag-5'>嵌入式開發(fā)</b>新選擇:<b class='flag-5'>LuatOS</b><b class='flag-5'>腳本</b><b class='flag-5'>框架入門</b>教程

    嵌入式達到什么水平才能就業(yè)?

    (三)實戰(zhàn)經驗擁有2-3 個完整嵌入式項目經驗:項目需包含需求分析、方案設計、代碼開發(fā)、測試優(yōu)化流程,能清晰闡述項目難點與解決方案能獨立排查項目中的軟硬聯調問題:比如通過示波器查看信號波形定位
    發(fā)表于 09-15 10:20

    嵌入式入門到進階,怎么學?

    嵌入式入門到進階,怎么學? 嵌入式學習的核心是 “軟硬結合的技術壁壘”,科學分層才能高效突破。以下是從入門到高階的精簡路線,幫你避開彎路: 1、基礎奠基層:構建技術底座 C 語言聚焦
    發(fā)表于 09-02 09:44

    BitsButton嵌入式按鍵處理框架

    配備了完整的按鍵測試用例,專為嵌入式C項目設計: 詳情見:按鍵測試用例 ?? 分層架構設計 核心層(core/): 測試框架基礎設施和運行器 工具層(utils/): 模擬工具、時間控制、斷言增強 測試
    發(fā)表于 08-02 11:24

    Linux嵌入式和單片機嵌入式的區(qū)別?

    新的應用程序和驅動程序來擴展功能。 6. 開發(fā)難度 : 單片機嵌入式開發(fā)難度相對較低,適合初學者入門。 Linux嵌入式
    發(fā)表于 06-20 09:46

    嵌入式開發(fā)入門指南:從零開始學習嵌入式

    隨著物聯網、智能硬件的發(fā)展,嵌入式開發(fā)成為熱門技能之一。以下將為初學者提供一份詳細的嵌入式開發(fā)入門指南,涵蓋學習路徑、必備工具、推薦資源等內容。 1. 嵌入式系統的定義與應用
    發(fā)表于 05-15 09:29

    LuatOS協程深度解析:小白也能10分鐘學會,代碼效率直接起飛!

    嵌入式開發(fā)如何兼顧效率與簡潔?LuatOS協程給出完美答案!它用類線程的語法封裝異步邏輯,讓多任務開發(fā)像單線程一樣簡單。本文用圖文并茂的方式拆解協程原理,10分鐘帶你輕松入門! ?
    的頭像 發(fā)表于 04-10 15:23 ?383次閱讀
    <b class='flag-5'>LuatOS</b>協程深度<b class='flag-5'>解析</b>:小白也能10分鐘學會,代碼效率直接起飛!

    嵌入式系統開發(fā)圣經【干貨】

    主管、系統設計分析人員及欲進入該領域的工程師。是一本開發(fā)嵌入式系統產品必備的入門圣經,進入嵌入式系統領域的寶典??梢苑捶?,學習一下嵌入式開發(fā)
    發(fā)表于 03-12 13:58

    嵌入式教育科普|GPIO接口全面解析

    知識的掌握直接影響著嵌入式項目實現功能的成效。本文將以GPIO接口為切入點,深入解析其工作原理與技術特性,希望能幫助屏幕前想要學習嵌入式開發(fā)的小伙伴建立嵌入式接口的
    的頭像 發(fā)表于 03-05 11:11 ?2411次閱讀
    <b class='flag-5'>嵌入式</b>教育科普|GPIO接口全面<b class='flag-5'>解析</b>

    入門必看】從菜鳥到大牛,嵌入式系統完整學習路線!看這篇就夠了!

    ?嵌入式Linux”的順序幫助您從入門到進階,掌握嵌入式開發(fā)的精髓。第一階段單片機單片機是嵌入式學習的基石,因為它集成了處理器、存儲器和輸入輸出接口于單一芯片中,提
    的頭像 發(fā)表于 02-20 10:53 ?2922次閱讀
    【<b class='flag-5'>入門</b>必看】從菜鳥到大牛,<b class='flag-5'>嵌入式</b>系統完整學習路線!看這篇就夠了!

    韓國企業(yè)Mythosia發(fā)布面向嵌入式行業(yè)的腳本驅動串行通信

    專注于嵌入式與固件開發(fā)的韓國企業(yè)Mythosia全新推出了一款基于腳本運行的串行通信監(jiān)控程序“CRMT”。 ? CRMT設計為可應用于包括Arduino在內的多種
    的頭像 發(fā)表于 12-30 11:20 ?967次閱讀
    韓國企業(yè)Mythosia發(fā)布面向<b class='flag-5'>嵌入式</b>行業(yè)的<b class='flag-5'>腳本</b>驅動串行通信

    新手怎么學嵌入式?

    運行機制。例如,了解數據結構中的鏈表、棧和隊列,對于在嵌入式編程中管理數據非常有幫助。 2. 選擇合適的編程語言 嵌入式開發(fā)中常用的編程語言有 C 和 C++。C 語言是嵌入式開發(fā)
    發(fā)表于 12-12 10:51

    嵌入式系統開發(fā)與硬件的關系 嵌入式系統開發(fā)常見問題解決

    系統開發(fā)與硬件關系的幾個關鍵點: 硬件依賴性 :嵌入式系統的軟件必須能夠在特定的硬件上運行,這包括處理器、內存、輸入/輸出接口等。軟件必須能夠充分利用硬件的特性,同時繞過其限制。 資源限制 :
    的頭像 發(fā)表于 12-09 09:38 ?1305次閱讀