ESP32使用LAN8720模块进行数据传输

开发板

基于 ESP32-WROOM-32 模组

Esp32DevKit32E 介绍

重要参数

串口芯片:CH340G

ROM:4M

RAM:520 KB

带有专用 DMA 的以太网 MAC 接口,支持 IEEE 1588

引脚说明

端口 输入 输出 备注信息
3.3V 3.3V电源输出
5V 5V电源输出
GND 接地
0 pulled up
1 TX Pin
2
3 RX Pin
4-5
6-11 模组集成SPI flash
12-33
34-35 输入引脚
36(VP) 输入引脚
39(VN) 输入引脚

通信模块

LAN8720 介绍:

LAN8720A 是低功耗的 10/100M 以太网 PHY 层芯片,I/O 引脚电压符合 IEEE802.3 - 2005 标准。它需配合集成 MAC 控制器的 MCU,通过 RMII 接口与以太网 MAC 层通信。芯片内置 10 - BASE - T/100BASE - TX 全双工传输模块,支持 10Mbps 和 100Mbps 速率。能通过自协商与目的主机确定最佳连接方式(速度和双工模式),还具备 HP Auto - MDIX 自动翻转功能,可免换网线实现直连与交叉连接的切换。

引脚使用说明:

PHY 芯片地址设置

  • 操作原理:MAC 层通过 SMI 总线对 PHY 内部寄存器读写,SMI 可控制 32 个 PHY 芯片,通过不同地址区分操作对象。
  • LAN8720A 配置:通过 PHYAD0 引脚(与 RXER 引脚复用)配置,芯片内部自带下拉电阻,默认值为 0。硬复位结束后,芯片读取该引脚电平作为 SMI 地址,接下拉电阻(或浮空)时 SMI 地址为 0,外接上拉电阻时为 1

nINT/REFCLKO 引脚功能配置

  • 功能选择:该引脚可作中断输出或参考时钟输出,由 LED2(nINTSEL)引脚决定。
  • 配置方式:芯片复位后读取 LED2 引脚值,接上拉电阻(或浮空,内置上拉电阻)时,nINT/REFCLKO 作为中断输出引脚(REF_CLK IN 模式);接下拉电阻时,作为参考时钟输出引脚(REF_CLK OUT 模式)。
  • 时钟要求:REF_CLK IN 模式下,外部需为 LAN8720A 的 XTAL1(CLKIN)引脚提供 50MHz 参考时钟;REF_CLK OUT 模式下,LAN8720A 可外接 25MHz 石英晶振,内部倍频到 50MHz 后通过 REFCLKO 引脚输出给 MAC 控制器。

RMII接口

  • 作用是达成以太网数据在 MCU 与 PHY 芯片之间的双向传输,为数据在两者间的交互提供通道,保障数据能顺利在 MCU 和 PHY 芯片之间流动。

SMI站点管理接口

  • 其功能是让 MCU 对 PHY 芯片内部的寄存器进行读(R)和写(W)操作,通过该接口,MCU 可对 PHY 芯片内部的各种参数和状态进行配置与查询,实现对 PHY 芯片的管理与控制。

nINT/REFCLKO引脚有两种功能模式,其模式选择由LED2(nINTSEL)引脚决定:

  • 中断输出模式(REF_CLK IN模式) :如果LED2引脚接上拉电阻(或者浮空,因为芯片内置上拉电阻),在芯片正常工作后,nINT/REFCLKO引脚将作为中断输出引脚。由于此时处于REF_CLK IN模式,外部需要提供50MHz参考时钟给LAN8720A的XTAL1(CLKIN)引脚。而模块上自带的50M晶振正好可以满足这个需求,为LAN8720提供稳定的时钟源。
  • 参考时钟输出模式(REF_CLK OUT模式) :当LED2引脚接下拉电阻时,正常工作后,nINT/REFCLKO引脚将作为参考时钟输出。不过在这种模式下,通常是外接25MHz石英晶振,通过内部倍频到50MHz,然后通过REFCLKO引脚输出50MHz参考时钟给MAC控制器。虽然模块自带50M晶振,但在该模式下可能不会使用这个晶振的输出作为REFCLKO的输出源(一般还是按设计用内部倍频方式),而是将其闲置,除非有特殊的电路设计和配置
  1. 外部直接提供50M时钟到REF_CLK,比如有源晶振,同时驱动MCU和PHY芯片。
  2. PHY芯片外接25M晶振,芯片内部倍频到50M,然后通过REF_CLK输出到MCU,(最常用的方案)。
  3. 由MCU配置引脚输出50M的时钟,然后将这个引脚接到REF_CLK ,同时驱动MCU和PHY芯片。

