
简介围绕德州仪器dp83867以太网控制器芯片整理的开发资料包内含芯片手册与C语言驱动源码面向嵌入式系统、物联网设备及网络驱动开发工程师用于解决以太网通信设计、驱动移植和硬件调试等实际问题。芯片手册对引脚定义、电气特性、寄存器字段、工作模式及介质无关接口进行了系统说明能指导开发者完成MAC地址配置、以太网数据收发以及IPv4、TCP、UDP等协议栈对接寄存器读写说明尤为细致是驱动开发时的重要依据。C语言源码从初始化流程入手覆盖寄存器配置、数据帧发送与接收、DMA传输、中断处理和错误恢复等核心模块代码可读性较好便于直接移植或按项目需求二次扩展也有助于理解PHY芯片底层的运行机制。资源包含2个文件即1份PDF规格书和1个C源程序压缩包大小约1.35MB体量精简不占用过多存储空间适合快速参考或离线学习。目前已有432人学习下载对希望掌握dp83867控制器开发、缩短驱动调试周期的工程师来说是一份兼具权威手册与可运行代码的实用资料。 如果你手头有一块原理图上画着DP83867CR的板子八成正在做千兆以太网或者工业交换类的东西。第一次打开dp83867cr.pdf面对几百页手册很多人会觉得不把所有寄存器背下来就没法用C程序把它驱动起来于是卡在“读手册”这一步迟迟不敢动手。实际上PHY芯片的驱动核心就三件事把MDIO这个两线协议跑通往对的位置写对值再正确地读出状态。这篇文章按这个思路讲一遍从硬件确认到寄存器再到C代码实现和一个真实排障案例适合正在调新板卡、或者刚接手网卡相关驱动开发的工程师。如果只是跑Linux下现成驱动倒也省事但一旦走到裸机、自研板卡或者要改PHY默认配置这条路下面这些内容迟早用得上。1. 拿到dp83867cr.pdf之后先确认硬件而不是背寄存器1.1 DP83867CR在板卡上扮演什么角色DP83867CR是TI的千兆工业级以太网PHY芯片。这里说的PHY可以理解成网络信号收发器主控芯片MAC通过RGMII或者SGMII接口把以太网帧送过来PHY负责把这些数字信号调制成差分模拟信号推到网口变压器和RJ45上接收方向则反过来把网线上的模拟信号解调成MAC能认的数字电平。也就是说PHY驱动上电后要完成的本质工作是通过MDIO/MDC两根管理线访问PHY内部的寄存器完成复位、自动协商配置、状态监控这些动作。要特别提醒的是DP83867CR不是那种代码一写就能自动跑起来的芯片。它的工作模式、PHY地址、时钟来源甚至一部分延迟参数都是由硬件管脚在复位瞬间的电平决定的。这也是为什么我拿到新板卡第一件事永远是看原理图而不是急着翻PDF里的寄存器表。跳过这个环节的代价往往是代码写得再漂亮板子上依然没有半点反应。1.2 strap引脚与PHY地址C代码里第一个数字的来历驱动里每次MDIO读写都要带一个phy_addr参数这个地址并不是随便定的而是PHY芯片在复位时从strap引脚上采到的电平组合。DP83867CR的strap采样点分布在RX_CLK、RX_D0到RX_D3、RX_CTL等位置不同封装上引脚名可能不完全一样但原理图上通常会直接标出PHY_AD0、PHY_AD1这类网络名有的也可能叫RX_D0、RX_D1配合PHY_AD前缀需要自己根据数据手册对照。拿到板卡的第一时间打开原理图把DP83867CR周围所有strap引脚的上拉和下拉电阻画一遍算出实际PHY地址。常见板卡上这个值可能是0x00、0x04、0x07一类的数字算完之后会直接写进C程序里。地址不对MDIO总线上就不会有ACK读任何寄存器都是0xFFFF代码写得再对也是白搭。注意strap引脚不只决定地址。MODE_SEL[1:0]决定PHY工作在RGMII还是SGMIIANEG_EN决定是否启用自动协商时钟选择引脚决定XI/XO接的是25M还是50M。复位后这些电平被锁存软件里改不了所以原理图阶段就得核对清楚。1.3 时钟、复位、RGMII延迟的硬件检查清单动手写C代码之前我建议先过一遍硬件检查清单能省掉后面大量调试时间供电VDD、VDDIO的电平是否满足手册要求上电时序是否合规。复位RESET_N引脚低电平持续时间是否够长释放沿干不干净。参考时钟XI/XO上的晶振或者外部时钟频率是否正确示波器确认波形。MDIO/MDCMDIO是否接对了上拉MDC上有没有串阻影响边沿。strap逐脚核对上下拉电阻和期望的工作模式是否一致。RGMII延迟板上有没有预留调整延迟的焊盘、跳线或者串阻。特别是RGMII时钟延迟这一项标准里讲究的是时钟和数据中心对齐但很多SoC的MAC和PHY两边都有自己的延迟配置叠在一起反而可能错位。硬件阶段只需要确认板子上留了调整空间具体怎么配后面案例里细讲。2. MDIO读写是C程序的敲门砖协议帧与寄存器地图2.1 MDIO帧格式搞懂TA阶段就不怕时序问题MDIO总线只有MDC时钟和MDIO数据两根线和I2C有点神似但帧格式完全不同。一个完整的MDIO帧是这样组织的先来32比特全1的前导码然后是2比特的ST等于01、2比特的OP读是10写是01、5比特的PHY地址、5比特的寄存器地址接着是2比特的TA转换段最后是16比特数据。读操作里TA第一拍master要把总线释放成高阻第二拍由从机把总线拉低之后才轮到从机按位输出数据。写操作则简单一些TA就是master直接输出10。很多自己手写C驱动的人通信不稳定往往就出在TA阶段没有做方向切换。GPIO模拟的时候尤其要注意如果MDIO引脚在TA阶段还是输出状态等于总线上一直有人在驱动高电平从机根本拉不动总线读回来的数据全是1。2.2 标准寄存器里最常用的几个位IEEE 802.3给PHY定义了一套通用寄存器DP83867CR在这套框架上是兼容的。写驱动最常碰的就是下面这几个偏移寄存器名关键位用途0x00BMCRbit15软复位bit12自协商使能bit14环回写0x8000触发软复位0x01BMSRbit12链路状态bit5自协商完成链路和协商状态检查0x02/0x03PHYIDR1/2厂商ID和版本号确认PHY地址和芯片型号0x04/0x05ANAR/ANLPAR本端/对端协商能力判断协商出的速度双工BMCR的软复位位有一个特点写1之后芯片复位完成会自动清零。所以正确的做法是写0x8000之后轮询等待它自己变回0而不是sleep固定时长就认为复位完成了。强制指定速度和双工的时候关掉自动协商使能位bit12用bit13和bit6组合出速率bit8决定全双工还是半双工。这个组合关系在手册的标准寄存器表里都有可以直接查。2.3 DP83867CR扩展寄存器看数据手册的正确姿势DP83867CR的扩展寄存器才是它区别于通用PHY的地方比如时钟延迟、LED行为、SGMII配置、诊断状态都在这些区域里。但这类寄存器的偏移在不同手册版本里出现过调整我不能拍着胸脯给你一份脱离手册的固定清单那是对你不负责。我的习惯是把dp83867cr.pdf里的寄存器图截下来在寄存器编号旁边做索引标注功能简写再配合原理图确认每个配置从哪来。C代码里也建议把常用寄存器偏移定义成宏注释里直接抄上手册的章节号或者页码。后面调板子的时候这份自己整理的寄存器地图比数据手册本身还好用。我还见过不少工程师直接抄网上流传的老版本寄存器值去调新批次芯片结果行为完全对不上。下载dp83867cr.pdf之后先翻到最后一页看Rev历史和版本说明再决定参考哪份资料。3. 手写C驱动从GPIO模拟MDIO到PHY上电初始化3.1 底层管脚抽象我写MDIO驱动时习惯把底层接口先抽象成几个原语这样换平台只需要改底部五个函数上层协议和业务逻辑完全不用动。这里以GPIO模拟为例需要的原语有写MDC电平、写MDIO电平、读MDIO电平、把MDIO切到输出、把MDIO切到输入。#include stdint.h /* 平台原语由BSP层提供 */ extern void mdc_write(int level); extern void mdio_write(int level); extern int mdio_read(void); extern void mdio_set_output(void); extern void mdio_set_input(void); extern void udelay_us(uint32_t us); static void mdc_cycle(void) { mdc_write(1); udelay_us(1); mdc_write(0); udelay_us(1); } static void mdio_send_bit(int bit) { mdio_set_output(); mdc_write(0); mdio_write(bit ? 1 : 0); udelay_us(1); mdc_write(1); /* 上升沿PHY采样 */ udelay_us(1); mdc_write(0); udelay_us(1); } static int mdio_recv_bit(void) { int bit; mdio_set_input(); mdc_write(1); udelay_us(1); bit mdio_read(); /* 在MDC高电平期间采样 */ mdc_write(0); udelay_us(1); return bit; }底层这部分我建议控制在2.5MHz以下的MDC频率跑GPIO模拟的精度有限不需要追极限速度。调试阶段稳定永远是第一位的。3.2 读寄存器与写寄存器的完整C实现有了上面的原语MDIO读写的实现就清晰了。以DP83867CR为例PHY地址和寄存器地址都是5位逐位发送即可。int phy_mdio_read(uint8_t phy_addr, uint8_t reg_addr, uint16_t *val) { int i; uint16_t data 0; /* 前导码32比特1 */ for (i 0; i 32; i) mdio_send_bit(1); /* ST: 01OP: 10读 */ mdio_send_bit(0); mdio_send_bit(1); mdio_send_bit(1); mdio_send_bit(0); /* PHY地址和寄存器地址从高位开始 */ for (i 4; i 0; i--) mdio_send_bit((phy_addr i) 1); for (i 4; i 0; i--) mdio_send_bit((reg_addr i) 1); /* TA第一拍释放总线 */ mdio_set_input(); mdc_cycle(); /* TA第二拍从机驱动0主机采样 */ if (mdio_recv_bit() ! 0) return -1; /* 16位数据MSB在前 */ for (i 15; i 0; i--) { int bit mdio_recv_bit(); data | ((uint16_t)bit i); } mdio_set_output(); if (val) *val data; return 0; } int phy_mdio_write(uint8_t phy_addr, uint8_t reg_addr, uint16_t data) { int i; for (i 0; i 32; i) mdio_send_bit(1); /* ST: 01OP: 01写 */ mdio_send_bit(0); mdio_send_bit(1); mdio_send_bit(0); mdio_send_bit(1); for (i 4; i 0; i--) mdio_send_bit((phy_addr i) 1); for (i 4; i 0; i--) mdio_send_bit((reg_addr i) 1); /* TA10 */ mdio_send_bit(1); mdio_send_bit(0); for (i 15; i 0; i--) mdio_send_bit((data i) 1); return 0; }读函数里那个mdio_recv_bit必须放在TA第二拍的位置因为它内部会做方向切换和高电平时采样。如果顺序写反读回来的第一个数据位就被吞掉了。3.3 上电初始化流程软复位、自协商、link检测驱动初始化流程我一般是按下面这个顺序走。第一步读PHY ID确认MDIO地址真的对顺便确认芯片型号。#define DP83867_BMCR_RESET 0x8000 #define DP83867_BMCR_AUTONEG 0x1000 #define BMSR_LINK_STATUS (1 12) #define BMSR_AN_COMPLETE (1 5) int dp83867_init(uint8_t phy_addr) { uint16_t phy_id1 0, phy_id2 0; uint16_t bmcr 0; if (phy_mdio_read(phy_addr, 0x02, phy_id1) ! 0) return -1; if (phy_mdio_read(phy_addr, 0x03, phy_id2) ! 0) return -1; /* 这里把 phy_id1/phy_id2 和手册对照能读出DP83867的固定ID */ /* 软复位 */ phy_mdio_write(phy_addr, 0x00, DP83867_BMCR_RESET); /* 轮询等待复位自动完成 */ do { phy_mdio_read(phy_addr, 0x00, bmcr); } while ((bmcr DP83867_BMCR_RESET) ! 0); /* 启动自动协商 */ phy_mdio_write(phy_addr, 0x00, DP83867_BMCR_AUTONEG); return dp83867_wait_link(phy_addr, 3000); }link等待函数这里给一个带超时的版本。自协商需要双方互相交换能力快的几百毫秒慢的能达到两三秒所以不建议只sleep固定时长用轮询更可靠。int dp83867_wait_link(uint8_t phy_addr, uint32_t timeout_ms) { uint16_t val; uint32_t elapsed 0; while (elapsed timeout_ms) { if (phy_mdio_read(phy_addr, 0x01, val) 0) { /* 第一次读清锁存第二次读拿真实状态 */ phy_mdio_read(phy_addr, 0x01, val); if ((val BMSR_LINK_STATUS) (val BMSR_AN_COMPLETE)) return 0; } delay_ms(10); elapsed 10; } return -1; }整个流程跑下来PHY基本就进入工作状态了。后面要做的就是周期性轮询链路状态或者等MAC的中断触发再检查这个看具体项目架构。4. 驱动里容易踩的坑状态锁存、协商等待、速度读取4.1 BMSR的Link Status是latching-low读一次等于没读BMSR的bit12是链路状态很多新手第一次读它发现是0马上断定link down这是最常见的一个误判。IEEE标准里这个位是锁存低电平的一旦发生过掉线即使现在链路已经恢复它也可能保持为0直到软件真正去读一次寄存器才会更新为当前真实状态。所以驱动里做状态检测我永远是连续读两次BMSR以第二次的结果为准。第一次读的动作本身就是在清锁存。这个特性和其他寄存器不一样光看数据手册的文字说明很容易忽略但实际调试时影响很大。我遇到过不止一次测试人员认为网口掉线了其实只是驱动把锁存值当成了实时值。4.2 自协商不是同步完成的需要轮询等待自协商需要链路两端互相发送能力配置这段时间可能是几百毫秒也可能长达两三秒。有些PHY还会因为对端设备响应慢出现反复重试。所以初始化代码里我倾向于写轮询加超时而不是sleep固定2秒然后直接读状态。如果固定等待时间太短后面读到的自协商完成位还是0程序就把正常情况当成异常返回了。另外如果PHY配置成强制速率和强制双工自动协商相关的状态位可能永远不会置位这时候就不能死等BMSR_AN_COMPLETE。判断依据应该是你先搞清楚自己到底跑的是协商模式还是强制模式再去选择要看哪一位。4.3 当前速度怎么读几个实用思路IEEE标准寄存器没有直接给一个“当前速率是多少”的现成字段这是很多人翻手册找不到答案的原因。实际中我常用三种办法读厂商的专用状态寄存器DP83867CR手册里有速度相关的状态位这个最直接但不同版本手册地址可能不同要以手上的PDF为准。通过ANLPAR和1000BASE-T状态寄存器去推算。自动协商完成后读本端ANAR和对端能力ANLPAR再结合千兆扩展状态基本能拼出速度双工。直接看MAC侧RGMII接口的速率指示信号。对于驱动的日常监控来说这一路未必需要寄存器参与。我个人的习惯是优先用厂商状态寄存器代码简单可读性也好。如果某天遇到新版本手册改了地址再退回ANLPAR推算方案保证功能不阻塞在查手册上。5. 一次真实排障link up却不通问题出在RGMII时钟延迟5.1 现象描述与寄存器证据之前调试一块板卡现象很典型PHY寄存器里link是up自协商也是complete对端设备抓包能看到物理链路已经建立但这台设备的网络就是PING不通偶发能通一两包丢包率极高。这种“寄存器看着全正常、业务就是跑不通”的问题比寄存器全乱的更难定位。我第一步先把BMSR、ANLPAR、PHYIDR都读了一遍确认PHY地址对协商结果也确实是对端千兆全双工。然后开始怀疑MAC侧配置翻主控SoC的手册时发现它的RGMII接口有独立的内部延迟开关需要和PHY侧的延迟配置配合使用。5.2 用示波器和寄存器组合定位根因接下来用示波器抓RGMII的TX_CLK、RX_CLK和数据线波形。RGMII本身就是双沿采样时钟沿和数据切换沿如果挨得太近采样窗口几乎没有余量。实测抓到的波形里时钟沿和数据跳变沿几乎是贴在一起的这在高速信号里基本等于没有时序预算。继续查PHY侧的延迟配置寄存器发现PHY内部延迟处于关闭状态而MAC侧只在接收方向开了延迟发送方向没开两边没有对齐。也就是说发送方向没有任何一方做延迟补偿数据采样自然就不稳定。5.3 修复配置与代码修改最后确定的方案是MAC侧关闭自身发送方向的内部延迟PHY侧把RGMII发送和接收延迟都打开让整体时序恢复到中心对齐的状态。修改后的C驱动里初始化阶段通过MDIO写DP83867CR的RGMII时钟延迟控制寄存器把对应的delay位使能然后在硬件复位后重新初始化PHY。改完再抓波形时钟沿和数据中心的位置关系明显好转数据眼图张开了。跑长ping测试百万包无丢包问题彻底解决。提示每个板卡的PCB走线长度不同RGMII延迟配置没有统一答案照搬同一份寄存器值不一定适用。调时序时应该以示波器实测为准而不是猜或抄。5.4 这类问题还能怎么预防新板卡第一次上电调试前我强烈建议先做一个PHY寄存器快照工具把关键寄存器值整体打出来。开发过程中遇到疑似PHY问题先读寄存器排除地址和协商配置问题再上示波器看物理层时序最后才动C代码逻辑。这样一圈下来能少走很多弯路。特别是RGMII这种对时序敏感的接口示波器一抓一个准别只靠眼睛猜寄存器。如果你也在调类似板卡别急着在网上到处找现成驱动。把dp83867cr.pdf里的寄存器图和原理图对照一遍整理成自己的表再动手写C整个过程顺利得多也省得后面踩完坑再回头补课。本文还有配套的精品资源点击获取