YT8512C的硬件解析+lwip数据接受与解析
(一):硬件部分
如图所示:EARTHNET,RMII 接口原理图

YT8512C:YT8512C 是一款国产百兆以太网物理层(PHY)芯片
- 发送方向:将 STM32(MAC 层)通过 RMII/MII 接口输出的 3.3V 数字逻辑电平(如 TXD0/TXD1),转换为符合 IEEE 802.3u 标准的差分模拟信号(TX+/TX-),通过网线传输;
- 接收方向:将网线中传来的差分模拟信号解调为数字逻辑电平(RXD0/RXD1),回传给 STM32 的 MAC 层,完成数据接收。
- 兼容 IEEE 802.3/802.3u 标准,支持10/100Mbps 速率自适应(自动识别对端设备如路由器的速率);
- 支持全双工 / 半双工模式、自动协商(Auto-Negotiation),无需手动配置,自动匹配最优通信模式;
- 支持交叉线 / 直通线自适应,无需区分网线类型,降低硬件使用门槛。
YT8512C 为 QFN32 封装的百兆以太网 PHY 芯片,引脚按功能分为电源与地、RMII/MII 接口、物理层差分信号、管理接口、时钟与复位、LED 与配置、模拟与校准7 大类
RX_DV 为高电平时,表示 PHY 输出到 MAC 的 RXD(接收数据)总线上的比特流是有效以太网帧数据(从帧前导码 + SFD 开始,到 CRC 校验结束);低电平时,RXD 上的信号为空闲码、链路协商码等无效数据,MAC 会忽略。→ 简单说:RX_DV 高电平 =“MAC 快收,这是正经数据!”;低电平 =“别理,都是无效信号”。
| RMII 模式(CRS_DV) | 数据有效(RX_DV)+ 半双工载波侦听 | 无有效数据,链路空闲 |
| RMII(百兆) | 仅 RXD [0]/RXD [1] 两位,并行传输(每时钟 2 比特) | 速率 50MHz 时钟,总带宽 100Mbps(与 MII 等效),是你 YT8512C+STM32F407 的核心用法 |
TXC(Transmit Clock,发送时钟)是以太网 RMII/MII 接口的核心同步时钟信号,在你的 YT8512C + STM32F407 场景中,它的作用是为发送、接收双向数据传输提供统一的时序基准,保证 PHY 与 MAC 之间的数据采样和传输不出现错位。
RESET_N(低电平有效硬件复位)是 YT8512C/STM32 等芯片的核心复位引脚,核心作用是通过低电平触发芯片复位,将内部寄存器、状态机恢复至出厂默认值,确保上电稳定或异常时可强制重启,避免逻辑混乱。
1. MDC(管理时钟)
- 信号方向:由MCU(STM32)输出,给 PHY 提供同步时钟。
- 功能:作为 MDIO 数据传输的节拍基准,控制数据收发的时序。
2. MDIO(管理数据)
- 信号方向:双向传输,MCU 可通过它向 PHY 写配置指令,也可读取 PHY 的状态寄存器。
- 功能:承载 PHY 寄存器的地址、读写命令和数据,是 MCU 与 PHY 的 “通信桥梁”。
CRS_DV 是 RMII 接口的 “接收状态总开关”,既简化了硬件设计,又兼顾数据有效性与信道状态监测。
RXD[0],RXD[1],TXD[0],RXD[1]不是传输数据的吗?为什么还要MDC,MDIO传输数据?
RXD [0:1]/TXD [0:1] 负责传输以太网业务数据帧(如 TCP/UDP 包),是高速数据通道;MDC/MDIO 负责传输PHY 管理配置与状态数据(如速率、双工、链路状态),是低速控制通道,二者功能完全独立、不可替代,共同支撑 STM32+YT8512C 的以太网正常工作。
RXD/TXD 是 “数据高速公路”,MDC/MDIO 是 “高速公路的交通指挥与监控系统”—— 没有指挥监控,高速公路无法正常通车;没有通车能力,指挥监控也失去意义,二者缺一不可。
PCB布线总结:(TPTXBO,TPTXNO)一组差分信号走线要符合100Ω的阻抗匹配,线宽7mil,线距7mil,PCB走线(TRXN1,TRXP1)一组差分信号走线要符合100Ω的阻抗匹配,线宽7mil,线距7mil,(RXD[0],RXD[1])一组因为是并行数据,所以需要等长处理,线宽按照10mil来,(TXD[0],TXD[1])一组因为是并行数据,所以需要等长处理,线宽按照10mil来,
其他线10mil就行,两个差分对的距离遵循3W原则,这些线都走外层顶层或者底层,外层1盎司,内层0.5盎司,处理差分对,其他信号线最小距离10mil,一个信号的过孔最多两个,差分对走外层时,对应内层(如下层)需铺完整的 GND 铜皮,且铜皮与差分对的距离尽量近(利用层压结构的介质厚度),增强阻抗稳定性和抗干扰能力。你觉得这个思路有需要完善的地方吗
1:网口阻抗匹配100Ω,等长处理TX,RX,在不等长的地方做等长处理;
同一路径内的信号必须与 TXC 等长,
假设要求 长度误差 ≤50mil,设定 TXC 的基线长度为 1000mil
- 发送组匹配:TXD [0]、TXD [1]、TX_EN 需与 TXC 长度差 ≤50mil → 这三个信号长度范围是 950~1050mil
- 接收组匹配:RXD [0]、RXD [1]、CRS_DV 需与 TXC 长度差 ≤50mil → 这三个信号长度范围是 950~1050mil
此时,TXD 组的长度(比如 960mil)和 RXD 组的长度(比如 1040mil)之间的差值是 80mil,完全满足 “各自与 TXC 等长” 的要求,但 TXD 和 RXD 本身并不等长 —— 这是完全合规的设计。


