简介
肝了这么久的讲解,一个评论和点赞都没有,没有一丝丝反馈,没有一丝丝赞扬。难受哦!!!
但是我还得肝,必须肝出一个赞。
如果大家能给点反馈,给点一键三连,我肝的更起劲。大家的评价是我前进最大的鼓舞。
好了,吐了这么多的苦水,我们接着来肝lua的库,本来今天我准备说一下短信的库的,但是短信的发送是基于AT指令的,用到了ril
模块,所以我们先来说一下ril
库。
ril
库主要是通过虚拟通道与底层进行AT交互,什么是虚拟通道尼,可以理解为是两个队列,一个是发送,一个是接收,底层有数据的时候往接收队列里面填充并抛给lua一个消息,lua紧接着调用读队列的接口去读取数据。当lua需要发送数据的时候,就往发送队列丢数据,底层至于怎么处理不太清楚。(有可能发送一个消息给一个task。task去读取队列然后处理数据,这样就可以实现异步操作。)
下面看下lua具体是如何实现的:
注册
进入ril
,拉倒最下面我们可以看到:
--注册“AT命令的虚拟串口数据接收消息”的处理函数
uart.on(uart.ATC, "receive", atcreader)
搜索一下uart.on
的实现:
--- 注册串口事件的处理函数
-- @number id 串口ID: 1表示串口1,2表示串口2,uart.ATC表示虚拟AT口
-- @string event 串口事件:
-- "recieve"表示串口收到数据,注意:使用uart.setup配置串口时,第6个参数设置为nil或者0,收到数据时,才会产生"receive"事件
-- "sent"表示串口数据发送完成,注意:使用uart.setup配置串口时,第7个参数设置为1,调用uart.write接口发送数据之后,才会产生"sent"事件
-- @function[opt=nil] callback 串口事件的处理函数
-- @return nil
-- @usage
-- uart.on(1,"receive",rcvFnc)
-- uart.on(1,"sent",sentFnc)
uart.on = function(id, event, callback)
if event == "receive" then
uartReceiveCallbacks[id] = callback
elseif event == "sent" then
uartSentCallbacks[id] = callback
end
end
注释写的已经很清楚了,uart.on
的实现和前面我们说过的rtos.on
的实现非常相似。主要功能就是注册底层消息回调。第一个参数是ID代表是哪个口,第二个参数event
,主要是为了区分是哪个表。第三个参数callback
就是相应的回调。
注意,可以看到callback
是根据id
进行映射的,如果设置相当的id
,callback
就会被覆盖。
接收指令
前面说过,开机就注册了一个回调atcreader
,底层来数据了,在sys.run
里面就会调用该回调进行处理,我们看下这里面做了什么吧。
--[[
函数名:atcreader
功能 :“AT命令的虚拟串口数据接收消息”的处理函数,当虚拟串口收到数据时,会走到此函数中
参数 :无
返回值:无
]]
local function atcreader()
local s
--这里是判断是透传模式
if not transparentmode then readat = true end
--循环读取虚拟串口收到的数据
while true do
--每次读取一行
s = vread(uart.ATC, "*l", 0)
if string.len(s) ~= 0 then
if transparentmode then
--透传模式下直接转发数据
rcvfunc(s)
else
--非透传模式下处理收到的数据
procatc(s)
if app_rilcb ~=nil then app_rilcb(s) end
end
else
break
end
end
if not transparentmode then
readat = false
--数据处理完以后继续执行AT命令发送
sendat()
end
end
上面的函数我们大体可以归纳为:
- 先判断是否是透传,不是就将
readat
置为true(我认为没这必要,本身就是消息处理,不存在被打断的情况,可能是为了兼容之前的版本)。 - 开始调用
vread
也就是uart.read
循环读取数据直至没有数据退出。 rcvfunc
是通过setransparentmode
设置的回调函数,procatc
是处理函数。readat
置为false,接着去sendat
(查看发送缓存)。
透传我们这里不讲解,透传基本也不需要讲解,直接给上层回调处理的。
- 刚开始判断是否是多行字符串的格式,如果是就进行拼接并且中间加上
\r\n
,直至匹配到OK\r\n
结束。 - 第二部分是判断是否有数据过滤器。所谓数据过滤器就是通过
regUrc
接口注册的主动上报处理回调。 - 第三部分主要判断指令的返回类型
cmdtype
,这个类型是有字符串,多行,数字啊等等.
这个类型是在sendat
的时候调用getcmd
,在RILCMD
表有注册相应的类型,通过AT指令的前缀进行匹配的。这个前缀是通过
local head = string.match(cmd, "AT([%+%*%^]*%u+)")
进行匹配获取的。
如果RILCMD
表中没有注册相应的AT指令,就赋值为NORESULT
发送指令
发送指令就是调用底层的一个接口,像通道里面发送数据,看具体的实现函数sendat
local function sendat()
--AT通道未准备就绪、正在读取虚拟串口数据、有AT命令在执行或者队列无命令、正延时发送某条AT
if not radioready or readat or currcmd ~= nil or delaying then
return
end
local item
while true do
--队列无AT命令
if #cmdqueue == 0 then
return
end
--读取第一条命令
item = table.remove(cmdqueue, 1)
--解析命令
getcmd(item)
--需要延迟发送
if curdelay then
--启动延迟发送定时器
sys.timerStart(delayfunc, curdelay)
--清除全局变量
currcmd, currarg, currsp, curdelay, cmdhead, cmdtype, rspformt = nil
item.delay = nil
--设置延迟发送标志
delaying = true
--把命令重新插入命令队列的队首
table.insert(cmdqueue, 1, item)
return
end
if currcmd ~= nil then
break
end
end
--启动AT命令应答超时定时器
sys.timerStart(atimeout, TIMEOUT)
log.info("ril.sendat", currcmd)
--向虚拟串口中发送AT命令
if currcmd:match("^AT%+POC=") then
vwrite(uart.ATC, currcmd .. "\r\n")
else
vwrite(uart.ATC, currcmd .. "\r")
end
end
从上面可以看出发送的主要步骤有:
- 移出
cmdqueue
表的第一个指令。 - 解析指令
getcmd
,主要是赋值--当前正在执行的AT命令,参数,反馈回调,延迟执行时间,命令头,类型,反馈格式 local currcmd, currarg, currsp, curdelay, cmdhead, cmdtype, rspformt
- 如果需要延迟发送就追加到发送缓存中
cmdqueue
,这个表在接收的时候也说过 - 启动超时定时器,调用
vwrite
进行发送。
但是我们上层一般调用的函数是:ril.request
--- 发送AT命令到底层软件
-- @param cmd AT命令内容
-- @param arg AT命令参数,例如AT+CMGS=12命令执行后,接下来会发送此参数;AT+CIPSEND=14命令执行后,接下来会发送此参数
-- @param onrsp AT命令应答的处理函数,只是当前发送的AT命令应答有效,处理之后就失效了
-- @param delay 延时delay毫秒后,才发送此AT命令
-- @return 无
-- @usage ril.request("AT+CENG=1,1")
-- @usage ril.request("AT+CRSM=214,28539,0,0,12,\"64f01064f03064f002fffff\"", nil, crsmResponse)
function request(cmd, arg, onrsp, delay)
if transparentmode then return end
--插入缓冲队列
if arg or onrsp or delay or formt then
table.insert(cmdqueue, {cmd = cmd, arg = arg, rsp = onrsp, delay = delay})
else
table.insert(cmdqueue, cmd)
end
--执行AT命令发送
sendat()
end
可以看出这个函数主要功能也就是将一些参数插入到cmdqueue
发送缓存中,如果需要回调或者一些其它参数的,则传入表,否则直接传入指令。
最后还是调用sendat
,进行AT指令的发送处理。
至此,ril
库的大概逻辑已经说完了,有问题的可以留言讨论