资讯详情

DM9000A在VxWorks下的驱动移植与调试实战

📅 2026/10/31 16:52:45 | 华诺云谱 👁 阅读
DM9000A在VxWorks下的驱动移植与调试实战
简介这份资源面向从事嵌入式网络驱动开发的工程师与学习者聚焦DM9000A以太网控制器在VxWorks实时操作系统下的驱动实现帮助解决硬件寄存器操作、数据收发与中断处理等核心问题。压缩包共2个文件包含1个c源码与1个txt说明文档整体约18KB源码对应驱动主体逻辑说明文档则用于交代接口与使用背景。已有140人学习下载可作为入门VxWorks网卡驱动或移植参考的轻量素材。读者可从中了解DM9000A的初始化流程、发送与接收数据路径、中断服务例程设计思路以及内核态驱动如何与硬件RAM缓冲配合降低CPU负担进而对照自身平台完成适配与调试提升网络通信模块的开发效率。1. DM9000A 在 VxWorks 下的驱动移植从网卡不识别到 ping 通的第一公里手上有一块工控板CPU 是 ARM9网络口用的是 DM9000ABSP 里默认的网卡驱动跑不起来ifconfig看不到设备串口一直打印dm9000: probe failed。这种场景在嵌入式网络开发里太常见了——DM9000A 这颗芯片便宜、供货稳、接口简单很多国产工控板和电力终端都在用但 VxWorks 的 BSP 往往只带一个通用模板寄存器基址、中断号、位宽模式对不上网卡就是死活不认。这篇笔记就围绕 DM9000A 在 VxWorks 下的驱动适配展开把芯片初始化、寄存器配置、中断挂接、收发环搭建这条链路拆开讲清楚。适合正在做 BSP 移植、调试以太网口、或者接手了一块网口不通的板子的嵌入式工程师。读完你至少能判断是硬件没焊好、时序不对还是驱动层寄存器写错了。2. DM9000A 的寄存器模型与 VxWorks 驱动挂接点2.1 先搞清楚 DM9000A 的 INDEX/DATA 双端口访问机制DM9000A 和很多以太网控制器不一样它没有把寄存器直接铺在地址空间上而是用两个物理地址做窗口一个 INDEX 端口写寄存器编号一个 DATA 端口读写该编号对应的数据。CPU 侧看到的就是两个地址比如基址0x88000000对应 INDEX0x88000004对应 DATA。每次访问某个寄存器都要先写 INDEX再读写 DATA这个顺序不能反中间也不能被中断打断否则寄存器窗口就串了。在 VxWorks 里这个基址通常由 BSP 的sysPhysMemDesc[]或sysLib.c里的地址映射决定。你需要确认两件事一是物理地址到虚拟地址的映射有没有做二是映射后的虚拟地址是不是按 4 字节对齐。很多板子跑不起来就是因为基址写成了0x88000000但实际硬件译码出来是0x88000000起始的 8 字节窗口DATA 偏移应该是4结果写成了2读出来的全是 0xFFFF。/* dm9000a.h - 寄存器索引定义只列关键几个 */ #define DM9000A_NCR 0x00 /* 网络控制寄存器 */ #define DM9000A_NSR 0x01 /* 网络状态寄存器 */ #define DM9000A_TCR 0x02 /* 发送控制寄存器 */ #define DM9000A_RCR 0x03 /* 接收控制寄存器 */ #define DM9000A_ISR 0x0E /* 中断状态寄存器 */ #define DM9000A_IMR 0x0F /* 中断屏蔽寄存器 */ #define DM9000A_RCR 0x03 #define DM9000A_PAR 0x10 /* 物理地址寄存器起始共6字节 */ #define DM9000A_MAR 0x16 /* 组播地址寄存器起始共8字节 */上面这段是寄存器索引的宏定义实际写驱动时你至少要把 NCR、NSR、ISR、IMR、PAR 这几个用对。NCR 的 bit0 是 RXENbit1 是 TXEN初始化时先关掉收发配好其他寄存器再打开。NSR 的 bit6 是 LINK_STAT用来判断网线插没插。ISR 是中断状态读一次会自动清但有些位需要写 1 清除这个在数据手册里写得很细别凭感觉写。2.2 VxWorks 网络驱动框架里 DM9000A 该挂在哪一层VxWorks 的网卡驱动通常走ENDEnhanced Network Driver框架也就是muxDevLoad/muxDevStart那一套。你需要实现一个END_OBJ结构填好start、stop、send、ioctl、muxBind这些回调。DM9000A 的驱动一般放在target/src/drv/end/下面编译进 BSP 或者做成可加载模块。关键挂接点有三个第一是dm9000aLoad()负责探测芯片、读 ID、分配END_OBJ第二是dm9000aStart()负责初始化寄存器、挂中断、启动收发第三是dm9000aSend()把上层协议栈下来的mBlk链拆成帧写进 DM9000A 的发送 FIFO。中断服务程序dm9000aIsr()里读 ISR判断是收还是发完成然后调用netJobAdd把实际处理推到任务级别在 ISR 里做太多事。/* 探测芯片读 VID 和 PIDDM9000A 固定为 0x0A46 / 0x9000 */ static UINT16 dm9000aReadReg(UINT32 base, UINT8 reg) { sysOutByte(base, reg); /* 先写 INDEX */ return sysInWord(base 4); /* 再读 DATA注意偏移 */ } static STATUS dm9000aProbe(UINT32 base) { UINT16 vid dm9000aReadReg(base, 0x28); /* VID 低16位 */ UINT16 pid dm9000aReadReg(base, 0x2A); /* PID 低16位 */ if (vid ! 0x0A46 || pid ! 0x9000) { logMsg(dm9000a: probe failed, vid0x%04X pid0x%04X\n, vid, pid, 2,3,4,5,6); return ERROR; } return OK; }这段探测代码里sysOutByte和sysInWord是 VxWorks 的 IO 访问原语具体用哪个取决于你的总线位宽。DM9000A 支持 8 位和 16 位模式16 位模式下 DATA 端口按字访问8 位模式下按字节访问。如果你读出来的 VID 是 0xFFFF 或者 0x0000先查硬件复位和片选再查 INDEX/DATA 偏移。VID 和 PID 对不上后面所有寄存器操作都是白费。3. 从复位到 ping 通DM9000A 初始化与收发环搭建3.1 上电复位、PHY 自协商与 MAC 地址写入DM9000A 上电后需要一次软复位写 NCR 的 bit0 为 1然后等至少 10us 再清掉。复位完成后先读 NSR 确认 PHY 状态如果 LINK_STAT 没置起来说明网线没插或者 PHY 自协商没完成。自协商一般不用手动干预DM9000A 内部 PHY 会自动跑但有些板子为了省成本用了外部 PHY那就得通过 SMI 接口去配这个后面避坑章节会讲。MAC 地址写入 PAR 寄存器从 0x10 到 0x15 共 6 个字节。注意 DM9000A 的 PAR 是字节序敏感的先写低字节还是高字节取决于你的 CPU 大小端。ARM 一般小端MAC 地址00:11:22:33:44:55写进去的顺序是 0x55、0x44、0x33、0x22、0x11、0x00。写反了能 ping 通自己但和外部设备通不了因为源 MAC 错了。static void dm9000aInitMac(UINT32 base, UINT8 *mac) { int i; for (i 0; i 6; i) { dm9000aWriteReg(base, DM9000A_PAR i, mac[5 - i]); /* 小端反序 */ } /* 使能收发 */ dm9000aWriteReg(base, DM9000A_NCR, 0x03); /* RXEN | TXEN */ }这段初始化里dm9000aWriteReg就是先写 INDEX 再写 DATA。MAC 地址通常从 BSP 的sysLib.c或者 flash 里读别硬编码在驱动里否则批量生产时每块板子 MAC 都一样网络里会冲突。写完后读回来校验一遍读不到就说明寄存器访问时序有问题。3.2 中断挂接与 ISR 里的收发分流DM9000A 的中断引脚接到 CPU 的哪个 GPIO 或外部中断线这个在 BSP 的sysIntr.c里配。VxWorks 下用intConnect挂 ISR中断号从 BSP 的hwint.h里拿。ISR 里第一件事读 ISR 寄存器判断是 RX 中断还是 TX 中断。RX 中断置位说明有帧进来了TX 中断置位说明上一帧发完了。ISR 里不要直接读 FIFO因为 DM9000A 的接收 FIFO 读操作需要连续进行中间被打断会丢数据。正确做法是在 ISR 里记一个标志然后netJobAdd把dm9000aRxJob推到网络任务里执行。dm9000aRxJob里读 MRCMDX 寄存器判断有没有新帧有的话读帧头、读长度、读数据最后封装成mBlk交给netJobAdd上层。static void dm9000aIsr(void *arg) { UINT32 base (UINT32)arg; UINT8 isr dm9000aReadReg(base, DM9000A_ISR); if (isr 0x01) { /* RX 中断 */ netJobAdd((FUNCPTR)dm9000aRxJob, base, 0,0,0,0,0); } if (isr 0x02) { /* TX 中断 */ /* 释放已发送的 mBlk唤醒等待的发送任务 */ dm9000aTxComplete(base); } /* 写 1 清除中断状态具体位看数据手册 */ dm9000aWriteReg(base, DM9000A_ISR, isr); }ISR 里读 ISR 后要写回清除但有些位是读清有些是写清DM9000A 的 ISR bit0 到 bit3 基本都是写 1 清除。如果你发现中断一直触发先查是不是没清干净。另外中断触发方式要配成边沿触发还是电平触发这个和硬件设计有关配错了要么丢中断要么中断风暴。3.3 发送路径mBlk 到 FIFO 的搬运与超时处理VxWorks 协议栈下来的数据是mBlk链发送时要先算总长度然后写 TCR 的 bit0 为 1 启动发送接着把数据按字或字节写进 MWCMD 寄存器。DM9000A 的发送 FIFO 是 2KB 左右超过这个长度的帧要分片但以太网帧最大 1518 字节一般不会超。写完后等 TX 中断或者轮询 NSR 的 bit2TX 完成标志。发送超时是常见问题。如果 TX 中断一直不来先查 TCR 有没有使能再查网线是不是没插。DM9000A 在链路断开时会自动停止发送但驱动层不一定知道所以发送函数里最好加一个超时计数器超时后返回 ERROR让上层协议栈重传。static STATUS dm9000aSend(END_OBJ *pEnd, M_BLK_ID pMblk) { UINT32 base DM9000A_BASE; UINT16 len pMblk-mBlkHdr.mLen; UINT8 *data mtod(pMblk, UINT8 *); int i; /* 等上一帧发完超时 100us */ for (i 0; i 100; i) { if (dm9000aReadReg(base, DM9000A_NSR) 0x04) break; sysUsDelay(1); } if (i 100) return ERROR; /* 写长度和启动发送 */ dm9000aWriteReg(base, DM9000A_TXPLL, len 0xFF); dm9000aWriteReg(base, DM9000A_TXPLH, (len 8) 0xFF); dm9000aWriteReg(base, DM9000A_TCR, 0x01); /* 搬运数据到 FIFO */ for (i 0; i len; i) { sysOutByte(base 4, data[i]); /* 8位模式 */ } return OK; }这段发送代码里TXPLL和TXPLH是发送长度低字节和高字节寄存器写反了长度就错了。数据搬运用 8 位模式还是 16 位模式取决于你的硬件连线16 位模式下用sysOutWord速度更快但要求数据长度是偶数奇数长度要补一个字节。发送完成后别忘了释放mBlk否则内存泄漏跑几个小时就挂了。4. DM9000A 驱动调试避坑从 ping 不通到稳定收发的排查清单4.1 坑一VID/PID 读出来是 0xFFFF但硬件量了电压正常现象是dm9000aProbe里读 VID 返回 0xFFFF用万用表量 DM9000A 的 VDD 和 VDDIO 都正常片选信号也有。原因通常是 INDEX/DATA 的地址偏移搞错了或者总线位宽配成了 8 位但硬件是 16 位。DM9000A 的 DATA 端口在 16 位模式下偏移是48 位模式下偏移是2这个在数据手册的“Host Interface”章节有明确表格。解决方法是先用示波器抓一次读写时序确认 CMD 引脚也就是 INDEX/DATA 选择在地址译码时有没有正确翻转然后对照 BSP 里的sysPhysMemDesc确认映射长度至少 8 字节。4.2 坑二能收不能发或者能发不能收现象是ifconfig能看到设备ping自己通但 ping 外部设备不通。原因可能是 MAC 地址写反了或者 RCR/TCR 的使能位没配对。DM9000A 的 RCR bit0 是 RXENTCR bit0 是 TXEN但 RCR 还有 bit1 是 PROMISCbit2 是 RUNT这些位配错了会导致帧被过滤掉。解决方法是先把 RCR 写成 0x3F 全开TCR 写成 0x01确认能通之后再逐位收紧。另外检查 PAR 写入顺序小端 CPU 下 MAC 地址要反序写写完后读回来对比。4.3 坑三中断进不去或者进去一次就再也不来了现象是网卡初始化完ping有发送但收不到回复串口没有中断打印。原因可能是中断号配错了或者 ISR 里没清中断状态。VxWorks 下用intConnect挂 ISR 时中断号要从 BSP 的hwint.h里拿不能随便写。ISR 里读 ISR 寄存器后必须写回清除DM9000A 的 ISR bit0 到 bit3 是写 1 清除bit4 到 bit7 是只读。如果只读不写中断会一直触发系统卡死。解决方法是 ISR 里先读 ISR处理完后把读到的值写回去但注意不要误清只读位。4.4 坑四大包丢帧小包正常现象是ping -l 1400不通ping -l 32通。原因通常是接收 FIFO 读时序不对或者mBlk分配的长度不够。DM9000A 的接收 FIFO 读操作要求连续中间不能插入其他寄存器访问否则 FIFO 指针会乱。解决方法是把接收处理放到任务级用netJobAdd推一个 job在 job 里一次性读完帧头和帧数据。另外检查mBlk的mBlkHdr.mLen有没有被截断VxWorks 默认的mBlk簇大小可能是 128 或 256大包需要mBlk链别只读第一个mBlk。4.5 坑五跑一段时间后网卡死掉重启就好现象是系统跑几个小时或几天后网络不通ifconfig看统计有大量错误。原因可能是发送完成中断丢了导致发送队列满或者接收mBlk没释放内存耗尽。解决方法是加看门狗定期检查 NSR 的 TX 完成位和接收队列长度发现异常就复位 DM9000A 的 NCR 重新初始化。另外在dm9000aSend里加超时超时后主动读一次 ISR 清中断别死等。5. 用 NSR 和 ISR 做在线诊断一个不重启就能定位网卡状态的技巧调试 DM9000A 最难受的是板子在现场不能随便重启网络断了只能猜。我后来养成了一个习惯在驱动里加一个dm9000aDiag函数通过ioctl暴露出来上层用一个小工具就能读。这个函数不复杂就是读几个关键寄存器然后按位解析成可读状态。比如 NSR 的 bit6 是链路状态bit5 是速度bit2 是发送完成ISR 的 bit0 是接收中断挂起。把这些位打出来基本能判断是物理层断了、还是驱动层卡了。/* 诊断函数通过 ioctl 的 SIOCGIFMEDIA 或自定义命令调用 */ void dm9000aDiag(UINT32 base) { UINT8 nsr dm9000aReadReg(base, DM9000A_NSR); UINT8 isr dm9000aReadReg(base, DM9000A_ISR); UINT8 rcr dm9000aReadReg(base, DM9000A_RCR); logMsg(NSR0x%02X LINK%d SPEED%d TXDONE%d\n, nsr, (nsr6)1, (nsr5)1, (nsr2)1, 4,5,6); logMsg(ISR0x%02X RX%d TX%d\n, isr, isr1, (isr1)1, 3,4,5,6); logMsg(RCR0x%02X RXEN%d PROMISC%d\n, rcr, rcr1, (rcr1)1, 4,5,6); }这个诊断函数我一般放在ioctl的SIOCGIFMEDIA分支里或者自定义一个SIOCGDM9000命令。上层用ifconfig或者自己写个小程序调一下就能看到当前网卡状态。有一次现场反馈网络时通时断我让他们跑这个诊断发现 LINK 位一直在跳最后查出来是网线水晶头氧化了换线就好了。如果没有这个诊断可能就要换板子、换驱动折腾好几天。还有一个技巧是统计寄存器。DM9000A 内部有接收错误计数和发送错误计数虽然不精确但能看出趋势。如果接收错误计数一直涨先查 RCR 的 RUNT 位和 CRC 位可能是对端设备发的小包或者坏包。如果发送错误计数涨查 TCR 的 CRC 使能位和填充位。这些统计不用频繁读一分钟读一次就够写个定时器任务把值打到串口或者存到 flash 里出问题时翻日志。最后说一个我踩过的坑DM9000A 的 ISR 寄存器读一次会自动清一些位但有些位必须写 1 清。我一开始只读不写结果中断触发一次后就再也不来了因为 ISR 的 RX 位一直是 1中断控制器认为是电平触发一直报。后来改成读完后把读到的值写回去但注意只写那些写 1 清的位只读位写 0 就行。这个细节数据手册里写得很清楚但英文手册翻起来费劲容易漏。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