轻松实现Lua脚本控制W5500

栏目: Lua · 发布时间: 5年前

内容简介:※已刊登在“无线电”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,符合了接入以太网的所有条件,对于刚刚接触以太网的攻城狮来说,是一款简单易上手的网络接口芯片。

轻松实现Lua脚本控制W5500 图 1 W5500EVB

我想象中的Demo是这样的:用户通过Web网页向W5500EVB提交一段能让W5500EVB连接到TCP服务器的Lua脚本代码,W5500EVB解析出来这段代码后通过已经运行的Lua虚拟机中的Lua接口函数来解释用户代码要实现的功能,最后 W5500EVB按照用户代码中的参数连接到一个指定的TCP服务器实现以太网数据通信。这个过程可以参考W5500官网提供的HTTP Server和TCP Client的例程。

轻松实现Lua脚本控制W5500 图 2 实施方案原理图

3、准备工作

(1)安装编译环境:Keil V5.11

(2)硬件:W5500EVB、Jlink调试器

(3)驱动:Lua最新驱动V5.3.2

4、宿主C部分

4.1 加载驱动

驱动包括STM32F103RC的单片机驱动、W5500以太网部分驱动以及Lua驱动。STM32F103RC驱动不必多说,W5500驱动和Lua驱动如下图所示,均可以在对应官网下载到。

轻松实现Lua脚本控制W5500 轻松实现Lua脚本控制W5500

图 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会相应的返回一个如下的示例网页并关闭这个连接,等待下一次请求。

轻松实现Lua脚本控制W5500

图 5 HTTP Server网页

以下是该嵌入式网页的源码:

轻松实现Lua脚本控制W5500 轻松实现Lua脚本控制W5500

第二个过程也差不多,用户在浏览器中输入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发起连接。

轻松实现Lua脚本控制W5500

图 6 登录网页并提交 Lua脚本

第二步 PC建立TCP Server监听、建立连接、数据收发。

轻松实现Lua脚本控制W5500 图 7 建立TCP连接并实现通信

至此,整个Demo就实现了,其实核心部分就是要搞清楚Lua程序与C是如何交互的,以及用户的Lua脚本字符串是如何输入并一步步传递至Lua环境。当然,站在产品的角度上,这样操作还有一些问题,如果模块重新上电,Lua脚本就会消失。我这里提供两种解决思路:第一,可以在单片机中运行文件系统,将网页提交的Lua脚本以.lua文件的形式存储起来;第二,实际的用户拿到模块后一般是需要一个MCU去控制的,可以将Lua脚本放在用户MCU中,这样就不需要用网页输入Lua脚本了。不知小编的理解是否合理,还请诸位看客指正,谢过!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning Python, 5th Edition

Learning Python, 5th Edition

Mark Lutz / O'Reilly Media / 2013-7-6 / USD 64.99

If you want to write efficient, high-quality code that's easily integrated with other languages and tools, this hands-on book will help you be productive with Python quickly. Learning Python, Fifth Ed......一起来看看 《Learning Python, 5th Edition》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具