LwIP应用开发实战指南:基于STM32(txt+pdf+epub+mobi电子书下载)


发布时间:2020-05-17 16:12:52

点击下载

作者:刘火良,杨森

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

LwIP应用开发实战指南:基于STM32

LwIP应用开发实战指南:基于STM32试读:

前言

如何学习本书

本书围绕LwIP 2.1.2版本源码讲解TCP/IP网络协议栈的基本知识,带领读者进入网络的世界。无论你是学生、嵌入式开发者还是物联网开发者,都可以从本书中学习到网络的相关知识,了解网络协议栈的处理思想。

本书将深入分析网络协议栈的原理与实现过程,涉及ARP、IP、ICMP、TCP、UDP、HTTP、MQTT等协议,还将深入讲解LwIP中内存管理、pbuf数据包、网卡接口管理的原理与实现,并详细介绍LwIP的移植过程,读者可以将其移植到无操作系统/有操作系统的环境中使用。

除此之外,本书还通过理论和实践相结合,使用LwIP接入当前较大的几个云平台,如阿里云、百度云、OneNET,以使读者熟练掌握接入方法。在本书中,读者可以熟练掌握LwIP中Netconn API与Socket API的使用方式,并且掌握多种网络调试工具,如Wireshark、Postman以及MQTT.fx等的使用方法。

全书内容循序渐进,不断迭代,建议读者在学习的时候做到两点:一是不能一味看书,要把代码和理论结合起来学习,一边看书,一边调试代码。通过执行每一个程序,检验程序的执行流程和执行效果与自己想的是否一致,不断总结经验;二是在每学完一章之后,必须将配套的例程重写一遍(切记不要复制,即使是一个符号),举一反三,确保真正理解。推荐阅读● LwIP官方源代码●《 STM32库开发实战指南》(已由机械工业出版社出版多个版

本)●《 FreeRTOS内核实现与应用开发实战指南:基于STM32》(已由

机械工业出版社出版,ISBN 978-7-111-61825-6)本书的技术论坛

如果在学习过程中遇到问题,可以到野火电子论坛(www.firebbs.cn)发帖交流,开源共享,共同进步。

鉴于水平有限,本书难免有错漏之处,热心的读者也可把勘误发送到论坛以便我们加以改进。祝你学习愉快,LwIP的世界,野火与你同行!第1章网络协议概述1.1 常用网络协议

互联网为人类社会带来巨大变革,几乎改变了人们生活的方方面面。互联网通信的本质是数字通信,任何数字通信都离不开通信协议,通信设备只有按照约定的、统一的方式去封装和解析信息,才能实现通信。互联网通信所要遵守的众多协议,被统称为TCP/IP。

TCP/IP是一个协议族,包含众多协议。对于网络应用开发人员,可能听到得更多的是其中的应用层协议,比如HTTP(Hyper Text Transfer Protocol,超文本传输协议)、FTP(File Transfer Protocol,文件传输协议)、MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)等。

HTTP的应用最为广泛。比如大家日常使用计算机时的一个常规操作:打开计算机,再打开浏览器,输入网址,最后按下Enter键,这一刻你就开启了HTTP通信。HTTP工作于<客户端-服务端>架构之上(服务端也称为服务器端,除特别说明外,本书出现的“服务端”即为“服务器端”),浏览器作为HTTP客户端,通过URL向HTTP服务端(即Web服务器)发送所有请求。Web服务器根据接收到的请求向客户端发送响应信息。借助这种浏览器和服务器之间的HTTP通信,我们能够足不出户地获得来自世界各地的信息。另外,网页不仅仅是大型服务器的专利,在物联网风潮盛行的今天,许多随处可见的小型设备(如空调、冰箱、插座、路由器等)都内嵌网页,在物理链路畅通的情况下,用户可以用手机、平板电脑上的浏览器随时随地监控这些设备。

FTP是工作在应用层的网络协议,使得主机间可以共享文件,用于在两台设备之间传输文件(双向传输)。它也是一个客户端-服务端框架系统。用户可以通过一个支持FTP的客户端程序,连接到远程主机上的FTP服务端程序,通过客户端程序向服务端程序发出命令,服务端程序执行用户所发出的命令,并将执行的结果返回客户端。FTP除了支持基本的文件上传/下载功能外,还支持目录操作、权限设置、身份验证机制,许多网盘的文件传输功能都是基于FTP实现的。

在物联网发展的初期,物联网场景中的设备使用何种应用层协议进行通信一直是备受争议的话题。很多开发人员习惯了网页开发模式,于是经常选择HTTP作为通信方式。使用HTTP有以下不利因素:HTTP是一种同步协议,设备需要等待服务器的响应才可以进行下一步的工作,然而在设备数量多、网络不可靠的场景下,实现同步通信很困难;HTTP是单向的,设备只能主动向服务器发出数据,无法被动接收来自网络的数据,这不适用于实时控制的场合;HTTP是有许多帧头和规则的重量级协议,在设备中实现需要耗费大量的系统资源。基于上述形势,MQTT和COAP等轻量级、异步的通信协议便得到了物联网设备开发商的青睐,尤其是MQTT。

MQTT协议是IBM公司于1990年设计并推出的一款通信协议,于2014年正式成为一个OASIS开放标准。近年来,MQTT的应用呈现爆炸性的增长势头,大有一统物联网的趋势。另外,MQTT在物联网以外的其他领域也得到了广泛的应用,比如许多公司在制作手机APP时,会使用MQTT来实现消息推送、即时聊天等功能。

嵌入式设备接入互联网的需求越来越大,有以下几点原因:

1)近些年,各种带网络接入功能的MCU、SoC层出不穷,开源轻量的TCP/IP协议栈日趋成熟和完善,云平台的市场越来越繁荣,这些因素大大降低了嵌入式设备的入网成本,也为许多资源受限的低端设备接入互联网提供了可能。

2)“物联网+”的风潮日渐盛行,设备能够被远程监控,这一点已经成为许多产品的技术要求。

