前言
由互联网-TCP/IP-以太网的顺序理解。互联网技术的诞生,彻底改变了人类信息交流的方式。如今,从云端数据中心到身边的嵌入式设备,几乎每一种电子系统都在以某种形式接入这张庞大的网络。而支撑互联网运转的核心,是一套名为 TCP/IP 的协议体系------它定义了数据如何在不同的设备、不同的网络之间可靠传输,是互联网事实上的"通用语言"。
TCP/IP 通常采用四层模型(应用层、传输层、网际互联层、网络接口层),每一层各司其职。然而,协议栈的实现最终需要落地到具体的物理介质上。
对于有线网络而言,这一底层基础就是 以太网(Ethernet)------它对应 TCP/IP 模型的网络接口层,规范了数据如何在网线、网卡、交换机之间以帧的形式传递。
在嵌入式领域(尤其是 STM32 平台),我们经常需要在不运行操作系统的裸机环境下实现以太网通信。这时,硬件层面依赖芯片内置的 ETH 外设与外部 PHY 芯片,软件层面则引入轻量级 TCP/IP 协议栈 LwIP (TCP/IP完整协议内容太大,从而引入轻量化的LwIP)。从理解"互联网如何工作",到掌握"TCP/IP 如何分层",再到亲手驱动"以太网控制器",是一条从理论到实践、从宏观到微观的必经之路。
一、ETH( **Ethernet,**以太网)
互联网技术对人类社会的影响甚大。如今,绝大多数电子设备都能以不同方式接入互联网。在家庭环境中,常见的做法是使用路由器组建小型局域网(LAN),然后通过互联网专线或调制解调器(Modem)经电话线网络连接到互联网服务提供商(ISP),由ISP将用户的局域网接入互联网。而在企业或学校等场景中,局域网规模通常较大,一般采用交换机组成局域网,再经过路由设备以不同的方式接入互联网。
以太网(Ethernet)是指遵循 IEEE 802.3 标准组建的局域网技术,属于互联网技术的一种。由于以太网在各类组网技术中应用最为广泛,很多人会直接将"以太网"理解为"互联网"。
二、ETH的发展
2.1 灵感之源与诞生
-
背景与挑战
1973年,施乐公司帕洛阿尔托研究中心(PARC)的年轻工程师罗伯特·梅特卡夫面临一个问题:如何将研究所内的第一台个人电脑"Alto"相互连接,实现资源共享和信息互通。
-
灵感来源
梅特卡夫的解决方案灵感来自夏威夷大学的 ALOHAnet 网络。他创造性地借鉴了 ALOHAnet 的无线电通信思想,并对其进行改进,形成了一种适用于有线网络的新型协议。
2.2 核心技术:CSMA/CD(以太网的"交通规则")
-
定义
CSMA/CD 全称为 载波监听多点接入/碰撞检测(Carrier Sense Multiple Access with Collision Detection)。
-
工作机制(三步理解)
-
先听后说:每台电脑在发送数据前,先"听"一下网线上是否有数据在传输。如果没有,则开始发送。
-
边听边说:在发送数据的同时,电脑依然保持"监听"。
-
冲突处理:若两台电脑同时发现网络空闲并一起发送,就会产生"冲突"。一旦检测到冲突,所有发送方立即停止发送,并各自等待一个随机的时间后再重新尝试。
-
-
核心目标
这套机制是为了"公平地"利用共享的网络通道。
2.3 从实验室到世界标准:标准化之路
-
从 Xerox 到 DIX
-
施乐公司起初未大力推广该技术。
-
1980 年,施乐联合 DEC(美国数字设备公司) 和 英特尔公司 ,共同推出 10 Mbps 的以太网标准,即 DIX 标准,并以此为基础向 IEEE 提交提案。
-
-
IEEE 802.3 的诞生
-
1983 年,IEEE(电气和电子工程师协会) 正式批准基于 DIX 的以太网标准,命名为 IEEE 802.3。
-
这是以太网发展史上最重要的转折点:开放的、非专属的标准被全世界接纳,为大规莫商业化铺平了道路。
-
2.4 演进之路:速率与介质的飞跃
- 速率里程碑(部分重要代际)
| 代际 | 速率 | 正式标准颁布时间 |
|---|---|---|
| 标准以太网 | 10 Mbit/s | 1983年 (IEEE 802.3) |
| 快速以太网 | 100 Mbit/s | 1995年 (IEEE 802.3u) |
| 千兆以太网 | 1 Gbit/s | 1998年 (IEEE 802.3z/ab) |
| 万兆以太网 | 10 Gbit/s | 2002年 (IEEE 802.3ae) |
| 百吉以太网 | 100 Gbit/s | 2010年 (IEEE 802.3ba) |
| 太比特以太网 | 400/800 Gbit/s, 1.6 Tbit/s | 2017年 (802.3bs) / 2024年进展中 |
-
介质的演变
- 从最初的 同轴电缆 → 更易使用的 双绞线 → 远距离高带宽的 光纤。
-
网络结构的进化
- 早期总线型网络中的 共享式集线器 → 全双工、无冲突的 交换式网络,大幅提升了效率。
-
特别说明(避免混淆)
-
截至 2026 年,以太坊网络(全球第二大加密货币平台)已演进到第 16 个版本"Pectra升级",ETH 币价一度突破 4000 美元/枚。
-
这是 以太坊 的升级,与物理层/数据链路层的 IEEE 802.3 以太网标准 无关。二者共享了"以太"(Ether)这一哲学术语的命名,但技术层面完全不同。
-
现在的光纤就是以太网的常用形式,以太网是基于互联网以及其TCP/IP而发展出来,理解网络模型后,进一步再次理解以太网是怎么进行工作的,从而明白集成在高性能单片机上的ETH接口怎么去使用。
三、网络模型与TCP/IP协议簇
通信至少涉及两个设备,需要相互兼容的硬件和软件支持,这种约定称为通信协议。以太网通信结构较为复杂,国际标准化组织(ISO)为此制定了OSI参考模型,将整个通信过程划分为七层,从高到低依次为:应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。每一层功能不同,在通信中各司其职,整个模型涵盖了硬件和软件的定义。需要注意的是,OSI模型是一个理想化的分层模型,实际的网络系统通常只涉及其中几层。
了解TCP/IP协议参考:
【Embedded Development】【TCP-IP】关于TCP-IP网络协议的学习记录以及基于TCP-IP网络协议的上层协议的初步理解_同轴 网线 接口 协议-CSDN博客
3.1 定义上的七层模型

