书接上文,本文说一下“闻”字诀。首先让我们回顾一下poweron reason表:
POWERON 事件 | POWERON 代码 | 解释 |
rtos. POWERON_KEY | 0 | 按键开机 |
rtos. POWERON_CHARGER | 1 | 充电开机 |
rtos. POWERON_ALARM | 2 | 闹钟开机 |
rtos. POWERON_RESTART | 3 | 软件重启开机 |
rtos. POWERON_EXCEPTION | 6 | 异常开机 |
rtos. POWERON_HOST | 7 | HOST工具控制重启开机 |
rtos. POWERON_WATCHDOG | 8 | 其他原因 |
如果说“望”字诀关注的是poweron reason 0,那么“闻”字诀需要重点关注的是poweron reason 3、6、8:
当poweron reason为3时,通常有两种情况:代码主动执行rtos.restart()或者sys.restart()实现软重启;代码运行出错(语法错误,内存不足、AT执行超时等多种可能性),底层自动重启;
当poweron reason为6时,只有这一种情况:底层出错,请上报bug;
当poweron reason为8时,通常是这种情况:Lua代码跑飞,底层亦无响应时,由外部看门狗芯片重启模块。
小提示:
为了调试方便,建议开发者首先在任意lua文件中加入如下代码:
For luaTask:
require”sys”
require”log”
sys.timerLoopStart(function() log.info(“RAM free size:”, 1024 –
collectgarbage(“count”), “KB”)
log.info(“ROM
free size:”, rtos.get_fs_free_size(), “KB”) end, 5000)
For luaScript:
require”sys”
sys.timer_loop_start(function() print(“RAM free size:”, 1024 –
collectgarbage(“count”), “KB”)
print(“ROM
free size:”, rtos.get_fs_free_size(), “KB”) end, 5000)
加入如上代码后,模块在运行时,即可间隔5秒打印一次RAM、ROM使用情况。
通常情况下,如果Lua代码语法错误,或者出现其他问题,Trace中会有如下提示:
报错格式为:
路径/源码文件名:出错行:调用的方法/函数(出错的原因)
堆栈回溯
如图所示,第一行内容提示开发者:/lua/wblist.lua这个文件的121行出错:调用fileSize失败(空值);
后边是堆栈回溯,告知开发者错误函数是如何被调用的。途径是main.lua –> sys.lua run() –> sys.lua saferun() –>
device.lua –> wblist.lua writecard()
如此一来,开发者就可以根据错误提示和堆栈回溯去追根溯源,查找代码错误。
下表是一些常见的错误提示和解决方法:
序号 | 错误提示 | 错误原因 | 解决方法 |
1 | attempt to index %s | 变量/函数 索引错误 | 修改代码 |
2 | attempt to call %s | 变量/函数 引用错误 | 修改代码 |
3 |
disp.init: error param width(%d) height(%d) |
disp初始化时,设置了错误的宽、高 | 修改为正确数值 |
4 | disp.init: pixel depth must be 16 | disp像素色深必须是16位 | 修改代码 |
6 |
i2c.write: data must be number,string,table |
i2c数据必须是数值、字符串或table | 修改数据类型 |
7 | i2c.read: size must < %d | i2c读取错误,数据长度超限 | |
8 | bad argument #%d (%s) | audio错误的参数 | 修改传入的参数 |
9 |
calling ” LUA_QS ” on bad self (%s) |
audio错误的调用 | |
10 |
name conflict for module ” LUA_QS, libname |
命名冲突 | 修改名称 |
11 | too many results to unpack | unpack方法传参错误 | |
12 | attempt to use a closed file | 文件已关闭,无法调用 | 打开文件 |
13 | file is already closed | 文件已关闭 | 打开文件 |
14 | wrong number of arguments | 传参 参数 个数错误 | 检查传参内容 |
15 | string slice too long | 字符串过长 | |
16 | attempt to use an invalid ICONV_TYPENAME | Iconv不支持的类型 | |
17 | BUG: Unable to fetch CJSON configuration | cjson配置错误 | |
18 |
JSON parser does not support UTF-16 or UTF-32 |
JSON不支持utf-16或utf-32字符编码 | |
19 |
Memory allocation error in CJSON protected call |
无法为CJSON分配内存 | |
20 | invalid pin | GPIO配置错误,不存在该pin | 检查代码和硬件设计手册 |
21 | invalid PIO operation | GPIO非法操作 | |
22 |
uart.setup can’t be called on virtual UARTs |
UART无法初始化 | 检查代码,uart.setup相关配置 |
23 | invalid number | UART错误的端口号 | 检查代码,uart.setup相关配置 |
24 | invalid format | Uart.setup错误的配置参数 | 检查代码,uart.setup相关配置 |
除了poweron reason外,我们还可以检查重启后打印的上次重启原因,分析问题所在(luaTask):
通常情况下,如果重启后不存在记录文件,则提示no open(意外掉电导致重启,代码未主动调用接口保存,都可能有这个提示):
如果存在错误文件,则打开该文件,显示内容后,删除该记录文件:
如此一来,我们就能根据记录文件的内容分析啦(上图所示,即软狗引发的重启)。
接下来,我们分析几个典型的具体案例,希望对开发者Debug有所帮助。
一、混用LIB
众所周知,目前Luat的LIB库目前有两个版本:第一版名为luaScript,它不含协程,代码也较为陈旧;第二版名为luaTask,它实现了lua的协程,架构更合理,运行也更稳定高效。
第二版LIB使用了驼峰命名法。故此,所有函数均和第一版luaScript的LIB不兼容(划重点)。如果开发者如果使用的是第一版的LIB进行开发,但是下载的时候却选择第二版luaTask的LIB,必然会出现错误(反之亦然哦)。
例如图中的代码,报错文件是”socketOutMsg.lua”,从文件名的命名法分析,这是一个luaTask的LIB,但是开发者调用的函数名称是misc.getclockstr(),这明显是第一版luaScript LIB 的函数(非驼峰命名法)。所以此处正确的调用方法应是:misc.getClock()。
开发者如果选择了某个版本的LIB,就要自始至终、始终如一、至死不渝、地老天荒的使用它。不同LIB版本混用就会出现问题。
二、语法错误
有的时候碰到这种没头没脑的报错,的确非常头痛。这种情况下建议检查条目:
1、是否存在语法错误:代码块缺少”end”、缺少”)”等;
2、是否存在隐含的逻辑错误;
3、源码文件字符编码是否为gb2312?
4、源码文件结尾处是否缺少一个新行(所有源码文件尾部必须包含一个内容为空的新行)。
最后一种情况(4),因为报错比较含糊,有可能是最难排查的。所以建议开发者养成一个“结尾随手加上几个新行”的习惯,避免这种情况。
如果以上的1~4都不奏效,那么只能使用“排除法”,依此删减“嫌疑度”最高的代码,下载运行,直到排查出问题所在,再进行分析、修改。
三、“代码跑飞了”?
实际上,图中的问题部分涉及“问题一、混用LIB”。看过上文的读者,应该很轻松就能发现问题所在。
基于lua的协程,使得luaTask更灵活,更稳定,也更强大。所以当协程出现错误时,并不会表现为重启,而是输出错误信息 [E]-[coroutine.resume] xxxx,然后继续执行。由于Trace打印速度极快,而且没有很明显的重启迹象,开发者很容易忽略这个错误。
故此,使用luaTask,模块没有因错重启,Trace也正常打印,但是函数被调用后,却没有正确执行时,开发者应仔细分析LOG有没有出现上述错误。根据错误提示再去Debug。
四、内存不足
模块可供lua使用的内存是1024KB,通常情况下是足够使用的。但是如果开发者不小心写了bug,会造成内存不足:
通常情况下,内存不足后,底层会自动重启。
但是某些时候(如图所示),内存不足,但是luaTask没有直接重启,而是继续恢复运行。直到最后彻底失去响应,才会让外部看门狗芯片引导模块重启(poweron reason为8):
为了杜绝内存不足这种情况,开发者首先要养成良好的习惯:
·
不要所有函数、变量都写成全局的。开发者应在适当的时候,才使用全局变量,其他情况一律使用local约束;
·
串口收到的数据、http下载的内容等,不要全部放到变量中;如果内容过多,应保存为文件;
·
如果使用变量储存str,不要忘记清空变量;
·
尽量减少全局变量的使用,如果是运行时的常量,可以使用读取配置文件代替变量(请参考nvm demo)
·
优化代码逻辑。
排查内存不足的情况,首先要在代码中加入前文提到的打印RAM和ROM的代码,然后观察Trace打印过程中RAM动态减少的情况。
如果模块在执行某个函数后,内存骤降,那么证明该函数存在问题,需要分析、修改;如果内存缓慢减少,那么应该按照前文所述的方法去更改代码,减少全局变量的使用。
如果开发者遇到底层库报错导致的内存不足,请把Trace文件提供给我司:
五、死循环
由于模块的CPU、RAM资源有限,所以任何空循环,死循环都会吃尽所有资源。底层发现资源不足是,会自动重启。所以任何时候,都要避免空循环、死循环的情况。
由于luaTask的特性,更需要额外注意。如:
require”sys”
require”log”
sys.taskInit(
function()
while true do
—此处如果执行的操作很短完成时间内完成(非阻塞等待),可视为死循环
log.info(“loop”, ”test”)
—为了避免循环吃尽所有资源,可以使用wait,实现1000ms循环一次
sys.wait(1000)
end
end
)
六、AT命令执行超时
这种情况较为罕见,通常是lua和底层交互是,底层没有及时的、正确的返回,lua判断AT命令执行超时(通常是60s),自动重启:
这种情况无须担心,是极为罕见的。重启后即可恢复正常运行。如有条件,建议更新到最新底层。
七、无法运行
很多时候,由于开发者一时疏忽,下载lod后忘记下载lua,所以导致无法运行,异常重启。
解决方法也很简单,记得下载lod后,下载lua即可。
结束语
研读Trace输出的内容,是开发者用以Debug的最高效方式,一定要认真分析输出的内容,才能事半功倍。