3)人们对于设备“智能性”的需求越来越高,当今热门的大数据、图像处理、语音识别、机器学习等功能都可以集成在云端,成为云平台能提供的服务。终端设备大多是计算、存储能力有限的设备,这些设备如果想要获取“智能”,最便捷的办法就是接入云平台,利用各项云服务。

互联网的基础就是TCP/IP。TCP/IP是一个非常复杂的协议族,即使我们能把它的设计思想和实现原理都解释清楚,你也不一定有时间和精力去学习它,所以本书的写作重点不在于对TCP/IP的解读,而在于对它的应用。另外,TCP/IP的复杂性也决定了它使用起来没那么简单,即使我们只关注应用开发,也依然需要对它的许多概念和设计思想有所了解,才能编写出正确、高效、健壮的应用程序。

希望能借此书,让嵌入式开发工程师以浓厚的兴趣和清晰的视野,搭上物联网发展的快车。1.2 网络协议的分层模型

TCP/IP是众多网络协议的集合,包括ARP、IP、ICMP、UDP、TCP、DNS、HTTP、FTP、MQTT等。这些协议按照功能,可以划分为几个不同的层次,如图1-1所示。我们在1.1节中介绍的HTTP、FTP、MQTT隶属于应用层。那么TCP/IP为什么需要分层,分层的依据又是什么呢?图1-1 TCP/IP的分层

TCP/IP协议栈中不同协议所完成的功能是不一样的,某些协议的实现要依赖于其他协议,依据这种依赖关系,可以将协议栈分层。在图1-1中,低层协议为相邻的上层协议提供服务,是上层协议得以实现的基础。

其中,物理层(PHY)规定了传输信号所需要的物理电平、介质特征。数据链路层(MAC)规定了数据帧能被网卡接收的条件,最常见的方式是利用网卡的MAC地址,发送方会在欲发送的数据帧的首部加上接收方网卡的MAC地址信息,接收方只有监听到属于自己的MAC地址信息后,才会去接收并处理该数据。每台网络设备都应该有自己的网络地址,网络层规定了主机的网络地址该如何定义,以及如何在网络地址和MAC地址之间进行映射,即ARP,还实现了数据包在主机之间的传递,而一台主机内部可能运行着多个网络程序。传输层可以区分数据包是属于哪一个应用程序的,可以说传输层实现了数据包端到端的传递。另外,数据包在传输过程中可能会出现丢包、乱序和重复的现象,网络层并没有提供应对这些错误的机制,而传输层可以解决这些问题,如TCP。应用层以下的各层完成了数据的传递,应用层则决定了你如何应用和处理这些数据。之所以会有许多应用层协议,是因为互联网中传递的数据种类很多、差异很大、应用场景十分多样。1.3 协议层报文间的封装与拆封

本书的后面章节会对TCP/IP协议栈中的每层协议进行分析和讲解。在这里,我们以图1-2简单解释一下在数据的发送和接收过程中,TCP/IP都做了什么。图1-2 TCP/IP协议栈各层的报文封装与拆封

当用户发送数据时,将数据向下交给传输层,这是应用层中的操作,应用层可以通过调用传输层的接口来编写特定的应用程序。而TCP/IP一般也会包含一些简单的应用程序,如Telnet远程登录、FTP文件传输、SMTP邮件传输等。传输层会在数据前面加上传输层首部(此处以TCP为例,图1-2所示的传输层首部为TCP首部,也可以是UDP首部),然后向下交给网络层。同样地,网络层会在数据前面加上网络层首部(IP首部),然后将数据向下交给数据链路层,数据链路层会对数据进行最后一次封装,即在数据前面加上数据链路层首部(此处使用以太网接口为例),然后将数据交给网卡。最后,网卡将数据转换成物理链路上的电平信号,数据就这样被发送到了网络中。数据的发送过程可以概括为TCP/IP的各层协议对数据进行封装的过程,如图1-2所示。

当设备的网卡接收到某个数据包后,它会将其放置在网卡的接收缓存中,并告知TCP/IP内核,然后TCP/IP内核就开始工作了,它会将数据包从接收缓存中取出,并逐层解析数据包中的协议首部信息,并最终将数据交给某个应用程序。数据的接收过程与发送过程正好相反,可以概括为TCP/IP的各层协议对数据进行解析的过程。第2章LwIP概述2.1 LwIP的优缺点

LwIP(Light weight IP)是轻量化的TCP/IP,是瑞典计算机科学院(SICS)的Adam Dunkels开发的一个小型开源的TCP/IP协议栈。设计LwIP的初衷是用少量的资源消耗实现一个较为完整的TCP/IP协议栈,其中“完整”主要是指TCP的完整性,实现的重点是,在保持TCP主要功能的基础上减少对RAM的占用。此外LwIP既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。[1]

本书以LwIP 2.1.2为主要对象进行讲解,后文中出现的LwIP如果没有特殊声明,均指2.1.2版本。编写本书时,LwIP 2.1.2为最新版本,可能当你读到本书时,LwIP又被更新了,对于学习而言,不必纠结于是否必须用最新的版本,因为2.1.2版本和它后面的版本在移植和应用方法上并不会有太大区别。

LwIP的主要特性:

1)支持ARP(地址解析协议)。

2)支持ICMP(因特网控制报文协议),用于网络的调试与维护。

3)支持IGMP(互联网组管理协议),可以实现多播数据的接收。

4)支持UDP(用户数据报协议)。

5)支持TCP(传输控制协议),包括阻塞控制、RTT估算、快速恢复和快速转发。

6)支持PPP(点对点通信协议),支持PPPoE。

7)支持DNS(域名解析)。

8)支持DHCP,动态分配IP地址。

9)支持IP,包括IPv4、IPv6,支持IP分片与重装功能以及多网络接口下的数据包转发。

10)支持SNMP(简单网络管理协议)。

11)支持AUTOIP,自动配置IP地址。

12)提供专门的内部回调接口(Raw API),用于提高应用程序性能。

13)提供可选择的Socket API、Netconn API(在多线程情况下使用)。

在嵌入式中使用LwIP具有以下优点:

1)资源开销低,即轻量化。LwIP内核有自己的内存管理策略和数据包管理策略,使得内核处理数据包的效率很高。另外,LwIP高度可剪裁,一切不需要的功能都可以通过宏编译选项去掉。LwIP的流畅运行需要40KB的代码ROM和几十千字节的RAM,这让它非常适合用在内存资源受限的嵌入式设备中。

2)支持的协议较为完整。几乎支持TCP/IP中所有常见的协议,这在嵌入式设备中早已够用。

3)实现了一些常见的应用程序:DHCP客户端、DNS客户端、HTTP服务器、MQTT客户端、TFTP服务器、SNTP客户端等。[2]

4)同时提供了3种编程接口:RAW API、Netconn API和Socket API。这3种API的执行效率、易用性、可移植性以及空间的开销各不相同,用户可以根据实际需要,平衡利弊,选择合适的API进行网络应用程序的开发。

5)高度可移植。其源代码全部用C实现,用户可以很方便地实现跨处理器、跨编译器的移植。另外,它对内核中会使用到操作系统功能的地方进行了抽象,使用了一套自定义的API,用户可以自己实现这些API,从而实现跨操作系统的移植工作。

6)开源、免费。

7)相比于嵌入式领域其他的TCP/IP协议栈,比如μC-TCP/IP、FreeRTOS-TCP等,LwIP的发展历史要更悠久一些,得到了更多的验证和测试。LwIP被广泛用在嵌入式网络设备中,国内一些物联网公司推出的物联网操作系统,其TCP/IP核心就是LwIP;物联网领域知名的WiFi模块ESP8266,其TCP/IP固件使用的就是LwIP。

尽管LwIP有如此多的优点,但它毕竟是为嵌入式而生的,所以并没有很完整地实现TCP/IP协议栈。相比于Linux和Windows系统自带的TCP/IP协议栈,LwIP的功能不算完整和强大,但对于大多数物联网领域的网络应用程序,使用LwIP已经足够了。2.2 LwIP的文件说明2.2.1 获取LwIP源码文件

LwIP的代码已经交由Savannah托管,项目主页是http://savannah.nongnu.org/projects/lwip/。这个主页中简单地介绍了一下LwIP,然后给出了许多链接,你可以通过这些链接去挖掘更多关于LwIP的信息。在这里,我们只关注两点,如图2-1中的方框所示。图2-1 LwIP项目主页截图

单击Project Homepage,会打开一个网页,如图2-2所示。这个网页可以看作LwIP的官方说明文档。我们可以通过这个网页获得关于LwIP的很多信息,包括使用LwIP的注意事项、数据的复制、系统初始化流程、多线程中要注意的问题、优化方法、内核模块的分类介绍、内核数据结构、内核重要全局变量、内核源码文件等。这些内容的专业性比较强,不建议初学时在这方面花费精力,并且里面的很多内容在本书的后续章节中会有所讲解,目前只需要了解即可。图2-2 LwIP官方说明文档

单击Download Area打开的网页如图2-3所示。通过这个网页,我们可以下载LwIP所有版本的源代码包和contrib包。每单击一个红色字体的资源链接,浏览器就会打开一个ftp连接,帮助你下载想要的文件。但是这个页面提供的下载链接在国内一般无法打开。这个网页最下方的黑字内容推荐我们使用另外一个下载页面:http://download-mirror.savannah.gnu.org/releases/。在这个页面下,用户可以下载到所有在Savannah托管的开源软件,但我们只关心LwIP。利用浏览器的搜索功能,按Ctrl+F快捷键可以快速找到lwip目录。在这里为了便于读者下载,我们直接给出最终的下载链接http://download-mirror.savannah.gnu.org/releases/lwip/。图2-3 LwIP的资源下载

可能你会问,什么是contrib包,它与源代码包有什么不同?源代码包中装的主要是LwIP内核的源码文件,而contrib包中装的是移植和应用LwIP的一些demo,即应用示例。contrib包不属于LwIP内核的一部分,里面的很多内容来自开源社区,因此对contrib包的版本管理不像内核源码那样严格和规范,但contrib包也是很有参考价值的。LwIP源码面世越久,开源社区对它的贡献就越大,所以越高版本的contrib包,提供的应用示例就越丰富,越有参考价值。在大版本区别不大的情况下,建议下载最新的contrib包。后文中我们会对contrib包中提供的应用示例进行讲解。另外,还有一些后缀为.sig的文件,这些文件是数字签名,忽略即可。2.2.2 LwIP文件说明

按照2.2.1节的介绍,我们下载两个包:lwip-2.1.2.zip(源码包)和contrib-2.1.0.zip(contrib包)。解压以后会得到两个文件夹,如图2-4所示。图2-4 下载解压后得到的源码包和contrib包

我们先打开lwip-2.1.2文件夹,如图2-5所示。图2-5 源码包的目录

该目录的主要内容为:

1)CHANGELOG文件记录了LwIP在版本升级过程中源代码发生的变化。

2)COPYING文件记录了LwIP这个开源软件的license。一个软件开源,不代表你能无限制地使用它,你需要在使用它的过程中遵守一定的规则,这些规则就是license。大家可以用记事本打开此文件看一看它的内容。开源软件的license有很多种,LwIP的属于BSD License。LwIP的开源程度很高,几乎可以无限制地使用它。

3)FILES文件用于介绍当前目录下的目录信息。

4)README文件对LwIP进行了一个简单的介绍。

5)UPGRADING文件记录了LwIP每个大版本的更新,以及会对用户使用和移植LwIP造成的影响。大版本更新指的是类似1.3.x—1.4.x—2.0.x—2.1.x。小版本更新,比如2.0.1—2.0.2—2.0.3,这个过程只进行了一些bug的修复和性能的改善,不会对用户的使用造成影响。用户只要将原有工程的目录中与LwIP相关的旧版本文件替换成新版本文件,重新编译,就能直接使用。

6)doc文件夹中是关于LwIP的一些文档,可以看作应用和移植LwIP的指南,但是这些文档比较零散,不成体系,此处可略过。

