书接上文,本文说一下“闻”字诀。首先让我们回顾一下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有所帮助。
众所周知,目前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
)
这种情况较为罕见,通常是lua和底层交互是,底层没有及时的、正确的返回,lua判断AT命令执行超时(通常是60s),自动重启:
这种情况无须担心,是极为罕见的。重启后即可恢复正常运行。如有条件,建议更新到最新底层。
很多时候,由于开发者一时疏忽,下载lod后忘记下载lua,所以导致无法运行,异常重启。
解决方法也很简单,记得下载lod后,下载lua即可。
研读Trace输出的内容,是开发者用以Debug的最高效方式,一定要认真分析输出的内容,才能事半功倍。
43 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!