3.2 常用于实际中描述TCP/IP的五层模型(更方便理解)


以太网的物理介质主要包括同轴电缆(已淘汰)、双绞线(最普及)和光纤(高速长距离)。
在TCP/IP混合参考模型中,数据链路层进一步被划分为逻辑链路层(LLC)和介质访问控制层(MAC)。对于绝大多数接入网络的终端设备,LLC层与MAC层之间恰好构成了硬件与软件的分界线:物理层和MAC子层通常由硬件实现,而LLC层及其以上的网络协议则由软件完成。
正因如此,不同网络技术的硬件差异非常显著------例如以太网和Wi‑Fi,前者通过双绞线或光纤传输电/光信号,后者通过电磁波进行无线通信,二者在物理层的调制方式、接口定义以及MAC层的载波侦听与冲突处理机制上都截然不同,因此网卡-对应以太网(或无线网卡-对应wifi)的硬件设计也完全不同。
然而,在LLC层及以上,无论是运行在PC上的完整TCP/IP协议栈,还是嵌入式系统中的轻量级协议栈,其软件逻辑并没有本质区别,只是PC通常支持所有标准协议,功能完整,而嵌入式领域会根据资源限制对协议栈进行适当裁剪。换言之,以太网和Wi‑Fi的差异主要体现在硬件层面,而软件层面的处理方式高度统一,这正是TCP/IP模型能够屏蔽底层物理介质差异、实现异构网络互联的关键所在。
所以,在MAC层之上的LLC层、网络层、传输层和应用层,其协议基本相同,这些层次都由软件实现,并对各层功能进行封装。按照TCP/IP协议模型:
(1)MAC层由具体的硬件设备决定。
LLC层负责处理传输错误,调节数据流,协调收发双方的数据速率,防止发送方过快导致接收方丢失数据,主要使用数据链路层协议。
(2)网络层(也称IP层)在LLC层将数据从线路一端送到另一端的基础上,解决不同网络之间的路由拓扑和路径选择问题,关键协议有IP和ICMP。
(3)传输层则在网络层建立的端到端路径之上,处理端到端的通信,主要协议包括TCP和UDP。
(4)应用层通过调用传输层接口编写具体的应用程序,而TCP/IP协议族本身也包含一些简单的应用,如Telnet远程登录、FTP文件传输、SMTP邮件传输等。
在实际发送数据时,数据经过网络协议栈的每一层,都会添加一个当前层的头部,再传递给下一层;接收方则逐层去掉对应的头部,将原始数据向上递交。
3.3 某些情况下会使用四层模型描述TCP/IP

四、对于STM32的以太网外设(ETH)的硬件部分
注:具体细节可查看STM32F4xx系列手册
软件层面比较统一,主要看数据链路层中的MAC层,和物理层方面的适配。
4.1 查看STM32F4系列手册


4.2 查看STM32中关于ETH中的具体的内部物理结构的抽象图


4.3 类比理解
在典型的个人电脑(PC)中,网卡(Network Interface Card,NIC)通常将 MAC 和 PHY 两个功能模块集成在同一颗芯片内,例如常见的 Realtek RTL8111、Intel I219-V 等。但在概念上,两者仍是分离的:
-
单片机的 MAC(以太网外设) :对应电脑网卡中的 MAC 控制器 部分。它的职责是处理数据链路层的介质访问控制、帧的封装与解析、CRC校验、DMA数据传输等。在电脑中,这部分逻辑通常集成在网卡芯片内部,或者作为南桥/CPU中的一部分(例如某些 SoC 型处理器)。
-
外部 PHY 芯片 :对应电脑网卡中的 PHY 收发器 部分。它的职责是处理物理层的信号转换、线路编码、模拟前端、自动协商、MDI/MDIX 等。在独立网卡或主板集成网卡上,PHY 与 MAC 封装在同一芯片中,但依然存在独立的 PHY 模块(有时仍称为 PHY 芯片)。
因此,更直接的类比是:
| STM32 以太网方案 | 个人电脑网卡 |
|---|---|
| STM32 内置的 MAC(ETH 外设) | 网卡中的 MAC 控制器(逻辑单元) |
| 外接的 PHY 芯片(如 LAN8720、DP83848) | 网卡中的 PHY 收发器(物理层单元) |
| RMII/MII 接口 | 芯片内部或板级走线上的 MAC-PHY 总线 |
| SMI 总线(MDC/MDIO) | 网卡内部的 管理总线(用于配置 PHY 寄存器) |
| STM32 的 CPU + 内存 + DMA | 电脑的 CPU + 内存 + 总线(例如 PCIe 或内部总线) |
需要注意的是,在大多数现代电脑上,用户看不到独立的 PHY 芯片,因为集成度极高;但在嵌入式开发板上,由于 STM32 没有集成 PHY(除非某些特定型号,如 STM32F407 内置了 PHY,实际上大部分 STM32 需要外接 PHY),所以必须外接一颗独立的 PHY 芯片。这个外接的 PHY 所起的作用,与电脑网卡内部隐藏的 PHY 模块完全相同。
4.4 物理链路的简要说明
STM32芯片中的ETH外设,通过专用的DMA去操作介质访问控制器(MAC),再通过MII或者RMII接口去访问或者操作外部的PHY芯片(比如LAN8720A),后续再通过网线进行传输数据
4.5 物理接口:SMI(Station Management Interface,站管理接口)

