系列导读
为什么需要 OA TC6 SPI 协议
MCU 通过 SPI 与 LAN8651 MAC-PHY 通信,但不能直接把以太网帧丢到 SPI 总线上。原因:
- 全双工同步 — SPI 是同步全双工的,TX 和 RX 同时发生,需要协调机制
- 帧边界标识 — SPI 是字节流,需要标记以太网帧的起止位置
- 流控 — MAC-PHY 内部缓冲区有限,MCU 不能无限制地灌数据
- 带外信息 — 需要传递状态(错误、中断、同步状态)而不干扰数据流
Open Alliance TC6 协议解决了这些问题:将以太网帧拆分为固定大小的 chunk,每个 chunk 附加 header(TX)或 footer(RX),在一次 SPI 全双工传输中同时完成数据发送和接收。
SPI 物理层配置
1 | // src/microchip/TC6_Io.cpp |
| 参数 | 值 | 说明 |
|---|---|---|
| 时钟频率 | 24 MHz | LAN8651 最大支持 25 MHz |
| 位序 | MSB First | 网络字节序 |
| SPI 模式 | Mode 0 (CPOL=0, CPHA=0) | 空闲低电平,上升沿采样 |
| CS | Active Low | 软件控制,每次 transaction 拉低 |
| IRQ | Active Low, Falling edge | MAC-PHY 请求 MCU 服务 |
| RESET | Active Low | 硬件复位,100ms 低 + 100ms 恢复 |
SPI 传输的核心实现极其简洁:
1 | // src/microchip/TC6_Io.cpp:88-99 |
注意这里的 trade-off:memcpy + in-place transfer 比分离的 TX/RX buffer 多一次拷贝,但简化了 Arduino SPI API 的调用。对于 24MHz SPI,64 字节传输约 21μs,memcpy 开销(< 1μs)可忽略。
数据传输帧结构
每次 SPI transaction 传输一个 chunk:4 字节 Header + 64 字节 Payload(TX 方向),同时接收 64 字节 Payload + 4 字节 Footer(RX 方向)。
TX Data Header(32 bits,MCU → MAC-PHY)
1 | Byte 0: [DNC(1) | SEQ(1) | NORX(1) | VS(2) | DV(1) | SV(1) | SWO[3](1)] |
1 | // src/microchip/lib/libtc6/src/tc6.cpp:70-80 |
关键字段解读:
| 字段 | 功能 | 值域 |
|---|---|---|
| DNC | 区分数据帧和控制帧 | 1=数据, 0=控制 |
| SEQ | 帧序列号,检测传输丢失 | 0/1 交替 |
| DV | 本 chunk 是否携带有效以太网数据 | 0=空 chunk(仅为接收服务) |
| SV+SWO | 以太网帧起始位置 | SWO 为 4-bit 字偏移 (0-15 words) |
| EV+EBO | 以太网帧结束位置 | EBO 为 6-bit 字节偏移 (0-63) |
帧拼接机制:一个以太网帧可能跨越多个 chunk。SV 标记起始 chunk,EV 标记结束 chunk,中间的 chunk 只设 DV=1。短帧可以在同一个 chunk 中同时设置 SV 和 EV。
RX Data Footer(32 bits,MAC-PHY → MCU)
1 | // src/microchip/lib/libtc6/src/tc6.cpp:102-116 |
Footer 中最关键的两个流控字段:
- TXC (TX Credits) — MAC-PHY 告知 MCU 还有多少 TX buffer 空间,MCU 每发一个有效 chunk 消耗一个 credit
- RCA (RX Chunks Available) — MAC-PHY 告知有多少 chunk 等待 MCU 读取,MCU 需要发送空 chunk(DV=0)来接收它们
控制传输帧结构
控制帧用于寄存器读写,与数据帧通过 DNC=0 区分:
1 | // src/microchip/lib/libtc6/src/tc6.cpp:86-94 |
寄存器地址空间通过 MMS(Memory Map Selector)分区:
| MMS | 地址空间 | 用途 |
|---|---|---|
| 0 | 0x0000xxxx | SPI 配置、中断状态 |
| 1 | 0x0001xxxx | MAC 寄存器(NETWORK_CONTROL, MAC 地址等) |
| 4 | 0x0004xxxx | PHY 寄存器(PLCA, 碰撞检测, 校准参数) |
| 10 | 0x000Axxxx | 间接访问寄存器(芯片修订, 校准数据) |
Secure Mode:控制帧可选启用保护模式,payload 区域同时包含正常数据和反转数据,MAC-PHY 做一致性校验。用于关键寄存器写入(MAC 地址、PLCA 配置等)。
端到端数据流
TX 路径
sequenceDiagram
participant App as "Arduino App"
participant UDP as "Arduino_10BASE_T1S_UDP"
participant lwIP as "lwIP Stack"
participant TC6 as "libtc6"
participant IO as "TC6_Io"
participant HW as "LAN8651"
App->>UDP: beginPacket() + write() + endPacket()
UDP->>lwIP: udp_sendto(pbuf)
lwIP->>lwIP: ARP resolve + IP header
lwIP->>TC6: lwIpOut(netif, pbuf)
TC6->>TC6: enqueue to eth_q
Note over TC6: service() 被调用时
TC6->>TC6: process_tx(): 从 eth_q 取帧
TC6->>TC6: mk_data_tx(): 拆分为 64B chunks
TC6->>TC6: 生成 TX Header (DNC=1, DV=1, SV/EV)
TC6->>IO: TC6_CB_OnSpiTransaction(pTx, pRx, len)
IO->>HW: SPI 全双工传输 (68 bytes)
HW->>HW: MAC 处理 + PHY 发送
RX 路径
sequenceDiagram
participant HW as "LAN8651"
participant IO as "TC6_Io"
participant TC6 as "libtc6"
participant lwIP as "lwIP Stack"
participant UDP as "Arduino_10BASE_T1S_UDP"
participant App as "Arduino App"
HW->>HW: PHY 接收 + MAC 校验
HW-->>IO: IRQ 拉低
IO->>IO: onInterrupt() (ISR, 计数器++)
Note over IO: service() 检测到中断
IO->>TC6: TC6_Service()
TC6->>IO: TC6_CB_OnSpiTransaction(empty_tx, pRx)
IO->>HW: SPI 传输(发空 chunk 换接收数据)
TC6->>TC6: process_rx(): 解析 Footer
TC6->>TC6: 根据 SV/EV/SWO/EBO 重组帧
TC6->>lwIP: TC6_CB_OnRxEthernetPacket()
lwIP->>lwIP: ethernet_input() -> ARP/IP
lwIP->>UDP: udp_recv callback
UDP->>UDP: 存入 _rx_pkt_list 队列
App->>UDP: parsePacket() + read()
Service Loop 核心逻辑
service() 是整个协议栈的驱动心跳,必须高频调用:
1 | // src/microchip/TC6_Arduino_10BASE_T1S.cpp:205-219 |
TC6_Service() 内部状态机:
- Control Phase — 处理挂起的寄存器读写请求
- Data Phase — 处理 TX 队列和 RX 接收
- Credit 检查 — 只有 TXC > 0 时才发送有效 chunk
Credit 流控机制
Credit 机制防止 MCU 溢出 MAC-PHY 的内部缓冲区:
1 | // src/microchip/lib/libtc6/src/tc6.cpp (TC6_t 结构体) |
graph TD
A["MCU 发送有效 chunk"] -->|"txc--"| B{"txc > 0?"}
B -->|"Yes"| C["继续发送"]
B -->|"No"| D["等待,发空 chunk"]
D --> E["读取 Footer 中 TXC 字段"]
E -->|"更新 txc"| A
F["Footer.RCA > 0"] --> G["MCU 发空 chunk (DV=0)"]
G --> H["接收 RX payload"]
H --> I["RCA-- 直到 0"]
空 chunk 的作用:当 MCU 没有数据要发但有数据要收时,发送 DV=0 的空 chunk,SPI 全双工特性保证同时能收到 MAC-PHY 的数据 chunk。这是 OA TC6 协议的关键设计——用”发送换接收”。
PLCA 多点总线管理
PLCA vs CSMA/CD
| 特性 | PLCA | CSMA/CD |
|---|---|---|
| 总线仲裁 | 确定性轮询 | 随机退避 |
| 最大延迟 | 可预测(与节点数线性相关) | 不确定(碰撞退避指数增长) |
| 需要协调器 | 是(Node 0) | 否 |
| 适合场景 | 实时控制 | 低负载通用 |
配置寄存器
1 | // src/microchip/lib/libtc6/src/tc6-regs.cpp:414-437 |
PLCA 寄存器映射
| 寄存器 | 地址 | 字段 | 说明 |
|---|---|---|---|
| PLCA_CONTROL_0 | 0x0004CA01 | bit[15] | PLCA 使能 |
| PLCA_CONTROL_1 | 0x0004CA02 | [15:8] Node Count, [7:0] Node ID | 节点配置 |
| PLCA_STATUS | 0x0004CA03 | bit[15] | PLCA 状态(活跃/回退到 CSMA/CD) |
| PLCA_BURST_MODE | 0x0004CA05 | [15:8] Burst Count, [7:0] Burst Timer | 突发模式 |
| COL_DET_CTRL0 | 0x00040087 | 模式选择 | 0x0083=PLCA, 0x8083=CSMA/CD |
运行时监控:应用代码每秒读取 PLCA_STATUS 寄存器,如果 bit[15]=0 表示 PLCA 失效(协调器离线),自动调用 enablePlca() 重新尝试。
LAN865x 初始化序列
初始化是一个严格有序的多阶段过程:
graph TD
A["硬件复位 (RESET pin LOW 100ms)"] --> B["软复位 (REG 0x03 = 0x01, unprotected)"]
B --> C["软复位 (REG 0x03 = 0x01, protected)"]
C --> D["读取 Chip ID (REG 0x01)"]
D --> E{"OUI=0x1F0, Model=0x1B?"}
E -->|"No"| F["初始化失败"]
E -->|"Yes"| G["读取 Chip Revision (REG 0x000A0094)"]
G --> H["写入 32 条初始化寄存器 (TC6_MEMMAP)"]
H --> I["修订相关配置 (Rev1: 0x5F21, Rev2: 0x3F31)"]
I --> J["写入 MAC 地址 (SPEC_ADD1/2)"]
J --> K["设置混杂模式 (NETWORK_CONFIG)"]
K --> L["PHY 校准 (InitChip - 读间接寄存器)"]
L --> M["PLCA 配置 (HandlePlca)"]
M --> N["Cut-Through/Store-Forward 模式 (CONFIG0)"]
N --> O["使能 MAC (NETWORK_CONTROL = 0x0C)"]
初始化寄存器表(部分关键项)
1 | // src/microchip/lib/libtc6/src/tc6-regs.cpp:297-331 |
Trade-off 分析:初始化使用了大量 “magic number”(如 0x9660, 0x1CF8),这些是 Microchip 的芯片特征化参数,没有公开文档说明含义。移植到竞品芯片时,这整个表需要用竞品 datasheet 的寄存器定义重写。
lwIP 集成架构
1 | // src/microchip/TC6_Arduino_10BASE_T1S.cpp:121-194 (begin 方法) |
lwIP 网络接口的两个关键回调:
lwIpInit()— 设置 MTU=1536, flags=BROADCAST|ETHARP|ETHERNET, 链接 output 函数lwIpOut()— lwIP 要发帧时调用,将 pbuf 转交 TC6 发送队列
TC6 内部状态机
1 | // src/microchip/lib/libtc6/src/tc6.cpp (TC6_t 结构体关键字段) |
状态转换:INVALID → REG (控制) → DATA (数据) → INVALID (空闲),每次 TC6_Service() 调用驱动一次状态转换。
小结
OA TC6 SPI 协议的核心设计决策:
- 固定 68 字节 chunk — 简化 DMA 和缓冲区管理,代价是短帧有 padding 浪费
- Footer 内嵌流控 — 零额外开销的信用机制,不需要专门的握手周期
- 空 chunk 换接收 — SPI 全双工特性的巧妙利用,简化中断处理
- Secure Mode 可选 — 关键寄存器写入双重校验,普通数据不额外开销
对于移植:如果竞品芯片兼容 OA TC6 协议,上述帧结构和流控逻辑可完全复用;如果使用私有 SPI 协议,则需要在 libtc6 这一层重新实现等效功能。