方案说明

方式一:MCU 集成 MAC 控制器,外接 PHY 芯片(本次方案)

  • 原理:MCU 内部集成 MAC 控制器(ETH),通过介质独立接口(MII)或简化介质独立接口(RMII)外挂一颗 PHY 芯片,利用站点管理接口(SMI)与外部 PHY 通讯,实现网口输出。
  • 适用场景:适用于内部有 MAC 控制器的 MCU,可借助外部 PHY 芯片完成物理层功能。
  • 示例芯片:LAN8720
  • 协议实现:MAC + PHY 完成 TCP/IP 五层模型的物理层和数据链路层,网络层、传输层、应用层需在 MCU 中使用开源 TCP/IP 协议栈(如 uIP、LwIP)实现。

方式二:MCU 无 MAC 控制器,外接 MAC 和 PHY 芯片

  • 原理:对于内部没有 MAC 控制器的 MCU,需外接 MAC 芯片和 PHY 芯片,这两颗芯片可以分立,也可集成在一颗芯片中。
  • 适用场景:针对没有集成 MAC 控制器的 MCU,补充其网络通信相关功能。
  • 示例芯片:ENC28J60
  • 协议实现:同方式一,MAC + PHY 负责物理层和数据链路层,其他高层协议在 MCU 用开源协议栈实现。

方式三:MCU 通过特定总线外挂集成 TCP/IP 协议的芯片

  • 原理:MCU 通过 SPI、8080 并行总线、串口等接口外挂一颗已经集成了 TCP/IP 协议的芯片,实现网口输出。
  • 适用场景:希望简化 MCU 开发,降低协议实现复杂度的场景。
  • 示例芯片:W5500
  • 协议实现:使用硬件 TCP/IP 协议栈,MCU 无需实现 TCP/IP 协议,仅需实现应用层代码,但存在 socket 个数有限、通信数据量不宜太大的缺点。

方式四:MCU 内部集成 PHY

  • 原理:部分 MCU 内部集成了 PHY,无需外挂额外的网络芯片即可实现网口输出。
  • 适用场景:对硬件体积、成本有严格要求,且对网络性能要求不是特别高的场景。
  • 示例芯片:沁恒 CH32V307(自带 10 兆以太网 PHY)

其他:高性能芯片集成 PHY 或通过 SDIO 外挂通信模组

  • 原理:高级嵌入式芯片内部集成 PHY 以实现网络功能,或者通过 SDIO 接口外挂通信模组来完成网口输出。
  • 适用场景:适用于对网络性能、功能扩展性有较高要求的复杂应用场景。

传感器

DS18B20

电气参数

参数类别 参数详情
工作电压 3V - 5.5V
测温范围 -55°C - +125°C(-67°F - +257°F)
精度 -55°C - +125°C 范围内:±2°C
-10°C - +85°C 范围内:±0.5°C
模块尺寸 21mm × 10mm

开发环境

Arduino IED 下载地址

https://www.arduino.cc/en/software/

开发板管理器地址

https://dl.espressif.com/dl/package_esp32_index.json

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

串口驱动官方资料地址

https://www.wch.cn/downloads/CH341SER_EXE.html

传感器资料地址

https://www.yourcee.com/productinfo/884740.html

接线说明

RMII 接口引脚连接

ESP32与LAN8720间RMII PHY的连接定义,用于以太网通信 ,需按照定义连接