SMI(站管理接口)是MAC内核用于访问PHY寄存器的专用接口,由两根信号线组成:数据线MDIO和时钟线MDC。该接口最多可支持访问32个PHY,在需要多个网口的设备中非常实用,但通常情况下一个设备只使用一个PHY。PHY芯片内部一般包含32个16位寄存器,用于配置其属性、工作环境、状态指示等,不过许多PHY并未用到全部寄存器位。MAC内核通过SMI接口向PHY寄存器写入数据或读取PHY状态,每次只能对其中一个PHY的某一个寄存器进行访问。SMI的最大通信频率为2.5 MHz,通过配置以太网MAC MII地址寄存器(ETH_MACMIIAR)中的CR位,可以选择合适的时钟频率。
4.5.1 SMI帧格式

当以太网MAC MII地址寄存器 (ETH_MACMIIAR)的写入位和繁忙位被置1时,SMI将向指定的PHY芯片指定寄存器写入ETH_MACMIIDR中的数据。

当以太网MAC MII地址寄存器 (ETH_MACMIIAR)的写入位为0并且繁忙位被置1时,SMI将从向指定的PHY芯片指定寄存器读取数据到ETH_MACMIIDR内。

4.6 物理接口:MII(Media Independent Interface,介质独立接口)(MII和RMII两者模式选择其中一个作为连接方式)
介质独立接口 (MII) 定义了 10 Mbit/s 和 100 Mbit/s 的数据传输速率下 MAC 子层与 PHY 之 间的互连。



因为要达到100Mbit/s传输速度,MII和RMII数据线数量不同,使用MII和RMII在时钟线的设计是完全不同的。对于MII接口,一般是外部为PHY提供25MHz时钟源,再由PHY提供TX_CLK和RX_CLK时钟。对于RMII接口,一般需要外部直接提供50MHz时钟源,同时接入MAC和PHY。


4.7 物理接口:RMII(Reduced Media Independent Interface, 简化介质独立接口(或精简介质独立接口))(MII和RMII两者模式选择其中一个作为连接方式)
注:开发板板载的PHY芯片型号为LAN8720A,该芯片只支持RMII接口。

精简介质独立接口 (RMII) 规范降低了 10/100 Mbit/s 下微控制器以太网外设与外部 PHY 间的 引脚数。根据 IEEE 802.3u 标准,MII 包括 16 个数据和控制信号的引脚。RMII 规范将引脚 数减少为 7 个(引脚数减少 62.5%)。

4.8 时钟方案

- 通过 SYSCFG_PMC 寄存器中的位 23 MII_RMII_SEL 控制 MII/RMII 选择。 要节省引脚,需在同一个 GPIO 引脚上复用 RMII_REF_CK 和 MII_RX_CLK 这两个输入时 钟信号。
注意:后续的部分mac信息需要自行查看手册,这里只是需要了解,从而应用。
4.9 外部PHY硬件:LAN8720A
LAN8720A是SMSC公司(已被Microchip公司收购)设计的一个体积小、功耗低、全能型10/100Mbps的以太网物理层收发器。它是针对消费类电子和企业应用而设计的。LAN8720A总共只有24Pin,仅支持RMII接口。

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

相关接线

五、对于STM32的以太网外设(ETH)的软件部分(即LwIP:轻型TCP/IP协议栈)
LwIP是Light Weight Internet Protocol 的缩写,是由瑞士计算机科学院Adam Dunkels等开发的适用于嵌入式领域的开源轻量级TCP/IP协议栈。它可以移植到含有操作系统的平台中,也可以在无操作系统的平台下运行。由于它开源、占用的RAM和ROM比较少、支持较为完整的TCP/IP协议、且十分便于裁剪、调试,被广泛应用在中低端的32位控制器平台。可以访问网站:lwIP - A Lightweight TCP/IP stack - Summary Savannah 获取更多LwIP信息。
暂时不不考虑手动移植,而是使用STMCubeMX进行配置生成工程文件。
5.1 STMCubeMX配置过程(无操作系统)
注:使用的是STM32F407VET6+LAN8720A+RJ45
5.1.1 直接进入对应的芯片中进行配置,选择RMII

5.1.2 对比引脚是否正确


建议把ETH的中断打开

5.1.3 配置LAN8720A的复位引脚PC0,低电平有效,常态情况拉高,手动配置GPIO
参考(如有侵权,请联系删除):
【ETH】以太网----PHY芯片LAN8720A----电路原理图 - 知乎


5.1.4 开启中间件lwip协议栈

5.1.5 选择lwip基于的哪款PHY芯片,LAN8720A是属于LAN8742同一个系列


5.1.6 关闭DHCP,设置静态ip

以及开启callback可进行热插拔

5.1.7 设置静态ip,必须要与电脑的以太网线路再同一个网段下进行通信


同时打开电脑的以太网静态ip设置,将其改为同一个网段下的线路


5.1.8 开启外部高速时钟

5.1.9 开启SWD

5.1.10 使用MX的自动配置时钟树,但是,一定要注意LAN8720A需要额外的时钟提供,如果LAN8720A硬件上无外接晶振,就需要手动配置MCO1