核心区别对比表
RJ45:8 针模块化连接器
C语言函数
void *memset(void *s, int c, size_t n);
作用:将起始地址为 s、长度为 n 字节的连续内存块,每个字节都填充为同一个值(c 的低 8 位)。
int strncmp(const char *str1, const char *str2, size_t n);
作用:按 ASCII 码逐字节比较两个字符串的前 n 个字符,
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
作用:将 format 指定的格式化字符串(含固定文本与格式化符)和后续可变参数按规则拼接,安全写入 buf 缓冲区(最多写 n-1 个字符,自动补 \0 结束符),同时返回格式化后字符串的真实长度(不含 \0),避免缓冲区溢出。
#include <string.h> // 必须包含该头文件(Keil/STM32中适用)
void *memcpy(void *restrict dest, const void *restrict src, size_t n);
memcpy 函数将 src(如 new_data)指向的内存区域中 前 n 个字节(如 sizeof(new_data))的数据,原样复制到 dest(如 g_lwip_demo_sendbuf)指向的内存区域,返回 dest 地址,实现内存块的二进制拷贝(不关心数据类型,仅按字节复制)。
#include <string.h> // 必须包含该头文件(Keil/STM32中适用)
char *strstr(const char *haystack, const char *needle);
strstr 函数在 haystack 指向的字符串(如 cmd)中查找 needle 指向的子字符串(如 "arc_incmove"),找到则返回子字符串在 haystack 中的起始地址,未找到则返回 NULL,核心用于判断一个字符串是否包含另一个子字符串。
#include <stdio.h> // 必须包含该头文件(Keil/STM32中适用)
int sscanf(const char *restrict s, const char *restrict format, ...);
sscanf 函数从 s 指向的字符串(如 cmd)中,按 format 指定的格式(如 "arc_incmove %ld %ld %ld %ld %lu %hhd")提取对应类型的数据,依次存入后续可变参数指向的内存地址(如 &start_x、&start_y 等),最终返回成功提取的参数个数,核心用于从字符串中解析结构化数据。
#include <stdio.h> // 必须包含此头文件(STM32/嵌入式开发也需包含)
int sscanf(const char *str, const char *format, ...);
例子:sscanf(cmd, "set %d %s", &num, state) == 2
作用:以 format 参数指定的格式规则为 “提取模板”,从 str 参数指向的源字符串中,筛选并转换出对应类型的数据,最终将转换后的结果分别存入可变参数 ... 指定的变量中,同时返回成功提取并赋值的参数个数,用于判断解析是否符合预期。
示例场景中,就是:从 cmd(str)里,按 set %d %s(format)的规则,提取 IO 编号和控制状态,分别存到 num 和 state(可变参数)中,再通过返回值 == 2 确认解析成功,为后续 IO 控制提供可靠参数。
---------------------------------------------------------------------------------------------------------------------------------
#include <stdio.h> // 必须包含此头文件
int sprintf(char *str, const char *format, ...);
示例:sprintf(reply, "[%d%d%d%d%d%d%d%d]\r\n",io_state[0], io_state[1], io_state[2], io_state[3],io_state[4], io_state[5], io_state[6], io_state[7]);
作用:将多个不同类型的数据(如整数、字符串、浮点数等)按照指定的格式,拼接成一个字符串,并存储到用户提供的字符数组(缓冲区)中。
lwip的发送函数
首先思路可以仿照按键发送的来 g_lwip_send_flag |= LWIP_SEND_DATA; /* 标记LWIP有数据要发送 */
其次下面这一个不知道为啥相同的数据只发送一次就没反应了
#include "lwip/sockets.h" // 必须包含 LwIP socket 头文件
int send(int s, const void *buf, size_t len, int flags);
作用:通过参数 s 指定的已建立网络连接(socket 通道),从参数 buf 指向的缓冲区中,读取 len 个字节的有效数据,按照 flags 设定的发送规则,将数据可靠发送到网络对端。
lwip通信接受上位机来的信息
1:在freertos_demo中定义创建一个队列和任务
/* 消息队列的数量 */
#define CMD_RECV__Q_NUM 20 /* 显示消息队列的数量 */
QueueHandle_t cmd_recv_queue; /* 显示消息队列句柄 */
cmd_recv_queue = xQueueCreate(CMD_RECV__Q_NUM,200); /* 创建消息Message_Queue,队列项长度是200长度 */
/**
* @brief 显示任务
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void display_task(void *pvParameters)
{
pvParameters = pvParameters;
uint8_t *buffer;
while(1)
{
buffer = mymalloc(SRAMIN,200);
if(g_display_queue != NULL)
{
memset(buffer,0,200); /* 清除缓冲区 */
if(cmd_recv_queue != NULL)
{
memset(buffer,0,200); /* 清除缓冲区 */
if (xQueueReceive(cmd_recv_queue,buffer,portMAX_DELAY))
{
//对buffer进行操作
}
}
myfree(SRAMIN,buffer); /*释放内存 */
vTaskDelay(5);
}
}
2:在lwip.demo中,进行写队列
extern QueueHandle_t cmd_recv_queue; /* 显示消息队列句柄 */
/* 将收到的数据发送到 FreeRTOS 消息队列(例如 LCD 显示任务) */
lwip_err = xQueueSend(cmd_recv_queue, &g_lwip_demo_recvbuf, 0);
网口的发送与接受数据进行逻辑处理
发送数据进行逻辑处理:
uint8_t g_lwip_demo_sendbuf[] = "ALIENTEK DATA \r\n";
修改这个数组里面的内容进行发送自己想发送的数据
对数据进行更改的思路?
首先把之前的数据全部清除
再进行写入新的数据
每执行一次下面这个代码就发送一次数据
g_lwip_send_flag |= LWIP_SEND_DATA; /* 标记LWIP有数据要发送 */
接受数据进行逻辑处理:
创建一个buffer进行储存队列的内容,再进行逻辑操作
uint8_t *buffer;
if (xQueueReceive(g_display_queue,buffer,portMAX_DELAY))
{
//对buffer进行逻辑判断进行逻辑操作
}
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)