Skip to content

LV001-以太网与DM9000硬件接口 ​

前置阅读:建议先看网络基础系列的十篇(分层模型 → 封装与帧 → IP 与掩码 → 网关 → 交换机与路由器 → ARP → 传输层 → DHCP → 调试工具 → 一次 ping 的旅程),再回到本系列的实现部分。本系列讲"怎么把它做出来",那十篇讲"它到底是什么"。

一、 以太网接入单片机的两条路 ​

给单片机接网线,业界有两条完全不同的路:

方案协议栈跑在哪代表芯片MCU 侧要做的事
全硬件协议栈网口芯片内部CH395Q只管收发数据,不理解 TCP/IP 报文
软件协议栈MCU 上DM9000(MAC + PHY)+ lwIP自己跑 TCP/IP,控制力强但代码量大

正点原子的两本手册正好各讲一条:《网络开发指南》讲的是 CH395Q 这条全硬件路线;《STM32F1 LWIP 开发手册(DM9000 版)》讲的是 DM9000 + lwIP 这条软件路线。战舰 V3 板载的网口芯片是后者,所以本系列文档都按 DM9000 + lwIP 走。

选软件协议栈的代价是:协议栈源码要自己搬、自己裁、自己适配;好处是 lwipopts.h 里每一个内存池、每一个协议开关都能按需收紧——这一点在 Bootloader 这种盯着 Flash 和 RAM 的场景里很重要。

二、 DM9000 芯片与访问方式 ​

1. 一颗"三合一"芯片 ​

DM9000 是一颗 10/100M 自适应的以太网控制器,把 MAC 和 PHY 集成在一颗芯片里:

  • 片内自带 MAC、PHY 与收发 SRAM(网卡缓冲),外部只需要一颗 25 MHz 晶振和网络隔离变压器(原理图上标为 EARTHNET / MULTI MEDIA DECODER 一带的电路)。
  • 对外是并口:一组数据线 + 一组控制线,MCU 像访问外部存储器一样读写它的寄存器与收发 FIFO。
  • 芯片身份由 ID 寄存器标识,读到 0x90000A46(VIDL / PIDL / VIDH / PIDH 拼起来)才算认到芯片。

2. 索引加数据口的访问模型 ​

DM9000 最特别的一点是寄存器访问方式:它只有一个"寄存器窗口",想访问哪个寄存器得先报号。

text
写地址线 A7 = 0  →  命令口:写"要访问哪个寄存器"
写地址线 A7 = 1  →  数据口:读/写这个寄存器的内容

也就是常说的"索引 + 数据"两个口。一次寄存器访问永远是两步:先写索引,再读写数据。

正因为它是"并口 + 索引/数据两个地址"的访问方式,把它挂到 FSMC 的某个片选上、用一根地址线区分命令口和数据口,是最省事的接法。驱动的具体实现见 LV005-DM9000驱动实现 第二节。

三、 原理图上的接线 ​

image-20261015064336492

翻原理图(WarShip STM32F1_V3.4_SCH.pdf)可以确认:DM9000 的数据与控制线全部接在 STM32 的 FSMC 总线上,只有中断与复位两根线是普通 GPIO。

DM9000 与外部 SRAM 在 FSMC 总线上的连接

1. 控制与片选 ​

信号STM32 引脚引脚号片上复用说明
NET_CSPG9124FSMC_NE2FSMC 片选 2,专给 DM9000,官方不建议改作普通 IO
NET_RDPD4118FSMC_NOE与 TFTLCD / IS62WV51216 共用
NET_WRPD5119FSMC_NWE与 TFTLCD / IS62WV51216 共用
NET_CMDPF1353FSMC_A7命令/数据选择线,与 IS62WV51216 共用
DM9000_INTPG691—中断,与 WIRELESS 接口的 NRF_IRQ 共用
DM9000_RSTPD7123—复位,与 RS485_RE 共用

2. 数据线 ​

16 位总线,D0 ~ D15 分散在 PD 与 PE 口上:

数据线引脚引脚号数据线引脚引脚号
D0PD1485D8PE1164
D1PD1586D9PE1265
D2PD0114D10PE1366
D3PD1115D11PE1467
D4PE758D12PE1568
D5PE859D13PD877
D6PE960D14PD978
D7PE1063D15PD1079

