1. 引言
物联网(Internet of Things, IoT)概念广为普及,嵌入式设备联网是大势所趋。RT-Thread其实就是一个实时支持多任务的物联网系统。物联网操作系统是以操作系统内核为基础,包括文件系统和图形库等较为完整的中间组件,具备低功耗、安全、通信协议支持和云端接入的软件平台。
本文以基于CORTEX-M3内核的STM32F103VET6为例,通过SPI总线驱动W5500,搭载RT-Thread操作系统,详细介绍如何在RT-Thread Studio集成开发环境中加载W5500驱动,编写应用进行TCP/IP通信测试。并介绍了RT-Thread中关于W5500的以太网驱动存在一个关键的问题:处理连续大量的数据时驱动采用的驱动方式是利用硬件的下降沿来驱动,而W5500在遇到连续的事件来不及处理时中断引脚输出持续的低电平,造成STM32处理器无法对新的事件进行处理,从而造成网络的假死状态。本文分析出原因并在接受任务时新建定时任务来监听引脚电平,在持续的低电平后会模拟中断事件来发送任务信号量,以解决连续数据引起的网络假死现象。
2. W5500介绍与硬件连接
2.1. W5500硬件介绍
W5500是一款全硬件TCP/IP嵌入式以太网控制器,为嵌设计与研发入式系统提供了更加方便的互联网连接方案 [1]。内部框架图如下图1。
W5500提供了一套标准的4线制SPI接口,接口最大通信速度可达80M,本系统主机选用STM32F103VET6作为SPI主机,SPI接口速率最大18M。可以直接连接W5500进行通信。W5500外围电路比较简单,除了需要提供一个25M的时钟外,一个标准的网络接口转换器,其它只需要少许电阻电容即可工作。与stm32的连接如下图2所示。
2.2. 在RT-Thread Studio中使能W5500及配置相关接口
W5500提供标准的4线制通信接口,加上中断和复位引脚总共占用6个IO口。W5500采用SPI接口通信。SPI系统可以直接与各个厂家生产的多种外围器件直接连接,工作在主从模式 [2]。SCLK提供时钟脉冲,MOSI和MISO和SCLK的脉冲下进行数据传输。SPI通信在主机和从机之间分别提供2个方向的数据线,因此可以进行全双工的数据传输。CPOL和CPHA代表不同的时钟极性,可以为0或者是1,因此提供4种时钟极性。W5500的SPI接口通信支持模式0与模式3,数据都是在上升沿锁存,下降沿输出。如下图3所示。
![](//html.hanspub.org/file/8-2690522x9_hanspub.png)
Figure 1. W5500 internal frame diagram
图1. W5500内部框架图
![](//html.hanspub.org/file/8-2690522x11_hanspub.png)
Figure 2. The interface circuit between W5500 and STM32
图2. W5500与STM32的接口电路
![](//html.hanspub.org/file/8-2690522x12_hanspub.png)
Figure 3. W5500 SPI communication mode
图3. W5500的SPI通信方式
2.2.1. RT Thread Studio简介
RT Thread Studio是一站式的RT-Thread开发工具,通过简单的图形化配置系统及丰富的软件包组件资源,让物联开发变得简单。它集成了工作创建与管理、代码编辑、SDK管理、RT-Thread配置、构建配置、调试配置、程序下载和调试功能 [3]。而且它还支持直接导入常用的KEIL工程和IAR FOR ARM工程,最新版本还支持STMCUBEMX,方便一站式配置ST旗下的设备,使之前独立的嵌入式设备快速接入互联网 [4]。
2.2.2. W5500的IO配置
在RT-Thread Studio中使能W5500,只需要在RT-Thread Settings中添加,图形化的界面使对应的配置比较简单。但是RT-Thread提供的引脚编号需要和芯片的引脚号区分开来,它们是不同的概念,引脚编号由PIN设备驱动的程序定义,不同的STM32型号的IO口对应不同的编号。我们可以使用函数GET_PIN (port, pin)来获取我们需要的引脚编号。W500除SPI的4根通信总线,还需要W5500_RST与W5500_INT引脚,它们分别对应W500的复位脚与中断脚,作用十分重要。由图1~2所示它们分别占用STM32的PD8与PD9脚,所以我们利用宏定义来配置引脚:#define WIZ_RST_PIN GET_PIN(D, 9);#define WIZ_IRQ_PIN GET_PIN (D, 8)。我们也可以在drv_gpio.c中找到对应PORTD脚的序号,图4为RT_Thread中GPIO的引脚定义。
由图4可知PD8与PD9分别对就的编号是56、57号,找到对应引脚之后我们就可以在RT Thread Studio中使能对应的功能。
![](//html.hanspub.org/file/8-2690522x13_hanspub.png)
Figure 4. PORTD pin definition in RT-Thread
图4. RT-Thread中PORTD引脚定义
2.2.3. SPI配置
RT_Thread使用虚拟的SPI总线来描述挂载在SPI上的不同设备。例如SPI10代表SPI1总线上的0号设备,由于我们使用的STM32F103VET6的SPI2功能,并且SPI2总线上只有一个设备,因此在配置W5500时我们在spi device name一栏填入spi20即可。且我们打开W5500的DHCP功能和DNS功能,以便可以动态获取IP地址与进行域名解析。最终配置完成如下图5所示。
![](//html.hanspub.org/file/8-2690522x14_hanspub.png)
Figure 5. RT-Thread Studio W5500 configuration
图5. RT-Thread Studio W5500配置
2.3. 初始化SPI并挂载SPI2总线
STM32F103VET的SPI2来和W5500需要使用通信,在W5500的初始化时首先会进行SPI的挂载,所以我们需要在RT-Thread Studio中初始化STM32F103VET6的SPI2口,集成环境中已经集成了STM32CubeMX,开发人员也可以STM32CubeMX对芯片进行相应的设置生成stm32f1xx_hal_msp.c文件,里面有对系统的一些初始化函数,例如对SPI的初始化函数:void HAL_SPI_MspInit (SPI_HandleTypeDef* hspi);在W5500调用SPI2进行初始化时,会自动找到该函数进行初始化。
在外面SPI和引脚配置之后,我们又加载了SPI2的初始化函数,进行下载和调试后通过串口打印出的信息仍提示:You should attach spi20 into SPI bus firstly。说明SPI2总线没有被正常的挂载,系统并没有在配置在完W5500自己挂载SPI2总线,所以我们设计了把SPI2的总线挂载子程序:
![](//html.hanspub.org/file/8-2690522x15_hanspub.png)
子程序对SPI总线中CS引脚做了初始化操作之后,通过INIT_DEVICE_EXPORT(w5500_spi_attach)把该函数挂载到RT-Thread的系统初始化链表中,再进行下载和调试发现W5500工作正常,连接串口提示:[I/wiz] RT-Thread WIZnet package (V2.0.0) initialize success。初始化完成之后我们需要编写应用来测试通信。
3. SAL层系统标准接口
在编写应用测试通信之前,我们要了解在编写应用的一些函数。W5500使用SAL函数进行应用层的编写,SAL层的应用大大提高了网络应用的兼容性问题,降低了嵌入式网络设备的开发难度与周期。提供Socket层面TLS加密传输特性,更好的保护嵌入式网络的数据安全,防止数据泄露。
3.1. SAL层网络框架
如图6的框架示意图,最顶层的为网络应用层,提供一套标准BSD Socket api接口函数,如常用的socket、connect等函数。往下一层即为SAL套接字抽象层,通过它RT-Thread系统能够适配下层不同的网络协议栈,并提供给上层统一的网络编程接口,方便不同的网络协议栈接入。SAL层为上层应用提供accept、send、recv等函数。
再往下则为netdev网上层,解决多网卡情况设备网络连接和网络管理相关问题。通过网卡层方便用户统一管理各个网卡信息及网络连接状态,并且可调用统一的网上调试命令接口。
第四层即为协议栈层,该层包括了常用的TCP/IP协议栈,例如轻量级的Lwip协议栈,以及用作AT指令进行相关操作的AT Socket网络功能协议栈,再有就是W5500上用的wiznet网络协议栈,这些协议栈能直接和硬件进行接触,完成从网络层到传输层的数据转化。
统一标准的的RT-Thread网络接口BSD Socket API,让不同的网络硬件对应用层变得透明,同样的应用不同的硬件只需要对硬件层做一些改动即可完成硬件升级。
3.2. 在RT-Thread Studio中打开SAL
使能SAL功能方法列举以下两种,1、只需要在rtconfig.h文件中添加
![](//html.hanspub.org/file/8-2690522x17_hanspub.png)
1、rt-thread studio提供了图形化配置界面,在rt-thread studio中使能只需要首先点开工程中的RT-Thread Settings,找到套接字抽象层,点击使能套接字抽象层即可(如图7):
3.3. SAL层BSD Socket API接口函数
下面我们列举几个需要在应用中用到的API接口函数。
3.3.1. 连接初始化和关闭函数
· 创建套接字:int socket (int domain,int type,int protocol)。此函数调用成功后会返回对应的int型套接字描述符。如果小于0则说明没有空闲的socket。domain参数为对应的协议类型,目前支持IPV4的AF_INET和IPV6的AF_INET6。第二个参数为协议类型,分别支持SOCK_STREAM(流套接字)、SOCK_DGRAM(数据报套接字)、SOCK_RAW(原始套接字)。
· 绑定套接字:int bind (int s, const struct sockaddr *name, socklen_t namelen)此函数的返回值小于0则说明绑定失败,其它情况都说明绑定成功。第1个参数s为socket的描述符。第2个参数为sockaddr的结构体指针name。第3个参数为代表sockaddr的结构体长度。
· 关闭套接字函数:int closesocket(int s)。此函数仅有一个参数,即为套接字描述符。用于关闭指定的socket连接。
3.3.2. 设备端连接函数
连接函数:int connect (int s, const struct sockaddr *name, socklen_t namelen)用于主动建立一个socket连接。当设备工作在TCP客户端模式和UDP模式时,需要先执行此函数进行连接。
3.3.3. UDP通信函数
UDP为无连接的通信函数,和TCP通信不同,在通信前不会主动建立连接,所以数据在发送时需要把指定目的地址,接收数据时获取源地址。
UDP发送函数:int sendto (int s, const void *dataptr, size_t size, int flags, const struct sockaddr *to, socklen_t tolen);UDP通信为无连接的通信,在发送数据前无需提前建立连接。函数的第一个参数s为socket描述符。第二个参数dataprt和第三个参数size分别是要发送的数据指针与长度。flags通常为0。to为目标地址的sock结构体指针。最后一个参数tolen为目标地址to的指针长度。
UDP接收函数:int recvfrom (int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);第一个参数s为socket描述符。第二个参数mem和len分别为接收指针和接收长度。第四个参数flags通常为0。from为sockaddr的结构体指针。fromlen为接收地址的长度。
UDP接收函数同样会阻塞进程,直到接收到数据进程转为就绪态。
4. 应用层网络能力测试
在应用层编写的时候首先我们要确定目的主机的IP地址和端口,然后我们开始进行一些必要的网络层初始化,然后才能开始进行通信。首先进行应用层的初始化,我们采用UDP进行通信,目的端口选为2000,相关的初始化如下:
4.1. UDP通信初始化
UDP (User Data Protocol,用户数据报协议)是一种无连接的不可靠的通信,因为在传输数据之前源端和终端不建立连接,当它想传送数据时就简单地去抓取数据,并尽可能地把它扔在网络上。因为不需要建立连接和维护连接状态,所以可以很方便的实现一台服务机向多台客户机的广播通信。而且网络开销小,吞吐量仅受应用层生成数据的速率,带宽以及源终端的主机性能的限制。
在rt-thread中初始化UDP的流程:
1) 获取网卡对象对象信息:netdev = netdev_get_by_name (W5500);
2) 申请socket并设定为UDP通信:NetSockFd = socket (AF_INET, SOCK_DGRAM, 0));
3) 初始化客户端信息包括本网卡的IP和通信端口,然后进行绑定,绑定代码:
Bind (NetSockFd, (struct sockaddr *)&client_addr, sizeof (struct sockaddr))
绑定完之后即可进行通信。
4.2. UDP通信测试
我们进行简单的UDP通信,只需要保证PC端和设备在同一个局域网下,就可以进行广播通信,我们已经设定本机通信端口为3000,目的端口为2000。打开网络调试助手,设定通信方式为UDP,目标地址设为255.255.255.255,目标端口设定为3000。进行广播查找设备。此时我们做了一套简单的通信协议,只有符合协议的内容才会被处理并返回信息。广播查找协议内容如下图8。
设置完网络助手并开启定时发送,设备发送和回复都设置为HEX发送与接收,通信界面如下图9,调试助手在收到消息回复时会把目的IP自动设置为接收数据的IP,此时方便在未知设置的IP情况下来获取对方消息:
![](//html.hanspub.org/file/8-2690522x19_hanspub.png)
Figure 8. Application layer test protocol
图8. 应用层测试协议
4.3. 问题发现、分析及优化
4.3.1. 问题发现
当我们在客户端设置自动发送时长为10 ms,在长时间的大量数据通信时,W5500会出现网络死机现象,数据发送正常,但是收不到设备端(W5500)的回复。在进行多次测试后发现这个问题的会多次重现。在使用进行万用表对相关引脚进行测试之后发现问题。
4.3.2. 问题分析
总结问题原因:RT-Thread中W5500接收数据过程如下图10所示。
如图10所示,W5500在收到数据或者发送成功后会产生中断,STM32F103VET6是根据W5500产生的下降沿中断来发送信号量。并来执行W5500的数据处理任务。但是当W5500的数据过多,而STM32F103VET6来不及处理时W5500的INT脚一直输出低电平,造成STM32无法处理新的数据,原因是STM32F103VET6的中断边沿触发只支持上升沿和下降沿中断。所以一旦W5500的接收发送或者其他事件产生中断时来不及处理时就会产生持续的低电平,造成STM32F103VET6无法处理新的数据而产生网络假死状态。
4.3.3. 问题优化
根据问题原因,我们添加中断监控信号量,并添加中断监控任务,一旦设备接收到新的数据时首先会进行中断,然后执行数据处理任务,之后会清除对应的中断,中断引脚会恢复高电平。我们在数据处理后引脚变高时向中断监控任务发送一个信号量。中断监控任务收到信号量检测W5500中断引脚电平,如果还为低电平,则会假装中断任务向W5500的数据处理任务发送一个信号量,让数据处理任务来进行一次处理并清中断的动作。就可以解决因为W5500的中断脚持续输出低电平而造成的网络假死状态。监控任务的信号发送在WIZ.C中的static void wiz_data_thread_entry (void *parameter)中添加:rt_sem_release (Sem_NetNoDie);//释放信号量,告诉监控任务进入了一次中断。监控服务程序不再添加。本实验需要通过长期运行来验证结果,笔者已经验证成功,具体测试方式如章节3.2中所示,只需要在软件界面勾选上循环发送,即可通过长时间的发送和响应来验证结果。具体程序处理流程如下图11。
![](//html.hanspub.org/file/8-2690522x22_hanspub.png)
Figure 11. W5500 receiving optimization flowchart
图11. W5500接收优化流程图
5. 结语
通过程序连续工作验证,对RT-Thread操作系统下W5500的优化解决了驱动本身的一些漏洞问题。RT-Thread的移植和应用都科学合理,移植方便。在物联网发展迅速的今天,本文为开发RT-Thread下相关嵌入式设备提供一种方案,对潜在的问题分析优化,有助于用本方案开发嵌入式设备的人员少走一些弯路,更好的推动RT-Thread的发展。