Lua脚本(实用角度)整理
-
本文以C语言视角,整理Lua脚本相关知识点。
-
Lua脚本由标准C语言开发,源码地址:Lua组织官网下载
1 变量
-
全局变量无需声明,访问野变量,得到
nil
。 -
局部变量前必须用local声明,注意作用域。
-
Lua中的值有8种类型:nil、boolean、number、string、userdata、function、thread和table。
-
总体:使用type(变量)得到类型,直接用print函数打印出来。
-
nil: nil类似于空,用于区分和清空(无效)该变量。
-
boolean :nil和false为假,其他值均视为真,同C语言类似。
-
number:类型表示浮点型数据时,需要使用带float的固件。
-
string:可以使用单双引号混用,转义同C语言。记住必须给修改后字符串新的内存,源无法改变。
-
table: Lua唯一数据结构,C语言中其他数据结构均可由此实现。主要通过键值对实现。下标或者说键名都可以是任意类型。
local test_table={233,["test"]="xiang",555,xx="wei"} print(test_table[2]) --输出555 重点:跳过了其他键值对 print(test_table["test"],test_table.test) --两者均输出xiang 重点:有键访问两种方法 print(test_table["xx"],test_table.xx) --两者均输出wei 重点:键名默认为字符串
- 推荐键名直接写 无需写成[“test”]
- 对于没有键名的值默认赋予递增的数值作为键名只能采用数组下标访问
-
2 表达式
-
C语言 != 不等于,替换为 ~= ,且== 与 ~= 可应用于任意类型。
-
C语言按位异或符号^,替换为指数符号。
-
较C语言多了字符串连接符
..
,即双点符号。 -
字符串长度取值符#,可以直接获取字符串长度。
-
C语言逻辑表达式,替换为,使用英文,and,or,not。其返回值与C语言有差别:
符号 含义 and 逻辑与操作符。 若 A 为 false,则返回 A,否则返回 B or 逻辑或操作符。 若 A 为 true,则返回 A,否则返回 B not 逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false
3 函数
-
函数形参和实参会自动匹配、忽略和补足。
-
函数返回支持多值返回,合并写成一行,使用逗号隔开。自动调整时,优先返回第一个值。
-
函数支持可变参输入,同C语言。使用时可以使用
arg[1]和#arg
。 -
字符串处理函数:
-
首先明白简化写法:
string.func(A,xx,xx)
等价为A:func(xx,xx)
。 -
sub函数:类似于python的切片方法。起始和终止坐标决定操作结果。
-
rep函数:rep(s, n)返回字符串 s 的 n 次拷贝。
-
len函数:少用,建议使用字符串长度符#替换。
-
lower和upper:大小写转换。
-
format:格式化函数。
-
char和byte: ASCII转字符和字符转ASCII, 输入可变参。
-
find:匹配字符串起始和终止坐标。
-
gsub:替换字串,并返回替换次数,注意:lua变量数据不可更改,必须用新变量接收。
-
match方法注意三点:
- 找到第一个能匹配模式的部分,并返回。未匹配到模式,则返回nil。注意一定要有匹配模式,为空则返回也为空。
- 若设置了捕获,则只返回捕获部分,若捕获不成功,则子模式返回对应nil,若未设置,则返回整个匹配的部分。
- 第三个可选参数,可以设置从哪个坐标开始搜索。
local s,res,res1,res2 s = 'http://www.freecls.com' --由于没有指定捕获,返回全部匹配的字符 --结果:http://www.freecls.com res = string.match(s,'http://%a+.%a+.com') print(res) --如果有捕获,则分别返回捕获结果 --结果:www freecls res1,res2 = string.match(s,'http://(%a+).(%a+).com') print(res1,res2)
-
gmatch:模式迭代匹配,一般与for循环匹配,返回类型为function类型。
--下面这个例子会循环迭代字符串 s 中所有的单词, 并逐行打印: s = "hello world from Lua" for w in string.gmatch(s, "%a+") do print(w) end -- 下一个例子从指定的字符串中收集所有的键值对 key=value 置入一张表: t = {} s = "from=world, to=Lua" for k, v in string.gmatch(s, "(%w+)=(%w+)") do t[k] = v end -- 对这个函数来说,模板前开始的 '^' 不会当成锚点。因为这样会阻止迭代。
-
-
支持的匹配模式详见手册:6.4.1字串匹配模式。注意需要整个模式均匹配!
-
需要理解四个名词:字符类别(Character Class),模式条目(Pattern Item),模式(Pattern),捕获(Captures)。
-
字符类别主要包括:字符本身,小数点(任何一个字符),%d(任何一个数字),%a(任何一个字母),%w(任何一个字母和数字),%l(任何一个小写字母),%u(任何一个大写字母),%c(任何一个控制字符eg:\r\n),%p(任何一个标点符号),%x(任何一个16进制数字),%s(任何一个空白字符),%%(使用%对转义为自身)。表示字符集的中括号[],在中括号内使用-进行连接,使用进行取补集,表示-和其本身需要转义。上述所有的字符类别中小写字母替换为其大写字母,均表示其补集。
-
模式条目主要包括:单个字符类别(匹配单个字符),字符类别跟
*
(匹配错误即停止),字符类别跟+
(匹配到最后一个字符),字符类别跟-
(没弄懂!!!),字符类别跟?
(没弄懂!!!),%n(匹配n号捕获物字串),%bxy(匹配x到y所有字符,若存在多对xy则匹配多对),%f[set]
(没弄懂!!!)。 -
模式主要包括:模式条目最前面加符号
^
表示识别时从字符左边到右边,最后面加$
表示字符从右到左开始匹配。注意:匹配结果仍然保持从左到右。 -
捕获主要包括:在内部用小括号括起来一个子模式,至于子模式的排序,则根据从左到右,从外到里的顺序依次标号,配合模式条目中的%n可以指定取出第n个子模式匹配的字串。
-
4 基本语法
-
赋值时,等号两边可以使用逗号隔开变量,将多行操作合并为一条。
-
C语言中左大括号,替换为then(条件语句),do(循环语句),右大括号,替换为end。
-
for循环较C语言大同小异,均满足三个要素:初始值,终止值,步进值(缺省为1)。另外,支持泛型for循环,即迭代器遍历,
i,v in ipairs(a)
,其中ipairs是Lua提供的一个迭代器函数
。-
注意:
ipairs
只能是循环表中下标为数字的值,而pairs
则循环所有值。 -
注意:在循环中,条件值是无法更改的,例如:
for i = 1, 10 do i=11 print(i) end --[[ 输出结果为10个数字11,说明尽管打印输入的i被更改为11, 但作为循环条件的i并不会因此改变,仍然遵循1到10,步进值为1。 ]]
-
若需要在循环体内改变循环条件值,那么请使用while循环替代。
-
-
break同C语言。lua5.1无continue
-
当我们获取 table 的长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数,而导致无法正确取得 table 的长度。
5 跨文件调用
- 在被调用的文件头部使用
module(..., package.seeall)
声明可以被任意文件调用。 - 在需要调用
test.lua
文件中函数的地方,使用require"test"
,将自动运行该文件的所有被调用的函数。注意,未被调起的函数并不会运行。且运行完该文件后,会返回一个包含该文件的所有非local的变量和函数的一张表。
6 面向对象
-
Lua语言原生支持面向过程编程,因此为了构造面向对象,需要补充下列知识点:
- 首先,构造出的面向对象,只支持单个实例!
- 由于原生数据结构只有table,因此构建对象必须基于table。lua对象=table + function。
- 注意:创建实例时需要依赖于Metatable。table作为其操作的基本单位。
- 在冒号函数内部有一个参数self,在点函数内部没有参数self。 即用冒号(:)调用函数时,会默认传一个值(调用者自身)作为第一个参数。注意,怎么定义就怎么使用,不要混用!
- Metatable具有以两个下划线开头的元方法,例如
__index
,具体作用此处不展开。
-- 元类 Shape = {area = 0} -- 基础类方法 new function Shape:new (o,side) o = o or {} setmetatable(o, self) self.__index = self side = side or 0 self.area = side*side; return o end -- 基础类方法 printArea function Shape:printArea () print("面积为 ",self.area) end -- 创建对象 Shape:new(nil,10) --这里不考虑继承等特性,所以不接收返回值 Shape:printArea()
-
根据实用性原则,lua编程暂时使用场景在嵌入式应用,且只能是单实例,所以暂时不考虑类的继承,故在创建对象时,并不接收返回值。
-
更多高阶应用参考:w3cschool编程学习网站以及菜鸟编程学习网站。
二、LUAT框架知识点
1 程序运行方法
-
直接调用: 在main.lua中通过require脚本A, 在该A脚本中,调用某函数,从而运行A脚本中的函数。
-
协程: 在main.lua中通过require脚本A,在该A脚本中,调用
sys.taskInit
,传入一个任务(协程),线程中使用sys.wait(1000)
,让出运行权限,计时满后,重新回到此处运行。 -
定时器: 在main.lua中通过require脚本A,在该A脚本中,调用
sys.timerLoopStart (func,5000)
,从而定时调用函数func
。 -
程序注册: LuaTask通过订阅发布来实现消息机制。主要用作联动,收到发布的消息后,可以调用回调函数,从而达到被动触发一些操作的目的。
-
调用
sys.publish("TEST")
发布消息,可以发布多个消息。- 调用
sys.subscribe("TEST",subCallBack)
订阅消息,并指定触发的回调。注意此方法只能执行一次,若需要执行多次,则需要在任务中,使用sys.waitUntil("TEST", 10000)
作为解除阻塞的条件,从而实现多次订阅。 - 通过源码理解,在订阅的消息里,将该消息作为键名,存储值为对应的回调。按此原理,则推断出:订阅回调仅以触发前,最后一次订阅为准。
- 调用
2 框架运行原理
- 首先,Lua原生支持协程(coroutine),这为LUAT框架的LuaTask提供了可能。
sys.taskInit
实际是封装了coroutine.create
和coroutine.resume
,并在wait函数中调用定时器(依赖于嵌入式系统接口rtos),且使用协程方法对该线程进行挂起,运行,停止等操作。- 在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
- 重点理解:协同程序有点类似同步的多线程(需要线程间同步后运行),在等待同一个线程锁的几个线程有点类似协同(谁先解锁就运行谁)。
3 框架注意事项
- 开始时需要延时一段时间,才能调用打印函数。
- 定时器若重复执行,则会被后者覆盖,本质上是计数初值被重置。利用该功能可作为看门狗使用。
- 实测补充1:覆盖的前提是,定时器的回调函数是同一个回调函数。
- 实测补充2:若回调为匿名函数,则认为是不同的回调函数。
- 理论补充3:每个任务会默认占用一个定时器,总定时器个数为32个。
- IIC从机地址一般为7位,若手册为8位,考虑是否右移一位。
- 休眠模式主频很低(可能为32k),高速通信,例如115200串口通信是无法正常工作的,需要先唤醒系统。
- 串口收发:
- 应用协议一般分为三种:帧头+帧尾、帧头+长度+CRC、超时(打包)
- 由于缓冲区为环形缓冲区,需要注意两点:及时取走数据以及发送方需要在包与包间适当延时。
- 串口收到数据----中断----DMA传送8个字节----FIFO(Air720为8K内存FIFO)----FIFO从空到非空产生中断----通知lua虚拟机取数据。
- 串口发送数据----缓冲区(例如8k)----FIFO----产生中断(但是只是RAM发送完成),DMA硬件并不一定发送完成。所以建议延迟8个字节的波特率对应的时长,才能保证D MA发送完成。不至于丢包。
- 补充知识1:波特率是以字符为单位的,而比特率是以二进制位为单位的。
- 补充知识2:波特率9600表示每个字符传送时间为0.1ms,波特率115200时间为8.7us。
- 远程升级:项目名称需要大写。
- nvm由于参数拼接操作消耗内存较多,仅适用于小数据量的简单键值对参数。其配置文件在线刷后被格式化掉了,而在OTA后,仍然保留。若需要更新为最新的配置文件,需要更改OTA升级源码,在升级成功后调用
nvm.remove()
删除保留的配置文件,并重新初始化配置nvm.init()
即可。
4 合宙API整理
-
延时程序的实现:
- 硬阻塞
rtos.sleep(time)
,期间所有代码停止执行,谨慎使用! - 框架任务延时:
sys.wait(time)
,挂起协程,运行其他协程。若在非任务中使用,可能出现问题! - 框架升级版:消息等待延时,
sys.waitUntil(msg, timeOut)
。同理只能用在任务函数中。 - 无论定时器是否开启,均能使用的一次性和循环定时器:
sys.timerStart(time)
和sys.timerLoopStart(time)
。 - 不精确的时间戳延时,
os.time()
通过手动获取时间戳,进行比较判断。这种方法并不适用于短时间延时,而且还占用一个定时器,但可以将时间戳写入flash中,作为掉电仍然保存的计时起点。 - 开机时间:
rtos.tick()
,单位换算为2G模块1/16384秒,4G模块为5毫秒。
- 硬阻塞
5 合宙开发工具
- 使用USB-BOOT强制下载模式,需要勾选烧录工具相关项,并在通电前,短接BOOT和1.8V
Air72xU系列模块ROM和RAM说明 ROM:128Mb, RAM :64Mb Lua版本剩余:700KB脚本,1M RAM 详细如下: Air72xU系列的模块Flash总空间都为64Mb=8MB 用户二次开发有两个分区可用,脚本区和文件系统区 脚本区:通过Luatools烧写的所有文件,都存放在此区域,目前总空间为700KB,不同版本的core可能会有差异,以版本每次的更新记录为准 文件系统区:程序运行过程中实时创建的文件都会存放在此区域,目前总空间为1.4MB,不同版本的core可能会有差异,可通过rtos.get_fs_free_size()查询剩余的文件系统可用空间 Air720xU系列模块的RAM总空间都为128Mb=16MB 其中Lua运行可用内存1180KB,可通过base.collectgarbage(“count”)查询已经使用的内存空间(返回值单位为KB)