引脚分配与「完全独立」标记来自配套的《战舰V3 IO引脚分配表》。注意 D0~D15、NOE、NWE、A7 都标了 N:它们被 TFTLCD、IS62WV51216、DM9000 三家共用,只有三家的片选都被禁止时才轮得到当普通 IO。

3. 两处复用带来的限制 ​

原理图与 IO 分配表都写明了这两处复用,实际用的时候要心里有数:

  • PG6 = NRF_IRQ 与 DM9000_INT 共用。也就是说,一旦把 PG6 配成 DM9000 的中断输入,WIRELESS 接口(NRF 模块)的中断就没法用了——除非你的逻辑能容忍两个中断源混在一根线上。
  • PD7 = DM9000_RST 与 RS485_RE 共用。板子上 DM9000 的复位是软件可控的(不像有些设计直接接 VCC),代价是 RS485 的收发使能脚被占掉了。

这类"引脚复用"的取舍必须在项目初期就定下来,因为它决定了后面能不能同时用这两个外设。

四、 地址:为什么命令口是 0xFE,数据口是 0x100 ​

1. FSMC 片选与基址 ​

STM32 的 FSMC Bank1 划分了四个片选区,每片 64 MB:

片选起始地址本板上挂了什么
NE10x60000000未使用
NE20x64000000DM9000
NE30x68000000外部 SRAM(IS62WV51216)
NE40x6C000000未使用

DM9000 挂 NE2,所以基址是 0x64000000。

2. 地址线 A7 区分两个口 ​

接下来要在一段地址里区分出命令口和数据口两个位置,靠的是地址线 A7:

(1)数据宽度配成 16 位后,HADDR 的第 8 位对应 FSMC_A7(16 位访问时地址左移一位)。

(2)所以 A7 = 0 的地址落在低位偏移 0x00 上,A7 = 1 落在偏移 0x100 上。

驱动里就把它写成两个 volatile uint16_t 成员——写 REG 就是写命令口,读写 DATA 就是读写数据口:

c
// Drivers/BSP/Ethernet/dm9000.h
typedef struct
{
    volatile uint16_t REG;    /* 命令口, 偏移 0xFE */
    volatile uint16_t DATA;   /* 数据口, 偏移 0x100 */
} DM9000_TypeDef;

#define DM9000_BASE ((uint32_t)(0x64000000U | 0x000000FEU))
#define DM9000      ((DM9000_TypeDef *)DM9000_BASE)

一次寄存器读写的实际动作就是两次内存访问:

c
DM9000->REG = reg;      /* 写索引: 命令口 */
value = DM9000->DATA;   /* 读数据: 数据口 */

发包时同理:先把 MWCMD(0xF2)写进命令口,之后对数据口的连续写就自动进入发送 SRAM,这也是"索引 + 数据口"这套访问方式的典型用法。

五、 与外部 SRAM 共用总线的约束 ​

1. FSMC 必须先初始化 ​

DM9000 与 IS62WV51216 共用 FSMC 的数据线、地址线和读写控制线,只靠片选区分,所以有两件事必须注意:

(1)MX_FSMC_Init() 必须已经在前面跑过。FSMC 没初始化就访问 0x64000000 或 0x68000000,CPU 拿不到总线应答,直接进 HardFault。

(2)两个设备在总线上是互斥的。虽然片选由硬件自动产生,但如果收发网卡数据的过程中被别的代码插进来访问外部 SRAM,总线利用率与访问时序会相互影响——这也是后面 IAP 把 128 KB 载荷缓冲放到外部 SRAM 时,要把"写索引 + 读数据"这段用临界区保护起来的原因。

2. CubeMX 侧的关键配置 ​

2.1 FSMC ​

CubeMX 左侧 Mode 面板中,在 NOR Flash/PSRAM/SRAM 2 实例上勾选 chip select,选择 SRAM 类型、16 bits 数据宽度即可;右侧 Configuration 面板生成的关键参数(对应 fsmc.c 里的 hsram2):