7)test文件夹中是测试LwIP内核性能的源码,将它们和LwIP源码加入工程中一起编译,调用它们提供的函数,可以获得许多与LwIP内核性能有关的指标。只有非常专业的人士才会用到这种内核性能测试功能。

8)src文件夹中就是我们最关心的LwIP源码文件,下面详细讲解。

打开src文件夹,如图2-6所示。图2-6 src目录(LwIP源码文件所在的目录)● api文件夹中是Netconn API和Socket API相关的源文件,只有在

操作系统的环境中才能被编译。● apps文件夹中是应用程序的源文件,包括常见的应用程序,如

httpd、mqtt、tftp、sntp、snmp等。● core文件夹中是LwIP的内核源文件,后续会详细讲解。● include文件夹中是LwIP所有模块对应的头文件。● netif文件夹中是与网卡移植有关的文件,这些文件为移植网卡提

供了模板,可以直接使用。

LwIP内核是由一系列模块组合而成的,这些模块包括:TCP/IP协议栈的各种协议、内存管理模块、数据包管理模块、网卡管理模块、网卡接口模块、基础功能类模块、API模块。每个模块是由相关的几个源文件和头文件组成的,通过头文件对外声明一些函数、宏、数据类型,使得其他模块可以方便地调用此模块的功能。构成每个模块的头文件都被组织在了include目录中,源文件则根据类型被分散地组织在api、apps、core、netif目录中。

接下来,我们详细介绍一下core文件夹,如图2-7所示。图2-7 core目录

这里逐一介绍一下这些源文件的功能。

ipv4文件夹中是与IPv4模块相关的源文件,它们实现了IPv4协议规定的对数据包的各种操作。ipv4文件夹中还包括一些并非属于IP,但会受IP影响的协议源文件,包括DHCP、ARP、ICMP、IGMP。

ipv6文件夹中是与IPv6模块相关的源文件,它们实现了IPv6协议规定的对数据包的各种操作。ipv6文件夹中也包括一些并非属于IP,但会受IP影响的协议源文件,如DHCP、ARP、ICMP、IGMP。

altcp.c、altcp_alloc.c、altcp_tcp.c等文件处于一个抽象层,用于应用层与TCP之间的连接,如TLS等,但此类接口我们并没有过多使用,如果选择使用安全的加密传输,可以配合mbed TLS使用。

def.c文件定义了一些基础类函数,比如主机序和网络序的转换、字符串的查找和比较、整数转换成字符串等,这些函数会被LwIP内核的很多模块所调用。include目录中的def.h文件对外声明了def.c所实现的函数,同时定义了许多宏,能实现一些基础操作,比如取最大值、取最小值、计算数组长度等,这些宏同样也被内核的许多模块调用。我们经常可以看到某个内核的源文件在开始处即有#include "def.h"。

dns.c文件实现了域名解析的功能,有了它,用户就可以在知道服务器域名的情况下,获得该服务器的IP地址。很多时候我们只记得服务器域名而不记得服务器IP地址,例如www.baidu.com就是一个域名,通过DNS功能,我们就可以得到与服务器域名对应的IP地址,这给用户使用带来了很多便利。

inet_chksum.c文件提供了LwIP所需的校验和功能,在IP、UDP、TCP的实现中,需要计算校验和。

init.c文件对LwIP的用户宏配置进行了检查,会将配置错误和不合理的地方通过编译器的#error和#warning功能标出。另外,init.c文件中定义了lwip_init初始化函数,这个函数会依次对LwIP的各个模块进行初始化。

ip.c文件实现了IP相关的函数,但只是封装了ipv4和ipv6文件夹中的函数。

mem.c文件实现了动态内存池管理机制,使得LwIP内核的各个模块可以灵活地申请和释放内存。

memp.c文件实现了静态内存堆管理机制,使得LwIP内核的各个模块可以快速地申请和释放内存。

netif.c文件实现了关于网卡的操作,比如注册/删除网卡、启用/禁用网卡、设置网卡的IP地址等。netif.c与include目录中的netif.h文件共同构成了LwIP的netif模块,它对网卡进行了抽象,使得LwIP内核可以方便地管理多个特性各异的物理网卡。

pbuf.c文件实现了LwIP对网络数据包的各种操作。网络数据包在LwIP内核中以pbuf结构体的形式存在,这提高了LwIP内核对数据包的处理效率,也提高了数据包在各层之间递交的效率。pbuf结构体也是我们使用RAW/Callback API进行网络应用程序开发的关键,第6章中会详细讲解。

raw.c文件实现了一个传输层协议的框架,我们可以在该文件的基础上修改和添加代码,实现自定义的传输层协议。与UDP/TCP一样,它可以与IP层(即网络层)直接进行交互,这类似RAW Socket。在实际应用中,我们常用UDP和TCP作为传输层协议,但有时,底层网络开发人员会认为UDP的可靠性太差,或者TCP虽然可靠性强,但是很耗费时间和内存,他们需要根据实际需求,平衡利弊,定义自己的传输层协议。LwIP的raw模块可以满足他们的需求。

stats.c文件实现了LwIP内核的统计功能,使用户可以实时地查看LwIP内核对网络数据包的处理情况。

sys.c文件和sys.h文件构成了LwIP的sys模块,提供了与临界区相关的操作。

tcp.c、tcp_in.c和tcp_out.c文件实现了TCP,包括对TCP连接的操作、对TCP数据包的输入输出操作和TCP定时器,它们和include目录中名称带tcp的头文件共同构成了LwIP的TCP模块。TCP模块的实现是LwIP的最大特点,它以很小的资源开销几乎实现了TCP中规定的全部内容。TCP是非常复杂的协议,这几个与TCP模块相关的文件占据了LwIP内核的绝大部分。

timeouts.c定义了LwIP内核的超时处理机制。LwIP内核中多个模块的实现需要借助超时处理机制完成,包括ARP表项的时间统计、IP分片报文的重装、TCP的各种定时器、实现各种应用层协议需要的超时处理。

