ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32驱动DM9000以太网控制器:从时序到ping通全攻略

STM32驱动DM9000以太网控制器:从时序到ping通全攻略 简介面向嵌入式开发与网络驱动初学者的单片机驱动DM9000E网卡芯片调试资料重点弥补网上仅有Linux/WinCE驱动介绍、缺乏单片机底层DM9000示例的空白适合正在学习ARM-Linux网络模块、或者需要在51/AVR等平台利用总线或IO模拟方式连接DM9000的开发者。内容依照实际操作流程展开从DM9000E支持8/16/32位处理器模式的选择到IOR、IOW、AEN、CMD、INT、RST及地址/数据引脚的连接再到C驱动编写中容易踩坑的大端/小端格式、CMD与DATA端口的区分、寄存器读写和ARP协议实现细节均有分步说明。包体为1个PDF文档容量仅33KB轻量便携便于直接下载后对照调试。已有381人浏览学习。借助这份PDF读者可快速掌握DM9000E初始化、索引/数据端口访问、总线时序模拟以及网络协议底层处理思路为后续向Linux驱动移植打下坚实根基。1. 为什么在单片机项目里死磕DM9000搞嵌入式网络通信的朋友对DM9000这颗芯片应该都不陌生。它是DAVICOM出品的一颗10/100M自适应以太网控制器支持8位和16位两种总线模式能和绝大多数MCU直接对接。我最早接触它是在一个基于STM32F103的远程数据采集项目里当时需要把传感器数据通过以太网上传主控资源又紧张选来选去最后还是落了这颗芯片。很多人可能会问现在带MAC的MCU一抓一大把为什么还要外挂DM9000原因其实很实在第一很多工业级老方案的主控芯片根本没有以太网控制器换主控等于整个硬件平台推倒重来成本太高第二DM9000这颗芯片资料多、参考设计成熟、驱动代码网上也能找到踩坑成本相对可控第三它支持自动协商、CRC校验、长线驱动这些功能物理层和链路层的事情基本都包了MCU只要做好数据搬运就行。所以这篇博文适合谁看正在做单片机以太网方案、被DM9000驱动搞得头疼的嵌入式开发者尤其是手里握着51、STM32、NXP这类不带MAC控制器主控的朋友。我会把从硬件接线、寄存器读写、初始化流程到ping通全过程的细节都拆开讲一遍包括我实际调试时踩过的坑和排查思路。读完你至少能有一个可以照着抄的驱动框架而不是面对一堆datasheet无从下手。2. 方案选型与整体设计思路2.1 为什么选DM9000而不是其他方案做网络方案之前我其实对比过好几条路。一是直接用带MAC的单片机比如STM32F407或者NXP的LPC1768但当时项目定型的MCU是STM32F103换芯片的连锁反应太大二是用SPI接口的ENC28J60省IO但实测吞吐量一般而且SPI中断处理在高负载下容易丢包三是用DM9000虽然要占用并行总线但胜在驱动成熟、吞吐量高和MCU之间只要处理好读写时序就行。DM9000还有一个隐性优势它内置了SRAM收发缓冲区约16KB可以缓解MCU侧内存紧张的问题。MCU只需要把要发送的数据包写入发送缓冲区然后触发发送寄存器接收时通过中断标志位判断有没有新包再用读操作把接收缓冲区里的数据搬出来。整个数据通路非常清晰特别适合裸机或者轻量级RTOS环境。2.2 硬件接口设计要点DM9000的控制接口本质上就是一个并行SRAM接口。地址线A0有些参考设计叫CMD引脚用于区分访问的是索引寄存器还是数据寄存器低电平表示当前总线上的地址是寄存器索引高电平表示读写的是寄存器数据。这个时序逻辑搞错后面所有寄存器操作都会乱套。我用的接法是16位数据总线模式SD0~SD15接到MCU的PB0~PB15A0接到PA8片选CS接到NE1STM32的Bank1区读使能RD和写使能WE分别接到NOE和NWE中断引脚INT接到PA15外部中断。硬件上还有一个小细节芯片的EEPROM接口EECS、EECK、EEDI、EEDO最好预留出来虽然我们一般不烧EEPROM但留着能用软件配置默认MAC地址调试起来方便很多。注意DM9000的复位引脚/RST是低电平有效复位电路最好用RC复位加手动复位按钮不要只靠MCU引脚控制否则程序跑飞后想重新初始化芯片会非常被动。2.3 为什么说驱动核心在时序很多人第一次调DM9000卡在第一步就动不了读寄存器返回的全是0xFF或者0x00。这时候别急着怀疑芯片坏了多半是时序没满足芯片手册要求。DM9000的总线访问时序里有几个关键参数地址建立时间、数据建立时间、读写脉冲宽度。MCU的FSMC或者普通GPIO模拟时序都要保证这些时间大于最小值。如果用的是STM32的FSMC配置起来相对简单只要选好Bank区、设置好读写时序参数即可。但如果像我一样用普通GPIO模拟总线就必须在每次读写操作里插入足够的延迟保证每个信号的稳定时间。我后面会在调试章节详细展开这部分因为这是整个驱动能否跑通的分水岭。3. 驱动初始化从寄存器说起3.1 寄存器操作基础DM9000的寄存器空间分两部分索引寄存器和数据寄存器。访问流程很简单先把想要访问的寄存器地址写到索引口A0为低然后通过数据口A0为高读写实际数据。因为这种设计驱动里只要实现两个最底层的函数就能操作所有寄存器void dm9000_write_reg(uint8_t reg, uint8_t data); uint8_t dm9000_read_reg(uint8_t reg);这两个函数内部怎么实现如果是FSMC方式直接对映射地址赋值或者取值就行注意16位模式下次地址要按2字节对齐。如果是GPIO模拟方式就需要按顺序拉高/拉低各个控制信号中间插入延时。我项目里用的GPIO模拟核心代码如下void dm9000_fastwrite(uint16_t addr, uint16_t data) { // 写寄存器地址 DM9000_CS_LOW(); DM9000_CMD_LOW(); set_data_lines(addr); DM9000_WE_LOW(); delay_us(1); DM9000_WE_HIGH(); // 写数据 DM9000_CMD_HIGH(); set_data_lines(data); DM9000_WE_LOW(); delay_us(1); DM9000_WE_HIGH(); DM9000_CS_HIGH(); }这里有个容易栽的坑设置数据线之后到拉低WE之间必须留出足够的建立时间否则芯片会采样到不稳定的电平。我一开始没加延时结果奇偶地址的数据总是错位折腾了半个下午。3.2 芯片ID识别与自检初始化芯片的第一步永远是读芯片ID判断总线通信是否正常。DM9000的芯片ID寄存器是0x00和0x01读出来应该是0x90和0x0A合起来是0x900A。这一步能通过说明地址线和数据线基本没问题控制时序也正确后面才谈得上配置。我习惯在ID校验后面再做一次软件复位。把0x03寄存器网络控制寄存器NCR的第0位写1延时20毫秒以上再清零让芯片内部完成一次复位。复位之后建议把寄存器0xFEPHY状态寄存器和0x3FPHY电源管理寄存器检查一遍确认PHY上电正常否则后面连不上网线也没法发现。uint16_t id dm9000_read_id(); if (id ! 0x900A) { // 打印错误信息终止初始化 } dm9000_write_reg(DM9000_NCR, 0x03); // 软复位 delay_ms(20); dm9000_write_reg(DM9000_NCR, 0x00);3.3 初始化配置中断、MAC地址、收发控制ID校验通过后就要按项目需求对DM9000做正式配置。这一步配置的东西比较多我列个最常见的初始化参数对照表寄存器值含义DM9000_NCR (0x00)0x00清除复位内部PHY使能DM9000_NSR (0x01)读状态确认PHY状态正常DM9000_ISR (0x02)0xFF清除所有中断标志DM9000_IMR (0x05)0x01使能PRX接收中断DM9000_RCR (0x05 注意实际)0x39使能RX缓存、丢弃错误包DM9000_MAR1~MAR8组播地址按需配置不组播填0DM9000_PAR1~PAR6MAC地址写入自己的MAC这里要注意IMR和RCR的地址在不同版本的datasheet里写法可能有差异一定要以手头芯片丝印对应的最新版手册为准。我最早参考老版本代码把RCR配置写到0x05结果中断完全进不来排查了很久才发现是寄存器地址错位。MAC地址一般从EEPROM读或者程序里写死。我们项目里用的方式是在配置宏里定义MAC然后打包写入PAR寄存器组。如果局域网有多台设备MAC一定不能重复否则交换机会丢包丢到怀疑人生。3.4 PHY寄存器配置DM9000内置PHYPHY的寄存器需要通过芯片的PHY访问接口来操作不能直接像普通寄存器那样读。具体做法是先写PHY地址到寄存器0x0APHY控制寄存器然后通过0x0CPHY状态寄存器和0x0DPHY数据寄存器来读写数据。要做的关键配置有两件一是检查PHY的链路状态判断有没有插网线网线断开的时候芯片会置位相应状态位二是配置自动协商让芯片和交换机自动协商出10M/100M模式和全双工/半双工模式。这些配置做好之后插上网线PHY状态寄存器的Link位应该很快变为1代表物理链路已经建立。4. 数据收发驱动主干是怎么跑的4.1 接收路径与中断处理DM9000接收数据包的基本流程是芯片收到完整以太网帧后会根据RCR里的设置判断是否接收接收成功后把数据写入内部接收缓冲区SRAM然后置位中断标志PRX向MCU发出中断请求。MCU收到中断后要通过读操作把数据从芯片SRAM里搬出来。整个接收过程有几个关键步骤读取中断状态寄存器ISR判断是PRX类型的中断。读取接收缓冲区状态字这个状态字包含当前数据包长度等信息。根据状态字指示的长度连续读取数据把整帧数据搬到MCU的缓冲区。处理完后写ISR清除中断标志使芯片能继续接收下一个包。如果用的是裸机轮询方式就在主循环里循环检查ISR的PRX位如果使用中断方式就把这块逻辑放在外部中断服务函数里。我实际项目中用的是外部中断加裸机主循环消费收到中断后置一个标志位主循环检测到标志再去取数据这样不会在中断里做重活避免了长中断导致的其他外设响应延迟。4.2 发送路径写SRAM、触发发送发送一个数据包的流程相对简单但要注意芯片的SRAM写入特性。大致步骤如下void dm9000_send_packet(uint8_t *data, uint16_t len) { // 清零发送状态 dm9000_write_reg(DM9000_NSR, 0x2C); // 写入发送数据长度 dm9000_write_reg(DM9000_TXPLL, len 0xFF); dm9000_write_reg(DM9000_TXPLH, (len 8) 0xFF); // 软件发送请求 dm9000_write_reg(DM9000_TCR, 0x01); // 写入数据到发送缓冲区 dm9000_fastwrite(DM9000_MWCMD, data, len); // 启动发送 dm9000_write_reg(DM9000_TCR, 0x00); // 等待发送完成标志 while (!(dm9000_read_reg(DM9000_NSR) 0x01)); }有个顺序问题我一开始没注意写TCR寄存器请求发送和写数据到MWCMD的顺序一定先触发发送请求再写数据还是先写数据再触发请求踩过坑之后我总结的经验是必须先写长度、再触发TCR、最后写数据这个顺序是芯片手册建议的一些参考代码里顺序不一样也能跑但按规范来最稳。发送完成标志位有好几个TCR为0x01时表示正在发送NSR的bit0表示发送完成。实际调试中发现轮询等待发送完成时最好加一个超时机制否则网线断开等异常情况下芯片可能一直不置位完成标志程序就死循环了。4.3 缓冲区管理与内存规划DM9000内部的SRAM空间有限MCU侧也要给收发缓冲区预留足够的内存。我一般在MCU侧开两个环形缓冲一个接收DMA环形队列一个应用层可读的队列。中断服务函数只负责从芯片SRAM把数据搬到接收环形队列主循环再从队列里取数据做协议解析。这个设计能有效应对突发流量。比如项目里我们曾经连续往板子发1000个包每个包64字节裸机不加环形缓冲的做法直接丢了十几个包加了环形缓冲后一包不丢。内存上我开了一个256字节的发送缓冲和一个1024字节的接收缓冲对大多数嵌入式应用已经够用。5. 调试实录从完全黑屏到ping通全链路5.1 常见问题速查表从我这次调试经历看大部分问题都集中在下面几个点上我整理成一个速查表方便大家对照排查现象可能原因排查方法芯片ID读不对总线时序不满足数据线接错芯片没复位成功先用GPIO模拟时序逐步加大延时检查复位引脚电平用万用表量电源和地中断频繁触发但没数据ISR未正确清除RX数据读取时序不对在中断里先读ISR再读状态字读完后写ISR清标志示波器量RD信号ping不通但状态正常MAC地址配置错误发送完成标志轮询超时网线或交换机问题抓包确认发出的包有没有ARP请求检查PHY协商模式换网线测试发送死循环卡死发送完成标志未被置位网线未连接加超时退出检查NSR寄存器确认TCR触发是否正确数据丢包严重接收缓冲区溢出中断响应不够快RX缓冲未及时释放加大接收缓冲区优化中断服务函数检查RX状态字处理逻辑5.2 我踩过的三个最深的坑第一个坑是GPIO模拟时序。我最初照搬网上现成的代码完全没注意人家用的是FSMC我这边GPIO模拟速度跟不上导致读ID一直在0xFFFF和0x0000之间跳。后来我把每次信号切换后的延时调到1微秒以上才稳定读到0x900A。这个教训告诉我别人的代码只能作为逻辑参考时序参数必须根据自己硬件和主频重新调。第二个坑是外部中断和接收数据之间的竞争。我的中断服务函数一开始直接在里面读SRAM数据结果主程序正在操作总线的时候被中断打断两边同时访问DM9000的数据口导致数据错乱。后来改成中断里只置标志位主循环统一处理数据读取这个bug就消失了。如果用了RTOS最好给DM9000的访问加一个互斥锁防止多线程同时操作。第三个坑是接收状态字的处理。DM9000的接收状态字长度有两个字节但实际有效位只有高字节的几位低字节是长度低位。我没按手册处理直接把两个字节当作一个16位长度用导致大包长255字节时接收数据被截断上层协议栈一直报CRC错误。改对了之后才真正理解什么叫“按手册办事”。5.3 用网络抓包验证驱动正确性驱动写完以后最有效的验证方法是用wireshark抓包。我把板子和电脑连在同一个交换机上板子上电后发送一个UDP广播包电脑上如果能看到这个包说明发送链路已经通了然后电脑往板子MAC发一个ARP请求板子如果能自动回复说明接收链路和协议栈处理也正常。抓包的时候要注意交换机可能会隔离广播域如果像我一样直接用网线把板子插到电脑网口需要手动把电脑网口配置成和板子同一网段的静态IP否则ARP请求到不了板子。我调试的时候就是在这里卡了很久一直以为是驱动问题最后发现是电脑防火墙把ICMP包拦截了关掉防火墙立刻通。5.4 移植lwIP时要注意的接口细节驱动调试到能发能收之后我把它接到了lwIP协议栈上。这里有两个接口必须实现好一个是底层发送函数把协议栈传下来的pbuf链按链路层帧格式发送出去另一个是接收回调把从DM9000收到的数据封装成pbuf交给协议栈处理。移植的时候有个容易忽略的点lwIP在无操作系统模式下依赖一个周期性的tcpip_timer用来处理TCP的超时重传。如果只在主循环里轮询记得把tcpip_thread的延时时间适当调大或者用定时器中断调用否则TCP连接会出现莫名其妙的超时。我项目里用的是FreeRTOS单独开了一个tcpip_task优先级设得比驱动接收任务低一点这样既不会占用过多CPU又能保证收包及时。6. 可靠性与性能优化驱动不是通了就完了6.1 链路异常处理驱动能ping通只是第一步真正考验可靠性的是各种异常场景网线热插拔、交换机重启、电磁干扰导致丢包。DM9000的PHY会自动检测链路状态变化但驱动层也要主动处理否则会出现“网线拔了再插回网络不通”的尴尬局面。我的做法是每500毫秒读取一次PHY状态寄存器检查Link位。如果发现链路断开就清空接收缓冲区、重置收发状态如果发现链路恢复就重新初始化PHY并等待自动协商完成。这个轮询逻辑放在一个定时器回调里不影响主数据通路实测下来网线插拔后恢复时间在1到2秒之间完全可以接受。6.2 收发性能实测数据驱动稳定后我做了几组简单的吞吐量测试。用电脑向板子连续发1000个UDP包每包1472字节UDP最大载荷在无丢包情况下板子每秒能处理约800包换算下来大概9.4Mbps从板子往电脑发速度稍高一些接近11Mbps。这个成绩对GPIO模拟总线的方案来说已经不错了毕竟瓶颈在MCU的数据搬运能力不在芯片本身。如果换成FSMC方式吞吐量还能提升不少。之前在一个LPC1768项目里同样用DM9000FSMC总线直接访问实测UDP吞吐能达到30Mbps以上。所以如果对性能有要求优先把硬件设计改成并行总线控制器而不是死磕GPIO模拟。6.3 降低CPU占用率的技巧GPIO模拟总线的方式非常消耗CPU因为每一位数据都需要MCU参与搬运。想降低CPU占用有两个思路一是把整个DM9000的寄存器访问封装好用DMA方式读SRAM数据前提是MCU的DMA支持外部存储器映射二是在驱动里减少无效轮询比如发送完成检测不要每次发完都死等而是发完立刻返回等中断或者定时器再确认。我最终在项目里用的是“发送后等待标志位加入超时接收中断置标志”的组合整体CPU占用率在UDP持续收发时约为25%72MHz主频下这个数值在可接受范围内。如果希望进一步优化就只能换FSMC或者换自带MAC的MCU了但那就是另一个话题。7. 一些掏心窝的调试心得DM9000这颗芯片我前前后后调过三次每次都有新收获。心得谈不上行业标准但都是实打实踩坑换来的。第一拿到芯片先别急着写代码把硬件电源、时钟、复位、片选每个引脚用电表量一遍。我试过因为一个引脚虚焊导致芯片始终处于复位状态花了一个晚上才排查出来。硬件不稳软件写得再好也是白搭。第二驱动分层一定要做好。底层总线访问和应用层收发分开不要全部揉在一个文件里。我最初的代码把所有功能堆在dm9000.c里改一个参数要翻几百行后面重构以后清爽很多也方便以后移植到其他MCU。第三善用示波器和逻辑分析仪。怀疑时序的时候直接在WE或者RD引脚上看波形确认有没有毛刺、占空比对不对。虽然有些老师傅靠“猜”也能调通但工具的帮助是不可替代的。第四也是我认为最实用的一条初期调驱动的时候把发送和接收分开验证。不要一上来就想着跑TCP/IP协议栈先从芯片ID开始再到PHY Link再到UDP广播一层层往上加。每层卡住都先假设是这层的问题而不是去怀疑上层协议这样定位问题会精确得多。调网络芯片不像调LED闪烁那样立竿见影第一次ping通对端设备的那一刻成就感还是很足的。希望这篇记录能帮正在调DM9000的兄弟少走点弯路如果读完有什么更好的调试思路也欢迎在评论区交流。本文还有配套的精品资源点击获取
返回列表