只有开启MCO1 Clock out才能设置时钟树那里的MCO1时钟源,MCO1时钟源设为外部高速时钟,才能提供25MHz给PA8/XTAL1/CLKIN



原因见原理图和LAN8720A手册



nINTSEL拉低了。
LAN8720A 的 nINTSEL 引脚兼具功能选择作用,且与 LED2 引脚复用,常规采用下拉接法。该芯片提供两种时钟配置模式,适配与 STM32F4xx 芯片的时钟对接需求:
(1)nINTSEL 拉低(时钟输出模式,常用方案) 外部在 XTAL1、XTAL2 端外接 25MHz 晶振 (也可由 XTAL1/CLKIN 输入 25MHz 时钟),芯片内部 PLL 电路将 25MHz 时钟倍频至 50MHz,并通过 nINT/REFCLKO 引脚向外输出。该 50MHz 时钟可直接接入 STM32F4xx 的 REF_CLK 引脚,作为 MAC 控制器的参考时钟。此模式下,nINT/REFCLKO 引脚仅用作时钟输出,中断功能失效 。该方案无需额外配置 50MHz 时钟源,可有效降低硬件 BOM 成本。(简要叙述)
(2)nINTSEL 拉高(时钟输入模式) 由外部电路直接提供 50MHz 时钟信号 ,同时接入 STM32F4xx 的 REF_CLK 引脚与 LAN8720A 的 XTAL1/CLKIN 引脚。此模式下,nINT/REFCLKO 引脚保留中断功能 ,不再输出参考时钟。(简要叙述)
(1)时钟输入模式(REF_CLK In Mode /nINTSEL = 1)
核心逻辑
- 模式触发条件 :
nINTSEL引脚拉高(默认是内部上拉,不额外下拉时默认进入此模式)。 - 时钟流向:外部直接提供 50MHz 时钟源,同时供给 MAC 和 PHY。
配置方式
- 时钟源 :外部电路提供 50MHz 参考时钟。
- 连接方式 :
- 一路接 STM32F4xx 的 REF_CLK 引脚(MAC 的 RMII 接口时钟)。
- 另一路接 LAN8720A 的 XTAL1/CLKIN 引脚(PHY 的工作时钟)。
- 引脚功能 :
nINT/REFCLKO引脚作为 中断输出(低电平有效) 使用,不能输出时钟。XTAL2引脚悬空或按常规晶振处理(但此时不使用晶振,时钟由外部提供)。
特点
- 是 RMII 标准的传统用法。
- 硬件成本略高(需要额外的 50MHz 时钟源)。
- 保留了 PHY 的中断功能。

(2)时钟输出模式(REF_CLK Out Mode /nINTSEL = 0)
核心逻辑
- 模式触发条件 :
nINTSEL引脚拉低(需要外接下拉电阻,覆盖默认上拉)。 - 时钟流向:PHY 内部通过 PLL 倍频,把 25MHz 源变成 50MHz,输出给 MAC。
配置方式(两种输入源)
1. 方案 A:25MHz 晶振输入(最常用,BOM 成本最低)
- 时钟源 :在
XTAL1和XTAL2之间接一个 25MHz 石英晶振。 - PHY 内部处理:LAN8720A 内部 PLL 把 25MHz 倍频到 50MHz。
- 输出给 MAC :50MHz 时钟从
nINT/REFCLKO引脚输出,接到 STM32F4xx 的REF_CLK引脚。 - 引脚功能变化 :
nINT/REFCLKO只做时钟输出,中断功能被禁用。XTAL1/CLKIN由晶振提供 25MHz 时钟,XTAL2接晶振另一端即可。

2. 方案 B:外部 25MHz 时钟源输入(选择这个)
- 时钟源 :外部电路提供 25MHz 时钟信号。
- 连接方式 :外部 25MHz 时钟直接接到
XTAL1/CLKIN引脚。 - PHY 内部处理:和晶振方案一样,内部 PLL 倍频到 50MHz。
- 输出给 MAC :50MHz 时钟同样从
nINT/REFCLKO输出给 STM32 的REF_CLK。 - 引脚功能 :
nINT/REFCLKO仅做时钟输出,中断功能同样被禁用。
特点
- 非 RMII 标准模式,需做时序分析以确保 MAC 和 PHY 时序兼容。
- 成本低,只需一个 25MHz 晶振 / 时钟源。
- 牺牲了 PHY 的中断功能。

提示:时钟输入模式(REF_CLK In Mode)到底是干嘛的?
它是 RMII 接口的标准传统用法 ,也是用于单片机的ETH的一种配置方案,核心作用是:
- 由外部统一提供稳定的 50MHz 时钟 ,同时供给 MAC(单片机)和 PHY(LAN8720A);
- 这种设计下,时钟源是外部的(比如独立的 50MHz 晶振 / 振荡器),LAN8720A 只作为时钟的 "接收方";
- 因为时钟不是由 LAN8720A 产生的,nINT/REFCLKO引脚就可以被释放出来,作为低电平有效的中断输出引脚 使用(比如链路状态变化、数据收发完成等事件都可以触发中断)。
5.1.11 开启PA8复用为MCO1(RCC)复用功能

5.1.12 开启辅助功能
(1)开一个GPIO OUT,作为调试LED

(2)开串口1,准备使用printf


5.1.12 进行工程配置


5.1.13 生成代码

5.1.14 重定向


int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return ch;
}
5.1.15 一定要LAN8720复位


HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);
HAL_Delay(55);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_Delay(55);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);
HAL_Delay(55);
5.1.16 在主循环中调用
MX_LWIP_Process();
否则无法正常运行lwip
也可以加入测试LED查看是否正常
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
HAL_Delay(300);
main:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "lwip.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("\r\n1-----------------running\r\n");
MX_LWIP_Init();
/* USER CODE BEGIN 2 */
printf("\r\n2-----------------running\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
MX_LWIP_Process();
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
HAL_Delay(300);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
5.1.17 烧录运行
成功现象:串口正常打印信息,PE5的LED正常闪烁,RJ45端子的绿黄色灯在没有烧录任何程序前(以及即使插上网线和电脑网口)都是不会亮RJ45端子的两灯,成功运行lwip后,绿灯常亮,黄灯间隔闪烁,以及使用ping命令测试电脑的以太网和单片机的ETH的网路是否是通畅的。



5.1.18 失败现象的补充
(1)如果烧录lwip后,RJ45端子的绿黄色灯都不亮,大概率是因为MX_LWIP_Init在最开始的时候就没有配置好,检查ETH是否配置正确
(2)如果烧录lwip后,RJ45端子的只有黄色灯亮,大概率是因为MX_LWIP_Init配置到一半,就抛出到Error_Handler,很可能是因为实际电路图需要时钟输出模式的第二方案没有配置PA8的MCO1复用功能,去提供25MHz时钟
(3)如果运行到MX_LWIP_Init卡死,也可能是因为没有正确读到LAN8720A,需要配置好LAN8720A地址,如果LAN8720A地址引脚悬空(内部下拉),则地址是0;如果LAN8720A地址引脚外部上拉,则地址是1


#define LAN8742A_PHY_ADDRESS 0x00U
5.2 测试udp client(无RTOS)
例程参考
STSW-STM32070 | Product - STMicroelectronics



5.2.1 直接写udp client模块文件

#ifndef __UDP_CLIENT_H__
#define __UDP_CLIENT_H__
#include <string.h>
#include <stdint.h>
#define UDP_CLIENT_REMOTE_IP "192.168.46.100" // 电脑端调试助手的IP
#define UDP_CLIENT_REMOTE_PORT 8881
#define UDP_CLIENT_PORT 8880 // 单片机本地绑定的端口
void udp_client_init(void);
void udp_client_send(char *pData);
#endif
#include "udp_client.h"
#include "lwip.h"
#include "udp.h"
static struct udp_pcb *upcb;
static void udp_receive_callback(void *arg, struct udp_pcb *upcb,
struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
uint32_t i;
/* 数据回传 */
// udp_send(upcb, p);
// udp_sendto(upcb, p, addr, port);
/* 打印接收到的数据 */
printf("get msg from %d:%d:%d:%d port:%d:\r\n",
*((uint8_t *)&addr->addr), *((uint8_t *)&addr->addr + 1),
*((uint8_t *)&addr->addr + 2), *((uint8_t *)&addr->addr + 3), port);
if (p != NULL)
{
struct pbuf *ptmp = p;
while(ptmp != NULL)
{
for (i = 0; i < p->len; i++)
{
printf("%c", *((char *)p->payload + i));
}
ptmp = p->next;
}
printf("\r\n");
}
/* 释放缓冲区数据 */
pbuf_free(p);
}
void udp_client_send(char *pData)
{
struct pbuf *p;
/* 分配缓冲区空间 */
p = pbuf_alloc(PBUF_TRANSPORT, strlen(pData), PBUF_POOL);
if (p != NULL)
{
/* 填充缓冲区数据 */
pbuf_take(p, pData, strlen(pData));
/* 发送udp数据 */
udp_send(upcb, p);
/* 释放缓冲区空间 */
pbuf_free(p);
}
}
/******************************************************************************
* 描述 : 创建udp客户端
* 参数 : 无
* 返回 : 无
******************************************************************************/
void udp_client_init(void)
{
ip_addr_t serverIP;
err_t err;
IP4_ADDR(&serverIP, 192, 168, 46, 100);
/* 创建udp控制块 */
upcb = udp_new();
if (upcb!=NULL)
{
/* 配置本地端口 */
upcb->local_port = UDP_CLIENT_PORT;
/* 配置服务器IP和端口 */
err= udp_connect(upcb, &serverIP, UDP_CLIENT_REMOTE_PORT);
if (err == ERR_OK)
{
/* 注册接收回调函数 */
udp_recv(upcb, udp_receive_callback, NULL);
printf("udp client connected\r\n");
}
else
{
udp_remove(upcb);
printf("can not connect udp pcb\r\n");
}
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("\r\n1-----------------running\r\n");
MX_LWIP_Init();
/* USER CODE BEGIN 2 */
printf("\r\n2-----------------running\r\n");
udp_client_init();
char str[] = "1234546\r\n";
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
MX_LWIP_Process();
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
HAL_Delay(300);
udp_client_send(str);
}
/* USER CODE END 3 */
}
main外部只需要导入头文件,然后再mian中操作
5.2.2 现象



5.3 测试udp server(无RTOS)
5.3.1 代码
#ifndef __UDP_SERVER_H__
#define __UDP_SERVER_H__
#include <string.h>
#include <stdint.h>
#define UDP_SERVER_REMOTE_IP "192.168.46.100" // 电脑端调试助手的IP
#define UDP_SERVER_REMOTE_PORT 8883
#define UDP_SERVER_PORT 8882 // 单片机本地绑定的端口
void udp_server_init(void);
#endif
#include "udp_server.h"
#include "lwip.h"
#include "udp.h"
static void udp_server_receive_callback(void *arg, struct udp_pcb *upcb,
struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
uint32_t i;
/* 数据回传 */
// udp_sendto(upcb, p, addr, port);
/* 打印接收到的数据 */
printf("get msg from %d:%d:%d:%d port:%d\r\n",
*((uint8_t *)&addr->addr), *((uint8_t *)&addr->addr + 1),
*((uint8_t *)&addr->addr + 2), *((uint8_t *)&addr->addr + 3), port);
if (p != NULL)
{
struct pbuf *ptmp = p;
while(ptmp != NULL)
{
for (i = 0; i < p->len; i++)
{
printf("%c", *((char *)p->payload + i));
}
ptmp = p->next;
}
printf("\r\n");
}
/* 释放缓冲区数据 */
pbuf_free(p);
}
void udp_server_init(void)
{
struct udp_pcb *upcb;
err_t err;
/* 创建udp控制块 */
upcb = udp_new();
if (upcb)
{
/* 绑定端口接收,接收对象为所有ip地址 */
err = udp_bind(upcb, IP_ADDR_ANY, UDP_SERVER_PORT);
if(err == ERR_OK)
{
/* 注册接收回调函数 */
udp_recv(upcb, udp_server_receive_callback, NULL);
}
else
{
/* 删除控制块 */
udp_remove(upcb);
printf("can not bind pcb\r\n");
}
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("\r\n1-----------------running\r\n");
MX_LWIP_Init();
/* USER CODE BEGIN 2 */
printf("\r\n2-----------------running\r\n");
// udp_client_init();
// char str[] = "1234546\r\n";
udp_server_init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
MX_LWIP_Process();
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
HAL_Delay(300);
// udp_client_send(str);
}
/* USER CODE END 3 */
}
5.3.2 现象




5.4 测试tcp client(有RTOS)
直接开FreeRTOS,使用socket或者netconn的api。在这里说明,尝试使用在无RTOS中多次使用RAW API,无论如何都无法测试tcp client成功。只好使用RTOS中的socket或者netconn的api,如果有知道原因的请在评论区说明。
以下皆是无RTOS下的RAW API尝试都失败了
#include "tcp_client.h"
#include "lwip.h"
#include "tcp.h"
#include "api.h"
//方法1-----------------------------------------------------------------------------
//static void client_err(void *arg, err_t err) //出现错误时调用这个函数,打印错误信息,并尝试重新连接
//{
// printf("client_err now\n");
//
// //连接失败的时候释放TCP控制块的内存
//// printf("关闭连接,释放TCP控制块内存\n");
// //tcp_close(client_pcb);
//
//
// //重新连接
// printf("restart tcp_client_init\n");
// tcp_client_init();
//
//}
//static err_t client_send(void *arg, struct tcp_pcb *tpcb) //发送函数,调用了tcp_write函数
//{
// uint8_t send_buf[]= "this is client_send test\r\n";
//
// //发送数据到服务器
// tcp_write(tpcb, send_buf, sizeof(send_buf), 1);
// tcp_output(tpcb);
//
// return ERR_OK;
//}
//static err_t client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
//{
// if (p != NULL)
// {
// /* 接收数据*/
// tcp_recved(tpcb, p->tot_len);
//
// /* 返回接收到的数据*/
// tcp_write(tpcb, p->payload, p->tot_len, 1);
//
// memset(p->payload, 0 , p->tot_len);
// pbuf_free(p);
// }
// else if (err == ERR_OK)
// {
// //服务器断开连接
// printf("server disconncted\r\n");
// tcp_close(tpcb);
//
// //重新连接
// tcp_client_init();
// }
// return ERR_OK;
//}
//static err_t client_connected(void *arg, struct tcp_pcb *pcb, err_t err)
//{
// printf("connected ok!\n");
//
// //注册一个周期性回调函数
// tcp_poll(pcb,client_send,2);
//
// //注册一个接收函数
// tcp_recv(pcb,client_recv);
//
// return ERR_OK;
//}
//void tcp_client_init(void)
//{
// struct tcp_pcb *client_pcb = NULL; //这一句一定要放在里面,否则会没用
// ip4_addr_t server_ip; //因为客户端要主动去连接服务器,所以要知道服务器的IP地址
// /* 创建一个TCP控制块 */
// client_pcb = tcp_new();
// IP4_ADDR(&server_ip, 192,168,46,100);//合并IP地址
// printf("tcp_client start\r\n");
//
// //注册异常处理
// tcp_err(client_pcb, client_err);
// printf("client_err callback created\r\n");
//
//
//
// //开始连接
// tcp_connect(client_pcb, &server_ip, TCP_CLIENT_REMOTE_PORT, client_connected);
// ip_set_option(client_pcb, SOF_KEEPALIVE);
//
// printf("used tcp_connect\r\n");
//}
//方法1-----------------------------------------------------------------------------
//方法2-----------------------------------------------------------------------------
//static err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb,
// struct pbuf *p, err_t err)
//{
// uint32_t i;
// /* 数据回传 */
// //tcp_write(tpcb, p->payload, p->len, 1);
// if (p != NULL)
// {
// struct pbuf *ptmp = p;
// /* 打印接收到的数据 */
// printf("get msg from %d:%d:%d:%d port:%d:\r\n",
// *((uint8_t *)&tpcb->remote_ip.addr),
// *((uint8_t *)&tpcb->remote_ip.addr + 1),
// *((uint8_t *)&tpcb->remote_ip.addr + 2),
// *((uint8_t *)&tpcb->remote_ip.addr + 3),
// tpcb->remote_port);
// while(ptmp != NULL)
// {
// for (i = 0; i < p->len; i++)
// {
// printf("%c", *((char *)p->payload + i));
// }
// ptmp = p->next;
// }
// printf("\r\n");
// tcp_recved(tpcb, p->tot_len);
// /* 释放缓冲区数据 */
// pbuf_free(p);
// }
// else if (err == ERR_OK)
// {
// printf("tcp client closed\r\n");
// tcp_recved(tpcb, p->tot_len);
// return tcp_close(tpcb);
// }
// return ERR_OK;
//}
//static err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
//{
// printf("tcp client connected\r\n");
// tcp_write(tpcb, "tcp client connected", strlen("tcp client connected"), 0);
// /* 注册接收回调函数 */
// tcp_recv(tpcb, tcp_client_recv);
// return ERR_OK;
//}
//void tcp_client_init(void)
//{
// struct tcp_pcb *tpcb;
// ip_addr_t serverIp;
// int i,ret;
// printf("tcp_client_init: flags=0x%x\r\n", netif_default->flags);
// /* 服务器IP */
// IP4_ADDR(&serverIp, 192, 168, 46, 100);
// /* 创建tcp控制块 */
// tpcb = tcp_new();
// if (tpcb != NULL)
// {
// err_t err;
// /* 绑定本地端号和IP地址 */
// err = tcp_bind(tpcb, IP_ADDR_ANY, TCP_CLIENT_PORT);
// tpcb->remote_ip = serverIp;
// printf("\r\nremote_ip:%x\r\n",tpcb->remote_ip.addr);
// if (err == ERR_OK)
// {
// /* 连接服务器 */
// for(i=0;i<5;i++)
// {
// printf("tcp_connect: flags=0x%x\r\n", netif_default->flags);
// HAL_Delay(1000);
// ret = tcp_connect(tpcb, &serverIp, TCP_CLIENT_REMOTE_PORT, tcp_client_connected);
// printf("Callback: %p, Expected: %p\r\n", tpcb->connected, (void*)tcp_client_connected);
// printf("State: %d\r\n", tpcb->state);
// if(ret == ERR_OK)break;
// }
// }
// else
// {
// memp_free(MEMP_TCP_PCB, tpcb);
// printf("can not bind pcb\r\n");
// }
// }
//}
//方法2-----------------------------------------------------------------------------
//方法3-----------------------------------------------------------------------------
//struct tcp_pcb *tcp_client_pcb;
//static void tcp_client_err(void *arg, err_t err)
//{
// printf("connect error! closed by core!!\r\n");
// printf("try to connect to server again!!\r\n");
// //连接失败的时候释放TCP控制块的内存
// tcp_abort(tcp_client_pcb);
// //重新连接
// tcp_client_init();
//}
//static err_t tcp_client_send(void *arg, struct tcp_pcb *tpcb)
//{
// uint8_t send_buf[]= "This is a TCP Client test...\n";
// //发送数据到服务器
// tcp_write(tpcb, send_buf, sizeof(send_buf), 1);
// return ERR_OK;
//}
//static err_t tcp_client_recv(void *arg,
// struct tcp_pcb *tpcb,
// struct pbuf *p,
// err_t err)
//{
// if (p != NULL)
// {
// /* 更新窗口 */
// tcp_recved(tpcb, p->tot_len);
// /* 返回接收到的数据*/
// tcp_write(tpcb, p->payload, p->tot_len, 1);
// memset(p->payload, 0 , p->tot_len);
// pbuf_free(p);
// }
// else if (err == ERR_OK)
// {
// //服务器断开连接
// printf("server has been disconnected!\n");
// tcp_close(tpcb);
// //重新连接
// tcp_client_init();
// }
// return ERR_OK;
//}
//static err_t tcp_client_connected(void *arg,
// struct tcp_pcb *pcb,
// err_t err)
// {
// printf("connected ok!\n");
// //注册一个周期性回调函数
// tcp_poll(pcb, tcp_client_send, 2);
// //注册一个接收函数
// tcp_recv(pcb, tcp_client_recv);
// return ERR_OK;
// }
//void tcp_client_init(void)
//{
// if(tcp_client_pcb != NULL)
// {
// printf("TCP 已存在,无需重复连接\r\n");
// return;
// }
//
// /* 创建一个TCP控制块 */
// tcp_client_pcb = tcp_new();
// if(tcp_client_pcb == NULL)
// {
// printf("tcp_new 失败,内存不足\r\n");
// return;
// }
//
// //注册异常处理
// tcp_err(tcp_client_pcb, tcp_client_err);
// tcp_bind(tcp_client_pcb, IP_ADDR_ANY, TCP_CLIENT_PORT);
// ip4_addr_t server_ip;
// IP4_ADDR(&server_ip, 192,168,46,100);
// printf("开始连接服务器 192.168.46.100:8881 \r\n");
// //开始连接
// err_t err_conn = tcp_connect(tcp_client_pcb, &server_ip, TCP_CLIENT_REMOTE_PORT, tcp_client_connected);
// if(err_conn != ERR_OK)
// {
// printf("tcp_connect start conn failed, err = %d \r\n", err_conn);
// tcp_abort(tcp_client_pcb);
// tcp_client_pcb = NULL;
// }
//}
//方法3-----------------------------------------------------------------------------
5.4.1 代码
#ifndef __TCP_CLIENT_H__
#define __TCP_CLIENT_H__
#include <string.h>
#include <stdint.h>
#define TCP_CLIENT_REMOTE_IP "192.168.46.100" // 电脑端调试助手的IP
#define TCP_CLIENT_REMOTE_PORT 6666
#define TCP_CLIENT_PORT 8880 // 单片机本地绑定的端口
void tcp_client_init(void);
#endif
#include "tcp_client.h"
#include "lwip.h"
#include "tcp.h"
#include "api.h"
void tcp_client_init(void)
{
uint8_t Server_IP[4] = {192,168,46,100};
uint16_t Server_Port = 6666;
struct netconn *conn;
ip_addr_t ipaddr;
err_t err = ERR_OK;
struct netbuf *rx_buff;
void *rx_data;
uint16_t rx_data_len;
char connect_ok[64] = "Successfully connected to Server!\r\n";
// 将数组Server_IP中IP地址转换合并到ipaddr中
IP4_ADDR(&ipaddr, Server_IP[0], Server_IP[1], Server_IP[2], Server_IP[3]);
/* Infinite loop */
for(;;)
{
conn = netconn_new(NETCONN_TCP); // 创建新的连接结构conn
if(conn == NULL)
{
osDelay(1000);
continue;
}
netif_set_link_up(netif_default);
err = netconn_connect(conn, &ipaddr, Server_Port); // 连接服务器
if(err != ERR_OK)
{
osDelay(1000);
netconn_delete(conn); // 删除连接结构体conn
continue;
}
printf("State: %d\r\n", conn->state);
netconn_write(conn, connect_ok, strlen(connect_ok), NETCONN_COPY);
while(netconn_recv(conn, &rx_buff) == ERR_OK) // 从服务器接收数据,LWIP默认阻塞式接收
{
do
{
netbuf_data(rx_buff, &rx_data, &rx_data_len); // 从接收结构体rx_buff中拷贝数据到rx_data
netconn_write(conn, rx_data, rx_data_len, NETCONN_COPY); // 向服务器发送消息
printf("%.*s\r\n",(int)rx_data_len, (const char*)rx_data);
memset(rx_data, 0, rx_data_len);
}
while(netbuf_next(rx_buff) >= 0); // netbuf中还有数据时 调用netbuf_next函数移动ptr指针指向下一个pbuf
netbuf_delete(rx_buff); // 删除接收结构体rx_buff
}
netbuf_delete(rx_buff); // 删除接收结构体rx_buff 只有当服务器关闭或出现其他错误才会运行到这里
netconn_close(conn); // 关闭连接到的服务器
netconn_delete(conn); // 删除连接结构conn
printf("Close connection!\r\n");
osDelay(10);
}
}
创建一个任务使用,必须等待lwip初始化完成

5.4.2 现象


5.5 测试tcp server(无RTOS)
5.5.1 代码
#ifndef __TCP_SERVER_H__
#define __TCP_SERVER_H__
#include <string.h>
#include <stdint.h>
#define TCP_SERVER_REMOTE_IP "192.168.46.100" // 电脑端调试助手的IP
#define TCP_SERVER_REMOTE_PORT 8883
#define TCP_SERVER_PORT 8882 // 单片机本地绑定的端口
void tcp_server_init(void);
#endif
cpp
#include "tcp_server.h"
#include "lwip.h"
#include "tcp.h"
static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err)
{
uint32_t i;
/* 数据回传 */
//tcp_write(tpcb, p->payload, p->len, 1);
if (p != NULL)
{
struct pbuf *ptmp = p;
/* 打印接收到的数据 */
printf("get msg from %d:%d:%d:%d port:%d:\r\n",
*((uint8_t *)&tpcb->remote_ip.addr),
*((uint8_t *)&tpcb->remote_ip.addr + 1),
*((uint8_t *)&tpcb->remote_ip.addr + 2),
*((uint8_t *)&tpcb->remote_ip.addr + 3),
tpcb->remote_port);
while(ptmp != NULL)
{
for (i = 0; i < p->len; i++)
{
printf("%c", *((char *)p->payload + i));
}
ptmp = p->next;
}
printf("\r\n");
tcp_recved(tpcb, p->tot_len);
/* 释放缓冲区数据 */
pbuf_free(p);
}
else if (err == ERR_OK)
{
printf("tcp client closed\r\n");
return tcp_close(tpcb);
}
return ERR_OK;
}
static err_t tcp_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
printf("tcp client connected\r\n");
printf("ip %d:%d:%d:%d port:%d\r\n",
*((uint8_t *)&newpcb->remote_ip.addr),
*((uint8_t *)&newpcb->remote_ip.addr + 1),
*((uint8_t *)&newpcb->remote_ip.addr + 2),
*((uint8_t *)&newpcb->remote_ip.addr + 3),
newpcb->remote_port);
tcp_write(newpcb, "tcp client connected", strlen("tcp client connected"), TCP_WRITE_FLAG_COPY);
tcp_output(newpcb); // 强制立即发送队列里的数据
/* 注册接收回调函数 */
tcp_recv(newpcb, tcp_server_recv);
return ERR_OK;
}
void tcp_server_init(void)
{
struct tcp_pcb *tpcb;
/* 创建tcp控制块 */
tpcb = tcp_new();
if (tpcb != NULL)
{
err_t err;
/* 绑定端口接收,接收对象为所有ip地址 */
err = tcp_bind(tpcb, IP_ADDR_ANY, TCP_SERVER_PORT);
if (err == ERR_OK)
{
/* 监听 */
tpcb = tcp_listen(tpcb);
/* 注册接入回调函数 */
tcp_accept(tpcb, tcp_server_accept);
printf("tcp server listening\r\n");
}
else
{
memp_free(MEMP_TCP_PCB, tpcb);
printf("can not bind pcb\r\n");
}
}
}
cpp
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_LWIP_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("\r\n2-----------------running\r\n");
// udp_client_init();
// char str[] = "1234546\r\n";
// udp_server_init();
// tcp_client_init();
tcp_server_init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
MX_LWIP_Process();
// HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
// HAL_Delay(300);
// udp_client_send(str);
// printf("\r\n-----------------running-----------------\r\n");
}
/* USER CODE END 3 */
}
5.5.2 现象



参考文章(如有侵权请联系删除):
38. ETH---Lwip以太网通信 --- 野火STM32 HAL库开发实战指南------基于野火F4系列开发板 文档
STM32CubeMX学习笔记(41)------ETH接口+LwIP协议栈使用(DHCP)_stm32cube学习笔记 eth lwip-CSDN博客