udp.c文件实现了UDP,包括对UDP连接的操作和UDP数据包的操作。2.3 LwIP的说明文档

下面简单浏览一下LwIP的官方说明文档(http://www.nongnu.org/lwip/2_1_x/index.html)。打开连接,可以看到LwIP的Overview(概述),简单阅读一下即可,单击左侧的Common pitfalls链接,查看一下LwIP的常见陷阱,在使用过程中遇到这些陷阱时注意一下即可。LwIP可以工作在无操作系统环境,也可以工作在有操作系统的环境中,关于Common pitfalls中提到的Mainloop Mode(主函数轮询模式)和OS Mode(操作系统模式),有一些事项需要注意,具体如图2-8所示。图2-8 Common pitfalls

此外,我们还可以单击左侧的Modules链接查看一些模块相关的说明以及示例,比如有/无操作系统模拟层、LwIP基础配置、内存管理模块、数据包缓冲区等,这些相关的说明可在Modules ->Infrastructure页面中找到,具体如图2-9所示。图2-9 Modules

当然,一些很重要的用户常用的API函数在Modules中也可以找到,例如"raw" API、Sequential-style API和Socket API等,如图2-10所示。图2-10 Modules->APIs

此外,Applications中还有一些应用层的相关说明,如HTTP server、MQTT client、TFTP server等,如图2-11所示。图2-11 Modules->Applications

Modules ->Data Structures中有一些与数据结构相关的说明,当在程序中看到不懂的数据结构时,可以在这里找到对应的说明。数据是比较重要的,LwIP的本质就是对数据进行处理,其中也使用了大量的数据结构,有时间可以深入研究,具体如图2-12所示。图2-12 Data Structures

当然,我们也能通过函数名的首字母来查找函数的作用,如图2-13所示。图2-13 Function2.4 使用vs code查看源码2.4.1 查看文件中的符号列表和函数列表

LwIP的源码很庞大,我们可以使用微软的开源软件vs code查看源码,并且快速找到源码的函数与定义。

首先,安装vs code。可以在https://code.visual-studio.com/download中下载适合自己计算机的vs code版本,安装即可。

然后,右击我们的源码文件夹,在弹出的菜单中选择Open with Code命令,这样就能直接在vs code中打开整个文件夹的源码了,具体如图2-14所示。图2-14 选择Open with Code命令

vs code中显示了我们打开的源码。LwIP中有那么多文件,如何快速找到源码文件中的某个函数呢?很简单,若我们知道某个函数的名称,就直接搜索,如果不记得函数名,只知道它存储在哪个文件中,或者只知道它存储在多个文件夹的某一个中,那么就需要逐个查找了。vs code提供了很强大的功能,就是可以快速查找文件中的符号列表和函数列表。首先打开一个源码文件,比如tcp.c,然后通过快捷键Shift+Ctrl+O即可打开对应源码文件的符号列表和函数列表,通过查看这些列表,就能知道该源码文件中是否有我们需要的函数或者宏定义等,具体如图2-15与图2-16所示。图2-15 符号列表图2-16 函数列表2.4.2 函数定义跳转

使用vs code查看源码是非常方便的,比如可以通过F12键跳转到定义,通过快捷键Alt+F12快速浏览定义,或者通过快捷键Ctrl+F12执行Go to Declaration,这些操作还是很方便的,当然,我们也能通过鼠标右键进行选择,具体如图2-17所示。如果在查看函数之后想返回跳转前的位置,只需要通过快捷键Alt+←跳回即可。图2-17 函数定义跳转2.5 LwIP源码里的示例

打开之前下载好的contrib-2.1.0文件夹,如图2-18所示(后面LwIP的基础例程主要直接使用或参考源码里的示例即可)。图2-18 contrib包中的文件和文件夹

我们先讲解一下主要文件夹:

1)addons文件夹。LwIP中很多模块的实现都是可以由用户干预的,比如校验和、TCP初始序列号。LwIP的内核代码通过宏编译选项的设置,可以将内核中某些模块的实现方法配置成LwIP默认的方法或者用户自定义的方法。用户自定义的方法通常需要用户在钩子函数中实现。在实际应用中,我们采用内核默认的方法就足够了,只有在特定的场合下,为了顾及性能、资源开销等,我们可能需要自己实现相关的模块,或者编写相应的钩子函数。那么这时该怎么办呢?addons文件夹中的内容就为我们提供了参考。对于初学者,没必要关心这个文件夹。

2)apps文件夹中实现了很多应用层协议。LwIP源码包中也有apps文件夹,但源码包中apps文件夹下的应用程序全部用RAW/Callback API实现,属于内核代码的一部分。而此apps文件夹中的应用程序可以是由3种API中的任何一种实现的。你可以把它看作内核源码提供的应用程序的一个补充。

3)examples文件夹中是一些LwIP的应用示例。对于使用LwIP开发应用程序时可能出现的典型问题,比如如何移植网卡、如何使用LwIP的API、如何使用源码中提供的应用程序,这个目录提供了参考。在后续章节中,会使用这个目录中的示例来讲解LwIP的应用程序。

4)ports文件夹中是一些移植文件,可以帮助我们将LwIP移植到某个具体的操作系统中。目前这个目录所提供的移植文件只支持FreeRTOS、UNIX、Win32。我们会在后续的章节中讲解如何移植LwIP。2.6 LwIP的3种编程接口

LwIP提供了3种编程接口,分别为RAW/Callback API、Netconn API、Socket API。它们的易用性从左到右依次提高,而执行效率从左到右依次降低,用户可以根据实际情况选择合适的API进行网络应用程序的开发。下面将分别介绍这3种API。2.6.1 RAW/Callback API

RAW/Callback API是指内核回调型的API,这在许多通信协议的C语言实现中都有所应用。对于从来没有接触过回调式编程的人来说,可能理解起来会比较困难,后面的章节中会详细介绍。

