网络连接是物联网的核心,Zephyr 提供了全面的嵌入式 TCP/IP 网络支持。本章假设您已具备计算机网络和 TCP/IP 的基础知识,重点将放在使用 Zephyr RTOS 网络框架开发应用程序上。
Zephyr 的网络栈是 Zephyr OS 的栈实现 。与大多数网络栈类似,它采用分层架构,其中每层接口为其他层提供服务。在 Zephyr 中,通过选择所需的 Kconfig 选项来指定要集成到应用程序中的网络栈功能。
图 8-1 网络应用
图 8-2 网络协议
图 8-3 网络设备驱动
Zephyr 网络应用可以使用 Zephyr 提供的应用层协议库。开发者也可以直接基于 Zephyr 实现的 BSD 套接字 API 来开发网络应用。Zephyr 还提供了 网络管理 API,可用于配置网络及设置网络参数,如网络链路选项、启动扫描(适用时)、监听网络配置事件等。通过 Zephyr 的网络接口 API 可执行分配 IP 地址给网络接口、关闭网络接口等操作。
Zephyr 支持的应用层协议包括 CoAP、LwM2M 和 MQTT。
Zephyr 支持的 TCP/IP 第三层和第四层协议包括 IPv6、IPv4、UDP、TCP、ICMPv4 和 ICMPv6,这些协议通过 BSD 套接字 API 进行访问。
Zephyr 支持的第二层(链路层)网络技术包括以太网、IEEE 802.15.4、蓝牙和 CAN 总线。
底层设备驱动负责网络数据包在物理层的发送和接收。
在实现 Zephyr 网络应用时,通常的做法是将应用程序代码运行在用户空间上下文,而网络协议栈代码运行在内核空间上下文。
Zephyr 网络接口抽象层在提供网络接口通用 API 功能方面起着重要作用,特别是具备启用或禁用网络接口的能力。发送和接收数据的传输都是通过网络接口进行的。在 Zephyr 中,网络接口不能在运行时创建。构建过程中会创建一个包含网络接口信息的特殊链接器段,并在链接时填充这些接口。 网络接口是通过使用 NET_DEVICE_INIT() 宏或其更专门的变体来创建的。对于以太网网络,应使用 ETH_NET_DEVICE_INIT() 宏 ,因为当 CONFIG_NET_VLAN 启用时,它会自动创建 VLAN 接口。 网络初始化宏主要用于网络设备驱动程序的实现中。
可以通过调用 net_if_up() 开启网络接口 ,调用 net_if_down() 关闭接口。在 Zephyr 中,默认行为是当网络设备通电时,网络接口也会自动开启。 网络接口可以通过 struct net_if *指针或网络接口索引来引用。可以通过调用 net_if_get_by_index()从索引解析网络接口,或通过调用 net_if_get_by_iface()从接口指针解析。
对于 IP 网络,必须正确设置网络设备的 IP 地址。IP 地址可以通过 DHCPv4 自动设置,或使用 API 函数如 net_if_ipv4_addr_add() 手动设置。
Zephyr 网络框架提供了数据包优先级分类机制,旨在让高优先级数据包比低优先级数据包更早发送或接收。要支持数据包优先级功能,Zephyr 提供了 CONFIG_NET_TC_TX_COUNT 和 CONFIG_NET_TC_RX_COUNT 配置选项。
CONFIG_NET_TC_TX_COUNT 用于定义网络设备可拥有的 Tx(发送)流量类别数量,而 CONFIG_NET_TC_RX_COUNT 则用于定义网络设备可拥有的 Rx(接收)流量类别数量。
随后可将网络数据包优先级映射到流量类别,使高优先级数据包能够优先于低优先级数据包被处理。这需要为每个优先级建立独立队列,每个队列由单独线程处理,且流量类别值越高对应的线程优先级值越低。对于传输类别而言,当类别数量为 0 时,传输网络流量将直接推送至驱动程序而不经过任何队列。在接收类别中,计数值为 0 意味着所有网络流量将从驱动程序直接推送至应用线程,不经过任何中间 RX 队列。设备驱动程序与应用之间存在接收套接字队列。禁用 RX 线程将导致网络设备驱动程序(通常运行在 IRQ 上下文中)直接处理数据包直至应用层。这种处理方式的后果是:若 RX 处理耗时相对于数据包到达速率较长,则其他传入数据包可能会丢失。
当启用了用户空间支持时,在当前实现中至少需要启用 1 个 TX 线程和 1 个 RX 线程。
Zephyr 可用于为适用的网络技术(如以太网 )配置混杂模式。若启用 CONFIG_NET_PROMISCUOUS_MODE 配置项,则网络设备驱动能接收的所有数据包都将被接受。如未配置混杂模式,则仅接受目标地址为以太网设备 MAC 地址的数据包。通常,混杂模式用于监控所有流量,在内存和处理资源有限的嵌入式系统中一般不会配置此模式。
Zephyr 与网络管理
RFC 2863 标准涉及管理信息库(MIB) 中与网络接口管理相关的受管对象部分。该标准定义了 Zephyr 的两种接口状态: 管理状态和运行状态 。 管理状态表示接口处于开启(ON)或关闭(OFF)状态。在 Zephyr 中,这由应用程序控制的 NET_IF_UP 标志表示,可通过调用 net_if_up()或 net_if_down()函数来修改其值。但接口处于开启状态并不意味着它已准备好收发数据包。 运行状态表示接口的内部状态,会在以下情况更新:应用程序启停接口导致管理状态变更、驱动/第二层通知接口 PHY(第一层)状态变化、或驱动/第二层通知接口加入/离开网络。
PHY 状态由 NET_IF_LOWER_UP 标志表示,可通过 net_if_carrier_on()和 net_if_carrier_off()函数进行修改。默认情况下,新初始化的接口会设置该标志。以以太网为例,当网线连接或断开时,载波状态将发生改变。
网络关联状态由 NET_IF_DORMANT 标志表示,可通过 net_if_dormant_on()和 net_if_dormant_off()进行修改。对于 Wi-Fi 而言,当 Wi-Fi 驱动成功连接到接入点时,休眠状态会发生变化。Wi-Fi 驱动在初始化期间将休眠状态设为 ON,当检测到已建立 Wi-Fi 网络连接时,则将休眠状态设为 OFF。
Zephyr 网络 API 提供了多个用于检测接口状态的函数,例如 net_if_is_admin_up()、net_if_is_carrier_ok() 以及 net_if_is_dormant()。
-
CoAP 协议
-
CoAP 客户端
-
HTTP 客户端
-
轻量级 M2M(LwM2M)
-
MQTT
-
MQTT-SN
-
SNTP(简单网络时间协议)
-
BSD 套接字
-
用于配置 IPv4/IPv6 的 API
-
用于配置 DNS 名称解析的 API
-
为应用程序添加网络管理和监控功能的各种 API
更多详细信息可在 Zephyr 项目文档[1]中找到。
开发基于 TCP/IP 网络的应用程序时,务必仅包含和配置必要的组件,因为应用程序是整体构建的最终产物,包含大量不必要的网络功能会耗尽有限的存储资源。本章并不旨在全面介绍 Zephyr 中所有的网络功能,而是着重讲解构建实用物联网应用时必须理解和掌握的核心组件。因此,重点将放在 IPv4 协议上,IPv6 属于更高级的课程范畴。示例应用将采用 Zephyr 实现的 BSD 套接字 API 进行开发,硬件平台选用内置以太网功能的 STM32 Nucleo 开发板——搭载 ARM Cortex M7 处理器的 Nucleo-F767ZI 开发板(SoC 系统级芯片)。
Nucleo-F767ZI 开发板
STM32F767ZI 微控制器采用 ARM Cortex M7 内核,具备 2MB 闪存和 512KB SRAM,支持外部中断的 GPIO 接口,24 通道 12 位 ADC,12 位 DAC 通道,USART/UART 串行外设,以及 I2C 和 SPI 外设。该处理器还包含通用定时器、高级控制定时器、基本定时器、低功耗定时器和看门狗定时器,同时集成 CAN 2.0B、SDMMC、USB 2.0 OTG HS/FS 高速/全速接口、随机数生成器以及以太网外设。
开发板配备三个用户 LED 指示灯、两个按钮(USER 和 RESET)以及一个以太网 RJ45 接口。
图 8-4 Nucleo-F767ZI 开发板
图 8-5 Nucleo-F767ZI 开发板布局
使用 STM32 Nucleo-F767ZI 开发板构建和调试 Zephyr 网络编程示例
Zephyr 网络编程示例涵盖了 TCP/IP 网络编程的多个方面,包括 dhcpv4_client、dns_resolve、sockets、ipv4_autoconf、lldp(链路层发现协议)以及 telnet。同时还提供了面向物联网的协议示例,包括 lwm2m_client(轻量级 M2M 客户端)和 mqtt_publisher 示例。
在为不同开发板构建 Zephyr 网络示例时可能会遇到问题,因为 Zephyr 项目的目标是支持广泛的开发板,这导致关于个别开发板"特性和功能"的文档有时较为有限。在构建和测试网络应用程序时,通常需要排查并仔细检查诸如手动分配 IP 地址和子网掩码的情况,或者如果地址是自动分配的,则需要正确设置和配置 DHCP。在实际网络中运行应用程序还需要掌握一定的网络配置和故障排除知识,这可能包括处理防火墙配置、网络路由和 NAT(网络地址转换)等问题,以及 IPv4 和 IPv6 配置细节,例如默认网关的 IP 地址和要使用的 DNS 解析器的 IP 地址。
Zephyr 源代码仓库包含全面的网络套接字示例集,其中包括演示简易 HTTP 服务器、基础多线程 HTTP 服务器、基于 TCP 和 UDP 运行的 echo 及客户端服务器、HTTP 客户端以及自定义 TCP 套接字客户端等示例。本章将重点探讨 UDP/TCP 回显服务器示例、异步 select 示例以及 WebSocket 客户端示例。这些示例大体对应 Linux TCP/IP 编程入门时涉及的典型范例。不同之处在于 Zephyr 应用程序采用单体架构,所有必需组件都必须内置其中。历史上 Zephyr 曾集成 CivetWeb 嵌入式 HTTP 服务器 ,但当前其 CivetWeb 模块已被弃用并移除。官方声明原因是维护第三方 Civet 项目分支作为外部模块的复杂性过高。
Zephyr HTTP 服务器模块的要求是:需与现有 Zephyr 构建模块对接,支持 ZTest 框架、HTTP、WebSocket、JSON 和 mbedTLS 库,并包含针对套接字、线程和文件系统操作的 POSIX API 支持,以及 Kconfig 和构建系统以实现服务器选项的调优。同时要求支持 Zephyr 可迭代区段功能,以便更灵活地定义 HTTP 服务和资源。
BSD 套接字 API
BSD 套接字 API(POSIX 标准的一部分)最初是在伯克利与 TCP/IP 协议原始版本的开发过程中设计的。
Zephyr 包含了该协议的实现,其设计目标是尽可能减少开销。Zephyr 的 BSD 套接字 API 采用命名空间约定,即在原生 BSD 套接字 API 名称前添加 zsock_前缀,例如用 zsock_socket()和 zsock_close()分别对应 BSD 的 socket()和 close()函数。这样做是为了避免与 libc 或其他 POSIX 兼容库中可能存在的名称(如 close())产生冲突。通过启用配置选项 CONFIG_NET_SOCKETS_POSIX_NAMES 可以暴露原生 POSIX 名称。支持的 BSD 函数包括:socket()、close()、recv()、recvfrom()、send()、sendto()、connect()、bind()、listen()、accept()、fcntl()(用于设置非阻塞模式)、getsockopt()、setsockopt()、poll()、select()、getaddrinfo()和 getnameinfo()。
Zephyr 网络 API 的实现尽可能利用了 POSIX API 的短读/短写特性,旨在最小化开销和代码复杂度。该特性允许在 SOCK_STREAM 类型套接字上执行 recv() 和 send() 等调用时,实际处理(接收或发送)的数据量可能少于用户请求的数量。例如,调用 recv(sock, 1000, 0) 可能返回 100,表示仅读取了 100 字节(短读)。要获取剩余的 900 字节, 应用程序需要重复调用 call() 直到接收完所有数据。
Zephyr 回显服务器示例概述
Zephyr 的 echo-server 示例应用实现了一个 UDP/TCP 服务器,该服务器监听传入的 IPv4(如果配置支持,还包括 IPv6)数据包(由 echo 客户端发送)并原样返回。此外,通过提供合适的 overlay-tls.conv 覆盖配置文件,可以将项目配置为包含 TLS 1.2 支持。示例代码可在 Zephyr 代码库[2]中找到。尽管 echo 服务器的概念很简单,但实际代码涵盖了在 Zephyr RTOS 中实现 TCP 和 UDP 服务器应用的诸多方面。在实际应用中,由于 Zephyr RTOS 运行的硬件资源限制,并发服务器应用的数量可能会很少。
最新版本的服务器示例支持客户端连接,服务器端口号配置为 4242。由于 TCP 和 UDP 端口号相互独立,该应用程序将同时使用 TCP 端口 4242 和 UDP 端口 4242。
可以通过设置第二块运行回声客户端应用的开发板,或通过在 Windows 或 Linux PC 上使用 C、C++或 Python 等语言实现合适的测试程序来测试回声服务器 。
west build -p -b nucleo_f767zi <path to echo_server project source code directory>
可使用命令 west flash --runner jlink 将代码烧录至开发板。
*** Booting Zephyr OS build v2.7.0-rc2-7-g0d538447144c ***
[00:00:01.560,000] <inf> net_config: Initializing network
[00:00:01.560,000] <inf> net_config: Waiting interface 1 (0x2002260c) to be up...
[00:00:02.051,000] <inf> net_config: Interface 1 (0x2002260c) coming up
实际上,这个示例包含多个组成部分,其中还包括 Zephyr OS Services shell 命令库模块,我们将在接下来进行介绍。
Zephyr OS 服务模块
Zephyr OS Services 模块使得创建和部署带有用户自定义命令集的解释器 shell 成为可能。相比简单的按钮和 LED 交互,该模块能够支持更复杂的交互和反馈功能。
该模块提供了一个(简化版) 类 Unix shell,具有许多实用功能。包括支持多实例、与日志框架协同工作,以及同时支持静态(编译时提供)和动态(应用程序运行时实现)命令。
其他实用功能还包括:支持字典命令、使用 Tab 键实现命令自动补全,以及一系列内置实用命令如 clear、shell、colors、echo、history 和 resize。最近执行的命令可通过上下方向键或元键查看。命令行文本编辑支持使用左右方向键、退格键、删除键、Home 键和 Insert 键。该模块还支持 ANSI 转义码和多行命令编辑、显示命令帮助的处理程序、支持通配符*和?的使用、支持元键操作,同时兼容 getopt 和 getopt_long 功能。通过相关 Kconfig 配置选项,可仅包含所需功能以优化应用程序内存使用。
Shell 可构建支持 Zephyr 网络命令的功能,从而能够运行 Linux 命令行网络命令。可用命令如下方屏幕截图所示。
Zephyr 代码库中的示例 zephyr\samples\net\sockets\echo_server 可用于探索部分 Zephyr shell 网络命令。关于将 Zephyr shell 集成到应用程序的更多详细信息,请参阅 Zephyr 文档 https://docs.zephyrproject.org/latest/services/shell/index.xhtml。