内容简介:※已刊登在“无线电”12月刊上轻松实现Lua脚本控制W5500作者:孔东明,张博Lua是巴西里约热内卢天主教大学里的一个研究小组于1993年基于标准C开发的一个轻量级的嵌入式脚本语言,其设计目的是为了将传统嵌入式程序“编写→编译→链接→运行”的复杂过程简化为“编写→运行”两个环节,从而为嵌入应用程序提供灵活的扩展和定制功能。
※已刊登在“无线电”12月刊上轻松实现 Lua 脚本控制W5500
作者:孔东明,张博
1、引言
Lua是巴西里约热内卢天主教大学里的一个研究小组于1993年基于标准C开发的一个轻量级的嵌入式脚本语言,其设计目的是为了将传统嵌入式程序“编写→编译→链接→运行”的复杂过程简化为“编写→运行”两个环节,从而为嵌入应用程序提供灵活的扩展和定制功能。
Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替XML,ini等文件格式,并且更容易理解和维护。一个完整的Lua解释器不过200K,在目前所有脚本引擎中,Lua的速度是最快的。这一切都决定了Lua是作为嵌入式脚本的最佳选择。
2、项目背景
随着物联网的快速发展,传统的工控、电力、银行机、闸机甚至家电等设备也纷纷加入了连接互联网大军。工厂的车床需要把运行数据实时上传至PLC,水表、电表、燃气表实现了远程抄录,点验钞机可以实时将RMB的冠字号上传至银行数据库,停车场无人值守,家里的窗帘用某猫精灵很方便的进行语音控制……
小编在一家做网络通信设备的公司上班,领导要求基于现有的串口转以太网模块开发出一款支持用户使用Lua语言进行二次开发的串口转以太网模块,项目工期1个月。小编刚刚毕业4个月,没有多少项目经验,只是在学校玩过ARM M3的开发板,C语言自我感觉勉强及格,以太网技术基本小白一枚,对如何实现用户使用Lua语言“二次开发”更是一窍不通。但是任务时间紧迫,再难也要搞定,要不然没有奖金就要勒紧裤腰带了。
接到项目当晚就去找度娘商讨对策。经过一番搜索,方才大致了解了什么是Lua,什么是脚本语言,为什么客户要二次开发。用户在使用串口转以太网模块时,由于应用场景的不同及嵌入式产品资源的限制,需要灵活的调用模块的各项功能去实现差异化应用,而传统的模块只能实现既定的功能,因此支持二次开发的产品应用范围将大为拓展。而用户二次开发输入的代码肯定是无法执行传统的“编写→编译→链接→运行”这整个过程,脚本语言将这个过程简化为“编写→运行”就可以完美的解决了这个问题,Lua便是一款最佳的嵌入式脚本语言。
原理理顺了,如何落实便成了当务之急,我需要先做一个Demo来模拟整个过程。我找来了之前开发串口转以太网模块用到的以太网开发板W5500EVB,如下图。W5500EVB是由ST的STM32F103RC+W5500网络芯片构成,STM32F103内部256K的Flash足以容纳最大200K的Lua驱动。W5500是一颗以太网接口芯片,它用全硬件逻辑门电路搭建了一整套全硬件TCP/IP协议栈,发送数据时单片机只需将用户数据通过SPI发送至W5500,W5500内部会自动完成数据TCP/IP封包,并发送至网口,接收数据时W5500内部自动完成解包,仅将MCU关心的用户数据提交。W5500内含8路完全独立的硬件Socket,这意味着W5500可以同时运行8个上层应用程序,而且传输速率互不影响,不会像软件协议栈那样线程增加,速度明显降下来。W5500内部还集成了MAC和PHY,符合了接入以太网的所有条件,对于刚刚接触以太网的攻城狮来说,是一款简单易上手的网络接口芯片。
图 1 W5500EVB
我想象中的Demo是这样的:用户通过Web网页向W5500EVB提交一段能让W5500EVB连接到TCP服务器的Lua脚本代码,W5500EVB解析出来这段代码后通过已经运行的Lua虚拟机中的Lua接口函数来解释用户代码要实现的功能,最后 W5500EVB按照用户代码中的参数连接到一个指定的TCP服务器实现以太网数据通信。这个过程可以参考W5500官网提供的HTTP Server和TCP Client的例程。
图 2 实施方案原理图
3、准备工作
(1)安装编译环境:Keil V5.11
(2)硬件:W5500EVB、Jlink调试器
(3)驱动:Lua最新驱动V5.3.2
4、宿主C部分
4.1 加载驱动
驱动包括STM32F103RC的单片机驱动、W5500以太网部分驱动以及Lua驱动。STM32F103RC驱动不必多说,W5500驱动和Lua驱动如下图所示,均可以在对应官网下载到。
图 3 W5500驱动 图 4 Lua驱动-V5.3.2
4.2 初始化部分
初始化部分包括STM32初始化及W5500初始化,Lua在用的时候才需要初始化。
01 /******* STM32 初始化 ********/
02 Systick_Init(72);
03 RCC_Configuration();
04 GPIO_Configuration();
05 Timer_Configuration();
06 NVIC_Configuration();
07 USART1_Init();
08 at24c16_init();
09
10 /******* W5500 初始化 ********/
11 printf(“W5500 Config….\r\n”);
12 Reset_W5500(); // 重启 W5500
13 WIZ_SPI_Init(); //SPI 初始化
14 set_default(); // 配置默认信息
15 set_network(); // 用默认信息初始化 W5500
4.3 Socket分配
W5500内含8个socket(0~7),可以同时进行8路独立的数据通信或上次应用服务。小编这里用Socket 7作为HTTP Server服务的端口,这样用户可以在后续的Lua脚本中任意使用socket 0~6来进行数据收发。
01 //Socket 0~6 can be used by user for data communication
02 #define SOCK_HTTP 7
4.4 HTTP Server部分
该部分主要是往W5500EVB内部嵌入一个HTTP Server服务和一个网页,以实现浏览器访问并接收用户Lua脚本的功能。HTTP Server服务是基于TCP Server的短连接实现的,第一个过程,用户在浏览器中输入HTTP Server的IP地址后,浏览器默认会向服务器请求index.html这个页面,W5500EVB会相应的返回一个如下的示例网页并关闭这个连接,等待下一次请求。
图 5 HTTP Server网页
以下是该嵌入式网页的源码:
第二个过程也差不多,用户在浏览器中输入Lua脚本并提交,HTTP Server收到Lua脚本后将其原原本本的解析出来,保存在C的缓存中,等待Lua虚拟机的调用。下面是HTTP的实现过程。
01 void do_http(void) {
02 uint8 ch = SOCK_HTTP; // 使用 Socket 7 来处理 HTTP Server 服务
03 uint16 len;
04
05 st_http_request *http_request;
06 memset(rx_buf,0x00,MAX_URI_SIZE);
07 http_request = (st_http_request*)rx_buf;
08 /* socket 轮询状态机 */
09 switch (getSn_SR(ch)) {
10 case SOCK_INIT: //socket 已经初始化
11 listen(ch); // 建立监听,等待 TCP Client 连接
12break;
13 case SOCK_LISTEN:
14break;
15 case SOCK_ESTABLISHED: // 已连接
16 if (getSn_IR(ch) & Sn_IR_CON) {
17 setSn_IR(ch, Sn_IR_CON);
18 }
19 if ((len = getSn_RX_RSR(ch)) > 0) {
20 len = recv(ch, (uint8*)http_request, len); // 接收 HTTP 请求
21 *(((uint8*)http_request)+len) = 0;
22 proc_http(ch, (uint8*)http_request); // 发送 HTTP 应答
23 disconnect(ch); // 关闭连接
24 }
25break;
26 case SOCK_CLOSE_WAIT: // 等待连接关闭过程中也要处理 HTTP
27 if ((len = getSn_RX_RSR(ch)) > 0) {
28 len = recv(ch, (uint8*)http_request, len);
29 *(((uint8*)http_request)+len) = 0;
30 proc_http(ch, (uint8*)http_request);
31 }
32 disconnect(ch);
33break;
34 case SOCK_CLOSED: //socket 处于关闭状态
35 socket(ch,Sn_MR_TCP,80,0×00); // 初始化并打开该 socket
36break;
37default:
38break;
39 }
5、C与Lua的交互
5.1 C与Lua的交互过程
下面是C与Lua的交互过程。第2行,Lua_State是Lua语言中的一种基本类型,用来初始化一个Lua虚拟机的运行环境。第3行,luaL_newstate是为了初始化并启动Lua虚拟机,包括创建Lua栈空间等(Lua与C交互主要通过栈来进行,因此需要足够的栈空间,建议设为800byte)。第4行,声明Lua基础库,Lua有丰富的库供调用,这里基础库主要用来处理字符串,声明后的Lua基础库会注册在Lua全局表(Global Table)中,形成了Lua全局表的一部分。第5、6行,声明Lua接口函数及运行用户Lua脚本。第7行,关闭Lua。
01 void do_lua(void) {
02 lua_State* L;
03 L= luaL_newstate(); // 初始化并启动 Lua 虚拟机
04 luaopen_base(L); // 声明 Lua 基础库
05 luaL_setfuncs(L, mylib, 0); // 声明 Lua 接口函数
06 luaL_dostring(L, LUA_SCRIPT_GLOBAL); // 运行解析出来的用户 Lua 脚本
07 lua_close(L); // 关闭 Lua 虚拟机
08 }
以下是声明Lua接口函数,声明后的Lua接口函数会注册在Lua全局表中,形成了Lua全局表的另一部分。这些接口函数其实是在C中定义的,在运行Lua脚本时,Lua虚拟机会在Lua全局表中查询已经注册的C接口函数,查到后就将Lua脚本中的参数经过栈传递给C,C通过对应的接口函数计算出结果,再通过栈传递给Lua虚拟机。
01 static const struct luaL_Reg mylib[]= { // 以下是声明 Lua 接口函数
02 {“Get_rxbuf”,get_rx_buf},
03 {“get_state”,get_state},
04 {“socket_config”,socket_config},
05 {“connet_server”,connet_server},
06 {“recv_server”,recv_server},
07 {“close_socket”,close_socket},
08 {NULL,NULL}
09 };
以下是运行用户Lua脚本,这是运行用户Lua脚本的入口。当执行Get_rxbuf()时,Lua虚拟机根据Lua全局表找到get_rx_buf这个C函数接口,然后通过栈操作执行之。
01 const char LUA_SCRIPT_GLOBAL[] =”\
02 local array \
03 array=Get_rxbuf() \
04 fun=load(array)\
05 fun()\
06 “;
以下是C执行get_rx_buf的过程。C将存放input_temp缓存中的HTTP传递给W5500EVB的Lua脚本字符串,通过栈压入给Lua虚拟机,Lua虚拟机会依照上述过程逐句解析该Lua脚本,再结合其他的C接口函数的计算,最终实现需要的功能。
01 static int get_rx_buf(lua_State *L) {
02 uint8 size=0;
03
04 size=strlen(input_temp);
05 if (size) {
06 lua_pushstring(L,input_temp);
07 return 1;
08 }
09 return 0;
10 }
5.2 解释Lua脚本
以下是用户通过网页上传的Lua示例脚本,Lua虚拟机通过解释该脚本,就可以连接到TCP Server,并实现数据通信。
01 lua_mode=1 // 进入轮询模式
02 local state=nil
03 local s=2 // 使用 socket 2 来进行数据 TCP 通信
04 local lport=5000 // 配置 TCP Client 端口号
05 local rport=6000 // 配置 TCP Server 端口号
06 local rip=”192.168.1.100″ // 配置 TCP Client 的 IP 地址
07
08 state=get_state(s) // 查询该 socket 的状态
09 if state==”SOCK_CLOSED” then // 该 socket 处于关闭状态
10 socket_config(s,lport) // 初始化并打开该 socket
11 elseif state==”SOCK_INIT” then // 该 socket 处于已初始化状态
12 connet_server(s,rip,rport) // 向服务器发起 TCP 连接请求
13 elseif state==”SOCK_ESTABLISHED” then // 该 socket 处于连接建立状态
14 recv_server(s) // 接收 TCP Server 发来的数据再回给 TCP Server
15 else
16 close_socket(s) // 该 socket 处于其他状态时,关闭该 socket
17 end
6、测试过程
第一步 在浏览器中输入HTTP Server的IP地址(192.168.1.111)登录网页并提交 Lua脚本,W5500EVB解析到Lua脚本后向TCP Server发起连接。
图 6 登录网页并提交 Lua脚本
第二步 PC建立TCP Server监听、建立连接、数据收发。
图 7 建立TCP连接并实现通信
至此,整个Demo就实现了,其实核心部分就是要搞清楚Lua程序与C是如何交互的,以及用户的Lua脚本字符串是如何输入并一步步传递至Lua环境。当然,站在产品的角度上,这样操作还有一些问题,如果模块重新上电,Lua脚本就会消失。我这里提供两种解决思路:第一,可以在单片机中运行文件系统,将网页提交的Lua脚本以.lua文件的形式存储起来;第二,实际的用户拿到模块后一般是需要一个MCU去控制的,可以将Lua脚本放在用户MCU中,这样就不需要用网页输入Lua脚本了。不知小编的理解是否合理,还请诸位看客指正,谢过!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 超实用的Tomcat启动脚本实现
- ASP脚本组件实现服务器重启
- shell脚本实现每秒执行一次任务
- Shell脚本结合Git实现增量项目部署
- python脚本实现Redis未授权批量提权
- BASE64编码原理分析脚本实现及逆向案例
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。