GPIO RMII Signal ESP32 EMAC Function 说明
GPIO21 TX_EN EMAC_TX_EN 用于控制数据发送使能
GPIO19 TX0 EMAC_TXD0 发送数据位 0
GPIO22 TX1 EMAC_TXD1 发送数据位 1
GPIO25 RX0 EMAC_RXD0 接收数据位 0
GPIO26 RX1 EMAC_RXD1 接收数据位 1
GPIO27 CRS_DV EMAC_RX_DRV 载波侦听/接收数据有效信号

SMI 接口引脚连接

SMI 接口用于 ESP32 读写 LAN8720 的寄存器,相关引脚理论上可连接到 ESP32 任何支持对应功能的引脚,默认连接如下:

GPIO RMII Signal 说明1 说明2
GPIO23 MDC 输出至 LAN8720 也可使用 ESP32 其他输出脚
GPIO18 MDIO 双向通讯 也可使用 ESP32 其他双向脚

Arduino 程序

参考资料使用Arduino开发ESP32(06):Ethernet的使用(基于LAN8720)_arduino lan8720-CSDN博客

/**
  ******************************************************************************
  * @file    eth_temp_server.ino
  * @author  mlm
  * @version V1.0
  * @date    2025-08-12
  * @brief   ESP32以太网温度监测
  * @details 基于LAN8720和DS18B20的UDP温度服务器
  *          功能特性:
  *          - 静态IP网络配置(192.168.4.1)
  *          - 12位高精度温度采集
  *          - UDP协议通信(端口2333)
  *          - 自动检测传感器断线
  ******************************************************************************
  * @attention
  *
  * 硬件配置:
  * - 开发板:ESP32-WROOM-32(ESP32-devkitc-32E)
  * - PHY芯片:LAN8720
  * - 温度传感器:DS18B20
  * 
  * 引脚连接:
  * | ESP32引脚 | 外设引脚  | 功能说明        |
  * |----------|----------|-----------------|
  * | GPIO4    | DS18B20  | 单总线数据线     |
  * | GPIO23   | LAN8720  | MDC时钟线        |
  * | GPIO18   | LAN8720  | MDIO数据线       |
  * | GPIO17   | LAN8720  | 50MHz时钟输出    |
  * 
  * 通信协议:
  * - 请求命令:"gettemp"(不含引号)
  * - 响应格式:"Temperature: xx.xx ℃"
  *
  * 更新日志:
  * - V1.0 2025-08-12 初始版本
  *   • 实现基础温度采集功能
  *   • 完成UDP通信框架
  *
  ******************************************************************************
  */

#include <ETH.h>
#include <WiFiUdp.h>
#include <OneWire.h>
#include <DallasTemperature.h>

// 以太网配置
#define ETH_PHY_ADDR    1
#define ETH_POWER_PIN   -1
#define ETH_MDC_PIN     23
#define ETH_MDIO_PIN    18
#define ETH_PHY_TYPE    ETH_PHY_LAN8720
#define ETH_CLK_MODE    ETH_CLOCK_GPIO17_OUT

// DS18B20 配置
#define DSB_PIN         4
OneWire oneWire(DSB_PIN);
DallasTemperature sensors(&oneWire);

// UDP 配置
WiFiUDP Udp;
unsigned int localUdpPort = 2333;

