【Embedded Development】【ETH】【Lwip】基于STM32的ETH-Lwip以太网通信的学习记录

前言

由互联网-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 灵感之源与诞生

  1. 背景与挑战

    1973年,施乐公司帕洛阿尔托研究中心(PARC)的年轻工程师罗伯特·梅特卡夫面临一个问题:如何将研究所内的第一台个人电脑"Alto"相互连接,实现资源共享和信息互通。

  2. 灵感来源

    梅特卡夫的解决方案灵感来自夏威夷大学的 ALOHAnet 网络。他创造性地借鉴了 ALOHAnet 的无线电通信思想,并对其进行改进,形成了一种适用于有线网络的新型协议。


2.2 核心技术:CSMA/CD(以太网的"交通规则")

  1. 定义

    CSMA/CD 全称为 载波监听多点接入/碰撞检测(Carrier Sense Multiple Access with Collision Detection)。

  2. 工作机制(三步理解)

    • 先听后说:每台电脑在发送数据前,先"听"一下网线上是否有数据在传输。如果没有,则开始发送。

    • 边听边说:在发送数据的同时,电脑依然保持"监听"。

    • 冲突处理:若两台电脑同时发现网络空闲并一起发送,就会产生"冲突"。一旦检测到冲突,所有发送方立即停止发送,并各自等待一个随机的时间后再重新尝试。

  3. 核心目标

    这套机制是为了"公平地"利用共享的网络通道。


2.3 从实验室到世界标准:标准化之路

  1. 从 Xerox 到 DIX

    • 施乐公司起初未大力推广该技术。

    • 1980 年,施乐联合 DEC(美国数字设备公司)英特尔公司 ,共同推出 10 Mbps 的以太网标准,即 DIX 标准,并以此为基础向 IEEE 提交提案。

  2. IEEE 802.3 的诞生

    • 1983 年,IEEE(电气和电子工程师协会) 正式批准基于 DIX 的以太网标准,命名为 IEEE 802.3

    • 这是以太网发展史上最重要的转折点:开放的、非专属的标准被全世界接纳,为大规莫商业化铺平了道路。


2.4 演进之路:速率与介质的飞跃

  1. 速率里程碑(部分重要代际)
代际 速率 正式标准颁布时间
标准以太网 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年进展中
  1. 介质的演变

    • 从最初的 同轴电缆 → 更易使用的 双绞线 → 远距离高带宽的 光纤
  2. 网络结构的进化

    • 早期总线型网络中的 共享式集线器 → 全双工、无冲突的 交换式网络,大幅提升了效率。
  3. 特别说明(避免混淆)

    • 截至 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)通常将 MACPHY 两个功能模块集成在同一颗芯片内,例如常见的 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 时钟方案

  1. 通过 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。

配置方式

  1. 时钟源 :外部电路提供 50MHz 参考时钟
  2. 连接方式
    • 一路接 STM32F4xx 的 REF_CLK 引脚(MAC 的 RMII 接口时钟)。
    • 另一路接 LAN8720A 的 XTAL1/CLKIN 引脚(PHY 的工作时钟)。
  3. 引脚功能
    • nINT/REFCLKO 引脚作为 中断输出(低电平有效) 使用,不能输出时钟。
    • XTAL2 引脚悬空或按常规晶振处理(但此时不使用晶振,时钟由外部提供)。
特点
  • 是 RMII 标准的传统用法。
  • 硬件成本略高(需要额外的 50MHz 时钟源)。
  • 保留了 PHY 的中断功能。

(2)时钟输出模式(REF_CLK Out Mode /nINTSEL = 0)

核心逻辑

  • 模式触发条件nINTSEL 引脚拉低(需要外接下拉电阻,覆盖默认上拉)。
  • 时钟流向:PHY 内部通过 PLL 倍频,把 25MHz 源变成 50MHz,输出给 MAC。

配置方式(两种输入源)

1. 方案 A:25MHz 晶振输入(最常用,BOM 成本最低)
  1. 时钟源 :在 XTAL1XTAL2 之间接一个 25MHz 石英晶振
  2. PHY 内部处理:LAN8720A 内部 PLL 把 25MHz 倍频到 50MHz。
  3. 输出给 MAC :50MHz 时钟从 nINT/REFCLKO 引脚输出,接到 STM32F4xx 的 REF_CLK 引脚。
  4. 引脚功能变化
    • nINT/REFCLKO 只做时钟输出,中断功能被禁用
    • XTAL1/CLKIN 由晶振提供 25MHz 时钟,XTAL2 接晶振另一端即可。
2. 方案 B:外部 25MHz 时钟源输入(选择这个)
  1. 时钟源 :外部电路提供 25MHz 时钟信号
  2. 连接方式 :外部 25MHz 时钟直接接到 XTAL1/CLKIN 引脚。
  3. PHY 内部处理:和晶振方案一样,内部 PLL 倍频到 50MHz。
  4. 输出给 MAC :50MHz 时钟同样从 nINT/REFCLKO 输出给 STM32 的 REF_CLK
  5. 引脚功能nINT/REFCLKO 仅做时钟输出,中断功能同样被禁用。

特点

  • 非 RMII 标准模式,需做时序分析以确保 MAC 和 PHY 时序兼容。
  • 成本低,只需一个 25MHz 晶振 / 时钟源。
  • 牺牲了 PHY 的中断功能。

提示:时钟输入模式(REF_CLK In Mode)到底是干嘛的?

它是 RMII 接口的标准传统用法 ,也是用于单片机的ETH的一种配置方案,核心作用是:

  1. 由外部统一提供稳定的 50MHz 时钟 ,同时供给 MAC(单片机)和 PHY(LAN8720A);
  2. 这种设计下,时钟源是外部的(比如独立的 50MHz 晶振 / 振荡器),LAN8720A 只作为时钟的 "接收方";
  3. 因为时钟不是由 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博客

lan8720a_lan8720a采购信息-立创电子元器件商城

stm32+lwip(二):UDP测试 - 54zorb - 博客园

相关推荐
阿钱真强道5 天前
28 鸿蒙LiteOS RK2206 LwIP Raw API 实现无阻塞UDP双向通信
udp·harmonyos·鸿蒙·lwip·开源鸿蒙
nanaki502136 天前
05-LWIP(内核框架)
网络·lwip
nanaki5021312 天前
04-LWIP(网络数据包PBUF)
网络·lwip
记帖14 天前
STM32C542开发(1)----点亮LED
嵌入式硬件·stm32cubemx·stm32cubeide·stm32cubemx2·stm32c542cct6
记帖14 天前
陀螺仪LSM6DSV80X开发(6)----单双击检测
stm32cubemx·mems·42688·lsm6dsv80x·stm32h503cbt6
记帖14 天前
STM32C542开发(2)----BOOT_SEL设置
stm32·stm32cubemx·stm32cubeide·stm32cubemx2·stm32c542cct6·boot_set·串口烧录
weifengdq2 个月前
LAN8671 10BASE-T1S STM32F407 RMII LwIP 测试笔记
stm32·lwip·iperf·rmii·10base-t1s·lan8671
送外卖的CV工程师2 个月前
STM32 CubeMX Makefile 工程编译 入门指南
stm32·单片机·嵌入式硬件·学习·makefile·stm32cubemx
nanaki502133 个月前
LWIP --------- netif网卡接口
网络·lwip