RAW/Callback API是LwIP的一大特色,在没有操作系统支持的裸机环境中,只能使用这种API进行开发,同时这种API也可以用在操作系统环境中。这里先简要说明一下“回调”的概念。你新建了一个TCP或者UDP连接,想等它接收到数据以后处理它们,这时需要把处理该数据的操作封装成一个函数,然后将这个函数的指针注册到LwIP内核中。LwIP内核会在需要时检测该连接是否收到数据,如果收到了数据,内核会在第一时间调用注册的函数,这个过程称为“回调”,这个注册函数称为“回调函数”。回调函数中有你想要的业务逻辑,在这个函数中,可以自由地处理接收到的数据,也可以发送任何数据,也就是说,这个回调函数就是你的应用程序。至此,我们可以发现,在回调编程中,LwIP内核把数据交给应用程序的过程只是一次简单的函数调用,这是非常节省时间和空间资源的。每个回调函数实际上只是一个普通的C函数,这个函数在TCP/IP内核中被调用。每一个回调函数都作为一个参数传递给当前TCP或UDP连接。为了保存程序的特定状态,可以向回调函数传递一个指定的状态,并且这个指定的状态是独立于TCP/IP协议栈的。

在有操作系统的环境中,如果使用RAW/Callback API,用户的应用程序就以回调函数的形式成为内核代码的一部分,用户应用程序和内核程序会处于同一个线程之中,这就省去了任务间通信和切换任务的开销。

简单来说,RAW/Callback API的优点有两个:

1)可以在没有操作系统的环境中使用。

2)在有操作系统的环境中使用时,对比另外两种API,可以提高应用程序的效率,节省内存开销。

RAW/Callback API的优点是显著的,但缺点也是显著的:

1)基于回调函数开发应用程序时的思维过程比较复杂,利用回调函数去实现复杂的业务逻辑时会很麻烦,而且代码的可读性较差。

2)在操作系统环境中,应用程序代码与内核代码处于同一个线程,虽然能够节省任务间通信和切换任务的开销,但是相应地,应用程序的执行会制约内核程序的执行,不同的应用程序之间也会互相制约。在应用程序执行的过程中,内核程序将不可能得到运行,这会影响网络数据包的处理效率。如果应用程序占用的时间过长,而且恰巧这时又有大量的数据包到达,由于内核代码长期得不到执行,网卡接收缓存里的数据包就持续积累,到最后很可能因为满载而丢弃一些数据包,从而造成丢包的现象。2.6.2 Netconn API

在操作系统环境中,可以使用Netconn API或者Socket API进行网络应用程序的开发。Netconn API是基于操作系统的IPC机制(即信号量和邮箱机制)实现的,它将LwIP内核代码和网络应用程序分离成了独立的线程。如此一来,LwIP内核线程就只负责数据包的TCP/IP封装和拆封,而不用进行数据的应用层处理,大大提高了系统对网络数据包的处理效率。

前面提到,使用RAW/Callback API会造成内核程序和网络应用程序、不同网络应用程序之间的相互制约,如果使用Netconn API或者Socket API,这种制约将不复存在。

在操作系统环境中,LwIP内核会被实现为一个独立的线程,名为tcpip_thread,使用Netconn API或者Socket API的应用程序处在不同的线程中,我们可以根据任务的重要性分配不同的优先级给这些线程,从而保证重要任务的时效性。分配优先级的原则具体如表2-1所示。表2-1 线程优先级分配原则

Netconn API使用了操作系统的IPC机制,对网络连接进行了抽象,用户可以像操作文件一样操作网络连接(打开/关闭、读/写数据)。但是Netconn API并不像操作文件的API那样简单易用。举个例子,调用f_read函数读文件时,读到的数据会被放在一个用户指定的数组中,用户操作起来很方便,而Netconn API的读数据API却没有那么人性化,用户获得的不是一个数组,而是一个特殊的数据结构netbuf,用户如果想使用好它,就需要对内核的pbuf和netbuf结构体有所了解,我们会在后续章节中对其进行讲解。Netconn API之所以采取这种设计,是为了避免数据包在内核程序和应用程序之间发生复制,从而降低程序运行效率。当然,用户如果不在意数据递交时的效率问题,也可以把netbuf中的数据取出来复制到一个数组中,然后处理这个数组。

简单来说,Netconn API的优缺点如下:

1)相较于RAW/Callback API,Netconn API简化了编程工作,使用户可以按照操作文件的方式来操作网络连接。但是,内核程序和网络应用程序之间的数据包传递,需要依靠操作系统的信号量和邮箱机制完成,这需要耗费更多的时间和内存,另外还要加上任务切换的时间开销,效率较低。

2)相较于Socket API,Netconn API避免了内核程序和网络应用程序之间的数据复制,提高了数据递交的效率。但是,Netconn API的易用性不如Socket API好,它需要用户对LwIP内核所使用的数据结构有一定的了解。2.6.3 Socket API

Socket即套接字,它对网络连接进行了高级的抽象,使用户可以像操作文件一样操作网络连接,十分易用。许多网络开发人员最早接触的就是Socket编程,Socket已经成为网络编程的标准。在不同的系统中,运行着不同的TCP/IP,但是只要它实现了Socket接口,那么用Socket编写的网络应用程序就能在其中运行,可见用Socket编写的网络应用程序具有很好的可移植性。

不同的系统有自己的一套Socket接口。Windows系统中支持的是WinSock,UNIX/Linux系统中支持的是BSD Socket,它们虽然风格不一致,但大同小异。LwIP中的Socket API是BSD Socket,但是LwIP并没有也没办法实现全部的BSD Socket,如果开发人员想要移植UNIX/Linux系统中的网络应用程序到使用LwIP的系统中,就要注意这一点。

相较于Netconn API,Socket API具有更好的易用性。使用Socket API编写的程序可读性好,便于维护,也便于移植到其他系统中。Socket API在内核程序和应用程序之间存在数据的复制,这会降低数据递交的效率。另外,LwIP的Socket API是基于Netconn API实现的,所以在效率上相较前者有所降低。

[1]LwIP 2.1.2版本的官方下载链接:http://savannah.nongnu.org/projects/lwip/。

[2]Netconn API即为Sequential API,为了统一,下文均采用Netconn API。第3章开发平台