// 静态IP
IPAddress localIP(192, 168, 4, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress gateway(192, 168, 4, 1);

// 读取温度
float readTemperature() {
    sensors.requestTemperatures();
    float tempC = sensors.getTempCByIndex(0);
    return (tempC != DEVICE_DISCONNECTED_C) ? tempC : -999.9;
}

// 发送UDP回复(带调试信息)
void sendUdpResponse(float tempC, IPAddress remoteIP, unsigned int remotePort) {
    String reply = "Temperature: " + String(tempC) + " ℃";
    Serial.print("[UDP] Sending: ");
    Serial.println(reply);

    if (!Udp.beginPacket(remoteIP, remotePort)) {
        Serial.println("[UDP] Error: beginPacket failed");
        return;
    }
    
    Udp.print(reply);
    if (!Udp.endPacket()) {
        Serial.println("[UDP] Error: endPacket failed");
    } else {
        Serial.println("[UDP] Reply sent successfully");
    }
}

void setup() {
    Serial.begin(115200);
    Serial.println("\nInitializing...");

    // 初始化以太网
    ETH.begin(ETH_PHY_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_PHY_TYPE, ETH_CLK_MODE);
    ETH.config(localIP, gateway, subnet);
    while (!ETH.localIP()) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nEthernet Connected");
    Serial.print("IP: ");
    Serial.println(ETH.localIP());

    // 初始化温度传感器
    sensors.begin();
    sensors.setResolution(12);
    Serial.println("DS18B20 Ready (12-bit resolution)");

    // 启动UDP
    Udp.begin(localUdpPort);
    Serial.println("UDP Server Started");
}

void loop() {
    float tempC = readTemperature();
    Serial.print(tempC);
    // 检查UDP数据
    int packetSize = Udp.parsePacket();
    if (packetSize > 0) {
        char buf[packetSize + 1];
        Udp.read(buf, packetSize);
        buf[packetSize] = '\0';

        // 打印接收到的原始数据(调试用)
        Serial.print("\n[UDP] Received: ");
        Serial.write(buf, packetSize);
        Serial.println();

        // 去除首尾空白字符后比较
        String received = String(buf);
        received.trim();
        if (received == "gettemp") {
            Serial.println("[UDP] Valid 'gettemp' request");
            sendUdpResponse(tempC, Udp.remoteIP(), Udp.remotePort());
        }
    }
    delay(1000);
}

2025-10-05 补充一下:

图片

过程解释:根据上方代码,发送 gettemp ESP32会返回温度数据,建立的是UDP连接,存在丢包的情况。

[
    {
        "id": "6cbafc4f6f60f953",
        "type": "udp in",
        "z": "udp-flow",
        "name": "",
        "iface": "",
        "port": "2335",
        "ipv": "udp4",
        "multicast": "false",
        "group": "",
        "datatype": "utf8",
        "x": 260,
        "y": 200,
        "wires": [
            [
                "c2cb7f5b86c2e2ce",
                "7cd4ebefeb1cf754"
            ]
        ]
    },
    {
        "id": "c2cb7f5b86c2e2ce",
        "type": "debug",
        "z": "udp-flow",
        "name": "debug 3",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 480,
        "y": 200,
        "wires": []
    },
    {
        "id": "6e8aeb1b539e8770",
        "type": "udp out",
        "z": "udp-flow",
        "name": "",
        "addr": "192.168.4.1",
        "iface": "",
        "port": "2332",
        "ipv": "udp4",
        "outport": "2335",
        "base64": false,
        "multicast": "false",
        "x": 560,
        "y": 120,
        "wires": []
    },
    {
        "id": "7a02b102c7c5b8df",
        "type": "inject",
        "z": "udp-flow",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "10",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "gettemp",
        "payloadType": "str",
        "x": 280,
        "y": 120,
        "wires": [
            [
                "6e8aeb1b539e8770",
                "789410be31ca6ca8"
            ]
        ]
    },
    {
        "id": "789410be31ca6ca8",
        "type": "debug",
        "z": "udp-flow",
        "name": "debug 4",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 540,
        "y": 60,
        "wires": []
    },
    {
        "id": "7cd4ebefeb1cf754",
        "type": "function",
        "z": "udp-flow",
        "name": "function 2",
        "func": "// 提取温度值(示例数据: \"Temperature: 28.25 ℃\")\nconst match = msg.payload.match(/Temperature: (\\d+\\.\\d+) ℃/);\nif (match) {\n  msg.payload = `解析成功: ${match[1]} ℃`;\n} else {\n  msg.payload = \"解析失败: 数据格式不符\";\n}\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 500,
        "y": 260,
        "wires": [
            [
                "d385b5de0cbc0eec"
            ]
        ]
    },
    {
        "id": "d385b5de0cbc0eec",
        "type": "debug",
        "z": "udp-flow",
        "name": "debug 5",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 660,
        "y": 260,
        "wires": []
    }
]
posted @ 2025-08-13 13:34  麻洛美  阅读(1422)  评论(0)    收藏  举报