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 最特别的一点是寄存器访问方式:它只有一个"寄存器窗口",想访问哪个寄存器得先报号。
写地址线 A7 = 0 → 命令口:写"要访问哪个寄存器"
写地址线 A7 = 1 → 数据口:读/写这个寄存器的内容也就是常说的"索引 + 数据"两个口。一次寄存器访问永远是两步:先写索引,再读写数据。
正因为它是"并口 + 索引/数据两个地址"的访问方式,把它挂到 FSMC 的某个片选上、用一根地址线区分命令口和数据口,是最省事的接法。驱动的具体实现见 LV005-DM9000驱动实现 第二节。
三、 原理图上的接线

翻原理图(WarShip STM32F1_V3.4_SCH.pdf)可以确认:DM9000 的数据与控制线全部接在 STM32 的 FSMC 总线上,只有中断与复位两根线是普通 GPIO。
1. 控制与片选
| 信号 | STM32 引脚 | 引脚号 | 片上复用 | 说明 |
|---|---|---|---|---|
| NET_CS | PG9 | 124 | FSMC_NE2 | FSMC 片选 2,专给 DM9000,官方不建议改作普通 IO |
| NET_RD | PD4 | 118 | FSMC_NOE | 与 TFTLCD / IS62WV51216 共用 |
| NET_WR | PD5 | 119 | FSMC_NWE | 与 TFTLCD / IS62WV51216 共用 |
| NET_CMD | PF13 | 53 | FSMC_A7 | 命令/数据选择线,与 IS62WV51216 共用 |
| DM9000_INT | PG6 | 91 | — | 中断,与 WIRELESS 接口的 NRF_IRQ 共用 |
| DM9000_RST | PD7 | 123 | — | 复位,与 RS485_RE 共用 |
2. 数据线
16 位总线,D0 ~ D15 分散在 PD 与 PE 口上:
| 数据线 | 引脚 | 引脚号 | 数据线 | 引脚 | 引脚号 |
|---|---|---|---|---|---|
| D0 | PD14 | 85 | D8 | PE11 | 64 |
| D1 | PD15 | 86 | D9 | PE12 | 65 |
| D2 | PD0 | 114 | D10 | PE13 | 66 |
| D3 | PD1 | 115 | D11 | PE14 | 67 |
| D4 | PE7 | 58 | D12 | PE15 | 68 |
| D5 | PE8 | 59 | D13 | PD8 | 77 |
| D6 | PE9 | 60 | D14 | PD9 | 78 |
| D7 | PE10 | 63 | D15 | PD10 | 79 |
引脚分配与「完全独立」标记来自配套的《战舰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:
| 片选 | 起始地址 | 本板上挂了什么 |
|---|---|---|
| NE1 | 0x60000000 | 未使用 |
| NE2 | 0x64000000 | DM9000 |
| NE3 | 0x68000000 | 外部 SRAM(IS62WV51216) |
| NE4 | 0x6C000000 | 未使用 |
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 就是读写数据口:
// 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)一次寄存器读写的实际动作就是两次内存访问:
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):
| 配置项 | 取值 | 说明 |
|---|---|---|
| NSBank | FSMC_NORSRAM_BANK2 | 对应 NE2 片选(Bank 1 NOR/PSRAM 2) |
| MemoryType | FSMC_MEMORY_TYPE_SRAM | 按 SRAM 时序访问,DM9000 不属于 NOR/NAND |
| MemoryDataWidth | FSMC_NORSRAM_MEM_BUS_WIDTH_16 | 16 位数据总线 |
| WriteOperation | FSMC_WRITE_OPERATION_ENABLE | 要能写寄存器与发送 FIFO |
| ExtendedMode | FSMC_EXTENDED_MODE_DISABLE | 异步模式,读/写共用一套时序 |
| AccessMode | FSMC_ACCESS_MODE_A | 异步模式 A |
| AddressSetupTime | 0 | 地址建立时间 0 个 HCLK 周期 |
| AddressHoldTime | 15 | 默认值,地址/数据非复用时不起作用 |
| DataSetupTime | 3 | 数据建立时间 3 个 HCLK 周期,决定单次总线访问的时长 |
| BusTurnAroundDuration | 0 | 总线周转时间 0 个 HCLK 周期 |
外部的 IS62WV51216 走的是另一个实例 NOR Flash/PSRAM/SRAM 1(hsram1,NE3/Bank3),时序参数与上面完全相同,这里不再赘述。
真正让"地址线区分命令/数据口"成立的是数据宽度这一项:它决定了地址线到 HADDR 的错位关系,也就决定了 0xFE / 0x100 这两个偏移。改数据宽度前一定要回头看驱动里的偏移量,否则会出现"寄存器读写全乱、ID 读出来是 0x00000000"这种很像是硬件故障的现象。

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() 主动释放,避免芯片在上电混乱期抢总线。

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,优先级保持默认即可。

六、 小结与参考资料
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. 参考资料
- 原理图:
30-STM32资料/01-ATK战舰-V3开发板原理图/WarShip STM32F1_V3.4_SCH.pdf - 引脚表:
30-STM32资料/01-ATK战舰-V3开发板原理图/战舰V3 IO引脚分配表.xlsx(PG6 / PG9 / PD7 / PF13 等行) - 手册:
30-STM32资料/10-lwIP/STM32F1 LWIP开发手册(DM9000版)_V1.1.pdf第一章 1.2 / 1.3 节 - 驱动源码:
atk-f103/Drivers/BSP/Ethernet/dm9000.c、dm9000.h - FSMC 配置:
atk-f103/Core/Src/fsmc.c、atk-f103/Core/Inc/main.h
本文档由 markdowncli 技能辅助生成