配置项取值说明
NSBankFSMC_NORSRAM_BANK2对应 NE2 片选(Bank 1 NOR/PSRAM 2)
MemoryTypeFSMC_MEMORY_TYPE_SRAM按 SRAM 时序访问,DM9000 不属于 NOR/NAND
MemoryDataWidthFSMC_NORSRAM_MEM_BUS_WIDTH_1616 位数据总线
WriteOperationFSMC_WRITE_OPERATION_ENABLE要能写寄存器与发送 FIFO
ExtendedModeFSMC_EXTENDED_MODE_DISABLE异步模式,读/写共用一套时序
AccessModeFSMC_ACCESS_MODE_A异步模式 A
AddressSetupTime0地址建立时间 0 个 HCLK 周期
AddressHoldTime15默认值,地址/数据非复用时不起作用
DataSetupTime3数据建立时间 3 个 HCLK 周期,决定单次总线访问的时长
BusTurnAroundDuration0总线周转时间 0 个 HCLK 周期

外部的 IS62WV51216 走的是另一个实例 NOR Flash/PSRAM/SRAM 1(hsram1,NE3/Bank3),时序参数与上面完全相同,这里不再赘述。

真正让"地址线区分命令/数据口"成立的是数据宽度这一项:它决定了地址线到 HADDR 的错位关系,也就决定了 0xFE / 0x100 这两个偏移。改数据宽度前一定要回头看驱动里的偏移量,否则会出现"寄存器读写全乱、ID 读出来是 0x00000000"这种很像是硬件故障的现象。

image-20261015064648517

2.2 GPIO ​

FSMC 之外的普通 GPIO 只有 DM9000 的中断脚和复位脚,配置要点:

引脚模式上下拉作用
PG6(DM9000_INT)GPIO_MODE_IT_FALLING(EXTI 下降沿中断)内部上拉DM9000 来包时把 INT 拉低,触发中断通知 CPU 收包
PD7(DM9000_RST)GPIO_MODE_OUTPUT_PP(推挽输出)无上下拉软件控制 DM9000 硬复位:DM9000_Reset() 里先拉低 10 ms 再拉高,等 100 ms 让内部 PHY/寄存器就绪

两个细节值得说明:

(1)INT 配成下降沿而不是低电平触发。DM9000 的 INT 是电平保持型——包到达拉低,驱动在 EXTI 回调里写 1 清除 DM9000 的 ISR 位后 INT 才会回高。所以用下降沿即可,且回调里必须清 ISR 位,否则下降沿只出现一次,之后所有包都漏。默认初始电平上 PG6 靠上拉保持为高,空闲时不会误触发。

(2)RST 初始输出为低电平。MX_GPIO_Init() 先把 PD7 写成 RESET,即上电后 DM9000 被按住复位,直到 DM9000_Reset() 主动释放,避免芯片在上电混乱期抢总线。

image-20261015065116793

2.3 NVIC ​

PG6 对应的 EXTI 线 6 属于 EXTI9_5_IRQn 这一个中断向量(EXTI 线 5~9 共用一个向量),NVIC 里只需使能它:

配置项取值说明
中断向量EXTI9_5_IRQn入口是 EXTI9_5_IRQHandler,CubeMX 生成的代码在其中调用 HAL_GPIO_EXTI_IRQHandler(DM9000_INT_Pin),最终分发到驱动里的 HAL_GPIO_EXTI_Callback()
抢占优先级 / 子优先级3 / 3优先级压得比较低,收包通知不必抢占实时性要求更高的中断;同时驱动在访问 DM9000 总线时用关中断(__disable_irq())做临界区,这个优先级与之配合即可

CubeMX 里只需在 NVIC 面板勾选 EXTI line[9:5] interrupts,优先级保持默认即可。

image-20261015065140785

六、 小结与参考资料 ​

1. 本篇小结 ​

  • 单片机接网线有全硬件协议栈与软件协议栈两条路;本板用 DM9000 + lwIP 的软件路线,好处是协议栈完全可控可裁。
  • DM9000 是"MAC + PHY + 收发 SRAM"三合一,对外是并口,靠索引/数据两个口访问寄存器。
  • 它挂在 FSMC 的 NE2 上,片选 PG9;命令/数据靠 A7(PF13) 区分;中断 PG6、复位 PD7 是普通 GPIO。
  • 命令口地址偏移是 0xFE、数据口是 0x100,由 FSMC 16 位数据宽度下的地址错位关系决定。
  • 中断与复位两根线都与板上别的东西复用了(NRF_IRQ、RS485_RE),取舍要在项目初期就定下来。
  • 与外部 SRAM 共用总线,所以 FSMC 必须最先初始化,且访问要防并发。

2. 参考资料 ​


本文档由 markdowncli 技能辅助生成