air202 使用lua程序开发个人理解(基于操作系统)
其实使用lua开发和C语言开发是一样的,只不过使用lua时把硬件的底层给封装好,不需要在自己配置寄存器了, 这一块就需要调用air202官方给封装好的lib库文件
一. 函数运行:
1. 当在文件只是定义实现函数时,它是不会运行的,需要在调用一下, 如:
--test.lua文件
local function TASK( )
print("TASK function test")
end
TASK() --直接调用,在main.lua文件中require"test"的时候则会调用该函数
此时只会打印一次"TASK function test"信息
2. 在协程中运行:死循环一直运行,通过wait挂起
--test.lua文件
--定义的函数时不会执行的,需要调用
local function ss( )
print("ss function test")
end
--调动sys.lua中的函数taskInit 弄出个协程来
sys.taskInit(function()
cnt = 0
while true do
ss()
sys.wait(1000) -- 挂起1000ms,同理为每隔1000ms运行一次
end
end)
定义的函数在协程中调用1000ms执行一次
3. 定时器和协程一起使用:
--test.lua文件
local function ss( )
print("Task function test")
end
local function TIM()
print("TIM function test")
end
--直接调用
--ss() --直接调用,在main.lua文件中require"test"的时候则会调用该函数
--协程
sys.taskInit(function()
cnt = 0
while true do
ss()
sys.wait(1000) -- 挂起1000ms,同理为每隔1000ms运行一次
end
end)
--定时器调用
--sys.timerStart(ss,3000) --3秒运行一次
sys.timerLoopStart (TIM,1000) --循环定时器,每隔1秒运行一次
定时器和协程一起运行结果:
二. 简单总结
1. 因为lua程序是顺序执行下来, 且只执行一遍的那么就需要另外的方法,让需要一直执行的程序块一直运行
2. 如果想要程序一直运行可以像C语言开发单片机那样, 直接用一个循环, 在循环内是一个需要一直运行的程序块,不过要记住把系统要挂起,不知会不会直接死在这.
3. air202 模块还使用了另一方法实现类似C语言开发单片机中的中断方式,当一个操作完成时就会发出一个信号; 其他函数可以订阅该消息并做对应的操作,在接收这个信号后执行某段程序(subscribe订阅方式) ; 调用 sys.lua( Luat协程调度框架 ) 中的函数
三. 程序注册 (抄的)
LuaTask通过订阅发布来实现消息机制。
当函数完成一个操作后,可以发布一个消息,其他函数可以订阅该消息并做对应的操作。举个例子,当socket发送完数据后发布“SEND_FINISH”。这时开发者想socket发布完成后通过串口发送数据或者改变某个IO口的状态。就可以订阅该消息subscribe("SEND_FINISH",callback)。callback为接收到SEND_FINISH消息后需要做的事。
先来看一个程序
testMsgPub.lua
--testMsgPub.lua
module(...,package.seeall)
require"sys"
local a = 2
local function pub()
print("pub")
sys.publish("TEST",a) --可以发布多个变量sys.publish("TEST",1,2,3)
end
pub()
testMsgSub.lua
--testMsgSub.lua
module(...,package.seeall)
require"sys"
local function subCallBack(...)
print("rev",arg[1])
end
sys.subscribe("TEST",subCallBack)
如果要在任务函数中订阅消息并做相应的处理,怎么办?
module(...,package.seeall)
require"sys"
local a = 2
local function pub()
print("pub")
sys.publish("TEST",a)
end
pub()
sys.taskInit(function()
while true do
result, data = sys.waitUntil("TEST", 10000)
if result == true then
print("rev")
print(data)
end
sys.wait(2000)
end
end)
调用sys.waitUntil()函数即可。
接下来分析实现的源码
为了更好的理解源码,需要以下的预备知识:
1、回调函数的实现
local function callBackTest(...)
print("callBack",arg[1])
end
local function test( a,callBackTest )
if a > 1 then
callBackTest(1)
end
end
test(2,callBackTest)
--输出
--callBack 1
2、不定参数
function g (a, b, ...) end
g(3) -- a=3, b=nil, arg={n=0} -- n为不定参数的个数
g(3, 4) -- a=3, b=4, arg={n=0}
g(3, 4, 5, 8) -- a=3, b=4, arg={5, 8; n=2}
进入正题
订阅和发布官方给的实例:
------------------------------------------ LUA应用消息订阅/发布接口 ------------------------------------------
-- 订阅者列表
local subscribers = {}
--内部消息队列
local messageQueue = {}
--- 订阅消息
-- @param id 消息id
-- @param callback 消息回调处理
-- @usage subscribe("NET_STATUS_IND", callback)
function subscribe(id, callback)
if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
log.warn("warning: sys.subscribe invalid parameter", id, callback)
return
end
if not subscribers[id] then subscribers[id] = {} end -- 如果没有重复消息
subscribers[id][callback] = true --标记id和callback关系
end
--- 取消订阅消息
-- @param id 消息id
-- @param callback 消息回调处理
-- @usage unsubscribe("NET_STATUS_IND", callback)
function unsubscribe(id, callback)
if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
log.warn("warning: sys.unsubscribe invalid parameter", id, callback)
return
end
if subscribers[id] then subscribers[id][callback] = nil end --删除id和callback关系
end
--- 发布内部消息,存储在内部消息队列中
-- @param ... 可变参数,用户自定义
-- @return 无
-- @usage publish("NET_STATUS_IND")
function publish(...)
table.insert(messageQueue, arg) -- 将不定参数插入队列中
end
-- 分发消息
local function dispatch()
while true do
if #messageQueue == 0 then --如果队列长度为 跳出循环
break
end
local message = table.remove(messageQueue, 1) --获取队列的第一个
if subscribers[message[1]] then --如果订消息存在
for callback, _ in pairs(subscribers[message[1]]) do
if type(callback) == "function" then
print("unpack",unpack(message, 2, #message))
callback(unpack(message, 2, #message)) -- 返回第二个到最后一个
elseif type(callback) == "thread" then
coroutine.resume(callback, unpack(message))
end
end
end
end
end
以sys.publish("TEST",a)和sys.subscribe("TEST",subCallBack),订阅者列表为local subscribers = {}。内部消息队列为local messageQueue = {}为例:
1、在publish函数中,将"TEST"消息和参数插入messageQueue列表中
此时messageQueue中为{{"TEST",2;n=1}}
2、在subscribe函数中判断消息和callback类型是否正确,如果正确则在subscribers中建立消息与回调函数之间的关系。
此时subscribers["TEST"][subCallBack] = true。表明TEST消息对应的回掉函数为subCallBack
3、在dispatch()函数中,获得表头列表。
local message = table.remove(messageQueue, 1)
此时message为{"TEST",2;n=1}
找到该消息对应的回调函数或消息。将message中的参数传给回调函数。
通过pairs遍历得到消息对应的回调函数或者任务。
如果callback是函数,那么将publish时候的参数传给回调函数。
如果callback是线程,那么唤醒该线程。
以上只是单个消息举例,多个消息同理,因为每次循环都会将messageQueue的头部出队列,满足FIFO原则。
在有上基础下容易的理解waitUntil()的实现
--- Task任务的条件等待函数(包括事件消息和定时器消息等条件),只能用于任务函数中。
-- @param id 消息ID
-- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
-- @return result 接收到消息返回true,超时返回false
-- @return data 接收到消息返回消息参数
-- @usage result, data = sys.waitUntil("SIM_IND", 120000)
function waitUntil(id, ms)
subscribe(id, coroutine.running())
local message = ms and {wait(ms)} or {coroutine.yield()}
unsubscribe(id, coroutine.running())
return message[1] ~= nil, unpack(message, 2, #message)
end
1、订阅id,并传入线程号
2、阻塞线程,如果接收到了消息,那么返回message
3、取消订阅该id
4、返回结果