LV020-网络通信实验
一、 实验准备
1. 硬件连接
- 一根网线:板子与 PC 直连(现代网卡能自动翻转,不需要交叉线),或者两端都接在同一台交换机/路由器上。
- 一根 USB 线:板载 CH340 提供串口终端,用来观察板子打印的日志、输入文本命令。
2. 网络参数
参数写死在 atk-f103/Components/lwip-2.1.3/port/lwip_comm.h 里,改 IP 要重新编译:
| 项 | 板子(默认) | PC 侧要求 |
|---|---|---|
| IP 地址 | 192.168.16.30 | 设成同网段的另一个地址,如 192.168.16.100 |
| 子网掩码 | 255.255.255.0 | 一致 |
| 网关 | 192.168.16.1 | 直连时用不到,可留空 |
同网段通信不走网关,所以 PC 这块网卡配不配网关都不影响 ping 板子。但如果随便填了一个实际上不存在的网关,PC 上其它跨网段的访问会莫名其妙失败,排查时容易误判——直连实验建议留空。
3. 上电自检输出
DM9000 的初始化在 lwip_comm_init() 里,而它在 IAP 业务层被调用。上电后串口应当依次打出:
ATK-F103 USART1 ready, SystemCoreClock = 72000000 Hz, build <日期> <时间>
IAP: ready, waiting for app.img frame (magic IAPBIN01, max payload 131072 bytes, frame timeout 3000 ms)
USART1: commands: 'go' jump to APP, 'reboot' restart chip
DM9000: ID = 0x90000A46
lwIP: MAC 02-00-00-XX-XX-XX, IP 192.168.16.30
lwIP: RX mode = interrupt (EXTI6 falling edge)
lwIP: ready, ping 192.168.16.30 to test
IAP-TCP: listening on port 8888逐行含义:
| 输出 | 说明 |
|---|---|
DM9000: ID = 0x90000A46 | 芯片认到了。不是这个值就停下,先查 FSMC 与片选 |
lwIP: MAC 02-00-00-XX-XX-XX | MAC 由芯片 UID 生成:高三字节固定本地管理地址块,低三字节取自 0x1FFFF7E8 |
lwIP: RX mode = interrupt | 当前收包走 EXTI 中断;显示 polling 说明宏被改成了轮询 |
lwIP: ready, ping ... to test | lwip_comm_init() 返回成功,可以开始 ping |
IAP-TCP: listening on port 8888 | TCP 升级通道已监听 |
如果只看到 lwIP: DM9000 init failed,说明 DM9000_Init() 返回了非 0,也就是 ID 读错。这时不用怀疑协议栈,直接回到硬件层查 FSMC 与片选。
二、 实验一:ping 通
1. 操作与预期
这是最省事的验证手段:ping 只要用上 ARP、ICMP、IP 三层,能通就说明"DM9000 收发 + 协议栈 + 网络参数"整条链路都活着,而且不需要写任何上位机代码。
ping 192.168.16.30预期:
- PC 侧先看到 ARP 请求、再收到 ICMP 应答,
ping正常返回; - 板子串口没有额外的报错输出(板子不打爆日志本身就是好消息)。
2. ping 不通时的排查顺序
按下表的顺序查(按"从物理到协议"排,逐层砍):
| 现象 | 先查什么 |
|---|---|
| 网口指示灯不亮 | 网线、对端口,以及 PHY 是否上电(DM9000 内部 PHY 靠 GPCR / GPR 控制) |
| 灯亮但 ping 无应答 | 串口有没有打出 DM9000: ID = 0x90000A46;MAC / IP 打印是否正常 |
| PC 收到 ARP 却没有应答 | 掩码是否一致、PC 是否被配了网关 |
| 时通时不通 | 收包模式(中断/轮询)是否与预期一致,见第四节 |
完整的排错决策树(含 arp -a 这一步)见 LV050-一次ping的完整旅程 第六节。
三、 实验二:TCP 服务器
板子上跑的是一个单连接 TCP 服务器,监听 8888 端口,用途是把固件帧从 PC 灌进 Bootloader。它的实现见 port/iap_tcp.c,与帧协议的关系见 LV030-网络升级帧协议与三条通道。
1. 会话规则
| 行为 | 实现 |
|---|---|
| 并发连接 | 只允许一个,忙时直接 tcp_abort 拒绝 |
| 建连时 | 摘除串口字节流消费器(usart1_set_rx_sink(0)),由 TCP 独占喂帧状态机 |
| 断连时 | 恢复串口消费器,两边都能继续用 |
| 文本命令 | TCP 字节在喂帧状态机前会先过一遍串口命令表,所以 go / reboot 走 TCP 同样有效 |
2. 先做一次不破坏数据的连通性测试
不需要跑升级脚本,用任意 TCP 客户端连一下就能验证"DM9000 + lwIP + TCP"三层都通:
nc 192.168.16.30 8888- 若 TCP 三次握手成功,板子会打印
IAP-TCP: client connected, waiting for IAPBIN01 frame; - 断开后打印
IAP-TCP: client disconnected, uart channel restored。
能看到这两行,就说明"连不上"这类问题已经排除,可以往下走真实升级。
3. 真实升级:把 app.img 发进去
上位机脚本是 tools/send_iap_tcp.py,它依赖同目录的 pack_iap.py:给裸 bin 时会自动组帧,并先校验向量表。
# 发送裸 bin(脚本自动打包成 IAPBIN01 帧)
python tools/send_iap_tcp.py -f flash-app.bin
# 指定板子 IP 与端口
python tools/send_iap_tcp.py -f flash-app.bin -i 192.168.16.30 -p 8888
# 只发 go 命令,让 Bootloader 跳转到 APP
python tools/send_iap_tcp.py -i 192.168.16.30 -g脚本会打印栈顶(MSP)、复位向量与载荷 CRC32,然后每次 sendall() 发 1460 字节(脚本里 CHUNK = 1460 的注释写"对齐 TCP MSS"),最后等回执。
【注意】1460 字节只是脚本每次写入 socket 的批量大小,不是线上 TCP 段的实际大小。线上分段由握手时协商出的 MSS 决定:本工程 lwipopts.h 未定义 TCP_MSS,走 lwIP 默认值 536,所以 PC 的 TCP 栈会把每个 1460 字节的大块再切成 ≤ 536 字节的小段发送——这就是为什么大文件传输时板子看到的包会比预期碎得多(MSS 与窗口的来历见 LV015-lwIP配置与初始化 第二节,传输层基础见 LV035-传输层端口UDP与TCP)。
脚本最后的回执对照:
| 回执 | 含义 |
|---|---|
RECV OK | 帧收齐且 CRC32 通过 |
FLASH OK | 写入 Flash 并回读校验成功 |
FLASH ERR | 写入失败 |
RECV ERR | 收帧失败(超时/格式错),串口有具体原因 |
板侧同一时刻也会打印对应日志。收到 FLASH OK 之后不会自动跳转,还要再发一次 go(或串口输入 go)——这是刻意留的确认环节,方便先回读确认写入结果。
四、 实验三:收包模式的切换
1. 中断模式与轮询模式
DM9000 的收包有两种驱动方式,由 dm9000.h 里的宏选择:
| 模式 | 宏值 | 工作机制 | 代价 |
|---|---|---|---|
| 中断 | DM9000_RX_INT_MODE = 1 | PG6 下降沿 → EXTI6 → HAL_GPIO_EXTI_Callback() 置软件标志 → 主循环取包 | 中断与主循环都要碰总线,需要临界区 |
| 轮询 | DM9000_RX_INT_MODE = 0 | 主循环每拍无条件查 RX FIFO | 空转开销,但逻辑简单、不依赖中断 |
2. 中断模式里那个必须清的标志
DM9000 的 INT 引脚是电平保持型:有包时拉低,且只有把 ISR 里对应的位写 1 清除之后才会回到高电平。所以回调里不能只置标志就返回:
do
{
isr = DM9000_ReadReg(DM9000_ISR);
if ((isr & ISR_PRS) != 0U) { s_rx_flag = 1; }
DM9000_WriteReg(DM9000_ISR, isr); /* 关键: 写 1 清中断源 */
} while (HAL_GPIO_ReadPin(DM9000_INT_GPIO_Port, DM9000_INT_Pin) == GPIO_PIN_RESET);漏掉这一步的典型症状是:第一次收包正常,之后所有包全丢——因为下降沿只出现过一次,之后 INT 一直保持在低电平。中断路径的完整说明见 LV005-DM9000驱动实现 第五节。
3. 两种模式都保留的价值
代码里两种模式都在,靠宏切换。这样做的价值在调试期:怀疑中断通路有问题时,把宏改成 0 就能立刻区分"是中断没进来"还是"网卡本身没在收"。
五、 诊断手段
1. 串口日志与计数统计
网络不通时最怕的是"完全没有观测点"。这个工程留了两层:
| 手段 | 开关/入口 | 内容 |
|---|---|---|
| 串口日志 | 常开 | 初始化结果、每帧收发的结论、错误原因 |
| 计数统计 | DM9000_NETDBG_EN | EXTI 触发次数、成功取包数、因状态/内存丢弃的包数、发送次数 |
| 周期打印 | DM9000_DbgPoll() | 每拍由 lwip_comm_poll() 调用,每 2 秒打一行 |
计数统计是排查"丢包"的主力,三种典型读数:
(1)exti 远大于 rx_ok:标志置了却没被主循环及时取走(主循环被什么堵住了)。
(2)rx_drop 持续增长:多半是 pbuf_alloc 失败,即 PBUF_POOL_SIZE = 8 被用完。
(3)nsr 低位异常:PHY 链路状态没协商好,先查网线再看驱动。
2. 排错速查
| 现象 | 可能原因 |
|---|---|
DM9000: ID = 0x00000000 或 0xFFFFFFFF | FSMC 未初始化、片选错、总线宽度/时序不对 |
串口完全没有 DM9000: 打印 | LWIP_APP_EN 被置 0,或 lwip_comm_init() 没被调用 |
| 能 ping 通但 TCP 连不上 | 端口被别的会话占着(单连接策略);或 IAP_TCP_EN 为 0 |
| 第一次收包后就再也没包 | 中断标志没清(第四节第 2 小节) |
| 升级到一半板子卡死 | 收帧超时(默认 3000 ms)后状态机重对齐,检查 PC 侧是否被防火墙/杀软拖慢 |
| 跳转 APP 后起步就卡死 | 跳转前网络通道没关干净,见 LV025-基于TFTP的网络升级实验 第六节 |
六、 小结与参考资料
1. 本篇小结
- 实验的三个层次:ping(验证 IP/ICMP/ARP)、TCP 连通性(
nc握手)、真实升级(发IAPBIN01帧)。 - 上电日志的五行分别对应链路层、网络层、收包路径、协议栈就绪与 TCP 通道,任何一行不对都能立刻缩小排查范围。
- TCP 通道是单连接的,建连时独占帧状态机、断连时归还串口;
go/reboot走 TCP 同样有效。 - 收包有中断与轮询两种模式,靠宏切换;中断模式必须写 1 清
ISR,否则只有第一次能收到包。 - 观测点分两层:常开的串口日志 + 可开关的驱动计数;计数用来区分"中断没来""主循环没取""内存不够"。
- 开发顺序是"先硬件与协议栈、再通道与协议",这个顺序本身就是一套排错方法论。
2. 参考资料
- 手册:
30-STM32资料/10-lwIP/STM32F1 LWIP开发手册(DM9000版)_V1.1.pdf第 1.6 节「下载验证」、第三章 UDP、第五章 TCP 服务器 - 源码:
lwip_comm.c、iap_tcp.c、dm9000.c - 上位机:
tools/send_iap_tcp.py、tools/pack_iap.py
本文档由 markdowncli 技能辅助生成