关于Lua的官方介绍,此处不在说明。有需要了解的看官可以在百度或者lua教程了解。关于其应用有:
游戏开发
独立应用脚本
Web 应用脚本
扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
安全系统,如入侵检测系统
但这些对此文章来时并不是重点,而重点在于将Lua移植到单片机上到底有什么用处。经过这些天的移植和测试,个人理解是这样:将通过某个方式将一段字符串输送给STM32。该段字符串就是一段lua脚本程序,而单片机就会根据这段脚本程序实现脚本的功能。你可以将这段脚本做成文件,用文件系统进行读取,或者通过串口输入,又后者用flash存储。那么它在单片机里具体有什么用呢,在我学习的过程中,在一些讨论群中很多网友都说这是个鸡肋,压根没什么用途。而本人则觉得如果对于一些懂得lua脚本编程的开发者来说未必会单片机开发能力,如果设备拥有脚本编程能力,对这类有设备需要的开发者来说还是挺好的。下面简单举个例子说明一下lua在单片机中如果灵活的使用。
假设当前有个主机设备,设备提供有串口3个(编号A,B,C),网口1个(net),继电器控制口8个(Q1-Q8)。485通信口2个(M1,M2)
现在有四个设备接在M1口上,分别为设备(A1-A4);这四个设备都可以通过命令进行查寻,而查询的结果可以让主机设备根据脚本进行不同的动作,比如,可以让查询A1设备的结果发送到网络,也可以查询A1的结果发送到串口,这样作为设备开发商对于这种不定性的要求是无法掌握的。如果主机能够支持脚本语言,虽然接口有限,但是却可以根据用户要求进行不同的转换或者处理。只要客户自行编写自己得脚本既可以。或者有厂家帮客户提供脚本服务。而不需要去编写底层代码,这类应有在一些PLC中控器是比较常见的。
这是目前个人对Lua的一点理解。如果说的不好也请见谅。这个并非本文的重点。接下来开始文章点重点,移植和使用。
准备:开发板一块,lua程序包(当前版本为Lua-5.3.5版本)
说明:由于当前版本Lua的内存开销如下
l RAM >= 7.5Kb,建议16KB以上
l ROM >= 65kb,建议128kb以上
因此RAM和ROM少于最低要求的芯片是无法移植的
先创建一个简单的工程项目,保存项目可以正常运行。
Lua的程序文件都在包中的scr文件夹下面,因此将整个文件夹复制到项目文件夹中,并添加到工程项目里,然后在include paths中添加Lua的路径。注意添加时有两个文件不需要添加,lua.c 和 luac.c (分别为PC的解释器和编译器,这个跟java类型,如果有java基础的应该会有一定的理解)。既然是面对PC机的,所以我们不需要,如果加到项目里,因为其各自有个mian函数就会有错误。
由于lua底层还是有一些标准库函数,因此还需要为其添加一些标准库函数,具体有下面三个:
本机时间函数:
time_t time(time_t * time){
return 0;
}
进程退出函数,用于退出进程(单片机系统本是功能单一的应用,一般都不会用到,Lua程序要求因此添加一个空函数)
void exit(int status){}
此处添加后会有警告,这是因为在stilib.h文件定义中,该函数是有返回值的,个人使用直接在头文件中修改了定义。
extern _ARMABI_NORETURN void exit(int /*status*/);
修改为
extern void exit(int status);
将命令名称或程序名称传给要被命令处理器执行环境,同样设置为空
int system(const char * string){
return 0;
}
然后全编译后,成功如下图
个人还是喜欢无错误无警告的程序,(^ __ ^)
主要用两个例子来说明Lua的应用
1、利用Lua脚本语言做个2的11次方运行,并通过串口打印出来。
2、添加自定义的接口函数实现LED灯的闪烁。其闪烁频率可以自己通过延时来设置。
第二个例子还是比较贴合实际应用,主要是了解如何将自定义点函数添加到Lua脚本解释器中,还有关于带有参数的接口程序如何实现。
串口打印运算结果:(此处不多少,直接上关键代码)
/*
* 库函数发送调用功能函数
* 形式参数:
* 返 回 值:
*/
int fputc(int ch, FILE *f)
{
USART_SendData(USART1 , (uint8_t)ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_ClearITPendingBit(USART1,USART_FLAG_TC);
return (ch);
}
/*
* lua串口输出运算的脚本程序
*/
const char lua_test[] = {
"print(\"Hello,I am lua!\\n--this is newline printf\")\n"
"function foo()\n"
" local i = 0\n"
" local sum = 1\n"
" while i <= 10 do\n"
" sum = sum * 2\n"
" i = i + 1\n"
" end\n"
"return sum\n"
"end\n"
"print(\"sum =\", foo())\n"
"print(\"and sum = 2^11 =\", 2 ^ 11)\n"
};
/*
* 编译器测试
*/
static int do_file_script(void){
lua_State *L;
L = luaL_newstate(); // 创建Lua编译器
luaopen_base(L); // 注册基本函数
luaL_dostring(L, lua_test); // 执行脚本语句
return 0;
}
/*
* 主函数
*/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
usart1_initialization();
do_file_script();
while(1){
}
}
其中lua代码中的print就是输出功能函数。
例子二,利用串口传入脚本,然后执行脚本语句,思路比较简单,就是通过串口接收一串数据然后直接送到脚本解释器解析就行。几个关键点函数如下:
/*
* 利用死循环在做得延时函数,为了方面
*/
static int delay_ms(lua_State *L){
int num;
int count = lua_tointeger(L,1); // 读取脚本中送入的参数,1表示第一个参数
for(int i = 0 ; i < count ; i++){
num = 20000;
while(num--);
}
return 1;
}
/*
* Led控制函数
*/
static int Lua_LED(lua_State *L){
bool flag = lua_toboolean(L,1); // 读取传入函数的参数
if(flag)GPIO_ResetBits(GPIOF, GPIO_Pin_6); // LED灯亮
else GPIO_SetBits(GPIOF, GPIO_Pin_6); // LED灯灭
return 1;
}
/*
* 自定义函数注册结构体数组
*/
static const struct luaL_Reg mylib[] ={
{"delay_ms",delay_ms},
{"Lua_LED",Lua_LED},
{NULL,NULL}
};
lua_State *L; // 解析器指针
static int do_file_script(void){
L = luaL_newstate(); // 创建解析器
luaopen_base(L); // 注册基础函数
luaL_setfuncs(L, mylib, 0); // 注册自定义函数
return 0;
}
/*
* 主函数
*/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
usart1_initialization(); // 串口初始
led_initialization(); // LED的初始化
printf("\r\n\r\n");
do_file_script();
while(1){
if(Usart1_Receive(150000)){ // 读取串口数据
luaL_dostring(L, (char*)usartBuf); // 读取到的数据直接送到解析器解析
memset(usartBuf,0,1024); // 清楚缓冲
}
}
}
// 下面为串口输的数据
/*
count = 10 \
while (count > 0) do \
Lua_LED(false) \
delay_ms(50) \
Lua_LED(true) \
delay_ms(50) \
count = count - 1 \
end
*/
最后实验结果如下:
(本以为能够上传视频,结果上传不了,只能截取个图片)
下面附上自己调试的源码,如果有需求朋友可以下载看看,不过我不知道在哪里设置分数,本来想设置成1分的就是没找到设置的位置。
https://download.csdn.net/download/weixin_41558887/11826346