本章主要讲解STM32的ETH以太网外设驱动,以便在后续章节中使用它作为LwIP移植时的底层接口。在学习过程中,可以结合[1]《STM32库开发实战指南》中以太网部分,以便更好地理解本章内容。3.1 以太网概述

以太网(Ethernet)是互联网技术的一种,由于它在组网技术中所占比例最高,很多人直接把以太网理解为互联网。但实际上以太网是指遵守IEEE 802.3标准组成的局域网,由IEEE 802.3标准规定的主要是位于参考模型的物理层(PHY层)和数据链路层中的介质访问控制子层(MAC子层)。家庭、企业和学校中所组建的PC局域网一般也是以太网,其标志是使用水晶头网线来连接(当然还有其他形式)。IEEE还有其他局域网标准,如IEEE 802.11是为无线局域网(Wi-Fi)制定的;IEEE 802.15是个人域网,即蓝牙技术,其中的802.15.4标准则是ZigBee技术。

现阶段,工业控制、环境监测、智能家居的嵌入式设备产生了接入互联网的需求,利用以太网技术,嵌入式设备可以非常容易地接入现有的计算机网络中。3.1.1 PHY层

在PHY层,由IEEE 802.3标准规定了以太网使用的传输介质、传输速度、数据编码方式和冲突检测机制。物理层一般是通过一个PHY芯片实现功能的,我们使用的是野火STM32F429挑战者开发板,板载的PHY芯片是LAN8720A。

1. 传输介质

传输介质包括同轴电缆、双绞线(水晶头网线是一种双绞线)、光纤。根据不同的传输速度和距离要求,基于这3类介质又衍生出很多不同种类的信号线。最常用的是“五类线”,适用于100BASE-T和10BASE-T网络,它们的网络速率分别为100Mbit/s和10Mbit/s。

2. 编码

为了让接收方在没有外部时钟参考的情况下也能确定每一位的起始、结束和中间位置,在传输信号时不直接采用二进制编码。在10BASE-T的传输方式中采用曼彻斯特编码,在100BASE-T中则采用4B/5B编码。

曼彻斯特编码把每一个二进制位的周期分为两个间隔,在表示1时,以前半个周期为高电平,后半个周期为低电平,表示0时则相反,具体如图3-1所示。图3-1 曼彻斯特编码

采用曼彻斯特编码在每个位周期都有电压变化,便于同步,但这样的编码方式效率太低,只有50%。

在100BASE-T中采用的4B/5B编码是把待发送数据位流的每4位分为一组,以特定的5位编码来表示,这些特定的5位编码能使数据流有足够多的跳变,从而方便达成同步的目的,而且效率也从曼彻斯特编码的50%提高到了80%。

3. CSMA/CD冲突检测

早期的以太网大多是多个节点连接到同一条网络总线上(总线型网络),存在信道竞争问题,因而每个连接到以太网上的节点都必须具备冲突检测功能。以太网具备CSMA/CD冲突检测机制,如果多个节点同时利用同一条总线发送数据,则会产生冲突,总线上的节点可通过将接收到的信号与原始发送的信号进行比较来检测是否存在冲突,若存在冲突,则停止发送数据,随机等待一段时间再重新发送。

现在组建大多数局域网时,很少采用总线型网络,更多的是将一个设备接入一个独立的路由或交换机接口,组成星形网络,不会产生冲突。但为了兼容,新出的产品还是带有冲突检测机制。3.1.2 MAC子层

1. MAC子层的功能

MAC子层属于数据链路层的下半部分,主要负责与物理层进行数据交接,如是否可以发送数据、发送的数据是否正确、对数据流进行控制等。它自动对来自上层的数据包加上一些控制信号,交给物理层。接收方得到正常数据后,自动去除MAC控制信号,把该数据包交给上层。

2. MAC数据包

IEEE对以太网上传输的数据包格式也进行了统一规定,如图3-2所示,该数据包称为MAC数据包。图3-2 MAC数据包格式①发送FCS时,首先发送位31,最后发送位0。

MAC数据包由前导字段、帧起始定界符(SFD)、目标地址(DA)、源地址(SA)、数据包的类型/长度、数据域、填充域、校验和域(FCS)组成。● 前导字段:也称报头,这是一段方波,用于使收发节点的时钟同

步,内容为连续7个字节的0x55。字段和帧起始定界符在MAC收

到数据包后会自动过滤掉。● 帧起始定界符:用于区分前导字段与数据域,内容为0xD5。● MAC地址:由48位数字组成,是网卡的物理地址。在以太网传

输的最底层,就是根据MAC地址来收发数据的。部分MAC地址

用于广播和多播,在同一个网络里不能有两个相同的MAC地

址。PC的网卡在出厂时已经设置好了MAC地址,但也可以通过

一些软件来进行修改,在嵌入式的以太网控制器中可由程序进行

配置。数据包中的DA是目标地址,SA是源地址。● 数据包的类型/长度:本区域可以用来描述本MAC数据包是属于

TCP/IP层的IP包、ARP包还是SNMP包,也可以用来描述本MAC

数据包数据段的长度。如果该值大于0x0600,则不用于长度描

述,而是用于描述类型,表示与以太网帧相关的MAC客户端协

议的种类。● 数据域:数据域是MAC包的核心内容,包含的数据来自MAC的

上层。其长度可以在0~1500字节间变化。● 填充域:由于协议要求整个MAC数据包的长度至少为64字节(接收到的数据包如果少于64字节,会被认为发生冲突,数据包

被自动丢弃),当数据域的字节少于46字节时,在填充域会自动

填上无效数据,以使数据包符合长度要求。● 校验和域:MAC数据包的尾部是校验和域,它保存了CRC校验

序列,用于检错。

以上是标准的MAC数据包,IEEE 802.3同时还规定了扩展的MAC数据包,它是在标准的MAC数据包的SA和数据包类型之间添加4个字节的QTag前缀字段,用于获取标志的MAC帧。前2个字节固定为0x8100,用于识别QTag前缀的存在,后两个字节分别为3个位的用户优先级、1个位的标准格式指示符(CFI)和一个12位的VLAN标识符。3.2 STM32的ETH外设

