基于ZYNQ的webserver设计与实现
前言
两年前,忽然心血来潮想在ZYNQ中用裸机实现1个简易的webserver,可以通过网页的形式对ZYNQ实现通信和控制。正好偶然接触了老外的1款激光雷达的web控制页面,于是闲暇之余一不做二不休照着样子模仿做了1个web界面,并在ZYNQ中设计了对应的裸机webserver程序。最近突然翻到了当时写的设计文档,决定分享出来。由于个人水平有限,成果不具备实用价值,仅作为技术爱好交流分享。
1 概述
设计一组网页文件,将该组网页文件加载到zynq的内存中,电脑端通过浏览器访问zynq所对应的IP地址,并通过http协议获取并打开该组网页文件,从而通过网页可以实现对zynq内部程序的通信与控制。整个过程实现了一个基于zynq的嵌入式精简版webserver。
要实现浏览器对于zynq端webserver的访问控制,需要完成两个工作。其一,在zynq中设计一个基于http协议的webserver;其二,设计一组被浏览器所使用并能够与zynq中webserver实现通信的网页文件。
本文基于Xilinx XAPP1026中的webserver DEMO的结构所设计。
2 网页设计
为了实现网页与webserver的通信,需要设计的网页文件包括:
- HTML 文件,直接被浏览器打开,定义了网页的内容;
- JavaScript 文件,被HTML文件所引用,定义了网页的行为;
- CSS 文件,被HTML文件所引用,定义了网页的布局;
- 其他文件,图片等在网页中所出现的元素,被HTML文件所引用。
设计的网页文件如下图所示:
其中,index.html为网页文件。main.js为index.html引用的主JavaScript 文件,jquery-3.3.1.min.js为main.js所引用的第三方JavaScript库。main.css为index.html所引用的CSS文件。favicon.ico为网页的收藏夹图标,显示在网页的左上角。如下图所示。
logo.png是网页中所需要显示的一幅图片。
所设计的网页文件打开效果如下图所示。
其中,网页上所呈现的所有内容都通过HTML文件进行定义,包括图片、文字、表单(图中所有方框)、按钮等。表单输入参数的使用、数据的动态显示、每个按钮所实现的功能,都通过JavaScript文件进行定义。网页中文字的字体、行距、表单方框大小和颜色、按钮的按下效果都在CSS文件中进行定义。
需要说明的是,JavaScript和CSS文件中的内容可以直接插入到HTML文件中,可以起到完全相同的效果,因而这两种文件不是必需的。其中JavaScript文件中的函数等可以插入到HTML文件的<script > </script>段中,CSS文件内容可以插入到<style> </ style >段中。但这样会使整个HTML文件过于庞大且不便于解读。因此,在实际应用中推荐单独设计JavaScript和CSS文件,作为HTML文件的引用即可。
2.1 HTML文件
index.html文件中主要包含了图片、文字、表单、按钮。html文件采用了HTML5标准。
2.1.1 图片
网页中包含了2个图片元素。其中,网页收藏夹和标题图标favicon.ico,分辨率为32×32。用户可以通过任意自己喜欢的图片制作相应的ico文件,有一款在线制作工具:http://www.faviconico.org/favicon,可以将用户上传的图片转换为ico文件下载。在html文件中设置favicon.ico的方式为:
另一个为网页中显示的图片logo.png,在html文件中设置favicon.ico的方式为:
2.1.2 文字
网页中的标题文字:
html中的设置方式:
表单中的文字:
html中的设置方式:
其中 代表1个空格。
2.1.3 表单
2.1.3.1 只读表单
网页中表单的主要作用为提供用户输入参数,或者给用户提供参数显示。
参数信息显示表单,不可输入,只提供显示。如下:
表单类型均为文本信息text,readonly表示只读,size用于设置方框的长度。用户在网页中点击相应方框无法输入任何数据。
2.1.3.2 单选按钮
两个单选按钮On和Off类型为“radio”,name必须一致,checked代表打开网页默认选中的一项。
后面的Set按钮,用于通过网页向zynq的webserver发送设置参数:
类型为“button”,onclick定义了按钮被按下所要调用的函数为param_set,该函数在main.js文件中定义。
2.1.3.3 下拉菜单
其中middle通过selected设置为默认选项。后面的Set按钮,用于通过网页向zynq的webserver发送设置参数,按下后同样调用param_set函数。
2.1.3.4 数字输入
类型为“number”,该类型的表单只能输入数字,其他字符无法输入。min和max分别表示允许输入的最小值和最大值,step表示每次增大或减小的值,required表示输入不能为空。
后面的“+”按钮表示增大输入的数值,每次点击都会调用data_increase函数,“-”按钮表示减小输入的数值,每次点击都会调用data_decrease函数。这两个函数同样在main.js文件中定义。Set按钮的功能同上。
2.1.3.5 文本输入
类型为“text”;onkeyup是指当用户通过键盘每按下1个键释放时都会调用函数ipinput_validate,在;oninput是指每次输入文字到表单时都会调用value=value.replace(/[^\d]/g,''),这句代码的含义是只允许输入0~9的数字,其他字符将无法输入(输入时被替换为空)。maxlength是指允许输入的最大字符个数。style中制定了输入框的宽度。
后面的Set按钮,每次被点击时都会调用trans_param_set函数。它和ipinput_validate都在main.js文件中定义。
2.1.4 按钮
Set、+、-按钮都在上文中作过描述。按钮被按下所调用的函数通过onclick来设置。
2.1.5 文件引用
引用外部文件需要指定文件路径。
2.1.5.1 图片文件
2.1.5.2 css文件
2.1.5.2 javascript文件
2.2 javascript文件
javascript文件中定义了html文件所需要实现的功能,例如网页初始化,按钮点击调用的函数,参数输入验证等。在设计时调用了第三方库jQuery,通过该库可以较为方便的进行网页功能设计开发,并且有助于较大程度上缩减代码量。
jQuery官网:https://jquery.com/
jQuery下载:https://jquery.com/download/
有两个版本的 jQuery 可供下载:
- 压缩的Production version - 用于实际的网站中,已被精简和压缩,如jquery-3.3.1.min.js
- 未压缩的Development version - 用于测试和开发(未压缩,是可读的代码),如jquery-3.3.1. js
设计中使用了最新版本的jQuery 3.3.1。使用的库文件为jquery-3.3.1.min.js。(xapp1026中使用了第三方库yui)
2.2.1 功能描述
main.js文件中定义了所有html文件所引用的函数。整个网页的工作流程如下,浏览器通过GET请求从webserver获取所需的网页文件,当所有网页文件在浏览器中加载完成后。网页向webserver依次发送POST请求获取设备信息、设备默认参数配置、设备当前状态信息。随后以1s的周期发送POST请求不断获取最新的设备状态信息。用户可以在网页中对设备参数进行设置,并通过点击Set按钮将参数以URL的形式随POST请求发送至webserver中。
2.2.2 函数列表
2.2.2.1 jQuery入口函数
jQuery 的入口函数是在 html 文件所有标签(DOM)都加载之后,就会自动执行。定义方式如下:
在入口函数中调用网页初始化函数web_init。
2.2.2.2 web_init函数
web_init函数主要功能是在网页文件html加载完之后,通过网页向zynq中的webserver发送POST请求,获取设备的信息和默认参数设置,并启动一个定时器周期性发送获取设备状态信息的POST请求。
获取设备信息的代码如下:
命令以URL的方式发送,为cmd_get/info?
设备信息显示在网页如下部分:
获取默认参数设置的代码如下:
命令以URL的方式发送,为cmd_get/config?
默认参数设置在网页如下部分显示:
设置定时器的代码如下:
设置1个周期为1000ms的定时器,每1000ms调用一次update_state函数来获取设备当前的一系列状态信息。
网页发送POST请求都通过了jQuery中的函数$.post实现,具体可查阅jQuery网站。函数定义如下:
zynq中的webserver向网页返回的设备信息、参数配置数据均为json格式。定义如下:
数据均为字符串。
2.2.2.3 update_state函数
update_state函数在被web_init中所设置的定时器周期性调用,向zynq中的webserver发送POST请求以获得设备当前的状态信息。命令以URL的方式发送,为cmd_get/state?
代码如下:
状态信息在网页如下部分显示:
zynq中的webserver向网页返回的设备信息、参数配置数据均为json格式。定义如下:
2.2.2.4 data_increase函数
html文件中“+”按钮每次按下所调用的函数为data_increase函数,函数对输入数据进行合法性判断,判断输入是否为空,是否超过所设定的最大值等。并将用户输入数字以html中表单对应的step的值进行递增。
2.2.2.5 data_decrease函数
html文件中“-”按钮每次按下所调用的函数为data_increase函数,函数对输入数据进行合法性判断,判断输入是否为空,是否超过所设定的最小值等。并将用户输入数字以html中表单对应的step的值进行递减。
2.2.2.6 param_set函数
以下参数后的Set按钮被按下时将调用param_set函数。
将用户输入的number、radio、select类型的参数设置进行合法性判断,并以POST请求的方式发送至zynq中的webserver,并可以对多个参数设置进行组合发送。
命令以URL的方式发送,发送的命令组合规则为:
cmd_set/参数1名称=参数值1&参数2名称=参数值2……?
不同参数之间通过&组合,参数设置以?结尾。
2.2.2.7 trans_param_set函数
用于对如下3个参数的设置发送POST请求。Set按钮被按下时调用trans_param_set函数。
发送的命令组合规则同上。
2.2.2.8 ip_param_set函数
用于对如下4个参数的设置发送POST请求。Set按钮被按下时调用ip_param_set函数。
发送的命令组合规则同上。
2.2.2.9 ipinput_validate函数
ipinput_validate对如下参数的合法性进行判断,防止用户输入不合法的ip地址配置。在相应参数的表单的onkeyup中被引用。用户每次通过按键输入1个数字后都会调用该函数。
限制输入数字范围为0~255,对于超过255的输入进行错误提示,并清空当前输入。并且会自动去掉数字之前无效的0。代码如下:
2.2.2.10 portinput_validate函数
portinput _validate对如下参数的合法性进行判断,防止用户输入不合法的端口号配置。在相应参数的表单的onkeyup中被引用。用户每次通过按键输入1个数字后都会调用该函数。
限制输入数字范围为0~65535,对于超过65535的输入进行错误提示,并清空当前输入。并且会自动去掉数字之前无效的0。代码如下:
2.3 CSS文件
CSS文件定义了网页的布局。为了兼容不同的浏览器,需要在CSS文件中指定一些网页格式参数。原因在于,若不指定某些格式相关的参数,不同的浏览器将使用自身的默认配置,而不同浏览器间的默认配置参数可能不完全一致,因而会导致通过不同浏览器打开的网页产生一定差异。main.css文件定义了如下内容:
网页字体:
去掉input类型为number时输入框右侧的上下箭头:
定义表单之间的行距:
定义input类型为number时输入框的宽和高,对齐方式以及边框的类型和颜色:
定义input类型为number时输入框的高度,对齐方式以及边框的类型和颜色:
定义Set按钮未被按下时的颜色:
定义Set按钮被按下时的颜色:
webserver基于xapp1026修改,需要使用xilinx的文件系统库xilmfs以及网络协议栈lwip库。在应用中,网页文件都需要存储在webserver端。浏览器通过IP地址访问webserver,通过http的GET请求获取所有的网页文件,并显示。在zynq中,所有的网页文件都通过MFS文件系统进行存储和管理,HTTP协议基于lwip的TCP函数进行设计。
3.1 xilmfs库设置
MFS是Xilinx自己设计的一套文件系统。在使用时,网页文件被加载到DDR3内存中,存储的起始地址即文件系统的起始地址base_address设为0x7200000。由于文件系统在初始化时所有的网页文件就已经被提前加载到起始地址为0x7200000的内存中,所以init_type选择MFS Image。need_utils对于应用没有影响,可以随意选择。文件系统的大小可以根据实际情况进行设置,这里使用0xA00000(10MB)。
3.2 lwip库设置
详见程序。
程序以xapp1026中的webserver部分为基础进行修改而成。
网页与webserver之间存在设备信息、设备参数配置、设备状态信息3种数据交换,webserver将这些数据以JSON格式作为网页POST请求的相应,发送至网页端。3中数据定义如下。设备信息为info,设备参数配置为config,设备状态信息为state。
get_cmd_type函数对网页发送的POST请求中URL里的命令进行解析。判断命令类型是get还是set类型,get表示设备信息、设备参数配置、设备状态信息3种数据的获取,set表示网页发送了新的设备参数配置。
当命令为get类型时,将相应的数据以JSON格式发送。将数据组合为JSON格式的代码如下:
当命令为set类型时,提取出命令设置字符串。主要实现代码如下:
然后,在do_http_post函数中对提取出的参数设置字符串进行解析,获取设置的参数名和参数值,对相应参数进行设置,设置成功返回字符串“success”,失败则返回字符串“failed=参数名1&参数名2……”。主要实现代码如下:
zynq端的网页文件是通过MFS文件系统进行存储和管理的,因此首先需要将各网页文件载入MFS文件系统内。将所有网页文件合并为.mfs文件便可以通过SDK在debug时载入MFS文件系统对应的DDR内存地址内。具体方法为,在SDK中打开命令窗,如下所示:
网页文件位于.sdk文件夹中的web文件夹中,
输入如下命令制作image.mfs文件。
生成的image.mfs文件位于web文件夹中。
在debug时,需要将mfs文件加载到指定内存地址0x7200000(与xilmfs库base_address设置一致),如下图所示。
将电脑网卡地址设备192.168.1.100,将程序下载到开发板中,随后打开浏览器访问ip地址192.168.1.10。此时浏览器中便出现了网页,对应的框中出现相应数据,为了表现出网页文件每1s读取一次状态信息,RPM和Phase中的数据会不断递增。如下图所示。
SDK打印信息如下:
wireshark抓包结果如下所示:
JSON格式的数据显示如下:
4.3.2 命令发送
如下区域的参数可以由用户进行设置。
依次点击Set按钮,发送POST请求,各命令的wireshark抓包结果如下,通过wireshark可以很直观的在URL中看到命令的格式,包括所需要设置的参数名和参数值。
设置成功的应答如下,可以看到返回数据的为字符串success。
各参数设置时的SDK打印信息:
为了测试命令设置失败的机制,修改程序故意使设置产生失败,修改程序如下:
点击网页中的一个set按钮,网页弹出如下提示:
SDK打印信息如下:
wireshark抓包结果如下所示,可以看到返回的数据为字符串:
failed=network_ip&mask&gateway&dhcp_onoff
以一个参数越界设置警告为例,
数值为100时点击“+”按钮:
数值为0时点击“-”按钮:
输入为空时点击Set按钮:
输入无效ip地址:
点击确定后无效输入自动清空。
输入无效端口号:
点击确定后无效输入自动清空。
获取设备信息失败:
获取设备配置失败:
获取设备状态失败:
5.1 BOOT.bin文件制作
在debug时,可以通过SDK软件将网页mfs文件加载到内存中。当程序固化后,上电启动时如何实现MFS文件系统自动加载呢?其实很简单,将mfs文件作为bin文件的一部分,上电后通过fsbl加载到内存中即可。
将mfs文件作为datafile加入boot.bin文件中,并将Load地址设为0x7200000(与xilmfs库base_address设置一致),如下图所示。
经bin文件烧进flash。
上电启动成功!