lwIP(A Lightweight TCP/IP stack)是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。LwIP是Light Weight (轻型)IP协议,有无操作系统的支持都可以运行。LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用,它只需十几KB的RAM和40K左右的ROM就可以运行。这样就可以让lwIP适用于资源有限的小型平台例如嵌入式系统。
- addons 目录。 LwIP 中很多模块的实现,都是可以由用户干预的,比如校验和、 TCP 初始序列号。 LwIP 的内核代码,通过宏编译选项的设置,可以将内核中某些模块的实现方法配置成 LwIP默认的方法,或者用户自定义的方法。用户自定义的方法通常需要用户在钩子函数中实现。在实际应用中,我们采用内核默认的方法就足够了,只有在非常特定的场合下,为了性能、资源开销等因素的考虑,我们可能会需要自己实现相关的模块,或者说编写相应的钩子函数。
- apps 目录里实现了很多应用层协议。 LwIP 源码包中也有 apps 目录,但源码包中 apps 目录下的应用程序全部用 RAW/Callback API 实现,属于内核代码的一部分。而此 apps 目录里的应用程序可以是由三种 API 中的任何一种实现的。读者可以把它看成是内核源码所提供的应用程序的一个补充。
- examples 目录里是一些 LwIP 的应用示例。在使用 LwIP 开发应用程序时会出现的典型问题,比如如何移植网卡、如何使用 LwIP 的 API、如何使用源码中提供的应用程序,对于这些问题,这个目录为我们提供了参考。我们在后续的章节中,会使用这个目录中的例子来讲解 LwIP 的应用程序。
- ports 目录里是一些移植文件,它可以帮助我们将 LwIP 移植到某个具体的操作系统中。目前这个目录所提供的移植文件,支持 RTOS、 UNIX、 Win32。
doc 文件夹里面是关于 LwIP 的一些文档,可以看成是应用和移植 LwIP 的指南。但是这些文档比较零散,不成体系,而且纯文本阅读起来很费劲,阅读意义不是很大。
├─doc
│ │ contrib.txt // LwIP作为开源软件,如果想要为其做贡献,则需要遵循一定的准则,例如:提交代码的风格、报告Bug等。该文档给出了详细的贡献准则。
│ │ doxygen_docs.zip // 用doxygen生成的LwIP的配套文档
│ │ FILES // 其中说明了该目录下的每个文件的用途
│ │ mdns.txt // MDNS的说明文档
│ │ mqtt_client.txt // MQTT
│ │ NO_SYS_SampleCode.c // 无操作系统 简单示例代码
│ │ ppp.txt // lwIP的PPP接口文档
│ │ savannah.txt // 说明了如何获取当前的开发源代码
│ │ ZeroCopyRx.c // 示例代码
│ │ *rawapi.txt // 参照2.0.3 使用说明,协议栈的Raw/Callback API进行编程
│ │ *sys_arch.txt // 参照2.0.3 在有操作系统的移植的时候会被使用到,包含了移植说明,规定了移植者需要实现的函数、宏定义等,后面有详细说明。
│ └─doxygen // doxygen脚本,主要用来维护LwIP的配套文档。对于使用LwIP来说用不到
│ │ generate.bat
│ │ generate.sh
│ │ lwip.Doxyfile
│ │ main_page.h
│ └─output
协议栈内核测试程序.在实际使用时一般用不到!可直接删除
- api 文件夹里面装的是 NETCONN API 和 Socket API 相关的源文件,只有在操作系统的环境中,才能被编译。
- apps 文件夹里面装的是应用程序的源文件,包括常见的应用程序,如 httpd、mqtt、tftp、sntp、snmp等。
- core 文件夹里面是 LwIP 的内核源文件。
高级封装 API 的代码。如果使用低级回调/raw API ,则不需要
- api_lib.c
- 包含 对外提供的 sequential API 函数的实现。函数名均以
netconn_
开头。主要分为三组API:同时可用于TCP和UDP的API、只能用于TCP的API、只能用于UDP的API- api_msg.c
- 包含sequential API内部自己调用的函数的实现。主要包含API消息的封装和处理函数
- err.c
- 错误管理模块
- if_api.c
- 接口识别,IPv6的基本套接字接口扩展
- netbuf.c
- 网络缓冲管理,包含了上层数据包管理函数的实现。应用程序描述待发送数据和已接收数据的基本结构。该结构只是对内核 pbuf 的简单封装,避免了数据的拷贝。缓冲区不能在多个线程之间共享。
- netdb.c
- 包含与主机名字转换相关的函数,主要在 socket 中被使用到
- netifapi.c
- 上层网络接口管理函数的实现
- sockets.c
- BSD Socket API 函数的实现
- tcpip.c
- Sequential API 主线程模块, 包含了上层 API 与协议栈内核交互的函数,它是整个上层 API 功能得以实现的一个枢纽,其实现的功能可以简单理解为:从 API 函数处接收消息,然后将消息递交给内核函数,内核函数根据消息做出相应的处理
使用lwIP低级Raw API 专门编程的 高层应用程序
TCP/IP协议栈核心:协议实现,内存和缓冲区管理以及低级Raw API。它包含了IP、ICMP、IGMP、TCP、UDP 等核 心协议以及建立在它们基础上的DNS、DHCP、SNMP 等上层应用协议。内核源代码可以单独运行,且不需要操作系统的支持。即:直接使用raw API编程。
- /ipv4目录 IPv4 标准中与IP层数据包处理相关的所有代码
- autoip.c
- lwIP TCP/IP协议栈的AutoIP实现。 它旨在符合RFC 3927
dhcp.c
实现了DHCP 客户端的所有代码,DHCP 称为动态主机配置协议,DHCP 可以使计算机使用者不必为主机的IP 地址的分配问题而烦恼。DHCP 也是一个上层应 用程序, 通常 DHCP 客户端通过使用UDP提供的功能来实现与DHCP服务器的通信, 从DHCP 服务器处获得一个有效的IP 地址
etharp.c
ARP(地址解析协议) 协议是以太网通信中的重要部分, 主要用来实现主机以太网物理地址到 IP 地址的映射。以太网中底层数据包的发送是基于网卡物理地址的,而不是主机的IP地址。 通过ARP协议, 主机可以发送请求, 得到邻居节点的IP 地址与物理地址等信息,为以太网数据包交互提供保证。
功能上,ARP分为两部分。 第一部分在发送数据包时将IP地址映射到物理地址,第二部分响应来自其他机器的请求以获取我们的物理地址。
icmp.c
ICMP(Internet Control Message Protocol)Internet控制报文协议。
包含了ICMP 协议实现的相关函数,ICMP 协议为IP 数据包传递过程中的差错 报告、差错纠正以及目的地址可达性测试提供了支持,常见的Ping 命令就属于ICMP 应用的一种
igmp.c
IGMP(Internet Group Management Protocol)Internet 组管理协议
包含了网络组管理协议IGMP 的实现,IGMP 为网络中的多播数据传输提供了 支持,主机加入某个多播组后,可以接收到该组的UDP 多播数据
ip4.c
- 包含了IPv4 协议实现的相关函数,如数据包的接收、递交、发送等
- ip4_addr.c
- 实现了几个比较简单 的IP 地址处理函数,如判断一个IP 地址是否为广播地址的函数,以及32 位IP 地址与点分十 进制地址间的转换函数等
- ip4_frag.c
- 提供了IPv4数据包分段和重组实现
- /ipv6目录 IPv6 标准中和IP层数据包处理相关的所有代码
- mld6.c
- IPv6的多播侦听器发现(Multicast listener discovery)。旨在符合RFC 2710。
- nd6.c
- IPv6的邻居发现和无状态地址自动配置。 旨在符合RFC 4861(邻居发现)和RFC 4862(地址自动配置)。
- altcp.c
- 应用层TCP连接API;应用程序层的TCP功能
- altcp_alloc.c
- 应用层TCP连接API;从TCPIP线程使用,此文件包含组合多个层的分配实现
- altcp_tcp.c
- 此文件包含调用tcp的基本实现。
- def.c
- 协议栈常用的功能函数,地址的转换、网络字节序与 主机字节序转换等
- dns.c
- 实现了DNS 客户端的所有代码,DNS 称为域名系统.通常用户要访问某个外 部的主机时,可能只知道该主机的名字,而不知道该主机的IP 地址
- inet_chksum.c
- 校验和算法的一些参考实现
- init.c
- LwIP 协议栈初始化密切相关的函数,以及一些 协议栈配置信息的检查与输出
- ip.c
- IPv4和IPv6共用的代码
- mem.c
协议栈内存堆管理函数的实现
这是标准C库malloc()的轻量级替代品
此文件即LwIP实现的动态内存堆(Heap)分配策略
在
lwipopts.h
使用宏值MEM_LIBC_MALLOC = 1
选择使用C库自带内存分配策略- memp.c
- 协议栈内存池管理函数的实现, lwIP有许多结构的专用池(netconn,协议控制块,数据包缓冲区......)。 所有这些池都在这里管理。
- netif.c
- 包含了协议栈网络接口管理的相关函数,协议栈支持多个网络接口
- pbuf.c
- 包含了协议栈内核使用的数据包管理函数,数据包pbuf管理的实现是整个协议栈中很有特色的地方,采用特殊的数据包pbuf 结构,可以避免数据在各个层次之间递交时的拷贝,这既提高了数据递交效率,也节省了内存空间
- raw.c
- 为应用层提供了一种直接和IP 数据包交互的方式,这类似于Socket 编程中原始套接字的概念,它同TCP、UDP 处于同一等级,享受IP 层提供的服务。使用原始套接字, 可以直接读取IP 层接收到的数据包,例如ICMP 包、UDP 包等,也可以自行构造ping包等, 为用户的程序编写提供了很大的方便。这是LwIP最底层的API部分。
- stats.c
- 包含了协议栈内部数据统计与显示的相关函数,如内存使用状况、邮箱、信号量等信息
- sys.c
- 实现了一个简单的函数void sys_msleep(u32_t ms);,它借助操作系统抽象层的信号量机制完成睡眠一定时间的功能,该函数主要在PPP 中使用。前面曾提到过,若需要使用协议栈的Sequential API和Socket API,则必须使用底层操作系统提供的邮箱与信号量机制,这时内核要求移植者提供一个称为sys_arch.c的操作系统模拟层文件,该文件主要完成对操作系统中邮箱与信号量函数的封装。需要注意,只有在提供文件sys_arch.c 的基础上,文件`sys.c 才有效,换句话说,在无操作系统环境下运行LwIP 时,sys.c文件都不会被编译。
- tcp.c
- 包含了对TCP 控制块操作的函数,也包括了TCP 定时处理函数
- tcp_in.c
- 包含了TCP 协议中数据接收、处理相关的函数,最重要的TCP 状态机函数也在这个文件中
- tcp_out.c
- 含了TCP 中数据发送相关的函数,例如数据包发送函数、超时重传函数等
- timeouts.c
- 统一完成了对内核各个协议定时事件处理函数的封装,同时对各个注册的定时事件进行处理。在有无操作系统模拟层的环境下,timeouts.c 采用不同的方法来实现定时:在无操作系统模拟层时,timeouts.c使用系统时钟函数sys_now(不同的平台的都需要移植该函数)来获得当前系统时间,从而可以判断出各个事件是否超时;在有操作系统模拟层时,timers.c 实现了对操作系统模拟层邮箱等待函数的再次封装,得到一个具有协议栈特色的邮箱操作函数。 所谓特色,就是在邮箱等待函数中加入一种机制,在邮箱上等待消息的同时,可以同时实现协议栈中各个定时事件的正确处理
- udp.c
- 包含了实现UDP 协议的相关函数,包括UDP 控制块管理、UDP 数据包发送函数、UDP 数据包接收函数等
- /compat
- /posix 主要是针对POSIX标准,进行的封装,里面的文件非常简单,基本都是对外部的引用。
- /lwip LwIP的各种头文件,其中需要注意的有
opt.h
文件,它包含了所有LwIP内核参数的默认配置值;还有init.h
文件,它包含了与当前 LwIP 源代码信息相关的宏定义,如协议版本号、是否为官方版等
- /apps
- 主要是针对源码中 app目录的各头文件
- /priv
- LwIP内部使用的头文件,禁止外部使用
- /prot
- 这个文件夹中主要是针对TCP/IP规约的各种定义。与netif文件夹中同名文件区别在于netif文件夹中的文件均为对外提供的API,本目录才是对规约层各种结构的定义。
该目录下主要包含通用网络接口设备驱动程序。实现最底层的相关协议,该部分的多数源码基本与与硬件相关
- /ppp
- 包含了PPP 协议实现的源代码。PPP 协议即点对点协议,它提供了 一种在点对点线路上传输多协议数据包的标准格式,PPP 协议为链路的建立、控制与认证提供 了标准。起初PPP 主要用来替代SLIP 这种简单的串行链路数据传输协议,但是由于其完整的认证机制,后来在以太网上也引入了PPP 机制,即PPPoE,它已成为近年来小区宽带拨号上网的主要方式。使用PPPoE,为用户的上网计费、配置、接入等提供了方便。LwIP 提供了对PPPoE 的支持,在ppp文件夹下的PPPoE.c文件中有相关的函数实现。
- ethernet.c
- 以太网接口的共享代码。两个函数:用来从网卡接收以太网数据包的函数和在网卡上发送以太网数据包的函数
- bridgeif.c 与 bridgeif_fdb.c
- 桥接相关,暂时未使用
- lowpan6.c
6LoWPAN协议的实现文件
6LoWPAN是一种基于IPv6的低速无线个域网标准,即IPv6 over IEEE 802.15.4
- lowpan6_ble.c
- 6LowPAN over BLE output for IPv6 (RFC7668).
- lowpan6_common.c
- Common 6LowPAN routines for IPv6
- slipif.c
- SLIP即串行链路IP,它提供了一种在串行链路上传送IP 数据包的函数定义。SLIP 协议比较简单,它只是定义了一系列的字符,以实现对链路上的IP数据包封装和发送,除此之外,它不提供任何寻址、错误检测、包类型识别机制,因此相关驱动程序的实现也比较简单.它需要一个sio(串行I/O)模块才能工作
- zepif.c
- 实现ZigBee封装协议(ZEP)的netif。
- ethernetif.c
包含了与以太网网卡密切相关的初始化、发送、接收等函数的实现。
注意:开发者根据自己的需求修改。
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) 基于回调函数开发应用程序时的思维过程比较复杂。在后面与 RAW/CallbackAPI 相关的章节中可以看到, 利用回调函数去实现复杂的业务逻辑时, 会很麻烦,而且代码的可读性较差。
(2) 在操作系统环境中, 应用程序代码与内核代码处于同一个线程,虽然能够节省任务间通信和切换任务的开销,但是相应地,应用程序的执行会制约内核程序的执行,不同的应用程序之间也会互相制约。 在应用程序执行的过程中,内核程序将不可能得到运行,这会影响网络数据包的处理效率。如果应用程序占用的时间过长,而且碰巧这时又有大量的数据包到达, 由于内核代码长期得不到执行,网卡接收缓存里的数据包就持续积累,到最后很可能因为满载而丢弃一些数据包,从而造成丢包的现象。
在操作系统环境中,可以使用 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 的应用程序处在不同的线程中,我们可以根据任务的重要性,分配不同的优先级给这些线程,从而保证重要任务的时效性, 分配优先级的原则具体见下表。
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 内核所使用数据结构有一定的了解。
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 实现的,所以效率上相较前者要打个折扣。
文章浏览阅读2k次。20220328磁盘扩容ASP.NET项目创建_vs2022 asp.net core mvc
文章浏览阅读79次。TouchAction1.源码可以在这个路径找到:Lib\site-packages\appium\webdriver\common\touch_action.pyclass TouchAction(object): def __init__(self, driver=None): self._driver = driver self._actions ..._tkinter九宫格解锁
文章浏览阅读7.1k次。/Users/pro/anaconda/lib/python2.7/site-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment. warnings.warn('Matplotlib is_matplotlib is building the font cache; this may take a moment.
文章浏览阅读373次。<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"><struts..._struts2 xml标签
文章浏览阅读89次。据英国媒体报道,中国种植的中药八角被发现是世界上对抗禽流感的唯一良方。 八角又名大茴香,是木兰科期莽草属树的果实,形若星状,因而得名。八角果实内含莽草酸成分,莽草酸是制造世界上唯一能抑制禽流感药物达菲的材料。 资料显示,全球八角90%在中国,广西是中国八角的主要产区。 但并非所有的八角都能被用作原材料。拥有“达菲”制造专利的瑞士药剂商罗氏集团声称,只有来自中国..._八角茴香是我国南亚热带地区的珍贵经济林树种,主要生长于我国广西等4个省份,每年
文章浏览阅读1.4k次。介绍在之前的文章中介绍过MongoDB的可视化管理工具,有Web端的,也有桌面端的,怎么用都大同小异,今天介绍的adminMongo也是一个MongoDB的Web端可视化管理工具,界面美观,安装操作简单。而且依旧是开源免费的。特性从连接级别进行管理,以便轻松访问多个数据库创建/删除数据库备份/恢复数据库创建/删除/编辑集合创建/删除/编辑文档创建/删除索引查询文件收集统计以JSON格式导出集合服务..._adminmongo
文章浏览阅读293次。作为编程人员,在工作中快速生成自己常用且实用的代码块,可以打打提高工作效率。因此,自己闲暇之余研究了一下如何生成自定义的vue代码块。首先 ,你需要进入到对应的配置页面(文件 -->首选项 --> -->用户片段) /(设置按钮 --> 用户代码片段)输入vue选择vue.json进入配置界面;输入如下代码块{ "Print to console": { "prefix": "vh", "body": [ "&l_vscode 自定义代码块,让鼠标自动聚焦
文章浏览阅读1.4k次。Java中String对象的replaceAll方法调用性能优化小技巧_replaceall 性能
文章浏览阅读570次。smallint 类型需先转为 int 类型,再转为布尔类型。_cannot cast type smallint to date 位置:106
文章浏览阅读3.7k次。React Fiber 是 React 框架的一种底层架构,为了改进 React 的渲染引擎,使其更加高效、灵活和可扩展。_react fiber
文章浏览阅读7.2k次,点赞7次,收藏49次。现在的开发环境比较多,在学习的过程中,经常会使用到不同的开发环境,最常用的就是TensorFlow和pytorch,以及其他的开发环境,我在学习的过程中使用的开发环境主要有pytorch和TensorFlow两种。今天我从百度中学习如何安装多种深度学习开发环境。查看Python环境的方法conda info --env:可以看到所有python环境,前面有个‘*’的代表当前环境1.创建pytorch开发环境..._安装pytorch后安装tensorflow
文章浏览阅读4k次,点赞6次,收藏25次。可能很多朋友对GPIO_TypeDef里的各个寄存器还不太了解,更会疑惑为何有了ODR,还要使用BSRR和BRR,下面我就我的认识,做一下简单的说明ODR寄存器可读可写:既能控制管脚为高电平,也能控制管脚为低电平。管脚对于位写1 gpio 管脚为高电平,写 0 为低电平·缺点:会因中断而打断,关闭中断明显会延迟或丢失一事件的捕获,所以控制GPIO的状态最好还是用BSRR和BRRBSRR 只写..._stm32odr是啥意思