STM32F42x系列控制器内部集成了一个以太网外设,它实际上是通过DMA控制器进行介质访问控制,功能就是实现MAC层的任务。借助以太网外设,STM32F42x控制器可以通过ETH外设按照IEEE 802.3-2002标准发送和接收MAC数据包。

ETH内部自带专用的DMA控制器用于MAC,ETH支持两个工业标准接口—介质独立接口(MII)和简化介质独立接口(RMII),用于与外部PHY芯片连接。MII和RMII接口用于MAC数据包传输,ETH还集成了站管理接口(SMI)专门用于与外部PHY通信,访问PHY芯片寄存器。

物理层定义了以太网使用的传输介质、传输速度、数据编码方式和冲突检测机制,PHY芯片是物理层功能实现的实体,生活中常用水晶头网线+水晶头插座+PHY组合构成物理层。

ETH有专用的DMA控制器,它通过AHB主从接口与内核和存储器相连,AHB主接口用于控制数据传输,而AHB从接口用于访问“控制与状态寄存器”(CSR)空间。在进行数据发送时,先将数据由存储器以DMA方式传输到缓冲区TX FIFO,然后由MAC内核发送;接收数据时,RX FIFO先接收以太网数据帧,再由DMA传输至存储器。ETH系统功能框图如图3-3所示。图3-3 ETH功能框图3.3 MII和RMII接口

MII用于理解MAC控制器和PHY芯片,提供数据传输路径。RMII接口是MII接口的简化版本,MII需要16根通信线,RMII只需要7根通信线,在功能上二者是相同的。图3-4所示为MII接口连接示意图,图3-5所示为RMII接口连接示意图。图3-4 MII接口连接示意图图3-5 RMII接口连接示意图● TX_CLK:数据发送时钟线。标称速率为10Mbit/s时为2.5MHz;

速率为100Mbit/s时为25MHz。RMII接口没有该线。● RX_CLK:数据接收时钟线。标称速率为10Mbit/s时为2.5MHz;

速率为100Mbit/s时为25MHz。RMII接口没有该线。● TX_EN:启用数据发送。在整个数据发送过程中保存有效电

平。● TXD[3:0]或TXD[1:0]:数据发送数据线。对于MII有4位,RMII只

有2位。只有在TX_EN处于有效电平时数据线才有效。● CRS:载波侦听信号,由PHY芯片负责驱动,当发送或接收介质

处于非空闲状态时启用该信号。在全双工模式下该信号线无效。● COL:冲突检测信号,由PHY芯片负责驱动,检测到介质上存在

冲突后,该线被启用,并且保持至冲突解除。在全双工模式该信

号线无效。● RXD[3:0]或RXD[1:0]:数据接收数据线,由PHY芯片负责驱动。

对于MII有4位,RMII只有2位。在MII模式下,当禁用RX_DV、启

用RX_ER时,特定的RXD[3:0]值用于传输来自PHY的特定信息。● RX_DV:接收数据有效信号,功能类似TX_EN,只不过用于数

据接收,由PHY芯片负责驱动。对于RMII接口,是把CRS和

RX_DV整合成CRS_DV信号线,当介质处于不同状态时会自切换

该信号状态。● RX_ER:接收错误信号线,由PHY驱动,向MAC控制器报告在

帧某处检测到错误。● REF_CLK:仅用于RMII接口,由外部时钟源提供50MHz参考时

钟。因为要达到100Mbit/s的传输速度,MII和RMII数据线的数量

不同,使用MII和RMII在时钟线的设计方面是完全不同的。对于

MII接口,一般是由外部为PHY提供25MHz时钟源,再由PHY提

供TX_CLK和RX_CLK时钟。对于RMII接口,一般需要外部直接

提供50MHz时钟源,同时接入MAC和PHY。

开发板板载的PHY芯片型号为LAN8720A,该芯片只支持RMII接口,设计电路时可参考图3-6。图3-6 ETH复用引脚①PPS_OUT是IEEE 1588定义的一个时钟同步机制。3.4 PHY:LAN8720A

LAN8720A是SMSC公司(已被Microchip公司收购)设计的一个体积小、功耗低、全能型10/100Mbit/s的以太网物理层收发器。它是针对消费类电子和企业应用而设计的。LAN8720A总共只有24个引脚,仅支持RMII接口。由它组成的网络结构如图3-7所示。

LAN8720A通过RMII与MAC连接。RJ45是网络插座,与LAN8720A之间还需要一个变压器才能连接,一般使用带电压转换和LED指示灯的HY911105A型号的插座。一般来说,必须为使用RMII接口的PHY提供50MHz的时钟源输入REF_CLK引脚,不过LAN8720A内部集成PLL,可以将25MHz的时钟源倍频到50MHz,并在指定引脚输出该时钟,所以可以直接使其与REF_CLK连接,达到提供50MHz时钟的效果。图3-7 由LAN8720A组成的网络结构

LAN8720A内部系统结构如图3-8所示。图3-8 LAN8720A内部系统结构

LAN8720A由各个具有不同功能的模块组成,其中最重要的是接收控制器和发送控制器,其他模块基本上都是与外部引脚挂钩,实现信号传输。部分引脚具有双重功能,比如PHYAD0与RXER引脚是共用的,在系统上电后LAN8720A会马上读取这部分共用引脚的电平,以确定系统的状态并保存在相关寄存器内,之后则自动转入作为另一功能引脚。

PHYAD[0]引脚用于配置SMI通信的LAN8720A地址,在芯片内部该引脚已经自带下拉电阻,默认为0(即使外部悬空),在系统上电时会检测该引脚获取到LAN8720A的地址为0或者1,并保存在特殊模式寄存器(R18)的PHYAD位中,该寄存器的PHYAD有5个位,在需要超过2个LAN8720A时可以通过软件设置不同的SMI通信地址。

MODE[2:0]引脚用于选择LAN8720A网络通信速率和工作模式,

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载